Funciones

Cuando comiences a escribir mucho código, desearás encontrar una manera de organizar y estructurar todo para hacerlo más entendible. Las funciones permiten hacer exactamente eso, permitiéndonos darle nombre a muchas partes de nuestro código. Veamos:

Definiendo Funciones

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

Aquí definimos una nueva función llamada foo. Lo hacemos con nuestro viejo amigo el bloque de do/end y la palabra mágica define seguidos del nombre que queremos darle a nuestra función. No necesitábamos llamarla foo, podríamos haberla llamado cualquier cosa que quisiésemos, tales como bar, baz o idealmente algo significativo para tí como main_section o lead_riff.

Recuerda anteponer dos puntos : al nombre de la función que defines.

Llamando las Funciones

Una vez hemos definido nuestra función, podemos llamarla simplemente escribiendo su nombre:

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

Incluso podemos usar foo dentro de bloques de iteración o cualquier lugar donde hayamos escrito play o sample. Esto nos da una fantástica manera de expresar y crear nuevas palabras significativas a usar en nuestras composiciones.

Las funciones permanecen en memoria

Hasta ahora, cada vez que presionamos el botón de Ejecutar, Sonic Pi ha comenzado de cero. Sólo conoce lo que está en el buffer actual. No puedes referenciar código en otro buffer o hilo. sin embargo, las funciones cambian eso. Cuando defines una función, Sonic Pi recuerda. Probemos borrando todo el código en tu buffer y reemplazandolo por:

foo

Presiona el botón de Ejecutar y escucha. ¿Dónde se fue el código?¿cómo supo Sonic Pi qué tocar? Sonic Pi recordó tu función, inclusive cuando la borraste del buffer. Esta conducta sólo funciona con las funciones creadas con define (y defonce).

Funciones parametrizadas

Quizás te interese saber que al igual que podías pasar valores mínimos y máximos con rrand, también puedes enseñar a tus funciones a aceptar argumentos. Miremos:

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

Esto no es tan excitante, pero ilustra el punto. Creamos nuestra propia versión de play llamada my_player y la cual está parametrizada.

Los parámetros deben ir después del do y define en el bloque do/end, rodeado de postes verticales | y separados por comas ,. Puedes usar cualquier palabra para los nombres de los parámetros.

Lo mágico sucede dentro del bloque do/end del define . Puedes usar nombres de parámetros como si fueran valores reales. En este ejemplo estoy tocando la nota n. Puedes considerar los parámetros como una especie de promesa de que cuando el código se ejecute, ellos serán reemplazados por los valores. Esto lo haces al pasar un parámetro a la función, cuando la llamas. Yo lo hago con my_player 80 para tocar la nota 80. Dentro de la definición de la función n se reemplaza con 80, así play n se convierte en play 80. Cuando la llamo otra vez con my_player 90, n es reemplazada por 90, así que play n se convierte en play 90.

Veamos un ejemplo más interesante:

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

Aquí usé repeats como si fuera un número para la línea de repeats.times do. También usé root como si fuera un nombre de nota en mi llamada a play.

¡Nota cómo podemos utilizar algo muy expresivo y fácil de leer sólo con mover mucho de nuestra lógica en el código!