## 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