My year teaching Sonic Pi - Week 13


#1

This week I was going to roll out an assignment for the students to complete using functions to create an ABACA form. We had gone over how to make a function last class and it seemed like everyone was getting the basic idea. As I was preparing the grading for the project, I was thinking about what to have for the criteria to get a 4. On a rubric with 4 levels, a 4 is generally the criteria for having gone beyond meeting the standard requirements. The last form project I had asked the students to just do an ABA form with an option to do an ABACA form which would earn them a 4 on the rubric. But that project was using conditional statements and so there was more knowledge that had to be exhibited in order to execute that concept correctly (using if else statements instead of if, assigning multiple variables, writing a third part which could be differentiated from the A and B section etc.).

For this project, there wasn’t much more knowledge exhibited by making three functions instead of two and calling them in ABACA form. So doing the form correctly only seemed to meet the criteria standard of making a function. I had considered teaching students how to add arguments to their functions when I first taught it, but I knew that would have taken more time to explain and I’ve been trying to be mindful about not taking too much time to explain concepts as well as trying to only explain one concept at a time to allow for deeper exploration of the concept during class. But for this project, adding arguments to a function seemed like a good way to increase the level of difficulty for students who wanted to explore a more advanced concept.

I was slightly torn between my coding mind and music teacher mind because I knew that by adding arguments to functions that were meant to execute musical code as sections in a form piece would mean that the A section might be altered enough each time it is played to the point where one might not considered each iteration of that section to be similar enough to be considered repeating the same section. But this was a time when I felt like the CS concept was more important than the musical one, firstly because they already had experience with the form concept, and second because the more I have been playing around with Sonic Pi on my own time, the more I feel like creating music with code opens up possibilities which abide by a different set of rules than more traditionally made music, so I should not always hold our coded music to the same standards and expectation.

Before rolling out the project in class, I did a quick review of what a function is and how it worked. I put the following function on the board and ran the code without calling the function. This was to remind students that by defining the function we are just making a set of instructions for Sonic PI to follow when it sees the name of the function. In order to play the code inside the function we have to “call” the function by writing its name. I called the function. Then to set up for what I was planning to demonstrate, I added a sleep 2 and then called the function again to have it play twice. I included the sleep to give a clear distinction from when it plays the first time and the second time.

define :a do
  use_synth :beep
  use_random_seed 98
  16.times do
    play scale(60, :major).choose
    sleep 0.25
  end
end

a
sleep 2
a

I then explained that functions can be very versatile and powerful because we can write our own functions as well as add something called arguments to our function. We have been using functions with arguments all along. Play is a function that takes one argument: what note to play, sleep is a function that takes one argument: how long to wait. Scale is a function that takes two arguments: the starting note and the scale to play. An argument is basically a variable within your function that you assign a value to every time you call your function. This allows you to change parts of the code in your function without having to rewrite it.

I showed them how to add an argument to the function by giving a name to the argument, in this case ‘x’ , between two goalposts. I told them that this is a variable I can include somewhere in my function. I am going to put it in the place of the play 60 and made it play x. Now when I call the function, I need to provide a value for that argument which I can include in parentheses after the name of the function. So for the first time I called the function, I gave it the original value of 60, but the second time I gave it the value of 90. So I pointed to the x in the function on the board and said “Now when I call the function the first time, the 60 will be the value of x, but the second time I call it, 90 will be the value of x. I ran the code. The first time the function sounded the same as it did the first time, but the second time it was clear to hear that everything was now higher pitched based on the value of 90 we used for the starting note.

define :a do  |x|
  use_synth :beep
  use_random_seed 98
  16.times do
    play scale(x, :major).choose
    sleep 0.25
  end
end

a(60)
sleep 2
a(90)

I then said “This x can be used for something different.” I changed the play back to 60 and this time put the x as the value for use_random_seed. I then changed the values for when I called the functions to 98 and 12345. I pointed out that the first time I play it, x will be 98 and the second time x will be 12345 causing the pattern to be different. I wanted to put a lot of visual emphasis on how the numbers that were next to the function being called represented the x that was in the defined function. Without that constant visual reference, I felt like some students might not make the connection between the those numbers and the x. I ran the code and also pointed to the functions as they were being played.

define :a do  |x|
  use_synth :beep
  use_random_seed x
  16.times do
    play scale(60, :major).choose
    sleep 0.25
  end
end

a(98)
sleep 2
a(12345)

At this point, I asked the class “Where else could I put x in this function?” Some said “sleep”. So I changed the sleep value to x and changed the argument values to 0.25 and 0.0625. I chose a faster value for the second just for the sake of time and because students always seem to enjoy making things go faster. In another class, before I had even asked where we could put ‘x’, someone asked if we could put it as the value for our repetition block. I said “Let’s find out” and put x.times in the function and gave the values of 16 and 8, again using a short amount for time and because I felt that it would be more pronounced for the second one to stop short. Going longer might be confusing and students might not pick up on the difference as clearly.

From there, I pointed out that x doesn’t even have to represent numbers. I put x in the place of the synth sound and added :beep and :chiplead to the arguments and ran the code. Next, I changed the scale to x and added :major and :bartok and ran the code. At this point I had gone through all the values in my function.

I then brought up the point that we can have more than one argument in our function. I added z to my defined arguments. I put the x back as the play value and made z the synth naem. I then changed the arguments to the called functions as 60, :beep and 40, :chiplead. I knew a higher value with chiplead would be a bit piercing for the students. They have a tendency to overreact to those types of sounds which then becomes a distraction from the point of why I am playing them the code in the first place. I ran the code and it was clear that both the function were working.

define :a do  |x, z|
  use_synth z
  use_random_seed 98
  16.times do
    play scale(x, :major).choose
    sleep 0.25
  end
end

a(60, :beep)
sleep 2
a(40, :chiplead)

At this point, I made it clear that since I had included two arguments when I defined my function, I HAVE to provide two arguments when calling my function. To demonstrate this, I removed the synth sound from the second called function and when it came, the code stopped and we got an error message indicating that the function was expecting two arguments but only got one. I also showed them what happens with not providing any arguments when calling a function with arguments. A similar error message appears. I like this example of an error message because it helps reinforce the term ‘argument’ by having Sonic PI give it as well. It also is a fairly easy error message to decipher if you understand what that term argument means.

49

That was all I needed to show for adding arguments to functions, so I moved on to the requirements for the project. Since we had already done a form project, I didn’t need to explain that concept in depth but I did make sure to review that form means the order of the parts of a song. In this project, we will be making a song with an ABACA form like we did with conditional statements but this time we will be using functions for each section, to which one student replied “That sounds a lot easier”. I made it clear that they would need three separate functions, one for each section. After defining all their functions they would need call the functions in the correct order.

I explained that the first part of the rubric would be on correctly defining functions and using the correctly to make an ABACA form. To achieve a 4 for the rubric, at least one of the functions would need to correctly incorporate an argument. The second part of the rubric was using arrays, scales and rings. Each function must include one of those in it. To achieve a 4, at least one function needed to have two of those data structures. I presented the following code as an example for the project.

define :a do
  use_synth :beep
  use_random_seed 98
  16.times do
    play scale(60, :major).choose
    sleep 0.25
  end
  6.times do
    play (ring, 60, 64, 67, 72, 67, 60).tick
    sleep (ring, 0.5, 0.5, 1).look
  end
end

define :b do |x|
  use_synth :chiplead
  32.times do
    play [50, 53, 56, 59, 62, 65, 68, 71].choose
    sleep x
  end
  play 74, release: 4
  sleep 4
end

define :c do
  use_synth :dsaw
  16.times do
    play scale(:fs4, :bhairav).choose
    sleep (ring, 0.333, 0.667, 0.667, 0.333).look
  end
end


a
b(0.125)
a
c
a

I pointed out that I had named each section :a, :b, and :c to make it easy when I called them. However, I said they could name their functions whatever they wanted as long as they call the functions in the correct order. I also pointed out the function where I had used a ring and a scale which would fulfill the requirement for a 4. I initially did not have an argument, so I showed them how I would add one by adding one argument to my second function to hold the sleep value. I did this to help reinforce how to add arguments one more time.
Here is a copy of the rubric and checklist used for the project: https://docs.google.com/document/d/1jHHQ9XEb1wjPZ220JiWFnra5brXtVCYUjGT4we6z4Go/edit?usp=sharing

From there I ran my code. I pointed out each section as it played. I had been sure to include a different synth sound for each section to make sure students were clear on when each section had changed. I told the class that I wasn’t crazy about the C section of my piece (which I wasn’t) and had I had more time, I would do something to change it. I told them this to try and to get them thinking about listening with a discerning ear and to not just accept their code as they wrote it the first time. While I feel that most of the time, students are making changes if they don’t like something, I also want them to try and think about what they want they piece to sound like prior to making any code.

Since this was a graded assignment, I gave the students the rest of class and the following week’s class period as well. For the next post, I will write about some of my observations from when they were working and the more common mistakes and issues I found when grading their assignments.