This is the third post in a series about designing and creating SoundFloored, an open source soundboard pedal! Check out the other posts in the series:
- Part 1 – Planning
- Part 2 – Software Implementation
- Part 3 – Breadboard Implementation (you’re already here!)
- Part 4 – Final Implementation
So this project has really been gaining some steam! I’ve figured out the design and managed to get a software implementation with two separate interfaces. Now though, I’m entering uncharted territory; the world of hardware electronics.
Now I say uncharted territory, but I’d be remiss if I didn’t mention that I’ve previously watched a Pluralsight course called something like “Introduction to Electronics” or “Electronics Fundamentals”, although it’s also worth mentioning that I remember approximately 5% of the content. Actually, one of the few things I did remember was how a breadboard works, which is a place to start at least!
So although I’ve owned a number of Raspberry Pis over the years and have a lot of HATs/pHATs to go with them, I’ve never ventured far enough into the hardware electronics side of things to have bought any components. As such, I ordered a pretty generic looking “Electronics Fun Kit” that had pretty much everything I might need for this project; a breadboard, wires (especially wires that I could use to connect the Pi directly to the breadboard), buttons, LEDs, resistors etc. I’m sure there are higher quality components out there, but since this is all for prototyping I decided that in this case cheap and cheerful was what I was looking for.
I also re-imaged the memory card in my Pi since the last time I used it was to play around with RetroPi. On that front, the new official Raspberry Pi Imager is fantastic and so easy to use; just select the image and SD card you want to use and everything is downloaded and written for you! I definitely recommend it as my preferred way of imaging Raspberry Pi SD Cards now.
The First Button
So the logical place to begin is to get a single button working to run code on the Pi. That’s all well and good, but figuring out where to start can be quite difficult when you’re starting from pretty much zero. Thankfully the internet is a pretty cool place and I found a very helpful tutorial on using a push button with a Raspberry Pi. Now this is also the point that I did what I usually do and managed to dive way too far into the topic, to the point that I started getting really invested in the apparent debate over whether connecting a button from GPIO to 3.3v or GPIO to ground is the best approach (#TeamGround).
Now the smart move would have been to just follow the tutorial from start to finish, but being the *ahem* “consummate professional” that I am I ended up following parts of the tutorial while also mashing in other bits of information from all over the place. Definitely not the approach I’d recommend, but it seemed to work for me since I was now running code with each button press!
Now to actually get it to play audio clips! Thanks to the interface approach I took with the original implementation, adding the button into SoundFloored was refreshingly straightforward; all I needed was a new
RpiInterface and an implementation of the
start method that configured the button and then sat in an infinite loop so that the configured callback had a chance to fire (a very similar approach to the
KeyboardInterface I implemented originally).
Slow audio startup
As soon as I connected the button to SoundFloored, I ran into an issue that threatened the usability of SoundFloored: compared to the other interfaces I’d implemented so far, the audio was really delayed when pressing the buttons on the breadboard, enough that it was really hard to time the clips correctly.
After some research, I found a very helpful question on Stack Overflow that gave a few potential fixes. After trying a few of the answers, one approach in particular (seen below) seemed to solve it completely.
import pygame pygame.mixer.pre_init(22050, -16, 2, 1024) pygame.init() pygame.mixer.quit() pygame.mixer.init(22050, -16, 2, 1024)
I’m really curious to see why this worked, but more than anything I’m thankful that it did!
The Rest of the Buttons
So I’ve got one button working; time for the rest! Installing the other buttons on the breadboard followed in an identical fashion to the first. This is also when I learned the trick for using the outer rails on a breadboard for ground so that you can use a single ground pin for a bunch of buttons, which really simplified things!
Configuring the other buttons in code followed the same approach as the first one, although I used a technique from
KeyboardInterface of defining input pin numbers and automatically associating them in the provided order with the inputs; this way I could easily support as many input buttons as I wanted with only a minor configuration change.
audio_clip_button_pins = [19, 13, 6, 5] for index, audio_clip_button_pin in enumerate(audio_clip_button_pins): GPIO.setup(audio_clip_button_pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.add_event_detect(audio_clip_button_pin, GPIO.FALLING, callback=partial(callback_wrapper, index), bouncetime=200)
Initial Live Test
So it was at this point (with a Raspberry Pi delicately hooked up to a breadboard) that I actually first used SoundFloored with the band. I took it down to our weekly band practice and hooked it up to the mixing desk through an aux cable. Although I was fully anticipating the usual demo gremlins to come out and cause trouble, it actually worked perfectly! Since I was still using the tiny breadboard components and some of the clips we needed were right on the beat, I found myself diving at the mixing desk and trying to hit the correct buttons which was an exercise in accuracy I wasn’t expecting! Still, it did a great job of validating the approach and I had some very positive feedback from my bandmates, especially since this was the first practice since I had initially discussed the idea!
So adding the screen and getting it working happened a couple of weeks after getting the initial buttons hooked up, mainly because it took me quite a bit longer to figure out which screen I needed/could work with and get around to ordering it.
To test the screen in the first place I took out all of the wires on my breadboard and plugged the screen in. I then followed a very helpful tutorial called “16×2 LCD Module Control Using Python” that specified how to connect the pins and came with some sample code to test the screen once it was connected. As such, I very carefully connected the pins to my Pi and then ran the sample and what do you know, it worked first time!
Putting It All Together
I’ve got a screen working and I’ve separately got buttons working, so I still need to figure out how to combine it all. Before that though, there’s also the fact that while I had tested the screen, I hadn’t actually integrated it with SoundFloored yet. Surely all I’d have to do is modify the sample screen code and go from there?
Not quite. After some investigation I found that I’d be running headlong into some licensing problems; since the code sample I wanted to use was licensed with GPL v3 and I was using the MIT license, my options were to either modify the licensing on SoundFloored (to dual license MIT and GPL or to relicense the whole thing as GPL) or to find some other code to use. In the end I went with the latter option and after a bit of searching (and finding out that the screen I’m using is based on the popular HD44780 controller), I found an excellent library that did exactly what I needed. As well as that, it’s highly configurable, available as a package on PIP and MIT licensed!
After configuring the screen to use my current pin layout, I wrote some methods on
RpiInterface to better abstract the implementation and dropped a call to
draw_screen into the main loop. Although this worked, there was a visible flicker since the screen was updating pretty much as fast as it possibly could. To fix this, I changed my approach and implemented
after_state_change_functions, which is a list of function objects (thanks to the magic of First-class functions) that would be called any time a state change occurred (such as changing bank or repeat style). This would give me a hook that would allow me to only update the screen when there was something to change (rather than constantly) which solved the problem with flicker.
Unfortunately this is when I found that even when using this new approach the screen was updating at a slow pace where you could see the cursor move through each cell, all the while blocking any further input or processing. This didn’t make sense to me since in my initial tests screen updates were close to instant, but after some testing and trying to pin down the issue I realised that it only occurred when the screen was updated during the main loop. I don’t know why that slowed down the actual update of the screen, but at least this was something to work with.
Although I don’t often work asynchronously in Python, I can (usually!) recognise when it’s needed. In this case I decided that the
multiprocessing module should do the trick and hopefully resolve the slow screen update. After a few false starts (mostly to do with trying to
start the same
Process multiple times) the issue was resolved and the screen updated without issue!
I’ve now got a screen that updates quickly and doesn’t block input along with buttons that work with no appreciable delay to play audio and change banks; that sounds like a working breadboard implementation to me!
This is a great prototype, but it definitely needs more work to get it to the final stage. What are the steps to get there?
1. Start work on phase three (final implementation)
So now I’ve got all of the electronics and logic working, I need to figure out how I’m going to get it into a portable and usable form factor; that means doing some woodworking!
2. Keep working on the software implementation
Software is never finished and SoundFloored is no exception, so I’ll almost definitely need to keep making modifications as I think of new features or have to resolve bugs.
This is the third post in a series on the creation of SoundFloored; check out the fourth post, this time on the final implementation!