├── .vscode ├── arduino.json ├── c_cpp_properties.json └── ipch │ ├── 64f695755a30f0c9 │ ├── ADSR.ipch │ └── mmap_address.bin │ ├── 679b64239d99c71b │ └── mmap_address.bin │ ├── 8d57f142091cea24 │ ├── SynthVoice.ipch │ └── mmap_address.bin │ ├── b53b35abae7be413 │ ├── TransformFactory.ipch │ └── mmap_address.bin │ └── d245055650c5293e │ ├── mmap_address.bin │ └── sound_synth.ipch ├── AD.cpp ├── AD.h ├── ADSR.cpp ├── ADSR.h ├── Config.h ├── DrumVoice.h ├── FileIO.h ├── FixedPointWaveTableOsc.hpp ├── LowPass.h ├── Matrix4.cpp ├── Matrix4.h ├── MultiWaveNumOsc.h ├── Num.h ├── NumWaveTableOsc.hpp ├── Parameters.h ├── Quaternion.cpp ├── Quaternion.h ├── README.md ├── StringMachine.cpp ├── SynthVoice.h ├── TransformFactory.cpp ├── TransformFactory.h ├── Trig.cpp ├── Trig.h ├── Util.cpp ├── Util.h ├── VADrum.cpp ├── VAEngine.cpp ├── Vector3.cpp ├── Vector3.h ├── Waveforms.h ├── esp32soundsynth.ino └── wavetableosc.hpp /.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "board": "esp32:esp32:esp32", 3 | "configuration": "PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,DebugLevel=none", 4 | "sketch": "sound_synth.ino", 5 | "port": "/dev/ttyUSB0" 6 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "/home/administrator/.arduino15/packages/esp32/tools/**", 7 | "/home/administrator/.arduino15/packages/esp32/hardware/esp32/1.0.1/**", 8 | "/home/administrator/Arduino/libraries/**", 9 | "../" 10 | ], 11 | "forcedInclude": [], 12 | "intelliSenseMode": "gcc-x64", 13 | "compilerPath": "/usr/bin/clang", 14 | "cStandard": "c11", 15 | "cppStandard": "c++17" 16 | } 17 | ], 18 | "version": 4 19 | } -------------------------------------------------------------------------------- /.vscode/ipch/64f695755a30f0c9/ADSR.ipch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokontep/esp32soundsynth/a3441c343eaf0b2f6aec3433628a822858954d0d/.vscode/ipch/64f695755a30f0c9/ADSR.ipch -------------------------------------------------------------------------------- /.vscode/ipch/64f695755a30f0c9/mmap_address.bin: -------------------------------------------------------------------------------- 1 | attack = attack; 23 | if (attack < Num(1.0)) 24 | { 25 | attack = Num(1.0); 26 | } 27 | attackCoef = Num(1.0)/attack; 28 | attackBase = Num(0); 29 | } 30 | 31 | 32 | void AD::SetDecay(Num decay) { 33 | this->decay = decay; 34 | if (decay < Num(1)) 35 | { 36 | decay = Num(1); 37 | } 38 | decayCoef = Num(1)/decay; 39 | 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /AD.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADSR.h 3 | // 4 | // Created by Nigel Redmon on 12/18/12. 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2012 Nigel Redmon 7 | // 8 | // For a complete explanation of the ADSR envelope generator and code, 9 | // read the series of articles by the author, starting here: 10 | // http://www.earlevel.com/main/2013/06/01/envelope-generators/ 11 | // 12 | // License: 13 | // 14 | // This source code is provided as is, without warranty. 15 | // You may copy and distribute verbatim copies of this document. 16 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 17 | // 18 | 19 | #ifndef AD_h 20 | #define AD_h 21 | #include "Num.h" 22 | using namespace Fixie; 23 | class AD { 24 | public: 25 | AD(void); 26 | ~AD(void); 27 | 28 | Num Process(void); 29 | Num GetOutput(void); 30 | int GetState(void); 31 | void SetAD(Num attack, Num decay); 32 | void Gate(int on); 33 | void SetAttack(Num attack); 34 | void SetDecay(Num decay); 35 | 36 | 37 | void Reset(void); 38 | 39 | enum envState { 40 | env_idle = 0, 41 | env_attack, 42 | env_decay 43 | }; 44 | 45 | protected: 46 | 47 | int state; 48 | Num output; 49 | Num attack; 50 | Num decay; 51 | int32_t counter; 52 | Num attackCoef; 53 | Num decayCoef; 54 | 55 | 56 | Num attackBase; 57 | Num decayBase; 58 | 59 | 60 | }; 61 | 62 | inline Num AD::Process() { 63 | switch (state) { 64 | case env_idle: 65 | output = Num(0); 66 | break; 67 | case env_attack: 68 | output = output+attackBase + attackCoef; 69 | 70 | if (output >= Num(1.0)) { 71 | output = Num(1.0); //http://www.earlevel.com/main/2013/06/01/envelope-generators/ 72 | state = env_decay; 73 | 74 | } 75 | break; 76 | case env_decay: 77 | output = output - decayCoef; 78 | 79 | if (output <= Num(0)) { 80 | output = Num(0); 81 | state = env_idle; 82 | } 83 | break; 84 | 85 | } 86 | return output; 87 | } 88 | 89 | inline void AD::Gate(int gate) { 90 | if (gate) 91 | { 92 | 93 | output = Num(0); 94 | state = env_attack; 95 | } 96 | 97 | } 98 | 99 | inline int AD::GetState() { 100 | return state; 101 | } 102 | 103 | inline void AD::Reset() { 104 | state = env_idle; 105 | output = Num(0); 106 | } 107 | 108 | inline Num AD::GetOutput() { 109 | return output; 110 | } 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /ADSR.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ADSR.cpp 3 | // 4 | // Created by Nigel Redmon on 12/18/12. 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2012 Nigel Redmon 7 | // 8 | // For a complete explanation of the ADSR envelope generator and code, 9 | // read the series of articles by the author, starting here: 10 | // http://www.earlevel.com/main/2013/06/01/envelope-generators/ 11 | // 12 | // License: 13 | // 14 | // This source code is provided as is, without warranty. 15 | // You may copy and distribute verbatim copies of this document. 16 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 17 | // 18 | // 1.01 2016-01-02 njr added calcCoef to SetTargetRatio functions that were in the ADSR widget but missing in this code 19 | // 1.02 2017-01-04 njr in calcCoef, checked for rate 0, to support non-IEEE compliant compilers 20 | // 21 | 22 | 23 | #include "ADSR.h" 24 | 25 | 26 | 27 | ADSR::ADSR(void) { 28 | Reset(); 29 | SetADSR(1, 1, 1.0, 1); 30 | 31 | } 32 | 33 | ADSR::~ADSR(void) { 34 | } 35 | 36 | void ADSR::SetADSR(Num attack, Num decay, Num sustain, Num release) 37 | { 38 | SetSustain(sustain); 39 | SetAttack(attack); 40 | SetDecay(decay); 41 | SetRelease(release); 42 | } 43 | void ADSR::SetAttack(Num attack) { 44 | this->attack = attack; 45 | if (attack < Num(1.0)) 46 | { 47 | attack = Num(1.0); 48 | } 49 | attackCoef = Num(1.0)/attack; 50 | attackBase = Num(0); 51 | } 52 | 53 | 54 | void ADSR::SetDecay(Num decay) { 55 | this->decay = decay; 56 | if (decay < Num(1)) 57 | { 58 | decay = Num(1); 59 | } 60 | decayCoef = (Num(1.0) - this->sustainLevel) / this->decay; 61 | } 62 | 63 | 64 | 65 | 66 | void ADSR::SetSustain(Num level) { 67 | sustainLevel = level; 68 | decayBase = sustainLevel; 69 | } 70 | 71 | void ADSR::SetRelease(Num releaseval) 72 | { 73 | release = releaseval; 74 | if (release < Num(1)) 75 | { 76 | release = Num(1); 77 | } 78 | releaseCoef = (this->sustainLevel / this->release); 79 | releaseBase = this->sustainLevel; 80 | } -------------------------------------------------------------------------------- /ADSR.h: -------------------------------------------------------------------------------- 1 | // 2 | // ADSR.h 3 | // 4 | // Created by Nigel Redmon on 12/18/12. 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2012 Nigel Redmon 7 | // 8 | // For a complete explanation of the ADSR envelope generator and code, 9 | // read the series of articles by the author, starting here: 10 | // http://www.earlevel.com/main/2013/06/01/envelope-generators/ 11 | // 12 | // License: 13 | // 14 | // This source code is provided as is, without warranty. 15 | // You may copy and distribute verbatim copies of this document. 16 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 17 | // 18 | 19 | #ifndef ADSR_h 20 | #define ADSR_h 21 | #include 22 | #include "Num.h" 23 | using namespace Fixie; 24 | IRAM_ATTR class ADSR { 25 | public: 26 | ADSR(void); 27 | ~ADSR(void); 28 | 29 | Num Process(void); 30 | Num GetOutput(void); 31 | int GetState(void); 32 | void SetADSR(Num attack, Num decay, Num sustain, Num release); 33 | void Gate(int on); 34 | void SetAttack(Num attack); 35 | void SetDecay(Num decay); 36 | void SetRelease(Num release); 37 | void SetSustain(Num level); 38 | Num getAttack(){return attack;} 39 | Num getDecay(){return decay;} 40 | Num getSustain(){return sustainLevel;} 41 | Num getRelease(){return release;} 42 | void Reset(void); 43 | 44 | enum envState { 45 | env_idle = 0, 46 | env_attack, 47 | env_decay, 48 | env_sustain, 49 | env_release 50 | }; 51 | 52 | protected: 53 | 54 | int state; 55 | Num output; 56 | Num attack; 57 | Num decay; 58 | Num release; 59 | int32_t counter; 60 | Num attackCoef; 61 | Num decayCoef; 62 | Num releaseCoef; 63 | Num sustainLevel; 64 | 65 | Num attackBase; 66 | Num decayBase; 67 | Num releaseBase; 68 | 69 | 70 | }; 71 | 72 | inline Num ADSR::Process() { 73 | switch (state) { 74 | case env_idle: 75 | output = Num(0); 76 | break; 77 | case env_attack: 78 | output = output+attackBase + attackCoef; 79 | 80 | if (output >= Num(1.0)) { 81 | output = Num(1.0); //http://www.earlevel.com/main/2013/06/01/envelope-generators/ 82 | state = env_decay; 83 | 84 | } 85 | break; 86 | case env_decay: 87 | output = output - decayCoef; 88 | 89 | if (output <= sustainLevel) { 90 | output = sustainLevel; 91 | state = env_sustain; 92 | } 93 | break; 94 | case env_sustain: 95 | //output = sustainLevel; 96 | break; 97 | case env_release: 98 | output = output - releaseCoef; 99 | 100 | if (output <= Num(0.0)) { 101 | output = Num(0); 102 | state = env_idle; 103 | } 104 | break; 105 | } 106 | return output; 107 | } 108 | 109 | inline void ADSR::Gate(int gate) { 110 | if (gate) 111 | { 112 | 113 | output = Num(0); 114 | state = env_attack; 115 | } 116 | else if (state != env_idle) 117 | { 118 | state = env_release; 119 | 120 | } 121 | } 122 | 123 | inline int ADSR::GetState() { 124 | return state; 125 | } 126 | 127 | inline void ADSR::Reset() { 128 | state = env_idle; 129 | output = Num(0); 130 | } 131 | 132 | inline Num ADSR::GetOutput() { 133 | return output; 134 | } 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /Config.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_CONFIG_H 2 | #define FIXIE_CONFIG_H 3 | 4 | namespace Fixie { 5 | namespace Config { 6 | const uint8_t fractionBits = 16; 7 | } 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /DrumVoice.h: -------------------------------------------------------------------------------- 1 | #ifndef DrumVoice_h 2 | #define DrumVoice_h 3 | #include "Num.h" 4 | #include "Util.h" 5 | #include "MultiWaveNumOsc.h" 6 | #include "AD.h" 7 | using namespace Fixie; 8 | 9 | class DrumVoice { 10 | public: 11 | DrumVoice() 12 | { 13 | this->sampleRate = 8000; 14 | } 15 | DrumVoice(double sampleRate) { 16 | this->sampleRate = sampleRate; 17 | } 18 | ~DrumVoice(void) { 19 | 20 | } 21 | void MidiNoteOn(uint8_t note, uint8_t vel) 22 | { 23 | 24 | double f = pow(2.0,(note*1.0-69.0)/12.0)*440.0; 25 | velocity = Num(vel/128.0); 26 | freq1 = f; 27 | freq2 = f; 28 | osc[0].SetFrequency(freq1,sampleRate); 29 | osc[1].SetFrequency(freq2,sampleRate); 30 | ad[0].Gate(1); 31 | ad[1].Gate(1); 32 | } 33 | void MidiNoteOff() 34 | { 35 | ad[0].Gate(0); 36 | ad[1].Gate(0); 37 | } 38 | void AddOsc1WaveTable(int len, int8_t *waveTableIn) 39 | { 40 | osc[0].AddWaveTable(len,waveTableIn); 41 | } 42 | void AddOsc2WaveTable(int len, int8_t *waveTableIn) 43 | { 44 | osc[1].AddWaveTable(len,waveTableIn); 45 | } 46 | void SetOsc1AD(Num a, Num d) 47 | { 48 | ad[0].SetAD(a,d); 49 | } 50 | void SetOsc2AD(Num a, Num d) 51 | { 52 | ad[1].SetAD(a,d); 53 | } 54 | void MidiBend(uint16_t bend) 55 | { 56 | double factor = ((bend - 8192.0)/8192.0); 57 | double mul = pow(2.0,(factor*12.0)/12.0); 58 | double bendfreq1 = freq1*mul; 59 | double bendfreq2 = freq2*mul; 60 | osc[0].SetFrequency(bendfreq1,sampleRate); 61 | osc[1].SetFrequency(bendfreq2,sampleRate); 62 | 63 | } 64 | Num Process() 65 | { 66 | return (velocity*ad[0].Process()*osc[0].Process()+velocity*ad[1].Process()*osc[1].Process())>>1; 67 | } 68 | bool IsPlaying() 69 | { 70 | if(ad[0].GetState()==AD::envState::env_idle && ad[0].GetState()==AD::envState::env_idle) 71 | { 72 | return false; 73 | } 74 | return true; 75 | } 76 | protected: 77 | MultiWaveNumOsc osc[2]; 78 | AD ad[2]; 79 | double sampleRate; 80 | double freq1; 81 | double freq2; 82 | Num velocity; 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /FileIO.h: -------------------------------------------------------------------------------- 1 | #include "FS.h" 2 | #include "FFat.h" 3 | 4 | // This file should be compiled with 'Partition Scheme' (in Tools menu) 5 | // set to 'Default with ffat' if you have a 4MB ESP32 dev module or 6 | // set to '16M Fat' if you have a 16MB ESP32 dev module. 7 | 8 | // You only need to format FFat the first time you run a test 9 | #define FORMAT_FFAT true 10 | const char* SETTINGS_FILE = "settings.cfg"; 11 | const char* PATTERNS_FILE = "patterns.dat"; 12 | const char* TUNINGS_FILE = "tunings.dat"; 13 | const char* PATCHES_FILE = "patches.dat"; 14 | File file; 15 | void initFileIO(char* msg) 16 | { 17 | if(!FFat.begin()) 18 | { 19 | sprintf(msg,"FILEIO INIT ERR"); 20 | } 21 | else 22 | { 23 | sprintf(msg, "FILEIO OK"); 24 | } 25 | } 26 | void formatFat() 27 | { 28 | FFat.format(); 29 | } 30 | bool openFile(const char* filename) 31 | { 32 | file = FFat.open(filename); 33 | return file?true:false; 34 | } 35 | bool openFileForWriting(const char* filename) 36 | { 37 | file = FFat.open(filename,FILE_WRITE); 38 | return (file>0)?true:false; 39 | } 40 | 41 | bool readLine(char* buf, int maxchars) 42 | { 43 | int i = 0; 44 | while(file.available() && i0) 56 | { 57 | return true; 58 | } 59 | return false; 60 | } 61 | bool writeLine(char* msg) 62 | { 63 | 64 | if(file.print(msg) && file.print("\n")) 65 | { 66 | return true; 67 | } 68 | else 69 | { 70 | return false; 71 | } 72 | } 73 | void closeFile() 74 | { 75 | file.close(); 76 | } 77 | -------------------------------------------------------------------------------- /FixedPointWaveTableOsc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #pragma once 3 | // 4 | // WaveTableOsc.h 5 | // 6 | // Created by Nigel Redmon on 2018-10-05 7 | // EarLevel Engineering: earlevel.com 8 | // Copyright 2018 Nigel Redmon 9 | // 10 | // For a complete explanation of the wavetable oscillator and code, 11 | // read the series of articles by the author, starting here: 12 | // www.earlevel.com/main/2012/05/03/a-wavetable-oscillator—introduction/ 13 | // 14 | // This version has optimizations described here: 15 | // www.earlevel.com/main/2019/04/28/wavetableosc-optimized/ 16 | // 17 | // License: 18 | // 19 | // This source code is provided as is, without warranty. 20 | // You may copy and distribute verbatim copies of this document. 21 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 22 | // 23 | 24 | #ifndef FixedPointWaveTableOsc_h 25 | #define FixedPointWaveTableOsc_h 26 | 27 | #include 28 | /////////////////////////////////////////////////////////////// 29 | // https://embeddedartistry.com/blog/2018/7/9/template-rayb2 // 30 | /////////////////////////////////////////////////////////////// 31 | #define FPFB 16 32 | inline double IRAM_ATTR fixed_to_float(int32_t input) 33 | { 34 | return ((double)input / (double)(1 << FPFB)); 35 | } 36 | 37 | 38 | inline int32_t IRAM_ATTR float_to_fixed(double input) 39 | { 40 | return (int32_t)(input * (1 << FPFB)); 41 | 42 | } 43 | 44 | 45 | IRAM_ATTR class FixedPointWaveTableOsc { 46 | public: 47 | FixedPointWaveTableOsc(void) { 48 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 49 | mWaveTables[idx].topFreq = 0; 50 | mWaveTables[idx].waveTableLen = 0; 51 | mWaveTables[idx].waveTable = 0; 52 | } 53 | } 54 | ~FixedPointWaveTableOsc(void) { 55 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 56 | int8_t *temp = mWaveTables[idx].waveTable; 57 | if (temp != 0) 58 | delete[] temp; 59 | } 60 | } 61 | 62 | 63 | void SetFrequency(double freq, double sampleRate) 64 | { 65 | double nfreq = freq / sampleRate; 66 | SetFrequency(nfreq); 67 | //printf("%f\r\n", freq); 68 | } 69 | 70 | // 71 | // SetFrequency: Set normalized frequency, typically 0-0.5 (must be positive and less than 1!) 72 | // 73 | void SetFrequency(double inc) { 74 | mPhaseInc = float_to_fixed(inc); 75 | 76 | // update the current wave table selector 77 | int curWaveTable = 0; 78 | while ((mPhaseInc >= mWaveTables[curWaveTable].topFreq) && (curWaveTable < (mNumWaveTables - 1))) { 79 | ++curWaveTable; 80 | } 81 | mCurWaveTable = curWaveTable; 82 | } 83 | 84 | // 85 | // SetPhaseOffset: Phase offset for PWM, 0-1 86 | // 87 | void SetPhaseOffset(double offset) { 88 | mPhaseOfs = offset; 89 | } 90 | 91 | // 92 | // UpdatePhase: Call once per sample 93 | // 94 | void UpdatePhase(void) { 95 | mPhasor += mPhaseInc; 96 | 97 | if (mPhasor >= float_to_fixed(1.0)) 98 | mPhasor -= float_to_fixed(1.0); 99 | } 100 | 101 | // 102 | // Process: Update phase and get output 103 | // 104 | int32_t Process(void) { 105 | UpdatePhase(); 106 | return GetOutput(); 107 | } 108 | 109 | // 110 | // GetOutput: Returns the current oscillator output 111 | // 112 | int32_t GetOutput(void) { 113 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 114 | 115 | // linear interpolation 116 | int32_t temp = mPhasor * waveTable->waveTableLen; 117 | int32_t intPart = (temp & 0xFFFFFF00); 118 | int32_t fracPart = temp - intPart; 119 | int32_t samp0 = waveTable->waveTable[intPart>>FPFB]; 120 | int32_t samp1 = waveTable->waveTable[intPart>>FPFB + 1]; 121 | return samp0 + (samp1 - samp0) * fracPart; 122 | } 123 | 124 | // 125 | // getOutputMinusOffset 126 | // 127 | // for variable pulse width: initialize to sawtooth, 128 | // set phaseOfs to duty cycle, use this for osc output 129 | // 130 | // returns the current oscillator output 131 | // 132 | int32_t GetOutputMinusOffset() { 133 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 134 | int len = waveTable->waveTableLen; 135 | int8_t *wave = waveTable->waveTable; 136 | 137 | // linear 138 | int32_t temp = mPhasor * len; 139 | int32_t intPart = temp; 140 | int32_t fracPart = temp - intPart; 141 | int32_t samp0 = wave[intPart]; 142 | int32_t samp1 = wave[intPart + 1]; 143 | int32_t samp = samp0 + (samp1 - samp0) * fracPart; 144 | 145 | // and linear again for the offset part 146 | int32_t offsetPhasor = mPhasor + mPhaseOfs; 147 | if (offsetPhasor > float_to_fixed(1.0)) 148 | offsetPhasor -= float_to_fixed(1.0); 149 | temp = offsetPhasor * len; 150 | intPart = temp; 151 | fracPart = temp - intPart; 152 | samp0 = wave[intPart]; 153 | samp1 = wave[intPart + 1]; 154 | return samp - (samp0 + (samp1 - samp0) * fracPart); 155 | } 156 | 157 | // 158 | // AddWaveTable 159 | // 160 | // add wavetables in order of lowest frequency to highest 161 | // topFreq is the highest frequency supported by a wavetable 162 | // wavetables within an oscillator can be different lengths 163 | // 164 | // returns 0 upon success, or the number of wavetables if no more room is available 165 | // 166 | int AddWaveTable(int len, int8_t *waveTableIn, double topFreq) { 167 | if (mNumWaveTables < numWaveTableSlots) { 168 | int8_t *waveTable = mWaveTables[mNumWaveTables].waveTable = new int8_t[len + 1]; 169 | mWaveTables[mNumWaveTables].waveTableLen = len; 170 | mWaveTables[mNumWaveTables].topFreq = topFreq; 171 | ++mNumWaveTables; 172 | 173 | // fill in wave 174 | for (long idx = 0; idx < len; idx++) 175 | waveTable[idx] = waveTableIn[idx]; 176 | waveTable[len] = waveTable[0]; // duplicate for interpolation wraparound 177 | 178 | return 0; 179 | } 180 | return mNumWaveTables; 181 | } 182 | 183 | protected: 184 | int32_t mPhasor = float_to_fixed(0.0); // phase accumulator 185 | int32_t mPhaseInc = float_to_fixed(0.0); // phase increment 186 | int32_t mPhaseOfs = float_to_fixed(0.5); // phase offset for PWM 187 | 188 | // array of wavetables 189 | int mCurWaveTable = 0; // current table, based on current frequency 190 | int mNumWaveTables = 0; // number of wavetable slots in use 191 | struct waveTable { 192 | double topFreq; 193 | int waveTableLen; 194 | int8_t *waveTable; 195 | }; 196 | static constexpr int numWaveTableSlots = 40; // simplify allocation with reasonable maximum 197 | waveTable mWaveTables[numWaveTableSlots]; 198 | }; 199 | 200 | #endif 201 | -------------------------------------------------------------------------------- /LowPass.h: -------------------------------------------------------------------------------- 1 | #ifndef LowPass_h 2 | #define LowPass_h 3 | #include 4 | #include "Num.h" 5 | using namespace Fixie; 6 | /* 7 | simple resonant filter posted to musicdsp.org by Paul Kellett http://www.musicdsp.org/archive.php?classid=3#259 8 | // set feedback amount given f and q between 0 and 1 9 | fb = q + q/(1.0 - f); 10 | // for each sample... 11 | buf0 = buf0 + f * (in - buf0 + fb * (buf0 - buf1)); 12 | buf1 = buf1 + f * (buf0 - buf1); 13 | out = buf1; 14 | Taken from mozzi 15 | */ 16 | 17 | IRAM_ATTR class LowPass { 18 | public: 19 | LowPass() { 20 | 21 | } 22 | ~LowPass(void) { 23 | 24 | } 25 | void SetParameters(Num f, Num q) 26 | { 27 | if(f==Num(1)) 28 | { 29 | f = Num(0.999); 30 | } 31 | this->f = f; 32 | this->q = q; 33 | fb = q + q/(Num(1) - f); 34 | } 35 | 36 | Num Process(Num in) 37 | { 38 | 39 | buf0 = buf0 + f * (in - buf0 + fb * (buf0 - buf1)); 40 | buf1 = buf1 + f * (buf0 - buf1); 41 | return buf1; 42 | } 43 | protected: 44 | Num q; 45 | Num f; 46 | Num fb; 47 | Num buf0; 48 | Num buf1; 49 | 50 | 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /Matrix4.cpp: -------------------------------------------------------------------------------- 1 | #include "Matrix4.h" 2 | 3 | namespace Fixie { 4 | Matrix4::Matrix4( 5 | Num c0, 6 | Num c1, 7 | Num c2, 8 | Num c3, 9 | Num c4, 10 | Num c5, 11 | Num c6, 12 | Num c7, 13 | Num c8, 14 | Num c9, 15 | Num c10, 16 | Num c11, 17 | Num c12, 18 | Num c13, 19 | Num c14, 20 | Num c15 21 | ) { 22 | components[0] = c0; 23 | components[1] = c1; 24 | components[2] = c2; 25 | components[3] = c3; 26 | components[4] = c4; 27 | components[5] = c5; 28 | components[6] = c6; 29 | components[7] = c7; 30 | components[8] = c8; 31 | components[9] = c9; 32 | components[10] = c10; 33 | components[11] = c11; 34 | components[12] = c12; 35 | components[13] = c13; 36 | components[14] = c14; 37 | components[15] = c15; 38 | } 39 | 40 | Matrix4::Matrix4() { } 41 | 42 | Num& Matrix4::operator[](const int index) { 43 | return components[index]; 44 | } 45 | 46 | const Num& Matrix4::operator[](const int index) const { 47 | return components[index]; 48 | } 49 | 50 | void Matrix4::reset() { 51 | for(uint8_t i=0; i<16; ++i) { 52 | components[i] = 0; 53 | } 54 | } 55 | 56 | Matrix4 Matrix4::operator*(Matrix4 vector) { 57 | Matrix4 result = *this; 58 | result *= vector; 59 | return result; 60 | } 61 | 62 | Matrix4& Matrix4::operator*=(Matrix4 other) { 63 | Matrix4 original = *this; 64 | reset(); 65 | 66 | int resultIndex; 67 | for(int row=0; 4>row; row++) { 68 | for(int column=0; 4>column; column++) { 69 | resultIndex = column*4+row; 70 | for(int step=0; 4>step; step++) { 71 | components[resultIndex] += original[row+step*4] * other[column*4+step]; 72 | } 73 | } 74 | } 75 | 76 | return *this; 77 | } 78 | 79 | Matrix4 Matrix4::identity() { 80 | Matrix4 matrix; 81 | 82 | matrix[0] = 1; 83 | matrix[1] = 0; 84 | matrix[2] = 0; 85 | matrix[3] = 0; 86 | matrix[4] = 0; 87 | matrix[5] = 1; 88 | matrix[6] = 0; 89 | matrix[7] = 0; 90 | matrix[8] = 0; 91 | matrix[9] = 0; 92 | matrix[10] = 1; 93 | matrix[11] = 0; 94 | matrix[12] = 0; 95 | matrix[13] = 0; 96 | matrix[14] = 0; 97 | matrix[15] = 1; 98 | 99 | return matrix; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Matrix4.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_MATRIX4_H 2 | #define FIXIE_MATRIX4_H 3 | 4 | #include "Num.h" 5 | 6 | namespace Fixie { 7 | class Matrix4 { 8 | public: 9 | Matrix4( 10 | Num c0, 11 | Num c1, 12 | Num c2, 13 | Num c3, 14 | Num c4, 15 | Num c5, 16 | Num c6, 17 | Num c7, 18 | Num c8, 19 | Num c9, 20 | Num c10, 21 | Num c11, 22 | Num c12, 23 | Num c13, 24 | Num c14, 25 | Num c15 26 | ); 27 | Matrix4(); 28 | Num& operator[](const int index); 29 | const Num& operator[](const int index) const; 30 | Matrix4 operator*(Matrix4 other); 31 | Matrix4& operator*=(Matrix4 other); 32 | static Matrix4 identity(); 33 | void reset(); 34 | private: 35 | Num components[16]; 36 | }; 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /MultiWaveNumOsc.h: -------------------------------------------------------------------------------- 1 | // 2 | // WaveTableOsc.h 3 | // 4 | // Created by Nigel Redmon on 2018-10-05 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2018 Nigel Redmon 7 | // 8 | // For a complete explanation of the wavetable oscillator and code, 9 | // read the series of articles by the author, starting here: 10 | // www.earlevel.com/main/2012/05/03/a-wavetable-oscillator—introduction/ 11 | // 12 | // This version has optimizations described here: 13 | // www.earlevel.com/main/2019/04/28/wavetableosc-optimized/ 14 | // 15 | // License: 16 | // 17 | // This source code is provided as is, without warranty. 18 | // You may copy and distribute verbatim copies of this document. 19 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 20 | // 21 | 22 | #ifndef MultiWaveNumOsc_h 23 | #define MultiWaveNumOsc_h 24 | #include "Num.h" 25 | #include "Util.h" 26 | using namespace Fixie; 27 | 28 | class MultiWaveNumOsc { 29 | public: 30 | MultiWaveNumOsc(void) { 31 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 32 | 33 | mWaveTables[idx].waveTableLen = 0; 34 | mWaveTables[idx].waveTable = 0; 35 | } 36 | } 37 | ~MultiWaveNumOsc(void) { 38 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 39 | int8_t *temp = mWaveTables[idx].waveTable; 40 | if (temp != 0) 41 | delete [] temp; 42 | } 43 | } 44 | 45 | 46 | 47 | 48 | void SetFrequency(double freq, double sampleRate) 49 | { 50 | SetFrequency (Num(freq/sampleRate)); 51 | } 52 | // 53 | // SetFrequency: Set normalized frequency, typically 0-0.5 (must be positive and less than 1!) 54 | // 55 | void SetFrequency(Num inc) { 56 | mPhaseInc = inc; 57 | 58 | 59 | 60 | } 61 | void SetWaveform(int index) 62 | { 63 | if(index>=0 && index= Num(1.0)) 86 | mPhasor -= Num(1.0); 87 | } 88 | 89 | // 90 | // Process: Update phase and get output 91 | // 92 | Num Process(void) { 93 | UpdatePhase(); 94 | return GetOutput(); 95 | } 96 | 97 | // 98 | // GetOutput: Returns the current oscillator output 99 | // 100 | Num GetOutput(void) { 101 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 102 | 103 | // linear interpolation 104 | Num temp = mPhasor * (waveTable->waveTableLen); 105 | Num intPart = Fixie::Util::floor(temp); 106 | Num fracPart = temp - intPart; 107 | Num samp0 = Num(waveTable->waveTable[intPart]); 108 | Num samp1 = Num(waveTable->waveTable[intPart + Num(1)]); 109 | return samp0 + (samp1 - samp0) * fracPart; 110 | } 111 | 112 | // 113 | // getOutputMinusOffset 114 | // 115 | // for variable pulse width: initialize to sawtooth, 116 | // set phaseOfs to duty cycle, use this for osc output 117 | // 118 | // returns the current oscillator output 119 | // 120 | Fixie::Num GetOutputMinusOffset() { 121 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 122 | Num len = waveTable->waveTableLen; 123 | int8_t *wave = waveTable->waveTable; 124 | 125 | // linear 126 | Num temp = mPhasor * len; 127 | Num intPart = Fixie::Util::floor(temp); 128 | Num fracPart = temp - intPart; 129 | Num samp0 = Num(wave[intPart]); 130 | Num samp1 = Num(wave[intPart+Num(1)]); 131 | Num samp = samp0 + (samp1 - samp0) * fracPart; 132 | 133 | // and linear again for the offset part 134 | Fixie::Num offsetPhasor = mPhasor + mPhaseOfs; 135 | if (offsetPhasor > Num(1.0)) 136 | offsetPhasor -= Num(1.0); 137 | temp = offsetPhasor * Num(len); 138 | intPart = temp; 139 | fracPart = temp - intPart; 140 | samp0 = wave[intPart]; 141 | samp1 = wave[intPart+Num(1)]; 142 | return samp - (samp0 + (samp1 - samp0) * fracPart); 143 | } 144 | 145 | // 146 | // AddWaveTable 147 | // 148 | // add wavetables in order of lowest frequency to highest 149 | // topFreq is the highest frequency supported by a wavetable 150 | // wavetables within an oscillator can be different lengths 151 | // 152 | // returns 0 upon success, or the number of wavetables if no more room is available 153 | // 154 | int AddWaveTable(int len, int8_t *waveTableIn) { 155 | if (mNumWaveTables < numWaveTableSlots) { 156 | int8_t *waveTable = mWaveTables[mNumWaveTables].waveTable = new int8_t[len + 1]; 157 | mWaveTables[mNumWaveTables].waveTableLen = len; 158 | 159 | ++mNumWaveTables; 160 | 161 | // fill in wave 162 | for (long idx = 0; idx < len; idx++) 163 | waveTable[idx] = waveTableIn[idx]; 164 | waveTable[len] = waveTable[0]; // duplicate for interpolation wraparound 165 | 166 | return 0; 167 | } 168 | return mNumWaveTables; 169 | } 170 | 171 | protected: 172 | Num mPhasor = Num(0.0); // phase accumulator 173 | Num mPhaseInc = Num(0.0); // phase increment 174 | Num mPhaseOfs = Num(0.5); // phase offset for PWM 175 | 176 | // array of wavetables 177 | int mCurWaveTable = 0; // current table, based on index 178 | int mNumWaveTables = 0; // number of wavetable slots in use 179 | struct waveTable { 180 | 181 | Num waveTableLen; 182 | int8_t *waveTable; 183 | }; 184 | static constexpr int numWaveTableSlots = 256; // simplify allocation with reasonable maximum 185 | waveTable mWaveTables[numWaveTableSlots]; 186 | }; 187 | 188 | #endif 189 | -------------------------------------------------------------------------------- /Num.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_NUM_H 2 | #define FIXIE_NUM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "Config.h" 8 | 9 | namespace Fixie { 10 | using namespace Config; 11 | 12 | class Num { 13 | public: 14 | int32_t raw = 0; 15 | Num() { } 16 | Num(int32_t x) { 17 | raw = x << fractionBits; 18 | } 19 | Num(double x) { 20 | raw = round(x * (1 << fractionBits)); 21 | } 22 | Num& operator+=(const Num &rhs) { 23 | raw += rhs.raw; 24 | return *this; 25 | } 26 | Num& operator-=(const Num &rhs) { 27 | raw -= rhs.raw; 28 | return *this; 29 | } 30 | Num& operator/=(const Num &rhs) { 31 | assert(rhs.raw != 0); 32 | const int32_t resultNegative = ((raw ^ rhs.raw) & 0x80000000) >> 31; 33 | const int32_t sign = resultNegative*-2+1; 34 | int64_t temp = static_cast(raw) << fractionBits; 35 | temp += rhs.raw/2*sign; 36 | raw = temp / rhs.raw; 37 | return *this; 38 | } 39 | Num& operator*=(const Num &rhs) { 40 | raw = (static_cast(raw) * rhs.raw) >> fractionBits; 41 | return *this; 42 | } 43 | Num operator+(const Num &other) const { 44 | Num result = *this; 45 | result += other; 46 | return result; 47 | } 48 | Num operator-(const Num &other) const { 49 | Num result = *this; 50 | result -= other; 51 | return result; 52 | } 53 | Num operator*(const Num &other) const { 54 | Num result = *this; 55 | result *= other; 56 | return result; 57 | } 58 | Num operator/(const Num &other) const { 59 | Num result = *this; 60 | result /= other; 61 | return result; 62 | } 63 | Num operator%(Num rhs) { 64 | int32_t a = *this; 65 | int32_t b = rhs; 66 | return a % b; 67 | } 68 | bool operator==(const Num &other) { 69 | return raw == other.raw; 70 | } 71 | bool operator!=(const Num &other) { 72 | return !(*this == other); 73 | } 74 | bool operator<(const Num &other) { 75 | return raw < other.raw; 76 | } 77 | bool operator<=(const Num &other) { 78 | return raw <= other.raw; 79 | } 80 | bool operator>(const Num &other) { 81 | return raw > other.raw; 82 | } 83 | bool operator>=(const Num &other) { 84 | return raw >= other.raw; 85 | } 86 | operator float() const { 87 | return static_cast(raw) / (1 << fractionBits); 88 | } 89 | operator double() const { 90 | return static_cast(raw) / (1 << fractionBits); 91 | } 92 | operator int32_t() const { 93 | return raw / (1 << fractionBits); 94 | } 95 | static Num createByRaw(int32_t raw) { 96 | Num n; 97 | n.raw = raw; 98 | return n; 99 | } 100 | }; 101 | } 102 | 103 | #endif 104 | -------------------------------------------------------------------------------- /NumWaveTableOsc.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // WaveTableOsc.h 3 | // 4 | // Created by Nigel Redmon on 2018-10-05 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2018 Nigel Redmon 7 | // 8 | // For a complete explanation of the wavetable oscillator and code, 9 | // read the series of articles by the author, starting here: 10 | // www.earlevel.com/main/2012/05/03/a-wavetable-oscillator—introduction/ 11 | // 12 | // This version has optimizations described here: 13 | // www.earlevel.com/main/2019/04/28/wavetableosc-optimized/ 14 | // 15 | // License: 16 | // 17 | // This source code is provided as is, without warranty. 18 | // You may copy and distribute verbatim copies of this document. 19 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 20 | // 21 | 22 | #ifndef NumWaveTableOsc_h 23 | #define NumWaveTableOsc_h 24 | #include 25 | #include "Num.h" 26 | #include "Util.h" 27 | using namespace Fixie; 28 | 29 | class IRAM_ATTR NumWaveTableOsc { 30 | public: 31 | NumWaveTableOsc(void) { 32 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 33 | 34 | mWaveTables[idx].waveTableLen = 0; 35 | mWaveTables[idx].waveTable = 0; 36 | } 37 | } 38 | ~NumWaveTableOsc(void) { 39 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 40 | int8_t *temp = mWaveTables[idx].waveTable; 41 | if (temp != 0) 42 | delete [] temp; 43 | } 44 | } 45 | 46 | 47 | 48 | 49 | void SetFrequency(double freq, double sampleRate) 50 | { 51 | SetFrequency (Num(freq/sampleRate)); 52 | } 53 | // 54 | // SetFrequency: Set normalized frequency, typically 0-0.5 (must be positive and less than 1!) 55 | // 56 | void SetFrequency(Num inc) { 57 | mPhaseInc = inc; 58 | 59 | // update the current wave table selector 60 | 61 | } 62 | 63 | // 64 | // SetPhaseOffset: Phase offset for PWM, 0-1 65 | // 66 | void SetPhaseOffset(Num offset) { 67 | mPhaseOfs = offset; 68 | } 69 | 70 | // 71 | // UpdatePhase: Call once per sample 72 | // 73 | void UpdatePhase(void) { 74 | mPhasor += mPhaseInc; 75 | 76 | if (mPhasor >= Num(1.0)) 77 | mPhasor -= Num(1.0); 78 | } 79 | 80 | // 81 | // Process: Update phase and get output 82 | // 83 | Num Process(void) { 84 | UpdatePhase(); 85 | if(mPhaseOfs!=Num(0)) 86 | { 87 | return GetOutputMinusOffset(); 88 | } 89 | else 90 | { 91 | return GetOutput(); 92 | } 93 | } 94 | 95 | // 96 | // GetOutput: Returns the current oscillator output 97 | // 98 | Num GetOutput(void) { 99 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 100 | 101 | // linear interpolation 102 | Num temp = mPhasor * (waveTable->waveTableLen); 103 | Num intPart = Fixie::Util::floor(temp); 104 | Num fracPart = temp - intPart; 105 | Num samp0 = Num(waveTable->waveTable[intPart]); 106 | Num samp1 = Num(waveTable->waveTable[intPart + Num(1)]); 107 | return samp0 + (samp1 - samp0) * fracPart; 108 | } 109 | 110 | // 111 | // getOutputMinusOffset 112 | // 113 | // for variable pulse width: initialize to sawtooth, 114 | // set phaseOfs to duty cycle, use this for osc output 115 | // 116 | // returns the current oscillator output 117 | // 118 | Num GetOutputMinusOffset() { 119 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 120 | Num len = waveTable->waveTableLen; 121 | int8_t *wave = waveTable->waveTable; 122 | 123 | 124 | 125 | Num temp = mPhasor * len; 126 | Num intPart = Fixie::Util::floor(temp); 127 | Num fracPart = temp - intPart; 128 | Num samp0 = Num(wave[intPart]); 129 | Num samp1 = Num(wave[intPart+Num(1)]); 130 | Num samp = samp0 + (samp1 - samp0) * fracPart; 131 | 132 | // and linear again for the offset part 133 | Fixie::Num offsetPhasor = mPhasor + mPhaseOfs; 134 | if (offsetPhasor > Num(1.0)) 135 | offsetPhasor -= Num(1.0); 136 | temp = offsetPhasor * Num(len); 137 | intPart = temp; 138 | fracPart = temp - intPart; 139 | samp0 = wave[intPart]; 140 | samp1 = wave[intPart+Num(1)]; 141 | return samp - (samp0 + (samp1 - samp0) * fracPart); 142 | } 143 | 144 | // 145 | // AddWaveTable 146 | // 147 | // add wavetables 148 | // wavetables within an oscillator can be different lengths 149 | // 150 | // returns 0 upon success, or the number of wavetables if no more room is available 151 | // 152 | 153 | 154 | int AddSharedWaveTable(int len, int8_t *waveTableIn) { 155 | if (mNumWaveTables < numWaveTableSlots) { 156 | mWaveTables[mNumWaveTables].waveTable = waveTableIn; 157 | mWaveTables[mNumWaveTables].waveTableLen = len-1; 158 | 159 | ++mNumWaveTables; 160 | 161 | 162 | 163 | return 0; 164 | } 165 | return mNumWaveTables; 166 | } 167 | 168 | int AddWaveTable(int len, int8_t *waveTableIn) { 169 | if (mNumWaveTables < numWaveTableSlots) { 170 | int8_t *waveTable = mWaveTables[mNumWaveTables].waveTable = new int8_t[len + 1]; 171 | mWaveTables[mNumWaveTables].waveTableLen = len; 172 | 173 | ++mNumWaveTables; 174 | 175 | // fill in wave 176 | for (long idx = 0; idx < len; idx++) 177 | waveTable[idx] = waveTableIn[idx]; 178 | waveTable[len] = waveTable[0]; // duplicate for interpolation wraparound 179 | 180 | return 0; 181 | } 182 | return mNumWaveTables; 183 | } 184 | 185 | void ResetPhase() 186 | { 187 | mPhasor = Num(0.0); 188 | } 189 | void SetWaveTable(int waveTableIdx) 190 | { 191 | this->mCurWaveTable = waveTableIdx%mNumWaveTables; 192 | } 193 | int GetWaveTableCount() 194 | { 195 | return this->mNumWaveTables; 196 | } 197 | protected: 198 | Num mPhasor = Num(0.0); // phase accumulator 199 | Num mPhaseInc = Num(0.0); // phase increment 200 | Num mPhaseOfs = Num(0); // phase offset for PWM 201 | 202 | // array of wavetables 203 | int mCurWaveTable = 0; // current table, based on current frequency 204 | int mNumWaveTables = 0; // number of wavetable slots in use 205 | struct waveTable { 206 | 207 | Num waveTableLen; 208 | int8_t *waveTable; 209 | }; 210 | static constexpr int numWaveTableSlots = 256; // simplify allocation with reasonable maximum 211 | waveTable mWaveTables[numWaveTableSlots]; 212 | }; 213 | 214 | #endif 215 | -------------------------------------------------------------------------------- /Parameters.h: -------------------------------------------------------------------------------- 1 | #ifndef PARAMETERS_H 2 | #define PARAMETERS_H 3 | #include "Num.h" 4 | struct Parameters 5 | { 6 | num adsr1_a; 7 | num adsr1_d; 8 | num adsr1_s; 9 | num adsr1_r; 10 | num adsr2_a; 11 | num adsr2_d; 12 | num adsr2_s; 13 | num adsr2_r; 14 | 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /Quaternion.cpp: -------------------------------------------------------------------------------- 1 | #include "Quaternion.h" 2 | 3 | namespace Fixie { 4 | Quaternion::Quaternion() { } 5 | 6 | Quaternion::Quaternion(Num real, Vector3 imaginaries) { 7 | this->real = real; 8 | this->imaginaries = imaginaries; 9 | } 10 | 11 | Quaternion Quaternion::identity() { 12 | return Quaternion(1, Vector3(0, 0, 0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Quaternion.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_QUATERNION_H 2 | #define FIXIE_QUATERNION_H 3 | 4 | #include "Num.h" 5 | #include "Vector3.h" 6 | 7 | namespace Fixie { 8 | class Quaternion { 9 | public: 10 | Quaternion(); 11 | Quaternion(Num real, Vector3 imaginaries); 12 | Num real; 13 | Vector3 imaginaries; 14 | static Quaternion identity(); 15 | }; 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | esp32soundsynth 2 | =============== 3 | 4 | A simple synthesizer engine for the esp32 5 | 6 | This is a midi controllable synthesizer engine for the esp32. The project does not aim to provide hifi audio. On the contrary a lofi 8bit wavetable engine is implemented and the sounds it produces have an 80s vibe. Currently the sound engine does not utilize an audio buffer and all synthesis takes place inside an Interrupt Service Routine that writes audio to the 2 8bit DACs of the esp32. For the audio, a wavetable engine is used based from http://www.earlevel.com/main/category/digital-audio/oscillators/wavetable-oscillators/. The envelope generator is from here: https://www.earlevel.com/main/2013/06/03/envelope-generators-adsr-code/. However the code from those two libraries is floating point and that is a no-no for code running on an ISR on the esp32. So instead of using floats, the library Fixie provides a fixed point implementation. Fixie can be found here: https://github.com/raroni/fixie 7 | 8 | This is a work in progress so this file will be updated with additional info as the project progresses. 9 | 10 | Hardware Testbed 11 | ================ 12 | For testing I assembled 2 boards using perfboards and thin wire. I used 2 different esp32 modules. One is an older ESP32DEVKIT DOIT V1 (plain module, 4MB flash) and the other one is a TTGO 1.7 esp32 module with 4MB of flash and 4MB of psram. Currently I do not use the psram. I added a midi in and midi out circuit, and a headphone jack for audio output. (pictures and videos coming soon...) 13 | Video here: 14 | https://www.youtube.com/watch?v=Zv0jsLpSGcA&feature=youtu.be 15 | In the video I use a breadboarded esp32 with integrated oled IIC 128x64 pixel display. I use it to display the audio waveform and status messages/menus. I also plan to add support for text IIC 2x16 character displays. 16 | 17 | 18 | -------------------------------------------------------------------------------- /StringMachine.cpp: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /SynthVoice.h: -------------------------------------------------------------------------------- 1 | #ifndef SynthVoice_h 2 | #define SynthVoice_h 3 | #include "Num.h" 4 | #include "Util.h" 5 | #include "NumWaveTableOsc.hpp" 6 | #include "ADSR.h" 7 | #include "LowPass.h" 8 | #include 9 | using namespace Fixie; 10 | 11 | class IRAM_ATTR SynthVoice { 12 | public: 13 | SynthVoice() 14 | { 15 | this->sampleRate = 8000; 16 | this->modulation = 0; 17 | this->pwm = Num(0.5); 18 | this->fmod1 = Num(1.0); 19 | this->fmod2 = Num(1.0); 20 | this->fmod3 = Num(0.0); 21 | this->ffreq = Num(1.0); 22 | this->fq = Num(0.1); 23 | lowpass.SetParameters(ffreq, fq); 24 | 25 | } 26 | SynthVoice(double sampleRate) { 27 | this->sampleRate = sampleRate; 28 | this->modulation = 0; 29 | this->pwm = Num(0.5); 30 | this->fmod1 = Num(1.0); 31 | this->fmod2 = Num(1.0); 32 | this->fmod3 = Num(0.0); 33 | this->ffreq = Num(1.0); 34 | this->fq = Num(0.1); 35 | lowpass.SetParameters(ffreq, fq); 36 | } 37 | ~SynthVoice(void) { 38 | 39 | } 40 | void MidiNoteOn(uint8_t note, uint8_t vel) 41 | { 42 | 43 | double f = pow(2.0,(note*1.0-69.0)/12.0)*440.0; 44 | velocity = Num(vel/128.0); 45 | freq1 = f; 46 | freq2 = f; 47 | osc[0].SetFrequency(freq1,sampleRate); 48 | osc[1].SetFrequency(freq2,sampleRate); 49 | adsr[0].Gate(1); 50 | adsr[1].Gate(1); 51 | } 52 | void MidiNoteOff() 53 | { 54 | adsr[0].Gate(0); 55 | adsr[1].Gate(0); 56 | } 57 | void AddOsc1WaveTable(int len, int8_t *waveTableIn) 58 | { 59 | osc[0].AddWaveTable(len,waveTableIn); 60 | } 61 | void AddOsc1SharedWaveTable(int len, int8_t *waveTableIn) 62 | { 63 | osc[0].AddSharedWaveTable(len, waveTableIn); 64 | } 65 | void AddOsc2WaveTable(int len, int8_t *waveTableIn) 66 | { 67 | osc[1].AddWaveTable(len,waveTableIn); 68 | } 69 | void AddOsc2SharedWaveTable(int len, int8_t *waveTableIn) 70 | { 71 | osc[1].AddSharedWaveTable(len, waveTableIn); 72 | } 73 | 74 | void SetOsc1ADSR(Num a, Num d, Num s, Num r) 75 | { 76 | adsr[0].SetADSR(a,d,s,r); 77 | } 78 | void SetOsc2ADSR(Num a, Num d, Num s, Num r) 79 | { 80 | adsr[1].SetADSR(a,d,s,r); 81 | } 82 | void SetFmod1(uint8_t fmod) 83 | { 84 | this->fmod1 = Num(fmod)/Num(64); 85 | } 86 | void SetFmod2(uint8_t fmod) 87 | { 88 | this->fmod2 = Num(fmod)/Num(64); 89 | } 90 | void SetFmod3(uint8_t fmod) 91 | { 92 | this->fmod3 = Num(fmod)/Num(64); 93 | } 94 | void MidiBend(uint16_t bend) 95 | { 96 | double factor = ((bend - 8192.0)/8192.0); 97 | double mul = pow(2.0,(factor*12.0)/12.0); 98 | double bendfreq1 = freq1*mul; 99 | double bendfreq2 = freq2*mul; 100 | osc[0].SetFrequency(bendfreq1,sampleRate); 101 | osc[1].SetFrequency(bendfreq2,sampleRate); 102 | 103 | } 104 | void MidiMod(uint8_t newmod) 105 | { 106 | modulation = Num(newmod)/Num(127.0); 107 | fmod1 = Num(1.0)-Num(modulation)/Num(127.0); 108 | fmod2 = Num(1.0)-Num(modulation)/Num(127.0); 109 | fmod3 = modulation; 110 | } 111 | void MidiPwm(uint8_t newmod) 112 | { 113 | pwm = Num(newmod)/Num(128); 114 | if(newmod == 0) 115 | { 116 | osc[0].SetPhaseOffset(0); 117 | osc[1].SetPhaseOffset(0); 118 | } 119 | else 120 | { 121 | osc[0].SetPhaseOffset(pwm); 122 | osc[1].SetPhaseOffset(pwm); 123 | } 124 | } 125 | int GetOsc1WaveTableCount() 126 | { 127 | return osc[0].GetWaveTableCount(); 128 | } 129 | int GetOsc2WaveTableCount() 130 | { 131 | return osc[1].GetWaveTableCount(); 132 | } 133 | void SetOsc1PhaseOffset(uint8_t newphase) 134 | { 135 | osc[0].SetPhaseOffset(newphase/127.0); 136 | } 137 | void SetOsc2PhaseOffset(uint8_t newphase) 138 | { 139 | osc[1].SetPhaseOffset(newphase/127.0); 140 | } 141 | void MidiOsc1Wave(uint8_t newwave) 142 | { 143 | osc[0].SetWaveTable(newwave); 144 | wt1_idx = newwave; 145 | } 146 | void MidiOsc2Wave(uint8_t newwave) 147 | { 148 | osc[1].SetWaveTable(newwave); 149 | wt2_idx = newwave; 150 | } 151 | void SetFilterParameters(uint8_t filter_freq, uint8_t filter_q) 152 | { 153 | lowpass.SetParameters(filter_freq/127.0,filter_q/127.0); 154 | } 155 | Num Process() 156 | { 157 | if(modulation==Num(0)) 158 | { 159 | return (lowpass.Process(velocity*adsr[0].Process()*osc[0].Process()*fmod1+velocity*adsr[1].Process()*osc[1].Process()*fmod2))>>1; 160 | } 161 | else 162 | { 163 | return lowpass.Process((velocity*adsr[0].Process()*osc[0].Process()*fmod1) + (velocity*adsr[1].Process()*osc[1].Process()*fmod2) + (velocity*(adsr[0].Process()*osc[0].Process()*osc[1].Process()*fmod3)))>>3; 164 | } 165 | } 166 | bool IsPlaying() 167 | { 168 | if(adsr[0].GetState()==ADSR::envState::env_idle && adsr[0].GetState()==ADSR::envState::env_idle) 169 | { 170 | return false; 171 | } 172 | return true; 173 | } 174 | 175 | protected: 176 | NumWaveTableOsc osc[2]; 177 | ADSR adsr[2]; 178 | double sampleRate; 179 | double freq1; 180 | double freq2; 181 | Num velocity; 182 | Num modulation; 183 | Num pwm; 184 | Num fmod1; 185 | Num fmod2; 186 | Num fmod3; 187 | Num ffreq; 188 | Num fq; 189 | LowPass lowpass; 190 | uint8_t wt1_idx; 191 | uint8_t wt2_idx; 192 | }; 193 | 194 | #endif 195 | -------------------------------------------------------------------------------- /TransformFactory.cpp: -------------------------------------------------------------------------------- 1 | #include "TransformFactory.h" 2 | 3 | namespace Fixie { 4 | namespace TransformFactory { 5 | Matrix4 translation(Vector3 translation) { 6 | Matrix4 matrix = Matrix4::identity(); 7 | matrix[12] = translation[0]; 8 | matrix[13] = translation[1]; 9 | matrix[14] = translation[2]; 10 | return matrix; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TransformFactory.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_TRANSFORM_FACTORY_H 2 | #define FIXIE_TRANSFORM_FACTORY_H 3 | 4 | #include "Vector3.h" 5 | #include "Matrix4.h" 6 | 7 | namespace Fixie { 8 | namespace TransformFactory { 9 | Matrix4 translation(Vector3 translation); 10 | } 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Trig.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Num.h" 3 | #include "Util.h" 4 | #include "Trig.h" 5 | 6 | #include 7 | 8 | namespace Fixie { 9 | namespace Trig { 10 | const Num pi = Num::createByRaw(3217); 11 | const Num twoPi = Num::createByRaw(6434); 12 | const Num halfPi = Num::createByRaw(1608); 13 | const Num inverseTwoPi = Num::createByRaw(163); 14 | 15 | int16_t sineTable[] = { 16 | 0, 6, 13, 19, 25, 31, 38, 44, 17 | 50, 57, 63, 69, 75, 82, 88, 94, 18 | 100, 107, 113, 119, 125, 132, 138, 144, 19 | 150, 156, 163, 169, 175, 181, 187, 194, 20 | 200, 206, 212, 218, 224, 230, 237, 243, 21 | 249, 255, 261, 267, 273, 279, 285, 291, 22 | 297, 303, 309, 315, 321, 327, 333, 339, 23 | 345, 351, 357, 363, 369, 374, 380, 386, 24 | 392, 398, 403, 409, 415, 421, 426, 432, 25 | 438, 443, 449, 455, 460, 466, 472, 477, 26 | 483, 488, 494, 499, 505, 510, 516, 521, 27 | 526, 532, 537, 543, 548, 553, 558, 564, 28 | 569, 574, 579, 584, 590, 595, 600, 605, 29 | 610, 615, 620, 625, 630, 635, 640, 645, 30 | 650, 654, 659, 664, 669, 674, 678, 683, 31 | 688, 692, 697, 702, 706, 711, 715, 720, 32 | 724, 729, 733, 737, 742, 746, 750, 755, 33 | 759, 763, 767, 771, 775, 779, 784, 788, 34 | 792, 796, 799, 803, 807, 811, 815, 819, 35 | 822, 826, 830, 834, 837, 841, 844, 848, 36 | 851, 855, 858, 862, 865, 868, 872, 875, 37 | 878, 882, 885, 888, 891, 894, 897, 900, 38 | 903, 906, 909, 912, 915, 917, 920, 923, 39 | 926, 928, 931, 934, 936, 939, 941, 944, 40 | 946, 948, 951, 953, 955, 958, 960, 962, 41 | 964, 966, 968, 970, 972, 974, 976, 978, 42 | 980, 982, 983, 985, 987, 989, 990, 992, 43 | 993, 995, 996, 998, 999, 1000, 1002, 1003, 44 | 1004, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 45 | 1013, 1014, 1015, 1016, 1016, 1017, 1018, 1018, 46 | 1019, 1020, 1020, 1021, 1021, 1022, 1022, 1022, 47 | 1023, 1023, 1023, 1024, 1024, 1024, 1024, 1024, 48 | 1024, 1024, 1024, 1024, 1024, 1024, 1023, 1023, 49 | 1023, 1022, 1022, 1022, 1021, 1021, 1020, 1020, 50 | 1019, 1018, 1018, 1017, 1016, 1016, 1015, 1014, 51 | 1013, 1012, 1011, 1010, 1009, 1008, 1007, 1006, 52 | 1004, 1003, 1002, 1000, 999, 998, 996, 995, 53 | 993, 992, 990, 989, 987, 985, 983, 982, 54 | 980, 978, 976, 974, 972, 970, 968, 966, 55 | 964, 962, 960, 958, 955, 953, 951, 948, 56 | 946, 944, 941, 939, 936, 934, 931, 928, 57 | 926, 923, 920, 917, 915, 912, 909, 906, 58 | 903, 900, 897, 894, 891, 888, 885, 882, 59 | 878, 875, 872, 868, 865, 862, 858, 855, 60 | 851, 848, 844, 841, 837, 834, 830, 826, 61 | 822, 819, 815, 811, 807, 803, 799, 796, 62 | 792, 788, 784, 779, 775, 771, 767, 763, 63 | 759, 755, 750, 746, 742, 737, 733, 729, 64 | 724, 720, 715, 711, 706, 702, 697, 692, 65 | 688, 683, 678, 674, 669, 664, 659, 654, 66 | 650, 645, 640, 635, 630, 625, 620, 615, 67 | 610, 605, 600, 595, 590, 584, 579, 574, 68 | 569, 564, 558, 553, 548, 543, 537, 532, 69 | 526, 521, 516, 510, 505, 499, 494, 488, 70 | 483, 477, 472, 466, 460, 455, 449, 443, 71 | 438, 432, 426, 421, 415, 409, 403, 398, 72 | 392, 386, 380, 374, 369, 363, 357, 351, 73 | 345, 339, 333, 327, 321, 315, 309, 303, 74 | 297, 291, 285, 279, 273, 267, 261, 255, 75 | 249, 243, 237, 230, 224, 218, 212, 206, 76 | 200, 194, 187, 181, 175, 169, 163, 156, 77 | 150, 144, 138, 132, 125, 119, 113, 107, 78 | 100, 94, 88, 82, 75, 69, 63, 57, 79 | 50, 44, 38, 31, 25, 19, 13, 6, 80 | 0, -6, -13, -19, -25, -31, -38, -44, 81 | -50, -57, -63, -69, -75, -82, -88, -94, 82 | -100, -107, -113, -119, -125, -132, -138, -144, 83 | -150, -156, -163, -169, -175, -181, -187, -194, 84 | -200, -206, -212, -218, -224, -230, -237, -243, 85 | -249, -255, -261, -267, -273, -279, -285, -291, 86 | -297, -303, -309, -315, -321, -327, -333, -339, 87 | -345, -351, -357, -363, -369, -374, -380, -386, 88 | -392, -398, -403, -409, -415, -421, -426, -432, 89 | -438, -443, -449, -455, -460, -466, -472, -477, 90 | -483, -488, -494, -499, -505, -510, -516, -521, 91 | -526, -532, -537, -543, -548, -553, -558, -564, 92 | -569, -574, -579, -584, -590, -595, -600, -605, 93 | -610, -615, -620, -625, -630, -635, -640, -645, 94 | -650, -654, -659, -664, -669, -674, -678, -683, 95 | -688, -692, -697, -702, -706, -711, -715, -720, 96 | -724, -729, -733, -737, -742, -746, -750, -755, 97 | -759, -763, -767, -771, -775, -779, -784, -788, 98 | -792, -796, -799, -803, -807, -811, -815, -819, 99 | -822, -826, -830, -834, -837, -841, -844, -848, 100 | -851, -855, -858, -862, -865, -868, -872, -875, 101 | -878, -882, -885, -888, -891, -894, -897, -900, 102 | -903, -906, -909, -912, -915, -917, -920, -923, 103 | -926, -928, -931, -934, -936, -939, -941, -944, 104 | -946, -948, -951, -953, -955, -958, -960, -962, 105 | -964, -966, -968, -970, -972, -974, -976, -978, 106 | -980, -982, -983, -985, -987, -989, -990, -992, 107 | -993, -995, -996, -998, -999, -1000, -1002, -1003, 108 | -1004, -1006, -1007, -1008, -1009, -1010, -1011, -1012, 109 | -1013, -1014, -1015, -1016, -1016, -1017, -1018, -1018, 110 | -1019, -1020, -1020, -1021, -1021, -1022, -1022, -1022, 111 | -1023, -1023, -1023, -1024, -1024, -1024, -1024, -1024, 112 | -1024, -1024, -1024, -1024, -1024, -1024, -1023, -1023, 113 | -1023, -1022, -1022, -1022, -1021, -1021, -1020, -1020, 114 | -1019, -1018, -1018, -1017, -1016, -1016, -1015, -1014, 115 | -1013, -1012, -1011, -1010, -1009, -1008, -1007, -1006, 116 | -1004, -1003, -1002, -1000, -999, -998, -996, -995, 117 | -993, -992, -990, -989, -987, -985, -983, -982, 118 | -980, -978, -976, -974, -972, -970, -968, -966, 119 | -964, -962, -960, -958, -955, -953, -951, -948, 120 | -946, -944, -941, -939, -936, -934, -931, -928, 121 | -926, -923, -920, -917, -915, -912, -909, -906, 122 | -903, -900, -897, -894, -891, -888, -885, -882, 123 | -878, -875, -872, -868, -865, -862, -858, -855, 124 | -851, -848, -844, -841, -837, -834, -830, -826, 125 | -822, -819, -815, -811, -807, -803, -799, -796, 126 | -792, -788, -784, -779, -775, -771, -767, -763, 127 | -759, -755, -750, -746, -742, -737, -733, -729, 128 | -724, -720, -715, -711, -706, -702, -697, -692, 129 | -688, -683, -678, -674, -669, -664, -659, -654, 130 | -650, -645, -640, -635, -630, -625, -620, -615, 131 | -610, -605, -600, -595, -590, -584, -579, -574, 132 | -569, -564, -558, -553, -548, -543, -537, -532, 133 | -526, -521, -516, -510, -505, -499, -494, -488, 134 | -483, -477, -472, -466, -460, -455, -449, -443, 135 | -438, -432, -426, -421, -415, -409, -403, -398, 136 | -392, -386, -380, -374, -369, -363, -357, -351, 137 | -345, -339, -333, -327, -321, -315, -309, -303, 138 | -297, -291, -285, -279, -273, -267, -261, -255, 139 | -249, -243, -237, -230, -224, -218, -212, -206, 140 | -200, -194, -187, -181, -175, -169, -163, -156, 141 | -150, -144, -138, -132, -125, -119, -113, -107, 142 | -100, -94, -88, -82, -75, -69, -63, -57, 143 | -50, -44, -38, -31, -25, -19, -13, -6 144 | }; 145 | 146 | int16_t cosineTable[] = { 147 | 1024, 1024, 1024, 1024, 1024, 1024, 1023, 1023, 148 | 1023, 1022, 1022, 1022, 1021, 1021, 1020, 1020, 149 | 1019, 1018, 1018, 1017, 1016, 1016, 1015, 1014, 150 | 1013, 1012, 1011, 1010, 1009, 1008, 1007, 1006, 151 | 1004, 1003, 1002, 1000, 999, 998, 996, 995, 152 | 993, 992, 990, 989, 987, 985, 983, 982, 153 | 980, 978, 976, 974, 972, 970, 968, 966, 154 | 964, 962, 960, 958, 955, 953, 951, 948, 155 | 946, 944, 941, 939, 936, 934, 931, 928, 156 | 926, 923, 920, 917, 915, 912, 909, 906, 157 | 903, 900, 897, 894, 891, 888, 885, 882, 158 | 878, 875, 872, 868, 865, 862, 858, 855, 159 | 851, 848, 844, 841, 837, 834, 830, 826, 160 | 822, 819, 815, 811, 807, 803, 799, 796, 161 | 792, 788, 784, 779, 775, 771, 767, 763, 162 | 759, 755, 750, 746, 742, 737, 733, 729, 163 | 724, 720, 715, 711, 706, 702, 697, 692, 164 | 688, 683, 678, 674, 669, 664, 659, 654, 165 | 650, 645, 640, 635, 630, 625, 620, 615, 166 | 610, 605, 600, 595, 590, 584, 579, 574, 167 | 569, 564, 558, 553, 548, 543, 537, 532, 168 | 526, 521, 516, 510, 505, 499, 494, 488, 169 | 483, 477, 472, 466, 460, 455, 449, 443, 170 | 438, 432, 426, 421, 415, 409, 403, 398, 171 | 392, 386, 380, 374, 369, 363, 357, 351, 172 | 345, 339, 333, 327, 321, 315, 309, 303, 173 | 297, 291, 285, 279, 273, 267, 261, 255, 174 | 249, 243, 237, 230, 224, 218, 212, 206, 175 | 200, 194, 187, 181, 175, 169, 163, 156, 176 | 150, 144, 138, 132, 125, 119, 113, 107, 177 | 100, 94, 88, 82, 75, 69, 63, 57, 178 | 50, 44, 38, 31, 25, 19, 13, 6, 179 | 0, -6, -13, -19, -25, -31, -38, -44, 180 | -50, -57, -63, -69, -75, -82, -88, -94, 181 | -100, -107, -113, -119, -125, -132, -138, -144, 182 | -150, -156, -163, -169, -175, -181, -187, -194, 183 | -200, -206, -212, -218, -224, -230, -237, -243, 184 | -249, -255, -261, -267, -273, -279, -285, -291, 185 | -297, -303, -309, -315, -321, -327, -333, -339, 186 | -345, -351, -357, -363, -369, -374, -380, -386, 187 | -392, -398, -403, -409, -415, -421, -426, -432, 188 | -438, -443, -449, -455, -460, -466, -472, -477, 189 | -483, -488, -494, -499, -505, -510, -516, -521, 190 | -526, -532, -537, -543, -548, -553, -558, -564, 191 | -569, -574, -579, -584, -590, -595, -600, -605, 192 | -610, -615, -620, -625, -630, -635, -640, -645, 193 | -650, -654, -659, -664, -669, -674, -678, -683, 194 | -688, -692, -697, -702, -706, -711, -715, -720, 195 | -724, -729, -733, -737, -742, -746, -750, -755, 196 | -759, -763, -767, -771, -775, -779, -784, -788, 197 | -792, -796, -799, -803, -807, -811, -815, -819, 198 | -822, -826, -830, -834, -837, -841, -844, -848, 199 | -851, -855, -858, -862, -865, -868, -872, -875, 200 | -878, -882, -885, -888, -891, -894, -897, -900, 201 | -903, -906, -909, -912, -915, -917, -920, -923, 202 | -926, -928, -931, -934, -936, -939, -941, -944, 203 | -946, -948, -951, -953, -955, -958, -960, -962, 204 | -964, -966, -968, -970, -972, -974, -976, -978, 205 | -980, -982, -983, -985, -987, -989, -990, -992, 206 | -993, -995, -996, -998, -999, -1000, -1002, -1003, 207 | -1004, -1006, -1007, -1008, -1009, -1010, -1011, -1012, 208 | -1013, -1014, -1015, -1016, -1016, -1017, -1018, -1018, 209 | -1019, -1020, -1020, -1021, -1021, -1022, -1022, -1022, 210 | -1023, -1023, -1023, -1024, -1024, -1024, -1024, -1024, 211 | -1024, -1024, -1024, -1024, -1024, -1024, -1023, -1023, 212 | -1023, -1022, -1022, -1022, -1021, -1021, -1020, -1020, 213 | -1019, -1018, -1018, -1017, -1016, -1016, -1015, -1014, 214 | -1013, -1012, -1011, -1010, -1009, -1008, -1007, -1006, 215 | -1004, -1003, -1002, -1000, -999, -998, -996, -995, 216 | -993, -992, -990, -989, -987, -985, -983, -982, 217 | -980, -978, -976, -974, -972, -970, -968, -966, 218 | -964, -962, -960, -958, -955, -953, -951, -948, 219 | -946, -944, -941, -939, -936, -934, -931, -928, 220 | -926, -923, -920, -917, -915, -912, -909, -906, 221 | -903, -900, -897, -894, -891, -888, -885, -882, 222 | -878, -875, -872, -868, -865, -862, -858, -855, 223 | -851, -848, -844, -841, -837, -834, -830, -826, 224 | -822, -819, -815, -811, -807, -803, -799, -796, 225 | -792, -788, -784, -779, -775, -771, -767, -763, 226 | -759, -755, -750, -746, -742, -737, -733, -729, 227 | -724, -720, -715, -711, -706, -702, -697, -692, 228 | -688, -683, -678, -674, -669, -664, -659, -654, 229 | -650, -645, -640, -635, -630, -625, -620, -615, 230 | -610, -605, -600, -595, -590, -584, -579, -574, 231 | -569, -564, -558, -553, -548, -543, -537, -532, 232 | -526, -521, -516, -510, -505, -499, -494, -488, 233 | -483, -477, -472, -466, -460, -455, -449, -443, 234 | -438, -432, -426, -421, -415, -409, -403, -398, 235 | -392, -386, -380, -374, -369, -363, -357, -351, 236 | -345, -339, -333, -327, -321, -315, -309, -303, 237 | -297, -291, -285, -279, -273, -267, -261, -255, 238 | -249, -243, -237, -230, -224, -218, -212, -206, 239 | -200, -194, -187, -181, -175, -169, -163, -156, 240 | -150, -144, -138, -132, -125, -119, -113, -107, 241 | -100, -94, -88, -82, -75, -69, -63, -57, 242 | -50, -44, -38, -31, -25, -19, -13, -6, 243 | 0, 6, 13, 19, 25, 31, 38, 44, 244 | 50, 57, 63, 69, 75, 82, 88, 94, 245 | 100, 107, 113, 119, 125, 132, 138, 144, 246 | 150, 156, 163, 169, 175, 181, 187, 194, 247 | 200, 206, 212, 218, 224, 230, 237, 243, 248 | 249, 255, 261, 267, 273, 279, 285, 291, 249 | 297, 303, 309, 315, 321, 327, 333, 339, 250 | 345, 351, 357, 363, 369, 374, 380, 386, 251 | 392, 398, 403, 409, 415, 421, 426, 432, 252 | 438, 443, 449, 455, 460, 466, 472, 477, 253 | 483, 488, 494, 499, 505, 510, 516, 521, 254 | 526, 532, 537, 543, 548, 553, 558, 564, 255 | 569, 574, 579, 584, 590, 595, 600, 605, 256 | 610, 615, 620, 625, 630, 635, 640, 645, 257 | 650, 654, 659, 664, 669, 674, 678, 683, 258 | 688, 692, 697, 702, 706, 711, 715, 720, 259 | 724, 729, 733, 737, 742, 746, 750, 755, 260 | 759, 763, 767, 771, 775, 779, 784, 788, 261 | 792, 796, 799, 803, 807, 811, 815, 819, 262 | 822, 826, 830, 834, 837, 841, 844, 848, 263 | 851, 855, 858, 862, 865, 868, 872, 875, 264 | 878, 882, 885, 888, 891, 894, 897, 900, 265 | 903, 906, 909, 912, 915, 917, 920, 923, 266 | 926, 928, 931, 934, 936, 939, 941, 944, 267 | 946, 948, 951, 953, 955, 958, 960, 962, 268 | 964, 966, 968, 970, 972, 974, 976, 978, 269 | 980, 982, 983, 985, 987, 989, 990, 992, 270 | 993, 995, 996, 998, 999, 1000, 1002, 1003, 271 | 1004, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 272 | 1013, 1014, 1015, 1016, 1016, 1017, 1018, 1018, 273 | 1019, 1020, 1020, 1021, 1021, 1022, 1022, 1022, 274 | 1023, 1023, 1023, 1024, 1024, 1024, 1024, 1024 275 | }; 276 | 277 | uint16_t arcCosineTable[] = { 278 | 3217, 3153, 3126, 3106, 3089, 3074, 3060, 3047, 279 | 3036, 3025, 3014, 3004, 2995, 2986, 2977, 2969, 280 | 2960, 2952, 2945, 2937, 2930, 2923, 2916, 2909, 281 | 2902, 2896, 2889, 2883, 2877, 2871, 2865, 2859, 282 | 2853, 2847, 2842, 2836, 2831, 2825, 2820, 2815, 283 | 2810, 2804, 2799, 2794, 2789, 2784, 2780, 2775, 284 | 2770, 2765, 2761, 2756, 2751, 2747, 2742, 2738, 285 | 2734, 2729, 2725, 2721, 2716, 2712, 2708, 2704, 286 | 2700, 2695, 2691, 2687, 2683, 2679, 2675, 2671, 287 | 2667, 2663, 2660, 2656, 2652, 2648, 2644, 2641, 288 | 2637, 2633, 2629, 2626, 2622, 2618, 2615, 2611, 289 | 2608, 2604, 2601, 2597, 2594, 2590, 2587, 2583, 290 | 2580, 2576, 2573, 2569, 2566, 2563, 2559, 2556, 291 | 2553, 2549, 2546, 2543, 2540, 2536, 2533, 2530, 292 | 2527, 2523, 2520, 2517, 2514, 2511, 2508, 2505, 293 | 2501, 2498, 2495, 2492, 2489, 2486, 2483, 2480, 294 | 2477, 2474, 2471, 2468, 2465, 2462, 2459, 2456, 295 | 2453, 2450, 2447, 2444, 2441, 2438, 2436, 2433, 296 | 2430, 2427, 2424, 2421, 2418, 2415, 2413, 2410, 297 | 2407, 2404, 2401, 2399, 2396, 2393, 2390, 2387, 298 | 2385, 2382, 2379, 2376, 2374, 2371, 2368, 2366, 299 | 2363, 2360, 2358, 2355, 2352, 2349, 2347, 2344, 300 | 2342, 2339, 2336, 2334, 2331, 2328, 2326, 2323, 301 | 2320, 2318, 2315, 2313, 2310, 2308, 2305, 2302, 302 | 2300, 2297, 2295, 2292, 2290, 2287, 2285, 2282, 303 | 2279, 2277, 2274, 2272, 2269, 2267, 2264, 2262, 304 | 2259, 2257, 2255, 2252, 2250, 2247, 2245, 2242, 305 | 2240, 2237, 2235, 2232, 2230, 2228, 2225, 2223, 306 | 2220, 2218, 2215, 2213, 2211, 2208, 2206, 2203, 307 | 2201, 2199, 2196, 2194, 2191, 2189, 2187, 2184, 308 | 2182, 2180, 2177, 2175, 2173, 2170, 2168, 2166, 309 | 2163, 2161, 2159, 2156, 2154, 2152, 2149, 2147, 310 | 2145, 2142, 2140, 2138, 2135, 2133, 2131, 2129, 311 | 2126, 2124, 2122, 2119, 2117, 2115, 2113, 2110, 312 | 2108, 2106, 2104, 2101, 2099, 2097, 2095, 2092, 313 | 2090, 2088, 2086, 2083, 2081, 2079, 2077, 2074, 314 | 2072, 2070, 2068, 2066, 2063, 2061, 2059, 2057, 315 | 2054, 2052, 2050, 2048, 2046, 2043, 2041, 2039, 316 | 2037, 2035, 2033, 2030, 2028, 2026, 2024, 2022, 317 | 2019, 2017, 2015, 2013, 2011, 2009, 2006, 2004, 318 | 2002, 2000, 1998, 1996, 1994, 1991, 1989, 1987, 319 | 1985, 1983, 1981, 1978, 1976, 1974, 1972, 1970, 320 | 1968, 1966, 1964, 1961, 1959, 1957, 1955, 1953, 321 | 1951, 1949, 1947, 1944, 1942, 1940, 1938, 1936, 322 | 1934, 1932, 1930, 1928, 1926, 1923, 1921, 1919, 323 | 1917, 1915, 1913, 1911, 1909, 1907, 1905, 1903, 324 | 1900, 1898, 1896, 1894, 1892, 1890, 1888, 1886, 325 | 1884, 1882, 1880, 1878, 1876, 1873, 1871, 1869, 326 | 1867, 1865, 1863, 1861, 1859, 1857, 1855, 1853, 327 | 1851, 1849, 1847, 1845, 1843, 1840, 1838, 1836, 328 | 1834, 1832, 1830, 1828, 1826, 1824, 1822, 1820, 329 | 1818, 1816, 1814, 1812, 1810, 1808, 1806, 1804, 330 | 1802, 1800, 1798, 1796, 1794, 1791, 1789, 1787, 331 | 1785, 1783, 1781, 1779, 1777, 1775, 1773, 1771, 332 | 1769, 1767, 1765, 1763, 1761, 1759, 1757, 1755, 333 | 1753, 1751, 1749, 1747, 1745, 1743, 1741, 1739, 334 | 1737, 1735, 1733, 1731, 1729, 1727, 1725, 1723, 335 | 1721, 1719, 1717, 1715, 1713, 1711, 1709, 1707, 336 | 1705, 1703, 1701, 1699, 1697, 1695, 1693, 1691, 337 | 1689, 1687, 1685, 1683, 1681, 1679, 1677, 1675, 338 | 1673, 1671, 1669, 1667, 1665, 1663, 1661, 1659, 339 | 1657, 1655, 1653, 1651, 1649, 1647, 1645, 1643, 340 | 1641, 1638, 1636, 1634, 1632, 1630, 1628, 1626, 341 | 1624, 1622, 1620, 1618, 1616, 1614, 1612, 1610, 342 | 1608, 1606, 1604, 1602, 1600, 1598, 1596, 1594, 343 | 1592, 1590, 1588, 1586, 1584, 1582, 1580, 1578, 344 | 1576, 1574, 1572, 1570, 1568, 1566, 1564, 1562, 345 | 1560, 1558, 1556, 1554, 1552, 1550, 1548, 1546, 346 | 1544, 1542, 1540, 1538, 1536, 1534, 1532, 1530, 347 | 1528, 1526, 1524, 1522, 1520, 1518, 1516, 1514, 348 | 1512, 1510, 1508, 1506, 1504, 1502, 1500, 1498, 349 | 1496, 1494, 1492, 1490, 1488, 1486, 1484, 1482, 350 | 1480, 1478, 1476, 1474, 1472, 1470, 1468, 1466, 351 | 1464, 1462, 1460, 1458, 1456, 1454, 1452, 1450, 352 | 1448, 1446, 1444, 1442, 1440, 1438, 1436, 1434, 353 | 1432, 1430, 1428, 1426, 1423, 1421, 1419, 1417, 354 | 1415, 1413, 1411, 1409, 1407, 1405, 1403, 1401, 355 | 1399, 1397, 1395, 1393, 1391, 1389, 1387, 1385, 356 | 1383, 1381, 1379, 1377, 1374, 1372, 1370, 1368, 357 | 1366, 1364, 1362, 1360, 1358, 1356, 1354, 1352, 358 | 1350, 1348, 1346, 1344, 1341, 1339, 1337, 1335, 359 | 1333, 1331, 1329, 1327, 1325, 1323, 1321, 1319, 360 | 1317, 1314, 1312, 1310, 1308, 1306, 1304, 1302, 361 | 1300, 1298, 1296, 1294, 1291, 1289, 1287, 1285, 362 | 1283, 1281, 1279, 1277, 1275, 1272, 1270, 1268, 363 | 1266, 1264, 1262, 1260, 1258, 1256, 1253, 1251, 364 | 1249, 1247, 1245, 1243, 1241, 1238, 1236, 1234, 365 | 1232, 1230, 1228, 1226, 1223, 1221, 1219, 1217, 366 | 1215, 1213, 1211, 1208, 1206, 1204, 1202, 1200, 367 | 1198, 1195, 1193, 1191, 1189, 1187, 1184, 1182, 368 | 1180, 1178, 1176, 1174, 1171, 1169, 1167, 1165, 369 | 1163, 1160, 1158, 1156, 1154, 1151, 1149, 1147, 370 | 1145, 1143, 1140, 1138, 1136, 1134, 1131, 1129, 371 | 1127, 1125, 1122, 1120, 1118, 1116, 1113, 1111, 372 | 1109, 1107, 1104, 1102, 1100, 1098, 1095, 1093, 373 | 1091, 1088, 1086, 1084, 1082, 1079, 1077, 1075, 374 | 1072, 1070, 1068, 1065, 1063, 1061, 1058, 1056, 375 | 1054, 1051, 1049, 1047, 1044, 1042, 1040, 1037, 376 | 1035, 1033, 1030, 1028, 1026, 1023, 1021, 1018, 377 | 1016, 1014, 1011, 1009, 1006, 1004, 1002, 999, 378 | 997, 994, 992, 989, 987, 985, 982, 980, 379 | 977, 975, 972, 970, 967, 965, 962, 960, 380 | 958, 955, 953, 950, 948, 945, 943, 940, 381 | 937, 935, 932, 930, 927, 925, 922, 920, 382 | 917, 915, 912, 909, 907, 904, 902, 899, 383 | 896, 894, 891, 889, 886, 883, 881, 878, 384 | 875, 873, 870, 867, 865, 862, 859, 857, 385 | 854, 851, 849, 846, 843, 840, 838, 835, 386 | 832, 830, 827, 824, 821, 818, 816, 813, 387 | 810, 807, 804, 802, 799, 796, 793, 790, 388 | 787, 784, 781, 779, 776, 773, 770, 767, 389 | 764, 761, 758, 755, 752, 749, 746, 743, 390 | 740, 737, 734, 731, 728, 725, 722, 719, 391 | 716, 712, 709, 706, 703, 700, 697, 694, 392 | 690, 687, 684, 681, 677, 674, 671, 668, 393 | 664, 661, 658, 654, 651, 648, 644, 641, 394 | 637, 634, 630, 627, 623, 620, 616, 613, 395 | 609, 606, 602, 599, 595, 591, 588, 584, 396 | 580, 576, 573, 569, 565, 561, 557, 554, 397 | 550, 546, 542, 538, 534, 530, 526, 522, 398 | 517, 513, 509, 505, 501, 496, 492, 488, 399 | 483, 479, 475, 470, 466, 461, 456, 452, 400 | 447, 442, 437, 433, 428, 423, 418, 413, 401 | 407, 402, 397, 392, 386, 381, 375, 370, 402 | 364, 358, 352, 346, 340, 334, 328, 321, 403 | 315, 308, 301, 294, 287, 280, 272, 265, 404 | 257, 248, 240, 231, 222, 213, 203, 192, 405 | 181, 170, 157, 143, 128, 111, 91, 64, 406 | 0 407 | }; 408 | 409 | Num normalizeAngle(Num angle) { 410 | return angle - twoPi * Util::floor(angle*inverseTwoPi); 411 | } 412 | 413 | Num sin(Num n) { 414 | const Num radians = normalizeAngle(n); 415 | const Num ratio = radians*inverseTwoPi; 416 | return Num::createByRaw(sineTable[ratio.raw]); 417 | } 418 | 419 | Num cos(Num n) { 420 | const Num radians = normalizeAngle(n); 421 | const Num ratio = radians*inverseTwoPi; 422 | return Num::createByRaw(cosineTable[ratio.raw]); 423 | } 424 | 425 | Num acos(Num n) { 426 | assert(n >= Num(-1) && n <= Num(1)); 427 | uint16_t index = Util::halve(n+Num(1)).raw; 428 | return Num::createByRaw(arcCosineTable[index]); 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /Trig.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_TRIG_H 2 | #define FIXIE_TRIG_H 3 | 4 | namespace Fixie { 5 | namespace Trig { 6 | extern const Num pi; 7 | extern const Num twoPi; 8 | extern const Num halfPi; 9 | extern const Num inverseTwoPi; 10 | 11 | Num sin(Num n); 12 | Num cos(Num n); 13 | Num acos(Num n); 14 | } 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /Util.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Util.h" 3 | 4 | namespace Fixie { 5 | namespace Util { 6 | Num halve(Num n) { 7 | return Num::createByRaw(n.raw >> 1); 8 | } 9 | 10 | Num floor(Num n) { 11 | n.raw &= 0xfffffc00; 12 | return n; 13 | } 14 | 15 | Num sqrt(Num n) { 16 | assert(n >= Num(0)); 17 | Num s = halve(n); 18 | for(uint8_t i=0; i<6; ++i) { 19 | s = halve(s + n/s); 20 | } 21 | return s; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Util.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_UTIL_H 2 | #define FIXIE_UTIL_H 3 | 4 | #include "Num.h" 5 | 6 | namespace Fixie { 7 | namespace Util { 8 | Num halve(Num n); 9 | Num floor(Num n); 10 | Num sqrt(Num n); 11 | } 12 | } 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /VADrum.cpp: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /VAEngine.cpp: -------------------------------------------------------------------------------- 1 | #include "SynthVoice.h" 2 | #define NUM_VOICES 2 3 | class VAEngine 4 | { 5 | public: 6 | VAEngine() 7 | { 8 | 9 | } 10 | ~VAEngine() 11 | { 12 | 13 | } 14 | Num Process() 15 | { 16 | s = 0; 17 | for (int i = 0; i < NUM_VOICES; i++) 18 | { 19 | s = s + (int32_t)(mSynthVoice[i].Process() + Num(127)); 20 | } 21 | } 22 | void handleNoteOn(uint8_t channel, uint8_t note, uint8_t velocity) 23 | { 24 | bool found = false; 25 | int maxnote = -1; 26 | int maxnoteidx = -1; 27 | for (int i = 0; i < NUM_VOICES; i++) 28 | { 29 | if (voices_notes[i] == -1) 30 | { 31 | voices_notes[i] = note; 32 | mSynthVoice[i].MidiNoteOn(note, velocity); 33 | found = true; 34 | return; 35 | } 36 | if (voices_notes[i] > maxnote) 37 | { 38 | maxnote = voices_notes[i]; 39 | maxnoteidx = i; 40 | } 41 | } 42 | voices_notes[maxnoteidx] = note; 43 | mSynthVoice[maxnoteidx].MidiNoteOn(note, velocity); 44 | } 45 | 46 | void handleNoteOff(uint8_t channel, uint8_t note, uint8_t velocity) 47 | { 48 | //digitalWrite(LED, LOW); 49 | for (int i = 0; i < NUM_VOICES; i++) 50 | { 51 | if (voices_notes[i] == note) 52 | { 53 | voices_notes[i] = -1; 54 | mSynthVoice[i].MidiNoteOff(); 55 | //break; 56 | } 57 | } 58 | } 59 | private: 60 | SynthVoice mSynthVoice[NUM_VOICES]; 61 | int voices_notes[NUM_VOICES]; 62 | volatile int64_t s = 0; 63 | 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /Vector3.cpp: -------------------------------------------------------------------------------- 1 | #include "Util.h" 2 | #include "Vector3.h" 3 | 4 | namespace Fixie { 5 | Vector3::Vector3() { } 6 | 7 | Vector3::Vector3(Num x, Num y, Num z) { 8 | components[0] = x; 9 | components[1] = y; 10 | components[2] = z; 11 | } 12 | 13 | Num& Vector3::operator[](const int index) { 14 | return components[index]; 15 | } 16 | 17 | const Num& Vector3::operator[](const int index) const { 18 | return components[index]; 19 | } 20 | 21 | Vector3 Vector3::operator+(Vector3 vector) { 22 | Vector3 result = *this; 23 | result += vector; 24 | return result; 25 | } 26 | 27 | Vector3& Vector3::operator+=(Vector3 other) { 28 | components[0] += other[0]; 29 | components[1] += other[1]; 30 | components[2] += other[2]; 31 | return *this; 32 | } 33 | 34 | Vector3 Vector3::operator-(Vector3 vector) { 35 | Vector3 result = *this; 36 | result -= vector; 37 | return result; 38 | } 39 | 40 | Vector3& Vector3::operator-=(Vector3 other) { 41 | components[0] -= other[0]; 42 | components[1] -= other[1]; 43 | components[2] -= other[2]; 44 | return *this; 45 | } 46 | 47 | Vector3 Vector3::operator*(Num divisor) { 48 | Vector3 result = *this; 49 | result *= divisor; 50 | return result; 51 | } 52 | 53 | Vector3& Vector3::operator*=(Num divisor) { 54 | components[0] *= divisor; 55 | components[1] *= divisor; 56 | components[2] *= divisor; 57 | return *this; 58 | } 59 | 60 | Vector3 Vector3::operator/(Num divisor) { 61 | Vector3 result = *this; 62 | result /= divisor; 63 | return result; 64 | } 65 | 66 | Vector3& Vector3::operator/=(Num divisor) { 67 | components[0] /= divisor; 68 | components[1] /= divisor; 69 | components[2] /= divisor; 70 | return *this; 71 | } 72 | 73 | Num Vector3::dot(Vector3 a, Vector3 b) { 74 | return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; 75 | } 76 | 77 | Vector3 Vector3::cross(Vector3 a, Vector3 b) { 78 | return Vector3( 79 | a[1]*b[2] - a[2]*b[1], 80 | a[2]*b[0] - a[0]*b[2], 81 | a[0]*b[1] - a[1]*b[0] 82 | ); 83 | } 84 | 85 | void Vector3::reset() { 86 | components[0] = 0; 87 | components[1] = 0; 88 | components[2] = 0; 89 | } 90 | 91 | Vector3 Vector3::normalize(Vector3 v) { 92 | return v/v.calcLength(); 93 | } 94 | 95 | Num Vector3::calcLength() const { 96 | return Util::sqrt(calcSquaredLength()); 97 | } 98 | 99 | Num Vector3::calcSquaredLength() const { 100 | return ( 101 | components[0] * components[0] + 102 | components[1] * components[1] + 103 | components[2] * components[2] 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Vector3.h: -------------------------------------------------------------------------------- 1 | #ifndef FIXIE_VECTOR3_H 2 | #define FIXIE_VECTOR3_H 3 | 4 | #include "Num.h" 5 | 6 | namespace Fixie { 7 | class Vector3 { 8 | public: 9 | Vector3(Num x, Num y, Num z); 10 | Vector3(); 11 | Num& operator[](const int index); 12 | const Num& operator[](const int index) const; 13 | Vector3 operator+(Vector3 other); 14 | Vector3& operator+=(Vector3 other); 15 | Vector3 operator-(Vector3 other); 16 | Vector3& operator-=(Vector3 other); 17 | Vector3 operator*(Num divisor); 18 | Vector3& operator*=(Num divisor); 19 | Vector3 operator/(Num divisor); 20 | Vector3& operator/=(Num divisor); 21 | Num calcLength() const; 22 | Num calcSquaredLength() const; 23 | void reset(); 24 | static Num dot(Vector3 a, Vector3 b); 25 | static Vector3 cross(Vector3 a, Vector3 b); 26 | static Vector3 normalize(Vector3 v); 27 | private: 28 | Num components[3]; 29 | }; 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /Waveforms.h: -------------------------------------------------------------------------------- 1 | #ifndef Waveforms_h 2 | #define Waveforms_h 3 | #define WAVEFORM_COUNT 256 4 | int8_t Waveforms[WAVEFORM_COUNT][WTLEN]; 5 | void initWaveForms() 6 | { 7 | for(int i=0;i 2 | #include "WiFi.h" 3 | #include 4 | 5 | //#include 6 | #include "LowPass.h" 7 | #ifdef U8X8_HAVE_HW_SPI 8 | #include 9 | #endif 10 | #ifdef U8X8_HAVE_HW_I2C 11 | #include 12 | #endif 13 | 14 | #include "FileIO.h" 15 | #include 16 | #include "SynthVoice.h" 17 | #define ANALOG_IN 36 18 | // specify the board to use for pinout 19 | //#define GENERIC 20 | //#define TTGO16MPROESP32OLED 21 | //#define ESP32TTGO_T8_V1_7 22 | 23 | #define ESP32TTGO_T2 24 | #ifdef ESP32TTGO_T2 25 | #include 26 | #include 27 | #ifndef TFT_DISPOFF 28 | #define TFT_DISPOFF 0x28 29 | #endif 30 | 31 | #ifndef TFT_SLPIN 32 | #define TFT_SLPIN 0x10 33 | #endif 34 | 35 | #define TFT_MOSI 19 36 | #define TFT_SCLK 18 37 | #define TFT_CS 5 38 | #define TFT_DC 16 39 | #define TFT_RST 23 40 | 41 | #define TFT_BL 4 // Display backlight control pin 42 | #define ADC_EN 14 43 | #define ADC_PIN 34 44 | #define LED 21 45 | #define MIDIRX 13 46 | #define MIDITX 12 47 | 48 | #endif 49 | 50 | #ifdef ESP32TTGO_T8_V1_7 51 | #define LED 21 52 | #define MIDIRX 22 53 | #define MIDITX 19 54 | #endif 55 | 56 | //#define IIC_1604_DISPLAY 57 | #ifdef IIC_1604_DISPLAY 58 | #define IIC_SDA_PIN 14 59 | #define IIC_SCL_PIN 27 60 | 61 | #include 62 | 63 | int lcdColumns = 16; 64 | int lcdRows = 2; 65 | 66 | 67 | #endif 68 | //#define ESP32DEVKIT1_DOIT 69 | //#define ESP32DEVKIT1_DOIT 70 | #ifdef ESP32TTGO_T8_V1_1 71 | 72 | #define LED 2 73 | #define MIDIRX 16 74 | #define MIDITX 17 75 | #endif 76 | #ifdef ESP32DEVKIT1_DOIT 77 | #define LED 2 78 | #define MIDIRX 16 79 | #define MIDITX 17 80 | #endif 81 | #ifdef TTGO16MPROESP32OLED 82 | //#define NOMIDI 83 | #define LED 2 84 | #define MIDIRX 36 85 | #define MIDITX 37 86 | #endif 87 | #ifdef GENERIC 88 | #define LED 21 89 | #define MIDIRX 22 90 | #define MIDITX 19 91 | #endif 92 | #define RED_BUTTON 4 93 | 94 | #define YELLOW_BUTTON 2 95 | 96 | #define SAMPLE_RATE 8000 97 | #define NUM_VOICES 4 98 | #define NUM_DRUMS 0 99 | #define WTLEN 256 100 | #define MIDI_COMMAND 128 101 | hw_timer_t * timer = NULL; 102 | volatile long t = 0; 103 | HardwareSerial console(0); 104 | HardwareSerial hSerial(2); 105 | #ifdef TTGO16MPROESP32OLED 106 | 107 | U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, 16,15,4); 108 | #endif 109 | #ifdef ESP32TTGO_T2 110 | TFT_eSPI tft = TFT_eSPI(135, 240); 111 | TFT_eSprite img = TFT_eSprite(&tft); 112 | const int numlines=6; 113 | char line[numlines][17]; 114 | volatile int currline=-1; 115 | 116 | #endif 117 | char buf0[17]= " \0"; 118 | char buf1[17]= " \0"; 119 | int8_t fp_sinWaveTable[WTLEN]; 120 | int8_t fp_sawWaveTable[WTLEN]; 121 | int8_t fp_triWaveTable[WTLEN]; 122 | int8_t fp_squWaveTable[WTLEN]; 123 | int8_t fp_plsWaveTable[WTLEN]; 124 | int8_t fp_rndWaveTable[WTLEN]; 125 | uint8_t dwfbuf_l[128]; 126 | uint8_t dwfbuf_r[128]; 127 | char line1[17]; 128 | char line2[17]; 129 | 130 | uint8_t dwfidx=0; 131 | 132 | double f = 22.5; 133 | int notes[] = {0,3,5,12,15,17,24,27,29}; 134 | int notelen = 9; 135 | int noteidx = 0; 136 | LowPass lowpass; 137 | int voices_notes[NUM_VOICES]; 138 | int drums_notes[NUM_DRUMS]; 139 | enum controller_states{CS_OSC=0, CS_ENV,CS_AMP,CS_FIL}; 140 | int controller_state = CS_OSC; 141 | uint8_t knob_values[4][8]; //first is state, second is knob; 142 | int value_pickup[8] = {0,0,0,0,0,0,0,0}; 143 | uint8_t ffreq = 127; 144 | uint8_t fq = 0; 145 | unsigned long lastUpdateScreenTime; 146 | void initFpSin() 147 | { 148 | for(int i=0;i>16; 239 | //s = nsinosc.Process()>>10; 240 | s = 0; 241 | for(int i=0;i 1 second is 1000000us */ 334 | /* Repeat the alarm (third parameter) */ 335 | //(int32_t)nsinosc.Process()+(128) 336 | timerAlarmWrite(timer, 1000000/SAMPLE_RATE, true); 337 | 338 | /* Start an alarm */ 339 | timerAlarmEnable(timer); 340 | //#ifdef TTGO16MPROESP32OLED 341 | //Serial.println("stalnrt timer"); 342 | xTaskCreatePinnedToCore(displayData,"displayData",20000,NULL,1,&Task1,0); 343 | //#endif 344 | 345 | } 346 | 347 | 348 | 349 | 350 | void printMidiMessage(uint8_t command, uint8_t data1, uint8_t data2) 351 | { 352 | unsigned long now = millis(); 353 | 354 | sprintf(buf0,"MIDI RX:%02X:%02X:%02X\0",command,data1,data2); 355 | if(now-lastUpdateScreenTime<200) 356 | { 357 | return; 358 | } 359 | 360 | 361 | 362 | 363 | console.print("MIDI DATA:"); 364 | console.print(command); 365 | console.print(":"); 366 | console.print(data1); 367 | console.print(":"); 368 | console.println(data2); 369 | console.flush(); 370 | 371 | } 372 | void testChords() 373 | { 374 | if(t_counter%32000==0) 375 | { 376 | for(int i = 0;imaxnote) 459 | { 460 | maxnote = voices_notes[i]; 461 | maxnoteidx = i; 462 | } 463 | } 464 | voices_notes[maxnoteidx]=note; 465 | voices[maxnoteidx].MidiNoteOn(note,velocity); 466 | } 467 | else 468 | { 469 | for(int i=0;imaxnote) 480 | { 481 | maxnote = voices_notes[i]; 482 | maxnoteidx = i; 483 | } 484 | } 485 | drums_notes[maxnoteidx]=note; 486 | drums[maxnoteidx].MidiNoteOn(note,velocity); 487 | } 488 | 489 | } 490 | void handleNoteOff(byte channel, byte note, byte velocity) 491 | { 492 | char buf[17]; 493 | sprintf(buf, "note:%03d vel:%03d",note, velocity); 494 | strcpy(line1,buf); 495 | strcpy(line2,""); 496 | #ifdef ESP32TTGO_T2 497 | writeTFTLine(buf); 498 | #endif 499 | if(channel!=10) 500 | { 501 | digitalWrite(LED,LOW); 502 | for(int i=0;i3 || diff<-3) && value_pickup[rotary] == 1) 628 | { 629 | return; 630 | } 631 | else 632 | { 633 | value_pickup[rotary] = 0; 634 | } 635 | knob_values[state][rotary] = data; 636 | switch(state) 637 | { 638 | 639 | case CS_OSC: 640 | switch(rotary) 641 | { 642 | case 0: 643 | for(int i=0;i>2),i+1,(dwfbuf_l[i+1]>>2)); 779 | u8g2.drawLine(i,(dwfbuf_r[i]>>2),i+1,(dwfbuf_r[i+1]>>2)); 780 | 781 | } 782 | 783 | //u8g2.setFont( u8g2_font_unifont_t_cyrillic); 784 | //u8g2.drawExtUTF8(-8,10,0,NULL,line1); 785 | u8g2.setFont( u8g2_font_unifont_t_greek); 786 | u8g2.drawExtUTF8(-8,10,0,NULL,line1); 787 | u8g2.drawExtUTF8(-8,21,0,NULL,line2); 788 | 789 | u8g2.sendBuffer(); // transfer internal memory to the display 790 | delay(10); 791 | 792 | } 793 | #endif 794 | #ifdef IIC_1604_DISPLAY 795 | LiquidCrystal_I2C lcd(0x27,20,2); 796 | lcd.init(IIC_SDA_PIN,IIC_SCL_PIN); 797 | lcd.backlight(); 798 | //const uint8_t* msg = (uint8_t*)("hello\n"); 799 | //lcd.write(msg); 800 | lcd.setCursor(0,0); 801 | lcd.print("ESP32SYNTH"); 802 | lcd.setCursor(0,1); 803 | lcd.print("BOKONTEP2019"); 804 | delay(2000); 805 | while(true) 806 | { 807 | lcd.setCursor(0,0); 808 | lcd.print(buf0); 809 | lcd.setCursor(0,1); 810 | lcd.print(buf1); 811 | delay(100); 812 | } 813 | #endif 814 | #ifdef ESP32TTGO_T2 815 | 816 | 817 | tft.init(); 818 | 819 | tft.setRotation(3); 820 | tft.fillScreen(TFT_BLUE); 821 | tft.setTextSize(2); 822 | tft.setTextColor(TFT_WHITE); 823 | tft.setCursor(0, 0); 824 | img.setColorDepth(8); 825 | img.createSprite(tft.width(),tft.height()); 826 | img.fillSprite(TFT_BLUE); 827 | img.setTextSize(2); 828 | writeTFTLine("ESP32SYNTH"); 829 | writeTFTLine("(C) BOKONTEP"); 830 | writeTFTLine("2020 "); 831 | //pinMode(TFT_BL, OUTPUT); 832 | while(true) 833 | { 834 | //sprintf(line1,"%s",buf0); 835 | //sprintf(line2,"%s",buf1); 836 | //img.fillScreen(TFT_BLUE); 837 | img.fillSprite(TFT_BLUE); 838 | img.setTextColor(TFT_WHITE); 839 | if(currline>1,(i+1)*1.875,(dwfbuf_l[i+1])>>1,TFT_YELLOW); 861 | //img.drawLine(i*1.8,(dwfbuf_r[i]>>1),i+1,(dwfbuf_r[i+1]>>1),TFT_RED); 862 | 863 | } 864 | img.pushSprite(0,0); 865 | delay(10); 866 | } 867 | 868 | #endif 869 | 870 | } 871 | void loop() 872 | { 873 | //testChords(); 874 | #ifndef NOMIDI 875 | scanMidi(); 876 | #endif 877 | //displayData(); 878 | 879 | 880 | } 881 | void scanMidi() 882 | { 883 | 884 | pinMode(LED,OUTPUT); 885 | if(firsttime) 886 | { 887 | //Serial.write(144); 888 | //Serial.write(59); 889 | //Serial.write(120); 890 | //Serial.flush(); 891 | firsttime = false; 892 | } 893 | switch(mstate) 894 | { 895 | case WAIT_COMMAND: 896 | serialData = hSerial.read(); 897 | if(serialData>-1) 898 | { 899 | commandByte = serialData; 900 | command = (commandByte>>4)&7; 901 | channel = commandByte & 15; 902 | 903 | //Serial.write(144); 904 | //Serial.write(command); 905 | //Serial.write(channel); 906 | //Serial.flush(); 907 | 908 | switch(command) 909 | { 910 | case 0: //NOTE OFF 911 | mstate = WAIT_DATA1; 912 | break; 913 | case 1: //NOTE ON 914 | mstate = WAIT_DATA1; 915 | break; 916 | case 2: //AFTERTOUCH 917 | break; 918 | case 3: //CONTINUOUS CONTROLLER (CC) 919 | mstate = WAIT_DATA1; 920 | break; 921 | case 4: // PATCH CHANGE 922 | break; 923 | case 5: // CHANNEL PRESSURE 924 | break; 925 | case 6: // PITCH BEND 926 | mstate = WAIT_DATA1; 927 | break; 928 | case 7: // NON MUSICAL COMMANDS 929 | break; 930 | } 931 | 932 | } 933 | return; 934 | case WAIT_DATA1: 935 | serialData = hSerial.read(); 936 | if(serialData>-1) 937 | { 938 | data1 = serialData; 939 | switch(command) 940 | { 941 | case 0: 942 | 943 | mstate = WAIT_DATA2; 944 | break; 945 | case 1: 946 | mstate = WAIT_DATA2; 947 | break; 948 | case 3: 949 | mstate = WAIT_DATA2; 950 | break; 951 | case 6: 952 | mstate = WAIT_DATA2; 953 | break; 954 | } 955 | 956 | } 957 | return; 958 | case WAIT_DATA2: 959 | serialData = hSerial.read(); 960 | if(serialData>-1) 961 | { 962 | data2 = serialData; 963 | switch(command) 964 | { 965 | case 0: 966 | printMidiMessage(command,data1,data2); 967 | handleNoteOff(channel,data1,data2); 968 | mstate = WAIT_COMMAND; 969 | break; 970 | case 1: 971 | printMidiMessage(command,data1,data2); 972 | if(data2==0) 973 | { 974 | handleNoteOff(channel,data1,data2); 975 | } 976 | else 977 | { 978 | handleNoteOn(channel,data1,data2); 979 | } 980 | mstate = WAIT_COMMAND; 981 | break; 982 | case 3: 983 | handleCC(channel, data1, data2,&value_pickup[0]); 984 | mstate = WAIT_COMMAND; 985 | break; 986 | case 6: 987 | handlePitchBend(channel,data1,data2); 988 | mstate = WAIT_COMMAND; 989 | break; 990 | } 991 | 992 | } 993 | return; 994 | } 995 | 996 | } 997 | 998 | void loadParameters() 999 | { 1000 | } 1001 | -------------------------------------------------------------------------------- /wavetableosc.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // WaveTableOsc.h 3 | // 4 | // Created by Nigel Redmon on 2018-10-05 5 | // EarLevel Engineering: earlevel.com 6 | // Copyright 2018 Nigel Redmon 7 | // 8 | // For a complete explanation of the wavetable oscillator and code, 9 | // read the series of articles by the author, starting here: 10 | // www.earlevel.com/main/2012/05/03/a-wavetable-oscillator—introduction/ 11 | // 12 | // This version has optimizations described here: 13 | // www.earlevel.com/main/2019/04/28/wavetableosc-optimized/ 14 | // 15 | // License: 16 | // 17 | // This source code is provided as is, without warranty. 18 | // You may copy and distribute verbatim copies of this document. 19 | // You may modify and use this source code to create binary code for your own purposes, free or commercial. 20 | // 21 | 22 | #ifndef WaveTableOsc_h 23 | #define WaveTableOsc_h 24 | 25 | class IRAM_ATTR WaveTableOsc { 26 | public: 27 | WaveTableOsc(void) { 28 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 29 | mWaveTables[idx].topFreq = 0; 30 | mWaveTables[idx].waveTableLen = 0; 31 | mWaveTables[idx].waveTable = 0; 32 | } 33 | } 34 | ~WaveTableOsc(void) { 35 | for (int idx = 0; idx < numWaveTableSlots; idx++) { 36 | double *temp = mWaveTables[idx].waveTable; 37 | if (temp != 0) 38 | delete [] temp; 39 | } 40 | } 41 | 42 | 43 | void SetFrequency(double frequency, double sample_rate) 44 | { 45 | SetFrequency(2.0*frequency/sample_rate); 46 | } 47 | // 48 | // SetFrequency: Set normalized frequency, typically 0-0.5 (must be positive and less than 1!) 49 | // 50 | void SetFrequency(double inc) { 51 | mPhaseInc = inc; 52 | 53 | // update the current wave table selector 54 | int curWaveTable = 0; 55 | while ((mPhaseInc >= mWaveTables[curWaveTable].topFreq) && (curWaveTable < (mNumWaveTables - 1))) { 56 | ++curWaveTable; 57 | } 58 | mCurWaveTable = curWaveTable; 59 | } 60 | 61 | // 62 | // SetPhaseOffset: Phase offset for PWM, 0-1 63 | // 64 | void SetPhaseOffset(double offset) { 65 | mPhaseOfs = offset; 66 | } 67 | 68 | // 69 | // UpdatePhase: Call once per sample 70 | // 71 | void UpdatePhase(void) { 72 | mPhasor += mPhaseInc; 73 | 74 | if (mPhasor >= 1.0) 75 | mPhasor -= 1.0; 76 | } 77 | 78 | // 79 | // Process: Update phase and get output 80 | // 81 | float Process(void) { 82 | UpdatePhase(); 83 | return GetOutput(); 84 | } 85 | 86 | // 87 | // GetOutput: Returns the current oscillator output 88 | // 89 | float GetOutput(void) { 90 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 91 | 92 | // linear interpolation 93 | double temp = mPhasor * waveTable->waveTableLen; 94 | int intPart = temp; 95 | double fracPart = temp - intPart; 96 | double samp0 = waveTable->waveTable[intPart]; 97 | double samp1 = waveTable->waveTable[intPart + 1]; 98 | return samp0 + (samp1 - samp0) * fracPart; 99 | } 100 | 101 | // 102 | // getOutputMinusOffset 103 | // 104 | // for variable pulse width: initialize to sawtooth, 105 | // set phaseOfs to duty cycle, use this for osc output 106 | // 107 | // returns the current oscillator output 108 | // 109 | float GetOutputMinusOffset() { 110 | waveTable *waveTable = &mWaveTables[mCurWaveTable]; 111 | int len = waveTable->waveTableLen; 112 | double *wave = waveTable->waveTable; 113 | 114 | // linear 115 | double temp = mPhasor * len; 116 | int intPart = temp; 117 | double fracPart = temp - intPart; 118 | double samp0 = wave[intPart]; 119 | double samp1 = wave[intPart+1]; 120 | double samp = samp0 + (samp1 - samp0) * fracPart; 121 | 122 | // and linear again for the offset part 123 | double offsetPhasor = mPhasor + mPhaseOfs; 124 | if (offsetPhasor > 1.0) 125 | offsetPhasor -= 1.0; 126 | temp = offsetPhasor * len; 127 | intPart = temp; 128 | fracPart = temp - intPart; 129 | samp0 = wave[intPart]; 130 | samp1 = wave[intPart+1]; 131 | return samp - (samp0 + (samp1 - samp0) * fracPart); 132 | } 133 | 134 | // 135 | // AddWaveTable 136 | // 137 | // add wavetables in order of lowest frequency to highest 138 | // topFreq is the highest frequency supported by a wavetable 139 | // wavetables within an oscillator can be different lengths 140 | // 141 | // returns 0 upon success, or the number of wavetables if no more room is available 142 | // 143 | int AddWaveTable(int len, double *waveTableIn, double topFreq) { 144 | if (mNumWaveTables < numWaveTableSlots) { 145 | double *waveTable = mWaveTables[mNumWaveTables].waveTable = new double[len + 1]; 146 | mWaveTables[mNumWaveTables].waveTableLen = len; 147 | mWaveTables[mNumWaveTables].topFreq = topFreq; 148 | ++mNumWaveTables; 149 | 150 | // fill in wave 151 | for (long idx = 0; idx < len; idx++) 152 | waveTable[idx] = waveTableIn[idx]; 153 | waveTable[len] = waveTable[0]; // duplicate for interpolation wraparound 154 | 155 | return 0; 156 | } 157 | return mNumWaveTables; 158 | } 159 | 160 | protected: 161 | double mPhasor = 0.0; // phase accumulator 162 | double mPhaseInc = 0.0; // phase increment 163 | double mPhaseOfs = 0.5; // phase offset for PWM 164 | 165 | // array of wavetables 166 | int mCurWaveTable = 0; // current table, based on current frequency 167 | int mNumWaveTables = 0; // number of wavetable slots in use 168 | struct waveTable { 169 | double topFreq; 170 | int waveTableLen; 171 | double *waveTable; 172 | }; 173 | static constexpr int numWaveTableSlots = 1; // simplify allocation with reasonable maximum 174 | waveTable mWaveTables[numWaveTableSlots]; 175 | }; 176 | 177 | #endif 178 | --------------------------------------------------------------------------------