cannam@0
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
cannam@0
|
2 /*
|
cannam@0
|
3 Vamp feature extraction plugin using the OnsetsDS onset detector.
|
cannam@0
|
4 This file copyright (c) 2008 Chris Cannam.
|
cannam@0
|
5
|
cannam@0
|
6 OnsetsDS - real time musical onset detection library.
|
cannam@0
|
7 Copyright (c) 2007 Dan Stowell. All rights reserved.
|
cannam@0
|
8
|
cannam@0
|
9 This program is free software; you can redistribute it and/or modify
|
cannam@0
|
10 it under the terms of the GNU General Public License as published by
|
cannam@0
|
11 the Free Software Foundation; either version 2 of the License, or
|
cannam@0
|
12 (at your option) any later version.
|
cannam@0
|
13
|
cannam@0
|
14 This program is distributed in the hope that it will be useful,
|
cannam@0
|
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
cannam@0
|
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
cannam@0
|
17 GNU General Public License for more details.
|
cannam@0
|
18
|
cannam@0
|
19 You should have received a copy of the GNU General Public License
|
cannam@0
|
20 along with this program; if not, write to the Free Software
|
cannam@0
|
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
cannam@0
|
22 */
|
cannam@0
|
23
|
cannam@0
|
24 #include "onsetsdsplugin.h"
|
cannam@0
|
25 #include <vamp-sdk/PluginAdapter.h>
|
cannam@0
|
26
|
cannam@0
|
27 using std::string;
|
cannam@0
|
28 using std::vector;
|
cannam@0
|
29 using std::cerr;
|
cannam@0
|
30 using std::endl;
|
cannam@0
|
31
|
cannam@0
|
32
|
cannam@0
|
33 OnsetsDSPlugin::OnsetsDSPlugin(float inputSampleRate) :
|
cannam@0
|
34 Vamp::Plugin(inputSampleRate),
|
cannam@0
|
35 m_ods(0),
|
cannam@0
|
36 m_odsdata(0),
|
cannam@0
|
37 m_dfType(ODS_ODF_RCOMPLEX),
|
cannam@1
|
38 m_threshold(0.5),
|
cannam@0
|
39 m_medspan(11),
|
cannam@1
|
40 m_stepSize(256),
|
cannam@1
|
41 m_fftSize(512)
|
cannam@0
|
42 {
|
cannam@0
|
43 }
|
cannam@0
|
44
|
cannam@0
|
45 OnsetsDSPlugin::~OnsetsDSPlugin()
|
cannam@0
|
46 {
|
cannam@0
|
47 delete[] m_odsdata;
|
cannam@0
|
48 delete m_ods;
|
cannam@0
|
49 }
|
cannam@0
|
50
|
cannam@0
|
51 string
|
cannam@0
|
52 OnsetsDSPlugin::getIdentifier() const
|
cannam@0
|
53 {
|
cannam@0
|
54 return "onsetsds";
|
cannam@0
|
55 }
|
cannam@0
|
56
|
cannam@0
|
57 string
|
cannam@0
|
58 OnsetsDSPlugin::getName() const
|
cannam@0
|
59 {
|
cannam@0
|
60 return "OnsetsDS Onset Detector";
|
cannam@0
|
61 }
|
cannam@0
|
62
|
cannam@0
|
63 string
|
cannam@0
|
64 OnsetsDSPlugin::getDescription() const
|
cannam@0
|
65 {
|
cannam@0
|
66 return "Detect note onsets";
|
cannam@0
|
67 }
|
cannam@0
|
68
|
cannam@0
|
69 string
|
cannam@0
|
70 OnsetsDSPlugin::getMaker() const
|
cannam@0
|
71 {
|
cannam@0
|
72 return "Dan Stowell";
|
cannam@0
|
73 }
|
cannam@0
|
74
|
cannam@0
|
75 int
|
cannam@0
|
76 OnsetsDSPlugin::getPluginVersion() const
|
cannam@0
|
77 {
|
dan@10
|
78 return 2;
|
cannam@0
|
79 }
|
cannam@0
|
80
|
cannam@0
|
81 string
|
cannam@0
|
82 OnsetsDSPlugin::getCopyright() const
|
cannam@0
|
83 {
|
cannam@0
|
84 return "Copyright (c) 2007-2008 Dan Stowell";
|
cannam@0
|
85 }
|
cannam@0
|
86
|
cannam@0
|
87 OnsetsDSPlugin::ParameterList
|
cannam@0
|
88 OnsetsDSPlugin::getParameterDescriptors() const
|
cannam@0
|
89 {
|
cannam@0
|
90 ParameterList list;
|
cannam@0
|
91
|
cannam@0
|
92 ParameterDescriptor desc;
|
cannam@0
|
93 desc.identifier = "dftype";
|
cannam@0
|
94 desc.name = "Onset detection function";
|
cannam@0
|
95 desc.description = "Method used to calculate the onset detection function";
|
cannam@0
|
96 desc.minValue = 0;
|
cannam@0
|
97 desc.maxValue = 6;
|
cannam@0
|
98 desc.defaultValue = 3;
|
cannam@0
|
99 desc.isQuantized = true;
|
cannam@0
|
100 desc.quantizeStep = 1;
|
cannam@0
|
101 desc.valueNames.push_back("Power");
|
cannam@0
|
102 desc.valueNames.push_back("Sum of magnitudes");
|
cannam@0
|
103 desc.valueNames.push_back("Complex-domain deviation");
|
cannam@0
|
104 desc.valueNames.push_back("Rectified complex-domain deviation");
|
cannam@0
|
105 desc.valueNames.push_back("Phase deviation");
|
cannam@0
|
106 desc.valueNames.push_back("Weighted phase deviation");
|
cannam@0
|
107 desc.valueNames.push_back("Modified Kullback-Liebler deviation");
|
cannam@0
|
108 list.push_back(desc);
|
cannam@0
|
109
|
cannam@1
|
110 desc.identifier = "threshold";
|
cannam@1
|
111 desc.name = "Detection threshold";
|
cannam@1
|
112 desc.description = "Onsets trigger when the function beats this value";
|
cannam@1
|
113 desc.minValue = 0;
|
cannam@1
|
114 desc.maxValue = 1;
|
cannam@1
|
115 desc.defaultValue = 0.5;
|
cannam@1
|
116 desc.isQuantized = false;
|
cannam@1
|
117 desc.valueNames.clear();
|
cannam@1
|
118 list.push_back(desc);
|
cannam@1
|
119
|
cannam@0
|
120 desc.identifier = "medspan";
|
cannam@0
|
121 desc.name = "Median frame span";
|
cannam@0
|
122 desc.description = "Number of past frames used in median calculation";
|
cannam@0
|
123 desc.minValue = 5;
|
cannam@0
|
124 desc.maxValue = 21;
|
cannam@0
|
125 desc.defaultValue = 11;
|
cannam@0
|
126 desc.isQuantized = true;
|
cannam@0
|
127 desc.quantizeStep = 2;
|
cannam@0
|
128 desc.valueNames.clear();
|
cannam@0
|
129 list.push_back(desc);
|
cannam@0
|
130
|
cannam@0
|
131 return list;
|
cannam@0
|
132 }
|
cannam@0
|
133
|
cannam@0
|
134 float
|
cannam@0
|
135 OnsetsDSPlugin::getParameter(std::string name) const
|
cannam@0
|
136 {
|
cannam@0
|
137 if (name == "dftype") {
|
cannam@0
|
138 switch (m_dfType) {
|
cannam@0
|
139 case ODS_ODF_POWER: return 0;
|
cannam@0
|
140 case ODS_ODF_MAGSUM: return 1;
|
cannam@0
|
141 case ODS_ODF_COMPLEX: return 2;
|
cannam@0
|
142 case ODS_ODF_RCOMPLEX: return 3;
|
cannam@0
|
143 case ODS_ODF_PHASE: return 4;
|
cannam@0
|
144 case ODS_ODF_WPHASE: return 5;
|
cannam@0
|
145 case ODS_ODF_MKL: return 6;
|
cannam@0
|
146 }
|
cannam@1
|
147 } else if (name == "threshold") {
|
cannam@1
|
148 return m_threshold;
|
cannam@0
|
149 } else if (name == "medspan") {
|
cannam@0
|
150 return m_medspan;
|
cannam@0
|
151 }
|
cannam@0
|
152 return 0.0;
|
cannam@0
|
153 }
|
cannam@0
|
154
|
cannam@0
|
155 void
|
cannam@0
|
156 OnsetsDSPlugin::setParameter(std::string name, float value)
|
cannam@0
|
157 {
|
cannam@0
|
158 if (name == "dftype") {
|
cannam@0
|
159 onsetsds_odf_types dfType = m_dfType;
|
cannam@0
|
160 switch (lrintf(value)) {
|
cannam@0
|
161 case 0: dfType = ODS_ODF_POWER; break;
|
cannam@0
|
162 case 1: dfType = ODS_ODF_MAGSUM; break;
|
cannam@0
|
163 case 2: dfType = ODS_ODF_COMPLEX; break;
|
cannam@0
|
164 case 3: dfType = ODS_ODF_RCOMPLEX; break;
|
cannam@0
|
165 case 4: dfType = ODS_ODF_PHASE; break;
|
cannam@0
|
166 case 5: dfType = ODS_ODF_WPHASE; break;
|
cannam@0
|
167 case 6: dfType = ODS_ODF_MKL; break;
|
cannam@0
|
168 }
|
cannam@0
|
169 if (dfType == m_dfType) return;
|
cannam@0
|
170 m_dfType = dfType;
|
cannam@1
|
171 } else if (name == "threshold") {
|
cannam@1
|
172 m_threshold = value;
|
cannam@0
|
173 } else if (name == "medspan") {
|
cannam@0
|
174 m_medspan = lrintf(value);
|
cannam@0
|
175 }
|
cannam@0
|
176 }
|
cannam@0
|
177
|
cannam@0
|
178 bool
|
cannam@0
|
179 OnsetsDSPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize)
|
cannam@0
|
180 {
|
cannam@0
|
181 if (channels < getMinChannelCount() ||
|
cannam@0
|
182 channels > getMaxChannelCount()) {
|
cannam@0
|
183 std::cerr << "OnsetsDSPlugin::initialise: Unsupported channel count: "
|
cannam@0
|
184 << channels << std::endl;
|
cannam@0
|
185 return false;
|
cannam@0
|
186 }
|
cannam@0
|
187
|
cannam@0
|
188 if (stepSize != getPreferredStepSize()) {
|
cannam@0
|
189 std::cerr << "WARNING: OnsetsDSPlugin::initialise: Using unusual step size: "
|
cannam@0
|
190 << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl;
|
cannam@0
|
191 }
|
cannam@0
|
192
|
cannam@0
|
193 if (blockSize != getPreferredBlockSize()) {
|
cannam@0
|
194 std::cerr << "WARNING: OnsetsDSPlugin::initialise: Using unusual block size: "
|
cannam@0
|
195 << blockSize << " (wanted " << (getPreferredBlockSize()) << ")" << std::endl;
|
cannam@0
|
196 }
|
cannam@0
|
197
|
cannam@0
|
198 m_stepSize = stepSize;
|
cannam@0
|
199 m_fftSize = blockSize;
|
cannam@0
|
200
|
cannam@0
|
201 delete[] m_odsdata;
|
cannam@0
|
202 delete m_ods;
|
cannam@0
|
203
|
cannam@0
|
204 m_odsdata = new float[onsetsds_memneeded(m_dfType, m_fftSize, m_medspan)];
|
cannam@0
|
205 m_ods = new OnsetsDS;
|
cannam@0
|
206 memset(m_ods, 0, sizeof(OnsetsDS));
|
cannam@0
|
207 onsetsds_init(m_ods, m_odsdata, ODS_FFT_FFTW3_R2C, m_dfType, m_fftSize,
|
cannam@0
|
208 m_medspan, m_inputSampleRate);
|
Chris@11
|
209 m_ods->thresh = m_threshold;
|
cannam@1
|
210
|
cannam@0
|
211 return true;
|
cannam@0
|
212 }
|
cannam@0
|
213
|
cannam@0
|
214 void
|
cannam@0
|
215 OnsetsDSPlugin::reset()
|
cannam@0
|
216 {
|
cannam@0
|
217 if (!m_ods) {
|
cannam@0
|
218 std::cerr << "ERROR: OnsetsDSPlugin::reset: Plugin has not been initialised" << std::endl;
|
cannam@0
|
219 return;
|
cannam@0
|
220 }
|
cannam@0
|
221 onsetsds_init(m_ods, m_odsdata, ODS_FFT_FFTW3_R2C, m_dfType, m_fftSize,
|
cannam@0
|
222 m_medspan, m_inputSampleRate);
|
Chris@11
|
223 m_ods->thresh = m_threshold;
|
cannam@0
|
224 }
|
cannam@0
|
225
|
cannam@0
|
226 size_t
|
cannam@0
|
227 OnsetsDSPlugin::getPreferredStepSize() const
|
cannam@0
|
228 {
|
cannam@1
|
229 return 256;
|
cannam@0
|
230 }
|
cannam@0
|
231
|
cannam@0
|
232 size_t
|
cannam@0
|
233 OnsetsDSPlugin::getPreferredBlockSize() const
|
cannam@0
|
234 {
|
cannam@1
|
235 return 512;
|
cannam@0
|
236 }
|
cannam@0
|
237
|
cannam@0
|
238 OnsetsDSPlugin::OutputList
|
cannam@0
|
239 OnsetsDSPlugin::getOutputDescriptors() const
|
cannam@0
|
240 {
|
cannam@0
|
241 OutputList list;
|
cannam@0
|
242
|
cannam@0
|
243 OutputDescriptor onsets;
|
cannam@0
|
244 onsets.identifier = "onsets";
|
cannam@0
|
245 onsets.name = "Note Onsets";
|
cannam@0
|
246 onsets.description = "Note onset positions";
|
cannam@0
|
247 onsets.unit = "";
|
cannam@0
|
248 onsets.hasFixedBinCount = true;
|
cannam@0
|
249 onsets.binCount = 0;
|
cannam@0
|
250 onsets.sampleType = OutputDescriptor::VariableSampleRate;
|
cannam@0
|
251 onsets.sampleRate = (m_inputSampleRate / m_stepSize);
|
dan@9
|
252 list.push_back(onsets);
|
cannam@0
|
253
|
dan@9
|
254 //--- Extracting the ODF
|
dan@9
|
255 onsets = OutputDescriptor();
|
dan@9
|
256 onsets.identifier = "odf";
|
dan@9
|
257 onsets.name = "Onset Detection Function";
|
dan@9
|
258 onsets.description = "Onset Detection Function used for detecting onsets";
|
dan@9
|
259 onsets.unit = "";
|
dan@9
|
260 onsets.hasFixedBinCount = false;
|
dan@9
|
261 onsets.binCount = 0;
|
dan@9
|
262 onsets.sampleType = OutputDescriptor::OneSamplePerStep;
|
dan@9
|
263 onsets.sampleRate = (m_inputSampleRate / m_stepSize);
|
dan@9
|
264 list.push_back(onsets);
|
dan@9
|
265
|
dan@9
|
266 //--- Extracting the Filtered ODF
|
dan@9
|
267 onsets = OutputDescriptor();
|
dan@9
|
268 onsets.identifier = "filtered_odf";
|
dan@9
|
269 onsets.name = "Filtered Onset Detection Function";
|
dan@9
|
270 onsets.description = "Filtered Onset Detection Function used for detecting onsets";
|
dan@9
|
271 onsets.unit = "";
|
dan@9
|
272 onsets.hasFixedBinCount = false;
|
dan@9
|
273 onsets.binCount = 0;
|
dan@9
|
274 onsets.sampleType = OutputDescriptor::OneSamplePerStep;
|
dan@9
|
275 onsets.sampleRate = (m_inputSampleRate / m_stepSize);
|
cannam@0
|
276 list.push_back(onsets);
|
cannam@0
|
277
|
cannam@0
|
278 return list;
|
cannam@0
|
279 }
|
cannam@0
|
280
|
cannam@0
|
281 OnsetsDSPlugin::FeatureSet
|
cannam@0
|
282 OnsetsDSPlugin::process(const float *const *inputBuffers,
|
cannam@0
|
283 Vamp::RealTime timestamp)
|
cannam@0
|
284 {
|
cannam@0
|
285 if (!m_ods) {
|
cannam@0
|
286 cerr << "ERROR: OnsetsDSPlugin::process: Plugin has not been initialised"
|
cannam@0
|
287 << endl;
|
cannam@0
|
288 return FeatureSet();
|
cannam@0
|
289 }
|
cannam@0
|
290
|
cannam@0
|
291 // We can const_cast because we happen to know onsetsds_process
|
cannam@0
|
292 // does not modify this buffer
|
cannam@0
|
293 bool result = onsetsds_process(m_ods, const_cast<float *>(inputBuffers[0]));
|
cannam@0
|
294
|
cannam@0
|
295 FeatureSet returnFeatures;
|
cannam@0
|
296
|
cannam@0
|
297 if (result) {
|
cannam@0
|
298 Feature feature;
|
cannam@0
|
299 feature.hasTimestamp = true;
|
cannam@0
|
300 feature.timestamp = timestamp;
|
cannam@0
|
301 returnFeatures[0].push_back(feature); // onsets are output 0
|
cannam@0
|
302 }
|
cannam@0
|
303
|
dan@9
|
304 //--- Extracting the ODF
|
dan@9
|
305 Feature odf_feature;
|
dan@9
|
306 odf_feature.hasTimestamp = false;
|
dan@9
|
307 odf_feature.values.push_back(m_ods->odfvals[0]);
|
dan@9
|
308 returnFeatures[1].push_back(odf_feature);
|
dan@9
|
309
|
dan@9
|
310 //--- Extracting the Filtered ODF
|
dan@9
|
311 Feature filtered_odf_feature;
|
dan@9
|
312 filtered_odf_feature.hasTimestamp = false;
|
dan@9
|
313 filtered_odf_feature.values.push_back(m_ods->odfvalpost);
|
dan@9
|
314 returnFeatures[2].push_back(filtered_odf_feature);
|
dan@9
|
315
|
cannam@0
|
316 return returnFeatures;
|
cannam@0
|
317 }
|
cannam@0
|
318
|
cannam@0
|
319 OnsetsDSPlugin::FeatureSet
|
cannam@0
|
320 OnsetsDSPlugin::getRemainingFeatures()
|
cannam@0
|
321 {
|
cannam@0
|
322 return FeatureSet();
|
cannam@0
|
323 }
|
cannam@0
|
324
|
cannam@0
|
325
|
cannam@0
|
326 static Vamp::PluginAdapter<OnsetsDSPlugin> adapter;
|
cannam@0
|
327
|
cannam@0
|
328 const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version,
|
cannam@0
|
329 unsigned int index)
|
cannam@0
|
330 {
|
cannam@0
|
331 if (version < 1) return 0;
|
cannam@0
|
332
|
cannam@0
|
333 switch (index) {
|
cannam@0
|
334 case 0: return adapter.getDescriptor();
|
cannam@0
|
335 default: return 0;
|
cannam@0
|
336 }
|
cannam@0
|
337 }
|
cannam@0
|
338
|