Chris@3: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@3: /* Chris@31: This file is Copyright (c) 2012 Chris Cannam Chris@31: Chris@3: Permission is hereby granted, free of charge, to any person Chris@3: obtaining a copy of this software and associated documentation Chris@3: files (the "Software"), to deal in the Software without Chris@3: restriction, including without limitation the rights to use, copy, Chris@3: modify, merge, publish, distribute, sublicense, and/or sell copies Chris@3: of the Software, and to permit persons to whom the Software is Chris@3: furnished to do so, subject to the following conditions: Chris@3: Chris@3: The above copyright notice and this permission notice shall be Chris@3: included in all copies or substantial portions of the Software. Chris@3: Chris@3: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, Chris@3: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF Chris@3: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND Chris@3: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR Chris@3: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF Chris@3: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION Chris@3: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chris@3: */ Chris@3: Chris@31: #include "CepstralPitchTracker.h" Chris@51: #include "Cepstrum.h" Chris@47: #include "MeanFilter.h" Chris@50: #include "PeakInterpolator.h" Chris@56: #include "AgentFeeder.h" Chris@3: Chris@26: #include "vamp-sdk/FFT.h" Chris@26: Chris@3: #include Chris@3: #include Chris@3: Chris@3: #include Chris@3: #include Chris@3: #include Chris@3: Chris@3: using std::string; Chris@7: using std::vector; Chris@16: using Vamp::RealTime; Chris@7: Chris@16: Chris@31: CepstralPitchTracker::CepstralPitchTracker(float inputSampleRate) : Chris@3: Plugin(inputSampleRate), Chris@3: m_channels(0), Chris@3: m_stepSize(256), Chris@3: m_blockSize(1024), Chris@3: m_fmin(50), Chris@25: m_fmax(900), Chris@18: m_vflen(1), Chris@3: m_binFrom(0), Chris@3: m_binTo(0), Chris@56: m_bins(0), Chris@57: m_nAccepted(0), Chris@56: m_feeder(0) Chris@3: { Chris@3: } Chris@3: Chris@31: CepstralPitchTracker::~CepstralPitchTracker() Chris@3: { Chris@56: delete m_feeder; Chris@3: } Chris@3: Chris@3: string Chris@31: CepstralPitchTracker::getIdentifier() const Chris@3: { Chris@39: return "cepstral-pitchtracker"; Chris@3: } Chris@3: Chris@3: string Chris@31: CepstralPitchTracker::getName() const Chris@3: { Chris@39: return "Cepstral Pitch Tracker"; Chris@3: } Chris@3: Chris@3: string Chris@31: CepstralPitchTracker::getDescription() const Chris@3: { Chris@3: return "Estimate f0 of monophonic material using a cepstrum method."; Chris@3: } Chris@3: Chris@3: string Chris@31: CepstralPitchTracker::getMaker() const Chris@3: { Chris@3: return "Chris Cannam"; Chris@3: } Chris@3: Chris@3: int Chris@31: CepstralPitchTracker::getPluginVersion() const Chris@3: { Chris@3: // Increment this each time you release a version that behaves Chris@3: // differently from the previous one Chris@3: return 1; Chris@3: } Chris@3: Chris@3: string Chris@31: CepstralPitchTracker::getCopyright() const Chris@3: { Chris@3: return "Freely redistributable (BSD license)"; Chris@3: } Chris@3: Chris@31: CepstralPitchTracker::InputDomain Chris@31: CepstralPitchTracker::getInputDomain() const Chris@3: { Chris@3: return FrequencyDomain; Chris@3: } Chris@3: Chris@3: size_t Chris@31: CepstralPitchTracker::getPreferredBlockSize() const Chris@3: { Chris@3: return 1024; Chris@3: } Chris@3: Chris@3: size_t Chris@31: CepstralPitchTracker::getPreferredStepSize() const Chris@3: { Chris@3: return 256; Chris@3: } Chris@3: Chris@3: size_t Chris@31: CepstralPitchTracker::getMinChannelCount() const Chris@3: { Chris@3: return 1; Chris@3: } Chris@3: Chris@3: size_t Chris@31: CepstralPitchTracker::getMaxChannelCount() const Chris@3: { Chris@3: return 1; Chris@3: } Chris@3: Chris@31: CepstralPitchTracker::ParameterList Chris@31: CepstralPitchTracker::getParameterDescriptors() const Chris@3: { Chris@3: ParameterList list; Chris@3: return list; Chris@3: } Chris@3: Chris@3: float Chris@31: CepstralPitchTracker::getParameter(string identifier) const Chris@3: { Chris@3: return 0.f; Chris@3: } Chris@3: Chris@3: void Chris@31: CepstralPitchTracker::setParameter(string identifier, float value) Chris@3: { Chris@3: } Chris@3: Chris@31: CepstralPitchTracker::ProgramList Chris@31: CepstralPitchTracker::getPrograms() const Chris@3: { Chris@3: ProgramList list; Chris@3: return list; Chris@3: } Chris@3: Chris@3: string Chris@31: CepstralPitchTracker::getCurrentProgram() const Chris@3: { Chris@3: return ""; // no programs Chris@3: } Chris@3: Chris@3: void Chris@31: CepstralPitchTracker::selectProgram(string name) Chris@3: { Chris@3: } Chris@3: Chris@31: CepstralPitchTracker::OutputList Chris@31: CepstralPitchTracker::getOutputDescriptors() const Chris@3: { Chris@3: OutputList outputs; Chris@3: Chris@3: OutputDescriptor d; Chris@3: Chris@3: d.identifier = "f0"; Chris@3: d.name = "Estimated f0"; Chris@3: d.description = "Estimated fundamental frequency"; Chris@3: d.unit = "Hz"; Chris@3: d.hasFixedBinCount = true; Chris@3: d.binCount = 1; Chris@3: d.hasKnownExtents = true; Chris@3: d.minValue = m_fmin; Chris@3: d.maxValue = m_fmax; Chris@3: d.isQuantized = false; Chris@3: d.sampleType = OutputDescriptor::FixedSampleRate; Chris@3: d.sampleRate = (m_inputSampleRate / m_stepSize); Chris@3: d.hasDuration = false; Chris@3: outputs.push_back(d); Chris@3: Chris@16: d.identifier = "notes"; Chris@16: d.name = "Notes"; Chris@16: d.description = "Derived fixed-pitch note frequencies"; Chris@16: d.unit = "Hz"; Chris@16: d.hasFixedBinCount = true; Chris@16: d.binCount = 1; Chris@16: d.hasKnownExtents = true; Chris@16: d.minValue = m_fmin; Chris@16: d.maxValue = m_fmax; Chris@16: d.isQuantized = false; Chris@16: d.sampleType = OutputDescriptor::FixedSampleRate; Chris@16: d.sampleRate = (m_inputSampleRate / m_stepSize); Chris@16: d.hasDuration = true; Chris@16: outputs.push_back(d); Chris@16: Chris@3: return outputs; Chris@3: } Chris@3: Chris@3: bool Chris@31: CepstralPitchTracker::initialise(size_t channels, size_t stepSize, size_t blockSize) Chris@3: { Chris@3: if (channels < getMinChannelCount() || Chris@3: channels > getMaxChannelCount()) return false; Chris@3: Chris@31: // std::cerr << "CepstralPitchTracker::initialise: channels = " << channels Chris@3: // << ", stepSize = " << stepSize << ", blockSize = " << blockSize Chris@3: // << std::endl; Chris@3: Chris@3: m_channels = channels; Chris@3: m_stepSize = stepSize; Chris@3: m_blockSize = blockSize; Chris@3: Chris@3: m_binFrom = int(m_inputSampleRate / m_fmax); Chris@3: m_binTo = int(m_inputSampleRate / m_fmin); Chris@3: Chris@3: if (m_binTo >= (int)m_blockSize / 2) { Chris@3: m_binTo = m_blockSize / 2 - 1; Chris@3: } Chris@56: if (m_binFrom >= m_binTo) { Chris@56: // shouldn't happen except for degenerate samplerate / blocksize combos Chris@56: m_binFrom = m_binTo - 1; Chris@56: } Chris@3: Chris@3: m_bins = (m_binTo - m_binFrom) + 1; Chris@3: Chris@3: reset(); Chris@3: Chris@3: return true; Chris@3: } Chris@3: Chris@3: void Chris@31: CepstralPitchTracker::reset() Chris@3: { Chris@56: delete m_feeder; Chris@56: m_feeder = new AgentFeeder(); Chris@57: m_nAccepted = 0; Chris@3: } Chris@3: Chris@3: void Chris@35: CepstralPitchTracker::addFeaturesFrom(NoteHypothesis h, FeatureSet &fs) Chris@30: { Chris@35: NoteHypothesis::Estimates es = h.getAcceptedEstimates(); Chris@30: Chris@35: for (int i = 0; i < (int)es.size(); ++i) { Chris@30: Feature f; Chris@30: f.hasTimestamp = true; Chris@30: f.timestamp = es[i].time; Chris@30: f.values.push_back(es[i].freq); Chris@30: fs[0].push_back(f); Chris@30: } Chris@30: Chris@30: Feature nf; Chris@30: nf.hasTimestamp = true; Chris@30: nf.hasDuration = true; Chris@35: NoteHypothesis::Note n = h.getAveragedNote(); Chris@30: nf.timestamp = n.time; Chris@30: nf.duration = n.duration; Chris@30: nf.values.push_back(n.freq); Chris@30: fs[1].push_back(nf); Chris@30: } Chris@30: Chris@57: void Chris@57: CepstralPitchTracker::addNewFeatures(FeatureSet &fs) Chris@57: { Chris@57: int n = m_feeder->getAcceptedHypotheses().size(); Chris@57: if (n == m_nAccepted) return; Chris@57: Chris@57: AgentFeeder::Hypotheses accepted = m_feeder->getAcceptedHypotheses(); Chris@57: Chris@57: for (int i = m_nAccepted; i < n; ++i) { Chris@57: addFeaturesFrom(accepted[i], fs); Chris@57: } Chris@57: Chris@57: m_nAccepted = n; Chris@57: } Chris@57: Chris@31: CepstralPitchTracker::FeatureSet Chris@31: CepstralPitchTracker::process(const float *const *inputBuffers, RealTime timestamp) Chris@3: { Chris@51: double *rawcep = new double[m_blockSize]; Chris@51: double magmean = Cepstrum(m_blockSize).process(inputBuffers[0], rawcep); Chris@3: Chris@3: int n = m_bins; Chris@3: double *data = new double[n]; Chris@51: MeanFilter(m_vflen).filterSubsequence Chris@51: (rawcep, data, m_blockSize, n, m_binFrom); Chris@51: Chris@3: delete[] rawcep; Chris@3: Chris@3: double maxval = 0.0; Chris@6: int maxbin = -1; Chris@3: Chris@3: for (int i = 0; i < n; ++i) { Chris@3: if (data[i] > maxval) { Chris@3: maxval = data[i]; Chris@3: maxbin = i; Chris@3: } Chris@3: } Chris@3: Chris@15: if (maxbin < 0) { Chris@15: delete[] data; Chris@57: return FeatureSet(); Chris@15: } Chris@15: Chris@15: double nextPeakVal = 0.0; Chris@15: for (int i = 1; i+1 < n; ++i) { Chris@15: if (data[i] > data[i-1] && Chris@15: data[i] > data[i+1] && Chris@15: i != maxbin && Chris@15: data[i] > nextPeakVal) { Chris@15: nextPeakVal = data[i]; Chris@15: } Chris@15: } Chris@8: Chris@50: PeakInterpolator pi; Chris@50: double cimax = pi.findPeakLocation(data, m_bins, maxbin); Chris@18: double peakfreq = m_inputSampleRate / (cimax + m_binFrom); Chris@15: Chris@15: double confidence = 0.0; Chris@51: double threshold = 0.1; // for magmean Chris@51: Chris@15: if (nextPeakVal != 0.0) { Chris@27: confidence = (maxval - nextPeakVal) * 10.0; Chris@25: if (magmean < threshold) confidence = 0.0; Chris@15: } Chris@15: Chris@57: delete[] data; Chris@57: Chris@35: NoteHypothesis::Estimate e; Chris@8: e.freq = peakfreq; Chris@8: e.time = timestamp; Chris@15: e.confidence = confidence; Chris@8: Chris@56: m_feeder->feed(e); Chris@14: Chris@57: FeatureSet fs; Chris@57: addNewFeatures(fs); Chris@3: return fs; Chris@3: } Chris@3: Chris@31: CepstralPitchTracker::FeatureSet Chris@31: CepstralPitchTracker::getRemainingFeatures() Chris@3: { Chris@56: m_feeder->finish(); Chris@56: Chris@3: FeatureSet fs; Chris@57: addNewFeatures(fs); Chris@3: return fs; Chris@3: }