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