Faust on the Teensy and Advanced Control

Generating and Using a Faust C++ Object

In order to run the examples in this lecture, you should install the Faust distribution on your system from the Faust Git Repository.

At the most fundamental level, the Faust compiler is a command line tool translating a Faust DSP object into C++ code. For example, assuming that Faust is properly installed on your system, given the following simple Faust program implementing a filtered sawtooth wave oscillator (FaustSynth.dsp):

import("stdfaust.lib");
freq = nentry("freq",200,50,1000,0.01);
gain = nentry("gain",0.5,0,1,0.01) : si.smoo;
gate = button("gate") : si.smoo;
cutoff = nentry("cutoff",10000,50,10000,0.01) : si.smoo;
process = os.sawtooth(freq)*gain*gate : fi.lowpass(3,cutoff) <: _,_;

running:

faust FaustSynth.dsp

will output the C++ code corresponding to this file in the terminal.

Faust comes with a system of C++ wrapper (called architectures in the Faust ecosystem) which can be used to customize the generated C++ code. faustMininal.h is a minimal architecture file including some C++ objects that can be used to facilitate interactions with the generated DSP:

#include <cmath>
#include <cstring>

#include "faust/gui/MapUI.h"
#include "faust/gui/meta.h"
#include "faust/dsp/dsp.h"

// BEGIN-FAUSTDSP

<<includeIntrinsic>>

<<includeclass>>

// END-FAUSTDSP

For instance, MapUI allows us to access the parameters of a Faust DSP object using the setParamValue method, etc.

To generate a C++ file using this architecture, you can run:

faust -i -a faustMinial.h FaustSynth.dsp -o FaustSynth.h

which will produce a FaustSynth.h file (feel free to click on it).

The -i inlines all the included C++ .h files in the generated file.

The faust-synth Teensy example project demonstrates how FaustSynth.h can be used. First, it is included in MyDsp.cpp and the following elements are declared in the corresponding header file:

private:
  MapUI* fUI;
  dsp* fDSP;
  float **outputs;

dsp is the actual Faust DSP, MapUI will be used to interact with it, and outputs is the multidimensional output buffer. These objects are then allocated in the constructor of MyDsp.cpp:

fDSP = new mydsp();
fDSP->init(AUDIO_SAMPLE_RATE_EXACT);
fUI = new MapUI();
fDSP->buildUserInterface(fUI);
outputs = new float*[AUDIO_OUTPUTS];
for (int channel = 0; channel < AUDIO_OUTPUTS; ++channel){
  outputs[channel] = new float[AUDIO_BLOCK_SAMPLES];
}

buildUserInterface is used to connect fUI to fDSP and then memory is allocated for the output buffer. Note that memory should be de-allocated in the destructor after this. In the update method, we just call the compute method of fDSP and then reformat the generated samples to transmit them via i2s:

void MyDsp::update(void) {
  fDSP->compute(AUDIO_BLOCK_SAMPLES,NULL,outputs);
  audio_block_t* outBlock[AUDIO_OUTPUTS];
  for (int channel = 0; channel < AUDIO_OUTPUTS; channel++) {
    outBlock[channel] = allocate();
    if (outBlock[channel]) {
      for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) {
        int16_t val = outputs[channel][i]*MULT_16;
        outBlock[channel]->data[i] = val;
      }
      transmit(outBlock[channel], channel);
      release(outBlock[channel]);
    }
  }
}

Note that outputs is used as an intermediate here and the first dimension of the array is the channel number and the second dimension the samples themselves.

The various parameters of the Faust object can then be changed just by calling the setParamValue method. The first argument of the method corresponds to the name of the parameter as specified in the Faust program:

void MyDsp::setFreq(float freq){
  fUI->setParamValue("freq",freq);
}

void MyDsp::setCutoff(float freq){
  fUI->setParamValue("cutoff",freq);
}

void MyDsp::setGate(int gate){
  fUI->setParamValue("gate",gate);
}

Better Control on the Teensy

In addition to controlling DSP parameters on the Teensy using external sensors connected to the board's GPIOs, other techniques can potentially be used. We briefly summarize this section.

MIDI

MIDI is THE standard in the world of music to control digital devices. It has been around since 1983 and even though it is very "low tech," it is still heavily used. While MIDI was traditionally transmitted over MIDI ports, USB is used nowadays to send MIDI.

USB MIDI is natively supported on the Teensy through the Teensy USB MIDI library: https://www.pjrc.com/teensy/td_midi.html. Interfacing this library with your DSP programs should be very straightforward. Please, also note that Teensys can be used to send MIDI messages over USB which means that implementing your own midi controller using a Teensy is fairly straightforward as well. If you're curious about this, you can check this page: https://ccrma.stanford.edu/courses/250a-winter-2018/labs/2/.

OSC

Open Sound Control (OSC) is a more modern communication standard used in the field of music technology. It is based on UDP which means that information can be transmitted via Ethernet or Wi-Fi. OSC uses a system of address/values to access the different parameters of a system. An OSC message can therefore look like:

/synth/freq 440

On the Teensy, dealing with OSC is a bit more tricky than MIDI because the Teensy 4.0 provided as part of your class kit don't have a built-in Ethernet port. Hence, the only way to get an Ethernet connection to the Teensy is to buy an external Ethernet adapter (that will likely connect to the Teensy through i2c, etc.). Another option is to buy a Teensy 4.1 which hosts an Ethernet chip (an Ethernet connector can just be soldered to the board).

Exercises

Faust Triangle Oscillator

The Faust libraries host a triangle wave oscillator:

os.triangle

Try to replace the sawtooth wave oscillator from the previous example by a triangle wave oscillator in Faust and run it on the Teensy.

Flanger

The Faust libraries host a flanger function:

pf.flanger_mono

Turn your Teensy into a flanger effect processor!