diff --git a/Source/CustomSynth.cpp b/Source/CustomSynth.cpp index ed1bf4d..ab295b8 100644 --- a/Source/CustomSynth.cpp +++ b/Source/CustomSynth.cpp @@ -10,32 +10,107 @@ #include "CustomSynth.h" #include "BaseVoice.h" +#include "TonalVoice.h" #include "PluginProcessor.h" CustomSynth::CustomSynth(Magical8bitPlug2AudioProcessor& p) : processor(p) {} -void CustomSynth::noteOn(int midiChannel, int midiNoteNumber, float velocity) { - // Poly +TonalVoice* CustomSynth::getVoiceIfShouldProcessInMonoMode() { if (!processor.settingRefs.isMonophonic()) { + return nullptr; + } + if (processor.settingRefs.monophonicBehavior() == kNonLegato) { + return nullptr; + } + + // Try casting into TonalVoice* and return it if success(otherwise returns nullptr) + return dynamic_cast(voices.getFirst()); +} + +void CustomSynth::noteOn(int midiChannel, int midiNoteNumber, float velocity) { + TonalVoice *voice = getVoiceIfShouldProcessInMonoMode(); + + if (voice == nullptr) { Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity); return; } // Mono - auto voice = voices.getFirst(); - if (voice == nullptr) { - return; - } + // Start thread guard + const ScopedLock sl (lock); + if (voice->isKeyDown()) { - ((BaseVoice*)voice)->changeNote(midiNoteNumber, velocity); + // Already key on + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + voice->addLegatoNote(midiNoteNumber, velocity); + break; + case kArpeggioUp: + voice->addArpeggioNoteAscending(midiNoteNumber); + break; + case kArpeggioDown: + voice->addArpeggioNoteDescending(midiNoteNumber); + break; + default: + // no-op + break; + } } else { - Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity); + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + // just start + Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity); + voice->setLegatoMode(*(processor.settingRefs.portamentoTime)); + break; + case kArpeggioUp: + case kArpeggioDown: + // calc arpeggio interval + // set arpeggio mode with this note number and arp interval + break; + default: + // no-op + break; + } } } void CustomSynth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) { - Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff); + TonalVoice *voice = getVoiceIfShouldProcessInMonoMode(); + + if (voice == nullptr) { + Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff); + return; + } + + // mono + + // Start thread guard + const ScopedLock sl (lock); + + if (!voice->isKeyDown()) { + // key is already off + return; + } + + switch (processor.settingRefs.monophonicBehavior()) { + case kLegato: + { + int numBuffer = voice->removeLegatoNote(midiNoteNumber); + if (numBuffer < 1) { + Synthesiser::noteOff(midiChannel, voice->getCurrentlyPlayingNote(), velocity, allowTailOff); + } + } + break; + case kArpeggioUp: + case kArpeggioDown: + // remove arpeggio note and get # of remaining arpeggio notes + // if zero + // all notes off + break; + default: + break; + } } void CustomSynth::allNotesOff (const int midiChannel, const bool allowTailOff) { diff --git a/Source/CustomSynth.h b/Source/CustomSynth.h index a839784..51dfa18 100644 --- a/Source/CustomSynth.h +++ b/Source/CustomSynth.h @@ -12,6 +12,7 @@ #include "../JuceLibraryCode/JuceHeader.h" class Magical8bitPlug2AudioProcessor; +class TonalVoice; class CustomSynth : public Synthesiser { public: @@ -22,5 +23,6 @@ public: void allNotesOff (const int midiChannel, const bool allowTailOff) override; private: + TonalVoice* getVoiceIfShouldProcessInMonoMode(); Magical8bitPlug2AudioProcessor& processor; }; diff --git a/Source/TonalVoice.cpp b/Source/TonalVoice.cpp index f0471bc..62cd1fa 100644 --- a/Source/TonalVoice.cpp +++ b/Source/TonalVoice.cpp @@ -16,7 +16,9 @@ // The base for pulse/triangle (Abstract) // //--------------------------------------------- -TonalVoice::TonalVoice (SettingRefs* sRefs) : BaseVoice (sRefs) {} +TonalVoice::TonalVoice (SettingRefs* sRefs) : BaseVoice (sRefs) { + // testArpeggioNotes(); +} void TonalVoice::startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int currentPitchBendPosition) { @@ -30,6 +32,12 @@ void TonalVoice::startNote (int midiNoteNumber, float velocity, SynthesiserSound float time = * (settingRefs->sweepTime); currentAutoBendAmount = iniPitch; autoBendDelta = -1.0 * iniPitch / (time * getSampleRate()); + + portamentoTime = 0; + currentArpeggioFrame = 0; + arpeggioFrameTimer = 0; + arpeggioFrameLength = 0; + currentNumNoteBuffer = 0; } void TonalVoice::advanceControlFrame() @@ -90,6 +98,127 @@ void TonalVoice::controllerMoved (int type, int amount) } } + +void TonalVoice::setLegatoMode(double time) { + portamentoTime = time; +// currentNumNoteBuffer = 1; +// noteBuffer[0] = noteNumber; +} + +void TonalVoice::addLegatoNote (int midiNoteNumber, float velocity) { + if (currentNumNoteBuffer >= 10) { + return; + } + + // Push to last +// noteBuffer[currentNumNoteBuffer] = midiNoteNumber; +// currentNumNoteBuffer++; + + int previousNoteNo = noteNumber; + BaseVoice::changeNote(midiNoteNumber, velocity); + + // Portamento implementation is just applying auto bend + if (portamentoTime > 0) { + currentAutoBendAmount = (double)(previousNoteNo - midiNoteNumber); + autoBendDelta = -1.0 * currentAutoBendAmount / (portamentoTime * getSampleRate()); + } +} + +int TonalVoice::removeLegatoNote(int midiNoteNumber) { +// if (currentNumNoteBuffer==0) { +// return 0; +// } +// +// int i; +// for (i=0; i= 10) { + return; + } + int i; + for (i = 0; i midiNoteNumber) break; + } + + pushNoteBuffer(i, midiNoteNumber); + + currentNumNoteBuffer++; +} + +void TonalVoice::addArpeggioNoteDescending(int midiNoteNumber) +{ + if (currentNumNoteBuffer >= 10) { + return; + } + int i; + for (i = 0; iindex; i--) { + noteBuffer[i] = noteBuffer[i-1]; + } + noteBuffer[index] = value; +} + +void TonalVoice::shiftNoteBuffer(int index) { + for (int i=index; i<9; i++) { + noteBuffer[i] = noteBuffer[i+1]; + } +} + double TonalVoice::noteNoToHeltzDouble (double noteNoInDouble, const double frequencyOfA) { return frequencyOfA * std::pow (2.0, (noteNoInDouble - 69) / 12.0); diff --git a/Source/TonalVoice.h b/Source/TonalVoice.h index 5146fb7..2acb0c0 100644 --- a/Source/TonalVoice.h +++ b/Source/TonalVoice.h @@ -13,7 +13,6 @@ struct TonalVoice : public BaseVoice // The base for Pulse and Triangle { - TonalVoice (SettingRefs* sRefs); float voltageForAngle (double angle) override = 0; @@ -35,6 +34,18 @@ struct TonalVoice : public BaseVoice // The base for Pulse and Triangle // Custom Pitch/Note states int currentPitchSequenceFrame = 0; + + // Legato/Arpeggio + int noteBuffer[10]; + int currentNumNoteBuffer = 0; + + // Legato + double portamentoTime = 0; + + // Arpeggio + int currentArpeggioFrame = 0; + double arpeggioFrameTimer = 0; + double arpeggioFrameLength = 0; // Unit: seconds. Set non-zero value to enable arpeggio void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int currentPitchWheelPosition) override; @@ -43,7 +54,92 @@ struct TonalVoice : public BaseVoice // The base for Pulse and Triangle void pitchWheelMoved (int) override; void controllerMoved (int, int) override; + void setLegatoMode(double time); + void addLegatoNote (int midiNoteNumber, float velocity); + int removeLegatoNote(int midiNoteNumber); + + void setArpeggioMode(double interval); + void addArpeggioNoteAscending(int midiNoteNumber); + void addArpeggioNoteDescending(int midiNoteNumber); + int removeArpeggioNote(int midiNoteNumber); + + void pushNoteBuffer(int index, int value); + void shiftNoteBuffer(int index); + double noteNoToHeltzDouble (double noteNoInDouble, const double frequencyOfA = 440); void onFrameAdvanced() override; + + bool isArpeggioEnabled() { + return arpeggioFrameLength > 0; + } + + /* + void testArpeggioNotes() { + startNote(100, 1, NULL, 0); + + setArpeggioMode(1.0); + jassert(isArpeggioEnabled()); + jassert(arpeggioNotes[0] == 100); + jassert(currentNumArpeggioNotes == 1); + + addArpeggioNoteAscending(90); + jassert(arpeggioNotes[0] == 90); + jassert(arpeggioNotes[1] == 100); + jassert(currentNumArpeggioNotes == 2); + + addArpeggioNoteAscending(95); + jassert(arpeggioNotes[0] == 90); + jassert(arpeggioNotes[1] == 95); + jassert(arpeggioNotes[2] == 100); + jassert(currentNumArpeggioNotes == 3); + + jassert(removeArpeggioNote(92) == 3); + jassert(arpeggioNotes[0] == 90); + jassert(arpeggioNotes[1] == 95); + jassert(arpeggioNotes[2] == 100); + + jassert(removeArpeggioNote(100) == 2); + jassert(arpeggioNotes[0] == 90); + jassert(arpeggioNotes[1] == 95); + + jassert(removeArpeggioNote(90) == 1); + jassert(arpeggioNotes[0] == 95); + + jassert(removeArpeggioNote(95) == 0); + + + startNote(100, 1, NULL, 0); + + setArpeggioMode(1.0); + jassert(isArpeggioEnabled()); + jassert(arpeggioNotes[0] == 100); + jassert(currentNumArpeggioNotes == 1); + + addArpeggioNoteDescending(90); + jassert(arpeggioNotes[0] == 100); + jassert(arpeggioNotes[1] == 90); + jassert(currentNumArpeggioNotes == 2); + + addArpeggioNoteDescending(95); + jassert(arpeggioNotes[0] == 100); + jassert(arpeggioNotes[1] == 95); + jassert(arpeggioNotes[2] == 90); + jassert(currentNumArpeggioNotes == 3); + + jassert(removeArpeggioNote(92) == 3); + jassert(arpeggioNotes[0] == 100); + jassert(arpeggioNotes[1] == 95); + jassert(arpeggioNotes[2] == 90); + + jassert(removeArpeggioNote(100) == 2); + jassert(arpeggioNotes[0] == 95); + jassert(arpeggioNotes[1] == 90); + + jassert(removeArpeggioNote(90) == 1); + jassert(arpeggioNotes[0] == 95); + + jassert(removeArpeggioNote(95) == 0); + } + */ };