Variabile

Un lucru util pe care il putem face in cadrul codului nostru este sa cream nume pentru obiecte. In Sonic Pi asta se face foarte usor, scrii un nume pe care vrei sa-l folosesti, semnul egal (ā€˜=ā€™), apoi obiectul pe care vrei sa-l memorezi:

sample_name = :loop_amen

Aici, am ā€˜memoratā€™ simbolul ā€˜:loop_amenā€™ in variabila ā€˜sample_nameā€™. Putem folosi ā€˜sample_nameā€™ oriunde am fi folosit ā€˜:loop_amenā€™. De exemplu:

sample_name = :loop_amen
sample sample_name

Exista trei motive principale pentru utilizarea variabilelor in Sonic Pi: comunicarea rolului, gestiunea repetitiilor si stocarea rezultatului unor actiuni.

Comunicarea rolului

Cand scrii cod e usor sa te gandesti doar ca tu ii spui calculatorului sa faca un anumit lucru - cat timp computerul intelege este OK. Totusi, e important sa te gandesti ca nu doar computerul citeste codul. Alte persoane ar putea sa il citeasca de asemenea si sa incerce sa inteleaga despre ce este vorba. De asemenea, e posibil ca tu sa iti recitesti propriul cod mai tarziu si sa vrei sa intelegi cum functioneaza. Desi ar putea sa ti se para evident acum, acest lucru ar putea sa nu fie valabil pentru altii sau chiar pentru tine in viitor!

Un mod de a-i ajuta pe altii sa inteleaga ce face codul tau este sa scrii comentarii (asa cum am vazut intr-o sectiune anterioare). Alt mod este sa folosesti variabile cu nume alese conform rolului lor. Priveste acest cod:

sleep 1.7533

De ce foloseste acest numar, 1.7533? De unde vine acest numar? Ce inseamna? Priveste acum acest cod:

loop_amen_duration = 1.7533
sleep loop_amen_duration

Acum este mult mai clar ce inseamna 1.7533: este durata esantionului ā€˜:loop_amenā€™! Desigur, ai putea spune ca e mai simplu sa scrii:

sleep sample_duration(:loop_amen)

Care este, desigur, un mod potrivit de a comunica scopul codului respectiv.

Gestionarea repetitiilor

Adesea poti vedea multe repetitii in cod si cand vrei sa schimbi ceva trebuie sa schimbi intr-o multime de locuri. Priveste acest cod:

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)

Folosim in multe locuri ā€˜:loop_amen!ā€™ Sa presupunem ca vrem sa auzim cum ar suna daca am folosi un alt esantion, cum ar fi ā€˜:loop_garzulā€™. Ar trebui sa inlocuim peste tot :loop_amen cu :loop_garzul. Ar putea fi OK daca ai avea o gramada de timp la dispozitie, dar daca interpretezi live pe scena? Uneori nu iti permiti luxul de a pierde timpul, mai ales cand vrei ca lumea sa danseze in continuu.

Daca ai fi scris codul astfel:

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)

Codul ar face acelasi lucru ca cel de mai sus (poti incerca). Dar ne da si posibilitatea de a modifica o singura linie din sample_name = :loop_amen in sample_name = :loop_garzul, ceea ce va schimba esantionul redat in mai multe locuri folosind magia variabilelor.

Stocarea rezultatelor

In fine, un motiv bun pentru a utiliza variabilele este acela ca ajuta la stocarea rezultatelor. De exemplu, ai putea dori sa faci anumite lucruri cu durata esantionului:

sd = sample_duration(:loop_amen)

Acum putem folosi ā€˜sdā€™ oriunde avem nevoie de durata esantionului ā€˜:loop_amenā€™.

Poate mai important, o variabila ne permite sa stocam rezultatul unui apel al unei functii ā€˜playā€™ sau ā€˜sampleā€™:

s = play 50, release: 8

Acum am memorat ā€˜sā€™ ca o variabila, ceea ce ne permite sa controlam sintetizatorul in timpul functionarii:

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

Vom intra in detalii privind controlul sintetizatoarelor intr-o sectiune ulterioara.

Variabile

Variabilele sunt foarte utile pentru a atribui nume diferitelor obiecte si pentru a pastra rezultatul unor calcule, dar este important sa stiti ca ele ar trebui in general sa fie folosite doar local in cadrul unui thread. De exemplu, nu faceti asta:

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 exemplul de mai sus am atribuit unei variabile a o lista circulara de numere si apoi am folosit-o in doua live-loop-uri separate. In prima bucla sortam lista (la (ring 1, 2, 3, 4, 5, 6)) si apoi o afisam in jurnal. Daca rulezi codul, vei vedea ca lista printata nu este intotdeauna sortata!. Lucrul acesta ar putea sa te surprinda - mai ales ca uneori lista este sortata, iar alteori nu. Acesta comportament este denumit nedeterminist si este rezultatul unei probleme neplacute cunoscuta ca race-condition. Aceasta problema apare datorita faptului ca a doua bucla incearca de asemenea sa manipuleze lista (in acest caz prin aleatorizarea ei), iar in momentul in care aceasta este tiparita, uneori tocmai a fost sortata iar alteori tocmai a fost amestecata. Cele doua bucle sunt in competitie pentru a efectua operatii diferite cu aceeasi variabila si de fiecare data o alta bucla ā€˜castigaā€™.

Exista doua solutii pentru aceasta problema. Prima ar fi nu utiliza aceeasi variabila in mai multe bucle live sau thread-uri. De exemplu, codul de mai jos va afisa intotdeauna lista sortata deoarece fiecare bucla are propria variabila:

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

Totusi, uneori chiar vrem sa folosim in comun diferite obiecte in cadrul unor thread-uri diferite. De exemplu: cheia curenta, ritmul, sintetizatorul etc. In aceste cazuri, solutia este folosirea sistemului de stari din Sonic Pi, folosind functiile get si set care asigura consistenta la nivel de thread. Acesta va fi prezentat in sectiunea 10.