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_