Chris@7
|
1 /*
|
Chris@7
|
2 CHP
|
Chris@7
|
3 Copyright (c) 2014 Queen Mary, University of London
|
Chris@7
|
4
|
Chris@7
|
5 Permission is hereby granted, free of charge, to any person
|
Chris@7
|
6 obtaining a copy of this software and associated documentation
|
Chris@7
|
7 files (the "Software"), to deal in the Software without
|
Chris@7
|
8 restriction, including without limitation the rights to use, copy,
|
Chris@7
|
9 modify, merge, publish, distribute, sublicense, and/or sell copies
|
Chris@7
|
10 of the Software, and to permit persons to whom the Software is
|
Chris@7
|
11 furnished to do so, subject to the following conditions:
|
Chris@7
|
12
|
Chris@7
|
13 The above copyright notice and this permission notice shall be
|
Chris@7
|
14 included in all copies or substantial portions of the Software.
|
Chris@7
|
15
|
Chris@7
|
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
Chris@7
|
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
Chris@7
|
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
Chris@7
|
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
Chris@7
|
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
Chris@7
|
21 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
Chris@7
|
22 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
Chris@7
|
23 */
|
Chris@0
|
24
|
Chris@0
|
25 #include "ConstrainedHarmonicPeak.h"
|
Chris@0
|
26
|
Chris@0
|
27 #include <cmath>
|
Chris@0
|
28 #include <cstdio>
|
Chris@14
|
29 #include <climits>
|
Chris@0
|
30
|
Chris@0
|
31 using std::cerr;
|
Chris@0
|
32 using std::endl;
|
Chris@0
|
33 using std::vector;
|
Chris@0
|
34
|
Chris@0
|
35 ConstrainedHarmonicPeak::ConstrainedHarmonicPeak(float inputSampleRate) :
|
Chris@0
|
36 Plugin(inputSampleRate),
|
Chris@4
|
37 m_fftSize(0),
|
Chris@0
|
38 m_minFreq(0),
|
Chris@6
|
39 m_maxFreq(22050),
|
Chris@1
|
40 m_harmonics(5)
|
Chris@0
|
41 {
|
Chris@0
|
42 }
|
Chris@0
|
43
|
Chris@0
|
44 ConstrainedHarmonicPeak::~ConstrainedHarmonicPeak()
|
Chris@0
|
45 {
|
Chris@0
|
46 }
|
Chris@0
|
47
|
Chris@0
|
48 string
|
Chris@0
|
49 ConstrainedHarmonicPeak::getIdentifier() const
|
Chris@0
|
50 {
|
Chris@0
|
51 return "constrainedharmonicpeak";
|
Chris@0
|
52 }
|
Chris@0
|
53
|
Chris@0
|
54 string
|
Chris@0
|
55 ConstrainedHarmonicPeak::getName() const
|
Chris@0
|
56 {
|
Chris@0
|
57 return "Frequency-Constrained Harmonic Peak";
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 string
|
Chris@0
|
61 ConstrainedHarmonicPeak::getDescription() const
|
Chris@0
|
62 {
|
Chris@6
|
63 return "Return the interpolated peak frequency of a harmonic product spectrum within a given frequency range";
|
Chris@0
|
64 }
|
Chris@0
|
65
|
Chris@0
|
66 string
|
Chris@0
|
67 ConstrainedHarmonicPeak::getMaker() const
|
Chris@0
|
68 {
|
Chris@0
|
69 return "Queen Mary, University of London";
|
Chris@0
|
70 }
|
Chris@0
|
71
|
Chris@0
|
72 int
|
Chris@0
|
73 ConstrainedHarmonicPeak::getPluginVersion() const
|
Chris@0
|
74 {
|
Chris@0
|
75 return 1;
|
Chris@0
|
76 }
|
Chris@0
|
77
|
Chris@0
|
78 string
|
Chris@0
|
79 ConstrainedHarmonicPeak::getCopyright() const
|
Chris@0
|
80 {
|
Chris@0
|
81 return "GPL";
|
Chris@0
|
82 }
|
Chris@0
|
83
|
Chris@0
|
84 ConstrainedHarmonicPeak::InputDomain
|
Chris@0
|
85 ConstrainedHarmonicPeak::getInputDomain() const
|
Chris@0
|
86 {
|
Chris@0
|
87 return FrequencyDomain;
|
Chris@0
|
88 }
|
Chris@0
|
89
|
Chris@0
|
90 size_t
|
Chris@0
|
91 ConstrainedHarmonicPeak::getPreferredBlockSize() const
|
Chris@0
|
92 {
|
Chris@0
|
93 return 2048;
|
Chris@0
|
94 }
|
Chris@0
|
95
|
Chris@0
|
96 size_t
|
Chris@0
|
97 ConstrainedHarmonicPeak::getPreferredStepSize() const
|
Chris@0
|
98 {
|
Chris@0
|
99 return 512;
|
Chris@0
|
100 }
|
Chris@0
|
101
|
Chris@0
|
102 size_t
|
Chris@0
|
103 ConstrainedHarmonicPeak::getMinChannelCount() const
|
Chris@0
|
104 {
|
Chris@0
|
105 return 1;
|
Chris@0
|
106 }
|
Chris@0
|
107
|
Chris@0
|
108 size_t
|
Chris@0
|
109 ConstrainedHarmonicPeak::getMaxChannelCount() const
|
Chris@0
|
110 {
|
Chris@0
|
111 return 1;
|
Chris@0
|
112 }
|
Chris@0
|
113
|
Chris@0
|
114 ConstrainedHarmonicPeak::ParameterList
|
Chris@0
|
115 ConstrainedHarmonicPeak::getParameterDescriptors() const
|
Chris@0
|
116 {
|
Chris@0
|
117 ParameterList list;
|
Chris@0
|
118
|
Chris@0
|
119 ParameterDescriptor d;
|
Chris@0
|
120 d.identifier = "minfreq";
|
Chris@0
|
121 d.name = "Minimum frequency";
|
Chris@6
|
122 d.description = "Minimum frequency for peak finding. Will be rounded down to the nearest spectral bin.";
|
Chris@0
|
123 d.unit = "Hz";
|
Chris@0
|
124 d.minValue = 0;
|
Chris@0
|
125 d.maxValue = m_inputSampleRate/2;
|
Chris@0
|
126 d.defaultValue = 0;
|
Chris@0
|
127 d.isQuantized = false;
|
Chris@0
|
128 list.push_back(d);
|
Chris@0
|
129
|
Chris@0
|
130 d.identifier = "maxfreq";
|
Chris@0
|
131 d.name = "Maximum frequency";
|
Chris@6
|
132 d.description = "Maximum frequency for peak finding. Will be rounded up to the nearest spectral bin.";
|
Chris@0
|
133 d.unit = "Hz";
|
Chris@0
|
134 d.minValue = 0;
|
Chris@0
|
135 d.maxValue = m_inputSampleRate/2;
|
Chris@6
|
136 d.defaultValue = 22050;
|
Chris@0
|
137 d.isQuantized = false;
|
Chris@0
|
138 list.push_back(d);
|
Chris@0
|
139
|
Chris@1
|
140 d.identifier = "harmonics";
|
Chris@1
|
141 d.name = "Harmonics";
|
Chris@1
|
142 d.description = "Maximum number of harmonics to consider";
|
Chris@1
|
143 d.unit = "";
|
Chris@1
|
144 d.minValue = 1;
|
Chris@1
|
145 d.maxValue = 20;
|
Chris@1
|
146 d.defaultValue = 5;
|
Chris@1
|
147 d.isQuantized = true;
|
Chris@1
|
148 d.quantizeStep = 1;
|
Chris@1
|
149 list.push_back(d);
|
Chris@1
|
150
|
Chris@0
|
151 return list;
|
Chris@0
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 float
|
Chris@0
|
155 ConstrainedHarmonicPeak::getParameter(string identifier) const
|
Chris@0
|
156 {
|
Chris@0
|
157 if (identifier == "minfreq") {
|
Chris@0
|
158 return m_minFreq;
|
Chris@0
|
159 } else if (identifier == "maxfreq") {
|
Chris@0
|
160 return m_maxFreq;
|
Chris@1
|
161 } else if (identifier == "harmonics") {
|
Chris@14
|
162 return float(m_harmonics);
|
Chris@0
|
163 }
|
Chris@0
|
164 return 0;
|
Chris@0
|
165 }
|
Chris@0
|
166
|
Chris@0
|
167 void
|
Chris@0
|
168 ConstrainedHarmonicPeak::setParameter(string identifier, float value)
|
Chris@0
|
169 {
|
Chris@0
|
170 if (identifier == "minfreq") {
|
Chris@0
|
171 m_minFreq = value;
|
Chris@0
|
172 } else if (identifier == "maxfreq") {
|
Chris@0
|
173 m_maxFreq = value;
|
Chris@1
|
174 } else if (identifier == "harmonics") {
|
Chris@1
|
175 m_harmonics = int(round(value));
|
Chris@0
|
176 }
|
Chris@0
|
177 }
|
Chris@0
|
178
|
Chris@0
|
179 ConstrainedHarmonicPeak::ProgramList
|
Chris@0
|
180 ConstrainedHarmonicPeak::getPrograms() const
|
Chris@0
|
181 {
|
Chris@0
|
182 ProgramList list;
|
Chris@0
|
183 return list;
|
Chris@0
|
184 }
|
Chris@0
|
185
|
Chris@0
|
186 string
|
Chris@0
|
187 ConstrainedHarmonicPeak::getCurrentProgram() const
|
Chris@0
|
188 {
|
Chris@0
|
189 return ""; // no programs
|
Chris@0
|
190 }
|
Chris@0
|
191
|
Chris@0
|
192 void
|
Chris@14
|
193 ConstrainedHarmonicPeak::selectProgram(string)
|
Chris@0
|
194 {
|
Chris@0
|
195 }
|
Chris@0
|
196
|
Chris@0
|
197 ConstrainedHarmonicPeak::OutputList
|
Chris@0
|
198 ConstrainedHarmonicPeak::getOutputDescriptors() const
|
Chris@0
|
199 {
|
Chris@0
|
200 OutputList list;
|
Chris@0
|
201
|
Chris@0
|
202 OutputDescriptor d;
|
Chris@0
|
203 d.identifier = "peak";
|
Chris@0
|
204 d.name = "Peak frequency";
|
Chris@6
|
205 d.description = "Interpolated frequency of the harmonic spectral peak within the given frequency range";
|
Chris@0
|
206 d.unit = "Hz";
|
Chris@0
|
207 d.sampleType = OutputDescriptor::OneSamplePerStep;
|
Chris@0
|
208 d.hasDuration = false;
|
Chris@0
|
209 list.push_back(d);
|
Chris@0
|
210
|
Chris@0
|
211 return list;
|
Chris@0
|
212 }
|
Chris@0
|
213
|
Chris@0
|
214 bool
|
Chris@14
|
215 ConstrainedHarmonicPeak::initialise(size_t channels, size_t, size_t blockSize)
|
Chris@0
|
216 {
|
Chris@0
|
217 if (channels < getMinChannelCount() ||
|
Chris@0
|
218 channels > getMaxChannelCount()) {
|
Chris@0
|
219 cerr << "ConstrainedHarmonicPeak::initialise: ERROR: channels " << channels
|
Chris@0
|
220 << " out of acceptable range " << getMinChannelCount()
|
Chris@0
|
221 << " -> " << getMaxChannelCount() << endl;
|
Chris@0
|
222 return false;
|
Chris@0
|
223 }
|
Chris@0
|
224
|
Chris@14
|
225 if (blockSize > INT_MAX) {
|
Chris@14
|
226 return false; // arf
|
Chris@14
|
227 }
|
Chris@14
|
228
|
Chris@14
|
229 m_fftSize = int(blockSize);
|
Chris@0
|
230
|
Chris@0
|
231 return true;
|
Chris@0
|
232 }
|
Chris@0
|
233
|
Chris@0
|
234 void
|
Chris@0
|
235 ConstrainedHarmonicPeak::reset()
|
Chris@0
|
236 {
|
Chris@0
|
237 }
|
Chris@0
|
238
|
Chris@1
|
239 double
|
Chris@1
|
240 ConstrainedHarmonicPeak::findInterpolatedPeak(const double *in,
|
Chris@1
|
241 int peakbin,
|
Chris@1
|
242 int bins)
|
Chris@1
|
243 {
|
Chris@1
|
244 // duplicate with SimpleCepstrum plugin
|
Chris@1
|
245 // after jos,
|
Chris@1
|
246 // https://ccrma.stanford.edu/~jos/sasp/Quadratic_Interpolation_Spectral_Peaks.html
|
Chris@1
|
247
|
Chris@1
|
248 if (peakbin < 1 || peakbin > bins - 2) {
|
Chris@1
|
249 return peakbin;
|
Chris@1
|
250 }
|
Chris@1
|
251
|
Chris@1
|
252 double alpha = in[peakbin-1];
|
Chris@1
|
253 double beta = in[peakbin];
|
Chris@1
|
254 double gamma = in[peakbin+1];
|
Chris@1
|
255
|
Chris@1
|
256 double denom = (alpha - 2*beta + gamma);
|
Chris@1
|
257
|
Chris@1
|
258 if (denom == 0) {
|
Chris@1
|
259 // flat
|
Chris@1
|
260 return peakbin;
|
Chris@1
|
261 }
|
Chris@1
|
262
|
Chris@1
|
263 double p = ((alpha - gamma) / denom) / 2.0;
|
Chris@1
|
264
|
Chris@1
|
265 return double(peakbin) + p;
|
Chris@1
|
266 }
|
Chris@1
|
267
|
Chris@0
|
268 ConstrainedHarmonicPeak::FeatureSet
|
Chris@14
|
269 ConstrainedHarmonicPeak::process(const float *const *inputBuffers, Vamp::RealTime)
|
Chris@0
|
270 {
|
Chris@0
|
271 FeatureSet fs;
|
Chris@0
|
272
|
Chris@6
|
273 // This could be better. The procedure here is
|
Chris@6
|
274 //
|
Chris@6
|
275 // 1 Produce a harmonic product spectrum within a limited
|
Chris@6
|
276 // frequency range by effectively summing the dB values of the
|
Chris@6
|
277 // bins at each multiple of the bin numbers (up to a given
|
Chris@6
|
278 // number of harmonics) in the range under consideration
|
Chris@6
|
279 // 2 Find the peak bin
|
Chris@6
|
280 // 3 Calculate the peak location by quadratic interpolation
|
Chris@6
|
281 // from the peak bin and its two neighbouring bins
|
Chris@6
|
282 //
|
Chris@6
|
283 // Problems with this:
|
Chris@6
|
284 //
|
Chris@6
|
285 // 1 Harmonics might not be located at integer multiples of the
|
Chris@6
|
286 // original bin frequency
|
Chris@6
|
287 // 2 Quadratic interpolation works "correctly" for dB-valued
|
Chris@6
|
288 // magnitude spectra but might not produce the right results in
|
Chris@6
|
289 // the dB-summed hps, especially in light of the first problem
|
Chris@6
|
290 // 3 Interpolation might not make sense at all if there are
|
Chris@6
|
291 // multiple nearby frequencies interfering across the three
|
Chris@6
|
292 // bins used for interpolation (we may be unable to identify
|
Chris@6
|
293 // the right frequency at all, but it's possible interpolation
|
Chris@6
|
294 // will make our guess worse rather than better)
|
Chris@6
|
295 //
|
Chris@6
|
296 // Possible improvements:
|
Chris@6
|
297 //
|
Chris@6
|
298 // 1 Find the higher harmonics by looking for the peak bin within
|
Chris@6
|
299 // a range around the nominal peak location
|
Chris@6
|
300 // 2 Once a peak has been identified as the peak of the HPS, use
|
Chris@6
|
301 // the original spectrum (not the HPS) to obtain the values for
|
Chris@6
|
302 // interpolation? (would help with problem 2 but might make
|
Chris@6
|
303 // problem 3 worse)
|
Chris@6
|
304
|
Chris@4
|
305 int hs = m_fftSize/2;
|
Chris@1
|
306
|
Chris@1
|
307 double *mags = new double[hs+1];
|
Chris@1
|
308 for (int i = 0; i <= hs; ++i) {
|
Chris@4
|
309 mags[i] = sqrt(inputBuffers[0][i*2] * inputBuffers[0][i*2] +
|
Chris@4
|
310 inputBuffers[0][i*2+1] * inputBuffers[0][i*2+1]);
|
Chris@1
|
311 }
|
Chris@1
|
312
|
Chris@1
|
313 // bin freq is bin * samplerate / fftsize
|
Chris@1
|
314
|
Chris@14
|
315 int minbin = int(floor((double(m_minFreq) * m_fftSize) / m_inputSampleRate));
|
Chris@14
|
316 int maxbin = int(ceil((double(m_maxFreq) * m_fftSize) / m_inputSampleRate));
|
Chris@1
|
317 if (minbin > hs) minbin = hs;
|
Chris@1
|
318 if (maxbin > hs) maxbin = hs;
|
Chris@1
|
319 if (maxbin <= minbin) return fs;
|
Chris@1
|
320
|
Chris@1
|
321 double *hps = new double[maxbin - minbin + 1];
|
Chris@1
|
322
|
Chris@1
|
323 // HPS in dB after MzHarmonicSpectrum
|
Chris@1
|
324
|
Chris@1
|
325 for (int bin = minbin; bin <= maxbin; ++bin) {
|
Chris@1
|
326
|
Chris@1
|
327 int i = bin - minbin;
|
Chris@1
|
328 hps[i] = 1.0;
|
Chris@1
|
329
|
Chris@1
|
330 int contributing = 0;
|
Chris@1
|
331
|
Chris@1
|
332 for (int j = 1; j <= m_harmonics; ++j) {
|
Chris@1
|
333 if (j * bin > hs) break;
|
Chris@1
|
334 hps[i] *= mags[j * bin];
|
Chris@1
|
335 ++contributing;
|
Chris@1
|
336 }
|
Chris@1
|
337
|
Chris@1
|
338 if (hps[i] <= 0.0) {
|
Chris@1
|
339 hps[i] = -120.0;
|
Chris@1
|
340 } else {
|
Chris@1
|
341 hps[i] = 20.0 / contributing * log10(hps[i]);
|
Chris@1
|
342 }
|
Chris@1
|
343 }
|
Chris@1
|
344
|
Chris@1
|
345 double maxdb = -120.0;
|
Chris@1
|
346 int maxidx = 0;
|
Chris@1
|
347 for (int i = 0; i <= maxbin - minbin; ++i) {
|
matthiasm@9
|
348 if (hps[i] > maxdb) {
|
matthiasm@9
|
349 maxdb = hps[i];
|
matthiasm@9
|
350 maxidx = i;
|
matthiasm@9
|
351 }
|
matthiasm@9
|
352 }
|
matthiasm@9
|
353
|
matthiasm@9
|
354 if (maxidx == 0 || maxidx == maxbin - minbin) { // edge cases are useless
|
matthiasm@9
|
355 return fs;
|
Chris@1
|
356 }
|
Chris@1
|
357
|
Chris@1
|
358 double interpolated = findInterpolatedPeak(hps, maxidx, maxbin - minbin + 1);
|
matthiasm@9
|
359
|
Chris@1
|
360 interpolated = interpolated + minbin;
|
Chris@1
|
361
|
Chris@4
|
362 double freq = interpolated * m_inputSampleRate / m_fftSize;
|
matthiasm@9
|
363
|
matthiasm@9
|
364 if (freq < m_minFreq || freq > m_maxFreq) {
|
matthiasm@9
|
365 return fs;
|
matthiasm@9
|
366 }
|
Chris@1
|
367
|
Chris@1
|
368 Feature f;
|
Chris@14
|
369 f.values.push_back(float(freq));
|
Chris@1
|
370 fs[0].push_back(f);
|
Chris@1
|
371
|
Chris@0
|
372 return fs;
|
Chris@0
|
373 }
|
Chris@0
|
374
|
Chris@0
|
375 ConstrainedHarmonicPeak::FeatureSet
|
Chris@0
|
376 ConstrainedHarmonicPeak::getRemainingFeatures()
|
Chris@0
|
377 {
|
Chris@0
|
378 FeatureSet fs;
|
Chris@0
|
379 return fs;
|
Chris@0
|
380 }
|
Chris@0
|
381
|