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.
Parts List
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.
Wiring Schematic
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 constructor.
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 @speaker
.
class Amplifier
def play(sound)
@speaker.run(sound)
end
end
Rock On
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.
This post originally published on The Gnar Company blog.
Learn more about how The Gnar builds Ruby on Rails applications.