Pyo is an extremely powerful sound synthesis and processing python module. It has excellent documentation, but very little in the way of tutorial material.
So in this article I’m going to explain how to make a simple midi controlled synthesiser. This covers:
- Getting and using MIDI input
If you haven’t used pyo before, have a skim over the official introductory tutorial.
For this tutorial you will need:
- Python with Pyo installed
- A MIDI input
- Audio out (dur)
Making the Synth
Start with the obligatory
from pyo import *
Setting Up the Server
The convention seems to be to store your server in a variable called
s, and I see no reason not to stick to that.
# Set Up Server s = Server() s.setMidiInputDevice(2) # Must be called before s.boot() s.boot() s.start()
The server must be set up before it is
s.boot() must be called before the audio processing chain is defined.Python starts engaging with the audio drivers when
s.start() is called.
Both of these tasks take a couple of seconds to run, so I tend to call them before doing anything else. Strictly speaking you can call
s.start() at the end of the script and it makes no difference.
Which MIDI Device?
pm_get_input_devices() in an interactive shell will give you a numbered list of MIDI inputs. The int you pass to
s.setMidiInputDevice() is simply the number of the device you’re using.
For example, I’m using a Novation ReMote SL Compact.
pm_get_input_devices() gives me this output:
>>> pm_get_input_devices() (['IAC Driver Bus 1', 'IAC Driver IAC Bus 2', 'ReMOTE SL Compact Port 1', 'ReMOTE SL Compact Port 2', 'ReMOTE SL Compact Port 3'], [0, 1, 2, 3, 4]) 0: IN, name: IAC Driver Bus 1, interface: CoreMIDI 1: IN, name: IAC Driver IAC Bus 2, interface: CoreMIDI 2: IN, name: ReMOTE SL Compact Port 1, interface: CoreMIDI 3: IN, name: ReMOTE SL Compact Port 2, interface: CoreMIDI 4: IN, name: ReMOTE SL Compact Port 3, interface: CoreMIDI
And I know I want MIDI from ReMOTE SL Compact Port 1, so I call:
Set Up MIDI
Receiving MIDI input is very simple:
# Set Up MIDI midi = Notein() # The defaults are more than adequate
midi variable is an object that exposes two streams,
velocity. They can be accessed via
 notation, e.g:
midi['pitch']. At any given moment, the value of
midi['pitch'] is zero or more integers representing midi note values.
Pyo works using a whole load of these real-time streams, as you’ll see later.
For the purposes of understanding, we need to take a leap forward to the end of the script and have a look at the object that’s actually producing the sound, so you understand what it needs to be given to function properly.
The class in question is
Osc, and to function it requires (at minimum) three variables:
- A frequency
- An amplitude (volume)
- A waveform
So, we need to generate a waveform for the Osc to play, and from the MIDI input we need to generate streams of frequencies and amplitudes at which to play that waveform.
If you try feeding the values from
midi['pitch'] into an
Osc, you get some seriously microtonal music. This is because the values provided by
midi['pitch'] are MIDI note numbers, but the Osc object we’ll be using to generate sound works with frequencies.
We need some way of translating the MIDI values into pitches, and the
MToF (MIDI to Frequency) object does just that:
# Pitch pitch = MToF(midi['pitch'])
So we’ve fed the pitch stream into this object, and it’s producing another stream representing exactly the same information, but encoded as frequencies instead of midi note numbers. Perfect!
Handling velocity and preventing everlasting notes
A handy way of getting extremely easy control of the amplitude of your sounds over time is to use the built-in
MidiAdsr class. We’ll stick to the defaults for the moment, so all we need to do is feed in the midi data:
# ADSR amp = MidiAdsr(midi['velocity'])
Given the stream of raw velocities, we get a stream of ADSR controlled amplitudes (values from 0-1) that we can feed into the Oscillator.
Setting Up the Oscillator
So, we have a
pitch stream and a
amp stream. The last bit of data we need is a waveform for the synth to play. Without going into too much detail, we need a pyo table. I’ll explain more about these in a future article, for the moment just use the built in square wave:
# Table wave = SquareTable()
Now for the actual oscillator:
# Osc osc = Osc(wave, freq=pitch, mul=amp)
And that’s it. If you’re following along in an interactive shell, run
osc.out() and hit some notes on your keyboard — you should get some sound! (If you don’t, you’ve probably missed something, but don’t worry — there’s a full code listing at the bottom)
Pyo has some awesome effects built in. They take an audio source (like the
osc object we’ve made) as an input and act as an audio source themselves. Try out the reverb:
verb = Freeverb(osc).out()
Pyo has GUIs built in for a great many of it’s objects. The one you’ll use the most is:
Essential for non-interactive scripts, this prevents the script from quitting, provides a useful interpreter, quick record feature and an easy way to start and stop the server.
ctrl() on some objects will pop up a GUI for controlling their parameters, e.g. try:
And twiddle the sliders whilst you’re playing. Should look like this:
The other graphical display that is indispensable whilst using pyo tables is
view(). Called on any table object, it produces a graphical representation of the waveform — brilliant when the docs say things I don’t understand like ‘Chebyshev polynomials of the first kind’. Try:
It should produce something like this:
Putting it all together
That was all a bit disjointed, so here’s the entire script, with comments:
from pyo import * # Set Up Server s = Server() s.setMidiInputDevice(2) # Change as required s.boot() s.start() # Set Up MIDI midi = Notein() # ADSR amp = MidiAdsr(midi['velocity']) # Pitch pitch = MToF(midi['pitch']) # Table wave = SquareTable() # Osc osc = Osc(wave, freq=pitch, mul=amp) # FX verb = Freeverb(osc).out() ### Go osc.out() s.gui(locals()) # Prevents immediate script termination.
And here’s a diagram of exactly what’s happening, just in case I’ve confused you:
Names of objects are: ClassName (variable name I’ve used)
I hope you have great fun using pyo! I’ll be explaining how to do more things as I learn more myself. Please direct any corrections, comments, issues, cool tricks etc to firstname.lastname@example.org