A więc udało Ci się przygotować zabójczy bassline oraz tłusty beat. W jaki sposób udaje Ci się je zagrać w tym samym czasie? Jednym z możliwych rozwiązań jest przeplecenie ich razem ręcznie - najpierw zagrać jakis bas, potem trochę bębnów, potem znowu bas… Jednakże, bardzo szybko chronometraż stanie się czymś, o czym będzie ciężko myśleć, zwłaszcza kiedy zaczniemy wplatać do naszej kompozycji kolejne elementy.
A co jeśli Sonic Pi mógłby przeplatać różne elementy za Ciebie automagicznie? Takie coś jest możliwe i możesz to robić używając specjalnego polecenia nazywanego wątkiem - thread.
Aby sprawić by kolejny przykład był prosty, musisz sobie wyobrazić jak
wygląda ten tłusty beat i zabójczy bassline:
loop do
sample :drum_heavy_kick
sleep 1
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
Jak już wcześniej mówiliśmy, pętle są jak czarne dziury dla naszych programów. Gdy już raz wejdziesz do pętli nie wyjdziesz z niej nigdy, chyba że naciśniesz Stop. W jaki zatem sposób możemy zagrać obie pętle jednocześnie? Musimy powiedzieć Sonic Pi, że chcemy rozpocząć coś w tym samym czasie gdy uruchamiamy pozostały kod. To jest właśnie moment, w którym z pomocą przychodzą nam Wątki (ang. threads).
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
Jeśli otoczymy pierwszą pętlę blokiem kodu do/end in_thread
, to w ten
sposób powiemy Sonic Pi żeby uruchomił zawartość tego bloku do/end
dokładnie w tym samym czasie, gdy zostają uruchomione kolejne polecenia,
które znajdują się tuż za blokiem kodu (w tym przypadku jest to druga pętla).
Spróbuj uruchomić ten kod a usłysz jednocześnie bębny oraz bassline, które
są przeplecione i grają jednocześnie!
A co teraz, jeśli chcielibyśmy dodać jeszcze jakiś syntezator (synth). Coś w tym stylu:
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
loop do
use_synth :zawa
play 52, release: 2.5, phase: 2, amp: 0.5
sleep 2
end
Znowu mamy taki sam problem jak przed chwilą. Pierwsza pętla jest uruchomiona
w tym samym momencie co druga pętla ze względu na to, że użyliśmy polecenia
in_thread
. Jednakże, trzecia pętla nigdy nie zostaje uruchomiona. Żeby to
naprawić potrzebujemy kolejnego wątku (thread):
in_thread do
loop do
sample :drum_heavy_kick
sleep 1
end
end
in_thread do
loop do
use_synth :fm
play 40, release: 0.2
sleep 0.5
end
end
loop do
use_synth :zawa
play 52, release: 2.5, phase: 2, amp: 0.5
sleep 2
end
Coś co może być dla Ciebie zaskakujące, to fakt, że gdy naciskasz przycisk Run, to w rzeczywistości tworzysz nowy wątek do uruchomienia twojego kodu. Dlatego też, gdy naciśniesz przycisk uruchom wiele razy to dźwięki zaczną nakładać się na siebie. Jako, że kolejne uruchomienia same są po prostu wątkami, to dźwięki zostaną dla Ciebie automatycznie poprzeplatane.
Gdy już nauczysz się w jaki sposób opanować Sonic Pi, nauczysz się również
tego, że wątki są jednym z najważniejszych materiałów używanych do
tworzenia twojej muzyki. Jednym z ważnych zadań, które do nich należą
to izolacja pojęcia aktualnych ustawień od innych wątków. Co to oznacza?
Otóż, kiedy zmieniasz syntezatory poleceniem use_synth
w rzeczywistości
po prostu zmieniasz syntezator dla aktualnego wątku (current thread)
- w żadnym innym wątku syntezator się nie zmieni. Zobaczmy te zachowanie
w akcji:
play 50
sleep 1
in_thread do
use_synth :tb303
play 50
end
sleep 1
play 50
Zauważyłeś, że środkowy dźwięk był inny od pozostałych? Polecenie
use_synth
wpłynęło tylko na to co zostało uruchomione w wątku, natomiast
pozostała, zewnętrzna część kodu została nietknięta.
Kiedy utworzysz nowy wątek z wykorzystaniem in_thread
, nowy wątek
automatycznie odziedziczy wszsystkie aktualne ustawienia z bieżącego
wątku. Spójrzmy na taki kod:
use_synth :tb303
play 50
sleep 1
in_thread do
play 55
end
Zauważyłeś, że druga nuta zostaje zagrana z użyciem syntezatora :tb303
mimo to, że została zagrana z oddzielnego wątku? Każde ustawienie
zmienione przy użyciu różnych funkcji use_*
będą zachowywać się tak samo.
Kiedy są tworzone kolejne wątki, dziedziczą one wszystkie ustawienia ze swoich rodziców, natomiast jakiekolwiek zmiany nie są udostępniane z powrotem.
Na koniec, możemy nadawać naszym Wątkom nazwy:
in_thread(name: :bass) do
loop do
use_synth :prophet
play chord(:e2, :m7).choose, release: 0.6
sleep 0.5
end
end
in_thread(name: :drums) do
loop do
sample :elec_snare
sleep 1
end
end
Spójrz na panel z logiem kiedy uruchomisz ten kod. Czy widzisz jak w logach przy kolejnych wiadomościach pojawiają się nazwy wątków?
[Run 36, Time 4.0, Thread :bass]
|- synth :prophet, {release: 0.6, note: 47}
Jedną ostatnią już rzecz, którą powinieneś wiedzieć o wątkach posiadających swoją nazwę to to, że w tym samym czasie może być uruchomiony tylko jeden wątek o tej samej nazwie. Spróbujmy to zbadać. Weźmy pod uwagę następujący kod:
in_thread do
loop do
sample :loop_amen
sleep sample_duration :loop_amen
end
end
Sróbuj śmiało wkleić powyższy kod do obszaru roboczego i naciśnij przycisk Run. Następnie naciśnij go jeszcze kilka razy. Usłyszysz kakofonię wielu zapętlonych w czasie sampli Amen Break. Ok, możesz teraz nacisnać przycisk Stop.
To jest zachowanie, które widzieliśmy już nie raz - jeśli naciśniesz przycisk Run, dźwięk zostanie nałożony na istniejące już dźwięki. Dlatego jeśli masz pętle i naciśniesz przycisk Run trzy raz, to będziesz miał trzy warstwy pętli, które będą grane jednocześnie.
Jednakże, w przypadku nazwanych wątków jest inaczej:
in_thread(name: :amen) do
loop do
sample :loop_amen
sleep sample_duration :loop_amen
end
end
Spróbuj teraz dla tego kodu nacisnąć przycisk Run kilkukrotnie. Nie usłyszysz teraz więcej niż jedną pętlę amen na raz. W logach zauważysz również taką wiadomość:
==> Skipping thread creation: thread with name :amen already exists.
Sonic Pi mówi Ci, że wątek o nazwie :amen
już istnieje, nie zostanie
więc utworzony kolejny.
Takie zachowanie może się teraz nie wydawac do niczego przydatne - ale będzie bardzo przydatne kiedy zaczniemy kodować na żywo…