New Reverbs and Delay for the standard AudioLib (int16_t)

Pio

Well-known member
If anyone would like to try a new interesting reverbs and a modulated ping-pong delay with the standard Teensy Audio Library i've ported a few effects:


Right now there are 4 effects available:
  • Stereo Plate Reverb - the new version with pitch shifters, shimmer, freeze etc.
  • Stereo Spring Reverb
  • Stereo Reverb SC - versatile and very different from the Plate one. Very RAM2 (DMARAM) hungry - ~387k. There is an option to use it with PSRAM +T4.1.
  • Stereo Modulated Ping-Pong Delay - lots of fun in stereo. Can do chorus/flange. Also an option to use PSRAM on T.4.1 for longer delay times.
Each effect has an example sketch including an html page with WebMidi control surfaces.
Target hardware is just Teensy4.x + Audio Adapter Rev.D.
Simply compile, upload, open a corresponding html file in Chrome (browser needs to support WebMidi) and play with the controls.
Internally all effects still work on floats, having the I/O scaled to int16_t.
 
New additions:
  • Stereo 12 Stage Phaser (no demo project yet)
  • GUI
Here is a short sample of the phaser used in a simple synth project. The stereo spread function, showed at the end, adds a phase shift between the left and right modulation waveform.
 
Thanks for your work on this pio, adding your reverb to my synth project sounds great and super easy to do with the int 16 version.
 
  • Like
Reactions: Pio
@Pio: Likewise, I added your Stereo Reverb SC to my TeensyMIDIPolySynth (TMPS) & the resulting sound is excellent (it is also much more processor efficient than anything else I had tried) !! I had previously experimented with several other reverbs that were available, but was not very satisfied with the way any of them worked, so I elected for the TMPS not to have a reverb capability. Then you posted your Stereo Reverb SC, & I'm happy to be able to say that the TMPS now has a very nice sounding reverb !! Thanks again for your continued contributions !!

Mark J Culross
KD5RXT
 
Glad to hear it's being used. Hope these add-ons will be easier to incorporate with the gui tool.
 
Unfortunately it appears I spoke too soon: When I reorganized my code suddenly the _I16 effects are no longer working, and instead I'm getting "Errno 6: Device not configured" when I try to use USB Audio. I'm a little stumped, and not sure if it's my code or the _I16 effects implementations. Can anybody help point me in the right direction?

In short, I have tried these configurations:
1. (Works) Getting dry audio output from Synth object (defined in synth.h) and piping it through AudioEffectPlateReverb_i16 in main.cpp
2. (Works) Adding a "reverb" chain in the synth.h signal path, where the "reverb" object is either an AudioAmplifier or an AudioEffectPlateReverb (NOT the _i16 version; from Pio's older work here: https://github.com/hexeguitar/t40fx/tree/main/Hx_PlateReverb)
3. (Fails: sketch compiles but no audio out and Serial reports Errno6) Adding a reverb chain in the synth.h signal path, where the "reverb" object is either an AudioEffectPlateReverb_i16 or AudioEffectSpringReverb_i16

Here's my main.cpp and synth.h files (based on https://github.com/OliverLSanz/teensy-headless-mpe-synth), with comments outlining how to switch between the 3 options above (currently set up for Option 1):

C++:
#include <Audio.h>
#include "Synth.h"
#include <Arduino.h>
#include <Wire.h>
#include "effect_platereverb_i16.h"

//#include "USBHost_t36.h"

// Set up USB host midi interface
//USBHost myUSBHost;
//MIDIDevice slaveMidiDevice(myUSBHost);

Synth *synth = new Synth();

AudioOutputI2S           i2s1;          
AudioOutputUSB usb0;

// **** CHANGES 1/2 ************************************************************

// Option 2/3 (works if *synth->reverb is AudioAmplifier or AudioEffectPlateReverb,
// fails if *synth->reverb is AudioEffectPlateReverb_i16)
//AudioConnection patchCordOutL(*synth->getOutputL(), 0, usb0, 0);
//AudioConnection patchCordOutR(*synth->getOutputR(), 0, usb0, 1);

// Option 1 (works; tested with *synth->reverb is AudioAmplifier)
AudioEffectPlateReverb_i16 reverb;
AudioConnection patchCordReverbL(*synth->getOutputL(), 0, reverb, 0);
AudioConnection patchCordReverbR(*synth->getOutputR(), 0, reverb, 1);
AudioConnection patchCordOutL(reverb, 0, usb0, 0);
AudioConnection patchCordOutR(reverb, 0, usb0, 1);

//AudioControlSGTL5000     sgtl5000_1;

void onNoteOn(byte channel, byte note, byte velocity) {
  synth->noteOn(channel, note, velocity);
  digitalWrite(LED_BUILTIN, HIGH);
}

void onNoteOff(byte channel, byte note, byte velocity) {
  synth->noteOff(channel, note, velocity);
  digitalWrite(LED_BUILTIN, LOW);
}

void onMidiControlChange(byte channel, byte control, byte value){
  synth->controlChange(channel, control, value);
}

void onPitchChange(byte channel, int pitch){
  synth->pitchChange(channel, pitch);
}

void onAfterTouch(byte channel, byte pressure){
  //Serial.println("Received Aftertouch Channel");
  synth->afterTouch(channel, pressure);
}

void onAfterTouchPoly(byte channel, byte note, byte pressure){
  //Serial.println("Received aftertouch Poly");
  synth->afterTouch(channel, pressure);
}


int next_check{millis()+5000};

void setup() {
  Serial.begin(115200);

  //myUSBHost.begin();
    //slaveMidiDevice.setHandleNoteOff(onNoteOff);
    //slaveMidiDevice.setHandleNoteOn(onNoteOn);
    //slaveMidiDevice.setHandleControlChange(onMidiControlChange);
  //slaveMidiDevice.setHandlePitchChange(onPitchChange);
  //slaveMidiDevice.setHandleAfterTouchChannel(onAfterTouch);

  //pinMode(LED_BUILTIN, OUTPUT);

  AudioMemory(120);

  //sgtl5000_1.enable();
  //sgtl5000_1.volume(0.5);

  usbMIDI.setHandleNoteOn(onNoteOn);
  usbMIDI.setHandleNoteOff(onNoteOff);
  usbMIDI.setHandleControlChange(onMidiControlChange);
  usbMIDI.setHandlePitchChange(onPitchChange);
  usbMIDI.setHandleAfterTouch(onAfterTouch);
  usbMIDI.setHandleAfterTouchPoly(onAfterTouchPoly);
 

  // **** Changes 2/2 **********************************************************

  // Option 2/3 (nothing)

  // Option 1 (Works)
  reverb.size(1.0);     // max reverb length
  reverb.lowpass(0.3);  // sets the reverb master lowpass filter
  reverb.lodamp(0.1);   // amount of low end loss in the reverb tail
  reverb.hidamp(0.2);   // amount of treble loss in the reverb tail
  reverb.diffusion(1.0);  // 1.0 is the detault setting, lower it to create more "echoey" reverb
  reverb.wet_level(1.0f);
  reverb.bypass_set(false);

  // **** END CHANGES 2/2 ******************************************************

  // Starting sequence
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.println("MPE Synth Ready!");

}

void loop() {
    //myUSBHost.Task();
      //slaveMidiDevice.read();
    usbMIDI.read();

    int now{millis()};

    if (now > next_check){
      Serial.print("%, Total CPU Usage: ");
      Serial.print(AudioProcessorUsageMax());
      Serial.println("%");
      AudioProcessorUsageMaxReset();
      next_check = now + 5000;
    }



}

And here's (working) synth.h, using an AudioAmplifier as a "reverb":

C++:
#ifndef Synth_h
#define Synth_h

#include <Audio.h>
#include "Voice.h"

// **** CHANGES 1/5 ************************************************************
#include "hexefx_audio_I16.h"
//#include "effect_platervbstereo.h"
// **** END CHANGES 1/5 ********************************************************

#ifndef ULLONG_MAX
#define ULLONG_MAX 0xffffffffffffffff
#endif

// Number of voices
const byte voiceCount = 1; // max = 16
const byte voicesPerMixer = 4;
const byte mixerCount = voiceCount/voicesPerMixer + voiceCount%voicesPerMixer > 0? 1:0;
//const byte mixerCount = 4;

/*
 * Synth
 */
class Synth{
 
  private:
    Voice *voices[voiceCount];
    
    AudioConnection* patchCordsVoices[mixerCount + voiceCount];
    AudioMixer4 *mixersVoices0[mixerCount];
    AudioMixer4 *mixerSynOut;

    AudioConnection *patchCordReverbSendDirectL;
    AudioConnection *patchCordReverbSendDirectR;

    AudioMixer4 *reverbSendL;
    AudioMixer4 *reverbSendR;

    AudioConnection *patchCordReverbL;
    AudioConnection *patchCordReverbR;

    // **** CHANGES 2/5 ********************************************************
    AudioAmplifier *reverb; // WORKS
    //AudioEffectPlateReverb_i16 *reverb; // Errno6: Device not configured
    //AudioEffectPlateReverb *reverb; // ALSO WORKS
    //AudioEffectSpringReverb_i16 *reverb; // Errno6: Device not configured
    
    
    // **** END CHANGES 2/5 ****************************************************

    AudioConnection *patchCordDirectRetL;
    AudioConnection *patchCordDirectRetR;
    AudioConnection *patchCordReverbRetL;
    AudioConnection *patchCordReverbRetR;

    AudioMixer4 *efxReturnsL;
    AudioMixer4 *efxReturnsR;

    AudioConnection *patchCordSendOutL;
    AudioConnection *patchCordSendOutR;

    AudioAmplifier *outputL;
    AudioAmplifier *outputR;

    
  public:
    Synth();

    void noteOn(byte channel, byte note, byte velocity);
    void noteOff(byte channel, byte note, byte velocity);
    void controlChange(byte channel, byte control, byte value);
    void pitchChange(byte channel, int pitch);
    void afterTouch(byte channel, byte pressure);
    Voice **getVoices();

    AudioAmplifier * getOutputL();
    AudioAmplifier * getOutputR();

};

/**
 * Constructor
 */

// create and mix synth voices
inline Synth::Synth(){
  this->mixerSynOut = new AudioMixer4();
  this->mixerSynOut->gain(0, 1.0/float(mixerCount));
  this->mixerSynOut->gain(1, 1.0/float(mixerCount));
  this->mixerSynOut->gain(2, 1.0/float(mixerCount));
  this->mixerSynOut->gain(3, 1.0/float(mixerCount));

  for (int i = 0; i < mixerCount; i++) {
    this->mixersVoices0[i] = new AudioMixer4();
    this->mixersVoices0[i]->gain(0, 0.4);
    this->mixersVoices0[i]->gain(1, 0.4);
    this->mixersVoices0[i]->gain(2, 0.4);
    this->mixersVoices0[i]->gain(3, 0.4);
    
    this->patchCordsVoices[i] =
      new AudioConnection(*this->mixersVoices0[i], 0, *this->mixerSynOut, i%voicesPerMixer);
  }
 
  for (int i = 0; i < voiceCount; i++) {
    this->voices[i] = new Voice();
    this->patchCordsVoices[i] =
      new AudioConnection(*this->voices[i]->getOutput(), 0, *this->mixersVoices0[i/voicesPerMixer], i%voicesPerMixer);
  }

  // EFX chain
 
  this->reverbSendL = new AudioMixer4();
  this->reverbSendR = new AudioMixer4();
 
  this->reverbSendL->gain(0, 1.0);
  this->reverbSendR->gain(0, 1.0);

  this->patchCordReverbSendDirectL =
    new AudioConnection(*this->mixerSynOut, 0, *this->reverbSendL, 0);
  this->patchCordReverbSendDirectR =
    new AudioConnection(*this->mixerSynOut, 0, *this->reverbSendR, 0);
 
  // ***** CHANGES 3/5  ********************************************************
  this->reverb = new AudioAmplifier(); //  WORKS
  // this->reverb = new AudioEffectPlateReverb(); // WORKS
  //this->reverb = new AudioEffectSpringReverb_i16(); // Errno6
  //this->reverb = new AudioEffectPlateReverb_i16(); // errno6
  // ***** END 3/5 *************************************************************

  this->patchCordReverbL =
    new AudioConnection(*this->reverbSendL, 0, *this->reverb, 0);

  // CHANGES 4/5 ***************************************************************
 
  // Option 1
  this->patchCordReverbR =
    new AudioConnection(*this->reverbSendR, 0, *this->reverb, 0);
 
  // Options 2/3
  //this->patchCordReverbR =
  //  new AudioConnection(*this->reverbSendR, 0, *this->reverb, 1);
  // END CHANGES 4/5 ***********************************************************

  this->efxReturnsL = new AudioMixer4();
  this->efxReturnsL->gain(0, 0.5);
  this->efxReturnsL->gain(1, 0.5);
  this->patchCordDirectRetL =
    new AudioConnection(*this->mixerSynOut, 0, *this->efxReturnsL, 0);
  this->patchCordReverbRetL =
    new AudioConnection(*this->reverb, 0, *this->efxReturnsL, 1);

  this->efxReturnsR = new AudioMixer4();
  this->efxReturnsR->gain(0, 0.5);
  this->efxReturnsR->gain(1, 0.5);
  this->patchCordDirectRetR =
    new AudioConnection(*this->mixerSynOut, 0, *this->efxReturnsR, 0);
  this->patchCordReverbRetR =
    new AudioConnection(*this->reverb, 0, *this->efxReturnsR, 1);

  this->outputL =
    new AudioAmplifier();
  this->outputL->gain(1.0);
  this->patchCordSendOutL =
    new AudioConnection(*this->efxReturnsL, *this->outputL);
 
  this->outputR =
    new AudioAmplifier();
  this->outputR->gain(1.0);
  this->patchCordSendOutR =
    new AudioConnection(*this->efxReturnsR, *this->outputR); 

 
  // Configure Reverb

  // **** CHANGES 5/5 **********************************************************

  this->reverb->gain(1.0); // use when reverb is AudioAmplifier; Works
 
  // Use when reverb is AudioEffectPlateReverb, AudioEffectPlateReverb_i16, or AudioEffectSpringReverb_i16
  //this->reverb->size(1.0);     // max reverb length
  //this->reverb->lowpass(0.3);  // sets the reverb master lowpass filter
  //this->reverb->lodamp(0.1);   // amount of low end loss in the reverb tail
  //this->reverb->hidamp(0.2);   // amount of treble loss in the reverb tail
  //this->reverb->diffusion(1.0);  // 1.0 is the detault setting, lower it to create more "echoey" reverb
 
  // Use only for _i16 effects
  //this->reverb->wet_level(1.0f);
  //this->reverb->bypass_set(false);
 
  // **** END CHANGES 5/5 ******************************************************
 

}

/**
 * Note on
 */
inline void Synth::noteOn(byte channel, byte note, byte velocity){
  bool foundOne = false;
  int oldestVoice = 0;
  unsigned long oldestVoiceTime = ULLONG_MAX;

  for (int i = 0; i < voiceCount; i++) {
    // Search for the oldest voice
    if(this->voices[i]->last_played < oldestVoiceTime){
      oldestVoiceTime = this->voices[i]->last_played;
      oldestVoice = i;
    }
    
    // Search for an inactive voice
    if(!this->voices[i]->isActive()){
      this->voices[i]->noteOn(channel, note, velocity);
      foundOne = true;
      break;
    }
  }

  // No inactive voice then will take over the oldest note
  if(!foundOne){
    this->voices[oldestVoice]->noteOn(channel, note, velocity);
  }
}

/**
 * Note off
 */
inline void Synth::noteOff(byte channel, byte note, byte velocity){
  for (int i = 0; i < voiceCount ; i++) {
    if(this->voices[i]->currentNote == note){
      this->voices[i]->noteOff(channel, note, velocity);
    }
  }
}

/**
 * Return the audio output
 */
inline AudioAmplifier * Synth::getOutputL(){
  return this->outputL;
}
inline AudioAmplifier * Synth::getOutputR(){
  return this->outputR;
}


/**
 * Control Change
 */
void Synth::controlChange(byte channel, byte control, byte value){
  for (int i = 0; i < voiceCount ; i++) {
    if(this->voices[i]->channel == channel && this->voices[i]->isActive()){
      this->voices[i]->controlChange(channel, control, value);
    }
  }
}


void Synth::pitchChange(byte channel, int pitch){
  for (int i = 0; i < voiceCount ; i++) {
    if(this->voices[i]->channel == channel && this->voices[i]->isActive()){
      this->voices[i]->pitchChange(channel, pitch);
    }
  }
}

void Synth::afterTouch(byte channel, byte pressure){
    //Serial.print("Received aftertouch");
  for (int i = 0; i < voiceCount ; i++) {
    if(this->voices[i]->channel == channel && this->voices[i]->isActive()){
      this->voices[i]->afterTouch(channel, pressure);
    }
  }
}

inline Voice** Synth::getVoices(){
  return this->voices;
}

#endif
 
Last edited:
After a bit of testing:
1. If i try the reverb_i16 outside the Synth class it works.
2. reverb_i16 used inside the Synth class result in a boot loop. This is probably why it shows Errno6.

I think the problem is caused by spreading the object creation and making connections all over the place instead of doing it the way the GUI it generates:
1. Create objects
2. Make all connections
There is a 4 channel mixer in each Voice object doing practically nothing. It could be used to set the volume for each voice, but the same can be done using the voice mixers.
There is also a huge audio memory pool allocated in your code: 120. Reverb uses a lot of DMARAM, AudioMemory is allocated there , too. This was also causing a problem. Reducing it to 24 fixed the boot loop. The max usage reported with the 16 voice polyphony is 17.

Btw, if there will be only one stereo input (stereo synth out) coming into the Reverb, you can use it's internal dry/wet mixer instead of the 4 channel Send mixers. There is one potential issue with the external mixer: setting the dry signal (Voice) and the reverb channel to 1 will result in output exceeding the -1 - +1 range and clipping. The reverb.mix( 0.0f ... 1.0f) controls the mix ratio and keeps the output amplitude in safe range.
It greatly simplify the internal routing, making it just a simple Synth->Reverb->Amplifier in series.
I played a bit with the code and here is a setup that compiles and works for me, using USB audio as output, sound sample included.
 

Attachments

  • usbaudiosynth.zip
    367.2 KB · Views: 5
Nice! Have fun!
I've also started working on a 8 voice poly-synth based on this one:
I guess, there will be more synth related components in this library.
One idea i'd like to implement is an envelope component with stereo output and volume/pan controls. Each voice will have a stereo output with Pan available also as a modulation input, thinking: LFO, autopanning or envelope controlled pan for each voice separately. Or just randomly pick the panorama setting for each appearing note. With reverb it can create very nice wide sounds.

I wonder if there is a WebMIDI based sequencer existing somewhere on the depths of inet. I've found some, but these were generating sounds, i'd like it to send a MIIDI stream to a chosen port instead. Combined with the touchMIDI it would be a nice tool to demo all the effects in a synth environment.
 
I have a PSRAM chip on my Teensy - are there any special tricks to getting the delay effect to use the PSRAM? Is it as simple as setting use_psram = true when I call new AudioEffectDelayStereo_i16()?
 
I answered my own question above - it seems to have really been that easy.

However, in trying to add delay to the chain I found that the code was really unstable - it would enter the boot loop 90% of the time, seemingly at random and independent of how much memory I allocated with AudioMemory(). It did this any time AudioEffectDelayStereo_i16 or AudioEffectPlateReverb_i16 were in the chain.

Some more googling and liberal use of CrashReport.breadcrumb() led me to here, where AudioBasicDelay.init() calls "free(bf)", which is where my program keeps crashing with a Data Access Violation error. I'm coming from python/R so the explicit memory management is still voodoo to me, but google leads me to believe that you can't free(bf) before it's been allocated here. I also don't know enough to see the implications of this, but just commenting out "free(bf)" resolves the issue for me with no apparent ill effects.
 
You're right about the delay block. I forgot members of a class are initialized only if there is a constructor, even a default one. AudioBasicDelay class did not have one, so it tried to free a buffer at some random address.
Freeing the buffer at the beginning of the init function allows to re-init the delay line to some other length.
I'll push the fix later.
Could you try to add it manually and check if it still crashes?
C++:
class AudioBasicDelay
{
public:
    AudioBasicDelay() { bf = NULL; } // <---- Add constructor here
    ~AudioBasicDelay()
    {
        if(bf) free(bf);
    }
    bool init(uint32_t size_samples,  bool psram=false)
    {
        if(bf) free(bf);
        use_psram = psram;
        size = size_samples;
        if (use_psram)     bf = (float *)extmem_malloc(size * sizeof(float));     // allocate buffer in PSRAM
        else             bf = (float *)malloc(size * sizeof(float));         // allocate buffer in DMARAM
        if (!bf) return false;
        idx = 0;
        reset();
        return true;
    }
    ...
 
Adding the constructor did allow the creation of both AudioEffectDelayStereo_i16 and the AudioEffectPlateREverb_i16, but the program is again crashing with a Data Access Violation after a second or two. This crash was not happening when I just commented out the "free(bf)" call, so I suspect it's some side effect of introducing the constructor. The program consistently makes it through setup and through at least one iteration of loop() before failing *if* I comment out the "delay()" call here, otherwise it crashes while waiting (breadcrumb 778):

C++:
void setup() {

  Serial.begin(9600);
  Serial.print(CrashReport);

  CrashReport.breadcrumb(3, 111111);



  Serial.begin(115200);

  //myUSBHost.begin();
    //slaveMidiDevice.setHandleNoteOff(onNoteOff);
    //slaveMidiDevice.setHandleNoteOn(onNoteOn);
    //slaveMidiDevice.setHandleControlChange(onMidiControlChange);
  //slaveMidiDevice.setHandlePitchChange(onPitchChange);
  //slaveMidiDevice.setHandleAfterTouchChannel(onAfterTouch);
  //pinMode(LED_BUILTIN, OUTPUT);
  CrashReport.breadcrumb(3, 222222);
  synth = new Synth();

  CrashReport.breadcrumb(3, 333333);
  // Option A (works if *synth->reverb is AudioAmplifier or AudioEffectPlateReverb,
  // fails if *synth->reverb is AudioEffectPlateReverb_i16)
  patchCordOutL = new AudioConnection(*synth->getOutputL(), 0, usb0, 0);
  patchCordOutR = new AudioConnection(*synth->getOutputR(), 0, usb0, 1);

  CrashReport.breadcrumb(3, 444444);

  AudioMemory(225);

  CrashReport.breadcrumb(3, 555555);
  //sgtl5000_1.enable();
  //sgtl5000_1.volume(0.5);
 
  usbMIDI.setHandleNoteOn(onNoteOn);
  usbMIDI.setHandleNoteOff(onNoteOff);
  usbMIDI.setHandleControlChange(onMidiControlChange);
  usbMIDI.setHandlePitchChange(onPitchChange);
  usbMIDI.setHandleAfterTouch(onAfterTouch);
  usbMIDI.setHandleAfterTouchPoly(onAfterTouchPoly);
 

  CrashReport.breadcrumb(3, 666666);
  // **** Changes 2/2 **********************************************************

  // Option A (nothing)

  // Option B (Works)
  /*
  reverb.size(1.0);     // max reverb length
  reverb.lowpass(0.3);  // sets the reverb master lowpass filter
  reverb.lodamp(0.1);   // amount of low end loss in the reverb tail
  reverb.hidamp(0.2);   // amount of treble loss in the reverb tail
  reverb.diffusion(1.0);  // 1.0 is the detault setting, lower it to create more "echoey" reverb
  reverb.wet_level(1.0f);
  reverb.bypass_set(false);
  */

  // **** END CHANGES 2/2 ******************************************************

  // Starting sequence
 
  CrashReport.breadcrumb(3, 777);
  digitalWrite(LED_BUILTIN, HIGH);

  CrashReport.breadcrumb(3, 778);
  delay(1000);

  CrashReport.breadcrumb(3, 779);
  digitalWrite(LED_BUILTIN, LOW);

  CrashReport.breadcrumb(3, 780);
  Serial.println("MPE Synth Ready!");

  CrashReport.breadcrumb(3, 781);
  next_check = millis()+5000UL;

  CrashReport.breadcrumb(3, 782);

}

The whole project is up at https://github.com/danfosterfire/TeensySynth in case anybody wants to run it, and I'll keep digging on my end.
 
I have uploaded your project to my T4.1+PSRAM device and apart from file name mismatched (capital 1st letter, fails on Linux) everything seems to work fine. I haven't changed anything else. I'm getting a very unpleasant clicks over USB audio, though. Even with all the effects disabled and on the delay repeats too. Maybe it's just a linux issue. No clicks when using a DAC over I2S.
This is using the delay with time set to max and a bit shorter reverb, WM8731 codec:

There were another changes in the library, i pushed a few fixes yesterday. Please pull the latest changes and try again.
 
I have uploaded your project to my T4.1+PSRAM device and apart from file name mismatched (capital 1st letter, fails on Linux) everything seems to work fine. I haven't changed anything else. I'm getting a very unpleasant clicks over USB audio, though. Even with all the effects disabled and on the delay repeats too. Maybe it's just a linux issue. No clicks when using a DAC over I2S.
This is using the delay with time set to max and a bit shorter reverb, WM8731 codec:

There were another changes in the library, i pushed a few fixes yesterday. Please pull the latest changes and try again.
The clicks on the USB Audio is a common issue with the USB Audio library that comes with Teensyduino. There's a couple threads that attacks this issue. I fixed it replacing the usb audio core files with the multi-input multi-output core that's on a thread. I'll search for it later and post it.
 
Coming soon: VoiceMixer, based on the stock 4 channel mixer, but with following changes:
  • 8 input mono channels for oscillator outputs
  • all mixed into stereo output
  • each channel has gain and panorama controls.
voiceMixer.png
One mixer instead of 3 for an 8 voice synth. Here is a short demo, each NoteOn generates a random panorama setting. I've made a Reaper JS plugin to control all the params via CC/PC:

 
VoiceMixer is getting two new inputs for modulation:
voicemixer_LFos.png

It's going to simplify adding a tremolo and auto-panner effects. Modulation inputs can be disabled.
The input range is positive only, the LFOs need offset 0.5 + amplitude 0.5 to operate in 0-1.0 range.
 
Curious as to why you've made these unipolar?

It would seem more intuitive that each input starts with a level and pan position, and modulation multipliers for those; these parameters would be set via the API. Then a -1.0 to +1.0 LFO (or other) modulation source is multiplied by the input's modulation multiplier, and the result added to the API-set value, limiting to ±1.0, of course. The modulation multipliers should also be bipolar, so a single LFO might make one voice louder while making another quieter; or pan one voice left to right while another goes right to left.

Also, consider what happens if a unipolar modulating LFO goes through a multiplier, e.g. with another LFO. Instead of a tremolo just getting more and less pronounced, the overall volume will also rise and fall...
 
Not sure yet if this will be the final version, but here is an idea for the volume modulation mixing:
the output value range will have to fit into 0.0-1.0 range. Volume going negative will deform the waveform and invert the phase. I'd like to avoid that. I've split the Volume knob range in two parts:
  1. Vol > 0.5, where the modulation input dominates over the volume setting.
  2. Vol < 0.5, where the volume setting has a higher priority. Lowering the volume decreases the modulation amplitude ending in silence. Otherwise adding modulation to a muted input would make it audible.
Here is an emulator made in P5.js:
Parameter mixer

t4synth_mod_emualtor.png

Panorama is symmetric, will have to use a slightly different approach.
 
Back
Top