1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
6 Centre for Digital Music, Queen Mary, University of London.
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. See the file
12 COPYING included with this distribution for more information.
15 #include "OnsetDetect.h"
17 #include "dsp/onsets/DetectionFunction.h"
18 #include "dsp/onsets/PeakPicking.h"
19 #include "dsp/tempotracking/TempoTrack.h"
26 float OnsetDetector::m_preferredStepSecs
= 0.01161;
28 class OnsetDetectorData
31 OnsetDetectorData(const DFConfig
&config
) : dfConfig(config
) {
32 df
= new DetectionFunction(config
);
34 ~OnsetDetectorData() {
39 df
= new DetectionFunction(dfConfig
);
41 origin
= Vamp::RealTime::zeroTime
;
45 DetectionFunction
*df
;
46 vector
<double> dfOutput
;
47 Vamp::RealTime origin
;
51 OnsetDetector::OnsetDetector(float inputSampleRate
) :
52 Vamp::Plugin(inputSampleRate
),
54 m_dfType(DF_COMPLEXSD
),
60 OnsetDetector::~OnsetDetector()
66 OnsetDetector::getIdentifier() const
68 return "qm-onsetdetector";
72 OnsetDetector::getName() const
74 return "Note Onset Detector";
78 OnsetDetector::getDescription() const
80 return "Estimate individual note onset positions";
84 OnsetDetector::getMaker() const
86 return "Queen Mary, University of London";
90 OnsetDetector::getPluginVersion() const
96 OnsetDetector::getCopyright() const
98 return "Plugin by Christian Landone, Chris Duxbury and Juan Pablo Bello. Copyright (c) 2006-2009 QMUL - All Rights Reserved";
101 OnsetDetector::ParameterList
102 OnsetDetector::getParameterDescriptors() const
106 ParameterDescriptor desc
;
107 desc
.identifier
= "dftype";
108 desc
.name
= "Onset Detection Function Type";
109 desc
.description
= "Method used to calculate the onset detection function";
112 desc
.defaultValue
= 3;
113 desc
.isQuantized
= true;
114 desc
.quantizeStep
= 1;
115 desc
.valueNames
.push_back("High-Frequency Content");
116 desc
.valueNames
.push_back("Spectral Difference");
117 desc
.valueNames
.push_back("Phase Deviation");
118 desc
.valueNames
.push_back("Complex Domain");
119 desc
.valueNames
.push_back("Broadband Energy Rise");
120 list
.push_back(desc
);
122 desc
.identifier
= "sensitivity";
123 desc
.name
= "Onset Detector Sensitivity";
124 desc
.description
= "Sensitivity of peak-picker for onset detection";
127 desc
.defaultValue
= 50;
128 desc
.isQuantized
= true;
129 desc
.quantizeStep
= 1;
131 desc
.valueNames
.clear();
132 list
.push_back(desc
);
134 desc
.identifier
= "whiten";
135 desc
.name
= "Adaptive Whitening";
136 desc
.description
= "Normalize frequency bin magnitudes relative to recent peak levels";
139 desc
.defaultValue
= 0;
140 desc
.isQuantized
= true;
141 desc
.quantizeStep
= 1;
143 list
.push_back(desc
);
149 OnsetDetector::getParameter(std::string name
) const
151 if (name
== "dftype") {
153 case DF_HFC
: return 0;
154 case DF_SPECDIFF
: return 1;
155 case DF_PHASEDEV
: return 2;
156 default: case DF_COMPLEXSD
: return 3;
157 case DF_BROADBAND
: return 4;
159 } else if (name
== "sensitivity") {
160 return m_sensitivity
;
161 } else if (name
== "whiten") {
162 return m_whiten
? 1.0 : 0.0;
168 OnsetDetector::setParameter(std::string name
, float value
)
170 if (name
== "dftype") {
171 int dfType
= m_dfType
;
172 switch (lrintf(value
)) {
173 case 0: dfType
= DF_HFC
; break;
174 case 1: dfType
= DF_SPECDIFF
; break;
175 case 2: dfType
= DF_PHASEDEV
; break;
176 default: case 3: dfType
= DF_COMPLEXSD
; break;
177 case 4: dfType
= DF_BROADBAND
; break;
179 if (dfType
== m_dfType
) return;
182 } else if (name
== "sensitivity") {
183 if (m_sensitivity
== value
) return;
184 m_sensitivity
= value
;
186 } else if (name
== "whiten") {
187 if (m_whiten
== (value
> 0.5)) return;
188 m_whiten
= (value
> 0.5);
193 OnsetDetector::ProgramList
194 OnsetDetector::getPrograms() const
196 ProgramList programs
;
197 programs
.push_back("");
198 programs
.push_back("General purpose");
199 programs
.push_back("Soft onsets");
200 programs
.push_back("Percussive onsets");
205 OnsetDetector::getCurrentProgram() const
207 if (m_program
== "") return "";
208 else return m_program
;
212 OnsetDetector::selectProgram(std::string program
)
214 if (program
== "General purpose") {
215 setParameter("dftype", 3); // complex
216 setParameter("sensitivity", 50);
217 setParameter("whiten", 0);
218 } else if (program
== "Soft onsets") {
219 setParameter("dftype", 3); // complex
220 setParameter("sensitivity", 40);
221 setParameter("whiten", 1);
222 } else if (program
== "Percussive onsets") {
223 setParameter("dftype", 4); // broadband energy rise
224 setParameter("sensitivity", 40);
225 setParameter("whiten", 0);
233 OnsetDetector::initialise(size_t channels
, size_t stepSize
, size_t blockSize
)
240 if (channels
< getMinChannelCount() ||
241 channels
> getMaxChannelCount()) {
242 std::cerr
<< "OnsetDetector::initialise: Unsupported channel count: "
243 << channels
<< std::endl
;
247 if (stepSize
!= getPreferredStepSize()) {
248 std::cerr
<< "WARNING: OnsetDetector::initialise: Possibly sub-optimal step size for this sample rate: "
249 << stepSize
<< " (wanted " << (getPreferredStepSize()) << ")" << std::endl
;
252 if (blockSize
!= getPreferredBlockSize()) {
253 std::cerr
<< "WARNING: OnsetDetector::initialise: Possibly sub-optimal block size for this sample rate: "
254 << blockSize
<< " (wanted " << (getPreferredBlockSize()) << ")" << std::endl
;
258 dfConfig
.DFType
= m_dfType
;
259 dfConfig
.stepSize
= stepSize
;
260 dfConfig
.frameLength
= blockSize
;
261 dfConfig
.dbRise
= 6.0 - m_sensitivity
/ 16.6667;
262 dfConfig
.adaptiveWhitening
= m_whiten
;
263 dfConfig
.whiteningRelaxCoeff
= -1;
264 dfConfig
.whiteningFloor
= -1;
266 m_d
= new OnsetDetectorData(dfConfig
);
271 OnsetDetector::reset()
273 if (m_d
) m_d
->reset();
277 OnsetDetector::getPreferredStepSize() const
279 size_t step
= size_t(m_inputSampleRate
* m_preferredStepSecs
+ 0.0001);
280 if (step
< 1) step
= 1;
281 // std::cerr << "OnsetDetector::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl;
286 OnsetDetector::getPreferredBlockSize() const
288 return getPreferredStepSize() * 2;
291 OnsetDetector::OutputList
292 OnsetDetector::getOutputDescriptors() const
296 float stepSecs
= m_preferredStepSecs
;
297 // if (m_d) stepSecs = m_d->dfConfig.stepSecs;
299 OutputDescriptor onsets
;
300 onsets
.identifier
= "onsets";
301 onsets
.name
= "Note Onsets";
302 onsets
.description
= "Perceived note onset positions";
304 onsets
.hasFixedBinCount
= true;
306 onsets
.sampleType
= OutputDescriptor::VariableSampleRate
;
307 onsets
.sampleRate
= 1.0 / stepSecs
;
310 df
.identifier
= "detection_fn";
311 df
.name
= "Onset Detection Function";
312 df
.description
= "Probability function of note onset likelihood";
314 df
.hasFixedBinCount
= true;
316 df
.hasKnownExtents
= false;
317 df
.isQuantized
= false;
318 df
.sampleType
= OutputDescriptor::OneSamplePerStep
;
320 OutputDescriptor sdf
;
321 sdf
.identifier
= "smoothed_df";
322 sdf
.name
= "Smoothed Detection Function";
323 sdf
.description
= "Smoothed probability function used for peak-picking";
325 sdf
.hasFixedBinCount
= true;
327 sdf
.hasKnownExtents
= false;
328 sdf
.isQuantized
= false;
330 sdf
.sampleType
= OutputDescriptor::VariableSampleRate
;
332 //!!! SV doesn't seem to handle these correctly in getRemainingFeatures
333 // sdf.sampleType = OutputDescriptor::FixedSampleRate;
334 sdf
.sampleRate
= 1.0 / stepSecs
;
336 list
.push_back(onsets
);
343 OnsetDetector::FeatureSet
344 OnsetDetector::process(const float *const *inputBuffers
,
345 Vamp::RealTime timestamp
)
348 cerr
<< "ERROR: OnsetDetector::process: "
349 << "OnsetDetector has not been initialised"
354 size_t len
= m_d
->dfConfig
.frameLength
/ 2;
357 // for (size_t i = 0; i < len; ++i) {
358 //// std::cerr << inputBuffers[0][i] << " ";
359 // mean += inputBuffers[0][i];
361 //// std::cerr << std::endl;
364 // std::cerr << "OnsetDetector::process(" << timestamp << "): "
365 // << "dftype " << m_dfType << ", sens " << m_sensitivity
366 // << ", len " << len << ", mean " << mean << std::endl;
368 double *magnitudes
= new double[len
];
369 double *phases
= new double[len
];
371 // We only support a single input channel
373 for (size_t i
= 0; i
< len
; ++i
) {
375 magnitudes
[i
] = sqrt(inputBuffers
[0][i
*2 ] * inputBuffers
[0][i
*2 ] +
376 inputBuffers
[0][i
*2+1] * inputBuffers
[0][i
*2+1]);
378 phases
[i
] = atan2(-inputBuffers
[0][i
*2+1], inputBuffers
[0][i
*2]);
381 double output
= m_d
->df
->process(magnitudes
, phases
);
386 if (m_d
->dfOutput
.empty()) m_d
->origin
= timestamp
;
388 m_d
->dfOutput
.push_back(output
);
390 FeatureSet returnFeatures
;
393 feature
.hasTimestamp
= false;
394 feature
.values
.push_back(output
);
396 // std::cerr << "df: " << output << std::endl;
398 returnFeatures
[1].push_back(feature
); // detection function is output 1
399 return returnFeatures
;
402 OnsetDetector::FeatureSet
403 OnsetDetector::getRemainingFeatures()
406 cerr
<< "ERROR: OnsetDetector::getRemainingFeatures: "
407 << "OnsetDetector has not been initialised"
412 if (m_dfType
== DF_BROADBAND
) {
413 for (size_t i
= 0; i
< m_d
->dfOutput
.size(); ++i
) {
414 if (m_d
->dfOutput
[i
] < ((110 - m_sensitivity
) *
415 m_d
->dfConfig
.frameLength
) / 200) {
416 m_d
->dfOutput
[i
] = 0;
421 double aCoeffs
[] = { 1.0000, -0.5949, 0.2348 };
422 double bCoeffs
[] = { 0.1600, 0.3200, 0.1600 };
424 FeatureSet returnFeatures
;
426 PPickParams ppParams
;
427 ppParams
.length
= m_d
->dfOutput
.size();
428 // tau and cutoff appear to be unused in PeakPicking, but I've
429 // inserted some moderately plausible values rather than leave
430 // them unset. The QuadThresh values come from trial and error.
431 // The rest of these are copied from ttParams in the BeatTracker
432 // code: I don't claim to know whether they're good or not --cc
433 ppParams
.tau
= m_d
->dfConfig
.stepSize
/ m_inputSampleRate
;
435 ppParams
.cutoff
= m_inputSampleRate
/4;
437 ppParams
.LPACoeffs
= aCoeffs
;
438 ppParams
.LPBCoeffs
= bCoeffs
;
439 ppParams
.WinT
.post
= 8;
440 ppParams
.WinT
.pre
= 7;
441 ppParams
.QuadThresh
.a
= (100 - m_sensitivity
) / 1000.0;
442 ppParams
.QuadThresh
.b
= 0;
443 ppParams
.QuadThresh
.c
= (100 - m_sensitivity
) / 1500.0;
445 PeakPicking
peakPicker(ppParams
);
447 double *ppSrc
= new double[ppParams
.length
];
448 for (unsigned int i
= 0; i
< ppParams
.length
; ++i
) {
449 ppSrc
[i
] = m_d
->dfOutput
[i
];
453 peakPicker
.process(ppSrc
, ppParams
.length
, onsets
);
455 for (size_t i
= 0; i
< onsets
.size(); ++i
) {
457 size_t index
= onsets
[i
];
459 if (m_dfType
!= DF_BROADBAND
) {
460 double prevDiff
= 0.0;
462 double diff
= ppSrc
[index
] - ppSrc
[index
-1];
463 if (diff
< prevDiff
* 0.9) break;
469 size_t frame
= index
* m_d
->dfConfig
.stepSize
;
472 feature
.hasTimestamp
= true;
473 feature
.timestamp
= m_d
->origin
+ Vamp::RealTime::frame2RealTime
474 (frame
, lrintf(m_inputSampleRate
));
476 returnFeatures
[0].push_back(feature
); // onsets are output 0
479 for (unsigned int i
= 0; i
< ppParams
.length
; ++i
) {
482 // feature.hasTimestamp = false;
483 feature
.hasTimestamp
= true;
484 size_t frame
= i
* m_d
->dfConfig
.stepSize
;
485 feature
.timestamp
= m_d
->origin
+ Vamp::RealTime::frame2RealTime
486 (frame
, lrintf(m_inputSampleRate
));
488 feature
.values
.push_back(ppSrc
[i
]);
489 returnFeatures
[2].push_back(feature
); // smoothed df is output 2
492 return returnFeatures
;