c@89: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ c@89: c@89: /* c@89: QM Vamp Plugin Set c@89: c@89: 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@89: */ c@89: c@89: #include "BarBeatTrack.h" c@89: c@89: #include c@89: #include c@89: #include c@89: #include c@89: #include c@89: c@89: using std::string; c@89: using std::vector; c@89: using std::cerr; c@89: using std::endl; c@89: c@89: float BarBeatTracker::m_stepSecs = 0.01161; // 512 samples at 44100 c@89: c@89: class BarBeatTrackerData c@89: { c@89: public: c@89: BarBeatTrackerData(float rate, const DFConfig &config) : dfConfig(config) { luis@144: df = new DetectionFunction(config); c@89: // decimation factor aims at resampling to c. 3KHz; must be power of 2 c@89: int factor = MathUtilities::nextPowerOfTwo(rate / 3000); c@95: // std::cerr << "BarBeatTrackerData: factor = " << factor << std::endl; c@89: downBeat = new DownBeat(rate, factor, config.stepSize); c@89: } c@89: ~BarBeatTrackerData() { luis@144: delete df; c@89: delete downBeat; c@89: } c@89: void reset() { luis@144: delete df; luis@144: df = new DetectionFunction(dfConfig); luis@144: dfOutput.clear(); c@89: downBeat->resetAudioBuffer(); c@89: origin = Vamp::RealTime::zeroTime; c@89: } c@89: c@89: DFConfig dfConfig; c@89: DetectionFunction *df; c@89: DownBeat *downBeat; c@89: vector dfOutput; c@89: Vamp::RealTime origin; c@89: }; luis@144: c@89: c@89: BarBeatTracker::BarBeatTracker(float inputSampleRate) : c@89: Vamp::Plugin(inputSampleRate), c@89: m_d(0), luis@144: m_bpb(4), luis@144: m_alpha(0.9), // changes are as per the BeatTrack.cpp luis@144: m_tightness(4.), // changes are as per the BeatTrack.cpp luis@144: m_inputtempo(120.), // changes are as per the BeatTrack.cpp luis@144: m_constraintempo(false) // changes are as per the BeatTrack.cpp c@89: { c@89: } c@89: c@89: BarBeatTracker::~BarBeatTracker() c@89: { c@89: delete m_d; c@89: } c@89: c@89: string c@89: BarBeatTracker::getIdentifier() const c@89: { c@89: return "qm-barbeattracker"; c@89: } c@89: c@89: string c@89: BarBeatTracker::getName() const c@89: { c@89: return "Bar and Beat Tracker"; c@89: } c@89: c@89: string c@89: BarBeatTracker::getDescription() const c@89: { c@89: return "Estimate bar and beat locations"; c@89: } c@89: c@89: string c@89: BarBeatTracker::getMaker() const c@89: { c@89: return "Queen Mary, University of London"; c@89: } c@89: c@89: int c@89: BarBeatTracker::getPluginVersion() const c@89: { c@145: return 3; c@89: } c@89: c@89: string c@89: BarBeatTracker::getCopyright() const c@89: { c@149: return "Plugin by Matthew Davies, Christian Landone and Chris Cannam. Copyright (c) 2006-2013 QMUL - All Rights Reserved"; c@89: } c@89: c@89: BarBeatTracker::ParameterList c@89: BarBeatTracker::getParameterDescriptors() const c@89: { c@89: ParameterList list; c@89: c@89: ParameterDescriptor desc; c@89: c@89: desc.identifier = "bpb"; c@89: desc.name = "Beats per Bar"; c@89: desc.description = "The number of beats in each bar"; c@89: desc.minValue = 2; c@89: desc.maxValue = 16; c@89: desc.defaultValue = 4; c@89: desc.isQuantized = true; c@89: desc.quantizeStep = 1; c@89: list.push_back(desc); c@89: luis@144: // changes are as per the BeatTrack.cpp luis@144: //Alpha Parameter of Beat Tracker luis@144: desc.identifier = "alpha"; luis@144: desc.name = "Alpha"; luis@144: desc.description = "Inertia - Flexibility Trade Off"; luis@144: desc.minValue = 0.1; luis@144: desc.maxValue = 0.99; luis@144: desc.defaultValue = 0.90; luis@144: desc.unit = ""; luis@144: desc.isQuantized = false; luis@144: list.push_back(desc); luis@144: c@148: // We aren't exposing tightness as a parameter, it's fixed at 4 luis@144: luis@144: // changes are as per the BeatTrack.cpp luis@144: //User input tempo luis@144: desc.identifier = "inputtempo"; c@148: desc.name = "Tempo Hint"; c@151: desc.description = "User-defined tempo on which to centre the tempo preference function"; luis@144: desc.minValue = 50; luis@144: desc.maxValue = 250; luis@144: desc.defaultValue = 120; luis@144: desc.unit = "BPM"; luis@144: desc.isQuantized = true; luis@144: list.push_back(desc); luis@144: luis@144: // changes are as per the BeatTrack.cpp luis@144: desc.identifier = "constraintempo"; luis@144: desc.name = "Constrain Tempo"; c@148: desc.description = "Constrain more tightly around the tempo hint, using a Gaussian weighting instead of Rayleigh"; luis@144: desc.minValue = 0; luis@144: desc.maxValue = 1; luis@144: desc.defaultValue = 0; luis@144: desc.isQuantized = true; luis@144: desc.quantizeStep = 1; luis@144: desc.unit = ""; luis@144: desc.valueNames.clear(); luis@144: list.push_back(desc); luis@144: luis@144: c@89: return list; c@89: } c@89: c@89: float c@89: BarBeatTracker::getParameter(std::string name) const c@89: { luis@144: if (name == "bpb") { luis@144: return m_bpb; luis@144: } else if (name == "alpha") { luis@144: return m_alpha; luis@144: } else if (name == "inputtempo") { luis@144: return m_inputtempo; luis@144: } else if (name == "constraintempo") { luis@144: return m_constraintempo ? 1.0 : 0.0; luis@144: } c@89: return 0.0; c@89: } c@89: c@89: void c@89: BarBeatTracker::setParameter(std::string name, float value) c@89: { luis@144: if (name == "bpb") { luis@144: m_bpb = lrintf(value); luis@144: } else if (name == "alpha") { luis@144: m_alpha = value; luis@144: } else if (name == "inputtempo") { luis@144: m_inputtempo = value; luis@144: } else if (name == "constraintempo") { luis@144: m_constraintempo = (value > 0.5); luis@144: } c@89: } c@89: c@89: bool c@89: BarBeatTracker::initialise(size_t channels, size_t stepSize, size_t blockSize) c@89: { c@89: if (m_d) { luis@144: delete m_d; luis@144: m_d = 0; c@89: } c@89: c@89: if (channels < getMinChannelCount() || luis@144: channels > getMaxChannelCount()) { c@89: std::cerr << "BarBeatTracker::initialise: Unsupported channel count: " c@89: << channels << std::endl; c@89: return false; c@89: } c@89: c@89: if (stepSize != getPreferredStepSize()) { c@89: std::cerr << "ERROR: BarBeatTracker::initialise: Unsupported step size for this sample rate: " c@89: << stepSize << " (wanted " << (getPreferredStepSize()) << ")" << std::endl; c@89: return false; c@89: } c@89: c@89: if (blockSize != getPreferredBlockSize()) { c@89: std::cerr << "WARNING: BarBeatTracker::initialise: Sub-optimal block size for this sample rate: " c@89: << blockSize << " (wanted " << getPreferredBlockSize() << ")" << std::endl; c@89: // return false; c@89: } c@89: c@89: DFConfig dfConfig; c@89: dfConfig.DFType = DF_COMPLEXSD; c@89: dfConfig.stepSize = stepSize; c@89: dfConfig.frameLength = blockSize; c@89: dfConfig.dbRise = 3; c@89: dfConfig.adaptiveWhitening = false; c@89: dfConfig.whiteningRelaxCoeff = -1; c@89: dfConfig.whiteningFloor = -1; luis@144: c@89: m_d = new BarBeatTrackerData(m_inputSampleRate, dfConfig); c@89: m_d->downBeat->setBeatsPerBar(m_bpb); c@89: return true; c@89: } c@89: c@89: void c@89: BarBeatTracker::reset() c@89: { c@89: if (m_d) m_d->reset(); c@89: } c@89: c@89: size_t c@89: BarBeatTracker::getPreferredStepSize() const c@89: { c@89: size_t step = size_t(m_inputSampleRate * m_stepSecs + 0.0001); c@95: if (step < 1) step = 1; c@89: // std::cerr << "BarBeatTracker::getPreferredStepSize: input sample rate is " << m_inputSampleRate << ", step size is " << step << std::endl; c@89: return step; c@89: } c@89: c@89: size_t c@89: BarBeatTracker::getPreferredBlockSize() const c@89: { c@89: size_t theoretical = getPreferredStepSize() * 2; c@89: c@89: // I think this is not necessarily going to be a power of two, and c@89: // the host might have a problem with that, but I'm not sure we c@89: // can do much about it here c@89: return theoretical; c@89: } c@89: c@89: BarBeatTracker::OutputList c@89: BarBeatTracker::getOutputDescriptors() const c@89: { c@89: OutputList list; c@89: c@89: OutputDescriptor beat; c@89: beat.identifier = "beats"; c@89: beat.name = "Beats"; c@89: beat.description = "Beat locations labelled with metrical position"; c@89: beat.unit = ""; c@89: beat.hasFixedBinCount = true; c@89: beat.binCount = 0; c@89: beat.sampleType = OutputDescriptor::VariableSampleRate; c@89: beat.sampleRate = 1.0 / m_stepSecs; c@89: c@89: OutputDescriptor bars; c@89: bars.identifier = "bars"; c@89: bars.name = "Bars"; c@89: bars.description = "Bar locations"; c@89: bars.unit = ""; c@89: bars.hasFixedBinCount = true; c@89: bars.binCount = 0; c@89: bars.sampleType = OutputDescriptor::VariableSampleRate; c@89: bars.sampleRate = 1.0 / m_stepSecs; c@89: c@89: OutputDescriptor beatcounts; c@89: beatcounts.identifier = "beatcounts"; c@89: beatcounts.name = "Beat Count"; c@89: beatcounts.description = "Beat counter function"; c@89: beatcounts.unit = ""; c@89: beatcounts.hasFixedBinCount = true; c@89: beatcounts.binCount = 1; c@89: beatcounts.sampleType = OutputDescriptor::VariableSampleRate; c@89: beatcounts.sampleRate = 1.0 / m_stepSecs; c@89: c@90: OutputDescriptor beatsd; c@90: beatsd.identifier = "beatsd"; c@90: beatsd.name = "Beat Spectral Difference"; c@90: beatsd.description = "Beat spectral difference function used for bar-line detection"; c@90: beatsd.unit = ""; c@90: beatsd.hasFixedBinCount = true; c@90: beatsd.binCount = 1; c@90: beatsd.sampleType = OutputDescriptor::VariableSampleRate; c@90: beatsd.sampleRate = 1.0 / m_stepSecs; c@90: c@89: list.push_back(beat); c@89: list.push_back(bars); c@89: list.push_back(beatcounts); c@90: list.push_back(beatsd); c@89: c@89: return list; c@89: } c@89: c@89: BarBeatTracker::FeatureSet c@89: BarBeatTracker::process(const float *const *inputBuffers, c@89: Vamp::RealTime timestamp) c@89: { c@89: if (!m_d) { luis@144: cerr << "ERROR: BarBeatTracker::process: " luis@144: << "BarBeatTracker has not been initialised" luis@144: << endl; luis@144: return FeatureSet(); c@89: } c@89: c@89: // We use time domain input, because DownBeat requires it -- so we c@89: // use the time-domain version of DetectionFunction::process which c@89: // does its own FFT. It requires doubles as input, so we need to c@89: // make a temporary copy c@89: c@89: // We only support a single input channel c@89: c@89: const int fl = m_d->dfConfig.frameLength; c@190: double *dfinput = new double[fl]; c@89: c@190: for (int i = 0; i < fl; ++i) dfinput[i] = inputBuffers[0][i]; c@153: double output = m_d->df->processTimeDomain(dfinput); c@89: c@190: delete[] dfinput; c@190: c@89: if (m_d->dfOutput.empty()) m_d->origin = timestamp; c@89: c@93: // std::cerr << "df[" << m_d->dfOutput.size() << "] is " << output << std::endl; c@89: m_d->dfOutput.push_back(output); c@89: c@89: // Downsample and store the incoming audio block. c@89: // We have an overlap on the incoming audio stream (step size is c@89: // half block size) -- this function is configured to take only a c@89: // step size's worth, so effectively ignoring the overlap. Note c@89: // however that this means we omit the last blocksize - stepsize c@89: // samples completely for the purposes of barline detection c@89: // (hopefully not a problem) c@89: m_d->downBeat->pushAudioBlock(inputBuffers[0]); c@89: c@89: return FeatureSet(); c@89: } c@89: c@89: BarBeatTracker::FeatureSet c@89: BarBeatTracker::getRemainingFeatures() c@89: { c@89: if (!m_d) { luis@144: cerr << "ERROR: BarBeatTracker::getRemainingFeatures: " luis@144: << "BarBeatTracker has not been initialised" luis@144: << endl; luis@144: return FeatureSet(); c@89: } c@89: c@89: return barBeatTrack(); c@89: } c@89: c@89: BarBeatTracker::FeatureSet c@89: BarBeatTracker::barBeatTrack() c@89: { c@89: vector df; c@89: vector beatPeriod; c@89: vector tempi; c@89: c@89: for (size_t i = 2; i < m_d->dfOutput.size(); ++i) { // discard first two elts c@89: df.push_back(m_d->dfOutput[i]); c@89: beatPeriod.push_back(0.0); c@89: } c@89: if (df.empty()) return FeatureSet(); c@89: c@89: TempoTrackV2 tt(m_inputSampleRate, m_d->dfConfig.stepSize); luis@144: luis@144: // changes are as per the BeatTrack.cpp - allow m_inputtempo and m_constraintempo to be set be the user luis@144: tt.calculateBeatPeriod(df, beatPeriod, tempi, m_inputtempo, m_constraintempo); c@89: c@89: vector beats; luis@144: // changes are as per the BeatTrack.cpp - allow m_alpha and m_tightness to be set be the user luis@144: tt.calculateBeats(df, beatPeriod, beats, m_alpha, m_tightness); luis@144: luis@144: // tt.calculateBeatPeriod(df, beatPeriod, tempi, 0., 0); // use default parameters luis@144: luis@144: // vector beats; luis@144: // tt.calculateBeats(df, beatPeriod, beats, 0.9, 4.); // use default parameters until i fix this plugin too c@89: c@89: vector downbeats; c@89: size_t downLength = 0; c@89: const float *downsampled = m_d->downBeat->getBufferedAudio(downLength); c@89: m_d->downBeat->findDownBeats(downsampled, downLength, beats, downbeats); c@89: c@90: vector beatsd; c@90: m_d->downBeat->getBeatSD(beatsd); c@90: c@89: // std::cerr << "BarBeatTracker: found downbeats at: "; c@89: // for (int i = 0; i < downbeats.size(); ++i) std::cerr << downbeats[i] << " " << std::endl; luis@144: c@89: FeatureSet returnFeatures; c@89: c@89: char label[20]; c@89: c@89: int dbi = 0; c@89: int beat = 0; c@89: int bar = 0; c@89: c@124: if (!downbeats.empty()) { c@124: // get the right number for the first beat; this will be c@124: // incremented before use (at top of the following loop) c@124: int firstDown = downbeats[0]; c@124: beat = m_bpb - firstDown - 1; c@124: if (beat == m_bpb) beat = 0; c@124: } c@124: c@178: for (int i = 0; i < int(beats.size()); ++i) { c@89: c@178: size_t frame = size_t(beats[i]) * m_d->dfConfig.stepSize; c@89: c@178: if (dbi < int(downbeats.size()) && i == downbeats[dbi]) { c@89: beat = 0; c@89: ++bar; c@89: ++dbi; c@89: } else { c@89: ++beat; c@89: } c@89: c@89: // outputs are: c@89: // c@89: // 0 -> beats c@89: // 1 -> bars c@89: // 2 -> beat counter function luis@144: c@178: Feature feature; c@178: feature.hasTimestamp = true; c@178: feature.timestamp = m_d->origin + Vamp::RealTime::frame2RealTime c@178: (frame, lrintf(m_inputSampleRate)); c@89: c@89: sprintf(label, "%d", beat + 1); c@89: feature.label = label; c@178: returnFeatures[0].push_back(feature); // labelled beats c@89: c@89: feature.values.push_back(beat + 1); c@89: returnFeatures[2].push_back(feature); // beat function c@89: c@178: if (i > 0 && i <= int(beatsd.size())) { c@90: feature.values.clear(); c@90: feature.values.push_back(beatsd[i-1]); c@90: feature.label = ""; c@90: returnFeatures[3].push_back(feature); // beat spectral difference c@90: } c@90: c@89: if (beat == 0) { c@89: feature.values.clear(); c@89: sprintf(label, "%d", bar); c@89: feature.label = label; c@89: returnFeatures[1].push_back(feature); // bars c@89: } c@89: } c@89: c@89: return returnFeatures; c@89: } c@89: