Anyone Can Play Guitar (With Ruby) - The Proposal
I’ve got the blues. I’ve been looking for the perfect guitar tone, but haven’t found it. To amp up my mood, let’s teach a computer to play the guitar through an amplifier.
Let’s string together object-oriented principles to orchestrate a blues shuffle. We’ll model our domain with the help of inheritance, composition, and dependency injection. This talk will strike a chord with you, whether you’ve strummed a guitar before or not.
To build a guitar and amplifier, this talk will introduce software design concepts. As we unwrap different requirements and intricacies, we’ll explore new concepts.
We’ll begin by modeling some objects in our domain. This will focus on how we can identify objects in our system. [7 - 10 minutes]
We’ll find different objects in our system the following ways:
- Some objects mirror the real-world counterpart that we’re implementing. We can decompose the overall object into its component parts. We could have a
Guitarclass that does everything you need a guitar to do. In our exploration, we’ll model our guitar as having a set of
Strings (not the data type) that work together.
- Some objects become clear after identifying a need to pass around related data and behavior. Rather than using a hash with the individual notes played, we’ll make a
Soundclass. That will be able to tell you if the notes combine to form a particular chord. This new behavior can live with the raw note data in this new class.
- A bundle of private methods only related to part of the entire class can become a separate object. It takes some work for a
Stringto determine which note it’s playing. We need that information to construct our
Sound. It’s also a bit misplaced within the full context of the
String’s responsibilities. By introducing a
Fretclass, we can relieve the
Stringclass from that work. We can rigorously test that part of the code in isolation. We also create a dedicated location for related behavior to live.
Our electric guitar is only one part of our system. We need to hear the sound with the help of an amplifier. When building our amplification needs, we’ll explore inheritance. [7 - 10 minutes]
- We’ll start with an
Amplifierclass that will make a noise when you call the
projectmethod to it, passing it a
Sound. Of course, it only makes a noise if the amp is on. The
projectmethod is the last step to
amplifyis the method that’s called externally.
- There are different types of amplifiers. The different components affect the way they sound. We’ll build amplifiers that inherit from the core
Amplifierfunctionality. They’ll extend that base to exhibit their differences.
TubeAmplifieruses electrical circuitry like a light bulb. They need to warm up to sound their best. They need to account for how warmed up they are as part of the
SolidStateAmplifieruses more modern transistors and semiconductors. Because of its components, applying too much distortion results in harsh audio clipping. You wouldn’t hear that from a tube amp.
Our amplifier design must incorporate composition to build the next type of amplifier. [5 minutes]
HybridAmplifierhas a tube pre amp, and a solid-state power amp. It needs elements of both the
SolidStateAmplifier. We can’t inherit from both in Ruby. Instead, we’ll compose
SolidStateAmplificationmodules together to produce a single
- The value in composing with modules is in sharing behavior generally. It’s not specific to addressing the lack of multiple inheritance in the language. We’ll apply this concept back to guitars. An acoustic guitar has a sound hole that will
projectthe sound. An electric guitar cannot do that without the help of an amplifier. An acoustic guitar is not an amplifier though. Instead, we’ll have a
SoundProjectionmodule. Both an acoustic guitar and an amplifier can use this module. The amplifier will use it as the basis of its amplification of the sound. The acoustic guitar will use it only to project its sounds.
We’ll combine our guitar and amplifier with the use of dependency injection. [3 minutes]
- Initially, our guitar will create an instance of an
Amplifieron object construction. It’ll use that amplifier so we can hear the sounds we’re playing.
putthe note that it plays to the screen. We don’t want everyone to hear that noise when we practice. We’ll build a separate
PracticeAmplifierthat outputs nothing. Now when we practice (in our tests), we don’t hear (or see) the result of our practice.
- To use our
PracticeAmplifier, we need to add mocks in our tests. When our guitar makes a new
Amplifier, it’ll be a
- We can make this easier by instead passing an instance of the amplifier we want to use to the guitar instance. Now our tests can use the practice amp without mocking.
- This also improves the usage of our guitar generally. Now we can plug our guitar into the different amplifiers we’ve created. We’re not stuck with the base model. Though it started as a testing benefit, this change helps our application as well.
At the end, we’ll use the code we’ve written in our presentation, and some we haven’t, for a performance. We’ll show how we can play a blues riff, either through Sonic Pi, sheet music, or guitar tablature.
Notes on time estimates 🔗
The modeling and inheritance section estimates are longer to introduce the business domain. Composition will use terminology that’s already introduced. We’ll reuse and change code we’ve already written. Dependency injection ties together the two subsystems we’re already familiar with.
This talk will serve a wide audience, though in different ways. For early career developers, this can introduce these concepts. It’ll apply them in a (hopefully) fun way, and should demystify some of the fancy terminology.
For intermediate developers, the domain modeling section will have the most value. Identifying and extracting objects out of an existing system may be new to them. They may be familiar with seeing tutorials or writing their own work from scratch. They likely have had fewer opportunities to work with existing code in need of a change.
Even if you know everything about object-oriented design, this talk may be for you. Experienced developers will leave with another ready-made analogy for these concepts. They can use this example to explain the topics to others with a more novel problem space that the classics. There will be no discussion of animal and species taxonomies here.
I’ve had the pleasure to speak at RubyConf and RailsConf in the past. I’ve used (different) musical equipment to speak about (different) technical concepts effectively before.
Kevin Murphy lives near Boston, Massachusetts, where he is a Software Developer at BookBub. Kevin owns two guitars, but feels it’s an insult to the instrument to say he knows how to play the guitar.