(max) simpler and better audio synth polyphony

In the previous post I showed you how to use the (MIDI) [poly] object to assign voice numbers to incoming note on/off messages, and by packing those voice assignments with the note number and velocity, sending them to the appropriate voice of a polyphonic audio synthesizer hosted in [poly~]. In this post I will show you a simpler, and better, way of voice handling in a polyphonic audio synth.

the problem

Using MIDI [poly] free voices are determined by the note off message – releasing the key on the MIDI controller. However, releasing a MIDI key simply triggers the release part of the envelope, which can take any specified amount of time. During the release part of the envelope, the actual audio voice is still busy. [poly] thinks that it is free, and can assign it another note to play.

changes to the synth

<SimpleSawKK2~.maxpat>

You need to have each instance of the synthesizer report its own free/busy state to the [poly~] object. You do this by inserting the [thispoly~] object into your synthesizer subpatcher, and you connect the audio out of the final [*~] to the input of [thispoly~]. I usually place [thispoly~] right next to the [out1~] (or other out~ object). When the audio input signal to [thispoly~] is non-zero (it contains signal), [thispoly~] will report that the voice instance is busy. No new note commands can be sent to it. When the audio input signal goes to zero, [thispoly~] reports that the voice is free, allowing new input to that voice instance. So, when the envelope is still sending out signal during its release phase, [thispoly~] reports that the instance is (still) busy.

changes to the parent patcher

<BasicPolySynth2.maxpat>

The parent patcher needs three changes: deleting [poly], using [prepend midinote] to format the midi note data before sending to [poly~], and a [steal 1] message sent to [poly~].

Since you want [poly~] and [thispoly~] to handle voice assignments, you don’t need the MIDI [poly] object. You still pack the note number and velocity together, but you send it to a [prepend] object with the argument “midinote”. [poly~] understands two types of messages to assign a message to a free voice, “note” and “midinote”. “midinote” must be used if you want to be able to later send a noteoff message (note number and velocity of zero) to the same voice that received the noteon message. “note” will route messages to free voices only, with the subpatcher having its own way of turning notes off (usually because it has an AR – attack release) envelope with no sustain, or a specified sustain time that doesn’t need a separate noteoff trigger.

Since [poly] was handling voice stealing if needed, you now need to turn on that feature in [poly~]. You turn on voice stealing by sending the message, “steal 1”, as [poly~] doesn’t have a defined argument for voice stealing.

communicating with all instances of a [poly~] subpatcher

In the first instance of my polyphonic synth patcher, I used “target 0” to send messages to all instances of a subpatcher. A simpler way is to use [send] and [receive] ([s] and [r]) objects. Since these objects pass messages across patchers to any open patcher, they work to send/receive to all instances of a subpatcher embedded in a parent patcher.

a clean, clean poly~ machine

<BasicPolySynthClean.maxpat>
<SiompleSquareKK2~.maxpat>

We’ll want to add more processing objects to our parent patcher, and we don’t need to see every object and number box along the way. I created <BasicPolySynthClean.maxpat> to use [p] objects to encapsulate most of the functions of the synth, including basic notein and ctlin functions.

Note input and message packing is done in the [p midiinput] patcher. Controller in and scaling of the output gain is handled in [p gainControl]. The only other objects external to the [poly~] is the steal message and the umenu to select an envelope preset.


Comments

Leave a Reply