Using Sonic Pi To Play Music With Ruby

  • December 14, 2020
  • Pete Whiting
  • 3 min read

by Kevin Murphy

Ruby Software Design Concert Series

  1. Dependency Injection: Plug In
  2. Shedding a Light on Duck Typing
  3. Synthesizing A Composition With Delegation
  4. Inheritance: Derivative Songwriting
  5. Using Sonic Pi To Play Music With Ruby
  6. 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.

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 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 @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.

Learn more about how The Gnar builds Ruby on Rails applications.

Interested in building with us?