Files
JaySynth/Source/JK_MidiClock.cpp
T
jens 978c752e8b - use jsy_min/jsy_max
- fixed includes
- removed redundant MW table
- added Missing JK_MidiClock
2023-04-21 14:25:04 +02:00

190 lines
9.2 KiB
C++

/*
//#######################################################################################
//Class to insert Midiclock messages into a given MidiBuffer, suitable for block
//based audio callbacks. The class can act on position jumps e.g. "Loops" by sending a
//positioning message.
//NOTE: No ballistik came in to play so I wouldn't recommend using it to drive a mechanical
//tape deck!!!
//
//solar3d-software, April- 10- 2012
//#######################################################################################
*/
#include "JK_MidiClock.h"
JK_MidiClock::JK_MidiClock()
:wasPlaying(false),
syncPpqPosition(-999.0),
posChangeThreshold(0.001),
ppqToStartSyncAt(0.0),
followSongPosition(true),
syncFlag(0),
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));
}
//=================================================================================================
void JK_MidiClock::generateMidiclock(AudioPlayHead::CurrentPositionInfo &lastPosInfo,
MidiBuffer* midiBuffer, const int bufferSize, const double sampleRate)
{
//###################################Some explanation about musical tempo #################
//A Time Signature, is two numbers, one on top of the other. The numerator describes the #
//number of Beats in a Bar, while the denominator describes of what note value a Beat is. #
//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)
{
//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;
}
}
}
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);
}
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);
}
syncPpqPosition = hostPpqPosition;
syncFlag = startSlave_;
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))
return true;
return false;
}
//=================================================================================================
void JK_MidiClock::sendSongPositionPointerMessage(const double ppqPosition, const int posInBuffer, MidiBuffer* buffer)
{
//This will cue the slave to the NEAREST
//16th note to the given ppqPosition.
int intBeat = int(ceil(ppqPosition * 4));
uint8* pSongPositionTime((uint8*)(songPositionMessage->getRawData()));
*(pSongPositionTime + 1) = (uint8)(intBeat & 0x7f);
*(pSongPositionTime + 2) = (uint8)((intBeat & 0x3f80)>>7);
buffer->addEvent(*songPositionMessage, posInBuffer);
}