// Minimoog - mega 2 /* * This program is part of a minimoog-like synthesizer based on teensy 4.0 * Copyright (C) 2020 Pierre-Loup Martin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* This program is a synthesizer very similar to the minimoog model D. * It is intended to run on a Teensy 4.0, using the PJRC audio library. * It also uses two Arduino Mega boards to manage all the user inputs : * keyboard, switches, potentiometers, etc. * All user inputs are handled and send to the teensy board using midi commands */ /* * This program is for the Arduino Mega 2, it handles the readings of : * Category input type pin MIDI * * Oscillators * global tune pot A0 CC 9 * osc 2 tune pot A1 CC 14 * osc 3 tune pot A2 CC 15 * Filter * filter pass pot A3 CC 19 * cutoff frequency pot A4 CC 20 * emphasis pot A5 CC 21 * contour pot A6 CC 22 * attack pot A7 CC 23 * decay pot A8 CC 24 * sustain pot A9 CC 25 * release pot A10 CC 26 * filter modulation switch 2 CC 109 * keyboard control 1 switch 3 CC 110 * keyboard control 2 switch 4 CC 111 * Enveloppe * attack pot A11 CC 27 * decay pot A12 CC 28 * sustain pot A13 CC 29 * release pot A14 CC 30 * Mixer * master volume pot A15 CC 07 * Communication * TX1 to teensy 18 * RX1 from teensy 19 */ /* * Note on switches : every switch is active low, using the internal pull-up resistor. */ /* * Note : Mega 2 has less things to handle then the Mega 1, but main functions are the same. * See Mega1.ino for more exhaustive comments on functions. */ // includes #include "MIDI.h" // https://github.com/FortySevenEffects/arduino_midi_library #include "PushButton.h" // https://github.com/troisiemetype/PushButton #include "ExpFilter.h" // https://github.com/troisiemetype/expfilter #include "defs.h" // Constants const uint8_t NUM_SWITCHES = 3; const uint8_t NUM_POTS = 16; const uint8_t POT_FILTER_COEF = 20; // Note : pins are defined via tables, to improve code efficiency. // Digital pin definition // Only three digital pins are used, from 2 to 4. So this one is used as base to set the table in setup(). const uint8_t PIN_FILTER_MOD = 2; /* const uint8_t PIN_KEYBOARD_CTRL_1 = 3; const uint8_t PIN_KEYBOARD_CTRL_2 = 4; */ // Analog pin definition /* const uint8_t APIN_GLOBAL_TUNE = A0; const uint8_t APIN_OSC2_TUNE = A1; const uint8_t APIN_OSC3_TUNE = A2; const uint8_t APIN_FILTER_BAND = A3; const uint8_t APIN_FILTER_CUTOFF = A4; const uint8_t APIN_FILTER_EMPHASIS = A5; const uint8_t APIN_FILTER_CONTOUR = A6; const uint8_t APIN_FILTER_ATTACK = A7; const uint8_t APIN_FILTER_DECAY = A8; const uint8_t APIN_FILTER_SUSTAIN = A9; const uint8_t APIN_FILTER_RELEASE = A10; const uint8_t APIN_ATTACK = A11; const uint8_t APIN_DECAY = A12; const uint8_t APIN_SUSTAIN = A13; const uint8_t APIN_RELEASE = A14; */ const uint8_t APIN[NUM_POTS] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15}; // Variables uint16_t potState[NUM_POTS]; PushButton switches[NUM_SWITCHES]; ExpFilter pots[NUM_POTS]; bool update = 0; struct midiSettings : public midi::DefaultSettings{ // static const bool UseRunningStatus = true; static const long BaudRate = 115200; }; // The one we use on synth MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial1, midi1, midiSettings); // For debug purposes //MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial, midi1, midiSettings); void setup(){ for(uint8_t i = 0; i < NUM_SWITCHES; ++i){ switches[i].begin(i + PIN_FILTER_MOD, INPUT_PULLUP); switches[i].setDebounceDelay(1); } for (uint8_t i = 0; i < NUM_POTS; ++i){ pots[i].begin(analogRead(APIN[i])); pots[i].setCoef(POT_FILTER_COEF); } // Run init sequence to debounce switches and filter pots, so it's running for stable values. // This one causes a bug on Mega 1, that I don't understand. // Here it works perfect, when the system powers up every control is updated to its current state. uint32_t initEnd = millis() + 500; while(millis() < initEnd){ for(uint8_t i = 0; i < NUM_SWITCHES; ++i){ switches[i].update(); } for(uint8_t i = 0; i < NUM_POTS; ++i){ uint16_t value = pots[i].filter(analogRead(APIN[i])); potState[i] = value; } } midi1.setHandleControlChange(handleControlChange); midi1.begin(1); midi1.turnThruOff(); } void loop(){ midi1.read(); updateControls(); updateSwitches(); update = 0; } void updateControls(){ for(uint8_t i = 0; i < NUM_POTS; ++i){ uint16_t value = 0; value = pots[i].filter(analogRead(APIN[i])); if((value != potState[i]) || update){ potState[i] = value; } else { // If not change, skip midi update continue; } int8_t controlChange = -1; switch(i){ case 0: controlChange = CC_OSC_TUNE; break; case 1: controlChange = CC_OSC2_TUNE; break; case 2: // This one has a problem : sends CC10 (instead of CC13) then CC45 as should be. // or maybe pure data has a bug that shifts bits. // It seems it's a bug of pure data : 13 are replaced by 10 also for values... controlChange = CC_OSC3_TUNE; break; case 3: controlChange = CC_FILTER_BAND; break; case 4: controlChange = CC_FILTER_CUTOFF_FREQ; break; case 5: controlChange = CC_FILTER_EMPHASIS; break; case 6: controlChange = CC_FILTER_CONTOUR; break; case 7: controlChange = CC_FILTER_ATTACK; break; case 8: controlChange = CC_FILTER_DECAY; break; case 9: controlChange = CC_FILTER_SUSTAIN; break; case 10: controlChange = CC_FILTER_RELEASE; break; case 11: controlChange = CC_EG_ATTACK; break; case 12: controlChange = CC_EG_DECAY; break; case 13: controlChange = CC_EG_SUSTAIN; break; case 14: controlChange = CC_EG_RELEASE; break; case 15: controlChange = CC_CHANNEL_VOL; break; default: continue; } uint8_t valueHigh = value >> 7; uint8_t valueLow = value & 0x7F; midi1.sendControlChange(controlChange, valueHigh, 1); midi1.sendControlChange(controlChange + 32, valueLow, 1); } } void updateSwitches(){ for(uint8_t i = 0; i < NUM_SWITCHES; ++i){ uint8_t change = 0; switches[i].update(); if(switches[i].justPressed()){ change = 127; } else if(switches[i].justReleased()){ change = 0; } else if(update){ change = (uint8_t)switches[i].isPressed(); change *= 127; } else { // If no change, skip the midi command sending. continue; } int8_t controlChange = -1; switch(i){ case 0: controlChange = CC_FILTER_MOD; break; case 1: controlChange = CC_FILTER_KEYTRACK_1; break; case 2: controlChange = CC_FILTER_KEYTRACK_2; break; default: continue; } midi1.sendControlChange(controlChange, change, 1); } } void handleControlChange(uint8_t channel, uint8_t command, uint8_t value){ switch(command){ case CC_ASK_FOR_DATA: update = 1; break; } }