Tutorial 02. Potentiometer control
MEAP has a few built-in devices for user input. The first ones we will look at are the two general purpose potentiometers (if you're unfamiliar with this term it is engineering-speak for a knob). They lie on either side of the volume knob with Potentiometer 0 being to its left and Potentiometer 1 being to its right. |
As you move through the tutorials and your own compositional journey you may end up using these potentiometers to control abstract features of a composition such as timbres or the amount of entropy, but for now we are going to use it as a frequency control for our oscillator.
- We will begin with the code we ended with in Tutorial
01 where we created a sine wave oscillator and
connected it to the output. The two things we need to do
in this tutorial are:
- Read data from the potentiometer
- Use that data to control our oscillator's frequency.
- Getting data from the potentiometer is easy, the
meap.readInputs()
function called in updateControl() continuously reads all of our inputs including the potentiometers. It stores the potentiometers' positions in an array calledmeap.pot_vals
where the position of Potentiometer 0 is stored at index 0 and Potentiometer 1's potition is stored at index 1. The position is stored as an integer between 0 and 4095 (which makes it an unsigned 12-bit integer if you want to think of it that way), with 0 indicating the potentiometer is turned fully counter-clockwise and 4095 indicating it is turned fully clockwise. - Control rate is plenty fast for user inputs like this so
let's add the following two lines of code to
updateControl()
.float my_freq = map(meap.pot_vals[0], 0, 4095, 20, 2000);
my_sine.setFreq(my_freq);
In the first line, we grab the current position of the potentiometer using meap.pot_vals[0]. This value could be anywhere from 0 to 4095 and if we used that variable directly to control the oscillator's frequency, we could end up with an oscillator playing frequencies below 20Hz (which is the approximate lower limit of human hearing). To avoid this, we want to scale this number in between 0-4095 to a new range. The Arduino library contains a convenient function called
map()
which will help us do this.map()
takes a number in one range and transposes it to another, provided by the programmer. I chose our output range somewhat arbitrarily to be 100-2000. As an example, if the position of our potentiometer is fully clockwise, we will get a reading from it of 4095 and themap()
function will rescale it to be 2000.map()
takes 5 arguments.- The first is the number that you want to scale
- The next pair are the minimum and maximum values that this number could be.
- The final pair are the minimum and maximum values that you want to scale this number to.
Once we have that value scaled, in the second line we use it to control the frequency of our oscillator, just like we had done in
setup()
- Upload your code, turn potentiometer 0 and you should hear the pitch of the sine track it.
FULL CODE BELOW
/*
Potentiometer 0 controls pitch of sine oscillator.
*/
#define CONTROL_RATE 128 // Hz, powers of 2 are most reliable
#include <Meap.h> // MEAP library, includes all dependent libraries, including all Mozzi modules
Meap meap; // creates MEAP object to handle inputs and other MEAP library functions
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); // defines MIDI in/out ports
// ---------- YOUR GLOBAL VARIABLES BELOW ----------
#include <tables/sin8192_int8.h> // loads sine wavetable
mOscil<8192, AUDIO_RATE> my_sine(SIN8192_DATA);
void setup() {
Serial.begin(115200); // begins Serial communication with computer
Serial1.begin(31250, SERIAL_8N1, 43, 44); // sets up MIDI: baud rate, serial mode, rx pin, tx pin
startMozzi(CONTROL_RATE); // starts Mozzi engine with control rate defined above
meap.begin(); // sets up MEAP object
// ---------- YOUR SETUP CODE BELOW ----------
my_sine.setFreq(220);
}
void loop() {
audioHook(); // handles Mozzi audio generation behind the scenes
}
/** Called automatically at rate specified by CONTROL_RATE macro, most of your mode should live in here
*/
void updateControl() {
meap.readInputs();
// ---------- YOUR updateControl CODE BELOW ----------
my_sine.setFreq(map(meap.pot_vals[0], 0, 4095, 100, 2000));
}
/** Called automatically at rate specified by AUDIO_RATE macro, for calculating samples sent to DAC, too much code in here can disrupt your output
*/
AudioOutput_t updateAudio() {
int out_sample = my_sine.next();
return StereoOutput::fromNBit(8, (out_sample * meap.volume_val)>>12, (out_sample * meap.volume_val)>>12);
}
/**
* Runs whenever a touch pad is pressed or released
*
* int number: the number (0-7) of the pad that was pressed
* bool pressed: true indicates pad was pressed, false indicates it was released
*/
void updateTouch(int number, bool pressed) {
if (pressed) { // Any pad pressed
} else { // Any pad released
}
switch (number) {
case 0:
if (pressed) { // Pad 0 pressed
Serial.println("t0 pressed ");
} else { // Pad 0 released
Serial.println("t0 released");
}
break;
case 1:
if (pressed) { // Pad 1 pressed
Serial.println("t1 pressed");
} else { // Pad 1 released
Serial.println("t1 released");
}
break;
case 2:
if (pressed) { // Pad 2 pressed
Serial.println("t2 pressed");
} else { // Pad 2 released
Serial.println("t2 released");
}
break;
case 3:
if (pressed) { // Pad 3 pressed
Serial.println("t3 pressed");
} else { // Pad 3 released
Serial.println("t3 released");
}
break;
case 4:
if (pressed) { // Pad 4 pressed
Serial.println("t4 pressed");
} else { // Pad 4 released
Serial.println("t4 released");
}
break;
case 5:
if (pressed) { // Pad 5 pressed
Serial.println("t5 pressed");
} else { // Pad 5 released
Serial.println("t5 released");
}
break;
case 6:
if (pressed) { // Pad 6 pressed
Serial.println("t6 pressed");
} else { // Pad 6 released
Serial.println("t6 released");
}
break;
case 7:
if (pressed) { // Pad 7 pressed
Serial.println("t7 pressed");
} else { // Pad 7 released
Serial.println("t7 released");
}
break;
}
}
/**
* Runs whenever a DIP switch is toggled
*
* int number: the number (0-7) of the switch that was toggled
* bool up: true indicated switch was toggled up, false indicates switch was toggled
*/
void updateDip(int number, bool up) {
if (up) { // Any DIP toggled up
} else { //Any DIP toggled down
}
switch (number) {
case 0:
if (up) { // DIP 0 up
Serial.println("d0 up");
} else { // DIP 0 down
Serial.println("d0 down");
}
break;
case 1:
if (up) { // DIP 1 up
Serial.println("d1 up");
} else { // DIP 1 down
Serial.println("d1 down");
}
break;
case 2:
if (up) { // DIP 2 up
Serial.println("d2 up");
} else { // DIP 2 down
Serial.println("d2 down");
}
break;
case 3:
if (up) { // DIP 3 up
Serial.println("d3 up");
} else { // DIP 3 down
Serial.println("d3 down");
}
break;
case 4:
if (up) { // DIP 4 up
Serial.println("d4 up");
} else { // DIP 4 down
Serial.println("d4 down");
}
break;
case 5:
if (up) { // DIP 5 up
Serial.println("d5 up");
} else { // DIP 5 down
Serial.println("d5 down");
}
break;
case 6:
if (up) { // DIP 6 up
Serial.println("d6 up");
} else { // DIP 6 down
Serial.println("d6 down");
}
break;
case 7:
if (up) { // DIP 7 up
Serial.println("d7 up");
} else { // DIP 7 down
Serial.println("d7 down");
}
break;
}
}