<!-- .slide: data-background-image="pics/EP2025_page002.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> <br> # How to use **Python** on a RPi<br>to develop a custom<br>headless guitar FX box ## Matthieu Amiguet ### Les Chemins de Traverse --- <!-- .slide: data-background-image="pics/EP2025_page001.svg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/Logo_CDT_Noir.png" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/map.jpg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/EP2025_page003.svg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/bikes.jpg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/CDT_WorkInProgress-6.jpg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/harpejji.jpg" --> <!-- .slide: data-background-size="100%" --> --- <iframe width="1600" height="900" src="https://www.youtube-nocookie.com/embed/NLbvldookZY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> Improvised prelude in baroque style, Matthieu Amiguet, harpejji (2024) --- <!-- .slide: data-background-image="pics/EP2025_page004.svg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/EP2025_page001.svg" --> <!-- .slide: data-background-size="100%" --> <div style="background: #DDDDDD; padding-bottom: 1.4em;"> ## Atypical choices - Custom audio processing software - Only FOSS - Python </div> --- <!-- .slide: data-background-image="pics/EP2025_page007.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> # Outline - FX programming with python - Going headless - User interaction --- <!-- .slide: data-background-image="pics/EP2025_page007.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> # FX programming<br>with python<br>and the `pyo` module --- ## `pyo` - Developed by Olivier Bélanger in Montréal - Sound engine programmed in C - Processing chain manipulated from python - Combines C efficiency with Python versatility - <http://ajaxsoundstudio.com/software/pyo/>  <!-- .element: class="r-stretch" --> --- <!-- .slide: data-background-image="pics/run.jpg" --> <!-- .slide: data-background-size="100%" --> ## Olivier Bélanger<br>*Run*<br>(2011) <!-- .element: style="text-align: right; color: #888" --> <div class="r-stretch"></div> <audio controls style="position: absolute; right: 0; bottom: 1em;"> <source src="sounds/belangeo_run_extract.mp3"> </audio> --- But `pyo` is also an <!-- .element: class="r-fit-text"--> *awesome* tool <!-- .element: class="r-fit-text"--> for real-time audio processing <!-- .element: class="r-fit-text"--> --- <!-- .slide: data-background-image="pics/EP2025_page008.svg" --> <!-- .slide: data-background-size="100%" --> ## Sound trough ### (a.k.a. *Hello Audio World*) ```python[|1|3|5-6|8|10] import pyo s = pyo.Server(audio='jack').boot() guitar = pyo.Input() guitar.out() s.start() input('Running. Press <enter> to terminate.') ``` --- <!-- .slide: data-background-image="pics/EP2025_page009.svg" --> <!-- .slide: data-background-size="100%" --> ## Sound trough, two inputs ```python[] bass = pyo.Input(0) melody = pyo.Input(1) harpejji = pyo.Mix([bass,melody]) harpejji.out() ``` --- <!-- .slide: data-background-image="pics/EP2025_page011.svg" --> <!-- .slide: data-background-size="100%" --> ## Adding Reverb ```python[|6|5|7-10] bass = pyo.Input(0) melody = pyo.Input(1) harpejji = pyo.Mix([bass,melody]).out() reverb = pyo.FreeVerb( harpejji, # input size=.6, # length of reverb damp=1, # High frequency attenuation bal=1, # Balance between dry and wet signal mul=.6 # reverb volume ).out() ``` --- <!-- .slide: data-background-image="pics/EP2025_page010.svg" --> <!-- .slide: data-background-size="100%" --> ## Auto-wah ```python[|5-10|4,11-12|15-21|] harpejji = pyo.Input([0,1]).mix(1) # no out - no dry sound! # Amplitude follower, smoothed out fol = pyo.SigTo( pyo.Follower( harpejji, # input freq=30, # cutoff frequency mul=10000, # multiply by 10000 add=40 # ..and add 40 ), time=0.01, ) # Bandpass filter wah = pyo.Biquad( harpejji, # input type=2, # 2 -> bandpass freq=fol, # center frequency q=5, # width mul=2.5 # amplification ) ``` <div class="r-stretch"></div> <audio controls> <source src="sounds/auto_wah.mp3"> </audio> --- <!-- .slide: data-background-image="pics/EP2025_page012.svg" --> <!-- .slide: data-background-size="100%" --> ## Make it bypass-able ```python[] selector = pyo.Selector( [harpejji, wah], voice=0, ).out() # Activate FX # selector.setVoice(1) # De-activate FX # selector.setVoice(0) ``` ---  <!-- .element: class="r-stretch" --> --- <!-- .slide: data-background-image="pics/EP2025_page005.svg" --> <!-- .slide: data-background-size="100%" --> # <br>Going headless --- <!-- .slide: class="center" --> # Going headless ## Hardware --- <!-- .slide: data-transition="slide-in none-out" --> ## Which computer for audio processing? --- <!-- .slide: data-transition="none-in slide-out" --> <!-- .slide: data-background-transition="fade-in slide-out" --> ## Which computer for audio processing? <!-- .slide: data-background-image="pics/EP2025_page006.svg" --> <!-- .slide: data-background-size="100%" --> --- <!-- .slide: data-background-image="pics/rpi.jpg" --> <!-- .slide: data-background-size="100%" --> --- ## Model selection  Can we achieve high quality low latency<br>audio processing on a 2016 single board? <!-- .element: class="fragment r-fit-text" --> --- ## USB-powered sound interface  Problems: - high-pitched hiss (switched mode power supply) - low buzz (only in certain configurations - floating ground) --- ## Pisound rpi hat with - high quality stereo audio in and out - MIDI - a button  --- ## Pisound (with case)  <!-- .element: class="r-stretch" --> --- ## To solve the buzz ### Don't try this at home! <!-- .element: class="fragment" data-fragment-index="2" --> ### (unless you know what you're doing) <!-- .element: class="fragment" data-fragment-index="2" --> <div class="r-stack r-stretch"> <img class="fragment" data-fragment-index="3" src="pics/cursed_connector.jpg" /> <img class="fragment" data-fragment-index="4" src="pics/dual_usb_c_2x.png" /> </div> --- <!-- .slide: class="center" --> # Going headless ## Software --- <!-- .slide: data-background-image="pics/EP2025_page023.svg" --> <!-- .slide: data-background-size="100%" --> # OS --- <!-- .slide: data-background-image="pics/EP2025_page013.svg" --> <!-- .slide: data-background-size="100%" --> # OS - rpi OS <!-- .element: class="fragment highlight-current-red" --> - no rt-kernel, latency to high - debian <!-- .element: class="fragment highlight-current-red" --> - possible with some tweaking (rt-kernel, cpu governor, jack config, ...) - see <https://matthieuamiguet.ch/blog/rpi-diy-fx-pedal/> - Patchbox OS by Blokas: works out of the box 🤩<!-- .element: class="fragment highlight-current-green" --> --- <!-- .slide: data-background-image="pics/bootscreen.jpg" --> <!-- .slide: data-background-size="100%" --> <div style="background: #FFFFFFDD; padding-bottom: 1em"> # Auto-start <pre><code data-trim data-noescape data-line-numbers="|14|9-11|1" data-fragment-index="0"> # /etc/systemd/system/harpejji.service [Unit] Description=Harpejji Wants=openstagecontrol.service [Service] Type=simple LimitRTPRIO=95 LimitMEMLOCK=infinity Environment=JACK_PROMISCUOUS_SERVER=jack User=patch Group=patch ExecStart=/usr/bin/python3 /home/patch/harpejji/harpejji.py [Install] WantedBy=multi-user.target </code></pre> then run <!-- .element: class="fragment" data-fragment-index="3" --> ``` # systemctl enable harpejji.service ``` <!-- .element: class="fragment" data-fragment-index="3" --> </div> --- <!-- .slide: data-background-image="pics/EP2025_page007.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> # User interaction --- <!-- .slide: data-background-image="pics/EP2025_page014.svg" --> <!-- .slide: data-background-size="100%" --> ## Three possibilities --- <!-- .slide: data-background-image="pics/EP2025_page019.svg" --> <!-- .slide: data-background-size="100%" --> # User interaction ## MIDI --- <!-- .slide: data-background-image="pics/EP2025_page015.svg" --> <!-- .slide: data-background-size="100%" --> ## Sound trough, two inputs with MIDI pedal volume ```python[|1|2|9|] pedal = pyo.Midictl(channel=1, ctlnumber=7) volume = pyo.SigTo(pedal, .1) bass = pyo.Input(0) melody = pyo.Input(1) harpejji = pyo.Mix( [bass,melody], mul=volume ) harpejji.out() ``` --- <!-- .slide: data-background-image="pics/EP2025_page016.svg" --> <!-- .slide: data-background-size="100%" --> ## Sustainer ```python[|5|6|7-12] class Sustainer: def __init__(self, input, dur=0.5, mul=1): self.buffer = pyo.NewTable(dur) self.bufrec = pyo.TableRec(input, self.buffer) self.sustain = pyo.Looper( self.buffer, dur=dur, xfade=[47, 48, 49, 50], mul=mul ).stop() self.out = pyo.Sig(self.sustain, mul=0) def on(self): ... def off(self): ... def stop(self): ... ``` --- <!-- .slide: data-background-image="pics/EP2025_page017.svg" --> <!-- .slide: data-background-size="100%" --> ## Double-buffered pedal sustainer ```python[|5|7-8] class DBPSus: def __init__(self, input, midi_pedal, port=1, dur=0.5): self.vol = pyo.SigTo(midi_pedal, time=port) self.buffers = [Sustainer(input, dur, mul=self.vol) for _ in range(2)] self.control = [ pyo.TrigFunc(pyo.Thresh(midi_pedal, 0.1, dir=0), self.on), pyo.TrigFunc(pyo.Thresh(midi_pedal, 0.1, dir=1), self.off), ] self.current_buf = 0 def on(self): self.buffers[self.current_buf].on() def off(self): self.buffers[self.current_buf].off() self.current_buf = 1 - self.current_buf ``` --- <iframe width="1600" height="900" src="https://www.youtube-nocookie.com/embed/z-YTbi5aG-A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> Matthieu Amiguet, Improvisation "like an organ", harpejji (2024) --- <!-- .slide: data-background-image="pics/EP2025_page020.svg" --> <!-- .slide: data-background-size="100%" --> # User interaction ## Open Sound Control (OSC) --- <!-- .slide: data-background-image="pics/EP2025_page022.svg" --> <!-- .slide: data-background-size="100%" --> ## Auto-wah (OSC version) ```python[|1|7-8,16|20,23] ui = pyo.OscReceive(9901, address=['/wah', '/wah_add', '/wah_mul']) fol = pyo.SigTo( pyo.Follower( harpejji, freq=30, mul=ui["/wah_mul"], add=ui["/wah_add"]), time=0.01, ) wah = pyo.Biquad(harpejji, freq=fol, q=5, type=2, mul=2.5) selector = pyo.Selector( [dry, wah], voice=ui["/wah"] ).out() # Activate FX # ~$ oscsend localhost 9901 /wah i 1 # De-activate FX # ~$ oscsend localhost 9901 /wah i 0 ``` --- ## Open Stage Control  <!-- .element: class="r-stretch" --> <https://openstagecontrol.ammd.net/> --- <iframe width="1600" height="900" src="https://www.youtube-nocookie.com/embed/dcc5k-49X4U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> Pierre-Yves Diacon, dance // Matthieu Amiguet, harpejji (2022) --- <!-- .slide: data-background-image="pics/EP2025_page021.svg" --> <!-- .slide: data-background-size="100%" --> # User interaction ## The Button --- <!-- .slide: data-background-image="pics/TheButton.jpg" --> <!-- .slide: data-background-size="100%" --> <div style="background: #aaaa; border-radius: 5px; box-shadow: #333 2px 2px 5px"> ## The PiSound Button The PiSound only has one button, but you can assign scripts to - Button down, button up - Single, double, triple clic (up to 8) - Button hold for 1 second (or 3, 5, 7 seconds) </div> --- ## Activate auto-wah on single button clic ```python[|5|2|] #!/bin/sh # /usr/local/pisound/scripts/pisound-btn/wah_on.sh . /usr/local/pisound/scripts/common/common.sh oscsend localhost 9901 /wah i 1 flash_leds 1 ``` then use `pisound-config` to map the action. <div style="height: 2em;"></div> **Note** There are some useful default actions like `shutdown` when the button is hold for 5 seconds <div class="r-stretch"></div> see <https://blokas.io/pisound/docs/the-button/> --- <!-- .slide: data-background-image="pics/EP2025_page007.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> ## Goodie: DSP load and *xrun* feedback --- <!-- .slide: data-background-image="pics/EP2025_page018.svg" --> <!-- .slide: data-background-size="100%" --> --- ```python[|2-3|14|20-22|26-33] import time import jack from pythonosc import udp_client client = jack.Client(name="OSC-status") osc_sender = udp_client.SimpleUDPClient("127.0.0.1", 8080) xruns = 0 total_delay = 0 last_xrun = time.monotonic() @client.set_xrun_callback def xrun(delay): global xruns global total_delay global last_xrun xruns += 1 total_delay += delay last_xrun = time.monotonic() with client: while True: now = time.monotonic() secs_since_last = now - last_xrun osc_sender.send_message("/cpu_load", int(client.cpu_load())) osc_sender.send_message( "/xruns", f"xruns: {xruns} ({total_delay}) - {secs_since_last:.2f}s" ) time.sleep(1) ``` --- <!-- .slide: data-background-image="pics/EP2025_page007.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> # Conclusion --- ## Main Takeaways - Low-latency audio processing on an older rpi is perfectly possible - python / `pyo` - Pisound - Patchbox OS - Systemd - MIDI / OSC --- <!-- .slide: data-background-image="pics/EP2025_page024.svg" --> <!-- .slide: data-background-size="100%" --> Music first! <!-- .element: class="r-fit-text"--> --- <iframe width="1600" height="900" src="https://www.youtube-nocookie.com/embed/oTp0PLJyvEg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> Improvisation inspired by Middle-east music, Matthieu Amiguet, harpejji (2024) --- <!-- .slide: data-background-image="pics/EP2025_page007.svg" --> <!-- .slide: data-background-size="100%" --> <!-- .slide: class="center" --> # Questions? <div style="height: 2em;"></div> Full code, slides and context: <https://inclu.re/ep25> <div style="height: 2em;"></div> ### Credits - Harpejji & contrabass flute & rig photos by Nicolas Meyer - Dual USB-C connector is [xkcd #2493](https://xkcd.com/2493/) - Drawings by Matthieu Amiguet - *Run* by Olivier Bélanger - Other audio & video by [Les Chemins de Traverse](https://www.lescheminsdetraverse.net/)