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