Experienced TeensySynth Designers: Bit of Help With Some Code Questions (first-timer)

Status
Not open for further replies.

trevorbryden

Active member
Hello,
I have started putting a Teensy synth together. It has analog pots and switches, all working, connected to a teensy 3.5 with audio shield.

The overall design target is: 3 oscillators (Two and Three are pitch and volume selectable, Three is octave selectable, and all three are waveform selectable) one LFO with four destinations, ADSR, filter with cutoff and resonance, and finally delay and reverb.

I've been getting it up step-by-step with success: first the oscillators made pitches through the headphones, their level controls seemed to work, the sound was smooth, choosing different wave types was fine, etc. No crackles or pops or artifacts or whatever. Just neat sounding synthy waves of different kinds happily mixing together.

Then I incorporated the filter and it seemed ok, too- though there was some crackling when i swept the cutoff knob around, it wasn't too terrible and I decided to keep going. I used the mono reverb from the Audio library and got a wet/dry knob working well for that. In fact, the crackling helped me hear the quality of the reverb!

Then, I got midi in to work and print midi messages to the serial monitor (one of the projects on Teensy website, the example midi input one here: https://www.pjrc.com/teensy/td_libs_MIDI.html
...I also used the schematic to wire my MIDI IN port.)

Currently, the synth 'works' (and I'm pretty excited about it, it's my first try ever) but there is quite a delay between pressing a key on my midi controller and the wave changing; there is no note-off yet; and there is some weird stuff going on with the pitches and if i just leave a note on there is quite a bit of crackling here and there, stuff I associate with 'code not working correctly' because I tested all the hardware and there was very good sound quality back when it was just waves, oscillators and so on. In other words, I'm not overdriving one of the mixers or anything. And the distortion in the filter is unrelated to the amount of resonance- it happens when the resonance is well below the stated .707 threshold.

I haven't done the LFO yet, or the ADSR envelopes, or the delay. But I'm a bit stuck where I am.

Code-wise, there's no doubt I have made a mess of things because I simply know nothing about how to run code efficiently. So I am hoping to post it here and pray for some GENTLE advice about:

1. the best coding approach to efficiently and usefully change midi note to frequency and set my OSC1 to that frequency

2. any comments about my filter implementation and why that step introduced some crackling

3. any guidance about how to figure out (for a newbie) voice allocation or just how to get MIDI on and off working cleanly, at least mono for now

4. any stuff in advance to look forward to with ADSR envelopes and getting the delay (time, wet/dry) to work...for now, I didn't want to get into multiplexing for this first synth so I have only a few knobs and decided to eschew delay feedback on this one

The problem is the huge gap between my ability to read and understand, say, the code that the Teensy Tsynth runs (way too complicated for me to follow) and other examples of midi synths I'm trying to use and borrow from. I really do need help in the most basic stuff- how functions return values, how to make the main loop of code run efficiently, etc.

There are ABSOLUTELY bits of code floating around that I'm not using but were left over from stuff I cut and pasted and tried out...for example, I left some things in for voice allocation to 8 voices even though at the moment I'd be fine with a working monophonic synth.

I'll try to post code correctly here and hoping i sorta get the tags and stuff right. Thanks!

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

// GUItool: begin automatically generated code
AudioSynthWaveformModulated Osc1;   //xy=129,88
AudioSynthWaveformModulated Osc2;   //xy=129,175
AudioEffectChorus        chorus1;        //xy=128,452
AudioSynthWaveformModulated Osc3;   //xy=133,256
AudioEffectEnvelope      envelope1;      //xy=136,496
AudioSynthWaveformModulated LFO_Osc2Freq;   //xy=295,313
AudioSynthWaveformModulated LFO_DelayTime;   //xy=300,393
AudioMixer4              Premix;         //xy=319,159
AudioSynthWaveformModulated LFO_Volume;   //xy=336,435
AudioSynthWaveformModulated LFO_Filter;   //xy=340,351
AudioFilterStateVariable LowPassFilter;        //xy=479,212
AudioEffectDelay         Delay;         //xy=589,340
AudioEffectFreeverb      Verb;      //xy=723,35
AudioOutputI2S           i2s1;           //xy=771,134
AudioMixer4              MasterR;         //xy=776,345
AudioMixer4              MasterL;         //xy=777,272
AudioConnection          patchCord1(Osc1, 0, Premix, 0);
AudioConnection          patchCord2(Osc2, 0, Premix, 1);
AudioConnection          patchCord3(Osc3, 0, Premix, 2);
AudioConnection          patchCord4(Premix, 0, LowPassFilter, 0);
AudioConnection          patchCord5(LowPassFilter, 0, MasterL, 0);
AudioConnection          patchCord6(LowPassFilter, 0, MasterR, 0);
AudioConnection          patchCord7(LowPassFilter, 0, Verb, 0);
AudioConnection          patchCord8(Verb, 0, MasterL, 1);
AudioConnection          patchCord9(Verb, 0, MasterR, 1);
AudioConnection          patchCord10(MasterR, 0, i2s1, 1);
AudioConnection          patchCord11(MasterL, 0, i2s1, 0);
AudioControlSGTL5000     sgtl5000_1;     //xy=771,409
// GUItool: end automatically generated code

//variables that hold the values/states of the knobs and switches

int attackVal;
int decayVal;
int sustainVal;
int releaseVal;
int resoVal;
int cutoffVal;
int osc2FreqVal;
int osc2LevelVal;
int osc3FreqVal;
int osc3LevelVal;
int osc1Shape1Val;
int osc1Shape2Val;
int osc2Shape1Val;
int osc2Shape2Val;
int osc3Shape1Val;
int osc3Shape2Val;
int osc3Octave1Val;
int osc3Octave2Val;
int lfoRateVal;
int lfoDepthVal;
int lfoShape1Val;
int lfoShape2Val;
int lfoDest1Val;
int lfoDest2Val;
int lfoDest3Val;
int volumeVal;
int delayLengthVal;
int delayMixVal;
int reverbMixVal;


//Pin assignments

int attackPin = 21;
int decayPin = 20;
int sustainPin = 17;
int releasePin = 16;
int resoPin = 31;
int cutoffPin = 32;
int osc1ShapePin1 = 1;
int osc1ShapePin2 = 2;
int osc2ShapePin1 = 27;
int osc2ShapePin2 = 28;
int osc3ShapePin1 = 8;
int osc3ShapePin2 = 5;
int osc2FreqPin = A14;
int osc2LevelPin = A18;
int osc3FreqPin = A20;
int osc3LevelPin = A17;
int osc3OctavePin1 = 30;
int osc3OctavePin2 = 29;
int lfoRatePin = A15;
int lfoDepthPin = A19;
int lfoShapePin1 = 3;
int lfoShapePin2 = 4;
int lfoDestPin1 = 25;
int lfoDestPin2 = 24;
int lfoDestPin3 = 26;
int volumePin = A1;
int delayLengthPin = A22;
int delayMixPin = A16;
int reverbMixPin = A21;

//oscillator and audio library object values

int osc1current_waveform = 0;
float osc1Pitch = 261.63;
int osc2current_waveform = 0;
float osc2Pitch = 130.86;
int osc3current_waveform = 0;
float osc3Pitch = 65.44;
float osc1Volume = 1.00;
float osc2Volume = 1.00;
float osc3Volume = 1.00;
float osc3Octave = 1.00;
float filterFreq = 6500.0;
float resAmount = 0.700;
float verbMix = 0.00;
float hertz = 1.000;

//midi code variables

unsigned long t=0;
int ledPin = 13;
// array for the notes to be assigned to one of eight voices
int voice[8];
//used to track how many voices are active
//when limit is reached, msg prints Voice Limit Exceeded!
int noteCount=0;

void setup() {
  
  Serial.begin(38400);
  
  pinMode (osc1ShapePin1, INPUT_PULLUP);
  pinMode (osc1ShapePin2, INPUT_PULLUP);
  pinMode (osc2ShapePin1, INPUT_PULLUP);
  pinMode (osc2ShapePin2, INPUT_PULLUP);
  pinMode (osc3ShapePin1, INPUT_PULLUP);
  pinMode (osc3ShapePin2, INPUT_PULLUP);
  pinMode (osc3OctavePin1, INPUT_PULLUP);
  pinMode (osc3OctavePin2, INPUT_PULLUP);
  pinMode (lfoShapePin1, INPUT_PULLUP);
  pinMode (lfoShapePin2, INPUT_PULLUP);
  pinMode (lfoDestPin1, INPUT_PULLUP);
  pinMode (lfoDestPin2, INPUT_PULLUP);
  pinMode (lfoDestPin3, INPUT_PULLUP);
   pinMode(ledPin, OUTPUT);
  
  AudioMemory(120);

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.4);
  LowPassFilter.frequency(filterFreq);
  Verb.roomsize(0.75);
  Verb.damping(0.45);

  MIDI.setHandleNoteOn(OnNoteOn);
  MIDI.setHandleNoteOff(OnNoteOff);
  MIDI.begin(MIDI_CHANNEL_OMNI);

}

void loop() {

  getReadings();
  setWaveForms();
  setWaveFreqs();
  startWaves();
  applyFilter();
  reverbMix();
  midiDetect();

}

void getReadings(){
  
  attackVal = analogRead(attackPin);
  decayVal = analogRead(decayPin);
  sustainVal = analogRead(sustainPin);
  releaseVal = analogRead(releasePin);
  resoVal = analogRead(resoPin);
  cutoffVal = analogRead(cutoffPin);
  osc1Shape1Val = digitalRead(osc1ShapePin1);
  osc1Shape2Val = digitalRead(osc1ShapePin2);
  osc2Shape1Val = digitalRead(osc2ShapePin1);
  osc2Shape2Val = digitalRead(osc2ShapePin2);
  osc3Shape1Val = digitalRead(osc3ShapePin1);
  osc3Shape2Val = digitalRead(osc3ShapePin2);
  osc3Octave1Val = digitalRead(osc3OctavePin1);
  osc3Octave2Val = digitalRead(osc3OctavePin2);
  osc2FreqVal = analogRead(osc2FreqPin);
  osc2LevelVal = analogRead(osc2LevelPin);
  osc3FreqVal = analogRead(osc3FreqPin);
  osc3LevelVal = analogRead(osc3LevelPin);
  lfoRateVal = analogRead(lfoRatePin);
  lfoDepthVal = analogRead(lfoDepthPin);
  lfoShape1Val = digitalRead(lfoShapePin1);
  lfoShape2Val = digitalRead(lfoShapePin2);
  lfoDest1Val = digitalRead(lfoDestPin1);
  lfoDest2Val = digitalRead(lfoDestPin2);
  lfoDest3Val = digitalRead(lfoDestPin3);
  volumeVal = analogRead(volumePin);  
  delayLengthVal = analogRead(delayLengthPin);
  delayMixVal = analogRead(delayMixPin);
  reverbMixVal = analogRead(reverbMixPin);  

}

void setWaveForms(){
 if (osc1Shape1Val == 0){
    osc1current_waveform = WAVEFORM_SQUARE;
  }
  else if (osc1Shape2Val == 0){
    osc1current_waveform = WAVEFORM_SAWTOOTH;
  }
  else {
    osc1current_waveform = WAVEFORM_SINE;
  } 

  if (osc2Shape1Val == 0){
    osc2current_waveform = WAVEFORM_SQUARE;
  }
  else if (osc2Shape2Val == 0){
    osc2current_waveform = WAVEFORM_SAWTOOTH;
  }
  else {
    osc2current_waveform = WAVEFORM_TRIANGLE;
  } 

   if (osc3Shape1Val == 0){
    osc3current_waveform = WAVEFORM_SQUARE;
  }
  else if (osc3Shape2Val == 0){
    osc3current_waveform = WAVEFORM_SAWTOOTH;
  }
  else {
    osc3current_waveform = WAVEFORM_TRIANGLE;
  } 
}

void setWaveFreqs(){
  osc1Pitch = hertz;
  osc2Pitch = (osc1Pitch * 3.00);
  
   if (osc3Octave1Val == 0){
    osc3Octave = 0.50;
  }
  else if (osc3Octave2Val == 0){
    osc3Octave = 2.00;
  }
  else {
    osc3Octave = 1.00;
  } 
 osc3Pitch = (osc1Pitch / osc3Octave); 
}

void startWaves(){
   osc2Volume = ((osc2FreqVal / 1023.00) * osc1Volume);
   osc3Volume = ((osc3LevelVal / 1023.00) * osc1Volume);
   
   Osc1.begin(osc1Volume, osc1Pitch, osc1current_waveform);
   Osc2.begin(osc2Volume, osc2Pitch, osc2current_waveform);
   Osc3.begin(osc3Volume, osc3Pitch, osc3current_waveform);
}

void applyFilter(){
  filterFreq =  expf((float)cutoffVal / 145.00) * 10.00 + 00.00;
  LowPassFilter.frequency(filterFreq);
  resAmount = (.690 + (resoVal / 235.000));
  LowPassFilter.resonance(resAmount);
}

void reverbMix(){
  verbMix = (reverbMixVal / 1023.00);
  MasterL.gain(0, 1.0);
  MasterR.gain(0, 1.0);
  MasterL.gain(1, verbMix);
  MasterR.gain(1, verbMix); 
}

void handleNoteOn(byte channel, byte pitch, byte velocity)
{
 
}



void handleNoteOff(byte channel, byte pitch, byte velocity)
{
    // Do something when the note is released.
    // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs.
}



void midiDetect(){
   int type, note, velocity, channel, d1, d2;
  if (MIDI.read()) {                    // Is there a MIDI message incoming ?
    byte type = MIDI.getType();
    switch (type) {
      case midi::NoteOn:
        note = MIDI.getData1();
        velocity = MIDI.getData2();
        channel = MIDI.getChannel();
        if (velocity > 0) {
          hertz = (pow (2.0, ((float)(note-69)/12.0)))*440;
          Serial.println(String("Note On:  ch=") + channel + ", note=" + note + ", velocity=" + velocity + ", hertz=" + hertz);
        } else {
          Serial.println(String("Note Off: ch=") + channel + ", note=" + note);
        }
        break;
      case midi::NoteOff:
        note = MIDI.getData1();
        velocity = MIDI.getData2();
        channel = MIDI.getChannel();
        Serial.println(String("Note Off: ch=") + channel + ", note=" + note + ", velocity=" + velocity);
        break;
      default:
        d1 = MIDI.getData1();
        d2 = MIDI.getData2();
        Serial.println(String("Message, type=") + type + ", data = " + d1 + " " + d2);
    }
    t = millis();
  }
  if (millis() - t > 10000) {
    t += 10000;
    Serial.println("(inactivity)");
  }
}



void MIDInoteOn(int note, int vel)
{

  if(noteCount==8){
    Serial.println("Voice Limit Exceeded!");
  }

  for (int i=0; i<8;i++){
 if(!voice[i]){
  voice[i]=note;
  Serial.print("New Note #  ");
  Serial.println(note);
  Serial.println(vel);
  Serial.print("Assigned to Voice #  ");
  Serial.println(i+1);

  digitalWrite(ledPin, HIGH);
  delay(100);
  digitalWrite(ledPin, LOW);
  noteCount++;

    if(voice[i]){return;
  }
}
}
}
 
//functin called when a Note Off msg is received.  Will scan the voice array to locate 
//the note to be shut off.  Thus returning the element to zero value.  which frees up that
//voice for use of a new note.

void MIDInoteOff(int note, int vel)
{
  
    for(int i=0; i<8; i++){
     
      if(voice[i]==note){
        voice[i]=0;
          Serial.print("-note off # ");
          Serial.println(note);
          Serial.print("Shut Off Voice # ");
          Serial.println(i+1);
          noteCount--;
          if(noteCount<0){noteCount=0;
      }
  digitalWrite(ledPin, HIGH);
  delay(100);
  digitalWrite(ledPin, LOW);

          
      }
    }
  }


void OnNoteOn(byte channel, byte note, byte velocity)
{
  MIDInoteOn(note, velocity);
}

void OnNoteOff(byte channel, byte note, byte velocity)
{
  MIDInoteOff(note, velocity);
  digitalWrite(ledPin, LOW);
  Serial.print("-note off # ");
  Serial.println(note);
}

Screen Shot 2021-03-14 at 7.46.17 PM.jpg

Trevor
 
Your can check my example at https://manicken.github.io
Available at the Tool (top-right menu) -examples-"manicken poly synth"
I belive it's a simple example.
I have used the code from "Notes and Volts"
https://m.youtube.com/watch?v=UJcZxyB5rVc
as a Starting point.

It uses handler functions to receive the midi data,
I think that makes it faster.

Note. currently it uses wavetable instrument files that need to be downloaded from my github, before it can be compiled, alternative the references need to be commented out.
 
You've created the I2S output before the master mixers, so it will be run before the mixers that provide its data.
There can be issues if things are run out of order like this, sometimes, and these are often intermittent, and
the latencies and timing wont be as expected of course. Its possible this might be making some glitches, not sure
though.

If you stick to the auto-generated code from the audio design tool it puts objects in a consistent order. The audio
processing chain is run strictly in the order the audio objects were created, irrespective of connection topology.

You have calls to delay(100) - that's not a good idea if you want timeliness of response.
 
Thanks! I did use the auto generate tool and did not alter what it created. And afaik the function with the delay in it is not called atm (it was part of an attempt to learn how multiple voices are assigned) but I will comment it out and make sure. Not sure why the audio tool generated the objects in the order it did...
 
Sounds like there's a bug? Or maybe I was wrong about it generating a breadth-first traversal (which is what the
Audio library seems to assume).

Thinking about it there's perhaps an assumption that if the signal wire runs backwards (graphically) its a cyclic link? Certainly
flattening the graph has to have some source of information about which links are "backwards" in any cycle. Those
links will have a block's worth of latency. Not fluent with JS though so don't really relish delving through the audio
tool code.
 
In my modified tool it sorts the items in columns based on the major grid size and then in each column it sorts by the y pos (row)
1 4 7
2 5 8
3 6 9
 
Can you provide any guidance about whether, in this exact design (three oscillators with three waveform types each) there are benefits to wavetables vs. using the modulation-ready oscillators in the audio library? And then, when does it become necessary or clearly more efficient to use wavetables? My next design has a fourth sound source using FM, and I'm pretty sure the arduino project i am swiping it from used wavetables. In this design, one of the waves will be modulated by an LFO, but that is it so far.
 
You can use AudioProcessorUsage() and AudioProcessorUsageMax() to instrument your code and see what the
audio lib CPU load is for various configurations.
 
Holy Toledo, that is NOT the Design Tool I've been using! That looks a lot more sophisticated and up to date. I'm switching.
...hey, wait. Where do I find that version besides your github? I want the ladder filter and some of the other functionality...is this your own design tool environment, not generally available to Teensy users?
 
Last edited:
Where do I find that version besides your github?

the current version can be downloaded from
https://github.com/manicken/manicken.github.io

I want the ladder filter and some of the other functionality...
I have updated it with the ladder filter.
Other functionality???

is this your own design tool environment, not generally available to Teensy users?
This is a modified version of the official tool.
And will be available as long as github allows it, and will always be free.

The funny thing is that I have not yet had any time to do any advanced synth stuff myself.
(except playing with the PureData alike interfacing I have implemented)
Have recently bought a teensy 4.1 to allow much more flash + native sd-card slot.

My thread about it is here
https://forum.pjrc.com/threads/65740-Audio-System-Design-Tool-update
 
Status
Not open for further replies.
Back
Top