Threads

Fizeste uma killer bassline e um fat beat. Como os tocar ao mesmo tempo? Uma solução é interligar os dois manualmente - tocar algum baixo, depois um pouco de bateria, e mais algum baixo… No entanto, o tempo torna-se difícil de pensar, especialmente se começares a entrelaçar mais elementos.

E se o Sonic Pi pode-se entrelaçar as coisas por ti automaticamente? Bem, ele pode, e fazes isso com algo chamado thread.

Loops infinitos

Para manter este exemplo simples, terás que imaginar que isto é um fat beat e uma killer bassline:

loop do
  sample :drum_heavy_kick
  sleep 1
end
loop do
  use_synth :fm
  play 40, release: 0.2
  sleep 0.5
end

Como discutimos previamente, loops são buracos negros para o programa. Uma vez neles nunca poderás sair até premires stop. Então como tocar ambos os loops ao mesmo tempo? Teremos que dizer ao Sonic Pi que queremos começar algo ao mesmo tempo que o resto do código. É ai que os threads vêm ao nosso auxilio.

Threads ao auxilio

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

Envolvendo o primeiro loop num in_thread do bloco do/end dizemos aos Sonic Pi para correr o conteúdo do bloco exactamente ao mesmo tempo que a declaração seguinte ao bloco (que até é o segundo loop). Experimenta e ouvirás a bateria e o baixo entrelaçados.

Agora e se queremos adicionar um synth por cima. Algo como:

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

Teremos o mesmo problema que anteriormente. O primeiro loop é tocado ao mesmo tempo que o segundo devido ao in_thread. No entanto, o terceiro loop nunca é alcançado. Assim necessitamos de outro 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

Correr como threads

O que te pode supreender é que quando carregas no botão Run, está a criar um novo thread para o código correr. É por isso que carregando múltiplas vezes irá adicionar camadas de som umas sobre as outras. As próprias corridas são threads, elas irão entrelaçar automaticamente os sons por ti.

Scope

Quando aprenderes a conquistar o Sonic Pi, aprenderas que os threads são o bloco de construção mais importante da tua musica. Um dos importantes trabalhos que fazem é isolar a noção de settings actuais de outros threads. O que significa isso? Bem, quando trocas de synth usando o use_synth apenas estás a mudar de sintetizador na thread actual - nenhum outro thread terá a mudança de sintetizador. Vamos ver isto em pratica:

play 50
sleep 1
in_thread do
  use_synth :tb303
  play 50
end
sleep 1
play 50

Repara como o som do meio foi diferente dos restantes? A declaração use_synth apenas afectou o thread onde estava e não o thread principal exterior que corre.

Herança

Quando crias um novo thread com in_thread, o novo thread irá automaticamente herdar todas as settings actuais do thread corrente. Vamos ver isso:

use_synth :tb303
play 50
sleep 1
in_thread do
  play 55
end

Repara como a segunda nota foi tocada com o sintetizador :tb303 apesar de ter sido tocada de uma thread separada? Todas as settings modificadas com as várias funções use_* irão se comportar da mesma maneira.

Quando os threads são criados eles herdam todas as settings dos seus pais mas não passam para eles qualquer mudança que tenham.

Nomeando threads

Finalmente, podemos dar nomes aos nossos threads:

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

Vê o painel de registro quando corres este código. Vê como o regsitro reporta o nome do thread na mensagem?

[Run 36, Time 4.0, Thread :bass]
 |- synth :prophet, {release: 0.6, note: 47}

Apenas é permitido um Thread por nome

Uma ultima coisa sobre nomear threads é que apenas um thread de um determinado nome pode estar a correr ao mesmo tempo. Vamos explorar isto. Considera o seguinte código:

in_thread do
  loop do
    sample :loop_amen
    sleep sample_duration :loop_amen
  end
end

Vamos lá e cola isso num buffer e carrega o butão Run. Carrega-o uma série de vezes. Ouve a cacofonia de vários amen break looping fora de tempo entre eles. Ok, podes carregar o Stop agora.

Este é o comportamento que vemos sempre - se carregares o botão Run, as camadas de som sobrepõem-se às do som existente. Assim se tiveres um loop e carregares o botão Run 3 vezes, terás 3 camadas de loops a correr simultaneamente.

No entanto, com threads com nome é diferente:

in_thread(name: :amen) do
  loop do
    sample :loop_amen
    sleep sample_duration :loop_amen
  end
end

Experimenta carregar o botão Run 3 vezes com este código. Apenas irás ouvir um amen break loop. Verás também isto no registro:

==> Skipping thread creation: thread with name :amen already exists.

Sonic Pi diz que um thread com o nome :amenjá está a tocar, e assim não irá criar outro.

Este comportamento pode não ser imediatamente útil para ti por enquanto - mas será muito quando começarmos com o live code…