Do Teensy Synths with Sine Waves always click?

This is how I allocate my poly-synth
https://github.com/manicken/teensy4...f68112bb3cd0cc6b4471ddb2dce836c95/src/Synth.h
line 88-122
the sustain part is
line 123-139

then in Voice.h

https://github.com/manicken/teensy4...f68112bb3cd0cc6b4471ddb2dce836c95/src/Voice.h
line 50-86

the sustain works by setting all voices isSustain to 1 when sustain is activated/pressed
then the voices don't execute the note off when isSustain is 1
the voices only execute the note off when the sustain is inactivated/released and if the voice is playing


it's all in my Polysynth example in the modified tool.

the tool generated code is also uploaded to my github:
https://github.com/manicken/teensy4polysynth2
note. the lines above differ in this version
 
So I started over and have some pretty good results.
But now there is a problem I can't even begin to understand!
So I have two voices working with two oscillators each, ADSR filter to smooth the transitions, bandpass filter to further control clicks, etc. And all is well! I can play nice sounds with either or both voices turned up and in any combination of waveforms (each one has a physical three-way switch to a pair of pullup resistors- so switch case 0, 1 is Saw, switch case 1, 0 is square, and switch case 1, 1 is sine.)
So!
If I play the attached MIDI controller, an AKAI Max25, everything works REALLY well. It's monophonic on purpose, the waveform switching and mixing is all good, no crackles or pops, etc.
Now when I turn the arpeggiator of the AKAI on...the first waveform arbitrarily switches to a square wave!
I've definitely run the serial monitor and have the program printing the pin states of the D1 and D2 (where that switch connects) at the moment it chooses the waveform...
Somehow, running the arpeggiator is causing the Teensy to pick up a different switch case!
What is weird is that no matter how fast I play, or how I overlap tones, or ANYTHING, this doesn't happen! Only the arpeggiator causes it...even if the tempo is glacial...
This is driving me nuts!
Any guesses?
My first thoughts were to the hardware- the grounds, the wires, etc.
But I can't make this happen while physically playing the MIDI controller!
And I have a test program that shows the pins in real time that I used when wiring up the hardware- no matter how i shake or slide the box or switches, they are working fine (showing 0 or 1 as pullup switches should be...)

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

// GUItool: begin automatically generated code
AudioSynthWaveform       Osc1a;      //xy=55,52.11111068725586
AudioSynthWaveform       Osc1b;      //xy=55,99.11111450195312
AudioSynthWaveform       Osc2a;      //xy=56,175
AudioSynthWaveform       Osc2b;      //xy=57,214
AudioFilterBiquad        Osc1bBPFilter;        //xy=192,100
AudioFilterBiquad        Osc1aBPFilter;        //xy=194,52
AudioFilterBiquad        Osc2aBPFilter;        //xy=197,175
AudioFilterBiquad        Osc2bBPFilter;        //xy=197,218
AudioEffectEnvelope      Osc1aAttack;      //xy=349,64
AudioEffectEnvelope      Osc1bAttack;      //xy=349,100
AudioEffectEnvelope      Osc2bAttack;      //xy=352,221
AudioEffectEnvelope      Osc2aAttack;      //xy=354,177
AudioMixer4              Osc1Mix;         //xy=488,99
AudioMixer4              Osc2Mix;         //xy=491,201
AudioMixer4              MasterL;         //xy=741.111083984375,70
AudioMixer4              MasterR;         //xy=740.888916015625,135.88888549804688
AudioOutputI2S           i2s1;           //xy=866,104
AudioConnection          patchCord1(Osc1a, Osc1aBPFilter);
AudioConnection          patchCord2(Osc1b, Osc1bBPFilter);
AudioConnection          patchCord3(Osc2a, Osc2aBPFilter);
AudioConnection          patchCord4(Osc2b, Osc2bBPFilter);
AudioConnection          patchCord5(Osc1bBPFilter, Osc1bAttack);
AudioConnection          patchCord6(Osc1aBPFilter, Osc1aAttack);
AudioConnection          patchCord7(Osc2aBPFilter, Osc2aAttack);
AudioConnection          patchCord8(Osc2bBPFilter, Osc2bAttack);
AudioConnection          patchCord9(Osc1aAttack, 0, Osc1Mix, 0);
AudioConnection          patchCord10(Osc1bAttack, 0, Osc1Mix, 1);
AudioConnection          patchCord11(Osc2bAttack, 0, Osc2Mix, 1);
AudioConnection          patchCord12(Osc2aAttack, 0, Osc2Mix, 0);
AudioConnection          patchCord13(Osc1Mix, 0, MasterL, 0);
AudioConnection          patchCord14(Osc1Mix, 0, MasterR, 0);
AudioConnection          patchCord15(Osc2Mix, 0, MasterL, 1);
AudioConnection          patchCord16(Osc2Mix, 0, MasterR, 1);
AudioConnection          patchCord17(MasterL, 0, i2s1, 0);
AudioConnection          patchCord18(MasterR, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=699.2222290039062,504.4444580078125
// GUItool: end automatically generated code


#include <MIDI.h>

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);



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;



int Osc1aTurn = 0;
int Osc1bTurn = 0;
int Osc2aTurn = 0;
int Osc2bTurn = 0;
int Osc1Playing = 0;
int Osc2Playing = 0;
int notePlaying = 0;
float Osc1aVolume = .5;
float Osc1bVolume = .5;
float Osc2Volume = .50;
float Osc1aPitch = 0.220;
float Osc1bPitch = 0.440;
float Osc2aPitch = 0.220;
float Osc2bPitch = 0.440;

int Osc1current_waveform = WAVEFORM_SINE;
int Osc2current_waveform = WAVEFORM_SINE;
int Osc3current_waveform = WAVEFORM_SINE;

void setup() {

  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);
  
  AudioMemory(120);

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8);
  
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.setHandleNoteOn(OnNoteOn);
  MIDI.setHandleNoteOff(OnNoteOff);
  
  Osc1Mix.gain(0, 0.7);
  Osc1Mix.gain(1, 0.7);
  Osc2Mix.gain(0, 0.7);
  Osc2Mix.gain(1, 0.7);
  MasterL.gain(0, 0.7);
  MasterL.gain(1, 0.7);
  MasterR.gain(0, 0.7);
  MasterR.gain(1, 0.7);

  Osc1aAttack.release(25);
  Osc1bAttack.release(25);
  Osc2aAttack.release(25);
  Osc2bAttack.release(25);
  Osc1aAttack.releaseNoteOn(15);
  Osc1bAttack.releaseNoteOn(15);
  Osc2aAttack.releaseNoteOn(15);
  Osc2bAttack.releaseNoteOn(15);
  Osc1aBPFilter.setBandpass(0, Osc1aPitch, 0.6);
  Osc1bBPFilter.setBandpass(0, Osc1bPitch, 0.6);
  Osc2aBPFilter.setBandpass(0, Osc2aPitch, 0.6);
  Osc2bBPFilter.setBandpass(0, Osc2bPitch, 0.6);
}

void loop() {
  getReadings();
  MIDI.read();
  Osc1a.begin(Osc1aVolume, Osc1aPitch, Osc1current_waveform);
  Osc1b.begin(Osc1bVolume, Osc1bPitch, Osc1current_waveform);
  Osc2a.begin((Osc2Volume * Osc1aVolume), Osc2aPitch, Osc2current_waveform);
  Osc2b.begin((Osc2Volume * Osc1bVolume), Osc2bPitch, Osc2current_waveform);
  
}

void OnNoteOn(byte channel, byte note, byte velocity)
{
  Serial.println("Note!");
  float hertz = (pow (2.0, ((float)(note-69)/12.0)))*440;
  Serial.print("Note: ");
  Serial.println(note);
  Serial.print("Velocity: ");
  Serial.println(velocity);
  Serial.print("");
  Serial.println("");
  
  if ((Osc1aTurn == 1) && (Osc1Playing == 0)){
    Osc1aPitch = hertz;
    Osc1aVolume = ((float)velocity/150.00); 
    Osc1aBPFilter.setBandpass(0, Osc1aPitch, 0.1);
    
    if ((osc1Shape1Val == 0) && (osc1Shape2Val == 1)){
    Osc1current_waveform = WAVEFORM_SQUARE;
    Serial.println("osc1 square");
    Serial.println(digitalRead(osc1ShapePin1));
    Serial.println(digitalRead(osc1ShapePin2));
    }
    else if (osc1Shape2Val == 0){
    Osc1current_waveform = WAVEFORM_SAWTOOTH;
    Serial.println("osc1 saw");
    }
    else if ((osc1Shape1Val == 1) && (osc1Shape2Val == 1)){
    Osc1current_waveform = WAVEFORM_SINE;
    Serial.println("osc1 sine");
    } 
    Osc1aAttack.noteOn();
    Serial.println("Osc1a On");
    Serial.print("Osc1a Volume: ");
    Serial.println(Osc1aVolume);
    Serial.print("Osc1a Frequency: ");
    Serial.println(Osc1aPitch);
    Osc1Playing = 1; 
    notePlaying = note;
    Osc1aTurn = 0;
  }
  else if ((Osc1Playing == 0) && (Osc1aTurn == 0)){
    Osc1bPitch = hertz;
    Osc1bVolume = ((float)velocity/150.00);
    Osc1bBPFilter.setBandpass(0, Osc1bPitch, 0.1);
    
    if ((osc1Shape1Val == 0) && (osc1Shape2Val == 1)){
    Osc1current_waveform = WAVEFORM_SQUARE;
    Serial.println("osc1 square");
    Serial.println(digitalRead(osc1ShapePin1));
    Serial.println(digitalRead(osc1ShapePin2));
    }
    else if (osc1Shape2Val == 0){
    Osc1current_waveform = WAVEFORM_SAWTOOTH;
    Serial.println("osc1 saw");
    }
    else if ((osc1Shape1Val == 1) && (osc1Shape2Val == 1)){
    Osc1current_waveform = WAVEFORM_SINE;
    Serial.println("osc1 sine");
    } 
    
    Osc1bAttack.noteOn();
    Serial.println("Osc1b On"); 
    Serial.print("Osc1b Volume: ");
    Serial.println(Osc1bVolume);
    Serial.print("Osc1b Frequency: ");
    Serial.println(Osc1bPitch);
    Osc1Playing = 1;
    notePlaying = note;
    Osc1aTurn = 1;
  }
 

if ((Osc2aTurn == 1) && (Osc2Playing == 0)){
    Osc2aPitch = hertz;
    Osc2Volume = ((float)velocity/150.00); 
    Osc2aBPFilter.setBandpass(0, Osc2aPitch, 0.1);
    
    if (osc2Shape1Val == 0){
    Osc2current_waveform = WAVEFORM_SQUARE;
    Serial.println("osc2 square");
    }
    else if (osc2Shape2Val == 0){
    Osc2current_waveform = WAVEFORM_SAWTOOTH;
    Serial.println("osc2 saw");
    }
    else {
    Osc2current_waveform = WAVEFORM_SINE;
    Serial.println("osc2 sine");
    } 
    Osc2aAttack.noteOn();
    Serial.println("Osc2a On");
    Serial.print("Osc2a Volume: ");
    Serial.println(Osc2Volume);
    Serial.print("Osc2a Frequency: ");
    Serial.println(Osc2aPitch);
    Osc2Playing = 1; 
   // notePlaying = note;
    Osc2aTurn = 0;
  }
  else if ((Osc2Playing == 0) && (Osc2aTurn == 0)){
    Osc2bPitch = hertz;
    Osc2Volume = ((float)velocity/150.00);
    Osc2bBPFilter.setBandpass(0, Osc2bPitch, 0.1);
    
    if (osc2Shape1Val == 0){
    Osc2current_waveform = WAVEFORM_SQUARE;
    Serial.println("osc2 square");
    }
    else if (osc2Shape2Val == 0){
    Osc2current_waveform = WAVEFORM_SAWTOOTH;
    Serial.println("osc2 saw");
    }
    else {
    Osc2current_waveform = WAVEFORM_SINE;
    Serial.println("osc2 sine");
    } 
    Osc2bAttack.noteOn();
    Serial.println("Osc2b On"); 
    Serial.print("Osc2b Volume: ");
    Serial.println(Osc2Volume);
    Serial.print("Osc2b Frequency: ");
    Serial.println(Osc2bPitch);
    Osc2Playing = 1;
    //notePlaying = note;
    Osc2aTurn = 1;
  }
 
  
  
}

void OnNoteOff(byte channel, byte note, byte velocity)
{
    if (note == notePlaying){
    Osc1aAttack.noteOff();
    Osc1bAttack.noteOff();  
    Osc2aAttack.noteOff();
    Osc2bAttack.noteOff();
    Osc1Playing = 0; 
    Osc2Playing = 0;
    }
  
} 

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);

  Osc2Volume = (analogRead(osc2LevelPin)/1027.00);

  
  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);  

}
 
Well, I kinda figured it out and it's capped off a pretty long day with a sad sort of ending.
I have to put a delay in my button reads...and now there's a little crackle at the beginning of each time through the main loop when on sine waves.
How do you read button press states without needing to put a little delay, or how can you make that little delay not interrupt the audio stream?
 
How much delay did you have to add ?? Maybe you could try just reading one switch each time thru the loop & use millis() instead of delay() to tell you when it is time to read the next switch. Use a variable that counts thru the number of buttons & a switch{} statement to decide which button to read. I did exactly this in my TeensyMIDIPolySynth, but in my case, my intent was to maximize how often I called MIDI.read() in order to minimize any MIDI latency.

Good luck & have fun !!

Mark J Culross
KD5RXT
 
Thats what I ended up doing, and it worked! I felt like I had landed on the moon. Learning so much, the hours simply melt away haha
 
If the sine waves you are trying to produce are mostly bass notes, which are the notes where the click is most noticeable anyway, then you can eliminate the click with a frequency filter that eliminates all frequencies above 250 Hz. That's how we solve it in the recording studio. Also, use a 5ms to 10s attack and cutoff time. That delay is barely perceptible to the human ear but extremely effective in removing the click.
 
Back
Top