Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add analyser support for stem #13106

Merged
4 changes: 3 additions & 1 deletion src/analyzer/analyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class Analyzer {
// 3. If the initialization failed log the internal error and return false.
virtual bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) = 0;

// Analyze the next chunk of audio samples and return true if successful.
Expand Down Expand Up @@ -68,9 +69,10 @@ class AnalyzerWithState final {

bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) {
DEBUG_ASSERT(!m_active);
return m_active = m_analyzer->initialize(track, sampleRate, frameLength);
return m_active = m_analyzer->initialize(track, sampleRate, channelCount, frameLength);
}

void processSamples(const CSAMPLE* pIn, const int count) {
Expand Down
40 changes: 38 additions & 2 deletions src/analyzer/analyzerbeats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ AnalyzerBeats::AnalyzerBeats(UserSettingsPointer pConfig, bool enforceBpmDetecti

bool AnalyzerBeats::initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) {
if (frameLength <= 0) {
return false;
Expand Down Expand Up @@ -86,6 +87,7 @@ bool AnalyzerBeats::initialize(const AnalyzerTrack& track,
<< "\nFast analysis:" << m_bPreferencesFastAnalysis;

m_sampleRate = sampleRate;
m_channelCount = channelCount;
// In fast analysis mode, skip processing after
// kFastAnalysisSecondsToAnalyze seconds are analyzed.
if (m_bPreferencesFastAnalysis) {
Expand Down Expand Up @@ -199,12 +201,46 @@ bool AnalyzerBeats::processSamples(const CSAMPLE* pIn, SINT count) {
return false;
}

m_currentFrame += count / mixxx::kAnalysisChannels;
SINT numFrames = count / m_channelCount;
const CSAMPLE* pBeatInput = pIn;
CSAMPLE* pDrumChannel = 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();
pDrumChannel = SampleUtil::alloc(count);

VERIFY_OR_DEBUG_ASSERT(pDrumChannel) {
return false;
}

if (m_bpmSettings.getStemStrategy() == BeatDetectionSettings::StemStrategy::Enforced) {
SampleUtil::copyOneStereoFromMulti(pDrumChannel, pIn, numFrames, m_channelCount, 0);
} else {
SampleUtil::mixMultichannelToStereo(pDrumChannel, pIn, numFrames, m_channelCount);
}

pBeatInput = pDrumChannel;
} else if (m_channelCount > mixxx::audio::ChannelCount::stereo()) {
DEBUG_ASSERT(!"Unsupported channel count");
return false;
}

m_currentFrame += numFrames;
if (m_currentFrame > m_maxFramesToProcess) {
return true; // silently ignore all remaining samples
}

return m_pPlugin->processSamples(pIn, count);
bool ret = m_pPlugin->processSamples(pBeatInput, count);
if (pDrumChannel) {
SampleUtil::free(pDrumChannel);
}
return ret;
}

void AnalyzerBeats::cleanup() {
Expand Down
2 changes: 2 additions & 0 deletions src/analyzer/analyzerbeats.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AnalyzerBeats : public Analyzer {

bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) override;
bool processSamples(const CSAMPLE* pIn, SINT count) override;
void storeResults(TrackPointer tio) override;
Expand All @@ -41,6 +42,7 @@ class AnalyzerBeats : public Analyzer {
bool m_bPreferencesFastAnalysis;

mixxx::audio::SampleRate m_sampleRate;
mixxx::audio::ChannelCount m_channelCount;
SINT m_maxFramesToProcess;
SINT m_currentFrame;
};
5 changes: 3 additions & 2 deletions src/analyzer/analyzerebur128.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ AnalyzerEbur128::~AnalyzerEbur128() {
bool AnalyzerEbur128::initialize(
const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) {
if (m_rgSettings.isAnalyzerDisabled(2, track.getTrack()) || frameLength <= 0) {
qDebug() << "Skipping AnalyzerEbur128";
return false;
}
DEBUG_ASSERT(m_pState == nullptr);
m_pState = ebur128_init(
mixxx::kAnalysisChannels,
channelCount,
sampleRate,
EBUR128_MODE_I);
return m_pState != nullptr;
Expand All @@ -50,7 +51,7 @@ bool AnalyzerEbur128::processSamples(const CSAMPLE* pIn, SINT count) {
return false;
}
ScopedTimer t(QStringLiteral("AnalyzerEbur128::processSamples()"));
size_t frames = count / mixxx::kAnalysisChannels;
size_t frames = count / m_pState->channels;
int e = ebur128_add_frames_float(m_pState, pIn, frames);
VERIFY_OR_DEBUG_ASSERT(e == EBUR128_SUCCESS) {
qWarning() << "AnalyzerEbur128::processSamples() failed with" << e;
Expand Down
1 change: 1 addition & 0 deletions src/analyzer/analyzerebur128.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class AnalyzerEbur128 : public Analyzer {

bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) override;
bool processSamples(const CSAMPLE* pIn, SINT count) override;
void storeResults(TrackPointer pTrack) override;
Expand Down
36 changes: 33 additions & 3 deletions src/analyzer/analyzergain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ AnalyzerGain::~AnalyzerGain() {

bool AnalyzerGain::initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) {
if (m_rgSettings.isAnalyzerDisabled(1, track.getTrack()) || frameLength <= 0) {
qDebug() << "Skipping AnalyzerGain";
return false;
}
m_channelCount = channelCount;

return m_pReplayGain->initialise(
sampleRate,
Expand All @@ -39,18 +41,46 @@ void AnalyzerGain::cleanup() {
bool AnalyzerGain::processSamples(const CSAMPLE* pIn, SINT count) {
ScopedTimer t(QStringLiteral("AnalyzerGain::process()"));

SINT numFrames = count / mixxx::kAnalysisChannels;
SINT numFrames = count / m_channelCount;

const CSAMPLE* pGainInput = pIn;
CSAMPLE* pMixedChannel = 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();
pMixedChannel = SampleUtil::alloc(count);
VERIFY_OR_DEBUG_ASSERT(pMixedChannel) {
return false;
}
SampleUtil::mixMultichannelToStereo(pMixedChannel, pIn, numFrames, m_channelCount);
pGainInput = pMixedChannel;
} else if (m_channelCount > mixxx::audio::ChannelCount::stereo()) {
DEBUG_ASSERT(!"Unsupported channel count");
return false;
}

if (numFrames > static_cast<SINT>(m_pLeftTempBuffer.size())) {
m_pLeftTempBuffer.resize(numFrames);
m_pRightTempBuffer.resize(numFrames);
}
SampleUtil::deinterleaveBuffer(m_pLeftTempBuffer.data(),
m_pRightTempBuffer.data(),
pIn,
pGainInput,
numFrames);
SampleUtil::applyGain(m_pLeftTempBuffer.data(), 32767, numFrames);
SampleUtil::applyGain(m_pRightTempBuffer.data(), 32767, numFrames);
return m_pReplayGain->process(m_pLeftTempBuffer.data(), m_pRightTempBuffer.data(), numFrames);
bool ret = m_pReplayGain->process(
m_pLeftTempBuffer.data(), m_pRightTempBuffer.data(), numFrames);
if (pMixedChannel) {
SampleUtil::free(pMixedChannel);
}
return ret;
}

void AnalyzerGain::storeResults(TrackPointer pTrack) {
Expand Down
2 changes: 2 additions & 0 deletions src/analyzer/analyzergain.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class AnalyzerGain : public Analyzer {

bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) override;
bool processSamples(const CSAMPLE* pIn, SINT count) override;
void storeResults(TrackPointer tio) override;
Expand All @@ -34,5 +35,6 @@ class AnalyzerGain : public Analyzer {
ReplayGainSettings m_rgSettings;
std::vector<CSAMPLE> m_pLeftTempBuffer;
std::vector<CSAMPLE> m_pRightTempBuffer;
mixxx::audio::ChannelCount m_channelCount;
ReplayGain* m_pReplayGain;
};
49 changes: 47 additions & 2 deletions src/analyzer/analyzerkey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#include "track/keyfactory.h"
#include "track/track.h"

namespace {
constexpr int excludeFirstChannelMask = 0x1;
}

Check warning on line 17 in src/analyzer/analyzerkey.cpp

View workflow job for this annotation

GitHub Actions / clang-tidy

anonymous namespace not terminated with a closing comment [google-readability-namespace-comments]

// static
QList<mixxx::AnalyzerPluginInfo> AnalyzerKey::availablePlugins() {
QList<mixxx::AnalyzerPluginInfo> analyzers;
Expand Down Expand Up @@ -43,6 +47,7 @@

bool AnalyzerKey::initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) {
if (frameLength <= 0) {
return false;
Expand Down Expand Up @@ -75,6 +80,7 @@
<< "\nFast analysis:" << m_bPreferencesFastAnalysisEnabled;

m_sampleRate = sampleRate;
m_channelCount = channelCount;
m_totalFrames = frameLength;
// In fast analysis mode, skip processing after
// kFastAnalysisSecondsToAnalyze seconds are analyzed.
Expand Down Expand Up @@ -155,12 +161,51 @@
return false;
}

m_currentFrame += count / mixxx::kAnalysisChannels;
SINT numFrames = count / m_channelCount;
m_currentFrame += numFrames;

if (m_currentFrame > m_maxFramesToProcess) {
return true; // silently ignore remaining samples
}

return m_pPlugin->processSamples(pIn, count);
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() {
Expand Down
2 changes: 2 additions & 0 deletions src/analyzer/analyzerkey.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class AnalyzerKey : public Analyzer {

bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) override;
bool processSamples(const CSAMPLE* pIn, SINT count) override;
void storeResults(TrackPointer tio) override;
Expand All @@ -35,6 +36,7 @@ class AnalyzerKey : public Analyzer {
std::unique_ptr<mixxx::AnalyzerKeyPlugin> m_pPlugin;
QString m_pluginId;
mixxx::audio::SampleRate m_sampleRate;
mixxx::audio::ChannelCount m_channelCount;
SINT m_totalFrames;
SINT m_maxFramesToProcess;
SINT m_currentFrame;
Expand Down
10 changes: 7 additions & 3 deletions src/analyzer/analyzersilence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ AnalyzerSilence::AnalyzerSilence(UserSettingsPointer pConfig)

bool AnalyzerSilence::initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) {
Q_UNUSED(sampleRate);
Q_UNUSED(frameLength);
Expand All @@ -52,6 +53,7 @@ bool AnalyzerSilence::initialize(const AnalyzerTrack& track,
m_framesProcessed = 0;
m_signalStart = -1;
m_signalEnd = -1;
m_channelCount = channelCount;

return true;
}
Expand Down Expand Up @@ -85,21 +87,23 @@ bool AnalyzerSilence::verifyFirstSound(
}

bool AnalyzerSilence::processSamples(const CSAMPLE* pIn, SINT count) {
SINT numFrames = count / m_channelCount;

std::span<const CSAMPLE> samples = mixxx::spanutil::spanFromPtrLen(pIn, count);
if (m_signalStart < 0) {
const SINT firstSoundSample = findFirstSoundInChunk(samples);
if (firstSoundSample < count) {
m_signalStart = m_framesProcessed + firstSoundSample / mixxx::kAnalysisChannels;
m_signalStart = m_framesProcessed + firstSoundSample / m_channelCount;
}
}
if (m_signalStart >= 0) {
const SINT lastSoundSample = findLastSoundInChunk(samples);
if (lastSoundSample < count - 1) { // not only sound or silence
m_signalEnd = m_framesProcessed + lastSoundSample / mixxx::kAnalysisChannels + 1;
m_signalEnd = m_framesProcessed + lastSoundSample / m_channelCount + 1;
}
}

m_framesProcessed += count / mixxx::kAnalysisChannels;
m_framesProcessed += numFrames;
return true;
}

Expand Down
2 changes: 2 additions & 0 deletions src/analyzer/analyzersilence.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class AnalyzerSilence : public Analyzer {

bool initialize(const AnalyzerTrack& track,
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount,
SINT frameLength) override;
bool processSamples(const CSAMPLE* pIn, SINT count) override;
void storeResults(TrackPointer pTrack) override;
Expand Down Expand Up @@ -51,6 +52,7 @@ class AnalyzerSilence : public Analyzer {

private:
UserSettingsPointer m_pConfig;
mixxx::audio::ChannelCount m_channelCount;
SINT m_framesProcessed;
SINT m_signalStart;
SINT m_signalEnd;
Expand Down
Loading
Loading