c@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@0
|
2
|
c@0
|
3 /*
|
c@0
|
4 QM Vamp Plugin Set
|
c@0
|
5
|
c@0
|
6 Centre for Digital Music, Queen Mary, University of London.
|
c@0
|
7 All rights reserved.
|
c@0
|
8 */
|
c@0
|
9
|
c@0
|
10 #include "BeatDetect.h"
|
c@0
|
11
|
c@3
|
12 #include <dsp/onsets/DetectionFunction.h>
|
c@3
|
13 #include <dsp/tempotracking/TempoTrack.h>
|
c@0
|
14
|
c@0
|
15 using std::string;
|
c@0
|
16 using std::vector;
|
c@0
|
17 using std::cerr;
|
c@0
|
18 using std::endl;
|
c@0
|
19
|
c@0
|
20 float BeatDetector::m_stepSecs = 0.01161;
|
c@0
|
21
|
c@0
|
22 class BeatDetectorData
|
c@0
|
23 {
|
c@0
|
24 public:
|
c@0
|
25 BeatDetectorData(const DFConfig &config) : dfConfig(config) {
|
c@0
|
26 df = new DetectionFunction(config);
|
c@0
|
27 }
|
c@0
|
28 ~BeatDetectorData() {
|
c@0
|
29 delete df;
|
c@0
|
30 }
|
c@0
|
31 void reset() {
|
c@0
|
32 delete df;
|
c@0
|
33 df = new DetectionFunction(dfConfig);
|
c@0
|
34 dfOutput.clear();
|
c@0
|
35 }
|
c@0
|
36
|
c@0
|
37 DFConfig dfConfig;
|
c@0
|
38 DetectionFunction *df;
|
c@0
|
39 vector<double> dfOutput;
|
c@0
|
40 };
|
c@0
|
41
|
c@0
|
42
|
c@0
|
43 BeatDetector::BeatDetector(float inputSampleRate) :
|
c@0
|
44 Vamp::Plugin(inputSampleRate),
|
c@0
|
45 m_d(0),
|
c@0
|
46 m_dfType(DF_COMPLEXSD)
|
c@0
|
47 {
|
c@0
|
48 }
|
c@0
|
49
|
c@0
|
50 BeatDetector::~BeatDetector()
|
c@0
|
51 {
|
c@0
|
52 delete m_d;
|
c@0
|
53 }
|
c@0
|
54
|
c@0
|
55 string
|
c@0
|
56 BeatDetector::getName() const
|
c@0
|
57 {
|
c@0
|
58 return "beats";
|
c@0
|
59 }
|
c@0
|
60
|
c@0
|
61 string
|
c@0
|
62 BeatDetector::getDescription() const
|
c@0
|
63 {
|
c@0
|
64 return "Beat Detector";
|
c@0
|
65 }
|
c@0
|
66
|
c@0
|
67 string
|
c@0
|
68 BeatDetector::getMaker() const
|
c@0
|
69 {
|
c@0
|
70 return "QMUL";
|
c@0
|
71 }
|
c@0
|
72
|
c@0
|
73 int
|
c@0
|
74 BeatDetector::getPluginVersion() const
|
c@0
|
75 {
|
c@0
|
76 return 1;
|
c@0
|
77 }
|
c@0
|
78
|
c@0
|
79 string
|
c@0
|
80 BeatDetector::getCopyright() const
|
c@0
|
81 {
|
c@0
|
82 return "Copyright (c) 2006 - All Rights Reserved";
|
c@0
|
83 }
|
c@0
|
84
|
c@0
|
85 BeatDetector::ParameterList
|
c@0
|
86 BeatDetector::getParameterDescriptors() const
|
c@0
|
87 {
|
c@0
|
88 ParameterList list;
|
c@0
|
89
|
c@0
|
90 ParameterDescriptor desc;
|
c@0
|
91 desc.name = "dftype";
|
c@0
|
92 desc.description = "Onset Detection Function Type";
|
c@0
|
93 desc.minValue = 0;
|
c@0
|
94 desc.maxValue = 3;
|
c@0
|
95 desc.defaultValue = 3;
|
c@0
|
96 desc.isQuantized = true;
|
c@0
|
97 desc.quantizeStep = 1;
|
c@0
|
98 desc.valueNames.push_back("High-Frequency Content");
|
c@0
|
99 desc.valueNames.push_back("Spectral Difference");
|
c@0
|
100 desc.valueNames.push_back("Phase Deviation");
|
c@0
|
101 desc.valueNames.push_back("Complex Domain");
|
c@0
|
102 list.push_back(desc);
|
c@0
|
103
|
c@0
|
104 return list;
|
c@0
|
105 }
|
c@0
|
106
|
c@0
|
107 float
|
c@0
|
108 BeatDetector::getParameter(std::string name) const
|
c@0
|
109 {
|
c@0
|
110 if (name == "dftype") {
|
c@0
|
111 switch (m_dfType) {
|
c@0
|
112 case DF_HFC: return 0;
|
c@0
|
113 case DF_SPECDIFF: return 1;
|
c@0
|
114 case DF_PHASEDEV: return 2;
|
c@0
|
115 default: case DF_COMPLEXSD: return 3;
|
c@0
|
116 }
|
c@0
|
117 }
|
c@0
|
118 return 0.0;
|
c@0
|
119 }
|
c@0
|
120
|
c@0
|
121 void
|
c@0
|
122 BeatDetector::setParameter(std::string name, float value)
|
c@0
|
123 {
|
c@0
|
124 if (name == "dftype") {
|
c@0
|
125 switch (lrintf(value)) {
|
c@0
|
126 case 0: m_dfType = DF_HFC; break;
|
c@0
|
127 case 1: m_dfType = DF_SPECDIFF; break;
|
c@0
|
128 case 2: m_dfType = DF_PHASEDEV; break;
|
c@0
|
129 default: case 3: m_dfType = DF_COMPLEXSD; break;
|
c@0
|
130 }
|
c@0
|
131 }
|
c@0
|
132 }
|
c@0
|
133
|
c@0
|
134 bool
|
c@0
|
135 BeatDetector::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@0
|
136 {
|
c@0
|
137 if (m_d) {
|
c@0
|
138 delete m_d;
|
c@0
|
139 m_d = 0;
|
c@0
|
140 }
|
c@0
|
141
|
c@0
|
142 if (channels < getMinChannelCount() ||
|
c@0
|
143 channels > getMaxChannelCount()) return false;
|
c@0
|
144
|
c@0
|
145 DFConfig dfConfig;
|
c@0
|
146 dfConfig.DFType = m_dfType;
|
c@0
|
147 dfConfig.stepSecs = float(stepSize) / m_inputSampleRate;
|
c@0
|
148 dfConfig.stepSize = stepSize;
|
c@0
|
149 dfConfig.frameLength = blockSize;
|
c@0
|
150
|
c@0
|
151 m_d = new BeatDetectorData(dfConfig);
|
c@0
|
152 return true;
|
c@0
|
153 }
|
c@0
|
154
|
c@0
|
155 void
|
c@0
|
156 BeatDetector::reset()
|
c@0
|
157 {
|
c@0
|
158 if (m_d) m_d->reset();
|
c@0
|
159 }
|
c@0
|
160
|
c@0
|
161 size_t
|
c@0
|
162 BeatDetector::getPreferredStepSize() const
|
c@0
|
163 {
|
c@0
|
164 return size_t(m_inputSampleRate * m_stepSecs + 0.0001);
|
c@0
|
165 }
|
c@0
|
166
|
c@0
|
167 size_t
|
c@0
|
168 BeatDetector::getPreferredBlockSize() const
|
c@0
|
169 {
|
c@0
|
170 return getPreferredStepSize() * 2;
|
c@0
|
171 }
|
c@0
|
172
|
c@0
|
173 BeatDetector::OutputList
|
c@0
|
174 BeatDetector::getOutputDescriptors() const
|
c@0
|
175 {
|
c@0
|
176 OutputList list;
|
c@0
|
177
|
c@0
|
178 OutputDescriptor beat;
|
c@0
|
179 beat.name = "beats";
|
c@0
|
180 beat.unit = "";
|
c@0
|
181 beat.description = "Detected Beats";
|
c@0
|
182 beat.hasFixedBinCount = true;
|
c@0
|
183 beat.binCount = 0;
|
c@0
|
184 beat.sampleType = OutputDescriptor::VariableSampleRate;
|
c@0
|
185 beat.sampleRate = 1.0 / m_stepSecs;
|
c@0
|
186
|
c@0
|
187 OutputDescriptor df;
|
c@0
|
188 df.name = "detection_fn";
|
c@0
|
189 df.unit = "";
|
c@0
|
190 df.description = "Beat Detection Function";
|
c@0
|
191 df.hasFixedBinCount = true;
|
c@0
|
192 df.binCount = 1;
|
c@0
|
193 df.hasKnownExtents = false;
|
c@0
|
194 df.isQuantized = false;
|
c@0
|
195 df.sampleType = OutputDescriptor::OneSamplePerStep;
|
c@0
|
196
|
c@0
|
197 list.push_back(beat);
|
c@0
|
198 list.push_back(df);
|
c@0
|
199
|
c@0
|
200 return list;
|
c@0
|
201 }
|
c@0
|
202
|
c@0
|
203 BeatDetector::FeatureSet
|
c@0
|
204 BeatDetector::process(float **inputBuffers, Vamp::RealTime /* timestamp */)
|
c@0
|
205 {
|
c@0
|
206 if (!m_d) {
|
c@0
|
207 cerr << "ERROR: BeatDetector::process: "
|
c@0
|
208 << "BeatDetector has not been initialised"
|
c@0
|
209 << endl;
|
c@0
|
210 return FeatureSet();
|
c@0
|
211 }
|
c@0
|
212
|
c@0
|
213 // convert float* to double*
|
c@0
|
214 double *tempBuffer = new double[m_d->dfConfig.frameLength];
|
c@0
|
215 for (size_t i = 0; i < m_d->dfConfig.frameLength; ++i) {
|
c@0
|
216 tempBuffer[i] = inputBuffers[0][i];
|
c@0
|
217 }
|
c@0
|
218
|
c@0
|
219 double output = m_d->df->process(tempBuffer);
|
c@0
|
220 delete[] tempBuffer;
|
c@0
|
221
|
c@0
|
222 m_d->dfOutput.push_back(output);
|
c@0
|
223
|
c@0
|
224 FeatureSet returnFeatures;
|
c@0
|
225
|
c@0
|
226 Feature feature;
|
c@0
|
227 feature.hasTimestamp = false;
|
c@0
|
228 feature.values.push_back(output);
|
c@0
|
229
|
c@0
|
230 returnFeatures[1].push_back(feature); // detection function is output 1
|
c@0
|
231 return returnFeatures;
|
c@0
|
232 }
|
c@0
|
233
|
c@0
|
234 BeatDetector::FeatureSet
|
c@0
|
235 BeatDetector::getRemainingFeatures()
|
c@0
|
236 {
|
c@0
|
237 if (!m_d) {
|
c@0
|
238 cerr << "ERROR: BeatDetector::getRemainingFeatures: "
|
c@0
|
239 << "BeatDetector has not been initialised"
|
c@0
|
240 << endl;
|
c@0
|
241 return FeatureSet();
|
c@0
|
242 }
|
c@0
|
243
|
c@0
|
244 double aCoeffs[] = { 1.0000, -0.5949, 0.2348 };
|
c@0
|
245 double bCoeffs[] = { 0.1600, 0.3200, 0.1600 };
|
c@0
|
246
|
c@0
|
247 TTParams ttParams;
|
c@0
|
248 ttParams.winLength = 512;
|
c@0
|
249 ttParams.lagLength = 128;
|
c@0
|
250 ttParams.LPOrd = 2;
|
c@0
|
251 ttParams.LPACoeffs = aCoeffs;
|
c@0
|
252 ttParams.LPBCoeffs = bCoeffs;
|
c@0
|
253 ttParams.alpha = 9;
|
c@0
|
254 ttParams.WinT.post = 8;
|
c@0
|
255 ttParams.WinT.pre = 7;
|
c@0
|
256
|
c@0
|
257 TempoTrack tempoTracker(ttParams);
|
c@0
|
258 vector<int> beats = tempoTracker.process(m_d->dfOutput);
|
c@0
|
259
|
c@0
|
260 FeatureSet returnFeatures;
|
c@0
|
261
|
c@0
|
262 for (size_t i = 0; i < beats.size(); ++i) {
|
c@0
|
263
|
c@0
|
264 size_t frame = beats[i] * m_d->dfConfig.stepSize;
|
c@0
|
265
|
c@0
|
266 Feature feature;
|
c@0
|
267 feature.hasTimestamp = true;
|
c@0
|
268 feature.timestamp = Vamp::RealTime::frame2RealTime
|
c@0
|
269 (frame, lrintf(m_inputSampleRate));
|
c@0
|
270
|
c@0
|
271 float bpm = 0.0;
|
c@0
|
272 int frameIncrement = 0;
|
c@0
|
273
|
c@0
|
274 if (i < beats.size() - 1) {
|
c@0
|
275
|
c@0
|
276 frameIncrement = (beats[i+1] - beats[i]) * m_d->dfConfig.stepSize;
|
c@0
|
277
|
c@0
|
278 // one beat is frameIncrement frames, so there are
|
c@0
|
279 // samplerate/frameIncrement bps, so
|
c@0
|
280 // 60*samplerate/frameIncrement bpm
|
c@0
|
281
|
c@0
|
282 if (frameIncrement > 0) {
|
c@0
|
283 bpm = (60.0 * m_inputSampleRate) / frameIncrement;
|
c@0
|
284 bpm = int(bpm * 100.0 + 0.5) / 100.0;
|
c@0
|
285 static char label[100];
|
c@0
|
286 sprintf(label, "%f bpm", bpm);
|
c@0
|
287 feature.label = label;
|
c@0
|
288 }
|
c@0
|
289 }
|
c@0
|
290
|
c@0
|
291 returnFeatures[0].push_back(feature); // beats are output 0
|
c@0
|
292 }
|
c@0
|
293
|
c@0
|
294 return returnFeatures;
|
c@0
|
295 }
|
c@0
|
296
|