Teensy 4 in TS-1 TEENSY- Synth

Status
Not open for further replies.

andyfras

Member
I wanted to build 'Notes and Volts' TS-1 which was originally designed for the Teensy 3.2.

See: https://www.youtube.com/watch?v=Inwbu9DgVeU&t=7s

As the Teensy 4 was available, it seemed a better choice, so I ordered one with its corresponding audio adapter. I had to change the TS-1 connections slightly to accommodate the changes between Teensy 3.2 and 4.0, and altered the sketch to correspond. Fortunately Teensyduino 1.49 was released just as I was about to load the sketch, so the Midi port option was there (I've since updated to Teensyduino 1.50 in case of a software issue). As speed isn't an issue, I'm running it at 150MHz.

Once it was basically connected up, it worked after fixing a couple of errors. I then fixed everything in place and screwed the bottom of the box on, feeling very confident and pleased with myself; even leaving a comment on the N&V Youtube page. Unfortunately, it's never worked since.

Here is the sketch:

Code:
// Teensy-Synth Part 10
// Hardware Controls
// By Notes and Volts
// www.notesandvolts.com

// Requires 'MIDI Library by Forty Seven Effects' 
// Install in Arduino IDE using 'Sketch/Include Library/Manage Libraries'

// Tools menu Settings:
// Board: "Teensy4.0"
// USB Type: "MIDI"

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
//#include <SD.h>
#include <SerialFlash.h>

#include <MIDI.h>

//Mux Pins
#define MS0 5  //A3 for RX1
#define MS1 A0 //A6 for Teensy 3.2/3.1
#define MS2 9 //8 for Teensy 3.2/3.1
#define MS3 A8 //A7 for Teensy 3.2/3.1
#define MZ A3  //A2 for RX1
#define MnumControls 15

#define MUXosc1 5
#define MUXosc2 6
#define MUXdetune 7
#define MUXmix1 9
#define MUXmix2 10
#define MUXmix3 11
#define MUXattack 3
#define MUXdecay 4
#define MUXsustain 2
#define MUXrelease 1
#define MUXlfospeed 0
#define MUXlfodepth 14
#define MUXlfomode 8
#define MUXfilterres 12
#define MUXfilterfreq 13

//Switch pin numbers
#define numSwitch 3
#define SWosc1 2
#define SWosc2 3
#define SWlfo 4

//MIDI CC control numbers
#define CCmixer1 100
#define CCmixer2 101
#define CCmixer3 102
#define CCoctave 103
#define CCattack 104
#define CCdecay 105
#define CCsustain 106
#define CCrelease 107
#define CCosc1 108
#define CCosc2 109
#define CCdetune 110
#define CCfilterfreq 111
#define CCfilterres 112
#define CCbendrange 113
#define CClfospeed 114
#define CClfodepth 115
#define CClfomode 116


// GUItool: begin automatically generated code
AudioSynthWaveform       waveform2;      //xy=382,453
AudioSynthWaveform       waveform1;      //xy=386,394
AudioSynthNoisePink      pink1;          //xy=394,504
AudioMixer4              mixer1;         //xy=577,454
AudioFilterStateVariable filter1;        //xy=725,457
AudioEffectEnvelope      envelope1;      //xy=888,458
AudioOutputI2S           i2s1;           //xy=1049,465
AudioConnection          patchCord1(waveform2, 0, mixer1, 1);
AudioConnection          patchCord2(waveform1, 0, mixer1, 0);
AudioConnection          patchCord3(pink1, 0, mixer1, 2);
AudioConnection          patchCord4(mixer1, 0, filter1, 0);
AudioConnection          patchCord5(filter1, 0, envelope1, 0);
AudioConnection          patchCord6(envelope1, 0, i2s1, 0);
AudioConnection          patchCord7(envelope1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=565,556
// GUItool: end automatically generated code




// GLOBAL VARIABLES
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 octave1 = 0;
int octave2 = 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;

byte osc1Mode = 255; // 255 = Nonsense value to force startup read
byte osc2Mode = 255;

MIDI_CREATE_INSTANCE(HardwareSerial, Serial4, MIDI); //changed to RX4 in case RX1 is faulty

void setup() {
  AudioMemory(20);

  pinMode(SWosc1, INPUT_PULLUP);
  pinMode(SWosc2, INPUT_PULLUP);
  pinMode(SWlfo, INPUT_PULLUP);

  pinMode(MS0, OUTPUT);
  pinMode(MS1, OUTPUT);
  pinMode(MS2, OUTPUT);
  pinMode(MS3, OUTPUT);

  MIDI.begin();

  MIDI.setHandleNoteOn(myNoteOn);
  MIDI.setHandleNoteOff(myNoteOff);
  MIDI.setHandlePitchBend(myPitchBend);
  MIDI.setHandleControlChange(myControlChange);

  usbMIDI.setHandleControlChange(myControlChange);
  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandlePitchChange(myPitchBend);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.32);
  waveform1.begin(WAVEFORM_SAWTOOTH);
  waveform1.amplitude(0.75);
  waveform1.frequency(82.41);
  waveform1.pulseWidth(0.15);

  waveform2.begin(WAVEFORM_SAWTOOTH);
  waveform2.amplitude(0.75);
  waveform2.frequency(123);
  waveform2.pulseWidth(0.15);

  pink1.amplitude(1.0);

  mixer1.gain(0, 1.0);
  mixer1.gain(1, 1.0);
  mixer1.gain(2, 0.0);

  envelope1.attack(0);
  envelope1.decay(0);
  envelope1.sustain(1);
  envelope1.release(500);
}


void loop() {
  usbMIDI.read();
  MIDI.read();
  LFOupdate(false, LFOmodeSelect, FILfactor, LFOdepth);
  checkMux();
}

void myNoteOn(byte channel, byte note, byte velocity) {
  if ( note > 23 && note < 108 ) {
    globalNote = note;
    globalVelocity = velocity;
    keyBuff(note, true);
    LFOupdate(true, LFOmodeSelect, FILfactor, LFOdepth);
  }
}

void myNoteOff(byte channel, byte note, byte velocity) {
  if ( note > 23 && note < 108 ) {
    keyBuff(note, false);
  }
}

void myPitchBend(byte channel, int bend) {
  float bendF = bend;
  bendF = bendF / 8192;
  bendF = bendF * bendRange;
  bendF = bendF / 12;
  bendFactor = pow(2, bendF);
  oscSet();
}

void keyBuff(byte note, bool playNote) {
  static byte buff[BUFFER];
  static byte buffSize = 0;

  // Add Note
  if (playNote == true && (buffSize < BUFFER) ) {
    oscPlay(note);
    buff[buffSize] = note;
    buffSize++;
    return;
  }

  // Remove Note
  else if (playNote == false && buffSize != 0) {
    for (byte found = 0; found < buffSize; found++) {
      if (buff[found] == note) {
        for (byte gap = found; gap < (buffSize - 1); gap++) {
          buff[gap] = buff[gap + 1];
        }
        buffSize--;
        buff[buffSize] = 255;
        if (buffSize != 0) {
          oscPlay(buff[buffSize - 1]);
          return;
        }
        else {
          oscStop();
          return;
        }
      }
    }
  }
}

void oscPlay(byte note) {
  waveform1.frequency(noteFreqs[note + octave1] * bendFactor * LFOpitch);
  waveform2.frequency(noteFreqs[note + octave2] * detuneFactor * bendFactor * LFOpitch);
  float velo = 0.75 * (globalVelocity * DIV127);//TEST velocity limit to 0.75
  waveform1.amplitude(velo);
  waveform2.amplitude(velo);
  pink1.amplitude(velo);
  //envelope1.releaseNoteOn(5);//TEST
  envelope1.noteOn();
}

void oscStop() {
  envelope1.noteOff();
}

void oscSet() {
  waveform1.frequency(noteFreqs[globalNote + octave1] * bendFactor * LFOpitch);
  waveform2.frequency(noteFreqs[globalNote + octave2] * detuneFactor * bendFactor * LFOpitch);
}

void myControlChange(byte channel, byte control, byte value) {
  switch (control) {
    case CCmixer1:
      mixer1.gain(0, 0.3 * (value * DIV127)); //TEST gain limit to 0.3
      break;

    case CCmixer2:
      mixer1.gain(1, 0.3 * (value * DIV127));
      break;

    case CCmixer3:
      mixer1.gain(2, 0.3 * (value * DIV127));
      break;

    case CCoctave:
      switch (value) {
        case 0:
          octave2 = 24;
          break;
        case 1:
          octave2 = 12;
          break;
        case 2:
          octave2 = 0;
          break;
        case 3:
          octave2 = -12;
          break;
        case 4:
          octave2 = -24;
          break;
      }
      oscSet();
      break;

    case CCattack:
      envelope1.attack((3000 * (value * DIV127)) + 10.5);//TEST Attack min limit to 10.5ms
      break;

    case CCdecay:
      envelope1.decay(3000 * (value * DIV127));
      break;

    case CCsustain:
      envelope1.sustain(value * DIV127);
      break;

    case CCrelease:
      envelope1.release(3000 * (value * DIV127));
      break;

    case CCosc1:
      switch (value) {
        case 0:
          waveform1.begin(WAVEFORM_SINE);
          osc1Mode = 0;
          break;
        case 1:
          waveform1.begin(WAVEFORM_TRIANGLE);
          osc1Mode = 1;
          break;
        case 2:
          waveform1.begin(WAVEFORM_SAWTOOTH);
          osc1Mode = 2;
          break;
        case 3:
          waveform1.begin(WAVEFORM_PULSE);
          osc1Mode = 3;
          break;
      }
      break;

    case CCosc2:
      switch (value) {
        case 0:
          waveform2.begin(WAVEFORM_SINE);
          osc2Mode = 0;
          break;
        case 1:
          waveform2.begin(WAVEFORM_TRIANGLE);
          osc2Mode = 1;
          break;
        case 2:
          waveform2.begin(WAVEFORM_SAWTOOTH);
          osc2Mode = 2;
          break;
        case 3:
          waveform2.begin(WAVEFORM_PULSE);
          osc2Mode = 3;
          break;
      }
      break;

    case CCdetune:
      detuneFactor = 1 - (0.05 * (value * DIV127));
      oscSet();
      break;

    case CCfilterfreq:
      FILfactor = value * DIV127;
      FILfreq = 10000 * (value * DIV127);
      if (LFOmodeSelect < 1 || LFOmodeSelect > 5)filter1.frequency(FILfreq);
      break;

    case CCfilterres:
      filter1.resonance((4.3 * (value * DIV127)) + 0.7);
      break;

    case CCbendrange:
      if (value <= 12 && value > 0) {
        bendRange = value;
      }
      break;

    case CClfospeed:
      {
        float xSpeed = value * DIV127;
        xSpeed = pow(100, (xSpeed - 1));
        LFOspeed = (70000 * xSpeed);
        break;
      }

    case CClfodepth:
      LFOdepth = value * DIV127;
      break;

    case CClfomode:
      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();
        filter1.frequency(FILfreq);
      }
      else if (mode >= 1 || mode <= 7) {
        LFOpitch = 1;
        oscSet();
      }
      else if (mode >= 9 || mode <= 13) {
        filter1.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
        filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 2: //Filter DOWN
        if (retriggered == true) {
          LFOdirection = true;
          LFO = 1.0;
        }
        filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 3: //Filter UP
        if (retriggered == true) {
          LFOdirection = false;
          LFO = 0;
        }
        filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 4: //Filter 1-DN
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = true;
          LFO = 1.0;
        }
        if (LFOstop == false) filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 5: //Filter 1-UP
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = false;
          LFO = 0;
        }
        if (LFOstop == false) filter1.frequency(10000 * ((LFOrange * LFO) + LFOdepth));
        break;
      case 8: //Pitch OFF
        return;
        break;
      case 9: //Pitch FREE
        LFOpitch = (LFO * LFOdepth) + 1;
        oscSet();
        break;
      case 10: //Pitch DOWN
        if (retriggered == true) {
          LFOdirection = true;
          LFO = 1.0;
        }
        LFOpitch = (LFO * LFOdepth) + 1;
        oscSet();
        break;
      case 11: //Pitch UP
        if (retriggered == true) {
          LFOdirection = false;
          LFO = 0;
        }
        LFOpitch = (LFO * LFOdepth) + 1;
        oscSet();
        break;
      case 12: //Pitch 1-DN
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = true;
          LFO = 1.0;
        }
        if (LFOstop == false) {
          LFOpitch = (LFO * LFOdepth) + 1;
          oscSet();
        }
        break;
      case 13: //Pitch 1-UP
        if (retriggered == true) {
          LFOstop = false;
          LFOdirection = false;
          LFO = 0;
        }
        if (LFOstop == false) {
          LFOpitch = (LFO * LFOdepth) + 1;
          oscSet();
        }
        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;
      }
    }
  }
}

void checkMux() {
  static byte muxInput = 0;
  static int muxValues[MnumControls] = {};
  unsigned long currentMicros = micros();
  static unsigned long LFOtime = 0;

  if (currentMicros - LFOtime >= 1000) {
    LFOtime = currentMicros;

    int muxRead = analogRead(MZ);

    if (muxRead > (muxValues[muxInput] + 7) || muxRead < (muxValues[muxInput] - 7)) {
      muxValues[muxInput] = muxRead;
      muxRead = (muxRead >> 3); //Change range to 0-127
      switch (muxInput) {
        case MUXosc1:
          muxRead = muxRead / 32;
          if (muxRead != osc1Mode) myControlChange(0, CCosc1, muxRead);
          break;
        case MUXosc2:
          muxRead = muxRead / 32;
          if (muxRead != osc2Mode) myControlChange(0, CCosc2, muxRead);
          break;
        case MUXdetune:
          myControlChange(0, CCdetune, muxRead);
          break;
        case MUXmix1:
          myControlChange(0, CCmixer1, muxRead);
          break;
        case MUXmix2:
          myControlChange(0, CCmixer2, muxRead);
          break;
        case MUXmix3:
          myControlChange(0, CCmixer3, muxRead);
          break;
        case MUXattack:
          myControlChange(0, CCattack, muxRead);
          break;
        case MUXdecay:
          myControlChange(0, CCdecay, muxRead);
          break;
        case MUXsustain:
          myControlChange(0, CCsustain, muxRead);
          break;
        case MUXrelease:
          myControlChange(0, CCrelease, muxRead);
          break;
        case MUXlfospeed:
          myControlChange(0, CClfospeed, muxRead);
          break;
        case MUXlfodepth:
          myControlChange(0, CClfodepth, muxRead);
          break;
        case MUXlfomode:
          muxRead = muxRead / 24;
          myControlChange(0, CClfomode, muxRead);
          break;
        case MUXfilterres:
          myControlChange(0, CCfilterres, muxRead);
          break;
        case MUXfilterfreq:
          myControlChange(0, CCfilterfreq, muxRead);
          break;
      }
    }

    muxInput++;
    if (muxInput >= MnumControls) muxInput = 0;
    digitalWrite(MS0, muxInput & B0001);
    digitalWrite(MS1, muxInput & B0010);
    digitalWrite(MS2, muxInput & B0100);
    digitalWrite(MS3, muxInput & B1000);

    checkSwitch();
  }
}

void checkSwitch() {

  if (digitalRead(SWosc1)) {
    octave1 = 12;
  } else {
    octave1 = 0;
  }

  if (digitalRead(SWosc2)) {
    octave2 = -12;
  } else {
    octave2 = 0;
  }

  if (digitalRead(SWlfo)) {
    if (LFOmodeSelect < 8) LFOmodeSelect = LFOmodeSelect + 8;
  } else {
    if (LFOmodeSelect >= 8) LFOmodeSelect = LFOmodeSelect - 8;
  }
}
It uses a 6N138 as a MIDI In opto-isolator and a 74HC4067 as a multiplexer. There is a circuit diagram in the YT presentation.

I have of course checked all of my connections and solder joints in case anything was dislodged during the final installation. I have also removed the Teensy from the circuit and soldered it back in again, but it refuses to work. I loaded a test sketch which runs an audio sweep, and that runs fine, so the audio adapter seems OK. In case there was a problem with the MIDI input, I tried running it via the USB from my PC, but it didn't respond.

I have checked with an oscilloscope and found that the MIDI data is going into the Rx pin (I've tried RX1 and RX4). But when I put the 'scope on the Mux pins (address or output), there was nothing, which makes me think that the Teensy has stopped.

I hope that I've provided enough information.
 
Slight update: When I measure on the 4 Mux selection pins, some are high and some are low, but all static with the corresponding potentiometer value on the Mux output, suggesting that the program has stopped. I'm beginning to conclude that the Teensy 4 must be faulty, although it runs the frequency sweep sketch OK. If it had never worked, I'd suspect a compatibility problem with the sketch written for a 3.2 running on a 4.0; but it did work once, so what can have changed? I must confess that I don't totally understand the section of code that reads the Mux inputs.

The Mux feeds are: S0 - D5, S1 - D14, S2 - D9, S3 - D22 which all seem to be available (i.e. not used by the audio shield).

Can anyone help please?
 
Success, mostly! I've found that if I comment out the last part of the code, it works fine:

Code:
  if (digitalRead(SWlfo)) {
    if (LFOmodeSelect < 8) LFOmodeSelect = LFOmodeSelect + 8;
  } else {
    if (LFOmodeSelect >= 8) LFOmodeSelect = LFOmodeSelect - 8;
  }
This seems a very complicated way of checking a switch position. I'll try to figure out what is the problem with it, and why it worked once.previously.
 
In:-
Code:
void LFOupdate(bool retrig, byte mode, float FILtop, float FILbottom)  //etc
It looks like the Lfo has 16 modes and
Code:
if (digitalRead(SWlfo)) {
    if (LFOmodeSelect < 8) LFOmodeSelect = LFOmodeSelect + 8;
  } else {
    if (LFOmodeSelect >= 8) LFOmodeSelect = LFOmodeSelect - 8;
  }
is intended to look at a buttonpress to step thru all 16 so am wondering if the wiring to the SWlfo is picking up noise and has got the code busy with continually changing LFOmode.
Maybe a pullup on the SWlfo pin? Am watching with interest as will be installing holes and building one soon.
 
Thanks for your reply. There is already a pullup on SWlfo:

Code:
  pinMode(SWosc1, INPUT_PULLUP);
  pinMode(SWosc2, INPUT_PULLUP);
  pinMode(SWlfo, INPUT_PULLUP);

LFO mode has 6 options, so I can't work out what the 8's are all about.

SWlfo is a switch which selects between LFO control of pitch or filter, so I can't see what relevance LFOmode has, but presumably N&V has a reason to do it this way.
 
I've gone back to the YT video to carefully check the explanation:

https://www.youtube.com/watch?v=zphkBTkOIyQ

I now understand how this works and have added some explanatory comments to the code.

Code:
if (digitalRead(SWlfo)) {
    if (LFOmodeSelect < 8) LFOmodeSelect = LFOmodeSelect + 8; //selects modes 8-13 (pitch)
  } else {
    if (LFOmodeSelect >= 8) LFOmodeSelect = LFOmodeSelect - 8; //selects modes 0-5 (filter)
  }

I've re-enabled this part of the code, and strangely it's now all working. Although I had to switch power on and off a few times this morning to get it to work.

One subtle change I've made is in line 578 - changing the divisor to 22 instead of 24 gives better range of the LFO Mode potentiometer, which makes it line up better with the legend on the panel.

Code:
        case MUXlfomode:
          muxRead = muxRead / 22; //was 24, but 22 improves range of control for 6 modes (128/22=5.82, 128/24=5.33)
 
Status
Not open for further replies.
Back
Top