242 lines
10 KiB
C++
242 lines
10 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"
|
|
#include "juce_core/maths/juce_MathsFunctions.h"
|
|
#include <cmath>
|
|
#include <synth/synth_defs.h>
|
|
|
|
|
|
JK_MidiClock::JK_MidiClock()
|
|
: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()!
|
|
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_sample_remain = 0;
|
|
}
|
|
//=================================================================================================
|
|
void JK_MidiClock::generateMidiclock(const AudioPlayHead::CurrentPositionInfo &posInfo,
|
|
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)
|
|
{
|
|
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;
|
|
|
|
const double clockDistanceInSamples_f = (60.0 * sampleRate) / (posInfo.bpm * 24.0);
|
|
const int clockDistanceInSamples = roundToInt(clockDistanceInSamples_f);
|
|
|
|
const double quant = (60.0 / posInfo.bpm) * sampleRate;
|
|
|
|
if (posInfo.isPlaying || posInfo.isRecording)
|
|
{
|
|
if (! m_wasPlaying)
|
|
{
|
|
//set the point where to start the slave
|
|
m_ppqToStartSyncAt = getNearestSixteenthInPPQ(hostPpqPosition);
|
|
SynthDebug("m_ppqToStartSyncAt %f", m_ppqToStartSyncAt);
|
|
|
|
//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);
|
|
SynthDebug("m_ppqToStartSyncAt %f", m_ppqToStartSyncAt);
|
|
}
|
|
m_sample_remain = clockDistanceInSamples;
|
|
|
|
}
|
|
else
|
|
{
|
|
//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;
|
|
}
|
|
}
|
|
m_sample_remain = clockDistanceInSamples;
|
|
}
|
|
}
|
|
|
|
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_;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//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.
|
|
int buffer_remain = bufferSize;
|
|
int posInBuffer = 0;
|
|
int buffer_consume = 0;
|
|
while(buffer_remain)
|
|
{
|
|
if (m_sample_remain > buffer_remain)
|
|
{
|
|
m_sample_remain -= buffer_remain;
|
|
buffer_consume = buffer_remain;
|
|
}
|
|
else
|
|
{
|
|
buffer_consume = m_sample_remain;
|
|
m_sample_remain = 0;
|
|
}
|
|
|
|
buffer_remain -= buffer_consume;
|
|
posInBuffer += buffer_consume;
|
|
|
|
if (buffer_remain == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (m_sample_remain == 0)
|
|
{
|
|
m_sample_remain = clockDistanceInSamples;
|
|
#if 0
|
|
SynthDebug("posInBuffer : %d", posInBuffer);
|
|
SynthDebug("clockDistanceInSamples_f (soll) : %f", clockDistanceInSamples_f);
|
|
SynthDebug("hostPpqPosition : %f", hostPpqPosition);
|
|
SynthDebug("hostPpqPosition (Q) : %f", getNearestInPPQ(hostPpqPosition, 96));
|
|
SynthDebug("hostSamplePos (Q) : %f", getNearestInPPQ(hostPpqPosition, 96) * quant);
|
|
#endif
|
|
midiBuffer->addEvent(*m_clockMessage, roundToInt(posInBuffer));
|
|
}
|
|
}
|
|
|
|
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 - ((m_posChangeThreshold * sampleRate) * ppqPerSample) ||
|
|
currentPosInPPQ > lastPosInPPQ + ((m_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*)(m_songPositionMessage->getRawData()));
|
|
|
|
*(pSongPositionTime + 1) = (uint8)(intBeat & 0x7f);
|
|
*(pSongPositionTime + 2) = (uint8)((intBeat & 0x3f80)>>7);
|
|
|
|
buffer->addEvent(*m_songPositionMessage, posInBuffer);
|
|
}
|