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@121
|
34 #include "cq/CQSpectrogram.h"
|
c@110
|
35
|
c@121
|
36 #include "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@113
|
46 static const int defaultLowestOctave = 0;
|
c@113
|
47 static const int defaultOctaveCount = 7;
|
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@130
|
103 return "Plugin by Chris Cannam. Method by Christian Schörkhuber and Anssi Klapuri. Copyright (c) 2014 QMUL. BSD/MIT licence.";
|
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@151
|
141 desc.defaultValue = defaultTuningFrequency;
|
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
|
c@113
|
212 int midiPitchLimit = (1 + highestOctave) * 12 + 12; // C just beyond top
|
c@113
|
213 double midiPitchLimitFreq =
|
c@113
|
214 Pitch::getFrequencyForPitch(midiPitchLimit, 0, m_tuningFrequency);
|
c@113
|
215
|
c@113
|
216 // Max frequency is frequency of the MIDI pitch just beyond the
|
c@113
|
217 // top octave range (midiPitchLimit) minus one bin, then minus
|
c@113
|
218 // floor(bins per semitone / 2)
|
c@113
|
219 int bps = m_bpo / 12;
|
c@113
|
220 m_maxFrequency = midiPitchLimitFreq / pow(2, (1.0 + floor(bps/2)) / m_bpo);
|
c@113
|
221
|
c@113
|
222 // Min frequency is frequency of midiPitchLimit lowered by the
|
c@113
|
223 // appropriate number of octaves.
|
c@113
|
224 m_minFrequency = midiPitchLimitFreq / pow(2, m_octaveCount + 1);
|
c@110
|
225
|
c@147
|
226 // cerr << "lowest octave: " << m_lowestOctave << ", highest octave: "
|
c@147
|
227 // << highestOctave << ", limit midi pitch: " << midiPitchLimit
|
c@147
|
228 // << ", min freq " << m_minFrequency << ", max freq " << m_maxFrequency
|
c@147
|
229 // << endl;
|
c@110
|
230
|
c@147
|
231 reset();
|
c@147
|
232
|
c@147
|
233 if (!m_cq || !m_cq->isValid()) {
|
c@147
|
234 cerr << "CQVamp::initialise: Constant-Q parameters not valid! Not initialising" << endl;
|
c@147
|
235 return false;
|
c@147
|
236 }
|
c@110
|
237
|
c@110
|
238 return true;
|
c@110
|
239 }
|
c@110
|
240
|
c@110
|
241 void
|
c@110
|
242 CQChromaVamp::reset()
|
c@110
|
243 {
|
c@147
|
244 delete m_cq;
|
c@147
|
245 CQParameters p(m_inputSampleRate, m_minFrequency, m_maxFrequency, m_bpo);
|
c@147
|
246 m_cq = new CQSpectrogram(p, CQSpectrogram::InterpolateLinear);
|
c@147
|
247
|
c@110
|
248 m_haveStartTime = false;
|
c@147
|
249 m_startTime = Vamp::RealTime::zeroTime;
|
c@110
|
250 m_columnCount = 0;
|
c@110
|
251 }
|
c@110
|
252
|
c@110
|
253 size_t
|
c@110
|
254 CQChromaVamp::getPreferredStepSize() const
|
c@110
|
255 {
|
c@110
|
256 return 0;
|
c@110
|
257 }
|
c@110
|
258
|
c@110
|
259 size_t
|
c@110
|
260 CQChromaVamp::getPreferredBlockSize() const
|
c@110
|
261 {
|
c@110
|
262 return 0;
|
c@110
|
263 }
|
c@110
|
264
|
c@110
|
265 CQChromaVamp::OutputList
|
c@110
|
266 CQChromaVamp::getOutputDescriptors() const
|
c@110
|
267 {
|
c@110
|
268 static const char *names[] = {
|
c@110
|
269 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
|
c@110
|
270 };
|
c@110
|
271
|
c@110
|
272 OutputList list;
|
c@110
|
273
|
c@110
|
274 OutputDescriptor d;
|
c@110
|
275 d.identifier = "chromagram";
|
c@110
|
276 d.name = "Chromagram";
|
c@110
|
277 d.unit = "";
|
c@110
|
278 d.description = "Chromagram obtained from output of constant-Q transform, folding over each process block into a single-octave vector";
|
c@110
|
279 d.hasFixedBinCount = true;
|
c@110
|
280 d.binCount = m_bpo;
|
c@110
|
281
|
c@110
|
282 if (m_cq) {
|
c@110
|
283 char name[20];
|
c@110
|
284 for (int i = 0; i < (int)d.binCount; ++i) {
|
c@110
|
285 float freq = m_cq->getBinFrequency(i);
|
c@110
|
286 int note = Pitch::getPitchForFrequency(freq, 0, m_tuningFrequency);
|
c@110
|
287 float nearestFreq =
|
c@110
|
288 Pitch::getFrequencyForPitch(note, 0, m_tuningFrequency);
|
c@110
|
289 sprintf(name, "%d", i);
|
c@110
|
290 if (fabs(freq - nearestFreq) < 0.01) {
|
c@110
|
291 d.binNames.push_back(name + std::string(" ") + names[note % 12]);
|
c@110
|
292 } else {
|
c@110
|
293 d.binNames.push_back(name);
|
c@110
|
294 }
|
c@110
|
295 }
|
c@110
|
296 }
|
c@110
|
297
|
c@110
|
298 d.hasKnownExtents = false;
|
c@110
|
299 d.isQuantized = false;
|
c@110
|
300 d.sampleType = OutputDescriptor::FixedSampleRate;
|
c@110
|
301 d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256);
|
c@110
|
302 list.push_back(d);
|
c@110
|
303
|
c@110
|
304 return list;
|
c@110
|
305 }
|
c@110
|
306
|
c@110
|
307 CQChromaVamp::FeatureSet
|
c@110
|
308 CQChromaVamp::process(const float *const *inputBuffers,
|
c@110
|
309 Vamp::RealTime timestamp)
|
c@110
|
310 {
|
c@110
|
311 if (!m_cq) {
|
c@110
|
312 cerr << "ERROR: CQChromaVamp::process: "
|
c@110
|
313 << "Plugin has not been initialised"
|
c@110
|
314 << endl;
|
c@110
|
315 return FeatureSet();
|
c@110
|
316 }
|
c@110
|
317
|
c@110
|
318 if (!m_haveStartTime) {
|
c@110
|
319 m_startTime = timestamp;
|
c@110
|
320 m_haveStartTime = true;
|
c@110
|
321 }
|
c@110
|
322
|
c@110
|
323 vector<double> data;
|
c@110
|
324 for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]);
|
c@110
|
325
|
c@110
|
326 vector<vector<double> > cqout = m_cq->process(data);
|
c@110
|
327 return convertToFeatures(cqout);
|
c@110
|
328 }
|
c@110
|
329
|
c@110
|
330 CQChromaVamp::FeatureSet
|
c@110
|
331 CQChromaVamp::getRemainingFeatures()
|
c@110
|
332 {
|
c@110
|
333 vector<vector<double> > cqout = m_cq->getRemainingOutput();
|
c@110
|
334 return convertToFeatures(cqout);
|
c@110
|
335 }
|
c@110
|
336
|
c@110
|
337 CQChromaVamp::FeatureSet
|
c@110
|
338 CQChromaVamp::convertToFeatures(const vector<vector<double> > &cqout)
|
c@110
|
339 {
|
c@110
|
340 FeatureSet returnFeatures;
|
c@110
|
341
|
c@110
|
342 int width = cqout.size();
|
c@110
|
343
|
c@110
|
344 for (int i = 0; i < width; ++i) {
|
c@110
|
345
|
c@110
|
346 vector<float> column(m_bpo, 0.f);
|
c@110
|
347
|
c@110
|
348 // fold and invert to put low frequencies at the start
|
c@110
|
349
|
c@110
|
350 int thisHeight = cqout[i].size();
|
c@147
|
351
|
c@110
|
352 for (int j = 0; j < thisHeight; ++j) {
|
c@110
|
353 column[m_bpo - (j % m_bpo) - 1] += cqout[i][j];
|
c@110
|
354 }
|
c@110
|
355
|
c@110
|
356 Feature feature;
|
c@110
|
357 feature.hasTimestamp = true;
|
c@110
|
358 feature.timestamp = m_startTime + Vamp::RealTime::frame2RealTime
|
c@110
|
359 (m_columnCount * m_cq->getColumnHop() - m_cq->getLatency(),
|
c@110
|
360 m_inputSampleRate);
|
c@110
|
361 feature.values = column;
|
c@110
|
362 feature.label = "";
|
c@110
|
363
|
c@110
|
364 if (feature.timestamp >= m_startTime) {
|
c@110
|
365 returnFeatures[0].push_back(feature);
|
c@110
|
366 }
|
c@110
|
367
|
c@110
|
368 ++m_columnCount;
|
c@110
|
369 }
|
c@110
|
370
|
c@110
|
371 return returnFeatures;
|
c@110
|
372 }
|
c@110
|
373
|