We move now to an example using dynamic voice allocation as described in Section 4.5. In the additive synthesis examples shown previously, each voice is used for a fixed purpose. In the present example, we allocate voices from a bank as needed to play notes in a control stream.
Example D11.sampler.poly.pd (Figure 4.20) shows the polyphonic sampler, which uses the abstraction sampvoice (whose interior is shown in Figure 4.21). The techniques for altering the pitch and other parameters in a one-shot sampler are shown in Example D10.sampler.notes.pd (not shown) which in turn is derived from the original one-shot sampler from the previous chapter (C05.sampler.oneshot.pd, shown in Figure 3.14).
The sampvoice objects in Figure 4.20 are arranged in a different kind of summing bus from the ones before; here, each one adds its own output to the signal on its inlet, and puts the sum on its outlet. At the bottom of the eight objects, the outlet therefore holds the sum of all eight. This has the advantage of being more explicit than the throw~ / catch~ busses, and is preferable when visual clutter is not a problem.
The main job of the patch, though, is to distribute the ``note" messages to the sampvoice objects. To do this we must introduce some new Pd objects:
: Integer modulus. For instance, 17 mod 10 gives 7, and -2 mod 10 gives 8. There is also an integer division object named div ; dividing 17 by 10 via div gives 1, and -2 by 10 gives -1.
: Polyphonic voice allocator. Creation arguments give the number of voices in the bank and a flag (1 if voice stealing is needed, 0 if not). The inlets are a numeric tag at left and a flag at right indicating whether to start or stop a voice with the given tag (nonzero numbers meaning ``start" and zero, ``stop"). The outputs are, at left, the voice number, the tag again at center, and the start/stop flag at right. In MIDI applications, the tag can be pitch and the start/stop flag can be the note's velocity.
: Supply delayed note-off messages to match note-on messages. The inlets are a tag and start/stop flag (``pitch" and ``velocity" in MIDI usage) and the desired duration in milliseconds. The tag/flag pair are repeated to the two outlets as they are received; then, after the delay, the tag is repeated with flag zero to stop the note after the desired duration.
The ``note" messages contain fields for pitch, amplitude, duration, sample number, start location in the sample, rise time, and decay time. For instance, the message,
60 90 1000 2 500 10 20(if received by the r note object) means to play a note at pitch 60 (MIDI units), amplitude 90 dB, one second long, from the wavetable named ``sample2", starting at a point 500 msec into the wavetable, with rise and decay times of 10 and 20 msec.
After unpacking the message into its seven components, the patch creates a tag for the note. To do this, first the t b f object outputs a bang after the last of the seven parameters appear separately. The combination of the +, f, and mod objects acts as a counter that repeats after a million steps, essentially generating a unique number corresponding to the note.
The next step is to use the poly object to determine which voice to play which note. The poly object expects separate messages to start and stop tasks (i.e., notes). So the tag and duration are first fed to the makenote object, whose outputs include a flag (``velocity") at right and the tag again at left. For each tag makenote receives, two pairs of numbers are output, one to start the note, and another, after a delay equal to the note duration, to stop it.
Having treated poly to this separated input, we now have to strip the messages corresponding to the ends of notes, since we really only need combined ``note" messages with duration fields. The stripnote object does this job. Finally, the voice number we have calculated is prepended to the seven parameters we started with (the pack object), so that the output of the pack object looks like this:
4 60 90 1000 2 500 10 20where the ``4" is the voice number output by the poly object. The voice number is used to route the message to the desired voice using the route object. The appropriate sampvoice object then gets the original list starting with ``60".
Inside the sampvoice object (Figure 4.21), the message is used to control the tabread4~ and surrounding line~ and vline~ objects. The control takes place with a delay of 5 msec as in the earlier sampler example. Here, however, we must store the seven parameters of the note (earlier there were no parameters). This is done using the six f objects, plus the right inlet of the rightmost delay object. These values are used after the delay of 5 msec. This is done in tandem with the muting mechanism described on Page , using another vline~ object.
When the 5 msec have elapsed, the vline~ object in charge of generating the wavetable index gets its marching orders (and, simultaneously, the wavetable number is set for the tabread4~ object and the amplitude envelope generator starts its attack.) The wavetable index must be set discontinuously to the starting index, then ramped to an ending index over an appropriate time duration to obtain the needed transposition. The starting index in samples is just 44.1 times the starting location in milliseconds, plus one to allow for four-point table interpolation. This becomes the third number in a packed list generated by the pack object at the center of the voice patch.
We arbitrarily decide that the ramp will last ten thousand seconds (this is the ``1e+07" appearing in the message box sent to the wavetable index generator), hoping that this is at least as long as any note we will play. The ending index is the starting index plus the number of samples to ramp through. At a transposition factor of one, we should move by 441,000,000 samples during those 10,000,000 milliseconds, or proportionally more or less depending on the transposition factor. This transposition factor is computed by the mtof object, dividing by 261.62 (the frequency corresponding to MIDI note 60) so that a specified ``pitch" of 60 results in a transposition factor of one.
These and other parameters are combined in one message via the pack object so that the following message boxes can generate the needed control messages. The only novelty is the makefilename object, which converts numbers such as ``2" to symbols such as ``sample2" so that the tabread4~ object's wavetable may be set.
At the bottom of the voice patch we see how a summing bus is implemented inside a subpatch; an inlet~ object picks up the sum of all the preceding voices, the output of the current voice is added in, and the result is sent on to the next voice via the outlet~ object.