Variabili

Una cosa che può risultarti utile è quella di dare dei nomi 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.

Variabili

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.