Simple Way to add Polyphony

Status
Not open for further replies.

MarkV

Member
Hey yall,

i'm currently messing around with a project i'm working on for a very long time now and was wondering myself if there is a simple and clever way to implement polyphony for this teensy 4.0 project of mine?

i've checked the MIDI examples before programming that particular project and there are playable synths from the teensyduino examples library but... they are all monophon...

In my project I've implemented these "myNoteOn" "myNoteOff" definitions from these MIDI examples I'm talking about and it works perfectly good so far - but only for one single voice.

Have some of you guys had the same thing going on maybe? I thought of some pseudo code by myself: something like

Code:
void myNoteOn(byte channel, byte note, byte velocity) {
Voice.adsr.Trigger(velocity);
while (!note) Voice.adsr.Trigger(velocity);
}

I know that these lines of code are horsecrap, but it could be somehow as simple as that, couldn't it? If I'm pushing another MIDI-note on my synth while one note is already playing i want to trigger it with another velocity, but with the same attack-decay-sustain-release parameters inside my voice as the note thats already playing, so they should be - if thats possible - coming from the same voice inside my synth.

Does this make sense or am i trying to pull myself out of a hole here?

Plan B would be to just duplicate a whole voice and then assign each note being played to a seperate voice... but how would i implement this method using those "myNoteOn" or "myNoteOff" functions from teensyduino i am using right now? maybe i need to write some other functions - something like "myNoteOn2" + "myNoteOff2" & "myNoteOn3" + "myNoteOff3" and so on?

I'm so confused sometimes, sorry guys for my dumb approach here and thanks a lot for your time and passionate work!
MarkV
 
@MarkV

I implemented polyphony in my <TeensyMIDIPolySynth>. The way I chose to implement my polyphony is not the only way to do it, but it is certainly one way that works:

- define an array of "poly_notes" (sized for how many poly notes you want to allow - I use the constant LIMIT_POLY for the max number of poly notes) to keep track of which notes are playing
- implement one copy of your audio chain per however many poly notes you want to allow (others have used arrays of audio devices...I chose more typing to help with clarity & readability - I wholeheartedly agree that arrays are the much better approach from a coding perspective, but using arrays may make it slightly harder for others to follow & at that time, I was providing this code to some friends who were working on their own poly synth implementations)
- pertinent code portions of handleNoteOn extracted from TeensyMIDIPolySynth.ino - look at the source file for actual (working) code:

Code:
   // MIDI message callback - note on
   void handleNoteOn(byte channel, byte pitch, byte velocity)
   {
      AudioNoInterrupts();

      int note_index = -1;

      for (int i = 0; i < LIMIT_POLY; i++)
      {
         // if this is a duplicate of a note already playing, then don't consume an additional note slot
         if (((poly_notes[i].channel == 0) && (poly_notes[i].base_pitch == 0)) || ((poly_notes[i].channel == channel) && (poly_notes[i].base_pitch == pitch)))
         {
            note_index = i;
            break;
         }
      }

      // see if we found a note match
      if (note_index != -1)
      {
         switch(note_index)
         {
            // provide a "case" statement for each of the number of poly notes that you want to allow (only case 0 shown here)
            case 0:
            {
               // start by turning the envelope off while changes are made
               VFOenvelope01.noteOff();

               VFOsine01A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsquare01A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOtriangle01A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsaw01A.frequency(fNotePitches[pitch] / octaveA_divisor);

               // range check the note before even bothering to try & play it !!
               if (((fNotePitches[pitch] / octaveA_divisor) >= 80.0) && ((fNotePitches[pitch] / octaveA_divisor) <= 20000.0))
               {
                  VFOstring01A.noteOn(fNotePitches[pitch] / octaveA_divisor, (float)velocity / 256.0);
               }

               VFOsine01A.amplitude((float)(velocity) / 256.0);
               VFOsquare01A.amplitude((float)(velocity) / 256.0);
               VFOtriangle01A.amplitude((float)(velocity) / 256.0);
               VFOsaw01A.amplitude((float)(velocity) / 256.0);
               VFOwhite01A.amplitude((float)(velocity) / 256.0);
               VFOpink01A.amplitude((float)(velocity) / 256.0);
            }
            break;
         }
      }
      AudioInterrupts();
   }

- pertinent code portions of handleNoteOff extracted from TeensyMIDIPolySynth.ino - look at the source file for actual (working) code:

Code:
   // MIDI message callback - note off
   void handleNoteOff(byte channel, byte pitch, byte velocity)
   {
      AudioNoInterrupts();

      int note_index = -1;

      // see if the note to be turned off is in our array of notes that we are playing
      for (int i = 0; i < LIMIT_POLY; i++)
      {
         if ((poly_notes[i].channel == channel) && (poly_notes[i].base_pitch == pitch))
         {
            note_index = i;
            break;
         }
      }

      // if we found a matching note . . .
      if (note_index != -1)
      {
         poly_notes[note_index].note_state = false;
         poly_notes[note_index].note_off_millis = millis();

         if (env_active_state == false)
         {
            switch(note_index)
            {
               // provide a "case" statement for each of the number of poly notes that you want to allow (only case 0 shown here)
               case 0:
               {
                  VFOsine01A.amplitude(0);
                  VFOsquare01A.amplitude(0);
                  VFOtriangle01A.amplitude(0);
                  VFOsaw01A.amplitude(0);
                  VFOstring01A.noteOff(0.0);
                  VFOwhite01A.amplitude(0);
                  VFOpink01A.amplitude(0);
                  VFOsweep01A.play(0.0, 10.0, 10.0, 1.0);
               }
               break;
            }

            // make this note entry available for (re)use
            poly_notes[note_index].channel = 0;
            poly_notes[note_index].base_pitch = 0;
            VFOenvelope01.noteOff();
         }
      }
      break;
      AudioInterrupts();
   }

Anyway, hope that this is enough to convey the primary ideas behind one way to allow polyphony implementation. If you have any questions, feel free to ask !!

Good luck & have fun !!

Mark J Culross
KD5RXT
 
If I'm pushing another MIDI-note on my synth while one note is already playing i want to trigger it with another velocity, but with the same attack-decay-sustain-release parameters inside my voice as the note thats already playing, so they should be - if thats possible - coming from the same voice inside my synth.

you could try something like this, it works for me:

Code:
AudioSynthWaveformSine   sine1;          //xy=445,148
AudioSynthWaveformSine   sine2;          //xy=449,226
AudioSynthWaveformSine   sine3;          //xy=457,312
AudioSynthWaveformSine   sine4;          //xy=458,400
AudioEffectEnvelope      envelope1;      //xy=617,147
AudioEffectEnvelope      envelope2;      //xy=622,225
AudioEffectEnvelope      envelope3;      //xy=627,309
AudioEffectEnvelope      envelope4;      //xy=627,401
AudioMixer4              mixer1;         //xy=875,269
AudioEffectEnvelope      envelope5;      //xy=1059,268
AudioOutputI2S           i2s1;           //xy=1246,270
AudioConnection          patchCord1(sine1, envelope1);
AudioConnection          patchCord2(sine2, envelope2);
AudioConnection          patchCord3(sine3, envelope3);
AudioConnection          patchCord4(sine4, envelope4);
AudioConnection          patchCord5(envelope1, 0, mixer1, 0);
AudioConnection          patchCord6(envelope2, 0, mixer1, 1);
AudioConnection          patchCord7(envelope3, 0, mixer1, 2);
AudioConnection          patchCord8(envelope4, 0, mixer1, 3);
AudioConnection          patchCord9(mixer1, envelope5);
AudioConnection          patchCord10(envelope5, 0, i2s1, 0);
AudioConnection          patchCord11(envelope5, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=925,51

envelope5 is the global ADSR (you mentioned you wanted this), envelopes 1-4 are just noteOn/noteOff for each note (4 voices in this example)

maybe i need to write some other functions - something like "myNoteOn2" + "myNoteOff2" & "myNoteOn3" + "myNoteOff3" and so on?

the way I do it like in the example above, is to have a noteOn/noteOff envelope for each voice. Then the function is something like evelope3.noteOn(); for voice 3 to turn on. you can do this with separate keys/buttons or with MIDI set flags like "if (envelope1.noteOn == true) {go to next voice}". I hope that's on the right track.
 
You could
try my modified Audio Tool
That makes it very easy to create any complex designs using classes and arrays
it includes code editor with autocomplete that fetch information from the built in help
http://manicken.github.io

here is an example you can copy/paste (by using Import-JSON)
it's 32 voice (can be any number JUST change the VOICE_COUNT constant node value on tab SynthMain)
it uses a mixer that allows any number of inputs(theMixer.h source is included in the JSON)
and is exported together with the other code

there is some code nodes included that contains some code I made when handwriting the classes before

the source for that project is at
https://github.com/manicken/teensy4.0polysynth

Code:
[{"type":"settings","data":[{"arduino":{"useExportDialog":true,"IOcheckAtExport":true,"WriteJSONtoExportedFile":true,"WebServerPort":8080}},{"view":{"showWorkspaceToolbar":true,"showNodeToolTip":true,"space_width":5000,"space_height":5000,"scaleFactor":1,"showGridHminor":true,"showGridHmajor":true,"showGridVminor":true,"showGridVmajor":true,"gridHminorSize":10,"gridHmajorSize":100,"gridVminorSize":10,"gridVmajorSize":100,"snapToGrid":true,"snapToGridHsize":5,"snapToGridVsize":5,"lineCurveScale":0.75,"lineConnectionsScale":1.5,"partialRenderLinks":false}},{"palette":{"categoryHeaderTextSize":12,"onlyShowOne":true}}]},{"type":"tab","id":"4c6f22c7.190434","label":"Voice","inputs":1,"outputs":1,"export":true,"nodes":[]},{"type":"tab","id":"Main","label":"SynthMain","inputs":0,"outputs":0,"export":true,"nodes":[]},{"id":"Voice_In1","type":"TabInput","name":"modIn","comment":"","x":60,"y":140,"z":"4c6f22c7.190434","bgColor":"#CCE6FF","wires":[["Voice_waveformMod1:0","Voice_waveformMod2:0"]]},{"id":"Main_dc1","type":"AudioSynthWaveformDc","name":"dcMod","comment":"","x":60,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_Voice1:0"]]},{"id":"Main_constValue1","type":"ConstValue","name":"VOICE_COUNT","value":"32","valueType":"int","x":235,"y":105,"z":"Main","bgColor":"#EB9834","wires":[]},{"id":"Voice_waveformMod1","type":"AudioSynthWaveformModulated","name":"waveformMod1","comment":"","x":255,"y":110,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_mixer:0"]]},{"id":"Main_Voice1","type":"Voice","name":"voices[VOICE_COUNT]","x":235,"y":150,"z":"Main","bgColor":"#CCFFCC","wires":[["Main_mixer1:0"]]},{"id":"Voice_waveformMod2","type":"AudioSynthWaveformModulated","name":"waveformMod2","comment":"","x":255,"y":165,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_mixer:1"]]},{"id":"Main_codeFile1","type":"CodeFile","name":"theMixer.h","comment":"/* Audio Library for Teensy 3.X\n * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com\n *\n * Development of this audio library was funded by PJRC.COM, LLC by sales of\n * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop\n * open source software by purchasing Teensy or other PJRC products.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice, development funding notice, and this permission\n * notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef theMixer_h_\n#define theMixer_h_\n\n#include \"Arduino.h\"\n#include \"AudioStream.h\"\n\n//#define AudioMixer4 AudioMixer<4>\n\n// the following Forward declarations \n// must be defined when we use template \n// the compiler throws some warnings that should be errors otherwise\nstatic inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b); \nstatic inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b);\nstatic inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift);\nstatic inline uint32_t pack_16b_16b(int32_t a, int32_t b);\nstatic inline uint32_t signed_add_16_and_16(uint32_t a, uint32_t b);\n\n// because of the template use applyGain and applyGainThenAdd functions\n// must be in this file and NOT in cpp file\n#if defined(__ARM_ARCH_7EM__)\n#define MIXER_MULTI_UNITYGAIN 65536\n#define MIXER_MULTI_UNITYGAIN_F 65536.0f\n#define MIXER_MAX_GAIN 32767.0f\n#define MIXER_MIN_GAIN -32767.0f\n#define MIXER_MULT_DATA_TYPE int32_t\n\n\tstatic void applyGain(int16_t *data, int32_t mult)\n\t{\n\t\tuint32_t *p = (uint32_t *)data;\n\t\tconst uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);\n\n\t\tdo {\n\t\t\tuint32_t tmp32 = *p; // read 2 samples from *data\n\t\t\tint32_t val1 = signed_multiply_32x16b(mult, tmp32);\n\t\t\tint32_t val2 = signed_multiply_32x16t(mult, tmp32);\n\t\t\tval1 = signed_saturate_rshift(val1, 16, 0);\n\t\t\tval2 = signed_saturate_rshift(val2, 16, 0);\n\t\t\t*p++ = pack_16b_16b(val2, val1);\n\t\t} while (p < end);\n\t}\n\n\tstatic void applyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult)\n\t{\n\t\tuint32_t *dst = (uint32_t *)data;\n\t\tconst uint32_t *src = (uint32_t *)in;\n\t\tconst uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);\n\n\t\tif (mult == MIXER_MULTI_UNITYGAIN) {\n\t\t\tdo {\n\t\t\t\tuint32_t tmp32 = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, *src++);\n\t\t\t\ttmp32 = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, *src++);\n\t\t\t} while (dst < end);\n\t\t} else {\n\t\t\tdo {\n\t\t\t\tuint32_t tmp32 = *src++; // read 2 samples from *data\n\t\t\t\tint32_t val1 =  signed_multiply_32x16b(mult, tmp32);\n\t\t\t\tint32_t val2 =  signed_multiply_32x16t(mult, tmp32);\n\t\t\t\tval1 =  signed_saturate_rshift(val1, 16, 0);\n\t\t\t\tval2 =  signed_saturate_rshift(val2, 16, 0);\n\t\t\t\ttmp32 =  pack_16b_16b(val2, val1);\n\t\t\t\tuint32_t tmp32b = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, tmp32b);\n\t\t\t} while (dst < end);\n\t\t}\n\t}\n\n#elif defined(KINETISL)\n#define MIXER_MULTI_UNITYGAIN 256\n#define MIXER_MULTI_UNITYGAIN_F 256.0f\n#define MIXER_MAX_GAIN 127.0f\n#define MIXER_MIN_GAIN -127.0f\n#define MIXER_MULT_DATA_TYPE int16_t\n\n\tstatic void applyGain(int16_t *data, int32_t mult)\n\t{\n\t\tconst int16_t *end = data + AUDIO_BLOCK_SAMPLES;\n\n\t\tdo {\n\t\t\tint32_t val = *data * mult;\n\t\t\t*data++ = signed_saturate_rshift(val, 16, 0);\n\t\t} while (data < end);\n\t}\n\n\tstatic void applyGainThenAdd(int16_t *dst, const int16_t *src, int32_t mult)\n\t{\n\t\tconst int16_t *end = dst + AUDIO_BLOCK_SAMPLES;\n\n\t\tif (mult == MIXER_MULTI_UNITYGAIN) {\n\t\t\tdo {\n\t\t\t\tint32_t val = *dst + *src++;\n\t\t\t\t*dst++ = signed_saturate_rshift(val, 16, 0);\n\t\t\t} while (dst < end);\n\t\t} else {\n\t\t\tdo {\n\t\t\t\tint32_t val = *dst + ((*src++ * mult) >> 8); // overflow possible??\n\t\t\t\t*dst++ = signed_saturate_rshift(val, 16, 0);\n\t\t\t} while (dst < end);\n\t\t}\n\t}\n#endif\n\ntemplate <int NN>\nclass AudioMixer : public AudioStream\n{\npublic:\n\tAudioMixer(void) : AudioStream(NN, inputQueueArray) {\n\t\tfor (int i=0; i<NN; i++) multiplier[i] = MIXER_MULTI_UNITYGAIN;\n\t}\n\t\n\tvoid update() {\n\t\taudio_block_t *in, *out=NULL;\n\t\tunsigned int channel;\n\n\t\tfor (channel=0; channel < NN; channel++) {\n\t\t\tif (!out) {\n\t\t\t\tout = receiveWritable(channel);\n\t\t\t\tif (out) {\n\t\t\t\t\tint32_t mult = multiplier[channel];\n\t\t\t\t\tif (mult != MIXER_MULTI_UNITYGAIN) applyGain(out->data, mult);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tin = receiveReadOnly(channel);\n\t\t\t\tif (in) {\n\t\t\t\t\tapplyGainThenAdd(out->data, in->data, multiplier[channel]);\n\t\t\t\t\trelease(in);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (out) {\n\t\t\ttransmit(out);\n\t\t\trelease(out);\n\t\t}\n\t}\n\t/**\n\t * this sets the individual gains\n\t * @param channel\n\t * @param gain\n\t */\n\tvoid gain(unsigned int channel, float gain) {\n\t\tif (channel >= NN) return;\n\t\tif (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;\n\t\telse if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;\n\t\tmultiplier[channel] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?\n\t}\n\t/**\n\t * set all channels to specified gain\n\t * @param gain\n\t */\n\tvoid gain(float gain) {\n\t\tfor (int i = 0; i < NN; i++)\n\t\t{\n\t\t\tif (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;\n\t\t\telse if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;\n\t\t\tmultiplier[i] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?\n\t\t} \n\t}\n\nprivate:\n\tMIXER_MULT_DATA_TYPE multiplier[NN];\n\taudio_block_t *inputQueueArray[NN];\n};\n\n#endif","x":420,"y":105,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_codeFile1","type":"CodeFile","name":"theMixer.h","comment":"","x":508,"y":92,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_code1","type":"Function","name":"noteOnOff","comment":"void noteOn(byte note, byte velocity)\n{\n    //digitalWrite(NOTE_PRESSED_STATE_LED, HIGH);\n    for (int i = 0; i < VOICE_COUNT; i++) \n    {\n        if (voices[i].note == note) // first check if the note was played recently\n        {\n            voices[i].noteOn(note, velocity);\n            //digitalWrite(NOTE_OVERFLOWN_LED, LOW);\n            return; \n        }\n    }\n    for (int i = 0; i < VOICE_COUNT; i++) // second see if there is any free \"spot\"\n    {\n        if (voices[i].isNotPlaying())\n        {\n            voices[i].noteOn(note, velocity);\n            //digitalWrite(NOTE_OVERFLOWN_LED, LOW);\n            return;\n        }\n    }\n    //digitalWrite(NOTE_OVERFLOWN_LED, HIGH);\n}\nvoid noteOff(byte note, byte velocity)\n{\n    //digitalWrite(NOTE_PRESSED_STATE_LED, LOW);\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        if (voices[i].note == note)\n        {\n            voices[i].noteOff(velocity);\n            return;\n        }\n    }\n}\nvoid activateSustain()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].isSustain = 1;\n\n    }\n}\nvoid deactivateSustain()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].isSustain = 0;\n        if (!voices[i].isNoteOn)\n            voices[i].noteOff(0);\n    }\n}","x":255,"y":224,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"Main_mixer1","type":"AudioMixer","name":"mixer","inputs":"1","comment":"","x":410,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[["i2s1:0","i2s1:1"]]},{"id":"Voice_vars1","type":"Variables","name":"noteFreqs","comment":"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};\n","x":250,"y":240,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_mixer","type":"AudioMixer","name":"mixer","inputs":"2","comment":"","x":480,"y":145,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_envelope1:0"]]},{"id":"sgtl5000_1","type":"AudioControlSGTL5000","name":"sgtl5000_1","x":595,"y":100,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"SynthMain_code2","type":"Function","name":"other functions","comment":"byte waveform1 = WAVEFORM_SINE;\nbyte waveform2 = WAVEFORM_SINE;\nbyte waveform1amp = 100;\nbyte waveform2amp = 100;\nconst float DIV100 = 0.01;\n    \nvoid set_waveform1(byte wf)\n{\n    if (wf > 8) wf = 8;\n    waveform1 = wf;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].waveformMod1.begin(wf);\n    }\n}\nvoid set_waveform2(byte wf)\n{\n    if (wf > 8) wf = 8;\n    waveform2 = wf;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].waveformMod2.begin(wf);\n    }\n}\nvoid set_waveform1_amplitude(byte value)\n{\n    if (value > 100) value = 100;\n    waveform1amp = value;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].mixer.gain(0,value*DIV100);\n    }\n}\nvoid set_waveform2_amplitude(byte value)\n{\n    if (value > 100) value = 100;\n    waveform2amp = value;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].mixer.gain(1, value*DIV100);\n    }\n}","x":270,"y":265,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_code1","type":"Function","name":"begin function","comment":"\nvoid begin()\n{\n    waveformMod1.begin(WAVEFORM_SINE);\n    waveformMod2.begin(WAVEFORM_SQUARE);\n    \n    mixer.gain(0, 0.5f);\n    mixer.gain(1, 0.5f);\n}\n\n","x":386.8833312988281,"y":238.88333129882812,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_code3","type":"Function","name":"begin function","comment":"void begin()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].begin();\n    }\n    mixer.gain(1.0f/(float)VOICE_COUNT);\n    dcMod.amplitude(0.0f);\n}\n","x":270,"y":305,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"i2s1","type":"AudioOutputI2S","name":"i2s1","x":600,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"Voice_envelope1","type":"AudioEffectEnvelope","name":"envelope","comment":"","x":650,"y":145,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_Out1:0"]]},{"id":"Voice_code2","type":"Function","name":"note on/off","comment":"byte note = 0;\nbyte isNoteOn = 0;\nbyte isSustain = 0;\n    \nvoid noteOn(byte Note, byte velocity)\n{\n    float newAmp = 0.0f;\n    if (Note >= sizeof(noteFreqs)) return;\n    \n    note = Note;\n    isNoteOn = 1;\n    \n    waveformMod1.frequency(noteFreqs[Note]);\n    waveformMod2.frequency(noteFreqs[Note]);\n    \n    newAmp = (float)velocity*(1.0f / 127.0f);\n    waveformMod1.amplitude(newAmp);\n    waveformMod2.amplitude(newAmp);\n    \n    envelope.noteOn();\n}\nvoid noteOff(byte velocity)\n{\n    isNoteOn = 0;\n    if (!isSustain)\n    {\n        envelope.noteOff();\n    }\n}\nbool isNotPlaying()\n{\n    if (!envelope.isActive())\n        return true;\n    else\n        return false;\n}","x":385,"y":280,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_Out1","type":"TabOutput","name":"Out1","comment":"","x":805,"y":145,"z":"4c6f22c7.190434","bgColor":"#cce6ff","wires":[]}
]

then if you combine it with this extension
https://github.com/manicken/arduinoAPIwebserver
that you copy to the (Arduino IDE install)\tools folder

if you don't want to try this extension it's recommended to create a new Tab in Arduino IDE
for example with the name PolySynth.h,
then do a include to this new "file" in the main sketch at the top #include "PolySynth.h"


there will be a manual soon

right now I am working with fixing the palette (the panel to the left) I broke it a little while trying to make sub categories auto close like I had on main categories.
 
@kd5rxt-mark
That's impressive work you have done
600patchcords
1000lines of designer code
+ 17000 lines of code (you must have a simpler version available that you make the changes on)

But it must be really hard to maintain and extend?

I did two versions of your design, available in the modified tool in right menu -> examples that just can be loaded.
 
You could
try my modified Audio Tool
That makes it very easy to create any complex designs using classes and arrays
it includes code editor with autocomplete that fetch information from the built in help
http://manicken.github.io
Oh wow! I wonder why this is not promoted to be the main Audio Design tool (even though that's already amazing)? This looks astronomically better than the original. The "Class Export" option makes it worth the "entrance fee" (;-) alone !

I was going to do my synth stuff in the traditional static mode first then group it into classes later but this looks like I can have a good design from the start.

Thanks for your work on this!
 
Wow guys!
First of all thanks a lot for sharing your work with me, I will do so, too, as soon as I've finished my project :)


Anyway, hope that this is enough to convey the primary ideas behind one way to allow polyphony implementation. If you have any questions, feel free to ask !!

@ Mark J Culross
I have to admit it is not clear to me right now what you would do if you were in lets say - case 3 - ? As you've just presented case 0 (=the monophon case, isn't it?) I can't get my head around the other cases... How would you handle 3 oder 4 notes playing at the same time with your code? Thanks for helping me out here :)







here is an example you can copy/paste (by using Import-JSON)
it's 32 voice (can be any number JUST change the VOICE_COUNT constant node value on tab SynthMain)
it uses a mixer that allows any number of inputs(theMixer.h source is included in the JSON)
and is exported together with the other code

@manicksan
I've extracted the most important code segment you've posted:

Code:
    void set_waveform1(byte wf)
{
    if (wf > 8) wf = 8;
    waveform1 = wf;
    for (int i = 0; i < VOICE_COUNT; i++)
    {
        voices[i].waveformMod1.begin(wf);
    }
}
    void set_waveform2(byte wf)
{
    if (wf > 8) wf = 8;
    waveform2 = wf;
    for (int i = 0; i < VOICE_COUNT; i++)
    {
        voices[i].waveformMod2.begin(wf);
    }
}
    void set_waveform1_amplitude(byte value)
{
    if (value > 100) value = 100;
    waveform1amp = value;
    for (int i = 0; i < VOICE_COUNT; i++)
    {
        voices[i].mixer.gain(0,value*DIV100);
    }
}
    void set_waveform2_amplitude(byte value)
{
    if (value > 100) value = 100;
    waveform2amp = value;
    for (int i = 0; i < VOICE_COUNT; i++)
    {
        voices[i].mixer.gain(1, value*DIV100);
    }
  }
    void begin()
  {
    waveformMod1.begin(WAVEFORM_SINE);
    waveformMod2.begin(WAVEFORM_SQUARE);
    
    mixer.gain(0, 0.5f);
    mixer.gain(1, 0.5f);
}

    
  {
    for (int i = 0; i < VOICE_COUNT; i++)
    {
        voices[i].begin();
    }
    mixer.gain(1.0f/(float)VOICE_COUNT);
    dcMod.amplitude(0.0f);
}

    byte isNoteOn = 0;
    byte isSustain = 0;
    
    void noteOn(byte Note, byte velocity)
  {
    float newAmp = 0.0f;
    if (Note >= sizeof(noteFreqs)) return;
    
    note = Note;
    isNoteOn = 1;
    
    waveformMod1.frequency(noteFreqs[Note]);
    waveformMod2.frequency(noteFreqs[Note]);
    
    newAmp = (float)velocity*(1.0f / 127.0f);
    waveformMod1.amplitude(newAmp);
    waveformMod2.amplitude(newAmp);
    
    envelope.noteOn();
  }
    void noteOff(byte velocity)
  {
    isNoteOn = 0;
    if (!isSustain)
    {
        envelope.noteOff();
    }
  }
    bool isNotPlaying()
  {
    if (!envelope.isActive())
        return true;
    else
        return false;
}

So if I'm getting this right you are boolean checking how many active notes are being played out of a possible total maximum of VOICE_COUNT and then assign those played voices - lets say as a example 3 voices - to a mixer[3], is that right?

Does anyone know if one could use a single mixer for this approach? So that you would be able to just like split your output-equation in 2 or more individual mathematical equations - dependent on the keys you're pushing on your MIDI-keyboard - and just simply add them up on the output?

As I'm using up a lot of space already, I would like to minimize the mix-bus, and would love to "just simply" precalculate the possible MIDI frequencys and add them up on one single mixer - dependent on which chords i am playing on my synth.
The only thing I had to modify then would be the myNoteOn() / myNoteOff() - functions... It's gotta be so easy... but I'm to confused on how to apply this method I'm thinking of correctly...
I keep working on it :)

MarkV
 
would love to "just simply" precalculate the possible MIDI frequencys and add them up on one single mixer
MarkV,

I don't see how you can avoid "an oscillator per note" and if you are going to do that then you are going to need mixers because you then have to combine the signals from each osc into the final output. Look at the alternative where you had one oscillator and the user pressed MIDI notes 47 and 62. Those are B2 at 123.47Hz and D4 at 293.64Hz. To hear the two notes you need to set one Osc to 123.47 and the other to 293.64. Any attempt to "combine" and use one Osc simply can't work. Say for instance you took the "middle" between the two. So that would be 123.47 + ((293.64 - 123.47)/2) which is 208.55Hz so you'd simply end up playing a single G3# (well not exactly but within a Hz or two). The user would not hear two (accurate) frequencies playing at once.

Going back to the original post:
Plan B would be to just duplicate a whole voice and then assign each note being played to a seperate voice... but how would i implement this method using those "myNoteOn" or "myNoteOff" functions from teensyduino i am using right now? maybe i need to write some other functions - something like "myNoteOn2" + "myNoteOff2" & "myNoteOn3" + "myNoteOff3" and so on?
Well you do need an osc per note. So you need to pick how wide your polyphony is going to be. Volca's for example just offer 3, a Microfreak offers 4 (paraphonic not polyphonic which is a bit simpler!), many synths and keyboards have 8 or 16 and real powerhouses have 32+. But 4 might be a nice number so now you need to keep track of which of each of 4 oscs is playing and which is available.

So if you have an array of 4 that will track the MIDI note numbers that come in then as the first noteOn for note 73 arrives you search the array to find a slot. Nothing is used so far so you put an entry in slot 1 to say 73 playing on osc 0. You then do a note2freq[] lookup on 73 to find the frequency to play then you set the frequency of Osc1 to that value. Now you have one note playing. Let's see the user keeps a finger on 73 but how hits 76. So you go back to the array to see if there is a free slot. Slot [1] is unused so you set it to 76 and Osc2 and then trigger that. Keep going as 81 and 84 (say) arrive. So now you have four oscillators playing 4 notes. Serval things may now happen. Suppose the user releases key 76 (but keeps the others pressed) now you get a NoteOff event so you have to scan the array to see if which slot has 76. It is Osc1 in slot [1] so you turn that osciallator off and clear the slot. You are now ready to handle another added note. But suppose two are now added. well one can re-use that slot (and Osc1) but you will then have a 5th note but no slot for it. So you have to decide on a "priroity" system. You could decide to say that once 4 notes are held you simply ignore any that come along so the user has to release at least one before more can be queued or you may implement some for of "last note priority" to say that the latest inbound notes can "knock out" ones already playing. But then you probably need the concept of "age" to know which was the first to arrive so the slots, instead of just having note number and oscillator in them will likely need a "time on" that you get from a system timer (like millis()). As the 5th note arrives you scan the four that exist to find the one with the oldest time on and you simply force that one off (as if you had received its noteOFF), then you can record the new key, oscillator used and time On in that slot.

It's probably easiest to start by implementing any such scheme with very limited polyphony (takes less fingers to test too!) and when you have the logic working you can scale it up for more.

BTW when you get MIDI note on 0..127 don't try and do live maths to calculate which note this is. Instead do as is done here:

https://github.com/PaulStoffregen/A...les/Synthesis/PlaySynthMusic/PlaySynthMusic.h

That tune_frequencies2[] array are the MIDI key numbers converted to frequency so when midi note 73 turns up just do wave.setFrequency(tune_frequencies2[noteNum]) which will pick up the 73rd entry from that array. which I think is 349.2282Hz.
 
ok here is the complete example

you can read the comment of note on in SynthMain.h

fyi: theMixer.h implements something called template
it means that when doing the instance:
AudioMixer<4> mixer;
this is gonna create a 4 input mixer exactly like the original AudioMixer4

the downside with this is that if the following instances are made:
AudioMixer<2> mixerWith2Inputs;
AudioMixer<3> mixerWith3Inputs;
AudioMixer<4> mixerWith4Inputs;
AudioMixer<5> mixerWith5Inputs;
then the compiler creates 4 different copies each with a different input size
which in turn will take up extra program space.

it could be done with dynamic memory allocation
but then we would not know the size of the allocated memory at compile time (of what i understand).

@wrightflyer "real powerhouses have 32+"
my version that I compiled and used with success on teensy4 to be able to play "Franz Liszt - La Campanella" and "Beethoven-Moonlight-Sonata 3rd mov." without any "OVERFLOWN"
(the computer plays it, I can play the beginning of fur elise)
anyway that have 3 OSC + 1 Wavetable on each voice with 80 voices(this means it's like a real piano), but I can feel that the teensy get a little warmer.
the keyboard is taken from a old electric organ and combined with 2*22 quad voltage comparators + 11*2 74hc165 shift registers and one MAX1270 CPLD

The teensy4 is a powerhouse.

json-string:
Code:
[{"type":"settings","data":[{"arduino":{"useExportDialog":true,"IOcheckAtExport":true,"WriteJSONtoExportedFile":true,"WebServerPort":8080}},{"view":{"showWorkspaceToolbar":true,"showNodeToolTip":true,"space_width":5000,"space_height":5000,"scaleFactor":1,"showGridHminor":true,"showGridHmajor":true,"showGridVminor":true,"showGridVmajor":true,"gridHminorSize":10,"gridHmajorSize":100,"gridVminorSize":10,"gridVmajorSize":100,"snapToGrid":true,"snapToGridHsize":5,"snapToGridVsize":5,"lineCurveScale":0.75,"lineConnectionsScale":1.5,"partialRenderLinks":false}},{"palette":{"categoryHeaderTextSize":12,"onlyShowOne":true}}]},{"type":"tab","id":"4c6f22c7.190434","label":"Voice","inputs":1,"outputs":1,"export":true,"nodes":[]},{"type":"tab","id":"Main","label":"SynthMain","inputs":0,"outputs":0,"export":true,"nodes":[]},{"type":"tab","id":"5e244a75.659154","label":"SynthController","inputs":0,"outputs":0,"export":true,"nodes":[]},{"id":"SynthController_includeDef3","type":"IncludeDef","name":"<Arduino.h>","comment":"","x":160,"y":70,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_ClassComment1","type":"ClassComment","name":"This is the root class of any sound objects","x":250,"y":40,"z":"Main","bgColor":"#CCFFCC","wires":[]},{"id":"Voice_In1","type":"TabInput","name":"modIn","comment":"","x":60,"y":140,"z":"4c6f22c7.190434","bgColor":"#CCE6FF","wires":[["Voice_waveformMod1:0","Voice_waveformMod2:0"]]},{"id":"Voice_ClassComment1","type":"ClassComment","name":"This is a single voice with two \"generators\" and one envelope","x":280,"y":40,"z":"4c6f22c7.190434","bgColor":"#CCFFCC","wires":[]},{"id":"Main_dc1","type":"AudioSynthWaveformDc","name":"dcMod","comment":"","x":60,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_Voice1:0"]]},{"id":"SynthController_ClassComment1","type":"ClassComment","name":"This class is the main controller of the whole project, and should not contain any Audio Nodes","x":335,"y":25,"z":"5e244a75.659154","bgColor":"#CCFFCC","wires":[]},{"id":"SynthController_includeDef1","type":"IncludeDef","name":"\"SynthMain.h\"","comment":"","x":165,"y":140,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Main_constValue1","type":"ConstValue","name":"VOICE_COUNT","value":"32","valueType":"int","x":235,"y":105,"z":"Main","bgColor":"#EB9834","wires":[]},{"id":"Voice_waveformMod1","type":"AudioSynthWaveformModulated","name":"waveformMod1","comment":"","x":255,"y":110,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_mixer:0"]]},{"id":"Main_Voice1","type":"Voice","name":"voices[VOICE_COUNT]","x":235,"y":150,"z":"Main","bgColor":"#CCFFCC","wires":[["Main_mixer1:0"]]},{"id":"SynthController_vars1","type":"Variables","name":"SynthMain instance","comment":"SynthMain synth;","x":180,"y":180,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_waveformMod2","type":"AudioSynthWaveformModulated","name":"waveformMod2","comment":"","x":255,"y":165,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_mixer:1"]]},{"id":"Main_codeFile1","type":"CodeFile","name":"theMixer.h","comment":"/* Audio Library for Teensy 3.X\n * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com\n *\n * Development of this audio library was funded by PJRC.COM, LLC by sales of\n * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop\n * open source software by purchasing Teensy or other PJRC products.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice, development funding notice, and this permission\n * notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef theMixer_h_\n#define theMixer_h_\n\n#include \"Arduino.h\"\n#include \"AudioStream.h\"\n\n//#define AudioMixer4 AudioMixer<4>\n\n// the following Forward declarations \n// must be defined when we use template \n// the compiler throws some warnings that should be errors otherwise\nstatic inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b); \nstatic inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b);\nstatic inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift);\nstatic inline uint32_t pack_16b_16b(int32_t a, int32_t b);\nstatic inline uint32_t signed_add_16_and_16(uint32_t a, uint32_t b);\n\n// because of the template use applyGain and applyGainThenAdd functions\n// must be in this file and NOT in cpp file\n#if defined(__ARM_ARCH_7EM__)\n#define MIXER_MULTI_UNITYGAIN 65536\n#define MIXER_MULTI_UNITYGAIN_F 65536.0f\n#define MIXER_MAX_GAIN 32767.0f\n#define MIXER_MIN_GAIN -32767.0f\n#define MIXER_MULT_DATA_TYPE int32_t\n\n\tstatic void applyGain(int16_t *data, int32_t mult)\n\t{\n\t\tuint32_t *p = (uint32_t *)data;\n\t\tconst uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);\n\n\t\tdo {\n\t\t\tuint32_t tmp32 = *p; // read 2 samples from *data\n\t\t\tint32_t val1 = signed_multiply_32x16b(mult, tmp32);\n\t\t\tint32_t val2 = signed_multiply_32x16t(mult, tmp32);\n\t\t\tval1 = signed_saturate_rshift(val1, 16, 0);\n\t\t\tval2 = signed_saturate_rshift(val2, 16, 0);\n\t\t\t*p++ = pack_16b_16b(val2, val1);\n\t\t} while (p < end);\n\t}\n\n\tstatic void applyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult)\n\t{\n\t\tuint32_t *dst = (uint32_t *)data;\n\t\tconst uint32_t *src = (uint32_t *)in;\n\t\tconst uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);\n\n\t\tif (mult == MIXER_MULTI_UNITYGAIN) {\n\t\t\tdo {\n\t\t\t\tuint32_t tmp32 = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, *src++);\n\t\t\t\ttmp32 = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, *src++);\n\t\t\t} while (dst < end);\n\t\t} else {\n\t\t\tdo {\n\t\t\t\tuint32_t tmp32 = *src++; // read 2 samples from *data\n\t\t\t\tint32_t val1 =  signed_multiply_32x16b(mult, tmp32);\n\t\t\t\tint32_t val2 =  signed_multiply_32x16t(mult, tmp32);\n\t\t\t\tval1 =  signed_saturate_rshift(val1, 16, 0);\n\t\t\t\tval2 =  signed_saturate_rshift(val2, 16, 0);\n\t\t\t\ttmp32 =  pack_16b_16b(val2, val1);\n\t\t\t\tuint32_t tmp32b = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, tmp32b);\n\t\t\t} while (dst < end);\n\t\t}\n\t}\n\n#elif defined(KINETISL)\n#define MIXER_MULTI_UNITYGAIN 256\n#define MIXER_MULTI_UNITYGAIN_F 256.0f\n#define MIXER_MAX_GAIN 127.0f\n#define MIXER_MIN_GAIN -127.0f\n#define MIXER_MULT_DATA_TYPE int16_t\n\n\tstatic void applyGain(int16_t *data, int32_t mult)\n\t{\n\t\tconst int16_t *end = data + AUDIO_BLOCK_SAMPLES;\n\n\t\tdo {\n\t\t\tint32_t val = *data * mult;\n\t\t\t*data++ = signed_saturate_rshift(val, 16, 0);\n\t\t} while (data < end);\n\t}\n\n\tstatic void applyGainThenAdd(int16_t *dst, const int16_t *src, int32_t mult)\n\t{\n\t\tconst int16_t *end = dst + AUDIO_BLOCK_SAMPLES;\n\n\t\tif (mult == MIXER_MULTI_UNITYGAIN) {\n\t\t\tdo {\n\t\t\t\tint32_t val = *dst + *src++;\n\t\t\t\t*dst++ = signed_saturate_rshift(val, 16, 0);\n\t\t\t} while (dst < end);\n\t\t} else {\n\t\t\tdo {\n\t\t\t\tint32_t val = *dst + ((*src++ * mult) >> 8); // overflow possible??\n\t\t\t\t*dst++ = signed_saturate_rshift(val, 16, 0);\n\t\t\t} while (dst < end);\n\t\t}\n\t}\n#endif\n\ntemplate <int NN>\nclass AudioMixer : public AudioStream\n{\npublic:\n\tAudioMixer(void) : AudioStream(NN, inputQueueArray) {\n\t\tfor (int i=0; i<NN; i++) multiplier[i] = MIXER_MULTI_UNITYGAIN;\n\t}\n\t\n\tvoid update() {\n\t\taudio_block_t *in, *out=NULL;\n\t\tunsigned int channel;\n\n\t\tfor (channel=0; channel < NN; channel++) {\n\t\t\tif (!out) {\n\t\t\t\tout = receiveWritable(channel);\n\t\t\t\tif (out) {\n\t\t\t\t\tint32_t mult = multiplier[channel];\n\t\t\t\t\tif (mult != MIXER_MULTI_UNITYGAIN) applyGain(out->data, mult);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tin = receiveReadOnly(channel);\n\t\t\t\tif (in) {\n\t\t\t\t\tapplyGainThenAdd(out->data, in->data, multiplier[channel]);\n\t\t\t\t\trelease(in);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (out) {\n\t\t\ttransmit(out);\n\t\t\trelease(out);\n\t\t}\n\t}\n\t/**\n\t * this sets the individual gains\n\t * @param channel\n\t * @param gain\n\t */\n\tvoid gain(unsigned int channel, float gain) {\n\t\tif (channel >= NN) return;\n\t\tif (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;\n\t\telse if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;\n\t\tmultiplier[channel] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?\n\t}\n\t/**\n\t * set all channels to specified gain\n\t * @param gain\n\t */\n\tvoid gain(float gain) {\n\t\tfor (int i = 0; i < NN; i++)\n\t\t{\n\t\t\tif (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;\n\t\t\telse if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;\n\t\t\tmultiplier[i] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?\n\t\t} \n\t}\n\nprivate:\n\tMIXER_MULT_DATA_TYPE multiplier[NN];\n\taudio_block_t *inputQueueArray[NN];\n};\n\n#endif","x":420,"y":105,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"SynthController_vars3","type":"Variables","name":"btnInputVars","comment":"#define btnInEnablePin 9\r\n#define btnSustainPin 23\r\n#define btnSostenutoPin 22\r\n#define btnSoftPedalPin 21\r\nuint8_t btnSustain = 0;\r\nuint8_t btnSostenuto = 0;\r\nuint8_t btnSoftPedal = 0;\r\nuint8_t btnSustainWasPressed = 0;\r\nuint8_t btnSostenutoWasPressed = 0;\r\nuint8_t btnSoftPedalWasPressed = 0;\r\n\r\n#define btnNextInstrumentPin 20\r\nuint8_t btnNextInstrument = 0;\r\nuint8_t btnNextInstrumentWasPressed = 0;","x":160,"y":260,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_codeFile1","type":"CodeFile","name":"theMixer.h","comment":"","x":508,"y":92,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_code1","type":"Function","name":"noteOnOff","comment":"void noteOn(byte note, byte velocity)\n{\n    //digitalWrite(NOTE_PRESSED_STATE_LED, HIGH); //any note \"pressed\"\n    // fist checks if this note is allready playing\n    // it that is the case then it \"reuses\" this \"slot\"\n    // this makes sure that not all \"slots\" is filled\n    // with the same playing note\n    // if the MIDI keyboard is for some reason\n    // not sending a noteoff (my keyboard is sometimes glitching)\n    // and when sending MIDI from my computer for fast playing songs\n    for (int i = 0; i < VOICE_COUNT; i++) \n    {\n        // first check if the note was played recently\n        if (voices[i].note == note) \n        {\n            voices[i].noteOn(note, velocity);\n            //digitalWrite(NOTE_OVERFLOWN_LED, LOW);\n            return; \n        }\n    }\n    // then if the note has not allready been played\n    // // second see if there is any free \"spot\"\n    for (int i = 0; i < VOICE_COUNT; i++) \n    {\n        if (voices[i].isNotPlaying())\n        {\n            voices[i].noteOn(note, velocity);\n            //digitalWrite(NOTE_OVERFLOWN_LED, LOW); // clear overflown notification\n            return;\n        }\n    }\n    //digitalWrite(NOTE_OVERFLOWN_LED, HIGH); // this is a notification that there was no free spots\n}\nvoid noteOff(byte note)\n{\n    //digitalWrite(NOTE_PRESSED_STATE_LED, LOW); //any note \"released\"\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        if (voices[i].note == note)\n        {\n            voices[i].noteOff();\n            return;\n        }\n    }\n}\nvoid activateSustain()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].isSustain = 1;\n\n    }\n}\nvoid deactivateSustain()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].isSustain = 0;\n        if (!voices[i].isNoteOn)\n            voices[i].noteOff();\n    }\n}","x":255,"y":224,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"Main_mixer1","type":"AudioMixer","name":"mixer","inputs":"1","comment":"","x":410,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[["i2s1:0","i2s1:1"]]},{"id":"Voice_vars1","type":"Variables","name":"noteFreqs","comment":"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};\n","x":250,"y":240,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_mixer","type":"AudioMixer","name":"mixer","inputs":"2","comment":"","x":480,"y":145,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_envelope1:0"]]},{"id":"SynthController_code2","type":"Function","name":"begin function","comment":"void begin()\n{\n    synth.begin();\n    \n    pinMode(btnSustainPin, INPUT);\n    pinMode(btnSostenutoPin, INPUT);\n    pinMode(btnSoftPedalPin, INPUT);\n    pinMode(btnNextInstrumentPin, INPUT);\n\n    pinMode(btnInEnablePin, OUTPUT);\n    digitalWrite(btnInEnablePin, LOW);\n    \n    btnSustainWasPressed = 0;\n    btnSoftPedalWasPressed = 0;\n    btnSostenutoWasPressed = 0;\n    btnNextInstrumentWasPressed = 0;\n}\n","x":165,"y":305,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_vars2","type":"Variables","name":"vars1","comment":"byte note = 0;\nbyte isNoteOn = 0;\nbyte isSustain = 0;","x":235,"y":275,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"sgtl5000_1","type":"AudioControlSGTL5000","name":"sgtl5000_1","x":595,"y":100,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"SynthMain_code2","type":"Function","name":"set functions","comment":"byte waveform1 = WAVEFORM_SINE;\nbyte waveform2 = WAVEFORM_SINE;\nbyte waveform1amp = 100;\nbyte waveform2amp = 100;\nconst float DIV100 = 0.01;\n    \nvoid set_waveform1(byte wf)\n{\n    if (wf > 8) wf = 8;\n    waveform1 = wf;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].waveformMod1.begin(wf);\n    }\n}\nvoid set_waveform2(byte wf)\n{\n    if (wf > 8) wf = 8;\n    waveform2 = wf;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].waveformMod2.begin(wf);\n    }\n}\nvoid set_waveform1_amplitude(byte value)\n{\n    if (value > 100) value = 100;\n    waveform1amp = value;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].mixer.gain(0,value*DIV100);\n    }\n}\nvoid set_waveform2_amplitude(byte value)\n{\n    if (value > 100) value = 100;\n    waveform2amp = value;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].mixer.gain(1, value*DIV100);\n    }\n}","x":270,"y":265,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"SynthController_code1","type":"Function","name":"MIDI functions","comment":"\nvoid loop_task()\n{\n   btnInputProcessTask();\n}\n\nvoid uartMidi_NoteOn(byte channel, byte note, byte velocity) {\n    //note += KEYBOARD_NOTE_SHIFT_CORRECTION; // this is only used for my homemade keyboard\n    velocity = 127 - velocity;\n    synth.noteOn(note, velocity);\n    \n}\n\nvoid uartMidi_NoteOff(byte channel, byte note, byte velocity) {\n    //note += KEYBOARD_NOTE_SHIFT_CORRECTION; // this is only used for my homemade keyboard\n    velocity = 127 - velocity;\n    synth.noteOff(note);\n    \n}\n\nvoid uartMidi_ControlChange(byte channel, byte control, byte value) {\n    \n}\n\nvoid uartMidi_PitchBend(byte channel, int value) {\n    \n}\n\nvoid usbMidi_NoteOn(byte channel, byte note, byte velocity) {\n    synth.noteOn(note, velocity);\n}\n\nvoid usbMidi_NoteOff(byte channel, byte note, byte velocity) {\n    synth.noteOff(note);    \n}\n\nvoid usbMidi_PitchBend(byte channel, int value) {\n  \n}\n\nvoid usbMidi_ControlChange(byte channel, byte control, byte value) {\n    switch (control) { // cases 20-31,102-119 is undefined in midi spec\n        case 64:\n          if (value == 0)\n            synth.deactivateSustain();\n          else if (value == 127)\n            synth.activateSustain();\n          break;\n        case 0:\n          //synth.set_InstrumentByIndex(value);\n          break;\n        case 20: // OSC A waveform select\n          synth.set_waveform1(value);\n          break;\n        case 21: // OSC B waveform select\n          synth.set_waveform2(value);\n          break;\n        \n\n        case 23:\n          //synth.set_OSC_A_pulseWidth(value);\n          break;\n        case 24:\n          //synth.set_OSC_B_pulseWidth(value);\n          break;\n        case 25:\n          //synth.set_OSC_C_pulseWidth(value);\n          break;\n\n        case 26:\n          //synth.set_OSC_A_phase(value);\n          break;\n        case 27:\n          //synth.set_OSC_B_phase(value);\n          break;\n        case 28:\n          //synth.set_OSC_C_phase(value);\n          break;\n\n        case 29:\n          synth.set_waveform1_amplitude(value);\n          break;\n        case 30:\n          synth.set_waveform2_amplitude(value);\n          break;\n        case 31:\n          //synth.set_OSC_C_amplitude(value);\n          break;\n        case 32: //(\"LSB for Control 0 (Bank Select)\" @ midi spec.)\n          //synth.set_OSC_D_amplitude(value);\n          break;\n\n        case 33: \n          //synth.set_mixVoices_gains(value);\n          break;\n        \n        case 100:\n          //synth.set_envelope_delay(value);\n          break;\n        case 101:\n          //synth.set_envelope_attack(value);\n          break;\n        case 102:\n          //synth.set_envelope_hold(value);\n          break;\n        case 103:\n          //synth.set_envelope_decay(value);\n          break;\n        case 104:\n          //synth.set_envelope_sustain(value);\n          break;\n        case 105:\n          //synth.set_envelope_release(value);\n          break;\n                 \n        case 108:\n          //synth.set_OSC_A_freqMult(value);\n          break;\n        case 109:\n          //synth.set_OSC_B_freqMult(value);\n          break;\n        case 110:\n          //synth.set_OSC_C_freqMult(value);\n          break;\n\n        case 115: // set wavetable as primary (Piano mode)\n          //synth.SetWaveTable_As_Primary();\n          break;\n        case 116:\n          //synth.SetWaveForm_As_Primary();\n          break;\n          \n        case 117: // EEPROM read settings\n          //synth.EEPROM_ReadSettings();\n          break;\n        case 118: // EEPROM save settings\n          //synth.EEPROM_SaveSettings();\n          break;\n\n        case 119: // get all values\n          //synth.sendAllSettings();\n        break;\n    }\n}\n","x":165,"y":340,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_code1","type":"Function","name":"begin function","comment":"void begin()\n{\n    waveformMod1.begin(WAVEFORM_SINE);\n    waveformMod2.begin(WAVEFORM_SQUARE);\n    \n    mixer.gain(0, 0.5f);\n    mixer.gain(1, 0.5f);\n}\n\n","x":260,"y":310,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_code3","type":"Function","name":"begin function","comment":"void begin()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].begin();\n    }\n    mixer.gain(1.0f/(float)VOICE_COUNT);\n    dcMod.amplitude(0.0f);\n}\n","x":270,"y":305,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"i2s1","type":"AudioOutputI2S","name":"i2s1","x":600,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"SynthController_code3","type":"Function","name":"btnInput function","comment":"\r\nvoid btnInputProcessTask(void)\r\n{\r\n  btnSustain = digitalRead(btnSustainPin);\r\n  btnSostenuto = digitalRead(btnSostenutoPin);\r\n  btnSoftPedal = digitalRead(btnSoftPedalPin);\r\n  btnNextInstrument = digitalRead(btnNextInstrumentPin);\r\n\r\n    // Sustain pedal\r\n    if ((btnSustain == LOW) && (btnSustainWasPressed == 0))\r\n    {\r\n        btnSustainWasPressed = 1;\r\n        usbMIDI.sendControlChange(0x40, 0x7F, 0x00);\r\n        synth.activateSustain();\r\n    }\r\n    else if ((btnSustain == HIGH) && (btnSustainWasPressed == 1))\r\n    {\r\n        btnSustainWasPressed = 0;\r\n        usbMIDI.sendControlChange(0x40, 0x00, 0x00);\r\n        synth.deactivateSustain();\r\n    }\r\n    // Sostenuto Pedal\r\n    if ((btnSostenuto == LOW) && (btnSostenutoWasPressed == 0))\r\n    {\r\n        btnSostenutoWasPressed = 1;\r\n        usbMIDI.sendControlChange(0x42, 0x7F, 0x00);\r\n    }\r\n    else if ((btnSostenuto == HIGH) && (btnSostenutoWasPressed == 1))\r\n    {\r\n        btnSostenutoWasPressed = 0;\r\n        usbMIDI.sendControlChange(0x42, 0x00, 0x00);\r\n    }\r\n    // Soft Pedal\r\n    if ((btnSoftPedal == LOW) && (btnSoftPedalWasPressed == 0))\r\n    {\r\n        btnSoftPedalWasPressed = 1;\r\n        usbMIDI.sendControlChange(0x43, 0x7F, 0x00);\r\n    }\r\n    else if ((btnSoftPedal == HIGH) && (btnSoftPedalWasPressed == 1))\r\n    {\r\n        btnSoftPedalWasPressed = 0;\r\n        usbMIDI.sendControlChange(0x43, 0x00, 0x00);\r\n    }\r\n    // Next Instrument button\r\n    if ((btnNextInstrument == LOW) && (btnNextInstrumentWasPressed == 0))\r\n    {\r\n        btnNextInstrumentWasPressed = 1;\r\n       // if (synth.currentWTinstrument == (InstrumentCount - 1)) synth.currentWTinstrument = 0;\r\n       // else synth.currentWTinstrument++;\r\n       // synth.set_InstrumentByIndex(synth.currentWTinstrument);\r\n       // usbMIDI.sendControlChange(0, synth.currentWTinstrument, 0x00);\r\n    }\r\n    else if ((btnNextInstrument == HIGH) && (btnNextInstrumentWasPressed == 1))\r\n    {\r\n        btnNextInstrumentWasPressed = 0;\r\n    }\r\n}","x":170,"y":375,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_envelope1","type":"AudioEffectEnvelope","name":"envelope","comment":"","x":650,"y":145,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_Out1:0"]]},{"id":"Voice_code2","type":"Function","name":"note on/off","comment":"/*\n * this takes care of all the tasks that\n * needs to be taken care of when doing\n * a note on/off\n */\n \nvoid noteOn(byte Note, byte velocity)\n{\n    float newAmp = 0.0f;\n    if (Note >= sizeof(noteFreqs)) return;\n    \n    note = Note;\n    isNoteOn = 1;\n    \n    waveformMod1.frequency(noteFreqs[Note]);\n    waveformMod2.frequency(noteFreqs[Note]);\n    \n    newAmp = (float)velocity*(1.0f / 127.0f);\n    waveformMod1.amplitude(newAmp);\n    waveformMod2.amplitude(newAmp);\n    \n    envelope.noteOn();\n}\n\nvoid noteOff()\n{\n    isNoteOn = 0;\n    if (!isSustain)\n    {\n        envelope.noteOff();\n    }\n}\n\nbool isNotPlaying()\n{\n    if (!envelope.isActive())\n        return true;\n    else\n        return false;\n}","x":250,"y":345,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_Out1","type":"TabOutput","name":"Out1","comment":"","x":805,"y":145,"z":"4c6f22c7.190434","bgColor":"#cce6ff","wires":[]}
]

sketch ino-file:
Code:
#include <Arduino.h>
#include <MIDI.h>
#include "SynthController.h"

SynthController synthCtrl;

const int ledPin = 13;
int ledState = LOW;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated
unsigned long currentMillis = 0;
unsigned long currentInterval = 0;
unsigned long ledBlinkOnInterval = 100;
unsigned long ledBlinkOffInterval = 2000;

void blinkLedTask(void); // forward declaration

// all this "RAW" midi stuff need to be here
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);

void uartMidi_NoteOn(byte channel, byte note, byte velocity) {
  synthCtrl.uartMidi_NoteOn(channel, note, velocity);
  usbMIDI.sendNoteOn(note, velocity, channel, 0); // midi passthrough from uart to usb }
}
void uartMidi_NoteOff(byte channel, byte note, byte velocity) {
  synthCtrl.uartMidi_NoteOff(channel, note, velocity);
  usbMIDI.sendNoteOff(note, velocity, channel, 0); // midi passthrough from uart to usb}
}
void uartMidi_ControlChange(byte channel, byte control, byte value) {
  synthCtrl.uartMidi_ControlChange( channel,  control,  value);
  usbMIDI.sendControlChange(control, value, channel, 0x00); // midi passthrough from uart to usb}
}
void uartMidi_PitchBend(byte channel, int value) {
  synthCtrl.uartMidi_PitchBend( channel,  value);
  usbMIDI.sendPitchBend(value, channel, 0x00); // midi passthrough from uart to usb }
}

void usbMidi_NoteOn(byte channel, byte note, byte velocity) {
  synthCtrl.usbMidi_NoteOn( channel,  note,  velocity);
}
void usbMidi_NoteOff(byte channel, byte note, byte velocity) {
  synthCtrl.usbMidi_NoteOff( channel,  note,  velocity);
}
void usbMidi_ControlChange(byte channel, byte control, byte value) {
  synthCtrl.usbMidi_ControlChange( channel,  control,  value);
}
void usbMidi_PitchBend(byte channel, int value) {
  synthCtrl.usbMidi_PitchBend( channel,  value);
}

void initMIDI()
{
    MIDI.begin();
    MIDI.setHandleNoteOn(uartMidi_NoteOn);
    MIDI.setHandleNoteOff(uartMidi_NoteOff);
    MIDI.setHandleControlChange(uartMidi_ControlChange);
    MIDI.setHandlePitchBend(uartMidi_PitchBend);
        
    usbMIDI.setHandleNoteOn(usbMidi_NoteOn);
    usbMIDI.setHandleNoteOff(usbMidi_NoteOff);
    usbMIDI.setHandleControlChange(usbMidi_ControlChange);
    usbMIDI.setHandlePitchChange(usbMidi_PitchBend);
}

void setup(){
  synthCtrl.begin();
  initMIDI();
}


void loop(){
    blinkLedTask();
    usbMIDI.read();
    MIDI.read();
    synthCtrl.loop_task();
}

void blinkLedTask(void)
{
    currentMillis = millis();
    currentInterval = currentMillis - previousMillis;
    
    if (ledState == LOW)
    {
        if (currentInterval > ledBlinkOffInterval)
        {
            previousMillis = currentMillis;
            ledState = HIGH;
            digitalWrite(ledPin, HIGH);
        }
    }
    else
    {
        if (currentInterval > ledBlinkOnInterval)
        {
            previousMillis = currentMillis;
            ledState = LOW;
            digitalWrite(ledPin, LOW);
        }
    }
}

SynthController.h
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Arduino.h>
#include "SynthMain.h"
// GUItool: begin automatically generated code

/**
 * This class is the main controller of the whole project, and should not contain any Audio Nodes
 */
class SynthController
{
 public:
    SynthMain synth;
    #define btnInEnablePin 9
    #define btnSustainPin 23
    #define btnSostenutoPin 22
    #define btnSoftPedalPin 21
    uint8_t btnSustain = 0;
    uint8_t btnSostenuto = 0;
    uint8_t btnSoftPedal = 0;
    uint8_t btnSustainWasPressed = 0;
    uint8_t btnSostenutoWasPressed = 0;
    uint8_t btnSoftPedalWasPressed = 0;
    
    #define btnNextInstrumentPin 20
    uint8_t btnNextInstrument = 0;
    uint8_t btnNextInstrumentWasPressed = 0;
    
    AudioConnection                  *patchCord[0]; // total patchCordCount:0 including array typed ones.

    SynthController() // constructor (this is called when class-object is created)
    {
        int pci = 0; // used only for adding new patchcords


    }

    void begin()
    {
        synth.begin();
        
        pinMode(btnSustainPin, INPUT);
        pinMode(btnSostenutoPin, INPUT);
        pinMode(btnSoftPedalPin, INPUT);
        pinMode(btnNextInstrumentPin, INPUT);
    
        pinMode(btnInEnablePin, OUTPUT);
        digitalWrite(btnInEnablePin, LOW);
        
        btnSustainWasPressed = 0;
        btnSoftPedalWasPressed = 0;
        btnSostenutoWasPressed = 0;
        btnNextInstrumentWasPressed = 0;
    }
    
    
    void loop_task()
    {
       btnInputProcessTask();
    }
    
    void uartMidi_NoteOn(byte channel, byte note, byte velocity) {
        //note += KEYBOARD_NOTE_SHIFT_CORRECTION; // this is only used for my homemade keyboard
        velocity = 127 - velocity;
        synth.noteOn(note, velocity);
        
    }
    
    void uartMidi_NoteOff(byte channel, byte note, byte velocity) {
        //note += KEYBOARD_NOTE_SHIFT_CORRECTION; // this is only used for my homemade keyboard
        velocity = 127 - velocity;
        synth.noteOff(note);
        
    }
    
    void uartMidi_ControlChange(byte channel, byte control, byte value) {
        
    }
    
    void uartMidi_PitchBend(byte channel, int value) {
        
    }
    
    void usbMidi_NoteOn(byte channel, byte note, byte velocity) {
        synth.noteOn(note, velocity);
    }
    
    void usbMidi_NoteOff(byte channel, byte note, byte velocity) {
        synth.noteOff(note);    
    }
    
    void usbMidi_PitchBend(byte channel, int value) {
      
    }
    
    void usbMidi_ControlChange(byte channel, byte control, byte value) {
        switch (control) { // cases 20-31,102-119 is undefined in midi spec
            case 64:
              if (value == 0)
                synth.deactivateSustain();
              else if (value == 127)
                synth.activateSustain();
              break;
            case 0:
              //synth.set_InstrumentByIndex(value);
              break;
            case 20: // OSC A waveform select
              synth.set_waveform1(value);
              break;
            case 21: // OSC B waveform select
              synth.set_waveform2(value);
              break;
            
    
            case 23:
              //synth.set_OSC_A_pulseWidth(value);
              break;
            case 24:
              //synth.set_OSC_B_pulseWidth(value);
              break;
            case 25:
              //synth.set_OSC_C_pulseWidth(value);
              break;
    
            case 26:
              //synth.set_OSC_A_phase(value);
              break;
            case 27:
              //synth.set_OSC_B_phase(value);
              break;
            case 28:
              //synth.set_OSC_C_phase(value);
              break;
    
            case 29:
              synth.set_waveform1_amplitude(value);
              break;
            case 30:
              synth.set_waveform2_amplitude(value);
              break;
            case 31:
              //synth.set_OSC_C_amplitude(value);
              break;
            case 32: //("LSB for Control 0 (Bank Select)" @ midi spec.)
              //synth.set_OSC_D_amplitude(value);
              break;
    
            case 33: 
              //synth.set_mixVoices_gains(value);
              break;
            
            case 100:
              //synth.set_envelope_delay(value);
              break;
            case 101:
              //synth.set_envelope_attack(value);
              break;
            case 102:
              //synth.set_envelope_hold(value);
              break;
            case 103:
              //synth.set_envelope_decay(value);
              break;
            case 104:
              //synth.set_envelope_sustain(value);
              break;
            case 105:
              //synth.set_envelope_release(value);
              break;
                     
            case 108:
              //synth.set_OSC_A_freqMult(value);
              break;
            case 109:
              //synth.set_OSC_B_freqMult(value);
              break;
            case 110:
              //synth.set_OSC_C_freqMult(value);
              break;
    
            case 115: // set wavetable as primary (Piano mode)
              //synth.SetWaveTable_As_Primary();
              break;
            case 116:
              //synth.SetWaveForm_As_Primary();
              break;
              
            case 117: // EEPROM read settings
              //synth.EEPROM_ReadSettings();
              break;
            case 118: // EEPROM save settings
              //synth.EEPROM_SaveSettings();
              break;
    
            case 119: // get all values
              //synth.sendAllSettings();
            break;
        }
    }
    
    
    void btnInputProcessTask(void)
    {
      btnSustain = digitalRead(btnSustainPin);
      btnSostenuto = digitalRead(btnSostenutoPin);
      btnSoftPedal = digitalRead(btnSoftPedalPin);
      btnNextInstrument = digitalRead(btnNextInstrumentPin);
    
        // Sustain pedal
        if ((btnSustain == LOW) && (btnSustainWasPressed == 0))
        {
            btnSustainWasPressed = 1;
            usbMIDI.sendControlChange(0x40, 0x7F, 0x00);
            synth.activateSustain();
        }
        else if ((btnSustain == HIGH) && (btnSustainWasPressed == 1))
        {
            btnSustainWasPressed = 0;
            usbMIDI.sendControlChange(0x40, 0x00, 0x00);
            synth.deactivateSustain();
        }
        // Sostenuto Pedal
        if ((btnSostenuto == LOW) && (btnSostenutoWasPressed == 0))
        {
            btnSostenutoWasPressed = 1;
            usbMIDI.sendControlChange(0x42, 0x7F, 0x00);
        }
        else if ((btnSostenuto == HIGH) && (btnSostenutoWasPressed == 1))
        {
            btnSostenutoWasPressed = 0;
            usbMIDI.sendControlChange(0x42, 0x00, 0x00);
        }
        // Soft Pedal
        if ((btnSoftPedal == LOW) && (btnSoftPedalWasPressed == 0))
        {
            btnSoftPedalWasPressed = 1;
            usbMIDI.sendControlChange(0x43, 0x7F, 0x00);
        }
        else if ((btnSoftPedal == HIGH) && (btnSoftPedalWasPressed == 1))
        {
            btnSoftPedalWasPressed = 0;
            usbMIDI.sendControlChange(0x43, 0x00, 0x00);
        }
        // Next Instrument button
        if ((btnNextInstrument == LOW) && (btnNextInstrumentWasPressed == 0))
        {
            btnNextInstrumentWasPressed = 1;
           // if (synth.currentWTinstrument == (InstrumentCount - 1)) synth.currentWTinstrument = 0;
           // else synth.currentWTinstrument++;
           // synth.set_InstrumentByIndex(synth.currentWTinstrument);
           // usbMIDI.sendControlChange(0, synth.currentWTinstrument, 0x00);
        }
        else if ((btnNextInstrument == HIGH) && (btnNextInstrumentWasPressed == 1))
        {
            btnNextInstrumentWasPressed = 0;
        }
    }
    
};
// GUItool: end automatically generated code

SynthMain.h
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include "Voice.h"
#include "theMixer.h"
// GUItool: begin automatically generated code

/**
 * This is the root class of any sound objects
 */
class SynthMain
{
 public:
    const static int VOICE_COUNT = 32;
    
    AudioSynthWaveformDc             dcMod;
    Voice                            voices[32];
    AudioMixer<32>                   mixer;
    AudioOutputI2S                   i2s1;
    AudioControlSGTL5000 sgtl5000_1;
    AudioConnection                  *patchCord[98]; // total patchCordCount:98 including array typed ones.

    SynthMain() // constructor (this is called when class-object is created)
    {
        int pci = 0; // used only for adding new patchcords


        patchCord[pci++] = new AudioConnection(mixer, 0, i2s1, 0);
        patchCord[pci++] = new AudioConnection(mixer, 0, i2s1, 1);
        for (int i = 0; i < 32; i++)
        {
            patchCord[pci++] = new AudioConnection(dcMod, 0, voices[i].waveformMod1, 0);
            patchCord[pci++] = new AudioConnection(dcMod, 0, voices[i].waveformMod2, 0);
            patchCord[pci++] = new AudioConnection(voices[i].envelope, 0, mixer, i);
        }
    }

    void noteOn(byte note, byte velocity)
    {
        //digitalWrite(NOTE_PRESSED_STATE_LED, HIGH); //any note "pressed"
        // fist checks if this note is allready playing
        // it that is the case then it "reuses" this "slot"
        // this makes sure that not all "slots" is filled
        // with the same playing note
        // if the MIDI keyboard is for some reason
        // not sending a noteoff (my keyboard is sometimes glitching)
        // and when sending MIDI from my computer for fast playing songs
        for (int i = 0; i < VOICE_COUNT; i++) 
        {
            // first check if the note was played recently
            if (voices[i].note == note) 
            {
                voices[i].noteOn(note, velocity);
                //digitalWrite(NOTE_OVERFLOWN_LED, LOW);
                return; 
            }
        }
        // then if the note has not allready been played
        // // second see if there is any free "spot"
        for (int i = 0; i < VOICE_COUNT; i++) 
        {
            if (voices[i].isNotPlaying())
            {
                voices[i].noteOn(note, velocity);
                //digitalWrite(NOTE_OVERFLOWN_LED, LOW); // clear overflown notification
                return;
            }
        }
        //digitalWrite(NOTE_OVERFLOWN_LED, HIGH); // this is a notification that there was no free spots
    }
    void noteOff(byte note)
    {
        //digitalWrite(NOTE_PRESSED_STATE_LED, LOW); //any note "released"
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            if (voices[i].note == note)
            {
                voices[i].noteOff();
                return;
            }
        }
    }
    void activateSustain()
    {
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].isSustain = 1;
    
        }
    }
    void deactivateSustain()
    {
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].isSustain = 0;
            if (!voices[i].isNoteOn)
                voices[i].noteOff();
        }
    }
    byte waveform1 = WAVEFORM_SINE;
    byte waveform2 = WAVEFORM_SINE;
    byte waveform1amp = 100;
    byte waveform2amp = 100;
    const float DIV100 = 0.01;
        
    void set_waveform1(byte wf)
    {
        if (wf > 8) wf = 8;
        waveform1 = wf;
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].waveformMod1.begin(wf);
        }
    }
    void set_waveform2(byte wf)
    {
        if (wf > 8) wf = 8;
        waveform2 = wf;
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].waveformMod2.begin(wf);
        }
    }
    void set_waveform1_amplitude(byte value)
    {
        if (value > 100) value = 100;
        waveform1amp = value;
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].mixer.gain(0,value*DIV100);
        }
    }
    void set_waveform2_amplitude(byte value)
    {
        if (value > 100) value = 100;
        waveform2amp = value;
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].mixer.gain(1, value*DIV100);
        }
    }
    void begin()
    {
        for (int i = 0; i < VOICE_COUNT; i++)
        {
            voices[i].begin();
        }
        mixer.gain(1.0f/(float)VOICE_COUNT);
        dcMod.amplitude(0.0f);
    }
    
    
};
// GUItool: end automatically generated code

Voice.h
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include "theMixer.h"
// GUItool: begin automatically generated code
// the following JSON string contains the whole project, 
// it's included in all generated files.
// JSON string:[{"type":"settings","data":[{"arduino":{"useExportDialog":true,"IOcheckAtExport":true,"WriteJSONtoExportedFile":true,"WebServerPort":8080}},{"view":{"showWorkspaceToolbar":true,"showNodeToolTip":true,"space_width":5000,"space_height":5000,"scaleFactor":1,"showGridHminor":true,"showGridHmajor":true,"showGridVminor":true,"showGridVmajor":true,"gridHminorSize":10,"gridHmajorSize":100,"gridVminorSize":10,"gridVmajorSize":100,"snapToGrid":true,"snapToGridHsize":5,"snapToGridVsize":5,"lineCurveScale":0.75,"lineConnectionsScale":1.5,"partialRenderLinks":false}},{"palette":{"categoryHeaderTextSize":12,"onlyShowOne":true}}]},{"type":"tab","id":"4c6f22c7.190434","label":"Voice","inputs":1,"outputs":1,"export":true,"nodes":[]},{"type":"tab","id":"Main","label":"SynthMain","inputs":0,"outputs":0,"export":true,"nodes":[]},{"type":"tab","id":"5e244a75.659154","label":"SynthController","inputs":0,"outputs":0,"export":true,"nodes":[]},{"id":"SynthController_includeDef3","type":"IncludeDef","name":"<Arduino.h>","comment":"","x":160,"y":70,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_ClassComment1","type":"ClassComment","name":"This is the root class of any sound objects","x":250,"y":40,"z":"Main","bgColor":"#CCFFCC","wires":[]},{"id":"Voice_In1","type":"TabInput","name":"modIn","comment":"","x":60,"y":140,"z":"4c6f22c7.190434","bgColor":"#CCE6FF","wires":[["Voice_waveformMod1:0","Voice_waveformMod2:0"]]},{"id":"Voice_ClassComment1","type":"ClassComment","name":"This is a single voice with two \"generators\" and one envelope","x":280,"y":40,"z":"4c6f22c7.190434","bgColor":"#CCFFCC","wires":[]},{"id":"Main_dc1","type":"AudioSynthWaveformDc","name":"dcMod","comment":"","x":60,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[["Main_Voice1:0"]]},{"id":"SynthController_ClassComment1","type":"ClassComment","name":"This class is the main controller of the whole project, and should not contain any Audio Nodes","x":335,"y":25,"z":"5e244a75.659154","bgColor":"#CCFFCC","wires":[]},{"id":"SynthController_includeDef1","type":"IncludeDef","name":"\"SynthMain.h\"","comment":"","x":165,"y":140,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Main_constValue1","type":"ConstValue","name":"VOICE_COUNT","value":"32","valueType":"int","x":235,"y":105,"z":"Main","bgColor":"#EB9834","wires":[]},{"id":"Voice_waveformMod1","type":"AudioSynthWaveformModulated","name":"waveformMod1","comment":"","x":255,"y":110,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_mixer:0"]]},{"id":"Main_Voice1","type":"Voice","name":"voices[VOICE_COUNT]","x":235,"y":150,"z":"Main","bgColor":"#CCFFCC","wires":[["Main_mixer1:0"]]},{"id":"SynthController_vars1","type":"Variables","name":"SynthMain instance","comment":"SynthMain synth;","x":180,"y":180,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_waveformMod2","type":"AudioSynthWaveformModulated","name":"waveformMod2","comment":"","x":255,"y":165,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_mixer:1"]]},{"id":"Main_codeFile1","type":"CodeFile","name":"theMixer.h","comment":"/* Audio Library for Teensy 3.X\n * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com\n *\n * Development of this audio library was funded by PJRC.COM, LLC by sales of\n * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop\n * open source software by purchasing Teensy or other PJRC products.\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice, development funding notice, and this permission\n * notice shall be included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#ifndef theMixer_h_\n#define theMixer_h_\n\n#include \"Arduino.h\"\n#include \"AudioStream.h\"\n\n//#define AudioMixer4 AudioMixer<4>\n\n// the following Forward declarations \n// must be defined when we use template \n// the compiler throws some warnings that should be errors otherwise\nstatic inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b); \nstatic inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b);\nstatic inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift);\nstatic inline uint32_t pack_16b_16b(int32_t a, int32_t b);\nstatic inline uint32_t signed_add_16_and_16(uint32_t a, uint32_t b);\n\n// because of the template use applyGain and applyGainThenAdd functions\n// must be in this file and NOT in cpp file\n#if defined(__ARM_ARCH_7EM__)\n#define MIXER_MULTI_UNITYGAIN 65536\n#define MIXER_MULTI_UNITYGAIN_F 65536.0f\n#define MIXER_MAX_GAIN 32767.0f\n#define MIXER_MIN_GAIN -32767.0f\n#define MIXER_MULT_DATA_TYPE int32_t\n\n\tstatic void applyGain(int16_t *data, int32_t mult)\n\t{\n\t\tuint32_t *p = (uint32_t *)data;\n\t\tconst uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);\n\n\t\tdo {\n\t\t\tuint32_t tmp32 = *p; // read 2 samples from *data\n\t\t\tint32_t val1 = signed_multiply_32x16b(mult, tmp32);\n\t\t\tint32_t val2 = signed_multiply_32x16t(mult, tmp32);\n\t\t\tval1 = signed_saturate_rshift(val1, 16, 0);\n\t\t\tval2 = signed_saturate_rshift(val2, 16, 0);\n\t\t\t*p++ = pack_16b_16b(val2, val1);\n\t\t} while (p < end);\n\t}\n\n\tstatic void applyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult)\n\t{\n\t\tuint32_t *dst = (uint32_t *)data;\n\t\tconst uint32_t *src = (uint32_t *)in;\n\t\tconst uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);\n\n\t\tif (mult == MIXER_MULTI_UNITYGAIN) {\n\t\t\tdo {\n\t\t\t\tuint32_t tmp32 = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, *src++);\n\t\t\t\ttmp32 = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, *src++);\n\t\t\t} while (dst < end);\n\t\t} else {\n\t\t\tdo {\n\t\t\t\tuint32_t tmp32 = *src++; // read 2 samples from *data\n\t\t\t\tint32_t val1 =  signed_multiply_32x16b(mult, tmp32);\n\t\t\t\tint32_t val2 =  signed_multiply_32x16t(mult, tmp32);\n\t\t\t\tval1 =  signed_saturate_rshift(val1, 16, 0);\n\t\t\t\tval2 =  signed_saturate_rshift(val2, 16, 0);\n\t\t\t\ttmp32 =  pack_16b_16b(val2, val1);\n\t\t\t\tuint32_t tmp32b = *dst;\n\t\t\t\t*dst++ =  signed_add_16_and_16(tmp32, tmp32b);\n\t\t\t} while (dst < end);\n\t\t}\n\t}\n\n#elif defined(KINETISL)\n#define MIXER_MULTI_UNITYGAIN 256\n#define MIXER_MULTI_UNITYGAIN_F 256.0f\n#define MIXER_MAX_GAIN 127.0f\n#define MIXER_MIN_GAIN -127.0f\n#define MIXER_MULT_DATA_TYPE int16_t\n\n\tstatic void applyGain(int16_t *data, int32_t mult)\n\t{\n\t\tconst int16_t *end = data + AUDIO_BLOCK_SAMPLES;\n\n\t\tdo {\n\t\t\tint32_t val = *data * mult;\n\t\t\t*data++ = signed_saturate_rshift(val, 16, 0);\n\t\t} while (data < end);\n\t}\n\n\tstatic void applyGainThenAdd(int16_t *dst, const int16_t *src, int32_t mult)\n\t{\n\t\tconst int16_t *end = dst + AUDIO_BLOCK_SAMPLES;\n\n\t\tif (mult == MIXER_MULTI_UNITYGAIN) {\n\t\t\tdo {\n\t\t\t\tint32_t val = *dst + *src++;\n\t\t\t\t*dst++ = signed_saturate_rshift(val, 16, 0);\n\t\t\t} while (dst < end);\n\t\t} else {\n\t\t\tdo {\n\t\t\t\tint32_t val = *dst + ((*src++ * mult) >> 8); // overflow possible??\n\t\t\t\t*dst++ = signed_saturate_rshift(val, 16, 0);\n\t\t\t} while (dst < end);\n\t\t}\n\t}\n#endif\n\ntemplate <int NN>\nclass AudioMixer : public AudioStream\n{\npublic:\n\tAudioMixer(void) : AudioStream(NN, inputQueueArray) {\n\t\tfor (int i=0; i<NN; i++) multiplier[i] = MIXER_MULTI_UNITYGAIN;\n\t}\n\t\n\tvoid update() {\n\t\taudio_block_t *in, *out=NULL;\n\t\tunsigned int channel;\n\n\t\tfor (channel=0; channel < NN; channel++) {\n\t\t\tif (!out) {\n\t\t\t\tout = receiveWritable(channel);\n\t\t\t\tif (out) {\n\t\t\t\t\tint32_t mult = multiplier[channel];\n\t\t\t\t\tif (mult != MIXER_MULTI_UNITYGAIN) applyGain(out->data, mult);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tin = receiveReadOnly(channel);\n\t\t\t\tif (in) {\n\t\t\t\t\tapplyGainThenAdd(out->data, in->data, multiplier[channel]);\n\t\t\t\t\trelease(in);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (out) {\n\t\t\ttransmit(out);\n\t\t\trelease(out);\n\t\t}\n\t}\n\t/**\n\t * this sets the individual gains\n\t * @param channel\n\t * @param gain\n\t */\n\tvoid gain(unsigned int channel, float gain) {\n\t\tif (channel >= NN) return;\n\t\tif (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;\n\t\telse if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;\n\t\tmultiplier[channel] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?\n\t}\n\t/**\n\t * set all channels to specified gain\n\t * @param gain\n\t */\n\tvoid gain(float gain) {\n\t\tfor (int i = 0; i < NN; i++)\n\t\t{\n\t\t\tif (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;\n\t\t\telse if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;\n\t\t\tmultiplier[i] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?\n\t\t} \n\t}\n\nprivate:\n\tMIXER_MULT_DATA_TYPE multiplier[NN];\n\taudio_block_t *inputQueueArray[NN];\n};\n\n#endif","x":420,"y":105,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"SynthController_vars3","type":"Variables","name":"btnInputVars","comment":"#define btnInEnablePin 9\r\n#define btnSustainPin 23\r\n#define btnSostenutoPin 22\r\n#define btnSoftPedalPin 21\r\nuint8_t btnSustain = 0;\r\nuint8_t btnSostenuto = 0;\r\nuint8_t btnSoftPedal = 0;\r\nuint8_t btnSustainWasPressed = 0;\r\nuint8_t btnSostenutoWasPressed = 0;\r\nuint8_t btnSoftPedalWasPressed = 0;\r\n\r\n#define btnNextInstrumentPin 20\r\nuint8_t btnNextInstrument = 0;\r\nuint8_t btnNextInstrumentWasPressed = 0;","x":160,"y":260,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_codeFile1","type":"CodeFile","name":"theMixer.h","comment":"","x":508,"y":92,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_code1","type":"Function","name":"noteOnOff","comment":"void noteOn(byte note, byte velocity)\n{\n    //digitalWrite(NOTE_PRESSED_STATE_LED, HIGH); //any note \"pressed\"\n    // fist checks if this note is allready playing\n    // it that is the case then it \"reuses\" this \"slot\"\n    // this makes sure that not all \"slots\" is filled\n    // with the same playing note\n    // if the MIDI keyboard is for some reason\n    // not sending a noteoff (my keyboard is sometimes glitching)\n    // and when sending MIDI from my computer for fast playing songs\n    for (int i = 0; i < VOICE_COUNT; i++) \n    {\n        // first check if the note was played recently\n        if (voices[i].note == note) \n        {\n            voices[i].noteOn(note, velocity);\n            //digitalWrite(NOTE_OVERFLOWN_LED, LOW);\n            return; \n        }\n    }\n    // then if the note has not allready been played\n    // // second see if there is any free \"spot\"\n    for (int i = 0; i < VOICE_COUNT; i++) \n    {\n        if (voices[i].isNotPlaying())\n        {\n            voices[i].noteOn(note, velocity);\n            //digitalWrite(NOTE_OVERFLOWN_LED, LOW); // clear overflown notification\n            return;\n        }\n    }\n    //digitalWrite(NOTE_OVERFLOWN_LED, HIGH); // this is a notification that there was no free spots\n}\nvoid noteOff(byte note)\n{\n    //digitalWrite(NOTE_PRESSED_STATE_LED, LOW); //any note \"released\"\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        if (voices[i].note == note)\n        {\n            voices[i].noteOff();\n            return;\n        }\n    }\n}\nvoid activateSustain()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].isSustain = 1;\n\n    }\n}\nvoid deactivateSustain()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].isSustain = 0;\n        if (!voices[i].isNoteOn)\n            voices[i].noteOff();\n    }\n}","x":255,"y":224,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"Main_mixer1","type":"AudioMixer","name":"mixer","inputs":"1","comment":"","x":410,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[["i2s1:0","i2s1:1"]]},{"id":"Voice_vars1","type":"Variables","name":"noteFreqs","comment":"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};\n","x":250,"y":240,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_mixer","type":"AudioMixer","name":"mixer","inputs":"2","comment":"","x":480,"y":145,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_envelope1:0"]]},{"id":"SynthController_code2","type":"Function","name":"begin function","comment":"void begin()\n{\n    synth.begin();\n    \n    pinMode(btnSustainPin, INPUT);\n    pinMode(btnSostenutoPin, INPUT);\n    pinMode(btnSoftPedalPin, INPUT);\n    pinMode(btnNextInstrumentPin, INPUT);\n\n    pinMode(btnInEnablePin, OUTPUT);\n    digitalWrite(btnInEnablePin, LOW);\n    \n    btnSustainWasPressed = 0;\n    btnSoftPedalWasPressed = 0;\n    btnSostenutoWasPressed = 0;\n    btnNextInstrumentWasPressed = 0;\n}\n","x":165,"y":305,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_vars2","type":"Variables","name":"vars1","comment":"byte note = 0;\nbyte isNoteOn = 0;\nbyte isSustain = 0;","x":235,"y":275,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"sgtl5000_1","type":"AudioControlSGTL5000","name":"sgtl5000_1","x":595,"y":100,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"SynthMain_code2","type":"Function","name":"set functions","comment":"byte waveform1 = WAVEFORM_SINE;\nbyte waveform2 = WAVEFORM_SINE;\nbyte waveform1amp = 100;\nbyte waveform2amp = 100;\nconst float DIV100 = 0.01;\n    \nvoid set_waveform1(byte wf)\n{\n    if (wf > 8) wf = 8;\n    waveform1 = wf;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].waveformMod1.begin(wf);\n    }\n}\nvoid set_waveform2(byte wf)\n{\n    if (wf > 8) wf = 8;\n    waveform2 = wf;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].waveformMod2.begin(wf);\n    }\n}\nvoid set_waveform1_amplitude(byte value)\n{\n    if (value > 100) value = 100;\n    waveform1amp = value;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].mixer.gain(0,value*DIV100);\n    }\n}\nvoid set_waveform2_amplitude(byte value)\n{\n    if (value > 100) value = 100;\n    waveform2amp = value;\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].mixer.gain(1, value*DIV100);\n    }\n}","x":270,"y":265,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"SynthController_code1","type":"Function","name":"MIDI functions","comment":"\nvoid loop_task()\n{\n   btnInputProcessTask();\n}\n\nvoid uartMidi_NoteOn(byte channel, byte note, byte velocity) {\n    //note += KEYBOARD_NOTE_SHIFT_CORRECTION; // this is only used for my homemade keyboard\n    velocity = 127 - velocity;\n    synth.noteOn(note, velocity);\n    \n}\n\nvoid uartMidi_NoteOff(byte channel, byte note, byte velocity) {\n    //note += KEYBOARD_NOTE_SHIFT_CORRECTION; // this is only used for my homemade keyboard\n    velocity = 127 - velocity;\n    synth.noteOff(note);\n    \n}\n\nvoid uartMidi_ControlChange(byte channel, byte control, byte value) {\n    \n}\n\nvoid uartMidi_PitchBend(byte channel, int value) {\n    \n}\n\nvoid usbMidi_NoteOn(byte channel, byte note, byte velocity) {\n    synth.noteOn(note, velocity);\n}\n\nvoid usbMidi_NoteOff(byte channel, byte note, byte velocity) {\n    synth.noteOff(note);    \n}\n\nvoid usbMidi_PitchBend(byte channel, int value) {\n  \n}\n\nvoid usbMidi_ControlChange(byte channel, byte control, byte value) {\n    switch (control) { // cases 20-31,102-119 is undefined in midi spec\n        case 64:\n          if (value == 0)\n            synth.deactivateSustain();\n          else if (value == 127)\n            synth.activateSustain();\n          break;\n        case 0:\n          //synth.set_InstrumentByIndex(value);\n          break;\n        case 20: // OSC A waveform select\n          synth.set_waveform1(value);\n          break;\n        case 21: // OSC B waveform select\n          synth.set_waveform2(value);\n          break;\n        \n\n        case 23:\n          //synth.set_OSC_A_pulseWidth(value);\n          break;\n        case 24:\n          //synth.set_OSC_B_pulseWidth(value);\n          break;\n        case 25:\n          //synth.set_OSC_C_pulseWidth(value);\n          break;\n\n        case 26:\n          //synth.set_OSC_A_phase(value);\n          break;\n        case 27:\n          //synth.set_OSC_B_phase(value);\n          break;\n        case 28:\n          //synth.set_OSC_C_phase(value);\n          break;\n\n        case 29:\n          synth.set_waveform1_amplitude(value);\n          break;\n        case 30:\n          synth.set_waveform2_amplitude(value);\n          break;\n        case 31:\n          //synth.set_OSC_C_amplitude(value);\n          break;\n        case 32: //(\"LSB for Control 0 (Bank Select)\" @ midi spec.)\n          //synth.set_OSC_D_amplitude(value);\n          break;\n\n        case 33: \n          //synth.set_mixVoices_gains(value);\n          break;\n        \n        case 100:\n          //synth.set_envelope_delay(value);\n          break;\n        case 101:\n          //synth.set_envelope_attack(value);\n          break;\n        case 102:\n          //synth.set_envelope_hold(value);\n          break;\n        case 103:\n          //synth.set_envelope_decay(value);\n          break;\n        case 104:\n          //synth.set_envelope_sustain(value);\n          break;\n        case 105:\n          //synth.set_envelope_release(value);\n          break;\n                 \n        case 108:\n          //synth.set_OSC_A_freqMult(value);\n          break;\n        case 109:\n          //synth.set_OSC_B_freqMult(value);\n          break;\n        case 110:\n          //synth.set_OSC_C_freqMult(value);\n          break;\n\n        case 115: // set wavetable as primary (Piano mode)\n          //synth.SetWaveTable_As_Primary();\n          break;\n        case 116:\n          //synth.SetWaveForm_As_Primary();\n          break;\n          \n        case 117: // EEPROM read settings\n          //synth.EEPROM_ReadSettings();\n          break;\n        case 118: // EEPROM save settings\n          //synth.EEPROM_SaveSettings();\n          break;\n\n        case 119: // get all values\n          //synth.sendAllSettings();\n        break;\n    }\n}\n","x":165,"y":340,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_code1","type":"Function","name":"begin function","comment":"void begin()\n{\n    waveformMod1.begin(WAVEFORM_SINE);\n    waveformMod2.begin(WAVEFORM_SQUARE);\n    \n    mixer.gain(0, 0.5f);\n    mixer.gain(1, 0.5f);\n}\n\n","x":260,"y":310,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"SynthMain_code3","type":"Function","name":"begin function","comment":"void begin()\n{\n    for (int i = 0; i < VOICE_COUNT; i++)\n    {\n        voices[i].begin();\n    }\n    mixer.gain(1.0f/(float)VOICE_COUNT);\n    dcMod.amplitude(0.0f);\n}\n","x":270,"y":305,"z":"Main","bgColor":"#DDFFBB","wires":[]},{"id":"i2s1","type":"AudioOutputI2S","name":"i2s1","x":600,"y":150,"z":"Main","bgColor":"#E6E0F8","wires":[]},{"id":"SynthController_code3","type":"Function","name":"btnInput function","comment":"\r\nvoid btnInputProcessTask(void)\r\n{\r\n  btnSustain = digitalRead(btnSustainPin);\r\n  btnSostenuto = digitalRead(btnSostenutoPin);\r\n  btnSoftPedal = digitalRead(btnSoftPedalPin);\r\n  btnNextInstrument = digitalRead(btnNextInstrumentPin);\r\n\r\n    // Sustain pedal\r\n    if ((btnSustain == LOW) && (btnSustainWasPressed == 0))\r\n    {\r\n        btnSustainWasPressed = 1;\r\n        usbMIDI.sendControlChange(0x40, 0x7F, 0x00);\r\n        synth.activateSustain();\r\n    }\r\n    else if ((btnSustain == HIGH) && (btnSustainWasPressed == 1))\r\n    {\r\n        btnSustainWasPressed = 0;\r\n        usbMIDI.sendControlChange(0x40, 0x00, 0x00);\r\n        synth.deactivateSustain();\r\n    }\r\n    // Sostenuto Pedal\r\n    if ((btnSostenuto == LOW) && (btnSostenutoWasPressed == 0))\r\n    {\r\n        btnSostenutoWasPressed = 1;\r\n        usbMIDI.sendControlChange(0x42, 0x7F, 0x00);\r\n    }\r\n    else if ((btnSostenuto == HIGH) && (btnSostenutoWasPressed == 1))\r\n    {\r\n        btnSostenutoWasPressed = 0;\r\n        usbMIDI.sendControlChange(0x42, 0x00, 0x00);\r\n    }\r\n    // Soft Pedal\r\n    if ((btnSoftPedal == LOW) && (btnSoftPedalWasPressed == 0))\r\n    {\r\n        btnSoftPedalWasPressed = 1;\r\n        usbMIDI.sendControlChange(0x43, 0x7F, 0x00);\r\n    }\r\n    else if ((btnSoftPedal == HIGH) && (btnSoftPedalWasPressed == 1))\r\n    {\r\n        btnSoftPedalWasPressed = 0;\r\n        usbMIDI.sendControlChange(0x43, 0x00, 0x00);\r\n    }\r\n    // Next Instrument button\r\n    if ((btnNextInstrument == LOW) && (btnNextInstrumentWasPressed == 0))\r\n    {\r\n        btnNextInstrumentWasPressed = 1;\r\n       // if (synth.currentWTinstrument == (InstrumentCount - 1)) synth.currentWTinstrument = 0;\r\n       // else synth.currentWTinstrument++;\r\n       // synth.set_InstrumentByIndex(synth.currentWTinstrument);\r\n       // usbMIDI.sendControlChange(0, synth.currentWTinstrument, 0x00);\r\n    }\r\n    else if ((btnNextInstrument == HIGH) && (btnNextInstrumentWasPressed == 1))\r\n    {\r\n        btnNextInstrumentWasPressed = 0;\r\n    }\r\n}","x":170,"y":375,"z":"5e244a75.659154","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_envelope1","type":"AudioEffectEnvelope","name":"envelope","comment":"","x":650,"y":145,"z":"4c6f22c7.190434","bgColor":"#E6E0F8","wires":[["Voice_Out1:0"]]},{"id":"Voice_code2","type":"Function","name":"note on/off","comment":"/*\n * this takes care of all the tasks that\n * needs to be taken care of when doing\n * a note on/off\n */\n \nvoid noteOn(byte Note, byte velocity)\n{\n    float newAmp = 0.0f;\n    if (Note >= sizeof(noteFreqs)) return;\n    \n    note = Note;\n    isNoteOn = 1;\n    \n    waveformMod1.frequency(noteFreqs[Note]);\n    waveformMod2.frequency(noteFreqs[Note]);\n    \n    newAmp = (float)velocity*(1.0f / 127.0f);\n    waveformMod1.amplitude(newAmp);\n    waveformMod2.amplitude(newAmp);\n    \n    envelope.noteOn();\n}\n\nvoid noteOff()\n{\n    isNoteOn = 0;\n    if (!isSustain)\n    {\n        envelope.noteOff();\n    }\n}\n\nbool isNotPlaying()\n{\n    if (!envelope.isActive())\n        return true;\n    else\n        return false;\n}","x":250,"y":345,"z":"4c6f22c7.190434","bgColor":"#DDFFBB","wires":[]},{"id":"Voice_Out1","type":"TabOutput","name":"Out1","comment":"","x":805,"y":145,"z":"4c6f22c7.190434","bgColor":"#cce6ff","wires":[]}]

/**
 * This is a single voice with two "generators" and one envelope
 */
class Voice
{
 public:
    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 note = 0;
    byte isNoteOn = 0;
    byte isSustain = 0;
    
    AudioSynthWaveformModulated      waveformMod1;
    AudioSynthWaveformModulated      waveformMod2;
    AudioMixer<2>                    mixer;
    AudioEffectEnvelope              envelope;
    AudioConnection                  *patchCord[3]; // total patchCordCount:3 including array typed ones.

    Voice() // constructor (this is called when class-object is created)
    {
        int pci = 0; // used only for adding new patchcords


        patchCord[pci++] = new AudioConnection(waveformMod1, 0, mixer, 0);
        patchCord[pci++] = new AudioConnection(waveformMod2, 0, mixer, 1);
        patchCord[pci++] = new AudioConnection(mixer, 0, envelope, 0);
    }

    void begin()
    {
        waveformMod1.begin(WAVEFORM_SINE);
        waveformMod2.begin(WAVEFORM_SQUARE);
        
        mixer.gain(0, 0.5f);
        mixer.gain(1, 0.5f);
    }
    
    
    /*
     * this takes care of all the tasks that
     * needs to be taken care of when doing
     * a note on/off
     */
     
    void noteOn(byte Note, byte velocity)
    {
        float newAmp = 0.0f;
        if (Note >= sizeof(noteFreqs)) return;
        
        note = Note;
        isNoteOn = 1;
        
        waveformMod1.frequency(noteFreqs[Note]);
        waveformMod2.frequency(noteFreqs[Note]);
        
        newAmp = (float)velocity*(1.0f / 127.0f);
        waveformMod1.amplitude(newAmp);
        waveformMod2.amplitude(newAmp);
        
        envelope.noteOn();
    }
    
    void noteOff()
    {
        isNoteOn = 0;
        if (!isSustain)
        {
            envelope.noteOff();
        }
    }
    
    bool isNotPlaying()
    {
        if (!envelope.isActive())
            return true;
        else
            return false;
    }
    
};
// GUItool: end automatically generated code

and for the complete code
theMixer.h
Code:
#ifndef theMixer_h_
#define theMixer_h_

#include "Arduino.h"
#include "AudioStream.h"

//#define AudioMixer4 AudioMixer<4>

// the following Forward declarations 
// must be defined when we use template 
// the compiler throws some warnings that should be errors otherwise
static inline int32_t signed_multiply_32x16b(int32_t a, uint32_t b); 
static inline int32_t signed_multiply_32x16t(int32_t a, uint32_t b);
static inline int32_t signed_saturate_rshift(int32_t val, int bits, int rshift);
static inline uint32_t pack_16b_16b(int32_t a, int32_t b);
static inline uint32_t signed_add_16_and_16(uint32_t a, uint32_t b);

// because of the template use applyGain and applyGainThenAdd functions
// must be in this file and NOT in cpp file
#if defined(__ARM_ARCH_7EM__)
#define MIXER_MULTI_UNITYGAIN 65536
#define MIXER_MULTI_UNITYGAIN_F 65536.0f
#define MIXER_MAX_GAIN 32767.0f
#define MIXER_MIN_GAIN -32767.0f
#define MIXER_MULT_DATA_TYPE int32_t

	static void applyGain(int16_t *data, int32_t mult)
	{
		uint32_t *p = (uint32_t *)data;
		const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);

		do {
			uint32_t tmp32 = *p; // read 2 samples from *data
			int32_t val1 = signed_multiply_32x16b(mult, tmp32);
			int32_t val2 = signed_multiply_32x16t(mult, tmp32);
			val1 = signed_saturate_rshift(val1, 16, 0);
			val2 = signed_saturate_rshift(val2, 16, 0);
			*p++ = pack_16b_16b(val2, val1);
		} while (p < end);
	}

	static void applyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult)
	{
		uint32_t *dst = (uint32_t *)data;
		const uint32_t *src = (uint32_t *)in;
		const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);

		if (mult == MIXER_MULTI_UNITYGAIN) {
			do {
				uint32_t tmp32 = *dst;
				*dst++ =  signed_add_16_and_16(tmp32, *src++);
				tmp32 = *dst;
				*dst++ =  signed_add_16_and_16(tmp32, *src++);
			} while (dst < end);
		} else {
			do {
				uint32_t tmp32 = *src++; // read 2 samples from *data
				int32_t val1 =  signed_multiply_32x16b(mult, tmp32);
				int32_t val2 =  signed_multiply_32x16t(mult, tmp32);
				val1 =  signed_saturate_rshift(val1, 16, 0);
				val2 =  signed_saturate_rshift(val2, 16, 0);
				tmp32 =  pack_16b_16b(val2, val1);
				uint32_t tmp32b = *dst;
				*dst++ =  signed_add_16_and_16(tmp32, tmp32b);
			} while (dst < end);
		}
	}

#elif defined(KINETISL)
#define MIXER_MULTI_UNITYGAIN 256
#define MIXER_MULTI_UNITYGAIN_F 256.0f
#define MIXER_MAX_GAIN 127.0f
#define MIXER_MIN_GAIN -127.0f
#define MIXER_MULT_DATA_TYPE int16_t

	static void applyGain(int16_t *data, int32_t mult)
	{
		const int16_t *end = data + AUDIO_BLOCK_SAMPLES;

		do {
			int32_t val = *data * mult;
			*data++ = signed_saturate_rshift(val, 16, 0);
		} while (data < end);
	}

	static void applyGainThenAdd(int16_t *dst, const int16_t *src, int32_t mult)
	{
		const int16_t *end = dst + AUDIO_BLOCK_SAMPLES;

		if (mult == MIXER_MULTI_UNITYGAIN) {
			do {
				int32_t val = *dst + *src++;
				*dst++ = signed_saturate_rshift(val, 16, 0);
			} while (dst < end);
		} else {
			do {
				int32_t val = *dst + ((*src++ * mult) >> 8); // overflow possible??
				*dst++ = signed_saturate_rshift(val, 16, 0);
			} while (dst < end);
		}
	}
#endif

template <int NN>
class AudioMixer : public AudioStream
{
public:
	AudioMixer(void) : AudioStream(NN, inputQueueArray) {
		for (int i=0; i<NN; i++) multiplier[i] = MIXER_MULTI_UNITYGAIN;
	}
	
	void update() {
		audio_block_t *in, *out=NULL;
		unsigned int channel;

		for (channel=0; channel < NN; channel++) {
			if (!out) {
				out = receiveWritable(channel);
				if (out) {
					int32_t mult = multiplier[channel];
					if (mult != MIXER_MULTI_UNITYGAIN) applyGain(out->data, mult);
				}
			} else {
				in = receiveReadOnly(channel);
				if (in) {
					applyGainThenAdd(out->data, in->data, multiplier[channel]);
					release(in);
				}
			}
		}
		if (out) {
			transmit(out);
			release(out);
		}
	}
	/**
	 * this sets the individual gains
	 * @param channel
	 * @param gain
	 */
	void gain(unsigned int channel, float gain) {
		if (channel >= NN) return;
		if (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;
		else if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;
		multiplier[channel] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?
	}
	/**
	 * set all channels to specified gain
	 * @param gain
	 */
	void gain(float gain) {
		for (int i = 0; i < NN; i++)
		{
			if (gain > MIXER_MAX_GAIN) gain = MIXER_MAX_GAIN;
			else if (gain < MIXER_MIN_GAIN) gain = MIXER_MIN_GAIN;
			multiplier[i] = gain * MIXER_MULTI_UNITYGAIN_F; // TODO: proper roundoff?
		} 
	}

private:
	MIXER_MULT_DATA_TYPE multiplier[NN];
	audio_block_t *inputQueueArray[NN];
};

#endif
 
I updated the tool with a new item IncludeDef
and fixed one code-editor autocomplete bug.

if you get a error notification about IncludeDef
clear browser cache, because github is a little slow

note.
sometimes the editor gets little buggy
with all text-lines not shown
if that happen press ok(save) and open it again.

I have added a short-key to save the whole design CTRL+S

and also there is a print function on the right menu and with short
CTRL+P
the print function only takes the contents of the current design without any window borders.
 
I have now tried to upload the code to the teensy4
but it did not work at first (expected a little)
there was something strange happening with the theMixer.h,
anyway here is the working version (in it's original state it's splitted into two files named .h and .tpp the .tpp is simply included from the .h file)

I found out what the problem was:
when using templates that uses "external" static functions
those functions is not included in the compile
when they are only used by a template class,
the fix is to create a DummyClass with a dummyFunction that uses
those functions, and voila they are included in the compile even
if the dummyclass is never used.

Code:
/* Audio Library for Teensy 3.X
 * Copyright (c) 2014, Paul Stoffregen, paul@pjrc.com
 *
 * Development of this audio library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#ifndef themixer_h_
#define themixer_h_

#include "Arduino.h"
#include "AudioStream.h"

//#define AudioMixer4 AudioMixer<4>

#if defined(__ARM_ARCH_7EM__)

#define MULTI_UNITYGAIN 65536
#define MULTI_UNITYGAIN_F 65536.0f
#define MAX_GAIN 32767.0f
#define MIN_GAIN -32767.0f
#define MULT_DATA_TYPE int32_t

#elif defined(KINETISL)

#define MULTI_UNITYGAIN 256
#define MULTI_UNITYGAIN_F 256.0f
#define MAX_GAIN 127.0f
#define MIN_GAIN -127.0f
#define MULT_DATA_TYPE int16_t

#endif

template <int NN> class AudioMixer : public AudioStream
{
public:
  AudioMixer(void) : AudioStream(NN, inputQueueArray) {
    for (int i=0; i<NN; i++) multiplier[i] = MULTI_UNITYGAIN;
  } 
  virtual void update();
  
  /**
   * this sets the individual gains
   * @param channel
   * @param gain
   */
  void gain(unsigned int channel, float gain);
  /**
   * set all channels to specified gain
   * @param gain
   */
  void gain(float gain);

private:
  MULT_DATA_TYPE multiplier[NN];
  audio_block_t *inputQueueArray[NN];
};

// the following Forward declarations 
// must be defined when we use template 
// the compiler throws some warnings that should be errors otherwise

static int32_t signed_multiply_32x16b(int32_t a, uint32_t b); 
static int32_t signed_multiply_32x16t(int32_t a, uint32_t b);
static int32_t signed_saturate_rshift(int32_t val, int bits, int rshift);
static uint32_t pack_16b_16b(int32_t a, int32_t b);
static uint32_t signed_add_16_and_16(uint32_t a, uint32_t b);

// because of the template use applyGain and applyGainThenAdd functions
// must be in this file and NOT in cpp file
#if defined(__ARM_ARCH_7EM__)

  static void applyGain(int16_t *data, int32_t mult)
  {
    uint32_t *p = (uint32_t *)data;
    const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);

    do {
      uint32_t tmp32 = *p; // read 2 samples from *data
      int32_t val1 = signed_multiply_32x16b(mult, tmp32);
      int32_t val2 = signed_multiply_32x16t(mult, tmp32);
      val1 = signed_saturate_rshift(val1, 16, 0);
      val2 = signed_saturate_rshift(val2, 16, 0);
      *p++ = pack_16b_16b(val2, val1);
    } while (p < end);
  }

  static void applyGainThenAdd(int16_t *data, const int16_t *in, int32_t mult)
  {
    uint32_t *dst = (uint32_t *)data;
    const uint32_t *src = (uint32_t *)in;
    const uint32_t *end = (uint32_t *)(data + AUDIO_BLOCK_SAMPLES);

    if (mult == MULTI_UNITYGAIN) {
      do {
        uint32_t tmp32 = *dst;
        *dst++ =  signed_add_16_and_16(tmp32, *src++);
        tmp32 = *dst;
        *dst++ =  signed_add_16_and_16(tmp32, *src++);
      } while (dst < end);
    } else {
      do {
        uint32_t tmp32 = *src++; // read 2 samples from *data
        int32_t val1 =  signed_multiply_32x16b(mult, tmp32);
        int32_t val2 =  signed_multiply_32x16t(mult, tmp32);
        val1 =  signed_saturate_rshift(val1, 16, 0);
        val2 =  signed_saturate_rshift(val2, 16, 0);
        tmp32 =  pack_16b_16b(val2, val1);
        uint32_t tmp32b = *dst;
        *dst++ =  signed_add_16_and_16(tmp32, tmp32b);
      } while (dst < end);
    }
  }

#elif defined(KINETISL)

  static void applyGain(int16_t *data, int32_t mult)
  {
    const int16_t *end = data + AUDIO_BLOCK_SAMPLES;

    do {
      int32_t val = *data * mult;
      *data++ = signed_saturate_rshift(val, 16, 0);
    } while (data < end);
  }

  static void applyGainThenAdd(int16_t *dst, const int16_t *src, int32_t mult)
  {
    const int16_t *end = dst + AUDIO_BLOCK_SAMPLES;

    if (mult == MULTI_UNITYGAIN) {
      do {
        int32_t val = *dst + *src++;
        *dst++ = signed_saturate_rshift(val, 16, 0);
      } while (dst < end);
    } else {
      do {
        int32_t val = *dst + ((*src++ * mult) >> 8); // overflow possible??
        *dst++ = signed_saturate_rshift(val, 16, 0);
      } while (dst < end);
    }
  }
#endif

template <int NN> void AudioMixer<NN>::gain(unsigned int channel, float gain) {
  if (channel >= NN) return;
  if (gain > MAX_GAIN) gain = MAX_GAIN;
  else if (gain < MIN_GAIN) gain = MIN_GAIN;
  multiplier[channel] = gain * MULTI_UNITYGAIN_F; // TODO: proper roundoff?
}

template <int NN> void AudioMixer<NN>::gain(float gain) {
  for (int i = 0; i < NN; i++) {
    if (gain > MAX_GAIN) gain = MAX_GAIN;
    else if (gain < MIN_GAIN) gain = MIN_GAIN;
    multiplier[i] = gain * MULTI_UNITYGAIN_F; // TODO: proper roundoff?
  } 
}

template <int NN> void AudioMixer<NN>::update() {
  audio_block_t *in, *out=NULL;
  unsigned int channel;
  for (channel=0; channel < NN; channel++) {
    if (!out) {
      out = receiveWritable(channel);
      if (out) {
        int32_t mult = multiplier[channel];
        if (mult != MULTI_UNITYGAIN) applyGain(out->data, mult);
      }
    } else {
      in = receiveReadOnly(channel);
      if (in) {
        applyGainThenAdd(out->data, in->data, multiplier[channel]);
        release(in);
      }
    }
  }
  if (out) {
    transmit(out);
    release(out);
  }
}
// this class and function forces include 
// of functions applyGainThenAdd and applyGain used by the template
class DummyClass
{
  public:
    virtual void dummyFunction();
};
void DummyClass::dummyFunction() {
  applyGainThenAdd(0, 0, 0);
  applyGain(0,0);
    
}

#endif

hope that someday it could be included in the official release
 
ahh
forgot the AudioMemory(100); in setup of sketch ino
without that nothing works
Code:
void setup(){
  AudioMemory(100);
  synthCtrl.begin();
  initMIDI();
}

With the current configuration I tried different voice_count 250 seems to use +90% ram
at a voice count of 130 strange effects occurred
 
Last edited:
@kd5rxt-mark
That's impressive work you have done
600patchcords
1000lines of designer code
+ 17000 lines of code (you must have a simpler version available that you make the changes on)

But it must be really hard to maintain and extend?

I did two versions of your design, available in the modified tool in right menu -> examples that just can be loaded.

@manicksan:

I developed everything as a single (monophonic) design, then used a copying/pasting/scripting to extend it to 4-poly. Once that was verified, I took it to 8-poly, then finally to 16-poly (my piano prodigy son was very critical of "only 8-poly ?!?!?"). Following that, I continued to change my mind about some of the functionality (changed the way I drove the LEDs, added sustain pedal...another of my son's requests, added strings, added portamento, added sweep, etc.). It's not super difficult to mod, but I must certainly pay attention to details & be diligent with the changes. Some of that can still be done with copying/pasting/scripting, but I would not want to make complicated changes to it in the current state.

I am extremely happy with the way everything operates, so in the end, any intermediate pain was still worth the effort !! Looking back after-the-fact, my favorite part about the whole thing is that it can be "tinkered with" without requiring any hardware other than the Teensy 4.x & the audio adapter, just by modifying the initial settings of each of the pots & button toggles !!

Thanks for your comments !!

Mark J Culross
KD5RXT
 
Wow guys!
First of all thanks a lot for sharing your work with me, I will do so, too, as soon as I've finished my project :)

@ Mark J Culross
I have to admit it is not clear to me right now what you would do if you were in lets say - case 3 - ? As you've just presented case 0 (=the monophon case, isn't it?) I can't get my head around the other cases... How would you handle 3 oder 4 notes playing at the same time with your code? Thanks for helping me out here :)

MarkV

@MarkV:

Sorry, I didn't do a good job of making it understandable. For polyphony, you would define four oscillators/VFOs (one for each simultaneously pressed key, as many as desired - e.g. if you wanted 4-poly, you would define 4 oscillators/VFOs). For the other cases in the note on processing, you would just substitute/activate the corresponding oscillator/VFO (here's the same handleNoteOn example I gave earlier expanded for 4-poly):

Code:
   // MIDI message callback - note on
   void handleNoteOn(byte channel, byte pitch, byte velocity)
   {
      AudioNoInterrupts();

      int note_index = -1;

      for (int i = 0; i < LIMIT_POLY; i++)
      {
         // if this is a duplicate of a note already playing, then don't consume an additional note slot
         if (((poly_notes[i].channel == 0) && (poly_notes[i].base_pitch == 0)) || ((poly_notes[i].channel == channel) && (poly_notes[i].base_pitch == pitch)))
         {
            note_index = i;
            break;
         }
      }

      // see if we found a note match
      if (note_index != -1)
      {
         switch(note_index)
         {
            // provide a "case" statement for each of the number of poly notes that you want to allow (first four cases shown here, which gives 4-poly)
            case 0:
            {
               // start by turning the envelope off while changes are made
               VFOenvelope01.noteOff();

               VFOsine01A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsquare01A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOtriangle01A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsaw01A.frequency(fNotePitches[pitch] / octaveA_divisor);

               // range check the note before even bothering to try & play it !!
               if (((fNotePitches[pitch] / octaveA_divisor) >= 80.0) && ((fNotePitches[pitch] / octaveA_divisor) <= 20000.0))
               {
                  VFOstring01A.noteOn(fNotePitches[pitch] / octaveA_divisor, (float)velocity / 256.0);
               }

               VFOsine01A.amplitude((float)(velocity) / 256.0);
               VFOsquare01A.amplitude((float)(velocity) / 256.0);
               VFOtriangle01A.amplitude((float)(velocity) / 256.0);
               VFOsaw01A.amplitude((float)(velocity) / 256.0);
               VFOwhite01A.amplitude((float)(velocity) / 256.0);
               VFOpink01A.amplitude((float)(velocity) / 256.0);
            }
            break;

            case 1:
            {
               // start by turning the envelope off while changes are made
               VFOenvelope02.noteOff();

               VFOsine02A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsquare02A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOtriangle02A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsaw02A.frequency(fNotePitches[pitch] / octaveA_divisor);

               // range check the note before even bothering to try & play it !!
               if (((fNotePitches[pitch] / octaveA_divisor) >= 80.0) && ((fNotePitches[pitch] / octaveA_divisor) <= 20000.0))
               {
                  VFOstring02A.noteOn(fNotePitches[pitch] / octaveA_divisor, (float)velocity / 256.0);
               }

               VFOsine02A.amplitude((float)(velocity) / 256.0);
               VFOsquare02A.amplitude((float)(velocity) / 256.0);
               VFOtriangle02A.amplitude((float)(velocity) / 256.0);
               VFOsaw02A.amplitude((float)(velocity) / 256.0);
               VFOwhite02A.amplitude((float)(velocity) / 256.0);
               VFOpink02A.amplitude((float)(velocity) / 256.0);
            }
            break;

            case 2:
            {
               // start by turning the envelope off while changes are made
               VFOenvelope03.noteOff();

               VFOsine03A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsquare03A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOtriangle03A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsaw03A.frequency(fNotePitches[pitch] / octaveA_divisor);

               // range check the note before even bothering to try & play it !!
               if (((fNotePitches[pitch] / octaveA_divisor) >= 80.0) && ((fNotePitches[pitch] / octaveA_divisor) <= 20000.0))
               {
                  VFOstring03A.noteOn(fNotePitches[pitch] / octaveA_divisor, (float)velocity / 256.0);
               }

               VFOsine03A.amplitude((float)(velocity) / 256.0);
               VFOsquare03A.amplitude((float)(velocity) / 256.0);
               VFOtriangle03A.amplitude((float)(velocity) / 256.0);
               VFOsaw03A.amplitude((float)(velocity) / 256.0);
               VFOwhite03A.amplitude((float)(velocity) / 256.0);
               VFOpink03A.amplitude((float)(velocity) / 256.0);
            }
            break;

            case 3:
            {
               // start by turning the envelope off while changes are made
               VFOenvelope04.noteOff();

               VFOsine04A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsquare04A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOtriangle04A.frequency(fNotePitches[pitch] / octaveA_divisor);
               VFOsaw04A.frequency(fNotePitches[pitch] / octaveA_divisor);

               // range check the note before even bothering to try & play it !!
               if (((fNotePitches[pitch] / octaveA_divisor) >= 80.0) && ((fNotePitches[pitch] / octaveA_divisor) <= 20000.0))
               {
                  VFOstring04A.noteOn(fNotePitches[pitch] / octaveA_divisor, (float)velocity / 256.0);
               }

               VFOsine04A.amplitude((float)(velocity) / 256.0);
               VFOsquare04A.amplitude((float)(velocity) / 256.0);
               VFOtriangle04A.amplitude((float)(velocity) / 256.0);
               VFOsaw04A.amplitude((float)(velocity) / 256.0);
               VFOwhite04A.amplitude((float)(velocity) / 256.0);
               VFOpink04A.amplitude((float)(velocity) / 256.0);
            }
            break;
         }
      }
      AudioInterrupts();
   }

In any case, when a MIDI note on message is received, the logic simply searches for the first oscillator/VFO which isn't already in use & assigns the new note to that oscillator/VFO. When a MIDI note off message is received, it searches the list of active notes for a match & turns that note/oscillator/VFO off (or triggers the end of an envelope, as appropriate). All of the oscillators/VFOs run into mixers to combine each of the individual sounds into the final output(s).

Hope that helps. Feel free to ask more questions !!

Good luck & have fun !!

Mark J Culross
KD5RXT
 
@MarkV:

Hope that helps. Feel free to ask more questions !!

KD5RXT


I still have problems implementing the above said. Thanks for sharing your code, the code segments from case 2 or higher seem absolutely plausible to me, but it's your project so it's easier to understand somebody's working code, than mine - not working code - right now... maybe you can help me...

Code:
#include <Audio.h>

#include "AudioOut.h"

#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <Bounce.h> 

AudioOut     audio_out;      
AudioOutputI2S           i2s1;           
AudioOutputAnalogStereo  dacs1;          
AudioConnection          patchCord1(audio_out, 0, i2s1, 0);
AudioConnection          patchCord2(audio_out, 0, dacs1, 0);
AudioControlSGTL5000     sgtl5000_1;    

void setup() {
  AudioMemory(10);
  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8); 

  Serial.begin(115200);
  
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleControlChange(myControlChange);

  audio_out.voice.adsr.SetAttack(15);  
  audio_out.voice.adsr.SetDecay(127);
  audio_out.voice.adsr.SetSustain(127);
  audio_out.voice.adsr.SetRelease(63);

  audio_out.voice.svf.SetCutoff(1000);
  audio_out.voice.svf.SetResonance(1);
  audio_out.voice.svf.SetSampleRate(AUDIO_SAMPLE_RATE_EXACT);
}

void loop() {
  AudioNoInterrupts();
  usbMIDI.read();
  AudioInterrupts();
}

void myNoteOn(byte channel, byte note, byte velocity) {
  float freq = 440*pow(2,(note-69)/12.f);
  audio_out.voice.vco.SetFrequency(freq);
  audio_out.voice.adsr.Trigger(velocity/256.f);
}

void myNoteOff(byte channel, byte note, byte velocity) {
  audio_out.voice.adsr.Release();
}

void myControlChange(byte channel, byte control, byte value) {
  if(control==22) 
    audio_out.voice.adsr.SetAttack(value);
  if(control==23)
    audio_out.voice.adsr.SetDecay(value);
  if(control==24)
    audio_out.voice.adsr.SetSustain(value);
  if(control==25)
    audio_out.voice.adsr.SetRelease(value);

  if(control==14)
    audio_out.voice.svf.SetType(value*2.5/127.f);
  if(control==15)
    audio_out.voice.svf.SetCutoff(value*20000*exp(-127/value)/127.f);
  if(control==16)
    audio_out.voice.svf.SetResonance(value*4/127.f);
}

This is the .ino file from my current project. I'm having one single voice atm on this working, monophon project. inside this voice i have some other classes like a filter svf and a envelope adsr. If i would create a copy of my voice, i would have an additional svf and adsr, but i just want another frequency with the same svf and adsr...:confused:

Assuming I would create another class "vco2" + another class "vco3" and all had the same svf and adsr classes, how would i connect them to the mixer with the AudioConnection class?
And how would I use vco2 and vco3 inside my "myNoteOn" , & "MyNoteOff" functions? I'm still having problems getting my synth polyphone, haven't ever heard more than a single note :(

I hope I'll get there
MarkV
 
Its hard to analyze the problem without AudioOut class code. Right now it seems like you are making a connection to the AudioOut class and if that class is not inherited from AudioStream that is one "problem"
/Jannik
 
I still have problems implementing the above said. Thanks for sharing your code, the code segments from case 2 or higher seem absolutely plausible to me, but it's your project so it's easier to understand somebody's working code, than mine - not working code - right now... maybe you can help me...

Assuming I would create another class "vco2" + another class "vco3" and all had the same svf and adsr classes, how would i connect them to the mixer with the AudioConnection class?
And how would I use vco2 and vco3 inside my "myNoteOn" , & "MyNoteOff" functions? I'm still having problems getting my synth polyphone, haven't ever heard more than a single note :(

I hope I'll get there
MarkV

@MarkV:

Here's a simple MIDI polyphonic example that I hope might help you to understand the method of managing polyphony that I attempted to describe. Rather than using audio classes (with which I have no experience), this makes use of standard audio adapter objects, once again in an attempt to make it as simple as possible to see what is going on. Play with this sketch & watch the spits in the serial monitor to see what happens when you press & release multiple notes (especially when releasing notes in a different order than than the order in which they were pressed). You should also be able to see what happens when you press more notes than the number of poly that it can currently support (this example is setup for poly=3, but can be extended for more).

I did not implement the filter that you had, but it can easily be added (in the same manner as the "envelope" in the array of structures). Make sure you rearrange the patch cords to account for the added filter if/when you add it. In addition, I did not specifically test the controls, but they should behave the same as you previously had them defined.


Code:
//
// Simple MIDI Synth - example demonstrating one method for managing note polyphony using an array of structures
//

#include <Audio.h>
#include <MIDI.h>


// uncomment the next line for DEBUG support over serial
#define SERIAL_DEBUG

// NOTE: if/when "POLY_COUNT" is increased, the array of structures grows automatically, & all noteOn/noteOff processing happens automatically as well
//       HOWEVER, you *must* add appropriate patch cords (from waveformMod output to envelope input, as well as from envelope output to mixer input)
// How many polyphony would you like ??
#define POLY_COUNT 3


// a structure to hold the parameters for one waveform generator & its associated envelope generator
struct waveform_modulator
{
   AudioSynthWaveformModulated waveformMod;
   AudioEffectEnvelope         envelope;
   int                         note;
   boolean                     in_use;
};


// this array of structures holds the parameters for the note that is playing on each (poly) waveform
waveform_modulator          poly_waveforms[POLY_COUNT];


// these are the audio adapter definitions/connections
AudioMixer4                 mixer;
AudioOutputI2S              i2s;
AudioConnection             patchCord1(poly_waveforms[0].waveformMod, 0, poly_waveforms[0].envelope, 0);
AudioConnection             patchCord2(poly_waveforms[1].waveformMod, 0, poly_waveforms[1].envelope, 0);
AudioConnection             patchCord3(poly_waveforms[2].waveformMod, 0, poly_waveforms[2].envelope, 0);
AudioConnection             patchCord4(poly_waveforms[0].envelope, 0, mixer, 0);
AudioConnection             patchCord5(poly_waveforms[1].envelope, 0, mixer, 1);
AudioConnection             patchCord6(poly_waveforms[2].envelope, 0, mixer, 2);
AudioConnection             patchCord7(mixer, 0, i2s, 0);
AudioConnection             patchCord8(mixer, 0, i2s, 1);
AudioControlSGTL5000        sgtl5000;



void setup()
{
  AudioMemory(10);
  sgtl5000.enable();
  sgtl5000.volume(0.8); 

  Serial.begin(115200);
  
  usbMIDI.setHandleNoteOn(myNoteOn);
  usbMIDI.setHandleNoteOff(myNoteOff);
  usbMIDI.setHandleControlChange(myControlChange);

  // setup the VFOs
  for (int i = 0; i < POLY_COUNT; i++)
  {
    poly_waveforms[i].waveformMod.begin(WAVEFORM_SQUARE);
    poly_waveforms[i].waveformMod.amplitude(0.5);
    
    poly_waveforms[i].note = -1;      // the value of -1 represents "no note"
    poly_waveforms[i].in_use = false;

    // set the initial envelope parameters
    poly_waveforms[i].envelope.delay(0);      // allow noteOn to initiate the sound with no delay (immediately)
    poly_waveforms[i].envelope.attack(15);    // attack in 15 milliseconds
    poly_waveforms[i].envelope.hold(2.5);     // hold the note for 2.5 milliseconds before dropping to the sustain level
    poly_waveforms[i].envelope.decay(127);    // decay in 127 milliseconds
    poly_waveforms[i].envelope.sustain(0.5);  // sustain at half volume until noteOff
    poly_waveforms[i].envelope.release(63);   // release in 63 milliseconds
  }

  // set the initial gain for each mixer input to 1/4 of the total (even though input #3 is currently not connected to anything)
  mixer.gain(0, 0.25);
  mixer.gain(1, 0.25);
  mixer.gain(2, 0.25);
  mixer.gain(3, 0.25);
} // setup()


void loop()
{
  usbMIDI.read();

  for (int i = 0; i < POLY_COUNT; i++)
  {
    // check any slot that shows it is "in use"
    if (poly_waveforms[i].in_use == true)
    {
      // check to see if the envelope any slot has completed - if so, then free up the slot
      if (poly_waveforms[i].envelope.isActive() == false)
      {
#ifdef SERIAL_DEBUG
        Serial.printf("\nIn loop(), release slot #%d (note: %d)\n", i, poly_waveforms[i].note);
#endif

        // clear out this slot & make it available for use once again
        poly_waveforms[i].note = -1;
        poly_waveforms[i].in_use = false;
      }
    }
  }
} // loop()


void myNoteOn(byte channel, byte note, byte velocity)
{
  int freq = (int)(440.0*pow(2.0,((float)note-69.0)/12.0));
  int poly_index = -1;  // the value of -1 represents "available VFO not found for this note"

#ifdef SERIAL_DEBUG
    Serial.printf("\nEntering myNoteOn() (note: %d, velocity: %d)\n", note, velocity);
#endif

  for (int i = 0; i < POLY_COUNT; i++)
  {
    if (poly_waveforms[i].in_use == false)
    {
      // take note of which entry in the array is "available" (not in use)
      poly_index = i;

      // since we found one available, break out of the loop
      break;
    }
  }

  // see if we found an available slot
  if (poly_index != -1)
  {
#ifdef SERIAL_DEBUG
    Serial.printf("     In myNoteOn(), activate slot #%d (note: %d, velocity: %d) (frequency: %d)\n", poly_index, note, velocity, freq);
#endif

    // mark this slot as "in use"
    poly_waveforms[poly_index].in_use = true;

    // save the note for this slot
    poly_waveforms[poly_index].note = note;

    // set the modulator frequency for this note
    poly_waveforms[poly_index].waveformMod.frequency(freq);

    // set the modulator amplitude for this note (using velocity)
    poly_waveforms[poly_index].waveformMod.amplitude((float)velocity / 255.0);

    // start the attack of this note
    poly_waveforms[poly_index].envelope.noteOn();
  } else {
#ifdef SERIAL_DEBUG
    Serial.printf("     In myNoteOn(), NO SLOTS AVAILABLE\n");
#endif
  }

#ifdef SERIAL_DEBUG
    Serial.printf("Leaving myNoteOn()\n");
#endif
} // myNoteOn()


void myNoteOff(byte channel, byte note, byte velocity)
{
  int poly_index = -1;  // the value of -1 represents "matching VFO not found for this note"

#ifdef SERIAL_DEBUG
    Serial.printf("\nEntering myNoteOff() (note: %d, velocity: %d)\n", note, velocity);
#endif

  for (int i = 0; i < POLY_COUNT; i++)
  {
    // find which of our waveforms matches the specified note
    if (poly_waveforms[i].note == note)
    {
      // take note of which entry in the array matches
      poly_index = i;

      // since we found a match, break out of the loop
      break;
    }
  }

  // see if we found an available slot
  if (poly_index != -1)
  {
#ifdef SERIAL_DEBUG
    Serial.printf("     In myNoteOff(), deactivate slot #%d (note: %d, velocity: %d)\n", poly_index, note, velocity);
#endif

    // start the deactivation of the envelope
    poly_waveforms[poly_index].envelope.noteOff();
  } else {
#ifdef SERIAL_DEBUG
    Serial.printf("     In myNoteOn(), NO MATCHING SLOT FOUND\n");
#endif
  }

#ifdef SERIAL_DEBUG
    Serial.printf("Leaving myNoteOff()\n");
#endif
} // myNoteOff()


void myControlChange(byte channel, byte control, byte value)
{
  if(control==22)
  {
    // modify the "attack" for every envelope
    for (int i = 0; i < POLY_COUNT; i++)
    {
      // set the envelope "attack" parameter
      poly_waveforms[i].envelope.attack(value);    // attack in milliseconds
    }
  }

  if(control==23)
  {
    // modify the "decay" for every envelope
    for (int i = 0; i < POLY_COUNT; i++)
    {
      // set the envelope "decay" parameter
      poly_waveforms[i].envelope.decay(value);    // decay in milliseconds
    }
  }

  if(control==24)
  {
    // modify the "sustain" for every envelope
    for (int i = 0; i < POLY_COUNT; i++)
    {
      // set the envelope "sustain" parameter
      poly_waveforms[i].envelope.sustain((float)value / 255.0);  // sustain level (255 max) until noteOff
    }
  }

  if(control==25)
  {
    // modify the "release" for every envelope
    for (int i = 0; i < POLY_COUNT; i++)
    {
      // set the envelope "release" parameter
      poly_waveforms[i].envelope.release(value);   // release in milliseconds
    }
  }
} // myControlChange()

// END OF FILE
Let us know how it goes !! Good luck & have fun !!

Mark J Culross
KD5RXT
 
Resurrecting an old post/thread but I just started to move my amorphous synth design to use the audio design tool at https://manicken.github.io/?info=Synth and the more I use it (and understand it) the more I realise what an utter work of genius this version of the audio designer is. The way you can make a single "voice" then, on a main synth tab, make an array of them connected to a mixer that auto-adapts the number of inputs to the array size so adding polyphony to any depth is just a few moments work is brilliant. (Really, folks, everyone should take a look at this !)

However one fairly minor and insignificant question: is "#includedef" implemented? and if so how do you make it work? I can put it on a tab and in the JSON string I can see the details, but it does not actually seem to generate any code (I was hoping to see some #include up near the top of the generated code).

Also, unless I've missed something "Simple" export is not currently working - it appears to "save" something but then no dialog appears to copy the generated text.
 
Code:
/*
  // Left connections for importation into Audio Design Tool for visual examination.  Uncommenting will break code.
  AudioSynthNoisePink      pink1;          //xy=55,87
  AudioSynthNoisePink      pink2; //xy=55,212
  AudioSynthNoisePink      pink3; //xy=55,339
  AudioSynthNoisePink      pink4;          //xy=55,464
  AudioSynthWaveform       waveform1;      //xy=64,21
  AudioSynthWaveform       waveform2;      //xy=64,54
  AudioSynthWaveform       waveform4; //xy=64,146
  AudioSynthWaveform       waveform3; //xy=64,179
  AudioSynthWaveform       waveform5; //xy=64,273
  AudioSynthWaveform       waveform6; //xy=64,306
  AudioSynthWaveform       waveform7;      //xy=64,398
  AudioSynthWaveform       waveform8;      //xy=64,431
  AudioMixer4              mixer1;         //xy=199,61
  AudioMixer4              mixer2; //xy=199,185
  AudioMixer4              mixer3; //xy=199,313
  AudioMixer4              mixer4;         //xy=199,437
  AudioFilterStateVariable filter1;        //xy=320,67
  AudioFilterStateVariable filter3; //xy=319,319
  AudioFilterStateVariable filter2; //xy=323,191
  AudioFilterStateVariable filter4;        //xy=323,443
  AudioEffectEnvelope      envelope1;      //xy=460,54
  AudioEffectEnvelope      envelope2; //xy=460,307
  AudioEffectEnvelope      envelope3; //xy=461,178
  AudioEffectEnvelope      envelope4;      //xy=461,430
  AudioMixer4              mixer5;         //xy=653,244
  AudioOutputI2S           i2s1;           //xy=779,245
  AudioConnection          patchCord1(pink1, 0, mixer1, 2);
  AudioConnection          patchCord2(pink2, 0, mixer2, 2);
  AudioConnection          patchCord3(pink3, 0, mixer3, 2);
  AudioConnection          patchCord4(pink4, 0, mixer4, 2);
  AudioConnection          patchCord5(waveform1, 0, mixer1, 0);
  AudioConnection          patchCord6(waveform2, 0, mixer1, 1);
  AudioConnection          patchCord7(waveform4, 0, mixer2, 0);
  AudioConnection          patchCord8(waveform3, 0, mixer2, 1);
  AudioConnection          patchCord9(waveform5, 0, mixer3, 0);
  AudioConnection          patchCord10(waveform6, 0, mixer3, 1);
  AudioConnection          patchCord11(waveform7, 0, mixer4, 0);
  AudioConnection          patchCord12(waveform8, 0, mixer4, 1);
  AudioConnection          patchCord13(mixer1, 0, filter1, 0);
  AudioConnection          patchCord14(mixer2, 0, filter2, 0);
  AudioConnection          patchCord15(mixer3, 0, filter3, 0);
  AudioConnection          patchCord16(mixer4, 0, filter4, 0);
  AudioConnection          patchCord17(filter1, 0, envelope1, 0);
  AudioConnection          patchCord18(filter3, 0, envelope3, 0);
  AudioConnection          patchCord19(filter2, 0, envelope2, 0);
  AudioConnection          patchCord20(filter4, 0, envelope4, 0);
  AudioConnection          patchCord21(envelope1, 0, mixer5, 0);
  AudioConnection          patchCord22(envelope3, 0, mixer5, 2);
  AudioConnection          patchCord23(envelope2, 0, mixer5, 1);
  AudioConnection          patchCord24(envelope4, 0, mixer5, 3);
  AudioConnection          patchCord25(mixer5, 0, i2s1, 0);
  AudioConnection          patchCord26(mixer5, 0, i2s1, 1);
  AudioControlSGTL5000     sgtl5000_1;     //xy=1023,20
  // GUItool: end automatically generated code

  AudioSynthNoisePink noiseBank[] = {pink1, pink2, pink3, pink4};
  AudioSynthWaveform oscBank1[] = {waveform1, waveform3, waveform5, waveform7};
  AudioSynthWaveform oscBank2[] = {waveform2, waveform4, waveform6, waveform8};
  AudioFilterStateVariable filterBank[] = {filter1, filter2, filter3, filter4};
  AudioEffectEnvelope envelopeBank[] = {envelope1, envelope2, envelope3, envelope4};
  AudioMixer4 oscMixerBank[] = {mixer1, mixer2, mixer3, mixer4};
*/

to

Code:
AudioSynthWaveform*      oscBank1 = new AudioSynthWaveform[4]; //xy=653,244
AudioSynthWaveform*      oscBank2 = new AudioSynthWaveform[4]; //xy=653,244
AudioSynthNoisePink*     noiseBank = new AudioSynthNoisePink[4]; //xy=653,244
AudioMixer4*             mixerBank = new AudioMixer4[4]; //xy=653,244
AudioEffectEnvelope*     envelopeBank = new AudioEffectEnvelope[4]; //xy=653,244
AudioFilterStateVariable* filterBank = new AudioFilterStateVariable[4]; //xy=653,244

the audio connections should be adjusted to reflect the changes... by uncommenting/commenting the pertinent sections in the code and importing via the audio tool import you can see the overall structure
 
@wrightflyer
I am sorry but some objects are not exported when using simple export.
That is a work in progress,
I have spent time of getting the "export classes" to work.

But I have been thinking of a solution that uses the same generating functionality for both the simple and class export.

So that for example the constructor for loop (to connect all connections) in class export is expanded to
a list of the connections instead.

I have also been thinkin of "exploding" the whole design when using class (tabs) so that it's more like a "classic" export.
But that is very hard to manage, "code vise" afterwards, so i think that is not a really good idea.

Instead I think the class export should be used for complex
repeating designs, and the "simple export" just for small simple projects.

About the simple export, that is only exporting the current tab,
so if for example if you export a tab "simple" and if other classes is used on that tab, then they will not be included in the export, and will generate compile errors.

Also there was a mistake (I changed the structure of the code) that was making that dialog not to show up.
I have fixed it now. (if it still not work then you have to clear the browser cache) or you can use the checkbox down here.
In the settings tab there is a checkbox that forces that dialog to show.


I recommend that you use the Arduino IDE plugin API_Webserver that is the very genius part of it all,
it makes developing like a child play (no more copy/paste)
because it sends all exported files directly to Arduino IDE.
And if you use VSCODE there is also a plugin for that.
Both plugins support the same functionality, there is compile output sent back to the Design Tool and with some simple clicks in the Design Tool you can Do export then Compile and/or Upload without even touching the Arduino IDE, (but you have to setup the board and settings in the IDE first).

Also I plan to do a GUI editor right in the design tool,
right now I only have resizable simple objects.
This GUI editor is primary to make live adjustments (Like a MIDI controller)
But I have plans to export the designs to Roboremo (that is a mobile phone app, that have "runtime" live GUI edit in the app)
I have made a editor right now for that app written in C#, because it's hard to do complex designs in the app.
It's price is ~$8 but that is really worth it, except that it currently only supports 8 different "panels" (interfaces it's called) but I did getting tired of waiting for an update so I hacked it to 32interfaces instead.
 
Last edited:
I have now added support for simple export of the following items:
IncludeDef
Variables
CodeFile
Function

the simple export has the following format:
Code:
var cppAPN = "// Audio Processing Nodes\n";
var cppAC = "// Audio Connections (all connections (aka wires or links))\n";
var cppCN = "// Control Nodes (all control nodes (no inputs or outputs))\n";

var cpp = getCppHeader(jsonString, includes);

cpp += "\n" + codeFiles + "\n" + cppAPN + "\n" + cppAC + "\n" + cppCN + "\n" + globalVars + "\n" + functions + "\n";
cpp += getCppFooter();

Also there is a new setting in the setting tab
Global Includes @ Arduino Export/Import

where the standard includes can be edited.

Those settings are saved together with the "project"-JSON.

/Jannik
 
Status
Not open for further replies.
Back
Top