Lab 2: Sensors -> Faust

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.

Electronic Basics

Sensors and Teensy Basics

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
}
#include <Bounce.h>
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
}
import("stdfaust.lib");
freq = hslider("freq[midi: ctrl 0]",300,50,2000,0.01) : si.smoo;
process = os.sawtooth(freq);

Exercise: Controlling Multiple Synth Parameters

Discrete Events: 2 Approaches

“Brute Force” Approach

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.

Taking Advantage of the Faust Polyphony Support

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.

A Few More Things

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.

Assignment (Due on Jan. 23, 2019)

Submissions

  • Luigi Sambuy
  • Adison Chang
  • Justin Cheung
  • Jason Choi
  • Jatin Chowdhury
  • Marina Barbara Cottrell
  • Cole DePasquale
  • Raul Gallo Dagir
  • Elisa Lupin-Jimenez
  • Lee Marom
  • Doug Turnbull McCausland
  • Matthew Molina
  • Camille Noufi
  • Salvador Perez
  • Austen Poteet
  • Andrea Stein
  • Michael Svolos
  • Arjun Tambe
  • Ye Akira Wang
  • Shenli Yuan
  • Austin Zambito-Valente