Het is handig om in jouw code is namen voor dingen te creëren. Sonic Pi maakt jou dit zeer eenvoudig, je schrijft de naam die je wenst te gebruiken, een gelijkteken (‘=’), en dan wat je wilt onthouden:
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.
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 .
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_amen
s 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.
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.
Alhoewel variabelen goed zijn voor het geven van namen aan dingen en het opslaan van de resultaten van dingen, is het belangrijk om te weten dat ze typisch alleen lokaal gebruikt dienen te worden binnen een thread. Bijvoorbeeld, doe dit niet:
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 het bovenstaande voorbeeld kenden we een ring van getallen toe aan variabele a
en werd deze gebruikt in twee live_loop
’s. In de eerste lus sorteren we de ring elke 0,5s (naar (ring 1, 2, 3, 4, 5, 6)
) en drukken deze af naar het log. Wanneer je de code draait kom je erachter dat de afgedrukte lijst niet altijd gesorteerd is!. Dit kan je verrassen, zeker omdat de lijst soms gesorteerd is afgedrukt en soms niet. Dit wordt niet deterministisch gedrag genoemd en is het resultaat van een behoorlijk naar probleem genaamd race conditie. Het probleem komt doordat de tweede lus de lijst ook manipuleert (in dit geval door elkaar schudden). Tegen de tijd dat de lijst wordt afgedrukt is deze soms net gesorteerd en soms net geschud. Beide lussen ‘racen’ om iets anders te doen met dezelfde variabele en elke keer ‘wint’ er een andere lus.
Er zijn hiervoor twee oplossingen. Ten eerste, gebruik dezelfde variabele niet in meerdere live lussen of threads. Bijvoorbeeld: de volgende code drukt altijd een gesorteerde lijst omdat iedere live lus een aparte eigen variabele heeft:
live_loop :geschud do
a = (ring 6, 5, 4, 3, 2, 1)
a = a.shuffle
sleep 0.5
end
live_loop :gesorteerd do
a = (ring 6, 5, 4, 3, 2, 1)
a = a.sort
sleep 0.5
puts "gesorteerd: ", a
end
Soms willen we echter dingen delen tussen threads. Bijvoorbeeld de huidige toonsoort, BPM, synth, etc. In deze gevallen is de oplossing om gebruik te maken van de speciale thread-safe state systemen in Sonic Pi via de functies get
en set
. Dit wordt verder beproken in sectie 10.