diff --git a/src/Makefile b/src/Makefile index 540ae68..ce9a545 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,6 +9,7 @@ CMSIS_DIR = ../CMSIS_5/CMSIS OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ + effect.o effect_chorus.o effect_delay.o \ effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o OPTIMIZE = -O3 diff --git a/src/effect.cpp b/src/effect.cpp new file mode 100644 index 0000000..30ace8a --- /dev/null +++ b/src/effect.cpp @@ -0,0 +1,63 @@ +#include "effect.h" + +AudioEffect::AudioEffect(float32_t samplerate) +{ + this->samplerate = samplerate; +} + +AudioEffect::~AudioEffect() +{ +} + +void AudioEffect::setBypass(bool bypass) +{ + this->bypass = bypass; +} + +bool AudioEffect::getBypass() +{ + return bypass; +} + +unsigned AudioEffect::getId() +{ + return EFFECT_NONE; +} + +void AudioEffect::process(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) +{ + if (bypass) { + return; + } + doProcess(inblockL, inblockR, outblockL, outblockR, len); +} + +void AudioEffect::doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) { + for (uint16_t i=0; i < len; i++) + { + outblockL[i] = inblockL[i]; + outblockR[i] = inblockR[i]; + } +} + +AudioEffectNone::AudioEffectNone(float32_t samplerate) : AudioEffect(samplerate) +{ +} + +AudioEffectNone::~AudioEffectNone() +{ +} + +void AudioEffectNone::doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) +{ + for (uint16_t i=0; i < len; i++) + { + outblockL[i] = inblockL[i]; + outblockR[i] = inblockR[i]; + } +} + +unsigned AudioEffectNone::getId() +{ + return EFFECT_NONE; +} \ No newline at end of file diff --git a/src/effect.h b/src/effect.h new file mode 100644 index 0000000..1bfd675 --- /dev/null +++ b/src/effect.h @@ -0,0 +1,42 @@ +#ifndef _EFFECT_H +#define _EFFECT_H + +#include +#include +#include "common.h" + +#define EFFECT_NONE 0 +#define EFFECT_CHORUS 1 +#define EFFECT_DELAY 2 + +class AudioEffect +{ +public: + AudioEffect(float32_t samplerate); + virtual ~AudioEffect(); + + void setBypass(bool bypass); + bool getBypass(); + + virtual unsigned getId(); + + void process(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len); +protected: + bool bypass = false; + float32_t samplerate; + + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len); +}; + +class AudioEffectNone : public AudioEffect +{ +public: + AudioEffectNone(float32_t samplerate); + virtual ~AudioEffectNone(); + + virtual unsigned getId(); +protected: + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len); +}; + +#endif // _EFFECT_H \ No newline at end of file diff --git a/src/effect_chorus.cpp b/src/effect_chorus.cpp new file mode 100644 index 0000000..e08c7aa --- /dev/null +++ b/src/effect_chorus.cpp @@ -0,0 +1,73 @@ +#include +#include "effect_chorus.h" + +LOGMODULE ("fx chorus"); + +AudioEffectChorus::AudioEffectChorus(float32_t samplerate) : AudioEffect(samplerate) +{ + engine = new ChorusEngine(samplerate); + + engine->setEnablesChorus(true, true); + engine->setChorus1LfoRate(5.0f / 10.0f); + engine->setChorus2LfoRate(8.3f / 10.0f); +} + +AudioEffectChorus::~AudioEffectChorus() +{ + delete engine; +} + +unsigned AudioEffectChorus::getId() +{ + return EFFECT_CHORUS; +} + +void AudioEffectChorus::doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) +{ + for (uint16_t i=0; i < len; i++) + { + outblockL[i] = inblockL[i]; + outblockR[i] = inblockR[i]; + engine->process(&outblockL[i], &outblockR[i]); + } +} + +unsigned AudioEffectChorus::getChorusI() +{ + return engine->isChorus1Enabled; +} + +void AudioEffectChorus::setChorusI(unsigned enable) +{ + engine->setEnablesChorus(enable == 1, engine->isChorus2Enabled); +} + +unsigned AudioEffectChorus::getChorusII() +{ + return engine->isChorus2Enabled; +} + +void AudioEffectChorus::setChorusII(unsigned enable) +{ + engine->setEnablesChorus(engine->isChorus1Enabled, enable == 1); +} + +unsigned AudioEffectChorus::getChorusIRate() +{ + return (int) roundf(engine->chorus1L->rate * 100); +} + +void AudioEffectChorus::setChorusIRate(unsigned int rate) +{ + engine->setChorus1LfoRate(((float) rate) / 100.0f); +} + +unsigned AudioEffectChorus::getChorusIIRate() +{ + return (int) roundf(engine->chorus2L->rate * 100); +} + +void AudioEffectChorus::setChorusIIRate(unsigned int rate) +{ + engine->setChorus2LfoRate(((float) rate) / 100.0f); +} diff --git a/src/effect_chorus.h b/src/effect_chorus.h new file mode 100644 index 0000000..aaae398 --- /dev/null +++ b/src/effect_chorus.h @@ -0,0 +1,33 @@ +#ifndef _EFFECT_CHORUS_H +#define _EFFECT_CHORUS_H + +#include "effect.h" +#include "ykchorus/ChorusEngine.h" + +class AudioEffectChorus : public AudioEffect +{ +public: + AudioEffectChorus(float32_t samplerate); + virtual ~AudioEffectChorus(); + + virtual unsigned getId(); + + unsigned getChorusI(); + void setChorusI(unsigned enable); + + unsigned getChorusII(); + void setChorusII(unsigned enable); + + unsigned getChorusIRate(); + void setChorusIRate(unsigned int rate); + + unsigned getChorusIIRate(); + void setChorusIIRate(unsigned int rate); +protected: + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len); + +private: + ChorusEngine *engine; +}; + +#endif // _EFFECT_CHORUS_H \ No newline at end of file diff --git a/src/effect_delay.cpp b/src/effect_delay.cpp new file mode 100644 index 0000000..a5a7b7c --- /dev/null +++ b/src/effect_delay.cpp @@ -0,0 +1,26 @@ +#include +#include "effect_delay.h" + +LOGMODULE ("fx chorus"); + +AudioEffectDelay::AudioEffectDelay(float32_t samplerate) : AudioEffect(samplerate) +{ +} + +AudioEffectDelay::~AudioEffectDelay() +{ +} + +unsigned AudioEffectDelay::getId() +{ + return EFFECT_DELAY; +} + +void AudioEffectDelay::doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) +{ + for (uint16_t i=0; i < len; i++) + { + outblockL[i] = inblockL[i]; + outblockR[i] = inblockR[i]; + } +} diff --git a/src/effect_delay.h b/src/effect_delay.h new file mode 100644 index 0000000..c131c74 --- /dev/null +++ b/src/effect_delay.h @@ -0,0 +1,26 @@ +#ifndef _EFFECT_DELAY_H +#define _EFFECT_DELAY_H + +#include "effect.h" + +class AudioEffectDelay : public AudioEffect +{ +public: + AudioEffectDelay(float32_t samplerate); + virtual ~AudioEffectDelay(); + + virtual unsigned getId(); +protected: + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len); +// private: + // const size_t MaxSampleDelayTime; + // unsigned write_pos_L_; + // unsigned write_pos_R_; + // float32_t* buffer_L_; + // float32_t* buffer_R_; + // float32_t delay_time_L_; // Left delay time in seconds (0.0 - 2.0) + // float32_t delay_time_R_; // Right delay time in seconds (0.0 - 2.0) + // float32_t feedback_; // Feedback (0.0 - 1.0) +}; + +#endif // _EFFECT_DELAY_H \ No newline at end of file diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 02d8fd6..8f077d7 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -91,6 +91,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_nAftertouchRange[i]=99; m_nAftertouchTarget[i]=0; + m_InsertFX[i] = new AudioEffectNone(pConfig->GetSampleRate ()); m_nReverbSend[i] = 0; m_uchOPMask[i] = 0b111111; // All operators on @@ -403,7 +404,9 @@ void CMiniDexed::Run (unsigned nCore) for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) { assert (m_pTG[nTG]); + float32_t Dummy[m_nFramesToProcess]; m_pTG[nTG]->getSamples (m_OutputLevel[nTG],m_nFramesToProcess); + m_InsertFX[nTG]->process(m_OutputLevel[nTG], Dummy, m_OutputLevel[nTG], Dummy, m_nFramesToProcess); } } } @@ -584,6 +587,31 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) m_UI.ParameterChanged (); } +void CMiniDexed::setInsertFXType (unsigned nType, unsigned nTG) +{ + nType=constrain((int) nType, 0, 2); + + assert (nTG < CConfig::ToneGenerators); + + delete m_InsertFX[nTG]; + + switch (nType) + { + case EFFECT_CHORUS: + m_InsertFX[nTG] = new AudioEffectChorus(m_pConfig->GetSampleRate()); + break; + case EFFECT_DELAY: + m_InsertFX[nTG] = new AudioEffectDelay(m_pConfig->GetSampleRate()); + break; + case EFFECT_NONE: + default: + m_InsertFX[nTG] = new AudioEffectNone(m_pConfig->GetSampleRate()); + break; + } + + m_UI.ParameterChanged (); +} + void CMiniDexed::SetReverbSend (unsigned nReverbSend, unsigned nTG) { nReverbSend=constrain((int)nReverbSend,0,99); @@ -922,6 +950,12 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT break; case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; + case TGParameterInsertFXType: setInsertFXType(nValue, nTG); break; + + case TGParameterFXChorusI: setChorusIEnable(nTG, nValue); break; + case TGParameterFXChorusII: setChorusIIEnable(nTG, nValue); break; + case TGParameterFXChorusIRate: setChorusIRate(nTG, nValue); break; + case TGParameterFXChorusIIRate: setChorusIIRate(nTG, nValue); break; default: assert (0); @@ -946,6 +980,7 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) case TGParameterResonance: return m_nResonance[nTG]; case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; case TGParameterReverbSend: return m_nReverbSend[nTG]; + case TGParameterInsertFXType: return m_InsertFX[nTG]->getId(); case TGParameterPitchBendRange: return m_nPitchBendRange[nTG]; case TGParameterPitchBendStep: return m_nPitchBendStep[nTG]; case TGParameterPortamentoMode: return m_nPortamentoMode[nTG]; @@ -973,6 +1008,10 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) case TGParameterATAmplitude: return getModController(3, 2, nTG); case TGParameterATEGBias: return getModController(3, 3, nTG); + case TGParameterFXChorusI: return getChorusIEnable(nTG); + case TGParameterFXChorusII: return getChorusIIEnable(nTG); + case TGParameterFXChorusIRate: return getChorusIRate(nTG); + case TGParameterFXChorusIIRate: return getChorusIIRate(nTG); default: assert (0); @@ -1064,7 +1103,9 @@ void CMiniDexed::ProcessSound (void) } float32_t SampleBuffer[nFrames]; + float32_t Dummy[nFrames]; m_pTG[0]->getSamples (SampleBuffer, nFrames); + m_InsertFX[0]->process(SampleBuffer, Dummy, SampleBuffer, Dummy, nFrames); // Convert single float array (mono) to int16 array int16_t tmp_int[nFrames]; @@ -1110,7 +1151,9 @@ void CMiniDexed::ProcessSound (void) for (unsigned i = 0; i < CConfig::TGsCore1; i++) { assert (m_pTG[i]); + float32_t Dummy[nFrames]; m_pTG[i]->getSamples (m_OutputLevel[i], nFrames); + m_InsertFX[i]->process(m_OutputLevel[i], Dummy, m_OutputLevel[i], Dummy, nFrames); } // wait for cores 2 and 3 to complete their work @@ -1942,3 +1985,91 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, } } + +unsigned CMiniDexed::getChorusIEnable (uint8_t nTG) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return 0; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->getChorusI(); +} + +void CMiniDexed::setChorusIEnable (uint8_t nTG, unsigned enable) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->setChorusI(enable); +} + +unsigned CMiniDexed::getChorusIIEnable (uint8_t nTG) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return 0; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->getChorusII(); +} + +void CMiniDexed::setChorusIIEnable (uint8_t nTG, unsigned enable) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->setChorusII(enable); +} + +unsigned CMiniDexed::getChorusIRate (uint8_t nTG) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return 0; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->getChorusIRate(); +} + +void CMiniDexed::setChorusIRate (uint8_t nTG, unsigned int rate) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->setChorusIRate(rate); +} + +unsigned CMiniDexed::getChorusIIRate (uint8_t nTG) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return 0; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->getChorusIIRate(); +} + +void CMiniDexed::setChorusIIRate (uint8_t nTG, unsigned int rate) +{ + AudioEffect* effect = m_InsertFX[nTG]; + if (effect->getId() != EFFECT_CHORUS) { + return; + } + + AudioEffectChorus* chorus = (AudioEffectChorus*) effect; + return chorus->setChorusIIRate(rate); +} diff --git a/src/minidexed.h b/src/minidexed.h index 8ca74c8..3ae6316 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -44,6 +44,9 @@ #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" +#include "effect.h" +#include "effect_chorus.h" +#include "effect_delay.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -94,6 +97,8 @@ public: void setBreathController (uint8_t value, unsigned nTG); void setAftertouch (uint8_t value, unsigned nTG); + void setInsertFXType (unsigned nType, unsigned nTG); + void SetReverbSend (unsigned nReverbSend, unsigned nTG); // 0 .. 127 void setMonoMode(uint8_t mono, uint8_t nTG); @@ -178,6 +183,7 @@ public: TGParameterProgram, TGParameterVolume, TGParameterPan, + TGParameterInsertFXType, TGParameterMasterTune, TGParameterCutoff, TGParameterResonance, @@ -188,7 +194,7 @@ public: TGParameterPortamentoMode, TGParameterPortamentoGlissando, TGParameterPortamentoTime, - TGParameterMonoMode, + TGParameterMonoMode, TGParameterMWRange, TGParameterMWPitch, @@ -209,6 +215,11 @@ public: TGParameterATPitch, TGParameterATAmplitude, TGParameterATEGBias, + + TGParameterFXChorusI, + TGParameterFXChorusIRate, + TGParameterFXChorusII, + TGParameterFXChorusIIRate, TGParameterUnknown }; @@ -234,6 +245,15 @@ 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 { @@ -283,6 +303,7 @@ private: unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators]; + AudioEffect* m_InsertFX[CConfig::ToneGenerators]; unsigned m_nReverbSend[CConfig::ToneGenerators]; uint8_t m_nRawVoiceData[156]; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 82a426a..3d8652d 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -35,7 +35,7 @@ LOGMODULE ("uimenu"); const CUIMenu::TMenuItem CUIMenu::s_MenuRoot[] = { - {"MiniDexed", MenuHandler, s_MainMenu}, + {"MiniDexed JN", MenuHandler, s_MainMenu}, {0} }; @@ -65,6 +65,7 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] = #ifdef ARM_ALLOW_MULTI_CORE {"Pan", EditTGParameter, 0, CMiniDexed::TGParameterPan}, #endif + {"Insert FX", MenuHandler, s_InsertFX}, {"Reverb-Send", EditTGParameter, 0, CMiniDexed::TGParameterReverbSend}, {"Detune", EditTGParameter, 0, CMiniDexed::TGParameterMasterTune}, {"Cutoff", EditTGParameter, 0, CMiniDexed::TGParameterCutoff}, @@ -136,6 +137,28 @@ const CUIMenu::TMenuItem CUIMenu::s_ReverbMenu[] = #endif +const CUIMenu::TMenuItem CUIMenu::s_InsertFX[] = +{ + {"Type", EditTGParameter2, 0, CMiniDexed::TGParameterInsertFXType}, + {"Edit", EditInsertFX}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXNone[] = +{ + {"None", EditTGParameter2}, + {0} +}; + +const CUIMenu::TMenuItem CUIMenu::s_FXChorus[] = +{ + {"Chorus I", EditTGParameter2, 0, CMiniDexed::TGParameterFXChorusI}, + {"Chorus II", EditTGParameter2, 0, CMiniDexed::TGParameterFXChorusII}, + {"Rate I", EditTGParameter2, 0, CMiniDexed::TGParameterFXChorusIRate}, + {"Rate II", EditTGParameter2, 0, CMiniDexed::TGParameterFXChorusIIRate}, + {0} +}; + // inserting menu items before "OP1" affect OPShortcutHandler() const CUIMenu::TMenuItem CUIMenu::s_EditVoiceMenu[] = { @@ -227,6 +250,7 @@ const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] {0, CSysExFileLoader::VoicesPerBank-1, 1}, // TGParameterProgram {0, 127, 8, ToVolume}, // TGParameterVolume {0, 127, 8, ToPan}, // TGParameterPan + {0, 2, 1, ToFXType}, // TGParameterInsertFXType {-99, 99, 1}, // TGParameterMasterTune {0, 99, 1}, // TGParameterCutoff {0, 99, 1}, // TGParameterResonance @@ -253,7 +277,12 @@ const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] {0, 99, 1}, //AT Range {0, 1, 1, ToOnOff}, //AT Pitch {0, 1, 1, ToOnOff}, //AT Amp - {0, 1, 1, ToOnOff} //AT EGBias + {0, 1, 1, ToOnOff}, //AT EGBias + + {0, 1, 1, ToOnOff}, // TGParameterFXChorusI + {0, 100, 1}, // TGParameterFXChorusIRate + {0, 1, 1, ToOnOff}, // TGParameterFXChorusII + {0, 100, 1}, // TGParameterFXChorusIIRate }; // must match DexedVoiceParameters in Synth_Dexed @@ -701,14 +730,14 @@ void CUIMenu::EditTGParameter (CUIMenu *pUIMenu, TMenuEvent Event) void CUIMenu::EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event) // second menu level. Redundant code but in order to not modified original code { - - unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; + + unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; CMiniDexed::TTGParameter Param = (CMiniDexed::TTGParameter) pUIMenu->m_nCurrentParameter; const TParameter &rParam = s_TGParameter[Param]; int nValue = pUIMenu->m_pMiniDexed->GetTGParameter (Param, nTG); - + switch (Event) { case MenuEventUpdate: @@ -750,7 +779,82 @@ void CUIMenu::EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event) // second me pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, Value.c_str (), nValue > rParam.Minimum, nValue < rParam.Maximum); - +} + +void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) +{ + unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; + + int fxType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterInsertFXType, nTG); + switch (fxType) + { + case EFFECT_CHORUS: + pUIMenu->m_pCurrentMenu = s_FXChorus; + break; + + default: + pUIMenu->m_pCurrentMenu = s_FXNone; + break; + } + + switch (Event) + { + case MenuEventUpdate: + break; + + case MenuEventSelect: // push menu + assert (pUIMenu->m_nCurrentMenuDepth < MaxMenuDepth); + pUIMenu->m_MenuStackParent[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pParentMenu; + pUIMenu->m_MenuStackMenu[pUIMenu->m_nCurrentMenuDepth] = pUIMenu->m_pCurrentMenu; + pUIMenu->m_nMenuStackItem[pUIMenu->m_nCurrentMenuDepth] + = pUIMenu->m_nCurrentMenuItem; + pUIMenu->m_nMenuStackSelection[pUIMenu->m_nCurrentMenuDepth] + = pUIMenu->m_nCurrentSelection; + pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth] + = pUIMenu->m_nCurrentParameter; + pUIMenu->m_nCurrentMenuDepth++; + + pUIMenu->m_pParentMenu = pUIMenu->m_pCurrentMenu; + pUIMenu->m_nCurrentParameter = + pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Parameter; + pUIMenu->m_pCurrentMenu = + pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].MenuItem; + pUIMenu->m_nCurrentMenuItem = pUIMenu->m_nCurrentSelection; + pUIMenu->m_nCurrentSelection = 0; + break; + + case MenuEventStepDown: + if (pUIMenu->m_nCurrentSelection > 0) + { + pUIMenu->m_nCurrentSelection--; + } + break; + + case MenuEventStepUp: + ++pUIMenu->m_nCurrentSelection; + if (!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name) // more entries? + { + pUIMenu->m_nCurrentSelection--; + } + break; + + default: + return; + } + + if (pUIMenu->m_pCurrentMenu) // if this is another menu? + { + pUIMenu->m_pUI->DisplayWrite ( + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + "", + pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name, + pUIMenu->m_nCurrentSelection > 0, + !!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name); + } + else + { + pUIMenu->EventHandler (MenuEventUpdate); // no, update parameter display + } } void CUIMenu::EditVoiceParameter (CUIMenu *pUIMenu, TMenuEvent Event) @@ -1146,6 +1250,17 @@ string CUIMenu::ToPolyMono (int nValue) } } +string CUIMenu::ToFXType (int nValue) +{ + switch (nValue) + { + case EFFECT_CHORUS: return "Juno Chorus"; + case EFFECT_DELAY: return "Delay"; + case EFFECT_NONE: + default: return "None"; + } +} + void CUIMenu::TGShortcutHandler (TMenuEvent Event) { assert (m_nCurrentMenuDepth >= 2); diff --git a/src/uimenu.h b/src/uimenu.h index d5b48dc..0baf3ed 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -92,6 +92,8 @@ private: static void PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event); static void SavePerformanceNewFile (CUIMenu *pUIMenu, TMenuEvent Event); static void EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); + + static void EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event); static std::string GetGlobalValueString (unsigned nParameter, int nValue); static std::string GetTGValueString (unsigned nTGParameter, int nValue); @@ -113,6 +115,7 @@ private: static std::string ToPortaMode (int nValue); static std::string ToPortaGlissando (int nValue); static std::string ToPolyMono (int nValue); + static std::string ToFXType (int nValue); void TGShortcutHandler (TMenuEvent Event); void OPShortcutHandler (TMenuEvent Event); @@ -147,6 +150,9 @@ private: static const TMenuItem s_TGMenu[]; static const TMenuItem s_EffectsMenu[]; static const TMenuItem s_ReverbMenu[]; + static const TMenuItem s_InsertFX[]; + static const TMenuItem s_FXNone[]; + static const TMenuItem s_FXChorus[]; static const TMenuItem s_EditVoiceMenu[]; static const TMenuItem s_OperatorMenu[]; static const TMenuItem s_SaveMenu[]; diff --git a/src/ykchorus/Chorus.h b/src/ykchorus/Chorus.h new file mode 100644 index 0000000..603e6bb --- /dev/null +++ b/src/ykchorus/Chorus.h @@ -0,0 +1,143 @@ +/* + ============================================================================== + This file is part of Tal-NoiseMaker by Patrick Kunz. + + Copyright(c) 2005-2010 Patrick Kunz, TAL + Togu Audio Line, Inc. + http://kunz.corrupt.ch + + This file may be licensed under the terms of of the + GNU General Public License Version 2 (the ``GPL''). + + Software distributed under the License is distributed + on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + express or implied. See the GPL for the specific language + governing rights and limitations. + + You should have received a copy of the GPL along with this + program. If not, go to http://www.gnu.org/licenses/gpl.html + or write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ============================================================================== + */ + +#if !defined(__Chorus_h) +#define __Chorus_h + +#include "OnePoleLP.h" +#include "math.h" + +class Chorus { +public: + float *delayLineStart; + float *delayLineEnd; + float *writePtr; + + int delayLineLength; + float rate; + float delayLineOutput; + + float sampleRate; + float delayTime; + + // Runtime variables + float offset, diff, frac, *ptr, *ptr2; + + int readPos; + + OnePoleLP *lp; + float z1; + float mult, sign; + + // lfo + float lfoPhase, lfoStepSize, lfoSign; + + Chorus(float sampleRate, float phase, float rate, float delayTime) { + this->rate = rate; + this->sampleRate = sampleRate; + this->delayTime = delayTime; + z1 = 0.0f; + sign = 0; + lfoPhase = phase * 2.0f - 1.0f; + lfoStepSize = (4.0f * rate / sampleRate); + lfoSign = 1.0f; + + // Compute required buffer size for desired delay and allocate it + // Add extra point to aid in interpolation later + delayLineLength = ((int)floorf(delayTime * sampleRate * 0.001f) * 2); + delayLineStart = new float[delayLineLength]; + + // Set up pointers for delay line + delayLineEnd = delayLineStart + delayLineLength; + writePtr = delayLineStart; + + // Zero out the buffer (silence) + do { + *writePtr = 0.0f; + } + while (++writePtr < delayLineEnd); + + // Set read pointer to end of delayline. Setting it to the end + // ensures the interpolation below works correctly to produce + // the first non-zero sample. + writePtr = delayLineStart + delayLineLength -1; + delayLineOutput = 0.0f; + lp = new OnePoleLP(); + } + + ~Chorus() { + delete[] delayLineStart; + delete lp; + } + + void setLfoRate(float rate) { + this->rate = rate; + lfoStepSize = (4.0f * rate / sampleRate); + } + + float process(float *sample) { + // Get delay time + offset = (nextLFO() * 0.3f + 0.4f) * delayTime * sampleRate * 0.001f; + + // Compute the largest read pointer based on the offset. If ptr + // is before the first delayline location, wrap around end point + ptr = writePtr - (int)floorf(offset); + if (ptr < delayLineStart) + ptr += delayLineLength; + + ptr2 = ptr - 1; + if (ptr2 < delayLineStart) + ptr2 += delayLineLength; + + frac = offset - (int)floorf(offset); + delayLineOutput = *ptr2 + *ptr * (1 - frac) - (1 - frac) * z1; + z1 = delayLineOutput; + + // Low pass + lp->tick(&delayLineOutput, 0.95f); + + // Write the input sample and any feedback to delayline + *writePtr = *sample; + + // Increment buffer index and wrap if necesary + if (++writePtr >= delayLineEnd) { + writePtr = delayLineStart; + } + return delayLineOutput; + } + + inline float nextLFO() { + if (lfoPhase >= 1.0f) + { + lfoSign = -1.0f; + } + else if (lfoPhase <= -1.0f) + { + lfoSign = +1.0f; + } + lfoPhase += lfoStepSize * lfoSign; + return lfoPhase; + } +}; + +#endif \ No newline at end of file diff --git a/src/ykchorus/ChorusEngine.h b/src/ykchorus/ChorusEngine.h new file mode 100644 index 0000000..df162ea --- /dev/null +++ b/src/ykchorus/ChorusEngine.h @@ -0,0 +1,114 @@ +/* + ============================================================================== + This file is part of Tal-NoiseMaker by Patrick Kunz. + + Copyright(c) 2005-2010 Patrick Kunz, TAL + Togu Audio Line, Inc. + http://kunz.corrupt.ch + + This file may be licensed under the terms of of the + GNU General Public License Version 2 (the ``GPL''). + + Software distributed under the License is distributed + on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + express or implied. See the GPL for the specific language + governing rights and limitations. + + You should have received a copy of the GPL along with this + program. If not, go to http://www.gnu.org/licenses/gpl.html + or write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ============================================================================== + */ + +#if !defined(__ChorusEngine_h) +#define __ChorusEngine_h + +#include "Chorus.h" +#include "DCBlock.h" + + +class ChorusEngine { +public: + Chorus *chorus1L; + Chorus *chorus1R; + Chorus *chorus2L; + Chorus *chorus2R; + + DCBlock *dcBlock1L; + DCBlock *dcBlock1R; + DCBlock *dcBlock2L; + DCBlock *dcBlock2R; + + bool isChorus1Enabled; + bool isChorus2Enabled; + + ChorusEngine(float sampleRate) { + dcBlock1L = new DCBlock(); + dcBlock1R = new DCBlock(); + dcBlock2L = new DCBlock(); + dcBlock2R = new DCBlock(); + + setUpChorus(sampleRate); + } + + ~ChorusEngine() { + delete chorus1L; + delete chorus1R; + delete chorus2L; + delete chorus2R; + delete dcBlock1L; + delete dcBlock1R; + delete dcBlock2L; + delete dcBlock2R; + } + + void setSampleRate(float sampleRate) { + setUpChorus(sampleRate); + } + + void setEnablesChorus(bool isChorus1Enabled, bool isChorus2Enabled) { + this->isChorus1Enabled = isChorus1Enabled; + this->isChorus2Enabled = isChorus2Enabled; + } + + void setChorus1LfoRate(float rate) { + chorus1L->setLfoRate(rate); + chorus1R->setLfoRate(rate); + } + + void setChorus2LfoRate(float rate) { + chorus2L->setLfoRate(rate); + chorus2R->setLfoRate(rate); + } + + void setUpChorus(float sampleRate) { + chorus1L = new Chorus(sampleRate, 1.0f, 0.5f, 7.0f); + chorus1R = new Chorus(sampleRate, 0.0f, 0.5f, 7.0f); + chorus2L = new Chorus(sampleRate, 0.0f, 0.83f, 7.0f); + chorus2R = new Chorus(sampleRate, 1.0f, 0.83f, 7.0f); + } + + inline void process(float *sampleL, float *sampleR) { + float resultR = 0.0f; + float resultL = 0.0f; + if (isChorus1Enabled) + { + resultL += chorus1L->process(sampleL); + resultR += chorus1R->process(sampleR); + dcBlock1L->tick(&resultL, 0.01f); + dcBlock1R->tick(&resultR, 0.01f); + } + if (isChorus2Enabled) + { + resultL += chorus2L->process(sampleL); + resultR += chorus2R->process(sampleR); + dcBlock2L->tick(&resultL, 0.01f); + dcBlock2R->tick(&resultR, 0.01f); + } + *sampleL = *sampleL + resultL * 1.4f; + *sampleR = *sampleR + resultR * 1.4f; + } +}; + +#endif \ No newline at end of file diff --git a/src/ykchorus/DCBlock.h b/src/ykchorus/DCBlock.h new file mode 100644 index 0000000..40dc1e4 --- /dev/null +++ b/src/ykchorus/DCBlock.h @@ -0,0 +1,45 @@ +/* + ============================================================================== + This file is part of Tal-NoiseMaker by Patrick Kunz. + + Copyright(c) 2005-2010 Patrick Kunz, TAL + Togu Audio Line, Inc. + http://kunz.corrupt.ch + + This file may be licensed under the terms of of the + GNU General Public License Version 2 (the ``GPL''). + + Software distributed under the License is distributed + on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + express or implied. See the GPL for the specific language + governing rights and limitations. + + You should have received a copy of the GPL along with this + program. If not, go to http://www.gnu.org/licenses/gpl.html + or write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ============================================================================== + */ + +#if !defined(__DCBlock_h) +#define __DCBlock_h + +class DCBlock { +public: + float inputs, outputs, lastOutput; + + DCBlock() { + lastOutput = inputs = outputs = 0.0f; + } + + ~DCBlock() {} + + inline void tick(float *sample, float cutoff) { + outputs = *sample - inputs + (0.999f - cutoff * 0.4f) * outputs; + inputs = *sample; + lastOutput = outputs; + *sample = lastOutput; + } +}; + +#endif \ No newline at end of file diff --git a/src/ykchorus/OnePoleLP.h b/src/ykchorus/OnePoleLP.h new file mode 100644 index 0000000..955ed53 --- /dev/null +++ b/src/ykchorus/OnePoleLP.h @@ -0,0 +1,44 @@ +/* + ============================================================================== + This file is part of Tal-NoiseMaker by Patrick Kunz. + + Copyright(c) 2005-2010 Patrick Kunz, TAL + Togu Audio Line, Inc. + http://kunz.corrupt.ch + + This file may be licensed under the terms of of the + GNU General Public License Version 2 (the ``GPL''). + + Software distributed under the License is distributed + on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either + express or implied. See the GPL for the specific language + governing rights and limitations. + + You should have received a copy of the GPL along with this + program. If not, go to http://www.gnu.org/licenses/gpl.html + or write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ============================================================================== + */ + +#if !defined(__OnePoleLP_h) +#define __OnePoleLP_h + +class OnePoleLP { +public: + float inputs, outputs, lastOutput; + + OnePoleLP() { + lastOutput = inputs = outputs = 0.0f; + } + + ~OnePoleLP() {} + + void tick(float *sample, float cutoff) { + float p = (cutoff * 0.98f) * (cutoff * 0.98f) * (cutoff * 0.98f) * (cutoff * 0.98f); + outputs = (1.0f - p) * (*sample) + p * outputs; + *sample = outputs; + } +}; + +#endif \ No newline at end of file