Dependency Injection

Summarized using AI

Anyone Can Play Guitar (With Ruby)

Kevin Murphy • February 28, 2023 • Providence, RI

In the talk "Anyone Can Play Guitar (With Ruby)" presented by Kevin Murphy at RubyConf 2022, the speaker explores how to programmatically teach a computer to play the guitar while exemplifying object-oriented programming principles like inheritance, composition, and dependency injection.

Key Points Discussed:

  • Introduction to Guitar Playing: Kevin begins by referencing Stevie Ray Vaughan, a legendary guitarist, to emphasize the complexities of guitar playing compared to programming.

  • Concept of Guitar Strings: He introduces the idea of a guitar string in programming, discussing the importance of thickness (gauges) and tuning, and highlighting how terminology matters in encoding the concept into the system.

  • Class Structure:

    • The guitar class is designed to minimize the complexity of tuning by using a separate tuner class for handling tuning functionalities.
    • This separation allows the guitar class to focus more on playing rather than tuning, promoting cleaner code and better object-oriented design practices.
  • Playing Mechanics: The mechanics of playing the guitar involve individual strings and frets, with the guitar class responsible for sending the correct commands to the string class, which manages note generation.

  • Octave Recognition: To improve the system, Kevin emphasizes the need to distinguish notes by octave. He addresses this by introducing a note class that encapsulates both note value and octave, enhancing code readability and usability.

  • Amplifier Basics:

    • Kevin transitions to discussing guitar amplifiers, describing the differences between tube amps, solid-state amps, and hybrid amps, explaining how they function and their appeal to guitarists.
    • He ties amplifier functionalities back to the previously created amplifier class, illustrating how to use inheritance to ensure all amps share a basic functionality while allowing for specialized behavior.
  • Module Composition: To manage the limitations of Ruby’s single inheritance, Kevin utilizes module composition for hybrid amps, enabling them to inherit functionalities from both tube and solid-state amps effectively.

  • Simplified Interfaces: The presentation concludes with refining the user interface for musicians, allowing guitarists to play without needing to manage their amplifiers directly. By implementing dependency injection, the guitar class can seamlessly integrate with various amplifier types at runtime.

Conclusions and Takeaways:

  • Object-oriented programming principles nourish robust software design, ensuring the separation of concerns, improving code readability, and enhancing flexibility.
  • The session underscores the importance of using appropriate structures and patterns in programming to simplify complexities—whether in music or software development—allowing focus on creativity and functionality.

Anyone Can Play Guitar (With Ruby)
Kevin Murphy • February 28, 2023 • Providence, RI

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.

RubyConf 2022 Mini

00:00:13.940 I'm going to get started with a question. Does anyone know who this is? Show of hands? One person? Dude, yeah. So this is Stevie Ray Vaughan. He's one of my favorite guitarists and he's also the reason that I have a Song for Stratocaster, though his is a little more beat up than mine.
00:00:21.600 And if I’m being honest, that’s where the similarities between Stevie and me kind of end. You see, he's good at playing the guitar and I can’t keep up with his fashion sense. One time, Stevie said, "I use heavy strings, tune low, play hard and floor it." That’s technical talk.
00:00:48.600 Is that something else? Steve and I actually have in common: we both enjoy some tongue-in-cheek technical talk. Thank you for coming to mine. My name is Kevin Murphy, and I work at a company called Pubmark. We make tools to help you find ebook deals, and we also have an audiobook marketplace.
00:01:05.220 Today, we're going to work on teaching our computer how to play the guitar, just like Stevie does, because I can’t play the guitar like Stevie does. So let’s get started.
00:01:24.900 Stevie uses heavy strings, so in order for us to do that, I’m going to propose something that’s rarely done in the world of programming. Let’s have a thing called a string. This will work, but just to eliminate any potential confusion, we’ll call our thing a guitar string. And we know our guitar string needs to know if it’s heavy, but what does that mean?
00:01:53.400 A regular guitar will have six strings on it, and each of those strings will be of different thicknesses. Those thicknesses are measured in thousandths of an inch. If you're going to get a guitar from the factory, it likely will be strung with a set of nines, meaning that the thinnest string is nine one thousandths of an inch. Stevie played nines, but he played 13s. These strings are harder to manipulate, harder to press down on, and harder to bend. It’s more work, and he called them heavy. So to know if we’re using heavy strings, we’re going to check the gauge of the string that we’re using and compare it to that common gauge. If it’s higher, it’s heavier.
00:02:32.700 Now, you and I may have called those strings thick or thin, but Stevie didn’t. He called them heavy, and so we’re going to use that terminology and encode that into our system so that we’re speaking the same language. Each of the notes, or rather, each of the strings on our guitar are also tuned to different notes. There’s a standard tuning you might expect if you’re using a guitar. Stevie, once again, did not use standard tuning; he tuned down a half step, meaning that each of these notes was just a little lower than you would regularly expect from a guitar.
00:03:01.500 We know we need to be able to tune our guitar, and we know we need to support multiple tunings, most commonly standard tuning. So we’re going to switch on the type of tuning we get. If we need to tune to standard tuning, we’ll tune all the strings to standard, and if we need to tune low, we’ll tune everything down a half step.
00:03:25.920 That’s not the only set of tunings you can tune your guitar to; there are a lot of them. This is just all that would fit on a slide. And as much as this method is becoming a bit unwieldy, overall, our entire guitar class is starting to become inundated with a lot of information about tuning such that you might reasonably conclude, if you’re just scanning through this class, that a guitar is primarily used in order to tune itself.
00:03:49.260 This is something I can do, but it can also do a lot more exciting things, and this is just taking up a lot of real estate in our mind when we explore what a guitar does. So, we still want to be able to tune our guitar, but let’s instead use a separate tuner class to handle that for us.
00:04:08.640 That tuner class will take in a guitar as a constructor argument, and we’ll handle the details about how to tune our guitar. We have the same functionality in our guitar class, but the details about how to tune it are now isolated in that tuner class. If we need to know how the tuning actually works, we can look there while still having the same API on our guitar class.
00:04:34.560 What got us here is being able to extract all of this tuning behavior into a separate named class that lets us isolate it all in one place.
00:04:58.680 So how do we actually play the guitar? Well, here is going to be a quick and mostly correct explanation. Let’s focus on one string first because we called it a string. Let’s tune it to the note of A. What this means is that if you were to pluck this string—if you were to play it without any modifications—it would play the note of A.
00:05:21.240 But that doesn’t mean it’s the only note that you can play on that string. A guitar has a neck that’s broken up into different sections called frets, and when you place one of your fingers down on one of those frets, it compresses the string, makes it a little shorter, and plays a higher note. This just happens to be the next note available to you in the world of music, on and on up the neck for all of the notes.
00:05:59.040 Now we don’t say we play the guitar string; we say we play the guitar. Our main interface will be on the guitar class, and we'll be able to use our guitar pick to play a string at a particular fret. To do this, what we need to do is call the right string's pluck method. That’s all there is to playing the guitar.
00:06:07.560 And it’s really lightweight because we have delegated all the responsibility onto the string class to handle the details about turning that input into a note. The guitar is responsible for knowing where to send the right message to, and then the string class handles the rest for us.
00:06:46.440 Let’s put some of this together. Let’s grab a guitar, put some heavy strings on it, tune it low, and pick some music to play. Stevie was known for playing the blues, so we’re going to play some blues. What we get are all of the notes in our song. This is great! We have taught our computer how to play the guitar.
00:07:14.280 At this point, the presentation could be over, but I do have more to share. Let’s look at some of the inputs, some of the things we asked our guitar to do throughout this song.
00:07:23.400 Early on, we asked it to play the fifth string at the first fret. When tuned down a half step, that’s an A note. Later on in the song, we asked it to play the third string at the third fret, which is also an A note. These A notes aren't the same; they’re different octaves apart from each other. One sounds higher than the other, but with our current output, an A note is just an A note, and we make no distinction there.
00:07:37.620 We’re losing some critical information to understand what this actually sounds like, so let’s try again. What we want is to be able to play our song and see not only the note but also the octave. To do this, in our string class that’s handling our details for us, we’re going to return both the note and also the octave as an array of elements.
00:08:18.120 Now in our song, when we’re playing each of the notes, we’re going to output the first value and the last value: the first value being the note, and the last being the octave. I can understand that because we just looked at that method. However, if I was reading this in isolation, I would have no clue what any of this means.
00:08:42.060 So instead of returning this array, let’s create an object called a note class. This note class will respond to the value and octave messages. In our song, we’re no longer going to ask it to output the first and last value; we'll ask it to output the value and the octave.
00:09:01.900 We’ve achieved a nice readability win here. We could have also done this by using a hash, which uses different syntax, but we achieve the same readability. Now that we have this bundle of data all put together, we can also assign custom behavior to it.
00:09:32.640 Now we can ask the note class to be responsible for knowing how to output itself into the world so that our song doesn’t need to be concerned about it at all; it can just ask the note to do it for us.
00:09:51.900 Right now, we can focus on playing our music with our song, and everything looks just the way we would expect with our notes and octaves. We have all the information that we need.
00:10:00.559 What helped us here is taking what was a primitive data structure, an array of elements, and converting it into an object that gave us a rich API to understand what this data is. After getting that readability win, we also had an isolated location to attach behavior to it. That’s it! That’s how to play the guitar.
00:10:25.020 Right, any of us could come up on stage, bring our computers, and sound just like Stevie does, delighting audiences the world over. Except in a room of this size, it would be pretty hard for people to hear you without some amplification.
00:11:10.920 As you might have guessed from our prior quote, Stevie wasn’t much for talking about the details of gear and the technical implementation. So in light of that, I'm going to give a really brief introduction about how guitar amps work.
00:11:28.740 An amplifier is a tool that amplifies sound. It does that by progressing through two stages: there’s a preamp and a power amp. We have now just built a fully functional amplifier. That’s it! That’s all there is. Now, there are a lot of different types of amplifiers. We’re going to talk about a few of them here.
00:12:06.360 The first is called a tube amp. A tube amp gets its name because of the electrical components it’s built out of; it uses vacuum tubes or valves, depending on the part of the world you’re from. Guitarists like these vacuum tubes because of the way it makes their guitar sound. They say it provides a particular warmth that they really enjoy, and when you really crank it, it gets super warm, and they just love that.
00:12:44.160 Now these tube amps are quite heavy, so it's really annoying to lug them on and off stage. But it’s worth it because of how it makes your guitar sound. With all of this functionality, one thing our tube amp can’t do yet is amplify sound, and that’s kind of important; it’s a core feature of what an amplifier does.
00:13:12.540 We already have a fully functioning amplifier, and this tube amp should act the same way as that amplifier does. So we’re going to inherit from the amplifier class, which will give us access to all of that behavior so that with this change, now our tube amp instances all know how to amplify sound.
00:13:48.900 We also have solid state amps. Solid state amps work a little differently, and they also have a particular tone that they provide. They put clarity onto your sound—they’re clean and clear as glass. That clarity persists whether it’s just turned on or fully cranked.
00:14:07.980 These solid state amps are built with a more modern electrical component: a new thing called a transistor. That means they’re light, so they’re easier to carry around, which is beneficial, and they come in different form factors.
00:14:24.000 Our solid state amp also needs to operate like any other kind of amplifier, so we’re also going to inherit from our amplifier class. Now, our solid state amp also knows how to amplify sound.
00:14:46.740 For both our tube amp and our solid state amp, we used inheritance to have a consistent API instead of behaviors that we respond to, while also applying specialization on top of that. Our regular amplifier class is fully functional and doesn’t change the input sound at all, while our tube amp applies its stereotypical warmth, and our solid state amp adds clarity.
00:15:14.760 There’s one other kind of amplifier we’re going to talk about today. It’s called a hybrid amp. It’s a best of both worlds solution, pulling in bits and pieces from a tube amp and parts from a solid state amp.
00:15:39.480 If we try to run this, we’ll get a syntax error. The reason for that is because Ruby doesn’t support multiple inheritance. You can't inherit from more than one thing directly.
00:16:01.680 So we need to consider a different way that we're going to get access to all this behavior on our hybrid amp. To do that, let’s think back to what our amplifier is and how it works: it’s built up of two different components, a preamp and a power amp. So let's separate those and build them individually.
00:16:26.880 The hybrid amp uses the same preamp as a tube amp, so we’re going to take all of the behavior related to a tube preamp and put it in a module to consolidate all that behavior in one place, notably the tone.
00:16:44.640 Now for our hybrid amp to access this behavior, we’re going to include that module. This will allow instances of our hybrid amp to have the tube amp warmth without having to write it themselves. To have a functional amplifier, we also need a power amp, and a hybrid amp uses the same power amp as a solid state amp.
00:17:14.820 This is lucky for us because those are lightweight. Now to access this behavior, we can also include that solid state power amp because we can include as many modules as we’d like in Ruby, unlike the number of classes we can inherit from.
00:17:36.780 Now, our hybrid amp also has a lightweight power amp. Here we used module composition to share behavior through our classes, right? Our tube amp is now also going to include the tube preamp, and the preamp tone method is going to be deleted from our tube amp entirely, giving us a consistent place for all this behavior.
00:17:55.080 Now, our hybrid amp also needs to behave like any old amplifier, so we are going to use inheritance here, inheriting from the amplifier class. Now our hybrid amp can actually amplify sound.
00:18:12.720 So we’ve built a couple of different amps. Now let’s put them together. Let’s grab an amp, turn it on, pick up our guitar, and play our blues.
00:18:35.220 For each note that comes out of our guitar, we’re going to send it through the amplifier. By doing this, we see the note and the octave that we saw when we were previously playing the song, alongside the tone that the amplifier adds to it and the volume that the amp is set to. We see this for all the notes we’re playing in our song.
00:19:05.640 This is great! Our guitar and amplifier are working together, but it doesn't match our mental model of what it’s like to play the guitar. You see, playing the guitar is hard enough; it’s even harder if you’re responsible for plucking sounds out of the air and pushing them through an amplifier.
00:19:31.380 You don’t need to interface with the amp at all when you’re playing the guitar; the guitar does that for you. It has pickups that take the electrical signal and send it through its cable into the amplifier, so you don’t need to worry about it at all. What we want is an API where we can focus on our guitar and playing the song without having to deal with our amplifier.
00:20:05.760 Now to do this, our guitar still needs to know about our amplifiers. When we create a guitar, we’ll also set up an amplifier, and now for each note that we play, we will send that resulting note to the amplifier. By making this change, we can play our guitar with the API we want and still see it amplified through the amplifier.
00:20:21.180 So we built a whole bunch of amps. It would be a shame not to use them all. If we want to try a different amp, we need to change our guitar class from using a solid state amp to instead use a cranked tube amp.
00:20:36.840 When we do that, we can play our song and get the warm tone that we would expect. But we’re going to be switching amps quite a bit, and we don’t want to have to make a change to our guitar class every time.
00:20:54.480 So instead, let’s accept it as an argument in our constructor, allowing us to use any amplifier we want when we create it. This is a term called dependency injection, which allows us to create a guitar that will work with a solid state amp or a guitar that will work with a tube amp without having to change our guitar class at all.
00:21:11.880 This dependency injection gives us flexibility to let us decide, later on, closer to runtime, how we’re actually going to define the way these things work together, but we know we have a common interface to do it.
00:21:35.520 Let’s flip through some of the things we’ve been discussing here. We’ve talked about ways to share and compose behavior in our systems. We’ve talked about a number of ways to do that. Right? We used inheritance to build our amplifiers, and we also used module composition.
00:21:57.960 We also composed behavior when we built our guitar. Our guitar knows how to tune itself, but it only knows how to do that through the tuner method. Our guitar knows how to work with an amplifier, but it needs to be told what the amplifier is at the time we’re constructing our guitar.
00:22:10.920 The good news is that we’ve shown throughout this presentation that all of these will work. They just have different trade-offs and constraints you need to apply. What it comes down to is knowing how you plan on assigning those constraints in your system.
00:22:19.260 For us, we’re building a tool that allows guitarists to play the guitar. So we want to be able to use amplifiers, but we don’t want to have to know the internals and details of our amplifiers. We want them to work relatively consistently, amplifying sound without us having to worry about that.
00:22:48.600 So, we used inheritance here to define a common interface through which they’ll all work together. We didn’t need the flexibility of knowing the internals and guts of the amplifier. That’s because we’re building a tool for guitarists. If we were instead building this for guitar techs or a repair shop, they might literally need to take preamps out of one amplifier and put them into another.
00:23:13.620 We don’t need that flexibility here, so we’re not going to build it—because if we build it, we need to maintain it. We do need that flexibility for our interface with our guitar and our amplifier, though. We want to support using a lot of different amplifiers for our guitars.
00:23:33.030 So, we provide that flexibility in our interface. We also have a lot of functionality that comes from our guitar, but we don’t need to be bogged down with all the details inside the guitar class, because we’ve extracted a number of collaborators that handle those details for us.
00:24:00.060 This gives us that class with an API without needing to know the inner workings unless we’re interested there. By making those choices and choosing those constraints, we’re able to focus on what we want to do, which is play music. And this is wonderful.
00:24:23.640 If you can read this and translate it to music in your head— which you probably can’t—that’s not a knock on your musical ability; it’s a result of the fact that I made this notation up for this presentation.
00:24:46.440 As far as I know, there's no standard emoji to guitar amplifier conversion, and if there is, I didn’t use it. And even if you might be able to see a D note of the third octave, you might not be able to translate that into what it actually sounds like. You might need to actually hear this to know if the music playing is actually any good.
00:25:11.760 Luckily, we’ve built our system so that it can interface with many different amplifiers to provide different outputs that allow us to define that output.
00:25:41.520 So, the next time you’re composing behaviors in your system, I hope you think about these examples in this presentation. If you’d like to listen to this song in your spare time—because who wouldn’t—you’re welcome to do so at kevinjmerphy.com/playguitar. While you’re there, you can also get a copy of these slides, access the code examples, and read some blog posts on related topics. I’d be happy to answer any questions individually. I’ll hang out over there on the side of the stage; feel free to come over and say hi.
00:26:42.000 I also brought my guitar, so if you think that I’ve prepared you to play just like Stevie did, you’re welcome to try. Just please be respectful to others around you and also to the instrument itself. I quite like it.
00:27:16.200 I want to thank the organizers: Gemma, Emily, and Andy, for putting this all together and for bringing us together in this room. I want to thank all of you for being here, and I hope you have a great rest of your conference. Thank you all very much.
Explore all talks recorded at RubyConf 2022 Mini
+29