Teensy LC + Audio Library + PWM?

Status
Not open for further replies.

blakeAlbion

Well-known member
Hello All,

tldr: Are some PWM pins timers mutually exclusive to the Teensy Audio Library on the LC?

I can share some snippets when I get on my main laptop this afternoon.

I wanted a MIDI-controller for 5-6 old school twin-T-oscillator drum sound circuits. The Teensy LC does this well, including envelopes/white noise from the Audio Library and PWM control of vactrols for the drum pitches. (I can easily make an 808-ish kick drum with a falling pitch this way.)

The problem I have is, the PWM pins seem to only work at their default frequencies, which is not ideal for the drum circuits. I mean, they work better than I thought they would, but there's still PWM hum in the circuits via the vactrols. I would like to take the PWM frequency above the range of hearing, if possible. However, changing the frequency seems to break the Audio Library (it behaves as if disconnected.)

My hope is, when I get a chance to work on it again, can I change the PWM frequency of SOME pins but leave the ones shared by the Audio Library timers unchanged? And if so, is there a map somewhere showing which pins would share a timer with the Audio Library? If "timer" is the wrong term, I apologize. I am assuming there's some shared resource between PWM and the Audio Library in the LC.

Right now I have a for loop that initializes all the PWM pins I use with the same settings. I think I would need to tweak that.

If I can adjust the PWM pitches, I could move away from vactrols to transistors and reduce the cost of this simple circuit even more.

Thanks,
Ben
 
Last edited:
here's the main INO file

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

#include "ext_envelope.h"
#include "observer.h"

// GUItool: begin automatically generated code
AudioSynthWaveformDc dc4;         // xy=627,242
AudioSynthWaveformDc dc3;         // xy=628,181
AudioSynthWaveformDc dc1;         // xy=633,62
AudioSynthWaveformDc dc2;         // xy=633,126
AudioSynthWaveformDc dc6;         // xy=632,398
AudioSynthWaveformDc dc5;         // xy=634,310
AudioExternalEnvelope envelope4;  // xy=870,261
AudioExternalEnvelope envelope6;  // xy=871,393
AudioExternalEnvelope envelope3;  // xy=872,191
AudioExternalEnvelope envelope5;  // xy=872,325
AudioExternalEnvelope envelope2;  // xy=878,132
AudioExternalEnvelope envelope1;  // xy=879,70
AudioMixer4 mixer1;               // xy=1049,116
AudioMixer4 mixer2;               // xy=1052,315
AudioSynthNoiseWhite noise1;      // xy=1063,424
AudioMixer4 mixer3;               // xy=1203,248
AudioOutputAnalog pwm1;           // xy=1370,264
AudioConnection patchCord1(dc4, envelope4);
AudioConnection patchCord2(dc3, envelope3);
AudioConnection patchCord3(dc1, envelope1);
AudioConnection patchCord4(dc2, envelope2);
AudioConnection patchCord5(dc6, envelope6);
AudioConnection patchCord6(dc5, envelope5);
AudioConnection patchCord7(envelope4, 0, mixer1, 3);
AudioConnection patchCord8(envelope6, 0, mixer2, 1);
AudioConnection patchCord9(envelope3, 0, mixer1, 2);
AudioConnection patchCord10(envelope5, 0, mixer2, 0);
AudioConnection patchCord11(envelope2, 0, mixer1, 1);
AudioConnection patchCord12(envelope1, 0, mixer1, 0);
AudioConnection patchCord13(mixer1, 0, mixer3, 0);
AudioConnection patchCord14(mixer2, 0, mixer3, 1);
AudioConnection patchCord15(noise1, 0, mixer3, 3);
AudioConnection patchCord16(mixer3, pwm1);
// GUItool: end automatically generated code

MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial3, MIDI3);

const int numLeds = 6;
int pwmLeds[numLeds] = {16, 6, 3, 10, 17, 20};
int triggerPins[numLeds] = {13, 2, 8, 12, 14, 19};

AudioExternalEnvelope* envelopes[numLeds] = {
    &envelope1, &envelope2, &envelope3, &envelope4, &envelope5, &envelope6};

AudioExternalEnvelope* envelope = NULL;

bool CLOCK_RUNNING = false;

int delayOneShot = 10;

// fixme these are fake
unsigned long futures[] = {0, 0, 0, 0, 0, 0};

const int baseline = 600;

int RECEIVE_CHANNEL = 10;

const int MAX_PINS = 24;
float pitches[MAX_PINS];
float envelopeAmounts[MAX_PINS];
int envelopeValues[MAX_PINS];
int currentPinValues[MAX_PINS];

unsigned long ttimer = 0;

// minimum refactor
// snare should trigger tom

// low    syn
// tom //snare    wb // rim
// high

void onEnvelopeChange(int refId, int value) {
  // Serial.println("onEnvelopeChange " + String(refId) + ", " +
  // String(triggerPins[refId]) + ", " + String(pwmLeds[refId]) + ", " +
  // String(value)); analogWrite(pwmLeds[refId], 100 + (value));
  envelopeValues[refId] = value;
  updatePitch(refId, false);
}

void setup() {
  Serial.begin(115200);
  Serial2.begin(19200);
  while (!Serial && millis() < 3000)
    ;
  AudioMemory(6);
  analogWriteResolution(12);
  setupMidi();

  setupModulation();

  noise1.amplitude(0.4);

  dc1.amplitude(1.0);
  dc2.amplitude(1.0);
  dc3.amplitude(1.0);
  dc4.amplitude(1.0);
  dc5.amplitude(1.0);
  dc6.amplitude(1.0);
  mixer1.gain(0, 1.0);
  mixer1.gain(1, 1.0);
  mixer1.gain(2, 1.0);
  mixer1.gain(3, 1.0);
  mixer2.gain(0, 1.0);
  mixer2.gain(1, 1.0);
  mixer2.gain(2, 1.0);
  mixer2.gain(3, 1.0);
  mixer3.gain(0, 0.0);
  mixer3.gain(1, 0.0);
  mixer3.gain(2, 0.0);
  mixer3.gain(3, 1.0);

  int pwmFreq = 600000;  // 600000
  for (int i = 0; i < numLeds; i++) {
    pinMode(pwmLeds[i], OUTPUT);
    pinMode(triggerPins[i], OUTPUT);
    // analogWriteFrequency(pwmLeds[i], pwmFreq);
  }

  //  pinMode(2, INPUT_PULLUP);
  //  pushbuttonTrigger.attach(2);
  //  pushbuttonTrigger.interval(5);

  digitalWrite(13, HIGH);

  Serial.println("OK");
  // put your setup code here, to run once:
  // envelope1.setObserver(this);
  setupEnvelopes();

  Serial.println("envelopes OK");

  delay(10);

  // analogWrite(20, 2175);

  digitalWrite(13, LOW);

  for (int i = 0; i < numLeds; i++) {
    updatePitch(pwmLeds[i], true);
  }

  Serial.println("DONE");

  // parseControlCommand("cc:1,7,43");
}

void parseControlCommand(String input) {
  int where = input.indexOf("cc:");
  String temp;
  byte ch;
  byte cc;
  byte val;
  String remain;
  if (where != -1) {
    where = input.indexOf(":");
    // everything after :
    temp = input.substring(where + 1);
    // Serial.println("A: " + temp);
    where = temp.indexOf(",");
    // from start to ,
    remain = temp.substring(where + 1);
    // Serial.println("remain: " + remain);
    temp = temp.substring(0, where);
    // Serial.println("B: " + temp);
    ch = temp.toInt();
    // Serial.println("channel: " + String(ch));
    where = remain.indexOf(",");
    temp = remain.substring(0, where);
    remain = remain.substring(where + 1);
    // Serial.println("remain: " + remain);
    cc = temp.toInt();
    // Serial.println("cc: " + String(cc));
    where = remain.indexOf(",");
    // from start to ,
    temp = remain.substring(0, where);
    // Serial.println("D: " + temp);
    val = temp.toInt();
    // Serial.println("value: " + String(val));
    handleControlChange(ch, cc, val);
  }
}

String buffer = "";

String checkSerial() {
  String payload = "";
  if (Serial2.available()) {
    int inByte = Serial2.read();
    buffer = buffer + String((char)inByte);
    if (inByte == 13) {
      payload = buffer.trim();
      // Serial1.println("ACK:" + buffer);
      buffer = "";
    }
  }
  return payload;
}

void setupModulation() {
  for (int i = 0; i < MAX_PINS; i++) {
    pitches[i] = 0.2f;
    envelopeAmounts[i] = 0.0f;
    envelopeValues[i] = 0;
    currentPinValues[i] = 0;
  }
}

void setupEnvelopes() {
  for (int i = 0; i < numLeds; i++) {
    envelope = envelopes[i];
    envelope->setRefId(pwmLeds[i]);
    envelope->setCallback(onEnvelopeChange);
    envelope->sustain(0);
    envelope->decay(200);
    envelope->release(200);
    // analogWrite(pwmLeds[i], baseline);
  }
  //  delay(10);
  //  for (int i = 0; i < numLeds; i++) {
  //    envelope = envelopes[i];
  //    envelope->noteOn();
  //  }
}

void handleNoteOnMidi(byte channel, byte pitch, byte velocity) {
  // digitalWrite(13, HIGH);
  Serial.println(String(pitch));
  // 36
  // 35
  // 38
  // 40
  // 42
  // 46
  // 50 l
  // 51
  // 47 m
  // 49
  // 43 h
  // 60 x 49
  // 61

  if (pitch < 37) {
    // sample_player_1.playBuffered(velocity); // kick
    triggerOneShot(0);
  } else if (pitch == 50) {  // High Tom
    triggerOneShot(2);
  } else if (pitch == 38 || pitch == 40) {  // Low-Mid Tom // will go to SD
    triggerOneShot(4);
  } else if (pitch == 43) {  // High Floor Tom
    triggerOneShot(5);
  } else if (pitch == 47) {  // Crash Cymbal 1
    triggerOneShot(1);
  } else if (pitch == 61) {  // Low Bongo
    triggerOneShot(3);
  }
}

// 0 2 4 1 5 3

void handleNoteOffMidi(byte channel, byte pitch, byte velocity) {
  // digitalWrite(13, LOW);
  //  if (pitch < 37) {
  //    // sample_player_1.playBuffered(velocity); // kick
  //    stopOneShot(0);
  //  } else if (pitch == 50) {
  //    stopOneShot(2);
  //  } else if (pitch == 47) {
  //    stopOneShot(4);
  //  } else if (pitch == 43) {
  //    stopOneShot(1);
  //  } else if (pitch == 61 || pitch == 49) {
  //    stopOneShot(5);
  //  } else if (pitch == 60 || pitch == 37) {
  //    stopOneShot(3);
  //  }
}

void onClockStart() {
  CLOCK_RUNNING = true;
  //  for (int i = 0; i < numLeds; i++) {
  //    updatePitch(pwmLeds[i], true);
  //  }
}

void onClockStop() {
  CLOCK_RUNNING = false;
  //  for (int i = 0; i < numLeds; i++) {
  //    analogWrite(pwmLeds[i], 0);
  //  }
  //  for (int i = 0; i < numLeds; i++) {
  //    updatePitch(pwmLeds[i], true);
  //  }
}

void setPitch(int pin, int value) {
  float ratio = value / 4064.0;
  pitches[pin] = ratio;
  updatePitch(pin, false);
}

void setEnvelope(int pin, int value) {
  // float ratio = value / 4064.0;
  float ratio = value / 4064.0;
  envelopeAmounts[pin] = ratio;
  updatePitch(pin, false);
}

int updatePitch(int pin, bool force) {
  // Multiply pitch by 4096
  // Multiply envelope * envelope amplitude
  if (force) {
    currentPinValues[pin] = -1;
  }
  float pitchSetting = pitches[pin];
  float envelopeSetting = envelopeAmounts[pin];
  int envelopeValue = envelopeValues[pin];
  int value = (pitchSetting * 4096) + (envelopeSetting * envelopeValue);
  if (value > 4096) {
    value = 4096;
  }
  // Serial.println("updatePitch " + String(pin) + ", " + String(value));
  if (currentPinValues[pin] != value) {
    //    if (value > 0) {
    //      Serial.println("updatePitch " + String(pin) + ", " + String(value));
    //    }
    currentPinValues[pin] = value;
    analogWrite(pin, value);
  }
}

int controlMode = 0;

void handleControlChange(unsigned char channel, unsigned char controlNumber,
                         unsigned char controlValue) {
  if (controlNumber == 71) {
    if (controlValue < 32) {
      controlMode = 0;
    } else if (controlValue < 64) {
      controlMode = 1;
    } else if (controlValue < 96) {
      controlMode = 2;
    } else if (controlValue < 128) {
      controlMode = 3;
    }
  }
  if (controlMode == 0) {
    // 0 2 4 1 5 3
    switch (controlNumber) {
      case 31:
        setPitch(pwmLeds[0], controlValue * 32);
        break;
      case 32:
        setPitch(pwmLeds[2], controlValue * 32);
        break;
      case 33:
        setPitch(pwmLeds[4], controlValue * 32);
        break;
      case 34:
        setPitch(pwmLeds[1], controlValue * 32);
        break;
      case 35:
        setPitch(pwmLeds[5], controlValue * 32);
        break;
      case 36:
        setPitch(pwmLeds[3], controlValue * 32);
        break;

      default:
        break;
    }
  } else if (controlMode == 1) {
    // 0 2 4 1 5 3
    switch (controlNumber) {
      case 31:
        setEnvelope(pwmLeds[0], controlValue * 32);
        break;
      case 32:
        setEnvelope(pwmLeds[2], controlValue * 32);
        break;
      case 33:
        setEnvelope(pwmLeds[4], controlValue * 32);
        break;
      case 34:
        setEnvelope(pwmLeds[1], controlValue * 32);
        break;
      case 35:
        setEnvelope(pwmLeds[5], controlValue * 32);
        break;
      case 36:
        setEnvelope(pwmLeds[3], controlValue * 32);
        break;
      default:
        break;
    }
  } else if (controlMode == 2) {
    // 0 2 4 1 5 3
    float decay = (controlValue * 16);
    switch (controlNumber) {
      case 31:
        envelope1.decay(decay);
        envelope1.release(decay);
        break;
      case 32:
        envelope3.decay(decay);
        envelope3.release(decay);
        break;
      case 33:
        envelope5.decay(decay);
        envelope5.release(decay);
        break;
      case 34:
        envelope2.decay(decay);
        envelope2.release(decay);
        break;
      case 35:
        envelope6.decay(decay);
        envelope6.release(decay);
        break;
      case 36:
        envelope4.decay(decay);
        envelope4.release(decay);
        break;
      default:
        break;
    }
  } else if (controlMode == 3) {
    RECEIVE_CHANNEL = controlValue / 8;
  }
}

void handlePitchBend(byte channel, int pitch) {}

void setupMidi() {
  Serial.println("setupMidi");
  // use MIDI_CHANNEL_OMNI for testing
  MIDI.begin(
      RECEIVE_CHANNEL);  // Launch MIDI, by default listening to channel 10.
  // Connect MIDI status changes involving a channel to handlers
  MIDI.setHandleNoteOn(handleNoteOnMidi);
  MIDI.setHandleNoteOff(handleNoteOffMidi);
  MIDI.setHandleControlChange(handleControlChange);
  //  MIDI.setHandleProgramChange(handleProgramChange);
  MIDI.setHandlePitchBend(handlePitchBend);
  //
  //  MIDI.setHandleClock(myClock);
  MIDI.setHandleStart(onClockStart);
  MIDI.setHandleContinue(onClockStart);
  MIDI.setHandleStop(onClockStop);

  MIDI3.begin(
      MIDI_CHANNEL_OMNI);  // Launch MIDI, by default listening to channel 10.
  // Connect MIDI status changes involving a channel to handlers
  MIDI3.setHandleNoteOn(handleNoteOnMidi);
  MIDI3.setHandleNoteOff(handleNoteOffMidi);
  MIDI3.setHandleControlChange(handleControlChange);
  //  MIDI.setHandleProgramChange(handleProgramChange);
  MIDI3.setHandlePitchBend(handlePitchBend);
  //
  //  MIDI.setHandleClock(myClock);
  MIDI3.setHandleStart(onClockStart);
  MIDI3.setHandleContinue(onClockStart);
  MIDI3.setHandleStop(onClockStop);

  Serial.println("setupMidi DONE");
}

void triggerOneShot(int which) {
  //  for (int i = 0; i < numLeds; i++) {
  //    envelope = envelopes[i];
  //    envelope->noteOn();
  //  }
  envelopes[which]->noteOn();
  Serial.println("triggerOneShot " + String(which) + ", " +
                 String(triggerPins[which]));
  digitalWrite(triggerPins[which], HIGH);
  futures[which] = millis() + delayOneShot;
}

void stopOneShot(int which) {
  envelopes[which]->noteOff();
  Serial.println("stopOneShot " + String(which) + ", " +
                 String(triggerPins[which]));
  digitalWrite(triggerPins[which], LOW);
  futures[which] = 0;
}

void loop() {
  MIDI.read();
  MIDI3.read();
  if (!CLOCK_RUNNING) {
    // checkSerial is not for time-critical loops
    String temp = checkSerial();
    if (temp.length() > 0) {
      Serial.println(temp);
      parseControlCommand(temp);
    }
  }
  for (int i = 0; i < numLeds; i++) {
    if (futures[i] > 0) {
      if (millis() >= futures[i]) {
        stopOneShot(i);
      }
    }
  }
  if (ttimer == 0) {
    ttimer = millis() + 250;
  }
}
 
This uses the Audio Library on the DAC port.
It uses a number of PWM pins and digital pins to control a PAIA drum tone board with vactrols added.
Code:
const int numLeds = 6;
int pwmLeds[numLeds] = {16, 6, 3, 10, 17, 20};
int triggerPins[numLeds] = {13, 2, 8, 12, 14, 19};

If I try to set the PWM frequencies, the audio library does not initialize.

Code:
  int pwmFreq = 600000;  // 600000
  for (int i = 0; i < numLeds; i++) {
    pinMode(pwmLeds[i], OUTPUT);
    pinMode(triggerPins[i], OUTPUT);
    // analogWriteFrequency(pwmLeds[i], pwmFreq); // NOOOO!
  }
 
LC documentation says,


TPM0 - Controls PWM pins 6, 9, 10, 20, 22, 23. Used by AltSoftSerial library and PulsePosition library.
TPM1 - Controls PWM pins 16, 17.
TPM2 - Controls PWM pins 3, 4.

I am assuming one of these timers is also used by the Teensy Audio Library?
 
This is part of a larger project using Teensy 4 and LC to control analog synthesizer circuits. The PAIA drum tone board provides the basic percussion sounds, and I am using the Teensy Audio Library to generate white noise, old-school cymbal sounds going into an analog state variable filter built with and AS2164 chip. I also built an analog bass synth which uses the Teensy Audio library for the DCOs.

1220670392.jpeg
https://vimeo.com/590288694
 
Status
Not open for further replies.
Back
Top