c@27: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ c@27: c@27: /* c@27: QM Vamp Plugin Set c@27: c@27: Centre for Digital Music, Queen Mary, University of London. c@135: c@135: This program is free software; you can redistribute it and/or c@135: modify it under the terms of the GNU General Public License as c@135: published by the Free Software Foundation; either version 2 of the c@135: License, or (at your option) any later version. See the file c@135: COPYING included with this distribution for more information. c@27: */ c@27: c@27: #include "OnsetDetect.h" c@27: c@27: #include c@27: #include c@27: #include c@27: c@27: using std::string; c@27: using std::vector; c@27: using std::cerr; c@27: using std::endl; c@27: c@32: float OnsetDetector::m_preferredStepSecs = 0.01161; c@27: c@27: class OnsetDetectorData c@27: { c@27: public: c@27: OnsetDetectorData(const DFConfig &config) : dfConfig(config) { c@27: df = new DetectionFunction(config); c@27: } c@27: ~OnsetDetectorData() { c@27: delete df; c@27: } c@27: void reset() { c@27: delete df; c@27: df = new DetectionFunction(dfConfig); c@27: dfOutput.clear(); c@85: origin = Vamp::RealTime::zeroTime; c@27: } c@27: c@27: DFConfig dfConfig; c@27: DetectionFunction *df; c@27: vector dfOutput; c@85: Vamp::RealTime origin; c@27: }; c@27: c@27: c@27: OnsetDetector::OnsetDetector(float inputSampleRate) : c@27: Vamp::Plugin(inputSampleRate), c@27: m_d(0), c@27: m_dfType(DF_COMPLEXSD), c@30: m_sensitivity(50), c@30: m_whiten(false) c@27: { c@27: } c@27: c@27: OnsetDetector::~OnsetDetector() c@27: { c@27: delete m_d; c@27: } c@27: c@27: string c@27: OnsetDetector::getIdentifier() const c@27: { c@27: return "qm-onsetdetector"; c@27: } c@27: c@27: string c@27: OnsetDetector::getName() const c@27: { c@27: return "Note Onset Detector"; c@27: } c@27: c@27: string c@27: OnsetDetector::getDescription() const c@27: { c@27: return "Estimate individual note onset positions"; c@27: } c@27: c@27: string c@27: OnsetDetector::getMaker() const c@27: { c@50: return "Queen Mary, University of London"; c@27: } c@27: c@27: int c@27: OnsetDetector::getPluginVersion() const c@27: { c@131: return 3; c@27: } c@27: c@27: string c@27: OnsetDetector::getCopyright() const c@27: { c@118: return "Plugin by Christian Landone, Chris Duxbury and Juan Pablo Bello. Copyright (c) 2006-2009 QMUL - All Rights Reserved"; c@27: } c@27: c@27: OnsetDetector::ParameterList c@27: OnsetDetector::getParameterDescriptors() const c@27: { c@27: ParameterList list; c@27: c@27: ParameterDescriptor desc; c@27: desc.identifier = "dftype"; c@27: desc.name = "Onset Detection Function Type"; c@27: desc.description = "Method used to calculate the onset detection function"; c@27: desc.minValue = 0; c@31: desc.maxValue = 4; c@27: desc.defaultValue = 3; c@27: desc.isQuantized = true; c@27: desc.quantizeStep = 1; c@27: desc.valueNames.push_back("High-Frequency Content"); c@27: desc.valueNames.push_back("Spectral Difference"); c@27: desc.valueNames.push_back("Phase Deviation"); c@27: desc.valueNames.push_back("Complex Domain"); c@27: desc.valueNames.push_back("Broadband Energy Rise"); c@27: list.push_back(desc); c@27: c@27: desc.identifier = "sensitivity"; c@27: desc.name = "Onset Detector Sensitivity"; c@27: desc.description = "Sensitivity of peak-picker for onset detection"; c@27: desc.minValue = 0; c@27: desc.maxValue = 100; c@27: desc.defaultValue = 50; c@27: desc.isQuantized = true; c@27: desc.quantizeStep = 1; c@27: desc.unit = "%"; c@27: desc.valueNames.clear(); c@27: list.push_back(desc); c@27: c@30: desc.identifier = "whiten"; c@30: desc.name = "Adaptive Whitening"; c@30: desc.description = "Normalize frequency bin magnitudes relative to recent peak levels"; c@30: desc.minValue = 0; c@30: desc.maxValue = 1; c@30: desc.defaultValue = 0; c@30: desc.isQuantized = true; c@30: desc.quantizeStep = 1; c@30: desc.unit = ""; c@30: list.push_back(desc); c@30: c@27: return list; c@27: } c@27: c@27: float c@27: OnsetDetector::getParameter(std::string name) const c@27: { c@27: if (name == "dftype") { c@27: switch (m_dfType) { c@27: case DF_HFC: return 0; c@27: case DF_SPECDIFF: return 1; c@27: case DF_PHASEDEV: return 2; c@27: default: case DF_COMPLEXSD: return 3; c@27: case DF_BROADBAND: return 4; c@27: } c@27: } else if (name == "sensitivity") { c@27: return m_sensitivity; c@30: } else if (name == "whiten") { c@30: return m_whiten ? 1.0 : 0.0; c@27: } c@27: return 0.0; c@27: } c@27: c@27: void c@27: OnsetDetector::setParameter(std::string name, float value) c@27: { c@27: if (name == "dftype") { c@30: int dfType = m_dfType; c@27: switch (lrintf(value)) { c@30: case 0: dfType = DF_HFC; break; c@30: case 1: dfType = DF_SPECDIFF; break; c@30: case 2: dfType = DF_PHASEDEV; break; c@30: default: case 3: dfType = DF_COMPLEXSD; break; c@30: case 4: dfType = DF_BROADBAND; break; c@27: } c@30: if (dfType == m_dfType) return; c@30: m_dfType = dfType; c@30: m_program = ""; c@27: } else if (name == "sensitivity") { c@30: if (m_sensitivity == value) return; c@27: m_sensitivity = value; c@30: m_program = ""; c@30: } else if (name == "whiten") { c@30: if (m_whiten == (value > 0.5)) return; c@30: m_whiten = (value > 0.5); c@30: m_program = ""; c@27: } c@27: } c@27: c@29: OnsetDetector::ProgramList c@29: OnsetDetector::getPrograms() const c@29: { c@29: ProgramList programs; c@30: programs.push_back(""); c@29: programs.push_back("General purpose"); c@29: programs.push_back("Soft onsets"); c@29: programs.push_back("Percussive onsets"); c@29: return programs; c@29: } c@29: c@29: std::string c@29: OnsetDetector::getCurrentProgram() const c@29: { c@30: if (m_program == "") return ""; c@29: else return m_program; c@29: } c@29: c@29: void c@29: OnsetDetector::selectProgram(std::string program) c@29: { c@29: if (program == "General purpose") { c@29: setParameter("dftype", 3); // complex c@29: setParameter("sensitivity", 50); c@30: setParameter("whiten", 0); c@29: } else if (program == "Soft onsets") { c@31: setParameter("dftype", 3); // complex c@31: setParameter("sensitivity", 40); c@31: setParameter("whiten", 1); c@29: } else if (program == "Percussive onsets") { c@29: setParameter("dftype", 4); // broadband energy rise c@29: setParameter("sensitivity", 40); c@30: setParameter("whiten", 0); c@29: } else { c@29: return; c@29: } c@29: m_program = program; c@29: } c@29: c@27: bool c@27: OnsetDetector::initialise(size_t channels, size_t stepSize, size_t blockSize) c@27: { c@27: if (m_d) { c@27: delete m_d; c@27: m_d = 0; c@27: } c@27: c@27: if (channels < getMinChannelCount() || c@27: channels > getMaxChannelCount()) { c@27: std::cerr << "OnsetDetector::initialise: Unsupported channel count: " c@27: << channels << std::endl; c@27: return false; c@27: } c@27: c@28: if (stepSize != getPreferredStepSize()) { c@32: std::cerr << "WARNING: OnsetDetector::initialise: Possibly sub-optimal step size for this sample rate: " c@28: << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl; c@27: } c@27: c@28: if (blockSize != getPreferredBlockSize()) { c@32: std::cerr << "WARNING: OnsetDetector::initialise: Possibly sub-optimal block size for this sample rate: " c@28: << blockSize << " (wanted " << (getPreferredBlockSize()) << ")" << std::endl; c@27: } c@27: c@27: DFConfig dfConfig; c@27: dfConfig.DFType = m_dfType; c@27: dfConfig.stepSize = stepSize; c@27: dfConfig.frameLength = blockSize; c@27: dfConfig.dbRise = 6.0 - m_sensitivity / 16.6667; c@30: dfConfig.adaptiveWhitening = m_whiten; c@30: dfConfig.whiteningRelaxCoeff = -1; c@30: dfConfig.whiteningFloor = -1; c@27: c@27: m_d = new OnsetDetectorData(dfConfig); c@27: return true; c@27: } c@27: c@27: void c@27: OnsetDetector::reset() c@27: { c@27: if (m_d) m_d->reset(); c@27: } c@27: c@27: size_t c@27: OnsetDetector::getPreferredStepSize() const c@27: { c@32: size_t step = size_t(m_inputSampleRate * m_preferredStepSecs + 0.0001); c@95: if (step < 1) step = 1; c@27: // std::cerr << "OnsetDetector::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl; c@27: return step; c@27: } c@27: c@27: size_t c@27: OnsetDetector::getPreferredBlockSize() const c@27: { c@27: return getPreferredStepSize() * 2; c@27: } c@27: c@27: OnsetDetector::OutputList c@27: OnsetDetector::getOutputDescriptors() const c@27: { c@27: OutputList list; c@27: c@32: float stepSecs = m_preferredStepSecs; c@88: // if (m_d) stepSecs = m_d->dfConfig.stepSecs; c@32: c@27: OutputDescriptor onsets; c@27: onsets.identifier = "onsets"; c@27: onsets.name = "Note Onsets"; c@27: onsets.description = "Perceived note onset positions"; c@27: onsets.unit = ""; c@27: onsets.hasFixedBinCount = true; c@27: onsets.binCount = 0; c@27: onsets.sampleType = OutputDescriptor::VariableSampleRate; c@32: onsets.sampleRate = 1.0 / stepSecs; c@27: c@27: OutputDescriptor df; c@27: df.identifier = "detection_fn"; c@27: df.name = "Onset Detection Function"; c@27: df.description = "Probability function of note onset likelihood"; c@27: df.unit = ""; c@27: df.hasFixedBinCount = true; c@27: df.binCount = 1; c@27: df.hasKnownExtents = false; c@27: df.isQuantized = false; c@27: df.sampleType = OutputDescriptor::OneSamplePerStep; c@27: c@27: OutputDescriptor sdf; c@27: sdf.identifier = "smoothed_df"; c@27: sdf.name = "Smoothed Detection Function"; c@27: sdf.description = "Smoothed probability function used for peak-picking"; c@27: sdf.unit = ""; c@27: sdf.hasFixedBinCount = true; c@27: sdf.binCount = 1; c@27: sdf.hasKnownExtents = false; c@27: sdf.isQuantized = false; c@27: c@27: sdf.sampleType = OutputDescriptor::VariableSampleRate; c@27: c@27: //!!! SV doesn't seem to handle these correctly in getRemainingFeatures c@27: // sdf.sampleType = OutputDescriptor::FixedSampleRate; c@32: sdf.sampleRate = 1.0 / stepSecs; c@27: c@27: list.push_back(onsets); c@27: list.push_back(df); c@27: list.push_back(sdf); c@27: c@27: return list; c@27: } c@27: c@27: OnsetDetector::FeatureSet c@27: OnsetDetector::process(const float *const *inputBuffers, c@29: Vamp::RealTime timestamp) c@27: { c@27: if (!m_d) { c@27: cerr << "ERROR: OnsetDetector::process: " c@27: << "OnsetDetector has not been initialised" c@27: << endl; c@27: return FeatureSet(); c@27: } c@27: c@153: size_t len = m_d->dfConfig.frameLength / 2 + 1; c@27: c@29: // float mean = 0.f; c@29: // for (size_t i = 0; i < len; ++i) { c@29: //// std::cerr << inputBuffers[0][i] << " "; c@29: // mean += inputBuffers[0][i]; c@29: // } c@29: //// std::cerr << std::endl; c@29: // mean /= len; c@29: c@29: // std::cerr << "OnsetDetector::process(" << timestamp << "): " c@29: // << "dftype " << m_dfType << ", sens " << m_sensitivity c@29: // << ", len " << len << ", mean " << mean << std::endl; c@29: c@153: double *reals = new double[len]; c@153: double *imags = new double[len]; c@27: c@27: // We only support a single input channel c@27: c@27: for (size_t i = 0; i < len; ++i) { c@153: reals[i] = inputBuffers[0][i*2]; c@153: imags[i] = inputBuffers[0][i*2+1]; c@27: } c@27: c@153: double output = m_d->df->processFrequencyDomain(reals, imags); c@27: c@153: delete[] reals; c@153: delete[] imags; c@27: c@85: if (m_d->dfOutput.empty()) m_d->origin = timestamp; c@85: c@27: m_d->dfOutput.push_back(output); c@27: c@27: FeatureSet returnFeatures; c@27: c@27: Feature feature; c@27: feature.hasTimestamp = false; c@27: feature.values.push_back(output); c@27: c@29: // std::cerr << "df: " << output << std::endl; c@29: c@27: returnFeatures[1].push_back(feature); // detection function is output 1 c@27: return returnFeatures; c@27: } c@27: c@27: OnsetDetector::FeatureSet c@27: OnsetDetector::getRemainingFeatures() c@27: { c@27: if (!m_d) { c@27: cerr << "ERROR: OnsetDetector::getRemainingFeatures: " c@27: << "OnsetDetector has not been initialised" c@27: << endl; c@27: return FeatureSet(); c@27: } c@27: c@27: if (m_dfType == DF_BROADBAND) { c@27: for (size_t i = 0; i < m_d->dfOutput.size(); ++i) { c@27: if (m_d->dfOutput[i] < ((110 - m_sensitivity) * c@27: m_d->dfConfig.frameLength) / 200) { c@27: m_d->dfOutput[i] = 0; c@27: } c@27: } c@27: } c@27: c@27: double aCoeffs[] = { 1.0000, -0.5949, 0.2348 }; c@27: double bCoeffs[] = { 0.1600, 0.3200, 0.1600 }; c@27: c@27: FeatureSet returnFeatures; c@27: c@27: PPickParams ppParams; c@27: ppParams.length = m_d->dfOutput.size(); c@27: // tau and cutoff appear to be unused in PeakPicking, but I've c@27: // inserted some moderately plausible values rather than leave c@27: // them unset. The QuadThresh values come from trial and error. c@27: // The rest of these are copied from ttParams in the BeatTracker c@27: // code: I don't claim to know whether they're good or not --cc c@27: ppParams.tau = m_d->dfConfig.stepSize / m_inputSampleRate; c@27: ppParams.alpha = 9; c@27: ppParams.cutoff = m_inputSampleRate/4; c@27: ppParams.LPOrd = 2; c@27: ppParams.LPACoeffs = aCoeffs; c@27: ppParams.LPBCoeffs = bCoeffs; c@27: ppParams.WinT.post = 8; c@27: ppParams.WinT.pre = 7; c@27: ppParams.QuadThresh.a = (100 - m_sensitivity) / 1000.0; c@27: ppParams.QuadThresh.b = 0; c@27: ppParams.QuadThresh.c = (100 - m_sensitivity) / 1500.0; c@27: c@27: PeakPicking peakPicker(ppParams); c@27: c@27: double *ppSrc = new double[ppParams.length]; cannam@239: for (int i = 0; i < ppParams.length; ++i) { c@27: ppSrc[i] = m_d->dfOutput[i]; c@27: } c@27: c@27: vector onsets; c@27: peakPicker.process(ppSrc, ppParams.length, onsets); c@27: c@27: for (size_t i = 0; i < onsets.size(); ++i) { c@27: c@27: size_t index = onsets[i]; c@27: c@27: if (m_dfType != DF_BROADBAND) { c@27: double prevDiff = 0.0; c@27: while (index > 1) { c@27: double diff = ppSrc[index] - ppSrc[index-1]; c@27: if (diff < prevDiff * 0.9) break; c@27: prevDiff = diff; c@27: --index; c@27: } c@27: } c@27: c@27: size_t frame = index * m_d->dfConfig.stepSize; c@27: c@27: Feature feature; c@27: feature.hasTimestamp = true; c@85: feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime c@27: (frame, lrintf(m_inputSampleRate)); c@27: c@27: returnFeatures[0].push_back(feature); // onsets are output 0 c@27: } c@27: cannam@239: for (int i = 0; i < ppParams.length; ++i) { c@27: c@27: Feature feature; cannam@239: c@27: feature.hasTimestamp = true; c@27: size_t frame = i * m_d->dfConfig.stepSize; c@85: feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime c@27: (frame, lrintf(m_inputSampleRate)); c@27: c@27: feature.values.push_back(ppSrc[i]); c@27: returnFeatures[2].push_back(feature); // smoothed df is output 2 c@27: } c@27: c@27: return returnFeatures; c@27: } c@27: