Boodler: Other Soundscape Tricks

Agents with arguments

We slipped a sneakery past you, a few sections back. Consider the line
        ag = FadeOutAgent(3)
This creates an instance of an agent class, but it passes in an argument as it does so. FadeOutAgent has a (mandatory) argument, which is the duration of the fade-out that occurs before the channel stops.

This is a nice trick, and you will sometimes want to create your own agents that take arguments. You do this by adding a constructor method to your class.

class Example2(Agent):
    name = 'repeat example'
    def __init__(self, pitch, reptime):
        Agent.__init__(self)
        self.pitch = float(pitch)
        self.reptime = float(reptime)
    def run(self):
        self.sched_note('environ/droplet-plink.aiff', self.pitch)
        self.resched(self.reptime)

class Example(Agent):
    name = 'repeat example'
    def run(self):
        ag = Example2(1.5, 0.5)
        self.sched_agent(ag)
        ag = Example2(1, 1.21)
        self.sched_agent(ag)
The Example agent creates two instances of Example2. The first has a high pitch (1.5) and a short repeat time (half a second). The other has a lower pitch and a longer repeat time (1.21 seconds). Both are scheduled to start immediately, so you hear a quick high plinking interspersed with a low, slower sound.

Here is how the constructor in Example2 works:

In Python, the constructor is always called __init__(). The first argument is self; it is followed by any arguments you want the caller to pass in.

        Agent.__init__(self)
The first line of __init__() must be this call. It calls the general agent setup code. If you neglect this line, you will get a ScheduleError: agent is uninitialized exception.

The remainder of the constructor is yours to do with as you will. But remember that __init__() is called when the agent instance is created. This is before the agent is scheduled, placed in a channel, or set running. Therefore, the __init__() method cannot schedule notes or agents, or create channels, or do anything else that affects the stream of sound. (If you think you want to do these things, you probably really want to create a separate agent class that does them in its run() method.)

Most often, your __init__() method will just take its arguments and store them away for run() time. In the example, we take the pitch argument and attach it to the agent instance, as self.pitch. We do the same with reptime.

Note that we call float() on pitch and reptime to convert them to real numbers. It is not obvious why this is necessary -- after all, Python automatically converts integers to real numbers when needed.

The reason is so that you can type this command:

python boodler.py bootest.Example2 1.25 0.75
When you run Boodler, all command-line arguments after the class name are passed on to the class as constructor arguments. However, they are passed as strings. (The Boodler startup mechanism cannot know that they are meant to be interpreted as numbers, so it can't convert them automatically.) If you think your agent might ever be run from the command line, it is polite to accept string values for your numeric arguments, by using the float() or int() functions to convert them.

An optional argument is easy to arrange:

class Example2(Agent):
    name = 'repeat example'
    def __init__(self, pitch=1, reptime=1):
        Agent.__init__(self)
        self.pitch = float(pitch)
        self.reptime = float(reptime)
    def run(self):
        self.sched_note('environ/droplet-plink.aiff', self.pitch)
        self.resched(self.reptime)
The line
    def __init__(self, pitch=1, reptime=1):
gives default values for both arguments, making them optional. If you run the Example2 agent from the command line without parameters, or do ag = Example2() in Python code, the agent will default to pitch 1.0 and a one-second repeat time. If you supply just one argument, it will be taken as the pitch, and the repeat time will default to 1.

Panning sounds and channels

You can control the stereo location of a sound by using the sched_note_pan() method instead of sched_note(). This method has an extra argument (after the sound), which indicates how far left or right to play the sound.
    self.sched_note_pan('environ/droplet-plink.aiff', 1.0)
    self.sched_note_pan('environ/droplet-plink.aiff', -1.0, 1, 1, 0.5)
The value -1.0 indicates a sound played entirely in the left channel; 1.0 means the right channel; 0.0 is center, the normal position.

(The usual optional arguments of pitch, volume, delay, and channel can follow the pan location. The second line above plays a note in the left channel, normal pitch, normal volume, and 0.5 seconds after the first note.)

Just as a channel can modify the volume of every sound within it, a channel can modify the stereo location of every sound within it.

    chan = self.new_channel_pan(0.5)
This creates a channel shifted halfway to the right. A sound played in this channel with sched_note() would be played at position 0.5, instead of the default (center) position. A sound played at location -1.0 would come out at position -0.5.

You can control the stereo location of a channel more precisely using the functions in the stereo module. For example:

    from boodle import stereo
    chan = self.new_channel_pan(stereo.fixed(0.5))
This channel renders all sounds within it, no matter where they think they're going, at location 0.5.

See the Programming Reference for more about the stereo module. You can use these functions to shift a channel, or compress it into a narrow range, or even swap the left and right channels. I leave the details to your enjoyment.


Designing Soundscapes

Return to Boodler docs index