diff --git a/Source/ButtonLED.cpp b/Source/ButtonLED.cpp index cb5107e..25ecbeb 100644 --- a/Source/ButtonLED.cpp +++ b/Source/ButtonLED.cpp @@ -197,7 +197,7 @@ void ButtonLED::paintButton (Graphics& g, bool /*isMouseOverButton*/, bool /*isB } } -void ButtonLED::flash(uint32_t duration_ms) +void ButtonLED::flash(uint32_t duration_ms, uint32_t brighness) { setToggleState(true, juce::NotificationType::dontSendNotification); startTimer((uint32_t)duration_ms); diff --git a/Source/ButtonLED.h b/Source/ButtonLED.h index 6f9ebf3..6ee847b 100644 --- a/Source/ButtonLED.h +++ b/Source/ButtonLED.h @@ -39,7 +39,7 @@ class ButtonLED : public Button, public Timer { public: - //============================================================================== + //============================================================================== ButtonLED (const String& name); ~ButtonLED(); @@ -49,7 +49,7 @@ public: void clicked(const ModifierKeys &modifiers); void mouseWheelMove(const MouseEvent &event, const MouseWheelDetails &wheel); void timerCallback(); - void flash(uint32_t duration_ms); + void flash(uint32_t duration_ms, uint32_t brightness); void setTriggeredOnMouseDown (bool triggeredOnMouseDown); bool getClickingTogglesState (void); void setClickingTogglesState (bool clickingTogglesState); diff --git a/Source/JaySynth.cpp b/Source/JaySynth.cpp index 8f21bae..92c5fe7 100644 --- a/Source/JaySynth.cpp +++ b/Source/JaySynth.cpp @@ -123,11 +123,10 @@ JaySynth::JaySynth (int num_voices, String pathToWaves) m_ppAudioThread[i]->startThread (12); } - m_clockdiv = 0; + m_clock_cnt_bar = 0; m_lastTime_ms = 0; - m_lastTimeError_ms = 0; m_estimatedTime_ms = 0; - + m_isMidiClockStarted = false; } JaySynth::~JaySynth() @@ -1022,26 +1021,39 @@ void JaySynth::renderNextBlock (AudioSampleBuffer& outputBuffer, } } +void JaySynth::updateMidiTiming(const MidiMessage& m) +{ + // Quarter clock generation + int nom; + int denom; + m.getTimeSignatureInfo(nom, denom); + + m_numMidiClocksPerBeat = m_numMidiclocksPerFourQuarterBeats/denom; + m_numMidiClocksPerBar = nom*m_numMidiClocksPerBeat; +} + void JaySynth::handleMidiEvent (const MidiMessage& m) { + updateMidiTiming(m); + if (m.isNoteOn()) { noteOn (m.getChannel(), m.getNoteNumber(), m.getFloatVelocity()); } - else if (m.isNoteOff()) + if (m.isNoteOff()) { noteOff (m.getChannel(), m.getNoteNumber(), true); } - else if (m.isAllNotesOff() || m.isAllSoundOff()) + if (m.isAllNotesOff() || m.isAllSoundOff()) { allNotesOff (m.getChannel(), true); } - else if (m.isPitchWheel()) + if (m.isPitchWheel()) { const int channel = m.getChannel(); const int wheelPos = m.getPitchWheelValue(); @@ -1049,80 +1061,121 @@ void JaySynth::handleMidiEvent (const MidiMessage& m) handlePitchWheel (channel, wheelPos); } - else if (m.isController()) + if (m.isController()) { handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); } - else if (m.isMidiMachineControlMessage()) + if (m.isMidiMachineControlMessage()) { SynthDebug("MidiMachineControlMessage(%08X)\n", m.getMidiMachineControlCommand()); } - else if (m.isTempoMetaEvent()) + if (m.isTempoMetaEvent()) { SynthDebug("TempoMetaEvent\n"); } - else if (m.isMidiStart()) + if (m.isTimeSignatureMetaEvent()) + { + int nom; + int denom; + + m.getTimeSignatureInfo(nom, denom); + + SynthDebug("isTimeSignatureMetaEvent\n"); + SynthDebug("Time signature %d/%d\n", nom, denom); + + } + if (m.isMidiStart()) { SynthDebug("MidiStart\n"); + m_isMidiClockStarted = true; } - else if (m.isMidiContinue()) + if (m.isMidiContinue()) { SynthDebug("MidiContinue\n"); + m_isMidiClockStarted = true; } - else if (m.isMidiStop()) + if (m.isMidiStop()) { SynthDebug("MidiStop\n"); + m_isMidiClockStarted = false; } - else if (m.isMidiClock()) - { - const double alpha_1 = 1.0; - const double alpha_2 = 1.0/48; - const double currTime_ms = juce::Time::getMillisecondCounterHiRes(); - - double deltaTime_ms = currTime_ms - m_lastTime_ms; - - if (m_lastTime_ms) - { - double timeError_ms = deltaTime_ms - m_estimatedTime_ms; - m_lastTimeError_ms = (1-alpha_1)*m_lastTimeError_ms + alpha_1*timeError_ms; - - if (std::abs(timeError_ms) > 5.0) - { - m_estimatedTime_ms = deltaTime_ms; - } - else - { - m_estimatedTime_ms = m_estimatedTime_ms + alpha_2*(m_lastTimeError_ms); - } - } - m_lastTime_ms = currTime_ms; - if (!m_clockdiv) - { - uint32_t dT = (uint32_t)(24*m_estimatedTime_ms + 0.5); - listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICLOCK, &dT); - m_clockdiv = 24; - } - m_clockdiv--; - } - else if (m.isSongPositionPointer()) + if (m.isSongPositionPointer()) { SynthDebug("SongPositionPointer\n"); - m_clockdiv = (6 * m.getSongPositionPointerMidiBeat()) % 24; + m_clock_cnt_bar = (6 * m.getSongPositionPointerMidiBeat()) % m_numMidiClocksPerBar; + SynthDebug("getSongPositionPointerMidiBeat(): %u -> %u clocks\n", m.getSongPositionPointerMidiBeat(), m_clock_cnt_bar); + m_lastTime_ms = 0; } - else if (m.isQuarterFrame()) + if (m.isQuarterFrame()) { SynthDebug("QuarterFrame\n"); } - else if (m.isFullFrame()) + if (m.isFullFrame()) { SynthDebug("FullFrame\n"); } - else + if (m.isTempoMetaEvent()) { - SynthDebug("Unknown\n"); + SynthDebug("TempoMetaEvent = %f\n", m.getTempoSecondsPerQuarterNote()); + SynthDebug("TimeStamp = %f\n", m.getTimeStamp()); + } + if (m.isMidiClock()) + { + const double alpha_1 = 2.0/m_numMidiclocksPerQuarterNote; + const double currTime_ms = juce::Time::getMillisecondCounterHiRes(); + + // BPM Estimation + double deltaTime_ms = currTime_ms - m_lastTime_ms; + if (m_lastTime_ms) + { + m_estimatedTime_ms = (1.0-alpha_1)*m_estimatedTime_ms + alpha_1*deltaTime_ms; + voiceSetMidiBpm(m_lastTime_ms*m_numMidiclocksPerQuarterNote); + } + m_lastTime_ms = currTime_ms; + + // Quarter clock generation + m_clock_cnt_bar = m_clock_cnt_bar % m_numMidiClocksPerBar; + if (m_clock_cnt_bar % m_numMidiClocksPerBeat == 0) + { + // Send quarter pulse with current BPM estimation + midi_quarter_clock_info_t info; + + info.bpm = (uint32_t)(m_numMidiclocksPerQuarterNote*m_estimatedTime_ms + 0.5); + info.type = 1; + if (m_clock_cnt_bar == 0) + { + info.type = 2; + } + if (m_isMidiClockStarted) + { + listeners.call (&JaySynthListener::synthChanged, SYNTH_CHANGED_MIDICLOCK, &info); + } + voiceSetMidiBeat(m_clock_cnt_bar); + } + m_clock_cnt_bar++; + } +} + +void JaySynth::voiceSetMidiBpm(synth_float_t bpm) +{ + for (int i=0; i < max_num_voices; i++) + { + JaySynthVoice* const voice = voices.getUnchecked (i); + voice->setMidiBpm(bpm); + } +} + +void JaySynth::voiceSetMidiBeat(int beatcount) +{ + for (int i=0; i < max_num_voices; i++) + { + JaySynthVoice* const voice = voices.getUnchecked (i); + voice->setMidiBeat(beatcount); + } + } //============================================================================== diff --git a/Source/JaySynth.h b/Source/JaySynth.h index dc1d68e..73de305 100644 --- a/Source/JaySynth.h +++ b/Source/JaySynth.h @@ -87,6 +87,8 @@ public: const float velocity, bool doTrigger); void Voicestop (JaySynthVoice* voice, const bool allowTailOff); + void voiceSetMidiBpm(synth_float_t bpm); + void voiceSetMidiBeat(int beatcount); int getNumVoicesPlaying (SynthesiserSound* soundToPlay); void clearKeysPressed(void); @@ -175,6 +177,12 @@ public: } midi_note_info_t; + typedef struct _smidi_quarter_clock_info_t + { + uint32_t type; + uint32_t bpm; + } midi_quarter_clock_info_t; + midiCC_info_t* getLastMidiCC_infos(void) { return last_midiCC_info; @@ -246,11 +254,15 @@ private: JaySynthThread **m_ppAudioThread; WaitableEvent **m_ppEventAudioThreadRdy; JaySynthMonophonicMGR m_monoMGR; - uint32_t m_clockdiv; + const int m_numMidiclocksPerQuarterNote = 24; + const int m_numMidiclocksPerFourQuarterBeats = 4*m_numMidiclocksPerQuarterNote; + int m_clock_cnt_bar; + int m_numMidiClocksPerBeat; + int m_numMidiClocksPerBar; double m_lastTime_ms; - double m_lastTimeError_ms; double m_estimatedTime_ms; - + bool m_isMidiClockStarted; + void updateMidiTiming(const MidiMessage& m); }; diff --git a/Source/JaySynthAudioProcessorEditor.cpp b/Source/JaySynthAudioProcessorEditor.cpp index b26d1ab..5a1a897 100644 --- a/Source/JaySynthAudioProcessorEditor.cpp +++ b/Source/JaySynthAudioProcessorEditor.cpp @@ -5111,9 +5111,9 @@ void JaySynthAudioProcessorEditor::actionListenerCallback (const String &message case JaySynthActionListener::JAYSYNTH_CHANGED_MIDICLOCK: { - uint32_t deltaTime_ms; - deltaTime_ms = spa.getValue("value", "-1").getIntValue(); - m_btn_midiClockLed->flash(100); + uint32_t deltaTime_ms = spa.getValue("deltatime", "-1").getIntValue(); + uint32_t brightness = spa.getValue("brightness", "-1").getIntValue(); + m_btn_midiClockLed->flash(brightness*50, brightness); uint32_t bpm = (uint32_t)(60*1000.0/deltaTime_ms + 0.5); m_labelDebug->setText(String("BPM = ") + String(bpm), juce::NotificationType::dontSendNotification); } diff --git a/Source/JaySynthVoice.h b/Source/JaySynthVoice.h index 47ed8d5..81cdf17 100644 --- a/Source/JaySynthVoice.h +++ b/Source/JaySynthVoice.h @@ -62,6 +62,19 @@ public: currentlyPlayingSound = nullptr; } + void setMidiBpm(synth_float_t bpm) + { + LFO_Param2Set(&m_pVoice->lfo[0], LFO_PARAM2_FREQ, 1.0/bpm); + } + + void setMidiBeat(int beatCounter) + { + if (beatCounter == 0) + { + LFO_Reset(&m_pVoice->lfo[0], 0); + } + } + private: friend class JaySynth; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 407ca41..10232e7 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -134,7 +134,8 @@ void JaySynthAudioProcessor::synthChanged(int type, void *pData) { JaySynth::midiCC_info_t *pMidiCC_info; JaySynth::midi_note_info_t *pMidi_note_info; - + JaySynth::midi_quarter_clock_info_t *pMidi_quarter_info; + switch(type) { case JaySynth::SYNTH_CHANGED_PARAM: @@ -167,8 +168,9 @@ void JaySynthAudioProcessor::synthChanged(int type, void *pData) break; case JaySynth::SYNTH_CHANGED_MIDICLOCK: + pMidi_quarter_info = (JaySynth::midi_quarter_clock_info_t*)pData; if (getEditor(this) != NULL) - actionListener.callMidiClock(*((uint32_t*)pData)); + actionListener.callMidiClock(pMidi_quarter_info->bpm, pMidi_quarter_info->type); break; default: diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 629a1c6..df6480a 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -113,12 +113,14 @@ public: sendActionMessage(s); } - void callMidiClock (uint32_t deltaTime_ms) + void callMidiClock (uint32_t deltaTime_ms, uint32_t type) { String s; s = String("type=") + String(JAYSYNTH_CHANGED_MIDICLOCK); s = s + String(" "); - s = s + String("value=") + String(deltaTime_ms); + s = s + String("deltatime=") + String(deltaTime_ms); + s = s + String(" "); + s = s + String("brightness=") + String(type); s = s + String(" "); sendActionMessage(s); }