fixing MidiClock (Zwischenstand)
This commit is contained in:
+158
-130
@@ -12,24 +12,30 @@
|
||||
|
||||
*/
|
||||
#include "JK_MidiClock.h"
|
||||
#include "juce_core/maths/juce_MathsFunctions.h"
|
||||
#include <synth/synth_defs.h>
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
+22
-19
@@ -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 <MidiMessage> clockMessage;
|
||||
ScopedPointer <MidiMessage> continueMessage;
|
||||
ScopedPointer <MidiMessage> stopMessage;
|
||||
ScopedPointer <MidiMessage> songPositionMessage;
|
||||
ScopedPointer <MidiMessage> m_clockMessage;
|
||||
ScopedPointer <MidiMessage> m_continueMessage;
|
||||
ScopedPointer <MidiMessage> m_stopMessage;
|
||||
ScopedPointer <MidiMessage> m_songPositionMessage;
|
||||
|
||||
void sendSongPositionPointerMessage(const double ppqPosition, const int posInBuffer, MidiBuffer* buffer);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user