Sonic Pi: Randomization

Now that we know how to play notes and samples, as well as how to fine tune their playback using ADSR envelopes, it's time to jump into a fun topic: randomization. This will be of particular use when it comes to generative music production.

You would think that choosing a random note to play would be as easy as something like:

play rrand(60, 80)

At first glance, this should choose a random note somewhere between 60 and 80 each time you hit Play. But that's not the case. If you try playing that several times you'll realize you get the same note over and over. That's because Sonic Pi prioritizes consistency between machines, so if you share your code (including "random" tones), that code should produce the same music on one machine as it does on another.

So how do we generate random tones? One way to do this is by putting the code above in a loop. On each iteration of the loop, a new tone will get selected:

loop do
  play rrand(60, 80)
  sleep 0.5
end

As an aside, you need the sleep in that loop, otherwise it throws an error. This is probably predictable as you would essentially be trying to call the same loop at the exact same time an infinite number of times. That said, shortening the sleep value produces some interesting results:

loop do
  play rrand(60, 80)
  sleep 0.1
end

Another famous example of this sort of thing is the Haunted Bells example within Sonic Pi:

loop do
  sample :perc_bell, rate: rrand(0.125, 1.5)
  sleep rrand(0.2, 2)
end

This takes the basic :perc_bell sample and changes its pitch and playback length using the rate opt in conjunction with random numbers. It also passes sleep a random value.

Here's another example of using rrand to pass random values to opts:

use_synth :tech_saws

loop do
  play 60, release: 0.2, cutoff: rrand(65, 92)
  sleep 0.1
end

By the way, the nature of this randomization, where numbers are referred to as random, but are actually predictable and consistent between machines, is called pseudo-randomization.

If you don't like the random tones produced by default, for example:

5.times do
  play rrand(66, 77)
  sleep 0.2
end

...then you tell the pseudo-random numbers to change by changing the seed value. The seed value is the deterministic sequence of bits that are used to approximate randomness. You can change the seed value using use_random_seed:

use_random_seed 420
5.times do
  play rrand(66, 77)
  sleep 0.2
end

Other Randomization Approaches

rrand_i

It's worth noting that rrand picks random float values between the two values you pass it, meaning it is most likely producing microtones (i.e. 60.123834684 instead of 60).

If you want to only play whole numbers (aka "integers"), use rrand_i:

5.times do
  play rrand_i(60, 80)
  sleep 0.2
end

rand

rand lets you specify a range between 0 and the value you pass it to choose a number from. The default value is 1. This range includes 0 but excludes 1 (or the specified value).

loop do
  play rand(66)
  sleep 1
end

This is useful for amp, attack_level, sustain_level, or release_level.

loop do
  play 60, amp: rand, attack_level: rand, sustain_level: rand, release_level: rand
  sleep 1
end

It also works with negative values:

loop do
  play 60, pan: rand(-1)
  sleep 1
end

rand_i

rand_i behaves the same as rand, but for whole integers.

choose

choose allows you to choose an item randomly from a list:

loop do
  play choose([60, 65, 72, 76, 80])
  sleep 1
end

This would be helpful for specifying a key or arpeggio to choose random notes from.

dice

You can use dice to simulate rolling a dice with the number of sides you specify. dice(6) is the same as rolling a traditional, 6-sided dice; dice(20) is the same as rolling a d20.

This is basically the same as rand_i, but instead of the lower bound being 0, the lower bound is 1.

loop do
  play dice(80)
  sleep 1
end

All of the above could be used for some fun generative sonic creations!