comparison vamp/CQChromaVamp.cpp @ 110:fdd32f995b0d

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