2 summary:: Template matching beat tracker
3 categories:: UGens>Analysis, UGens>FFT
4 related:: Classes/BeatTrack
7 This beat tracker footnote::
8 Research 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.
10 This is the cross-corellation 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.
11 The 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.
13 This 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.
21 [sk] Audio input to track, already analysed into N features, passed in via a control bus number from which to retrieve consecutive streams.
23 argument:: numfeatures
24 [s] How many features (ie how many control buses) are provided
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.
29 argument:: 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 trialed for 180 bpm. Larger phaseaccuracy means more tests and more CPU cost.
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.
35 argument:: 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.
41 #beattick, eighthtick, groovetick, tempo, phase, groove = BeatTrack2.kr(busindex, numfeatures)
50 //required for MFCCs used below
51 b = Buffer.alloc(s,1024,1); //for sampling rates 44100 and 48000
52 //b = Buffer.alloc(s,2048,1); //for sampling rates 88200 and 96000
54 //this is a one minute pop song; you should load something equivalent for testing
55 d=Buffer.read(s,"/Volumes/data/stevebeattrack/samples/100.wav");
56 d=Buffer.read(s,"/Volumes/data/stevebeattrack/samples/019.wav");
59 //very feature dependent
61 a= SynthDef(\help_beattrack2_1,{arg vol=1.0, beepvol=1.0, lock=0;
63 var trackb,trackh,trackq,tempo, phase, period, groove;
64 var bsound,hsound,qsound, beep;
66 var feature1, feature2, feature3;
68 in= PlayBuf.ar(1,d.bufnum,BufRateScale.kr(d.bufnum),1,0,1);
71 //Create some features
72 fft = FFT(b.bufnum, in);
74 feature1= RunningSum.rms(in,64);
75 feature2= MFCC.kr(fft,2); //two coefficients
76 feature3= A2K.kr(LPF.ar(in,1000));
78 kbus= Out.kr(0, [feature1, feature3]++feature2);
80 //Look at four features
81 #trackb,trackh,trackq,tempo, phase, period, groove=BeatTrack2.kr(0,4,2.0, 0.02, lock, -2.5);
83 beep= SinOsc.ar(1000,0.0,Decay.kr(trackb,0.1));
85 Out.ar(0,Pan2.ar((vol*in)+(beepvol*beep),0.0));
96 a.set(\lock,1); //fix it rigidly from current phase/period solution
97 a.set(\lock,0); //unfix, back to tracking
103 //same thing, trying with Onsets UGen raw output
105 a= SynthDef(\help_beattrack2_1,{arg vol=1.0, beepvol=1.0, lock=0;
107 var trackb,trackh,trackq,tempo, phase, period, groove;
108 var bsound,hsound,qsound, beep;
110 var feature1, feature2, feature3;
112 in= PlayBuf.ar(1,d.bufnum,BufRateScale.kr(d.bufnum),1,0,1);
113 //in = SoundIn.ar(0);
115 //Create some features
116 fft = FFT(b.bufnum, in);
118 feature1= Onsets.kr(fft,odftype:\mkl, rawodf:1);
120 feature2= Onsets.kr(fft,odftype:\complex, rawodf:1);//two coefficients
122 kbus= Out.kr(0, [feature1,feature2]);
124 //Look at four features
125 #trackb,trackh,trackq,tempo, phase, period, groove=BeatTrack2.kr(0,2,3.0, 0.02, lock, -2.5);
127 beep= SinOsc.ar(1000,0.0,Decay.kr(trackb,0.1));
129 Out.ar(0,Pan2.ar((vol*in)+(beepvol*beep),0.0));
135 //favour higher tempi in own weighting scheme
137 c=Array.fill(120,{arg i; 0.5+(0.5*(i/120))});
138 e=Buffer.sendCollection(s, c, 1);
142 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
145 SynthDef(\help_beattrack2_2,{
146 var trackb,trackh,trackq,tempo;
148 var bsound,hsound,qsound;
150 source= SoundIn.ar(0); //PlayBuf.ar(1,d.bufnum,BufRateScale.kr(d.bufnum),1,0,1);
152 //downsampling automatic via kr from ar
153 kbus= Out.kr(0, LPF.ar(source,1000)); //([feature1, feature3]++feature2);
155 #trackb,trackh,trackq,tempo=BeatTrack2.kr(0,1,weightingscheme:e.bufnum);
157 bsound= Pan2.ar(LPF.ar(WhiteNoise.ar*(Decay.kr(trackb,0.05)),1000),0.0);
159 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);
163 Out.ar(0, source + bsound+hsound+qsound);
168 geometric tempo placement very similar to linear, and linear easier to deal with looking up related tempi at double and half speed
171 var startbps= 1, endbps=3;
176 ratio= (endbps/startbps)**((numtempi-1).reciprocal);
178 tempi= Array.geom(numtempi, startbps, ratio);
180 periods= tempi.reciprocal;
182 Post << (tempi*60) << nl;
183 Post << periods << nl;
186 //create linear periods
187 Post << ((Array.series(120,1,2/120)).reciprocal) << nl;
190 Post << (Array.fill(120,{arg i; 0.2*((1.0- ((abs(i-60))/60.0))**0.5) + 0.8; })) << nl;