Create a file in the current directory called bootest.py.
(It has to be where Python can import it -- either the current directory,
or a directory in your PYTHONPATH
, or a directory in
BOODLER_EFFECTS_PATH
.)
In this file, put the following:
from boodle.agent import * class Example(Agent): name = 'plinking example' def run(self): self.sched_note('environ/droplet-plink.aiff')(Remember that indentation is important. The "from" and "class" lines must not be indented. The "name" and "def" lines must be indented the same distance -- it doesn't matter how far, as long as they're the same. The "self" line must be indented more than the previous two lines.)
Now run it:
python boodler.py bootest.ExampleA plink! This is a working Python agent.
(If it doesn't work, go back to Installation and make sure your environment is set up correctly.)
Here is the meaning of it all:
from boodle.agent import *This loads all the necessary definitions from Boodler's own Python code. (This line is always necessary at the beginning of a soundscape file, but I will omit it in later examples.
class Example(Agent):This begins the definition of an
Agent
class, which will
be called Example
.
Agent
is a general Python class, defined by Boodler, which
contains all the code an agent needs -- methods to schedule notes, create
channels, and so on. You are creating a specialized class, the
Example
agent, which makes use of that code.
name = 'plinking example'Every agent class should have a descriptive label. (This is the name which is printed when Boodler begins running.) Really, this line is optional -- if you neglect it, the agent will be labelled "unnamed agent".
def run(self):This begins the definition of the class's
run()
method.
This is the function which Boodler calls when it is time for your
agent to run.
The run()
method should always have one argument,
self
.
self.sched_note('environ/droplet-plink.aiff')This is the entire body of the
run()
method. It calls
the note-scheduling function, which is called self.sched_note()
.
(Most Boodler functions that agents use are defined within the agent
itself -- they are part of the general Agent
class.
This is why their invocations begin with self.
)
We pass the sched_note()
method one argument, the name
of the sound to play. The note is played at its default pitch and full
volume, and it is played immediately.
environ/wind-hard.aiff
is a good wind sound. To play it
more softly, we add some optional arguments to sched_note()
:
self.sched_note('environ/wind-hard.aiff', 1, 0.5)
sched_note()
must take at least one argument, which is the
name of the sound. If there is a second argument, it is taken as the
pitch of the sound -- recall that 1 means the sound is played at its
original pitch. If there is a third argument, it is taken as the volume;
we give 0.5, meaning half volume.
(Note that you can't give the third argument -- volume -- unless you also give the second -- the pitch. If you only care about volume, give the pitch as 1.)
So, our new agent class:
class Example(Agent): name = 'plinkwind example' def run(self): self.sched_note('environ/wind-hard.aiff', 1, 0.5) self.sched_note('environ/droplet-plink.aiff')Does this work? Well, no. The plink and the wind are played at the same time. (At least, they start at the same time. Since the plink is short, it finishes first.)
This points out an important rule of sched_note()
:
it schedules a note to play at a given time -- by default,
immediately. The function does not actually start the note playing,
and it does not wait until the note is finished.
The time at which a note plays is the fourth optional argument to
sched_note()
. Again, you have to give the pitch and volume
arguments before you can give the time, so let us amend our example:
class Example(Agent): name = 'wind plink example' def run(self): self.sched_note('environ/wind-hard.aiff', 1, 0.5) self.sched_note('environ/droplet-plink.aiff', 1, 1, 2.7)The first sound plays at pitch 1, volume 0.5, and immediately. The second plays at pitch 1, volume 1, but not until 2.7 seconds have passed. (That is, 2.7 seconds after the
Example
agent runs.)
Since the wind sound, as it happens, is about 2.51 seconds long, the
plink will not be heard until the wind gust is finished.
You can schedule any number of notes, at any time, and set each to play at any time. The order in which you put them on the schedule is unimportant. The agent would behave exactly the same if you swapped the two lines:
class Example(Agent): name = 'wind plink example' def run(self): self.sched_note('environ/droplet-plink.aiff', 1, 1, 2.7) self.sched_note('environ/wind-hard.aiff', 1, 0.5)Sometimes you want one sound to follow immediately on the heels of another. Conveniently, the
sched_note()
function returns
the duration of the sound it plays (taking into account the pitch and
duration that you specify). You can use this information:
class Example(Agent): name = 'silence wind plink example' def run(self): dur = self.sched_note('environ/wind-hard.aiff', 1, 0.5, 1.5) self.sched_note('environ/droplet-plink.aiff', 1, 1, 1.5+dur)This produces the wind, but not until 1.5 seconds have passed (in silence). Precisely when the wind ends, the plink sound begins.
class Example(Agent): name = 'six plinks example' def run(self): self.sched_note('environ/droplet-plink.aiff', 1, 1, 0.0) self.sched_note('environ/droplet-plink.aiff', 1, 1, 0.5) self.sched_note('environ/droplet-plink.aiff', 1, 1, 1.0) self.sched_note('environ/droplet-plink.aiff', 1, 1, 1.5) self.sched_note('environ/droplet-plink.aiff', 1, 1, 2.0) self.sched_note('environ/droplet-plink.aiff', 1, 1, 2.5)We could even use the magic of Python to schedule a whole lot of notes at once:
class Example(Agent): name = '100 plinks example' def run(self): for num in range(100): self.sched_note('environ/droplet-plink.aiff', 1, 1, 0.5*num)But you cannot schedule an infinite number of notes at once.
class Example(Agent): # broken! infinite loop! name = 'infinite loop example' def run(self): num = 0 while (1): self.sched_note('environ/droplet-plink.aiff', 1, 1, 0.5*num) num = num+1This will cause an infinite loop, as the system tries to schedule notes forever. It will never get around to playing any. (Actually, in the current version of Boodler, the system will throw an error when num gets too high -- it cannot schedule notes more than an hour in the future. It will then play the hour's worth of notes that have been scheduled. Which is sort of like infinity, but not much.)
The correct way to run a soundscape forever is to have an agent schedule another agent -- or itself.
class Example(Agent): name = 'plink forever example' def run(self): self.sched_note('environ/droplet-plink.aiff') self.resched(0.5)The
run()
method plays the plink sound -- and note that
it only gives one argument, so the default values of "original pitch",
"full volume", and "start immediately" are employed. The method then
uses the resched()
method to schedule itself
to run again, half a second in the future. When that time comes, the
agent will run again, and schedule another plink note and yet another
iteration of itself. And so on.
Scheduling another agent is not much harder. You create the new
agent instance, and then use the sched_agent()
method.
class Example(Agent): name = 'trill forever example' def run(self): ag = Example2() self.sched_agent(ag) self.resched(1.0) class Example2(Agent): name = 'trill once example' def run(self): self.sched_note('environ/droplet-plink.aiff', 1.0, 1, 0.0) self.sched_note('environ/droplet-plink.aiff', 1.2, 1, 0.1) self.sched_note('environ/droplet-plink.aiff', 1.4, 1, 0.2) self.sched_note('environ/droplet-plink.aiff', 1.6, 1, 0.3) self.sched_note('environ/droplet-plink.aiff', 1.8, 1, 0.4)The
Example2
agent schedules just five notes; you can hear
the effect by typing
python boodler.py bootest.Example2But if you run
Example
, you will hear the full effect.
The Example
agent creates an instance of the
Example2
class, schedules it to run immediately, and then
reschedules itself (the Example
agent) to run one second
later. Thus, a trill repeated forever.
Be wary of accidentally unleashing an unbounded flood of agents. You could also have made a trill repeat forever with the following single agent:
class Example2(Agent): name = 'trill forever example' def run(self): self.sched_note('environ/droplet-plink.aiff', 1.0, 1, 0.0) self.sched_note('environ/droplet-plink.aiff', 1.2, 1, 0.1) self.sched_note('environ/droplet-plink.aiff', 1.4, 1, 0.2) self.sched_note('environ/droplet-plink.aiff', 1.6, 1, 0.3) self.sched_note('environ/droplet-plink.aiff', 1.8, 1, 0.4) self.resched(0.91213)But what happens if you run
Example
in combination with
this version of Example2
? Every second it will fire off
another instance of Example2
. But this Example2
doesn't stop after five notes; it runs forever. After ten seconds,
there are ten Example2
instances
hanging around, firing off fifty notes at a time.
After thirty seconds, there are thirty of them. The sound rapidly
builds up to a meaningless blare, and then starts to overload the
Boodler engine, causing skips or clipping noise.
Don't do that.
The Python random
module supports several different handy
randomness functions. For example, random.uniform(min, max)
returns a random real number between min
and max
.
We can use this to provide an irregular sequence of plinks:
from boodle.agent import * import random class Example(Agent): name = 'plink forever example' def run(self): self.sched_note('environ/droplet-plink.aiff') delay = random.uniform(0.25, 0.75) self.resched(delay)(Note that we have to import the
random
module at the beginning
of the file.) Each time this agent runs, it reschedules itself to run again --
but the delay can be anywhere between a quarter-second and three-quarters of
a second.
Other useful functions include
random.randint(min, max)
(return a random integer between
min
and max
, inclusive) and
random.choice(list)
(return a randomly-chosen element of the
list). The Python reference documentation has complete details.