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 Uruchom. 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.
Jednakż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, który jest aktualnie uruchomiony.
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 do wszystkich innych aktualnie uruchomionych wątków. Domyślnie inne wątki nie są tym zainteresowane i ignorują tę wiadomość, jednakże możesz bardzo łatwo wywołać takie zaintrygowanie za pomocą funkcji sync
.
Ważną rzeczą, której należy być świadomym, jest to, że funkcja sync
wygląda podobnie 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 funkcji sync
nie wiesz, jak długo będziesz chciał poczekać, ponieważ ta funkcja 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 z nich 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 słów - 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
.