Chris@0
|
1
|
Chris@0
|
2 #include "TuningDifference.h"
|
Chris@0
|
3
|
Chris@1
|
4 #include <iostream>
|
Chris@1
|
5
|
Chris@4
|
6 #include <cmath>
|
Chris@4
|
7 #include <cstdio>
|
Chris@4
|
8
|
Chris@1
|
9 using std::cerr;
|
Chris@1
|
10 using std::endl;
|
Chris@1
|
11
|
Chris@5
|
12 static double targetFmin = 60.0;
|
Chris@5
|
13 static double targetFmax = 1500.0;
|
Chris@5
|
14
|
Chris@0
|
15 TuningDifference::TuningDifference(float inputSampleRate) :
|
Chris@0
|
16 Plugin(inputSampleRate)
|
Chris@0
|
17 {
|
Chris@0
|
18 }
|
Chris@0
|
19
|
Chris@0
|
20 TuningDifference::~TuningDifference()
|
Chris@0
|
21 {
|
Chris@0
|
22 }
|
Chris@0
|
23
|
Chris@0
|
24 string
|
Chris@0
|
25 TuningDifference::getIdentifier() const
|
Chris@0
|
26 {
|
Chris@1
|
27 return "tuning-difference";
|
Chris@0
|
28 }
|
Chris@0
|
29
|
Chris@0
|
30 string
|
Chris@0
|
31 TuningDifference::getName() const
|
Chris@0
|
32 {
|
Chris@1
|
33 return "Tuning Difference";
|
Chris@0
|
34 }
|
Chris@0
|
35
|
Chris@0
|
36 string
|
Chris@0
|
37 TuningDifference::getDescription() const
|
Chris@0
|
38 {
|
Chris@0
|
39 // Return something helpful here!
|
Chris@0
|
40 return "";
|
Chris@0
|
41 }
|
Chris@0
|
42
|
Chris@0
|
43 string
|
Chris@0
|
44 TuningDifference::getMaker() const
|
Chris@0
|
45 {
|
Chris@0
|
46 // Your name here
|
Chris@0
|
47 return "";
|
Chris@0
|
48 }
|
Chris@0
|
49
|
Chris@0
|
50 int
|
Chris@0
|
51 TuningDifference::getPluginVersion() const
|
Chris@0
|
52 {
|
Chris@0
|
53 // Increment this each time you release a version that behaves
|
Chris@0
|
54 // differently from the previous one
|
Chris@0
|
55 return 1;
|
Chris@0
|
56 }
|
Chris@0
|
57
|
Chris@0
|
58 string
|
Chris@0
|
59 TuningDifference::getCopyright() const
|
Chris@0
|
60 {
|
Chris@0
|
61 // This function is not ideally named. It does not necessarily
|
Chris@0
|
62 // need to say who made the plugin -- getMaker does that -- but it
|
Chris@0
|
63 // should indicate the terms under which it is distributed. For
|
Chris@0
|
64 // example, "Copyright (year). All Rights Reserved", or "GPL"
|
Chris@0
|
65 return "";
|
Chris@0
|
66 }
|
Chris@0
|
67
|
Chris@0
|
68 TuningDifference::InputDomain
|
Chris@0
|
69 TuningDifference::getInputDomain() const
|
Chris@0
|
70 {
|
Chris@1
|
71 return FrequencyDomain;
|
Chris@0
|
72 }
|
Chris@0
|
73
|
Chris@0
|
74 size_t
|
Chris@0
|
75 TuningDifference::getPreferredBlockSize() const
|
Chris@0
|
76 {
|
Chris@2
|
77 return 16384;
|
Chris@0
|
78 }
|
Chris@0
|
79
|
Chris@0
|
80 size_t
|
Chris@0
|
81 TuningDifference::getPreferredStepSize() const
|
Chris@0
|
82 {
|
Chris@1
|
83 return 0;
|
Chris@0
|
84 }
|
Chris@0
|
85
|
Chris@0
|
86 size_t
|
Chris@0
|
87 TuningDifference::getMinChannelCount() const
|
Chris@0
|
88 {
|
Chris@1
|
89 return 2;
|
Chris@0
|
90 }
|
Chris@0
|
91
|
Chris@0
|
92 size_t
|
Chris@0
|
93 TuningDifference::getMaxChannelCount() const
|
Chris@0
|
94 {
|
Chris@1
|
95 return 2;
|
Chris@0
|
96 }
|
Chris@0
|
97
|
Chris@0
|
98 TuningDifference::ParameterList
|
Chris@0
|
99 TuningDifference::getParameterDescriptors() const
|
Chris@0
|
100 {
|
Chris@0
|
101 ParameterList list;
|
Chris@0
|
102 return list;
|
Chris@0
|
103 }
|
Chris@0
|
104
|
Chris@0
|
105 float
|
Chris@1
|
106 TuningDifference::getParameter(string) const
|
Chris@0
|
107 {
|
Chris@0
|
108 return 0;
|
Chris@0
|
109 }
|
Chris@0
|
110
|
Chris@0
|
111 void
|
Chris@1
|
112 TuningDifference::setParameter(string, float)
|
Chris@0
|
113 {
|
Chris@0
|
114 }
|
Chris@0
|
115
|
Chris@0
|
116 TuningDifference::ProgramList
|
Chris@0
|
117 TuningDifference::getPrograms() const
|
Chris@0
|
118 {
|
Chris@0
|
119 ProgramList list;
|
Chris@0
|
120 return list;
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 string
|
Chris@0
|
124 TuningDifference::getCurrentProgram() const
|
Chris@0
|
125 {
|
Chris@0
|
126 return ""; // no programs
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 void
|
Chris@1
|
130 TuningDifference::selectProgram(string)
|
Chris@0
|
131 {
|
Chris@0
|
132 }
|
Chris@0
|
133
|
Chris@0
|
134 TuningDifference::OutputList
|
Chris@0
|
135 TuningDifference::getOutputDescriptors() const
|
Chris@0
|
136 {
|
Chris@0
|
137 OutputList list;
|
Chris@0
|
138
|
Chris@1
|
139 OutputDescriptor d;
|
Chris@1
|
140 d.identifier = "cents";
|
Chris@1
|
141 d.name = "Tuning Difference";
|
Chris@1
|
142 d.description = "Difference in averaged frequency profile between channels 1 and 2, in cents. A positive value means channel 2 is higher.";
|
Chris@1
|
143 d.unit = "cents";
|
Chris@1
|
144 d.hasFixedBinCount = true;
|
Chris@1
|
145 d.binCount = 1;
|
Chris@1
|
146 d.hasKnownExtents = false;
|
Chris@1
|
147 d.isQuantized = false;
|
Chris@1
|
148 d.sampleType = OutputDescriptor::VariableSampleRate;
|
Chris@1
|
149 d.hasDuration = false;
|
Chris@1
|
150 list.push_back(d);
|
Chris@0
|
151
|
Chris@1
|
152 d.identifier = "tuningfreq";
|
Chris@1
|
153 d.name = "Relative Tuning Frequency";
|
Chris@1
|
154 d.description = "Tuning frequency of channel 2, if channel 1 is assumed to contain the same music as it at a tuning frequency of A=440Hz.";
|
Chris@4
|
155 d.unit = "hz";
|
Chris@1
|
156 d.hasFixedBinCount = true;
|
Chris@1
|
157 d.binCount = 1;
|
Chris@1
|
158 d.hasKnownExtents = false;
|
Chris@1
|
159 d.isQuantized = false;
|
Chris@1
|
160 d.sampleType = OutputDescriptor::VariableSampleRate;
|
Chris@1
|
161 d.hasDuration = false;
|
Chris@1
|
162 list.push_back(d);
|
Chris@1
|
163
|
Chris@4
|
164 d.identifier = "curve";
|
Chris@4
|
165 d.name = "Shift Correlation Curve";
|
Chris@4
|
166 d.description = "Correlation between shifted and unshifted sources, for each probed shift in cents.";
|
Chris@0
|
167 d.unit = "";
|
Chris@0
|
168 d.hasFixedBinCount = true;
|
Chris@0
|
169 d.binCount = 1;
|
Chris@0
|
170 d.hasKnownExtents = false;
|
Chris@0
|
171 d.isQuantized = false;
|
Chris@1
|
172 d.sampleType = OutputDescriptor::FixedSampleRate;
|
Chris@1
|
173 d.sampleRate = 100;
|
Chris@0
|
174 d.hasDuration = false;
|
Chris@0
|
175 list.push_back(d);
|
Chris@0
|
176
|
Chris@5
|
177 int targetBinMin = int(floor(targetFmin * m_blockSize / m_inputSampleRate));
|
Chris@5
|
178 int targetBinMax = int(ceil(targetFmax * m_blockSize / m_inputSampleRate));
|
Chris@5
|
179 cerr << "target bin range: " << targetBinMin << " -> " << targetBinMax << endl;
|
Chris@5
|
180
|
Chris@4
|
181 d.identifier = "averages";
|
Chris@4
|
182 d.name = "Spectrum averages";
|
Chris@4
|
183 d.description = "Average magnitude spectrum for each channel.";
|
Chris@4
|
184 d.unit = "";
|
Chris@4
|
185 d.hasFixedBinCount = true;
|
Chris@5
|
186 d.binCount = (targetBinMax > targetBinMin ? targetBinMax - targetBinMin + 1 : 100);
|
Chris@4
|
187 d.hasKnownExtents = false;
|
Chris@4
|
188 d.isQuantized = false;
|
Chris@4
|
189 d.sampleType = OutputDescriptor::FixedSampleRate;
|
Chris@5
|
190 d.sampleRate = 1;
|
Chris@4
|
191 d.hasDuration = false;
|
Chris@4
|
192 list.push_back(d);
|
Chris@4
|
193
|
Chris@0
|
194 return list;
|
Chris@0
|
195 }
|
Chris@0
|
196
|
Chris@0
|
197 bool
|
Chris@0
|
198 TuningDifference::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
Chris@0
|
199 {
|
Chris@0
|
200 if (channels < getMinChannelCount() ||
|
Chris@0
|
201 channels > getMaxChannelCount()) return false;
|
Chris@0
|
202
|
Chris@1
|
203 if (blockSize != getPreferredBlockSize() ||
|
Chris@1
|
204 stepSize != blockSize/2) return false;
|
Chris@0
|
205
|
Chris@1
|
206 m_blockSize = blockSize;
|
Chris@1
|
207
|
Chris@1
|
208 reset();
|
Chris@1
|
209
|
Chris@0
|
210 return true;
|
Chris@0
|
211 }
|
Chris@0
|
212
|
Chris@0
|
213 void
|
Chris@0
|
214 TuningDifference::reset()
|
Chris@0
|
215 {
|
Chris@1
|
216 m_sum[0].clear();
|
Chris@1
|
217 m_sum[1].clear();
|
Chris@1
|
218 m_frameCount = 0;
|
Chris@0
|
219 }
|
Chris@0
|
220
|
Chris@0
|
221 TuningDifference::FeatureSet
|
Chris@0
|
222 TuningDifference::process(const float *const *inputBuffers, Vamp::RealTime timestamp)
|
Chris@0
|
223 {
|
Chris@1
|
224 for (int c = 0; c < 2; ++c) {
|
Chris@4
|
225 if (m_sum[c].size() == 0) {
|
Chris@4
|
226 m_sum[c].resize(m_blockSize/2 + 1, 0.0);
|
Chris@4
|
227 }
|
Chris@4
|
228 for (int i = 0; i <= m_blockSize/2; ++i) {
|
Chris@1
|
229 double energy =
|
Chris@1
|
230 inputBuffers[c][i*2 ] * inputBuffers[c][i*2 ] +
|
Chris@1
|
231 inputBuffers[c][i*2+1] * inputBuffers[c][i*2+1];
|
Chris@5
|
232 double mag = sqrt(energy);
|
Chris@5
|
233 m_sum[c][i] += mag;
|
Chris@5
|
234 m_sum[c][i/2] += mag;
|
Chris@1
|
235 }
|
Chris@1
|
236 }
|
Chris@1
|
237
|
Chris@1
|
238 ++m_frameCount;
|
Chris@0
|
239 return FeatureSet();
|
Chris@0
|
240 }
|
Chris@0
|
241
|
Chris@0
|
242 TuningDifference::FeatureSet
|
Chris@0
|
243 TuningDifference::getRemainingFeatures()
|
Chris@0
|
244 {
|
Chris@1
|
245 int n = m_sum[0].size();
|
Chris@1
|
246 if (n == 0) return FeatureSet();
|
Chris@1
|
247
|
Chris@1
|
248 Feature f;
|
Chris@1
|
249 FeatureSet fs;
|
Chris@3
|
250
|
Chris@4
|
251 int maxshift = 2400; // cents
|
Chris@4
|
252
|
Chris@4
|
253 int bestshift = -1;
|
Chris@4
|
254 double bestdist = 0;
|
Chris@4
|
255
|
Chris@4
|
256 for (int i = 0; i < n; ++i) {
|
Chris@4
|
257 m_sum[0][i] /= m_frameCount;
|
Chris@4
|
258 m_sum[1][i] /= m_frameCount;
|
Chris@4
|
259 }
|
Chris@4
|
260
|
Chris@4
|
261 for (int c = 0; c < 2; ++c) {
|
Chris@4
|
262 double tot = 0.0;
|
Chris@1
|
263 for (int i = 0; i < n; ++i) {
|
Chris@4
|
264 tot += m_sum[c][i];
|
Chris@4
|
265 }
|
Chris@4
|
266 if (tot != 0.0) {
|
Chris@4
|
267 for (int i = 0; i < n; ++i) {
|
Chris@4
|
268 m_sum[c][i] /= tot;
|
Chris@1
|
269 }
|
Chris@1
|
270 }
|
Chris@4
|
271 }
|
Chris@5
|
272
|
Chris@5
|
273 int targetBinMin = int(floor(targetFmin * m_blockSize / m_inputSampleRate));
|
Chris@5
|
274 int targetBinMax = int(ceil(targetFmax * m_blockSize / m_inputSampleRate));
|
Chris@5
|
275 cerr << "target bin range: " << targetBinMin << " -> " << targetBinMax << endl;
|
Chris@4
|
276
|
Chris@5
|
277 f.values.clear();
|
Chris@5
|
278 for (int i = targetBinMin; i < targetBinMax; ++i) {
|
Chris@4
|
279 f.values.push_back(m_sum[0][i]);
|
Chris@5
|
280 }
|
Chris@5
|
281 fs[3].push_back(f);
|
Chris@5
|
282 f.values.clear();
|
Chris@5
|
283 for (int i = targetBinMin; i < targetBinMax; ++i) {
|
Chris@4
|
284 f.values.push_back(m_sum[1][i]);
|
Chris@1
|
285 }
|
Chris@5
|
286 fs[3].push_back(f);
|
Chris@1
|
287
|
Chris@4
|
288 f.values.clear();
|
Chris@5
|
289
|
Chris@4
|
290 for (int shift = -maxshift; shift <= maxshift; ++shift) {
|
Chris@4
|
291
|
Chris@4
|
292 double multiplier = pow(2.0, double(shift) / 1200.0);
|
Chris@4
|
293 double dist = 0.0;
|
Chris@4
|
294
|
Chris@4
|
295 // cerr << "shift = " << shift << ", multiplier = " << multiplier << endl;
|
Chris@5
|
296
|
Chris@5
|
297 int contributing = 0;
|
Chris@4
|
298
|
Chris@5
|
299 for (int i = targetBinMin; i < targetBinMax; ++i) {
|
Chris@4
|
300
|
Chris@4
|
301 double source = i / multiplier;
|
Chris@4
|
302 int s0 = int(source), s1 = s0 + 1;
|
Chris@4
|
303 double p1 = source - s0;
|
Chris@4
|
304 double p0 = 1.0 - p1;
|
Chris@4
|
305
|
Chris@4
|
306 double value = 0.0;
|
Chris@4
|
307 if (s0 >= 0 && s0 < n) {
|
Chris@4
|
308 value += p0 * m_sum[1][s0];
|
Chris@5
|
309 ++contributing;
|
Chris@4
|
310 }
|
Chris@4
|
311 if (s1 >= 0 && s1 < n) {
|
Chris@4
|
312 value += p1 * m_sum[1][s1];
|
Chris@5
|
313 ++contributing;
|
Chris@4
|
314 }
|
Chris@4
|
315
|
Chris@4
|
316 // if (shift == -1) {
|
Chris@4
|
317 // cerr << "for multiplier " << multiplier << ", target " << i << ", source " << source << ", value " << p0 << " * " << m_sum[1][s0] << " + " << p1 << " * " << m_sum[1][s1] << " = " << value << ", other " << m_sum[0][i] << endl;
|
Chris@4
|
318 // }
|
Chris@4
|
319
|
Chris@4
|
320 double diff = fabs(m_sum[0][i] - value);
|
Chris@4
|
321 dist += diff;
|
Chris@4
|
322 }
|
Chris@4
|
323
|
Chris@5
|
324 dist /= contributing;
|
Chris@5
|
325
|
Chris@4
|
326 f.values.clear();
|
Chris@4
|
327 f.values.push_back(dist);
|
Chris@4
|
328 char label[100];
|
Chris@4
|
329 sprintf(label, "%f at shift %d freq mult %f", dist, shift, multiplier);
|
Chris@4
|
330 f.label = label;
|
Chris@4
|
331 fs[2].push_back(f);
|
Chris@4
|
332
|
Chris@4
|
333 if (bestshift == -1 || dist < bestdist) {
|
Chris@4
|
334 bestshift = shift;
|
Chris@4
|
335 bestdist = dist;
|
Chris@4
|
336 }
|
Chris@4
|
337 }
|
Chris@4
|
338
|
Chris@4
|
339 f.timestamp = Vamp::RealTime::zeroTime;
|
Chris@4
|
340 f.hasTimestamp = true;
|
Chris@5
|
341 f.label = "";
|
Chris@4
|
342
|
Chris@4
|
343 f.values.clear();
|
Chris@4
|
344 // cerr << "best dist = " << bestdist << " at shift " << bestshift << endl;
|
Chris@4
|
345 f.values.push_back(-bestshift);
|
Chris@4
|
346 fs[0].push_back(f);
|
Chris@4
|
347
|
Chris@4
|
348 f.values.clear();
|
Chris@4
|
349 f.values.push_back(440.0 / pow(2.0, double(bestshift) / 1200.0));
|
Chris@4
|
350 fs[1].push_back(f);
|
Chris@4
|
351
|
Chris@1
|
352 return fs;
|
Chris@0
|
353 }
|
Chris@0
|
354
|