| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 
 | class:: BeatTrack2
summary:: Template matching beat tracker
categories:: UGens>Analysis, UGens>FFT
related:: Classes/BeatTrack
description::
This beat tracker footnote::
Research note: Designed by Nick Collins following work by Jean Laroche
:: 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.
This 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.
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.
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.
classmethods::
private:: categories
method:: kr
argument:: busindex
[sk] Audio input to track, already analysed into N features, passed in via a control bus number from which to retrieve consecutive streams.
argument:: numfeatures
[s] How many features (ie how many control buses) are provided
argument:: windowsize
[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.
argument:: phaseaccuracy
[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.
argument:: lock
[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.
argument:: weightingscheme
[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.
returns::
Six k-rate outputs:
code::
#beattick, eighthtick, groovetick, tempo, phase, groove = BeatTrack2.kr(busindex, numfeatures)
::
instancemethods::
private:: init
examples::
code::
// you should load something useful for testing, like a one minute pop song
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
//very feature dependent
(
SynthDef(\help_beattrack2_1, { |out, vol=1.0, beepvol=1.0, lock=0, bufnum|
	var in, kbus;
	var trackb, trackh, trackq, tempo, phase, period, groove;
	var bsound, hsound, qsound, beep;
	var fft;
	var feature1, feature2, feature3;
	in = PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum), 1, 0, 1);
	//in = SoundIn.ar(0);
	//Create some features
	fft = FFT(LocalBuf(1024), in); //for sampling rates 44100 and 48000
	//fft = FFT(LocalBuf(2048), in); //for sampling rates 88200 and 96000
	feature1 = RunningSum.rms(in, 64);
	feature2 = MFCC.kr(fft,2); //two coefficients
	feature3 = A2K.kr(LPF.ar(in,1000));
	kbus = Out.kr(0, [feature1, feature3] ++ feature2);
	//Look at four features
	#trackb, trackh, trackq, tempo, phase, period, groove = BeatTrack2.kr(0, 4, 2.0, 0.02, lock, -2.5);
	beep= SinOsc.ar(1000, 0.0, Decay.kr(trackb, 0.1));
	beep = Pan2.ar((vol * in) + (beepvol * beep), 0.0);
	Out.ar(out, beep);
}).add;
)
a = Synth(\help_beattrack2_1, [\bufnum, b]);
a.set(\vol, 0.0);
a.set(\vol, 1.0);
a.set(\beepvol, 1.0);
a.set(\beepvol, 0.0);
a.set(\lock, 1); //fix it rigidly from current phase/period solution
a.set(\lock, 0); //unfix, back to tracking
a.free;
::
code::
//same thing, trying with Onsets UGen raw output
(
a= SynthDef(\help_beattrack2_1, { |out, vol=1.0, beepvol=1.0, lock=0, bufnum|
	var in, kbus;
	var trackb, trackh, trackq, tempo, phase, period, groove;
	var bsound, hsound, qsound, beep;
	var fft;
	var feature1, feature2, feature3;
	in = PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum),1,0,1);
	//in = SoundIn.ar(0);
	//Create some features
	fft = FFT(LocalBuf(1024), in); // for sampling rates 44100 and 48000
	//fft = FFT(LocalBuf(2048), in); // for sampling rates 88200 and 96000
	feature1 = Onsets.kr(fft, odftype:\mkl, rawodf:1);
	feature2 = Onsets.kr(fft, odftype:\complex, rawodf:1);//two coefficients
	kbus= Out.kr(0, [feature1,feature2]);
	//Look at four features
	#trackb, trackh, trackq, tempo, phase, period, groove = BeatTrack2.kr(0, 2, 3.0, 0.02, lock, -2.5);
	beep = SinOsc.ar(1000, 0.0, Decay.kr(trackb, 0.1));
	beep = Pan2.ar((vol * in) + (beepvol * beep), 0.0);
	Out.ar(out, beep);
}).add;
)
a = Synth(\help_beattrack2_1, [\bufnum, b]);
::
code::
//favour higher tempi in own weighting scheme
(
c = Array.fill(120, { |i| 0.5 + (0.5 * (i / 120)) });
e = Buffer.sendCollection(s, c, 1);
)
::
code::
// 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
(
SynthDef(\help_beattrack2_2, { |out|
	var trackb, trackh, trackq, tempo;
	var source, kbus;
	var bsound, hsound, qsound;
	source = SoundIn.ar(0);
	//downsampling automatic via kr from ar
	kbus = Out.kr(0, LPF.ar(source, 1000)); //([feature1, feature3]++feature2);
	#trackb, trackh, trackq, tempo = BeatTrack2.kr(0,1,weightingscheme: e.bufnum);
	bsound = Pan2.ar(LPF.ar(WhiteNoise.ar * (Decay.kr(trackb, 0.05)), 1000), 0.0);
	hsound = Pan2.ar(BPF.ar(WhiteNoise.ar * (Decay.kr(trackh, 0.05)), 3000, 0.66),-0.5);
	qsound = Pan2.ar(HPF.ar(WhiteNoise.ar * (Decay.kr(trackq, 0.05)), 5000), 0.5);
	Out.ar(out, source + bsound + hsound + qsound);
}).play;
)
::
code::
// geometric tempo placement very similar to linear, and linear easier to deal with looking up related tempi at double and half speed
(
var startbps = 1, endbps = 3;
var numtempi = 100;
var ratio, tempi, periods;
ratio = (endbps / startbps) ** (numtempi-1).reciprocal;
tempi = Array.geom(numtempi, startbps, ratio);
periods = tempi.reciprocal;
Post << (tempi*60) << nl;
Post << periods << nl;
)
//create linear periods
Post << ((Array.series(120,1,2/120)).reciprocal) << nl;
//tempo weights
 Post << (Array.fill(120,{arg i;  0.2*((1.0- ((abs(i-60))/60.0))**0.5) + 0.8; })) << nl;
::
 |