c@41: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ c@41: c@41: /* c@41: * SegmenterPlugin.cpp c@41: * c@41: * Copyright 2008 Centre for Digital Music, Queen Mary, University of London. c@41: * All rights reserved. c@41: */ c@41: c@41: #include c@41: #include c@41: c@41: #include "SimilarityPlugin.h" c@41: #include "dsp/mfcc/MFCC.h" c@41: #include "dsp/rateconversion/Decimator.h" c@41: c@41: using std::string; c@41: using std::vector; c@41: using std::cerr; c@41: using std::endl; c@41: using std::ostringstream; c@41: c@41: SimilarityPlugin::SimilarityPlugin(float inputSampleRate) : c@41: Plugin(inputSampleRate), c@41: m_mfcc(0), c@41: m_decimator(0), c@41: m_K(20), c@41: m_blockSize(0), c@41: m_channels(0) c@41: { c@41: c@41: } c@41: c@41: SimilarityPlugin::~SimilarityPlugin() c@41: { c@41: delete m_mfcc; c@41: delete m_decimator; c@41: } c@41: c@41: string c@41: SimilarityPlugin::getIdentifier() const c@41: { c@41: return "qm-similarity"; c@41: } c@41: c@41: string c@41: SimilarityPlugin::getName() const c@41: { c@41: return "Similarity"; c@41: } c@41: c@41: string c@41: SimilarityPlugin::getDescription() const c@41: { c@41: return "Return a distance metric for overall timbral similarity between the input audio channels"; c@41: } c@41: c@41: string c@41: SimilarityPlugin::getMaker() const c@41: { c@41: return "Chris Cannam, Queen Mary, University of London"; c@41: } c@41: c@41: int c@41: SimilarityPlugin::getPluginVersion() const c@41: { c@41: return 1; c@41: } c@41: c@41: string c@41: SimilarityPlugin::getCopyright() const c@41: { c@41: return "Copyright (c) 2008 - All Rights Reserved"; c@41: } c@41: c@41: size_t c@41: SimilarityPlugin::getMinChannelCount() const c@41: { c@41: return 2; c@41: } c@41: c@41: size_t c@41: SimilarityPlugin::getMaxChannelCount() const c@41: { c@41: return 1024; c@41: } c@41: c@41: bool c@41: SimilarityPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) c@41: { c@41: if (channels < getMinChannelCount() || c@41: channels > getMaxChannelCount()) return false; c@41: c@41: if (stepSize != getPreferredStepSize()) { c@41: std::cerr << "SimilarityPlugin::initialise: supplied step size " c@41: << stepSize << " differs from required step size " c@41: << getPreferredStepSize() << std::endl; c@41: return false; c@41: } c@41: c@41: if (blockSize != getPreferredBlockSize()) { c@41: std::cerr << "SimilarityPlugin::initialise: supplied block size " c@41: << blockSize << " differs from required block size " c@41: << getPreferredBlockSize() << std::endl; c@41: return false; c@41: } c@41: c@41: m_blockSize = blockSize; c@41: m_channels = channels; c@41: c@41: int decimationFactor = getDecimationFactor(); c@41: if (decimationFactor > 1) { c@41: m_decimator = new Decimator(getPreferredBlockSize(), decimationFactor); c@41: } c@41: c@41: MFCCConfig config; c@41: config.FS = lrintf(m_inputSampleRate) / decimationFactor; c@41: config.fftsize = 2048; c@41: config.nceps = m_K - 1; c@41: config.want_c0 = true; c@41: m_mfcc = new MFCC(config); c@41: c@41: for (int i = 0; i < m_channels; ++i) { c@41: m_mfeatures.push_back(MFCCFeatureVector()); c@41: } c@41: c@41: return true; c@41: } c@41: c@41: void c@41: SimilarityPlugin::reset() c@41: { c@41: //!!! c@41: } c@41: c@41: int c@41: SimilarityPlugin::getDecimationFactor() const c@41: { c@41: int rate = lrintf(m_inputSampleRate); c@41: int internalRate = 22050; c@41: int decimationFactor = rate / internalRate; c@41: if (decimationFactor < 1) decimationFactor = 1; c@41: c@41: // must be a power of two c@41: while (decimationFactor & (decimationFactor - 1)) ++decimationFactor; c@41: c@41: return decimationFactor; c@41: } c@41: c@41: size_t c@41: SimilarityPlugin::getPreferredStepSize() const c@41: { c@41: return 1024 * getDecimationFactor(); c@41: } c@41: c@41: size_t c@41: SimilarityPlugin::getPreferredBlockSize() const c@41: { c@41: return 2048 * getDecimationFactor(); c@41: } c@41: c@41: SimilarityPlugin::ParameterList SimilarityPlugin::getParameterDescriptors() const c@41: { c@41: ParameterList list; c@41: return list; c@41: } c@41: c@41: float c@41: SimilarityPlugin::getParameter(std::string param) const c@41: { c@41: std::cerr << "WARNING: SimilarityPlugin::getParameter: unknown parameter \"" c@41: << param << "\"" << std::endl; c@41: return 0.0; c@41: } c@41: c@41: void c@41: SimilarityPlugin::setParameter(std::string param, float value) c@41: { c@41: std::cerr << "WARNING: SimilarityPlugin::setParameter: unknown parameter \"" c@41: << param << "\"" << std::endl; c@41: } c@41: c@41: SimilarityPlugin::OutputList c@41: SimilarityPlugin::getOutputDescriptors() const c@41: { c@41: OutputList list; c@41: c@41: OutputDescriptor similarity; c@41: similarity.identifier = "distance"; c@41: similarity.name = "Distance"; c@41: similarity.description = "Distance Metric for Timbral Similarity (smaller = more similar)"; c@41: similarity.unit = ""; c@41: similarity.hasFixedBinCount = true; c@41: similarity.binCount = m_channels; c@41: similarity.hasKnownExtents = false; c@41: similarity.isQuantized = false; c@41: similarity.sampleType = OutputDescriptor::FixedSampleRate; c@41: similarity.sampleRate = 1; c@41: c@41: list.push_back(similarity); c@41: c@41: OutputDescriptor means; c@41: means.identifier = "means"; c@41: means.name = "MFCC Means"; c@41: means.description = ""; c@41: means.unit = ""; c@41: means.hasFixedBinCount = true; c@41: means.binCount = m_channels; c@41: means.hasKnownExtents = false; c@41: means.isQuantized = false; c@41: means.sampleType = OutputDescriptor::VariableSampleRate; c@41: means.sampleRate = m_inputSampleRate / getPreferredStepSize(); c@41: c@41: list.push_back(means); c@41: c@41: OutputDescriptor variances; c@41: variances.identifier = "variances"; c@41: variances.name = "MFCC Variances"; c@41: variances.description = ""; c@41: variances.unit = ""; c@41: variances.hasFixedBinCount = true; c@41: variances.binCount = m_channels; c@41: variances.hasKnownExtents = false; c@41: variances.isQuantized = false; c@41: variances.sampleType = OutputDescriptor::VariableSampleRate; c@41: variances.sampleRate = m_inputSampleRate / getPreferredStepSize(); c@41: c@41: list.push_back(variances); c@41: c@41: return list; c@41: } c@41: c@41: SimilarityPlugin::FeatureSet c@41: SimilarityPlugin::process(const float *const *inputBuffers, Vamp::RealTime /* timestamp */) c@41: { c@41: double *dblbuf = new double[m_blockSize]; c@41: double *decbuf = dblbuf; c@41: if (m_decimator) decbuf = new double[m_mfcc->getfftlength()]; c@41: double *ceps = new double[m_K]; c@41: c@41: for (size_t c = 0; c < m_channels; ++c) { c@41: c@41: for (int i = 0; i < m_blockSize; ++i) { c@41: dblbuf[i] = inputBuffers[c][i]; c@41: } c@41: c@41: if (m_decimator) { c@41: m_decimator->process(dblbuf, decbuf); c@41: } c@41: c@41: m_mfcc->process(m_mfcc->getfftlength(), decbuf, ceps); c@41: c@41: MFCCFeature mf(m_K); c@41: for (int i = 0; i < m_K; ++i) mf[i] = ceps[i]; c@41: c@41: m_mfeatures[c].push_back(mf); c@41: } c@41: c@41: if (m_decimator) delete[] decbuf; c@41: delete[] dblbuf; c@41: delete[] ceps; c@41: c@41: return FeatureSet(); c@41: } c@41: c@41: SimilarityPlugin::FeatureSet c@41: SimilarityPlugin::getRemainingFeatures() c@41: { c@41: std::vector m(m_channels); c@41: std::vector v(m_channels); c@41: c@41: //!!! bail if m_mfeatures vectors are empty c@41: c@41: for (int i = 0; i < m_channels; ++i) { c@41: c@41: MFCCFeature mean(m_K), variance(m_K); c@41: c@41: for (int j = 0; j < m_K; ++j) { c@41: c@41: mean[j] = variance[j] = 0.0; c@41: int count; c@41: c@41: count = 0; c@41: for (int k = 0; k < m_mfeatures[i].size(); ++k) { c@41: double val = m_mfeatures[i][k][j]; c@41: // std::cout << "val = " << val << std::endl; c@41: if (isnan(val) || isinf(val)) continue; c@41: mean[j] += val; c@41: // std::cout << "mean now = " << mean[j] << std::endl; c@41: ++count; c@41: } c@41: if (count > 0) mean[j] /= count; c@41: // std::cout << "divided by " << count << ", mean now " << mean[j] << std::endl; c@41: c@41: count = 0; c@41: for (int k = 0; k < m_mfeatures[i].size(); ++k) { c@41: double val = ((m_mfeatures[i][k][j] - mean[j]) * c@41: (m_mfeatures[i][k][j] - mean[j])); c@41: if (isnan(val) || isinf(val)) continue; c@41: variance[j] += val; c@41: ++count; c@41: } c@41: if (count > 0) variance[j] /= count; c@41: } c@41: c@41: m[i] = mean; c@41: v[i] = variance; c@41: } c@41: c@41: // std::cout << "m[0][0] = " << m[0][0] << std::endl; c@41: c@41: // so we sorta return a matrix of the distances between channels, c@41: // but Vamp doesn't have a matrix return type so we actually c@41: // return a series of vectors c@41: c@41: std::vector > distances; c@41: c@41: for (int i = 0; i < m_channels; ++i) { c@41: distances.push_back(std::vector()); c@41: for (int j = 0; j < m_channels; ++j) { c@41: double d = -2.0 * m_K; c@41: for (int k = 0; k < m_K; ++k) { c@41: // m[i][k] is the mean of mfcc k for channel i c@41: // v[i][k] is the variance of mfcc k for channel i c@41: d += v[i][k] / v[j][k] + v[j][k] / v[i][k]; c@41: d += (m[i][k] - m[j][k]) c@41: * (1.0 / v[i][k] + 1.0 / v[j][k]) c@41: * (m[i][k] - m[j][k]); c@41: } c@41: d /= 2.0; c@41: distances[i].push_back(d); c@41: } c@41: } c@41: c@41: FeatureSet returnFeatures; c@41: c@41: for (int i = 0; i < m_channels; ++i) { c@41: c@41: Feature feature; c@41: feature.hasTimestamp = true; // otherwise hosts will tend to stamp them at the end of the file, which is annoying c@41: feature.timestamp = Vamp::RealTime(i, 0); c@41: c@41: feature.values.clear(); c@41: for (int k = 0; k < m_K; ++k) { c@41: feature.values.push_back(m[i][k]); c@41: } c@41: c@41: returnFeatures[1].push_back(feature); c@41: c@41: feature.values.clear(); c@41: for (int k = 0; k < m_K; ++k) { c@41: feature.values.push_back(v[i][k]); c@41: } c@41: c@41: returnFeatures[2].push_back(feature); c@41: c@41: feature.values.clear(); c@41: for (int j = 0; j < m_channels; ++j) { c@41: feature.values.push_back(distances[i][j]); c@41: } c@41: ostringstream oss; c@41: oss << "Distance from " << (i + 1); c@41: feature.label = oss.str(); c@41: c@41: returnFeatures[0].push_back(feature); c@41: } c@41: c@41: return returnFeatures; c@41: }