My year teaching Sonic Pi - Week 22

As we moved into the last marking period and had finished the in_thread project, I felt like we were ready to move onto live_loops. The students now had a decent working knowledge of having multiple pieces of code running concurrently and how to get the rhythms in each block to align with one another. So live_loops felt like the next logical step. I had decided to forego introducing the basic loop function within an in_thread block which is discussed in the help tutorials. I feel that the live loop is much more powerful and versatile. I also don’t like that the loop function will overlap if you run it twice without stopping the first time. This is a mistake I know many students would experience that I would rather just avoid all together. Finally, the live loop is the main part of Sonic Pi needed to live code and I wanted to allow students to get somewhat familiar with that before the year comes to an end.

My plan was to first just present the live loop as an extension to what we have been doing with in_thread blocks. The difference being that an in_thread block only plays one time while a loop will play an infinite amount of times until you decide to stop it. However, I also wanted them to understand what makes a live loop special. So I started by presenting the regular loop function and explaining the basic concept of a loop which is to play continuously without stopping. I wrote out a basic loop to play a single note. I stuck with making a variable to use for my sleep value since we had been doing that in the previous project and the more I do it the more intuitive I find it versus hard coding subdivisions as decimals (0.5, 0.25, 0.125 etc).

The code looked like this

t = 1.0

loop do
  play 60
  sleep t
end

“Now let’s say I want to change the sleep value to t/2”. I changed the sleep value and ran the code again, intentionally not stopping the code first. I asked what they heard. Most of them could tell something was off but none of them were able to really articulate what had happened. (In hindsight, it probably would have been better to change the note rather than the sleep value since that would be easier to hear).I explained that we now have two loops playing together at the same time, one sleeping for t (1) and one sleeping for t/2 (0.5). Since I didn’t stop the code before starting the second loop, the first loop continued playing when the second loop started. Now we could easily just stop the first loop before starting the changed version. But using a live loop in Sonic Pi makes it so we don’t have to.

I changed the loop into a live_loop. I explained that each loop needs a name which could be anything. I started by playing the initial code I had written before. I showed them that I was changing the sleep value again to t/2 and will rerun the code without stopping it. When I did that, I pointed out that the code changed, ut we do not get any overlap or multiple loops running together. The live_loop will make the adjustment in the code and move onto the new code. I continued to demonstrate this by change the notes, adding other play and sleep commands, in essence, doing a quick live coding demo. When I stopped, I brought to their attention that this feature is what can elevate Sonic Pi from just a compositional tool where we write everything first and then press play to run it all, to actually performing with the code and making changes on the fly, just like playing an instrument. But this isn’t really what we will be using the live loop for in class at this point.

What we will be doing with the live_loop is going to act very similar to the in_thread which allowed us to have multiple parts playing at the same time. This difference is we were writing in_thread blocks that only would happen one time. These loops will play over and over and over again until we stop the program. This way we can have a continuous piece of music play rather than it only lasting a certain number of beats.

I erased the code I had and started with a new live loop which I named :kick and added a :bd_haus sample that slept for t/2. I changed the sleep value and reran the code just to illustrate that the live loop would change in the moment. I changed it to t/8 next just to drive the point home and then returned to t/2.

t = 1.0

live_loop :kick do
  sample :bd_haus
  sleep t/2
  end

I then started to type another live loop which I called hats. I added sample :drum_cymbal_closed and slept for t/4. When I ran the code it was clear that they did not line up correctly. I stopped the code and pointed that out. I explained that there were are ways we could make them sync up but for now, I just wanted them to be focused on using the live loop, so I didn’t want to add more to think about. If you don’t follow this in_thread forum closely, the sync command and how to use it has been the topic of a few different conversations (link1 , link2) and can be a bit confusing to get at first. So I wasn’t going to get into it right off the bat. I just told the students to stop the code and run it again when adding something new to the mix. I did just that and the kick and hi hat lined up correctly.

live_loop :hats do
  sample :drum_cymbal_closed
  sleep t/4
end

The next live loop I called :melody. I intended to make this a synth sound instead of a sample. I said I could easily do a ring of notes that will play over and over again in the loop, but I wanted to do something using a random pattern. So I made a scale and used the .choose option as well as sleep for t/2. I played the code and told the students that everytime through the loop we are getting a different random note from the scale I chose.

live_loop :melody do
    play scale(:e3, :minor_pentatonic).choose
    sleep t/2
end

But what if I want the same random pattern to repeat?

FIrst thing I would need to do is decide how many notes I want the pattern to be. To do this, I would need a repetition block and the length of the pattern will be the number of times it repeats. So I chose 16 as a nice even number. I added a 16.times do/end to my code and ran it again. I didn’t bother asking if they heard what was happening because I know some of them might think they hear a pattern where there isn’t one. I just explained that now the loop is choosing a series of 16 random notes, followed by another series of 16 random notes and continuing to do that. So even though I have added more to the code, nothing has changed in terms of what we are hearing. I asked what should I add to chose the same series of 16 random notes over and over again. At this point in the year, there are a handful of students who seem to have a strong grasp of what we have been doing and are the ones to usually raise their hands when these types of questions are presented. While I want to get more of the class involved and make sure everyone is following along what we are doing, I also want to validate these students who are more engaged as they are the ones who are getting the most out of it and hopefully going on to explore Sonic Pi outside of the classroom. So when calling on one of these students they correctly respond that we need a use_random_seed. I added use_random_seed with a random number before the 16.times block in my code and ran it again.

live_loop :melody do
  use_random_seed 234
  16.times do
    play scale(:e3, :minor_pentatonic).choose
    sleep t/2
  end
end

This time we are able to hear a pattern emerge that plays over and over again. I explain that if I don’t like the pattern, I can just change the the number of my random seed and I’ll get a different 16 note pattern. I can also change the number of notes in the pattern by changing the .times number. I can also change the sleep value to make the pattern go faster or slower. I did all of these things without stopping the code highlight that feature of the live loop and to keep things moving along.

The last thing I wanted to add was having a longer sample play. I made another live loop called :moon. I named it this because I was planning on using the :ambi_lunar_land sample which I know is a longer sample. I let the class know this was my reasoning as well. I first had the sample play and then sleep for t. As it played, it was clear that the sample was overlapping. I asked what I need to do to have the sample play all the way through before starting again. Another one of the more savvy students brought up sample_duration. I said this was correct and that by putting sample duration along with the name of that sample, it will sleep for the exact amount of time that this specific sample lasts for. Since it is a loop, it will then start the sample over again. I added sample_duration to the code and ran it.

live_loop :moon do
  sample :ambi_lunar_land
  sleep sample_duration :ambi_lunar_land
end

It was clear to hear there was no longer any overlap, although I pointed this out to the students anyway. But I also pointed out that the sample always started at a different time in relation to the other loops that were playing. There was no real connection or alignment to anything else that was happening in my code. It was just whenever the sample has finished, it plays again. But what if I want this sample to only start at the beginning of my melody loop? By this I mean that whenever the 16 note pattern gets back to the beginning, I want this sample to start at the same time.

To do this, I explained, I need a new command called sync. I reminded them that when we were doing in_threads and I was talking about making all the rhythms align, I purposely avoided using the word “sync” to describe that effect. This was because I didn’t want to confuse them by saying we were “syncing” the rhythms up because sync is a command that has somewhat of a different meaning. In this case, sync is going to tell this loop that it can only start when another loop tells it to.

For this example, all I need to do is add a command in my :moon loop that tells it to sync with the :melody loop. I asked if anyone had a guess as to what that command might be called, to sync one loop with another. I put a lot of emphasis on the word “sync” when I said it and this time I held out until a few students who normally don’t answer questions put their hands up. “Is it ‘sync’?” “Yes, it is really that simple”. I added sync, but reminded them that I need to specify which loop I want it to sync with. In this case, it is the :melody loop, so I should add the name of that loop. I try to ask these types of question when I can to get the students aware that sometimes the most obvious answer is the right one. I feel that when facing these types of questions, students will automatically overlook the simplest solution because they don’t believe an answer could be so simple, at least at this point in their school careers. We are always trying to get them to think critically and solve multi-step problems with different procedures. It is important to remind them that sometimes the obvious answer is the right one. I think this is empowering and can give them some confidence, especially after other questions where they might have felt completely lost as to what the answer might have been.

The code looked like this:

live_loop :moon do
  sync :melody
  sample :ambi_lunar_land
  sleep sample_duration :ambi_lunar_land
end

Upon running the code now, after the melody loop completed, the sample played and then I was able to gesture when the sample would come around and play again. This gives our music more predictability in terms of when these samples are now playing, so the parts feel more connected. This is not to say that there is anything wrong with the code being unpredictable, but it is better to understand how to do it and then consciously decide not to do it rather then it just happening that way and not understanding why. I was happy with this as an introduction to the concept of sync, especially given the multiple ways that sync can be used and the counterintuitiveness that can go along with it.

This was the end of my introduction to using live loops. My finished code looked like this:


t = 1.0

live_loop :kick do
  sample :bd_haus
  sleep t/2
end

live_loop :hat do
  sample :drum_cymbal_closed
  sleep t/4
end

live_loop :melody do
  use_random_seed 234
  16.times do
    play scale(:e3, :minor_pentatonic).choose
    sleep t/2
  end
end

live_loop :moon do
  sync :melody
  sample :ambi_lunar_land
  sleep sample_duration :ambi_lunar_land
end

Since this was a new concept, I had the students submit their code on Google Classroom so I could check on their progress and see how well they understood the concept and get a sense of common misconceptions or errors that were coming up in their code. I also provided a reference sheet on making live loops which covered everything I had presented in class with the exception of the sync command.

2 Likes