We closed this forum 18 June 2010. It has served us well since 2005 as the ALPHA forum did before it from 2002 to 2005. New discussions are ongoing at the new URL http://forum.processing.org. You'll need to sign up and get a new user account. We're sorry about that inconvenience, but we think it's better in the long run. The content on this forum will remain online.
Page Index Toggle Pages: 1
Precise timing (Read 5164 times)
Precise timing
Jun 16th, 2008, 8:53am
 
Hi all,

I'm trying to create a sketch that generates MIDI notes in real-time using ProMIDI. The documentation on ProMIDI is sparse and I can't figure out how to make the Song, Track, Pattern and Sequencing features work. All I can figure out is how to send notes and ccs out. I'm sending these to Propellerheads Reason.

So I've been trying to write my own routine for sending notes in a specific interval (half notes, quarter notes, eighth notes, etc.). Basically Unfortunately, I'm running into a problem: they don't seem to be triggering precisely.

Basically, my code looks like this:

int tempoval = 120;
int note = 8;

int m = millis();



// This gets the length of a measure in milliseconds.

 int interval = (1000 / (tempoval / 60));



// This gets the length of the designated note in
// milliseconds, so in this case, interval would be 500
// and beat would = 62 (500 / 8)

 int beat = interval / note;
 
if(m % beat == 0){
 
 Note xnote = playNote();
 midiOut.sendNote(xnote);
 }



In theory, this should trigger my playNote function every time the current millisecond time has a modulo of 0 with beat. But it seems to work sporadically and the timing seems totally random.

I tried printing the current milliseconds every frame, but it jitters like a mofo.

Any thoughts? Is it possible to make things happen separate of framerate (like the Timer object in Flash)?
Re: Precise timing
Reply #1 - Jun 16th, 2008, 11:03am
 
Unfortunately the problem here is that the millis/frame is somewhat quantised, for 60 frames/second each frame will be 16.66 ms after the previous one.

So if a frame happens every 16ms, that give you frame times of 16,32,48,64 etc.. now that'll jump over your 62ms timer, and not trigger it.
What you need to do to best approximate it is check when the millis passes your beat time, and trigger then, but even that will be slightly out.
Re: Precise timing
Reply #2 - Jun 16th, 2008, 12:09pm
 
I think your problem is a bit more involved and there're several things to try out...

If you have Java 5 or newer installed on your machine you could try using System.nanoTime() instead since the old System.currentTimeMillis() of earlier versions has a notoriously bad timing resolution...

Code:
int numIterations=100000;

double min=Integer.MAX_VALUE;
double max=-min;
double avg=0;


for(int i=0; i<numIterations; i++) {
long now = System.nanoTime();
long delta = System.nanoTime()-now;
avg+=delta;
if (delta<min) min=delta;
if (delta>max) max=delta;
}

avg/=numIterations;
// scale nanoseconds back into milliseconds
avg/=1e6;
min/=1e6;
max/=1e6;
println("average: "+avg+"ms");
println("min: "+min+"ms / max: "+max+"ms");


Am getting these results here (resulotion between 1-2 MICRO seconds)

average: 0.00189156105ms
min: 0.001396ms / max: 0.844241ms

That itself is not the final answer though. You should run place your MIDI event sending into a separate thread to make it independent from the main framerate of the Processing app:

Code:
MidiThread midi;

void setup() {
size(100,100);
// create new thread running at 160bpm, bit of D'n'B
midi=new MidiThread(160);
midi.start();
}

void draw() {
// do whatever
}

// also shutdown the midi thread when the applet is stopped
public void stop() {
if (midi!=null) midi.isActive=false;
super.stop();
}

class MidiThread extends Thread {

long previousTime;
boolean isActive=true;
double interval;

MidiThread(double bpm) {
// interval currently hard coded to quarter beats
interval = 1000.0 / (bpm / 60.0);
previousTime=System.nanoTime();
}

void run() {
try {
while(isActive) {
// calculate time difference since last beat & wait if necessary
double timePassed=(System.nanoTime()-previousTime)*1.0e-6;
while(timePassed<interval) {
timePassed=(System.nanoTime()-previousTime)*1.0e-6;
}
// insert your midi event sending code here
println("midi out: "+timePassed+"ms");
// calculate real time until next beat
long delay=(long)(interval-(System.nanoTime()-previousTime)*1.0e-6);
previousTime=System.nanoTime();
Thread.sleep(delay);
}
}
catch(InterruptedException e) {
println("force quit...");
}
}
}


The output from that example (at 160bpm) should be something like:

midi out: 375.0018615722656ms
midi out: 375.0001525878906ms
midi out: 375.0001525878906ms
midi out: 375.0015563964844ms
midi out: 375.00103759765625ms
midi out: 375.0004577636719ms
midi out: 375.0004577636719ms
...

So you can see the difference in timing will be negligible (at least for musical purposes)...

Hope that helps!
Re: Precise timing
Reply #3 - Jun 16th, 2008, 12:34pm
 
Toxi, you are awesome. That's perfect. I didn't know how to run things outside of the draw() function in Processing. I'll totally have to look at your code and figure that out....

[Edit: Aha! The Thread class! I'm finding out about that over at Shiffman's site.]

[And can I just say in general, thank you guys so much for being clever and helpful!]
Re: Precise timing
Reply #4 - Aug 24th, 2008, 2:50pm
 
Hello all;

We are also wrestling with timing issues on OS X (10.4) running Java > 5 and trying both Processing 135 and 144 for good luck...

Our observed jitter with the 160 bpm code suggested by Toxi is on the order of 14ms (i.e 14/360 jitter, much too high for comfort...)

Unfortunately, the nanoTime thread sleep solution also fails to provide adequate resolution (similar observed jitter). It seems as if garbage collection and background processes are probably to blame, as the jitter comes in bursts -- rock-solid performance interrupted by occasional, unpredictable lapses.

We have tried pausing all unnecessary processes with App Stop and boosting the exported app's priority, but alas, no improvements.

Has anyone had success getting reliable system-level timing under OS X?

Thanks from sunny Montreal!
Re: Precise timing
Reply #5 - Sep 16th, 2008, 2:48pm
 
man_with_ties, I just tested the demo above on my iMac running Processing 0148, Java 6 and OSX 10.5.4 and the timing discrepancy each tick is just as under Windows, somewhere in the realm of 1/100-1/10 of a millisecond. So I guess, the fluctuation you experienced has to do with still running 10.4...

Another additional thing to try out is giving the Midi thread higher than normal priority:

Code:
midi.setPriority(Thread.NORM_PRIORITY+2); 

Re: Precise timing
Reply #6 - Feb 27th, 2009, 1:03pm
 
Hi,

I'm having severe timing problems in a midi sequencer i'm currently writing. I tested toxi's script, and i'm experiencing the same problems as men_with_ties. I've tried setting the thread priority high, but it doesn't seem to help - i'm still getting bursts of bad timing. Also, I'm running Windows XP SP2, so this is clearly not a problem isolated to OS X.  

I've been making initial attempts to get to grips with this by using the javax.sound.midi sequencer, but so far i haven't been able to get it working, I've tried some of the basic examples in the documentation, but somehow processing fails to allocate the sequencer. I'm pretty new to java, so maybe i'm missing something fundamental here. Has anyone been able to use this sequencer from processing? I'm using JDK 6 update 12. Any help would be greatly appreciated!
Re: Precise timing
Reply #7 - Feb 27th, 2009, 1:54pm
 
That's very interesting, but I really couldn't tell why this isn't working for you. Meanwhile I've released the sequencer as part of the audio package of toxiclibs, so maybe have a gander at the demo bundled with it and see if this works for you. Before releasing this as library I've also used this for a recent installation which was running for 3 months non-stop without any hiccups and you can listen to the realtime generated audio in the video over here: http://vimeo.com/2705718 - if you listen to some of the more rhythmic sections in the 2nd part of the video you can tell there're no timing issues, so I'm really stumped why you experience some. Having said this, I've not ever used the code to send actual MIDI data, but always just used it to execute other code parts (e.g. trigger audio samples). Maybe your problem lies with the MIDI library used instead I've also done a networked version of the above installation which is using OSC to communicate musical events. There were slight timing issues due to network lag/latency, though minimal enough to be ignored for our purposes...
Re: Precise timing
Reply #8 - Feb 27th, 2009, 2:04pm
 
Thanks for the quick reply, toxi! You have a valid point regarding the midi lib. However, i've tried both rwmidi (midirw?) and promidi, with the same results. It could also have something to do with midiyoke, which i use to send the midi signals. Or with the program playing the sound. So many error sources, so little time... Smiley

3 months non stop certainly sounds stable enough for my purposes! Smiley I'll have a look at it tonight and let you know if it works for me.

Edit after looking at the video: Wow, that's great work, both musically and graphically. Consider me impressed!
Re: Precise timing
Reply #9 - Feb 27th, 2009, 2:48pm
 
Hello all;

Lovely work, Toxi!

We weren't able to resolve our timing issues in Processing, for a number of tedious reasons, including but not limited to garbage collection (Java is not really optimized for real-time, though it approximates it under some circumstances).

FWIW, we have had much better success to date with Supercollider, which is also optimized for MIDI in a way that Processing is not. OSC should bridge the two if such is needed.

Regards,

men_with_ties

Re: Precise timing
Reply #10 - Feb 27th, 2009, 2:53pm
 
Slightly off-topic: toxi - I can't see your work anywhere in the exhibition. Is it Forever not Processing-based? It's really quite something.
Re: Precise timing
Reply #11 - Feb 27th, 2009, 3:23pm
 
Thanks all Smiley I fully agree that SuperCollider or Chuck etc. are better suited for musical use cases, though as I said I've been fairing quite well so far with Java too. I guess it really depends on your exact circumstances (doesn't it always?) if it's a hit or miss...

As for documenting Forever - it's actually the one recent project on which i've collaborated, in which Processing was playing a rather small role (only really used for generating 6000 unique postcards for the installation). For the main application I've decided to go the straight Java/JOGL, GLSL and Eclipse EMF route, the latter was used for generating the actual code for the music composition system. I've been meaning to write more about it since November, but simply couldn't due to ongoing crazy workload...
Re: Precise timing
Reply #12 - May 6th, 2009, 6:05pm
 
I made a few adjustments to Toki's code and thought I'd share:

Code:

class TimerThread extends Thread {

 boolean running = false;
 
 long previousTime;

 boolean isActive=true;
 
 double interval;
 double total_time_passed_real;
 double total_time_passed_ideal;
 double timing_error;      
 
 TimerThread(double new_bpm)
 {
   bpm = new_bpm;
   interval = (1000.0 / (bpm / 240)) / 96;
   previousTime=System.nanoTime();
 }
 
 void set_bpm(double new_bpm)
 {
   bpm = new_bpm;
   interval = (1000.0 / (bpm / 240)) / 96;
   total_time_passed_real = 0;
   total_time_passed_ideal = 0;
 }

 void run()
 {
   long rest_period = 0;
   
   while(this.isActive)
   {  
     if (this.running)
     {
       // put your code here...

       // calculate time difference since last beat & wait if necessary
       double timePassed=(System.nanoTime()-previousTime)*1.0e-6;
       
       // Sleep for a while
       rest_period = (long) (interval * .75);
       try
       {
         if ((rest_period > 1) && (timePassed < rest_period)) Thread.sleep(rest_period);
       }
       catch(InterruptedException e)
       {
         println("force quit...");
       }
 
       // Wake up a little early and watch the alarm clock
       while(timePassed < (interval - timing_error))
       {
         timePassed=(System.nanoTime()-previousTime)*1.0e-6;
       }
       previousTime = System.nanoTime();
 
       total_time_passed_real += timePassed;
       total_time_passed_ideal += interval;
 
       // if more time has passed between notes than should have passed, then slow down things a little
       timing_error = total_time_passed_real - total_time_passed_ideal;
     }
     else
     {
       try
       {
         Thread.sleep(100);
       }
       catch(InterruptedException e) { }
     }
   }
 }
 
 void do_stop()
 {
   this.running = false;
 }
 
 void do_start()
 {
   total_time_passed_real = 0;
   total_time_passed_ideal = 0;
   previousTime=System.nanoTime();
   this.running = true;
 }
}


Page Index Toggle Pages: 1