diff --git a/src/Makefile b/src/Makefile index 3522c14..1d744ee 100644 --- a/src/Makefile +++ b/src/Makefile @@ -19,3 +19,5 @@ OPTIMIZE = -O3 include ./Synth_Dexed.mk include ./Rules.mk + +EXTRACLEAN += moddistortion/*.[od] \ No newline at end of file diff --git a/src/effect_base.h b/src/effect_base.h index f8efb0a..4bf7a0b 100644 --- a/src/effect_base.h +++ b/src/effect_base.h @@ -35,6 +35,13 @@ public: { } + /** + * Set the tempo in BPM. + */ + virtual void setTempo(unsigned tempo) + { + } + virtual void setParameter(unsigned param, unsigned value) { } diff --git a/src/effect_delay.cpp b/src/effect_delay.cpp index 9d51eec..9088aac 100644 --- a/src/effect_delay.cpp +++ b/src/effect_delay.cpp @@ -21,9 +21,9 @@ AudioEffectDelay::AudioEffectDelay(float32_t samplerate) : AudioEffect(samplerat memset(this->bufferL, 0, this->bufferSize * sizeof(float32_t)); memset(this->bufferR, 0, this->bufferSize * sizeof(float32_t)); - this->timeL = 0.36f; - this->timeR = 0.36f; - this->feedback = 0.6f; + this->setParameter(AudioEffectDelay::Param::TIME_L, 360); + this->setParameter(AudioEffectDelay::Param::TIME_R, 360); + this->setParameter(AudioEffectDelay::Param::FEEDBACK, 60); this->pingPongMode = false; this->setMix(0.5f); } @@ -45,6 +45,13 @@ void AudioEffectDelay::initializeSendFX() this->setParameter(AudioEffectDelay::Param::MIX, 100); } +void AudioEffectDelay::setTempo(unsigned tempo) +{ + this->tempo = (float32_t) tempo; + this->setParameter(AudioEffectDelay::Param::TIME_L, timeLValue); + this->setParameter(AudioEffectDelay::Param::TIME_R, timeRValue); +} + void AudioEffectDelay::setParameter(unsigned param, unsigned value) { switch (param) @@ -53,10 +60,12 @@ void AudioEffectDelay::setParameter(unsigned param, unsigned value) this->setBypass(value == 1); break; case AudioEffectDelay::Param::TIME_L: - this->timeL = (float32_t) value / 1000.0f; + this->timeLValue = value; + this->timeL = this->calculateTime(value); break; case AudioEffectDelay::Param::TIME_R: - this->timeR = (float32_t) value / 1000.0f; + this->timeRValue = value; + this->timeR = this->calculateTime(value); break; case AudioEffectDelay::Param::FEEDBACK: this->feedback = (float32_t) value / 100.0f; @@ -82,9 +91,9 @@ unsigned AudioEffectDelay::getParameter(unsigned param) case AudioEffectDelay::Param::BYPASS: return this->getBypass() ? 1 : 0; case AudioEffectDelay::Param::TIME_L: - return roundf(this->timeL * 1000); + return this->timeLValue; case AudioEffectDelay::Param::TIME_R: - return roundf(this->timeR * 1000); + return this->timeRValue; case AudioEffectDelay::Param::FEEDBACK: return roundf(this->feedback * 100); case AudioEffectDelay::Param::TONE: @@ -98,6 +107,81 @@ unsigned AudioEffectDelay::getParameter(unsigned param) } } +float32_t AudioEffectDelay::calculateTime(unsigned value) +{ + if (value < AudioEffectDelay::MAX_DELAY_TIME * 1000) + { + return (float32_t) value / 1000.0f; + } + float32_t numerator; + float32_t denominator; + switch (value - AudioEffectDelay::MAX_DELAY_TIME * 1000) + { + case AudioEffectDelay::SyncTime::T_1_32: + numerator = 1.0f; + denominator = 32.0f; + break; + case AudioEffectDelay::SyncTime::T_1_24: + numerator = 1.0f; + denominator = 24.0f; + break; + case AudioEffectDelay::SyncTime::T_1_16: + numerator = 1.0f; + denominator = 16.0f; + break; + case AudioEffectDelay::SyncTime::T_1_12: + numerator = 1.0f; + denominator = 12.0f; + break; + case AudioEffectDelay::SyncTime::T_3_32: + numerator = 3.0f; + denominator = 32.0f; + break; + case AudioEffectDelay::SyncTime::T_1_8: + numerator = 1.0f; + denominator = 8.0f; + break; + case AudioEffectDelay::SyncTime::T_1_6: + numerator = 1.0f; + denominator = 6.0f; + break; + case AudioEffectDelay::SyncTime::T_3_16: + numerator = 3.0f; + denominator = 16.0f; + break; + case AudioEffectDelay::SyncTime::T_1_4: + numerator = 1.0f; + denominator = 4.0f; + break; + case AudioEffectDelay::SyncTime::T_1_3: + numerator = 1.0f; + denominator = 3.0f; + break; + case AudioEffectDelay::SyncTime::T_3_8: + numerator = 3.0f; + denominator = 8.0f; + break; + case AudioEffectDelay::SyncTime::T_1_2: + numerator = 1.0f; + denominator = 2.0f; + break; + case AudioEffectDelay::SyncTime::T_2_3: + numerator = 2.0f; + denominator = 3.0f; + break; + case AudioEffectDelay::SyncTime::T_3_4: + numerator = 3.0f; + denominator = 4.0f; + break; + case AudioEffectDelay::SyncTime::T_1_1: + default: + numerator = 1.0f; + denominator = 1.0f; + break; + } + return 60000.0f / this->tempo * numerator / denominator / 1000.0f; +} + void AudioEffectDelay::setMix(float32_t mix) { this->mix = mix; diff --git a/src/effect_delay.h b/src/effect_delay.h index 15b81b5..ec72475 100644 --- a/src/effect_delay.h +++ b/src/effect_delay.h @@ -2,7 +2,8 @@ * Stereo Delay * Features: * - Tone control using Low Pass Filter - * - Ping Pong mode. + * - Ping Pong mode + * - Tempo Sync * Javier Nonis (https://github.com/jnonis) - 2024 */ #ifndef _EFFECT_DELAY_H @@ -28,12 +29,33 @@ public: UNKNOWN }; + enum SyncTime + { + T_1_32, + T_1_24, + T_1_16, + T_1_12, + T_3_32, + T_1_8, + T_1_6, + T_3_16, + T_1_4, + T_1_3, + T_3_8, + T_1_2, + T_2_3, + T_3_4, + T_1_1, + T_UNKNOWN + }; + AudioEffectDelay(float32_t samplerate); virtual ~AudioEffectDelay(); virtual unsigned getId(); virtual void initializeSendFX(); + virtual void setTempo(unsigned tempo); virtual void setParameter(unsigned param, unsigned value); virtual unsigned getParameter(unsigned param); protected: @@ -48,15 +70,19 @@ private: float32_t* bufferR; unsigned index; - float32_t timeL; // Left delay time in seconds (0.0 - 2.0) - float32_t timeR; // Right delay time in seconds (0.0 - 2.0) + unsigned timeLValue; // To keep the time value for both millis or sync time + unsigned timeRValue; // To keep the time value for both millis or sync time + float32_t timeL; // Left delay time in seconds + float32_t timeR; // Right delay time in seconds float32_t feedback; // Feedback (0.0 - 1.0) AudioEffectLPF* lpf; bool pingPongMode; float32_t mix; float32_t dryMix; float32_t wetMix; + float32_t tempo = 120; + float32_t calculateTime(unsigned value); void setMix(float32_t mix); }; diff --git a/src/effect_talreverb3.h b/src/effect_talreverb3.h index cd8eb11..c9440bc 100644 --- a/src/effect_talreverb3.h +++ b/src/effect_talreverb3.h @@ -13,8 +13,6 @@ class AudioEffectTalReverb3 : public AudioEffect { public: - static const unsigned MAX_DELAY_TIME = 1; - enum Param { BYPASS, diff --git a/src/minidexed.cpp b/src/minidexed.cpp index f04702d..8f2b2a1 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -64,7 +64,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nClockCounter = 0; m_mClockTime = 0; - m_nBPM = 120; + m_nTempo = 120; for (unsigned i = 0; i < CConfig::ToneGenerators; i++) { @@ -96,8 +96,10 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nAftertouchRange[i]=99; m_nAftertouchTarget[i]=0; +#ifdef ARM_ALLOW_MULTI_CORE memset(m_OutputLevel[i][0], 0, CConfig::MaxChunkSize * sizeof(float32_t)); memset(m_OutputLevel[i][1], 0, CConfig::MaxChunkSize * sizeof(float32_t)); +#endif m_InsertFXSpinLock[i] = new CSpinLock(); m_InsertFX[i] = new AudioEffectNone(pConfig->GetSampleRate ()); @@ -610,6 +612,7 @@ void CMiniDexed::setInsertFXType (unsigned nType, unsigned nTG) m_InsertFXSpinLock[nTG]->Acquire(); delete m_InsertFX[nTG]; m_InsertFX[nTG] = newAudioEffect(nType, m_pConfig->GetSampleRate()); + m_InsertFX[nTG]->setTempo(m_nTempo); m_InsertFXSpinLock[nTG]->Release(); m_UI.ParameterChanged (); @@ -623,6 +626,7 @@ void CMiniDexed::setSendFXType (unsigned nType) delete m_SendFX; } m_SendFX = newAudioEffect(nType, m_pConfig->GetSampleRate()); + m_SendFX->setTempo(m_nTempo); m_SendFX->initializeSendFX(); m_SendFXSpinLock.Release(); @@ -728,9 +732,25 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) unsigned CMiniDexed::getTempo (void) { - return this->m_nBPM; + return this->m_nTempo; } +void CMiniDexed::setTempo(unsigned nValue) +{ + m_nTempo = nValue; + + // Set Tempo to FXs + m_SendFX->setTempo(m_nTempo); + for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + m_InsertFX[nTG]->setTempo(m_nTempo); + } + + // Update UI + m_UI.ParameterChanged(); +} + + void CMiniDexed::handleClock (void) { if (m_nClockCounter == 0) @@ -747,11 +767,11 @@ void CMiniDexed::handleClock (void) // Calculate BPM auto now = std::chrono::high_resolution_clock::now(); unsigned timeDelta = std::chrono::duration_cast(now.time_since_epoch()).count() - m_mClockTime; - m_nBPM = roundf(60000 / timeDelta); - - // Set BPM to FXs - - m_UI.ParameterChanged(); + unsigned newTempo = roundf(60000 / timeDelta); + if (m_nTempo != newTempo) + { + this->setTempo(newTempo); + } } } @@ -950,6 +970,10 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) BankSelectPerformance(nValue); break; + case ParameterTempo: + this->setTempo(nValue); + break; + default: assert (0); break; @@ -966,6 +990,8 @@ int CMiniDexed::GetParameter (TParameter Parameter) return m_SendFX->getId(); case ParameterSendFXLevel: return roundf(m_SendFXLevel * 100); + case ParameterTempo: + return this->getTempo(); default: return m_nParameter[Parameter]; } @@ -1193,9 +1219,17 @@ void CMiniDexed::ProcessSound (void) m_InsertFX[0]->process(SampleBuffer[0], SampleBuffer[0], SampleBuffer[0], SampleBuffer[1], nFrames); m_InsertFXSpinLock[0]->Release(); + + // Convert dual float array (left, right) to single int16 array (left/right) + float32_t tmp_float[nFrames*2]; + for(uint16_t i=0; iWrite (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) { diff --git a/src/minidexed.h b/src/minidexed.h index 729cc3f..b93a140 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -82,6 +82,7 @@ public: void SetMIDIChannel (uint8_t uchChannel, unsigned nTG); unsigned getTempo(void); + void setTempo(unsigned nValue); void handleClock(void); void keyup (int16_t pitch, unsigned nTG); @@ -167,6 +168,7 @@ public: ParameterReverbLevel, ParameterPerformanceSelectChannel, ParameterPerformanceBank, + ParameterTempo, ParameterUnknown }; @@ -251,15 +253,6 @@ private: void LoadPerformanceParameters(void); void ProcessSound (void); - unsigned getChorusIEnable(uint8_t nTG); - void setChorusIEnable(uint8_t nTG, unsigned enable); - unsigned getChorusIIEnable(uint8_t nTG); - void setChorusIIEnable(uint8_t nTG, unsigned enable); - unsigned getChorusIRate(uint8_t nTG); - void setChorusIRate(uint8_t nTG, unsigned int rate); - unsigned getChorusIIRate(uint8_t nTG); - void setChorusIIRate(uint8_t nTG, unsigned int rate); - #ifdef ARM_ALLOW_MULTI_CORE enum TCoreStatus { @@ -365,7 +358,7 @@ private: unsigned m_nClockCounter; unsigned long m_mClockTime; - unsigned m_nBPM; + unsigned m_nTempo; // Tempo in BPM }; #endif diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 24677e1..b37d3c3 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -53,6 +53,7 @@ const CUIMenu::TMenuItem CUIMenu::s_MainMenu[] = {"TG8", MenuHandler, s_TGMenu, 7}, #endif {"Effects", MenuHandler, s_EffectsMenu}, + {"Tempo", EditGlobalParameter, 0, CMiniDexed::ParameterTempo}, {"Performance", MenuHandler, s_PerformanceMenu}, {0} }; @@ -305,7 +306,7 @@ const CUIMenu::TMenuItem CUIMenu::s_SaveMenu[] = // must match CMiniDexed::TParameter const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknown] = { - {0, 1, 1, ToOnOff}, // ParameterCompessorEnable + {0, 1, 1, ToOnOff}, // ParameterCompressorEnable {0, 7, 1, ToFXType}, // ParameterSendFXType {0, 100, 1}, // ParameterSendFXLevel {0, 1, 1, ToOnOff}, // ParameterReverbEnable @@ -316,7 +317,8 @@ const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknow {0, 99, 1}, // ParameterReverbDiffusion {0, 99, 1}, // ParameterReverbLevel {0, CMIDIDevice::ChannelUnknown-1, 1, ToMIDIChannel}, // ParameterPerformanceSelectChannel - {0, NUM_PERFORMANCE_BANKS, 1} // ParameterPerformanceBank + {0, NUM_PERFORMANCE_BANKS, 1}, // ParameterPerformanceBank + {30, 250, 1} // ParameterTempo }; // must match CMiniDexed::TTGParameter @@ -372,8 +374,8 @@ const CUIMenu::TParameter CUIMenu::s_TGFXChorusParam[AudioEffectChorus::Param::U const CUIMenu::TParameter CUIMenu::s_TGFXDelayParam[AudioEffectDelay::Param::UNKNOWN] = { {0, 1, 1, ToOnOff}, // BYPASS - {0, AudioEffectDelay::MAX_DELAY_TIME * 1000, 1}, // TIME_L - {0, AudioEffectDelay::MAX_DELAY_TIME * 1000, 1}, // TIME_R + {0, AudioEffectDelay::MAX_DELAY_TIME * 1000 + AudioEffectDelay::SyncTime::T_UNKNOWN, 1, ToDelayTime}, // TIME_L + {0, AudioEffectDelay::MAX_DELAY_TIME * 1000 + AudioEffectDelay::SyncTime::T_UNKNOWN, 1, ToDelayTime}, // TIME_R {0, 100, 1}, // FEEDBACK, {0, 100, 1}, // TONE {0, 1, 1, ToOnOff}, // PING_PONG @@ -1786,6 +1788,47 @@ string CUIMenu::ToMix (int nValue) } } +string CUIMenu::ToDelayTime (int nValue) +{ + if (nValue < (int) (AudioEffectDelay::MAX_DELAY_TIME * 1000)) { + return to_string (nValue); + } + switch (nValue - AudioEffectDelay::MAX_DELAY_TIME * 1000) + { + case AudioEffectDelay::SyncTime::T_1_32: + return "1/32"; + case AudioEffectDelay::SyncTime::T_1_24: + return "1/24"; + case AudioEffectDelay::SyncTime::T_1_16: + return "1/16"; + case AudioEffectDelay::SyncTime::T_1_12: + return "1/12"; + case AudioEffectDelay::SyncTime::T_3_32: + return "3/32"; + case AudioEffectDelay::SyncTime::T_1_8: + return "1/8"; + case AudioEffectDelay::SyncTime::T_1_6: + return "1/6"; + case AudioEffectDelay::SyncTime::T_3_16: + return "3/16"; + case AudioEffectDelay::SyncTime::T_1_4: + return "1/4"; + case AudioEffectDelay::SyncTime::T_1_3: + return "1/3"; + case AudioEffectDelay::SyncTime::T_3_8: + return "3/8"; + case AudioEffectDelay::SyncTime::T_1_2: + return "1/2"; + case AudioEffectDelay::SyncTime::T_2_3: + return "2/3"; + case AudioEffectDelay::SyncTime::T_3_4: + return "3/4"; + case AudioEffectDelay::SyncTime::T_1_1: + default: + return "1/1"; + } +} + void CUIMenu::TGShortcutHandler (TMenuEvent Event) { assert (m_nCurrentMenuDepth >= 2); diff --git a/src/uimenu.h b/src/uimenu.h index 06bef4e..2c95aac 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -128,7 +128,8 @@ private: static std::string ToPolyMono (int nValue); static std::string ToFXType (int nValue); static std::string ToMix (int nValue); - + static std::string ToDelayTime (int nValue); + void TGShortcutHandler (TMenuEvent Event); void OPShortcutHandler (TMenuEvent Event);