Tutorial 01. Program structure and audio output
There are several functions that form the skeleton of any MEAP program. The first set of tutorials are designed to get you familiar with this structure that you will be building your program around.
Like many computer music programs, MEAP separates events into two categories:
- Audio rate events: Processes that actually generate or modify audio, such as an oscillator or a filter affecting that oscillator's output. These processes need to be calculated at a rate in the range of human hearing. MEAP's default sampling rate (which is the rate at which these audio rate processes are updated) is 32768 Hz. This allows frequencies up to 16,384 to be generated without aliasing. This is a bit lower than sampling rates you may be used to but in most cases you won't notice it!
- Control rate events: Events that don't need to be updated as often as audio rate events, such as an LFO modulating the cutoff frequency of a filter. In most cases, our ears won't be able to tell the difference between that LFO being updated 32768 times per second or 128 times per second, so we can save some computational resources by updating it less often. MEAP's default control rate is 128 Hz, which should work fine for most use cases.
When working with a microcontroller, it is important to understand that it won't be able to handle everything you throw at it the way a modern laptop or desktop computer might and we need to do our best to not ask too much of it. A big part of this is not asking it to calculate things more often than we need them. Running as many events as possible at control rate rather than audio rate will help ease the load!
. . . . . . .
The following functions occur in every MEAP program and are the main places you will be adding code.
AudioOutput_t updateAudio()
updateAudio() runs at the sampling rate. It is in charge of generating new samples of audio and sending them to the output DAC. This is where most of your audio generation code will live; such as grabbing samples from oscillators or processing them with filters or effects.
void updateControl()
updateControl() runs at the control rate. This is where the brains of your program go. Changing the frequency of oscillators, running compositional structures, etc.
void setup()
setup() is executed once each time your board is powered up or reset. Code placed in here is typically used for initialization; for example setting the initial frequency of an oscillator, the shape of an envelope or the starting state of a generative algorithm.
void loop()
loop() runs repeatedly at a high speed between calls to updateControl() and updateAudio(). It handles some behind the scenes parts of the audio engine, and generally you should avoid adding extra code to this section as too many calculations may disrupt your sampling rate.
void updateTouch()
updateTouch() is automatically called whenever one of the touch pads is pressed or released. This is where you handle anything you may want to be controlled by the touch pads.
void updateDip()
updateDip() is automatically called whenever one of the DIP switches is toggled up or down. This is where you handle anything you may want to be controlled by the DIP switches.
. . . . . . .
So let's write our first MEAP program!
-
Start by opening
MEAP_BASIC_TEMPLATEwithin the0_Testing_And_Templatesfolder of the MEAP library. This template is a good starting point for any MEAP program as it includes all of the code blocks you will typically use.You can find this template:
- through the Arduino IDE's
examples menu:
File > examples > Meap-main > 0_Testing_And_Templates > MEAP_BASIC_TEMPLATE > MEAP_BASIC_TEMPLATE.ino - or on the MEAP GitHub
Create a new Arduino sketch by pressing CMD+N and copy all of the template code into it.
- through the Arduino IDE's
examples menu:
Most of the audio functions we will be using to begin with come from classes in the Mozzi library which are all extensively documented. For this tutorial, we will be using the mOscil class (which is based on Mozzi's Oscil class, with a few minor improvements) which implements a basic wavetable-based oscillator. Before we can use the class, however, we will need to choose a wavetable, which will determine the waveform of our oscillator. The MEAP library includes a number of wavetables which can be found in the
tablesdirectory of the MEAP library folder. In this example we will be using thesin8192_int16wavetable.To include this wavetable, add the following line to your global variable section (beneath the line that says
// ---------- YOUR GLOBAL VARIABLES BELOW ----------)#include<tables/sin8192_int16.h>Now that we have included the wavetable in our sketch, we can create an oscillator that uses it. Add the following line to your global variable section as well:
mOscil<sin8192_int16_NUM_CELLS, AUDIO_RATE, int16_t> my_sine(sin8192_int16_DATA);This line:
- creates an oscillator object:
mOscil - tells the object how many samples are in the
wavetable:
sin8192_int16_NUM_CELLS - specifies that the oscillator will be an audio rate
oscillator (rather than a control rate LFO):
AUDIO_RATE - specifies that the wavetable contains 16-bit
samples:
int16_t - names the oscillator object:
my_sine - and finally points it to the
wavetable we
included:
sin8192_int16_DATA
- creates an oscillator object:
In the setup() function, we want to specify the frequency of this oscillator. Beneath the
// ---------- YOUR SETUP CODE BELOW ----------line, add the following:my_sine.setFreq(220);This will tell the oscillator to continuously cycle at 220Hz (an A3 in 12-tone equal temperament) until it is told to change to a different frequency.
Now we just need to connect this oscillator to the audio output.
In the updateAudio() function, rather than setting the variable
out_sampleequal to zero we want to set it to the output ofmy_sineas follows.int out_sample = my_sine.next();Finally we need to tell the output what bit-rate of audio we are dealing with. We defined a 16-bit oscillator (which has numbers oscillating between -32768 and 32767) and by default, the output expects 8-bit audio (numbers between -128 and 127), so if we leave this as is, we'll hear a lot of distortion as the output is given numbers outside the range it is expecting.
Change the last line of the updateAudio() function to the following:
return StereoOutput::fromNBit(16, (out_sample * meap.volume_val) >> 12, (out_sample * meap.volume_val) >> 12);This will allow the output to accept numbers up to a 16-bit range. We'll look more at this concept of bit-rate being tied to volume in future tutorials, but for now just know that the bitrate specified in our output function needs to be at least as high as the loudest audio we send to it to avoid distortion.
Upload the code to your MEAP
Enable the speaker (turn the speaker switch, located near the potentiometers, to the right) or plug in headphones.
Turn up the volume knob (the center of the three knobs)
You should hear a constant sine tone.
- Save this code as it will be the starting point of the next couple tutorials.
sin8192_int16_NUM_CELLS and
sin8192_int16_DATA are macros which are defined in
sin8192_int16.h.
Every table will have macros
named in this way.