/* //####################################################################################### //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); }