Tracking the Beat

Last month in this series we took a deep technical dive into the randomisation system underpinning Sonic Pi. We explored how we can use it to deterministically add new levels of dynamic control over our code. This month we’re going to continue our technical dive and turn our attention to Sonic Pi’s unique tick system. By the end of this article you’ll be ticking your way through rhythms and riffs on your way to being a live coding DJ.

Beat Counting

When making music we often want to do a different thing depending on which beat it is. Sonic Pi has a special beat counting system called tick to give you precise control over when a beat actually occurs and even supports multiple beats with their own tempos.

Let’s have a play - to advance the beat we just need to call tick. Open up a fresh buffer, type in the following and hit Run:

puts tick #=> 0

This will return the current beat: 0. Notice that even if you press the Run button a few times it will always return 0. This is because each run starts a fresh beat counting from 0. However, whilst the run is still active, we can advance the beat as many times as we want:

puts tick #=> 0
puts tick #=> 1
puts tick #=> 2

Whenever you see the symbol #=> at the end of a line of code it means that that line will log the text on the right-hand-side. For example, puts foo #=> 0 means the code puts foo prints 0 to the log at that point in the program.

Checking the Beat

We’ve seen that tick does two things. It increments (adds one) and returns the current beat. Sometimes we just want to look at the current beat without having to increment it which we can do via look:

puts tick #=> 0
puts tick #=> 1
puts look #=> 1
puts look #=> 1

En este código incrementamos el beat dos veces haciendo tick y entonces llamamos look dos veces. Veremos los siguientes valores en el log: 0, 1, 1, 1. Los dos primeros ticks han devuelto 0, después 1 como esperábamos, finalmente los dos looks simplemente devolvieron el ultimo valor del beat dos veces, lo cual ha sido 1.

Anillos

So now we can advance the beat with tick and check the beat with look. What next? We need something to tick over. Sonic Pi uses rings for representing riffs, melodies and rhythms and the tick system has been specifically designed to work very closely with them. In fact, rings have their own dot version of tick which does two things. Firstly, it acts like a regular tick and increments the beat. Secondly it looks up the ring value using the beat as the index. Let’s take a look:

puts (ring :a, :b, :c).tick #=> :a

.tick is a special dot version of tick which will return the first value of the ring :a. We can grab each of the values in the ring by calling .tick multiple times:

puts (ring :a, :b, :c).tick #=> :a
puts (ring :a, :b, :c).tick #=> :b
puts (ring :a, :b, :c).tick #=> :c
puts (ring :a, :b, :c).tick #=> :a
puts look                   #=> 3

Mira el log y allí verás :a, :b, :c y de nuevo :a. Fíjate que look devuelve 3. Las llamadas a .tick funcionan como si fueran llamadas normales a tick- incrementan el beat local.

Un Arpegiador de Live Loop

El poder read viene cuando mezclas tick con rings y live_loops. Cuando están combinados tenemos todas las herramientas necesarias para construir y también entender un simple arpegiador. Solo necesitamos cuatro cosas:

Un ring que contiene las notas que queremos repetir. Una forma de incrementar y obtener el beat. La capacidad de tocar una nota en función del beat actual. Una bucle para mantener al arpegiador repitiéndose.

Estos conceptos pueden ser comprobados en el siguiente código:

notes = (ring 57, 62, 55, 59, 64)
live_loop :arp do
  use_synth :dpulse
  play notes.tick, release: 0.2
  sleep 0.125
end

Veamos cada una de estas líneas. Primero definimos nuestro ring de notas que reproduciremos continuamente. Entonces creamos un live_loop llamado :arp que hace loop a nuestro alrededor. Cada vuelta en el live_loop ponemos nuestro synth en :dpulse y entonces tocamos la siguiente nota usando.tick. Recuerda que esto incrementará nuestro contador de beats y usará el último valor del beat como el índice para nuestro ring de notas. Finalmente, esperamos un octavo de beat antes de volver a empezar el loop.

Múltiples Ritmos Simultáneos

Una cosa que hay que saber es que los ticks son locales al live_loop. Esto quiere decir que cada live_loop tiene su propio contador de beats independiente. Esto es mucho más potente que tener un metrónomo y ritmo global . Veamos como funciona esto:

notes = (ring 57, 62, 55, 59, 64)
with_fx :reverb do
  live_loop :arp do
    use_synth :dpulse
    play notes.tick + 12, release: 0.1
    sleep 0.125
  end
end
live_loop :arp2 do
  use_synth :dsaw
  play notes.tick - 12, release: 0.2
  sleep 0.75
end

Ritmos que chocan

Una causa de confusiones habituales con el sistema de tick de Sonic Pi es cuando la gente quiere hacer tick en varios rings dentro del mismo live_loop:

use_bpm 300
use_synth :blade
live_loop :foo do
  play (ring :e1, :e2, :e3).tick
  play (scale :e3, :minor_pentatonic).tick
  sleep 1
end

Even though each live_loop has its own independent beat counter, we’re calling .tick twice within the same live_loop. This means that the beat will be incremented twice every time round. This can produce some interesting polyrhythms but is often not what you want. There are two solutions to this problem. One option is to manually call tick at the start of the live_loop and then use .look to look up the current beat in each live_loop. The second solution is to pass a unique name to each call to .tick such as .tick(:foo). Sonic Pi will then create and track a separate beat counter for each named tick you use. That way you can work with as many beats as you need! See the section on named ticks in 9.4 of the built-in tutorial for more information.

Bringing it all together

Juntemos todo este conocimiento de los ticks, rings y live_loops en un ejemplo final. Como de costumbre, no consideres esto como una pieza acabada. Empieza cambiando cosas y jugando con ellos a ver en que puedes transformarlo. Nos vemos…

use_bpm 240
notes = (scale :e3, :minor_pentatonic).shuffle
live_loop :foo do
  use_synth :blade
  with_fx :reverb, reps: 8, room: 1 do
    tick
    co = (line 70, 130, steps: 32).tick(:cutoff)
    play (octs :e3, 3).look, cutoff: co, amp: 2
    play notes.look, amp: 4
    sleep 1
  end
end
live_loop :bar do
  tick
  sample :bd_ada if (spread 1, 4).look
  use_synth :tb303
  co = (line 70, 130, steps: 16).look
  r = (line 0.1, 0.5, steps: 64).mirror.look
  play notes.look, release: r, cutoff: co
  sleep 0.5
end