In this post, I will be documenting the process of creating a software synthesis system which interfaces with hardware MIDI devices. The goal of this project is to bring together the powerful expressiveness of software synthesis with the intuition of hardware interaction.
Getting Started
Motivation
I have a MIDI controller that I would like to bring into the mix more (so to speak) in my music workflow. The great thing about hardware designed to work with software on a computer is that it offers a lot of flexibility; however, that comes with the price of requiring a bit of effort and creativity on the software end to take full advantage of the hardware.
When it comes to digital sound synthesis, there is perhaps no program more powerful than SuperCollider. SuperCollider runs as a server which can be sent commands from clients. The server is usually are controlled using the `sclang` programming language. The program and language are designed specifically for electroacoustics and generative music. See the video below for an example of a project that used SuperCollider for both of these functions.
The laptop as an instrument is a rather new concept, but the techniques used in digital synthesis and generative music are decades old. With this project, I aim to tap into and expand upon that legacy.
Development Tools
SuperCollider has its own IDE called scide
, but I will be working in the Emacs development environment. Emacs is a general purpose text editor which I use for most of my work that involves plain text. Emacs is well suited for SuperCollider development because Emacs itself runs with a REPL (Read–eval–print loop). This encourages a workflow of writing small chucks of code, sending them to the server to be evaluated, and then analyzing the results.
SuperCollider works by interfacing with the JACK Audio Connection Kit. Like SuperCollider itself, JACK works as a server that directs signals from many different sources. It is designed for real-time audio applications and thus tends to have very low latency. I use a suite of tools called Cadence to control and connect my JACK applications. The figure above shows how I have wired together the SuperCollider server with my system capture (microphone) and system playback (speakers or headphones). Using JACK allows SuperCollider to interact with other audio programs such as a DAW (digital audio workstation).
Making Some Sounds
Now that I have all the tools needed to run SuperCollider set up, let’s start making some noise. I first needed to boot up Emacs running the SuperCollider environment.
emacs -sclang
I then booted the SuperCollider server.
s = Server.local.boot;
s
is a special variable that is used exclusively for the Server
. The other letters of the alphabet can be used as global variables. It is best to attach functions or any other sound generator to a variable so that they can be stopped or modified when needed. To start, I used a function that combined a sine oscillator with pink noise. The arguments for the sine oscillator indicate frequency, phase, and amplitude. The argument for the PinkNoise
generator indicates volume.
g = { SinOsc.ar(440, 0, 0.1) + PinkNoise.ar(0.01) }.play;
This sound will play indefinitely until we free the function.
g.free;
Running and then freeing the function produces the following output:
We now have sound being generated by SuperCollider. In the next post, I will be setting up MIDI input.
Making Connections: MIDI in SuperCollider
The previous section demonstrated the process of setting up SuperCollider and generating a tone. In this next post, I will be explaining how to set up MIDI input in SuperCollider.
MIDI is a standard protocol that dates back to the early 1980s. It supports up to sixteen channels and can be used to communicate pitch, velocity, and other information important for the operation of musical instruments. In the long term, I would like to be able to choose different timbres by mapping them to different MIDI channels. I would also like to be able to change parameters using control change messages.
First, however, I needed to set up SuperCollider to accept MIDI input.
Enabling MIDI in SuperCollider
Start the SuperCollider server if it is not already running.
s.boot;
From the Catia patchbay, it is clear that the SuperCollider instance does not currently accept MIDI input.
We can change this by running
MIDIClient.init;
MIDIIn.connectAll;
On my system, this created three MIDI input ports and one output port.
In this case, I was only interested in controlling the server from one source, so I only needed one MIDI input. The documentation for MIDIClient
shows by default running MIDIClient.init
“opens as many inports as there are MIDI sources”. To only have one inport, I reset the MIDIClient
and reinitialized it with the correct number of ports specified.
MIDIClient.disposeClient;
MIDIClient.init(1, 1);
Now I had one input port and one output port.
Getting input
MIDIdef.noteOn
allows us to run a function whenever a note is pressed. To test this out, I created a simple function that prints the associated MIDI information whenever a key is pressed.
MIDIdef.noteOn(\print, {arg val, num, chan, src; [src,chan, num, val].postln});
I then opened my DAW and created a simple MIDI pattern in the piano roll. I then configured the DAW to export any MIDI playback on that track to the program’s output. Connecting the DAW’s output to SuperCollider’s printed gave the following information:
[ 8454144, 0, 60, 127 ]
[ 8454144, 0, 63, 127 ]
[ 8454144, 0, 67, 127 ]
[ 8454144, 0, 65, 59 ]
[ 8454144, 0, 68, 59 ]
[ 8454144, 0, 72, 59 ]
This indicates that the source is identified by the integer 8454144 and that the MIDI notes were sent on the first channel (they are indexed starting with zero). The third number in the arrays represent notes and the last number represents the velocity of the note (ranging from zero to 127).
We can filter the notes such that the function is only called for a certain source or channel:
MIDIdef.noteOn(\test4, {arg val, num, chan, src;
[src,chan, num, val].postln;
}, chan: 1);
Down the road, this will give us the ability to set up multiple instruments that can be selected using the MIDI channel.
Mapping MIDI Channels to Multiple Instruments in SuperCollider
Being able to control a polyphonic instrument in MIDI is good, but being able to control multiple instruments is even better. SuperCollider offers a lot of flexibility when it comes to timbre. For my personal workflow, I like to try out a lot of different sounds to see what best in the mix. Thus when thinking about how I want to use the MIDI controller in connection with SuperCollider, it makes sense to me to be able to switch between instruments fluidly.
Finding some sounds
If you do not want to start from scratch, there are a number of excellent resources for finding SuperCollider =SynthDef=s:
- GitHub is a service that hosts millions of software projects created and maintained by developers around the world. The source code for SuperCollider itself is hosted on GitHub in addition to hundreds of other projects written in the SuperCollider language.
- SuperCollider Code is a community-driven website which allows users to post snippets of their SuperCollider code. These snippets use tagging, which makes it easy to search for specific timbres. The website also hosts the SuperCollider documentation.
- patchstorage has a few SuperCollider patches, but seems to have rather limited activity currently.
To start, I copied a few SynthDefs
:
- The first channel is for the simple sine wave
SynthDef
. - I attached the second channel to a piano
SynthDef
which usesMdaPiano
, a generator provided bysc3-plugins
. - The third channel provides an Electric Piano timber found on sccode.org.
- The fourth channel is used for an organ instrument meant to emulate a classic Hammond organ.
I considered these sounds to be a good starting point for emulating many classic keyboard instruments.
Switching instruments
To allow these different timbres to be selected, I made a few changes to the function defined in the last section. First, I created a second array with sixteen elements to hold the names of the different `SynthDef`s.
// https://gist.github.com/umbrellaprocess/973d2aa16e95bf329ee2
var keys, instruments;
keys = Array.newClear(128);
instruments = Array.newClear(16);
instruments.put(0, \sinpk);
instruments.put(1, \piano);
instruments.put(2, \rhodey_sc);
instruments.put(3, \hammond);
I then modified the NoteOn
function such that the correct instrument is selected based on its position in the `instruments` array.
~noteOnFunc = {arg val, num, chan, src;
var node;
node = keys.at(num);
if (node.notNil, {
node.release;
keys.put(num, nil);
});
node = Synth(instruments.at(chan), [\freq, num.midicps, \vel, val]);
[num, chan].postln;
keys.put(num, node);
};
Now I could select the appropriate instrument by simply changing the MIDI channel on my controller.
A quick demo
Putting it all together, I created a simple track to demonstrate these different timbers (accompanied with some mandolin):
This posts has worked through some building blocks for using SuperCollider as a platform for creativity. As I wrote in “The Paradox of Creativity”, I find the creative process to be best when applied to areas that are challenging. I believe it is for this reason that I find SuperCollider to be such an interesting platform: it provides the pieces for expansive sonic possibilities, but it takes a bit of effort and curiosity to make the most of it.