diff options
Diffstat (limited to 'libs/vamp-plugins/KeyDetect.cpp')
-rw-r--r-- | libs/vamp-plugins/KeyDetect.cpp | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/libs/vamp-plugins/KeyDetect.cpp b/libs/vamp-plugins/KeyDetect.cpp new file mode 100644 index 0000000000..a339784335 --- /dev/null +++ b/libs/vamp-plugins/KeyDetect.cpp @@ -0,0 +1,407 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + QM Vamp Plugin Set + + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "KeyDetect.h" + +using std::string; +using std::vector; +//using std::cerr; +using std::endl; + +#include <cmath> + + +// Order for circle-of-5ths plotting +static int conversion[24] = +{ 7, 12, 5, 10, 3, 8, 1, 6, 11, 4, 9, 2, + 16, 21, 14, 19, 24, 17, 22, 15, 20, 13, 18, 23 }; + + +KeyDetector::KeyDetector(float inputSampleRate) : + Plugin(inputSampleRate), + m_stepSize(0), + m_blockSize(0), + m_tuningFrequency(440), + m_length(10), + m_getKeyMode(0), + m_inputFrame(0), + m_prevKey(-1) +{ +} + +KeyDetector::~KeyDetector() +{ + delete m_getKeyMode; + if ( m_inputFrame ) { + delete [] m_inputFrame; + } +} + +string +KeyDetector::getIdentifier() const +{ + return "qm-keydetector"; +} + +string +KeyDetector::getName() const +{ + return "Key Detector"; +} + +string +KeyDetector::getDescription() const +{ + return "Estimate the key of the music"; +} + +string +KeyDetector::getMaker() const +{ + return "Queen Mary, University of London"; +} + +int +KeyDetector::getPluginVersion() const +{ + return 4; +} + +string +KeyDetector::getCopyright() const +{ + return "Plugin by Katy Noland and Christian Landone. Copyright (c) 2006-2009 QMUL - All Rights Reserved"; +} + +KeyDetector::ParameterList +KeyDetector::getParameterDescriptors() const +{ + ParameterList list; + + ParameterDescriptor desc; + desc.identifier = "tuning"; + desc.name = "Tuning Frequency"; + desc.description = "Frequency of concert A"; + desc.unit = "Hz"; + desc.minValue = 420; + desc.maxValue = 460; + desc.defaultValue = 440; + desc.isQuantized = false; + list.push_back(desc); + + desc.identifier = "length"; + desc.name = "Window Length"; + desc.unit = "chroma frames"; + desc.description = "Number of chroma analysis frames per key estimation"; + desc.minValue = 1; + desc.maxValue = 30; + desc.defaultValue = 10; + desc.isQuantized = true; + desc.quantizeStep = 1; + list.push_back(desc); + + return list; +} + +float +KeyDetector::getParameter(std::string param) const +{ + if (param == "tuning") { + return m_tuningFrequency; + } + if (param == "length") { + return m_length; + } + std::cerr << "WARNING: KeyDetector::getParameter: unknown parameter \"" + << param << "\"" << std::endl; + return 0.0; +} + +void +KeyDetector::setParameter(std::string param, float value) +{ + if (param == "tuning") { + m_tuningFrequency = value; + } else if (param == "length") { + m_length = int(value + 0.1); + } else { + std::cerr << "WARNING: KeyDetector::setParameter: unknown parameter \"" + << param << "\"" << std::endl; + } +} + +bool +KeyDetector::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (m_getKeyMode) { + delete m_getKeyMode; + m_getKeyMode = 0; + } + + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_getKeyMode = new GetKeyMode(int(m_inputSampleRate + 0.1), + m_tuningFrequency, + m_length, m_length); + + m_stepSize = m_getKeyMode->getHopSize(); + m_blockSize = m_getKeyMode->getBlockSize(); + + if (stepSize != m_stepSize || blockSize != m_blockSize) { + std::cerr << "KeyDetector::initialise: ERROR: step/block sizes " + << stepSize << "/" << blockSize << " differ from required " + << m_stepSize << "/" << m_blockSize << std::endl; + delete m_getKeyMode; + m_getKeyMode = 0; + return false; + } + + m_inputFrame = new double[m_blockSize]; + + m_prevKey = -1; + m_first = true; + + return true; +} + +void +KeyDetector::reset() +{ + if (m_getKeyMode) { + delete m_getKeyMode; + m_getKeyMode = new GetKeyMode(int(m_inputSampleRate + 0.1), + m_tuningFrequency, + m_length, m_length); + } + + if (m_inputFrame) { + for( unsigned int i = 0; i < m_blockSize; i++ ) { + m_inputFrame[ i ] = 0.0; + } + } + + m_prevKey = -1; + m_first = true; +} + + +KeyDetector::OutputList +KeyDetector::getOutputDescriptors() const +{ + OutputList list; + + float osr = 0.0f; + if (m_stepSize == 0) (void)getPreferredStepSize(); + osr = m_inputSampleRate / m_stepSize; + + OutputDescriptor d; + d.identifier = "tonic"; + d.name = "Tonic Pitch"; + d.unit = ""; + d.description = "Tonic of the estimated key (from C = 1 to B = 12)"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.isQuantized = true; + d.minValue = 1; + d.maxValue = 12; + d.quantizeStep = 1; + d.sampleRate = osr; + d.sampleType = OutputDescriptor::VariableSampleRate; + list.push_back(d); + + d.identifier = "mode"; + d.name = "Key Mode"; + d.unit = ""; + d.description = "Major or minor mode of the estimated key (major = 0, minor = 1)"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.isQuantized = true; + d.minValue = 0; + d.maxValue = 1; + d.quantizeStep = 1; + d.sampleRate = osr; + d.sampleType = OutputDescriptor::VariableSampleRate; + list.push_back(d); + + d.identifier = "key"; + d.name = "Key"; + d.unit = ""; + d.description = "Estimated key (from C major = 1 to B major = 12 and C minor = 13 to B minor = 24)"; + d.hasFixedBinCount = true; + d.binCount = 1; + d.hasKnownExtents = true; + d.isQuantized = true; + d.minValue = 1; + d.maxValue = 24; + d.quantizeStep = 1; + d.sampleRate = osr; + d.sampleType = OutputDescriptor::VariableSampleRate; + list.push_back(d); + + d.identifier = "keystrength"; + d.name = "Key Strength Plot"; + d.unit = ""; + d.description = "Correlation of the chroma vector with stored key profile for each major and minor key"; + d.hasFixedBinCount = true; + d.binCount = 25; + d.hasKnownExtents = false; + d.isQuantized = false; + d.sampleType = OutputDescriptor::OneSamplePerStep; + for (int i = 0; i < 24; ++i) { + if (i == 12) d.binNames.push_back(" "); + int idx = conversion[i]; + std::string label = getKeyName(idx > 12 ? idx-12 : idx, + i >= 12, + true); + d.binNames.push_back(label); + } + list.push_back(d); + + return list; +} + +KeyDetector::FeatureSet +KeyDetector::process(const float *const *inputBuffers, + Vamp::RealTime now) +{ + if (m_stepSize == 0) { + return FeatureSet(); + } + + FeatureSet returnFeatures; + + for ( unsigned int i = 0 ; i < m_blockSize; i++ ) { + m_inputFrame[i] = (double)inputBuffers[0][i]; + } + +// int key = (m_getKeyMode->process(m_inputFrame) % 24); + int key = m_getKeyMode->process(m_inputFrame); + bool minor = m_getKeyMode->isModeMinor(key); + int tonic = key; + if (tonic > 12) tonic -= 12; + + int prevTonic = m_prevKey; + if (prevTonic > 12) prevTonic -= 12; + + if (m_first || (tonic != prevTonic)) { + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = now; +// feature.timestamp = now; + feature.values.push_back((float)tonic); + feature.label = getKeyName(tonic, minor, false); + returnFeatures[0].push_back(feature); // tonic + } + + if (m_first || (minor != (m_getKeyMode->isModeMinor(m_prevKey)))) { + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = now; + feature.values.push_back(minor ? 1.f : 0.f); + feature.label = (minor ? "Minor" : "Major"); + returnFeatures[1].push_back(feature); // mode + } + + if (m_first || (key != m_prevKey)) { + Feature feature; + feature.hasTimestamp = true; + feature.timestamp = now; + feature.values.push_back((float)key); + feature.label = getKeyName(tonic, minor, true); + returnFeatures[2].push_back(feature); // key + } + + m_prevKey = key; + m_first = false; + + Feature ksf; + ksf.values.reserve(25); + double *keystrengths = m_getKeyMode->getKeyStrengths(); + for (int i = 0; i < 24; ++i) { + if (i == 12) ksf.values.push_back(-1); + ksf.values.push_back(keystrengths[conversion[i]-1]); + } + ksf.hasTimestamp = false; + returnFeatures[3].push_back(ksf); + + return returnFeatures; +} + +KeyDetector::FeatureSet +KeyDetector::getRemainingFeatures() +{ + return FeatureSet(); +} + + +size_t +KeyDetector::getPreferredStepSize() const +{ + if (!m_stepSize) { + GetKeyMode gkm(int(m_inputSampleRate + 0.1), + m_tuningFrequency, m_length, m_length); + m_stepSize = gkm.getHopSize(); + m_blockSize = gkm.getBlockSize(); + } + return m_stepSize; +} + +size_t +KeyDetector::getPreferredBlockSize() const +{ + if (!m_blockSize) { + GetKeyMode gkm(int(m_inputSampleRate + 0.1), + m_tuningFrequency, m_length, m_length); + m_stepSize = gkm.getHopSize(); + m_blockSize = gkm.getBlockSize(); + } + return m_blockSize; +} + +std::string +KeyDetector::getKeyName(int index, bool minor, bool includeMajMin) const +{ + // Keys are numbered with 1 => C, 12 => B + // This is based on chromagram base set to a C in qm-dsp's GetKeyMode.cpp + + static const char *namesMajor[] = { + "C", "Db", "D", "Eb", + "E", "F", "F# / Gb", "G", + "Ab", "A", "Bb", "B" + }; + + static const char *namesMinor[] = { + "C", "C#", "D", "Eb / D#", + "E", "F", "F#", "G", + "G#", "A", "Bb", "B" + }; + + if (index < 1 || index > 12) { + return "(unknown)"; + } + + std::string base; + + if (minor) base = namesMinor[index - 1]; + else base = namesMajor[index - 1]; + + if (!includeMajMin) return base; + + if (minor) return base + " minor"; + else return base + " major"; +} + |