Variables
A useful thing to do in your code is to create names for things. Sonic Pi makes this very easy: you write the name you wish to use, an equal sign (=
), then the thing you want to remember:
sample_name = :loop_amen
Here, weâve ârememberedâ the symbol :loop_amen
in the variable sample_name
. We can now use sample_name
everywhere we might have used :loop_amen
. For example:
sample_name = :loop_amen
sample sample_name
There are three main reasons for using variables in Sonic Pi: communicating meaning, managing repetition and capturing the results of things.
Communicating Meaning
When you write code itâs easy to just think youâre telling the computer how to do stuff - as long as the computer understands itâs OK. However, itâs important to remember that itâs not just the computer that reads the code. Other people may read it too and try to understand whatâs going on. Also, youâre likely to read your own code in the future and try to understand whatâs going on. Although it might seem obvious to you now - it might not be so obvious to others or even your future self!
One way to help others understand what your code is doing is to write comments (as we saw in a previous section). Another is to use meaningful variable names. Look at this code:
sleep 1.7533
Why does it use the number 1.7533
? Where did this number come from? What does it mean? However, look at this code:
loop_amen_duration = 1.7533
sleep loop_amen_duration
Now, itâs much clearer what 1.7533
means: itâs the duration of the sample :loop_amen
! Of course, you might say why not simply write:
sleep sample_duration(:loop_amen)
Which, of course, is a very nice way of communicating the intent of the code.
Managing Repetition
Often you see a lot of repetition in your code and when you want to change things, you have to change it in a lot of places. Take a look at this code:
sample :loop_amen
sleep sample_duration(:loop_amen)
sample :loop_amen, rate: 0.5
sleep sample_duration(:loop_amen, rate: 0.5)
sample :loop_amen
sleep sample_duration(:loop_amen)
Weâre doing a lot of things with :loop_amen
! What if we wanted to hear what it sounded like with another loop sample such as :loop_garzul
? Weâd have to find and replace all :loop_amen
s with :loop_garzul
. That might be fine if you have lots of time - but what if youâre performing on stage? Sometimes you donât have the luxury of time - especially if you want to keep people dancing.
What if youâd written your code like this:
sample_name = :loop_amen
sample sample_name
sleep sample_duration(sample_name)
sample sample_name, rate: 0.5
sleep sample_duration(sample_name, rate: 0.5)
sample sample_name
sleep sample_duration(sample_name)
Now, that does exactly the same as above (try it). It also gives us the ability to just change one line sample_name = :loop_amen
to sample_name = :loop_garzul
and we change it in many places through the magic of variables.
Capturing Results
Finally, a good motivation for using variables is to capture the results of things. For example, you may wish to do things with the duration of a sample:
sd = sample_duration(:loop_amen)
We can now use sd
anywhere we need the duration of the :loop_amen
sample.
Perhaps more importantly, a variable allows us to capture the result of a call to play
or sample
:
s = play 50, release: 8
Now we have caught and remembered s
as a variable, which allows us to control the synth as it is running:
s = play 50, release: 8
sleep 2
control s, note: 62
Weâll look into controlling synths in more detail in a later section.
Warning: Variables and Threads
Whilst variables are great for giving things names and capturing the results of things, it is important to know that they should typically only be used locally within a thread. For example, donât do this:
a = (ring 6, 5, 4, 3, 2, 1)
live_loop :sorted do
a = a.sort
sleep 0.5
puts "sorted: ", a
end
live_loop :shuffled do
a = a.shuffle
sleep 0.5
end
In the above example we assign a ring of numbers to a variable a
and then used it within two separate live_loop
s. In the first live loop every 0.5
s we sort the ring (to (ring 1, 2, 3, 4, 5, 6)
) and then print it out to the log. If you run the code, youâll find that the printed list is not always sorted!. This may surprise you - especially that sometimes the list is printed as sorted, and sometimes it is not. This is called non-deterministic behaviour and is the result of a rather nasty problem called a race-condition. The problem is due to the fact that the second live loop is also manipulating the list (in this case shuffling it) and by the time the list is printed, sometimes it has just been sorted and sometimes it has just been shuffled. Both live loops are racing to do something different to the same variable and every time round a different loop âwinsâ.
There are two solutions to this. Firstly, donât use the same variable in multiple live loops or threads. For example, the following code will always print a sorted list as each live loop has its own separate variable:
live_loop :shuffled do
a = (ring 6, 5, 4, 3, 2, 1)
a = a.shuffle
sleep 0.5
end
live_loop :sorted do
a = (ring 6, 5, 4, 3, 2, 1)
a = a.sort
sleep 0.5
puts "sorted: ", a
end
However, sometimes we do want to share things across threads. For example, the current key, BPM, synth etc. In these cases, the solution is to use Sonic Piâs special thread-safe state system via the fns get
and set
. This is discussed later on in section 10.