From 159fa60b084bfe2c142bc73d77dd88081de6c481 Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Sat, 16 Aug 2025 14:16:13 +0200 Subject: [PATCH] fixing MidiClock (Zwischenstand) --- src/plug/JK_MidiClock.cpp | 288 +++++++++++++++++++++----------------- src/plug/JK_MidiClock.h | 41 +++--- 2 files changed, 180 insertions(+), 149 deletions(-) diff --git a/src/plug/JK_MidiClock.cpp b/src/plug/JK_MidiClock.cpp index 12e7df3..6ec0075 100644 --- a/src/plug/JK_MidiClock.cpp +++ b/src/plug/JK_MidiClock.cpp @@ -12,24 +12,30 @@ */ #include "JK_MidiClock.h" +#include "juce_core/maths/juce_MathsFunctions.h" +#include + JK_MidiClock::JK_MidiClock() - :wasPlaying(false), - syncPpqPosition(-999.0), - posChangeThreshold(0.001), - ppqToStartSyncAt(0.0), - followSongPosition(true), - syncFlag(0), - ppqOffset(0) + :m_wasPlaying(false), + m_syncPpqPosition(-999.0), + m_posChangeThreshold(0.001), + m_ppqToStartSyncAt(0.0), + m_followSongPosition(true), + m_syncFlag(0), + m_ppqOffset(0) { //Prepare Midi messages to avoid blocking the caller of generateMidiclock()! - continueMessage = new MidiMessage(MidiMessage::midiContinue()); - stopMessage = new MidiMessage(MidiMessage::midiStop()); - clockMessage = new MidiMessage(MidiMessage::midiClock()); - songPositionMessage = new MidiMessage(MidiMessage::songPositionPointer(0)); + m_continueMessage = new MidiMessage(MidiMessage::midiContinue()); + m_stopMessage = new MidiMessage(MidiMessage::midiStop()); + m_clockMessage = new MidiMessage(MidiMessage::midiClock()); + m_songPositionMessage = new MidiMessage(MidiMessage::songPositionPointer(0)); + m_last_sample_pos_sync = 0; + m_last_sample_pos_sync = 0; + m_syncSamplePos = 0; } //================================================================================================= -void JK_MidiClock::generateMidiclock(AudioPlayHead::CurrentPositionInfo &lastPosInfo, +void JK_MidiClock::generateMidiclock(const AudioPlayHead::CurrentPositionInfo &posInfo, MidiBuffer* midiBuffer, const int bufferSize, const double sampleRate) { //###################################Some explanation about musical tempo ################# @@ -38,137 +44,159 @@ void JK_MidiClock::generateMidiclock(AudioPlayHead::CurrentPositionInfo &lastPos //So 4/4 would be four quarter-notes per Bar, while 4/2 would be four half-notes per Bar, # //4/8 would be four eighth-notes per Bar, and 2/4 would be two quarter-notes per Bar. # //######################################################################################### - - - if (midiBuffer != nullptr) + + if (midiBuffer == nullptr) { - //PPQ value of one sample - const double ppqPerSample = (lastPosInfo.bpm / 60.0) / sampleRate; - - //PPQ offset to compensate Midi interface latency - double hostPpqPosition = lastPosInfo.ppqPosition + ppqOffset * ppqPerSample;; - - if (lastPosInfo.isPlaying || lastPosInfo.isRecording) - { - if (! wasPlaying) - { - //set the point where to start the slave - ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition); - - //Special case: Master is set to always start playback from the previous start position... - if (positionJumped(syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample)) - { - //Cue Midiclock slave to the nearest sixteenth note to new start position - //because the one calculated in stop mode isn't valid anymore. - sendSongPositionPointerMessage(ppqToStartSyncAt, 0, midiBuffer); - } - } - else - { - //Position jump (loop or manually position change while playing) - if (positionJumped(syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample)) - { - //set the point where to start the slave - ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition); - - //User has changed position manually while playing - if (syncFlag == 0) - { - midiBuffer->addEvent(*stopMessage, 0); - sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer); - - syncFlag = startSlave_; - } - else - { - if (followSongPosition) - { - sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer); - - syncFlag = startSlave_; - } - else - syncFlag = 0; - } - } - } + return; + } + + //PPQ value of one sample + const double ppqPerSample = (posInfo.bpm / 60.0) / sampleRate; + + //PPQ offset to compensate Midi interface latency + double hostPpqPosition = posInfo.ppqPosition + m_ppqOffset * ppqPerSample;; + + if (posInfo.isPlaying || posInfo.isRecording) + { + if (! m_wasPlaying) + { + //set the point where to start the slave + m_ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition); - for (int posInBuffer = 0; posInBuffer < bufferSize; ++posInBuffer) - { - syncPpqPosition = hostPpqPosition + (posInBuffer * ppqPerSample); - - const int clockDistanceInSamples = roundToInt((60.0 * sampleRate) / (lastPosInfo.bpm * 24.0)); - const int64 hostSamplePos = roundToInt64((hostPpqPosition * (60.0 / lastPosInfo.bpm)) * sampleRate); - const int64 syncSamplePos = hostSamplePos + posInBuffer; - - //Some hosts like Cubase come up with a wacky ppqPosition - //that could break the timing! Best is to "wait" - //here for the right ppqPosition to jump on. - if (syncPpqPosition >= ppqToStartSyncAt) - { - if ((syncFlag & startSlave_) == startSlave_) - { - midiBuffer->addEvent(*continueMessage, posInBuffer); - - syncFlag &= cycleEnd_; - } - - //Cycle mode on - if (lastPosInfo.isLooping && lastPosInfo.ppqLoopStart != lastPosInfo.ppqLoopEnd) - { - const double ppqToCycleEnd = fabs(lastPosInfo.ppqLoopEnd - syncPpqPosition); - const int64 samplesToCycleEnd = roundToInt64(ppqToCycleEnd * (60.0 / lastPosInfo.bpm) * sampleRate); - - if ((syncFlag & cycleEnd_) == 0) - { - if (samplesToCycleEnd <= clockDistanceInSamples) //For fine tuning tweak here - { - //We have reached the cycle- end position - //and must stop the Midiclock slave here - if (followSongPosition) - midiBuffer->addEvent(*stopMessage, posInBuffer); - - syncFlag |= cycleEnd_; - } - } - } - } - - //For best timing we should never interupt Midiclock messages! - //Seems that some slaves constantly adjusting their internal clock - //to Midiclock even if they are in stop mode. - if (syncSamplePos % clockDistanceInSamples == 0) - midiBuffer->addEvent(*clockMessage, posInBuffer); + //Special case: Master is set to always start playback from the previous start position... + if (positionJumped(m_syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample)) + { + //Cue Midiclock slave to the nearest sixteenth note to new start position + //because the one calculated in stop mode isn't valid anymore. + sendSongPositionPointerMessage(m_ppqToStartSyncAt, 0, midiBuffer); } - - wasPlaying = true; } else - { - //Send positioning message if the user has stopped or if he changed the playhead position - //manually in stop mode! This will also initially cue slave after loading plugin instance. - if (wasPlaying || positionJumped(syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample)) - { - midiBuffer->addEvent(*stopMessage, 0); - sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer); + { + //Position jump (loop or manually position change while playing) + if (positionJumped(m_syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample)) + { + SynthDebug("positionJumped"); + //set the point where to start the slave + m_ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition); + + //User has changed position manually while playing + if (m_syncFlag == 0) + { + SynthDebug("Sync=0"); + midiBuffer->addEvent(*m_stopMessage, 0); + sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer); + + m_syncFlag = startSlave_; + } + else + { + SynthDebug("Sync=1"); + if (m_followSongPosition) + { + sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer); + + m_syncFlag = startSlave_; + } + else + { + m_syncFlag = 0; + } + } + } + } + + const int clockDistanceInSamples = roundToInt((60.0 * sampleRate) / (posInfo.bpm * 24.0)); + const double quant = (60.0 / posInfo.bpm) * sampleRate; + const double hostSamplePos_f = hostPpqPosition * quant; + + double syncSamplePos_f = hostSamplePos_f; + int64 syncSamplePos = roundToInt64(syncSamplePos_f); + double sample_offset = syncSamplePos_f - m_syncSamplePos; + + for (int posInBuffer = 0; posInBuffer < bufferSize; ++posInBuffer) + { + m_syncPpqPosition = hostPpqPosition + (posInBuffer * ppqPerSample); + + //Some hosts like Cubase come up with a wacky ppqPosition + //that could break the timing! Best is to "wait" + //here for the right ppqPosition to jump on. + if (m_syncPpqPosition >= m_ppqToStartSyncAt) + { + if ((m_syncFlag & startSlave_) == startSlave_) + { + midiBuffer->addEvent(*m_continueMessage, posInBuffer); + + m_syncFlag &= cycleEnd_; + } + + //Cycle mode on + if (posInfo.isLooping && posInfo.ppqLoopStart != posInfo.ppqLoopEnd) + { + const double ppqToCycleEnd = fabs(posInfo.ppqLoopEnd - m_syncPpqPosition); + const int64 samplesToCycleEnd = roundToInt64(ppqToCycleEnd * (60.0 / posInfo.bpm) * sampleRate); + + if ((m_syncFlag & cycleEnd_) == 0) + { + if (samplesToCycleEnd <= clockDistanceInSamples) //For fine tuning tweak here + { + //We have reached the cycle- end position + //and must stop the Midiclock slave here + if (m_followSongPosition) + midiBuffer->addEvent(*m_stopMessage, posInBuffer); + + m_syncFlag |= cycleEnd_; + } + } + } } - syncPpqPosition = hostPpqPosition; - - syncFlag = startSlave_; - - wasPlaying = false; + //For best timing we should never interupt Midiclock messages! + //Seems that some slaves constantly adjusting their internal clock + //to Midiclock even if they are in stop mode. + double sample_pos = (double)posInBuffer + sample_offset; + double distance = fabs(syncSamplePos_f-m_last_sample_pos_sync); +// SynthDebug("Distance: %f", distance); +// SynthDebug("Quant: %f", quant); +// if (fabs(distance - clockDistanceInSamples) < quant and sample_pos >= 0) + if ((syncSamplePos % clockDistanceInSamples) == 0 and sample_pos >= 0) + { +// SynthDebug("MidiClock at Sample Sync: %d | PPQ Host: %f | PPQ Sync: %f", syncSamplePos-m_last_sample_pos_sync, hostPpqPosition-m_last_ppq_pos_host, m_syncPpqPosition-m_last_syncPpqPosition); + SynthDebug("MidiClock at Sample Sync: %f, diff %f", sample_pos, syncSamplePos_f-m_last_sample_pos_sync); + m_last_sample_pos_sync = syncSamplePos_f; + m_last_ppq_pos_host = hostPpqPosition; + m_last_syncPpqPosition = m_syncPpqPosition; + midiBuffer->addEvent(*m_clockMessage, roundToInt(sample_pos)); + } + m_syncSamplePos = ++syncSamplePos; } + + m_wasPlaying = true; } -} + else + { + //Send positioning message if the user has stopped or if he changed the playhead position + //manually in stop mode! This will also initially cue slave after loading plugin instance. + if (m_wasPlaying || positionJumped(m_syncPpqPosition, hostPpqPosition, sampleRate, ppqPerSample)) + { + midiBuffer->addEvent(*m_stopMessage, 0); + sendSongPositionPointerMessage(hostPpqPosition, 0, midiBuffer); + } + + m_syncPpqPosition = hostPpqPosition; + m_syncFlag = startSlave_; + m_wasPlaying = false; + } +} + //================================================================================================= bool JK_MidiClock::positionJumped(const double lastPosInPPQ, const double currentPosInPPQ, const double sampleRate, const double ppqPerSample) { //This returns true if the user has changed the playhead position manually or if //a jump has occured! The comperator's default threshold is lastPosInPPQ +- 10ms. - if (currentPosInPPQ < lastPosInPPQ - ((posChangeThreshold * sampleRate) * ppqPerSample) || - currentPosInPPQ > lastPosInPPQ + ((posChangeThreshold * sampleRate) * ppqPerSample)) + if (currentPosInPPQ < lastPosInPPQ - ((m_posChangeThreshold * sampleRate) * ppqPerSample) || + currentPosInPPQ > lastPosInPPQ + ((m_posChangeThreshold * sampleRate) * ppqPerSample)) return true; return false; @@ -180,10 +208,10 @@ void JK_MidiClock::sendSongPositionPointerMessage(const double ppqPosition, cons //16th note to the given ppqPosition. int intBeat = int(ceil(ppqPosition * 4)); - uint8* pSongPositionTime((uint8*)(songPositionMessage->getRawData())); + uint8* pSongPositionTime((uint8*)(m_songPositionMessage->getRawData())); *(pSongPositionTime + 1) = (uint8)(intBeat & 0x7f); *(pSongPositionTime + 2) = (uint8)((intBeat & 0x3f80)>>7); - buffer->addEvent(*songPositionMessage, posInBuffer); + buffer->addEvent(*m_songPositionMessage, posInBuffer); } diff --git a/src/plug/JK_MidiClock.h b/src/plug/JK_MidiClock.h index f403e78..53f7457 100644 --- a/src/plug/JK_MidiClock.h +++ b/src/plug/JK_MidiClock.h @@ -23,14 +23,14 @@ class JK_MidiClock public: JK_MidiClock(); - void setPositionJumpThreshold(const double ms) {posChangeThreshold = ms / 1000.0;} - double getPositionJumpThreshold() {return posChangeThreshold;} - void setFollowSongPosition(const bool shouldFollow) {followSongPosition = shouldFollow;} - bool getFollowSongPosition() {return followSongPosition;} - void setOffset(const int offset) {ppqOffset = offset;} - int getOffset() {return ppqOffset;} + void setPositionJumpThreshold(const double ms) {m_posChangeThreshold = ms / 1000.0;} + double getPositionJumpThreshold() {return m_posChangeThreshold;} + void setFollowSongPosition(const bool shouldFollow) {m_followSongPosition = shouldFollow;} + bool getFollowSongPosition() {return m_followSongPosition;} + void setOffset(const int offset) {m_ppqOffset = offset;} + int getOffset() {return m_ppqOffset;} - void generateMidiclock(AudioPlayHead::CurrentPositionInfo &lastPosInfo, + void generateMidiclock(const AudioPlayHead::CurrentPositionInfo &lastPosInfo, MidiBuffer* midiBuffer, const int bufferSize, const double sampleRate); @@ -45,21 +45,24 @@ private: } - bool wasPlaying; - double syncPpqPosition; - double posChangeThreshold; - double ppqToStartSyncAt; - bool followSongPosition; - uint8 syncFlag; - int ppqOffset; - + bool m_wasPlaying; + double m_syncPpqPosition; + double m_posChangeThreshold; + double m_ppqToStartSyncAt; + bool m_followSongPosition; + uint8 m_syncFlag; + int m_ppqOffset; + double m_last_sample_pos_sync; + double m_last_ppq_pos_host; + double m_last_syncPpqPosition; + double m_syncSamplePos; static const int cycleEnd_ = 1; static const int startSlave_ = 2; - ScopedPointer clockMessage; - ScopedPointer continueMessage; - ScopedPointer stopMessage; - ScopedPointer songPositionMessage; + ScopedPointer m_clockMessage; + ScopedPointer m_continueMessage; + ScopedPointer m_stopMessage; + ScopedPointer m_songPositionMessage; void sendSongPositionPointerMessage(const double ppqPosition, const int posInBuffer, MidiBuffer* buffer);