c@21
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@21
|
2
|
c@21
|
3 /*
|
c@38
|
4 QM Vamp Plugin Set
|
c@21
|
5
|
c@21
|
6 Centre for Digital Music, Queen Mary, University of London.
|
c@135
|
7
|
c@135
|
8 This program is free software; you can redistribute it and/or
|
c@135
|
9 modify it under the terms of the GNU General Public License as
|
c@135
|
10 published by the Free Software Foundation; either version 2 of the
|
c@135
|
11 License, or (at your option) any later version. See the file
|
c@135
|
12 COPYING included with this distribution for more information.
|
c@21
|
13 */
|
c@21
|
14
|
c@21
|
15 #include "KeyDetect.h"
|
c@21
|
16
|
c@21
|
17 using std::string;
|
c@21
|
18 using std::vector;
|
c@21
|
19
|
c@21
|
20 #include <cmath>
|
c@21
|
21
|
c@21
|
22
|
c@60
|
23 // Order for circle-of-5ths plotting
|
c@60
|
24 static int conversion[24] =
|
c@60
|
25 { 7, 12, 5, 10, 3, 8, 1, 6, 11, 4, 9, 2,
|
c@60
|
26 16, 21, 14, 19, 24, 17, 22, 15, 20, 13, 18, 23 };
|
c@60
|
27
|
c@60
|
28
|
c@21
|
29 KeyDetector::KeyDetector(float inputSampleRate) :
|
c@21
|
30 Plugin(inputSampleRate),
|
c@21
|
31 m_stepSize(0),
|
c@21
|
32 m_blockSize(0),
|
c@21
|
33 m_tuningFrequency(440),
|
c@21
|
34 m_length(10),
|
c@21
|
35 m_getKeyMode(0),
|
c@21
|
36 m_inputFrame(0),
|
c@21
|
37 m_prevKey(-1)
|
c@21
|
38 {
|
c@21
|
39 }
|
c@21
|
40
|
c@21
|
41 KeyDetector::~KeyDetector()
|
c@21
|
42 {
|
c@21
|
43 delete m_getKeyMode;
|
c@21
|
44 if ( m_inputFrame ) {
|
c@21
|
45 delete [] m_inputFrame;
|
c@21
|
46 }
|
c@21
|
47 }
|
c@21
|
48
|
c@21
|
49 string
|
c@22
|
50 KeyDetector::getIdentifier() const
|
c@21
|
51 {
|
c@21
|
52 return "qm-keydetector";
|
c@21
|
53 }
|
c@21
|
54
|
c@21
|
55 string
|
c@22
|
56 KeyDetector::getName() const
|
c@22
|
57 {
|
c@22
|
58 return "Key Detector";
|
c@22
|
59 }
|
c@22
|
60
|
c@22
|
61 string
|
c@21
|
62 KeyDetector::getDescription() const
|
c@21
|
63 {
|
c@50
|
64 return "Estimate the key of the music";
|
c@21
|
65 }
|
c@21
|
66
|
c@21
|
67 string
|
c@21
|
68 KeyDetector::getMaker() const
|
c@21
|
69 {
|
c@50
|
70 return "Queen Mary, University of London";
|
c@21
|
71 }
|
c@21
|
72
|
c@21
|
73 int
|
c@21
|
74 KeyDetector::getPluginVersion() const
|
c@21
|
75 {
|
cannam@244
|
76 return 6;
|
c@21
|
77 }
|
c@21
|
78
|
c@21
|
79 string
|
c@21
|
80 KeyDetector::getCopyright() const
|
c@21
|
81 {
|
cannam@242
|
82 return "Plugin by Katy Noland and Christian Landone. Copyright (c) 2006-2019 QMUL - All Rights Reserved";
|
c@21
|
83 }
|
c@21
|
84
|
c@21
|
85 KeyDetector::ParameterList
|
c@21
|
86 KeyDetector::getParameterDescriptors() const
|
c@21
|
87 {
|
c@21
|
88 ParameterList list;
|
c@21
|
89
|
c@21
|
90 ParameterDescriptor desc;
|
c@22
|
91 desc.identifier = "tuning";
|
c@22
|
92 desc.name = "Tuning Frequency";
|
c@52
|
93 desc.description = "Frequency of concert A";
|
c@21
|
94 desc.unit = "Hz";
|
c@21
|
95 desc.minValue = 420;
|
c@21
|
96 desc.maxValue = 460;
|
c@21
|
97 desc.defaultValue = 440;
|
c@21
|
98 desc.isQuantized = false;
|
c@21
|
99 list.push_back(desc);
|
c@21
|
100
|
c@22
|
101 desc.identifier = "length";
|
c@22
|
102 desc.name = "Window Length";
|
c@21
|
103 desc.unit = "chroma frames";
|
c@52
|
104 desc.description = "Number of chroma analysis frames per key estimation";
|
c@21
|
105 desc.minValue = 1;
|
c@21
|
106 desc.maxValue = 30;
|
c@21
|
107 desc.defaultValue = 10;
|
c@21
|
108 desc.isQuantized = true;
|
c@21
|
109 desc.quantizeStep = 1;
|
c@21
|
110 list.push_back(desc);
|
c@21
|
111
|
c@21
|
112 return list;
|
c@21
|
113 }
|
c@21
|
114
|
c@21
|
115 float
|
c@21
|
116 KeyDetector::getParameter(std::string param) const
|
c@21
|
117 {
|
c@21
|
118 if (param == "tuning") {
|
c@21
|
119 return m_tuningFrequency;
|
c@21
|
120 }
|
c@21
|
121 if (param == "length") {
|
cannam@244
|
122 return float(m_length);
|
c@21
|
123 }
|
c@52
|
124 std::cerr << "WARNING: KeyDetector::getParameter: unknown parameter \""
|
c@21
|
125 << param << "\"" << std::endl;
|
c@21
|
126 return 0.0;
|
c@21
|
127 }
|
c@21
|
128
|
c@21
|
129 void
|
c@21
|
130 KeyDetector::setParameter(std::string param, float value)
|
c@21
|
131 {
|
c@21
|
132 if (param == "tuning") {
|
c@21
|
133 m_tuningFrequency = value;
|
c@21
|
134 } else if (param == "length") {
|
c@21
|
135 m_length = int(value + 0.1);
|
c@21
|
136 } else {
|
c@52
|
137 std::cerr << "WARNING: KeyDetector::setParameter: unknown parameter \""
|
c@21
|
138 << param << "\"" << std::endl;
|
c@21
|
139 }
|
cannam@242
|
140
|
cannam@242
|
141 // force recalculate:
|
cannam@242
|
142 m_stepSize = 0;
|
cannam@242
|
143 m_blockSize = 0;
|
cannam@242
|
144 }
|
cannam@242
|
145
|
cannam@242
|
146 GetKeyMode::Config
|
cannam@242
|
147 KeyDetector::getConfig() const
|
cannam@242
|
148 {
|
cannam@242
|
149 GetKeyMode::Config config(m_inputSampleRate, m_tuningFrequency);
|
cannam@242
|
150 config.hpcpAverage = m_length;
|
cannam@242
|
151 config.medianAverage = m_length;
|
cannam@251
|
152 config.frameOverlapFactor = 1;
|
cannam@242
|
153 config.decimationFactor = 8;
|
cannam@242
|
154 return config;
|
c@21
|
155 }
|
c@21
|
156
|
c@21
|
157 bool
|
c@21
|
158 KeyDetector::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@21
|
159 {
|
c@21
|
160 if (m_getKeyMode) {
|
c@21
|
161 delete m_getKeyMode;
|
c@21
|
162 m_getKeyMode = 0;
|
c@21
|
163 }
|
c@21
|
164
|
c@21
|
165 if (channels < getMinChannelCount() ||
|
c@21
|
166 channels > getMaxChannelCount()) return false;
|
c@21
|
167
|
cannam@242
|
168 m_getKeyMode = new GetKeyMode(getConfig());
|
c@21
|
169
|
c@21
|
170 m_stepSize = m_getKeyMode->getHopSize();
|
c@21
|
171 m_blockSize = m_getKeyMode->getBlockSize();
|
c@21
|
172
|
c@21
|
173 if (stepSize != m_stepSize || blockSize != m_blockSize) {
|
c@52
|
174 std::cerr << "KeyDetector::initialise: ERROR: step/block sizes "
|
c@21
|
175 << stepSize << "/" << blockSize << " differ from required "
|
c@21
|
176 << m_stepSize << "/" << m_blockSize << std::endl;
|
c@21
|
177 delete m_getKeyMode;
|
c@21
|
178 m_getKeyMode = 0;
|
c@21
|
179 return false;
|
c@21
|
180 }
|
c@21
|
181
|
c@21
|
182 m_inputFrame = new double[m_blockSize];
|
c@21
|
183
|
c@21
|
184 m_prevKey = -1;
|
c@95
|
185 m_first = true;
|
c@95
|
186
|
c@21
|
187 return true;
|
c@21
|
188 }
|
c@21
|
189
|
c@21
|
190 void
|
c@21
|
191 KeyDetector::reset()
|
c@21
|
192 {
|
c@21
|
193 if (m_getKeyMode) {
|
c@21
|
194 delete m_getKeyMode;
|
cannam@242
|
195 m_getKeyMode = new GetKeyMode(getConfig());
|
c@21
|
196 }
|
c@21
|
197
|
c@21
|
198 if (m_inputFrame) {
|
c@21
|
199 for( unsigned int i = 0; i < m_blockSize; i++ ) {
|
c@21
|
200 m_inputFrame[ i ] = 0.0;
|
c@21
|
201 }
|
c@21
|
202 }
|
c@21
|
203
|
c@21
|
204 m_prevKey = -1;
|
c@95
|
205 m_first = true;
|
c@21
|
206 }
|
c@21
|
207
|
c@21
|
208
|
c@21
|
209 KeyDetector::OutputList
|
c@21
|
210 KeyDetector::getOutputDescriptors() const
|
c@21
|
211 {
|
c@21
|
212 OutputList list;
|
c@21
|
213
|
c@72
|
214 float osr = 0.0f;
|
c@72
|
215 if (m_stepSize == 0) (void)getPreferredStepSize();
|
cannam@244
|
216 osr = m_inputSampleRate / float(m_stepSize);
|
c@72
|
217
|
c@21
|
218 OutputDescriptor d;
|
c@22
|
219 d.identifier = "tonic";
|
c@22
|
220 d.name = "Tonic Pitch";
|
c@21
|
221 d.unit = "";
|
c@52
|
222 d.description = "Tonic of the estimated key (from C = 1 to B = 12)";
|
c@21
|
223 d.hasFixedBinCount = true;
|
c@21
|
224 d.binCount = 1;
|
c@21
|
225 d.hasKnownExtents = true;
|
c@21
|
226 d.isQuantized = true;
|
c@48
|
227 d.minValue = 1;
|
c@48
|
228 d.maxValue = 12;
|
c@21
|
229 d.quantizeStep = 1;
|
c@72
|
230 d.sampleRate = osr;
|
c@72
|
231 d.sampleType = OutputDescriptor::VariableSampleRate;
|
c@21
|
232 list.push_back(d);
|
c@21
|
233
|
c@22
|
234 d.identifier = "mode";
|
c@22
|
235 d.name = "Key Mode";
|
c@21
|
236 d.unit = "";
|
c@52
|
237 d.description = "Major or minor mode of the estimated key (major = 0, minor = 1)";
|
c@21
|
238 d.hasFixedBinCount = true;
|
c@21
|
239 d.binCount = 1;
|
c@21
|
240 d.hasKnownExtents = true;
|
c@21
|
241 d.isQuantized = true;
|
c@21
|
242 d.minValue = 0;
|
c@21
|
243 d.maxValue = 1;
|
c@21
|
244 d.quantizeStep = 1;
|
c@72
|
245 d.sampleRate = osr;
|
c@72
|
246 d.sampleType = OutputDescriptor::VariableSampleRate;
|
c@21
|
247 list.push_back(d);
|
c@21
|
248
|
c@22
|
249 d.identifier = "key";
|
c@22
|
250 d.name = "Key";
|
c@21
|
251 d.unit = "";
|
c@52
|
252 d.description = "Estimated key (from C major = 1 to B major = 12 and C minor = 13 to B minor = 24)";
|
c@21
|
253 d.hasFixedBinCount = true;
|
c@21
|
254 d.binCount = 1;
|
c@21
|
255 d.hasKnownExtents = true;
|
c@21
|
256 d.isQuantized = true;
|
c@48
|
257 d.minValue = 1;
|
c@48
|
258 d.maxValue = 24;
|
c@21
|
259 d.quantizeStep = 1;
|
c@72
|
260 d.sampleRate = osr;
|
c@72
|
261 d.sampleType = OutputDescriptor::VariableSampleRate;
|
c@21
|
262 list.push_back(d);
|
c@21
|
263
|
c@60
|
264 d.identifier = "keystrength";
|
c@60
|
265 d.name = "Key Strength Plot";
|
c@60
|
266 d.unit = "";
|
c@60
|
267 d.description = "Correlation of the chroma vector with stored key profile for each major and minor key";
|
c@60
|
268 d.hasFixedBinCount = true;
|
c@60
|
269 d.binCount = 25;
|
c@60
|
270 d.hasKnownExtents = false;
|
c@60
|
271 d.isQuantized = false;
|
c@73
|
272 d.sampleType = OutputDescriptor::OneSamplePerStep;
|
cannam@245
|
273 d.binNames.clear();
|
c@60
|
274 for (int i = 0; i < 24; ++i) {
|
c@60
|
275 if (i == 12) d.binNames.push_back(" ");
|
c@60
|
276 int idx = conversion[i];
|
c@63
|
277 std::string label = getKeyName(idx > 12 ? idx-12 : idx,
|
c@63
|
278 i >= 12,
|
c@63
|
279 true);
|
c@60
|
280 d.binNames.push_back(label);
|
c@60
|
281 }
|
c@60
|
282 list.push_back(d);
|
c@60
|
283
|
cannam@247
|
284 d.identifier = "mergedkeystrength";
|
cannam@247
|
285 d.name = "Merged Key Strength Plot";
|
cannam@244
|
286 d.unit = "";
|
cannam@247
|
287 d.description = "Correlation of the chroma vector with stored key profile for each key, with major and minor alternatives merged";
|
cannam@244
|
288 d.hasFixedBinCount = true;
|
cannam@244
|
289 d.binCount = 12;
|
cannam@244
|
290 d.hasKnownExtents = false;
|
cannam@244
|
291 d.isQuantized = false;
|
cannam@244
|
292 d.sampleType = OutputDescriptor::OneSamplePerStep;
|
cannam@245
|
293 d.binNames.clear();
|
cannam@244
|
294 for (int i = 0; i < 12; ++i) {
|
cannam@244
|
295 int idx = conversion[i];
|
cannam@247
|
296 std::string label = getBothKeyNames(idx > 12 ? idx-12 : idx);
|
cannam@244
|
297 d.binNames.push_back(label);
|
cannam@244
|
298 }
|
cannam@244
|
299 list.push_back(d);
|
cannam@244
|
300
|
c@21
|
301 return list;
|
c@21
|
302 }
|
c@21
|
303
|
c@21
|
304 KeyDetector::FeatureSet
|
c@21
|
305 KeyDetector::process(const float *const *inputBuffers,
|
c@21
|
306 Vamp::RealTime now)
|
c@21
|
307 {
|
c@21
|
308 if (m_stepSize == 0) {
|
c@21
|
309 return FeatureSet();
|
c@21
|
310 }
|
c@21
|
311
|
c@21
|
312 FeatureSet returnFeatures;
|
c@21
|
313
|
c@21
|
314 for ( unsigned int i = 0 ; i < m_blockSize; i++ ) {
|
c@21
|
315 m_inputFrame[i] = (double)inputBuffers[0][i];
|
c@21
|
316 }
|
c@21
|
317
|
c@21
|
318 int key = m_getKeyMode->process(m_inputFrame);
|
cannam@242
|
319
|
c@21
|
320 int tonic = key;
|
c@21
|
321 if (tonic > 12) tonic -= 12;
|
c@21
|
322
|
c@21
|
323 int prevTonic = m_prevKey;
|
c@21
|
324 if (prevTonic > 12) prevTonic -= 12;
|
c@21
|
325
|
cannam@242
|
326 bool minor = (key > 12);
|
cannam@242
|
327 bool prevMinor = (m_prevKey > 12);
|
cannam@242
|
328
|
c@95
|
329 if (m_first || (tonic != prevTonic)) {
|
c@21
|
330 Feature feature;
|
c@72
|
331 feature.hasTimestamp = true;
|
c@72
|
332 feature.timestamp = now;
|
c@21
|
333 feature.values.push_back((float)tonic);
|
c@63
|
334 feature.label = getKeyName(tonic, minor, false);
|
c@21
|
335 returnFeatures[0].push_back(feature); // tonic
|
c@21
|
336 }
|
c@21
|
337
|
cannam@242
|
338 if (m_first || (minor != prevMinor)) {
|
c@21
|
339 Feature feature;
|
c@72
|
340 feature.hasTimestamp = true;
|
c@72
|
341 feature.timestamp = now;
|
c@63
|
342 feature.values.push_back(minor ? 1.f : 0.f);
|
c@21
|
343 feature.label = (minor ? "Minor" : "Major");
|
c@21
|
344 returnFeatures[1].push_back(feature); // mode
|
c@21
|
345 }
|
c@21
|
346
|
c@95
|
347 if (m_first || (key != m_prevKey)) {
|
c@21
|
348 Feature feature;
|
c@72
|
349 feature.hasTimestamp = true;
|
c@72
|
350 feature.timestamp = now;
|
c@21
|
351 feature.values.push_back((float)key);
|
c@63
|
352 feature.label = getKeyName(tonic, minor, true);
|
c@21
|
353 returnFeatures[2].push_back(feature); // key
|
c@21
|
354 }
|
c@21
|
355
|
c@21
|
356 m_prevKey = key;
|
c@95
|
357 m_first = false;
|
c@21
|
358
|
c@60
|
359 Feature ksf;
|
cannam@244
|
360 ksf.hasTimestamp = false;
|
c@60
|
361 ksf.values.reserve(25);
|
cannam@244
|
362
|
cannam@244
|
363 Feature tsf;
|
cannam@244
|
364 tsf.hasTimestamp = false;
|
cannam@244
|
365 tsf.values.reserve(12);
|
cannam@244
|
366
|
c@60
|
367 double *keystrengths = m_getKeyMode->getKeyStrengths();
|
cannam@244
|
368
|
c@60
|
369 for (int i = 0; i < 24; ++i) {
|
cannam@244
|
370
|
c@60
|
371 if (i == 12) ksf.values.push_back(-1);
|
cannam@244
|
372 ksf.values.push_back(float(keystrengths[conversion[i]-1]));
|
cannam@244
|
373
|
cannam@244
|
374 if (i < 12) {
|
cannam@244
|
375 tsf.values.push_back(float(keystrengths[conversion[i]-1]));
|
cannam@244
|
376 } else {
|
cannam@244
|
377 tsf.values[i-12] += float(keystrengths[conversion[i]-1]);
|
cannam@244
|
378 }
|
c@60
|
379 }
|
cannam@244
|
380
|
c@60
|
381 returnFeatures[3].push_back(ksf);
|
cannam@244
|
382 returnFeatures[4].push_back(tsf);
|
c@60
|
383
|
c@21
|
384 return returnFeatures;
|
c@21
|
385 }
|
c@21
|
386
|
c@21
|
387 KeyDetector::FeatureSet
|
c@21
|
388 KeyDetector::getRemainingFeatures()
|
c@21
|
389 {
|
c@21
|
390 return FeatureSet();
|
c@21
|
391 }
|
c@21
|
392
|
c@21
|
393 size_t
|
c@21
|
394 KeyDetector::getPreferredStepSize() const
|
c@21
|
395 {
|
c@21
|
396 if (!m_stepSize) {
|
cannam@242
|
397 GetKeyMode gkm(getConfig());
|
c@21
|
398 m_stepSize = gkm.getHopSize();
|
c@21
|
399 m_blockSize = gkm.getBlockSize();
|
c@21
|
400 }
|
c@21
|
401 return m_stepSize;
|
c@21
|
402 }
|
c@21
|
403
|
c@21
|
404 size_t
|
c@21
|
405 KeyDetector::getPreferredBlockSize() const
|
c@21
|
406 {
|
c@21
|
407 if (!m_blockSize) {
|
cannam@242
|
408 GetKeyMode gkm(getConfig());
|
c@21
|
409 m_stepSize = gkm.getHopSize();
|
c@21
|
410 m_blockSize = gkm.getBlockSize();
|
c@21
|
411 }
|
c@21
|
412 return m_blockSize;
|
c@21
|
413 }
|
c@21
|
414
|
c@63
|
415 std::string
|
c@63
|
416 KeyDetector::getKeyName(int index, bool minor, bool includeMajMin) const
|
c@21
|
417 {
|
c@48
|
418 // Keys are numbered with 1 => C, 12 => B
|
c@48
|
419 // This is based on chromagram base set to a C in qm-dsp's GetKeyMode.cpp
|
c@63
|
420
|
c@63
|
421 static const char *namesMajor[] = {
|
c@63
|
422 "C", "Db", "D", "Eb",
|
c@21
|
423 "E", "F", "F# / Gb", "G",
|
c@63
|
424 "Ab", "A", "Bb", "B"
|
c@21
|
425 };
|
c@63
|
426
|
c@63
|
427 static const char *namesMinor[] = {
|
c@65
|
428 "C", "C#", "D", "Eb / D#",
|
c@65
|
429 "E", "F", "F#", "G",
|
c@65
|
430 "G#", "A", "Bb", "B"
|
c@63
|
431 };
|
c@63
|
432
|
c@21
|
433 if (index < 1 || index > 12) {
|
c@21
|
434 return "(unknown)";
|
c@21
|
435 }
|
c@63
|
436
|
c@63
|
437 std::string base;
|
c@63
|
438
|
c@63
|
439 if (minor) base = namesMinor[index - 1];
|
c@63
|
440 else base = namesMajor[index - 1];
|
c@63
|
441
|
c@63
|
442 if (!includeMajMin) return base;
|
c@63
|
443
|
c@63
|
444 if (minor) return base + " minor";
|
c@63
|
445 else return base + " major";
|
c@21
|
446 }
|
c@21
|
447
|
cannam@247
|
448 std::string
|
cannam@247
|
449 KeyDetector::getBothKeyNames(int index) const
|
cannam@247
|
450 {
|
cannam@247
|
451 // Keys are numbered with 1 => C, 12 => B
|
cannam@247
|
452
|
cannam@247
|
453 static const char *names[] = {
|
cannam@247
|
454 "C maj / A min",
|
cannam@247
|
455 "Db maj / Bb min",
|
cannam@247
|
456 "D maj / B min",
|
cannam@247
|
457 "Eb maj / C min",
|
cannam@247
|
458 "E maj / C# min",
|
cannam@247
|
459 "F maj / D min",
|
cannam@247
|
460 "F#/Gb maj / Eb/D# min",
|
cannam@247
|
461 "G maj / E min",
|
cannam@247
|
462 "Ab maj / F min",
|
cannam@247
|
463 "A maj / F# min",
|
cannam@247
|
464 "Bb maj / G min",
|
cannam@247
|
465 "B maj / G# min"
|
cannam@247
|
466 };
|
cannam@247
|
467
|
cannam@247
|
468 if (index < 1 || index > 12) {
|
cannam@247
|
469 return "(unknown)";
|
cannam@247
|
470 }
|
cannam@247
|
471
|
cannam@247
|
472 return names[index - 1];
|
cannam@247
|
473 }
|
cannam@247
|
474
|
cannam@247
|
475
|