Una cosa che può risultarti utile è quella di creare nomi da assegnare alle cose. Sonic Pi rende questo processo molto semplice: basta che scrivi il nome desiderato, il segno uguale (‘=’), e dopo la cosa che vuoi ricordarti:
sample_name = :loop_amen
Nell’esempio sopra ci siamo ‘ricordati’ il simbolo ‘:loop_amen’ grazie alla variabile ‘sample_name’. Adesso, possiamo utilizzare ‘sample_name’ ovunque avremmo usato ‘:loop_amen’. Ecco un esempio:
sample_name = :loop_amen
sample sample_name
Ci sono tre ragioni principali che rendono utile l’impiego di variabili in Sonic_Pi: comunicare significati, organizzare le ripetizioni e catturare i risultati delle cose.
Quando programmi è facile pensare semplicemente che stai dicendo al computer come fare qualcosa. E finché il computer capisce, va tutto bene! Tuttavia, è importante tenere a mente che non solo il computer legge il codice,ma anche altre persone potrebbero farlo per capire cosa sta succedendo. Inoltre, è probabile che tu stesso/a andrai prima o poi a rileggere il tuo codice e cercare di capire a che cosa serve. Anche se adesso qualche passaggio ti sembra ovvio, potrebbe non esserlo per qualcun altro o per te stesso/a nel futuro!
Un modo per aiutare gli altri a comprendere il tuo programma è scrivere commenti (come abbiamo visto nella sezione precedente. Un altro modo è usare dei nomi di variabili che abbiano un significato. Per esempio, guarda il codice seguente:
sleep 1.7533
Perché in questo codice è scritto il numero ‘1,7533’? Da dove salta fuori? Che cosa significa? Guarda invece questo codice:
loop_amen_duration = 1.7533
sleep loop_amen_duration
Adesso è molto più chiaro a cosa si riferisce ‘1,7533’:è la durata del campione ‘:loop_amen’! Adesso, potresti domandarti perché non scrivere semplicemente:
sleep sample_duration(:loop_amen)
Questa è certamente un modo molto semplice e carino di comunicare lo scopo di un codice.
Spesso può succedere che nel codice ci siano molte ripetizioni e quando vuoi cambiare qualcosa, è necessario farlo in molti punti. Guarda questo esempio:
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)
Facciamo un sacco di cose con :loop_amen
! E se volessimo sentire la stessa cosa ma con un loop diverso come, ad esempio, :loop_garzul
? Saremmo costretti a trovare e rimpiazzare tutti i :loop_amen
con :loop_garzul
. Potresti farlo se avessi a disposizione molto tempo ma se ti stai esibendo su un palco? Spesso non hai il lusso di avere molto tempo, specialmente se non vuoi che la gente smetta di ballare.
E se avessimo scritto il codice in questo modo:
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)
Questo codice fa esattamente la stessa cosa di quello di prima (prova) ma ci dà la possibilità di cambiare una riga di codice sample_name = :loop_amen
in sample_name = :loop_garzul
e, grazie a questa variabile magica, verrà cambiato dappertutto.
Un buon motivo per utilizzare le variabili è la possibilità di catturare i risultati delle cose. Per esempio, magari vuoi provare a lavorare con la durata di un sample:
sd = sample_duration(:loop_amen)
Ora possiamo usare sd
in ogni punto dove abbiamo bisogno della durata del camptione :loop_amen
.
Un risultato ancora più importante, forse, è che una variabile ci permette di catturare il risultato quando chiamiamo play
o sample
:
s = play 50, release: 8
Ora che abbiamo catturato s
come variabile, possiamo controllare il sintetizzatore mentre sta funzionando:
s = play 50, release: 8
sleep 2
control s, note: 62
Vedremo come controllare i sintetizzatori in modo più dettagliato più tardi in questa sezione.
Mentre le variabili sono ottime per assegnare nomi alle cose ed acquisire i relativi risultati, è importante sapere che dovrebbero essere generalmente usate in locale all’interno di un thread. Per esempio, non fare questo:
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
Nell’esempio di sopra, assegniamo un cerchio di numeri a un variabile a
e poi l’usiamo in due live_loop
(cicli dinamici) diversi. Nel primo live_loop
, ogni 0.5
secondi, ordiniamo il cerchio (a (ring 1,2,3,4,5,6)
) e lo stampiamo sul log. Se esegui il codice, trovi che l’elenco stampato non è sempre ordinato!. Questo forse ti sorprenderà — specialmente che a volte l’elenco viene stampato in ordine e a volte no. Questo si chiama comportamento “non-deterministico” ed è il risultato di un problema piuttosto difficile che si chiama “condizione di gara”. Il problema è dovuto al fatto che anche il secondo ciclo sta manipolando l’elenco (in questo caso, rimescolandolo) e al punto che l’elenco viene stampato, alcune volte è appena stato ordinato ed alcune volte è appena stato rimescolato. Entrambi i cicli fanno una gara di fare qualcosa differente con lo stesso variabile ed ogni volta “vince” un ciclo diverso.
Esistono due soluzioni a questo. Prima, non usare lo stesso variable in cicli o thread molteplici. Per esempio, il codice seguente sempre stamperà un elenco ordinato perché ogni ciclo ha un proprio variabile:
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
Comunque, a volte vogliamo condividere cose fra diversi thread. Per esempio, la chiave attuale, BPM, sintetizzatore, ecc. In questi casi, la soluzione è usare il sistema speciale di stato “thread-safe” (sicuro per i thread) di Sonic Pi attraverso le funzioni get
e set
. Questo si descrive dopo, nella sezione 10.