cannam@198: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@198: cannam@198: /* cannam@198: Vamp cannam@198: cannam@198: An API for audio analysis and feature extraction plugins. cannam@198: cannam@198: Centre for Digital Music, Queen Mary, University of London. cannam@198: Copyright 2006-2008 Chris Cannam and QMUL. cannam@198: cannam@198: Permission is hereby granted, free of charge, to any person cannam@198: obtaining a copy of this software and associated documentation cannam@198: files (the "Software"), to deal in the Software without cannam@198: restriction, including without limitation the rights to use, copy, cannam@198: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@198: of the Software, and to permit persons to whom the Software is cannam@198: furnished to do so, subject to the following conditions: cannam@198: cannam@198: The above copyright notice and this permission notice shall be cannam@198: included in all copies or substantial portions of the Software. cannam@198: cannam@198: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@198: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@198: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@198: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@198: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@198: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@198: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@198: cannam@198: Except as contained in this notice, the names of the Centre for cannam@198: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@198: shall not be used in advertising or otherwise to promote the sale, cannam@198: use or other dealings in this Software without prior written cannam@198: authorization. cannam@198: */ cannam@198: cannam@198: #include "FixedTempoEstimator.h" cannam@198: cannam@198: using std::string; cannam@198: using std::vector; cannam@198: using std::cerr; cannam@198: using std::endl; cannam@198: cannam@198: using Vamp::RealTime; cannam@198: cannam@198: #include cannam@198: cannam@198: cannam@198: FixedTempoEstimator::FixedTempoEstimator(float inputSampleRate) : cannam@198: Plugin(inputSampleRate), cannam@198: m_stepSize(0), cannam@198: m_blockSize(0), cannam@198: m_priorMagnitudes(0), cannam@198: m_df(0) cannam@198: { cannam@198: } cannam@198: cannam@198: FixedTempoEstimator::~FixedTempoEstimator() cannam@198: { cannam@198: delete[] m_priorMagnitudes; cannam@198: delete[] m_df; cannam@198: } cannam@198: cannam@198: string cannam@198: FixedTempoEstimator::getIdentifier() const cannam@198: { cannam@198: return "fixedtempo"; cannam@198: } cannam@198: cannam@198: string cannam@198: FixedTempoEstimator::getName() const cannam@198: { cannam@198: return "Simple Fixed Tempo Estimator"; cannam@198: } cannam@198: cannam@198: string cannam@198: FixedTempoEstimator::getDescription() const cannam@198: { cannam@198: return "Study a short section of audio and estimate its tempo, assuming the tempo is constant"; cannam@198: } cannam@198: cannam@198: string cannam@198: FixedTempoEstimator::getMaker() const cannam@198: { cannam@198: return "Vamp SDK Example Plugins"; cannam@198: } cannam@198: cannam@198: int cannam@198: FixedTempoEstimator::getPluginVersion() const cannam@198: { cannam@198: return 1; cannam@198: } cannam@198: cannam@198: string cannam@198: FixedTempoEstimator::getCopyright() const cannam@198: { cannam@198: return "Code copyright 2008 Queen Mary, University of London. Freely redistributable (BSD license)"; cannam@198: } cannam@198: cannam@198: size_t cannam@198: FixedTempoEstimator::getPreferredStepSize() const cannam@198: { cannam@198: return 0; cannam@198: } cannam@198: cannam@198: size_t cannam@198: FixedTempoEstimator::getPreferredBlockSize() const cannam@198: { cannam@198: return 128; cannam@198: } cannam@198: cannam@198: bool cannam@198: FixedTempoEstimator::initialise(size_t channels, size_t stepSize, size_t blockSize) cannam@198: { cannam@198: if (channels < getMinChannelCount() || cannam@198: channels > getMaxChannelCount()) return false; cannam@198: cannam@198: m_stepSize = stepSize; cannam@198: m_blockSize = blockSize; cannam@198: cannam@198: float dfLengthSecs = 8.f; cannam@198: m_dfsize = (dfLengthSecs * m_inputSampleRate) / m_stepSize; cannam@198: cannam@198: m_priorMagnitudes = new float[m_blockSize/2]; cannam@198: m_df = new float[m_dfsize]; cannam@198: cannam@198: for (size_t i = 0; i < m_blockSize/2; ++i) { cannam@198: m_priorMagnitudes[i] = 0.f; cannam@198: } cannam@198: for (size_t i = 0; i < m_dfsize; ++i) { cannam@198: m_df[i] = 0.f; cannam@198: } cannam@198: cannam@198: m_n = 0; cannam@198: cannam@198: return true; cannam@198: } cannam@198: cannam@198: void cannam@198: FixedTempoEstimator::reset() cannam@198: { cannam@198: std::cerr << "FixedTempoEstimator: reset called" << std::endl; cannam@198: cannam@198: if (!m_priorMagnitudes) return; cannam@198: cannam@198: std::cerr << "FixedTempoEstimator: resetting" << std::endl; cannam@198: cannam@198: for (size_t i = 0; i < m_blockSize/2; ++i) { cannam@198: m_priorMagnitudes[i] = 0.f; cannam@198: } cannam@198: for (size_t i = 0; i < m_dfsize; ++i) { cannam@198: m_df[i] = 0.f; cannam@198: } cannam@198: cannam@198: m_n = 0; cannam@198: cannam@198: m_start = RealTime::zeroTime; cannam@198: m_lasttime = RealTime::zeroTime; cannam@198: } cannam@198: cannam@198: FixedTempoEstimator::ParameterList cannam@198: FixedTempoEstimator::getParameterDescriptors() const cannam@198: { cannam@198: ParameterList list; cannam@198: return list; cannam@198: } cannam@198: cannam@198: float cannam@198: FixedTempoEstimator::getParameter(std::string id) const cannam@198: { cannam@198: return 0.f; cannam@198: } cannam@198: cannam@198: void cannam@198: FixedTempoEstimator::setParameter(std::string id, float value) cannam@198: { cannam@198: } cannam@198: cannam@198: FixedTempoEstimator::OutputList cannam@198: FixedTempoEstimator::getOutputDescriptors() const cannam@198: { cannam@198: OutputList list; cannam@198: cannam@198: OutputDescriptor d; cannam@198: d.identifier = "tempo"; cannam@198: d.name = "Tempo"; cannam@198: d.description = "Estimated tempo"; cannam@198: d.unit = "bpm"; cannam@198: d.hasFixedBinCount = true; cannam@198: d.binCount = 1; cannam@198: d.hasKnownExtents = false; cannam@198: d.isQuantized = false; cannam@198: d.sampleType = OutputDescriptor::VariableSampleRate; cannam@198: d.sampleRate = m_inputSampleRate; cannam@198: d.hasDuration = true; // our returned tempo spans a certain range cannam@198: list.push_back(d); cannam@198: cannam@198: d.identifier = "detectionfunction"; cannam@198: d.name = "Detection Function"; cannam@198: d.description = "Onset detection function"; cannam@198: d.unit = ""; cannam@198: d.hasFixedBinCount = 1; cannam@198: d.binCount = 1; cannam@198: d.hasKnownExtents = true; cannam@198: d.minValue = 0.0; cannam@198: d.maxValue = 1.0; cannam@198: d.isQuantized = false; cannam@198: d.quantizeStep = 0.0; cannam@198: d.sampleType = OutputDescriptor::FixedSampleRate; cannam@198: if (m_stepSize) { cannam@198: d.sampleRate = m_inputSampleRate / m_stepSize; cannam@198: } else { cannam@198: d.sampleRate = m_inputSampleRate / (getPreferredBlockSize()/2); cannam@198: } cannam@198: d.hasDuration = false; cannam@198: list.push_back(d); cannam@198: cannam@198: d.identifier = "acf"; cannam@198: d.name = "Autocorrelation Function"; cannam@198: d.description = "Autocorrelation of onset detection function"; cannam@198: d.hasKnownExtents = false; cannam@198: list.push_back(d); cannam@198: cannam@198: d.identifier = "filtered_acf"; cannam@198: d.name = "Filtered Autocorrelation"; cannam@198: d.description = "Filtered autocorrelation of onset detection function"; cannam@198: list.push_back(d); cannam@198: cannam@198: return list; cannam@198: } cannam@198: cannam@198: FixedTempoEstimator::FeatureSet cannam@198: FixedTempoEstimator::process(const float *const *inputBuffers, RealTime ts) cannam@198: { cannam@198: FeatureSet fs; cannam@198: cannam@198: if (m_stepSize == 0) { cannam@198: cerr << "ERROR: FixedTempoEstimator::process: " cannam@198: << "FixedTempoEstimator has not been initialised" cannam@198: << endl; cannam@198: return fs; cannam@198: } cannam@198: cannam@198: if (m_n < m_dfsize) std::cerr << "m_n = " << m_n << std::endl; cannam@198: cannam@198: if (m_n == 0) m_start = ts; cannam@198: m_lasttime = ts; cannam@198: cannam@198: if (m_n == m_dfsize) { cannam@198: fs = calculateFeatures(); cannam@198: ++m_n; cannam@198: return fs; cannam@198: } cannam@198: cannam@198: if (m_n > m_dfsize) return FeatureSet(); cannam@198: cannam@198: int count = 0; cannam@198: cannam@198: for (size_t i = 1; i < m_blockSize/2; ++i) { cannam@198: cannam@198: float real = inputBuffers[0][i*2]; cannam@198: float imag = inputBuffers[0][i*2 + 1]; cannam@198: cannam@198: float sqrmag = real * real + imag * imag; cannam@198: cannam@198: if (m_priorMagnitudes[i] > 0.f) { cannam@198: float diff = 10.f * log10f(sqrmag / m_priorMagnitudes[i]); cannam@198: if (diff >= 3.f) ++count; cannam@198: } cannam@198: cannam@198: m_priorMagnitudes[i] = sqrmag; cannam@198: } cannam@198: cannam@198: m_df[m_n] = float(count) / float(m_blockSize/2); cannam@198: ++m_n; cannam@198: return fs; cannam@198: } cannam@198: cannam@198: FixedTempoEstimator::FeatureSet cannam@198: FixedTempoEstimator::getRemainingFeatures() cannam@198: { cannam@198: FeatureSet fs; cannam@198: if (m_n > m_dfsize) return fs; cannam@198: fs = calculateFeatures(); cannam@198: ++m_n; cannam@198: return fs; cannam@198: } cannam@198: cannam@198: float cannam@198: FixedTempoEstimator::lag2tempo(int lag) { cannam@198: return 60.f / ((lag * m_stepSize) / m_inputSampleRate); cannam@198: } cannam@198: cannam@198: FixedTempoEstimator::FeatureSet cannam@198: FixedTempoEstimator::calculateFeatures() cannam@198: { cannam@198: FeatureSet fs; cannam@198: Feature feature; cannam@198: feature.hasTimestamp = true; cannam@198: feature.hasDuration = false; cannam@198: feature.label = ""; cannam@198: feature.values.clear(); cannam@198: feature.values.push_back(0.f); cannam@198: cannam@198: char buffer[20]; cannam@198: cannam@198: if (m_n < m_dfsize / 4) return fs; // not enough data (perhaps we should return the duration of the input as the "estimated" beat length?) cannam@198: cannam@198: std::cerr << "FixedTempoEstimator::calculateTempo: m_n = " << m_n << std::endl; cannam@198: cannam@198: int n = m_n; cannam@198: float *f = m_df; cannam@198: cannam@198: for (int i = 0; i < n; ++i) { cannam@198: feature.timestamp = RealTime::frame2RealTime(i * m_stepSize, cannam@198: m_inputSampleRate); cannam@198: std::cerr << "step = " << m_stepSize << ", timestamp = " << feature.timestamp << std::endl; cannam@198: feature.values[0] = f[i]; cannam@198: feature.label = ""; cannam@198: fs[1].push_back(feature); cannam@198: } cannam@198: cannam@198: float *r = new float[n/2]; cannam@198: for (int i = 0; i < n/2; ++i) r[i] = 0.f; cannam@198: cannam@198: int minlag = 10; cannam@198: cannam@198: for (int i = 0; i < n/2; ++i) { cannam@198: for (int j = i; j < n-1; ++j) { cannam@198: r[i] += f[j] * f[j - i]; cannam@198: } cannam@198: r[i] /= n - i - 1; cannam@198: } cannam@198: cannam@198: for (int i = 0; i < n/2; ++i) { cannam@198: feature.timestamp = RealTime::frame2RealTime(i * m_stepSize, cannam@198: m_inputSampleRate); cannam@198: feature.values[0] = r[i]; cannam@198: sprintf(buffer, "%f bpm", lag2tempo(i)); cannam@198: feature.label = buffer; cannam@198: fs[2].push_back(feature); cannam@198: } cannam@198: cannam@198: float max = 0.f; cannam@198: int maxindex = 0; cannam@198: cannam@198: std::cerr << "n/2 = " << n/2 << std::endl; cannam@198: cannam@198: for (int i = minlag; i < n/2; ++i) { cannam@198: cannam@198: if (i == minlag || r[i] > max) { cannam@198: max = r[i]; cannam@198: maxindex = i; cannam@198: } cannam@198: cannam@198: if (i == 0 || i == n/2-1) continue; cannam@198: cannam@198: if (r[i] > r[i-1] && r[i] > r[i+1]) { cannam@198: std::cerr << "peak at " << i << " (value=" << r[i] << ", tempo would be " << lag2tempo(i) << ")" << std::endl; cannam@198: } cannam@198: } cannam@198: cannam@198: std::cerr << "overall max at " << maxindex << " (value=" << max << ")" << std::endl; cannam@198: cannam@198: float tempo = lag2tempo(maxindex); cannam@198: cannam@198: std::cerr << "provisional tempo = " << tempo << std::endl; cannam@198: cannam@198: float t0 = 60.f; cannam@198: float t1 = 180.f; cannam@198: cannam@198: int p0 = ((60.f / t1) * m_inputSampleRate) / m_stepSize; cannam@198: int p1 = ((60.f / t0) * m_inputSampleRate) / m_stepSize; cannam@198: cannam@198: std::cerr << "p0 = " << p0 << ", p1 = " << p1 << std::endl; cannam@198: cannam@198: int pc = p1 - p0 + 1; cannam@198: std::cerr << "pc = " << pc << std::endl; cannam@198: // float *filtered = new float[pc]; cannam@198: // for (int i = 0; i < pc; ++i) filtered[i] = 0.f; cannam@198: cannam@198: int maxpi = 0; cannam@198: float maxp = 0.f; cannam@198: cannam@198: for (int i = p0; i <= p1; ++i) { cannam@198: cannam@198: // int fi = i - p0; cannam@198: cannam@198: float filtered = 0.f; cannam@198: cannam@198: for (int j = 1; j <= (n/2)/p1; ++j) { cannam@198: std::cerr << "j = " << j << ", i = " << i << std::endl; cannam@198: filtered += r[i * j]; cannam@198: } cannam@198: cannam@198: if (i == p0 || filtered > maxp) { cannam@198: maxp = filtered; cannam@198: maxpi = i; cannam@198: } cannam@198: cannam@198: feature.timestamp = RealTime::frame2RealTime(i * m_stepSize, cannam@198: m_inputSampleRate); cannam@198: feature.values[0] = filtered; cannam@198: sprintf(buffer, "%f bpm", lag2tempo(i)); cannam@198: feature.label = buffer; cannam@198: fs[3].push_back(feature); cannam@198: } cannam@198: cannam@198: std::cerr << "maxpi = " << maxpi << " for tempo " << lag2tempo(maxpi) << " (value = " << maxp << ")" << std::endl; cannam@198: cannam@198: tempo = lag2tempo(maxpi); cannam@198: cannam@198: delete[] r; cannam@198: cannam@198: feature.hasTimestamp = true; cannam@198: feature.timestamp = m_start; cannam@198: cannam@198: feature.hasDuration = true; cannam@198: feature.duration = m_lasttime - m_start; cannam@198: cannam@198: feature.values[0] = tempo; cannam@198: cannam@198: fs[0].push_back(feature); cannam@198: cannam@198: return fs; cannam@198: }