Variabili

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.

Comunicare i significati

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.

Gestire le ripetizioni

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_amencon :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.

Catturare i risultati

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.

Attenzione: Variabili e Thread

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.