Skip to content

DatanoiseTV/VultTeensyBoilerplate

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vult Teensy 3.1/3.2 Boilerplate

This is a template for using Vult, a transcompiler well suited to write high-performance DSP code, on the Teensy 3.1/3.2 as a base for:

  • Musical Instruments (e.g. Synthesizers)
  • Audio Effect Processors
  • Sound Generators
  • Other Audio DSP tasks

Vult itself describes as:

Vult is a simple and powerful language to program high-performance algorithms
that may run in small microprocessors or microcontrollers. Vult is specially
useful when programming Digital Signal Processing (DSP) algorithms like
audio effects or synthesizers.

Getting Started

The core being used as "host" is the Teensy Audio library. Core generated by Vult in our setup can be used as an "Audio"-Object in Teensyduino.

This is our src/main.cpp, which contains the C++ code base:

#include <ArduinoGimmicks.h>
#include <Audio.h>
#include <SPI.h>
#include <Wire.h>

#include "VultGen.h"

AudioOutputI2S           i2s1;           //xy=565,241
AudioControlSGTL5000     audioShield;     //xy=586,175
VultGen                  voice;
AudioConnection          patchCord1(voice, 0, i2s1, 0);
AudioConnection          patchCord2(voice, 0, i2s1, 1);

// Handles the ontrol change
void OnControlChange(byte channel, byte control, byte value){
  voice.controlChange(control,value);
}

// Handles note on events
void OnNoteOn(byte channel, byte note, byte velocity){
  voice.noteOn(note,velocity);
}
// Handles note on events
void OnNoteOff(byte channel, byte note, byte velocity){
  voice.noteOff(note,velocity);
}

void setup() {
  AudioMemory(8);
  audioShield.enable();
  audioShield.volume(0.7);

  voice.begin();

  usbMIDI.setHandleControlChange(OnControlChange);
  usbMIDI.setHandleNoteOn(OnNoteOn);
  usbMIDI.setHandleNoteOff(OnNoteOff);

}

void loop() {
  usbMIDI.read();
}

and in src/phasedist.vult lies your Vult DSP code:

/*
   = A simple synthesizer with one LFO and a Delay effect
  CC30 - Volume
  CC31 - Detune/Resonance
  CC32 - LFO rate
  CC33 - LFO amount (bipolar)
  CC34 - Delay time
  CC35 - Delay feedback
*/

// Used to soften the transitions of controls
fun smooth(input:real) : real {
   mem x;
   x = x+(input-x)*0.005;
   return x;
}

// Returns true every time the input value changes
fun change(x:real):bool {
    mem pre_x;
    val v:bool = pre_x<>x;
    pre_x = x;
    return v;
}

// Returns true if the value changes from 0 to anything
fun edge(x:bool):bool {
    mem pre_x;
    val v:bool = (pre_x<>x) && (pre_x==false);
    pre_x = x;
    return v;
}

// Returns true every 'n' calls
fun each(n:int) : bool {
   mem count;
   val ret = (count == 0);
   count = (count + 1) % n;
   return ret;
}

// Converts the MIDI note to increment rate at a 44100 sample rate
fun pitchToRate(d) return 8.1758*exp(0.0577623*d)/44100.0;

fun phasor(pitch:real,reset:bool){
    mem rate,phase;
    if(change(pitch))
        rate = pitchToRate(pitch);
    phase = if reset then 0.0 else (phase + rate) % 1.0;
    return phase;
}

// A simple LFO with reset signal
fun lfo(f:real,gate:bool) : real {
    mem phase;
    val rate = f * 10.0/44100.0;
    if(edge(gate)) phase = 0.0;
    phase = phase + rate;
    if(phase>1.0) phase = phase-1.0;
    return sin(phase*2.0*3.141592653589793)-0.5;
}

// Implements the resonant filter simulation as shown in
// http://en.wikipedia.org/wiki/Phase_distortion_synthesis
fun phd_osc(pitch:real,detune:real) : real {
   mem pre_phase1; // used to detect when the phase wrapps from 1 to 0
   val phase1 = phasor(pitch,false);
   val comp   = 1.0 - phase1;
   val reset  = (pre_phase1 - phase1) > 0.5;
   pre_phase1 = phase1;
   val phase2 = phasor(pitch+smooth(detune)*32.0,reset);
   val sine  = sin(2.0*3.14159265359*phase2);
   return sine*comp;
}

// Simple delay.
fun delay(x:real, time:real, feedback:real) : real {
   mem buffer : array(real,1500);
   mem write_pos;
   // Constraints the parameter values
   time     = clip(time,0.0,1.0);
   feedback = clip(feedback,0.0,1.0);
   // Gets the position in the buffer to read
   val index_r  = real(size(buffer)) * time;
   val index_i  = int(floor(index_r));
   val delta    = write_pos - index_i;
   val read_pos = if delta < 0 then size(buffer)+delta else delta;
   // Gets the decimal part of the position
   val decimal  = index_r - real(index_i);
   // Reads the values in the buffer
   val x1 = get(buffer,read_pos);
   val x2 = get(buffer,(read_pos+1) % size(buffer));
   // Interpolates the value
   val ret = (x2-x1)*decimal + x1;
   // Write the data to the buffer
   write_pos = (write_pos+1) % size(buffer);
   _ = set(buffer,write_pos,clip(x+feedback*ret,-1.0,1.0));
   return ret;
}

/* These three functions handle midi on/off events in order to behave
 * like a monophonic sinthesizer that can hold 4 notes */

// Activates a note and returns the current note value
fun mono_noteOn(n:int){
   mem count,pre;
   mem notes : array(int,4);
   // Do not add more that the size of array
   if(count < size(notes)) {
     _ = set(notes,count,n);
     pre = n;
     if(count < size(notes)) count = count + 1;
   }
   return pre;
}

// Deactivates a note and returns the following note value;
and mono_noteOff(n:int){
   mem count,pre;
   mem notes : array(int,4);
   val found = false;
   val pos;
   val i = 0;
   // if there are no notes, no dot do anything
   if(count == 0)
      return pre;
   // Finds the location of the note
   while(i < size(notes) && not(found)){
      if(get(notes,i) == n) {
         pos = i;
         found = true;
      }
      i = i + 1;
   }
   // if the note was found moves all the notes one location
   if(found) {
      val k = pos + 1;
      while(k < size(notes)) {
         _ = set(notes,k-1,get(notes,k));
         k = k + 1;
      }
      // If found, decrease the number of active notes
      if(found && count>0) {
         count = count - 1;
         pre = get(notes,count - 1);
      }
   }
   return pre;
}

// Returns 1 if any note is active
and mono_isGateOn() {
   mem count;
   return count > 0;
}

// Main processing function
fun process(input:real){
   mem volume,detune; // values set in 'controlChange'
   mem pitch;
   mem lfo_rate,lfo_amt;
   mem time, feedback;

   val gate = notes:mono_isGateOn();
   // creates one LFO
   val lfo_val = lfo(lfo_rate,gate)*lfo_amt;
   // creates one oscillator
   val o1 = phd_osc(pitch,detune+lfo_val);
   // gets the amplification by using a low-pass on the gate
   val amp = smooth(if gate then 1.0 else 0.0);
   val osc_out = o1 * amp;
   val delay_out = delay(osc_out,smooth(time),smooth(feedback));
   return volume * (osc_out+delay_out)  /2.0;
}

// Called when a note On is received
and noteOn(note:int,velocity:int){
   mem pitch = real(notes:mono_noteOn(note));
}

// Called when a note Off is received
and noteOff(note){
   mem pitch = real(notes:mono_noteOff(note));
}

// Called when a control changes
and controlChange(control,value){
   mem volume;
   mem detune;
   mem lfo_rate, lfo_amt;
   mem time, feedback;
   // Control 30 defines the volume
   if(control==30) volume = value/127.0;
   if(control==31) detune = value/127.0;
   if(control==32) lfo_rate = value/127.0;
   if(control==33) lfo_amt = 2.0*((real(value)/127.)-0.5);
   if(control==34) time = value/127.0;
   if(control==35) feedback = value/127.0;
}

// Called on initialization to define initial values
and default(){
   mem volume = 1.0;
   mem pitch = 45.0;
   mem detune = 0.8;
   mem lfo_rate = 0.07;
   mem lfo_amt = -0.8;
   mem time = 0.5;
   mem feedback = 0.5;
}

This will generate the files VultGen.cpp and VultGen.h containing the class VultGen.

This example requires a Teensy board 3.1 or 3.2 in addition to the Audio Adaptor Board. You have to configure the Teensy speed to 96 Mz for better performance. The USB type should be set to MIDI in order to receive messages. The available messages are:

MIDI note on/off in any channel to control the pitch Control Change (CC31) to control the detune parameter of the oscillator The source of the oscillator is the file phasedist.vult. In order to modify or regenerate the code you need to have the development version of Vult (https://github.com/modlfo/vult).

About

A DSP boilerplate using Vult for Teensy 3.1/3.2

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published