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