Mercurial > hg > vamp-test-plugin
view VampTestPlugin.cpp @ 35:b35d5b2abf84
Bump version
author | Chris Cannam |
---|---|
date | Thu, 15 Sep 2016 10:28:14 +0100 |
parents | 60d48ba79ca1 |
children | bd22e6f4809d |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Vamp Test Plugin Copyright (c) 2013-2016 Queen Mary, University of London Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the names of the Centre for Digital Music and Queen Mary, University of London shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. */ #include "VampTestPlugin.h" #include <vamp-sdk/FFT.h> #include <iostream> #include <sstream> #include <cmath> using namespace std; using Vamp::RealTime; VampTestPlugin::VampTestPlugin(float inputSampleRate, bool freq) : Plugin(inputSampleRate), m_frequencyDomain(freq), m_produceOutput(true), m_n(0), m_channels(1), m_stepSize(0), m_blockSize(0) { for (int i = 0; i < 10; ++i) { m_instants.push_back(RealTime::fromSeconds(1.5 * i)); } } VampTestPlugin::~VampTestPlugin() { } string VampTestPlugin::getIdentifier() const { if (m_frequencyDomain) { return "vamp-test-plugin-freq"; } else { return "vamp-test-plugin"; } } string VampTestPlugin::getName() const { if (m_frequencyDomain) { return "Vamp Test Plugin (Frequency-Domain Input)"; } else { return "Vamp Test Plugin"; } } string VampTestPlugin::getDescription() const { return "Test plugin for hosts handling various output types"; } string VampTestPlugin::getMaker() const { return "Chris Cannam"; } int VampTestPlugin::getPluginVersion() const { return 4; } string VampTestPlugin::getCopyright() const { return "BSD"; } VampTestPlugin::InputDomain VampTestPlugin::getInputDomain() const { return m_frequencyDomain ? FrequencyDomain : TimeDomain; } size_t VampTestPlugin::getPreferredBlockSize() const { return 0; } size_t VampTestPlugin::getPreferredStepSize() const { return 0; } size_t VampTestPlugin::getMinChannelCount() const { return 1; } size_t VampTestPlugin::getMaxChannelCount() const { return 10; } VampTestPlugin::ParameterList VampTestPlugin::getParameterDescriptors() const { ParameterList list; // Provide one parameter, and make it so that we can easily tell // whether it has been changed ParameterDescriptor d; d.identifier = "produce_output"; d.name = "Produce some output"; d.description = "Whether to produce any output. If this parameter is switched off, the plugin will produce no output. This is intended for basic testing of whether a host's parameter setting logic is functioning."; d.unit = ""; d.minValue = 0; d.maxValue = 1; d.defaultValue = 1; d.isQuantized = true; d.quantizeStep = 1; list.push_back(d); return list; } float VampTestPlugin::getParameter(string identifier) const { if (identifier == "produce_output") { return m_produceOutput ? 1.f : 0.f; } return 0; } void VampTestPlugin::setParameter(string identifier, float value) { if (identifier == "produce_output") { m_produceOutput = (value > 0.5); } } VampTestPlugin::ProgramList VampTestPlugin::getPrograms() const { ProgramList list; return list; } string VampTestPlugin::getCurrentProgram() const { return ""; // no programs } void VampTestPlugin::selectProgram(string) { } VampTestPlugin::OutputList VampTestPlugin::getOutputDescriptors() const { OutputList list; int n = 0; OutputDescriptor d; d.identifier = "instants"; d.name = "Instants"; d.description = "Single time points without values"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 0; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::VariableSampleRate; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "curve-oss"; d.name = "Curve: OneSamplePerStep"; d.description = "A time series with one value per process block"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::OneSamplePerStep; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "curve-fsr"; d.name = "Curve: FixedSampleRate"; d.description = "A time series with equally-spaced values (independent of process step size)"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::FixedSampleRate; d.sampleRate = 2.5; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "curve-fsr-timed"; d.name = "Curve: FixedSampleRate/Timed"; d.description = "A time series with a fixed sample rate (independent of process step size) but with timestamps on features"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::FixedSampleRate; d.sampleRate = 2.5; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "curve-fsr-mixed"; d.name = "Curve: FixedSampleRate/Mixed"; d.description = "A time series with a fixed sample rate (independent of process step size) and with timestamps on some features"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::FixedSampleRate; d.sampleRate = 2.5; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "curve-vsr"; d.name = "Curve: VariableSampleRate"; d.description = "A variably-spaced series of values"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::VariableSampleRate; d.sampleRate = 0; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "grid-oss"; d.name = "Grid: OneSamplePerStep"; d.description = "A fixed-height grid of values with one column per process block"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 10; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::OneSamplePerStep; d.sampleRate = 0; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "grid-fsr"; d.name = "Grid: FixedSampleRate"; d.description = "A fixed-height grid of values with equally-spaced columns (independent of process step size)"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 10; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::FixedSampleRate; d.sampleRate = 2.5; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "notes-regions"; d.name = "Notes or Regions"; d.description = "Variably-spaced features with one value and duration"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::VariableSampleRate; d.sampleRate = 0; d.hasDuration = true; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "input-summary"; d.name = "Data derived from inputs"; d.description = "One-sample-per-step features with n values, where n is the number of input channels. Each feature contains, for each input channel, the first sample value on that channel plus the total number of non-zero samples on that channel. (\"Non-zero\" is determined by comparison against a magnitude threshold which is actually 1e-6 rather than exactly zero.)"; d.unit = ""; d.hasFixedBinCount = true; d.binCount = m_channels; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::OneSamplePerStep; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); d.identifier = "input-timestamp"; d.name = "Input timestamp"; d.description = "One-sample-per-step features with one value, containing the time in sample frames converted from the timestamp of the corresponding process input block."; d.unit = "samples"; d.hasFixedBinCount = true; d.binCount = 1; d.hasKnownExtents = false; d.isQuantized = false; d.sampleType = OutputDescriptor::OneSamplePerStep; d.hasDuration = false; m_outputNumbers[d.identifier] = n++; list.push_back(d); return list; } bool VampTestPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) { if (channels < getMinChannelCount() || channels > getMaxChannelCount()) return false; m_channels = channels; m_stepSize = stepSize; m_blockSize = blockSize; return true; } void VampTestPlugin::reset() { m_n = 0; } static Vamp::Plugin::Feature instant(RealTime r, int i, int n) { stringstream s; Vamp::Plugin::Feature f; f.hasTimestamp = true; f.timestamp = r; f.hasDuration = false; s << i+1 << " of " << n << " at " << r.toText(); f.label = s.str(); return f; } static Vamp::Plugin::Feature untimedCurveValue(RealTime r, int i, int n) { stringstream s; Vamp::Plugin::Feature f; f.hasTimestamp = false; f.hasDuration = false; float v = float(i) / float(n); f.values.push_back(v); s << i+1 << " of " << n << ": " << v << " at " << r.toText(); f.label = s.str(); return f; } static Vamp::Plugin::Feature timedCurveValue(RealTime r, int i, int n) { stringstream s; Vamp::Plugin::Feature f; f.hasTimestamp = true; f.timestamp = r; f.hasDuration = false; float v = float(i) / float(n); f.values.push_back(v); s << i+1 << " of " << n << ": " << v << " at " << r.toText(); f.label = s.str(); return f; } static Vamp::Plugin::Feature snappedCurveValue(RealTime r, RealTime sn, int i, int n) { stringstream s; Vamp::Plugin::Feature f; f.hasTimestamp = true; f.timestamp = r; f.hasDuration = false; float v = float(i) / float(n); f.values.push_back(v); s << i+1 << " of " << n << ": " << v << " at " << r.toText() << " snap to " << sn.toText(); f.label = s.str(); return f; } static Vamp::Plugin::Feature gridColumn(RealTime r, int i, int n) { stringstream s; Vamp::Plugin::Feature f; f.hasTimestamp = false; f.hasDuration = false; for (int j = 0; j < 10; ++j) { float v = float(j + i + 2) / float(n + 10); f.values.push_back(v); } s << i+1 << " of " << n << " at " << r.toText(); f.label = s.str(); return f; } static Vamp::Plugin::Feature noteOrRegion(RealTime r, RealTime d, int i, int n) { stringstream s; Vamp::Plugin::Feature f; f.hasTimestamp = true; f.timestamp = r; f.hasDuration = true; f.duration = d; float v = float(i) / float(n); f.values.push_back(v); s << i+1 << " of " << n << ": " << v << " at " << r.toText() << " dur. " << d.toText(); f.label = s.str(); return f; } static float snap(float x, float r) { int n = int(x / r + 0.5); return n * r; } Vamp::Plugin::FeatureSet VampTestPlugin::featuresFrom(RealTime timestamp, bool final) { FeatureSet fs; RealTime endTime = timestamp + RealTime::frame2RealTime (m_stepSize, m_inputSampleRate); for (int i = 0; i < (int)m_instants.size(); ++i) { if (m_instants[i] >= timestamp && (final || m_instants[i] < endTime)) { fs[m_outputNumbers["instants"]] .push_back(instant(m_instants[i], i, m_instants.size())); } RealTime variCurveTime = m_instants[i] / 2; if (variCurveTime >= timestamp && (final || variCurveTime < endTime)) { fs[m_outputNumbers["curve-vsr"]] .push_back(timedCurveValue(variCurveTime, i, m_instants.size())); } RealTime noteTime = (m_instants[i] + m_instants[i]) / 3; RealTime noteDuration = RealTime::fromSeconds((i % 2 == 0) ? 1.75 : 0.5); if (noteTime >= timestamp && (final || noteTime < endTime)) { fs[m_outputNumbers["notes-regions"]] .push_back(noteOrRegion(noteTime, noteDuration, i, m_instants.size())); } } if (!final) { if (m_n < 20) { fs[m_outputNumbers["curve-oss"]] .push_back(untimedCurveValue(timestamp, m_n, 20)); } if (m_n < 5) { fs[m_outputNumbers["curve-fsr"]] .push_back(untimedCurveValue(RealTime::fromSeconds(m_n / 2.5), m_n, 10)); float s = (m_n / 4) * 2; if ((m_n % 4) > 0) { s += float((m_n % 4) - 1) / 6.0; } fs[m_outputNumbers["curve-fsr-timed"]] .push_back(snappedCurveValue(RealTime::fromSeconds(s), RealTime::fromSeconds(snap(s, 0.4)), m_n, 10)); } if (m_n < 20) { fs[m_outputNumbers["grid-oss"]] .push_back(gridColumn(timestamp, m_n, 20)); } } else { for (int i = (m_n > 5 ? 5 : m_n); i < 10; ++i) { fs[m_outputNumbers["curve-fsr"]] .push_back(untimedCurveValue(RealTime::fromSeconds(i / 2.5), i, 10)); float s = (i / 4) * 2; if ((i % 4) > 0) { s += float((i % 4) - 1) / 6.0; } fs[m_outputNumbers["curve-fsr-timed"]] .push_back(snappedCurveValue(RealTime::fromSeconds(s), RealTime::fromSeconds(snap(s, 0.4)), i, 10)); } for (int i = 0; i < 10; ++i) { static std::vector<float> times { 2.4, 2.9, 3.14, 3.5, 4.0, 4.4, 3.9, 4.4, 4.8, 5 }; float s = times[i]; float sn = snap(s, 0.4) + 1e-5; // to avoid printing e.g. 2.799 if (i == 4 || i == 5 || i == 8) { fs[m_outputNumbers["curve-fsr-mixed"]] .push_back(untimedCurveValue(RealTime::fromSeconds(s), i, 10)); } else { fs[m_outputNumbers["curve-fsr-mixed"]] .push_back(snappedCurveValue(RealTime::fromSeconds(s), RealTime::fromSeconds(sn), i, 10)); } } for (int i = 0; i < 10; ++i) { fs[m_outputNumbers["grid-fsr"]] .push_back(gridColumn(RealTime::fromSeconds(i / 2.5), i, 10)); } } m_lastTime = endTime; m_n = m_n + 1; return fs; } VampTestPlugin::FeatureSet VampTestPlugin::process(const float *const *inputBuffers, RealTime timestamp) { if (!m_produceOutput) return FeatureSet(); FeatureSet fs = featuresFrom(timestamp, false); Feature f; float eps = 1e-6f; for (int c = 0; c < m_channels; ++c) { if (!m_frequencyDomain) { // first value plus number of non-zero values float sum = inputBuffers[c][0]; for (int i = 0; i < m_blockSize; ++i) { if (fabsf(inputBuffers[c][i]) >= eps) sum += 1; } f.values.push_back(sum); } else { // If we're in frequency-domain mode, we convert back to // time-domain to calculate the input-summary feature // output. That should help the caller check that // time-frequency conversion has gone more or less OK, // though they'll still have to bear in mind windowing and // FFT shift (i.e. phase shift which puts the first // element in the middle of the frame) vector<double> ri(m_blockSize, 0.0); vector<double> ii(m_blockSize, 0.0); vector<double> ro(m_blockSize, 0.0); vector<double> io(m_blockSize, 0.0); for (int i = 0; i <= m_blockSize/2; ++i) { ri[i] = inputBuffers[c][i*2]; ii[i] = inputBuffers[c][i*2 + 1]; if (i > 0) ri[m_blockSize-i] = ri[i]; if (i > 0) ii[m_blockSize-i] = -ii[i]; } Vamp::FFT::inverse(m_blockSize, &ri[0], &ii[0], &ro[0], &io[0]); float sum = 0; for (int i = 0; i < m_blockSize; ++i) { if (fabs(ro[i]) >= eps) sum += 1; } sum += ro[0]; f.values.push_back(sum); } } fs[m_outputNumbers["input-summary"]].push_back(f); f.values.clear(); float frame = RealTime::realTime2Frame(timestamp, m_inputSampleRate); f.values.push_back(frame); fs[m_outputNumbers["input-timestamp"]].push_back(f); return fs; } VampTestPlugin::FeatureSet VampTestPlugin::getRemainingFeatures() { if (!m_produceOutput) return FeatureSet(); FeatureSet fs = featuresFrom(m_lastTime, true); return fs; }