c@110
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
c@110
|
2 /*
|
c@110
|
3 Constant-Q library
|
c@110
|
4 Copyright (c) 2013-2014 Queen Mary, University of London
|
c@110
|
5
|
c@110
|
6 Permission is hereby granted, free of charge, to any person
|
c@110
|
7 obtaining a copy of this software and associated documentation
|
c@110
|
8 files (the "Software"), to deal in the Software without
|
c@110
|
9 restriction, including without limitation the rights to use, copy,
|
c@110
|
10 modify, merge, publish, distribute, sublicense, and/or sell copies
|
c@110
|
11 of the Software, and to permit persons to whom the Software is
|
c@110
|
12 furnished to do so, subject to the following conditions:
|
c@110
|
13
|
c@110
|
14 The above copyright notice and this permission notice shall be
|
c@110
|
15 included in all copies or substantial portions of the Software.
|
c@110
|
16
|
c@110
|
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
c@110
|
18 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
c@110
|
19 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
c@110
|
20 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
c@110
|
21 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
c@110
|
22 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
c@110
|
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
c@110
|
24
|
c@110
|
25 Except as contained in this notice, the names of the Centre for
|
c@110
|
26 Digital Music; Queen Mary, University of London; and Chris Cannam
|
c@110
|
27 shall not be used in advertising or otherwise to promote the sale,
|
c@110
|
28 use or other dealings in this Software without prior written
|
c@110
|
29 authorization.
|
c@110
|
30 */
|
c@110
|
31
|
c@110
|
32 #include "CQChromaVamp.h"
|
c@110
|
33
|
c@110
|
34 #include "cpp-qm-dsp/CQSpectrogram.h"
|
c@110
|
35
|
c@110
|
36 #include "base/Pitch.h"
|
c@110
|
37
|
c@110
|
38 #include <algorithm>
|
c@110
|
39 #include <cstdio>
|
c@110
|
40
|
c@110
|
41 using std::string;
|
c@110
|
42 using std::vector;
|
c@110
|
43 using std::cerr;
|
c@110
|
44 using std::endl;
|
c@110
|
45
|
c@110
|
46 static const int defaultLowestOctave = 0;
|
c@110
|
47 static const int defaultOctaveCount = 10;
|
c@110
|
48 static const int defaultBPO = 36;
|
c@110
|
49 static const float defaultTuningFrequency = 440.f;
|
c@110
|
50
|
c@110
|
51 CQChromaVamp::CQChromaVamp(float inputSampleRate) :
|
c@110
|
52 Vamp::Plugin(inputSampleRate),
|
c@110
|
53 m_lowestOctave(defaultLowestOctave),
|
c@110
|
54 m_octaveCount(defaultOctaveCount),
|
c@110
|
55 m_tuningFrequency(defaultTuningFrequency),
|
c@110
|
56 m_bpo(defaultBPO),
|
c@110
|
57 m_cq(0),
|
c@110
|
58 m_maxFrequency(0),
|
c@110
|
59 m_minFrequency(0),
|
c@110
|
60 m_haveStartTime(false),
|
c@110
|
61 m_columnCount(0)
|
c@110
|
62 {
|
c@110
|
63 }
|
c@110
|
64
|
c@110
|
65 CQChromaVamp::~CQChromaVamp()
|
c@110
|
66 {
|
c@110
|
67 delete m_cq;
|
c@110
|
68 }
|
c@110
|
69
|
c@110
|
70 string
|
c@110
|
71 CQChromaVamp::getIdentifier() const
|
c@110
|
72 {
|
c@110
|
73 return "cqchromavamp";
|
c@110
|
74 }
|
c@110
|
75
|
c@110
|
76 string
|
c@110
|
77 CQChromaVamp::getName() const
|
c@110
|
78 {
|
c@110
|
79 return "Chromagram";
|
c@110
|
80 }
|
c@110
|
81
|
c@110
|
82 string
|
c@110
|
83 CQChromaVamp::getDescription() const
|
c@110
|
84 {
|
c@110
|
85 return "Extract a Constant-Q spectrogram with constant ratio of centre frequency to resolution from the audio, then wrapping it around into a single-octave chromagram.";
|
c@110
|
86 }
|
c@110
|
87
|
c@110
|
88 string
|
c@110
|
89 CQChromaVamp::getMaker() const
|
c@110
|
90 {
|
c@110
|
91 return "Queen Mary, University of London";
|
c@110
|
92 }
|
c@110
|
93
|
c@110
|
94 int
|
c@110
|
95 CQChromaVamp::getPluginVersion() const
|
c@110
|
96 {
|
c@110
|
97 return 1;
|
c@110
|
98 }
|
c@110
|
99
|
c@110
|
100 string
|
c@110
|
101 CQChromaVamp::getCopyright() const
|
c@110
|
102 {
|
c@110
|
103 return "Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2013 QMUL";
|
c@110
|
104 }
|
c@110
|
105
|
c@110
|
106 CQChromaVamp::ParameterList
|
c@110
|
107 CQChromaVamp::getParameterDescriptors() const
|
c@110
|
108 {
|
c@110
|
109 ParameterList list;
|
c@110
|
110
|
c@110
|
111 ParameterDescriptor desc;
|
c@110
|
112
|
c@110
|
113 desc.identifier = "lowestoct";
|
c@110
|
114 desc.name = "Lowest Contributing Octave";
|
c@110
|
115 desc.unit = "";
|
c@110
|
116 desc.description = "Octave number of the lowest octave to include in the chromagram. Octave numbering is ASA standard, with -1 as the first octave in the MIDI range and middle-C being C4. The octave starts at C.";
|
c@110
|
117 desc.minValue = -1;
|
c@110
|
118 desc.maxValue = 12;
|
c@110
|
119 desc.defaultValue = defaultLowestOctave;
|
c@110
|
120 desc.isQuantized = true;
|
c@110
|
121 desc.quantizeStep = 1;
|
c@110
|
122 list.push_back(desc);
|
c@110
|
123
|
c@110
|
124 desc.identifier = "octaves";
|
c@110
|
125 desc.name = "Contributing Octave Count";
|
c@110
|
126 desc.unit = "octaves";
|
c@110
|
127 desc.description = "Number of octaves to use when generating the Constant-Q transform. All octaves are wrapped around and summed to produce a single octave chromagram as output.";
|
c@110
|
128 desc.minValue = 1;
|
c@110
|
129 desc.maxValue = 12;
|
c@110
|
130 desc.defaultValue = defaultOctaveCount;
|
c@110
|
131 desc.isQuantized = true;
|
c@110
|
132 desc.quantizeStep = 1;
|
c@110
|
133 list.push_back(desc);
|
c@110
|
134
|
c@110
|
135 desc.identifier = "tuning";
|
c@110
|
136 desc.name = "Tuning Frequency";
|
c@110
|
137 desc.unit = "Hz";
|
c@110
|
138 desc.description = "Frequency of concert A";
|
c@110
|
139 desc.minValue = 360;
|
c@110
|
140 desc.maxValue = 500;
|
c@110
|
141 desc.defaultValue = 440;
|
c@110
|
142 desc.isQuantized = false;
|
c@110
|
143 list.push_back(desc);
|
c@110
|
144
|
c@110
|
145 desc.identifier = "bpo";
|
c@110
|
146 desc.name = "Bins per Octave";
|
c@110
|
147 desc.unit = "bins";
|
c@110
|
148 desc.description = "Number of constant-Q transform bins per octave";
|
c@110
|
149 desc.minValue = 2;
|
c@110
|
150 desc.maxValue = 480;
|
c@110
|
151 desc.defaultValue = defaultBPO;
|
c@110
|
152 desc.isQuantized = true;
|
c@110
|
153 desc.quantizeStep = 1;
|
c@110
|
154 list.push_back(desc);
|
c@110
|
155
|
c@110
|
156 return list;
|
c@110
|
157 }
|
c@110
|
158
|
c@110
|
159 float
|
c@110
|
160 CQChromaVamp::getParameter(std::string param) const
|
c@110
|
161 {
|
c@110
|
162 if (param == "lowestoct") {
|
c@110
|
163 return m_lowestOctave;
|
c@110
|
164 }
|
c@110
|
165 if (param == "octaves") {
|
c@110
|
166 return m_octaveCount;
|
c@110
|
167 }
|
c@110
|
168 if (param == "tuning") {
|
c@110
|
169 return m_tuningFrequency;
|
c@110
|
170 }
|
c@110
|
171 if (param == "bpo") {
|
c@110
|
172 return m_bpo;
|
c@110
|
173 }
|
c@110
|
174 std::cerr << "WARNING: CQChromaVamp::getParameter: unknown parameter \""
|
c@110
|
175 << param << "\"" << std::endl;
|
c@110
|
176 return 0.0;
|
c@110
|
177 }
|
c@110
|
178
|
c@110
|
179 void
|
c@110
|
180 CQChromaVamp::setParameter(std::string param, float value)
|
c@110
|
181 {
|
c@110
|
182 if (param == "lowestoct") {
|
c@110
|
183 m_lowestOctave = lrintf(value);
|
c@110
|
184 } else if (param == "octaves") {
|
c@110
|
185 m_octaveCount = lrintf(value);
|
c@110
|
186 } else if (param == "tuning") {
|
c@110
|
187 m_tuningFrequency = value;
|
c@110
|
188 } else if (param == "bpo") {
|
c@110
|
189 m_bpo = lrintf(value);
|
c@110
|
190 } else {
|
c@110
|
191 std::cerr << "WARNING: CQChromaVamp::setParameter: unknown parameter \""
|
c@110
|
192 << param << "\"" << std::endl;
|
c@110
|
193 }
|
c@110
|
194 }
|
c@110
|
195
|
c@110
|
196 bool
|
c@110
|
197 CQChromaVamp::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
c@110
|
198 {
|
c@110
|
199 if (m_cq) {
|
c@110
|
200 delete m_cq;
|
c@110
|
201 m_cq = 0;
|
c@110
|
202 }
|
c@110
|
203
|
c@110
|
204 if (channels < getMinChannelCount() ||
|
c@110
|
205 channels > getMaxChannelCount()) return false;
|
c@110
|
206
|
c@110
|
207 m_stepSize = stepSize;
|
c@110
|
208 m_blockSize = blockSize;
|
c@110
|
209
|
c@110
|
210 int highestOctave = m_lowestOctave + m_octaveCount - 1;
|
c@110
|
211 int highestMIDIPitch = (1 + highestOctave) * 12 + 11;
|
c@110
|
212
|
c@110
|
213 m_maxFrequency = Pitch::getFrequencyForPitch
|
c@110
|
214 (highestMIDIPitch, 0, m_tuningFrequency);
|
c@110
|
215 m_minFrequency = m_maxFrequency / pow(2, m_octaveCount + 1) *
|
c@110
|
216 pow(2, 1.0 / m_bpo);
|
c@110
|
217
|
c@110
|
218 cerr << "lowest octave: " << m_lowestOctave << ", highest octave: "
|
c@110
|
219 << highestOctave << ", highest midi pitch: " << highestMIDIPitch
|
c@110
|
220 << ", min freq " << m_minFrequency << ", max freq " << m_maxFrequency
|
c@110
|
221 << endl;
|
c@110
|
222
|
c@110
|
223 m_cq = new CQSpectrogram
|
c@110
|
224 (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo,
|
c@110
|
225 CQSpectrogram::InterpolateLinear);
|
c@110
|
226
|
c@110
|
227 return true;
|
c@110
|
228 }
|
c@110
|
229
|
c@110
|
230 void
|
c@110
|
231 CQChromaVamp::reset()
|
c@110
|
232 {
|
c@110
|
233 if (m_cq) {
|
c@110
|
234 delete m_cq;
|
c@110
|
235 m_cq = new CQSpectrogram
|
c@110
|
236 (m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo,
|
c@110
|
237 CQSpectrogram::InterpolateLinear);
|
c@110
|
238 }
|
c@110
|
239 m_haveStartTime = false;
|
c@110
|
240 m_columnCount = 0;
|
c@110
|
241 }
|
c@110
|
242
|
c@110
|
243 size_t
|
c@110
|
244 CQChromaVamp::getPreferredStepSize() const
|
c@110
|
245 {
|
c@110
|
246 return 0;
|
c@110
|
247 }
|
c@110
|
248
|
c@110
|
249 size_t
|
c@110
|
250 CQChromaVamp::getPreferredBlockSize() const
|
c@110
|
251 {
|
c@110
|
252 return 0;
|
c@110
|
253 }
|
c@110
|
254
|
c@110
|
255 CQChromaVamp::OutputList
|
c@110
|
256 CQChromaVamp::getOutputDescriptors() const
|
c@110
|
257 {
|
c@110
|
258 static const char *names[] = {
|
c@110
|
259 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
|
c@110
|
260 };
|
c@110
|
261
|
c@110
|
262 OutputList list;
|
c@110
|
263
|
c@110
|
264 OutputDescriptor d;
|
c@110
|
265 d.identifier = "chromagram";
|
c@110
|
266 d.name = "Chromagram";
|
c@110
|
267 d.unit = "";
|
c@110
|
268 d.description = "Chromagram obtained from output of constant-Q transform, folding over each process block into a single-octave vector";
|
c@110
|
269 d.hasFixedBinCount = true;
|
c@110
|
270 d.binCount = m_bpo;
|
c@110
|
271
|
c@110
|
272 if (m_cq) {
|
c@110
|
273 char name[20];
|
c@110
|
274 for (int i = 0; i < (int)d.binCount; ++i) {
|
c@110
|
275 float freq = m_cq->getBinFrequency(i);
|
c@110
|
276 int note = Pitch::getPitchForFrequency(freq, 0, m_tuningFrequency);
|
c@110
|
277 float nearestFreq =
|
c@110
|
278 Pitch::getFrequencyForPitch(note, 0, m_tuningFrequency);
|
c@110
|
279 sprintf(name, "%d", i);
|
c@110
|
280 if (fabs(freq - nearestFreq) < 0.01) {
|
c@110
|
281 d.binNames.push_back(name + std::string(" ") + names[note % 12]);
|
c@110
|
282 } else {
|
c@110
|
283 d.binNames.push_back(name);
|
c@110
|
284 }
|
c@110
|
285 }
|
c@110
|
286 }
|
c@110
|
287
|
c@110
|
288 d.hasKnownExtents = false;
|
c@110
|
289 d.isQuantized = false;
|
c@110
|
290 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@110
|
291 d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256);
|
c@110
|
292 list.push_back(d);
|
c@110
|
293
|
c@110
|
294 return list;
|
c@110
|
295 }
|
c@110
|
296
|
c@110
|
297 CQChromaVamp::FeatureSet
|
c@110
|
298 CQChromaVamp::process(const float *const *inputBuffers,
|
c@110
|
299 Vamp::RealTime timestamp)
|
c@110
|
300 {
|
c@110
|
301 if (!m_cq) {
|
c@110
|
302 cerr << "ERROR: CQChromaVamp::process: "
|
c@110
|
303 << "Plugin has not been initialised"
|
c@110
|
304 << endl;
|
c@110
|
305 return FeatureSet();
|
c@110
|
306 }
|
c@110
|
307
|
c@110
|
308 if (!m_haveStartTime) {
|
c@110
|
309 m_startTime = timestamp;
|
c@110
|
310 m_haveStartTime = true;
|
c@110
|
311 }
|
c@110
|
312
|
c@110
|
313 vector<double> data;
|
c@110
|
314 for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]);
|
c@110
|
315
|
c@110
|
316 vector<vector<double> > cqout = m_cq->process(data);
|
c@110
|
317 return convertToFeatures(cqout);
|
c@110
|
318 }
|
c@110
|
319
|
c@110
|
320 CQChromaVamp::FeatureSet
|
c@110
|
321 CQChromaVamp::getRemainingFeatures()
|
c@110
|
322 {
|
c@110
|
323 vector<vector<double> > cqout = m_cq->getRemainingOutput();
|
c@110
|
324 return convertToFeatures(cqout);
|
c@110
|
325 }
|
c@110
|
326
|
c@110
|
327 CQChromaVamp::FeatureSet
|
c@110
|
328 CQChromaVamp::convertToFeatures(const vector<vector<double> > &cqout)
|
c@110
|
329 {
|
c@110
|
330 FeatureSet returnFeatures;
|
c@110
|
331
|
c@110
|
332 int width = cqout.size();
|
c@110
|
333
|
c@110
|
334 for (int i = 0; i < width; ++i) {
|
c@110
|
335
|
c@110
|
336 vector<float> column(m_bpo, 0.f);
|
c@110
|
337
|
c@110
|
338 // fold and invert to put low frequencies at the start
|
c@110
|
339
|
c@110
|
340 int thisHeight = cqout[i].size();
|
c@110
|
341 for (int j = 0; j < thisHeight; ++j) {
|
c@110
|
342 column[m_bpo - (j % m_bpo) - 1] += cqout[i][j];
|
c@110
|
343 }
|
c@110
|
344
|
c@110
|
345 Feature feature;
|
c@110
|
346 feature.hasTimestamp = true;
|
c@110
|
347 feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime
|
c@110
|
348 (m_columnCount * m_cq->getColumnHop() - m_cq->getLatency(),
|
c@110
|
349 m_inputSampleRate);
|
c@110
|
350 feature.values = column;
|
c@110
|
351 feature.label = "";
|
c@110
|
352
|
c@110
|
353 if (feature.timestamp >= m_startTime) {
|
c@110
|
354 returnFeatures[0].push_back(feature);
|
c@110
|
355 }
|
c@110
|
356
|
c@110
|
357 ++m_columnCount;
|
c@110
|
358 }
|
c@110
|
359
|
c@110
|
360 return returnFeatures;
|
c@110
|
361 }
|
c@110
|
362
|