diff --git a/src/common.h b/src/common.h index 902f74b..b882156 100644 --- a/src/common.h +++ b/src/common.h @@ -1,7 +1,8 @@ - #ifndef _common_h #define _common_h +#include + inline long maplong(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } @@ -16,11 +17,11 @@ inline float32_t mapfloat(int val, int in_min, int in_max, float32_t out_min, fl return (val - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } -#define constrain(amt, low, high) ({ \ - __typeof__(amt) _amt = (amt); \ - __typeof__(low) _low = (low); \ - __typeof__(high) _high = (high); \ - (_amt < _low) ? _low : ((_amt > _high) ? _high : _amt); \ -}) +template +// Code that used to use constrain shall use clamp instead +// This was renamed to avoid conflicts with the Synth_Dexed submodule +inline T clamp(T amt, T low, T high) { + return (amt < low) ? low : ((amt > high) ? high : amt); +} #endif diff --git a/src/dexedadapter.h b/src/dexedadapter.h index 573e80e..7b84e48 100644 --- a/src/dexedadapter.h +++ b/src/dexedadapter.h @@ -32,6 +32,8 @@ class CDexedAdapter : public Dexed { +private: + CSpinLock m_SpinLock; public: CDexedAdapter (uint8_t maxnotes, int rate) : Dexed (maxnotes, rate) @@ -80,8 +82,46 @@ public: m_SpinLock.Release (); } -private: - CSpinLock m_SpinLock; -}; + // Unison wrapper: for now, simulate unison by calling keydown/keyup multiple times with detune/pan offsets + void keydown_unison(int16_t pitch, uint8_t velo, unsigned unisonVoices, unsigned unisonDetune, unsigned unisonSpread, int baseDetune, unsigned basePan) { + if (unisonVoices < 1) unisonVoices = 1; + for (unsigned v = 0; v < unisonVoices; ++v) { + float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; + int detune = baseDetune + (int)detuneOffset; + m_SpinLock.Acquire(); + this->setMasterTune((int8_t)detune); + Dexed::keydown(pitch, velo); + m_SpinLock.Release(); + } + } + void keyup_unison(int16_t pitch, unsigned unisonVoices, unsigned unisonDetune, unsigned unisonSpread, int baseDetune, unsigned basePan) { + if (unisonVoices < 1) unisonVoices = 1; + for (unsigned v = 0; v < unisonVoices; ++v) { + float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; + int detune = baseDetune + (int)detuneOffset; + m_SpinLock.Acquire(); + this->setMasterTune((int8_t)detune); + Dexed::keyup(pitch); + m_SpinLock.Release(); + } + } +#ifdef ARM_ALLOW_MULTI_CORE + // Stereo version for unison pan + void getSamplesStereo(float32_t* bufferL, float32_t* bufferR, uint16_t n_samples) { + m_SpinLock.Acquire(); + Dexed::getSamplesStereo(bufferL, bufferR, n_samples); + m_SpinLock.Release(); + } + // Set unison parameters for Dexed, now with base pan + void setUnisonParameters(unsigned voices, unsigned detune, unsigned spread, float basePan) { + m_SpinLock.Acquire(); + // Map detune: 0..99 -> 0..0.02 (2 cents) + float detuneCents = ((float)detune / 99.0f) * 0.02f; + Dexed::setUnisonParameters((uint8_t)voices, detuneCents, (float)spread / 99.0f, basePan); + m_SpinLock.Release(); + } #endif +}; + +#endif // _dexedadapter_h \ No newline at end of file diff --git a/src/effect_platervbstereo.h b/src/effect_platervbstereo.h index 23538c4..4b080d7 100644 --- a/src/effect_platervbstereo.h +++ b/src/effect_platervbstereo.h @@ -64,7 +64,7 @@ public: void size(float n) { - n = constrain(n, 0.0f, 1.0f); + n = clamp(n, 0.0f, 1.0f); n = mapfloat(n, 0.0f, 1.0f, 0.2f, rv_time_k_max); float32_t attn = mapfloat(n, 0.0f, rv_time_k_max, 0.5f, 0.25f); rv_time_k = n; @@ -73,27 +73,27 @@ public: void hidamp(float n) { - n = constrain(n, 0.0f, 1.0f); + n = clamp(n, 0.0f, 1.0f); lp_hidamp_k = 1.0f - n; } void lodamp(float n) { - n = constrain(n, 0.0f, 1.0f); + n = clamp(n, 0.0f, 1.0f); lp_lodamp_k = -n; rv_time_scaler = 1.0f - n * 0.12f; // limit the max reverb time, otherwise it will clip } void lowpass(float n) { - n = constrain(n, 0.0f, 1.0f); + n = clamp(n, 0.0f, 1.0f); n = mapfloat(n*n*n, 0.0f, 1.0f, 0.05f, 1.0f); master_lowpass_f = n; } void diffusion(float n) { - n = constrain(n, 0.0f, 1.0f); + n = clamp(n, 0.0f, 1.0f); n = mapfloat(n, 0.0f, 1.0f, 0.005f, 0.65f); in_allp_k = n; loop_allp_k = n; @@ -101,7 +101,7 @@ public: void level(float n) { - reverb_level = constrain(n, 0.0f, 1.0f); + reverb_level = clamp(n, 0.0f, 1.0f); } float32_t get_size(void) {return rv_time_k;} diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 97f5b5c..31b994d 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -30,9 +30,7 @@ #include #include #include "arm_float_to_q23.h" - -// Forward declaration for getPhysicalTG -static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); +#include "common.h" const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; @@ -846,100 +844,78 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) m_UI.ParameterChanged (); } +#ifndef ARM_ALLOW_MULTI_CORE +// Use original mono logic for single-core +void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) +{ + assert (nTG < CConfig::AllToneGenerators); + if (nTG >= m_nToneGenerators) return; // Not an active TG + assert (m_pTG[nTG]); + pitch = ApplyNoteLimits (pitch, nTG); + if (pitch < 0) return; + m_pTG[nTG]->keydown(pitch, velocity); +} void CMiniDexed::keyup (int16_t pitch, unsigned nTG) { assert (nTG < CConfig::AllToneGenerators); if (nTG >= m_nToneGenerators) return; // Not an active TG - assert (m_pTG[nTG]); - pitch = ApplyNoteLimits (pitch, nTG); if (pitch < 0) return; - - unsigned unisonVoices = m_nUnisonVoices[nTG]; - if (unisonVoices < 1) unisonVoices = 1; - - int baseDetune = m_nMasterTune[nTG]; - unsigned basePan = m_nPan[nTG]; - unsigned unisonDetune = m_nUnisonDetune[nTG]; - unsigned unisonSpread = m_nUnisonSpread[nTG]; - - for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); - if (physicalTG >= m_nToneGenerators) break; - // Ensure virtual TG plays the same voice as logical TG - if (physicalTG != nTG) { - uint8_t voiceData[156]; - m_pTG[nTG]->getVoiceData(voiceData); - m_pTG[physicalTG]->loadVoiceParameters(voiceData); - } - float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; - float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; - int detune = baseDetune + (int)detuneOffset; - unsigned pan = basePan + (int)panOffset; - detune = constrain(detune, -99, 99); - pan = constrain((int)pan, 0, 127); - m_pTG[physicalTG]->setMasterTune((int8_t)detune); - tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); - m_pTG[physicalTG]->keyup(pitch); - } + m_pTG[nTG]->keyup(pitch); } - +#endif +#ifdef ARM_ALLOW_MULTI_CORE +// Use unison/stereo logic for multicore void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) { assert (nTG < CConfig::AllToneGenerators); if (nTG >= m_nToneGenerators) return; // Not an active TG - assert (m_pTG[nTG]); - pitch = ApplyNoteLimits (pitch, nTG); if (pitch < 0) return; - - unsigned unisonVoices = m_nUnisonVoices[nTG]; - if (unisonVoices < 1) unisonVoices = 1; - unsigned maxLogicalTGs = m_nToneGenerators / unisonVoices; - if (nTG >= maxLogicalTGs) return; // Don't exceed available physical TGs - - int baseDetune = m_nMasterTune[nTG]; - unsigned basePan = m_nPan[nTG]; - unsigned unisonDetune = m_nUnisonDetune[nTG]; - unsigned unisonSpread = m_nUnisonSpread[nTG]; - - for (unsigned v = 0; v < unisonVoices; ++v) { - unsigned physicalTG = getPhysicalTG(nTG, v, unisonVoices); - if (physicalTG >= m_nToneGenerators) break; - float detuneOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonDetune; - float panOffset = ((float)v - (unisonVoices - 1) / 2.0f) * (float)unisonSpread; - int detune = baseDetune + (int)detuneOffset; - unsigned pan = basePan + (int)panOffset; - detune = constrain(detune, -99, 99); - pan = constrain((int)pan, 0, 127); - m_pTG[physicalTG]->setMasterTune((int8_t)detune); - tg_mixer->pan(physicalTG, mapfloat(pan, 0, 127, 0.0f, 1.0f)); - m_pTG[physicalTG]->keydown(pitch, velocity); - } + // Set unison parameters for Dexed engine before keydown + m_pTG[nTG]->setUnisonParameters( + m_nUnisonVoices[nTG], + m_nUnisonDetune[nTG], + m_nUnisonSpread[nTG], + mapfloat(m_nPan[nTG], 0, 127, 0.0f, 1.0f) + ); + m_pTG[nTG]->keydown(pitch, velocity); } - -int16_t CMiniDexed::ApplyNoteLimits (int16_t pitch, unsigned nTG) +void CMiniDexed::keyup (int16_t pitch, unsigned nTG) { assert (nTG < CConfig::AllToneGenerators); - if (nTG >= m_nToneGenerators) return -1; // Not an active TG + if (nTG >= m_nToneGenerators) return; // Not an active TG + assert (m_pTG[nTG]); + pitch = ApplyNoteLimits (pitch, nTG); + if (pitch < 0) return; + // Set unison parameters for Dexed engine before keyup + m_pTG[nTG]->setUnisonParameters( + m_nUnisonVoices[nTG], + m_nUnisonDetune[nTG], + m_nUnisonSpread[nTG], + mapfloat(m_nPan[nTG], 0, 127, 0.0f, 1.0f) + ); + m_pTG[nTG]->keyup(pitch); +} +#endif - if ( pitch < (int16_t) m_nNoteLimitLow[nTG] - || pitch > (int16_t) m_nNoteLimitHigh[nTG]) - { - return -1; - } +// Complete the ApplyNoteLimits function definition +int16_t CMiniDexed::ApplyNoteLimits(int16_t pitch, unsigned nTG) +{ + assert (nTG < CConfig::AllToneGenerators); + if (nTG >= m_nToneGenerators) return -1; // Not an active TG - pitch += m_nNoteShift[nTG]; + if (pitch < (int16_t)m_nNoteLimitLow[nTG] || pitch > (int16_t)m_nNoteLimitHigh[nTG]) + return -1; - if ( pitch < 0 - || pitch > 127) - { - return -1; - } + pitch += m_nNoteShift[nTG]; - return pitch; + if (pitch < 0 || pitch > 127) + return -1; + + return pitch; } void CMiniDexed::setSustain(bool sustain, unsigned nTG) @@ -1182,18 +1158,19 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; - case TGParameterUnisonVoices: - m_nUnisonVoices[nTG] = constrain(nValue, 1, 4); - break; - case TGParameterUnisonDetune: - m_nUnisonDetune[nTG] = constrain(nValue, 0, 99); - break; - case TGParameterUnisonSpread: - m_nUnisonSpread[nTG] = constrain(nValue, 0, 99); - break; - case TGParameterUnknown: - // No action needed for unknown parameter - break; + case TGParameterUnisonVoices: + m_nUnisonVoices[nTG] = constrain(nValue, 1, 4); + panic(0, nTG); // Stop all notes on TG when unison voices changes + break; + case TGParameterUnisonDetune: + m_nUnisonDetune[nTG] = constrain(nValue, 0, 99); + break; + case TGParameterUnisonSpread: + m_nUnisonSpread[nTG] = constrain(nValue, 0, 99); + break; + case TGParameterUnknown: + // No action needed for unknown parameter + break; } } @@ -1342,12 +1319,16 @@ void CMiniDexed::ProcessSound (void) m_GetChunkTimer.Start (); } - float32_t SampleBuffer[nFrames]; - m_pTG[0]->getSamples (SampleBuffer, nFrames); + float32_t SampleBufferL[nFrames]; + float32_t SampleBufferR[nFrames]; + m_pTG[0]->getSamplesStereo(SampleBufferL, SampleBufferR, nFrames); - // Convert single float array (mono) to int16 array - int32_t tmp_int[nFrames]; - arm_float_to_q23(SampleBuffer,tmp_int,nFrames); + // Convert stereo float arrays to interleaved int32 array + int32_t tmp_int[nFrames * 2]; + for (unsigned i = 0; i < nFrames; ++i) { + tmp_int[i*2] = float_to_q23(SampleBufferL[i]); + tmp_int[i*2+1] = float_to_q23(SampleBufferR[i]); + } if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) { @@ -1888,8 +1869,10 @@ void CMiniDexed::setVoiceDataElement(uint8_t data, uint8_t number, uint8_t nTG) if (nTG >= m_nToneGenerators) return; // Not an active TG assert (m_pTG[nTG]); + assert (number <= 99); + assert (data <= 155); - m_pTG[nTG]->setVoiceDataElement(constrain(data, 0, 155),constrain(number, 0, 99)); + m_pTG[nTG]->setVoiceDataElement (data, number); m_UI.ParameterChanged (); } @@ -2174,7 +2157,8 @@ void CMiniDexed::SetVoiceName (const std::string &VoiceName, unsigned nTG) assert (m_pTG[nTG]); char Name[11]; - strncpy(Name, VoiceName.c_str(),10); + // Fix strncpy overload issue by casting to const char* + strncpy(Name, VoiceName.c_str(), 10); Name[10] = '\0'; m_pTG[nTG]->setName (Name); } @@ -2561,10 +2545,4 @@ bool CMiniDexed::InitNetwork() LOGNOTE("CMiniDexed::InitNetwork: Network is not enabled in configuration"); return false; } -} - -// Forward declaration and definition for getPhysicalTG -static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices); -static unsigned getPhysicalTG(unsigned logicalTG, unsigned unisonVoice, unsigned unisonVoices) { - return logicalTG * unisonVoices + unisonVoice; -} +} \ No newline at end of file diff --git a/src/patches/dexed.cpp b/src/patches/dexed.cpp new file mode 100644 index 0000000..d0717a5 --- /dev/null +++ b/src/patches/dexed.cpp @@ -0,0 +1,1937 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield. + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + (c)2018-2021 H. Wirtz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ +#include +#include +#include +#include +#include +#include +#include "dexed.h" +#include "synth.h" +#include "fm_core.h" +#include "exp2.h" +#include "sin.h" +#include "freqlut.h" +#include "controllers.h" +#include "PluginFx.h" +#include "porta.h" +#include "compressor.h" + +Dexed::Dexed(uint8_t maxnotes, uint16_t rate) +{ + samplerate = float32_t(rate); + + Exp2::init(); + Tanh::init(); + Sin::init(); + + Freqlut::init(rate); + Lfo::init(rate); + PitchEnv::init(rate); + Env::init_sr(rate); + Porta::init_sr(rate); + fx.init(rate); + + currentNote = 0; + resetControllers(); + controllers.masterTune = 0; + controllers.opSwitch = 0x3f; // enable all operators + lastKeyDown = -1; + lfo.reset(data + 137); + sustain = false; + sostenuto = false; + hold = false; + voices = NULL; + + max_notes=maxnotes; + if (max_notes > 0) + { + voices = new ProcessorVoice[max_notes]; // sizeof(ProcessorVoice) = 20 + for (uint8_t i = 0; i < max_notes; i++) + { + voices[i].dx7_note = new Dx7Note; // sizeof(Dx7Note) = 692 + voices[i].keydown = false; + voices[i].sustained = false; + voices[i].sostenuted = false; + voices[i].held = false; + voices[i].live = false; + voices[i].key_pressed_timer = 0; + } + } + else + voices = NULL; + + used_notes=max_notes; + setMonoMode(false); + loadInitVoice(); + + xrun = 0; + render_time_max = 0; + + setVelocityScale(MIDI_VELOCITY_SCALING_OFF); + setNoteRefreshMode(false); + + engineMsfa = new EngineMsfa; + engineMkI = new EngineMkI; + engineOpl = new EngineOpl; + setEngineType(MKI); + +#ifndef TEENSYDUINO + compressor = new Compressor(samplerate); +#endif + use_compressor = false; +} + +Dexed::~Dexed() +{ + currentNote = -1; + + for (uint8_t note = 0; note < max_notes; note++) + delete voices[note].dx7_note; + delete[] voices; +} + +void Dexed::setEngineType(uint8_t engine) +{ + panic(); + + switch(engine) + { + case MSFA: + controllers.core = (FmCore*)engineMsfa; + engineType=MSFA; + break; + case MKI: + controllers.core = (FmCore*)engineMkI; + engineType=MKI; + break; + case OPL: + controllers.core = (FmCore*)engineOpl; + engineType=OPL; + break; + default: + controllers.core = (FmCore*)engineMsfa; + engineType=MSFA; + break; + } + + controllers.refresh(); +} + +uint8_t Dexed::getEngineType(void) +{ + return(engineType); +} + +FmCore* Dexed::getEngineAddress(void) +{ + return(controllers.core); +} + +void Dexed::setMaxNotes(uint8_t new_max_notes) +{ + panic(); + used_notes = constrain(new_max_notes, 0, max_notes); +} + +void Dexed::activate(void) +{ + panic(); + controllers.refresh(); +} + +void Dexed::deactivate(void) +{ + panic(); +} + +void Dexed::getSamples(float* buffer, uint16_t n_samples) +{ + if (refreshVoice) + { + for (uint8_t i = 0; i < used_notes; i++) + { + if ( voices[i].live ) + voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity, voices[i].porta, &controllers); + } + lfo.reset(data + 137); + refreshVoice = false; + } + + arm_fill_f32(0.0, buffer, n_samples); + + for (uint16_t i = 0; i < n_samples; i += _N_) + { + AlignedBuf audiobuf; + + for (uint8_t j = 0; j < _N_; ++j) + { + audiobuf.get()[j] = 0; + } + + int32_t lfovalue = lfo.getsample(); + int32_t lfodelay = lfo.getdelay(); + + for (uint8_t note = 0; note < used_notes; note++) + { + if (voices[note].live) + { + voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers); + + for (uint8_t j = 0; j < _N_; ++j) + { + buffer[i + j] += signed_saturate_rshift(audiobuf.get()[j] >> 4, 24, 9) / 32768.0; + audiobuf.get()[j] = 0; + } + } + } + } + + fx.process(buffer, n_samples); // Needed for fx.Gain()!!! + +#ifndef TEENSYDUINO + if (use_compressor == true) + compressor->doCompression(buffer, n_samples); +#endif +} + +void Dexed::getSamples(int16_t* buffer, uint16_t n_samples) +{ + float tmp[n_samples]; + + getSamples(tmp, n_samples); + arm_float_to_q15(tmp, (q15_t*)buffer, n_samples); +} + +void Dexed::keydown(uint8_t pitch, uint8_t velo) { + if (velo == 0) { + keyup(pitch); + return; + } + velo = uint8_t((float(velo)/127.0)*velocity_diff+0.5)+velocity_offset; + pitch += data[144] - TRANSPOSE_FIX; + int32_t previousKeyDown = lastKeyDown; + lastKeyDown = pitch; + int32_t porta = -1; + if (controllers.portamento_enable_cc && previousKeyDown >= 0) + porta = controllers.portamento_cc; + // --- UNISON LOGIC --- + if (pitch < kMaxMidiNotes) { + unisonDetunedCount[pitch] = unisonVoices; + for (uint8_t u = 0; u < unisonVoices; ++u) { + float detuneOffset = ((float)u - (unisonVoices - 1) / 2.0f) * unisonDetune; + float detunedPitch = (float)pitch + detuneOffset; + unisonDetunedPitches[pitch][u] = detunedPitch; + // Combine basePan and unison spread + float pan = 0.5f; + if (unisonVoices > 1) { + float pos = (float)u / (unisonVoices - 1); + pan = 0.5f + (pos - 0.5f) * unisonSpread; + } + // Add basePan offset and clamp + pan = basePan + (pan - 0.5f); + if (pan < 0.0f) pan = 0.0f; + if (pan > 1.0f) pan = 1.0f; + + uint8_t note = currentNote; + uint8_t keydown_counter = 0; + bool found = false; + if (!monoMode && noteRefreshMode) { + for (uint8_t i = 0; i < used_notes; i++) { + if (voices[i].midi_note == detunedPitch && voices[i].keydown == false && voices[i].live && + (voices[i].sustained == true || voices[i].held == true)) { + voices[i].dx7_note->keyup(); + voices[i].midi_note = detunedPitch; + voices[i].velocity = velo; + voices[i].keydown = true; + voices[i].sustained = sustain; + voices[i].held = hold; + voices[i].live = true; + voices[i].dx7_note->init(data, detunedPitch, velo, detunedPitch, porta, &controllers); + voices[i].key_pressed_timer = millis(); + unisonVoiceIndices[pitch][u] = i; + found = true; + goto next_unison; + } + } + } + + for (uint8_t i = 0; i <= used_notes; i++) { + if (i == used_notes) { + uint32_t min_timer = 0xffffffff; + + if (monoMode) + break; + + // no free sound slot found, so use the oldest note slot + for (uint8_t n = 0; n < used_notes; n++) { + if (voices[n].key_pressed_timer < min_timer) { + min_timer = voices[n].key_pressed_timer; + note = n; + } + } + voices[note].keydown = false; + voices[note].sustained = false; + voices[note].sostenuted = false; + voices[note].held = false; + voices[note].live = false; + voices[note].key_pressed_timer = 0; + keydown_counter--; + } + + if (!voices[note].keydown && !voices[note].sostenuted) { + currentNote = (note + 1) % used_notes; + lfo.keydown(); + voices[note].midi_note = detunedPitch; + voices[note].velocity = velo; + voices[note].sustained = sustain; + voices[note].sostenuted = false; + voices[note].held = hold; + voices[note].keydown = true; + int32_t srcnote = (previousKeyDown >= 0) ? previousKeyDown : detunedPitch; + voices[note].dx7_note->init(data, detunedPitch, velo, srcnote, porta, &controllers); + if (data[136]) + voices[note].dx7_note->oscSync(); + voices[note].key_pressed_timer = millis(); + keydown_counter++; + // --- Unison LOGIC --- + // set pan for each unison voice + voices[note].pan = pan; + unisonVoiceIndices[pitch][u] = note; + found = true; + break; + } else { + keydown_counter++; + } + note = (note + 1) % used_notes; + } + + if (!found && monoMode) { + for (uint8_t i = 0; i < used_notes; i++) { + if ( voices[i].live ) { + // all keys are up, only transfer signal + if ( ! voices[i].keydown ) { + voices[i].live = false; + voices[note].dx7_note->transferSignal(*voices[i].dx7_note); + unisonVoiceIndices[pitch][u] = note; + found = true; + break; + } + if ( voices[i].midi_note < detunedPitch ) { + voices[i].live = false; + voices[note].dx7_note->transferState(*voices[i].dx7_note); + unisonVoiceIndices[pitch][u] = note; + found = true; + break; + } + goto next_unison; + } + } + } + + voices[note].live = true; + next_unison:; + } + } +} + +void Dexed::keyup(uint8_t pitch) { + pitch = constrain(pitch, 0, 127); + + pitch += data[144] - TRANSPOSE_FIX; + + // Release all detuned unison voices for this note + if (pitch < kMaxMidiNotes) { + uint8_t count = unisonDetunedCount[pitch]; + for (uint8_t u = 0; u < count; ++u) { + int note = unisonVoiceIndices[pitch][u]; + if (note < 0 || note >= used_notes) continue; + if (!voices[note].keydown) continue; + voices[note].keydown = false; + voices[note].key_pressed_timer = 0; + + if ( monoMode ) + { + int8_t highNote = -1; + uint8_t target = 0; + for (int8_t i = 0; i < used_notes; i++) { + if ( voices[i].keydown && voices[i].midi_note > highNote ) { + target = i; + highNote = voices[i].midi_note; + } + } + + if ( highNote != -1 && voices[note].live ) { + voices[note].live = false; + voices[note].key_pressed_timer = 0; + voices[target].live = true; + voices[target].dx7_note->transferState(*voices[note].dx7_note); + } + } + + if ( voices[note].sostenuted ) + continue; + + if ( sustain ) { + voices[note].sustained = true; + } else if ( hold ) { + voices[note].held = true; + } else { + voices[note].dx7_note->keyup(); + } + } + unisonDetunedCount[pitch] = 0; + } +} + +void Dexed::doRefreshVoice(void) +{ + refreshVoice = true; +} + +void Dexed::setOPAll(uint8_t ops) +{ + controllers.opSwitch = ops; +} + +bool Dexed::getMonoMode(void) { + return monoMode; +} + +void Dexed::setMonoMode(bool mode) { + if (monoMode == mode) + return; + + notesOff(); + monoMode = mode; +} + +void Dexed::setNoteRefreshMode(bool mode) { + noteRefreshMode = mode; +} + +void Dexed::setSustain(bool s) +{ + if (sustain == s) + return; + + sustain = s; + + if (!getSustain()) + { + for (uint8_t note = 0; note < getMaxNotes(); note++) + { + if (voices[note].sustained && !voices[note].keydown) + { + voices[note].dx7_note->keyup(); + voices[note].sustained = false; + } + } + } +} + +bool Dexed::getSustain(void) +{ + return sustain; +} + +void Dexed::setSostenuto(bool s) +{ + if (sostenuto == s) + return; + + sostenuto = s; + + if (sostenuto) + { + for (uint8_t note = 0; note < getMaxNotes(); note++) + { + if (voices[note].keydown) + { + voices[note].sostenuted = true; + } + } + } + else + { + for (uint8_t note = 0; note < getMaxNotes(); note++) + { + if (voices[note].sostenuted) + { + voices[note].dx7_note->keyup(); + voices[note].sostenuted = false; + } + } + } +} + +bool Dexed::getSostenuto(void) +{ + return sostenuto; +} + +void Dexed::setHold(bool h) +{ + if (hold == h) + return; + + hold = h; + + if (!getHold()) + { + for (uint8_t note = 0; note < getMaxNotes(); note++) + { + if (voices[note].held) + { + voices[note].dx7_note->keyup(); + voices[note].held = false; + } + } + } +} + +bool Dexed::getHold(void) +{ + return hold; +} + +void Dexed::panic(void) +{ + for (uint8_t i = 0; i < max_notes; i++) + { + if (voices[i].live == true) { + voices[i].keydown = false; + voices[i].live = false; + voices[i].sustained = false; + voices[i].sostenuted = false; + voices[i].held = false; + voices[i].key_pressed_timer = 0; + if ( voices[i].dx7_note != NULL ) { + voices[i].dx7_note->oscSync(); + } + } + } + setSustain(0); +} + +void Dexed::resetControllers(void) +{ + controllers.values_[kControllerPitch] = 0x2000; + controllers.values_[kControllerPitchRange] = 0; + controllers.values_[kControllerPitchStep] = 0; + controllers.values_[kControllerPortamentoGlissando] = 0; + + controllers.modwheel_cc = 0; + controllers.foot_cc = 0; + controllers.breath_cc = 0; + controllers.aftertouch_cc = 0; + controllers.portamento_enable_cc = false; + controllers.portamento_cc = 0; + controllers.refresh(); +} + +void Dexed::notesOff(void) { + for (uint8_t i = 0; i < max_notes; i++) { + if (voices[i].live == true) { + voices[i].keydown = false; + voices[i].live = false; + } + } +} + +uint8_t Dexed::getMaxNotes(void) +{ + return used_notes; +} + +uint8_t Dexed::getNumNotesPlaying(void) +{ + uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers + uint8_t i; + uint8_t count_playing_voices = 0; + + for (i = 0; i < used_notes; i++) + { + if (voices[i].live == true) + { + uint8_t op_amp = 0; + uint8_t op_carrier_num = 0; + + memset(&voiceStatus, 0, sizeof(VoiceStatus)); + voices[i].dx7_note->peekVoiceStatus(voiceStatus); + + for (uint8_t op = 0; op < 6; op++) + { + if ((op_carrier & (1 << op))) + { + // this voice is a carrier! + op_carrier_num++; + if (voiceStatus.amp[op] <= VOICE_SILENCE_LEVEL && voiceStatus.ampStep[op] == 4) + { + // this voice produces no audio output + op_amp++; + } + } + } + + if (op_amp == op_carrier_num) + { + // all carrier-operators are silent -> disable the voice + voices[i].live = false; + voices[i].sustained = false; + voices[i].sostenuted = false; + voices[i].held = false; + voices[i].keydown = false; +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.print(F("Shutdown voice: ")); + Serial.println(i, DEC); +#endif + } + else + count_playing_voices++; + } + } + return (count_playing_voices); +} + +bool Dexed::decodeVoice(uint8_t* new_data, uint8_t* encoded_data) +{ + uint8_t* p_data = new_data; + uint8_t op; + uint8_t tmp; + char dexed_voice_name[11]; + + panic(); + + for (op = 0; op < 6; op++) + { + // DEXED_OP_EG_R1, // 0 + // DEXED_OP_EG_R2, // 1 + // DEXED_OP_EG_R3, // 2 + // DEXED_OP_EG_R4, // 3 + // DEXED_OP_EG_L1, // 4 + // DEXED_OP_EG_L2, // 5 + // DEXED_OP_EG_L3, // 6 + // DEXED_OP_EG_L4, // 7 + // DEXED_OP_LEV_SCL_BRK_PT, // 8 + // DEXED_OP_SCL_LEFT_DEPTH, // 9 + // DEXED_OP_SCL_RGHT_DEPTH, // 10 + memcpy(&new_data[op * 21], &encoded_data[op * 17], 11); + tmp = encoded_data[(op * 17) + 11]; + *(p_data + DEXED_OP_SCL_LEFT_CURVE + (op * 21)) = (tmp & 0x03); + *(p_data + DEXED_OP_SCL_RGHT_CURVE + (op * 21)) = (tmp & 0x0c) >> 2; + tmp = encoded_data[(op * 17) + 12]; + *(p_data + DEXED_OP_OSC_DETUNE + (op * 21)) = (tmp & 0x78) >> 3; + *(p_data + DEXED_OP_OSC_RATE_SCALE + (op * 21)) = (tmp & 0x07); + tmp = encoded_data[(op * 17) + 13]; + *(p_data + DEXED_OP_KEY_VEL_SENS + (op * 21)) = (tmp & 0x1c) >> 2; + *(p_data + DEXED_OP_AMP_MOD_SENS + (op * 21)) = (tmp & 0x03); + *(p_data + DEXED_OP_OUTPUT_LEV + (op * 21)) = encoded_data[(op * 17) + 14]; + tmp = encoded_data[(op * 17) + 15]; + *(p_data + DEXED_OP_FREQ_COARSE + (op * 21)) = (tmp & 0x3e) >> 1; + *(p_data + DEXED_OP_OSC_MODE + (op * 21)) = (tmp & 0x01); + *(p_data + DEXED_OP_FREQ_FINE + (op * 21)) = encoded_data[(op * 17) + 16]; + } + // DEXED_PITCH_EG_R1, // 0 + // DEXED_PITCH_EG_R2, // 1 + // DEXED_PITCH_EG_R3, // 2 + // DEXED_PITCH_EG_R4, // 3 + // DEXED_PITCH_EG_L1, // 4 + // DEXED_PITCH_EG_L2, // 5 + // DEXED_PITCH_EG_L3, // 6 + // DEXED_PITCH_EG_L4, // 7 + memcpy(&new_data[DEXED_VOICE_OFFSET], &encoded_data[102], 8); + tmp = encoded_data[110]; + *(p_data + DEXED_VOICE_OFFSET + DEXED_ALGORITHM) = (tmp & 0x1f); + tmp = encoded_data[111]; + *(p_data + DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC) = (tmp & 0x08) >> 3; + *(p_data + DEXED_VOICE_OFFSET + DEXED_FEEDBACK) = (tmp & 0x07); + // DEXED_LFO_SPEED, // 11 + // DEXED_LFO_DELAY, // 12 + // DEXED_LFO_PITCH_MOD_DEP, // 13 + // DEXED_LFO_AMP_MOD_DEP, // 14 + memcpy(&new_data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED], &encoded_data[112], 4); + tmp = encoded_data[116]; + *(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS) = (tmp & 0x30) >> 4; + *(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_WAVE) = (tmp & 0x0e) >> 1; + *(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_SYNC) = (tmp & 0x01); + *(p_data + DEXED_VOICE_OFFSET + DEXED_TRANSPOSE) = encoded_data[117]; + memcpy(&new_data[DEXED_VOICE_OFFSET + DEXED_NAME], &encoded_data[118], 10); + panic(); + doRefreshVoice(); + + strncpy(dexed_voice_name, (char *)&encoded_data[118], sizeof(dexed_voice_name) - 1); +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.print(F("Voice [")); + Serial.print(dexed_voice_name); + Serial.println(F("] decoded.")); +#endif + + return (true); +} + +bool Dexed::encodeVoice(uint8_t* encoded_data) +{ + uint8_t* p_data = data; + uint8_t op; + + for (op = 0; op < 6; op++) + { + // DEXED_OP_EG_R1, // 0 + // DEXED_OP_EG_R2, // 1 + // DEXED_OP_EG_R3, // 2 + // DEXED_OP_EG_R4, // 3 + // DEXED_OP_EG_L1, // 4 + // DEXED_OP_EG_L2, // 5 + // DEXED_OP_EG_L3, // 6 + // DEXED_OP_EG_L4, // 7 + // DEXED_OP_LEV_SCL_BRK_PT, // 8 + // DEXED_OP_SCL_LEFT_DEPTH, // 9 + // DEXED_OP_SCL_RGHT_DEPTH, // 10 + memcpy(&encoded_data[op * 17], &data[op * 21], 11); + encoded_data[(op * 17) + 11] = ((*(p_data + DEXED_OP_SCL_RGHT_CURVE + (op * 21)) & 0x0c) << 2) | (*(p_data + DEXED_OP_SCL_LEFT_CURVE + (op * 21)) & 0x03); + encoded_data[(op * 17) + 12] = ((*(p_data + DEXED_OP_OSC_DETUNE + (op * 21)) & 0x0f) << 3) | (*(p_data + DEXED_OP_OSC_RATE_SCALE + (op * 21)) & 0x07); + encoded_data[(op * 17) + 13] = ((*(p_data + DEXED_OP_KEY_VEL_SENS + (op * 21)) & 0x07) << 2) | (*(p_data + DEXED_OP_AMP_MOD_SENS + (op * 21)) & 0x03); + encoded_data[(op * 17) + 14] = *(p_data + DEXED_OP_OUTPUT_LEV + (op * 21)); + encoded_data[(op * 17) + 15] = ((*(p_data + DEXED_OP_FREQ_COARSE + (op * 21)) & 0x1f) << 1) | (*(p_data + DEXED_OP_OSC_MODE + (op * 21)) & 0x01); + encoded_data[(op * 17) + 16] = *(p_data + DEXED_OP_FREQ_FINE + (op * 21)); + } + // DEXED_PITCH_EG_R1, // 0 + // DEXED_PITCH_EG_R2, // 1 + // DEXED_PITCH_EG_R3, // 2 + // DEXED_PITCH_EG_R4, // 3 + // DEXED_PITCH_EG_L1, // 4 + // DEXED_PITCH_EG_L2, // 5 + // DEXED_PITCH_EG_L3, // 6 + // DEXED_PITCH_EG_L4, // 7 + memcpy(&encoded_data[102], &data[DEXED_VOICE_OFFSET], 8); + encoded_data[110] = (*(p_data + DEXED_VOICE_OFFSET + DEXED_ALGORITHM) & 0x1f); + encoded_data[111] = (((*(p_data + DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC) & 0x01) << 3) | ((*(p_data + DEXED_VOICE_OFFSET + DEXED_FEEDBACK)) & 0x07)); + // DEXED_LFO_SPEED, // 11 + // DEXED_LFO_DELAY, // 12 + // DEXED_LFO_PITCH_MOD_DEP, // 13 + // DEXED_LFO_AMP_MOD_DEP, // 14 + memcpy(&encoded_data[112], &data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED], 4); + encoded_data[116] = (((*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS) & 0x07) << 4) | (((*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_WAVE)) & 0x07) << 1) | ((*(p_data + DEXED_VOICE_OFFSET + DEXED_LFO_SYNC)) & 0x01)); + encoded_data[117] = *(p_data + DEXED_VOICE_OFFSET + DEXED_TRANSPOSE); + memset(&encoded_data[118], 0, 10); + memcpy(&encoded_data[118], &data[DEXED_VOICE_OFFSET + DEXED_NAME], 10); + + return (true); +} + +bool Dexed::getVoiceData(uint8_t* data_copy) +{ + memcpy(data_copy, data, sizeof(data)); + return (true); +} + +void Dexed::setVoiceDataElement(uint8_t address, uint8_t value) +{ + address = constrain(address, 0, NUM_VOICE_PARAMETERS); + data[address] = value; + doRefreshVoice(); +} + +uint8_t Dexed::getVoiceDataElement(uint8_t address) +{ + address = constrain(address, 0, NUM_VOICE_PARAMETERS); + return (data[address]); +} + +void Dexed::loadVoiceParameters(uint8_t* new_data) +{ +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + char dexed_voice_name[11]; +#endif + + panic(); + memcpy(&data, new_data, 155); + doRefreshVoice(); +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + strncpy(dexed_voice_name, (char *)&new_data[145], sizeof(dexed_voice_name) - 1); + + Serial.print(F("Voice [")); + Serial.print(dexed_voice_name); + Serial.println(F("] loaded.")); +#endif +} + +void Dexed::loadInitVoice(void) +{ + loadVoiceParameters(init_voice); +} + +void Dexed::setPBController(uint8_t pb_range, uint8_t pb_step) +{ +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.println(F("Dexed::setPBController")); +#endif + + pb_range = constrain(pb_range, 0, 12); + pb_step = constrain(pb_step, 0, 12); + + controllers.values_[kControllerPitchRange] = pb_range; + controllers.values_[kControllerPitchStep] = pb_step; + + controllers.refresh(); +} + +void Dexed::setMWController(uint8_t mw_range, uint8_t mw_assign, uint8_t mw_mode) +{ +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.println(F("Dexed::setMWController")); +#endif + + mw_range = constrain(mw_range, 0, 99); + mw_assign = constrain(mw_assign, 0, 7); + mw_mode = constrain(mw_mode, 0, MIDI_CONTROLLER_MODE_MAX); + + controllers.wheel.setRange(mw_range); + controllers.wheel.setTarget(mw_assign); + controllers.wheel.setMode(mw_mode); + + controllers.refresh(); +} + +void Dexed::setFCController(uint8_t fc_range, uint8_t fc_assign, uint8_t fc_mode) +{ +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.println(F("Dexed::setFCController")); +#endif + + fc_range = constrain(fc_range, 0, 99); + fc_assign = constrain(fc_assign, 0, 7); + fc_mode = constrain(fc_mode, 0, MIDI_CONTROLLER_MODE_MAX); + + controllers.foot.setRange(fc_range); + controllers.foot.setTarget(fc_assign); + controllers.foot.setMode(fc_mode); + + controllers.refresh(); +} + +void Dexed::setBCController(uint8_t bc_range, uint8_t bc_assign, uint8_t bc_mode) +{ +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.println(F("Dexed::setBCController")); +#endif + + bc_range = constrain(bc_range, 0, 99); + bc_assign = constrain(bc_assign, 0, 7); + bc_mode = constrain(bc_mode, 0, MIDI_CONTROLLER_MODE_MAX); + + controllers.breath.setRange(bc_range); + controllers.breath.setTarget(bc_assign); + controllers.breath.setMode(bc_mode); + + controllers.refresh(); +} + +void Dexed::setATController(uint8_t at_range, uint8_t at_assign, uint8_t at_mode) +{ +#if defined(MICRODEXED_VERSION) && defined(DEBUG) + Serial.println(F("Dexed::setATController")); +#endif + + at_range = constrain(at_range, 0, 99); + at_assign = constrain(at_assign, 0, 7); + at_mode = constrain(at_mode, 0, MIDI_CONTROLLER_MODE_MAX); + + controllers.at.setRange(at_range); + controllers.at.setTarget(at_assign); + controllers.at.setMode(at_mode); + + controllers.refresh(); +} + +void Dexed::setPortamento(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time) +{ + portamento_mode = constrain(portamento_mode, 0, 1); + portamento_glissando = constrain(portamento_glissando, 0, 1); + portamento_time = constrain(portamento_time, 0, 99); + + controllers.portamento_cc = portamento_time; + controllers.portamento_enable_cc = portamento_mode > 0; + + if (portamento_time > 0) + controllers.portamento_enable_cc = true; + else + controllers.portamento_enable_cc = false; + + controllers.values_[kControllerPortamentoGlissando] = portamento_glissando; + + controllers.refresh(); +} + +void Dexed::setPortamentoMode(uint8_t portamento_mode) +{ + portamento_mode = constrain(portamento_mode, 0, 1); + controllers.portamento_enable_cc = portamento_mode > 0; + + controllers.refresh(); +} + +uint8_t Dexed::getPortamentoMode(void) +{ + return(controllers.portamento_enable_cc); +} + +void Dexed::setPortamentoGlissando(uint8_t portamento_glissando) +{ + portamento_glissando = constrain(portamento_glissando, 0, 1); + controllers.values_[kControllerPortamentoGlissando] = portamento_glissando; + + controllers.refresh(); +} + +uint8_t Dexed::getPortamentoGlissando(void) +{ + return(controllers.values_[kControllerPortamentoGlissando]); +} + +void Dexed::setPortamentoTime(uint8_t portamento_time) +{ + portamento_time = constrain(portamento_time, 0, 99); + controllers.portamento_cc = portamento_time; + + if (portamento_time > 0) + controllers.portamento_enable_cc = true; + else + controllers.portamento_enable_cc = false; + + controllers.refresh(); +} + +uint8_t Dexed::getPortamentoTime(void) +{ + return(controllers.portamento_cc); +} + +int16_t Dexed::checkSystemExclusive(const uint8_t* sysex, const uint16_t len) +/* + -1: SysEx end status byte not detected. + -2: SysEx vendor not Yamaha. + -3: Unknown SysEx parameter change. + -4: Unknown SysEx voice or function. + -5: Not a SysEx voice bulk upload. + -6: Wrong length for SysEx voice bulk upload (not 155). + -7: Checksum error for one voice. + -8: Not a SysEx bank bulk upload. + -9: Wrong length for SysEx bank bulk upload (not 4096). + -10: Checksum error for bank. + -11: Unknown SysEx message. + 64-77: Function parameter changed. + 100: Voice loaded. + 200: Bank loaded. + 300-455: Voice parameter changed. +*/ +{ + int32_t bulk_checksum_calc = 0; + const int8_t bulk_checksum = sysex[161]; + + // Check for SYSEX end byte + if (sysex[len - 1] != 0xf7) + return(-1); + + // check for Yamaha sysex + if (sysex[1] != 0x43) + return(-2); + + // Decode SYSEX by means of length + switch (len) + { + case 7: // parse parameter change + if (((sysex[3] & 0x7c) >> 2) != 0 && ((sysex[3] & 0x7c) >> 2) != 2) + return(-3); + + if ((sysex[3] & 0x7c) >> 2 == 0) // Voice parameter + { + setVoiceDataElement((sysex[4] & 0x7f) + ((sysex[3] & 0x03) * 128), sysex[5]); + doRefreshVoice(); + return((sysex[4] & 0x7f) + ((sysex[3] & 0x03) * 128)+300); + } + else if ((sysex[3] & 0x7c) >> 2 == 2) // Function parameter + return(sysex[4]); + else + return(-4); + break; + case 163: // 1 Voice bulk upload + if ((sysex[3] & 0x7f) != 0) + return(-5); + + if (((sysex[4] << 7) | sysex[5]) != 0x9b) + return(-6); + + // checksum calculation + for (uint8_t i = 0; i < 155 ; i++) + bulk_checksum_calc -= sysex[i + 6]; + bulk_checksum_calc &= 0x7f; + + if (bulk_checksum_calc != bulk_checksum) + return(-7); + + return(100); + break; + case 4104: // 1 Bank bulk upload + if ((sysex[3] & 0x7f) != 9) + return(-8); + + if (((sysex[4] << 7) | sysex[5]) != 0x1000) + return(-9); + + // checksum calculation + for (uint16_t i = 0; i < 4096 ; i++) + bulk_checksum_calc -= sysex[i + 6]; + bulk_checksum_calc &= 0x7f; + + if (bulk_checksum_calc != bulk_checksum) + return(-10); + + return(200); + break; + default: + return(-11); + } +} + +uint32_t Dexed::getXRun(void) +{ + return (xrun); +} + +uint16_t Dexed::getRenderTimeMax(void) +{ + return (render_time_max); +} + +void Dexed::resetRenderTimeMax(void) +{ + render_time_max = 0; +} + +void Dexed::ControllersRefresh(void) +{ + controllers.refresh(); +} + +void Dexed::setMasterTune(int8_t mastertune) +{ + mastertune = constrain(mastertune, -99, 99); + + controllers.masterTune = (int(mastertune / 100.0 * 0x4000) << 11) * (1.0 / 12.0); +} + +int8_t Dexed::getMasterTune(void) +{ + return (controllers.masterTune); +} + +void Dexed::setModWheel(uint8_t value) +{ + value = constrain(value, 0, 127); + + controllers.modwheel_cc = value; +} + +uint8_t Dexed::getModWheel(void) +{ + return (controllers.modwheel_cc); +} + +void Dexed::setBreathController(uint8_t value) +{ + value = constrain(value, 0, 127); + + controllers.breath_cc = value; +} + +uint8_t Dexed::getBreathController(void) +{ + return (controllers.breath_cc); +} + +void Dexed::setFootController(uint8_t value) +{ + value = constrain(value, 0, 127); + + controllers.foot_cc = value; +} + +uint8_t Dexed::getFootController(void) +{ + return (controllers.foot_cc); +} + +void Dexed::setAftertouch(uint8_t value) +{ + value = constrain(value, 0, 127); + + controllers.aftertouch_cc = value; +} + +uint8_t Dexed::getAftertouch(void) +{ + return (controllers.aftertouch_cc); +} + +void Dexed::setPitchbend(uint8_t value1, uint8_t value2) +{ + setPitchbend(uint16_t(((value2 & 0x7f) << 7) | (value1 & 0x7f))); +} + +void Dexed::setPitchbend(int16_t value) +{ + value = constrain(value, -8192, 8191); + + controllers.values_[kControllerPitch] = value + 0x2000; // -8192 to +8191 --> 0 to 16383 + setPitchbend(uint16_t(value + 0x2000)); // -8192 to +8191 --> 0 to 16383 +} + +void Dexed::setPitchbend(uint16_t value) +{ + controllers.values_[kControllerPitch] = (value & 0x3fff); +} + +int16_t Dexed::getPitchbend(void) +{ + return (controllers.values_[kControllerPitch] - 0x2000); +} + +void Dexed::setPitchbendRange(uint8_t range) +{ + range = constrain(range, 0, 12); + + controllers.values_[kControllerPitchRange] = range; +} + +uint8_t Dexed::getPitchbendRange(void) +{ + return (controllers.values_[kControllerPitchRange]); +} + +void Dexed::setPitchbendStep(uint8_t step) +{ + step = constrain(step, 0, 12); + + controllers.values_[kControllerPitchStep] = step; +} + +uint8_t Dexed::getPitchbendStep(void) +{ + return (controllers.values_[kControllerPitchStep]); +} + +void Dexed::setModWheelRange(uint8_t range) +{ + range = constrain(range, 0, 12); + + controllers.wheel.setRange(range); +} + +uint8_t Dexed::getModWheelRange(void) +{ + return (controllers.wheel.getRange()); +} + +void Dexed::setModWheelTarget(uint8_t target) +{ + target = constrain(target, 0, 7); + + controllers.wheel.setTarget(target); +} + +uint8_t Dexed::getModWheelTarget(void) +{ + return (controllers.wheel.getTarget()); +} + +void Dexed::setFootControllerRange(uint8_t range) +{ + range = constrain(range, 0, 12); + + controllers.foot.setRange(range); +} + +uint8_t Dexed::getFootControllerRange(void) +{ + return (controllers.foot.getRange()); +} + +void Dexed::setFootControllerTarget(uint8_t target) +{ + target = constrain(target, 0, 7); + + controllers.foot.setTarget(target); +} + +uint8_t Dexed::getFootControllerTarget(void) +{ + return (controllers.foot.getTarget()); +} + +void Dexed::setBreathControllerRange(uint8_t range) +{ + range = constrain(range, 0, 12); + + controllers.breath.setRange(range); +} + +uint8_t Dexed::getBreathControllerRange(void) +{ + return (controllers.breath.getRange()); +} + +void Dexed::setBreathControllerTarget(uint8_t target) +{ + target = constrain(target, 0, 7); + + controllers.breath.setTarget(target); +} + +uint8_t Dexed::getBreathControllerTarget(void) +{ + return (controllers.breath.getTarget()); +} + +void Dexed::setAftertouchRange(uint8_t range) +{ + range = constrain(range, 0, 12); + + controllers.at.setRange(range); +} + +uint8_t Dexed::getAftertouchRange(void) +{ + return (controllers.at.getRange()); +} + +void Dexed::setAftertouchTarget(uint8_t target) +{ + target = constrain(target, 0, 7); + + controllers.at.setTarget(target); +} + +uint8_t Dexed::getAftertouchTarget(void) +{ + return (controllers.at.getTarget()); +} + +void Dexed::setFilterCutoff(float cutoff) +{ + fx.Cutoff = cutoff; +} + +float Dexed::getFilterCutoff(void) +{ + return (fx.Cutoff); +} + +void Dexed::setFilterResonance(float resonance) +{ + fx.Reso = resonance; +} + +float Dexed::getFilterResonance(void) +{ + return (fx.Reso); +} + +void Dexed::setGain(float gain) +{ + fx.Gain = gain; +} + +float Dexed::getGain(void) +{ + return (fx.Gain); +} + +void Dexed::setOPRateAll(uint8_t rate) +{ + rate = constrain(rate, 0, 99); + + for (uint8_t op = 0; op < 6; op++) + { + for (uint8_t step = 0; step < 4; step++) + { + data[(op * 21) + DEXED_OP_EG_R1 + step] = rate; + } + } + doRefreshVoice(); +} + +void Dexed::setOPLevelAll(uint8_t level) +{ + level = constrain(level, 0, 99); + + for (uint8_t op = 0; op < 6; op++) + { + for (uint8_t step = 0; step < 4; step++) + { + data[(op * 21) + DEXED_OP_EG_L1 + step] = level; + } + } + doRefreshVoice(); +} + +void Dexed::setOPRateAllModulator(uint8_t step, uint8_t rate) +{ + uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers + + rate = constrain(rate, 0, 99); + step = constrain(step, 0, 3); + + for (uint8_t op = 0; op < 6; op++) + { + if ((op_carrier & (1 << op)) == 0) + data[(op * 21) + DEXED_OP_EG_R1 + step] = rate; + } + doRefreshVoice(); +} + +void Dexed::setOPLevelAllModulator(uint8_t step, uint8_t level) +{ + uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers + + step = constrain(step, 0, 3); + level = constrain(level, 0, 99); + + for (uint8_t op = 0; op < 6; op++) + { + if ((op_carrier & (1 << op)) == 0) + data[(op * 21) + DEXED_OP_EG_L1 + step] = level; + } + doRefreshVoice(); +} + +void Dexed::setOPRateAllCarrier(uint8_t step, uint8_t rate) +{ + uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers + + rate = constrain(rate, 0, 99); + step = constrain(step, 0, 3); + + for (uint8_t op = 0; op < 6; op++) + { + if ((op_carrier & (1 << op)) == 1) + data[(op * 21) + DEXED_OP_EG_R1 + step] = rate; + } + doRefreshVoice(); +} + +void Dexed::setOPLevelAllCarrier(uint8_t step, uint8_t level) +{ + uint8_t op_carrier = controllers.core->get_carrier_operators(data[134]); // look for carriers + + level = constrain(level, 0, 99); + step = constrain(step, 0, 3); + + for (uint8_t op = 0; op < 6; op++) + { + if ((op_carrier & (1 << op)) == 1) + data[(op * 21) + DEXED_OP_EG_L1 + step] = level; + } + doRefreshVoice(); +} + +void Dexed::setOPRate(uint8_t op, uint8_t step, uint8_t rate) +{ + op = constrain(op, 0, 5); + step = constrain(step, 0, 3); + rate = constrain(rate, 0, 99); + + data[(op * 21) + DEXED_OP_EG_R1 + step] = rate; + doRefreshVoice(); +} + +uint8_t Dexed::getOPRate(uint8_t op, uint8_t step) +{ + op = constrain(op, 0, 5); + step = constrain(step, 0, 3); + + return (data[(op * 21) + DEXED_OP_EG_R1 + step]); +} + +void Dexed::setOPLevel(uint8_t op, uint8_t step, uint8_t level) +{ + op = constrain(op, 0, 5); + step = constrain(step, 0, 3); + level = constrain(level, 0, 99); + + data[(op * 21) + DEXED_OP_EG_L1 + step] = level; + doRefreshVoice(); +} + +uint8_t Dexed::getOPLevel(uint8_t op, uint8_t step) +{ + op = constrain(op, 0, 5); + step = constrain(step, 0, 3); + + return (data[(op * 21) + DEXED_OP_EG_L1 + step]); +} + +void Dexed::setOPKeyboardLevelScalingBreakPoint(uint8_t op, uint8_t level) +{ + op = constrain(op, 0, 5); + level = constrain(level, 0, 99); + + data[(op * 21) + DEXED_OP_LEV_SCL_BRK_PT] = level; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardLevelScalingBreakPoint(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_LEV_SCL_BRK_PT]); +} + +void Dexed::setOPKeyboardLevelScalingDepthLeft(uint8_t op, uint8_t depth) +{ + op = constrain(op, 0, 5); + depth = constrain(depth, 0, 99); + + data[(op * 21) + DEXED_OP_SCL_LEFT_DEPTH] = depth; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardLevelScalingDepthLeft(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_SCL_LEFT_DEPTH]); +} + +void Dexed::setOPKeyboardLevelScalingDepthRight(uint8_t op, uint8_t depth) +{ + op = constrain(op, 0, 5); + depth = constrain(depth, 0, 99); + + data[(op * 21) + DEXED_OP_SCL_RGHT_DEPTH] = depth; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardLevelScalingDepthRight(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_SCL_RGHT_DEPTH]); +} + +void Dexed::setOPKeyboardLevelScalingCurveLeft(uint8_t op, uint8_t curve) +{ + op = constrain(op, 0, 5); + curve = constrain(curve, 0, 3); + + data[(op * 21) + DEXED_OP_SCL_LEFT_CURVE] = curve; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardLevelScalingCurveLeft(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_SCL_LEFT_CURVE]); +} + +void Dexed::setOPKeyboardLevelScalingCurveRight(uint8_t op, uint8_t curve) +{ + op = constrain(op, 0, 5); + curve = constrain(curve, 0, 3); + + data[(op * 21) + DEXED_OP_SCL_RGHT_CURVE] = curve; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardLevelScalingCurveRight(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_SCL_RGHT_CURVE]); +} + +void Dexed::setOPKeyboardRateScale(uint8_t op, uint8_t scale) +{ + op = constrain(op, 0, 5); + scale = constrain(scale, 0, 7); + + data[(op * 21) + DEXED_OP_OSC_RATE_SCALE] = scale; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardRateScale(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_OSC_RATE_SCALE]); +} + +void Dexed::setOPAmpModulationSensity(uint8_t op, uint8_t sensitivity) +{ + op = constrain(op, 0, 5); + sensitivity = constrain(sensitivity, 0, 3); + + data[(op * 21) + DEXED_OP_AMP_MOD_SENS] = sensitivity; + doRefreshVoice(); +} + +uint8_t Dexed::getOPAmpModulationSensity(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_AMP_MOD_SENS]); +} + +void Dexed::setOPKeyboardVelocitySensity(uint8_t op, uint8_t sensitivity) +{ + op = constrain(op, 0, 5); + sensitivity = constrain(sensitivity, 0, 7); + + data[(op * 21) + DEXED_OP_KEY_VEL_SENS] = sensitivity; + doRefreshVoice(); +} + +uint8_t Dexed::getOPKeyboardVelocitySensity(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_KEY_VEL_SENS]); +} + +void Dexed::setOPOutputLevel(uint8_t op, uint8_t level) +{ + op = constrain(op, 0, 5); + level = constrain(level, 0, 99); + + data[(op * 21) + DEXED_OP_OUTPUT_LEV] = level; + doRefreshVoice(); +} + +uint8_t Dexed::getOPOutputLevel(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_OUTPUT_LEV]); +} + +void Dexed::setOPMode(uint8_t op, uint8_t mode) +{ + op = constrain(op, 0, 5); + mode = constrain(mode, 0, 1); + + data[(op * 21) + DEXED_OP_OSC_MODE] = mode; + doRefreshVoice(); +} + +uint8_t Dexed::getOPMode(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_OSC_MODE]); +} + +void Dexed::setOPFrequencyCoarse(uint8_t op, uint8_t frq_coarse) +{ + op = constrain(op, 0, 5); + frq_coarse = constrain(frq_coarse, 0, 31); + + data[(op * 21) + DEXED_OP_FREQ_COARSE] = frq_coarse; + doRefreshVoice(); +} + +uint8_t Dexed::getOPFrequencyCoarse(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_FREQ_COARSE ]); +} + +void Dexed::setOPFrequencyFine(uint8_t op, uint8_t frq_fine) +{ + op = constrain(op, 0, 5); + frq_fine = constrain(frq_fine, 0, 99); + + data[(op * 21) + DEXED_OP_FREQ_FINE] = frq_fine; + doRefreshVoice(); +} + +uint8_t Dexed::getOPFrequencyFine(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_FREQ_FINE]); +} + +void Dexed::setOPDetune(uint8_t op, uint8_t detune) +{ + op = constrain(op, 0, 5); + detune = constrain(detune, 0, 14); + + data[(op * 21) + DEXED_OP_OSC_DETUNE] = detune; + doRefreshVoice(); +} + +uint8_t Dexed::getOPDetune(uint8_t op) +{ + op = constrain(op, 0, 5); + + return (data[(op * 21) + DEXED_OP_OSC_DETUNE]); +} + +void Dexed::setPitchRate(uint8_t step, uint8_t rate) +{ + step = constrain(step, 0, 3); + rate = constrain(rate, 0, 99); + + data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_R1 + step] = rate; + doRefreshVoice(); +} + +uint8_t Dexed::getPitchRate(uint8_t step) +{ + step = constrain(step, 0, 3); + + return (data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_R1 + step]); +} + +void Dexed::setPitchLevel(uint8_t step, uint8_t level) +{ + step = constrain(step, 0, 3); + level = constrain(level, 0, 99); + + data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_L1 + step] = level; + doRefreshVoice(); +} + +uint8_t Dexed::getPitchLevel(uint8_t step) +{ + step = constrain(step, 0, 3); + + return (data[DEXED_VOICE_OFFSET + DEXED_PITCH_EG_L1 + step]); +} + +void Dexed::setAlgorithm(uint8_t algorithm) +{ + algorithm = constrain(algorithm, 0, 31); + + data[DEXED_VOICE_OFFSET + DEXED_ALGORITHM] = algorithm; + doRefreshVoice(); +} + +uint8_t Dexed::getAlgorithm(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_ALGORITHM]); +} + +void Dexed::setFeedback(uint8_t feedback) +{ + feedback = constrain(feedback, 0, 31); + + data[DEXED_VOICE_OFFSET + DEXED_FEEDBACK] = feedback; + doRefreshVoice(); +} + +uint8_t Dexed::getFeedback(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_FEEDBACK]); +} + +void Dexed::setOscillatorSync(bool sync) +{ + data[DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC] = sync; + doRefreshVoice(); +} + +bool Dexed::getOscillatorSync(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_OSC_KEY_SYNC]); +} + +void Dexed::setLFOSpeed(uint8_t speed) +{ + speed = constrain(speed, 0, 99); + + data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED] = speed; + lfo.reset(data + 137); +} + +uint8_t Dexed::getLFOSpeed(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_SPEED]); +} + +void Dexed::setLFODelay(uint8_t delay) +{ + delay = constrain(delay, 0, 99); + + data[DEXED_VOICE_OFFSET + DEXED_LFO_DELAY] = delay; + lfo.reset(data + 137); +} + +uint8_t Dexed::getLFODelay(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_DELAY]); +} + +void Dexed::setLFOPitchModulationDepth(uint8_t depth) +{ + depth = constrain(depth, 0, 99); + + data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_DEP] = depth; + lfo.reset(data + 137); +} + +uint8_t Dexed::getLFOPitchModulationDepth(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_DEP]); +} + +void Dexed::setLFOAmpModulationDepth(uint8_t depth) +{ + depth = constrain(depth, 0, 99); + + data[DEXED_VOICE_OFFSET + DEXED_LFO_AMP_MOD_DEP] = depth; + lfo.reset(data + 137); +} + +uint8_t Dexed::getLFOAmpModulationDepth(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_AMP_MOD_DEP]); +} + +void Dexed::setLFOSync(bool sync) +{ + data[DEXED_VOICE_OFFSET + DEXED_LFO_SYNC] = sync; +} + +bool Dexed::getLFOSync(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_SYNC]); + lfo.reset(data + 137); +} + +void Dexed::setLFOWaveform(uint8_t waveform) +{ + waveform = constrain(waveform, 0, 5); + + data[DEXED_VOICE_OFFSET + DEXED_LFO_WAVE] = waveform; + lfo.reset(data + 137); +} + +uint8_t Dexed::getLFOWaveform(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_WAVE]); +} + +void Dexed::setLFOPitchModulationSensitivity(uint8_t sensitivity) +{ + sensitivity = constrain(sensitivity, 0, 5); + + data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS] = sensitivity; + lfo.reset(data + 137); +} + +uint8_t Dexed::getLFOPitchModulationSensitivity(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_LFO_PITCH_MOD_SENS]); +} + +void Dexed::setTranspose(uint8_t transpose) +{ + transpose = constrain(transpose, 0, 48); + + data[DEXED_VOICE_OFFSET + DEXED_TRANSPOSE] = transpose; +} + +uint8_t Dexed::getTranspose(void) +{ + return (data[DEXED_VOICE_OFFSET + DEXED_TRANSPOSE]); +} + +void Dexed::setName(char name[11]) +{ + strncpy((char*)&data[DEXED_VOICE_OFFSET + DEXED_NAME], name, 10); +} + +void Dexed::getName(char buffer[11]) +{ + strncpy(buffer, (char*)&data[DEXED_VOICE_OFFSET + DEXED_NAME], 10); + buffer[10] = 0; +} + +void Dexed::setVelocityScale(uint8_t offset, uint8_t max) +{ + velocity_offset = offset & 0x7f; + velocity_max = max & 0x7f; + velocity_diff = velocity_max - velocity_offset; +} + +void Dexed::getVelocityScale(uint8_t* offset, uint8_t* max) +{ + *offset = velocity_offset; + *max = velocity_max; +} + +void Dexed::setVelocityScale(uint8_t setup = MIDI_VELOCITY_SCALING_OFF) +{ + switch(setup) + { + case MIDI_VELOCITY_SCALING_OFF: + velocity_offset=0; + velocity_max=127; + break; + case MIDI_VELOCITY_SCALING_DX7: + velocity_offset=16; + velocity_max=109; + break; + case MIDI_VELOCITY_SCALING_DX7II: + velocity_offset=6; + velocity_max=119; + break; + default: + velocity_offset=0; + velocity_max=127; + break; + } + setVelocityScale(velocity_offset, velocity_max); +} + +#ifndef TEENSYDUINO +void Dexed::setCompressor(bool enable_compressor) +{ + use_compressor = enable_compressor; +} + +bool Dexed::getCompressor(void) +{ + return (use_compressor); +} + +void Dexed::setCompressorPreGain_dB(float pre_gain) +{ + compressor->setPreGain_dB(pre_gain); +} + +void Dexed::setCompressorAttack_sec(float attack_sec) +{ + compressor->setAttack_sec(attack_sec, samplerate); +} + +void Dexed::setCompressorRelease_sec(float release_sec) +{ + compressor->setRelease_sec(release_sec, samplerate); +} + +void Dexed::setCompressorThresh_dBFS(float thresh_dBFS) +{ + compressor->setThresh_dBFS(thresh_dBFS); +} + +void Dexed::setCompressionRatio(float comp_ratio) +{ + compressor->setCompressionRatio(comp_ratio); +} + +float Dexed::getCompressorPreGain_dB(void) +{ + return (compressor->getPreGain_dB()); +} + +float Dexed::getCompressorAttack_sec(void) +{ + return (compressor->getAttack_sec()); +} + +float Dexed::getCompressorRelease_sec(void) +{ + return (compressor->getRelease_sec()); +} + +float Dexed::getCompressorThresh_dBFS(void) +{ + return (compressor->getThresh_dBFS()); +} + +float Dexed::getCompressionRatio(void) +{ + return (compressor->getCompressionRatio()); +} +#endif + +#ifdef ARM_ALLOW_MULTI_CORE +void Dexed::setUnisonParameters(uint8_t voices, float detune, float spread, float basePan) { + unisonVoices = voices; + unisonDetune = detune; + unisonSpread = spread; + // Store basePan for use in keydown + this->basePan = basePan; +} + +void Dexed::getSamplesStereo(float* bufferL, float* bufferR, uint16_t n_samples) { + if (refreshVoice) { + for (uint8_t i = 0; i < used_notes; i++) { + if (voices[i].live) + voices[i].dx7_note->update(data, voices[i].midi_note, voices[i].velocity, voices[i].porta, &controllers); + } + lfo.reset(data + 137); + refreshVoice = false; + } + arm_fill_f32(0.0, bufferL, n_samples); + arm_fill_f32(0.0, bufferR, n_samples); + for (uint16_t i = 0; i < n_samples; i += _N_) { + AlignedBuf audiobuf; + for (uint8_t j = 0; j < _N_; ++j) { + audiobuf.get()[j] = 0; + } + int32_t lfovalue = lfo.getsample(); + int32_t lfodelay = lfo.getdelay(); + for (uint8_t note = 0; note < used_notes; note++) { + if (voices[note].live) { + voices[note].dx7_note->compute(audiobuf.get(), lfovalue, lfodelay, &controllers); + float pan = voices[note].pan; + float leftGain = cosf(pan * 1.57079633f); // pan: 0.0=left, 1.0=right + float rightGain = sinf(pan * 1.57079633f); + for (uint8_t j = 0; j < _N_; ++j) { + float sample = signed_saturate_rshift(audiobuf.get()[j] >> 4, 24, 9) / 32768.0f; + bufferL[i + j] += sample * leftGain; + bufferR[i + j] += sample * rightGain; + audiobuf.get()[j] = 0; + } + } + } + } + fx.process(bufferL, n_samples); + fx.process(bufferR, n_samples); + +#ifndef TEENSYDUINO + if (use_compressor == true) { + compressor->doCompression(bufferL, n_samples); + compressor->doCompression(bufferR, n_samples); + } +#endif +} +#endif diff --git a/src/patches/dexed.h b/src/patches/dexed.h new file mode 100644 index 0000000..200b768 --- /dev/null +++ b/src/patches/dexed.h @@ -0,0 +1,415 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield. + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + (c)2018-2021 H. Wirtz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +//#define DISABLE_DEXED_COMPRESSOR 1 + +#ifndef DEXED_H_INCLUDED +#define DEXED_H_INCLUDED + +#include +#include +#include +#if defined(TEENSYDUINO) +#include +#endif +#include "fm_op_kernel.h" +#include "synth.h" +#include "env.h" +#include "aligned_buf.h" +#include "pitchenv.h" +#include "controllers.h" +#include "dx7note.h" +#include "lfo.h" +#include "PluginFx.h" +#include "compressor.h" +#include "EngineMsfa.h" +#include "EngineMkI.h" +#include "EngineOpl.h" + +#define NUM_VOICE_PARAMETERS 156 + +struct ProcessorVoice { + uint8_t midi_note; + uint8_t velocity; + int16_t porta; + bool keydown; + bool sustained; + bool sostenuted; + bool held; + bool live; + uint32_t key_pressed_timer; + Dx7Note *dx7_note; + float pan; // pan value for stereo spread (0.0 = left, 1.0 = right) +}; + +enum DexedVoiceOPParameters { + DEXED_OP_EG_R1, // 0 + DEXED_OP_EG_R2, // 1 + DEXED_OP_EG_R3, // 2 + DEXED_OP_EG_R4, // 3 + DEXED_OP_EG_L1, // 4 + DEXED_OP_EG_L2, // 5 + DEXED_OP_EG_L3, // 6 + DEXED_OP_EG_L4, // 7 + DEXED_OP_LEV_SCL_BRK_PT, // 8 + DEXED_OP_SCL_LEFT_DEPTH, // 9 + DEXED_OP_SCL_RGHT_DEPTH, // 10 + DEXED_OP_SCL_LEFT_CURVE, // 11 + DEXED_OP_SCL_RGHT_CURVE, // 12 + DEXED_OP_OSC_RATE_SCALE, // 13 + DEXED_OP_AMP_MOD_SENS, // 14 + DEXED_OP_KEY_VEL_SENS, // 15 + DEXED_OP_OUTPUT_LEV, // 16 + DEXED_OP_OSC_MODE, // 17 + DEXED_OP_FREQ_COARSE, // 18 + DEXED_OP_FREQ_FINE, // 19 + DEXED_OP_OSC_DETUNE // 20 +}; + +#define DEXED_VOICE_OFFSET 126 +enum DexedVoiceParameters { + DEXED_PITCH_EG_R1, // 0 + DEXED_PITCH_EG_R2, // 1 + DEXED_PITCH_EG_R3, // 2 + DEXED_PITCH_EG_R4, // 3 + DEXED_PITCH_EG_L1, // 4 + DEXED_PITCH_EG_L2, // 5 + DEXED_PITCH_EG_L3, // 6 + DEXED_PITCH_EG_L4, // 7 + DEXED_ALGORITHM, // 8 + DEXED_FEEDBACK, // 9 + DEXED_OSC_KEY_SYNC, // 10 + DEXED_LFO_SPEED, // 11 + DEXED_LFO_DELAY, // 12 + DEXED_LFO_PITCH_MOD_DEP, // 13 + DEXED_LFO_AMP_MOD_DEP, // 14 + DEXED_LFO_SYNC, // 15 + DEXED_LFO_WAVE, // 16 + DEXED_LFO_PITCH_MOD_SENS, // 17 + DEXED_TRANSPOSE, // 18 + DEXED_NAME // 19 +}; + +enum ADSR { + ATTACK, + DECAY, + SUSTAIN, + RELEASE +}; + +enum OPERATORS { + OP1, + OP2, + OP3, + OP4, + OP5, + OP6 +}; + +enum CONTROLLER_ASSIGN { + NONE, + PITCH, + AMP, + PITCH_AMP, + EG, + PITCH_EG, + AMP_EG, + PITCH_AMP_EG +}; + +enum PORTAMENTO_MODE { + RETAIN, + FOLLOW +}; + +enum ON_OFF { + OFF, + ON +}; + +enum VELOCITY_SCALES { + MIDI_VELOCITY_SCALING_OFF, + MIDI_VELOCITY_SCALING_DX7, + MIDI_VELOCITY_SCALING_DX7II +}; + +enum ENGINES { + MSFA, + MKI, + OPL +}; + +// GLOBALS + +//============================================================================== + +class Dexed +{ + public: + Dexed(uint8_t maxnotes, uint16_t rate); + ~Dexed(); + + // Global methods + void activate(void); + void deactivate(void); + bool getMonoMode(void); + void setMonoMode(bool mode); + void setNoteRefreshMode(bool mode); + uint8_t getMaxNotes(void); + void doRefreshVoice(void); + void setOPAll(uint8_t ops); + bool decodeVoice(uint8_t* data, uint8_t* encoded_data); + bool encodeVoice(uint8_t* encoded_data); + bool getVoiceData(uint8_t* data_copy); + void setVoiceDataElement(uint8_t address, uint8_t value); + uint8_t getVoiceDataElement(uint8_t address); + void loadInitVoice(void); + void loadVoiceParameters(uint8_t* data); + uint8_t getNumNotesPlaying(void); + uint32_t getXRun(void); + uint16_t getRenderTimeMax(void); + void resetRenderTimeMax(void); + void ControllersRefresh(void); + void setVelocityScale(uint8_t offset, uint8_t max); + void getVelocityScale(uint8_t* offset, uint8_t* max); + void setVelocityScale(uint8_t setup); + void setMaxNotes(uint8_t n); + void setEngineType(uint8_t engine); + uint8_t getEngineType(void); + FmCore* getEngineAddress(void); +#ifndef TEENSYDUINO + void setCompressor(bool comp); + bool getCompressor(void); + void setCompressorPreGain_dB(float pre_gain); + void setCompressorAttack_sec(float attack_sec); + void setCompressorRelease_sec(float release_sec); + void setCompressorThresh_dBFS(float thresh_dBFS); + void setCompressionRatio(float comp_ratio); + float getCompressorPreGain_dB(void); + float getCompressorAttack_sec(void); + float getCompressorRelease_sec(void); + float getCompressorThresh_dBFS(void); + float getCompressionRatio(void); +#endif + int16_t checkSystemExclusive(const uint8_t* sysex, const uint16_t len); + + // Sound methods + void keyup(uint8_t pitch); + void keydown(uint8_t pitch, uint8_t velo); + void setSustain(bool sustain); + bool getSustain(void); + void setSostenuto(bool sostenuto); + bool getSostenuto(void); + void setHold(bool hold); + bool getHold(void); + void panic(void); + void notesOff(void); + void resetControllers(void); + void setMasterTune(int8_t mastertune); + int8_t getMasterTune(void); + void setPortamento(uint8_t portamento_mode, uint8_t portamento_glissando, uint8_t portamento_time); + void setPortamentoMode(uint8_t portamento_mode); + uint8_t getPortamentoMode(void); + void setPortamentoGlissando(uint8_t portamento_glissando); + uint8_t getPortamentoGlissando(void); + void setPortamentoTime(uint8_t portamento_time); + uint8_t getPortamentoTime(void); + void setPBController(uint8_t pb_range, uint8_t pb_step); + void setMWController(uint8_t mw_range, uint8_t mw_assign, uint8_t mw_mode); + void setFCController(uint8_t fc_range, uint8_t fc_assign, uint8_t fc_mode); + void setBCController(uint8_t bc_range, uint8_t bc_assign, uint8_t bc_mode); + void setATController(uint8_t at_range, uint8_t at_assign, uint8_t at_mode); + void setModWheel(uint8_t value); + uint8_t getModWheel(void); + void setBreathController(uint8_t value); + uint8_t getBreathController(void); + void setFootController(uint8_t value); + uint8_t getFootController(void); + void setAftertouch(uint8_t value); + uint8_t getAftertouch(void); + void setPitchbend(uint8_t value1, uint8_t value2); + void setPitchbend(int16_t value); + void setPitchbend(uint16_t value); + int16_t getPitchbend(void); + void setPitchbendRange(uint8_t range); + uint8_t getPitchbendRange(void); + void setPitchbendStep(uint8_t step); + uint8_t getPitchbendStep(void); + void setModWheelRange(uint8_t range); + uint8_t getModWheelRange(void); + void setModWheelTarget(uint8_t target); + uint8_t getModWheelTarget(void); + void setFootControllerRange(uint8_t range); + uint8_t getFootControllerRange(void); + void setFootControllerTarget(uint8_t target); + uint8_t getFootControllerTarget(void); + void setBreathControllerRange(uint8_t range); + uint8_t getBreathControllerRange(void); + void setBreathControllerTarget(uint8_t target); + uint8_t getBreathControllerTarget(void); + void setAftertouchRange(uint8_t range); + uint8_t getAftertouchRange(void); + void setAftertouchTarget(uint8_t target); + uint8_t getAftertouchTarget(void); + void setFilterCutoff(float cutoff); + float getFilterCutoff(void); + void setFilterResonance(float resonance); + float getFilterResonance(void); + void setGain(float gain); + float getGain(void); + + // Voice configuration methods + void setOPRateAll(uint8_t rate); + void setOPLevelAll(uint8_t level); + void setOPRateAllCarrier(uint8_t step, uint8_t rate); + void setOPLevelAllCarrier(uint8_t step, uint8_t level); + void setOPRateAllModulator(uint8_t step, uint8_t rate); + void setOPLevelAllModulator(uint8_t step, uint8_t level); + void setOPRate(uint8_t op, uint8_t step, uint8_t rate); + uint8_t getOPRate(uint8_t op, uint8_t step); + void setOPLevel(uint8_t op, uint8_t step, uint8_t level); + uint8_t getOPLevel(uint8_t op, uint8_t step); + void setOPKeyboardLevelScalingBreakPoint(uint8_t op, uint8_t level); + uint8_t getOPKeyboardLevelScalingBreakPoint(uint8_t op); + void setOPKeyboardLevelScalingDepthLeft(uint8_t op, uint8_t depth); + uint8_t getOPKeyboardLevelScalingDepthLeft(uint8_t op); + void setOPKeyboardLevelScalingDepthRight(uint8_t op, uint8_t depth); + uint8_t getOPKeyboardLevelScalingDepthRight(uint8_t op); + void setOPKeyboardLevelScalingCurveLeft(uint8_t op, uint8_t curve); + uint8_t getOPKeyboardLevelScalingCurveLeft(uint8_t op); + void setOPKeyboardLevelScalingCurveRight(uint8_t op, uint8_t curve); + uint8_t getOPKeyboardLevelScalingCurveRight(uint8_t op); + void setOPKeyboardRateScale(uint8_t op, uint8_t scale); + uint8_t getOPKeyboardRateScale(uint8_t op); + void setOPAmpModulationSensity(uint8_t op, uint8_t sensitivity); + uint8_t getOPAmpModulationSensity(uint8_t op); + void setOPKeyboardVelocitySensity(uint8_t op, uint8_t sensitivity); + uint8_t getOPKeyboardVelocitySensity(uint8_t op); + void setOPOutputLevel(uint8_t op, uint8_t level); + uint8_t getOPOutputLevel(uint8_t op); + void setOPMode(uint8_t op, uint8_t mode); + uint8_t getOPMode(uint8_t op); + void setOPFrequencyCoarse(uint8_t op, uint8_t frq_coarse); + uint8_t getOPFrequencyCoarse(uint8_t op); + void setOPFrequencyFine(uint8_t op, uint8_t frq_fine); + uint8_t getOPFrequencyFine(uint8_t op); + void setOPDetune(uint8_t op, uint8_t detune); + uint8_t getOPDetune(uint8_t op); + void setPitchRate(uint8_t step, uint8_t rate); + uint8_t getPitchRate(uint8_t step); + void setPitchLevel(uint8_t step, uint8_t level); + uint8_t getPitchLevel(uint8_t step); + void setAlgorithm(uint8_t algorithm); + uint8_t getAlgorithm(void); + void setFeedback(uint8_t feedback); + uint8_t getFeedback(void); + void setOscillatorSync(bool sync); + bool getOscillatorSync(void); + void setLFOSpeed(uint8_t speed); + uint8_t getLFOSpeed(void); + void setLFODelay(uint8_t delay); + uint8_t getLFODelay(void); + void setLFOPitchModulationDepth(uint8_t depth); + uint8_t getLFOPitchModulationDepth(void); + void setLFOAmpModulationDepth(uint8_t delay); + uint8_t getLFOAmpModulationDepth(void); + void setLFOSync(bool sync); + bool getLFOSync(void); + void setLFOWaveform(uint8_t waveform); + uint8_t getLFOWaveform(void); + void setLFOPitchModulationSensitivity(uint8_t sensitivity); + uint8_t getLFOPitchModulationSensitivity(void); + void setTranspose(uint8_t transpose); + uint8_t getTranspose(void); + void setName(char name[11]); + void getName(char buffer[11]); + + ProcessorVoice* voices; + + protected: + uint8_t init_voice[NUM_VOICE_PARAMETERS] = { + 99, 99, 99, 99, 99, 99, 99, 00, 33, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, // OP6 eg_rate_1-4, level_1-4, kbd_lev_scl_brk_pt, kbd_lev_scl_lft_depth, kbd_lev_scl_rht_depth, kbd_lev_scl_lft_curve, kbd_lev_scl_rht_curve, kbd_rate_scaling, amp_mod_sensitivity, key_vel_sensitivity, operator_output_level, osc_mode, osc_freq_coarse, osc_freq_fine, osc_detune + 99, 99, 99, 99, 99, 99, 99, 00, 33, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, // OP5 + 99, 99, 99, 99, 99, 99, 99, 00, 33, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, // OP4 + 99, 99, 99, 99, 99, 99, 99, 00, 33, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, // OP4 + 99, 99, 99, 99, 99, 99, 99, 00, 33, 00, 00, 00, 00, 00, 00, 00, 00, 00, 01, 00, 00, // OP4 + 99, 99, 99, 99, 99, 99, 99, 00, 33, 00, 00, 00, 00, 00, 00, 00, 99, 00, 01, 00, 00, // OP4 + 99, 99, 99, 99, 50, 50, 50, 50, // 4 * pitch EG rates, 4 * pitch EG level + 01, 00, 01, // algorithm, feedback, osc sync + 35, 00, 00, 00, 01, 00, // lfo speed, lfo delay, lfo pitch_mod_depth, lfo_amp_mod_depth, lfo_sync, lfo_waveform + 03, 48, // pitch_mod_sensitivity, transpose + 73, 78, 73, 84, 32, 86, 79, 73, 67, 69 // 10 * char for name ("INIT VOICE") + }; + float samplerate; + uint8_t data[NUM_VOICE_PARAMETERS]; + uint8_t max_notes; + uint8_t used_notes; + PluginFx fx; + Controllers controllers; + int32_t lastKeyDown; + uint32_t xrun; + uint16_t render_time_max; + int16_t currentNote; + bool sustain; + bool sostenuto; + bool hold; + bool monoMode; + bool noteRefreshMode; + bool refreshVoice; + uint8_t engineType; + VoiceStatus voiceStatus; + Lfo lfo; + EngineMsfa* engineMsfa; + EngineMkI* engineMkI; + EngineOpl* engineOpl; + void getSamples(float* buffer, uint16_t n_samples); + void getSamples(int16_t* buffer, uint16_t n_samples); + void compress(float* wav_in, float* wav_out, uint16_t n, float threshold, float slope, uint16_t sr, float tla, float twnd, float tatt, float trel); + bool use_compressor; + uint8_t velocity_offset; + uint8_t velocity_max; + float velocity_diff; +#ifndef TEENSYDUINO + Compressor* compressor; +#endif + // Unison state + uint8_t unisonVoices = 1; + float unisonDetune = 0.0f; + float unisonSpread = 0.0f; +#ifdef ARM_ALLOW_MULTI_CORE + // Unison parameters and stereo output only for multicore + void setUnisonParameters(uint8_t voices, float detune, float spread, float basePan); + void getSamplesStereo(float* bufferL, float* bufferR, uint16_t n_samples); + float basePan = 0.5f; +#endif + // --- Unison note tracking for robust note-off --- + static constexpr int kMaxMidiNotes = 128; + static constexpr int kMaxUnison = 8; // adjust as needed + float unisonDetunedPitches[kMaxMidiNotes][kMaxUnison] = {}; + uint8_t unisonDetunedCount[kMaxMidiNotes] = {}; + int unisonVoiceIndices[kMaxMidiNotes][kMaxUnison] = {}; +}; + +#endif diff --git a/src/patches/dx7note.cpp b/src/patches/dx7note.cpp new file mode 100644 index 0000000..f4330b0 --- /dev/null +++ b/src/patches/dx7note.cpp @@ -0,0 +1,401 @@ +/* + Copyright 2016-2017 Pascal Gauthier. + Copyright 2012 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include +#include "synth.h" +#include "freqlut.h" +#include "exp2.h" +#include "controllers.h" +#include "dx7note.h" +#include "porta.h" +//#include + +const int FEEDBACK_BITDEPTH = 8; + +int32_t midinote_to_logfreq(int midinote) { + //const int32_t base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) + const int32_t base = 50857777; // (1 << 24) * (LOG_FUNC(440) / LOG_FUNC(2) - 69/12) + const int32_t step = (1 << 24) / 12; + return base + step * midinote; +} + +int32_t logfreq_round2semi(int freq) { + const int base = 50857777; // (1 << 24) * (log(440) / log(2) - 69/12) + const int step = (1 << 24) / 12; + const int rem = (freq - base) % step; + return freq - rem; +} + +const int32_t coarsemul[] = { + -16777216, 0, 16777216, 26591258, 33554432, 38955489, 43368474, 47099600, + 50331648, 53182516, 55732705, 58039632, 60145690, 62083076, 63876816, + 65546747, 67108864, 68576247, 69959732, 71268397, 72509921, 73690858, + 74816848, 75892776, 76922906, 77910978, 78860292, 79773775, 80654032, + 81503396, 82323963, 83117622 +}; + +int32_t osc_freq(int midinote, int mode, int coarse, int fine, int detune) { + // TODO: pitch randomization + int32_t logfreq; + if (mode == 0) { + logfreq = midinote_to_logfreq(midinote); + // could use more precision, closer enough for now. those numbers comes from my DX7 + //FRAC_NUM detuneRatio = 0.0209 * exp(-0.396 * (((float)logfreq) / (1 << 24))) / 7; + FRAC_NUM detuneRatio = 0.0209 * EXP_FUNC(-0.396 * (((float)logfreq) / (1 << 24))) / 7; + logfreq += detuneRatio * logfreq * (detune - 7); + + logfreq += coarsemul[coarse & 31]; + if (fine) { + // (1 << 24) / log(2) + //logfreq += (int32_t)floor(24204406.323123 * log(1 + 0.01 * fine) + 0.5); + logfreq += (int32_t)floor(24204406.323123 * LOG_FUNC(1 + 0.01 * fine) + 0.5); + } + + // // This was measured at 7.213Hz per count at 9600Hz, but the exact + // // value is somewhat dependent on midinote. Close enough for now. + // //logfreq += 12606 * (detune -7); + } else { + // ((1 << 24) * log(10) / log(2) * .01) << 3 + logfreq = (4458616 * ((coarse & 3) * 100 + fine)) >> 3; + logfreq += detune > 7 ? 13457 * (detune - 7) : 0; + } + return logfreq; +} + +const uint8_t velocity_data[64] = { + 0, 70, 86, 97, 106, 114, 121, 126, 132, 138, 142, 148, 152, 156, 160, 163, + 166, 170, 173, 174, 178, 181, 184, 186, 189, 190, 194, 196, 198, 200, 202, + 205, 206, 209, 211, 214, 216, 218, 220, 222, 224, 225, 227, 229, 230, 232, + 233, 235, 237, 238, 240, 241, 242, 243, 244, 246, 246, 248, 249, 250, 251, + 252, 253, 254 +}; + +// See "velocity" section of notes. Returns velocity delta in microsteps. +int ScaleVelocity(int velocity, int sensitivity) { + int clamped_vel = std::max(0, std::min(127, velocity)); + int vel_value = velocity_data[clamped_vel >> 1] - 239; + int scaled_vel = ((sensitivity * vel_value + 7) >> 3) << 4; + return scaled_vel; +} + +int ScaleRate(int midinote, int sensitivity) { + int x = std::min(31, std::max(0, midinote / 3 - 7)); + int qratedelta = (sensitivity * x) >> 3; +#ifdef SUPER_PRECISE + int rem = x & 7; + if (sensitivity == 3 && rem == 3) { + qratedelta -= 1; + } else if (sensitivity == 7 && rem > 0 && rem < 4) { + qratedelta += 1; + } +#endif + return qratedelta; +} + +const uint8_t exp_scale_data[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 16, 19, 23, 27, 33, 39, 47, 56, 66, + 80, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 250 +}; + +int ScaleCurve(int group, int depth, int curve) { + int scale; + if (curve == 0 || curve == 3) { + // linear + scale = (group * depth * 329) >> 12; + } else { + // exponential + int n_scale_data = sizeof(exp_scale_data); + int raw_exp = exp_scale_data[std::min(group, n_scale_data - 1)]; + scale = (raw_exp * depth * 329) >> 15; + } + if (curve < 2) { + scale = -scale; + } + return scale; +} + +int ScaleLevel(int midinote, int break_pt, int left_depth, int right_depth, + int left_curve, int right_curve) { + int offset = midinote - break_pt - 17; + if (offset >= 0) { + return ScaleCurve((offset + 1) / 3, right_depth, right_curve); + } else { + return ScaleCurve(-(offset - 1) / 3, left_depth, left_curve); + } +} + +static const uint8_t pitchmodsenstab[] = { + 0, 10, 20, 33, 55, 92, 153, 255 +}; + +// 0, 66, 109, 255 +static const uint32_t ampmodsenstab[] = { + 0, 4342338, 7171437, 16777216 +}; + +Dx7Note::Dx7Note() { + for (int op = 0; op < 6; op++) { + params_[op].phase = 0; + params_[op].gain_out = 0; + } +} + +//void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity) { +void Dx7Note::init(const uint8_t patch[156], float midinote, int velocity, float srcnote, int porta, const Controllers *ctrls) { + int rates[4]; + int levels[4]; + for (int op = 0; op < 6; op++) { + int off = op * 21; + for (int i = 0; i < 4; i++) { + rates[i] = patch[off + i]; + levels[i] = patch[off + 4 + i]; + } + int outlevel = patch[off + 16]; + outlevel = Env::scaleoutlevel(outlevel); + int level_scaling = ScaleLevel((int)midinote, patch[off + 8], patch[off + 9], + patch[off + 10], patch[off + 11], patch[off + 12]); + outlevel += level_scaling; + outlevel = std::min(127, outlevel); + outlevel = outlevel << 5; + outlevel += ScaleVelocity(velocity, patch[off + 15]); + outlevel = std::max(0, outlevel); + int rate_scaling = ScaleRate((int)midinote, patch[off + 13]); + env_[op].init((const int32_t*)rates, (const int32_t*)levels, outlevel, rate_scaling); + + int mode = patch[off + 17]; + int coarse = patch[off + 18]; + int fine = patch[off + 19]; + int detune = patch[off + 20]; + int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); + opMode[op] = mode; + basepitch_[op] = freq; + ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; + + // Always set porta_curpitch_ to basepitch_ if no portamento transition + if (porta < 0 || porta >= 128 || srcnote == midinote) { + porta_curpitch_[op] = freq; + } else { + porta_curpitch_[op] = osc_freq(srcnote, mode, coarse, fine, detune); + } + } + for (int i = 0; i < 4; i++) { + rates[i] = patch[126 + i]; + levels[i] = patch[130 + i]; + } + pitchenv_.set((const int32_t *)rates, (const int32_t*)levels); + algorithm_ = patch[134]; + int feedback = patch[135]; + fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16; + pitchmoddepth_ = (patch[139] * 165) >> 6; + pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; + ampmoddepth_ = (patch[140] * 165) >> 6; + porta_rateindex_ = (porta < 128) ? porta : 127; + porta_gliss_ = ctrls->values_[kControllerPortamentoGlissando]; +} + +void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls) { + // ==== PITCH ==== + uint32_t pmd = pitchmoddepth_ * lfo_delay; // Q32 + int32_t senslfo = pitchmodsens_ * (lfo_val - (1 << 23)); + int32_t pmod_1 = (((int64_t) pmd) * (int64_t) senslfo) >> 39; + pmod_1 = abs(pmod_1); + int32_t pmod_2 = (int32_t)(((int64_t)ctrls->pitch_mod * (int64_t)senslfo) >> 14); + pmod_2 = abs(pmod_2); + int32_t pitch_mod = std::max(pmod_1, pmod_2); + pitch_mod = pitchenv_.getsample() + (pitch_mod * (senslfo < 0 ? -1 : 1)); + + // ---- PITCH BEND ---- + int pitchbend = ctrls->values_[kControllerPitch]; + int32_t pb = (pitchbend - 0x2000); + if (pb != 0) { + if (ctrls->values_[kControllerPitchStep] == 0) { + pb = ((float) (pb << 11)) * ((float) ctrls->values_[kControllerPitchRange]) / 12.0; + } else { + int stp = 12 / ctrls->values_[kControllerPitchStep]; + pb = pb * stp / 8191; + pb = (pb * (8191 / stp)) << 11; + } + } + int32_t pitch_base = pb + ctrls->masterTune; + pitch_mod += pitch_base; + + // ==== AMP MOD ==== + lfo_val = (1 << 24) - lfo_val; + uint32_t amod_1 = (uint32_t)(((int64_t) ampmoddepth_ * (int64_t) lfo_delay) >> 8); // Q24 :D + amod_1 = (uint32_t)(((int64_t) amod_1 * (int64_t) lfo_val) >> 24); + uint32_t amod_2 = (uint32_t)(((int64_t) ctrls->amp_mod * (int64_t) lfo_val) >> 7); // Q?? :| + uint32_t amd_mod = std::max(amod_1, amod_2); + + // ==== EG AMP MOD ==== + uint32_t amod_3 = (ctrls->eg_mod + 1) << 17; + amd_mod = std::max((1 << 24) - amod_3, amd_mod); + + // ==== OP RENDER ==== + for (int op = 0; op < 6; op++) { + // if ( ctrls->opSwitch[op] == '0' ) { + if (!(ctrls->opSwitch & (1 << op))) { + env_[op].getsample(); // advance the envelop even if it is not playing + params_[op].level_in = 0; + } else { + //int32_t gain = pow(2, 10 + level * (1.0 / (1 << 24))); + + int32_t basepitch = basepitch_[op]; + + if (opMode[op]) { + params_[op].freq = Freqlut::lookup(basepitch + pitch_base); + } else { + // If portamento is enabled but there is no transition, use basepitch_ + if (porta_rateindex_ >= 0) { + if (porta_curpitch_[op] != basepitch_[op]) { + basepitch = porta_curpitch_[op]; + if (porta_gliss_) + basepitch = logfreq_round2semi(basepitch); + } + // else: no transition, use basepitch_ as is + } + params_[op].freq = Freqlut::lookup(basepitch + pitch_mod); + } + + int32_t level = env_[op].getsample(); + if (ampmodsens_[op] != 0) { + uint32_t sensamp = (uint32_t)(((uint64_t) amd_mod) * ((uint64_t) ampmodsens_[op]) >> 24); + + // TODO: mehhh.. this needs some real tuning. + //uint32_t pt = exp(((float)sensamp) / 262144 * 0.07 + 12.2); + uint32_t pt = EXP_FUNC(((float)sensamp) / 262144 * 0.07 + 12.2); + uint32_t ldiff = (uint32_t)(((uint64_t)level) * (((uint64_t)pt << 4)) >> 28); + level -= ldiff; + } + params_[op].level_in = level; + } + } + + // ==== PORTAMENTO ==== + int porta = porta_rateindex_; + if (porta >= 0) { + int32_t rate = Porta::rates[porta]; + for (int op = 0; op < 6; op++) { + int32_t cur = porta_curpitch_[op]; + int32_t dst = basepitch_[op]; + + bool going_up = cur < dst; + int32_t newpitch = cur + (going_up ? +rate : -rate); + + // Clamp to destination if we would overshoot/undershoot + if ((going_up && newpitch > dst) || (!going_up && newpitch < dst)) + newpitch = dst; + + porta_curpitch_[op] = newpitch; + } + } + + ctrls->core->render(buf, params_, algorithm_, fb_buf_, fb_shift_); +} + +void Dx7Note::keyup() { + for (int op = 0; op < 6; op++) { + env_[op].keydown(false); + } + pitchenv_.keydown(false); +} + +void Dx7Note::update(const uint8_t patch[156], float midinote, int velocity, int porta, const Controllers *ctrls) { + int rates[4]; + int levels[4]; + for (int op = 0; op < 6; op++) { + int off = op * 21; + int mode = patch[off + 17]; + int coarse = patch[off + 18]; + int fine = patch[off + 19]; + int detune = patch[off + 20]; + int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); + basepitch_[op] = freq; + ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; + opMode[op] = mode; + + // Always set porta_curpitch_ to basepitch_ if no portamento transition + if (porta < 0 || porta >= 128) { + porta_curpitch_[op] = freq; + } + // else: porta_curpitch_ will be handled by portamento logic + + for (int i = 0; i < 4; i++) { + rates[i] = patch[off + i]; + levels[i] = patch[off + 4 + i]; + } + int outlevel = patch[off + 16]; + outlevel = Env::scaleoutlevel(outlevel); + int level_scaling = ScaleLevel((int)midinote, patch[off + 8], patch[off + 9], + patch[off + 10], patch[off + 11], patch[off + 12]); + outlevel += level_scaling; + outlevel = std::min(127, outlevel); + outlevel = outlevel << 5; + outlevel += ScaleVelocity(velocity, patch[off + 15]); + outlevel = std::max(0, outlevel); + int rate_scaling = ScaleRate((int)midinote, patch[off + 13]); + env_[op].update((const int32_t*)rates, (const int32_t*)levels, (int32_t)outlevel, rate_scaling); + } + algorithm_ = patch[134]; + int feedback = patch[135]; + fb_shift_ = feedback != 0 ? FEEDBACK_BITDEPTH - feedback : 16; + pitchmoddepth_ = (patch[139] * 165) >> 6; + pitchmodsens_ = pitchmodsenstab[patch[143] & 7]; + ampmoddepth_ = (patch[140] * 165) >> 6; + porta_rateindex_ = (porta < 128) ? porta : 127; + porta_gliss_ = ctrls->values_[kControllerPortamentoGlissando]; + +} + +void Dx7Note::peekVoiceStatus(VoiceStatus &status) { + for (int i = 0; i < 6; i++) { + status.amp[i] = Exp2::lookup(params_[i].level_in - (14 * (1 << 24))); + env_[i].getPosition(&status.ampStep[i]); + } + pitchenv_.getPosition(&status.pitchStep); +} + +/** + Used in monophonic mode to transfer voice state from different notes +*/ +void Dx7Note::transferState(Dx7Note &src) { + for (int i = 0; i < 6; i++) { + env_[i].transfer(src.env_[i]); + params_[i].gain_out = src.params_[i].gain_out; + params_[i].phase = src.params_[i].phase; + } +} + +void Dx7Note::transferSignal(Dx7Note &src) { + for (int i = 0; i < 6; i++) { + params_[i].gain_out = src.params_[i].gain_out; + params_[i].phase = src.params_[i].phase; + } +} + +void Dx7Note::transferPortamento(Dx7Note &src) { + for (int i = 0; i < 6; i++) { + porta_curpitch_[i] = src.porta_curpitch_[i]; + } +} + +void Dx7Note::oscSync() { + for (int i = 0; i < 6; i++) { + params_[i].gain_out = 0; + params_[i].phase = 0; + } +} diff --git a/src/patches/dx7note.h b/src/patches/dx7note.h new file mode 100644 index 0000000..77443a0 --- /dev/null +++ b/src/patches/dx7note.h @@ -0,0 +1,83 @@ +/* + Copyright 2016-2017 Pascal Gauthier. + Copyright 2012 Google Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef SYNTH_DX7NOTE_H_ +#define SYNTH_DX7NOTE_H_ + +// This is the logic to put together a note from the MIDI description +// and run the low-level modules. + +// It will continue to evolve a bit, as note-stealing logic, scaling, +// and real-time control of parameters live here. + +#include +#include "env.h" +#include "pitchenv.h" +#include "fm_core.h" + +struct VoiceStatus { + uint32_t amp[6]; + char ampStep[6]; + char pitchStep; +}; + +class Dx7Note { + public: + Dx7Note(); + // Change midinote to float for fractional detune + void init(const uint8_t patch[156], float midinote, int velocity, float srcnote, int porta, const Controllers *ctrls); + + // Note: this _adds_ to the buffer. Interesting question whether it's + // worth it... + void compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Controllers *ctrls); + + void keyup(); + + // TODO: some way of indicating end-of-note. Maybe should be a return + // value from the compute method? (Having a count return from keyup + // is also tempting, but if there's a dynamic parameter change after + // keyup, that won't work. + + // PG:add the update + void update(const uint8_t patch[156], float midinote, int velocity, int porta, const Controllers *ctrls); + void peekVoiceStatus(VoiceStatus &status); + void transferState(Dx7Note& src); + void transferSignal(Dx7Note &src); + void transferPortamento(Dx7Note &src); + void oscSync(); + + private: + Env env_[6]; + FmOpParams params_[6]; + PitchEnv pitchenv_; + int32_t basepitch_[6]; + int32_t fb_buf_[2]={0 ,0}; + int32_t fb_shift_; + int32_t ampmodsens_[6]; + int32_t opMode[6]; + + int ampmoddepth_; + int algorithm_; + int pitchmoddepth_; + int pitchmodsens_; + + int porta_rateindex_; + int porta_gliss_; + int32_t porta_curpitch_[6]; +}; + +#endif