1class:: BeatTrack2 2summary:: Template matching beat tracker 3categories:: UGens>Analysis, UGens>FFT 4related:: Classes/BeatTrack 5 6description:: 7This beat tracker footnote:: 8Research note: Designed by Nick Collins following work by Jean Laroche 9:: is based on exhaustively testing particular template patterns against feature streams; the testing takes place every 0.5 seconds. The two basic templates are a straight (groove=0) and a swung triplet (groove=1) pattern of 16th notes; this pattern is tried out at scalings corresponding to the tempi from 60 to 180 bpm. 10This is the cross-correlation method of beat tracking. A majority vote is taken on the best tempo detected, but this must be confirmed by a consistency check after a phase estimate. Such a consistency check helps to avoid wild fluctuating estimates, but is at the expense of an additional half second delay. 11The latency of the beat tracker with default settings is thus at least 2.5 seconds; because of block-based amortisation of calculation, it is actually around 2.8 seconds latency for a 2.0 second temporal window. 12 13This beat tracker is designed to be flexible for user needs; you can try out different window sizes, tempo weights and combinations of features. However, there are no guarantees on stability and effectiveness, and you will need to explore such parameters for a particular situation. 14 15classmethods:: 16private:: categories 17 18method:: kr 19 20argument:: busindex 21[sk] Audio input to track, already analysed into N features, passed in via a control bus number from which to retrieve consecutive streams. 22 23argument:: numfeatures 24[s] How many features (ie how many control buses) are provided 25 26argument:: windowsize 27[s] Size of the temporal window desired (2.0 to 3.0 seconds models the human temporal window). You might use longer values for stability of estimate at the expense of reactiveness. 28 29argument:: phaseaccuracy 30[s] Relates to how many different phases to test. At the default, 50 different phases spaced by phaseaccuracy seconds would be tried out for 60bpm; 16 would be tried for 180 bpm. Larger phaseaccuracy means more tests and more CPU cost. 31 32argument:: lock 33[sk] If this argument is greater than 0.5, the tracker will lock at its current periodicity and continue from the current phase. Whilst it updates the model's phase and period, this is not reflected in the output until lock goes back below 0.5. 34 35argument:: weightingscheme 36[s] Use (-2.5) for flat weighting of tempi, (-1.5) for compensation weighting based on the number of events tested (because different periods allow different numbers of events within the temporal window) or otherwise a bufnum from 0 upwards for passing an array of 120 individual tempo weights; tempi go from 60 to 179 bpm in steps of one bpm, so you must have a buffer of 120 values. 37 38returns:: 39Six k-rate outputs: 40code:: 41#beattick, eighthtick, groovetick, tempo, phase, groove = BeatTrack2.kr(busindex, numfeatures) 42:: 43 44instancemethods:: 45private:: init 46 47examples:: 48 49code:: 50 51// you should load something useful for testing, like a one minute pop song 52b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); 53 54//very feature dependent 55( 56SynthDef(\help_beattrack2_1, { |out, vol=1.0, beepvol=1.0, lock=0, bufnum| 57 var in, kbus; 58 var trackb, trackh, trackq, tempo, phase, period, groove; 59 var bsound, hsound, qsound, beep; 60 var fft; 61 var feature1, feature2, feature3; 62 63 in = PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum), 1, 0, 1); 64 //in = SoundIn.ar(0); 65 66 //Create some features 67 fft = FFT(LocalBuf(1024), in); //for sampling rates 44100 and 48000 68 //fft = FFT(LocalBuf(2048), in); //for sampling rates 88200 and 96000 69 70 feature1 = RunningSum.rms(in, 64); 71 feature2 = MFCC.kr(fft,2); //two coefficients 72 feature3 = A2K.kr(LPF.ar(in,1000)); 73 74 kbus = Out.kr(0, [feature1, feature3] ++ feature2); 75 76 //Look at four features 77 #trackb, trackh, trackq, tempo, phase, period, groove = BeatTrack2.kr(0, 4, 2.0, 0.02, lock, -2.5); 78 79 beep= SinOsc.ar(1000, 0.0, Decay.kr(trackb, 0.1)); 80 beep = Pan2.ar((vol * in) + (beepvol * beep), 0.0); 81 Out.ar(out, beep); 82}).add; 83) 84 85a = Synth(\help_beattrack2_1, [\bufnum, b]); 86a.set(\vol, 0.0); 87a.set(\vol, 1.0); 88 89a.set(\beepvol, 1.0); 90a.set(\beepvol, 0.0); 91 92a.set(\lock, 1); //fix it rigidly from current phase/period solution 93a.set(\lock, 0); //unfix, back to tracking 94 95a.free; 96:: 97 98code:: 99//same thing, trying with Onsets UGen raw output 100( 101a= SynthDef(\help_beattrack2_1, { |out, vol=1.0, beepvol=1.0, lock=0, bufnum| 102 var in, kbus; 103 var trackb, trackh, trackq, tempo, phase, period, groove; 104 var bsound, hsound, qsound, beep; 105 var fft; 106 var feature1, feature2, feature3; 107 108 in = PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum),1,0,1); 109 //in = SoundIn.ar(0); 110 111 //Create some features 112 fft = FFT(LocalBuf(1024), in); // for sampling rates 44100 and 48000 113 //fft = FFT(LocalBuf(2048), in); // for sampling rates 88200 and 96000 114 115 feature1 = Onsets.kr(fft, odftype:\mkl, rawodf:1); 116 117 feature2 = Onsets.kr(fft, odftype:\complex, rawodf:1);//two coefficients 118 119 kbus= Out.kr(0, [feature1,feature2]); 120 121 //Look at four features 122 #trackb, trackh, trackq, tempo, phase, period, groove = BeatTrack2.kr(0, 2, 3.0, 0.02, lock, -2.5); 123 124 125 beep = SinOsc.ar(1000, 0.0, Decay.kr(trackb, 0.1)); 126 beep = Pan2.ar((vol * in) + (beepvol * beep), 0.0); 127 Out.ar(out, beep); 128}).add; 129) 130 131a = Synth(\help_beattrack2_1, [\bufnum, b]); 132 133:: 134 135code:: 136//favour higher tempi in own weighting scheme 137( 138c = Array.fill(120, { |i| 0.5 + (0.5 * (i / 120)) }); 139e = Buffer.sendCollection(s, c, 1); 140) 141:: 142 143 144code:: 145// track audio in (try clapping a beat or beatboxing, but allow up to 6 seconds for tracking to begin) and spawning stuff at quarters, eighths and sixteenths 146( 147SynthDef(\help_beattrack2_2, { |out| 148 var trackb, trackh, trackq, tempo; 149 var source, kbus; 150 var bsound, hsound, qsound; 151 152 source = SoundIn.ar(0); 153 154 //downsampling automatic via kr from ar 155 kbus = Out.kr(0, LPF.ar(source, 1000)); //([feature1, feature3]++feature2); 156 157 #trackb, trackh, trackq, tempo = BeatTrack2.kr(0,1,weightingscheme: e.bufnum); 158 159 bsound = Pan2.ar(LPF.ar(WhiteNoise.ar * (Decay.kr(trackb, 0.05)), 1000), 0.0); 160 hsound = Pan2.ar(BPF.ar(WhiteNoise.ar * (Decay.kr(trackh, 0.05)), 3000, 0.66),-0.5); 161 qsound = Pan2.ar(HPF.ar(WhiteNoise.ar * (Decay.kr(trackq, 0.05)), 5000), 0.5); 162 163 Out.ar(out, source + bsound + hsound + qsound); 164}).play; 165) 166:: 167 168 169code:: 170// geometric tempo placement very similar to linear, and linear easier to deal with looking up related tempi at double and half speed 171( 172var startbps = 1, endbps = 3; 173var numtempi = 100; 174var ratio, tempi, periods; 175 176ratio = (endbps / startbps) ** (numtempi-1).reciprocal; 177 178tempi = Array.geom(numtempi, startbps, ratio); 179 180periods = tempi.reciprocal; 181 182Post << (tempi*60) << nl; 183Post << periods << nl; 184) 185 186//create linear periods 187Post << ((Array.series(120,1,2/120)).reciprocal) << nl; 188 189//tempo weights 190 Post << (Array.fill(120,{arg i; 0.2*((1.0- ((abs(i-60))/60.0))**0.5) + 0.8; })) << nl; 191:: 192 193