Create your own real-time guitar effects with python

publié le 15 mai 2014

[fr] Pyo est un module python combinant l'efficacité du C pour le traitement en temps-réel d'un signal audio et la souplesse du python pour la logique générale. Cet article explore la possibilité d'utiliser cette combinaison détonante pour programmer des effets de guitare (plus ou moins) classiques.

As much as I love python, I would of course never do real-time DSP (Digital Signal Processing) in python. But with Olivier Belanger's excellent pyo, one can now do DSP from python.

Pyo's website says

pyo allows the creation of sophisticated signal processing chains with all the benefits of a mature, and widely used, general programming language.

The idea is that all the heavy work is done in C for efficiency, but you get a bunch of nice handles to drive the process from python (see my previous article about pyo for more details).

Hmmm... wait a minute... does that mean that I can code my own guitar effects in my favorite programming language?

Chorus

Let's start with something simple:

import pyo

s = pyo.Server(audio='jack', nchnls=1).boot()
s.start()

a = pyo.Input(chnl=0)
chorus = pyo.Chorus(a, depth=.5, feedback=0.5, bal=0.5).out()

s.gui()

This is all you need for a fully-functional chorus. Just plug in your guitar and start rocking!

Just to give you an idea, here is an example (play mp3) of how it sounds (all examples are played on my harpejji, but a guitar would sound very similar).

The code is very simple:

  • first create and initialize the pyo server; here I'm using the jack backend, which allows me to get insanely low latencies, but pyo also supports portaudio and coreaudio)
  • then define the stream a that represents the input on the audio server's first channel. This is the basic idea of pyo: don't bother to work with 48'000 samples per seconds, just use a simple "handle"!
  • ... feed that stream as input to a Chorus stream with some classical parameters
  • ... and send that chorus to the output of the audio server (the .out() part)
  • The last line just displays a very simple GUI that allows you to start/stop the server and record its output.

Of course, this is a very complicated way to get a chorus if that's all you want. But writing the code yourself allows you to experiment with ideas that no hardware pedal (and few ready-made software) would allow you to realize.

Let's see:

Delay and getting wild

Let's modify the example above to get a delay:

import pyo

s = pyo.Server(audio='jack', nchnls=1).boot()
s.start()

a = pyo.Input(chnl=0).out()
delay = pyo.Delay(a, delay=.5, feedback=.5).out()

s.gui()

First listen (play mp3) to the result.

This is very similar to the chorus example, except for the .out() on the Input object. Here we need some dry sound in addition to the processed one!

But pyo allows to do much more. As an example, let's try this:

import pyo

s = pyo.Server(audio='jack', nchnls=1).boot()
s.start()

a = pyo.Input(chnl=0).out()

lfo = pyo.Sine(freq=4, phase=.5, mul=.5, add=.3)
delay = pyo.Delay(a, delay=.5, feedback=lfo, maxdelay=3).out()

s.gui()

Here we add a slow oscillator (a Sine with a frequency of 4Hz) and we use it to modulate the feedback amount of the delay. This very simple change results in a very distinctive sound (play mp3) that is completely different from the first version of the delay.

This example shows a great idea of pyo: most numerical arguments can be replaced by streams, modulating the value over time. And this can be chained: you could possibly add another oscillator modulating the frequency of the oscillator modulating the feedback of the delay (although it remains to be seen if that makes sense from a musical point of view...)

Auto-wah

In the example above, the feedback argument was modulated in a very simple and regular way.

Let's try something more fancy: drive the input through a resonant bandpass filter whose center frequency is modulated by the envelope of the very same input. Sounds complicated? This basically means that loud sounds will be boosted in the higher part of the spectrum and softer sounds in lower parts. This is precisely what is known as an auto-wah effect.

import pyo

s = pyo.Server(audio='jack', nchnls=1).boot()
s.start()

a = pyo.Input(chnl=0)
fol = pyo.Follower(a, freq=30, mul=4000, add=40)
f = pyo.Biquad(a, freq=fol, q=5, type=2).out()

s.gui()

The result (play mp3) is not bad at all for a 7-lines code!

The Follower object returns a stream with the continuous mean amplitude of its input; this value will be between 0 and 1. This is too low for the boost frequency of our wah effect. But every stream in pyo can be scaled and shifted with the mul and add arguments, so here we get values between 40 and 4040. Tune these values to get the sound you want! (you might also want to play with the "Q" value of the Biquad filter, which represents more or less the "width" of the emphasized part of the audio spectrum)

NB: recent versions of pyo include a Reson object that's both more readable and more efficient that the generic Biquad filter, but I don't have it yet on my (conservative) music production machine...

Wah pedal

Until now, every value came from the program or the input signal; but it's as easy to use some interaction with the user. Let's replace the envelope follower of the previous example with the (scaled) value of a MIDI expression pedal:

import pyo

s = pyo.Server(audio='jack', nchnls=1).boot()
s.setMidiInputDevice(pyo.pm_get_default_input())
s.start()

a = pyo.Input(chnl=0)

ctl = pyo.Midictl(7, minscale=200, maxscale=2000)
f = pyo.Biquad(a, freq=ctl, q=5, type=2).out()

s.gui()

The resulting sound (play mp3) will be similar to the auto-wah above but the wah effect will be driven by a pedal instead of the loudness of the sound.

Conclusion

By bringing together the speed of DSP in C and the versatility of python, pyo opens up a new world of possibilities. We only scratched the surface with these very simple examples, but the sky is the limit.

So go, play with it, explore... and don't forget to come back with beautiful music!

Voir aussi: