Funções

Quando começares a escrever muito código, podes querer encontrar uma maneira de organizar e estruturar as coisas que forma elegante e de fácil compreensão. Funções são maneiras muito poderosas de conseguir isso. Elas dão-nos a habilidade de dar nome a uma serie de código. Vamos dar uma olhada.

Definindo funções

define :foo do
  play 50
  sleep 1
  play 55
  sleep 2
end

Aqui definimos uma nova função chamada foo. Fazemos isso com o nosso amigo bloco do/end e a palavra magica define seguida pelo nome que queremos dar à nossa função. Não temos que a chamar foo, podemos chamar-lhe o que quisermos como bar, baz ou idealmente algo com sentido para ti como secção_principalou lead_riff.

Lembra-te de acrescentar dois pontos : ao nome da tua função quando a definires.

Chamando funções

Depois de definirmos a nossa função podemos a chamar escrevendo o seu nome:

define :foo do
  play 50
  sleep 1
  play 55
  sleep 0.5
end
foo
sleep 1
2.times do
  foo
end

Até podemos usar foo dentro de um bloco de iteração ou em qualquer lado que tenhamos escrito play ou sample. Isto dá-nos uma excelente maneira de nos expressar e cria palavras significativas para usar nas nossas composições.

As funções são recordadas ao longo das execuções

Até agora, cada vez que carregas no botão Run, o Sonic Pi começa completamente do zero. Ele não sabe nada excepto o que está no buffer. Não podes referir código noutro buffer ou noutra thread. No entanto as funções mudam isso. Quando defines a função, o Sonic Pi lembra-se dela. Vamos experimentar. Apaga todo o código na teu buffer e substitui por:

foo

Carrega no botão Run - ouve a tua função a tocar. Onde foi o código? Como é que o Sonic Pi sabe o que tocar? O Sonic Pi lembra-se da tua função - assim mesmo que apagues do buffer, ele lembra-se do que escreveste. Este comportamento apenas funciona com funções criadas usando o define (e defonce).

Funções parametrizadas

Podes estar interessado em saber que tal como tu podes passar valores mínimos e máximos a rrand, tu também podes ensinar as tuas funções a aceitar argumentos. Vamos dar uma olhada:

define :my_player do |n|
  play n
end
my_player 80
sleep 0.5
my_player 90

Isto não é muito excitante, mas ilustra o ponto. Criamos a nossa versão de playchamada my_playerque é parametrizada.

Os parâmetros devem vir a seguir ao do do bloco define, rodeados pela barra vertical | e separados por virgulas ,. Podes usar qualquer palavra para o nome dos parâmetros.

A magia acontece dentro do bloco define. Podes querer usar nomes de parâmetros como se fossem valores reais. Neste exemplo estou a tocar a nota n. Podes considerar que os parâmetros são uma espécie de promessa que quando o código corre serão substituídos por valores actuais. Fazes isso passando um parâmetro às função quando a chamas. Eu faço isso com my_player 80 para tocar a nota 80. Dentro da definição da função, né então substituído por 80, assim play nfica play 80. Quando a chamas novamente com my_play 90, o né substituído por 90 e play nfica play 90.

Vamos ver um exemplo mais interessante:

define :chord_player do |root, repeats| 
  repeats.times do
    play chord(root, :minor), release: 0.3
    sleep 0.5
  end
end
chord_player :e3, 2
sleep 0.5
chord_player :a3, 3
chord_player :g3, 4
sleep 0.5
chord_player :e3, 3

Onde usei repeat como se fosse um número na linha repeat.times do. Também usei root como se fosse o nome de uma nota na minha chamada a play.

Vê como podemos escrever algo muito expressivo e facil à leitura movendo muito da nossa lógica para uma função!