My struggle with the timing

One main thing I want to do with openFrameWorks is to implement my midi sequencer on it, and to combine it with the display of pictures and video.
I use rtMidi, and it works correctely on Mac (Tiger) and Windows (XP).

To do this I need precise timing.

My first attempt was to use the following code :

float CurTime = 0, NextTime = 0, fDelay = 0.17045; // initialisations…

// and inside of the method testApp::draw(

CurTime = ofGetElapsedTimef();
if(CurTime >= NextTime) {
seqTimerFunc(); // midi processing here …
NextTime = CurTime + fDelay;
}

Problems with this technique :
Slow down when moving the mouse, or selecting another window
On Windows : Stop playing when click on windows bar
On Macintosh : Stop playing when selecting a menu

After a few hours the program crash (maybe overflow on ofGetElapsedTimef() ?)

After that I tried the famous glut timer function. It is a little better, specially in a separate thread,
but still not precise enough.

Finally, the best solution I found was to use the Timer of Poco, by adapting an example file in
the poco library to my needs.

Here is my code : (quick and dirty example but works)

// in file “testApp.h” :

#include “ofMain.h”
#include “Poco/Stopwatch.h”
#include “Poco/Thread.h”
#include “Poco/Timestamp.h”
#include “Poco/Timer.h”

using Poco::Stopwatch;
using Poco::Thread;
using Poco::Timer;
using Poco::TimerCallback;

class seqTimer
{
public:
seqTimer()
{
stopwatch.start();
}

void onTimer(Timer& timer)
{
void seqTimerFunc(Poco::Timestamp::TimeDiff curTime); // prototype of my function
seqTimerFunc(stopwatch.elapsed()); // function call
}

private:
Stopwatch stopwatch;
};

// and I declare in class testApp : public ofBaseApp
seqTimer sTimer;
Timer * timer;

// in file “testApp.cpp” :

Poco::Timestamp::TimeDiff hdelay = 125000; // delay between 2 events, in microseconds
Poco::Timestamp::TimeDiff nextTime = 0; // time when next event occurs (calculated in seqTimerFunc)

void seqTimerFunc(Poco::Timestamp::TimeDiff curTime);
void seqTimerFunc(Poco::Timestamp::TimeDiff curTime)
// Events that are regularely sent are processed here
{
if(curTime >= nextTime) {
nextTime += hdelay;
cout << (long)(curTime / 1000) << " … " << (long)(nextTime / 1000) << endl;
// Execute real messages (midi etc) here …
}
}

// and in the method void testApp::setup(){
timer = new Timer(0, 10); // parameters : immediate and delay of 10 milliseconds (fast enough i think)
timer->start(TimerCallback(sTimer, &seqTimer::onTimer), Thread::PRIO_HIGHEST);

My tests :
On a 1 GHz Macintosh with Tiger it works without problem

On a 2.66 GHZ PC with Windows XP it is ok if no other program (f.e. Firefox) is extensely used, otherwise there are notes that become irregular.
Maybe Windows ignores the Thread::PRIO_HIGHEST parameter ?

i do all the musical timing stuff in the audio thread. then you can do everything with sample precision. but it’s tricky because there are issues with threading. so to send data to or from the audio thread you have to use mutex or locks. but those tend to slow down your program. therefore i use Ringbuffers that can be used without locks or mutexes and still don’t crash the threads.
you’ll have to change OF a little bit to get access to the audio thread. i disabled the audio support of OF completely and rewrote the audio stuff to do realtime rendering of audio instead of just streaming or playing samples.

best
joerg

It’s certainly a good idea when working with audio, but a little to complicated for working with midi. I wiil considere what you told when I will start with the audio part of the program

Thank you for the suggestion
Bernard

jroge, are you using FMOD or rtAudio for your audio thread?
Yout solution sounds interesting, please post some code if you’re willing to share some…

i am using rtAudio. posting code is not really easy because it’s interwoven into the engine i am developing.

but i can post the basic setup part:

  
  
class Audio  
{  
	static bool running;  
	bool underflow;  
	  
	RtAudio adac;  
	  
public:  
	Audio();  
	  
	void setup();  
	  
	bool getUnderflow() {bool uf = underflow; underflow = false; return uf;}  
	  
	static int audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *data);  
	  
	static void start() {running = true;}  
	static void stop() {running = false;}  
	static bool isRunning() {return running;}  
};  
  
void Audio::setup()  
{  
	if(adac.getDeviceCount() < 1)  
	{  
		std::cout << "\nNo audio devices found!\n";  
	}  
  
	// Set the same number of channels for both input and output.  
	//unsigned int bufferBytes;  
	unsigned int bufferFrames = SNDBUFSIZE;  
	RtAudio::StreamParameters iParams, oParams;  
	RtAudio::StreamOptions options;  
	iParams.deviceId = adac.getDefaultInputDevice(); // first available device  
	iParams.nChannels = 2;  
	iParams.firstChannel = 0;  
	oParams.deviceId = adac.getDefaultOutputDevice(); // first available device  
	oParams.nChannels = 2;  
	oParams.firstChannel = 0;  
	options.flags = RTAUDIO_SCHEDULE_REALTIME; //RTAUDIO_MINIMIZE_LATENCY;  
	options.numberOfBuffers = 0;  
  
	try  
	{  
		adac.openStream(&oParams, &iParams, RTAUDIO_FLOAT32, SAMPLERATE, &bufferFrames, &Audio::audioCallback, this, &options);  
		adac.startStream();  
	}  
	catch(RtError& e)   
	{  
		e.printMessage();  
	}  
	  
	printf("########## RTAudio is set up.\n");	  
}  
  
int Audio::audioCallback(void *outputBuffer, void *inputBuffer, unsigned int nBufferFrames, double streamTime, RtAudioStreamStatus status, void *_thisPtr)  
{  
	Audio *thisPtr = (Audio *)_thisPtr;  
  
	if(status == RTAUDIO_OUTPUT_UNDERFLOW)  
	{  
		thisPtr->underflow = true;  
		std::cout << ".";  
	}  
  
	if(isRunning())  
	{  
		processAudio((float *)inputBuffer, (float *)outputBuffer, nBufferFrames, 2);  
	}  
	  
	return 0;  
}  
  
  

i can also post the Ringbuffer-code if you’d like to see it (but i think i posted it before in this forum just have a look and ask me again if you don’t find it).

best
joerg

1 Like

Actually it was the ring-buffer code I was interested in (nor the rtAudio setup). I can’t seem to find it, so if you could post it again that would be great!

EDIT:
I found your code…I’m checking it out, thanks

here it is:

Ringbuffer.h

#ifndef _RINGBUFFER
#define _RINGBUFFER

// ringbuffer
#define ringbuffer_next(x) (( (x) + 1) % size)

template<class T, int size> class Ringbuffer
{
T buffer[size];
int writePtr;
int readPtr;
T zero;

public:
Ringbuffer(T _zero) {zero = _zero; writePtr = 0; readPtr = 0; for(int i = 0; i < size; i++) buffer[i] = zero;}

bool add(T sg)
{
if(ringbuffer_next(writePtr) == readPtr)
return false;
writePtr = ringbuffer_next(writePtr);
buffer[writePtr] = sg;
return true;
}

bool isEmpty()
{
return readPtr == writePtr;
}

T get()
{
if(isEmpty())
return zero;
//int r = readPtr;
readPtr = ringbuffer_next(readPtr);
T r = buffer[readPtr];
buffer[readPtr] = zero;
return r;
}
};

#endif // _RINGBUFFER

you have to initialize the ringbuffer with a zero element. a cookie to determine wether a slot is occupied or not. if you use a ringbuffer for pointers you would use NULL for that.

best
joerg