|
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
}
|