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