Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/engine/channels/enginedeck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ EngineDeck::EngineDeck(

m_stemGain.reserve(mixxx::kMaxSupportedStems);
m_stemMute.reserve(mixxx::kMaxSupportedStems);
m_stemVuMeter.reserve(mixxx::kMaxSupportedStems);
for (int stemIdx = 0; stemIdx < mixxx::kMaxSupportedStems; stemIdx++) {
m_stemGain.emplace_back(std::make_unique<ControlPotmeter>(
ConfigKey(getGroupForStem(getGroup(), stemIdx), QStringLiteral("volume"))));
Expand All @@ -80,6 +81,9 @@ EngineDeck::EngineDeck(
ConfigKey(getGroupForStem(getGroup(), stemIdx), QStringLiteral("mute")));
pMuteButton->setButtonMode(mixxx::control::ButtonMode::PowerWindow);
m_stemMute.push_back(std::move(pMuteButton));

m_stemVuMeter.emplace_back(std::make_unique<EngineVuMeter>(
getGroupForStem(getGroup(), stemIdx), QString(), false));
}
#endif
}
Expand Down Expand Up @@ -126,7 +130,8 @@ void EngineDeck::addStemHandle(const ChannelHandleAndGroup& stemHandleGroup) {
void EngineDeck::processStem(CSAMPLE* pOut, const std::size_t bufferSize) {
mixxx::audio::ChannelCount chCount = m_pBuffer->getChannelCount();
VERIFY_OR_DEBUG_ASSERT(m_stems.size() <= chCount &&
m_stemMute.size() <= chCount && m_stemGain.size() <= chCount) {
m_stemMute.size() <= chCount && m_stemGain.size() <= chCount &&
m_stemVuMeter.size() <= chCount) {
return;
};
mixxx::audio::SampleRate sampleRate = mixxx::audio::SampleRate::fromDouble(m_sampleRate.get());
Expand Down Expand Up @@ -184,6 +189,8 @@ void EngineDeck::processStem(CSAMPLE* pOut, const std::size_t bufferSize) {
// gain) gain changes will yield to audio cracks.
m_stemsGainCache[stemIdx] = stemGain;

m_stemVuMeter[stemIdx]->process(pOut, bufferSize);
Comment thread
JoergAtGithub marked this conversation as resolved.

// Put back the stem frames into the steam buffer (LRLR -> LR......LR......)
SampleUtil::insertStereoToMulti(
pIn,
Expand Down Expand Up @@ -299,6 +306,11 @@ EngineChannel::ActiveState EngineDeck::updateActiveState() {
}
if (m_active) {
m_vuMeter.reset();
#ifdef __STEM__
for (auto& stemVuMeter : m_stemVuMeter) {
stemVuMeter->reset();
}
#endif
m_active = false;
return ActiveState::WasActive;
}
Expand Down
1 change: 1 addition & 0 deletions src/engine/channels/enginedeck.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class EngineDeck : public EngineChannel, public AudioDestination {
std::unique_ptr<ControlObject> m_pStemCount;
std::vector<std::unique_ptr<ControlPotmeter>> m_stemGain;
std::vector<std::unique_ptr<ControlPushButton>> m_stemMute;
std::vector<std::unique_ptr<EngineVuMeter>> m_stemVuMeter;
bool m_stemClonedState;
#endif

Expand Down
35 changes: 20 additions & 15 deletions src/engine/enginevumeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace {

// Rate at which the vumeter is updated (using a sample rate of 44100 Hz):
constexpr unsigned int kVuUpdateRate = 30; // in Hz (1/s), fits to display frame rate
constexpr int kPeakDuration = 500; // in ms
constexpr int kPeakDuration = 500; // in ms

// Smoothing Factors
// Must be from 0-1 the lower the factor, the more smoothing that is applied
Expand All @@ -17,21 +17,27 @@ constexpr CSAMPLE kDecaySmoothing = 0.1f; //.16//.4

} // namespace

EngineVuMeter::EngineVuMeter(const QString& group, const QString& legacyGroup)
EngineVuMeter::EngineVuMeter(const QString& group,
const QString& legacyGroup,
bool createLegacyAliases)
: m_vuMeter(ConfigKey(group, QStringLiteral("vu_meter"))),
m_vuMeterLeft(ConfigKey(group, QStringLiteral("vu_meter_left"))),
m_vuMeterRight(ConfigKey(group, QStringLiteral("vu_meter_right"))),
m_peakIndicator(ConfigKey(group, QStringLiteral("peak_indicator"))),
m_peakIndicatorLeft(ConfigKey(group, QStringLiteral("peak_indicator_left"))),
m_peakIndicatorRight(ConfigKey(group, QStringLiteral("peak_indicator_right"))),
m_peakIndicatorLeft(
ConfigKey(group, QStringLiteral("peak_indicator_left"))),
m_peakIndicatorRight(
ConfigKey(group, QStringLiteral("peak_indicator_right"))),
m_sampleRate(QStringLiteral("[App]"), QStringLiteral("samplerate")) {
const QString& aliasGroup = legacyGroup.isEmpty() ? group : legacyGroup;
m_vuMeter.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeter")));
m_vuMeterLeft.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeterL")));
m_vuMeterRight.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeterR")));
m_peakIndicator.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicator")));
m_peakIndicatorLeft.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicatorL")));
m_peakIndicatorRight.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicatorR")));
if (createLegacyAliases) {
const QString& aliasGroup = legacyGroup.isEmpty() ? group : legacyGroup;
m_vuMeter.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeter")));
m_vuMeterLeft.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeterL")));
m_vuMeterRight.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeterR")));
m_peakIndicator.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicator")));
m_peakIndicatorLeft.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicatorL")));
m_peakIndicatorRight.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicatorR")));
}
// Initialize the calculation:
reset();
}
Expand Down Expand Up @@ -105,18 +111,17 @@ void EngineVuMeter::process(CSAMPLE* pIn, const std::size_t bufferSize) {
: 0.0);
}

void EngineVuMeter::doSmooth(CSAMPLE &currentVolume, CSAMPLE newVolume)
{
void EngineVuMeter::doSmooth(CSAMPLE& currentVolume, CSAMPLE newVolume) {
if (currentVolume > newVolume) {
currentVolume -= kDecaySmoothing * (currentVolume - newVolume);
} else {
currentVolume += kAttackSmoothing * (newVolume - currentVolume);
}
if (currentVolume < 0) {
currentVolume=0;
currentVolume = 0;
}
if (currentVolume > 1.0) {
currentVolume=1.0;
currentVolume = 1.0;
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/engine/enginevumeter.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
class EngineVuMeter : public EngineObject {
Q_OBJECT
public:
EngineVuMeter(const QString& group, const QString& legacyGroup = QString());
EngineVuMeter(const QString& group,
const QString& legacyGroup = QString(),
bool createLegacyAliases = true);

virtual void process(CSAMPLE* pInOut, const std::size_t bufferSize);

void reset();

private:
void doSmooth(CSAMPLE &currentVolume, CSAMPLE newVolume);
void doSmooth(CSAMPLE& currentVolume, CSAMPLE newVolume);

ControlObject m_vuMeter;
ControlObject m_vuMeterLeft;
Expand Down
47 changes: 47 additions & 0 deletions src/test/stemcontrolobjecttest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ class StemControlFixture : public BaseSignalPathTest,
m_pStem3FXEnabled->set(0.0);
m_pStem4FXEnabled->set(0.0);

m_pStem1VuMeter = std::make_unique<PollingControlProxy>(
getGroupForStem(m_sGroup1, 1), "vu_meter");
m_pStem2VuMeter = std::make_unique<PollingControlProxy>(
getGroupForStem(m_sGroup1, 2), "vu_meter");
m_pStem3VuMeter = std::make_unique<PollingControlProxy>(
getGroupForStem(m_sGroup1, 3), "vu_meter");
m_pStem4VuMeter = std::make_unique<PollingControlProxy>(
getGroupForStem(m_sGroup1, 4), "vu_meter");

m_pStemCount = std::make_unique<PollingControlProxy>(m_sGroup1, "stem_count");
}

Expand Down Expand Up @@ -151,6 +160,10 @@ class StemControlFixture : public BaseSignalPathTest,
std::unique_ptr<PollingControlProxy> m_pStem2FXEnabled;
std::unique_ptr<PollingControlProxy> m_pStem3FXEnabled;
std::unique_ptr<PollingControlProxy> m_pStem4FXEnabled;
std::unique_ptr<PollingControlProxy> m_pStem1VuMeter;
std::unique_ptr<PollingControlProxy> m_pStem2VuMeter;
std::unique_ptr<PollingControlProxy> m_pStem3VuMeter;
std::unique_ptr<PollingControlProxy> m_pStem4VuMeter;
std::unique_ptr<PollingControlProxy> m_pStemCount;
};

Expand Down Expand Up @@ -329,6 +342,40 @@ TEST_P(StemControlFixture, Mute) {
QStringLiteral("StemMuteControlFull"));
}

TEST_P(StemControlFixture, VuMeter) {
m_pChannel1->getEngineBuffer()->queueNewPlaypos(
mixxx::audio::FramePos{0}, EngineBuffer::SEEK_STANDARD);
m_pPlay->set(1.0);

// Initial check: silence
EXPECT_EQ(m_pStem1VuMeter->get(), 0.0);

// Process buffer to play sound
// Run enough cycles to trigger VU meter update (30Hz update rate vs ~44kHz/buffer)
for (int i = 0; i < 50; ++i) {
m_pEngineMixer->process(kProcessBufferSize);
}

// Check if VU meters picked up the signal
EXPECT_GT(m_pStem1VuMeter->get(), 0.0);
EXPECT_GT(m_pStem2VuMeter->get(), 0.0);

// Mute Stem 1
m_pStem1Mute->set(1.0);

// Process enough buffers to allow VU meter to decay to 0
// Decay is exponential, so it takes time.
for (int i = 0; i < 600; ++i) {
m_pEngineMixer->process(kProcessBufferSize);
}

// VU Meter should be near zero (allow small epsilon for imperfect decay)
EXPECT_NEAR(m_pStem1VuMeter->get(), 0.0, 0.001);

// Stem 2 should still be playing
EXPECT_GT(m_pStem2VuMeter->get(), 0.0);
}

INSTANTIATE_TEST_SUITE_P(
StemControlTest,
StemControlFixture,
Expand Down