Variabelen

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

Hier, we hebben het symbool :loop_amen ‘onthouden’ in de variabele sample_name . Nu kunnen we sample_name gebruiken, overal waar we :loop_amen hebben gebruikt. Bijvoorbeeld:

sample_name = :loop_amen
sample sample_name

Er zijn drie belangrijke redenen voor het gebruik van variabelen in Sonic Pi: in het communiceren, het beheer van herhaling en het vastleggen van de resultaten van zaken.

In de zin van Communicatie

Als je code schrijft, denk ,je snel dat je je computer verteld wat hij moet doen, en zolang de computer deze kan verstaan, alles OK is. Maar het is ook belangrijk om er op te letten dat niet alleen de computer jouw code leest. Andere mensen kunnen deze ook lezen en proberen te begrijpen wat er gaande is. Ook, wil je waarschijnlijk jouw eigen code in de toekomst lezen en proberen te begrijpen hoe die werkt. Hoewel deze code voor jou nu vanzelfsprekend lijkt - is het misschien niet zo duidelijk voor anderen of zelfs je toekomstige zelf!

Een manier om andere jouw code te laten begrijpen is deze van een commentaar-lijn voorzien (zoals we in een voorgaande sectie hebben gezien). Een ander is zinvolle variabele namen gebruiken. Kijk naar deze code:

sleep 1.7533

Waarom maakt men hier gebruik van het cijfer 1.7533? Waar komt dit getal vandaan? Wat betekent het? En kijk nu eens naar deze code:

loop_amen_duration = 1.7533
sleep loop_amen_duration

Het is nu veel duidelijker wat ‘1.7533’ betekent: het is de duur van de sample :loop_amen! Natuurlijk, zou je kunnen zeggen, waarom schrijf je gewoon niet:

sleep sample_duration(:loop_amen)

Wat natuurlijk een zeer mooie manier is om de intentie van de code te communiceren .

Herhaling Beheren

Vaak zie je een heleboel herhaling in je code en als je dingen wil veranderen, moet je deze op een heleboel plaatsen veranderen. Kijk even naar deze 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 doen hier heel wat met ‘: loop_amen’! Maar als we nu willen horen hoe dit klinkt met een andere sample loop, zoals :loop_garzul? Dan zouden we deze moeten gaan zoeken en alle :loop_amens met :loop_garzul vervangen’. Dat zou fijn zijn als je veel tijd hebt- maar wat als je dit uitvoert op het podium? Soms heb je de luxe van de tijd niet - vooral niet als je de mensen aan het dansen wil houden.

Wat als je je code als het volgt zou schrijven:

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)

Nu dat doet net het zelfde als deze hierboven (probeer maar). Het geeft ons ook de mogelijkheid om één enkele lijn te moeten gaan veranderen: sample_naam = :loop_amen naar `sample_naam = :loop_garzul, en we veranderen deze op vele plaatsen binnen onze code door de magie van variabelen.

Vastleggen van de resultaten

Tot slot, een goede motivatie om variabelen te gaan gebruiken is om resultaten van zaken vast te leggen. Misschien wilt u bijvoorbeeld iets doen met de duur van een sample:

sd = sample_duration(:loop_amen)

We kunnen nu ‘sd’ overal gebruiken waar we de duur van de sample :loop_amen nodig hebben.

Wat misschien nog belangrijker is, een variabele stelt ons in staat het resultaat te capteren van een oproep naar play of sample:

s = play 50, release: 8

Nu hebben we s gevangen en onthouden als een variabele, hetgeen ons toelaat de synth te controleren wanneer deze speelt:

s = play 50, release: 8
sleep 2
control s, note: 62

We zullen het manipuleren van synth’s in een later sectie meer in detail bekijken.

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 :shuffled do
  a = a.shuffle
  sleep 0.5
end
live_loop :sorted do
  a = a.sort
  sleep 0.5
  puts "sorted: ", a
end

In the above example we assign a ring of numbers to a variable a and then used it within two separate live_loops. In the first live loop every 0.5s 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.