MIDI sequence timer



I’m building some simple MIDI-based sequencing examples (without audio). For timing the sync of the sequencer obviously the Timer class is not suitable (unless I misunderstand something). Instead I’m using a Thread which seems pretty stable using a constant sleep() value in a loop in its run() method. But I can imagine that should the loop get even slightly more complex the timing might drift slightly. I figured that I could time how long each loop’s iteration takes to run and subtract this from the constant value sent to sleep to compensate. I’m also compensating for the int/double rounding errors on subsequent loop iterations, and the duration of the iteration-time measurement functions themselves (by doing an average at the start of the app). Does this seem a sensible approach?


#include <juce/juce.h>

class MainComponent  :	public Component,
						public ButtonListener,
						public ComboBoxListener,
						public Thread
		StringArray midiDevices;
		MidiOutput* midiOutput;
		ComboBox* midiOutputSelector;
		TextButton* play;
		TextButton* stop;
		Label* text;
		Array<int> noteSeq;
		int	index;
		int rate;
		double timeCorrectionError;
		double measurementError;

		MainComponent () 
			:	Thread(T("Sequencer")),
			// simple sequencing example,  using a thread
			midiDevices = MidiOutput::getDevices();
			addAndMakeVisible(midiOutputSelector = new ComboBox("MIDI Output Selector"));
			midiOutputSelector->setBounds(10, 10, 270, 20);
			for(int i = 0; i < midiDevices.size(); i++)
				midiOutputSelector->addItem(midiDevices[i], i+1);
			midiOutputSelector->setSelectedId(1, false);
			addAndMakeVisible(play = new TextButton(T("Play")));
			play->setBounds(10, 40, 270, 20);
			addAndMakeVisible(stop = new TextButton(T("Stop")));
			stop->setBounds(10, 70, 270, 20);
			addAndMakeVisible(text = new Label(T("Text"),T("Starting...")));
			text->setBounds(10, 100, 270, 20);
			// add some notes to the sequence
			// measure the measurement error!
			int n = 100000;				// iterations for the test
			double oneOverN = 1.0/n;	// for calulcating the mean
			double meanError = 0.0;		// accumulate the mean error
			for(int i = 0; i < n; i++)
				double time1 =	Time::getMillisecondCounterHiRes();		// start time
				Time::getMillisecondCounterHiRes();						// dummy for the loop top meaurement
				Time::getMillisecondCounterHiRes();						// dummy for the loop bottom meaurement
				getTimeCorrection(0.0, 0.0);							// dummy for the calculations
				double time4 = Time::getMillisecondCounterHiRes();		// end time
				meanError += (time4-time1) * oneOverN;
			measurementError = meanError;
			text->setText(String(T("Measurement error: "))+String(measurementError, 6)+String(T("ms")), false);
		~MainComponent ()
		void buttonClicked(Button* button)
			if(button == play)
				text->setText(T("Playing"), false);
				startThread(10); // 10 = highest priority
			else if(button == stop)
				text->setText(T("Stopped"), false);
				stopThread(3000); // stop and timeout after 3 secs and then force if necessary
		void comboBoxChanged(ComboBox* comboBox)
			if(comboBox == midiOutputSelector)
				midiOutput = MidiOutput::openDevice(midiOutputSelector->getSelectedItemIndex());
		forcedinline int getTimeCorrection(double loopTopTime, double loopBottomTime)
			double timeCorrection = loopBottomTime-loopTopTime+measurementError;			// time the main body of the loop took to run (plus these measurements)
			double timeCorrectionWithPrevErr = timeCorrection + timeCorrectionError;		// add on the error from the last iteration
			int timeCorrectionRounded = roundDoubleToInt(timeCorrectionWithPrevErr);		// round this to an int since sleep() needs int ms
			timeCorrectionError = timeCorrectionWithPrevErr-timeCorrectionRounded;			// measure the error between int & double for next time
			return timeCorrectionRounded;
		void run()
			while( ! threadShouldExit() )
				double loopTopTime = Time::getMillisecondCounterHiRes();
				if(midiOutput != 0)
					midiOutput->sendMessageNow(MidiMessage::noteOff(1, noteSeq[(index-1) % noteSeq.size()]));
					midiOutput->sendMessageNow(MidiMessage::noteOn(1, noteSeq[index % noteSeq.size()], 0.9f));
				double loopBottomTime = Time::getMillisecondCounterHiRes();
				sleep(jmax(0, rate - getTimeCorrection(loopTopTime, loopBottomTime)));
			if(midiOutput != 0) 



It sounds workable, if a little convoluted. I do something like this:

getTimeNow (do this first)
do what I need to do
work out how much time elapsed
sleep for period - elapsed

As with anything multi-threaded, this could get pre-empted which would throw timing off, but other solutions suffer from the same problem.

BTW, I’m doing sleep on Mac and Linux with nanosleep, as it’s more accurate that Thread::sleep, there’s probably a Windows equivalent.



I wrote Time::waitForMillisecondCounter to do exactly this - that’s what tracktion uses in its midi playback thread, and it works pretty well.

Unless you’re using a very old juce version, I think Thread::sleep() just calls nanosleep().


But in whole millisecs? That’s a bit coarser than I prefer. Can we call that a feature request then please, a wait method with microseconds or nanoseconds?