From 0cb0553f25f5815e57da98d9cafde804da352c5b Mon Sep 17 00:00:00 2001 From: jnonis Date: Thu, 18 Jul 2024 04:15:19 +0000 Subject: [PATCH 01/11] Not working Arp --- src/Makefile | 7 +- src/midi_arp.cpp | 101 ++++ src/midi_arp.h | 40 ++ src/minidexed.cpp | 12 +- src/minidexed.h | 2 + src/modarpeggiator/arpeggiator.cpp | 648 ++++++++++++++++++++++ src/modarpeggiator/arpeggiator.hpp | 122 ++++ src/modarpeggiator/common/clock.cpp | 241 ++++++++ src/modarpeggiator/common/clock.hpp | 80 +++ src/modarpeggiator/common/commons.h | 33 ++ src/modarpeggiator/common/midiHandler.cpp | 65 +++ src/modarpeggiator/common/midiHandler.hpp | 60 ++ src/modarpeggiator/common/pattern.cpp | 242 ++++++++ src/modarpeggiator/common/pattern.hpp | 93 ++++ src/modarpeggiator/plugin.cpp | 339 +++++++++++ src/modarpeggiator/plugin.hpp | 104 ++++ src/modarpeggiator/utils.cpp | 43 ++ src/modarpeggiator/utils.hpp | 15 + 18 files changed, 2242 insertions(+), 5 deletions(-) create mode 100644 src/midi_arp.cpp create mode 100644 src/midi_arp.h create mode 100644 src/modarpeggiator/arpeggiator.cpp create mode 100644 src/modarpeggiator/arpeggiator.hpp create mode 100644 src/modarpeggiator/common/clock.cpp create mode 100644 src/modarpeggiator/common/clock.hpp create mode 100644 src/modarpeggiator/common/commons.h create mode 100644 src/modarpeggiator/common/midiHandler.cpp create mode 100644 src/modarpeggiator/common/midiHandler.hpp create mode 100644 src/modarpeggiator/common/pattern.cpp create mode 100644 src/modarpeggiator/common/pattern.hpp create mode 100644 src/modarpeggiator/plugin.cpp create mode 100644 src/modarpeggiator/plugin.hpp create mode 100644 src/modarpeggiator/utils.cpp create mode 100644 src/modarpeggiator/utils.hpp diff --git a/src/Makefile b/src/Makefile index 1d744ee..72f33c9 100644 --- a/src/Makefile +++ b/src/Makefile @@ -13,11 +13,14 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ effect_talreverb3.o effect_ds1.o effect_bigmuff.o \ moddistortion/Distortion_DS1.o moddistortion/Distortion_BigMuff.o \ moddistortion/HyperbolicTables.o moddistortion/OverSample.o \ - effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o + effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ + midi_arp.o modarpeggiator/common/clock.o \ + modarpeggiator/common/midiHandler.o modarpeggiator/common/pattern.o \ + modarpeggiator/utils.o modarpeggiator/arpeggiator.o OPTIMIZE = -O3 include ./Synth_Dexed.mk include ./Rules.mk -EXTRACLEAN += moddistortion/*.[od] \ No newline at end of file +EXTRACLEAN += moddistortion/*.[od] modarpeggiator/*.[od] modarpeggiator/common/*.[od] \ No newline at end of file diff --git a/src/midi_arp.cpp b/src/midi_arp.cpp new file mode 100644 index 0000000..82bcc2b --- /dev/null +++ b/src/midi_arp.cpp @@ -0,0 +1,101 @@ +#include "midi_arp.h" +#include + +MidiArp::MidiArp(float32_t samplerate, CDexedAdapter* synth) +{ + this->samplerate = samplerate; + this->syncMode = 1; + this->synth = synth; + + arpeggiator.transmitHostInfo(0, 4, 1, 1, 120.0); + arpeggiator.setSampleRate(samplerate); + arpeggiator.setDivision(7); + + arpeggiator.getMidiBuffer(); +} + +MidiArp::~MidiArp() +{ +} + +void MidiArp::keydown(int16_t pitch, uint8_t velocity) +{ + MidiEvent event; + event.data[0] = MIDI_NOTE_ON << 4; + event.data[1] = pitch; + event.data[2] = velocity; + event.size = 3; + event.frame = 0; + this->events.push_back(event); +} + +void MidiArp::keyup(int16_t pitch) +{ + MidiEvent event; + event.data[0] = MIDI_NOTE_OFF << 4; + event.data[1] = pitch; + event.data[2] = 0; + event.size = 3; + event.frame = 0; + this->events.push_back(event); +} + +void MidiArp::process(uint16_t len) +{ + arpeggiator.emptyMidiBuffer(); + + // Check if host supports Bar-Beat-Tick position + /* + const TimePosition& position = getTimePosition(); + if (!position.bbt.valid) { + // set-arpeggiator in free running mode + arpeggiator.setSyncMode(0); + } else { + arpeggiator.setSyncMode(syncMode); + arpeggiator.transmitHostInfo(position.playing, position.bbt.beatsPerBar, position.bbt.beat, position.bbt.barBeat, static_cast(position.bbt.beatsPerMinute)); + } + */ + + arpeggiator.process(events.data(), events.size(), len); + events.clear(); + events.shrink_to_fit(); + + /* + printf("Before Send Midi\n"); + fflush(NULL); + struct MidiBuffer buffer = arpeggiator.getMidiBuffer(); + for (unsigned x = 0; x < buffer.numBufferedEvents + buffer.numBufferedThroughEvents; x++) { + printf("Loop x: %d\n", x); + fflush(NULL); + + MidiEvent event = buffer.bufferedEvents[x]; + unsigned eventType = event.data[0] >> 4; + + switch (eventType) + { + case MIDI_NOTE_ON: + if (event.data[2] > 0) + { + if (event.data[2] <= 127) + { + this->synth->keydown(event.data[1], event.data[2]); + } + } + else + { + this->synth->keyup(event.data[1]); + } + break; + + case MIDI_NOTE_OFF: + this->synth->keyup(event.data[1]); + break; + + default: + break; + } + } + printf("After Send Midi\n"); + fflush(NULL); + */ +} \ No newline at end of file diff --git a/src/midi_arp.h b/src/midi_arp.h new file mode 100644 index 0000000..6a72419 --- /dev/null +++ b/src/midi_arp.h @@ -0,0 +1,40 @@ +/* + * Base AudioEffect interface + * Javier Nonis (https://github.com/jnonis) - 2024 + */ +#ifndef _MIDI_ARP_H +#define _MIDI_ARP_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 +{ +public: + MidiArp(float32_t samplerate, CDexedAdapter* synth); + ~MidiArp(); + + 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; + +private: + static const unsigned MIDI_NOTE_OFF = 0b1000; + static const unsigned MIDI_NOTE_ON = 0b1001; + + CDexedAdapter* synth; + Arpeggiator arpeggiator; + int syncMode; + std::vector events; +}; + +#endif // _MIDI_ARP_H \ No newline at end of file diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 8f2b2a1..ebebea2 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -108,7 +108,8 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_pTG[i] = new CDexedAdapter (CConfig::MaxNotes, pConfig->GetSampleRate ()); assert (m_pTG[i]); - + m_MidiArp[i] = new MidiArp(pConfig->GetSampleRate(), m_pTG[i]); + m_pTG[i]->setEngineType(pConfig->GetEngineType ()); m_pTG[i]->activate (); } @@ -416,6 +417,7 @@ void CMiniDexed::Run (unsigned nCore) for (unsigned i = 0; i < CConfig::TGsCore23; i++, nTG++) { assert (m_pTG[nTG]); + m_MidiArp[nTG]->process(m_nFramesToProcess); 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); @@ -783,7 +785,8 @@ void CMiniDexed::keyup (int16_t pitch, unsigned nTG) pitch = ApplyNoteLimits (pitch, nTG); if (pitch >= 0) { - m_pTG[nTG]->keyup (pitch); + m_MidiArp[nTG]->keyup(pitch); + //m_pTG[nTG]->keyup (pitch); } } @@ -795,7 +798,8 @@ void CMiniDexed::keydown (int16_t pitch, uint8_t velocity, unsigned nTG) pitch = ApplyNoteLimits (pitch, nTG); if (pitch >= 0) { - m_pTG[nTG]->keydown (pitch, velocity); + m_MidiArp[nTG]->keydown(pitch, velocity); + //m_pTG[nTG]->keydown (pitch, velocity); } } @@ -1214,6 +1218,7 @@ void CMiniDexed::ProcessSound (void) } float32_t SampleBuffer[2][nFrames]; + m_MidiArp[0]->process(nFrames); m_pTG[0]->getSamples (SampleBuffer[0], nFrames); m_InsertFXSpinLock[0]->Acquire(); m_InsertFX[0]->process(SampleBuffer[0], SampleBuffer[0], SampleBuffer[0], SampleBuffer[1], nFrames); @@ -1271,6 +1276,7 @@ void CMiniDexed::ProcessSound (void) for (unsigned i = 0; i < CConfig::TGsCore1; i++) { assert (m_pTG[i]); + m_MidiArp[i]->process(nFrames); 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); diff --git a/src/minidexed.h b/src/minidexed.h index b93a140..52b1729 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -41,6 +41,7 @@ #include #include #include "common.h" +#include "midi_arp.h" #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" @@ -302,6 +303,7 @@ private: unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators]; + MidiArp* m_MidiArp[CConfig::ToneGenerators]; AudioEffect* m_InsertFX[CConfig::ToneGenerators]; unsigned m_nReverbSend[CConfig::ToneGenerators]; diff --git a/src/modarpeggiator/arpeggiator.cpp b/src/modarpeggiator/arpeggiator.cpp new file mode 100644 index 0000000..7bec02b --- /dev/null +++ b/src/modarpeggiator/arpeggiator.cpp @@ -0,0 +1,648 @@ +#include "arpeggiator.hpp" + +Arpeggiator::Arpeggiator() : + notesPressed(0), + activeNotes(0), + notePlayed(0), + octaveMode(0), + octaveSpread(1), + arpMode(0), + noteLength(0.8), + pitch(0), + previousMidiNote(0), + velocity(80), + previousSyncMode(0), + activeNotesIndex(0), + activeNotesBypassed(0), + timeOutTime(1000), + firstNoteTimer(0), + barBeat(0.0), + pluginEnabled(true), + first(true), + arpEnabled(true), + latchMode(false), + previousLatch(false), + latchPlaying(false), + trigger(false), + firstNote(false), + quantizedStart(false), + resetPattern(false), + midiNotesCopied(false), + panic(false), + division(0), + sampleRate(48000), + bpm(0) +{ + clock.transmitHostInfo(0, 4, 1, 1, 120.0); + clock.setSampleRate(static_cast(48000.0)); + clock.setDivision(7); + + arpPattern = new Pattern*[6]; + + arpPattern[0] = new PatternUp(); + arpPattern[1] = new PatternDown(); + arpPattern[2] = new PatternUpDown(); + arpPattern[3] = new PatternUpDownAlt(); + arpPattern[4] = new PatternUp(); + arpPattern[5] = new PatternRandom(); + + + octavePattern = new Pattern*[5]; + + octavePattern[0] = new PatternUp(); + octavePattern[1] = new PatternDown(); + octavePattern[2] = new PatternUpDown(); + octavePattern[3] = new PatternUpDownAlt(); + octavePattern[4] = new PatternCycle(); + + for (unsigned i = 0; i < NUM_VOICES; i++) { + midiNotes[i][0] = EMPTY_SLOT; + midiNotes[i][1] = 0; + midiNotesBypassed[i] = EMPTY_SLOT; + } + for (unsigned i = 0; i < NUM_VOICES; i++) { + noteOffBuffer[i][MIDI_NOTE] = EMPTY_SLOT; + noteOffBuffer[i][MIDI_CHANNEL] = 0; + noteOffBuffer[i][TIMER] = 0; + } +} + +Arpeggiator::~Arpeggiator() +{ + + delete arpPattern[0]; + delete arpPattern[1]; + delete arpPattern[2]; + delete arpPattern[3]; + delete arpPattern[4]; + delete arpPattern[5]; + delete octavePattern[0]; + delete octavePattern[1]; + delete octavePattern[2]; + delete octavePattern[3]; + delete octavePattern[4]; + + delete[] arpPattern; + arpPattern = nullptr; + delete[] octavePattern; + octavePattern = nullptr; +} + +void Arpeggiator::setArpEnabled(bool arpEnabled) +{ + this->arpEnabled = arpEnabled; +} + +void Arpeggiator::setLatchMode(bool latchMode) +{ + this->latchMode = latchMode; +} + +void Arpeggiator::setSampleRate(float newSampleRate) +{ + if (newSampleRate != sampleRate) { + clock.setSampleRate(newSampleRate); + sampleRate = newSampleRate; + } +} + +void Arpeggiator::setSyncMode(int mode) +{ + + switch (mode) + { + case FREE_RUNNING: + clock.setSyncMode(FREE_RUNNING); + quantizedStart = false; + break; + case HOST_BPM_SYNC: + clock.setSyncMode(HOST_BPM_SYNC); + quantizedStart = false; + break; + case HOST_QUANTIZED_SYNC: + clock.setSyncMode(HOST_QUANTIZED_SYNC); + quantizedStart = true; + break; + } +} + +void Arpeggiator::setBpm(double newBpm) +{ + if (newBpm != bpm) { + clock.setInternalBpmValue(static_cast(newBpm)); + bpm = newBpm; + } +} + +void Arpeggiator::setDivision(int newDivision) +{ + if (newDivision != division) { + clock.setDivision(newDivision); + division = newDivision; + } +} + +void Arpeggiator::setVelocity(uint8_t velocity) +{ + this->velocity = velocity; +} + +void Arpeggiator::setNoteLength(float noteLength) +{ + this->noteLength = noteLength; +} + +void Arpeggiator::setOctaveSpread(int octaveSpread) +{ + this->octaveSpread = octaveSpread; +} + +void Arpeggiator::setArpMode(int arpMode) +{ + arpPattern[arpMode]->setStep(arpPattern[this->arpMode]->getStep()); + arpPattern[arpMode]->setDirection(arpPattern[this->arpMode]->getDirection()); + + this->arpMode = arpMode; +} + +void Arpeggiator::setOctaveMode(int octaveMode) +{ + octavePattern[octaveMode]->setStep(octavePattern[this->octaveMode]->getStep()); + octavePattern[octaveMode]->setDirection(octavePattern[this->octaveMode]->getDirection()); + + this->octaveMode = octaveMode; +} + +void Arpeggiator::setPanic(bool panic) +{ + this->panic = panic; +} + +bool Arpeggiator::getArpEnabled() const +{ + return arpEnabled; +} + +bool Arpeggiator::getLatchMode() const +{ + return latchMode; +} + +float Arpeggiator::getSampleRate() const +{ + return clock.getSampleRate(); +} + +int Arpeggiator::getSyncMode() const +{ + return clock.getSyncMode(); +} + +float Arpeggiator::getBpm() const +{ + return clock.getInternalBpmValue(); +} + +int Arpeggiator::getDivision() const +{ + return clock.getDivision(); +} + +uint8_t Arpeggiator::getVelocity() const +{ + return velocity; +} + +float Arpeggiator::getNoteLength() const +{ + return noteLength; +} + +int Arpeggiator::getOctaveSpread() const +{ + return octaveSpread; +} + +int Arpeggiator::getArpMode() const +{ + return arpMode; +} + +int Arpeggiator::getOctaveMode() const +{ + return octaveMode; +} + +bool Arpeggiator::getPanic() const +{ + return panic; +} + +void Arpeggiator::transmitHostInfo(const bool playing, const float beatsPerBar, + const int beat, const float barBeat, const double bpm) +{ + clock.transmitHostInfo(playing, beatsPerBar, beat, barBeat, bpm); + this->barBeat = barBeat; +} + +void Arpeggiator::reset() +{ + clock.reset(); + clock.setNumBarsElapsed(0); + + for (unsigned a = 0; a < NUM_ARP_MODES; a++) { + arpPattern[arpMode]->reset(); + } + for (unsigned o = 0; o < NUM_OCTAVE_MODES; o++) { + octavePattern[o]->reset(); + } + + activeNotesIndex = 0; + firstNoteTimer = 0; + notePlayed = 0; + activeNotes = 0; + notesPressed = 0; + activeNotesBypassed = 0; + latchPlaying = false; + firstNote = false; + first = true; + + for (unsigned i = 0; i < NUM_VOICES; i++) { + midiNotes[i][MIDI_NOTE] = EMPTY_SLOT; + midiNotes[i][MIDI_CHANNEL] = 0; + } +} + +void Arpeggiator::emptyMidiBuffer() +{ + midiHandler.emptyMidiBuffer(); +} + +void Arpeggiator::allNotesOff() +{ + for (unsigned i = 0; i < NUM_VOICES; i++) { + midiNotesBypassed[i] = EMPTY_SLOT; + } + notesPressed = 0; + activeNotes = 0; + reset(); +} + +struct MidiBuffer Arpeggiator::getMidiBuffer() +{ + return midiHandler.getMidiBuffer(); +} + +void Arpeggiator::process(const MidiEvent* events, uint32_t eventCount, uint32_t n_frames) +{ + struct MidiEvent midiEvent; + struct MidiEvent midiThroughEvent; + + if (!arpEnabled && !latchMode) { + + reset(); + + for (unsigned clear_notes = 0; clear_notes < NUM_VOICES; clear_notes++) { + midiNotes[clear_notes][MIDI_NOTE] = EMPTY_SLOT; + midiNotes[clear_notes][MIDI_CHANNEL] = 0; + } + } + + if (!latchMode && previousLatch && notesPressed <= 0) { + reset(); + } + if (latchMode != previousLatch) { + previousLatch = latchMode; + } + + if (panic) { + reset(); + panic = false; + } + + for (uint32_t i=0; i NUM_VOICES - 1) { + reset(); + } else { + if (first) { + firstNote = true; + } + if (notesPressed == 0) { + if (!latchPlaying) { //TODO check if there needs to be an exception when using sync + octavePattern[octaveMode]->reset(); + clock.reset(); + notePlayed = 0; + } + if (latchMode) { + latchPlaying = true; + activeNotes = 0; + for (unsigned i = 0; i < NUM_VOICES; i++) { + midiNotes[i][MIDI_NOTE] = EMPTY_SLOT; + midiNotes[i][MIDI_CHANNEL] = 0; + } + } + resetPattern = true; + } + + findFreeVoice = 0; + findActivePitch = 0; + voiceFound = false; + pitchFound = false; + + while (findActivePitch < NUM_VOICES && !pitchFound) + { + if (midiNotes[findActivePitch][MIDI_NOTE] == (uint32_t)midiNote) { + pitchFound = true; + } + findActivePitch++; + } + + if (!pitchFound) { + while (findFreeVoice < NUM_VOICES && !voiceFound) + { + if (midiNotes[findFreeVoice][MIDI_NOTE] == EMPTY_SLOT) { + midiNotes[findFreeVoice][MIDI_NOTE] = midiNote; + midiNotes[findFreeVoice][MIDI_CHANNEL] = channel; + voiceFound = true; + } + findFreeVoice++; + } + notesPressed++; + activeNotes++; + } + + if (arpMode != ARP_PLAYED) + utils.quicksort(midiNotes, 0, NUM_VOICES - 1); + if (midiNote < midiNotes[notePlayed - 1][MIDI_NOTE] && notePlayed > 0) { + notePlayed++; + } + } + break; + case MIDI_NOTEOFF: + searchNote = 0; + foundNote = 0; + noteOffFoundInBuffer = false; + noteToFind = midiNote; + + if (!latchMode) { + latchPlaying = false; + } else { + latchPlaying = true; + } + + while (searchNote < NUM_VOICES) + { + if (midiNotes[searchNote][MIDI_NOTE] == noteToFind) + { + foundNote = searchNote; + noteOffFoundInBuffer = true; + searchNote = NUM_VOICES; + } + searchNote++; + } + + if (noteOffFoundInBuffer) { + + notesPressed = (notesPressed > 0) ? notesPressed - 1 : 0; + + if (!latchPlaying) { + activeNotes = notesPressed; + } + + if (!latchMode) { + midiNotes[foundNote][MIDI_NOTE] = EMPTY_SLOT; + midiNotes[foundNote][MIDI_CHANNEL] = 0; + if (arpMode != ARP_PLAYED) + utils.quicksort(midiNotes, 0, NUM_VOICES - 1); + } + } else { + midiThroughEvent.frame = events[i].frame; + midiThroughEvent.size = events[i].size; + for (unsigned d = 0; d < midiThroughEvent.size; d++) { + midiThroughEvent.data[d] = events[i].data[d]; + } + midiHandler.appendMidiThroughMessage(midiThroughEvent); + } + if (activeNotes == 0 && !latchPlaying && !latchMode) { + reset(); + } + break; + default: + midiThroughEvent.frame = events[i].frame; + midiThroughEvent.size = events[i].size; + for (unsigned d = 0; d < midiThroughEvent.size; d++) { + midiThroughEvent.data[d] = events[i].data[d]; + } + midiHandler.appendMidiThroughMessage(midiThroughEvent); + break; + } + } else { //if arpeggiator is off + + if (!midiNotesCopied) { + for (unsigned b = 0; b < NUM_VOICES; b++) { + midiNotesBypassed[b] = midiNotes[b][MIDI_NOTE]; + } + midiNotesCopied = true; + } + + if (latchMode) { + + uint8_t noteToFind = midiNote; + size_t searchNote = 0; + + switch (status) + { + case MIDI_NOTEOFF: + while (searchNote < NUM_VOICES) + { + if (midiNotesBypassed[searchNote] == noteToFind) { + midiNotesBypassed[searchNote] = EMPTY_SLOT; + searchNote = NUM_VOICES; + notesPressed = (notesPressed > 0) ? notesPressed - 1 : 0; + } + searchNote++; + } + break; + } + } + + if (midiNote == 0x7b && events[i].size == 3) { + allNotesOff(); + } + //send MIDI message through + midiHandler.appendMidiThroughMessage(events[i]); + first = true; + } + } + + arpPattern[arpMode]->setPatternSize(activeNotes); + + int patternSize; + + switch (arpMode) + { + case ARP_UP_DOWN: + patternSize = (activeNotes >= 3) ? activeNotes + (activeNotes - 2) : activeNotes; + break; + case ARP_UP_DOWN_ALT: + patternSize = (activeNotes >= 3) ? activeNotes * 2 : activeNotes; + break; + default: + patternSize = activeNotes; + break; + } + + switch (octaveMode) + { + case ONE_OCT_UP_PER_CYCLE: + octavePattern[octaveMode]->setPatternSize(patternSize); + octavePattern[octaveMode]->setCycleRange(octaveSpread); + break; + default: + octavePattern[octaveMode]->setPatternSize(octaveSpread); + break; + } + + for (unsigned s = 0; s < n_frames; s++) { + + bool timeOut = (firstNoteTimer > (int)timeOutTime) ? false : true; + + if (firstNote) { + clock.closeGate(); //close gate to prevent opening before timeOut + firstNoteTimer++; + } + + if (clock.getSyncMode() <= 1 && first) { + clock.setPos(0); + clock.reset(); + } + + clock.tick(); + + if ((clock.getGate() && !timeOut)) { + + if (arpEnabled) { + + if (resetPattern) { + octavePattern[octaveMode]->reset(); + if (octaveMode == ARP_DOWN) { + octavePattern[octaveMode]->setStep(activeNotes - 1); //TODO maybe put this in reset() + } + + arpPattern[arpMode]->reset(); + if (arpMode == ARP_DOWN) { + arpPattern[arpMode]->setStep(activeNotes - 1); + } + + resetPattern = false; + + notePlayed = arpPattern[arpMode]->getStep(); + } + + if (first) { + //send all notes off, on current active MIDI channel + midiEvent.size = 3; + midiEvent.data[2] = 0; + + midiEvent.frame = s; + midiEvent.data[0] = 0xb0 | midiNotes[notePlayed][MIDI_CHANNEL]; + midiEvent.data[1] = 0x40; // sustain pedal + midiHandler.appendMidiMessage(midiEvent); + midiEvent.data[1] = 0x7b; // all notes off + midiHandler.appendMidiMessage(midiEvent); + + first = false; + } + } + + size_t searchedVoices = 0; + bool noteFound = false; + + while (!noteFound && searchedVoices < NUM_VOICES && activeNotes > 0 && arpEnabled) + { + notePlayed = (notePlayed < 0) ? 0 : notePlayed; + + if (midiNotes[notePlayed][MIDI_NOTE] > 0 + && midiNotes[notePlayed][MIDI_NOTE] < 128) + { + //create MIDI note on message + uint8_t midiNote = midiNotes[notePlayed][MIDI_NOTE]; + uint8_t channel = midiNotes[notePlayed][MIDI_CHANNEL]; + + if (arpEnabled) { + + uint8_t octave = octavePattern[octaveMode]->getStep() * 12; + octavePattern[octaveMode]->goToNextStep(); + + midiNote = midiNote + octave; + + midiEvent.frame = s; + midiEvent.size = 3; + midiEvent.data[0] = MIDI_NOTEON | channel; + midiEvent.data[1] = midiNote; + midiEvent.data[2] = velocity; + + midiHandler.appendMidiMessage(midiEvent); + + noteOffBuffer[activeNotesIndex][MIDI_NOTE] = (uint32_t)midiNote; + noteOffBuffer[activeNotesIndex][MIDI_CHANNEL] = (uint32_t)channel; + activeNotesIndex = (activeNotesIndex + 1) % NUM_NOTE_OFF_SLOTS; + noteFound = true; + firstNote = false; + } + } + arpPattern[arpMode]->goToNextStep(); + notePlayed = arpPattern[arpMode]->getStep(); + searchedVoices++; + } + clock.closeGate(); + } + + for (size_t i = 0; i < NUM_NOTE_OFF_SLOTS; i++) { + if (noteOffBuffer[i][MIDI_NOTE] != EMPTY_SLOT) { + noteOffBuffer[i][TIMER] += 1; + if (noteOffBuffer[i][TIMER] > static_cast(clock.getPeriod() * noteLength)) { + midiEvent.frame = s; + midiEvent.size = 3; + midiEvent.data[0] = MIDI_NOTEOFF | noteOffBuffer[i][MIDI_CHANNEL]; + midiEvent.data[1] = static_cast(noteOffBuffer[i][MIDI_NOTE]); + midiEvent.data[2] = 0; + + midiHandler.appendMidiMessage(midiEvent); + + noteOffBuffer[i][MIDI_NOTE] = EMPTY_SLOT; + noteOffBuffer[i][MIDI_CHANNEL] = 0; + noteOffBuffer[i][TIMER] = 0; + + } + } + } + } + midiHandler.mergeBuffers(); +} diff --git a/src/modarpeggiator/arpeggiator.hpp b/src/modarpeggiator/arpeggiator.hpp new file mode 100644 index 0000000..f92b008 --- /dev/null +++ b/src/modarpeggiator/arpeggiator.hpp @@ -0,0 +1,122 @@ +#ifndef _H_ARPEGGIATOR_ +#define _H_ARPEGGIATOR_ + +#include + +#include "common/commons.h" +#include "common/clock.hpp" +#include "common/pattern.hpp" +#include "common/midiHandler.hpp" +#include "utils.hpp" + +#define NUM_VOICES 32 +#define NUM_NOTE_OFF_SLOTS 32 +#define PLUGIN_URI "http://moddevices.com/plugins/mod-devel/arpeggiator" + +#define MIDI_NOTEOFF 0x80 +#define MIDI_NOTEON 0x90 + +#define MIDI_NOTE 0 +#define MIDI_CHANNEL 1 +#define TIMER 2 + +#define NUM_ARP_MODES 6 +#define NUM_OCTAVE_MODES 5 + +#define NUM_MIDI_CHANNELS 16 + +#define ONE_OCT_UP_PER_CYCLE 4 + +class Arpeggiator { +public: + enum ArpModes { + ARP_UP = 0, + ARP_DOWN, + ARP_UP_DOWN, + ARP_UP_DOWN_ALT, + ARP_PLAYED, + ARP_RANDOM + }; + Arpeggiator(); + ~Arpeggiator(); + void setArpEnabled(bool arpEnabled); + void setLatchMode(bool latchMode); + void setSampleRate(float sampleRate); + void setSyncMode(int mode); + void setBpm(double bpm); + void setDivision(int division); + void setVelocity(uint8_t velocity); + void setNoteLength(float noteLength); + void setOctaveSpread(int octaveSpread); + void setArpMode(int arpMode); + void setOctaveMode(int octaveMode); + void setPanic(bool panic); + bool getArpEnabled() const; + bool getLatchMode() const; + float getSampleRate() const; + int getSyncMode() const; + float getBpm() const; + int getDivision() const; + uint8_t getVelocity() const; + float getNoteLength() const; + int getOctaveSpread() const; + int getArpMode() const; + int getOctaveMode() const; + bool getPanic() const; + void transmitHostInfo(const bool playing, const float beatsPerBar, + const int beat, const float barBeat, const double bpm); + void reset(); + void emptyMidiBuffer(); + void allNotesOff(); + struct MidiBuffer getMidiBuffer(); + void process(const MidiEvent* event, uint32_t eventCount, uint32_t n_frames); +private: + uint8_t midiNotes[NUM_VOICES][2]; + uint8_t midiNotesBypassed[NUM_VOICES]; + uint32_t noteOffBuffer[NUM_NOTE_OFF_SLOTS][3]; + + int notesPressed; + int activeNotes; + int notePlayed; + + int octaveMode; + int octaveSpread; + int arpMode; + + float noteLength; + + uint8_t pitch; + uint8_t previousMidiNote; + uint8_t velocity; + int previousSyncMode; + int activeNotesIndex; + int activeNotesBypassed; + int timeOutTime; + int firstNoteTimer; + float barBeat; + + bool pluginEnabled; + bool first; + bool arpEnabled; + bool latchMode; + bool previousLatch; + bool latchPlaying; + bool trigger; + bool firstNote; + bool quantizedStart; + bool resetPattern; + bool midiNotesCopied; + bool panic; + + int division; + float sampleRate; + double bpm; + + ArpUtils utils; + Pattern **arpPattern; + Pattern **octavePattern; + MidiHandler midiHandler; + PluginClock clock; +}; + +#endif //_H_ARPEGGIATOR_ diff --git a/src/modarpeggiator/common/clock.cpp b/src/modarpeggiator/common/clock.cpp new file mode 100644 index 0000000..612932b --- /dev/null +++ b/src/modarpeggiator/common/clock.cpp @@ -0,0 +1,241 @@ +#include "clock.hpp" + +PluginClock::PluginClock() : + gate(false), + trigger(false), + beatSync(true), + phaseReset(false), + playing(false), + previousPlaying(false), + endOfBar(false), + init(false), + period(0), + halfWavelength(0), + quarterWaveLength(0), + pos(0), + beatsPerBar(1.0), + bpm(120.0), + internalBpm(120.0), + previousBpm(0), + sampleRate(48000.0), + division(1), + hostBarBeat(0.0), + beatTick(0.0), + syncMode(1), + previousSyncMode(0), + hostTick(0), + hostBeat(0), + barLength(4), + numBarsElapsed(0), + previousBeat(0), + arpMode(0) +{ +} + +PluginClock::~PluginClock() +{ +} + +void PluginClock::transmitHostInfo(const bool playing, const float beatsPerBar, + const int hostBeat, const float hostBarBeat, const float hostBpm) +{ + this->beatsPerBar = beatsPerBar; + this->hostBeat = hostBeat; + this->hostBarBeat = hostBarBeat; + this->hostBpm = hostBpm; + this->playing = playing; + + if (playing && !previousPlaying && beatSync) { + syncClock(); + } + if (playing != previousPlaying) { + previousPlaying = playing; + } + + if (!init) { + calcPeriod(); + init = true; + } +} + +void PluginClock::setSyncMode(int mode) +{ + switch (mode) + { + case FREE_RUNNING: + beatSync = false; + break; + case HOST_BPM_SYNC: + beatSync = false; + break; + case HOST_QUANTIZED_SYNC: + beatSync = true; + break; + } + + this->syncMode = mode; +} + +void PluginClock::setInternalBpmValue(float internalBpm) +{ + this->internalBpm = internalBpm; +} + +void PluginClock::setBpm(float bpm) +{ + this->bpm = bpm; + calcPeriod(); +} + +void PluginClock::setSampleRate(float sampleRate) +{ + this->sampleRate = sampleRate; + calcPeriod(); +} + +void PluginClock::setDivision(int setDivision) +{ + this->division = setDivision; + this->divisionValue = divisionValues[setDivision]; + + calcPeriod(); +} + +void PluginClock::syncClock() +{ + pos = static_cast(fmod(sampleRate * (60.0f / bpm) * (hostBarBeat + (numBarsElapsed * beatsPerBar)), sampleRate * (60.0f / (bpm * (divisionValue / 2.0f))))); +} + +void PluginClock::setPos(uint32_t pos) +{ + this->pos = pos; +} + +void PluginClock::setNumBarsElapsed(uint32_t numBarsElapsed) +{ + this->numBarsElapsed = numBarsElapsed; +} + +void PluginClock::calcPeriod() +{ + period = static_cast(sampleRate * (60.0f / (bpm * (divisionValue / 2.0f)))); + halfWavelength = static_cast(period / 2.0f); + quarterWaveLength = static_cast(halfWavelength / 2.0f); + period = (period <= 0) ? 1 : period; +} + +void PluginClock::closeGate() +{ + gate = false; +} + +void PluginClock::reset() +{ + trigger = false; +} + +float PluginClock::getSampleRate() const +{ + return sampleRate; +} + +bool PluginClock::getGate() const +{ + return gate; +} + +int PluginClock::getSyncMode() const +{ + return syncMode; +} + +float PluginClock::getInternalBpmValue() const +{ + return internalBpm; +} + +int PluginClock::getDivision() const +{ + return division; +} + +uint32_t PluginClock::getPeriod() const +{ + return period; +} + +uint32_t PluginClock::getPos() const +{ + return pos; +} + +void PluginClock::tick() +{ + int beat = static_cast(hostBarBeat); + + if (beatsPerBar <= 1) { + if (hostBarBeat > 0.99 && !endOfBar) { + endOfBar = true; + } + else if (hostBarBeat < 0.1 && endOfBar) { + numBarsElapsed++; + endOfBar = false; + } + } else { + if (beat != previousBeat) { + numBarsElapsed = (beat == 0) ? numBarsElapsed + 1 : numBarsElapsed; + previousBeat = beat; + } + } + + float threshold = 0.009; //TODO might not be needed + + switch (syncMode) + { + case FREE_RUNNING: + if ((internalBpm != previousBpm) || (syncMode != previousSyncMode)) { + setBpm(internalBpm); + previousBpm = internalBpm; + previousSyncMode = syncMode; + } + break; + case HOST_BPM_SYNC: + if ((hostBpm != previousBpm && (fabs(previousBpm - hostBpm) > threshold)) || (syncMode != previousSyncMode)) { + setBpm(hostBpm); + previousBpm = hostBpm; + previousSyncMode = syncMode; + } + break; + case HOST_QUANTIZED_SYNC: //TODO fix this duplicate + if ((hostBpm != previousBpm && (fabs(previousBpm - hostBpm) > threshold)) || (syncMode != previousSyncMode)) { + setBpm(hostBpm); + if (playing) { + syncClock(); + } + previousBpm = hostBpm; + previousSyncMode = syncMode; + } + break; + } + + if (pos > period) { + pos = 0; + } + + if (pos < quarterWaveLength && !trigger) { + gate = true; + trigger = true; + } else if (pos > halfWavelength && trigger) { + if (playing && beatSync) { + syncClock(); + } + trigger = false; + } + + if (playing && beatSync) { + syncClock(); //hard-sync to host position + } + else if (!beatSync) { + pos++; + } +} diff --git a/src/modarpeggiator/common/clock.hpp b/src/modarpeggiator/common/clock.hpp new file mode 100644 index 0000000..93c1f96 --- /dev/null +++ b/src/modarpeggiator/common/clock.hpp @@ -0,0 +1,80 @@ +#ifndef _H_CLOCK_ +#define _H_CLOCK_ + +#include +#include + +enum SyncMode { + FREE_RUNNING = 0, + HOST_BPM_SYNC, + HOST_QUANTIZED_SYNC +}; + +class PluginClock { +public: + PluginClock(); + ~PluginClock(); + void transmitHostInfo(const bool playing, const float beatstPerBar, + const int hostBeat, const float hostBarBeat, const float hostBpm); + void setSampleRate(float sampleRate); + void setSyncMode(int mode); + void setInternalBpmValue(float internalBpm); + void setDivision(int division); + void syncClock(); + void setPos(uint32_t pos); + void setNumBarsElapsed(uint32_t numBarsElapsed); + void calcPeriod(); + void closeGate(); + void reset(); + bool getGate() const; + float getSampleRate() const; + int getSyncMode() const; + float getInternalBpmValue() const; + int getDivision() const; + uint32_t getPeriod() const; + uint32_t getPos() const; + void tick(); + +private: + void setBpm(float bpm); + + bool gate; + bool trigger; + bool beatSync; + bool phaseReset; + bool playing; + bool previousPlaying; + bool endOfBar; + bool init; + + uint32_t period; + uint32_t halfWavelength; + uint32_t quarterWaveLength; + uint32_t pos; + + float beatsPerBar; + float bpm; + float internalBpm; + float hostBpm; + float previousBpm; + float sampleRate; + int division; + float divisionValue; + + float hostBarBeat; + float beatTick; + int syncMode; + int previousSyncMode; + int hostTick; + int hostBeat; + int barLength; + int numBarsElapsed; + int previousBeat; + + int arpMode; + + // "1/1" "1/2" "1/3" "1/4" "1/4." "1/4T" "1/8" "1/8." "1/8T" "1/16" "1/16." "1/16T" "1/32" + float divisionValues[13] {0.5, 1, 1.5, 2.0, 2.66666, 3.0, 4.0, 5.33333, 6.0, 8.0, 10.66666, 12.0, 16.0}; +}; + +#endif diff --git a/src/modarpeggiator/common/commons.h b/src/modarpeggiator/common/commons.h new file mode 100644 index 0000000..7fff133 --- /dev/null +++ b/src/modarpeggiator/common/commons.h @@ -0,0 +1,33 @@ +#ifndef _H_COMMONS_ +#define _H_COMMONS_ + +#include + +/** + MIDI event. + */ +struct MidiEvent { + /** + Size of internal data. + */ + static const uint32_t kDataSize = 4; + + /** + Time offset in frames. + */ + uint32_t frame; + + /** + Number of bytes used. + */ + uint32_t size; + + /** + MIDI data.@n + If size > kDataSize, dataExt is used (otherwise null). + */ + uint8_t data[kDataSize]; + const uint8_t* dataExt; +}; + +#endif \ No newline at end of file diff --git a/src/modarpeggiator/common/midiHandler.cpp b/src/modarpeggiator/common/midiHandler.cpp new file mode 100644 index 0000000..7f4100d --- /dev/null +++ b/src/modarpeggiator/common/midiHandler.cpp @@ -0,0 +1,65 @@ +#include "midiHandler.hpp" +#include + +MidiHandler::MidiHandler() +{ + printf("MidiHandler constructor\n"); + printf("MidiEvent size: %d\n", sizeof(MidiEvent)); + fflush(NULL); + /* + memset(buffer.bufferedEvents, 0, MIDI_BUFFER_SIZE * sizeof(MidiEvent)); + memset(buffer.bufferedMidiThroughEvents, 0, MIDI_BUFFER_SIZE * sizeof(MidiEvent)); + memset(buffer.midiOutputBuffer, 0, MIDI_BUFFER_SIZE * sizeof(MidiEvent)); + */ + for (unsigned i = 0; i < MIDI_BUFFER_SIZE; i++) { + printf("i: %d\n", i); + fflush(NULL); + for (unsigned x = 0; x < buffer.bufferedEvents[i].kDataSize; i++) { + printf("x: %d\n", x); + fflush(NULL); + /* + buffer.bufferedEvents[i].data[x] = 0; + buffer.bufferedMidiThroughEvents[i].data[x] = 0; + buffer.midiOutputBuffer[i].data[x] = 0; + */ + } + } + emptyMidiBuffer(); + //printf("buffer.bufferedEvents: %d\n", buffer.bufferedEvents[0].data); + //fflush(NULL); +} + +MidiHandler::~MidiHandler() +{ +} + +void MidiHandler::emptyMidiBuffer() +{ + buffer.numBufferedEvents = 0; + buffer.numBufferedThroughEvents = 0; +} + +void MidiHandler::appendMidiMessage(MidiEvent event) +{ + buffer.bufferedEvents[buffer.numBufferedEvents] = event; + buffer.numBufferedEvents = (buffer.numBufferedEvents + 1) % buffer.maxBufferSize; +} + +void MidiHandler::appendMidiThroughMessage(MidiEvent event) +{ + buffer.bufferedMidiThroughEvents[buffer.numBufferedThroughEvents] = event; + buffer.numBufferedThroughEvents = (buffer.numBufferedThroughEvents + 1) % buffer.maxBufferSize; +} + +void MidiHandler::mergeBuffers() +{ + for (unsigned e = 0; e < buffer.numBufferedThroughEvents; e++) { + buffer.bufferedEvents[e + buffer.numBufferedEvents] = buffer.bufferedMidiThroughEvents[e]; + } +} + +struct MidiBuffer MidiHandler::getMidiBuffer() +{ + mergeBuffers(); + return buffer; +} diff --git a/src/modarpeggiator/common/midiHandler.hpp b/src/modarpeggiator/common/midiHandler.hpp new file mode 100644 index 0000000..098f571 --- /dev/null +++ b/src/modarpeggiator/common/midiHandler.hpp @@ -0,0 +1,60 @@ +#ifndef _H_MIDI_HANDLER_ +#define _H_MIDI_HANDLER_ + +#include "commons.h" + +#include +#include + +#define MIDI_BUFFER_SIZE 2048 +#define EMPTY_SLOT 200 + +#define MIDI_NOTEOFF 0x80 +#define MIDI_NOTEON 0x90 +#define MIDI_SYSTEM_EXCLUSIVE 0xF0 +#define MIDI_MTC_QUARTER_FRAME 0xF1 +#define MIDI_SONG_POSITION_POINTER 0xF2 +#define MIDI_SONG_SELECT 0xF3 +#define MIDI_UNDEFINED_F4 0xF4 +#define MIDI_UNDEFINED_F5 0xF5 +#define MIDI_TUNE_REQUEST 0xF6 +#define MIDI_END_OF_EXCLUSIVE 0xF7 +#define MIDI_TIMING_CLOCK 0xF8 +#define MIDI_UNDEFINED_F9 0xF9 +#define MIDI_START 0xFA +#define MIDI_CONTINUE 0xFB +#define MIDI_STOP 0xFC +#define MIDI_UNDEFINED_FD 0xFD +#define MIDI_ACTIVE_SENSING 0xFE +#define MIDI_SYSTEM_RESET 0xFF + +struct MidiBuffer { + unsigned maxBufferSize = MIDI_BUFFER_SIZE; + + MidiEvent bufferedEvents[MIDI_BUFFER_SIZE]; + unsigned numBufferedEvents; + + MidiEvent bufferedMidiThroughEvents[MIDI_BUFFER_SIZE]; + unsigned numBufferedThroughEvents; + + MidiEvent midiOutputBuffer[MIDI_BUFFER_SIZE]; + unsigned numOutputEvents; +}; + +class MidiHandler { +public: + MidiHandler(); + ~MidiHandler(); + void emptyMidiBuffer(); + void appendMidiMessage(MidiEvent event); + void appendMidiThroughMessage(MidiEvent event); + void resetBuffer(); + int getNumEvents(); + void mergeBuffers(); + MidiEvent getMidiEvent(int index); + struct MidiBuffer getMidiBuffer(); +private: + MidiBuffer buffer; +}; + +#endif //_H_MIDI_HANDLER_ diff --git a/src/modarpeggiator/common/pattern.cpp b/src/modarpeggiator/common/pattern.cpp new file mode 100644 index 0000000..178e110 --- /dev/null +++ b/src/modarpeggiator/common/pattern.cpp @@ -0,0 +1,242 @@ +#include "pattern.hpp" + +Pattern::Pattern() : size(1), step(0), range(1) +{ +} + +Pattern::~Pattern() +{ +} + +void Pattern::setPatternSize(int size) +{ + this->size = size; +} + +void Pattern::setStep(int step) +{ + this->step = step; +} + +void Pattern::setCycleRange(int range) +{ + this->range = range; +} + +int Pattern::getSize() +{ + return size; +} + +int Pattern::getStep() +{ + return step; +} + +int Pattern::getDirection() +{ + return direction; +} + +PatternUp::PatternUp() +{ + reset(); +} + +PatternUp::~PatternUp() +{ +} + +void PatternUp::setDirection(int direction) +{ + this->direction = abs(direction); +} + +void PatternUp::reset() +{ + step = 0; + direction = 1; +} + +void PatternUp::goToNextStep() +{ + if (size > 0) { + step = (step + 1) % size; + } else { + step = 0; + } +} + +PatternDown::PatternDown() +{ + reset(); +} + +PatternDown::~PatternDown() +{ +} + +void PatternDown::setDirection(int direction) +{ + this->direction = abs(direction) * -1; +} + +void PatternDown::reset() +{ + step = 0; + direction = -1; +} + +void PatternDown::goToNextStep() +{ + if (size > 0) { + step = (step + direction < 0) ? size - 1 : step + direction; + } else { + step = 0; + } +} + +PatternUpDown::PatternUpDown() +{ + reset(); +} + +PatternUpDown::~PatternUpDown() +{ +} + +void PatternUpDown::setDirection(int direction) +{ + this->direction = direction; +} + +void PatternUpDown::reset() +{ + step = 0; + direction = 1; +} + +void PatternUpDown::goToNextStep() +{ + if (size > 1) { + int nextStep = step + direction; + direction = (nextStep >= size) ? -1 : direction; + direction = (nextStep < 0) ? 1 : direction; + step += direction; + } else { + step = 0; + } +} + +PatternUpDownAlt::PatternUpDownAlt() +{ + reset(); +} + +PatternUpDownAlt::~PatternUpDownAlt() +{ +} + +void PatternUpDownAlt::setDirection(int direction) +{ + this->direction = direction; +} + +void PatternUpDownAlt::reset() +{ + step = 0; + direction = 1; + checked = false; + skip = false; +} + +void PatternUpDownAlt::goToNextStep() +{ + if (size > 1) { + int nextStep = step + direction; + + if (!checked) { + if (nextStep >= size) { + direction = -1; + skip = true; + checked = true; + } + if (nextStep < 0) { + direction = 1; + skip = true; + checked = true; + } + } + + if (!skip) { + step += direction; + checked = false; + } + skip = false; + } else { + step = 0; + //TODO init other values + } +} + +PatternRandom::PatternRandom() +{ + reset(); +} + +PatternRandom::~PatternRandom() +{ +} + +void PatternRandom::setDirection(int direction) +{ + this->direction = direction; +} + +void PatternRandom::reset() +{ + goToNextStep(); +} + +void PatternRandom::goToNextStep() +{ + step = rand() % size; +} + +PatternCycle::PatternCycle() +{ + reset(); +} + +PatternCycle::~PatternCycle() +{ +} + +void PatternCycle::setDirection(int direction) +{ + this->direction = abs(direction); +} + +void PatternCycle::reset() +{ + step = 0; + tempStep = 0; + direction = 1; +} + +void PatternCycle::goToNextStep() +{ + if (size >= 1) { + int nextStep = tempStep + direction; + + if (range > 0 && size > 0) { + if (nextStep >= size) { + step = (step + 1) % range; + } + tempStep = (tempStep + direction) % size; + } + } else { + step = 0; + tempStep = 0; + } +} diff --git a/src/modarpeggiator/common/pattern.hpp b/src/modarpeggiator/common/pattern.hpp new file mode 100644 index 0000000..c5b3859 --- /dev/null +++ b/src/modarpeggiator/common/pattern.hpp @@ -0,0 +1,93 @@ +#ifndef _H_PATTERN_ +#define _H_PATTERN_ + +#include +#include + +class Pattern { + +enum { + ARP_UP = 0, + ARP_DOWN +}; + +public: + Pattern(); + virtual ~Pattern(); + void setPatternSize(int size); + void setStep(int step); + void setCycleRange(int range); + int getSize(); + int getStepSize(); + int getStep(); + int getDirection(); + virtual void setDirection(int direction) = 0; + virtual void reset() = 0; + virtual void goToNextStep() = 0; +protected: + int size; + int step; + int direction; + int range; +}; + +class PatternUp : public Pattern { +public: + PatternUp(); + ~PatternUp(); + void setDirection(int direction) override; + void reset() override; + void goToNextStep() override; +}; + +class PatternDown : public Pattern { +public: + PatternDown(); + ~PatternDown(); + void setDirection(int direction) override; + void reset() override; + void goToNextStep() override; +}; + +class PatternUpDown : public Pattern { +public: + PatternUpDown(); + ~PatternUpDown(); + void setDirection(int direction) override; + void reset() override; + void goToNextStep() override; +}; + +class PatternUpDownAlt : public Pattern { +public: + PatternUpDownAlt(); + ~PatternUpDownAlt(); + void setDirection(int direction) override; + void reset() override; + void goToNextStep() override; +private: + bool checked; + bool skip; +}; + +class PatternRandom : public Pattern { +public: + PatternRandom(); + ~PatternRandom(); + void setDirection(int direction) override; + void reset() override; + void goToNextStep() override; +}; + +class PatternCycle : public Pattern { +public: + PatternCycle(); + ~PatternCycle(); + void setDirection(int direction) override; + void reset() override; + void goToNextStep() override; +private: + int tempStep; +}; + +#endif // _H_PATTERN_ diff --git a/src/modarpeggiator/plugin.cpp b/src/modarpeggiator/plugin.cpp new file mode 100644 index 0000000..b0573f7 --- /dev/null +++ b/src/modarpeggiator/plugin.cpp @@ -0,0 +1,339 @@ +#include "plugin.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- + +PluginArpeggiator::PluginArpeggiator() : + Plugin(paramCount, 0, 0), // paramCount params, 0 program(s), 0 states + syncMode(1) +{ + arpeggiator.transmitHostInfo(0, 4, 1, 1, 120.0); + arpeggiator.setSampleRate(static_cast(getSampleRate())); + arpeggiator.setDivision(7); +} + +// ----------------------------------------------------------------------- +// Init + +void PluginArpeggiator::initParameter(uint32_t index, Parameter& parameter) +{ + if (index >= paramCount) return; + + switch (index) { + case paramSyncMode: + parameter.hints = kParameterIsAutomable | kParameterIsInteger; + parameter.name = "Sync"; + parameter.symbol = "sync"; + parameter.ranges.def = 0; + parameter.ranges.min = 0; + parameter.ranges.max = 2; + parameter.enumValues.count = 3; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const channels = new ParameterEnumerationValue[13]; + parameter.enumValues.values = channels; + channels[0].label = "Free Running"; + channels[0].value = 0; + channels[1].label = "Host Sync"; + channels[1].value = 1; + channels[2].label = "Host Sync (Quantized Start)"; + channels[2].value = 2; + } + break; + case paramBpm: + parameter.hints = kParameterIsAutomable; + parameter.name = "Bpm"; + parameter.symbol = "Bpm"; + parameter.ranges.def = 120.f; + parameter.ranges.min = 20.f; + parameter.ranges.max = 280.f; + break; + case paramDivision: + parameter.hints = kParameterIsAutomable | kParameterIsInteger; + parameter.name = "Divison"; + parameter.symbol = "Divisons"; + parameter.ranges.def = 9; + parameter.ranges.min = 0; + parameter.ranges.max = 12; + parameter.enumValues.count = 13; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const channels = new ParameterEnumerationValue[13]; + parameter.enumValues.values = channels; + channels[0].label = "1/1"; + channels[0].value = 0; + channels[1].label = "1/2"; + channels[1].value = 1; + channels[2].label = "1/3"; + channels[2].value = 2; + channels[3].label = "1/4"; + channels[3].value = 3; + channels[4].label = "1/4."; + channels[4].value = 4; + channels[5].label = "1/4T"; + channels[5].value = 5; + channels[6].label = "1/8"; + channels[6].value = 6; + channels[7].label = "1/8."; + channels[7].value = 7; + channels[8].label = "1/8T"; + channels[8].value = 8; + channels[9].label = "1/16"; + channels[9].value = 9; + channels[10].label = "1/16."; + channels[10].value = 10; + channels[11].label = "1/16T"; + channels[11].value = 11; + channels[12].label = "1/32"; + channels[12].value = 12; + } + break; + case paramVelocity: + parameter.hints = kParameterIsAutomable | kParameterIsInteger; + parameter.name = "Velocity"; + parameter.symbol = "velocity"; + parameter.ranges.def = 0; + parameter.ranges.min = 0; + parameter.ranges.max = 127; + break; + case paramNoteLength: + parameter.hints = kParameterIsAutomable; + parameter.name = "Note Length"; + parameter.symbol = "noteLength"; + parameter.unit = ""; + parameter.ranges.def = 0.f; + parameter.ranges.min = 0.f; + parameter.ranges.max = 1.f; + break; + case paramOctaveSpread: + parameter.hints = kParameterIsAutomable | kParameterIsInteger; + parameter.name = "Octave Spread"; + parameter.symbol = "octaveSpread"; + parameter.ranges.def = 0; + parameter.ranges.min = 0; + parameter.ranges.max = 3; + parameter.enumValues.count = 4; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const channels = new ParameterEnumerationValue[4]; + parameter.enumValues.values = channels; + channels[0].label = "1 oct"; + channels[0].value = 0; + channels[1].label = "2 oct"; + channels[1].value = 1; + channels[2].label = "3 oct"; + channels[2].value = 2; + channels[3].label = "4 oct"; + channels[3].value = 3; + } + break; + case paramArpMode: + parameter.hints = kParameterIsAutomable | kParameterIsInteger; + parameter.name = "Arp Mode"; + parameter.symbol = "arpMode"; + parameter.ranges.def = 0; + parameter.ranges.min = 0; + parameter.ranges.max = 5; + parameter.enumValues.count = 6; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const channels = new ParameterEnumerationValue[6]; + parameter.enumValues.values = channels; + channels[0].label = "Up"; + channels[0].value = 0; + channels[1].label = "Down"; + channels[1].value = 1; + channels[2].label = "Up-Down"; + channels[2].value = 2; + channels[3].label = "Up-Down (alt)"; + channels[3].value = 3; + channels[4].label = "Played"; + channels[4].value = 4; + channels[5].label = "Random"; + channels[5].value = 5; + } + break; + case paramOctaveMode: + parameter.hints = kParameterIsAutomable | kParameterIsInteger; + parameter.name = "Octave Mode"; + parameter.symbol = "octaveMode"; + parameter.ranges.def = 0; + parameter.ranges.min = 0; + parameter.ranges.max = 4; + parameter.enumValues.count = 5; + parameter.enumValues.restrictedMode = true; + { + ParameterEnumerationValue* const channels = new ParameterEnumerationValue[5]; + parameter.enumValues.values = channels; + channels[0].label = "Up"; + channels[0].value = 0; + channels[1].label = "Down"; + channels[1].value = 1; + channels[2].label = "Up-Down"; + channels[2].value = 2; + channels[3].label = "Down-up"; + channels[3].value = 3; + channels[4].label = "1 Up / Cycle"; + channels[4].value = 4; + } + break; + case paramLatch: + parameter.hints = kParameterIsAutomable | kParameterIsBoolean; + parameter.name = "Latch"; + parameter.symbol = "latch"; + parameter.unit = ""; + parameter.ranges.def = 0.f; + parameter.ranges.min = 0.f; + parameter.ranges.max = 1.f; + break; + case paramPanic: + parameter.hints = kParameterIsAutomable | kParameterIsTrigger; + parameter.name = "Panic"; + parameter.symbol = "Panic"; + parameter.unit = ""; + parameter.ranges.def = 0; + parameter.ranges.min = 0; + parameter.ranges.max = 1; + break; + case paramEnabled: + parameter.hints = kParameterIsBoolean; + parameter.name = "Enabled"; + parameter.symbol = "enabled"; + parameter.unit = ""; + parameter.ranges.def = 1.f; + parameter.ranges.min = 0.f; + parameter.ranges.max = 1.f; + break; + } +} + +// ----------------------------------------------------------------------- +// Internal data + +/** +Optional callback to inform the plugin about a sample rate change. +*/ +void PluginArpeggiator::sampleRateChanged(double newSampleRate) +{ + (void) newSampleRate; + + arpeggiator.setSampleRate(static_cast(newSampleRate)); +} + +/** +Get the current value of a parameter. +*/ +float PluginArpeggiator::getParameterValue(uint32_t index) const +{ + switch (index) + { + case paramSyncMode: + return arpeggiator.getSyncMode(); + case paramBpm: + return arpeggiator.getBpm(); + case paramDivision: + return arpeggiator.getDivision(); + case paramVelocity: + return arpeggiator.getVelocity(); + case paramNoteLength: + return arpeggiator.getNoteLength(); + case paramOctaveSpread: + return arpeggiator.getOctaveSpread(); + case paramArpMode: + return arpeggiator.getArpMode(); + case paramOctaveMode: + return arpeggiator.getOctaveMode(); + case paramLatch: + return arpeggiator.getLatchMode(); + case paramPanic: + return arpeggiator.getPanic(); + case paramEnabled: + return arpeggiator.getArpEnabled(); + } +} + +/** +Change a parameter value. +*/ +void PluginArpeggiator::setParameterValue(uint32_t index, float value) +{ + switch (index) + { + case paramSyncMode: + syncMode = static_cast(value); + break; + case paramBpm: + arpeggiator.setBpm(value); + break; + case paramDivision: + arpeggiator.setDivision(static_cast(value)); + break; + case paramVelocity: + arpeggiator.setVelocity(static_cast(value)); + break; + case paramNoteLength: + arpeggiator.setNoteLength(value); + break; + case paramOctaveSpread: + arpeggiator.setOctaveSpread(static_cast(value)); + break; + case paramArpMode: + arpeggiator.setArpMode(static_cast(value)); + break; + case paramOctaveMode: + arpeggiator.setOctaveMode(static_cast(value)); + break; + case paramLatch: + arpeggiator.setLatchMode(static_cast(value)); + break; + case paramPanic: + arpeggiator.setPanic(static_cast(value)); + break; + case paramEnabled: + arpeggiator.setArpEnabled(static_cast(value)); + break; + } +} + +// ----------------------------------------------------------------------- +// Process + +void PluginArpeggiator::activate() +{ + // plugin is activated +} + +void PluginArpeggiator::run(const float**, float**, uint32_t n_frames, + const MidiEvent* events, uint32_t eventCount) +{ + arpeggiator.emptyMidiBuffer(); + + // Check if host supports Bar-Beat-Tick position + const TimePosition& position = getTimePosition(); + if (!position.bbt.valid) { + // set-arpeggiator in free running mode + arpeggiator.setSyncMode(0); + } else { + arpeggiator.setSyncMode(syncMode); + arpeggiator.transmitHostInfo(position.playing, position.bbt.beatsPerBar, position.bbt.beat, position.bbt.barBeat, static_cast(position.bbt.beatsPerMinute)); + } + + arpeggiator.process(events, eventCount, n_frames); + + struct MidiBuffer buffer = arpeggiator.getMidiBuffer(); + for (unsigned x = 0; x < buffer.numBufferedEvents + buffer.numBufferedThroughEvents; x++) { + writeMidiEvent(buffer.bufferedEvents[x]); //needs to be one struct or array? + } +} + +// ----------------------------------------------------------------------- + +Plugin* createPlugin() +{ + return new PluginArpeggiator(); +} + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/src/modarpeggiator/plugin.hpp b/src/modarpeggiator/plugin.hpp new file mode 100644 index 0000000..455c8ba --- /dev/null +++ b/src/modarpeggiator/plugin.hpp @@ -0,0 +1,104 @@ +#ifndef _H_PLUGIN_ARPEGGIATOR_ +#define _H_PLUGIN_ARPEGGIATOR_ + +#include "arpeggiator.hpp" +#include "common/commons.h" +#include "common/clock.hpp" +#include "common/pattern.hpp" + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------- + +class PluginArpeggiator : public Plugin { +public: + enum Parameters { + paramSyncMode = 0, + paramBpm, + paramDivision, + paramVelocity, + paramNoteLength, + paramOctaveSpread, + paramArpMode, + paramOctaveMode, + paramLatch, + paramPanic, + paramEnabled, + paramCount + }; + + PluginArpeggiator(); + +protected: + // ------------------------------------------------------------------- + // Information + + const char* getLabel() const noexcept override { + return "Arpeggiator"; + } + + const char* getDescription() const override { + return "A MIDI arpeggiator"; + } + + const char* getMaker() const noexcept override { + return "MOD"; + } + + const char* getHomePage() const override { + return ""; + } + + const char* getLicense() const noexcept override { + return "https://spdx.org/licenses/GPL-2.0-or-later"; + } + + uint32_t getVersion() const noexcept override { + return d_version(1, 1, 2); + } + + int64_t getUniqueId() const noexcept override { + return d_cconst('M', 'O', 'A', 'P'); + } + + // ------------------------------------------------------------------- + // Init + + void initParameter(uint32_t index, Parameter& parameter) override; + + // ------------------------------------------------------------------- + // Internal data + + float getParameterValue(uint32_t index) const override; + void setParameterValue(uint32_t index, float value) override; + + // ------------------------------------------------------------------- + // Optional + + // Optional callback to inform the plugin about a sample rate change. + void sampleRateChanged(double newSampleRate) override; + + // ------------------------------------------------------------------- + // Process + + void activate() override; + + void run(const float**, float**, uint32_t, + const MidiEvent* midiEvents, uint32_t midiEventCount) override; + + + // ------------------------------------------------------------------- + +private: + Arpeggiator arpeggiator; + float fParams[paramCount]; + int syncMode; + + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginArpeggiator) +}; + +// ----------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO + +#endif //_H_PLUGIN_ARPEGGIATOR_ diff --git a/src/modarpeggiator/utils.cpp b/src/modarpeggiator/utils.cpp new file mode 100644 index 0000000..16eebae --- /dev/null +++ b/src/modarpeggiator/utils.cpp @@ -0,0 +1,43 @@ +#include "utils.hpp" + +ArpUtils::ArpUtils() +{ + +} + +ArpUtils::~ArpUtils() +{ + +} + +void ArpUtils::swap(uint8_t *a, uint8_t *b) +{ + int temp = *a; + *a = *b; + *b = temp; +} + +//got the code for the quick sort algorithm here https://medium.com/human-in-a-machine-world/quicksort-the-best-sorting-algorithm-6ab461b5a9d0 +void ArpUtils::quicksort(uint8_t arr[][2], int l, int r) +{ + if (l >= r) + { + return; + } + + int pivot = arr[r][0]; + + int cnt = l; + + for (int i = l; i <= r; i++) + { + if (arr[i][0] <= pivot) + { + swap(&arr[cnt][0], &arr[i][0]); + swap(&arr[cnt][1], &arr[i][1]); + cnt++; + } + } + quicksort(arr, l, cnt-2); + quicksort(arr, cnt, r); +} diff --git a/src/modarpeggiator/utils.hpp b/src/modarpeggiator/utils.hpp new file mode 100644 index 0000000..2614440 --- /dev/null +++ b/src/modarpeggiator/utils.hpp @@ -0,0 +1,15 @@ +#ifndef _H_UTILS_ +#define _H_UTILS_ + +#include + +class ArpUtils { +public: + ArpUtils(); + ~ArpUtils(); + void quicksort(uint8_t arr[][2], int l, int r); +private: + void swap(uint8_t *a, uint8_t *b); +}; + +#endif //_H_UTILS_ From d4f99f88ae6b7f17df7152252397ba366c970fb2 Mon Sep 17 00:00:00 2001 From: jnonis Date: Thu, 18 Jul 2024 04:31:42 +0000 Subject: [PATCH 02/11] Almost working Arp --- src/midi_arp.cpp | 5 ++-- src/modarpeggiator/common/midiHandler.cpp | 30 +++++++---------------- src/modarpeggiator/common/midiHandler.hpp | 6 ++--- 3 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/midi_arp.cpp b/src/midi_arp.cpp index 82bcc2b..6b69630 100644 --- a/src/midi_arp.cpp +++ b/src/midi_arp.cpp @@ -11,7 +11,7 @@ MidiArp::MidiArp(float32_t samplerate, CDexedAdapter* synth) arpeggiator.setSampleRate(samplerate); arpeggiator.setDivision(7); - arpeggiator.getMidiBuffer(); + arpeggiator.setBpm(120); } MidiArp::~MidiArp() @@ -45,6 +45,7 @@ void MidiArp::process(uint16_t len) arpeggiator.emptyMidiBuffer(); // Check if host supports Bar-Beat-Tick position + arpeggiator.setSyncMode(0); /* const TimePosition& position = getTimePosition(); if (!position.bbt.valid) { @@ -60,7 +61,6 @@ void MidiArp::process(uint16_t len) events.clear(); events.shrink_to_fit(); - /* printf("Before Send Midi\n"); fflush(NULL); struct MidiBuffer buffer = arpeggiator.getMidiBuffer(); @@ -97,5 +97,4 @@ void MidiArp::process(uint16_t len) } printf("After Send Midi\n"); fflush(NULL); - */ } \ No newline at end of file diff --git a/src/modarpeggiator/common/midiHandler.cpp b/src/modarpeggiator/common/midiHandler.cpp index 7f4100d..4d9589d 100644 --- a/src/modarpeggiator/common/midiHandler.cpp +++ b/src/modarpeggiator/common/midiHandler.cpp @@ -4,33 +4,21 @@ MidiHandler::MidiHandler() { printf("MidiHandler constructor\n"); - printf("MidiEvent size: %d\n", sizeof(MidiEvent)); fflush(NULL); - /* - memset(buffer.bufferedEvents, 0, MIDI_BUFFER_SIZE * sizeof(MidiEvent)); - memset(buffer.bufferedMidiThroughEvents, 0, MIDI_BUFFER_SIZE * sizeof(MidiEvent)); - memset(buffer.midiOutputBuffer, 0, MIDI_BUFFER_SIZE * sizeof(MidiEvent)); - */ - for (unsigned i = 0; i < MIDI_BUFFER_SIZE; i++) { - printf("i: %d\n", i); - fflush(NULL); - for (unsigned x = 0; x < buffer.bufferedEvents[i].kDataSize; i++) { - printf("x: %d\n", x); - fflush(NULL); - /* - buffer.bufferedEvents[i].data[x] = 0; - buffer.bufferedMidiThroughEvents[i].data[x] = 0; - buffer.midiOutputBuffer[i].data[x] = 0; - */ - } - } + buffer.bufferedEvents = new MidiEvent[MIDI_BUFFER_SIZE]; + buffer.bufferedMidiThroughEvents = new MidiEvent[MIDI_BUFFER_SIZE]; + buffer.midiOutputBuffer = new MidiEvent[MIDI_BUFFER_SIZE]; + emptyMidiBuffer(); - //printf("buffer.bufferedEvents: %d\n", buffer.bufferedEvents[0].data); - //fflush(NULL); + printf("MidiHandler constructor finished\n"); + fflush(NULL); } MidiHandler::~MidiHandler() { + delete buffer.bufferedEvents; + delete buffer.bufferedMidiThroughEvents; + delete buffer.midiOutputBuffer; } void MidiHandler::emptyMidiBuffer() diff --git a/src/modarpeggiator/common/midiHandler.hpp b/src/modarpeggiator/common/midiHandler.hpp index 098f571..cb854fc 100644 --- a/src/modarpeggiator/common/midiHandler.hpp +++ b/src/modarpeggiator/common/midiHandler.hpp @@ -31,13 +31,13 @@ struct MidiBuffer { unsigned maxBufferSize = MIDI_BUFFER_SIZE; - MidiEvent bufferedEvents[MIDI_BUFFER_SIZE]; + MidiEvent* bufferedEvents; unsigned numBufferedEvents; - MidiEvent bufferedMidiThroughEvents[MIDI_BUFFER_SIZE]; + MidiEvent* bufferedMidiThroughEvents; unsigned numBufferedThroughEvents; - MidiEvent midiOutputBuffer[MIDI_BUFFER_SIZE]; + MidiEvent* midiOutputBuffer; unsigned numOutputEvents; }; From b2dcc29d73b897b81a40988e0b346af0261743f9 Mon Sep 17 00:00:00 2001 From: jnonis Date: Thu, 18 Jul 2024 04:34:13 +0000 Subject: [PATCH 03/11] Removed log from Arp --- src/midi_arp.cpp | 7 ------- src/modarpeggiator/common/midiHandler.cpp | 5 ----- 2 files changed, 12 deletions(-) diff --git a/src/midi_arp.cpp b/src/midi_arp.cpp index 6b69630..4989153 100644 --- a/src/midi_arp.cpp +++ b/src/midi_arp.cpp @@ -61,13 +61,8 @@ void MidiArp::process(uint16_t len) events.clear(); events.shrink_to_fit(); - printf("Before Send Midi\n"); - fflush(NULL); struct MidiBuffer buffer = arpeggiator.getMidiBuffer(); for (unsigned x = 0; x < buffer.numBufferedEvents + buffer.numBufferedThroughEvents; x++) { - printf("Loop x: %d\n", x); - fflush(NULL); - MidiEvent event = buffer.bufferedEvents[x]; unsigned eventType = event.data[0] >> 4; @@ -95,6 +90,4 @@ void MidiArp::process(uint16_t len) break; } } - printf("After Send Midi\n"); - fflush(NULL); } \ No newline at end of file diff --git a/src/modarpeggiator/common/midiHandler.cpp b/src/modarpeggiator/common/midiHandler.cpp index 4d9589d..0bbbcee 100644 --- a/src/modarpeggiator/common/midiHandler.cpp +++ b/src/modarpeggiator/common/midiHandler.cpp @@ -3,15 +3,10 @@ MidiHandler::MidiHandler() { - printf("MidiHandler constructor\n"); - fflush(NULL); buffer.bufferedEvents = new MidiEvent[MIDI_BUFFER_SIZE]; buffer.bufferedMidiThroughEvents = new MidiEvent[MIDI_BUFFER_SIZE]; buffer.midiOutputBuffer = new MidiEvent[MIDI_BUFFER_SIZE]; - emptyMidiBuffer(); - printf("MidiHandler constructor finished\n"); - fflush(NULL); } MidiHandler::~MidiHandler() From 61f66848dcd6b28734aca597eeac5dd72a9d4a71 Mon Sep 17 00:00:00 2001 From: jnonis Date: Fri, 19 Jul 2024 04:56:58 +0000 Subject: [PATCH 04/11] Added Arp to TG menu --- src/midi_arp.cpp | 81 ++++++++++++++++++++++++++++++--- src/midi_arp.h | 45 ++++++++++++++++--- src/midi_effect_base.h | 96 +++++++++++++++++++++++++++++++++++++++ src/minidexed.cpp | 62 ++++++++++++++++++++++--- src/minidexed.h | 5 +++ src/uimenu.cpp | 100 +++++++++++++++++++++++++++++++++++++++++ src/uimenu.h | 4 ++ 7 files changed, 375 insertions(+), 18 deletions(-) create mode 100644 src/midi_effect_base.h 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[]; From 8f597543fc97275624265cd3042cfe887c4fb20e Mon Sep 17 00:00:00 2001 From: jnonis Date: Fri, 19 Jul 2024 16:41:09 +0000 Subject: [PATCH 05/11] Arp fixes --- src/midi_arp.h | 1 - src/midi_effect_base.h | 2 +- src/uimenu.cpp | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/midi_arp.h b/src/midi_arp.h index 006bc5e..9f0e7ec 100644 --- a/src/midi_arp.h +++ b/src/midi_arp.h @@ -62,7 +62,6 @@ private: static const unsigned MIDI_NOTE_OFF = 0b1000; static const unsigned MIDI_NOTE_ON = 0b1001; - CDexedAdapter* synth; Arpeggiator arpeggiator; int syncMode; std::vector events; diff --git a/src/midi_effect_base.h b/src/midi_effect_base.h index 4b8b37d..9c9b8b6 100644 --- a/src/midi_effect_base.h +++ b/src/midi_effect_base.h @@ -76,7 +76,7 @@ public: { return; } - doProcess(len); + this->doProcess(len); } protected: bool bypass = false; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 98487b2..50b3916 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -75,7 +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}, + {"Arp", MenuHandler, s_Arp}, {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, {"Edit Voice", MenuHandler, s_EditVoiceMenu}, {0} From 6cd3a82253254521260410bb53538784903f44ff Mon Sep 17 00:00:00 2001 From: Javier Nonis Date: Sat, 20 Jul 2024 16:21:47 -0300 Subject: [PATCH 06/11] Handle Arp as a Midi FX --- src/midi_arp.cpp | 17 ++- src/midi_arp.h | 56 ++++++++- src/midi_effect_base.h | 32 +++-- src/midi_effects.h | 107 +++++++++++++++++ src/minidexed.cpp | 24 +++- src/minidexed.h | 5 +- src/uimenu.cpp | 259 ++++++++++++++++++++++++++++++++++++----- src/uimenu.h | 15 ++- 8 files changed, 458 insertions(+), 57 deletions(-) create mode 100644 src/midi_effects.h diff --git a/src/midi_arp.cpp b/src/midi_arp.cpp index ccddb40..f454afc 100644 --- a/src/midi_arp.cpp +++ b/src/midi_arp.cpp @@ -3,13 +3,18 @@ MidiArp::MidiArp(float32_t samplerate, CDexedAdapter* synth) : MidiEffect(samplerate, synth) { - this->syncMode = 1; - arpeggiator.transmitHostInfo(0, 4, 1, 1, 120.0); arpeggiator.setSampleRate(samplerate); - arpeggiator.setDivision(7); - + arpeggiator.setBpm(120); + + this->setParameter(MidiArp::Param::LATCH, 0); + this->setParameter(MidiArp::Param::ARP_MODE, 0); + this->setParameter(MidiArp::Param::DIVISION, 9); + this->setParameter(MidiArp::Param::NOTE_LENGTH, 70); + this->setParameter(MidiArp::Param::VELOCITY, 110); + this->setParameter(MidiArp::Param::OCTAVE_SPREAD, 1); + this->setParameter(MidiArp::Param::OCTAVE_MODE, 4); } MidiArp::~MidiArp() @@ -41,7 +46,7 @@ void MidiArp::setParameter(unsigned param, unsigned value) this->arpeggiator.setDivision(value); break; case MidiArp::Param::NOTE_LENGTH: - this->arpeggiator.setNoteLength(value); + this->arpeggiator.setNoteLength((float) value / 100.0f); break; case MidiArp::Param::VELOCITY: this->arpeggiator.setVelocity(value); @@ -75,7 +80,7 @@ unsigned MidiArp::getParameter(unsigned param) case MidiArp::Param::DIVISION: return this->arpeggiator.getDivision(); case MidiArp::Param::NOTE_LENGTH: - return this->arpeggiator.getNoteLength(); + return roundf(this->arpeggiator.getNoteLength() * 100); case MidiArp::Param::VELOCITY: return this->arpeggiator.getVelocity(); case MidiArp::Param::OCTAVE_SPREAD: diff --git a/src/midi_arp.h b/src/midi_arp.h index 9f0e7ec..c68820e 100644 --- a/src/midi_arp.h +++ b/src/midi_arp.h @@ -18,7 +18,9 @@ class MidiArp : public MidiEffect { public: - static const unsigned MIDI_EFFECT_ARP = 1; + // ID must be unique for each MidiEffect + static const unsigned ID = 1; + static constexpr const char* NAME = "Arp"; enum Param { @@ -35,20 +37,65 @@ public: UNKNOWN }; + enum Mode + { + UP, + DOWN, + UP_DOWN, + UP_DOWN_ALT, + PLAYED, + RANDOM, + MODE_UNKNOWN + }; + + enum Division + { + D_1_1, + D_1_2, + D_1_3, + D_1_4, + D_1_4D, + D_1_4T, + D_1_8, + D_1_8D, + D_1_8T, + D_1_16, + D_1_16D, + D_1_16T, + D_1_32, + D_UNKNOWN + }; + + enum OctMode + { + OM_UP, + OM_DOWN, + OM_UP_DOWN, + OM_DOWN_UP, + OM_UP_CYCLE, + OM_UNKNOWN + }; + + MidiArp(float32_t samplerate, CDexedAdapter* synth); virtual ~MidiArp(); virtual unsigned getId() { - return MIDI_EFFECT_ARP; + return MidiArp::ID; + } + + virtual std::string getName() + { + return MidiArp::NAME; } 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); + virtual void keydown(int16_t pitch, uint8_t velocity); + virtual void keyup(int16_t pitch); protected: virtual size_t getParametersSize() @@ -63,7 +110,6 @@ private: static const unsigned MIDI_NOTE_ON = 0b1001; Arpeggiator arpeggiator; - int syncMode; std::vector events; }; diff --git a/src/midi_effect_base.h b/src/midi_effect_base.h index 9c9b8b6..accc3dd 100644 --- a/src/midi_effect_base.h +++ b/src/midi_effect_base.h @@ -5,13 +5,16 @@ #ifndef _MIDI_EFFECT_H #define _MIDI_EFFECT_H +#include #include #include "dexedadapter.h" class MidiEffect { public: - static const unsigned MIDI_EFFECT_NONE = 0; + // ID must be unique for each MidiEffect + static const unsigned ID = 0; + static constexpr const char* NAME = "None"; MidiEffect(float32_t samplerate, CDexedAdapter* synth) { @@ -23,19 +26,24 @@ public: { } - void setBypass(bool bypass) + virtual unsigned getId() { - this->bypass = bypass; + return MidiEffect::ID; } - bool getBypass() + virtual std::string getName() { - return bypass; + return MidiEffect::NAME; } - virtual unsigned getId() + void setBypass(bool bypass) { - return MIDI_EFFECT_NONE; + this->bypass = bypass; + } + + bool getBypass() + { + return this->getId() == MidiEffect::ID ? true : this->bypass; } virtual void setTempo(unsigned tempo) @@ -70,9 +78,17 @@ public: return params; } + virtual void keydown(int16_t pitch, uint8_t velocity) + { + } + + virtual void keyup(int16_t pitch) + { + } + void process(uint16_t len) { - if (this->bypass) + if (this->getBypass()) { return; } diff --git a/src/midi_effects.h b/src/midi_effects.h new file mode 100644 index 0000000..a1f2afd --- /dev/null +++ b/src/midi_effects.h @@ -0,0 +1,107 @@ +#ifndef _MIDI_EFFECTS_H +#define _MIDI_EFFECTS_H + +#include "midi_effect_base.h" +#include "midi_arp.h" + +inline MidiEffect* newMidiEffect(unsigned type, float32_t samplerate, CDexedAdapter* synth) +{ + switch (type) + { + case MidiArp::ID: + return new MidiArp(samplerate, synth); + default: + return new MidiEffect(samplerate, synth); + } +} + +inline std::string getMidiFXTypeName(int type) +{ + switch (type) + { + case MidiArp::ID: + return MidiArp::NAME; + case MidiEffect::ID: + default: + return MidiEffect::NAME; + } +} + +inline std::string ToMidiFXType (int value) +{ + return getMidiFXTypeName(value); +} + +inline std::string ToArpDivision (int value) +{ + switch (value) + { + case MidiArp::Division::D_1_1: + return "1/1"; + case MidiArp::Division::D_1_2: + return "1/2"; + case MidiArp::Division::D_1_3: + return "1/3"; + case MidiArp::Division::D_1_4: + return "1/4"; + case MidiArp::Division::D_1_4D: + return "1/4."; + case MidiArp::Division::D_1_4T: + return "1/4T"; + case MidiArp::Division::D_1_8: + return "1/8"; + case MidiArp::Division::D_1_8D: + return "1/8."; + case MidiArp::Division::D_1_8T: + return "1/8T"; + case MidiArp::Division::D_1_16: + return "1/16"; + case MidiArp::Division::D_1_16D: + return "1/16."; + case MidiArp::Division::D_1_16T: + return "1/16T"; + case MidiArp::Division::D_1_32: + default: + return "1/32"; + } +} + +inline std::string ToArpMode (int value) +{ + switch (value) + { + case MidiArp::Mode::UP: + return "Up"; + case MidiArp::Mode::DOWN: + return "Down"; + case MidiArp::Mode::UP_DOWN: + return "Up-Down"; + case MidiArp::Mode::UP_DOWN_ALT: + return "Up-Down Alt"; + case MidiArp::Mode::PLAYED: + return "Played"; + case MidiArp::Mode::RANDOM: + default: + return "Random"; + } +} + +inline std::string ToArpOctMode (int value) +{ + switch (value) + { + case MidiArp::OctMode::OM_UP: + return "Up"; + case MidiArp::OctMode::OM_DOWN: + return "Down"; + case MidiArp::OctMode::OM_UP_DOWN: + return "Up-Down"; + case MidiArp::OctMode::OM_DOWN_UP: + return "Down-Up"; + case MidiArp::OctMode::OM_UP_CYCLE: + default: + return "Up-Cycle"; + } +} + +#endif // _MIDI_EFFECTS_H \ No newline at end of file diff --git a/src/minidexed.cpp b/src/minidexed.cpp index d9fa8c6..6e2f4c9 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -109,7 +109,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_MidiArp[i] = new MidiEffect(pConfig->GetSampleRate(), m_pTG[i]); m_pTG[i]->setEngineType(pConfig->GetEngineType ()); m_pTG[i]->activate (); @@ -626,6 +626,24 @@ void CMiniDexed::setInsertFXType (unsigned nType, unsigned nTG) m_UI.ParameterChanged (); } +void CMiniDexed::setMidiFXType (unsigned nType, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + + // If the effect type is already set just return + if (m_MidiArp[nTG]->getId() == nType) { + return; + } + + m_MidiArpSpinLock[nTG]->Acquire(); + delete m_MidiArp[nTG]; + m_MidiArp[nTG] = newMidiEffect(nType, m_pConfig->GetSampleRate(), m_pTG[nTG]); + m_MidiArp[nTG]->setTempo(m_nTempo); + m_MidiArpSpinLock[nTG]->Release(); + + m_UI.ParameterChanged (); +} + void CMiniDexed::setSendFXType (unsigned nType) { m_SendFXSpinLock.Acquire(); @@ -1073,9 +1091,7 @@ 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; + case TGParameterMidiFXType: setMidiFXType(nValue, nTG); break; default: assert (0); diff --git a/src/minidexed.h b/src/minidexed.h index 15b786f..160c608 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -41,11 +41,11 @@ #include #include #include "common.h" -#include "midi_arp.h" #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" #include "effects.h" +#include "midi_effects.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -101,6 +101,7 @@ public: void setAftertouch (uint8_t value, unsigned nTG); void setInsertFXType (unsigned nType, unsigned nTG); + void setMidiFXType (unsigned nType, unsigned nTG); void setSendFXType (unsigned nType); void setSendFXLevel (unsigned nValue); @@ -307,7 +308,7 @@ private: unsigned m_nNoteLimitHigh[CConfig::ToneGenerators]; int m_nNoteShift[CConfig::ToneGenerators]; - MidiArp* m_MidiArp[CConfig::ToneGenerators]; + MidiEffect* m_MidiArp[CConfig::ToneGenerators]; AudioEffect* m_InsertFX[CConfig::ToneGenerators]; unsigned m_nReverbSend[CConfig::ToneGenerators]; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 50b3916..6a409a8 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -75,7 +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_Arp}, + {"Midi FX", MenuHandlerMidiFX}, {"Channel", EditTGParameter, 0, CMiniDexed::TGParameterMIDIChannel}, {"Edit Voice", MenuHandler, s_EditVoiceMenu}, {0} @@ -237,18 +237,31 @@ 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}, +const CUIMenu::TMenuItem CUIMenu::s_MidiFX[] = +{ + {"Type:", EditTGParameter2, 0, CMiniDexed::TGParameterMidiFXType}, + {"Edit:", EditMidiFX}, + {0} +}; + +CUIMenu::TMenuItem CUIMenu::s_MidiFXNone[] = +{ + {"None"}, + {0} +}; + +CUIMenu::TMenuItem CUIMenu::s_MidiFXArp[] = +{ + {"Bypass", EditTGMidiFXParameter, 0, MidiArp::Param::BYPASS}, + {"Latch", EditTGMidiFXParameter, 0, MidiArp::Param::LATCH}, + {"Sync", EditTGMidiFXParameter, 0, MidiArp::Param::SYNC}, + {"Arp Mode", EditTGMidiFXParameter, 0, MidiArp::Param::ARP_MODE}, + {"Division", EditTGMidiFXParameter, 0, MidiArp::Param::DIVISION}, + {"Note Length", EditTGMidiFXParameter, 0, MidiArp::Param::NOTE_LENGTH}, + {"Velocity", EditTGMidiFXParameter, 0, MidiArp::Param::VELOCITY}, + {"Oct Spread", EditTGMidiFXParameter, 0, MidiArp::Param::OCTAVE_SPREAD}, + {"Oct Mode", EditTGMidiFXParameter, 0, MidiArp::Param::OCTAVE_MODE}, + {"Panic", EditTGMidiFXParameter, 0, MidiArp::Param::PANIC}, {0} }; @@ -347,7 +360,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 + {0, 1, 1, ToMidiFXType}, // TGParameterMidiFXType {-99, 99, 1}, // TGParameterMasterTune {0, 99, 1}, // TGParameterCutoff {0, 99, 1}, // TGParameterResonance @@ -454,18 +467,18 @@ const CUIMenu::TParameter CUIMenu::s_TGFXReverbParam[AudioEffectPlateReverb::Par }; // must match MidiArp::Param -const CUIMenu::TParameter CUIMenu::s_ArpParam[MidiArp::Param::UNKNOWN] = +const CUIMenu::TParameter CUIMenu::s_TGMidiFXArpParam[MidiArp::Param::UNKNOWN] = { {0, 1, 1, ToOnOff}, // BYPASS - {0, 1, 1}, // LATCH + {0, 1, 1, ToOnOff}, // LATCH {0, 2, 1}, // SYNC - {0, 5, 1}, // ARP_MODE - {0, 12, 1}, // DIVISION + {0, 5, 1, ToArpMode}, // ARP_MODE + {0, 12, 1, ToArpDivision}, // DIVISION {0, 100, 1}, // NOTE_LENGTH {0, 127, 1}, // VELOCITY {1, 4, 1}, // OCTAVE_SPREAD - {0, 4, 1}, // OCTAVE_MODE - {0, 1, 1} // PANIC + {0, 4, 1, ToArpOctMode}, // OCTAVE_MODE + {0, 1, 1, ToOnOff} // PANIC }; // must match DexedVoiceParameters in Synth_Dexed @@ -698,6 +711,93 @@ void CUIMenu::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event) } } +void CUIMenu::MenuHandlerMidiFX (CUIMenu *pUIMenu, TMenuEvent Event) +{ + // Setup menu when it's open + if (!pUIMenu->m_pCurrentMenu) + { + pUIMenu->m_pCurrentMenu = s_MidiFX; + } + + 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; + + case MenuEventPressAndStepDown: + case MenuEventPressAndStepUp: + pUIMenu->TGShortcutHandler (Event); + return; + + default: + return; + } + + if (pUIMenu->m_pCurrentMenu) // if this is another menu? + { + // Identify TG + unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-1]; + // Create TG label + string TG ("TG"); + TG += to_string (nTG+1); + + // Get current FX type + int nFxType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterMidiFXType, nTG); + + // Create Paramter with type label + std::string value; + value.append(pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name); + value.append(getMidiFXTypeName(nFxType)); + + pUIMenu->m_pUI->DisplayWrite ( + TG.c_str (), + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + value.c_str(), + pUIMenu->m_nCurrentSelection > 0, + !!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name); + } + else + { + pUIMenu->EventHandler (MenuEventUpdate); // no, update parameter display + } +} + void CUIMenu::MenuHandlerInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) { // Setup menu when it's open @@ -1135,12 +1235,12 @@ void CUIMenu::EditTGParameter2 (CUIMenu *pUIMenu, TMenuEvent Event) // second me nValue > rParam.Minimum, nValue < rParam.Maximum); } -void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) +void CUIMenu::EditMidiFX (CUIMenu *pUIMenu, TMenuEvent Event) { unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; - int nFXType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterInsertFXType, nTG); - pUIMenu->m_pCurrentMenu = getInsertFXMenuItem(nFXType); + int nFXType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterMidiFXType, nTG); + pUIMenu->m_pCurrentMenu = getMidiFXMenuItem(nFXType); switch (Event) { @@ -1148,7 +1248,7 @@ void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) break; case MenuEventSelect: // push menu - if (nFXType == 0) + if (nFXType == MidiEffect::ID) { break; } @@ -1198,7 +1298,7 @@ void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) pUIMenu->m_pUI->DisplayWrite ( TG.c_str (), - getFXTypeName(nFXType).c_str(), + getMidiFXTypeName(nFXType).c_str(), pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name, pUIMenu->m_nCurrentSelection > 0, !!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name); @@ -1209,17 +1309,17 @@ void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) } } -void CUIMenu::EditTGArpParameter (CUIMenu *pUIMenu, TMenuEvent Event) +void CUIMenu::EditTGMidiFXParameter (CUIMenu *pUIMenu, TMenuEvent Event) { // Get TG - unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; + unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-3]; // Get FX type int nFXType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterMidiFXType, nTG); // Get Param unsigned nParam = pUIMenu->m_nCurrentParameter; - TParameter pParam = s_ArpParam[nParam]; + TParameter pParam = getMidiFXParameter(nFXType, nParam); const TParameter &rParam = pParam; int nValue = pUIMenu->m_pMiniDexed->GetMidiFXParameter (nParam, nTG, nFXType); @@ -1277,6 +1377,80 @@ void CUIMenu::EditTGArpParameter (CUIMenu *pUIMenu, TMenuEvent Event) nValue > rParam.Minimum, nValue < rParam.Maximum); } +void CUIMenu::EditInsertFX (CUIMenu *pUIMenu, TMenuEvent Event) +{ + unsigned nTG = pUIMenu->m_nMenuStackParameter[pUIMenu->m_nCurrentMenuDepth-2]; + + int nFXType = pUIMenu->m_pMiniDexed->GetTGParameter(CMiniDexed::TGParameterInsertFXType, nTG); + pUIMenu->m_pCurrentMenu = getInsertFXMenuItem(nFXType); + + switch (Event) + { + case MenuEventUpdate: + break; + + case MenuEventSelect: // push menu + if (nFXType == 0) + { + break; + } + 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? + { + string TG ("TG"); + TG += to_string (nTG+1); + + pUIMenu->m_pUI->DisplayWrite ( + TG.c_str (), + getFXTypeName(nFXType).c_str(), + 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::EditTGFXParameter (CUIMenu *pUIMenu, TMenuEvent Event) { // Get TG @@ -2719,3 +2893,32 @@ CUIMenu::TParameter CUIMenu::getFXParameter(unsigned type, unsigned nParam) } return pParam; } + +CUIMenu::TMenuItem* CUIMenu::getMidiFXMenuItem(unsigned type) +{ + CUIMenu::TMenuItem* menu; + switch (type) + { + case MidiArp::ID: + menu = s_MidiFXArp; + break; + case MidiEffect::ID: + default: + menu = s_MidiFXNone; + break; + } + return menu; +} + +CUIMenu::TParameter CUIMenu::getMidiFXParameter(unsigned type, unsigned nParam) +{ + TParameter pParam; + switch (type) + { + case MidiArp::ID: + pParam = s_TGMidiFXArpParam[nParam]; + default: + break; + } + return pParam; +} \ No newline at end of file diff --git a/src/uimenu.h b/src/uimenu.h index 6c164bd..0700502 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -93,13 +93,18 @@ private: static void SavePerformanceNewFile (CUIMenu *pUIMenu, TMenuEvent Event); static void EditPerformanceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); + static void MenuHandlerMidiFX (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditMidiFX (CUIMenu *pUIMenu, TMenuEvent Event); + static void EditTGMidiFXParameter (CUIMenu *pUIMenu, TMenuEvent Event); + static CUIMenu::TMenuItem* getMidiFXMenuItem(unsigned type); + static CUIMenu::TParameter getMidiFXParameter(unsigned type, unsigned param); + static CUIMenu::TMenuItem* getFXMenuItem(unsigned type); static CUIMenu::TParameter getFXParameter(unsigned type, unsigned param); 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); @@ -130,7 +135,7 @@ private: 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); @@ -166,6 +171,7 @@ private: static const TMenuItem s_SendFXMenu[]; static const TMenuItem s_ReverbMenu[]; static const TMenuItem s_InsertFX[]; + static const TMenuItem s_MidiFX[]; static TMenuItem s_FXNone[]; static TMenuItem s_FXChorus[]; @@ -176,7 +182,8 @@ private: static TMenuItem s_FXTalReverb3[]; static TMenuItem s_FXReverb[]; - static TMenuItem s_Arp[]; + static TMenuItem s_MidiFXNone[]; + static TMenuItem s_MidiFXArp[]; static const TMenuItem s_EditVoiceMenu[]; static const TMenuItem s_OperatorMenu[]; @@ -197,7 +204,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_TGMidiFXArpParam[]; static const TParameter s_VoiceParameter[]; static const TParameter s_OPParameter[]; From 652db3e05f1e00b30d8b9c80dd403b62c4d2efca Mon Sep 17 00:00:00 2001 From: Javier Nonis Date: Sat, 20 Jul 2024 17:49:53 -0300 Subject: [PATCH 07/11] Store Midi FX settings into performance --- src/minidexed.cpp | 12 ++++ src/performanceconfig.cpp | 113 +++++++++++++++++++++++--------------- src/performanceconfig.h | 9 +++ 3 files changed, 89 insertions(+), 45 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6e2f4c9..a3185e3 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1510,6 +1510,12 @@ bool CMiniDexed::DoSavePerformance (void) m_PerformanceConfig.SetInsertFXParams (pParams, nTG); pParams.clear(); pParams.shrink_to_fit(); + + m_PerformanceConfig.SetMidiFX (m_MidiArp[nTG]->getId(), nTG); + std::vector pMidiFXParams = m_MidiArp[nTG]->getParameters(); + m_PerformanceConfig.SetMidiFXParams (pMidiFXParams, nTG); + pMidiFXParams.clear(); + pMidiFXParams.shrink_to_fit(); m_PerformanceConfig.SetDetune (m_nMasterTune[nTG], nTG); m_PerformanceConfig.SetCutoff (m_nCutoff[nTG], nTG); @@ -1967,6 +1973,12 @@ void CMiniDexed::LoadPerformanceParameters(void) m_InsertFX[nTG]->setParameters(pParams); pParams.clear(); pParams.shrink_to_fit(); + + setMidiFXType(m_PerformanceConfig.GetMidiFX (nTG), nTG); + std::vector pMidiFXParams = m_PerformanceConfig.GetMidiFXParams(nTG); + m_MidiArp[nTG]->setParameters(pMidiFXParams); + pMidiFXParams.clear(); + pMidiFXParams.shrink_to_fit(); m_nNoteLimitLow[nTG] = m_PerformanceConfig.GetNoteLimitLow (nTG); m_nNoteLimitHigh[nTG] = m_PerformanceConfig.GetNoteLimitHigh (nTG); diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index dfe1efc..ce82378 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -139,6 +139,12 @@ bool CPerformanceConfig::Load (void) PropertyName.Format ("InsertFXParams%u", nTG+1); m_sInsertFXParams[nTG] = m_Properties.GetString (PropertyName, ""); + PropertyName.Format ("MidiFX%u", nTG+1); + m_nMidiFX[nTG] = m_Properties.GetNumber (PropertyName, 0); + + PropertyName.Format ("MidiFXParams%u", nTG+1); + m_sMidiFXParams[nTG] = m_Properties.GetString (PropertyName, ""); + PropertyName.Format ("Detune%u", nTG+1); m_nDetune[nTG] = m_Properties.GetSignedNumber (PropertyName, 0); @@ -274,6 +280,12 @@ bool CPerformanceConfig::Save (void) PropertyName.Format ("InsertFXParams%u", nTG+1); m_Properties.SetString (PropertyName, m_sInsertFXParams[nTG].c_str()); + PropertyName.Format ("MidiFX%u", nTG+1); + m_Properties.SetNumber (PropertyName, m_nMidiFX[nTG]); + + PropertyName.Format ("MidiFXParams%u", nTG+1); + m_Properties.SetString (PropertyName, m_sMidiFXParams[nTG].c_str()); + PropertyName.Format ("Detune%u", nTG+1); m_Properties.SetSignedNumber (PropertyName, m_nDetune[nTG]); @@ -437,21 +449,19 @@ unsigned CPerformanceConfig::GetInsertFX (unsigned nTG) const std::vector CPerformanceConfig::GetInsertFXParams (unsigned nTG) const { assert (nTG < CConfig::ToneGenerators); + return StringToVector(m_sInsertFXParams[nTG]); +} - std::vector tokens; - std::string params = m_sInsertFXParams[nTG]; - if (params.empty()) { - return tokens; - } +unsigned CPerformanceConfig::GetMidiFX (unsigned nTG) const +{ + assert (nTG < CConfig::ToneGenerators); + return m_nMidiFX[nTG]; +} - char delimiter = ','; - std::stringstream ss(params); - std::string temp; - while (getline(ss, temp, delimiter)) - { - tokens.push_back(stoi(temp)); - } - return tokens; +std::vector CPerformanceConfig::GetMidiFXParams (unsigned nTG) const +{ + assert (nTG < CConfig::ToneGenerators); + return StringToVector(m_sMidiFXParams[nTG]); } int CPerformanceConfig::GetDetune (unsigned nTG) const @@ -535,16 +545,19 @@ void CPerformanceConfig::SetInsertFX (unsigned nValue, unsigned nTG) void CPerformanceConfig::SetInsertFXParams (std::vector pParams, unsigned nTG) { assert (nTG < CConfig::ToneGenerators); + m_sInsertFXParams[nTG] = VectorToString(pParams); +} - std::string params = ""; - for (size_t i = 0; i < pParams.size(); i++) - { - if (i != 0) { - params += ","; - } - params += std::to_string(pParams[i]); - } - m_sInsertFXParams[nTG] = params; +void CPerformanceConfig::SetMidiFX (unsigned nValue, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + m_nMidiFX[nTG] = nValue; +} + +void CPerformanceConfig::SetMidiFXParams (std::vector pParams, unsigned nTG) +{ + assert (nTG < CConfig::ToneGenerators); + m_sMidiFXParams[nTG] = VectorToString(pParams); } void CPerformanceConfig::SetDetune (int nValue, unsigned nTG) @@ -601,20 +614,7 @@ unsigned CPerformanceConfig::GetSendFX (void) const std::vector CPerformanceConfig::GetSendFXParams (void) const { - std::vector tokens; - std::string params = m_sSendFXParams; - if (params.empty()) { - return tokens; - } - - char delimiter = ','; - std::stringstream ss(params); - std::string temp; - while (getline(ss, temp, delimiter)) - { - tokens.push_back(stoi(temp)); - } - return tokens; + return StringToVector(m_sSendFXParams); } unsigned CPerformanceConfig::GetSendFXLevel (void) const @@ -670,15 +670,7 @@ void CPerformanceConfig::SetSendFX (unsigned nValue) void CPerformanceConfig::SetSendFXParams (std::vector pParams) { - std::string params = ""; - for (size_t i = 0; i < pParams.size(); i++) - { - if (i != 0) { - params += ","; - } - params += std::to_string(pParams[i]); - } - m_sSendFXParams = params; + m_sSendFXParams = VectorToString(pParams); } void CPerformanceConfig::SetSendFXLevel (unsigned nValue) @@ -1490,3 +1482,34 @@ bool CPerformanceConfig::IsValidPerformanceBank(unsigned nBankID) } return true; } + +std::string CPerformanceConfig::VectorToString (std::vector pParams) +{ + std::string params = ""; + for (size_t i = 0; i < pParams.size(); i++) + { + if (i != 0) { + params += ","; + } + params += std::to_string(pParams[i]); + } + return params; +} + +std::vector CPerformanceConfig::StringToVector (std::string sParams) const +{ + std::vector tokens; + if (sParams.empty()) { + return tokens; + } + + char delimiter = ','; + std::stringstream ss(sParams); + std::string temp; + while (getline(ss, temp, delimiter)) + { + tokens.push_back(stoi(temp)); + } + return tokens; +} + diff --git a/src/performanceconfig.h b/src/performanceconfig.h index f6e5632..48e224a 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -51,6 +51,8 @@ public: unsigned GetPan (unsigned nTG) const; // 0 .. 127 unsigned GetInsertFX (unsigned nTG) const; // 0 .. X std::vector GetInsertFXParams (unsigned nTG) const; + unsigned GetMidiFX (unsigned nTG) const; // 0 .. X + std::vector GetMidiFXParams (unsigned nTG) const; int GetDetune (unsigned nTG) const; // -99 .. 99 unsigned GetCutoff (unsigned nTG) const; // 0 .. 99 unsigned GetResonance (unsigned nTG) const; // 0 .. 99 @@ -81,6 +83,8 @@ public: void SetPan (unsigned nValue, unsigned nTG); void SetInsertFX (unsigned nValue, unsigned nTG); void SetInsertFXParams (std::vector pParams, unsigned nTG); + void SetMidiFX (unsigned nValue, unsigned nTG); + void SetMidiFXParams (std::vector pParams, unsigned nTG); void SetDetune (int nValue, unsigned nTG); void SetCutoff (unsigned nValue, unsigned nTG); void SetResonance (unsigned nValue, unsigned nTG); @@ -170,6 +174,8 @@ private: unsigned m_nPan[CConfig::ToneGenerators]; unsigned m_nInsertFX[CConfig::ToneGenerators]; std::string m_sInsertFXParams[CConfig::ToneGenerators]; + unsigned m_nMidiFX[CConfig::ToneGenerators]; + std::string m_sMidiFXParams[CConfig::ToneGenerators]; int m_nDetune[CConfig::ToneGenerators]; unsigned m_nCutoff[CConfig::ToneGenerators]; unsigned m_nResonance[CConfig::ToneGenerators]; @@ -218,6 +224,9 @@ private: unsigned m_nReverbLowPass; unsigned m_nReverbDiffusion; unsigned m_nReverbLevel; + + std::string VectorToString (std::vector pParams); + std::vector StringToVector (std::string sParams) const; }; #endif From d7336ee180f195b59df2edadb898e4240917d13d Mon Sep 17 00:00:00 2001 From: Javier Nonis Date: Sat, 20 Jul 2024 20:14:08 -0300 Subject: [PATCH 08/11] Save Tempo into Performance --- src/minidexed.cpp | 5 +++++ src/performanceconfig.cpp | 15 +++++++++++++++ src/performanceconfig.h | 3 +++ 3 files changed, 23 insertions(+) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index a3185e3..a716245 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1016,6 +1016,7 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) break; case ParameterTempo: + nValue=constrain((int)nValue,30,250); this->setTempo(nValue); break; @@ -1562,6 +1563,8 @@ bool CMiniDexed::DoSavePerformance (void) m_PerformanceConfig.SetReverbDiffusion (m_nParameter[ParameterReverbDiffusion]); m_PerformanceConfig.SetReverbLevel (m_nParameter[ParameterReverbLevel]); + m_PerformanceConfig.SetTempo (m_nTempo); + if(m_bSaveAsDeault) { m_PerformanceConfig.SetNewPerformance(0); @@ -2021,6 +2024,8 @@ void CMiniDexed::LoadPerformanceParameters(void) SetParameter (ParameterReverbLowPass, m_PerformanceConfig.GetReverbLowPass ()); SetParameter (ParameterReverbDiffusion, m_PerformanceConfig.GetReverbDiffusion ()); SetParameter (ParameterReverbLevel, m_PerformanceConfig.GetReverbLevel ()); + + SetParameter (ParameterTempo, m_PerformanceConfig.GetTempo ()); } std::string CMiniDexed::GetNewPerformanceDefaultName(void) diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index ce82378..8bd107c 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -223,6 +223,8 @@ bool CPerformanceConfig::Load (void) m_nReverbDiffusion = m_Properties.GetNumber ("ReverbDiffusion", 65); m_nReverbLevel = m_Properties.GetNumber ("ReverbLevel", 100); + m_nTempo = m_Properties.GetNumber ("Tempo", 120); + // Set EFFECT_REVERB as Default for backward compatibility // EFFECT_REVERB 7 m_nSendFX = m_Properties.GetNumber ("SendFX", 7); @@ -407,6 +409,8 @@ bool CPerformanceConfig::Save (void) tokens.shrink_to_fit(); } + m_Properties.SetNumber ("Tempo", m_nTempo); + return m_Properties.Save (); } @@ -658,6 +662,11 @@ unsigned CPerformanceConfig::GetReverbLevel (void) const return m_nReverbLevel; } +unsigned CPerformanceConfig::GetTempo (void) const +{ + return m_nTempo; +} + void CPerformanceConfig::SetCompressorEnable (bool bValue) { m_bCompressorEnable = bValue; @@ -712,6 +721,12 @@ void CPerformanceConfig::SetReverbLevel (unsigned nValue) { m_nReverbLevel = nValue; } + +void CPerformanceConfig::SetTempo (unsigned nValue) +{ + m_nTempo = nValue; +} + // Pitch bender and portamento: void CPerformanceConfig::SetPitchBendRange (unsigned nValue, unsigned nTG) { diff --git a/src/performanceconfig.h b/src/performanceconfig.h index 48e224a..1810674 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -122,6 +122,7 @@ public: unsigned GetReverbLowPass (void) const; // 0 .. 99 unsigned GetReverbDiffusion (void) const; // 0 .. 99 unsigned GetReverbLevel (void) const; // 0 .. 99 + unsigned GetTempo (void) const; void SetCompressorEnable (bool bValue); void SetSendFX (unsigned nValue); @@ -134,6 +135,7 @@ public: void SetReverbLowPass (unsigned nValue); void SetReverbDiffusion (unsigned nValue); void SetReverbLevel (unsigned nValue); + void SetTempo (unsigned nValue); bool VoiceDataFilled(unsigned nTG); bool ListPerformances(); @@ -224,6 +226,7 @@ private: unsigned m_nReverbLowPass; unsigned m_nReverbDiffusion; unsigned m_nReverbLevel; + unsigned m_nTempo; std::string VectorToString (std::vector pParams); std::vector StringToVector (std::string sParams) const; From bf30d1a1c95ccef846f8c4bd14b8de66fdd6f17b Mon Sep 17 00:00:00 2001 From: Javier Nonis Date: Sat, 20 Jul 2024 20:15:13 -0300 Subject: [PATCH 09/11] UI fix for Tempo display --- src/minidexed.cpp | 10 ++++++ src/minidexed.h | 4 +++ src/uimenu.cpp | 79 +++++++++++++++++++++++++++++++++++++++++++++-- src/uimenu.h | 1 + 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index a716245..6c2816a 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -778,6 +778,16 @@ void CMiniDexed::setTempo(unsigned nValue) } +bool CMiniDexed::isPlaying(void) +{ + return m_bPlaying; +} + +void CMiniDexed::setPlaying(bool bValue) +{ + m_bPlaying = bValue; +} + void CMiniDexed::handleClock (void) { if (m_nClockCounter == 0) diff --git a/src/minidexed.h b/src/minidexed.h index 160c608..990cdea 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -86,6 +86,9 @@ public: void setTempo(unsigned nValue); void handleClock(void); + bool isPlaying(void); + void setPlaying(bool bValue); + void keyup (int16_t pitch, unsigned nTG); void keydown (int16_t pitch, uint8_t velocity, unsigned nTG); @@ -367,6 +370,7 @@ private: unsigned m_nClockCounter; unsigned long m_mClockTime; unsigned m_nTempo; // Tempo in BPM + bool m_bPlaying = false; }; #endif diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 6a409a8..5c18b82 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", MainMenuHandler, s_MainMenu}, {0} }; @@ -649,6 +649,81 @@ void CUIMenu::EventHandler (TMenuEvent Event) } } +void CUIMenu::MainMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event) +{ + bool menuChange = false; + 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; + menuChange = true; + 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? + { + string strTempo; + if (menuChange) + { + strTempo = ""; + } + else + { + strTempo = (pUIMenu->m_pMiniDexed->isPlaying() ? ">" : "#") + + to_string(pUIMenu->m_pMiniDexed->getTempo()); + } + + pUIMenu->m_pUI->DisplayWrite ( + pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, + strTempo.c_str(), + 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::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event) { switch (Event) @@ -700,7 +775,7 @@ void CUIMenu::MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event) { pUIMenu->m_pUI->DisplayWrite ( pUIMenu->m_pParentMenu[pUIMenu->m_nCurrentMenuItem].Name, - to_string(pUIMenu->m_pMiniDexed->getTempo()).c_str(), + "", pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection].Name, pUIMenu->m_nCurrentSelection > 0, !!pUIMenu->m_pCurrentMenu[pUIMenu->m_nCurrentSelection+1].Name); diff --git a/src/uimenu.h b/src/uimenu.h index 0700502..2799697 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -79,6 +79,7 @@ private: }; private: + static void MainMenuHandler (CUIMenu *pUIMenu, TMenuEvent Event); static void MenuHandler (CUIMenu *pUIMenu, TMenuEvent Event); static void EditGlobalParameter (CUIMenu *pUIMenu, TMenuEvent Event); static void EditVoiceBankNumber (CUIMenu *pUIMenu, TMenuEvent Event); From 6d673aa8be20b2c4ee20ed575101784dda15b131 Mon Sep 17 00:00:00 2001 From: Javier Nonis Date: Mon, 22 Jul 2024 16:46:33 -0300 Subject: [PATCH 10/11] Added MVerb effect --- src/Makefile | 2 +- src/effect_base.h | 1 + src/effect_mverb.cpp | 116 ++++++ src/effect_mverb.h | 54 +++ src/effect_talreverb3.cpp | 3 +- src/effect_talreverb3.h | 3 +- src/effects.h | 4 + src/mverb/MVerb.h | 842 ++++++++++++++++++++++++++++++++++++++ src/uimenu.cpp | 40 +- src/uimenu.h | 2 + 10 files changed, 1062 insertions(+), 5 deletions(-) create mode 100644 src/effect_mverb.cpp create mode 100644 src/effect_mverb.h create mode 100644 src/mverb/MVerb.h diff --git a/src/Makefile b/src/Makefile index 72f33c9..71337ba 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,7 +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_base.o effect_chorus.o effect_delay.o \ + effect_base.o effect_chorus.o effect_delay.o effect_mverb.o \ effect_talreverb3.o effect_ds1.o effect_bigmuff.o \ moddistortion/Distortion_DS1.o moddistortion/Distortion_BigMuff.o \ moddistortion/HyperbolicTables.o moddistortion/OverSample.o \ diff --git a/src/effect_base.h b/src/effect_base.h index 4bf7a0b..01d559c 100644 --- a/src/effect_base.h +++ b/src/effect_base.h @@ -16,6 +16,7 @@ #define EFFECT_BIGMUFF 5 #define EFFECT_TALREVERB3 6 #define EFFECT_REVERB 7 +#define EFFECT_MVERB 8 class AudioEffect { diff --git a/src/effect_mverb.cpp b/src/effect_mverb.cpp new file mode 100644 index 0000000..2e58847 --- /dev/null +++ b/src/effect_mverb.cpp @@ -0,0 +1,116 @@ +/* + * MVerb Reverb Port + * Ported from https://github.com/DISTRHO/MVerb + * Original https://github.com/martineastwood/mverb/ + * + * Javier Nonis (https://github.com/jnonis) - 2024 + */ +#include "effect_mverb.h" + +AudioEffectMVerb::AudioEffectMVerb(float32_t samplerate) : AudioEffect(samplerate) +{ + fVerb.setSampleRate(samplerate); + + fVerb.setParameter(MVerb::DAMPINGFREQ, 0.5f); + fVerb.setParameter(MVerb::DENSITY, 0.5f); + fVerb.setParameter(MVerb::BANDWIDTHFREQ, 0.5f); + fVerb.setParameter(MVerb::DECAY, 0.5f); + fVerb.setParameter(MVerb::PREDELAY, 0.5f); + fVerb.setParameter(MVerb::SIZE, 0.75f); + fVerb.setParameter(MVerb::GAIN, 1.0f); + fVerb.setParameter(MVerb::MIX, 0.5f); + fVerb.setParameter(MVerb::EARLYMIX, 0.5f); + fVerb.reset(); +} + +AudioEffectMVerb::~AudioEffectMVerb() +{ +} + +void AudioEffectMVerb::initializeSendFX() +{ + this->setParameter(AudioEffectMVerb::Param::MIX, 100); +} + +void AudioEffectMVerb::setParameter(unsigned param, unsigned value) +{ + switch (param) + { + case AudioEffectMVerb::Param::BYPASS: + this->setBypass(value == 1); + break; + case AudioEffectMVerb::Param::DAMPINGFREQ: + fVerb.setParameter(MVerb::DAMPINGFREQ, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::DENSITY: + fVerb.setParameter(MVerb::DENSITY, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::BANDWIDTHFREQ: + fVerb.setParameter(MVerb::BANDWIDTHFREQ, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::DECAY: + fVerb.setParameter(MVerb::DECAY, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::PREDELAY: + fVerb.setParameter(MVerb::PREDELAY, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::SIZE: + fVerb.setParameter(MVerb::SIZE, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::GAIN: + fVerb.setParameter(MVerb::GAIN, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::MIX: + fVerb.setParameter(MVerb::MIX, (float) value / 100.0f); + break; + case AudioEffectMVerb::Param::EARLYMIX: + fVerb.setParameter(MVerb::EARLYMIX, (float) value / 100.0f); + break; + default: + break; + } +} + +unsigned AudioEffectMVerb::getParameter(unsigned param) +{ + switch (param) + { + case AudioEffectMVerb::Param::BYPASS: + return this->getBypass() ? 1 : 0; + case AudioEffectMVerb::Param::DAMPINGFREQ: + return roundf(fVerb.getParameter(MVerb::DAMPINGFREQ) * 100); + case AudioEffectMVerb::Param::DENSITY: + return roundf(fVerb.getParameter(MVerb::DENSITY) * 100); + case AudioEffectMVerb::Param::BANDWIDTHFREQ: + return roundf(fVerb.getParameter(MVerb::BANDWIDTHFREQ) * 100); + case AudioEffectMVerb::Param::DECAY: + return roundf(fVerb.getParameter(MVerb::DECAY) * 100); + case AudioEffectMVerb::Param::PREDELAY: + return roundf(fVerb.getParameter(MVerb::PREDELAY) * 100); + case AudioEffectMVerb::Param::SIZE: + return roundf(fVerb.getParameter(MVerb::SIZE) * 100); + case AudioEffectMVerb::Param::GAIN: + return roundf(fVerb.getParameter(MVerb::GAIN) * 100); + case AudioEffectMVerb::Param::MIX: + return roundf(fVerb.getParameter(MVerb::MIX) * 100); + case AudioEffectMVerb::Param::EARLYMIX: + return roundf(fVerb.getParameter(MVerb::EARLYMIX) * 100); + default: + return 0; + } +} + + +void AudioEffectMVerb::doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) +{ + const float32_t* inputs[2]; + inputs[0] = inblockL; + inputs[1] = inblockR; + + float32_t* outputs[2]; + outputs[0] = outblockL; + outputs[1] = outblockR; + + fVerb.process(inputs, outputs, static_cast(len)); +} + diff --git a/src/effect_mverb.h b/src/effect_mverb.h new file mode 100644 index 0000000..5f50235 --- /dev/null +++ b/src/effect_mverb.h @@ -0,0 +1,54 @@ +/* + * MVerb Reverb Port + * Ported from https://github.com/DISTRHO/MVerb + * Original https://github.com/martineastwood/mverb/ + * + * Javier Nonis (https://github.com/jnonis) - 2024 + */ +#ifndef _EFFECT_MVERB_H +#define _EFFECT_MVERB_H + +#include "effect_base.h" +#include "mverb/MVerb.h" + +class AudioEffectMVerb : public AudioEffect +{ +public: + enum Param + { + BYPASS, + DAMPINGFREQ, + DENSITY, + BANDWIDTHFREQ, + DECAY, + PREDELAY, + SIZE, + GAIN, + MIX, + EARLYMIX, + UNKNOWN + }; + + AudioEffectMVerb(float32_t samplerate); + virtual ~AudioEffectMVerb(); + + virtual unsigned getId() + { + return EFFECT_MVERB; + } + + virtual void initializeSendFX(); + virtual void setParameter(unsigned param, unsigned value); + virtual unsigned getParameter(unsigned param); +protected: + virtual size_t getParametersSize() + { + return AudioEffectMVerb::Param::UNKNOWN; + } + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len); + +private: + MVerb fVerb; +}; + +#endif // _EFFECT_MVERB_H diff --git a/src/effect_talreverb3.cpp b/src/effect_talreverb3.cpp index aa16922..f6d2d05 100644 --- a/src/effect_talreverb3.cpp +++ b/src/effect_talreverb3.cpp @@ -1,7 +1,8 @@ /* * Tal Reverb 3 Port * Ported from https://github.com/DISTRHO/DISTRHO-Ports/tree/master/ports-juce5/tal-reverb-3 - * + * Original https://tal-software.com/ + * * Javier Nonis (https://github.com/jnonis) - 2024 */ #include diff --git a/src/effect_talreverb3.h b/src/effect_talreverb3.h index c9440bc..0504ceb 100644 --- a/src/effect_talreverb3.h +++ b/src/effect_talreverb3.h @@ -1,7 +1,8 @@ /* * Tal Reverb 3 Port * Ported from https://github.com/DISTRHO/DISTRHO-Ports/tree/master/ports-juce5/tal-reverb-3 - * + * Original https://tal-software.com/ + * * Javier Nonis (https://github.com/jnonis) - 2024 */ #ifndef _EFFECT_TALREVERB3_H diff --git a/src/effects.h b/src/effects.h index 1c80e7b..2053774 100644 --- a/src/effects.h +++ b/src/effects.h @@ -14,6 +14,7 @@ #include "effect_bigmuff.h" #include "effect_talreverb3.h" #include "effect_platervbstereo.h" +#include "effect_mverb.h" inline AudioEffect* newAudioEffect(unsigned type, float32_t samplerate) { @@ -33,6 +34,8 @@ inline AudioEffect* newAudioEffect(unsigned type, float32_t samplerate) return new AudioEffectTalReverb3(samplerate); case EFFECT_REVERB: return new AudioEffectPlateReverb(samplerate); + case EFFECT_MVERB: + return new AudioEffectMVerb(samplerate); case EFFECT_NONE: default: return new AudioEffectNone(samplerate); @@ -50,6 +53,7 @@ inline std::string getFXTypeName(int nValue) case EFFECT_BIGMUFF: return "Big Muff"; case EFFECT_TALREVERB3: return "TalRvrb3"; case EFFECT_REVERB: return "Reverb"; + case EFFECT_MVERB: return "MVerb"; case EFFECT_NONE: default: return "None"; } diff --git a/src/mverb/MVerb.h b/src/mverb/MVerb.h new file mode 100644 index 0000000..0394cc3 --- /dev/null +++ b/src/mverb/MVerb.h @@ -0,0 +1,842 @@ +// Copyright (c) 2010 Martin Eastwood +// This code is distributed under the terms of the GNU General Public License + +// MVerb 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. +// +// MVerb 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 MVerb. If not, see . + +#ifndef _EMVERB_H +#define _EMVERB_H + +#include +#include + +//forward declaration +template class Allpass; +template class StaticAllpassFourTap; +template class StaticDelayLine; +template class StaticDelayLineFourTap; +template class StaticDelayLineEightTap; +template class StateVariable; + +template +class MVerb +{ +private: + Allpass allpass[4]; + StaticAllpassFourTap allpassFourTap[4]; + StateVariable bandwidthFilter[2]; + StateVariable damping[2]; + StaticDelayLine predelay; + StaticDelayLineFourTap staticDelayLine[4]; + StaticDelayLineEightTap earlyReflectionsDelayLine[2]; + T SampleRate, DampingFreq, Density1, Density2, BandwidthFreq, PreDelayTime, Decay, Gain, Mix, EarlyMix, Size; + T MixSmooth, EarlyLateSmooth, BandwidthSmooth, DampingSmooth, PredelaySmooth, SizeSmooth, DensitySmooth, DecaySmooth; + T PreviousLeftTank, PreviousRightTank; + int ControlRate, ControlRateCounter; + +public: + enum + { + DAMPINGFREQ=0, + DENSITY, + BANDWIDTHFREQ, + DECAY, + PREDELAY, + SIZE, + GAIN, + MIX, + EARLYMIX, + NUM_PARAMS + }; + + MVerb(){ + DampingFreq = 0.9; + BandwidthFreq = 0.9; + SampleRate = 44100.; + Decay = 0.5; + Gain = 1.; + Mix = 1.; + Size = 1.; + EarlyMix = 1.; + PreviousLeftTank = 0.; + PreviousRightTank = 0.; + PreDelayTime = 100 * (SampleRate / 1000); + MixSmooth = EarlyLateSmooth = BandwidthSmooth = DampingSmooth = PredelaySmooth = SizeSmooth = DecaySmooth = DensitySmooth = 0.; + ControlRate = SampleRate / 1000; + ControlRateCounter = 0; + reset(); + } + + ~MVerb(){ + //nowt to do here + } + + void process(const T **inputs, T **outputs, int sampleFrames){ + T OneOverSampleFrames = 1. / sampleFrames; + T MixDelta = (Mix - MixSmooth) * OneOverSampleFrames; + T EarlyLateDelta = (EarlyMix - EarlyLateSmooth) * OneOverSampleFrames; + T BandwidthDelta = (((BandwidthFreq * 18400.) + 100.) - BandwidthSmooth) * OneOverSampleFrames; + T DampingDelta = (((DampingFreq * 18400.) + 100.) - DampingSmooth) * OneOverSampleFrames; + T PredelayDelta = ((PreDelayTime * 200 * (SampleRate / 1000)) - PredelaySmooth) * OneOverSampleFrames; + T SizeDelta = (Size - SizeSmooth) * OneOverSampleFrames; + T DecayDelta = (((0.7995f * Decay) + 0.005) - DecaySmooth) * OneOverSampleFrames; + T DensityDelta = (((0.7995f * Density1) + 0.005) - DensitySmooth) * OneOverSampleFrames; + for(int i=0;i= ControlRate){ + ControlRateCounter = 0; + bandwidthFilter[0].Frequency(BandwidthSmooth); + bandwidthFilter[1].Frequency(BandwidthSmooth); + damping[0].Frequency(DampingSmooth); + damping[1].Frequency(DampingSmooth); + } + ++ControlRateCounter; + predelay.SetLength(PredelaySmooth); + Density2 = DecaySmooth + 0.15; + if (Density2 > 0.5) + Density2 = 0.5; + if (Density2 < 0.25) + Density2 = 0.25; + allpassFourTap[1].SetFeedback(Density2); + allpassFourTap[3].SetFeedback(Density2); + allpassFourTap[0].SetFeedback(Density1); + allpassFourTap[2].SetFeedback(Density1); + T bandwidthLeft = bandwidthFilter[0](left) ; + T bandwidthRight = bandwidthFilter[1](right) ; + T earlyReflectionsL = earlyReflectionsDelayLine[0] ( bandwidthLeft * 0.5 + bandwidthRight * 0.3 ) + + earlyReflectionsDelayLine[0].GetIndex(2) * 0.6 + + earlyReflectionsDelayLine[0].GetIndex(3) * 0.4 + + earlyReflectionsDelayLine[0].GetIndex(4) * 0.3 + + earlyReflectionsDelayLine[0].GetIndex(5) * 0.3 + + earlyReflectionsDelayLine[0].GetIndex(6) * 0.1 + + earlyReflectionsDelayLine[0].GetIndex(7) * 0.1 + + ( bandwidthLeft * 0.4 + bandwidthRight * 0.2 ) * 0.5 ; + T earlyReflectionsR = earlyReflectionsDelayLine[1] ( bandwidthLeft * 0.3 + bandwidthRight * 0.5 ) + + earlyReflectionsDelayLine[1].GetIndex(2) * 0.6 + + earlyReflectionsDelayLine[1].GetIndex(3) * 0.4 + + earlyReflectionsDelayLine[1].GetIndex(4) * 0.3 + + earlyReflectionsDelayLine[1].GetIndex(5) * 0.3 + + earlyReflectionsDelayLine[1].GetIndex(6) * 0.1 + + earlyReflectionsDelayLine[1].GetIndex(7) * 0.1 + + ( bandwidthLeft * 0.2 + bandwidthRight * 0.4 ) * 0.5 ; + T predelayMonoInput = predelay(( bandwidthRight + bandwidthLeft ) * 0.5f); + T smearedInput = predelayMonoInput; + for(int j=0;j<4;j++) + smearedInput = allpass[j] ( smearedInput ); + T leftTank = allpassFourTap[0] ( smearedInput + PreviousRightTank ) ; + leftTank = staticDelayLine[0] (leftTank); + leftTank = damping[0](leftTank); + leftTank = allpassFourTap[1](leftTank); + leftTank = staticDelayLine[1](leftTank); + T rightTank = allpassFourTap[2] (smearedInput + PreviousLeftTank) ; + rightTank = staticDelayLine[2](rightTank); + rightTank = damping[1] (rightTank); + rightTank = allpassFourTap[3](rightTank); + rightTank = staticDelayLine[3](rightTank); + PreviousLeftTank = leftTank * DecaySmooth; + PreviousRightTank = rightTank * DecaySmooth; + T accumulatorL = (0.6*staticDelayLine[2].GetIndex(1)) + +(0.6*staticDelayLine[2].GetIndex(2)) + -(0.6*allpassFourTap[3].GetIndex(1)) + +(0.6*staticDelayLine[3].GetIndex(1)) + -(0.6*staticDelayLine[0].GetIndex(1)) + -(0.6*allpassFourTap[1].GetIndex(1)) + -(0.6*staticDelayLine[1].GetIndex(1)); + T accumulatorR = (0.6*staticDelayLine[0].GetIndex(2)) + +(0.6*staticDelayLine[0].GetIndex(3)) + -(0.6*allpassFourTap[1].GetIndex(2)) + +(0.6*staticDelayLine[1].GetIndex(2)) + -(0.6*staticDelayLine[2].GetIndex(3)) + -(0.6*allpassFourTap[3].GetIndex(2)) + -(0.6*staticDelayLine[3].GetIndex(2)); + accumulatorL = ((accumulatorL * EarlyMix) + ((1 - EarlyMix) * earlyReflectionsL)); + accumulatorR = ((accumulatorR * EarlyMix) + ((1 - EarlyMix) * earlyReflectionsR)); + left = ( left + MixSmooth * ( accumulatorL - left ) ) * Gain; + right = ( right + MixSmooth * ( accumulatorR - right ) ) * Gain; + outputs[0][i] = left; + outputs[1][i] = right; + } + } + + void reset(){ + ControlRateCounter = 0; + bandwidthFilter[0].SetSampleRate (SampleRate ); + bandwidthFilter[1].SetSampleRate (SampleRate ); + bandwidthFilter[0].Reset(); + bandwidthFilter[1].Reset(); + damping[0].SetSampleRate (SampleRate ); + damping[1].SetSampleRate (SampleRate ); + damping[0].Reset(); + damping[1].Reset(); + predelay.Clear(); + predelay.SetLength(PreDelayTime); + allpass[0].Clear(); + allpass[1].Clear(); + allpass[2].Clear(); + allpass[3].Clear(); + allpass[0].SetLength (0.0048 * SampleRate); + allpass[1].SetLength (0.0036 * SampleRate); + allpass[2].SetLength (0.0127 * SampleRate); + allpass[3].SetLength (0.0093 * SampleRate); + allpass[0].SetFeedback (0.75); + allpass[1].SetFeedback (0.75); + allpass[2].SetFeedback (0.625); + allpass[3].SetFeedback (0.625); + allpassFourTap[0].Clear(); + allpassFourTap[1].Clear(); + allpassFourTap[2].Clear(); + allpassFourTap[3].Clear(); + allpassFourTap[0].SetLength(0.020 * SampleRate * Size); + allpassFourTap[1].SetLength(0.060 * SampleRate * Size); + allpassFourTap[2].SetLength(0.030 * SampleRate * Size); + allpassFourTap[3].SetLength(0.089 * SampleRate * Size); + allpassFourTap[0].SetFeedback(Density1); + allpassFourTap[1].SetFeedback(Density2); + allpassFourTap[2].SetFeedback(Density1); + allpassFourTap[3].SetFeedback(Density2); + allpassFourTap[0].SetIndex(0,0,0,0); + allpassFourTap[1].SetIndex(0,0.006 * SampleRate * Size, 0.041 * SampleRate * Size, 0); + allpassFourTap[2].SetIndex(0,0,0,0); + allpassFourTap[3].SetIndex(0,0.031 * SampleRate * Size, 0.011 * SampleRate * Size, 0); + staticDelayLine[0].Clear(); + staticDelayLine[1].Clear(); + staticDelayLine[2].Clear(); + staticDelayLine[3].Clear(); + staticDelayLine[0].SetLength(0.15 * SampleRate * Size); + staticDelayLine[1].SetLength(0.12 * SampleRate * Size); + staticDelayLine[2].SetLength(0.14 * SampleRate * Size); + staticDelayLine[3].SetLength(0.11 * SampleRate * Size); + staticDelayLine[0].SetIndex(0, 0.067 * SampleRate * Size, 0.011 * SampleRate * Size , 0.121 * SampleRate * Size); + staticDelayLine[1].SetIndex(0, 0.036 * SampleRate * Size, 0.089 * SampleRate * Size , 0); + staticDelayLine[2].SetIndex(0, 0.0089 * SampleRate * Size, 0.099 * SampleRate * Size , 0); + staticDelayLine[3].SetIndex(0, 0.067 * SampleRate * Size, 0.0041 * SampleRate * Size , 0); + earlyReflectionsDelayLine[0].Clear(); + earlyReflectionsDelayLine[1].Clear(); + earlyReflectionsDelayLine[0].SetLength(0.089 * SampleRate); + earlyReflectionsDelayLine[0].SetIndex (0, 0.0199*SampleRate, 0.0219*SampleRate, 0.0354*SampleRate,0.0389*SampleRate, 0.0414*SampleRate, 0.0692*SampleRate, 0); + earlyReflectionsDelayLine[1].SetLength(0.069 * SampleRate); + earlyReflectionsDelayLine[1].SetIndex (0, 0.0099*SampleRate, 0.011*SampleRate, 0.0182*SampleRate,0.0189*SampleRate, 0.0213*SampleRate, 0.0431*SampleRate, 0); + } + + void setParameter(int index, T value){ + switch(index){ + case DAMPINGFREQ: + DampingFreq = /* 1. - */ value; // FIXME? + break; + case DENSITY: + Density1 = value; + break; + case BANDWIDTHFREQ: + BandwidthFreq = value; + break; + case PREDELAY: + PreDelayTime = value; + break; + case SIZE: + Size = value; + allpassFourTap[0].Clear(); + allpassFourTap[1].Clear(); + allpassFourTap[2].Clear(); + allpassFourTap[3].Clear(); + allpassFourTap[0].SetLength(0.020 * SampleRate * Size); + allpassFourTap[1].SetLength(0.060 * SampleRate * Size); + allpassFourTap[2].SetLength(0.030 * SampleRate * Size); + allpassFourTap[3].SetLength(0.089 * SampleRate * Size); + allpassFourTap[1].SetIndex(0,0.006 * SampleRate * Size, 0.041 * SampleRate * Size, 0); + allpassFourTap[3].SetIndex(0,0.031 * SampleRate * Size, 0.011 * SampleRate * Size, 0); + staticDelayLine[0].Clear(); + staticDelayLine[1].Clear(); + staticDelayLine[2].Clear(); + staticDelayLine[3].Clear(); + staticDelayLine[0].SetLength(0.15 * SampleRate * Size); + staticDelayLine[1].SetLength(0.12 * SampleRate * Size); + staticDelayLine[2].SetLength(0.14 * SampleRate * Size); + staticDelayLine[3].SetLength(0.11 * SampleRate * Size); + staticDelayLine[0].SetIndex(0, 0.067 * SampleRate * Size, 0.011 * SampleRate * Size , 0.121 * SampleRate * Size); + staticDelayLine[1].SetIndex(0, 0.036 * SampleRate * Size, 0.089 * SampleRate * Size , 0); + staticDelayLine[2].SetIndex(0, 0.0089 * SampleRate * Size, 0.099 * SampleRate * Size , 0); + staticDelayLine[3].SetIndex(0, 0.067 * SampleRate * Size, 0.0041 * SampleRate * Size , 0); + break; + case DECAY: + Decay = value; + break; + case GAIN: + Gain = value; + break; + case MIX: + Mix = value; + break; + case EARLYMIX: + EarlyMix = value; + break; + } + } + + float getParameter(int index) const{ + switch(index){ + case DAMPINGFREQ: + return DampingFreq; + break; + case DENSITY: + return Density1; + break; + case BANDWIDTHFREQ: + return BandwidthFreq; + break; + case PREDELAY: + return PreDelayTime; + break; + case SIZE: + return Size; + break; + case DECAY: + return Decay; + break; + case GAIN: + return Gain; + break; + case MIX: + return Mix; + break; + case EARLYMIX: + return EarlyMix; + break; + default: return 0.f; + break; + + } + } + + void setSampleRate(T sr){ + SampleRate = sr; + ControlRate = SampleRate / 1000; + reset(); + } +}; + + + +template +class Allpass +{ +private: + T buffer[maxLength]; + int index; + int Length; + T Feedback; + +public: + Allpass() + { + SetLength ( maxLength - 1 ); + Clear(); + Feedback = 0.5; + } + + T operator()(T input) + { + T output; + T bufout; + bufout = buffer[index]; + T temp = input * -Feedback; + output = bufout + temp; + buffer[index] = input + ((bufout+temp)*Feedback); + if(++index>=Length) index = 0; + return output; + } + + void SetLength (int Length) + { + if( Length >= maxLength ) + Length = maxLength; + if( Length < 0 ) + Length = 0; + + this->Length = Length; + } + + void SetFeedback(T feedback) + { + Feedback = feedback; + } + + void Clear() + { + std::memset(buffer, 0, sizeof(buffer)); + index = 0; + } + + int GetLength() const + { + return Length; + } +}; + +template +class StaticAllpassFourTap +{ +private: + T buffer[maxLength]; + int index1, index2, index3, index4; + int Length; + T Feedback; + +public: + StaticAllpassFourTap() + { + SetLength ( maxLength - 1 ); + Clear(); + Feedback = 0.5; + } + + T operator()(T input) + { + T output; + T bufout; + + bufout = buffer[index1]; + T temp = input * -Feedback; + output = bufout + temp; + buffer[index1] = input + ((bufout+temp)*Feedback); + + if(++index1>=Length) + index1 = 0; + if(++index2 >= Length) + index2 = 0; + if(++index3 >= Length) + index3 = 0; + if(++index4 >= Length) + index4 = 0; + + return output; + } + + void SetIndex (int Index1, int Index2, int Index3, int Index4) + { + index1 = Index1; + index2 = Index2; + index3 = Index3; + index4 = Index4; + } + + T GetIndex (int Index) + { + switch (Index) + { + case 0: + return buffer[index1]; + break; + case 1: + return buffer[index2]; + break; + case 2: + return buffer[index3]; + break; + case 3: + return buffer[index4]; + break; + default: + return buffer[index1]; + break; + } + } + + void SetLength (int Length) + { + if( Length >= maxLength ) + Length = maxLength; + if( Length < 0 ) + Length = 0; + + this->Length = Length; + } + + + void Clear() + { + std::memset(buffer, 0, sizeof(buffer)); + index1 = index2 = index3 = index4 = 0; + } + + void SetFeedback(T feedback) + { + Feedback = feedback; + } + + int GetLength() const + { + return Length; + } +}; + +template +class StaticDelayLine +{ +private: + T buffer[maxLength]; + int index; + int Length; + T Feedback; + +public: + StaticDelayLine() + { + SetLength ( maxLength - 1 ); + Clear(); + } + + T operator()(T input) + { + T output = buffer[index]; + buffer[index++] = input; + if(index >= Length) + index = 0; + return output; + } + + void SetLength (int Length) + { + if( Length >= maxLength ) + Length = maxLength; + if( Length < 0 ) + Length = 0; + + this->Length = Length; + } + + void Clear() + { + std::memset(buffer, 0, sizeof(buffer)); + index = 0; + } + + int GetLength() const + { + return Length; + } +}; + +template +class StaticDelayLineFourTap +{ +private: + T buffer[maxLength]; + int index1, index2, index3, index4; + int Length; + T Feedback; + +public: + StaticDelayLineFourTap() + { + SetLength ( maxLength - 1 ); + Clear(); + } + + //get ouput and iterate + T operator()(T input) + { + T output = buffer[index1]; + buffer[index1++] = input; + if(index1 >= Length) + index1 = 0; + if(++index2 >= Length) + index2 = 0; + if(++index3 >= Length) + index3 = 0; + if(++index4 >= Length) + index4 = 0; + return output; + } + + void SetIndex (int Index1, int Index2, int Index3, int Index4) + { + index1 = Index1; + index2 = Index2; + index3 = Index3; + index4 = Index4; + } + + + T GetIndex (int Index) + { + switch (Index) + { + case 0: + return buffer[index1]; + break; + case 1: + return buffer[index2]; + break; + case 2: + return buffer[index3]; + break; + case 3: + return buffer[index4]; + break; + default: + return buffer[index1]; + break; + } + } + + + void SetLength (int Length) + { + if( Length >= maxLength ) + Length = maxLength; + if( Length < 0 ) + Length = 0; + + this->Length = Length; + } + + + void Clear() + { + std::memset(buffer, 0, sizeof(buffer)); + index1 = index2 = index3 = index4 = 0; + } + + + int GetLength() const + { + return Length; + } +}; + +template +class StaticDelayLineEightTap +{ +private: + T buffer[maxLength]; + int index1, index2, index3, index4, index5, index6, index7, index8; + int Length; + T Feedback; + +public: + StaticDelayLineEightTap() + { + SetLength ( maxLength - 1 ); + Clear(); + } + + //get ouput and iterate + T operator()(T input) + { + T output = buffer[index1]; + buffer[index1++] = input; + if(index1 >= Length) + index1 = 0; + if(++index2 >= Length) + index2 = 0; + if(++index3 >= Length) + index3 = 0; + if(++index4 >= Length) + index4 = 0; + if(++index5 >= Length) + index5 = 0; + if(++index6 >= Length) + index6 = 0; + if(++index7 >= Length) + index7 = 0; + if(++index8 >= Length) + index8 = 0; + return output; + + } + + void SetIndex (int Index1, int Index2, int Index3, int Index4, int Index5, int Index6, int Index7, int Index8) + { + index1 = Index1; + index2 = Index2; + index3 = Index3; + index4 = Index4; + index5 = Index5; + index6 = Index6; + index7 = Index7; + index8 = Index8; + } + + + T GetIndex (int Index) + { + switch (Index) + { + case 0: + return buffer[index1]; + break; + case 1: + return buffer[index2]; + break; + case 2: + return buffer[index3]; + break; + case 3: + return buffer[index4]; + break; + case 4: + return buffer[index5]; + break; + case 5: + return buffer[index6]; + break; + case 6: + return buffer[index7]; + break; + case 7: + return buffer[index8]; + break; + default: + return buffer[index1]; + break; + } + } + + void SetLength (int Length) + { + if( Length >= maxLength ) + Length = maxLength; + if( Length < 0 ) + Length = 0; + + this->Length = Length; + } + + + void Clear() + { + std::memset(buffer, 0, sizeof(buffer)); + index1 = index2 = index3 = index4 = index5 = index6 = index7 = index8 = 0; + } + + + int GetLength() const + { + return Length; + } +}; + +template +class StateVariable +{ +public: + + enum FilterType + { + LOWPASS, + HIGHPASS, + BANDPASS, + NOTCH, + FilterTypeCount + }; + +private: + + T sampleRate; + T frequency; + T q; + T f; + + T low; + T high; + T band; + T notch; + + T *out; + +public: + StateVariable() + { + SetSampleRate(44100.); + Frequency(1000.); + Resonance(0); + Type(LOWPASS); + Reset(); + } + + T operator()(T input) + { + for(unsigned int i = 0; i < OverSampleCount; i++) + { + low += f * band + 1e-25; + high = input - low - q * band; + band += f * high; + notch = low + high; + } + return *out; + } + + void Reset() + { + low = high = band = notch = 0; + } + + void SetSampleRate(T sampleRate) + { + this->sampleRate = sampleRate * OverSampleCount; + UpdateCoefficient(); + } + + void Frequency(T frequency) + { + this->frequency = frequency; + UpdateCoefficient(); + } + + void Resonance(T resonance) + { + this->q = 2 - 2 * resonance; + } + + void Type(int type) + { + switch(type) + { + case LOWPASS: + out = &low; + break; + + case HIGHPASS: + out = &high; + break; + + case BANDPASS: + out = &band; + break; + + case NOTCH: + out = ¬ch; + break; + + default: + out = &low; + break; + } + } + +private: + void UpdateCoefficient() + { + f = 2. * std::sin(PI * frequency / sampleRate); + } +}; +#endif \ No newline at end of file diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 5c18b82..507ba39 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -237,6 +237,21 @@ CUIMenu::TMenuItem CUIMenu::s_FXReverb[] = {0} }; +CUIMenu::TMenuItem CUIMenu::s_FXMVerb[] = +{ + {"Bypass", EditTGFXParameter, 0, AudioEffectMVerb::Param::BYPASS}, + {"Damp Fq", EditTGFXParameter, 0, AudioEffectMVerb::Param::DAMPINGFREQ}, + {"Density", EditTGFXParameter, 0, AudioEffectMVerb::Param::DENSITY}, + {"Band Fq", EditTGFXParameter, 0, AudioEffectMVerb::Param::BANDWIDTHFREQ}, + {"Decay", EditTGFXParameter, 0, AudioEffectMVerb::Param::DECAY}, + {"Predelay", EditTGFXParameter, 0, AudioEffectMVerb::Param::PREDELAY}, + {"Size", EditTGFXParameter, 0, AudioEffectMVerb::Param::SIZE}, + {"Gain", EditTGFXParameter, 0, AudioEffectMVerb::Param::GAIN}, + {"Mix", EditTGFXParameter, 0, AudioEffectMVerb::Param::MIX}, + {"Early Mix", EditTGFXParameter, 0, AudioEffectMVerb::Param::EARLYMIX}, + {0} +}; + const CUIMenu::TMenuItem CUIMenu::s_MidiFX[] = { {"Type:", EditTGParameter2, 0, CMiniDexed::TGParameterMidiFXType}, @@ -336,7 +351,7 @@ const CUIMenu::TMenuItem CUIMenu::s_SaveMenu[] = const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknown] = { {0, 1, 1, ToOnOff}, // ParameterCompressorEnable - {0, 7, 1, ToFXType}, // ParameterSendFXType + {0, 8, 1, ToFXType}, // ParameterSendFXType {0, 100, 1}, // ParameterSendFXLevel {0, 1, 1, ToOnOff}, // ParameterReverbEnable {0, 99, 1}, // ParameterReverbSize @@ -359,7 +374,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, 7, 1, ToFXType}, // TGParameterInsertFXType + {0, 8, 1, ToFXType}, // TGParameterInsertFXType {0, 1, 1, ToMidiFXType}, // TGParameterMidiFXType {-99, 99, 1}, // TGParameterMasterTune {0, 99, 1}, // TGParameterCutoff @@ -466,6 +481,21 @@ const CUIMenu::TParameter CUIMenu::s_TGFXReverbParam[AudioEffectPlateReverb::Par {0, 99, 1}, // LEVEL }; +// must match AudioEffectPlateReverb::Param +const CUIMenu::TParameter CUIMenu::s_TGFXMVerbParam[AudioEffectMVerb::Param::UNKNOWN] = +{ + {0, 1, 1, ToOnOff}, // BYPASS + {0, 100, 1}, // DAMPINGFREQ + {0, 100, 1}, // DENSITY + {0, 100, 1}, // BANDWIDTHFREQ + {0, 100, 1}, // DECAY + {0, 100, 1}, // PREDELAY + {0, 100, 1}, // SIZE + {0, 100, 1}, // GAIN + {0, 100, 1, ToMix}, // MIX + {0, 100, 1}, // EARLYMIX +}; + // must match MidiArp::Param const CUIMenu::TParameter CUIMenu::s_TGMidiFXArpParam[MidiArp::Param::UNKNOWN] = { @@ -2905,6 +2935,9 @@ CUIMenu::TMenuItem* CUIMenu::getFXMenuItem(unsigned type) case EFFECT_REVERB: menu = s_FXReverb; break; + case EFFECT_MVERB: + menu = s_FXMVerb; + break; case EFFECT_NONE: default: menu = s_FXNone; @@ -2963,6 +2996,9 @@ CUIMenu::TParameter CUIMenu::getFXParameter(unsigned type, unsigned nParam) case EFFECT_REVERB: pParam = s_TGFXReverbParam[nParam]; break; + case EFFECT_MVERB: + pParam = s_TGFXMVerbParam[nParam]; + break; default: break; } diff --git a/src/uimenu.h b/src/uimenu.h index 2799697..0ca3b6d 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -182,6 +182,7 @@ private: static TMenuItem s_FXBigMuff[]; static TMenuItem s_FXTalReverb3[]; static TMenuItem s_FXReverb[]; + static TMenuItem s_FXMVerb[]; static TMenuItem s_MidiFXNone[]; static TMenuItem s_MidiFXArp[]; @@ -205,6 +206,7 @@ private: static const TParameter s_TGFXBigMuffParam[]; static const TParameter s_TGFXTalReverb3Param[]; static const TParameter s_TGFXReverbParam[]; + static const TParameter s_TGFXMVerbParam[]; static const TParameter s_TGMidiFXArpParam[]; static const TParameter s_VoiceParameter[]; static const TParameter s_OPParameter[]; From d6fbbb508dca345df91fd1cdb465150c58747952 Mon Sep 17 00:00:00 2001 From: Javier Nonis Date: Mon, 22 Jul 2024 18:18:08 -0300 Subject: [PATCH 11/11] Added 3 Band EQ --- src/effect_3bandeq.h | 176 +++++++++++++++++++++++++++++++++++++++++++ src/effect_base.h | 1 + src/effects.h | 4 + src/uimenu.cpp | 36 ++++++++- src/uimenu.h | 2 + 5 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/effect_3bandeq.h diff --git a/src/effect_3bandeq.h b/src/effect_3bandeq.h new file mode 100644 index 0000000..2d604b3 --- /dev/null +++ b/src/effect_3bandeq.h @@ -0,0 +1,176 @@ +/* + * DISTHRO 3 Band EQ + * Ported from https://github.com/DISTRHO/Mini-Series/blob/master/plugins/3BandEQ + * + * Javier Nonis (https://github.com/jnonis) - 2024 + */ +#ifndef _EFFECT_3BANDEQ_H +#define _EFFECT_3BANDEQ_H + +#include +#include "effect_base.h" + +class AudioEffect3BandEQ : public AudioEffect +{ +public: + static constexpr float kAMP_DB = 8.656170245f; + static constexpr float kDC_ADD = 1e-30f; + static constexpr float kPI = 3.141592654f; + + enum Param + { + BYPASS, + EQ_LOW, + EQ_MID, + EQ_HIGH, + MASTER, + LOW_MID_FQ, + MID_HIGH_FQ, + UNKNOWN + }; + + AudioEffect3BandEQ(float32_t samplerate) : AudioEffect(samplerate) + { + // Default values + fLow = 0.0f; + fMid = 0.0f; + fHigh = 0.0f; + fMaster = 0.0f; + fLowMidFreq = 440.0f; + fMidHighFreq = 2000.0f; + + // Internal stuff + lowVol = midVol = highVol = outVol = 1.0f; + freqLP = 200.0f; + freqHP = 2000.0f; + + // reset filter values + xLP = std::exp(-2.0f * kPI * freqLP / samplerate); + + a0LP = 1.0f - xLP; + b1LP = -xLP; + + xHP = std::exp(-2.0f * kPI * freqHP / samplerate); + a0HP = 1.0f - xHP; + b1HP = -xHP; + + out1LP = out2LP = out1HP = out2HP = 0.0f; + tmp1LP = tmp2LP = tmp1HP = tmp2HP = 0.0f; + } + + virtual ~AudioEffect3BandEQ() + { + } + + virtual unsigned getId() + { + return EFFECT_3BANDEQ; + } + + virtual void setParameter(unsigned param, unsigned value) + { + switch (param) + { + case AudioEffect3BandEQ::Param::BYPASS: + this->setBypass(value == 1); + break; + case AudioEffect3BandEQ::Param::EQ_LOW: + fLow = (value / 100.0f) * 48.0f - 24.0f; + lowVol = std::exp( (fLow/48.0f) * 48.0f / kAMP_DB); + break; + case AudioEffect3BandEQ::Param::EQ_MID: + fMid = (value / 100.0f) * 48.0f - 24.0f; + midVol = std::exp( (fMid/48.0f) * 48.0f / kAMP_DB); + break; + case AudioEffect3BandEQ::Param::EQ_HIGH: + fHigh = (value / 100.0f) * 48.0f - 24.0f; + highVol = std::exp( (fHigh/48.0f) * 48.0f / kAMP_DB); + break; + case AudioEffect3BandEQ::Param::MASTER: + fMaster = (value / 100.0f) * 48.0f - 24.0f; + outVol = std::exp( (fMaster/48.0f) * 48.0f / kAMP_DB); + break; + case AudioEffect3BandEQ::Param::LOW_MID_FQ: + fLowMidFreq = std::min((float) value, fMidHighFreq); + freqLP = fLowMidFreq; + xLP = std::exp(-2.0f * kPI * freqLP / (float)samplerate); + a0LP = 1.0f - xLP; + b1LP = -xLP; + break; + case AudioEffect3BandEQ::Param::MID_HIGH_FQ: + fMidHighFreq = std::max((float) value, fLowMidFreq); + freqHP = fMidHighFreq; + xHP = std::exp(-2.0f * kPI * freqHP / (float)samplerate); + a0HP = 1.0f - xHP; + b1HP = -xHP; + break; + default: + break; + } + } + + virtual unsigned getParameter(unsigned param) + { + switch (param) + { + case AudioEffect3BandEQ::Param::BYPASS: + return this->getBypass() ? 1 : 0; + case AudioEffect3BandEQ::Param::EQ_LOW: + return roundf(((fLow + 24.0f) / 48.0f) * 100.0f); + case AudioEffect3BandEQ::Param::EQ_MID: + return roundf(((fMid + 24.0f) / 48.0f) * 100.0f); + case AudioEffect3BandEQ::Param::EQ_HIGH: + return roundf(((fHigh + 24.0f) / 48.0f) * 100.0f); + case AudioEffect3BandEQ::Param::MASTER: + return roundf(((fMaster + 24.0f) / 48.0f) * 100.0f); + case AudioEffect3BandEQ::Param::LOW_MID_FQ: + return fLowMidFreq; + case AudioEffect3BandEQ::Param::MID_HIGH_FQ: + return fMidHighFreq; + default: + return 0; + } + } + +protected: + virtual size_t getParametersSize() + { + return AudioEffect3BandEQ::Param::UNKNOWN; + } + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) + { + const float* in1 = inblockL; + const float* in2 = inblockR; + float* out1 = outblockL; + float* out2 = outblockR; + + for (uint32_t i=0; i < len; ++i) + { + tmp1LP = a0LP * in1[i] - b1LP * tmp1LP + kDC_ADD; + tmp2LP = a0LP * in2[i] - b1LP * tmp2LP + kDC_ADD; + out1LP = tmp1LP - kDC_ADD; + out2LP = tmp2LP - kDC_ADD; + + tmp1HP = a0HP * in1[i] - b1HP * tmp1HP + kDC_ADD; + tmp2HP = a0HP * in2[i] - b1HP * tmp2HP + kDC_ADD; + out1HP = in1[i] - tmp1HP - kDC_ADD; + out2HP = in2[i] - tmp2HP - kDC_ADD; + + out1[i] = (out1LP*lowVol + (in1[i] - out1LP - out1HP)*midVol + out1HP*highVol) * outVol; + out2[i] = (out2LP*lowVol + (in2[i] - out2LP - out2HP)*midVol + out2HP*highVol) * outVol; + } + } +private: + float fLow, fMid, fHigh, fMaster, fLowMidFreq, fMidHighFreq; + + float lowVol, midVol, highVol, outVol; + float freqLP, freqHP; + + float xLP, a0LP, b1LP; + float xHP, a0HP, b1HP; + + float out1LP, out2LP, out1HP, out2HP; + float tmp1LP, tmp2LP, tmp1HP, tmp2HP; +}; + +#endif // _EFFECT_3BANDEQ_H diff --git a/src/effect_base.h b/src/effect_base.h index 01d559c..303f37c 100644 --- a/src/effect_base.h +++ b/src/effect_base.h @@ -17,6 +17,7 @@ #define EFFECT_TALREVERB3 6 #define EFFECT_REVERB 7 #define EFFECT_MVERB 8 +#define EFFECT_3BANDEQ 9 class AudioEffect { diff --git a/src/effects.h b/src/effects.h index 2053774..ddb90bf 100644 --- a/src/effects.h +++ b/src/effects.h @@ -15,6 +15,7 @@ #include "effect_talreverb3.h" #include "effect_platervbstereo.h" #include "effect_mverb.h" +#include "effect_3bandeq.h" inline AudioEffect* newAudioEffect(unsigned type, float32_t samplerate) { @@ -36,6 +37,8 @@ inline AudioEffect* newAudioEffect(unsigned type, float32_t samplerate) return new AudioEffectPlateReverb(samplerate); case EFFECT_MVERB: return new AudioEffectMVerb(samplerate); + case EFFECT_3BANDEQ: + return new AudioEffect3BandEQ(samplerate); case EFFECT_NONE: default: return new AudioEffectNone(samplerate); @@ -54,6 +57,7 @@ inline std::string getFXTypeName(int nValue) case EFFECT_TALREVERB3: return "TalRvrb3"; case EFFECT_REVERB: return "Reverb"; case EFFECT_MVERB: return "MVerb"; + case EFFECT_3BANDEQ: return "3Band EQ"; case EFFECT_NONE: default: return "None"; } diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 507ba39..d54dd93 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -252,6 +252,18 @@ CUIMenu::TMenuItem CUIMenu::s_FXMVerb[] = {0} }; +CUIMenu::TMenuItem CUIMenu::s_FX3BandEQ[] = +{ + {"Bypass", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::BYPASS}, + {"Low", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::EQ_LOW}, + {"Mid", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::EQ_MID}, + {"High", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::EQ_HIGH}, + {"Master", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::MASTER}, + {"Low Mid FQ", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::LOW_MID_FQ}, + {"Mid High FQ", EditTGFXParameter, 0, AudioEffect3BandEQ::Param::MID_HIGH_FQ}, + {0} +}; + const CUIMenu::TMenuItem CUIMenu::s_MidiFX[] = { {"Type:", EditTGParameter2, 0, CMiniDexed::TGParameterMidiFXType}, @@ -351,7 +363,7 @@ const CUIMenu::TMenuItem CUIMenu::s_SaveMenu[] = const CUIMenu::TParameter CUIMenu::s_GlobalParameter[CMiniDexed::ParameterUnknown] = { {0, 1, 1, ToOnOff}, // ParameterCompressorEnable - {0, 8, 1, ToFXType}, // ParameterSendFXType + {0, 9, 1, ToFXType}, // ParameterSendFXType {0, 100, 1}, // ParameterSendFXLevel {0, 1, 1, ToOnOff}, // ParameterReverbEnable {0, 99, 1}, // ParameterReverbSize @@ -374,7 +386,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, 8, 1, ToFXType}, // TGParameterInsertFXType + {0, 9, 1, ToFXType}, // TGParameterInsertFXType {0, 1, 1, ToMidiFXType}, // TGParameterMidiFXType {-99, 99, 1}, // TGParameterMasterTune {0, 99, 1}, // TGParameterCutoff @@ -481,7 +493,7 @@ const CUIMenu::TParameter CUIMenu::s_TGFXReverbParam[AudioEffectPlateReverb::Par {0, 99, 1}, // LEVEL }; -// must match AudioEffectPlateReverb::Param +// must match AudioEffectMVerb::Param const CUIMenu::TParameter CUIMenu::s_TGFXMVerbParam[AudioEffectMVerb::Param::UNKNOWN] = { {0, 1, 1, ToOnOff}, // BYPASS @@ -496,6 +508,18 @@ const CUIMenu::TParameter CUIMenu::s_TGFXMVerbParam[AudioEffectMVerb::Param::UNK {0, 100, 1}, // EARLYMIX }; +// must match AudioEffect3BandEQ::Param +const CUIMenu::TParameter CUIMenu::s_TGFX3BandEQParam[AudioEffect3BandEQ::Param::UNKNOWN] = +{ + {0, 1, 1, ToOnOff}, // BYPASS + {0, 100, 1}, // LOW + {0, 100, 1}, // MID + {0, 100, 1}, // HIGH + {0, 100, 1}, // MASTER + {0, 1000, 10}, // LOW_MID_FQ + {1000, 20000, 10}, // MID_HIGH_FQ +}; + // must match MidiArp::Param const CUIMenu::TParameter CUIMenu::s_TGMidiFXArpParam[MidiArp::Param::UNKNOWN] = { @@ -2938,6 +2962,9 @@ CUIMenu::TMenuItem* CUIMenu::getFXMenuItem(unsigned type) case EFFECT_MVERB: menu = s_FXMVerb; break; + case EFFECT_3BANDEQ: + menu = s_FX3BandEQ; + break; case EFFECT_NONE: default: menu = s_FXNone; @@ -2999,6 +3026,9 @@ CUIMenu::TParameter CUIMenu::getFXParameter(unsigned type, unsigned nParam) case EFFECT_MVERB: pParam = s_TGFXMVerbParam[nParam]; break; + case EFFECT_3BANDEQ: + pParam = s_TGFX3BandEQParam[nParam]; + break; default: break; } diff --git a/src/uimenu.h b/src/uimenu.h index 0ca3b6d..7b9490a 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -183,6 +183,7 @@ private: static TMenuItem s_FXTalReverb3[]; static TMenuItem s_FXReverb[]; static TMenuItem s_FXMVerb[]; + static TMenuItem s_FX3BandEQ[]; static TMenuItem s_MidiFXNone[]; static TMenuItem s_MidiFXArp[]; @@ -207,6 +208,7 @@ private: static const TParameter s_TGFXTalReverb3Param[]; static const TParameter s_TGFXReverbParam[]; static const TParameter s_TGFXMVerbParam[]; + static const TParameter s_TGFX3BandEQParam[]; static const TParameter s_TGMidiFXArpParam[]; static const TParameter s_VoiceParameter[]; static const TParameter s_OPParameter[];