Chris@0: Chris@49: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@52: Sonic Visualiser Chris@52: An audio file viewer and annotation editor. Chris@52: Centre for Digital Music, Queen Mary, University of London. Chris@52: This file copyright 2006 Chris Cannam. Chris@0: Chris@52: This program is free software; you can redistribute it and/or Chris@52: modify it under the terms of the GNU General Public License as Chris@52: published by the Free Software Foundation; either version 2 of the Chris@52: License, or (at your option) any later version. See the file Chris@52: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include "FeatureExtractionPluginTransform.h" Chris@0: Chris@0: #include "plugin/FeatureExtractionPluginFactory.h" Chris@66: #include "plugin/PluginXml.h" Chris@66: #include "vamp-sdk/Plugin.h" Chris@0: Chris@0: #include "base/Model.h" Chris@67: #include "base/Window.h" Chris@0: #include "model/SparseOneDimensionalModel.h" Chris@0: #include "model/SparseTimeValueModel.h" Chris@0: #include "model/DenseThreeDimensionalModel.h" Chris@0: #include "model/DenseTimeValueModel.h" Chris@115: #include "model/NoteModel.h" Chris@0: Chris@67: #include Chris@67: Chris@0: #include Chris@0: Chris@0: FeatureExtractionPluginTransform::FeatureExtractionPluginTransform(Model *inputModel, Chris@0: QString pluginId, Chris@64: int channel, Chris@56: QString configurationXml, Chris@0: QString outputName) : Chris@0: Transform(inputModel), Chris@0: m_plugin(0), Chris@64: m_channel(channel), Chris@68: m_stepSize(0), Chris@68: m_blockSize(0), Chris@0: m_descriptor(0), Chris@0: m_outputFeatureNo(0) Chris@0: { Chris@117: // std::cerr << "FeatureExtractionPluginTransform::FeatureExtractionPluginTransform: plugin " << pluginId.toStdString() << ", outputName " << outputName.toStdString() << std::endl; Chris@0: Chris@0: FeatureExtractionPluginFactory *factory = Chris@0: FeatureExtractionPluginFactory::instanceFor(pluginId); Chris@0: Chris@0: if (!factory) { Chris@0: std::cerr << "FeatureExtractionPluginTransform: No factory available for plugin id \"" Chris@0: << pluginId.toStdString() << "\"" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: m_plugin = factory->instantiatePlugin(pluginId, m_input->getSampleRate()); Chris@0: Chris@0: if (!m_plugin) { Chris@0: std::cerr << "FeatureExtractionPluginTransform: Failed to instantiate plugin \"" Chris@0: << pluginId.toStdString() << "\"" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@56: if (configurationXml != "") { Chris@66: PluginXml(m_plugin).setParametersFromXml(configurationXml); Chris@56: } Chris@56: Chris@68: m_blockSize = m_plugin->getPreferredBlockSize(); Chris@68: m_stepSize = m_plugin->getPreferredStepSize(); Chris@68: Chris@68: if (m_blockSize == 0) m_blockSize = 1024; //!!! todo: ask user Chris@68: if (m_stepSize == 0) m_stepSize = m_blockSize; //!!! likewise Chris@68: Chris@114: DenseTimeValueModel *input = getInput(); Chris@114: if (!input) return; Chris@114: Chris@114: size_t channelCount = input->getChannelCount(); Chris@114: if (m_plugin->getMaxChannelCount() < channelCount) { Chris@114: channelCount = 1; Chris@114: } Chris@114: if (m_plugin->getMinChannelCount() > channelCount) { Chris@114: std::cerr << "FeatureExtractionPluginTransform:: " Chris@114: << "Can't provide enough channels to plugin (plugin min " Chris@114: << m_plugin->getMinChannelCount() << ", max " Chris@114: << m_plugin->getMaxChannelCount() << ", input model has " Chris@114: << input->getChannelCount() << ")" << std::endl; Chris@114: return; Chris@114: } Chris@114: Chris@114: if (!m_plugin->initialise(channelCount, m_stepSize, m_blockSize)) { Chris@114: std::cerr << "FeatureExtractionPluginTransform: Plugin " Chris@114: << m_plugin->getName() << " failed to initialise!" << std::endl; Chris@114: return; Chris@114: } Chris@114: Chris@74: Vamp::Plugin::OutputList outputs = m_plugin->getOutputDescriptors(); Chris@0: Chris@0: if (outputs.empty()) { Chris@0: std::cerr << "FeatureExtractionPluginTransform: Plugin \"" Chris@0: << pluginId.toStdString() << "\" has no outputs" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@0: for (size_t i = 0; i < outputs.size(); ++i) { Chris@0: if (outputName == "" || outputs[i].name == outputName.toStdString()) { Chris@0: m_outputFeatureNo = i; Chris@66: m_descriptor = new Vamp::Plugin::OutputDescriptor Chris@0: (outputs[i]); Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: if (!m_descriptor) { Chris@0: std::cerr << "FeatureExtractionPluginTransform: Plugin \"" Chris@0: << pluginId.toStdString() << "\" has no output named \"" Chris@0: << outputName.toStdString() << "\"" << std::endl; Chris@0: return; Chris@0: } Chris@0: Chris@117: // std::cerr << "FeatureExtractionPluginTransform: output sample type " Chris@117: // << m_descriptor->sampleType << std::endl; Chris@0: Chris@70: int binCount = 1; Chris@0: float minValue = 0.0, maxValue = 0.0; Chris@0: Chris@70: if (m_descriptor->hasFixedBinCount) { Chris@70: binCount = m_descriptor->binCount; Chris@0: } Chris@0: Chris@117: // std::cerr << "FeatureExtractionPluginTransform: output bin count " Chris@117: // << binCount << std::endl; Chris@114: Chris@70: if (binCount > 0 && m_descriptor->hasKnownExtents) { Chris@0: minValue = m_descriptor->minValue; Chris@0: maxValue = m_descriptor->maxValue; Chris@0: } Chris@0: Chris@0: size_t modelRate = m_input->getSampleRate(); Chris@0: size_t modelResolution = 1; Chris@0: Chris@0: switch (m_descriptor->sampleType) { Chris@0: Chris@66: case Vamp::Plugin::OutputDescriptor::VariableSampleRate: Chris@0: if (m_descriptor->sampleRate != 0.0) { Chris@0: modelResolution = size_t(modelRate / m_descriptor->sampleRate + 0.001); Chris@0: } Chris@0: break; Chris@0: Chris@66: case Vamp::Plugin::OutputDescriptor::OneSamplePerStep: Chris@72: modelResolution = m_stepSize; Chris@0: break; Chris@0: Chris@66: case Vamp::Plugin::OutputDescriptor::FixedSampleRate: Chris@73: modelRate = size_t(m_descriptor->sampleRate + 0.001); Chris@0: break; Chris@0: } Chris@0: Chris@70: if (binCount == 0) { Chris@0: Chris@20: m_output = new SparseOneDimensionalModel(modelRate, modelResolution, Chris@20: false); Chris@0: Chris@115: } else if (binCount == 1) { Chris@0: Chris@115: SparseTimeValueModel *model = new SparseTimeValueModel Chris@115: (modelRate, modelResolution, minValue, maxValue, false); Chris@115: model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str()); Chris@115: Chris@115: m_output = model; Chris@115: Chris@115: } else if (m_descriptor->sampleType == Chris@66: Vamp::Plugin::OutputDescriptor::VariableSampleRate) { Chris@115: Chris@115: // We don't have a sparse 3D model, so interpret this as a Chris@115: // note model. There's nothing to define which values to use Chris@115: // as which parameters of the note -- for the moment let's Chris@115: // treat the first as pitch, second as duration in frames, Chris@115: // third (if present) as velocity. (Our note model doesn't Chris@115: // yet store velocity.) Chris@115: //!!! todo: ask the user! Chris@0: Chris@115: NoteModel *model = new NoteModel Chris@63: (modelRate, modelResolution, minValue, maxValue, false); Chris@63: model->setScaleUnits(outputs[m_outputFeatureNo].unit.c_str()); Chris@63: Chris@63: m_output = model; Chris@0: Chris@0: } else { Chris@0: Chris@0: m_output = new DenseThreeDimensionalModel(modelRate, modelResolution, Chris@70: binCount, false); Chris@20: Chris@70: if (!m_descriptor->binNames.empty()) { Chris@20: std::vector names; Chris@70: for (size_t i = 0; i < m_descriptor->binNames.size(); ++i) { Chris@70: names.push_back(m_descriptor->binNames[i].c_str()); Chris@20: } Chris@20: (dynamic_cast(m_output)) Chris@20: ->setBinNames(names); Chris@20: } Chris@0: } Chris@0: } Chris@0: Chris@0: FeatureExtractionPluginTransform::~FeatureExtractionPluginTransform() Chris@0: { Chris@0: delete m_plugin; Chris@0: delete m_descriptor; Chris@0: } Chris@0: Chris@0: DenseTimeValueModel * Chris@0: FeatureExtractionPluginTransform::getInput() Chris@0: { Chris@0: DenseTimeValueModel *dtvm = Chris@0: dynamic_cast(getInputModel()); Chris@0: if (!dtvm) { Chris@0: std::cerr << "FeatureExtractionPluginTransform::getInput: WARNING: Input model is not conformable to DenseTimeValueModel" << std::endl; Chris@0: } Chris@0: return dtvm; Chris@0: } Chris@0: Chris@0: void Chris@0: FeatureExtractionPluginTransform::run() Chris@0: { Chris@0: DenseTimeValueModel *input = getInput(); Chris@0: if (!input) return; Chris@0: Chris@0: if (!m_output) return; Chris@0: Chris@114: size_t sampleRate = m_input->getSampleRate(); Chris@114: Chris@0: size_t channelCount = input->getChannelCount(); Chris@0: if (m_plugin->getMaxChannelCount() < channelCount) { Chris@0: channelCount = 1; Chris@0: } Chris@0: Chris@0: float **buffers = new float*[channelCount]; Chris@0: for (size_t ch = 0; ch < channelCount; ++ch) { Chris@68: buffers[ch] = new float[m_blockSize]; Chris@0: } Chris@0: Chris@67: double *fftInput = 0; Chris@67: fftw_complex *fftOutput = 0; Chris@67: fftw_plan fftPlan = 0; Chris@68: Window windower(HanningWindow, m_blockSize); Chris@67: Chris@67: if (m_plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain) { Chris@67: Chris@68: fftInput = (double *)fftw_malloc(m_blockSize * sizeof(double)); Chris@68: fftOutput = (fftw_complex *)fftw_malloc(m_blockSize * sizeof(fftw_complex)); Chris@68: fftPlan = fftw_plan_dft_r2c_1d(m_blockSize, fftInput, fftOutput, Chris@67: FFTW_ESTIMATE); Chris@67: if (!fftPlan) { Chris@67: std::cerr << "ERROR: FeatureExtractionPluginTransform::run(): fftw_plan failed! Results will be garbage" << std::endl; Chris@67: } Chris@67: } Chris@67: Chris@70: long startFrame = m_input->getStartFrame(); Chris@70: long endFrame = m_input->getEndFrame(); Chris@70: long blockFrame = startFrame; Chris@0: Chris@70: long prevCompletion = 0; Chris@0: Chris@70: while (1) { Chris@70: Chris@70: if (fftPlan) { Chris@73: if (blockFrame - int(m_blockSize)/2 > endFrame) break; Chris@70: } else { Chris@70: if (blockFrame >= endFrame) break; Chris@70: } Chris@0: Chris@0: // std::cerr << "FeatureExtractionPluginTransform::run: blockFrame " Chris@0: // << blockFrame << std::endl; Chris@0: Chris@70: long completion = Chris@68: (((blockFrame - startFrame) / m_stepSize) * 99) / Chris@68: ( (endFrame - startFrame) / m_stepSize); Chris@0: Chris@0: // channelCount is either m_input->channelCount or 1 Chris@0: Chris@73: for (size_t ch = 0; ch < channelCount; ++ch) { Chris@70: if (fftPlan) { Chris@70: getFrames(ch, channelCount, Chris@70: blockFrame - m_blockSize/2, m_blockSize, buffers[ch]); Chris@70: } else { Chris@70: getFrames(ch, channelCount, Chris@70: blockFrame, m_blockSize, buffers[ch]); Chris@70: } Chris@70: } Chris@70: Chris@67: if (fftPlan) { Chris@73: for (size_t ch = 0; ch < channelCount; ++ch) { Chris@73: for (size_t i = 0; i < m_blockSize; ++i) { Chris@67: fftInput[i] = buffers[ch][i]; Chris@67: } Chris@67: windower.cut(fftInput); Chris@73: for (size_t i = 0; i < m_blockSize/2; ++i) { Chris@67: double temp = fftInput[i]; Chris@68: fftInput[i] = fftInput[i + m_blockSize/2]; Chris@68: fftInput[i + m_blockSize/2] = temp; Chris@67: } Chris@67: fftw_execute(fftPlan); Chris@73: for (size_t i = 0; i < m_blockSize/2; ++i) { Chris@67: buffers[ch][i*2] = fftOutput[i][0]; Chris@67: buffers[ch][i*2 + 1] = fftOutput[i][1]; Chris@67: } Chris@67: } Chris@67: } Chris@67: Chris@66: Vamp::Plugin::FeatureSet features = m_plugin->process Chris@66: (buffers, Vamp::RealTime::frame2RealTime(blockFrame, sampleRate)); Chris@0: Chris@0: for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) { Chris@66: Vamp::Plugin::Feature feature = Chris@0: features[m_outputFeatureNo][fi]; Chris@0: addFeature(blockFrame, feature); Chris@0: } Chris@0: Chris@0: if (blockFrame == startFrame || completion > prevCompletion) { Chris@0: setCompletion(completion); Chris@0: prevCompletion = completion; Chris@0: } Chris@0: Chris@68: blockFrame += m_stepSize; Chris@0: } Chris@0: Chris@67: if (fftPlan) { Chris@67: fftw_destroy_plan(fftPlan); Chris@67: fftw_free(fftInput); Chris@67: fftw_free(fftOutput); Chris@67: } Chris@67: Chris@66: Vamp::Plugin::FeatureSet features = m_plugin->getRemainingFeatures(); Chris@0: Chris@0: for (size_t fi = 0; fi < features[m_outputFeatureNo].size(); ++fi) { Chris@66: Vamp::Plugin::Feature feature = Chris@0: features[m_outputFeatureNo][fi]; Chris@0: addFeature(blockFrame, feature); Chris@0: } Chris@0: Chris@0: setCompletion(100); Chris@0: } Chris@0: Chris@70: void Chris@70: FeatureExtractionPluginTransform::getFrames(int channel, int channelCount, Chris@70: long startFrame, long size, Chris@70: float *buffer) Chris@70: { Chris@70: long offset = 0; Chris@70: Chris@70: if (startFrame < 0) { Chris@70: for (int i = 0; i < size && startFrame + i < 0; ++i) { Chris@70: buffer[i] = 0.0f; Chris@70: } Chris@70: offset = -startFrame; Chris@70: size -= offset; Chris@70: if (size <= 0) return; Chris@70: startFrame = 0; Chris@70: } Chris@70: Chris@73: long got = getInput()->getValues Chris@70: ((channelCount == 1 ? m_channel : channel), Chris@70: startFrame, startFrame + size, buffer + offset); Chris@70: Chris@70: while (got < size) { Chris@70: buffer[offset + got] = 0.0; Chris@70: ++got; Chris@70: } Chris@74: Chris@74: if (m_channel == -1 && channelCount == 1 && Chris@74: getInput()->getChannelCount() > 1) { Chris@74: // use mean instead of sum, as plugin input Chris@74: int cc = getInput()->getChannelCount(); Chris@74: for (long i = 0; i < size; ++i) { Chris@74: buffer[i] /= cc; Chris@74: } Chris@74: } Chris@70: } Chris@0: Chris@0: void Chris@0: FeatureExtractionPluginTransform::addFeature(size_t blockFrame, Chris@66: const Vamp::Plugin::Feature &feature) Chris@0: { Chris@0: size_t inputRate = m_input->getSampleRate(); Chris@0: Chris@0: // std::cerr << "FeatureExtractionPluginTransform::addFeature(" Chris@0: // << blockFrame << ")" << std::endl; Chris@0: Chris@70: int binCount = 1; Chris@70: if (m_descriptor->hasFixedBinCount) { Chris@70: binCount = m_descriptor->binCount; Chris@0: } Chris@0: Chris@0: size_t frame = blockFrame; Chris@0: Chris@0: if (m_descriptor->sampleType == Chris@66: Vamp::Plugin::OutputDescriptor::VariableSampleRate) { Chris@0: Chris@0: if (!feature.hasTimestamp) { Chris@0: std::cerr Chris@0: << "WARNING: FeatureExtractionPluginTransform::addFeature: " Chris@0: << "Feature has variable sample rate but no timestamp!" Chris@0: << std::endl; Chris@0: return; Chris@0: } else { Chris@66: frame = Vamp::RealTime::realTime2Frame(feature.timestamp, inputRate); Chris@0: } Chris@0: Chris@0: } else if (m_descriptor->sampleType == Chris@66: Vamp::Plugin::OutputDescriptor::FixedSampleRate) { Chris@0: Chris@0: if (feature.hasTimestamp) { Chris@0: //!!! warning: sampleRate may be non-integral Chris@66: frame = Vamp::RealTime::realTime2Frame(feature.timestamp, Chris@66: m_descriptor->sampleRate); Chris@0: } else { Chris@0: frame = m_output->getEndFrame() + 1; Chris@0: } Chris@0: } Chris@0: Chris@70: if (binCount == 0) { Chris@0: Chris@0: SparseOneDimensionalModel *model = getOutput(); Chris@0: if (!model) return; Chris@0: model->addPoint(SparseOneDimensionalModel::Point(frame, feature.label.c_str())); Chris@0: Chris@115: } else if (binCount == 1) { Chris@0: Chris@0: float value = 0.0; Chris@0: if (feature.values.size() > 0) value = feature.values[0]; Chris@0: Chris@0: SparseTimeValueModel *model = getOutput(); Chris@0: if (!model) return; Chris@0: model->addPoint(SparseTimeValueModel::Point(frame, value, feature.label.c_str())); Chris@115: Chris@115: } else if (m_descriptor->sampleType == Chris@115: Vamp::Plugin::OutputDescriptor::VariableSampleRate) { Chris@115: Chris@115: float pitch = 0.0; Chris@115: if (feature.values.size() > 0) pitch = feature.values[0]; Chris@115: Chris@115: float duration = 1; Chris@115: if (feature.values.size() > 1) duration = feature.values[1]; Chris@115: Chris@115: float velocity = 100; Chris@115: if (feature.values.size() > 2) velocity = feature.values[2]; Chris@115: Chris@115: NoteModel *model = getOutput(); Chris@115: if (!model) return; Chris@115: Chris@115: model->addPoint(NoteModel::Point(frame, pitch, duration, feature.label.c_str())); Chris@0: Chris@0: } else { Chris@0: Chris@0: DenseThreeDimensionalModel::BinValueSet values = feature.values; Chris@0: Chris@0: DenseThreeDimensionalModel *model = getOutput(); Chris@0: if (!model) return; Chris@0: Chris@0: model->setBinValues(frame, values); Chris@0: } Chris@0: } Chris@0: Chris@0: void Chris@0: FeatureExtractionPluginTransform::setCompletion(int completion) Chris@0: { Chris@70: int binCount = 1; Chris@70: if (m_descriptor->hasFixedBinCount) { Chris@70: binCount = m_descriptor->binCount; Chris@0: } Chris@0: Chris@70: if (binCount == 0) { Chris@0: Chris@0: SparseOneDimensionalModel *model = getOutput(); Chris@0: if (!model) return; Chris@0: model->setCompletion(completion); Chris@0: Chris@115: } else if (binCount == 1) { Chris@115: Chris@115: SparseTimeValueModel *model = getOutput(); Chris@115: if (!model) return; Chris@115: model->setCompletion(completion); Chris@115: Chris@115: } else if (m_descriptor->sampleType == Chris@66: Vamp::Plugin::OutputDescriptor::VariableSampleRate) { Chris@0: Chris@115: NoteModel *model = getOutput(); Chris@0: if (!model) return; Chris@0: model->setCompletion(completion); Chris@0: Chris@0: } else { Chris@0: Chris@19: DenseThreeDimensionalModel *model = getOutput(); Chris@19: if (!model) return; Chris@19: model->setCompletion(completion); Chris@0: } Chris@0: } Chris@0: