Sonic Pi has a global memory store called Time State. The two main things you do with it are to set
information and get
information. Let’s dive deeper…
To store information into the Time State we need two things:
For example, we might want to store the number 3000
with the key :intensity
. This is possible using the set
function:
set :intensity, 3000
We can use any name for our key. If information has already been stored with that key, our new set
will override it:
set :intensity, 1000
set :intensity, 3000
In the above example, as we stored both numbers under the same key, the last call to set
‘wins’, so the number associated with :intensity
will be 3000
as the first call to set
is effectively overridden.
To fetch information from the Time State we just need the key we used to set
it, which in our case is :intensity
. We then just need to call get[:intensity]
which we can see by printing out the result to the log:
print get[:intensity] #=> prints 3000
Notice that calls to get
can return information that was set
in a previous run. Once a piece of information has been set
it is available until either the information is overridden (just like we clobbered the :intensity
value of 1000
to 3000
above) or Sonic Pi is closed.
The main benefit of the Time State system is that it can be safely used across threads or live loops. For example, you could have one live loop setting information and another one getting it:
live_loop :setter do
set :foo, rrand(70, 130)
sleep 1
end
live_loop :getter do
puts get[:foo]
sleep 0.5
end
The nice thing about using get
and set
across threads like this is that it will always produce the same result every time you hit run. Go on, try it. See if you get the following in your log:
{run: 0, time: 0.0}
└─ 125.72265625
{run: 0, time: 0.5}
└─ 125.72265625
{run: 0, time: 1.0}
└─ 76.26220703125
{run: 0, time: 1.5}
└─ 76.26220703125
{run: 0, time: 2.0}
└─ 114.93408203125
{run: 0, time: 2.5}
└─ 114.93408203125
{run: 0, time: 3.0}
└─ 75.6048583984375
{run: 0, time: 3.5}
└─ 75.6048583984375
Try running it a few times - see, it’s the same every time. This is what we call deterministic behaviour and it’s really very important when we want to share our music as code and know that the person playing the code is hearing exactly what we wanted them to hear (just like playing an MP3 or internet stream sounds the same for all listeners).
Back in Section 5.6 we discussed why using variables across threads can lead to random behaviour. This stops us from being able to reliably reproduce code such as this:
## An Example of Non-Deterministic Behaviour
## (due to race conditions caused by multiple
## live loops manipulating the same variable
## at the same time).
##
## If you run this code you'll notice
## that the list that's printed is
## not always sorted!
a = (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do
a = a.shuffle
sleep 0.5
end
live_loop :sorted do
a = a.sort
sleep 0.5
puts "sorted: ", a
end
Let’s take a look at how this might look using get
and set
:
## An Example of Deterministic Behaviour
## (despite concurrent access of shared state)
## using Sonic Pi's new Time State system.
##
## When this code is executed, the list that's
## printed is always sorted!
set :a, (ring 6, 5, 4, 3, 2, 1)
live_loop :shuffled do
set :a, get[:a].shuffle
sleep 0.5
end
live_loop :sorted do
set :a, get[:a].sort
sleep 0.5
puts "sorted: ", get[:a]
end
Notice how this code is pretty much identical to the version using a variable before it. However when you run the code, it behaves as you would expect with any typical Sonic Pi code - it does the same thing every time in this case thanks to the Time State system.
Therefore, when sharing information across live loops and threads, use get
and set
instead of variables for deterministic, reproducible behaviour.