Hey all,
I threw together a quick and dirty polyphonic example of the synth outlined on Notes and Volts. I just frankensteined the midi synth example to the synth to demonstrate how to do so, maybe it will help someone! Certainly room for improvement, specifically with the mixer levels outputting uniformly and such. Number of voices can be expanded easily, although you will need to manually update the connections. You can control midi using the Pure Data template here: https://www.notesandvolts.com/2018/09/teensy-synth-part-8-lfo.html. If you don't have a midi controller you will also want to use something like midi ox to play notes via your keyboard.
I threw together a quick and dirty polyphonic example of the synth outlined on Notes and Volts. I just frankensteined the midi synth example to the synth to demonstrate how to do so, maybe it will help someone! Certainly room for improvement, specifically with the mixer levels outputting uniformly and such. Number of voices can be expanded easily, although you will need to manually update the connections. You can control midi using the Pure Data template here: https://www.notesandvolts.com/2018/09/teensy-synth-part-8-lfo.html. If you don't have a midi controller you will also want to use something like midi ox to play notes via your keyboard.
Code:
// Teensy-Synth Part 8
// LFO Test
// By Notes and Volts
// www.notesandvolts.com
// components from MidiSynth example
// polyphonic edition by 12ABB17
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
/*
// Left connections for importation into Audio Design Tool for visual examination. Uncommenting will break code.
AudioSynthNoisePink pink1; //xy=55,87
AudioSynthNoisePink pink2; //xy=55,212
AudioSynthNoisePink pink3; //xy=55,339
AudioSynthNoisePink pink4; //xy=55,464
AudioSynthWaveform waveform1; //xy=64,21
AudioSynthWaveform waveform2; //xy=64,54
AudioSynthWaveform waveform4; //xy=64,146
AudioSynthWaveform waveform3; //xy=64,179
AudioSynthWaveform waveform5; //xy=64,273
AudioSynthWaveform waveform6; //xy=64,306
AudioSynthWaveform waveform7; //xy=64,398
AudioSynthWaveform waveform8; //xy=64,431
AudioMixer4 mixer1; //xy=199,61
AudioMixer4 mixer2; //xy=199,185
AudioMixer4 mixer3; //xy=199,313
AudioMixer4 mixer4; //xy=199,437
AudioFilterStateVariable filter1; //xy=320,67
AudioFilterStateVariable filter3; //xy=319,319
AudioFilterStateVariable filter2; //xy=323,191
AudioFilterStateVariable filter4; //xy=323,443
AudioEffectEnvelope envelope1; //xy=460,54
AudioEffectEnvelope envelope2; //xy=460,307
AudioEffectEnvelope envelope3; //xy=461,178
AudioEffectEnvelope envelope4; //xy=461,430
AudioMixer4 mixer5; //xy=653,244
AudioOutputI2S i2s1; //xy=779,245
AudioConnection patchCord1(pink1, 0, mixer1, 2);
AudioConnection patchCord2(pink2, 0, mixer2, 2);
AudioConnection patchCord3(pink3, 0, mixer3, 2);
AudioConnection patchCord4(pink4, 0, mixer4, 2);
AudioConnection patchCord5(waveform1, 0, mixer1, 0);
AudioConnection patchCord6(waveform2, 0, mixer1, 1);
AudioConnection patchCord7(waveform4, 0, mixer2, 0);
AudioConnection patchCord8(waveform3, 0, mixer2, 1);
AudioConnection patchCord9(waveform5, 0, mixer3, 0);
AudioConnection patchCord10(waveform6, 0, mixer3, 1);
AudioConnection patchCord11(waveform7, 0, mixer4, 0);
AudioConnection patchCord12(waveform8, 0, mixer4, 1);
AudioConnection patchCord13(mixer1, 0, filter1, 0);
AudioConnection patchCord14(mixer2, 0, filter2, 0);
AudioConnection patchCord15(mixer3, 0, filter3, 0);
AudioConnection patchCord16(mixer4, 0, filter4, 0);
AudioConnection patchCord17(filter1, 0, envelope1, 0);
AudioConnection patchCord18(filter3, 0, envelope3, 0);
AudioConnection patchCord19(filter2, 0, envelope2, 0);
AudioConnection patchCord20(filter4, 0, envelope4, 0);
AudioConnection patchCord21(envelope1, 0, mixer5, 0);
AudioConnection patchCord22(envelope3, 0, mixer5, 2);
AudioConnection patchCord23(envelope2, 0, mixer5, 1);
AudioConnection patchCord24(envelope4, 0, mixer5, 3);
AudioConnection patchCord25(mixer5, 0, i2s1, 0);
AudioConnection patchCord26(mixer5, 0, i2s1, 1);
AudioControlSGTL5000 sgtl5000_1; //xy=1023,20
// GUItool: end automatically generated code
AudioSynthNoisePink noiseBank[] = {pink1, pink2, pink3, pink4};
AudioSynthWaveform oscBank1[] = {waveform1, waveform3, waveform5, waveform7};
AudioSynthWaveform oscBank2[] = {waveform2, waveform4, waveform6, waveform8};
AudioFilterStateVariable filterBank[] = {filter1, filter2, filter3, filter4};
AudioEffectEnvelope envelopeBank[] = {envelope1, envelope2, envelope3, envelope4};
AudioMixer4 oscMixerBank[] = {mixer1, mixer2, mixer3, mixer4};
*/
AudioSynthWaveform* oscBank1 = new AudioSynthWaveform[4]; //xy=653,244
AudioSynthWaveform* oscBank2 = new AudioSynthWaveform[4]; //xy=653,244
AudioSynthNoisePink* noiseBank = new AudioSynthNoisePink[4]; //xy=653,244
AudioMixer4* mixerBank = new AudioMixer4[4]; //xy=653,244
AudioEffectEnvelope* envelopeBank = new AudioEffectEnvelope[4]; //xy=653,244
AudioFilterStateVariable* filterBank = new AudioFilterStateVariable[4]; //xy=653,244
AudioMixer4 mixer5; //xy=653,244
AudioOutputI2S i2s1; //xy=779,245
AudioConnection patchCord1(noiseBank[0], 0, mixerBank[0], 2);
AudioConnection patchCord2(noiseBank[1], 0, mixerBank[1], 2);
AudioConnection patchCord3(noiseBank[2], 0, mixerBank[2], 2);
AudioConnection patchCord4(noiseBank[3], 0, mixerBank[3], 2);
AudioConnection patchCord5(oscBank1[0], 0, mixerBank[0], 0);
AudioConnection patchCord6(oscBank2[0], 0, mixerBank[0], 1);
AudioConnection patchCord7(oscBank1[1], 0, mixerBank[1], 0);
AudioConnection patchCord8(oscBank2[1], 0, mixerBank[1], 1);
AudioConnection patchCord9(oscBank1[2], 0, mixerBank[2], 0);
AudioConnection patchCord10(oscBank2[2], 0, mixerBank[2], 1);
AudioConnection patchCord11(oscBank1[3], 0, mixerBank[3], 0);
AudioConnection patchCord12(oscBank2[3], 0, mixerBank[3], 1);
AudioConnection patchCord13(mixerBank[0], 0, filterBank[0], 0);
AudioConnection patchCord14(mixerBank[1], 0, filterBank[1], 0);
AudioConnection patchCord15(mixerBank[2], 0, filterBank[2], 0);
AudioConnection patchCord16(mixerBank[3], 0, filterBank[3], 0);
AudioConnection patchCord17(filterBank[0], 0, envelopeBank[0], 0);
AudioConnection patchCord18(filterBank[1], 0, envelopeBank[1], 0);
AudioConnection patchCord19(filterBank[2], 0, envelopeBank[2], 0);
AudioConnection patchCord20(filterBank[3], 0, envelopeBank[3], 0);
AudioConnection patchCord21(envelopeBank[0], 0, mixer5, 0);
AudioConnection patchCord22(envelopeBank[1], 0, mixer5, 1);
AudioConnection patchCord23(envelopeBank[2], 0, mixer5, 2);
AudioConnection patchCord24(envelopeBank[3], 0, mixer5, 3);
AudioConnection patchCord25(mixer5, 0, i2s1, 0);
AudioConnection patchCord26(mixer5, 0, i2s1, 1);
AudioControlSGTL5000 sgtl5000_1; //xy=1023,20
// GUItool: end automatically generated code
const int TOTAL_VOICES = 4;
int last_id = 0;
struct voice_t {
int wavetable_id;
byte channel;
byte note;
};
voice_t voices[TOTAL_VOICES];
const byte BUFFER = 8; //Size of keyboard buffer
const float noteFreqs[128] = {8.176, 8.662, 9.177, 9.723, 10.301, 10.913, 11.562, 12.25, 12.978, 13.75, 14.568, 15.434, 16.352, 17.324, 18.354, 19.445, 20.602, 21.827, 23.125, 24.5, 25.957, 27.5, 29.135, 30.868, 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 48.999, 51.913, 55, 58.27, 61.735, 65.406, 69.296, 73.416, 77.782, 82.407, 87.307, 92.499, 97.999, 103.826, 110, 116.541, 123.471, 130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942, 261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883, 523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767, 1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533, 2093.005, 2217.461, 2349.318, 2489.016, 2637.02, 2793.826, 2959.955, 3135.963, 3322.438, 3520, 3729.31, 3951.066, 4186.009, 4434.922, 4698.636, 4978.032, 5274.041, 5587.652, 5919.911, 6271.927, 6644.875, 7040, 7458.62, 7902.133, 8372.018, 8869.844, 9397.273, 9956.063, 10548.08, 11175.3, 11839.82, 12543.85};
byte globalNote = 0;
byte globalVelocity = 0;
int octave = 0;
const float DIV127 = (1.0 / 127.0);
float detuneFactor = 1;
float bendFactor = 1;
int bendRange = 12;
unsigned int LFOspeed = 2000;
float LFOpitch = 1;
float LFOdepth = 0;
byte LFOmodeSelect = 0;
int FILfreq = 10000;
float FILfactor = 1;
void setup() {
Init();
}
void loop() {
usbMIDI.read();
LFOupdate(false, LFOmodeSelect, FILfactor, LFOdepth);
}
int allocateVoice(byte channel, byte note);
int findVoice(byte channel, byte note);
void freeVoices();
int used_voices = 0;
int stopped_voices = 0;
int evict_voice = 0;
int notes_played = 0;
void myNoteOn(byte channel, byte note, byte velocity) {
//if ( note > 23 && note < 108 ) {
notes_played++;
//Serial.printf("**** NoteOn: channel==%hhu,note==%hhu ****\n", channel, note);
//printVoices();
freeVoices();
int wavetable_id = allocateVoice(channel, note);
last_id = wavetable_id;
globalNote = note;
globalVelocity = velocity;
oscPlay(note, wavetable_id);
LFOupdate(true, LFOmodeSelect, FILfactor, LFOdepth);
printVoices();
//}
}
void myNoteOff(byte channel, byte note, byte velocity) {
//Serial.printf("\n**** NoteOff: channel==%hhu,note==%hhu ****", channel, note);
//printVoices();
int wavetable_id = findVoice(channel, note);
if (wavetable_id != TOTAL_VOICES)
envelopeBank[wavetable_id].noteOff();
//printVoices();
}
void myPitchBend(byte channel, int bend) {
float bendF = bend;
bendF = bendF / 8192;
bendF = bendF * bendRange;
bendF = bendF / 12;
bendFactor = pow(2, bendF);
oscSet(last_id);
}
void oscPlay(byte note, int wavetable_id) {
oscBank1[wavetable_id].frequency(noteFreqs[note] * bendFactor * LFOpitch);
oscBank2[wavetable_id].frequency(noteFreqs[note + octave] * detuneFactor * bendFactor * LFOpitch);
float velo = (globalVelocity * DIV127);
oscBank1[wavetable_id].amplitude(velo);
oscBank2[wavetable_id].amplitude(velo);
noiseBank[wavetable_id].amplitude(velo);
envelopeBank[wavetable_id].noteOn();
Serial.print("Velocity in OscPlay: ");
Serial.print(velo);
Serial.println();
Serial.print("envelope is active after noteOn: ");
Serial.print(envelopeBank[0].isActive());
Serial.println();
}
void oscStop(int wavetable_id) {
//envelope1.noteOff();
envelopeBank[wavetable_id].noteOff();
}
void oscSet(int wavetable_id) {
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank1[last_id].frequency(noteFreqs[globalNote] * bendFactor * LFOpitch);
oscBank2[last_id].frequency(noteFreqs[globalNote + octave] * detuneFactor * bendFactor * LFOpitch);
}
}
void myControlChange(byte channel, byte control, byte value) {
switch (control) {
case 100:
//mixerBank[0].gain(0, (value * DIV127));
for (int i = 0; i < TOTAL_VOICES; i++)
{
mixerBank[i].gain(0, (value * DIV127));
}
break;
case 101:
//mixerBank[0].gain(1, (value * DIV127));
for (int i = 0; i < TOTAL_VOICES; i++)
{
mixerBank[i].gain(1, (value * DIV127));
}
break;
case 102:
//mixerBank[0].gain(2, (value * DIV127));
for (int i = 0; i < TOTAL_VOICES; i++)
{
mixerBank[i].gain(2, (value * DIV127));
}
break;
case 103:
switch (value) {
case 0:
octave = 24;
break;
case 1:
octave = 12;
break;
case 2:
octave = 0;
break;
case 3:
octave = -12;
break;
case 4:
octave = -24;
break;
}
oscSet(last_id);
break;
case 104:
//envelopeBank[0].attack(3000 * (value * DIV127));
for (int i = 0; i < TOTAL_VOICES; i++)
{
envelopeBank[i].attack(3000 * (value * DIV127));
}
break;
case 105:
for (int i = 0; i < TOTAL_VOICES; i++)
{
envelopeBank[i].decay(3000 * (value * DIV127));
}
break;
case 106:
for (int i = 0; i < TOTAL_VOICES; i++)
{
envelopeBank[i].sustain(3000 * (value * DIV127));
}
break;
case 107:
for (int i = 0; i < TOTAL_VOICES; i++)
{
envelopeBank[i].attack(3000 * (value * DIV127));
}
break;
case 108:
switch (value) {
case 0:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank1[i].begin(WAVEFORM_SINE);
}
break;
case 1:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank1[i].begin(WAVEFORM_TRIANGLE);
}
break;
case 2:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank1[i].begin(WAVEFORM_SAWTOOTH);
}
break;
case 3:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank1[i].begin(WAVEFORM_PULSE);
}
break;
}
case 109:
switch (value) {
case 0:
//oscBank1[0].begin(WAVEFORM_SINE);
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank2[i].begin(WAVEFORM_SINE);
}
break;
case 1:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank1[i].begin(WAVEFORM_TRIANGLE);
}
break;
case 2:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank2[i].begin(WAVEFORM_SAWTOOTH);
}
break;
case 3:
for (int i = 0; i < TOTAL_VOICES; i++)
{
oscBank2[i].begin(WAVEFORM_PULSE);
}
break;
}
case 110:
detuneFactor = 1 - (0.05 * (value * DIV127));
oscSet(last_id);
break;
case 111:
FILfactor = value * DIV127;
FILfreq = 10000 * (value * DIV127);
if (LFOmodeSelect < 1 || LFOmodeSelect > 5)filterBank[0].frequency(FILfreq);
break;
case 112:
//filterBank[0].resonance((4.3 * (value * DIV127)) + 0.7);
for (int i = 0; i < TOTAL_VOICES; i++)
{
filterBank[i].resonance((4.3 * (value * DIV127)) + 0.7);
}
break;
case 113:
if (value <= 12 && value > 0) bendRange = value;
break;
case 114:
{
float xSpeed = value * DIV127;
xSpeed = pow(100, (xSpeed - 1));
LFOspeed = (70000 * xSpeed);
break;
}
case 115:
LFOdepth = value * DIV127;
break;
case 116:
LFOmodeSelect = value;
break;
}
}
void LFOupdate(bool retrig, byte mode, float FILtop, float FILbottom) {
static float LFO = 0;
static unsigned long LFOtime = 0;
static bool LFOdirection = false;
unsigned long currentMicros = micros();
static bool LFOstop = false;
static float LFOrange = 0;
static byte oldMode = 0;
static bool retriggered = false;
if (retrig == true) retriggered = true;
if (currentMicros - LFOtime >= LFOspeed) {
LFOtime = currentMicros;
if (mode != oldMode) {
if (mode == 0 || mode == 8) {
LFOpitch = 1;
oscSet(last_id);
filterBank[0].frequency(FILfreq);
}
else if (mode >= 1 || mode <= 7) {
LFOpitch = 1;
oscSet(last_id);
}
else if (mode >= 9 || mode <= 13) {
filterBank[0].frequency(FILfreq);
}
oldMode = mode;
}
LFOrange = FILtop - FILbottom;
if (LFOrange < 0) LFOrange = 0;
// LFO Modes
switch (mode) {
case 0: //Filter OFF
return;
break;
case 1: //Filter FREE
filterBank[0].frequency(10000 * ((LFOrange * LFO) + LFOdepth));
break;
case 2: //Filter DOWN
if (retriggered == true) {
LFOdirection = true;
LFO = 1.0;
}
filterBank[0].frequency(10000 * ((LFOrange * LFO) + LFOdepth));
break;
case 3: //Filter UP
if (retriggered == true) {
LFOdirection = false;
LFO = 0;
}
filterBank[0].frequency(10000 * ((LFOrange * LFO) + LFOdepth));
break;
case 4: //Filter 1-DN
if (retriggered == true) {
LFOstop = false;
LFOdirection = true;
LFO = 1.0;
}
if (LFOstop == false) filterBank[0].frequency(10000 * ((LFOrange * LFO) + LFOdepth));
break;
case 5: //Filter 1-UP
if (retriggered == true) {
LFOstop = false;
LFOdirection = false;
LFO = 0;
}
if (LFOstop == false) filterBank[0].frequency(10000 * ((LFOrange * LFO) + LFOdepth));
break;
case 8: //Pitch OFF
return;
break;
case 9: //Pitch FREE
LFOpitch = (LFO * LFOdepth) + 1;
oscSet(last_id);
break;
case 10: //Pitch DOWN
if (retriggered == true) {
LFOdirection = true;
LFO = 1.0;
}
LFOpitch = (LFO * LFOdepth) + 1;
oscSet(last_id);
break;
case 11: //Pitch UP
if (retriggered == true) {
LFOdirection = false;
LFO = 0;
}
LFOpitch = (LFO * LFOdepth) + 1;
oscSet(last_id);
break;
case 12: //Pitch 1-DN
if (retriggered == true) {
LFOstop = false;
LFOdirection = true;
LFO = 1.0;
}
if (LFOstop == false) {
LFOpitch = (LFO * LFOdepth) + 1;
oscSet(last_id);
}
break;
case 13: //Pitch 1-UP
if (retriggered == true) {
LFOstop = false;
LFOdirection = false;
LFO = 0;
}
if (LFOstop == false) {
LFOpitch = (LFO * LFOdepth) + 1;
oscSet(last_id);
}
break;
}
retriggered = false;
// Update LFO
if (LFOdirection == false) { //UP
LFO = (LFO + 0.01);
if (LFO >= 1) {
LFOdirection = true;
LFOstop = true;
}
}
if (LFOdirection == true) { //Down
LFO = (LFO - 0.01);
if (LFO <= 0) {
LFOdirection = false;
LFOstop = true;
}
}
}
}
int allocateVoice(byte channel, byte note) {
int i;
int nonfree_voices = stopped_voices + used_voices;
Serial.print("Nonfree voices: ");
Serial.print(nonfree_voices);
Serial.println();
if (nonfree_voices < TOTAL_VOICES) {
for (i = nonfree_voices; i < TOTAL_VOICES && voices[i].channel != channel; ++i);
if (i < TOTAL_VOICES) {
voice_t temp = voices[i];
voices[i] = voices[nonfree_voices];
voices[nonfree_voices] = temp;
}
i = nonfree_voices;
used_voices++;
}
else {
if (stopped_voices) {
i = evict_voice % stopped_voices;
voice_t temp = voices[i];
stopped_voices--;
voices[i] = voices[stopped_voices];
voices[stopped_voices] = temp;
used_voices++;
i = stopped_voices;
}
else
i = evict_voice;
}
voices[i].channel = channel;
voices[i].note = note;
evict_voice++;
evict_voice %= TOTAL_VOICES;
return voices[i].wavetable_id;
}
int findVoice(byte channel, byte note) {
int i;
//find match
int nonfree_voices = stopped_voices + used_voices;
for (i = stopped_voices; i < nonfree_voices && !(voices[i].channel == channel && voices[i].note == note); ++i);
//return TOTAL_VOICES if no match
if (i == (nonfree_voices)) return TOTAL_VOICES;
voice_t temp = voices[i];
voices[i] = voices[stopped_voices];
voices[stopped_voices] = temp;
--used_voices;
return voices[stopped_voices++].wavetable_id;
}
void freeVoices() {
for (int i = 0; i < stopped_voices; i++)
if (!true) {
voice_t temp = voices[i];
--stopped_voices;
voices[i] = voices[stopped_voices];
int nonfree_voices = stopped_voices + used_voices;
voices[stopped_voices] = voices[nonfree_voices];
voices[nonfree_voices] = temp;
}
}
const char* note_map[] = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
};
void printVoices() {
static int last_notes_played = notes_played;
if (last_notes_played == notes_played)
return;
last_notes_played = notes_played;
int usage = AudioProcessorUsage();
Serial.printf("\nCPU:%03i voices:%02i CPU/Voice:%02i evict:%02i", usage, used_voices, usage / used_voices, evict_voice);
for (int i = 0; i < used_voices; ++i)
Serial.printf(" %02hhu %-2s", voices[i].channel, note_map[voices[i].note % 12]);
}
void Init()
{
AudioMemory(120);
usbMIDI.setHandleControlChange(myControlChange);
usbMIDI.setHandleNoteOff(myNoteOff);
usbMIDI.setHandleNoteOn(myNoteOn);
usbMIDI.setHandlePitchChange(myPitchBend);
sgtl5000_1.enable();
sgtl5000_1.volume(0.52);
sgtl5000_1.dacVolumeRamp();
for (int i = 0; i < TOTAL_VOICES; i++)
{
voices[i].wavetable_id = i;
voices[i].channel = voices[i].note = 0xFF;
oscBank1[i].begin(WAVEFORM_SINE);
oscBank1[i].amplitude(1.0);
oscBank1[i].frequency(82.41);
oscBank1[i].pulseWidth(1);
oscBank2[i].begin(WAVEFORM_TRIANGLE);
oscBank2[i].amplitude(1.0);
oscBank2[i].frequency(82.41);
oscBank2[i].pulseWidth(.5);
for (int j = 0; j < 2; j++)
{
mixerBank[i].gain(j, 1.0);
}
mixerBank[i].gain(2, 0.0);
noiseBank[i].amplitude(0.0);
envelopeBank[i].delay(0);
envelopeBank[i].attack(0);
envelopeBank[i].decay(0);
envelopeBank[i].sustain(1);
envelopeBank[i].release(100.0 * DIV127);
//envelopeBank[i].releaseNoteOn(5.0);
mixer5.gain(i, 1.0);
}
}