by Kevin Murphy
Ruby Software Design Concert Series
- Dependency Injection: Plug In
- Shedding a Light on Duck Typing
- Synthesizing A Composition With Delegation
- Inheritance: Derivative Songwriting
- Using Sonic Pi To Play Music With Ruby
- Stringing Code Together To Play Music
Setting the Stage
My RubyConf 2020 talk about Ruby's Coverage module uses examples about playing live music. As such, I had the ambitious goal of delivering a live performance of some music during the presentation. This ended up getting cut for a variety of reasons (time, concern about the audio working on the streaming platform, the reality of ambition turning into actual work to do), but I built out the structure to support this for one instrument, the guitar. This is the first of two posts that'll describe the work that I did to support this.
First, I had to figure out if it was possible to make this happen. I wanted to hook into my existing code samples and trigger musical notes from them somehow. As such, I decided to build my first amplifier, virtually, without fear of blowing up any capacitors.
In the earlier post on dependency injection, I created a
PracticeAmplifier class that did nothing so I could use it in tests, rather than the "regular" amplifier.
What the "regular" amplifier does is interface with Sonic Pi, which is awesome software that'll make sound and music driven by code. Sonic Pi comes with an IDE of sorts that you can use to program the composition you'd like to play, and get immediate feedback from hearing how your code is translated into audio. It's a great way to lose track of time for a night or two (or more). However, I was envisioning controlling my audio from the code examples directly. I didn't want to have to work within the IDE.
To get around using the IDE directly, I found the sonic-pi-cli gem. Its principal use case is to be used directly in the terminal. However, it's a gem, and written in ruby, and the core functionality is available in a class that you can use in any of your code.
With enough knowledge and conviction to be dangerous, I set out wiring up my amplifier. The CLI requires that Sonic Pi itself is running, and first ensures it can communicate with it - and to do so, it needs to know what port the software is running on. Sonic Pi used to always run on the same port; however, it has since changed to run on a dynamically-determined port.
The CLI already implemented the functionality to find the port to send to the
SonicPi class, so for demonstration purposes, I copied that in my initializer.
class Amplifier def initialize @port = find_port @speaker = SonicPi.new(@port) end private def find_port # Code from sonic-pi-cli end end
Needing to find the port is now something that the
SonicPi class can do by itself as of version v0.2.0; however, this work preceded that.
The rest of the functionality in the
Amplifier class is now to delegate commands to the
class Amplifier def play(sound) @speaker.run(sound) end end
Using this amplifier still requires knowing all the correct commands to send to Sonic Pi, and Sonic Pi must be running; however, we can now trigger it to execute these commands from outside of its IDE. We have a way to send sound out of our ruby code.
In our next post, we'll take a look at how we generate the sound to send from a guitar to an amplifier.
Learn more about how The Gnar builds Ruby on Rails applications.