Controlling Synths with MIDI in SuperCollider

Notes on acoustics

I previously showed how to set up SuperCollider to communicate with other programs and external hardware using MIDI. Today I am going to use these connections to manipulate instruments.

Controlling the tone with MIDI

In my notes on setting up SuperCollider, I created a function that generated a simple tone.

g = { SinOsc.ar(440, 0, 0.1) + PinkNoise.ar(0.01) }.play;
g.free;

To give more control over the tone, we need to define the generator using SynthDef. This class can be thought of as the instructions or recipe which can be used to create Synth instances.

SynthDef.new(\sinpk, 
    { Out.ar(0, SinOsc.ar(440, 0, 0.1) + PinkNoise.ar(0.01)) }
).play;

Let us deconstruct this SynthDef. \sinpk is the name of the SynthDef. It can be used when creating instances, for example by calling Synth.new(\sinpk). The definition itself contains the same tone generator function used previously, but the output is being explicitly sent to the first bus in Out.ar. Pan2.ar ensures that the sound is in stereo.

Of course, we are going to want to add some parameters so that we can modify the tone over time.

SynthDef.new(\sinpk, { arg freq = 440;
	Out.ar(0, Pan2.ar(SinOsc.ar(freq, 0, 0.1) + PinkNoise.ar(0.01)));
}).add;

freq is an argument representing the frequency of the sine wave. Arguments are parameters which can be sent when creating a new Synth and which can be modified later on. Instances of a Synth can be created by calling Synth.

h = Synth(\sinpk, [\freq, 440]);

This call creates a new Synth node and assigns it to the variable h. The frequency is being set to 440 hertz. MIDI uses incriminating integers instead to represent notes, so we will need to convert these numbers to frequencies using midicps.

h.set("freq", (69).midicps);

We can now use MIDI to control the note being generated by the node stored in h.

MIDIdef.noteOn(\changefreq, {arg val, num, chan, src;
	h.set("freq", (num).midicps);
});

This attaches a new functions that responds to MIDI note presses called \changefreq. The function is passed arguments representing the velocity, note, channel, and source. Each time a note is pressed, the frequency will be changed to match the note.

To unattach the function and any other function that is triggered by MIDI, run MIDIdef.freeAll.

Creating an instrument

The note generator is monophonic and the note continues to play perpetually. To make it polyphonic, we are going to do things slightly differently. First we need a sound for SuperCollider to generate whenever a note is pressed. We also need to make sure that the sound stops being made when the note is released. In SuperCollider, this is typically done by setting gate variable when the note ends.

SynthDef(\sinpk, { arg freq = 440, gate = 1;
    var x;
    x = SinOsc.ar(freq, 0, 0.1) + PinkNoise.ar(0.01);
    x = EnvGen.kr(Env.asr, gate, doneAction: 2) * x;
	Out.ar(0, Pan2.ar(x));
}).add;

We need a way to keep track of which notes are currently pressed. To do this, create an array which can store the notes. Each time a note is pressed, create a new Synth and add it to the position in the array corresponding to the note. Every time a key is pressed, release the note.

(
// https://gist.github.com/umbrellaprocess/973d2aa16e95bf329ee2
var keys;
keys = Array.newClear(128);

~noteOnFunc = {arg val, num, chan, src;
	var node;
	node = keys.at(num);
	if (node.notNil, {
		node.release;
		keys.put(num, nil);
	});
	node = Synth(\sinpk, [\freq, num.midicps]);
	keys.put(num, node);
};

MIDIdef.noteOn(\on, ~noteOnFunc);

~noteOffFunc = {arg val, num, chan, src;
	var node;
	node = keys.at(num);
	if (node.notNil, {
		node.release;
		keys.put(num, nil);
	});
};

MIDIdef.noteOff(\off, ~noteOffFunc);
)

Evaluating this block allows notes to be pressed and released by pressing and releasing the keys.

The instrument now can be controlled over MIDI. In the next post, I will be setting up multiple instruments which can be selected using one of the sixteen MIDI channels.