accurate sequencing


#1

Hi all,

A while back I started a beginners thread on accurate sequencing, eg creating a metronome.
http://forum.openframeworks.cc/t/accurate-sequencing/2377/0

That thread is now a nice resource showing 3 approaches.

using update - not so accurate, not reccomended
using oFSoundStream - sample accurate… Thanks Marek… I am using this now :smiley:
using threads… - also sounded good but threads are warned to be complex

I am now looking to do something similar on the iPhone and wondering if anyone new which of these approaches would be best.

I guess ofSoundStream or threads but I have no idea about the intricacies of these on an iphone.

Any ideas before I waste too much time would be greatly appreciated.

Many thanks

Simon


#2

i’ve been reading through thread in the beginners section you mentioned.
which method is to be used in a thread in order to achieve accuracy?

i’d be willing to test the thread mode in iPhone and share the results.


#3

Hey thanks Bruno,

I think threads was mentioned as the most accurate method of timing but there are only examples of the other two approaches.

Yesterday I got a metronome working on the iPhone using ofxiPhoneSoundStream.mm as the clock.

It works fine but I did need to update to the latest ofxiPhoneSoundStream.mm file to get it to work.

I haven’t analyzed it but it sounds pretty solid so I am not sure if there would be a benefit to using a thread but maybe when I start to add more functionality the timing might slip.

Do share your results if you do try to do a threaded version.

But this (example below) should work on an iPhone.

Cheers

Simon

// in testApp.cc

//--------------------------------------------------------------
void testApp::setup(){

SAMPLE_RATE = 44100;
pos = 0; // running sample count
bpm = 120;
setBPM(bpm*2);

ofSoundStreamSetup(1,0,this);
}

// timing mechanism
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){
tt.play();
}
}

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;
}

// in the header file you need to declare these

int pos;
float BPM;
int SAMPLE_RATE;
float lengthOfOneBeatInSamples;
int bpm;
void setBPM(float targetBPM);
void audioRequested(float *output, int bufferSize, int numChannels);
void playSound();


#4

did anyone get good results using the sound stream for timing on Windows? (I didn’t, and am looking for a way to get accurate timing on Win…)


#5

ok, been reading through the thread again and found this:

“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.”

So i should try to implement the normal method approached in there (the one which is inaccurate), but instead of doing it inside the update() or draw() methods, it should be inside a thread that sleeps for 5ms.
right?


#6

Has anyone tried the Poco Timer? It runs in a thread and is pretty accurate in my experience.

I have a wrapper class for it I made, check it out and let me know what you think, it returns the time in seconds, milliseconds and microseconds, I tried to set it up like the OF timer functions so its pretty easy to integrate

-Steve

ofxPocoTimer.zip


#7

I use something similar.
It works very good.

Bernard


#8

Hey Bernard,

Yes actually if you look at my code…I credit you :slight_smile: I based this off the poco stuff you posted a while back, just wrapped it up so its easier to deal with…works really great!

-Steve


#9

is poco supported on the iPhone?


#10

I think poco is coming to the iPhone, but isn’t in there yet.

Sequencing using the soundstream is definitely the most accurate way to do sequencing, but in order to use this you need to write to the soundstream there and then, rather than calling somesound.play(); - this is gonna be innacurate because the sound doesn’t actually come out of the speaker as you’re going through the for-loop in audioRequested(), it’s just preparing the buffer to be sent to the hardware.

For the iPhone, if you’re not too familiar with audio programming, I would say use ofxThread.

  
  
class Sequencer: public ofxThread {  
public:  
	Sequencer() {  
		startThread(true, false);  
	}  
  
	void threadedFunction() {  
		while( isThreadRunning() != 0 ){  
			long time = ofGetElapsedTimeMillis();  
			if( lock() ){  
				sequence();  
				unlock();  
				ofSleepMillis(2); // tweak this to a tolerable resolution  
			}  
		}  
	}  
	void sequence() {  
		long time = ofGetElapsedTimeMillis();  
		// work out here whether to trigger your note  
	}  
};  
  


#11

Ah yea I’m using Poco on iPhone as part of Memo’s OF build from github, its not an official release though…I think it should be incorporated soon…having Poco on iphone is so great.


#12

Thanks for this everyone… really useful resource… as ever.
Cheers

Simon


#13

[quote author=“simonblackmore”]using threads… - also sounded good but threads are warned to be complex[/quote]I just created a clock based on ofxThread and it works perfectly. I’m creating a multi-touch performance app akin to some of Ableton Live’s more basic features and found this approach to be lightweight and spot on.

My clock’s header:

  
#ifndef _CLOCK_THREAD  
#define _CLOCK_THREAD  
  
#include "ofMain.h"  
#define OF_ADDON_USING_OFXTHREAD  
#include "ofxThread.h"  
  
class testApp;  
  
class clockThread : public ofxThread  
{  
  
public:  
  
    // Timing properties  
    int notes;  
    int notesPerPhrase;  
  
    // Methods  
    void start(testApp* p);  
    void stop();  
    void threadedFunction();  
  
private:  
  
    // Parent application  
    testApp* parent;  
  
};  
  
#endif  
  

The CPP file:

  
  
#include "clockThread.h"  
#include "testApp.h"  
  
//--------------------------------------------------------------  
void clockThread::start(testApp* p)  
{  
  
    // Get parent app  
    parent = p;  
  
    // Initialize note count  
    notes = 0;  
  
    // Start thread -- blocking, venbose  
    startThread(true, false);  
  
}  
  
//--------------------------------------------------------------  
void clockThread::stop()  
{  
  
    // Stop thread  
    stopThread();  
  
}  
  
//--------------------------------------------------------------  
void clockThread::threadedFunction()  
{  
  
    // Thread is running  
    while (isThreadRunning() != 0)  
    {  
  
        // Lock thread  
        if (lock())  
        {  
  
            // Increment count and unlock  
            notes++;  
            unlock();  
  
            // Phrase complete  
            if (notes >= notesPerPhrase)  
            {  
  
                // Call function on main app  
                parent->phraseComplete();  
  
                // Reset count  
                notes = 0;  
  
            }  
  
            // Sleep for duration of one note  
            ofSleepMillis(parent->calculateNoteDuration());  
  
        }  
  
    }  
  
}  
  

Inside my test app…

  
  
#ifndef _TEST_APP  
#define _TEST_APP  
  
#include "ofMain.h"  
#include "clockThread.h"  
  
class testApp : public ofBaseApp  
{  
  
public:  
  
    // Routines  
    void setup();  
    void update();  
    void draw();  
  
    // Events  
    void phraseComplete();  
  
    // Gets note duration  
    int calculateNoteDuration();  
  
private:  
  
    // Clock  
    clockThread clock;  
    float tempo;  
  
};  
  
#endif  
  
  

And lastly, the test app CPP

  
  
#include "testApp.h"  
  
//--------------------------------------------------------------  
void testApp::setup()  
{  
  
    // Set up clock  
    tempo = 125.0f;  
    clock.notesPerPhrase = 16;  
    clock.start(this);  
  
}  
  
//--------------------------------------------------------------  
void testApp::update()  
{  
}  
  
//--------------------------------------------------------------  
void testApp::draw()  
{  
  
}  
  
//--------------------------------------------------------------  
void testApp::phraseComplete()  
{  
  
    // Play sounds here  
    // This is called exactly at every four measures at 125bpm  
  
}  
  
//--------------------------------------------------------------  
int testApp::calculateNoteDuration()  
{  
  
    // Translate tempo to milliseconds  
    return (int)floor(60000.0000f / tempo);  
  
}  
  
  

I’ve obviously removed all the sound playing code from here to strip it down to the timing. Note that I define testApp in clockThread.h and then include testApp.h in the CPP file. This allows me to have a pointer to the main application to make callbacks and adjust the sleep time to the tempo.


#14

Just thought I’d give y’all an update. The thread method worked fairly well until I started doing more stuff with the sounds. The little bit of processing each sound got when I started adding FX, etc.

The audioRequested/ofSoundStream method works best for keeping looped WAVs in sync. When I change the tempo, I simply reset my soundstream so that the buffer size is the length of one note. When I load the sounds, I figure out out how long a note is for each file (I keep tempo info in the XML).

I have a global note counter that I increment every time “audioRequested” is called, and reset to 0 whenever 16 notes are played. If the note is 0, I start any WAVs I haven’t started playing. For everything else, on every note, I move the WAV to the exact correct sample (the note number times the WAV’s length of note in samples). This usually makes no change at all, but with the conversion of time/tempo (accuracy of 1000 per second) to samples (44,100 per second), there’s bound to be some drift. There’s just no way to use setSpeed() alone to keep WAVs synced at the sample level.

Thought I’d share.


#15

Thanks for that update…that gives me a good direction where to start!

K.


Threading issue? audioRequested does not update iOS UI
#16

Hi,

It’s an old topic, but thought I’d post here, rather than start a new thread…

Just wanted to mention that I tried simonblackmore’s code, the soundstream one, and it worked great in my OF iOS project. The timing is pretty good… Thank you Simon!

I’m not 100% sure how audioRequested works, so have a few questions:

  1. The ‘pos’ variable is never reset. I think it would be ok to reset it whenever the quarter note is detected like this:
  
if(fmod(pos,lengthOfOneBeatInSamples)==0) {  
         startBeatDetected=true;  
         pos = 0;  
      }  

Tested and seems to work ok, but any problems with doing that?

  1. I replaced
  
audioRequested(float *output, int bufferSize, int numChannels)  

with

  
audioOut(float *output, int bufferSize, int numChannels)  

as I saw that audioRequested was marked as legacy in ofBaseTypes.h.
Just wondering if this is ok, just in case iOS is calling audioRequested directly not audioOut. Still, testing seemed to show it was ok…

  1. Wondering why the *2 is there in the
  
setBPM(bpm*2);  

Seems to work correctly without the *2…

  1. Also I’m assuming that the
  
 tt.play();  

should be calling whatever method you want to call at the BPM rate… e.g. the

  
 void playSound();  

in the header.

Works very nicely already though, just want to tie up the loose ends. Tested on OF073 and OF08 and both good - in fact one less error in the soundstream set up when using OF08… :slight_smile:

Thanks for any feedback…


#17

Hey Gwydion,

I am surprised this thread has picked up after so long, actually I have just been revisiting it myself and its great that you have used it for IOS and it works. Yeah I am sure you are right about resetting “pos”. It never occurred to me at the time but seems to work both ways but your way seems tidier.

The " setBPM(bpm*2); " thing is from an application I had when I was playing contrabeats so wanted it to run at double the bpm. Just ignore that.

It still works ok for me but I have had sound related crashes lately so I am wondering whether using SoundStream for timing and audio in creates any conflicts.

Too be honest I am still hacking about and not really sure what I am doing but I am glad to see that this is still a pretty solid way of doing timing. Keep us updated.

Cheers
Simon


#18

hey folks! newbie here, been looking at reasonably accurate sequencers and found this updated thread - i want to get this going and need a further update to the update on the ofSoundStream method, since *audioOut(float output, int bufferSize, int numChannels) is now deprecated.

so in the h. file should i be just referencing void audioOut(ofSoundBuffer& buffer) instead? it doesn’t seem to work. here’s the parts i have:

in the header, under public:

void audioOut(ofSoundBuffer& buffer);

in the main app as a function:
> void ofApp::audioOut(ofSoundBuffer& buffer)
> {
> bool startBeatDetected = false;
> int i;
> for (i = 0; i < buffer.size; i++) {
> samplePos++; //move the sample pos by 1
> if (fmod(samplePos, beatLengthInSamples) == 0) {
> startBeatDetected = true;
> cout << “beat detected!”;
> }
> }
> if (startBeatDetected) {
> kick.play();
> }
> }

Two errors - both on the for statement.
‘ofSoundBuffer::size’: non-standard syntax; use ‘&’ to create a pointer to member testSeq2

no conversion from ‘size_t (__thiscall ofSoundBuffer::* )(void) const’ to ‘int’ testSeq2

i’m brand new to oFx and pretty new to C++ (one intro course), but familiar with some C#. i’m figuring i probably have to declare a buffer now and then reference a pointer to the buffer?

one other thing - when i first copied this from Simon’s excellent example, it was called audioRequested, and this got changed to audioOut. does the ofSoundStreamSetup call the audioOut function in the cpp? because otherwise it would seem the function never gets called.

any assistance appreciated!


#19

@metaphysician In researching this topic myself I came across some great sequencing capabilities in ofxPDSP extremely useful addon for anyone looking to work with audio timing in openframeworks.