Music sequencer BPM slightly off

#1

Hi

I’m attempting to include a simple sequencer that plays back audio samples in OpenFrameworks in a project.

I have successfully gotten ofxSequencer and the 09-step-sequencer example from https://github.com/ofws/ofwMaxim to play back sounds, and they both work great except for one small thing:

None of them seem to be completely in BPM-sync if I test them against a metronome inside Ableton Live, and they booth appear (at least to my untrained ear) to start sounding “loose/untight” the more samples I playback at once.

Has anybody experienced anything similar when trying to make a music sequencer?

Any hints / tips for alternatives / etc much appreciated!

Cheers
Andreas

#2

both ofxSequencer and the example you point out in ofxMaxim seem to use the system clock (ofGetElapsedTime*) to calculate the timing. that’s not correct for sound where the clock should come from the sound card instead of the cpu so the tick is always in sync with the sound. to get the time from the sound card you can use the sound callback in OF to get the current tick and use the sound settings to calculate the current time:

void ofApp::audioOut(ofSoundBuffer & buffer){
    auto seconds = buffer.getTickCount() * buffer.getNumFrames() /  double(buffer.getSamplerRate());
}
4 Likes
#3

That is great, thanks @arturo!

Being relatively new to OF and not having much experience with low level audio stuff, I have had some nasty encounters with void ofApp::audioOut producing really really (!) loud noise when not set properly.

I just want to play back samples, not do any low level audio output with the hazard of damaging my or other peoples ears.

How do I best use the ofApp::audioOut to keep track of time, without the risk of loud noises?

(Sorry if this is a slightly different question, and thanks again for you help!)

#4

Are you using Windows? if not you could try using ofxPDSP, it manages the audio output callback for you and its internal sequencer is sample accurate, but you have to make your own classes for step sequencing if you don’t want to simply write sequences inside the source code (you write sequences as inlined arrays).

( I still have to make libaudiodecoder work on Windows for sample file loading )

1 Like
#5

Hi @npisanti

I need it to work on Windows, did have a look at ofxPDSP otherwise - looks really great!

You would not happen to know a best practise for making sure that the void ofApp::audioOut does never produces any sound, if I want to use the clock from the sound card as @arturo suggests?

Best
Andreas

#7

just fill the buffer with zeroes:

void ofApp::audioOut(ofSoundBuffer & buffer){
   auto seconds = buffer.getTickCount() * buffer.getNumFrames() / double(buffer.getSamplerRate());

   float * samples = buffer.getBuffer().data();
   int max = buffer.getNumFrames() * buffer.getNumChannels();
   for (int n=0; n<max; ++n){
       samples[n] = 0.0f;
   }
}
1 Like
#8

Thanks a lot!

#9

you can also do buffer.set(0) but you usually want to generate your sound using the same callback that is used to calculate the time. you can attach 2 or more different listeners for the sound stream and that’s still ok since the call will happen one after another but trying to use the audioOut callback for timing and for example ofSoundPlayer to playback sounds will probably not be as accurate although might be better than using the system clock

#10

I was in doubt if commenting here or opening another thread.

I’m trying to make a master midi clock in a software, and already tried ofxBPM and ofxMSABpmTapper, which uses the high resolution mach_clock
but they both drift from time to time, in reference to other instruments.
I’ve started to write my own bpm class but it drifts anyway.
should I use audioOut for dealing with BPM, even when not using anything audio on OFW?
or just another thread? float precision is enough when using seconds as the standard unit?
Thank you

Extend to ofxSequencer
#11

i don’t know if has something related/useful to this, but I remember another addon called ofxThreadedMidiPlayer…
When I tried to trig midi sequences with it, I remember some drifts when minimizing the window or swapping between programs…

#12

hey @dimitre, I am trying to sync my ofApp to the Ableton BPM. Did you tryed some successful addon like ofxBPM or ofxMSABpmTapper? I played too with ofxSequencer but I am not sure that the sync is working fine… Any idea of what to use? Maybe some MTC clock time syncro…?

#13

I don’t need to play any audio or video stuff from OF App. I need to play time-based blink animations only…

#14

Yeah in fact you can use midi messages to sync ableton clock with yours.
Roughly it sends 24 midi messages per beat (or 48, not so sure), so if you parse them you can make any midi device (Ableton included) control your BPM.

I’ll edit this post if I find more information. message is this one

1 Like
#15
#16

new midiTimingExample included. MTC and TIMECODE

#17

Hello @arturo, I’ve finally implemented successfully a steady timer coming from the audio clock, as in your example.
In fact it is best with very low buffer size, like 32. using 512 buffers it is more erratic than ofGetElapsedTimef()

#18

I’ve made this bpm counter which is working OK, checking with an external device. I’m only puzzled by variations in intervals but it is probably floating point imprecision, or maybe beating between the audio buffer and bpm intervals.
My ideal audio buffer size now is 32. lower than this and I’ve got overflow/underflow, higher and it gets imprecise.
I’ve decided to use double all around, including a modulus function.
Any improvement ideas will be very welcome.


#define USEAUDIOOUT 1

#ifdef USEAUDIOOUT
ofSoundStream soundStream;
ofSoundStreamSettings settings;

// double lastSec;
//--------------------------------------------------------------
void audioOut(ofSoundBuffer & buffer) {
	double seconds = double(buffer.getTickCount() * buffer.getNumFrames()) / double(buffer.getSampleRate());
	bn.setSeconds(seconds);
}
#endif

void bpmUpdate() {
#ifndef USEAUDIOOUT
	bn.setSeconds(ofGetElapsedTimef());
#endif
	//bn.update();
}

void bpmSetup() {
#ifdef USEAUDIOOUT
	cout << "bpmSetup with audioOut" << endl;
	settings.setOutListener(this);
//	settings.sampleRate = 44100;
	settings.sampleRate = 48000;
	settings.numOutputChannels = 1;
	settings.numInputChannels = 0;
	//settings.bufferSize = 32;
	settings.bufferSize = 32;
	soundStream.setup(settings);
#endif
}



struct bpmNeue {
public:
	double interval, seconds;
	double bpm = 120;
	double divisions = 1;
	double percent, lastPercent;
	double clockPercent, lastClockPercent;
	double lastClockTime;
	double lastBeatTime;
	ofxMidiOut midiOut;

	bpmNeue() {
		updateDivision();
		string device = "minilogue SOUND";
		device = "UM-1G";
		device = "OP-1 Midi Device";
		cout << "bpm opening :: " << device << endl;
		cout << midiOut.openPort(device) << endl;
	}
	
	void updateDivision() {
		interval = (double)60.0 / ((double) bpm);
	}

	bool isBeat() {
		if (lastPercent > percent) {
			cout << "beat difference: " << (seconds - lastBeatTime) << endl;
			lastBeatTime = seconds;
		}
		return lastPercent > percent ;
	}

	bool isClock() {
		if (lastClockPercent > clockPercent) {
			//cout << "clock difference: " << (seconds - lastClockTime) << endl;
			lastClockTime = seconds;
		}
		return lastClockPercent > clockPercent;
	}

	void setBpm(double b) {
		bpm = b;
		updateDivision();
	}
	
	void update() {
		lastPercent = percent;
		lastClockPercent = clockPercent;
		
		// removed ofMap which uses float.
		percent = dmod(seconds, interval) / interval;
		clockPercent = dmod(seconds*24.0, interval) / interval;
//		percent = ofMap(dmod(seconds, interval), 0, interval, 0, 1);
//		clockPercent = ofMap(dmod(seconds*24.0, interval), 0, interval, 0, 1);
//		percent = ofMap(fmod(seconds, interval), 0, interval, 0, 1);
//		clockPercent = ofMap(fmod(seconds*48.0, interval), 0, interval, 0, 1);
	}
	
	double dmod(double x, double y) {
		return x - (int)(x/y) * y;
	}

	double getPercent() {
		return percent;
	}
	
	void setSeconds(double d) {
		seconds = d;
		update();
		if (isClock()) {
			midiOut.sendMidiByte(MIDI_TIME_CLOCK);
		}
		if (isBeat()) {
			
		}
	}
} bn;