Przydatną rzeczą, którą możesz robić w swoim kodzie, to tworzenie nazw dla różnych rzeczy. Sonic Pi sprawia, że jest to bardzo łatwe - wpisujesz wybraną przez siebie nazwę, znak równości (=
), a następnie rzecz do zapamiętania:
sample_name = :loop_amen
W powyższym kawałku kodu ‘zapisaliśmy’ wartość symbolu :loop_amen
w zmiennej sample_name
. Od teraz możemy używać nazwy sample_name
wszędzie, gdzie do tej pory użylibyśmy sampla :loop_amen
. Na przykład:
sample_name = :loop_amen
sample sample_name
Są trzy podstawowe powody na korzystanie ze zmiennych w Sonic Pi: komunikowanie znaczenia, zarządzanie powtórzeniami oraz przechwytywanie wyników różnych rzeczy.
Kiedy piszesz kod, łatwo jest myśleć, że jedyne, co robisz, to mówisz komputerowi, jak ma wykonać jakieś rzeczy - tak długo, jak on rozumie, co do niego mówisz, to jest w porządku. Jednakże ważne jest, aby pamiętać, że nie tylko komputer czyta kod. Inni ludzie również mogą chcieć przeczytać go i spróbować zrozumieć, co się w nim dzieje. Ponadto bardzo prawdopodobne, że Ty również będziesz czytał swój własny kod w przyszłości i próbował zrozumieć, o co w nim chodzi. Chociaż w tej chwili jego znaczenie może być dla Ciebie oczywiste - może nie być takie oczywiste dla innych lub nawet dla Ciebie samego w przyszłości!
Jedną z metod, która pomoże innym zrozumieć, co Twój kod robi, jest pisanie komentarzy (co widzieliśmy już we wcześniejszej sekcji tego samouczka). Inną jest używanie takich nazw dla zmiennych, które coś znaczą. Spójrz na poniższy kod:
sleep 1.7533
Dlaczego używa on liczby 1.7533
? Skąd wzięła się ta liczba? Co ona oznacza? A teraz spójrz na poniższy kod:
loop_amen_duration = 1.7533
sleep loop_amen_duration
Teraz zrozumienie tego, co oznacza liczba 1.7533
, jest znacznie prostsze: oznacza ona długość trwania sampla :loop_amen
! Oczywiście możesz zapytać, dlaczego po prostu nie napisaliśmy:
sleep sample_duration(:loop_amen)
Co jak najbardziej jest bardzo fajnym sposobem zakomunikowania intencji zawartych w kodzie.
Często widzisz dużo powtórzeń w Twoim kodzie i kiedy chcesz coś zmienić, musisz wprowadzić zmiany w wielu miejscach. Spójrz na poniższy kawałek kodu:
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)
Robiliśmy tutaj sporo rzeczy z samplem :loop_amen
! Co, jeśli chcielibyśmy usłyszeć, jak ten kawałek kodu brzmi z innym samplem, na przykład :loop_garzul
? Musielibyśmy wtedy znaleźć i zamienić wszystkie wystąpienia sampla :loop_amen
na :loop_garzul
. To może być całkiem w porządku, jeśli masz sporo luzu - ale co, gdy właśnie występujesz na scenie? Czasami nie masz tego luksusu, że masz czasu tyle, ile chcesz - zwłaszcza wtedy, gdy chcesz utrzymać ludzi na parkiecie.
A co, jeśli powyższy kawałek kodu przepiszemy na coś takiego?:
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)
Teraz ten kod robi dokładnie to samo, co wcześniejszy (spróbuj). Oprócz tego daje nam możliwość zmiany tylko jednej linijki z obecnej sample_name = :loop_amen
na sample_name = :loop_garzul
, aby jednocześnie zmienić brzmienie w wielu miejscach dzięki magii zmiennych.
I na koniec dobrym powodem do używania zmiennych jest przechwytywanie wyniku wykonania różnych rzeczy. Przykładowo możesz chcieć robić różne rzeczy z długością trwania sampla:
sd = sample_duration(:loop_amen)
Możemy teraz używać zmiennej sd
wszędzie tam, gdzie potrzebujemy użyć długości trwania sampla :loop_amen
.
Możliwe, że nawet bardziej ważne jest to, iż zmienne pozwalają nam na przechwycenie i zapisanie wyniku uruchomienia polecenia play
lub sample
:
s = play 50, release: 8
Teraz złapaliśmy i zapamiętaliśmy s
jako zmienną, co pozwala nam na kontrolę syntezatora w trakcie jego działania:
s = play 50, release: 8
sleep 2
control s, note: 62
Przyjrzymy się bardziej kontrolowaniu syntezatorów w kolejnej sekcji.
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_loop
s. In the first live loop every 0.5
s 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.