diff --git a/src/midi_arp.cpp b/src/midi_arp.cpp index 4989153..ccddb40 100644 --- a/src/midi_arp.cpp +++ b/src/midi_arp.cpp @@ -1,12 +1,10 @@ #include "midi_arp.h" #include -MidiArp::MidiArp(float32_t samplerate, CDexedAdapter* synth) +MidiArp::MidiArp(float32_t samplerate, CDexedAdapter* synth) : MidiEffect(samplerate, synth) { - this->samplerate = samplerate; this->syncMode = 1; - this->synth = synth; - + arpeggiator.transmitHostInfo(0, 4, 1, 1, 120.0); arpeggiator.setSampleRate(samplerate); arpeggiator.setDivision(7); @@ -18,6 +16,79 @@ MidiArp::~MidiArp() { } +void MidiArp::setTempo(unsigned tempo) +{ + arpeggiator.setBpm(tempo); +} + +void MidiArp::setParameter(unsigned param, unsigned value) +{ + switch (param) + { + case MidiArp::Param::BYPASS: + this->setBypass(value == 1); + break; + case MidiArp::Param::LATCH: + this->arpeggiator.setLatchMode(value == 1); + break; + case MidiArp::Param::SYNC: + this->arpeggiator.setSyncMode(value); + break; + case MidiArp::Param::ARP_MODE: + this->arpeggiator.setArpMode(value); + break; + case MidiArp::Param::DIVISION: + this->arpeggiator.setDivision(value); + break; + case MidiArp::Param::NOTE_LENGTH: + this->arpeggiator.setNoteLength(value); + break; + case MidiArp::Param::VELOCITY: + this->arpeggiator.setVelocity(value); + break; + case MidiArp::Param::OCTAVE_SPREAD: + this->arpeggiator.setOctaveSpread(value); + break; + case MidiArp::Param::OCTAVE_MODE: + this->arpeggiator.setOctaveMode(value); + break; + case MidiArp::Param::PANIC: + this->arpeggiator.setPanic(value == 1); + break; + default: + break; + } +} + +unsigned MidiArp::getParameter(unsigned param) +{ + switch (param) + { + case MidiArp::Param::BYPASS: + return this->getBypass() ? 1 : 0; + case MidiArp::Param::LATCH: + return this->arpeggiator.getLatchMode() ? 1 : 0; + case MidiArp::Param::SYNC: + return this->arpeggiator.getSyncMode(); + case MidiArp::Param::ARP_MODE: + return this->arpeggiator.getArpMode(); + case MidiArp::Param::DIVISION: + return this->arpeggiator.getDivision(); + case MidiArp::Param::NOTE_LENGTH: + return this->arpeggiator.getNoteLength(); + case MidiArp::Param::VELOCITY: + return this->arpeggiator.getVelocity(); + case MidiArp::Param::OCTAVE_SPREAD: + return this->arpeggiator.getOctaveSpread(); + case MidiArp::Param::OCTAVE_MODE: + return this->arpeggiator.getOctaveMode(); + case MidiArp::Param::PANIC: + return this->arpeggiator.getPanic(); + default: + return 0; + } +} + void MidiArp::keydown(int16_t pitch, uint8_t velocity) { MidiEvent event; @@ -40,7 +111,7 @@ void MidiArp::keyup(int16_t pitch) this->events.push_back(event); } -void MidiArp::process(uint16_t len) +void MidiArp::doProcess(uint16_t len) { arpeggiator.emptyMidiBuffer(); diff --git a/src/midi_arp.h b/src/midi_arp.h index 6a72419..006bc5e 100644 --- a/src/midi_arp.h +++ b/src/midi_arp.h @@ -1,31 +1,62 @@ /* - * Base AudioEffect interface + * MOD Arpeggiator port + * Ported from https://ithub.com/moddevices/mod-arpeggiator-lv2 + * * Javier Nonis (https://github.com/jnonis) - 2024 */ #ifndef _MIDI_ARP_H #define _MIDI_ARP_H +#include "midi_effect_base.h" #include #include #include "modarpeggiator/common/commons.h" #include "modarpeggiator/arpeggiator.hpp" #include "modarpeggiator/common/clock.hpp" #include "modarpeggiator/common/pattern.hpp" -#include "dexedadapter.h" -class MidiArp +class MidiArp : public MidiEffect { public: + static const unsigned MIDI_EFFECT_ARP = 1; + + enum Param + { + BYPASS, + LATCH, + SYNC, + ARP_MODE, + DIVISION, + NOTE_LENGTH, + VELOCITY, + OCTAVE_SPREAD, + OCTAVE_MODE, + PANIC, + UNKNOWN + }; + MidiArp(float32_t samplerate, CDexedAdapter* synth); - ~MidiArp(); + virtual ~MidiArp(); + virtual unsigned getId() + { + return MIDI_EFFECT_ARP; + } + + virtual void setTempo(unsigned tempo); + virtual void setParameter(unsigned param, unsigned value); + virtual unsigned getParameter(unsigned param); + void keydown(int16_t pitch, uint8_t velocity); void keyup(int16_t pitch); - void process(uint16_t len); protected: - bool bypass = false; - float32_t samplerate; + virtual size_t getParametersSize() + { + return MidiArp::Param::UNKNOWN; + } + + virtual void doProcess(uint16_t len); private: static const unsigned MIDI_NOTE_OFF = 0b1000; diff --git a/src/midi_effect_base.h b/src/midi_effect_base.h new file mode 100644 index 0000000..4b8b37d --- /dev/null +++ b/src/midi_effect_base.h @@ -0,0 +1,96 @@ +/* + * Base MidiEffect interface + * Javier Nonis (https://github.com/jnonis) - 2024 + */ +#ifndef _MIDI_EFFECT_H +#define _MIDI_EFFECT_H + +#include +#include "dexedadapter.h" + +class MidiEffect +{ +public: + static const unsigned MIDI_EFFECT_NONE = 0; + + MidiEffect(float32_t samplerate, CDexedAdapter* synth) + { + this->samplerate = samplerate; + this->synth = synth; + } + + virtual ~MidiEffect() + { + } + + void setBypass(bool bypass) + { + this->bypass = bypass; + } + + bool getBypass() + { + return bypass; + } + + virtual unsigned getId() + { + return MIDI_EFFECT_NONE; + } + + virtual void setTempo(unsigned tempo) + { + } + + virtual void setParameter(unsigned param, unsigned value) + { + } + + virtual unsigned getParameter(unsigned param) + { + return 0; + } + + void setParameters(std::vector params) + { + for (size_t i = 0; i < params.size(); i++) + { + this->setParameter(i, params[i]); + } + } + + std::vector getParameters() + { + size_t len = this->getParametersSize(); + std::vector params; + for (size_t i = 0; i < len; i++) + { + params.push_back(getParameter(i)); + } + return params; + } + + void process(uint16_t len) + { + if (this->bypass) + { + return; + } + doProcess(len); + } +protected: + bool bypass = false; + float32_t samplerate; + CDexedAdapter* synth; + + virtual size_t getParametersSize() + { + return 0; + } + + virtual void doProcess(uint16_t len) + { + } +}; + +#endif // _MIDI_EFFECT_H \ No newline at end of file diff --git a/src/minidexed.cpp b/src/minidexed.cpp index ebebea2..d9fa8c6 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -108,6 +108,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); assert (m_pTG[i]); + m_MidiArpSpinLock[i] = new CSpinLock(); m_MidiArp[i] = new MidiArp(pConfig->GetSampleRate(), m_pTG[i]); m_pTG[i]->setEngineType(pConfig->GetEngineType ()); @@ -417,8 +418,13 @@ void CMiniDexed::Run (unsigned nCore) for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) { assert (m_pTG[nTG]); + + m_MidiArpSpinLock[nTG]->Acquire(); m_MidiArp[nTG]->process(m_nFramesToProcess); + m_MidiArpSpinLock[nTG]->Release(); + m_pTG[nTG]->getSamples (m_OutputLevel[nTG][0],m_nFramesToProcess); + m_InsertFXSpinLock[nTG]->Acquire(); m_InsertFX[nTG]->process(m_OutputLevel[nTG][0], m_OutputLevel[nTG][0], m_OutputLevel[nTG][0], m_OutputLevel[nTG][1], m_nFramesToProcess); m_InsertFXSpinLock[nTG]->Release(); @@ -746,6 +752,7 @@ void CMiniDexed::setTempo(unsigned nValue) for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { m_InsertFX[nTG]->setTempo(m_nTempo); + m_MidiArp[nTG]->setTempo(m_nTempo); } // Update UI @@ -785,8 +792,16 @@ void CMiniDexed::keyup (int16_t pitch, unsigned nTG) pitch = ApplyNoteLimits (pitch, nTG); if (pitch >= 0) { - m_MidiArp[nTG]->keyup(pitch); - //m_pTG[nTG]->keyup (pitch); + if (m_MidiArp[nTG]->getBypass()) + { + m_pTG[nTG]->keyup(pitch); + } + else + { + m_MidiArpSpinLock[nTG]->Acquire(); + m_MidiArp[nTG]->keyup(pitch); + m_MidiArpSpinLock[nTG]->Release(); + } } } @@ -798,8 +813,16 @@ void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) pitch = ApplyNoteLimits (pitch, nTG); if (pitch >= 0) { - m_MidiArp[nTG]->keydown(pitch, velocity); - //m_pTG[nTG]->keydown (pitch, velocity); + if (m_MidiArp[nTG]->getBypass()) + { + m_pTG[nTG]->keydown (pitch, velocity); + } + else + { + m_MidiArpSpinLock[nTG]->Acquire(); + m_MidiArp[nTG]->keydown(pitch, velocity); + m_MidiArpSpinLock[nTG]->Release(); + } } } @@ -1050,7 +1073,10 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT case TGParameterReverbSend: SetReverbSend (nValue, nTG); break; case TGParameterInsertFXType: setInsertFXType(nValue, nTG); break; - + case TGParameterMidiFXType: + // TODO + break; + default: assert (0); break; @@ -1075,6 +1101,7 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) case TGParameterMIDIChannel: return m_nMIDIChannel[nTG]; case TGParameterReverbSend: return m_nReverbSend[nTG]; case TGParameterInsertFXType: return m_InsertFX[nTG]->getId(); + case TGParameterMidiFXType: return m_MidiArp[nTG]->getId(); case TGParameterPitchBendRange: return m_nPitchBendRange[nTG]; case TGParameterPitchBendStep: return m_nPitchBendStep[nTG]; case TGParameterPortamentoMode: return m_nPortamentoMode[nTG]; @@ -1108,6 +1135,20 @@ int CMiniDexed::GetTGParameter (TTGParameter Parameter, unsigned nTG) } } +void CMiniDexed::SetMidiFXParameter (unsigned Parameter, int nValue, unsigned nTG, unsigned nFXType) { + assert (nTG < CConfig::ToneGenerators); + assert (m_MidiArp[nTG]->getId() == nFXType); + + m_MidiArp[nTG]->setParameter(Parameter, nValue); +} + +int CMiniDexed::GetMidiFXParameter (unsigned Parameter, unsigned nTG, unsigned nFXType) { + assert (nTG < CConfig::ToneGenerators); + assert (m_MidiArp[nTG]->getId() == nFXType); + + return m_MidiArp[nTG]->getParameter(Parameter); +} + void CMiniDexed::SetTGFXParameter (unsigned Parameter, int nValue, unsigned nTG, unsigned nFXType) { assert (nTG < CConfig::ToneGenerators); assert (m_InsertFX[nTG]->getId() == nFXType); @@ -1218,13 +1259,17 @@ void CMiniDexed::ProcessSound (void) } float32_t SampleBuffer[2][nFrames]; + + m_MidiArpSpinLock[0]->Acquire(); m_MidiArp[0]->process(nFrames); + m_MidiArpSpinLock[0]->Release(); + m_pTG[0]->getSamples (SampleBuffer[0], nFrames); + m_InsertFXSpinLock[0]->Acquire(); 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; iAcquire(); m_MidiArp[i]->process(nFrames); + m_MidiArpSpinLock[i]->Release(); + m_pTG[i]->getSamples (m_OutputLevel[i][0], nFrames); + m_InsertFXSpinLock[i]->Acquire(); m_InsertFX[i]->process(m_OutputLevel[i][0], m_OutputLevel[i][0], m_OutputLevel[i][0], m_OutputLevel[i][1], nFrames); m_InsertFXSpinLock[i]->Release(); diff --git a/src/minidexed.h b/src/minidexed.h index 52b1729..15b786f 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -192,6 +192,7 @@ public: TGParameterVolume, TGParameterPan, TGParameterInsertFXType, + TGParameterMidiFXType, TGParameterMasterTune, TGParameterCutoff, TGParameterResonance, @@ -230,6 +231,9 @@ public: void SetTGParameter (TTGParameter Parameter, int nValue, unsigned nTG); int GetTGParameter (TTGParameter Parameter, unsigned nTG); + void SetMidiFXParameter (unsigned Parameter, int nValue, unsigned nTG, unsigned nFXType); + int GetMidiFXParameter (unsigned Parameter, unsigned nTG, unsigned nFXType); + void SetTGFXParameter (unsigned Parameter, int nValue, unsigned nTG, unsigned nFXType); int GetTGFXParameter (unsigned Parameter, unsigned nTG, unsigned nFXType); @@ -341,6 +345,7 @@ private: AudioEffect* m_SendFX = NULL; float32_t m_SendFXLevel = 1.0f; + CSpinLock* m_MidiArpSpinLock[CConfig::ToneGenerators]; CSpinLock* m_InsertFXSpinLock[CConfig::ToneGenerators]; CSpinLock m_SendFXSpinLock; CSpinLock m_ReverbSpinLock; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index b37d3c3..98487b2 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -75,6 +75,7 @@ const CUIMenu::TMenuItem CUIMenu::s_TGMenu[] = {"Portamento", MenuHandler, s_EditPortamentoMenu}, {"Poly/Mono", EditTGParameter, 0, CMiniDexed::TGParameterMonoMode}, {"Modulation", MenuHandler, s_ModulationMenu}, + {"Arp", MenuHandler, s_ModulationMenu}, {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, {"Edit Voice", MenuHandler, s_EditVoiceMenu}, {0} @@ -236,6 +237,21 @@ CUIMenu::TMenuItem CUIMenu::s_FXReverb[] = {0} }; +CUIMenu::TMenuItem CUIMenu::s_Arp[] = +{ + {"Bypass", EditTGArpParameter, 0, MidiArp::Param::BYPASS}, + {"Latch", EditTGArpParameter, 0, MidiArp::Param::LATCH}, + {"Sync", EditTGArpParameter, 0, MidiArp::Param::SYNC}, + {"Arp Mode", EditTGArpParameter, 0, MidiArp::Param::ARP_MODE}, + {"Division", EditTGArpParameter, 0, MidiArp::Param::DIVISION}, + {"Note Length", EditTGArpParameter, 0, MidiArp::Param::NOTE_LENGTH}, + {"Velocity", EditTGArpParameter, 0, MidiArp::Param::VELOCITY}, + {"Oct Spread", EditTGArpParameter, 0, MidiArp::Param::OCTAVE_SPREAD}, + {"Oct Mode", EditTGArpParameter, 0, MidiArp::Param::OCTAVE_MODE}, + {"Panic", EditTGArpParameter, 0, MidiArp::Param::PANIC}, + {0} +}; + // inserting menu items before "OP1" affect OPShortcutHandler() const CUIMenu::TMenuItem CUIMenu::s_EditVoiceMenu[] = { @@ -331,6 +347,7 @@ const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] {0, 127, 8, ToVolume}, // TGParameterVolume {0, 127, 8, ToPan}, // TGParameterPan {0, 7, 1, ToFXType}, // TGParameterInsertFXType + {0, 1, 1}, // TGParameterMidiFXType {-99, 99, 1}, // TGParameterMasterTune {0, 99, 1}, // TGParameterCutoff {0, 99, 1}, // TGParameterResonance @@ -436,6 +453,21 @@ const CUIMenu::TParameter CUIMenu::s_TGFXReverbParam[AudioEffectPlateReverb::Par {0, 99, 1}, // LEVEL }; +// must match MidiArp::Param +const CUIMenu::TParameter CUIMenu::s_ArpParam[MidiArp::Param::UNKNOWN] = +{ + {0, 1, 1, ToOnOff}, // BYPASS + {0, 1, 1}, // LATCH + {0, 2, 1}, // SYNC + {0, 5, 1}, // ARP_MODE + {0, 12, 1}, // DIVISION + {0, 100, 1}, // NOTE_LENGTH + {0, 127, 1}, // VELOCITY + {1, 4, 1}, // OCTAVE_SPREAD + {0, 4, 1}, // OCTAVE_MODE + {0, 1, 1} // PANIC +}; + // must match DexedVoiceParameters in Synth_Dexed const CUIMenu::TParameter CUIMenu::s_VoiceParameter[] = { @@ -1177,6 +1209,74 @@ void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) } } +void CUIMenu::EditTGArpParameter (CUIMenu *pUIMenu, TMenuEvent Event) +{ + // Get TG + unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; + + // Get FX type + int nFXType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterMidiFXType, nTG); + + // Get Param + unsigned nParam = pUIMenu->m_nCurrentParameter; + TParameter pParam = s_ArpParam[nParam]; + const TParameter &rParam = pParam; + + int nValue = pUIMenu->m_pMiniDexed->GetMidiFXParameter (nParam, nTG, nFXType); + + switch (Event) + { + case MenuEventUpdate: + break; + + case MenuEventPressAndStepDown: + nValue -= rParam.Increment * 9; + case MenuEventStepDown: + nValue -= rParam.Increment; + if (nValue < rParam.Minimum) + { + nValue = rParam.Minimum; + } + pUIMenu->m_pMiniDexed->SetMidiFXParameter (nParam, nValue, nTG, nFXType); + break; + + case MenuEventPressAndStepUp: + nValue += rParam.Increment * 9; + case MenuEventStepUp: + nValue += rParam.Increment; + if (nValue > rParam.Maximum) + { + nValue = rParam.Maximum; + } + pUIMenu->m_pMiniDexed->SetMidiFXParameter (nParam, nValue, nTG, nFXType); + break; + + default: + return; + } + + string TG ("TG"); + TG += to_string (nTG+1); + + // Get value again after change + nValue = pUIMenu->m_pMiniDexed->GetMidiFXParameter (nParam, nTG, nFXType); + CUIMenu::TToString *pToString = rParam.ToString; + string Value; + if (pToString) + { + Value = (*pToString) (nValue); + } + else + { + Value = to_string (nValue); + } + + pUIMenu->m_pUI->DisplayWrite (TG.c_str (), + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + Value.c_str (), + nValue > rParam.Minimum, nValue < rParam.Maximum); +} + void CUIMenu::EditTGFXParameter (CUIMenu *pUIMenu, TMenuEvent Event) { // Get TG diff --git a/src/uimenu.h b/src/uimenu.h index 2c95aac..6c164bd 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -99,6 +99,7 @@ private: static void MenuHandlerInsertFX (CUIMenu *pUIMenu, TMenuEvent Event); static void EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event); static void EditTGFXParameter (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditTGArpParameter (CUIMenu *pUIMenu, TMenuEvent Event); static CUIMenu::TMenuItem* getInsertFXMenuItem(unsigned type); static void MenuHandlerSendFX (CUIMenu *pUIMenu, TMenuEvent Event); @@ -175,6 +176,8 @@ private: static TMenuItem s_FXTalReverb3[]; static TMenuItem s_FXReverb[]; + static TMenuItem s_Arp[]; + static const TMenuItem s_EditVoiceMenu[]; static const TMenuItem s_OperatorMenu[]; static const TMenuItem s_SaveMenu[]; @@ -194,6 +197,7 @@ private: static const TParameter s_TGFXBigMuffParam[]; static const TParameter s_TGFXTalReverb3Param[]; static const TParameter s_TGFXReverbParam[]; + static const TParameter s_ArpParam[]; static const TParameter s_VoiceParameter[]; static const TParameter s_OPParameter[];