Tutorial 19. Randomness

In which we discuss how computers can make decisions and generate a naive random "melody".


In the world of computer music, one of our most fundamental tools is randomness. We often want to give some form of autonomy to the computational portion of our program, essentially giving the computer the option to choose some musical parameter. This is often not purely random; we like to give the computer certain rules or weightings to follow, but at the end of the day, if we want this to evaluate in a non-deterministic way, we will need a random number generator of some sort running behind the scenes. Luckily, in MEAP we have a "true" random number generator.

In actuality, it is difficult for a computer to generate a truly random number. Computation is fundamentally a deterministic process and it turns out that without external intervention, there is no algorithm that a computer could use to generate a sequence of truly random numbers. Instead, what they have are pseudorandom number generators. These are generators that are able to quickly calculate a sequence of numbers that have the properties of a random sequence:

The big problem that pseudorandom number generators have is that they will repeat exactly the same every time. If you turn on your computer and ask its pseudorandom number generator to generate a sequence of ten numbers, it will give you the same ten numbers every time, which seems like it would be a horrible problem. What we are able to do though, is give the generator a seed which will basically start the sequence at a different point, giving you a new set of numbers! You can set this seed number manually (which is how perfectly recreatable minecraft worlds are generated, for example), or you can have some physical process set the seed for you. Often times, a computer or microcontroller will have some sort of sensor that is set up to sense a physical noise process, read that sensor and use that true noise value as the seed to your pseudorandom number generator. Sometimes this is a heat sensor that tries to sense the ambient air temperature with a high degree of accuracy, inevitably resulting in a random value, as there are always slight fluctuations in air temperature. The ESP32-S3 uses one of its WiFi sensors for this purpose. It tries to detect the strength of WiFi signals it receives and, whether there is a router in range or not, the exact strength of this signal will have some random variance, so it makes a great seed our random number generator.

Fortunately, in MEAP this all happens behind the scenes and we have a few functions we can use to generate random numbers:


In this tutorial, we will be using meap.irand() to generate random melodies from a C major scale.

First let's create some global objects:

a 16-bit triangle wave oscillator whose volume is controlled by an ADSR envelope as well as an EventDelay to trigger a new note every 250ms.

EventDelay metro1;
int metro1_period = 250;  // constant period of 250 ms
mOscil oscil1(tri8192_int16_DATA);
ADSR env1;

We also want to define a set of notes for our random number generator to choose from. In this case we want it to select one of eight notes in one octave of a C major scale, so let's create an array that contains the midi note numbers of a C major scale, starting on C2.

int notes[8] = {48, 50, 52, 53, 55, 57, 59, 60};

Let's get the envelope initialized in setup()

env1.setADLevels(255, 0);  // set envelope sustain level to 0, effectively skipping sustain and release phases
env1.setTimes(1, 75, 1, 1);

Control the oscillator's volume with the envelope, and send the result to the DAC in updateAudio():

env1.update();
int64_t out_sample = oscil1.next() * env1.next(); // 16bit osc * 8 bit env = 24bits
return StereoOutput::fromNBit(24, (out_sample * meap.volume_val) >> 12, (out_sample * meap.volume_val) >> 12);

And finally, the randomness in updateControl()

if (metro1.ready()) {
    metro1.start(metro1_period);
    int note_index = meap.irand(0, 7);
    int midi_note = notes[note_index];
    oscil1.setFreq(mtof(midi_note));
    env1.noteOn();
}

Whenever our metronome ticks (every 250ms), we do the following:

  1. Re-start the metronome
  2. Generate a random number between 0 and 7 to choose which note to play from the notes[] array we defined above.
  3. Grab that note from the notes[] array as a MIDI note number.
  4. Convert that note number to a frequency in Hz with mtof() and set the oscillator to that frequency.
  5. Trigger the envelope to start


Your program is now ready to upload. You should hear a constant stream of random notes from a C major scale. Of course, there is no structure to this randomness so it sounds somewhat aimless. In further tutorials we will explore some methods of adding structure, from leveraging random distributions to more complex state-based algorithms.


FULL CODE HERE