accurate sequencing

Hi there,

I have just built a sequencer in OF that I am pretty happy with but think could be improved as
it occasionally drifts.

At the moment I am using this code to move through the sequence.

  
  
if ((ofGetElapsedTimeMillis()-lastTime) >= toggleTime  && (timeFlag == 0)){tapeCounter += 1; // trigger my sequencer here }  
  
// lastTime is then updated in the function in my sequencer  
  

I guess this is a pretty standard approach but I am noticing some lag here and there.

Just wondering if anyone knows of a better rock solid solution. Or is this the best way to do it ?

Many thanks

Simon

you could try this (pseudocode/untested):

  
  
in the .h:  
  
int nextFrameTimer;  
  
in the .cpp:  
  
static const int FRAME_TIME = 100; // number of millis per sequencer tick/frame  
  
setup()  
{  
tapeCounter = 0;  
nextFrameTimer = FRAME_TIME;  
}  
  
update()  
{  
nextFrameTimer -= ofGetElapsedTimeMills();  
while (nextFrameTimer < 0 )  
{  
  tapeCounter+=1;  
  nextFrameTimer += FRAME_TIME;  
}  
}  
  

the problem with your method is that you’re throwing away the missing bits of time, when they need to be accumulated.

hope this helps
d

Thanks for this Daniel,

I am a bit unsure about it though.

  
  
  
nextFrameTimer -= ofGetElapsedTimeMillis();  
while (nextFrameTimer < 0 )  
{  
  cout << "before" << nextFrameTimer << "\n";  
  bang();  
  nextFrameTimer += FRAME_TIME;  
  cout << "after" << nextFrameTimer << "\n";  
}  
  
  

For me nextFrameTimer is always and increasingly below 0 so constantly triggering the sample.

If I put something like

  
  
static const int FRAME_TIME = 1000000;  
  

It achieves a bouncing ball effect.

This solves that problem but seems no more steady

  
  
void testApp::update(){  
  
newtime = ofGetElapsedTimeMillis()-lasttime;  
nextFrameTimer -= newtime;  
while (nextFrameTimer < 0 )  
{  
  cout << "before" << nextFrameTimer << "\n";  
  bang();  
  nextFrameTimer += FRAME_TIME;  
  cout << "after" << nextFrameTimer << "\n";  
  lasttime = ofGetElapsedTimeMillis();  
}  
  
   
}  
  

Cheers

Simon

Hey Simon,

Damian’s code contains a false assumption: ofGetElapsedTimeMillis() returns the time since the PC (or your app) booted, not since the last frame. Therefor his code will not work correctly. Other than that, he is right about you ‘throwing time away’.

The difference between your code and Damian’s, is that his code will compensate for any hickups in the execution of your application. I suspect you want something to happen exactly at e.g. 100ms intervals? In your code, the application might call your function after 30, 60, 90 and 120ms. On the last occasion, your code notices it needs to trigger the action and reset itself, but it does not take into account the 20ms that it was too late. These 20ms get lost on the reset and this causes noticeable errors.

In Damian’s case, he does not reset to 0, but gives your code a 20ms head-start (correction). Here’s an overview for how things work in a situation where your code gets called every 30ms and your timer interval is set to 100ms:

  
  
Time  Simon    Damian  
0     -        -  
30    -        -  
60    -        -  
90    -        -  
120   trigger  trigger  
150   -        -  
180   -        -  
210   -        trigger  
240   trigger  -  
270   -        -  
300   -        trigger  
330   -        -  
360   trigger  -  
390   -        -  
420   -        trigger  
  

Your code may look steadier, but Damian’s is more accurate. It triggers closer to the exact 100ms interval and after 400ms it indeed triggered 4 times!

There is one problem with Damian’s code, though: if for some reason your application stalls for a few seconds and then resumes, it will try to catch up on all the triggers it missed. This may cause another hickup and it can seriously freeze your application. To prevent this, you could do something like this (untested pseudo-code):

  
  
float tInterval = 100.0f; // in milliseconds  
float tTriggered = ofGetElapsedTimeMillis();  
  
void update(){  
  // t will go from 0 to 1 every 100ms  
  float t = (ofGetElapsedTimeMillis() - tTriggered) / tInterval;  
  
  if( t >= 1.0f ){  
    tTriggered += ( (float) floor(t) * tInterval );  
  
    // perform your action now  
  }  
}  
  

Now it will catch up without triggering all missed occasions.

Paul

PS: nice to meet you on the forum’s, Damian. As you can see, here at IJsfontein we finally made the jump to OpenFrameworks :slight_smile:

Hi Paul and Damian,

Thanks for these ideas, they have been really useful.

I have just put the code together as an example and attached it.

metronomeExample.zip

Please hack and improve.

Cheers

Simon

metronomeExample.zip

Hello

I have no idea why but I get no sound and nothing is displayed. :cry:

Hi,

The First thing that comes to mind is that the data files are not linked properly. There should be a folder that contains the font and the sound file.

If these were in the wrong place there would be no sound and just a grey window. Check in
the other examples for where these live.

I hope that helps.

cheers

Simon

Thanks Simon, works for me…nice little example, you should build on it :slight_smile:

Indeed, I had tested to move the data and relink it but it didn’t work but I tried once again and now it worked! I guess I didn’t do it right in the first time

Sorry !

Thank you :slight_smile:

Nice piece !

Thats nice that its a useful example.

I think an obvious improvement would be a GUI slider and then the possibility to add accents.

As a flamenco guitar enthusiast I may develop a twelve beat compas that allows for different accenting.

A 440 hertz tone would also be useful for tuning your instrument.

It might be nice to develop a series of metronome examples that get more and more complex, but maybe restrict them to using oF’s core functions.

Any other suggestions would be welcome. What would be the best GUI slider to use for a beginners example ? Or should I just build one from scratch ?

Cheers

Simon

Programming accents sounds good. Or slightly more challenging perhaps, what about swing/shuffle? :wink:

As for GUI, try one of these two perhaps…

http://forum.openframeworks.cc/t/another-gui/1469/0
http://forum.openframeworks.cc/t/ofxsimplegui—the-world-simplest-gui/1111/0

A 440 HZ tone is pretty easy, just check out ofSoundStream example, I think something like that is already done.

@simonblackmore

This is an interesting thread for me too, just getting started with OF and i’m interested primarily in the musical possibilities. Thanks for sharing this stuff.

One thing i noticed in the metronome example that threw me off initially; If i understand it correctly you have a variable called bpm, but this isn’t actually storing a value that corresponds to beats per minute, but instead it’s storing milliseconds per beat. This is a bit confusing.

I don’t mean to blow my own horn, but I approached a similar problem with sequencing/timekeeping in Flash AS3. My solution was to use a super-fast internal clock that perpetuated a slower, self-correcting ‘user clock’.

http://anny.fm/2009/08/as3-timekeeper/

Not sure if it’ll be any help at this stage or in OF but there it is :o

Doing the time checking in the update method is ok, but the update method only gets called every time a new frame needs to be drawn. Depending on how much you’re drawing, it can be as slow as 30 times a second (33ms intervals) - so your sound can be at most 33ms in error, which is really audible to a musician.

For really good timing, you want at most 5ms error.

There are 2 ways to achieve this.

  1. The easy way, with threads - check the ofxThread example that comes with oF FAT - you can make a loop that sleeps for 5ms and then rechecks the time etc.

  2. The not so easy way, with ofSoundStream. You can use the timing of the calls to audioRequested() and synthesize your clicks to be on time to the nearest sample, i.e. 44100th of a second. Maybe that’s overkill.

[quote author=“marek”]
There are 2 ways to achieve this.

  1. The easy way, with threads - check the ofxThread example that comes with oF FAT - you can make a loop that sleeps for 5ms and then rechecks the time etc.

  2. The not so easy way, with ofSoundStream. You can use the timing of the calls to audioRequested() and synthesize your clicks to be on time to the nearest sample, i.e. 44100th of a second. Maybe that’s overkill.[/quote]
    Thanks Marek! I was looking around for how one would do sample-accurate sequencing, and your post is a good clue that ofSoundStream is what you’d need.

Here’s a version of simon’s code that i modified to more closely match my intuitions about how a system like this would work. Some of this might be a bit naive though and i daren’t say that it’s an improvement. The timing seems ok, but metronome ticks get skipped sometimes when GUI stuff is happening (dragging the window etc).

testApp.cpp

  
#include "testApp.h"  
void testApp::setup(){  
	click.loadSound("click.wav");  
	tickCounter=0;   
	PPQ=96; // pulses per quarternote (tick resolution). 96 ppq is the same as the mmt8 hardware sequencer  
	setBPM(120.0);  
}  
  
//--------------------------------------------------------------  
void testApp::update(){  
	// find out how many whole ticks should have passed by now  
	// if that number has changed since the last time, do the new tick routine  
	double newWholeTicksPassed=floor(ofGetElapsedTimeMillis()/tickInterval);  
	if(newWholeTicksPassed>previousWholeTicksPassed){  
		tickCounter=newWholeTicksPassed;  
		tickBang(); // perform all tick actions now  
	}  
	previousWholeTicksPassed=newWholeTicksPassed;  
}  
  
void testApp::draw(){  
	ofSetColor(0,0,0);  
	ofDrawBitmapString("Click to Randomise BPM", 100, 90);  
	ofSetColor(255,255,255);  
	ofDrawBitmapString("BPM: "+ofToString(bpm), 100, 110);  
	ofDrawBitmapString("Tick Counter: "+ofToString(tickCounter), 100, 130);  
}  
  
// Inefficient, long-winded conversion, for clarity  
void testApp::setBPM(double targetBPM){  
	double beatspersecond=targetBPM/60.0;  
	double secondsperbeat=1/beatspersecond;  
	double secondsperpulse=secondsperbeat/PPQ;  
	double msperpulse=secondsperpulse*1000;  
	tickInterval=msperpulse;  
	bpm=targetBPM;  
}  
  
void testApp::tickBang(){  
	/* if pulses-per-quarter goes into the current tick counter  
	 value a whole number of times, we know that this tick corresponds  
	 to the start of a new quarter beat, so we play the metronome sound */  
	double x=(double)tickCounter/(double)PPQ;  
	if ((x-floor(x))==0.0){  
		click.play();  
	}  
}  
void testApp::mousePressed(int x, int y, int button){  
	setBPM(ofRandom(60.0, 200.0));  
}  

testApp.h

  
#ifndef _TEST_APP  
#define _TEST_APP  
#include "ofMain.h"  
  
class testApp : public ofBaseApp{  
  
	public:  
		void setup();  
		void update();  
		void draw();  
		void setBPM(double targetBPM);		  
		ofSoundPlayer click;  
		void tickBang();  
		void mousePressed(int x, int y, int button);  
		void drawTempo();  
		  
		double tickInterval;  
		float bpm;  
		int PPQ;  
		int tickCounter;  
		double previousWholeTicksPassed;  
};  
  
#endif  

Yeah, you definitely don’t want to use your update method as a place for your timing code. It just doesn’t get called often enough, and you’ll get

There’s in fact 2 different ways of using ofSoundStream.

a. This way is a bit hacky, but you can just have your update code inside audioRequested, and then as long as you set up the ofSoundStream with a buffer of 256 (your computer will probably not like 128, and 512 isn’t often enough), then your update code will be called 172 times a second, which is ok resolution.

b. If you want *sample accurate* timing, you have to actually write your own sample data to the ofSoundStream. As you loop through each sample, you know that you’ve gone forward in time 1/44100th of a second, so you can use this info to trigger your sounds.

Here’s an example

  
  
  
int pos = 0;  
int BPM = 120;  
int SAMPLE_RATE = 44100;  
  
int lengthOfOneBeatInSamples = (float)SAMPLE_RATE*60.f/BPM;  
  
void audioRequested(float *output, int bufferSize, int numChannels) {  
...  
     
    for(i = 0; i < bufferSize; i++) {  
        pos++; // this gets incremented with every sample  
          
        // when this is zero, your at the beginning of a beat  
        // so this is when you'd want to do your trigger  
        if(pos%lengthOfOneBeatInSamples==0) {  
            tick.trigger();  
        }  
        // write the sample out to the stereo output buffer  
        output[i*2] = output[i*2+1] = tick.getSample();  
    }  
}  
  

Where “tick” is an object that holds your sample data, and has two methods - trigger() and getSample(). trigger() sets the read position of the sampleData, and getSample() which just returns the next sample in the list (and increments the read position). If the read position goes past the end of the sample data, it just returns zero.

Thanks marek, that’s very interesting and clear. I understand now how sample-accurate sequencing of audio data would work.

Rather than audio, I have in mind a sequencer that would playback OSC ‘events’. Am i right in thinking that sample-accurate generation of these events is out of the question, and the best i could hope for is frame-accurate triggering? (ie. accurate to within 256 samples)

256 samples is still quite a high resolution, it’s 5ms. It should be pretty imperceptible. There may be weaker links in the chain, like if you’re sending over a network.

You could get better resolution still if you used an ofxThread rather than the audioRequested() hack. Then you could have as much resolution as you like.

However, I’d probably start out with chucking your update code into audioRequested and having a listen, because it’s easy and less code. Then if it’s not good enough do a thread.

What are you sending the OSC to?

Yes 5 ms is probably fine. I’ll be sending OSC stuff to a max patch running on the same machine. The max patch will be doing synthesis based on the OSC commands.

Based on your example code I put together this frame-accurate version of the metronome, I think i’ll be using an approach that’s something like this:

testApp.cpp

  
#include "testApp.h"  
  
void testApp::setup(){  
	SAMPLE_RATE = 44100;  
	setBPM(120.0);  
	pos = 0; // running sample count  
	click.loadSound("click.wav"); // the metronome sound  
	ofSoundStreamSetup(1,0,this); // sound stream initialised so samples can be counted  
  
}  
  
void testApp::audioRequested(float *output, int bufferSize, int numChannels) {	  
	bool startBeatDetected=false;  
	int i;  
	for(i = 0; i < bufferSize; i++) {  
        pos++; // this gets incremented with every sample	  
		// when lengthOfOneBeatInSamples goes into   
		// pos a whole number of times, we've entered a new quarter beat  
        if(fmod(pos,lengthOfOneBeatInSamples)==0) {  
            startBeatDetected=true;  
        }  
    }  
	// was a new beat region entered during the last frame?  
	if(startBeatDetected){  
		click.play();  
	}  
}  
  
void testApp::draw(){  
	ofSetColor(0,0,0);  
	ofDrawBitmapString("Click to Randomise BPM", 100, 90);  
	ofSetColor(255,255,255);  
	ofDrawBitmapString("BPM: "+ofToString(BPM), 100, 110);  
	ofDrawBitmapString("Pos: "+ofToString(pos), 100, 130);  
	ofDrawBitmapString("Samples per beat: "+ofToString(lengthOfOneBeatInSamples), 100, 150);  
}  
  
void testApp::setBPM(float targetBPM){  
	// NB. Currently the target BPM might not actually be achieved,  
	// because permitted BPMs are limited to divisible by a whole number of samples.  
	lengthOfOneBeatInSamples = (int)((SAMPLE_RATE*60.0f)/targetBPM);  
	BPM=(SAMPLE_RATE*60.0f)/lengthOfOneBeatInSamples;  
}  
  
void testApp::mousePressed(int x, int y, int button){  
	setBPM(ofRandom(60.0, 200.0));  
}  

testApp.h

  
#ifndef _TEST_APP  
#define _TEST_APP  
#include "ofMain.h"  
  
class testApp : public ofBaseApp{  
  
	public:  
		void setup();  
		void draw();  
		void setBPM(float targetBPM);		  
		void audioRequested(float *output, int bufferSize, int numChannels);  
		void mousePressed(int x, int y, int button);  
  
		ofSoundPlayer click;  
		int pos;  
		float BPM;  
		int SAMPLE_RATE;  
		float lengthOfOneBeatInSamples;  
};  
#endif