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.
Zmienne są świetne do nadawania rzeczom nazw oraz zapisywania wyników różnych operacji, ale musisz pamiętać, że zazwyczaj powinny być używane tylko lokalnie w ramach jednego wątku. Na przykład, nie rób tego:
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
W powyższym przykładzie do zmiennej a
przypisujemy pierścień liczb a następnie wykorzystujemy go wewnątrz dwóch różnych żywych pętli. W pierwszej pętli co każde pół uderzenia sortujemy pierścień (aby wyglądał następująco (ring 1, 2, 3, 4, 5, 6)
) a następnie wyświetlamy jego zawartość w panelu z logami. Gdy uruchomisz kod, zauważysz że lista wyświetlana w logach nie zawsze jest dobrze posortowana!. Może to być dla Ciebie niespodzianką - zwłaszcza, że czasami lista jest wyświetlana jako posortowany zbiór, a czasami nie. To jest tak zwane niedeterministyczne zachowanie i jest wynikiem raczej nieprzyjemnego problemu zwanego wyścigiem. Przyczyną problemu jest fakt, że druga żywa pętla również modyfikuje listę (w tym wypadku tasuje ją) i tak naprawdę w momencie wyświetlenia czasem będzie one posortowana a czasem potasowana. Obie żywe pętle ścigają się aby zrobić coś innego z tą samą zmienną i za każdym razem ‘wygrywa’ inna pętla.
Istnieją dwa rozwiązania tego problemu. Po pierwsze, nie używaj tej samej zmiennej w wielu różnych żywych pętlach lub wątkach. Na przykład, następujący kod zawsze wyświetli posortowaną listę ponieważ każda żywa pętla ma swoją własną, niezależną zmienną:
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
Czasami jednak zdarza się, że chcielibyśmy współdzielić rzeczy pomiędzy różnymi wątkami. Na przykład aktualną tonację, BPM, syntezator (synth), itd. W takich sytuacjach rozwiązaniem jest używanie specjalnego wątkowo-bezpiecznego systemu stanu wykorzystując funkcje get
i set
. Zostanie to przedyskutowane dalej w sekcji 10.