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