Sincronización de hilos

Cuando estés lo suficientemente avanzado en live-coding con un número de funciones e hilos simultáneamente, notarás que es muy fácil cometer un error en uno de los hilos y eso lo mata. Eso no es gran cosa, porque puedes fácilmente recomenzar el hilo con apretar Run. Sin embargo, cuando recomiences el hilo estará out of time con los hilos actuales.

Tiempo heredado

Como discutíamos anteriormente, nuevos hilos creados con in_thread heredan todos los seteos del hilo padre. Esto incluye el tiempo actual, lo que significa que los hilos estarán siempre en el tiempo con los demás cuando comenzados simultáneamente.

Sin embargo, cuando comienzas un hilo aparte, comienza con su propio tiempo que difícilmente estará sincronizado con alguno de los otros que estén siendo corridos.

Cue y Sync (funciones)

Sonic Pi provee una solución a este problema con las funciones cue y sync.

cue permite enviar mensajes de pulso a todos los otros hilos, los cuales, en principio no están interesados e ignoran estos mensajes. Sin embargo. puedes registrar fácilmente su interés con la función sync.

Lo importante de notar es que sync es similar a sleep en que para el hilo actual por un tiempo. Sin embargo, con sleep tu especificas cuánto tiempo va a esperar, en cambio con sync no sabes cuánto tiempo esperará - ya que sync espera por el siguiente cue de otro hilo.

Exploremos esto en más detalle:

in_thread do
  loop do
    cue :tick
    sleep 1
  end
end
in_thread do
  loop do
    sync :tick
    sample :drum_heavy_kick
  end
end

Aquí tenemos dos hilos - uno actuando como un metrónomo, no haciendo sonidos sino que enviando :tick mensajes de pulso. El segundo hilo está sincronizandose con los mensajes de tick y cuando recibe uno, hereda el tiempo del hilo de cue y continúa tocando.

El resultado es que escuchamos el sample de :drum_heavy_kick exactamente cuando el otro hilo envía el mensaje :tick, aunque ambos hilos no comenzaron su ejecución al mismo tiempo:

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

Esa llamada sleep típicamente haría que el segundo hilo vaya fuera de fase con el primero. Sin embargo, por estar usando cue y sync, sincronizamos ambos hilos automáticamente.

Nombres de Cue

Eres libre de utilizar cualquier nombre que quieras a los mensajes de cue - no sólo :tick. Sólo debes asegurarte que los otros hilos estén sync sincronizándose en el nombre correctoe - de otro modo se quedarán esperando por siempre (o hasta presionar el botón de Parar)

Juguemos con unos cuantos nombres para cue:

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

Aquí tenemos un cue principal bucleado, el cual envía aleatoriamente uno de los nombres de pulso :foo, :bar o :baz. Después tenemos también tres bucles de hilos sincronizándose entre ellos independientemente y tocando un sampleo diferente. El efecto es que escuchamos un sonido cada 0.5 pulsos que cada uno de los hilos está aleatoriamente sincronizándose sync con el hilo de cue y toca su sampleo.

Por supuesto que esto también funciona si ordenas los hilos en reversa, ya que los hilos sync siempre esperarán al siguiente cue.