Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Vamp feature extraction plugin for the BeatRoot beat tracker. Chris@0: Chris@0: Centre for Digital Music, Queen Mary, University of London. Chris@0: This file copyright 2011 Simon Dixon, Chris Cannam and QMUL. Chris@0: Chris@0: This program is free software; you can redistribute it and/or Chris@0: modify it under the terms of the GNU General Public License as Chris@0: published by the Free Software Foundation; either version 2 of the Chris@0: License, or (at your option) any later version. See the file Chris@0: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include "BeatRootVampPlugin.h" Chris@2: #include "BeatRootProcessor.h" Chris@0: Chris@10: #include "Event.h" Chris@10: Chris@10: #include Chris@9: #include Chris@0: Chris@0: BeatRootVampPlugin::BeatRootVampPlugin(float inputSampleRate) : Chris@31: Plugin(inputSampleRate), Chris@31: m_firstFrame(true) Chris@0: { Chris@23: m_processor = new BeatRootProcessor(inputSampleRate, AgentParameters()); Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::~BeatRootVampPlugin() Chris@0: { Chris@0: delete m_processor; Chris@0: } Chris@0: Chris@0: string Chris@0: BeatRootVampPlugin::getIdentifier() const Chris@0: { Chris@0: return "beatroot"; Chris@0: } Chris@0: Chris@0: string Chris@0: BeatRootVampPlugin::getName() const Chris@0: { Chris@0: return "BeatRoot Beat Tracker"; Chris@0: } Chris@0: Chris@0: string Chris@0: BeatRootVampPlugin::getDescription() const Chris@0: { Chris@0: return "Identify beat locations in music"; Chris@0: } Chris@0: Chris@0: string Chris@0: BeatRootVampPlugin::getMaker() const Chris@0: { Chris@0: return "Simon Dixon (plugin by Chris Cannam)"; Chris@0: } Chris@0: Chris@0: int Chris@0: BeatRootVampPlugin::getPluginVersion() const Chris@0: { Chris@0: // Increment this each time you release a version that behaves Chris@0: // differently from the previous one Chris@0: return 1; Chris@0: } Chris@0: Chris@0: string Chris@0: BeatRootVampPlugin::getCopyright() const Chris@0: { Chris@0: return "GPL"; Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::InputDomain Chris@0: BeatRootVampPlugin::getInputDomain() const Chris@0: { Chris@0: return FrequencyDomain; Chris@0: } Chris@0: Chris@0: size_t Chris@0: BeatRootVampPlugin::getPreferredBlockSize() const Chris@0: { Chris@0: return m_processor->getFFTSize(); Chris@0: } Chris@0: Chris@0: size_t Chris@0: BeatRootVampPlugin::getPreferredStepSize() const Chris@0: { Chris@0: return m_processor->getHopSize(); Chris@0: } Chris@0: Chris@0: size_t Chris@0: BeatRootVampPlugin::getMinChannelCount() const Chris@0: { Chris@0: return 1; Chris@0: } Chris@0: Chris@0: size_t Chris@0: BeatRootVampPlugin::getMaxChannelCount() const Chris@0: { Chris@0: return 1; Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::ParameterList Chris@0: BeatRootVampPlugin::getParameterDescriptors() const Chris@0: { Chris@0: ParameterList list; Chris@23: Chris@23: ParameterDescriptor desc; Chris@23: Chris@23: desc.identifier = "preMarginFactor"; Chris@23: desc.name = "Pre-Margin Factor"; Chris@23: desc.description = "The maximum amount by which a beat can be earlier than the predicted beat time, expressed as a fraction of the beat period."; Chris@23: desc.minValue = 0; Chris@23: desc.maxValue = 1; Chris@23: desc.defaultValue = AgentParameters::DEFAULT_PRE_MARGIN_FACTOR; Chris@23: desc.isQuantized = false; Chris@23: list.push_back(desc); Chris@23: Chris@23: desc.identifier = "postMarginFactor"; Chris@23: desc.name = "Post-Margin Factor"; Chris@23: desc.description = "The maximum amount by which a beat can be later than the predicted beat time, expressed as a fraction of the beat period."; Chris@23: desc.minValue = 0; Chris@23: desc.maxValue = 1; Chris@23: desc.defaultValue = AgentParameters::DEFAULT_POST_MARGIN_FACTOR; Chris@23: desc.isQuantized = false; Chris@23: list.push_back(desc); Chris@23: Chris@23: desc.identifier = "maxChange"; Chris@23: desc.name = "Maximum Change"; Chris@23: desc.description = "The maximum allowed deviation from the initial tempo, expressed as a fraction of the initial beat period."; Chris@23: desc.minValue = 0; Chris@23: desc.maxValue = 1; Chris@23: desc.defaultValue = AgentParameters::DEFAULT_MAX_CHANGE; Chris@23: desc.isQuantized = false; Chris@23: list.push_back(desc); Chris@23: Chris@23: desc.identifier = "expiryTime"; Chris@23: desc.name = "Expiry Time"; Chris@23: desc.description = "The default value of expiryTime, which is the time (in seconds) after which an Agent that has no Event matching its beat predictions will be destroyed."; Chris@23: desc.minValue = 2; Chris@23: desc.maxValue = 120; Chris@23: desc.defaultValue = AgentParameters::DEFAULT_EXPIRY_TIME; Chris@23: desc.isQuantized = false; Chris@23: list.push_back(desc); Chris@23: Chris@23: // Simon says... Chris@23: Chris@23: // These are the parameters that should be exposed (Agent.cpp): Chris@23: Chris@23: // If Pop, both margins should be lower (0.1). If classical Chris@23: // music, post margin can be increased Chris@23: // Chris@23: // double Agent::POST_MARGIN_FACTOR = 0.3; Chris@23: // double Agent::PRE_MARGIN_FACTOR = 0.15; Chris@23: // Chris@23: // Max Change tells us how much tempo can change - so for Chris@23: // classical we should make it higher Chris@23: // Chris@23: // double Agent::MAX_CHANGE = 0.2; Chris@23: // Chris@23: // The EXPIRY TIME default should be defaulted to 100 (usual cause Chris@23: // of agents dying....) it should also be exposed in order to Chris@23: // troubleshoot eventual problems in songs with big silences in Chris@23: // the beggining/end. Chris@23: // Chris@23: // const double Agent::DEFAULT_EXPIRY_TIME = 10.0; Chris@23: Chris@0: return list; Chris@0: } Chris@0: Chris@0: float Chris@0: BeatRootVampPlugin::getParameter(string identifier) const Chris@0: { Chris@23: if (identifier == "preMarginFactor") { Chris@23: return m_parameters.preMarginFactor; Chris@23: } else if (identifier == "postMarginFactor") { Chris@23: return m_parameters.postMarginFactor; Chris@23: } else if (identifier == "maxChange") { Chris@23: return m_parameters.maxChange; Chris@23: } else if (identifier == "expiryTime") { Chris@23: return m_parameters.expiryTime; Chris@23: } Chris@23: Chris@0: return 0; Chris@0: } Chris@0: Chris@0: void Chris@0: BeatRootVampPlugin::setParameter(string identifier, float value) Chris@0: { Chris@23: if (identifier == "preMarginFactor") { Chris@23: m_parameters.preMarginFactor = value; Chris@23: } else if (identifier == "postMarginFactor") { Chris@23: m_parameters.postMarginFactor = value; Chris@23: } else if (identifier == "maxChange") { Chris@23: m_parameters.maxChange = value; Chris@23: } else if (identifier == "expiryTime") { Chris@23: m_parameters.expiryTime = value; Chris@23: } Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::ProgramList Chris@0: BeatRootVampPlugin::getPrograms() const Chris@0: { Chris@0: ProgramList list; Chris@0: return list; Chris@0: } Chris@0: Chris@0: string Chris@0: BeatRootVampPlugin::getCurrentProgram() const Chris@0: { Chris@0: return ""; // no programs Chris@0: } Chris@0: Chris@0: void Chris@0: BeatRootVampPlugin::selectProgram(string name) Chris@0: { Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::OutputList Chris@0: BeatRootVampPlugin::getOutputDescriptors() const Chris@0: { Chris@0: OutputList list; Chris@0: Chris@0: // See OutputDescriptor documentation for the possibilities here. Chris@0: // Every plugin must have at least one output. Chris@0: Chris@0: OutputDescriptor d; Chris@0: d.identifier = "beats"; Chris@0: d.name = "Beats"; Chris@0: d.description = "Estimated beat locations"; Chris@0: d.unit = ""; Chris@0: d.hasFixedBinCount = true; Chris@0: d.binCount = 0; Chris@0: d.hasKnownExtents = false; Chris@0: d.isQuantized = false; Chris@0: d.sampleType = OutputDescriptor::VariableSampleRate; Chris@19: d.sampleRate = m_inputSampleRate; Chris@0: d.hasDuration = false; Chris@0: list.push_back(d); Chris@0: Chris@36: d.identifier = "unfilled"; Chris@36: d.name = "Un-interpolated beats"; Chris@36: d.description = "Locations of detected beats, before agent interpolation occurs"; Chris@36: list.push_back(d); Chris@36: Chris@0: return list; Chris@0: } Chris@0: Chris@0: bool Chris@0: BeatRootVampPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) Chris@0: { Chris@0: if (channels < getMinChannelCount() || Chris@0: channels > getMaxChannelCount()) { Chris@0: std::cerr << "BeatRootVampPlugin::initialise: Unsupported number (" Chris@0: << channels << ") of channels" << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (stepSize != getPreferredStepSize()) { Chris@0: std::cerr << "BeatRootVampPlugin::initialise: Unsupported step size " Chris@0: << "for sample rate (" << stepSize << ", required step is " Chris@0: << getPreferredStepSize() << " for rate " << m_inputSampleRate Chris@0: << ")" << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@0: if (blockSize != getPreferredBlockSize()) { Chris@0: std::cerr << "BeatRootVampPlugin::initialise: Unsupported block size " Chris@0: << "for sample rate (" << blockSize << ", required size is " Chris@0: << getPreferredBlockSize() << " for rate " << m_inputSampleRate Chris@0: << ")" << std::endl; Chris@0: return false; Chris@0: } Chris@0: Chris@23: // Delete the processor that was created with default parameters Chris@23: // and used to determine the expected step and block size; replace Chris@23: // with one using the actual parameters we have Chris@23: delete m_processor; Chris@23: m_processor = new BeatRootProcessor(m_inputSampleRate, m_parameters); Chris@0: Chris@0: return true; Chris@0: } Chris@0: Chris@0: void Chris@0: BeatRootVampPlugin::reset() Chris@0: { Chris@0: m_processor->reset(); Chris@31: m_firstFrame = true; Chris@31: m_origin = Vamp::RealTime::zeroTime; Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::FeatureSet Chris@0: BeatRootVampPlugin::process(const float *const *inputBuffers, Vamp::RealTime timestamp) Chris@0: { Chris@31: if (m_firstFrame) { Chris@31: m_origin = timestamp; Chris@31: m_firstFrame = false; Chris@31: } Chris@31: Chris@10: m_processor->processFrame(inputBuffers); Chris@0: return FeatureSet(); Chris@0: } Chris@0: Chris@0: BeatRootVampPlugin::FeatureSet Chris@0: BeatRootVampPlugin::getRemainingFeatures() Chris@0: { Chris@36: EventList unfilled; Chris@36: EventList el = m_processor->beatTrack(&unfilled); Chris@22: Chris@10: Feature f; Chris@10: f.hasTimestamp = true; Chris@10: f.hasDuration = false; Chris@10: f.label = ""; Chris@10: f.values.clear(); Chris@10: Chris@10: FeatureSet fs; Chris@10: Chris@13: for (EventList::const_iterator i = el.begin(); i != el.end(); ++i) { Chris@31: f.timestamp = m_origin + Vamp::RealTime::fromSeconds(i->time); Chris@10: fs[0].push_back(f); Chris@10: } Chris@10: Chris@36: for (EventList::const_iterator i = unfilled.begin(); Chris@36: i != unfilled.end(); ++i) { Chris@36: f.timestamp = m_origin + Vamp::RealTime::fromSeconds(i->time); Chris@36: fs[1].push_back(f); Chris@36: } Chris@36: Chris@10: return fs; Chris@0: } Chris@0: Chris@0: Chris@0: static Vamp::PluginAdapter brAdapter; Chris@0: Chris@0: const VampPluginDescriptor *vampGetPluginDescriptor(unsigned int version, Chris@0: unsigned int index) Chris@0: { Chris@0: if (version < 1) return 0; Chris@0: Chris@0: switch (index) { Chris@0: case 0: return brAdapter.getDescriptor(); Chris@0: default: return 0; Chris@0: } Chris@0: } Chris@0: