Skoro osiągnąłeś już wystarczająco zaawansowane umiejętności kodowania na żywo (live coding) wraz z wykorzystaniem jednocześnie funkcji i wątków, na pewno zauważyłeś już, że bardzo łatwo jest popełnić błąd w jednym z wątków co powoduje, że zostaje on zabity. To nie jest wielka rzecz, ponieważ możesz bardzo łatwo zrestartować wątek poprzez ponowne naciśnięcie przycisku Run. Jednakże, kiedy zrestartujesz wątek to wypadnie on teraz z rytmu oryginalnych wątków.
Jak już mówiliśmy wcześniej, nowe wątki tworzone z wykorzystaniem polecenia
in_thread
dziedziczą wszystkie ustawienia z wątku rodzica. W skład
tych ustawień wchodzi też aktualny czas. Oznacza to, że wątki pozostają
zawsze w korelacji czasowej kiedy zostaną uruchomione jednocześnie.
Jendakże, kiedy uruchomisz samodzielny wątek, rozpoczyna się on ze swoim własnym czasem i jest mało prawdopodobne, że będzie on w synchronizacji z jakimkolwiek innym uruchomionym aktualnie wątkiem.
Sonic Pi udostępnia rozwiązanie dla tego problemu za pomocą funkcji
cue
(wskazówka) i sync
(synchronizacja).
cue
pozwala nam na wysłanie wiadomości o pulsie to wszystkich innych
aktualnie uruchomionych wątków. Domyślnie inne wątki nie są tym zainteresowane
i ignorują tę wiadomość o pulsie. Jendakże, możesz bardzo łatwo wywołać
takie zainteresowanie za pomocą funkcji sync
.
Ważną rzeczą, której należy być świadomym jest to, że funkcja sync
jest podobna do funkcji sleep
w taki sposób, że powstrzymuje ona
aktualny wątek od robienia czegokolwiek przez pewien okres czasu.
Jednakże, w przypadku funkcji sleep
musisz zdefiniować jak długo
chcesz poczekać, natomiast w przypadku funkcji sync
nie wiesz jak
długo będziesz chicał poczekać, ponieważ funkcja sync
czeka na
następny punkt cue
z innego wątku, co może nastąpić szybciej lub
później.
Spróbujmy przyjrzeć się temu trochę bardziej dokładnie:
in_thread do
loop do
cue :tick
sleep 1
end
end
in_thread do
loop do
sync :tick
sample :drum_heavy_kick
end
end
Mamy tutaj dwa wątki - jeden zachowuje się jak metronom, nie gra żadnych
dźwięków tylko wysyła komunikat :tik
(cyk) przy każdym uderzeniu. Drugi
wątek synchronizuje się natomiast przy otrzymaniu każdej kolejnej wiadomości
:tick
i kiedy otrzymuje kolejną odziedzicza czas uruchomienia cue
i rozpoczyna swoje działanie.
Jako wynik słyszymy sampel :drum_heavy_kick
dokładnie w tym samym momencie
gdy inny wątek wysyła komunikat :tick
, nawet jeśli obydwa wątki nie
zostały uruchomione w tym samym czasie.
in_thread do
loop do
cue :tick
sleep 1
end
end
sleep(0.3)
in_thread do
loop do
sync :tick
sample :drum_heavy_kick
end
end
Te niegrzeczne polecenie sleep
normalnie stworzyłoby drugi wątek,
który byłby zupełnie niezsynchronizowany z pierwszym. Jednakże, dzięki
użyciu poleceń cue
i sync
automatycznie synchronizujemy wątki blokując
jednocześnie jakiekolwiek nieprzewidziane rozjazdy w czasie.
Dla nazw twoich komunikatów cue
możesz używać dowolnych nazw - może to
być nie tylko :tick
. Musisz się tylko upewnić, że inne wątki
sync
hronizują się z właściwą nazwą - w przeciwnym wypadku będą czekać
w nieskończoność (albo do momentu, w którym naciśniesz przycisk Stop).
Spróbujmy pobawić się z kilkoma komunikatami cue
o różnych nazwach:
in_thread do
loop do
cue [:foo, :bar, :baz].choose
sleep 0.5
end
end
in_thread do
loop do
sync :foo
sample :elec_beep
end
end
in_thread do
loop do
sync :bar
sample :elec_flip
end
end
in_thread do
loop do
sync :baz
sample :elec_blup
end
end
Mamy tutaj główną pętlę cue
, która co iterację wysyła komunikat z
losową wybraną nazwą punktu cue do synchronizacji - :foo
, :bar
lub
:baz
. Następnie mamy trzy wątki z pętlami, które synchronizują się
na każdym z tych komunikatów niezależnie i odtwarzają różne sample.
Wynikiem tego jest to, że słyszymy dźwięk co każde 0.5 uderzenia
jako, że każdy z synchronizowanych (sync
) wątków jest wybierany
losowo z wątku cue
po czym odtwarza swojego sampla.
Oczywiście to wszystko zadziała analogicznie nawet jeśli poukładasz
wątki w odwrotnej kolejności jako, że wątek sync
będzie po prostu
czekał na kolejną wartość wysłaną przez wątek cue
.