Commission Music

Commission Music
Bespoke Noise!!

Saturday, 27 May 2006

A class for live timing

I've just made a simple class to handle some timing issues for me. It's for a tap timer in which one can tap on a button, an external device or keyboard key in order to get timing. Useful for live situations. Also compatible with BBCut. I just wrote it today, so it probably has bugs in it or misunderstandings of how clock works, but I find it's fixing the little timing errors I get by trying to trigger things from the computer keyboard. If I'm always a bit ahead, everything works out. Easy for a tuba player (it takes like 1/16th of a second for the sound to get from a tubists lips to the end of the horn!)

To do keyboard triggering, you use the Document class. For example:

var doc, timer;

timer = TapTimer.new(32);
doc = Document.new;
doc.keyDownAction_({arg thisDoc, key;
  var time;
  if((key == $t), {
      time = Main.elapsedTime;
      timer.tap(time);
  });
});
Then, when you want something to happen according to the clock, you wrap it in a routine. From within the same doc.keyDownAction:
    if((key == $a) , {
        Routine.new({Synth(\example).play; }).play(timer.tempoclock);
    }, { if ((key == $b), {
        Routine.new({Pbind.play}).play(timer.tempoclock);
    }) });
You can also pass a clock to a Pbind, but the results don't work the way I expect them to.

Anyway, my class gets times and does a bit of averaging if you hit 't' a bunch of times in the above example. It has a start_tap method, which you would use if you wanted to start playing by triggering a sample or starting to record, but only wanted the first time you did that to be able to mess with the timing. also, it has some convenience methods for changing the phrase length, but not the clock, in case you want to make your samples play longer or shorter. And finally, I built in the idea of a maximum length because of constrains on Buffer sizes or delay lines, but if you want your taps to be indefinitely far apart, just pass in inf as the first argument to the constructor and it will do the right thing.

It's short, so here it is:

TapTimer {

 var <externalclock, last_time, <phrase_len, <tempo, <beats_per_phrase, mAX_LEN, timearr,
  <>error_margin;
 
 
 *new { arg max = 16, phrase_len = 4, beats_per_phrase = 4, error_margin = 0.01;
 
  ^super.new.init(max, phrase_len, beats_per_phrase, error_margin);
 }
 
 
 init { arg max = 16, len = 4, beats = 4, error = 0.05;
 
  mAX_LEN = max;
  phrase_len = len;
  beats_per_phrase = beats;
  last_time = 0;
  tempo = phrase_len / beats_per_phrase;
  externalclock = ExternalClock(TempoClock(tempo)).play;
  timearr = [];
  error_margin = error;
 }
 
 tempoclock {
 
  ^externalclock.tempoclock;
 }
 
 beats_per_phrase_ { arg beats;
 
  beats_per_phrase = beats;
  tempo = phrase_len / beats_per_phrase;
  externalclock = ExternalClock(TempoClock(tempo)).play;
 }
 
 
 start_tap { arg time;
 
  
  (last_time == 0). if ({
   ((time.notNil).not). if ({
    time = Main.elapsedTime;
   });
   last_time = time;
   "first tap".postln;
  });
 }
 
 
 tap  { arg time;
   var current, avg, fudge;
 
  ((time.notNil).not). if ({
   time = Main.elapsedTime;
  });
  
  (last_time == 0). if ({
   last_time = time;
  } , {
   current = time - last_time;

   (current <= mAX_LEN) .if ({

    avg = timearr.sum / timearr.size;
    fudge = error_margin * current;
    
    (( avg < ( current + fudge)) &&
     ( avg > ( current - fudge))). if ({
     
      timearr = timearr.add(current);
      phrase_len = timearr.sum / timearr.size;
      tempo = phrase_len / beats_per_phrase;
       externalclock = ExternalClock(TempoClock(tempo)).play;
    } , {
    
     phrase_len = current;
     tempo = phrase_len / beats_per_phrase;
      externalclock = ExternalClock(TempoClock(tempo)).play;
      timearr = [current];
     });
    }); 
    last_time = time;
  });
  phrase_len.postln;
 }
 
 double {
 
  var new_len;
  
  new_len = phrase_len * 2;
  
  (new_len <= mAX_LEN).if ({
   phrase_len = new_len;
  });
 
 }
 
 half {
 
  phrase_len = phrase_len / 2;
 
 }
 
 quad {
  var new_len;
  
  new_len = phrase_len * 4;
  
  (new_len <= mAX_LEN).if ({
   phrase_len = new_len;
  });
 
 }
 
 eight {
  var new_len;
  
  new_len = phrase_len * 8;
  
  (new_len <= mAX_LEN).if ({
   phrase_len = new_len;
  });
 
 }
}

I've always been more involved with coding for the interpreter, stuff like this than doing weird SynthDefs. The other day, a commenter here told me about the PitchShift UGen. I don't know if I just missed seeing it or it's new or what, but I have never heard of it before. It's so exciting! Also, all the wonky, buggy granualization code I wrote to pitch shift was for naught! Sort of. So leave a comment and tell me what your favorite Ugen is. Mine is Ringz, cuz I do love the bell sounds.

Tags: , ,

No comments: