Skip to content

Commit

Permalink
Merge pull request #13070 from acolombier/feat/add-engine-support-for…
Browse files Browse the repository at this point in the history
…-stem

Add support for stem in the engine
  • Loading branch information
daschuer authored Jun 25, 2024
2 parents 4c11d9b + 329d89a commit 6b62210
Show file tree
Hide file tree
Showing 67 changed files with 1,176 additions and 410 deletions.
5 changes: 3 additions & 2 deletions src/analyzer/analyzersilence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ SINT AnalyzerSilence::findLastSoundInChunk(std::span<const CSAMPLE> samples) {
// static
bool AnalyzerSilence::verifyFirstSound(
std::span<const CSAMPLE> samples,
mixxx::audio::FramePos firstSoundFrame) {
mixxx::audio::FramePos firstSoundFrame,
mixxx::audio::ChannelCount channelCount) {
const SINT firstSoundSample = findFirstSoundInChunk(samples);
if (firstSoundSample < static_cast<SINT>(samples.size())) {
return mixxx::audio::FramePos::fromEngineSamplePos(firstSoundSample)
return mixxx::audio::FramePos::fromSamplePos(firstSoundSample, channelCount)
.toLowerFrameBoundary() == firstSoundFrame.toLowerFrameBoundary();
}
return false;
Expand Down
3 changes: 2 additions & 1 deletion src/analyzer/analyzersilence.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class AnalyzerSilence : public Analyzer {
/// last analysis run and is an indicator for file edits or decoder
/// changes/issues
static bool verifyFirstSound(std::span<const CSAMPLE> samples,
mixxx::audio::FramePos firstSoundFrame);
mixxx::audio::FramePos firstSoundFrame,
mixxx::audio::ChannelCount channelCount);

private:
UserSettingsPointer m_pConfig;
Expand Down
2 changes: 1 addition & 1 deletion src/analyzer/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace mixxx {
// depending on the track length. A block size of 4096 frames per block
// seems to do fine. Signal processing during analysis uses the same,
// fixed number of channels like the engine does, usually 2 = stereo.
constexpr audio::ChannelCount kAnalysisChannels = mixxx::kEngineChannelCount;
constexpr audio::ChannelCount kAnalysisChannels = mixxx::kEngineChannelOutputCount;
constexpr SINT kAnalysisFramesPerChunk = 4096;
constexpr SINT kAnalysisSamplesPerChunk =
kAnalysisFramesPerChunk * kAnalysisChannels;
Expand Down
33 changes: 31 additions & 2 deletions src/audio/frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,25 @@ class FramePos final {
/// "invalid" positions (e.g. when parsing values from control objects),
/// use `FramePos::fromEngineSamplePosMaybeInvalid` instead.
static constexpr FramePos fromEngineSamplePos(double engineSamplePos) {
return FramePos(engineSamplePos / mixxx::kEngineChannelCount);
return FramePos(engineSamplePos / mixxx::kEngineChannelOutputCount);
}

static constexpr FramePos fromSamplePos(double samplePos,
mixxx::audio::ChannelCount channelCount) {
return FramePos(static_cast<double>(samplePos) / channelCount);
}

static constexpr FramePos fromSamplePos(double samplePos,
const mixxx::audio::SignalInfo& signalInfo) {
return FramePos(static_cast<double>(samplePos) / signalInfo.getChannelCount());
}

/// Return an engine sample position. The `FramePos` is expected to be
/// valid. If invalid positions are possible (e.g. for control object
/// values), use `FramePos::toEngineSamplePosMaybeInvalid` instead.
double toEngineSamplePos() const {
DEBUG_ASSERT(isValid());
double engineSamplePos = value() * mixxx::kEngineChannelCount;
double engineSamplePos = value() * mixxx::kEngineChannelOutputCount;
// In the rare but possible instance that the position is valid but
// the engine sample position is exactly -1.0, we nudge the position
// because otherwise fromEngineSamplePosMaybeInvalid() will think
Expand All @@ -55,6 +65,10 @@ class FramePos final {
return engineSamplePos;
}

double toSamplePos(mixxx::audio::ChannelCount channelCount) const {
DEBUG_ASSERT(isValid());
return value() * channelCount;
}
/// Return a `FramePos` from a given engine sample position. Sample
/// positions that equal `kLegacyInvalidEnginePosition` are considered
/// invalid and result in an invalid `FramePos` instead.
Expand All @@ -70,6 +84,14 @@ class FramePos final {
return fromEngineSamplePos(engineSamplePos);
}

static constexpr FramePos fromSamplePosMaybeInvalid(
double samplePos, mixxx::audio::ChannelCount channelCount) {
if (samplePos == kLegacyInvalidEnginePosition) {
return {};
}
return fromSamplePos(samplePos, channelCount);
}

/// Return an engine sample position. If the `FramePos` is invalid,
/// `kLegacyInvalidEnginePosition` is returned instead.
///
Expand All @@ -84,6 +106,13 @@ class FramePos final {
return toEngineSamplePos();
}

double toSamplePosMaybeInvalid(mixxx::audio::ChannelCount channelCount) const {
if (!isValid()) {
return kLegacyInvalidEnginePosition;
}
return toSamplePos(channelCount);
}

/// Return true if the frame position is valid. Any finite value is
/// considered valid, i.e. any value except NaN and negative/positive
/// infinity.
Expand Down
17 changes: 17 additions & 0 deletions src/audio/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ class ChannelCount {
return ChannelCount(valueFromInt(value));
}

static ChannelCount fromDouble(double value) {
const auto channelCount = ChannelCount(static_cast<value_t>(value));
// The channel count should always be an integer value
// and this conversion is supposed to be lossless.
DEBUG_ASSERT(channelCount.toDouble() == value);
return channelCount;
}

static constexpr ChannelCount mono() {
return ChannelCount(static_cast<value_t>(1));
}
Expand All @@ -88,6 +96,10 @@ class ChannelCount {
return ChannelCount(static_cast<value_t>(2));
}

static constexpr ChannelCount stem() {
return ChannelCount(static_cast<value_t>(8)); // 4 stereo channels
}

explicit constexpr ChannelCount(
value_t value = kValueDefault)
: m_value(value) {
Expand Down Expand Up @@ -115,6 +127,11 @@ class ChannelCount {
return value();
}

// Helper cast for COs
constexpr double toDouble() const {
return static_cast<double>(value());
}

private:
value_t m_value;
};
Expand Down
29 changes: 20 additions & 9 deletions src/engine/bufferscalers/enginebufferscale.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,37 @@

#include "engine/engine.h"
#include "moc_enginebufferscale.cpp"
#include "soundio/soundmanagerconfig.h"

EngineBufferScale::EngineBufferScale()
: m_outputSignal(
: m_signal(
mixxx::audio::SignalInfo(
mixxx::kEngineChannelCount,
mixxx::kEngineChannelOutputCount,
mixxx::audio::SampleRate())),
m_dBaseRate(1.0),
m_bSpeedAffectsPitch(false),
m_dTempoRatio(1.0),
m_dPitchRatio(1.0),
m_effectiveRate(1.0) {
DEBUG_ASSERT(!m_outputSignal.isValid());
DEBUG_ASSERT(!m_signal.isValid());
}

void EngineBufferScale::setSampleRate(
mixxx::audio::SampleRate sampleRate) {
void EngineBufferScale::setSignal(
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCount) {
DEBUG_ASSERT(sampleRate.isValid());
if (sampleRate != m_outputSignal.getSampleRate()) {
m_outputSignal.setSampleRate(sampleRate);
onSampleRateChanged();
DEBUG_ASSERT(channelCount.isValid());
bool changed = false;
if (sampleRate != m_signal.getSampleRate()) {
m_signal.setSampleRate(sampleRate);
changed = true;
}
DEBUG_ASSERT(m_outputSignal.isValid());
if (channelCount != m_signal.getChannelCount()) {
m_signal.setChannelCount(channelCount);
changed = true;
}
if (changed) {
onSignalChanged();
}
DEBUG_ASSERT(m_signal.isValid());
}
13 changes: 7 additions & 6 deletions src/engine/bufferscalers/enginebufferscale.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ class EngineBufferScale : public QObject {
m_dPitchRatio = *pPitchRatio;
}

// Set the desired output sample rate.
void setSampleRate(
mixxx::audio::SampleRate sampleRate);
// Set the desired output signal.
void setSignal(
mixxx::audio::SampleRate sampleRate,
mixxx::audio::ChannelCount channelCout);

const mixxx::audio::SignalInfo& getOutputSignal() const {
return m_outputSignal;
return m_signal;
}

// Called from EngineBuffer when seeking, to ensure the buffers are flushed */
Expand All @@ -64,9 +65,9 @@ class EngineBufferScale : public QObject {
SINT iOutputBufferSize) = 0;

private:
mixxx::audio::SignalInfo m_outputSignal;
mixxx::audio::SignalInfo m_signal;

virtual void onSampleRateChanged() = 0;
virtual void onSignalChanged() = 0;

protected:
double m_dBaseRate;
Expand Down
Loading

0 comments on commit 6b62210

Please sign in to comment.