Chris@31: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@31: Chris@31: /* Chris@31: Silvet Chris@31: Chris@31: A Vamp plugin for note transcription. Chris@31: Centre for Digital Music, Queen Mary University of London. Chris@31: Chris@31: This program is free software; you can redistribute it and/or Chris@31: modify it under the terms of the GNU General Public License as Chris@31: published by the Free Software Foundation; either version 2 of the Chris@31: License, or (at your option) any later version. See the file Chris@31: COPYING included with this distribution for more information. Chris@31: */ Chris@31: Chris@31: #include "Silvet.h" Chris@31: Chris@31: #include "data/include/templates.h" Chris@31: Chris@32: #include "maths/MedianFilter.h" Chris@31: #include "dsp/rateconversion/Resampler.h" Chris@31: Chris@32: #include "constant-q-cpp/cpp-qm-dsp/CQInterpolated.h" Chris@31: Chris@31: #include Chris@31: Chris@32: #include Chris@32: Chris@31: using std::vector; Chris@31: using std::cerr; Chris@31: using std::endl; Chris@31: Chris@31: static int processingSampleRate = 44100; Chris@31: static int processingBPO = 60; Chris@32: static int processingHeight = 545; Chris@31: Chris@31: Silvet::Silvet(float inputSampleRate) : Chris@31: Plugin(inputSampleRate), Chris@31: m_resampler(0), Chris@31: m_cq(0) Chris@31: { Chris@31: } Chris@31: Chris@31: Silvet::~Silvet() Chris@31: { Chris@31: delete m_resampler; Chris@31: delete m_cq; Chris@32: for (int i = 0; i < (int)m_filterA.size(); ++i) { Chris@32: delete m_filterA[i]; Chris@32: delete m_filterB[i]; Chris@32: } Chris@31: } Chris@31: Chris@31: string Chris@31: Silvet::getIdentifier() const Chris@31: { Chris@31: return "silvet"; Chris@31: } Chris@31: Chris@31: string Chris@31: Silvet::getName() const Chris@31: { Chris@31: return "Silvet Note Transcription"; Chris@31: } Chris@31: Chris@31: string Chris@31: Silvet::getDescription() const Chris@31: { Chris@31: // Return something helpful here! Chris@31: return ""; Chris@31: } Chris@31: Chris@31: string Chris@31: Silvet::getMaker() const Chris@31: { Chris@31: // Your name here Chris@31: return ""; Chris@31: } Chris@31: Chris@31: int Chris@31: Silvet::getPluginVersion() const Chris@31: { Chris@31: return 1; Chris@31: } Chris@31: Chris@31: string Chris@31: Silvet::getCopyright() const Chris@31: { Chris@31: // This function is not ideally named. It does not necessarily Chris@31: // need to say who made the plugin -- getMaker does that -- but it Chris@31: // should indicate the terms under which it is distributed. For Chris@31: // example, "Copyright (year). All Rights Reserved", or "GPL" Chris@31: return ""; Chris@31: } Chris@31: Chris@31: Silvet::InputDomain Chris@31: Silvet::getInputDomain() const Chris@31: { Chris@31: return TimeDomain; Chris@31: } Chris@31: Chris@31: size_t Chris@31: Silvet::getPreferredBlockSize() const Chris@31: { Chris@31: return 0; Chris@31: } Chris@31: Chris@31: size_t Chris@31: Silvet::getPreferredStepSize() const Chris@31: { Chris@31: return 0; Chris@31: } Chris@31: Chris@31: size_t Chris@31: Silvet::getMinChannelCount() const Chris@31: { Chris@31: return 1; Chris@31: } Chris@31: Chris@31: size_t Chris@31: Silvet::getMaxChannelCount() const Chris@31: { Chris@31: return 1; Chris@31: } Chris@31: Chris@31: Silvet::ParameterList Chris@31: Silvet::getParameterDescriptors() const Chris@31: { Chris@31: ParameterList list; Chris@31: return list; Chris@31: } Chris@31: Chris@31: float Chris@31: Silvet::getParameter(string identifier) const Chris@31: { Chris@31: return 0; Chris@31: } Chris@31: Chris@31: void Chris@31: Silvet::setParameter(string identifier, float value) Chris@31: { Chris@31: } Chris@31: Chris@31: Silvet::ProgramList Chris@31: Silvet::getPrograms() const Chris@31: { Chris@31: ProgramList list; Chris@31: return list; Chris@31: } Chris@31: Chris@31: string Chris@31: Silvet::getCurrentProgram() const Chris@31: { Chris@31: return ""; Chris@31: } Chris@31: Chris@31: void Chris@31: Silvet::selectProgram(string name) Chris@31: { Chris@31: } Chris@31: Chris@31: Silvet::OutputList Chris@31: Silvet::getOutputDescriptors() const Chris@31: { Chris@31: OutputList list; Chris@31: Chris@31: OutputDescriptor d; Chris@31: d.identifier = "transcription"; Chris@31: d.name = "Transcription"; Chris@31: d.description = ""; //!!! Chris@31: d.unit = "Hz"; Chris@31: d.hasFixedBinCount = true; Chris@31: d.binCount = 2; Chris@31: d.binNames.push_back("Frequency"); Chris@31: d.binNames.push_back("Velocity"); Chris@31: d.hasKnownExtents = false; Chris@31: d.isQuantized = false; Chris@31: d.sampleType = OutputDescriptor::VariableSampleRate; Chris@32: d.sampleRate = m_inputSampleRate / (m_cq ? m_cq->getColumnHop() : 256); Chris@31: d.hasDuration = true; Chris@32: m_notesOutputNo = list.size(); Chris@32: list.push_back(d); Chris@32: Chris@32: d.identifier = "inputgrid"; Chris@32: d.name = "Filtered time-frequency grid"; Chris@32: d.description = "The pre-processed constant-Q time-frequency distribution used as input to the PLCA step"; Chris@32: d.unit = ""; Chris@32: d.hasFixedBinCount = true; Chris@32: d.binCount = processingHeight; Chris@32: d.binNames.clear(); Chris@32: if (m_cq) { Chris@32: char name[20]; Chris@32: for (int i = 0; i < processingHeight; ++i) { Chris@32: float freq = m_cq->getBinFrequency(i + 55); Chris@32: sprintf(name, "%.1f Hz", freq); Chris@32: d.binNames.push_back(name); Chris@32: } Chris@32: } Chris@32: d.hasKnownExtents = false; Chris@32: d.isQuantized = false; Chris@32: d.sampleType = OutputDescriptor::FixedSampleRate; Chris@32: d.sampleRate = 25; Chris@32: d.hasDuration = false; Chris@32: m_cqOutputNo = list.size(); Chris@31: list.push_back(d); Chris@31: Chris@31: return list; Chris@31: } Chris@31: Chris@31: bool Chris@31: Silvet::initialise(size_t channels, size_t stepSize, size_t blockSize) Chris@31: { Chris@31: if (channels < getMinChannelCount() || Chris@31: channels > getMaxChannelCount()) return false; Chris@31: Chris@31: if (stepSize != blockSize) { Chris@31: cerr << "Silvet::initialise: Step size must be the same as block size (" Chris@31: << stepSize << " != " << blockSize << ")" << endl; Chris@31: return false; Chris@31: } Chris@31: Chris@31: m_blockSize = blockSize; Chris@31: Chris@31: reset(); Chris@31: Chris@31: return true; Chris@31: } Chris@31: Chris@31: void Chris@31: Silvet::reset() Chris@31: { Chris@31: delete m_resampler; Chris@31: delete m_cq; Chris@31: Chris@31: if (m_inputSampleRate != processingSampleRate) { Chris@31: m_resampler = new Resampler(m_inputSampleRate, processingSampleRate); Chris@31: } else { Chris@31: m_resampler = 0; Chris@31: } Chris@31: Chris@32: m_cq = new CQInterpolated Chris@32: (processingSampleRate, 27.5, processingSampleRate / 3, processingBPO, Chris@32: CQInterpolated::Linear); Chris@31: Chris@32: for (int i = 0; i < (int)m_filterA.size(); ++i) { Chris@32: delete m_filterA[i]; Chris@32: delete m_filterB[i]; Chris@32: } Chris@32: m_filterA.clear(); Chris@32: m_filterB.clear(); Chris@32: for (int i = 0; i < processingHeight; ++i) { Chris@32: m_filterA.push_back(new MedianFilter(40)); Chris@32: m_filterB.push_back(new MedianFilter(40)); Chris@32: } Chris@32: m_columnCount = 0; Chris@32: m_reducedColumnCount = 0; Chris@31: } Chris@31: Chris@31: Silvet::FeatureSet Chris@31: Silvet::process(const float *const *inputBuffers, Vamp::RealTime timestamp) Chris@31: { Chris@31: vector data; Chris@31: for (int i = 0; i < m_blockSize; ++i) data.push_back(inputBuffers[0][i]); Chris@31: Chris@31: if (m_resampler) { Chris@31: data = m_resampler->process(data.data(), data.size()); Chris@31: } Chris@31: Chris@32: Grid cqout = m_cq->process(data); Chris@32: Grid filtered = preProcess(cqout); Chris@31: Chris@32: FeatureSet fs; Chris@32: Chris@32: for (int i = 0; i < (int)filtered.size(); ++i) { Chris@32: Feature f; Chris@32: for (int j = 0; j < processingHeight; ++j) { Chris@32: f.values.push_back(float(filtered[i][j])); Chris@32: } Chris@32: fs[m_cqOutputNo].push_back(f); Chris@32: } Chris@32: Chris@32: return fs; Chris@31: } Chris@31: Chris@31: Silvet::FeatureSet Chris@31: Silvet::getRemainingFeatures() Chris@31: { Chris@31: Chris@31: return FeatureSet(); Chris@31: } Chris@31: Chris@32: Silvet::Grid Chris@32: Silvet::preProcess(const Grid &in) Chris@32: { Chris@32: int width = in.size(); Chris@32: Chris@32: // reduce to 100 columns per second, or one column every 441 samples Chris@32: Chris@32: int spacing = processingSampleRate / 100; Chris@32: Chris@32: Grid out; Chris@32: Chris@32: for (int i = 0; i < width; ++i) { Chris@32: Chris@32: int prevSampleNo = (m_columnCount - 1) * m_cq->getColumnHop(); Chris@32: int sampleNo = m_columnCount * m_cq->getColumnHop(); Chris@32: Chris@32: bool select = (sampleNo / spacing != prevSampleNo / spacing); Chris@32: Chris@32: if (select) { Chris@32: vector inCol = in[i]; Chris@32: vector outCol(processingHeight); Chris@32: Chris@32: // we reverse the column as we go (the CQ output is Chris@32: // "upside-down", with high frequencies at the start of Chris@32: // each column, and we want it the other way around) and Chris@32: // then ignore the first 55 (lowest-frequency) bins, Chris@32: // giving us 545 bins instead of 600 Chris@32: Chris@32: for (int j = 0; j < processingHeight; ++j) { Chris@32: Chris@32: int ix = inCol.size() - j - 55; Chris@32: Chris@32: double val = inCol[ix]; Chris@32: m_filterA[j]->push(val); Chris@32: Chris@32: double a = m_filterA[j]->get(); Chris@32: m_filterB[j]->push(std::min(a, val)); Chris@32: Chris@32: double filtered = m_filterB[j]->get(); Chris@32: outCol[j] = filtered; Chris@32: } Chris@32: Chris@32: // then we only use every fourth filtered column, for 25 Chris@32: // columns per second in the eventual grid Chris@32: Chris@32: if (m_reducedColumnCount % 4 == 0) { Chris@32: out.push_back(outCol); Chris@32: } Chris@32: Chris@32: ++m_reducedColumnCount; Chris@32: } Chris@32: Chris@32: ++m_columnCount; Chris@32: } Chris@32: Chris@32: return out; Chris@32: } Chris@32: