Wednesday, October 17, 2012

Max for Live Basics Tutorial 4: Dealing with Polyphony

Overview
The aim of this tutorial is to cover the basics of dealing with polyphony in Max for Live. This tutorial should be seen as a starting point that can be expanded upon. This tutorial assumes that you have read Max for Live Basics 1, Max for Live Basics 2 and Max for Live Basics 3.  

Max for Live is an extension for Ableton Live. This allows users to load patches made in Cycling 74's Max inside of Ableton as Live devices. Max is a graphical programming language whereby patches are created by connecting various objects together. Each object performs a specific function, and multiple objects - interconnected in specific ways - can perform complex operations.

In Max for Live, three categories of devices can be created:
• Audio effects
• MIDI effects
• Instruments

An audio effect takes an audio signal as an input and outputs an audio signal.

A MIDI effect takes MIDI data as an input and outputs MIDI data.

An instrument takes MIDI data as an input and outputs an audio signal.

This tutorial will cover how to create a polyphonic sinewave synthesiser as a Max Instrument device.

video










Basics: Polyphony
The creation and management of a polyphonic synth is done in at least two steps:
• Create an "outside" max for live patch that handles the gui and MIDI routing
• Create an "inside" max patch that handles a singular voice instance of the synth (in this case a simple sine wave generator)

This may sound complicated, but let's break it down into simple steps.


Let's start from scratch! Create a new MIDI track and drag a "blank" Max Instrument device to it from Live Devices browser > Instruments > Max Instrument. You should see the blank template in the instruments and effects area.










Basics: Creating the "outside" Max for Live patch

Open Max by clicking on the edit button (first button in the upper right hand corner of the device).


Go to File > Save as and save the patch in the Instruments folder (Max for Live will automatically navigate there). I've labelled mine Seb.ExamplePoly.amxd


Let's clear away some of the clutter of the comments. Select the appropriate items to delete, and hit delete. 


We need more room to create our synth. Move the plugout~ object down to create space. 


Create a midiparse object. Connect the outlet from the midiin object to the midiparse object. 


Create a prepend object with the argument midinote. Connect the first outlet of the midiparse object to the inlet of the prepend object. The first outlet of the midiparse object outputs a list of values. The preprend object will add the word "midinote" before each combination. 

For example, if we send a C3 note-on with a velocity of 120 to the Max Instrument (via a keyboard or Live's sequencer), then the output of the prepend midinote object will be a list of three items of the form [word] [pitch number] [velocity]:

[midinote] [60] [120]


Our synth will have a standard ADSR envelope. Create four live.dial objects. These four dials will be the attack, decay, sustain and release parameters of our synth.


Select each live.dial object and open the inspector (command i). We need to change a number of aspects for each of these dials before we can really use them in our patch.

For the attack live.dial, change the long name to "Attack", the short name to "attack", the range / enum to "0. 1000." and the unit style to "Time".

For the decay live.dial, change the long name to "Decay", the short name to "Decay", the range / enum to "0. 1000." and the unit style to "Time".

For the sustain live.dial, change the long name to "Sustain", the short name to "Sustain", the range / enum to "0. 100." and the unit style to "% percentage".

For the release live.dial, change the long name to "Release", the short name to "Release", the range / enum to "0. 1000." and the unit style to "Time".


For the time being, let's leave our "outside" patch and let's begin working on our "inside" patch.










Basics: Creating the "inside" Max patch

 Let's start making our "inside" Max patch.  Create a new, completely blank patcher by going to File > New Patcher (command n).


Let's save this patcher in the same directory as our Max for Live device "outside" patch and let's give this "inside" patch an appropriate name.

I'm calling mine Seb.ExamplePoly.voice.maxpat I've called it .voice because it is a single voice - of a number of voices - of our synth, and represents an archetypal instance of a synth voice with control over frequency and amplitude.

The idea is that our "outside" Max for Live instrument patch will load up multiple instances of this one patch  which will behave identically apart from the MIDI note and velocity that are received per instance or voice.

This process will become more clear in the coming steps.


Create an in object with the argument 1. This will be an inlet going into our patcher, and will receive incoming pitch and velocity data.


Create an in object with the argument 2. This will be an inlet going into our patcher, and will receive the attack parameter values. 


Create an in object with the argument 3. This will be an inlet going into our patcher, and will receive the decay parameter values.  


Create an in object with the argument 4. This will be an inlet going into our patcher, and will receive the sustain parameter values.   


Create an in object with the argument 5. This will be an inlet going into our patcher, and will receive the release parameter values.    


As our in 1 object will be receiving both the MIDI pitch and velocity, we need to unpack and thus separate the MIDI pitch from the MIDI velocity.

Create an unpack i i object and connect the outlet of in 1 to the inlet of the unpack i i object. The argument "i" in this context refers to the fact that both the MIDI and the pitch that make up the list coming from the in 1 object are integers (whole numbers).

The pitch will be used to derive the frequency and the velocity will be used to trigger our ADSR envelope as well as control the overall volume of our ADSR envelope.

This is similar to how we dealt with a MIDI pitch and velocity list in Max for Live Basics 1.

The list will be unpacked and separated by the unpack object. The pitch will be sent to the first outlet and the velocity will be sent to the second outlet.


Create an mtof object. Connect the first outlet of the unpack i i object (the MIDI note number) to the inlet of the mtof object. The mtof converts MIDI note number to frequency value.


Create a / 127. object. Connect the second outlet of the unpack i i. This will divide the velocity by 127, thereby scaling it from an original range of 0 - 127 to a new range of 0.0 to 1.0. Don't forget the decimal place!


Let's create a sinewave, which is the basis of our voice and thus our polyphonic synth.

Create a cycle~ object. Connect the outlet of the mtof object to the first inlet of the cycle~ object.

The output of the mtof object is sending a floating point value (i.e. decimal number) of the frequency. The first inlet of the cycle~ object is expecting a value that will control the frequency of the sine wave.

This is similar to how we dealt with mtof and cycle~ in Max for Live Basics 1.


Create a *~ object. Connect the outlet of the cycle~ object to the first inlet of the *~ object.

The outlet of the cycle~ object is our sine waves, sent as a series of samples corresponding to amplitude values between -1. and 1.

We will use the *~ object to multiply the amplitude of the sinwave by other values so as to control the amplitude of the sine wave using an ADSR envelope.

This is similar to how we dealt with cycle~ and *~ in Max for Live Basics 1.


As we are making a polyphonic synth, we need to decide how many voices our synth will have. Sixteen seems like an appropriate number. As a result, we need to divide the amplitude of each voice instance by sixteen so that our total amplitude output does not exceed the range of -1. to 1., which would result in clipping.

Create a /~ 16. object. Connect the outlet of the *~ object to the first inlet of the /~ 16. object. 


Create an out~ 1 (note the tilde ~). Connect the outlet of the /~ 16. object to the inlet of the out~ 1 object.

The out~ 1 object represents an audio output of the "inside" voicing instance patch.

This will allow us to route audio out of each "inside" voicing instance to the "outside" Max for Live device patch.


Create an adsr~ object. Connect the outlet of the / 127. object to the first inlet of the adsr~ object.

The adsr~ object will generate the ADSR envelope that will control the amplitude of the sine wave.

The first inlet of the adsr~ object expects a value from 0. to 1.. If it receives a value other than 0., then it will generate the attack, decay and sustain portions of the ADSR envelope. If it receive a value of 0., then it will generate the release portion of the ADSR envelope.

Consider that our velocity - which is coming out of the unpack i i object - has an initial range of 0 - 127. A note on is a value in the range of 1 - 127, whilst a note off has a value of 0. The / 127. object scales this range from 0 - 127 to a new range of 0. to 1.. 

Thus, the adsr~ object receives the scaled velocity in the range of 0. to 1., where 0. represents a note off.

This corresponds perfectly with how our envelope needs to be triggered and released.


Connect the outlet of the in 2 object to the second inlet of the adsr~ object.

The in 2 object receives values for the attack parameter in milliseconds. The second inlet of the adsr~ object expects values for the attack in milliseconds.


Connect the outlet of the in 3 object to the third inlet of the adsr~ object.

The in 3 object receives values for the decay parameter in milliseconds. The third inlet of the adsr~ object expects values for the decay in milliseconds.

Connect the outlet of the in 4 object to the fourth inlet of the adsr~ object.

The in 4 object receives values for the sustain parameter in milliseconds. The fourth inlet of the adsr~ object expects values for the sustain in terms of a gain multiplier with a range of 0. to 1..


Connect the outlet of the in 5 object to the fifth inlet of the adsr~ object.

The in 5 object receives values for the release parameter in milliseconds. The fifth inlet of the adsr~ object expects values for the release in milliseconds.

Connect the first outlet of the adsr~ object to the second inlet of the *~ object.

The first outlet of the adsr~ object is the control signal that represents the generated ADSR envelope.


Create a thispoly~ object. Connect the first outlet of the adsr~ object to the inlet of the thispoly~ object.

The inlet of the thispoly~ object accepts a signal. If the signal is not zero, then the current instance / voice of the "inside" patch is in a "busy" state. If the signal is zero, then the current instance / voice of the "inside" is in a "not busy" state.

If we consider the output of the ADSR envelope generator, then at ANYTIME that an envelope is being generated, the output signal is not zero. As soon as the release portion of the envelope finishes, however, the control signal of the ADSR envelope drops to zero.

This functionality is used by the thispoly~ object to signify whether or not the ADSR envelope has is currently generating a part of an envelope, or whether the voice is currently silent (i.e. with an amplitude of zero).

In turn, this "busy" or "not busy" state is used by the Max for Live device to signify whether or not a voice is free or not.

If the voice is free - i.e. the "not busy" state and silence with an amplitude of zero - then the Max for Live device knows that that particular instance of the "inside" patch can be used.

If the voice is not free - i.e. the "busy" state with the adsr~ object generating an ADSR envelope - then the Max for Live device knows that that particular instance of the "inside" patch cannot be used, and moves on to the next one to see whether or not it is free or not (in our case up to sixteen voices can be used at once in this fashion).

Save your Max patch. Let's go back to the Max for Live "outside" patch now...











Basics: Finishing the "outside" Max patch



We need to add the "inside" patch to the "outside" patch and connect the parameters and MIDI routing so that it can reach the "inside" patch. Let's start off by creating a poly~ object with two arguments.

The first argument is the file name of the Max patch that was saved as the "inside" voice patch.

The second argument is the number of voices to assign to this "inside" voice patch i.e. how the maximum number of notes that should be playable at once.

In my case, I've created the following poly~ object:

poly~ Seb.ExamplePoly.maxpat 16


Connect the outlet of the prepend midinote object to the first inlet of the poly~ object.

This way, we will be sending lists of note ons / note offs through to each of the instances of the poly~ patch that has been loaded as the "inside" patch.

Max for Live determines which particular voice out of the sixteen voices / instances to send the MIDI pitch and velocity to based on each voices' / instances' "busy" or "not busy" state (as determined by the thispoly~ object inside each of the voices / instances - see previous section).


Connect the outlet of the poly~ object to both of the inlets of the plugout~ object.

This will send the audio from each of the sixteen voices to the output of the devices.


Connect the left outlet of the attack parameter to the second inlet of the poly~ object.

This will send the parameter to each and every voice so that they all have the same value.


Connect the left outlet of the decay parameter to the third inlet of the poly~ object.

This will send the parameter to each and every voice so that they all have the same value.

Connect the right outlet of the sustain parameter to the fourth inlet of the poly~ object.

This will send the parameter to each and every voice so that they all have the same value.

Connect the left outlet of the release parameter to the fifth inlet of the poly~ object.

This will send the parameter to each and every voice so that they all have the same value.


Create a loadmess object with two arguments: "target" and "0".

The loadmess object sends any arguments as a message to its outlet whenever a patch is loaded.

If the message "target 0" is received by a poly~ object, then any data values that it receives in its inlets are sent to each and every instance of the loaded poly~ "inside" patch.

The result of this is that all of the data values that we are sending - the attack, decay, sustain and release parameters - are sent to all sixteen voices due to the loadmess target 0 object. 


Let's clean up the patch. Move all of the objects to the left (you can do a select all).


Select all of the non-graphic user interface object by option-clicking and dragging. Press command k to hide these objects from view when the synth is active. Save the file, and go back to Ableton Live.


The synth is finished! Try it out by playing chords into the track and adjusting the ADSR parameters.










Exercises
• Adjust the "inside" patch so that multiple waveforms play back at once (hint objects: rect~, saw~, noise~)
• Add a waveform selector so that different waveforms can be selected for the "inside" patch from the "outside" patch (hint objects: umenu)
• You can put a poly~ inside of a poly~...
• Any signals (i.e. yellow patch cables) sent to a poly~ object will be processed by all poly~ loaded patches simultaneously...

0 comments: