Something went wrong. Try again.
Mixxx DJ software with MCP, Subsonic/Navidrome streaming library support — browse and load tracks from your self-hosted music server mixxx.org
subsonic navidrome streaming audio dj mac linux midi
Something went wrong. Try again.
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243#include "analyzer/analyzerkey.h"
#include <QtDebug>
#include "analyzer/analyzertrack.h"#include "analyzer/constants.h"#if defined __KEYFINDER__#include "analyzer/plugins/analyzerkeyfinder.h"#endif#include "analyzer/plugins/analyzerqueenmarykey.h"#include "proto/keys.pb.h"#include "track/keyfactory.h"#include "track/track.h"
namespace {constexpr int excludeFirstChannelMask = 0x1;} // namespace
// staticQList<mixxx::AnalyzerPluginInfo> AnalyzerKey::availablePlugins() { QList<mixxx::AnalyzerPluginInfo> analyzers; // First one below is the default analyzers.push_back(mixxx::AnalyzerQueenMaryKey::pluginInfo());#if defined __KEYFINDER__ analyzers.push_back(mixxx::AnalyzerKeyFinder::pluginInfo());#endif return analyzers;}
// staticmixxx::AnalyzerPluginInfo AnalyzerKey::defaultPlugin() { const auto plugins = availablePlugins(); DEBUG_ASSERT(!plugins.isEmpty()); return plugins.at(0);}
AnalyzerKey::AnalyzerKey(const KeyDetectionSettings& keySettings) : m_keySettings(keySettings), m_sampleRate(0), m_totalFrames(0), m_maxFramesToProcess(0), m_currentFrame(0), m_bPreferencesKeyDetectionEnabled(true), m_bPreferencesFastAnalysisEnabled(false), m_bPreferencesReanalyzeEnabled(false) {}
bool AnalyzerKey::initialize(const AnalyzerTrack& track, mixxx::audio::SampleRate sampleRate, mixxx::audio::ChannelCount channelCount, SINT frameLength) { if (frameLength <= 0) { return false; }
m_bPreferencesKeyDetectionEnabled = m_keySettings.getKeyDetectionEnabled(); if (!m_bPreferencesKeyDetectionEnabled) { qDebug() << "Key detection is deactivated"; return false; }
m_bPreferencesFastAnalysisEnabled = m_keySettings.getFastAnalysis(); m_bPreferencesReanalyzeEnabled = m_keySettings.getReanalyzeWhenSettingsChange();
const auto plugins = availablePlugins(); if (!plugins.isEmpty()) { m_pluginId = defaultPlugin().id(); QString pluginId = m_keySettings.getKeyPluginId(); for (const auto& info : plugins) { if (info.id() == pluginId) { m_pluginId = pluginId; // configured Plug-In available break; } } }
qDebug() << "AnalyzerKey preference settings:" << "\nPlugin:" << m_pluginId << "\nRe-analyze when settings change:" << m_bPreferencesReanalyzeEnabled << "\nFast analysis:" << m_bPreferencesFastAnalysisEnabled;
m_sampleRate = sampleRate; m_channelCount = channelCount; m_totalFrames = frameLength; // In fast analysis mode, skip processing after // kFastAnalysisSecondsToAnalyze seconds are analyzed. if (m_bPreferencesFastAnalysisEnabled) { m_maxFramesToProcess = mixxx::kFastAnalysisSecondsToAnalyze * m_sampleRate; } else { m_maxFramesToProcess = frameLength; } m_currentFrame = 0;
// if we can't load a stored track reanalyze it bool bShouldAnalyze = shouldAnalyze(track.getTrack());
DEBUG_ASSERT(!m_pPlugin); if (bShouldAnalyze) { if (m_pluginId == mixxx::AnalyzerQueenMaryKey::pluginInfo().id()) { m_pPlugin = std::make_unique<mixxx::AnalyzerQueenMaryKey>();#if defined __KEYFINDER__ } else if (m_pluginId == mixxx::AnalyzerKeyFinder::pluginInfo().id()) { m_pPlugin = std::make_unique<mixxx::AnalyzerKeyFinder>();#endif } else { // This must not happen, because we have already verified above // that the PlugInId is valid DEBUG_ASSERT(false); }
if (m_pPlugin) { if (m_pPlugin->initialize(mixxx::audio::SampleRate(m_sampleRate))) { qDebug() << "Key calculation started with plugin" << m_pluginId; } else { qDebug() << "Key calculation will not start."; m_pPlugin.reset(); bShouldAnalyze = false; } } else { bShouldAnalyze = false; } } return bShouldAnalyze;}
bool AnalyzerKey::shouldAnalyze(TrackPointer pTrack) const { bool bPreferencesFastAnalysisEnabled = m_keySettings.getFastAnalysis(); QString pluginID = m_keySettings.getKeyPluginId(); if (pluginID.isEmpty()) { pluginID = defaultPlugin().id(); }
const Keys keys = pTrack->getKeys(); if (keys.getGlobalKey() != mixxx::track::io::key::INVALID) { QString version = keys.getVersion(); QString subVersion = keys.getSubVersion();
QHash<QString, QString> extraVersionInfo = getExtraVersionInfo( pluginID, bPreferencesFastAnalysisEnabled); QString newVersion = KeyFactory::getPreferredVersion(); QString newSubVersion = KeyFactory::getPreferredSubVersion(extraVersionInfo);
if (version == newVersion && subVersion == newSubVersion) { // If the version and settings have not changed then if the world is // sane, re-analyzing will do nothing. qDebug() << "Keys version/sub-version unchanged since previous analysis. Not analyzing."; return false; } if (!m_bPreferencesReanalyzeEnabled) { qDebug() << "Track has previous key detection result that is not up" << "to date with latest settings but user preferences" << "indicate we should not re-analyze it."; return false; } } return true;}
bool AnalyzerKey::processSamples(const CSAMPLE* pIn, SINT count) { VERIFY_OR_DEBUG_ASSERT(m_pPlugin) { return false; }
SINT numFrames = count / m_channelCount; m_currentFrame += numFrames;
if (m_currentFrame > m_maxFramesToProcess) { return true; // silently ignore remaining samples }
const CSAMPLE* pKeyInput = pIn; CSAMPLE* pHarmonicMixedChannel = nullptr;
if (m_channelCount == mixxx::audio::ChannelCount::stem()) { // We have an 8 channel soundsource. The only implemented soundsource with // 8ch is the NI STEM file format. // TODO: If we add other soundsources with 8ch, we need to rework this condition. // // For NI STEM we mix all the stems together except the first one, // which contains drums or beats by convention. count = numFrames * mixxx::audio::ChannelCount::stereo(); pHarmonicMixedChannel = SampleUtil::alloc(count); VERIFY_OR_DEBUG_ASSERT(pHarmonicMixedChannel) { return false; }
if (m_keySettings.getStemStrategy() == KeyDetectionSettings::StemStrategy::Enforced) { SampleUtil::mixMultichannelToStereo(pHarmonicMixedChannel, pIn, numFrames, m_channelCount, excludeFirstChannelMask); } else { SampleUtil::mixMultichannelToStereo( pHarmonicMixedChannel, pIn, numFrames, m_channelCount); }
pKeyInput = pHarmonicMixedChannel; } else if (m_channelCount > mixxx::audio::ChannelCount::stereo()) { DEBUG_ASSERT(!"Unsupported channel count"); return false; }
bool ret = m_pPlugin->processSamples(pKeyInput, count); if (pHarmonicMixedChannel) { SampleUtil::free(pHarmonicMixedChannel); } return ret;}
void AnalyzerKey::cleanup() { m_pPlugin.reset();}
void AnalyzerKey::storeResults(TrackPointer tio) { VERIFY_OR_DEBUG_ASSERT(m_pPlugin) { return; }
if (!m_pPlugin->finalize()) { qWarning() << "Key detection failed"; return; }
KeyChangeList key_changes = m_pPlugin->getKeyChanges(); QHash<QString, QString> extraVersionInfo = getExtraVersionInfo( m_pluginId, m_bPreferencesFastAnalysisEnabled); Keys track_keys = KeyFactory::makePreferredKeys( key_changes, extraVersionInfo, m_sampleRate, m_totalFrames); tio->setKeys(track_keys);}
// staticQHash<QString, QString> AnalyzerKey::getExtraVersionInfo( const QString& pluginId, bool bPreferencesFastAnalysis) { QHash<QString, QString> extraVersionInfo; extraVersionInfo["vamp_plugin_id"] = pluginId; if (bPreferencesFastAnalysis) { extraVersionInfo["fast_analysis"] = "1"; } return extraVersionInfo;}