The goal of this lab is to control Faust synthesizers with sensors connected to the Teensy micro-controller (which is part of your lab kit). Various mapping strategies are also introduced.
void setup() {
Serial.begin(9600); // initializing serial port
}
void loop() {
int sensorValue = analogRead(A0); // retrieving sensor value on Analog pin 0
Serial.println(sensorValue);
delay(30); // wait for 30ms
}
Bounce.h
must be included in your program:#include <Bounce.h>
usbMIDI.sendControlChange()
can be used to send MIDI control events (CC). This function takes three arguments: the MIDI CC number, the value to send, and the MIDI channel number (0-15). The first two values should be 7 bits integers (0-127), per the MIDI standard.sensorValue
must be scaled down to fit within this range:int midiValue = sensorValue*127/1024;
The previous code can now be updated to send MIDI values instead of plotting sensor data to the terminal:
#include <Bounce.h>
void setup() {
}
void loop() {
int sensorValue = analogRead(A0); // retrieving sensor value on Analog pin 0
int midiCC = 0;
int midiValue = sensorValue*127/1024; // value between 0-127
int midiChannel = 0; // doesn't really matter
usbMIDI.sendControlChange(midiCC,midiValue,midiChannel); // sending on CC 0
delay(30); // wait for 30ms
}
midiChannel
doesn’t really matter as it will be ignored by Faust.Tools/USB Type/MIDI
.import("stdfaust.lib");
freq = hslider("freq[midi: ctrl 0]",300,50,2000,0.01) : si.smoo;
process = os.sawtooth(freq);
freq
parameter is being controlled here by MIDI CC 0. Since we’re sending control messages from the Teensy on that CC number, freq
should now be controlled by the photoresistor. Try it!usbMIDI.sendControlChange
messages can be sent in parallel to control several parameters of the synth at the same time.The goal of this section is to make an instrument with the following behavior:
First, add a button to your circuit. The buttons provided in your kits have 4 pins (2 sets of 2 pins connected to each other). Place the button as follows on the breadboard:
Then connect one of the 2 pins to power and the other pin to the digital input 0 of your Teensy. You should add a pull-down resistor to this pin too. Note that an analog input could have been used as in the previous examples, but since we’re retrieving a discrete signal it doesn’t really make sense.
On the Teensy side, run the following program:
#include <Bounce.h>
int notes[] = {60,60,60,62,64,62,60,64,62,62,60}; // the melody
int cnt = 0; // index to loop through the notes
bool change = true; // did the note status changed?
bool on = false; // what is the note status?
void setup() {
pinMode(0, INPUT); // configuring digital pin 0 as an input
}
void loop() {
if (digitalRead(0)) { // button is pressed
if(!on) change = true; // did the status of the button changed
on = true;
}
else {
if(on) change = true; // did the status of the button change?
on = false;
}
if(change){ // if the status of the button changed
if(on){ // if the button is pressed
usbMIDI.sendControlChange(0,127,0); // set "gate" to 1
usbMIDI.sendControlChange(1,notes[cnt%11],0); // set "pitch"
cnt++; // next note
}
else{
usbMIDI.sendControlChange(0,0,0); // set "gate" to 0
}
change = false; // status changed
}
}
Notes are saved in an integer array as MIDI note numbers. When digital pin 0 receives a signal, the on
variable is set to true
and vice versa. The change
variable is used to make sure that we only send messages when necessary. This allows us to get rid of the delay()
in our program since there’s no risk the overload the MIDI bus in this case.
A Faust program compatible with this system could look like that:
import("stdfaust.lib");
freq = hslider("pitch[midi: ctrl 1]",0,0,127,1) : ba.midikey2hz;
gate = hslider("gate[midi: ctrl 0]",0,0,127,1)>0;
process = os.sawtooth(freq)*gate;
freq
is computed by retrieving MIDI note numbers and converting them to frequencies using ba.midikey2hz
. Since the gate
parameter is retrieving a MIDI value that can be any number between 0 and 127, it is declared as a slider (as opposed to a button). However, the value of the slider is turned into a discrete value by comparing it to 0. The result of this operation will be 0 when the received message is 0 and 1 in any other case.
Of course, this instrument is not polyphonic. In fact, the same result can be achieved in a simpler way by taking advantage of the polyphonic mode of the Faust online editor. This approach is presented in the following section.
Control change is not the only type of MIDI event that can be used to interact with a Faust program. In fact, a wide range of MIDI event types are supported by the Teensy (see https://www.pjrc.com/teensy/td_midi.html).
In this section, we use usbMIDI.sendNoteOn
and usbMIDI.sendNoteOff
instead of usbMIDI.sendControlChange
to trigger our Faust synthesizer.
sendNoteOn
takes 3 arguments: the MIDI note number, the velocity of the note to be played and the MIDI channel number (which doesn’t matter in our case). The Teensy program from above can be changed to reflect these changes:
#include <Bounce.h>
int notes[] = {60,60,60,62,64,62,60,64,62,62,60}; // the melody
int cnt = 0; // index to loop through the notes
bool change = true; // did the note status changed?
bool on = false; // what is the note status?
void setup() {
pinMode(0, INPUT); // configuring digital pin 0 as an input
}
void loop() {
if (digitalRead(0)) { // button is pressed
if(!on) change = true; // did the status of the button changed
on = true;
}
else {
if(on) change = true; // did the status of the button change?
on = false;
}
if(change){ // if the status of the button changed
int velocity = 127;
if(on){ // if the button is pressed
usbMIDI.sendNoteOn(notes[cnt%11],velocity,0);
}
else{
usbMIDI.sendNoteOff(notes[cnt%11],velocity,0);
cnt++; // next note
}
change = false; // status changed
}
}
On the Faust side, standard MIDI parameter names can be used to map the note-on and note-off events to the synthesizer (as we did lab 1):
import("stdfaust.lib");
freq = hslider("freq",300,50,2000,0.01);
gain = hslider("gain",0.8,0,1,0.01);
gate = button("gate");
process = os.sawtooth(freq)*gain*gate;
In that case, MIDI note numbers are automatically converted to frequencies by Faust. gain
is controlled by the value of velocity and gate
will be equal to 1 when receiving a note-on event and 0 when receiving a note-off event.
Try it out in the Faust online editor (and don’t forget to activate the polyphony mode)! The result should be the same as in the previous section.
Keep in mind that in both cases, continuous control change events (usbMIDI.sendControlChange
) can be sent in parallel of this to control any other parameter of the synthesizer. For example, a filter could be added here and it’s cutoff frequency could be controlled by a potentiometer or even the photoresistor.
As you probably noticed, there’s always various ways to do the same thing with this type of system. Just use the one that best serves your needs. As for the mapping, you might wonder if it’s better to do it in Faust or on the Teensy. Both ways work, however it’s usually a good practice to do as much as you can on the Teensy to save potential computation on your computer.