Sync to external MidiClock


#1

I’m trying to set this up, but I’m a little confused.

When I get a midiClock message I can use the first two to calculate the tempo (although constantly checking this would be better). but creating a MidiMessageSequence seems to require me calling these before I add any Notes or CCs.

			midiSeqences[i]->addEvent(MidiMessage::timeSignatureMetaEvent(4, 4));
			midiSeqences[i]->addEvent(MidiMessage::tempoMetaEvent(microsecondsperquarter));
			midiSeqences[i]->addEvent(MidiMessage::midiChannelMetaEvent(i+1));

Any thoughts on a good way to set this up?


#2

When you get this working please post the result, I need to do the same thing!


#3

Not working yet, and some very hacky code,… but this is what I have so far.

void PerformerManagerComponent::handleIncomingMidiMessage (MidiInput *source, const MidiMessage &message)
{	
	if(recEnabled == true && playEnabled == true && extSyncEnabled == true)
	{
		if(message.isMidiStart () )
		{
			DBG("StartRecordingMidi");
			
			for(int i = 0; i < midiSeqences.size(); i++)
			{
				midiSeqences[i]->clear();
				midiSeqences[i]->addEvent(MidiMessage::timeSignatureMetaEvent(4, 4));
				midiSeqences[i]->addEvent(MidiMessage::tempoMetaEvent(microsecondsPerQuarter));
				midiSeqences[i]->addEvent(MidiMessage::midiChannelMetaEvent(i+1));
			}
			previousMessageStamp = message.getTimeStamp();
                        startTime = (Time::getMillisecondCounterHiRes() * .001);
		}
		
		if(message.isMidiStop () )
		{
			DBG("stop");
			saveMidiFile();
			return;
		}
		
		if(message.isMidiClock() )
		{
			DBG("microsecondsPerQuarter");
			DBG(microsecondsPerQuarter);
			
			double scaler = 24000000000.00;
			microsecondsPerQuarter = (scaler  * (message.getTimeStamp()  - previousMessageStamp));
			previousMessageStamp = message.getTimeStamp();
			previousMicroseconds = microsecondsPerQuarter;
			
			if((microsecondsPerQuarter - previousMicroseconds) > 100)
			{
				for(int i = 0; i < midiSeqences.size(); i++)
				{
					midiSeqences[i]->addEvent(MidiMessage::timeSignatureMetaEvent(4, 4));
					midiSeqences[i]->addEvent(MidiMessage::tempoMetaEvent(microsecondsPerQuarter));
					midiSeqences[i]->addEvent(MidiMessage::midiChannelMetaEvent(i+1));
				}
			}
		}
	}
	

	const int timeStamp = (int)(((message.getTimeStamp() - startTime)/(microsecondsPerQuarter * .01)) * Time::getHighResolutionTicksPerSecond());
	
	if(recEnabled == true && playEnabled == true)
	{
		if(message.isMidiClock() == false && message.isMidiStart() == false && message.isMidiStop() == false)
		{
			for (int i = 0; i < enabledMidiInputChannels.size(); i++)
			{
				if(source->getName() == enabledMidiInputChannels[i])
				{
					MidiMessage* const newOne = new MidiMessage (message);
					
					newOne->setTimeStamp (timeStamp);
					newOne->setChannel(i+1);
					
					midiSeqences[i]->addEvent(*newOne);
					midiSeqences[i]->updateMatchedPairs();
					
					/*
					 DBG( String("Message: chn: ") + String(newOne->getChannel())
					 + String(" Note: ") + String(newOne->getNoteNumber() )
					 + String(" Vel: ") + String(newOne->getVelocity() )
					 + String(" T: ") + String(newOne->getTimeStamp()) ); 
					 */
				}
				
			}
		}
	}
}

It writes a midiFile using my saveMidiFile(); (nothing fancy there), seems to be recognized by Ableton, loads looking like the right tempo, but the events are all pushed and pulled… I’m figuring it has to do partly with the noisy sync pulse coming from ableton. I’ve done no smoothing yet. Also, the midi seems to start exactly a 16th late when I re import it into Ableton.

Any thoughts?


#4

*** I was tired and pasted code without adding the sync changes, doh!! This is working with sync now, although I still seem to get a slight offset. 02/09/11

This seems to be working so far, with the exception that I am offset by 2 ticks? So the time Stamps read 2, 98, 194, etc…

It uses a Kalman filter I found online at http://snippets.dzone.com/posts/show/11215. A basic averaging filter didn’t seem to work, but this does. The link has all the values for the filter (I took out what wasn’t needed, and added the input). I tried messing with the Q and R, but the initial settings seemed to work the best so far.

Any thoughts?

void PerformerManagerComponent::handleIncomingMidiMessage (MidiInput *source, const MidiMessage &message)
{		
	
	if(message.isMidiStart () )
	{
		startTime = message.getTimeStamp();
		
		DBG("StartRecordingMidi");
		
		for(int i = 0; i < midiSeqences.size(); i++)
		{
			midiSeqences[i]->clear();
			midiSeqences[i]->addEvent(MidiMessage::timeSignatureMetaEvent(4, 4));
			midiSeqences[i]->addEvent(MidiMessage::tempoMetaEvent(microsecondsPerQuarter));
			midiSeqences[i]->addEvent(MidiMessage::midiChannelMetaEvent(1));
		}
		
		previousMessageStamp = startTime;
		x_est_last = microsecondsPerQuarter;
	}
	
	else if(message.isMidiStop () )
	{
		DBG("stop");
		saveMidiFile();
		return;
	}
	
	else if(message.isMidiClock() )
	{
		
		DBG(String("microsecondsPerQuarter: ") + String(microsecondsPerQuarter));
		DBG(String("BPM: ") + String((double)((Time::getHighResolutionTicksPerSecond() * 60.0000)/microsecondsPerQuarter)));
		
		double scaler = 24000000000.00;
		microsecondsPerQuarter = (scaler  * (message.getTimeStamp()  - previousMessageStamp));
		
		x_temp_est = x_est_last;
		P_temp = P_last + Q;
		//calculate the Kalman gain
		K = P_temp * (1.0/(P_temp + R));
		//measure
		z_measured = microsecondsPerQuarter; //the real noisy measurement 
		//correct
		microsecondsPerQuarter = x_temp_est + K * (z_measured - x_temp_est); 
		P = (1- K) * P_temp;
		//we have our new system
		
		//update our last's
		P_last = P;
		x_est_last = microsecondsPerQuarter;
		previousMessageStamp = message.getTimeStamp();
		
		bpm.setValue((double)((Time::getHighResolutionTicksPerSecond() * 60.0000)/microsecondsPerQuarter) );
		
		for(int i = 0; i < midiSeqences.size(); i++)
		{
			midiSeqences[i]->addEvent(MidiMessage::tempoMetaEvent(microsecondsPerQuarter));
		}
		
	}
	else
	{
		const int timeStamp = (message.getTimeStamp() - startTime)*((float)bpm.getValue()/60.0)*96;
		
		for (int i = 0; i < enabledMidiInputChannels.size(); i++)
		{
			if(source->getName() == enabledMidiInputChannels[i])
			{
				MidiMessage* const newOne = new MidiMessage (message);
				
				if(message.isNoteOnOrOff() == true && deviceManager.isNoteEnabled(i, true) == true)
				{
					//DBG("Note message");
					
					newOne->setTimeStamp (timeStamp);
					newOne->setChannel(1);
					
					midiSeqences[i]->addEvent(*newOne);
					if(newOne->isNoteOff(false) )
						midiSeqences[i]->updateMatchedPairs();
				}
				
				if(message.isController() == true && deviceManager.isCcEnabled(i, true) == true)
				{
					//DBG("CC message");
					
					newOne->setTimeStamp (timeStamp);
					newOne->setChannel(1);
					
					midiSeqences[i]->addEvent(*newOne);
				}
			}				
		}
	}
}