class Example(Agent): name = 'channel example' def run(self): chan = self.new_channel() self.sched_note('environ/droplet-plink.aiff', 1, 1, 0, chan)The
new_channel()method creates a channel, which is contained inside the channel that the agent is running in -- that is, inside the root channel. We then call
sched_note()with five arguments: pitch, volume, time, and channel. (As usual, to provide the fifth argument we must also give the first four.)
This plays a note inside our new channel. As you hear, this sounds exactly the same as playing it in the root channel.
So what's the point? Consider this example:
class Example(Agent): name = 'channel example' def run(self): ag = Example2() loudchan = self.new_channel(1) self.sched_agent(ag, 0, loudchan) ag = Example2() softchan = self.new_channel(0.25) self.sched_agent(ag, 0.5, softchan) class Example2(Agent): name = 'plink forever example' def run(self): self.sched_note('environ/droplet-plink.aiff') self.resched(1.0)The
Example2agent repeats a plink sound once per second, forever.
Examplecreates two channels and two instances of
Example2, and sets them off.
We've dropped in some new optional arguments here. The first argument
new_channel() is the channel volume. It defaults to
1.0, meaning (as usual) "full volume". We set the first channel
to full volume, but for the second channel we set
one-quarter volume instead. The three arguments to
are the agent, the scheduling time, and the channel.
So the first agent runs in a full-volume channel, and starts immediately. The second agent runs in a 25% channel, and is scheduled to start one half-second in the future. Since each of the two agents repeats a plink every second, you hear one every half-second, but they alternate loud-soft-loud-soft.
(A quick footnote that doesn't really belong here: note that we
create two instances of
Example2. It would not be legal
to create one instance and schedule it twice:
class Example(Agent): # broken! schedules an agent instance twice! name = 'channel example' def run(self): ag = Example2() loudchan = self.new_channel(1) self.sched_agent(ag, 0, loudchan) softchan = self.new_channel(0.25) self.sched_agent(ag, 0.5, softchan)No instance of an
Agentclass may wait on the schedule twice at the same time. On the other hand, once an agent starts running, it's off the schedule and it's legal to put it back on; this is why the
class Example(Agent): name = 'fade-out example' def run(self): ag = Example2() chan = self.new_channel(1) self.sched_agent(ag, 0, chan) chan.set_volume(0, 5) class Example2(Agent): name = 'plink forever example' def run(self): self.sched_note('environ/droplet-plink.aiff') self.resched(0.5)We create the channel with full volume, but we then call
chan.set_volume(). (Note that this is a method of the channel, not of
self.) The first argument is the volume to change to; the second is how long it takes to slide to that level. We are scheduling the volume to fade from 1 (full) to 0 (silent) over a five-second interval. The
Example2agent runs continuously during this time, but since it is running in the channel, its notes are affected by the volume change.
Note that Boodler does not shut down after the five seconds are over.
Example2 is still running and playing notes; they're just
at zero volume.
(By the way, you should never call
with a zero-second fade interval. Changing the volume instantaneously produces
clicking or popping in the sound stream. If you leave off the second argument
-- for example,
chan.set_volume(0)-- then the default interval will be 0.005, or five milliseconds. This is short enough to sound instantaneously, but long enough to prevent popping. You should not use an interval shorter than this.)
We frequently want a soundscape that starts at zero volume, fades in,
plays for a few seconds, and then fades out.
chan.set_volume() does not have an argument
for starting time. It always schedules the volume change beginning
immediately. To schedule a volume change starting in the future, we must
create and schedule an agent.
class Example(Agent): name = 'fade-in-out example' def run(self): ag = Example2() chan = self.new_channel(0) self.sched_agent(ag, 0, chan) chan.set_volume(1, 3) ag = ExampleFadeOut() self.sched_agent(ag, 6, chan) class ExampleFadeOut(Agent): name = 'fade-out example' def run(self): chan = self.channel chan.set_volume(0, 3) class Example2(Agent): name = 'plink forever example' def run(self): self.sched_note('environ/droplet-plink.aiff') self.resched(0.5)We create a zero-volume channel, start the plinker immediately, and schedule a volume change from 0 to 1 over three seconds. Then we schedule an instance of
ExampleFadeOutto begin running after six seconds. (This gives us three seconds of fade-in followed by three seconds of full volume.) The
ExampleFadeOutagent does nothing but schedule a three-second fade-out, from volume 1 to 0. After nine seconds, we are back to silence.
It would be nice if Boodler shut down after that nine-second sequence.
chan.stop() method will kill a channel, and all notes
and agents running in it. (And also any channels inside that channel.)
But this method, like
chan.set_volume(), takes effect
immediately. To delay it, we must add another agent.
class ExampleFadeOut(Agent): name = 'fade-out example' def run(self): chan = self.channel chan.set_volume(0, 3) ag = ExampleStop() self.sched_agent(ag, 3) class ExampleStop(Agent): name = 'stop example' def run(self): chan = self.channel chan.stop()(The other two agents are as before.) Note that when
ExampleStop, it does not need to pass a second argument to
sched_agent(); the default behavior is to schedule the new agent in the same channel as the current agent, and that's the channel that
Also note that the sequence of events has gotten quite elaborate.
Example launches two agents, one immediate and one delayed.
The immediate agent runs forever, rescheduling itself every half-second.
The delayed agent launches a third agent after yet another delay.
(We could have scheduled
ExampleStop both directly from
this arrangement makes the soundscape easier to modify. If we wanted
to change the full-volume interlude from three seconds to five, we would
just have to change the 6 in
Example to an 8.
ExampleFadeOut would run later, but it would still schedule
chan.set_volume() and the
the correct times, relative to each other.)
Actually, this sequence -- fade out and stop -- is so common that Boodler has a built-in agent to handle it. The example above could be rewritten:
class Example(Agent): name = 'fade-in-out example' def run(self): ag = Example2() chan = self.new_channel(0) self.sched_agent(ag, 0, chan) chan.set_volume(1, 3) ag = FadeOutAgent(3) self.sched_agent(ag, 6, chan) class Example2(Agent): name = 'plink forever example' def run(self): self.sched_note('environ/droplet-plink.aiff') self.resched(0.5)The
FadeOutAgentclass is defined in Boodler's
agentmodule, which we import. We create an instance, passing the fade-out interval (three seconds) as an argument. Then we schedule it like any other agent.
channel.stop()method kills a channel instantaneously; any sounds that are playing get cut off. This, like an instantaneous volume change, can cause clicks and pops. It is wise to fade the volume to zero before you stop the channel.
(In fact, since
FadeOutAgent does both these things, it's
just wise to use
channel.set_volume() system is somewhat limited. You cannot
schedule two volume changes on the same channel at the same time.
Overlapping volume changes will sound wrong, as the later change
pre-empts the first. In fact, if a volume change even begins too soon after
the previous one ends (on a given channel), the results will not be exactly
right. The lesson here is that volume changes should not be used for
frequent, short-term effects on a channel. Keep them a few seconds apart.
On the other hand, there's no problem with volume changes on different channels. It is perfectly fine for two agents to be running, each creating its own channels, and fading them in and out independently. They will not interfere with each other. In fact, it may be that (unbeknownst to them) the root channel is slowly fading out.
(You may wonder what happens when a note is playing in a channel, inside
another channel, inside the root channel, and each of these has its own
volume level. Simply: the loudness of a note is found by multiplying its
own volume (the volume passed to
sched_note()) by the
volume of every channel it is inside. The default volume, 1, has no effect
on the final product. On the other hand, if any channel has volume zero,
the product will be zero. This is what we expect: you can silence a note
by silencing the root channel, or the channel that directly contains the
note, or any channel in between.)
This brings up one final, somewhat abstract question: who is in charge of a channel's volume? Since two agents trying to change the volume of a single channel will interfere with each other, we need a guideline.
The general guideline is: an agent takes responsibility for controlling the volume of the channels it creates. An agent should not try to change the volume of the channel it is running in.
This is because, in general, an agent does not know what other agents might be running in the channel with it. If they all tried to manipulate that channel's volume, none of them would get what they wanted. Instead, if you want to mess with channel volumes, do what we do in the examples above: create a channel for your own use, run agents in it, and change the volume of that channel.
This does not mean that it is absolutely forbidden for an agent to change
or stop its own channel.
ExampleFadeOut above does just that.
But it is part of a system of agents that make up a soundscape. In fact,
Example is employing it for the specific purpose of changing
the volume of the channel that
But it would be a bad idea for
Example to call
Example is a complete
soundscape, and some other soundscape might want to invoke it running in
a channel with several other agents.
Return to Boodler docs index