cannam@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@0: /* cannam@0: Vamp feature extraction plugin using the OnsetsDS onset detector. cannam@0: This file copyright (c) 2008 Chris Cannam. cannam@0: cannam@0: OnsetsDS - real time musical onset detection library. cannam@0: Copyright (c) 2007 Dan Stowell. All rights reserved. cannam@0: cannam@0: This program is free software; you can redistribute it and/or modify cannam@0: it under the terms of the GNU General Public License as published by cannam@0: the Free Software Foundation; either version 2 of the License, or cannam@0: (at your option) any later version. cannam@0: cannam@0: This program is distributed in the hope that it will be useful, cannam@0: but WITHOUT ANY WARRANTY; without even the implied warranty of cannam@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the cannam@0: GNU General Public License for more details. cannam@0: cannam@0: You should have received a copy of the GNU General Public License cannam@0: along with this program; if not, write to the Free Software cannam@0: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA cannam@0: */ cannam@0: cannam@0: #include "onsetsdsplugin.h" cannam@0: #include cannam@0: cannam@0: using std::string; cannam@0: using std::vector; cannam@0: using std::cerr; cannam@0: using std::endl; cannam@0: cannam@0: cannam@0: OnsetsDSPlugin::OnsetsDSPlugin(float inputSampleRate) : cannam@0: Vamp::Plugin(inputSampleRate), cannam@0: m_ods(0), cannam@0: m_odsdata(0), cannam@0: m_dfType(ODS_ODF_RCOMPLEX), cannam@1: m_threshold(0.5), cannam@0: m_medspan(11), cannam@1: m_stepSize(256), cannam@1: m_fftSize(512) cannam@0: { cannam@0: } cannam@0: cannam@0: OnsetsDSPlugin::~OnsetsDSPlugin() cannam@0: { cannam@0: delete[] m_odsdata; cannam@0: delete m_ods; cannam@0: } cannam@0: cannam@0: string cannam@0: OnsetsDSPlugin::getIdentifier() const cannam@0: { cannam@0: return "onsetsds"; cannam@0: } cannam@0: cannam@0: string cannam@0: OnsetsDSPlugin::getName() const cannam@0: { cannam@0: return "OnsetsDS Onset Detector"; cannam@0: } cannam@0: cannam@0: string cannam@0: OnsetsDSPlugin::getDescription() const cannam@0: { cannam@0: return "Detect note onsets"; cannam@0: } cannam@0: cannam@0: string cannam@0: OnsetsDSPlugin::getMaker() const cannam@0: { cannam@0: return "Dan Stowell"; cannam@0: } cannam@0: cannam@0: int cannam@0: OnsetsDSPlugin::getPluginVersion() const cannam@0: { dan@10: return 2; cannam@0: } cannam@0: cannam@0: string cannam@0: OnsetsDSPlugin::getCopyright() const cannam@0: { cannam@0: return "Copyright (c) 2007-2008 Dan Stowell"; cannam@0: } cannam@0: cannam@0: OnsetsDSPlugin::ParameterList cannam@0: OnsetsDSPlugin::getParameterDescriptors() const cannam@0: { cannam@0: ParameterList list; cannam@0: cannam@0: ParameterDescriptor desc; cannam@0: desc.identifier = "dftype"; cannam@0: desc.name = "Onset detection function"; cannam@0: desc.description = "Method used to calculate the onset detection function"; cannam@0: desc.minValue = 0; cannam@0: desc.maxValue = 6; cannam@0: desc.defaultValue = 3; cannam@0: desc.isQuantized = true; cannam@0: desc.quantizeStep = 1; cannam@0: desc.valueNames.push_back("Power"); cannam@0: desc.valueNames.push_back("Sum of magnitudes"); cannam@0: desc.valueNames.push_back("Complex-domain deviation"); cannam@0: desc.valueNames.push_back("Rectified complex-domain deviation"); cannam@0: desc.valueNames.push_back("Phase deviation"); cannam@0: desc.valueNames.push_back("Weighted phase deviation"); cannam@0: desc.valueNames.push_back("Modified Kullback-Liebler deviation"); cannam@0: list.push_back(desc); cannam@0: cannam@1: desc.identifier = "threshold"; cannam@1: desc.name = "Detection threshold"; cannam@1: desc.description = "Onsets trigger when the function beats this value"; cannam@1: desc.minValue = 0; cannam@1: desc.maxValue = 1; cannam@1: desc.defaultValue = 0.5; cannam@1: desc.isQuantized = false; cannam@1: desc.valueNames.clear(); cannam@1: list.push_back(desc); cannam@1: cannam@0: desc.identifier = "medspan"; cannam@0: desc.name = "Median frame span"; cannam@0: desc.description = "Number of past frames used in median calculation"; cannam@0: desc.minValue = 5; cannam@0: desc.maxValue = 21; cannam@0: desc.defaultValue = 11; cannam@0: desc.isQuantized = true; cannam@0: desc.quantizeStep = 2; cannam@0: desc.valueNames.clear(); cannam@0: list.push_back(desc); cannam@0: cannam@0: return list; cannam@0: } cannam@0: cannam@0: float cannam@0: OnsetsDSPlugin::getParameter(std::string name) const cannam@0: { cannam@0: if (name == "dftype") { cannam@0: switch (m_dfType) { cannam@0: case ODS_ODF_POWER: return 0; cannam@0: case ODS_ODF_MAGSUM: return 1; cannam@0: case ODS_ODF_COMPLEX: return 2; cannam@0: case ODS_ODF_RCOMPLEX: return 3; cannam@0: case ODS_ODF_PHASE: return 4; cannam@0: case ODS_ODF_WPHASE: return 5; cannam@0: case ODS_ODF_MKL: return 6; cannam@0: } cannam@1: } else if (name == "threshold") { cannam@1: return m_threshold; cannam@0: } else if (name == "medspan") { cannam@0: return m_medspan; cannam@0: } cannam@0: return 0.0; cannam@0: } cannam@0: cannam@0: void cannam@0: OnsetsDSPlugin::setParameter(std::string name, float value) cannam@0: { cannam@0: if (name == "dftype") { cannam@0: onsetsds_odf_types dfType = m_dfType; cannam@0: switch (lrintf(value)) { cannam@0: case 0: dfType = ODS_ODF_POWER; break; cannam@0: case 1: dfType = ODS_ODF_MAGSUM; break; cannam@0: case 2: dfType = ODS_ODF_COMPLEX; break; cannam@0: case 3: dfType = ODS_ODF_RCOMPLEX; break; cannam@0: case 4: dfType = ODS_ODF_PHASE; break; cannam@0: case 5: dfType = ODS_ODF_WPHASE; break; cannam@0: case 6: dfType = ODS_ODF_MKL; break; cannam@0: } cannam@0: if (dfType == m_dfType) return; cannam@0: m_dfType = dfType; cannam@1: } else if (name == "threshold") { cannam@1: m_threshold = value; cannam@0: } else if (name == "medspan") { cannam@0: m_medspan = lrintf(value); cannam@0: } cannam@0: } cannam@0: cannam@0: bool cannam@0: OnsetsDSPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) cannam@0: { cannam@0: if (channels < getMinChannelCount() || cannam@0: channels > getMaxChannelCount()) { cannam@0: std::cerr << "OnsetsDSPlugin::initialise: Unsupported channel count: " cannam@0: << channels << std::endl; cannam@0: return false; cannam@0: } cannam@0: cannam@0: if (stepSize != getPreferredStepSize()) { cannam@0: std::cerr << "WARNING: OnsetsDSPlugin::initialise: Using unusual step size: " cannam@0: << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl; cannam@0: } cannam@0: cannam@0: if (blockSize != getPreferredBlockSize()) { cannam@0: std::cerr << "WARNING: OnsetsDSPlugin::initialise: Using unusual block size: " cannam@0: << blockSize << " (wanted " << (getPreferredBlockSize()) << ")" << std::endl; cannam@0: } cannam@0: cannam@0: m_stepSize = stepSize; cannam@0: m_fftSize = blockSize; cannam@0: cannam@0: delete[] m_odsdata; cannam@0: delete m_ods; cannam@0: cannam@0: m_odsdata = new float[onsetsds_memneeded(m_dfType, m_fftSize, m_medspan)]; cannam@0: m_ods = new OnsetsDS; cannam@0: memset(m_ods, 0, sizeof(OnsetsDS)); cannam@0: onsetsds_init(m_ods, m_odsdata, ODS_FFT_FFTW3_R2C, m_dfType, m_fftSize, cannam@0: m_medspan, m_inputSampleRate); Chris@11: m_ods->thresh = m_threshold; cannam@1: cannam@0: return true; cannam@0: } cannam@0: cannam@0: void cannam@0: OnsetsDSPlugin::reset() cannam@0: { cannam@0: if (!m_ods) { cannam@0: std::cerr << "ERROR: OnsetsDSPlugin::reset: Plugin has not been initialised" << std::endl; cannam@0: return; cannam@0: } cannam@0: onsetsds_init(m_ods, m_odsdata, ODS_FFT_FFTW3_R2C, m_dfType, m_fftSize, cannam@0: m_medspan, m_inputSampleRate); Chris@11: m_ods->thresh = m_threshold; cannam@0: } cannam@0: cannam@0: size_t cannam@0: OnsetsDSPlugin::getPreferredStepSize() const cannam@0: { cannam@1: return 256; cannam@0: } cannam@0: cannam@0: size_t cannam@0: OnsetsDSPlugin::getPreferredBlockSize() const cannam@0: { cannam@1: return 512; cannam@0: } cannam@0: cannam@0: OnsetsDSPlugin::OutputList cannam@0: OnsetsDSPlugin::getOutputDescriptors() const cannam@0: { cannam@0: OutputList list; cannam@0: cannam@0: OutputDescriptor onsets; cannam@0: onsets.identifier = "onsets"; cannam@0: onsets.name = "Note Onsets"; cannam@0: onsets.description = "Note onset positions"; cannam@0: onsets.unit = ""; cannam@0: onsets.hasFixedBinCount = true; cannam@0: onsets.binCount = 0; cannam@0: onsets.sampleType = OutputDescriptor::VariableSampleRate; cannam@0: onsets.sampleRate = (m_inputSampleRate / m_stepSize); dan@9: list.push_back(onsets); cannam@0: dan@9: //--- Extracting the ODF dan@9: onsets = OutputDescriptor(); dan@9: onsets.identifier = "odf"; dan@9: onsets.name = "Onset Detection Function"; dan@9: onsets.description = "Onset Detection Function used for detecting onsets"; dan@9: onsets.unit = ""; dan@9: onsets.hasFixedBinCount = false; dan@9: onsets.binCount = 0; dan@9: onsets.sampleType = OutputDescriptor::OneSamplePerStep; dan@9: onsets.sampleRate = (m_inputSampleRate / m_stepSize); dan@9: list.push_back(onsets); dan@9: dan@9: //--- Extracting the Filtered ODF dan@9: onsets = OutputDescriptor(); dan@9: onsets.identifier = "filtered_odf"; dan@9: onsets.name = "Filtered Onset Detection Function"; dan@9: onsets.description = "Filtered Onset Detection Function used for detecting onsets"; dan@9: onsets.unit = ""; dan@9: onsets.hasFixedBinCount = false; dan@9: onsets.binCount = 0; dan@9: onsets.sampleType = OutputDescriptor::OneSamplePerStep; dan@9: onsets.sampleRate = (m_inputSampleRate / m_stepSize); cannam@0: list.push_back(onsets); cannam@0: cannam@0: return list; cannam@0: } cannam@0: cannam@0: OnsetsDSPlugin::FeatureSet cannam@0: OnsetsDSPlugin::process(const float *const *inputBuffers, cannam@0: Vamp::RealTime timestamp) cannam@0: { cannam@0: if (!m_ods) { cannam@0: cerr << "ERROR: OnsetsDSPlugin::process: Plugin has not been initialised" cannam@0: << endl; cannam@0: return FeatureSet(); cannam@0: } cannam@0: cannam@0: // We can const_cast because we happen to know onsetsds_process cannam@0: // does not modify this buffer cannam@0: bool result = onsetsds_process(m_ods, const_cast(inputBuffers[0])); cannam@0: cannam@0: FeatureSet returnFeatures; cannam@0: cannam@0: if (result) { cannam@0: Feature feature; cannam@0: feature.hasTimestamp = true; cannam@0: feature.timestamp = timestamp; cannam@0: returnFeatures[0].push_back(feature); // onsets are output 0 cannam@0: } cannam@0: dan@9: //--- Extracting the ODF dan@9: Feature odf_feature; dan@9: odf_feature.hasTimestamp = false; dan@9: odf_feature.values.push_back(m_ods->odfvals[0]); dan@9: returnFeatures[1].push_back(odf_feature); dan@9: dan@9: //--- Extracting the Filtered ODF dan@9: Feature filtered_odf_feature; dan@9: filtered_odf_feature.hasTimestamp = false; dan@9: filtered_odf_feature.values.push_back(m_ods->odfvalpost); dan@9: returnFeatures[2].push_back(filtered_odf_feature); dan@9: cannam@0: return returnFeatures; cannam@0: } cannam@0: cannam@0: OnsetsDSPlugin::FeatureSet cannam@0: OnsetsDSPlugin::getRemainingFeatures() cannam@0: { cannam@0: return FeatureSet(); cannam@0: } cannam@0: cannam@0: cannam@0: static Vamp::PluginAdapter adapter; cannam@0: cannam@0: const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version, cannam@0: unsigned int index) cannam@0: { cannam@0: if (version < 1) return 0; cannam@0: cannam@0: switch (index) { cannam@0: case 0: return adapter.getDescriptor(); cannam@0: default: return 0; cannam@0: } cannam@0: } cannam@0: