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
1 change: 1 addition & 0 deletions app/src/main/assets/autoeq_targets.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ add_library(
DirectOutputBridge.cpp
DirectOutputStream.cpp
DspProcessor.cpp
ParametricEq.cpp
ParametricEqBridge.cpp
NativeBridge.cpp
SecretsBridge.cpp
)
Expand Down
22 changes: 20 additions & 2 deletions app/src/main/cpp/DspProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ void DspProcessor::configure(double sampleRate) noexcept {
appliedEqualizerRevision_ = targetEqualizerRevision_.load(std::memory_order_acquire);
limiterGain_ = 1.0F;
clarityChainActive_ = currentWet_ > 0.0F;
parametricEq_.configure(sampleRate_, 2);
reset();
}

Expand All @@ -209,6 +210,7 @@ void DspProcessor::reset() noexcept {
airExciterFilter_.clear();
crossfeed_.clear();
for (auto& band : equalizerBands_) band.clear();
parametricEq_.reset();
limiterGain_ = 1.0F;
microFadePosition_ = 0;
dcXPrev_.fill(0.0);
Expand Down Expand Up @@ -350,7 +352,20 @@ void DspProcessor::process(
std::int32_t frameCount,
std::int32_t channelCount) noexcept {
if (samples == nullptr || frameCount <= 0 || (channelCount != 1 && channelCount != 2)) return;
if (bitPerfectEnabled_.load(std::memory_order_acquire)) return;
const bool parametricApplied = parametricEq_.process(samples, frameCount, channelCount);
const auto clampParametricOutput = [&]() noexcept {
if (!parametricApplied) return;
const std::size_t count = static_cast<std::size_t>(frameCount) *
static_cast<std::size_t>(channelCount);
for (std::size_t i = 0; i < count; ++i) {
const float value = samples[i];
samples[i] = std::isfinite(value) ? std::clamp(value, -1.0F, 1.0F) : 0.0F;
}
};
if (bitPerfectEnabled_.load(std::memory_order_acquire)) {
clampParametricOutput();
return;
}
// Atmos-aware bypass forces the clarity chain off independent of the
// on/off toggle, keeping multichannel/spatial content untouched while
// preserving the toggle state for stereo afterwards.
Expand Down Expand Up @@ -395,7 +410,10 @@ void DspProcessor::process(
std::abs(limiterGain_ - 1.0F) < 0.00001F;
// With every enhancement disabled, decoded PCM stays transparent. This
// avoids the old unconditional -1 dB attenuation and limiter/clamp pass.
if (controlsBypassed && stateBypassed) return;
if (controlsBypassed && stateBypassed) {
clampParametricOutput();
return;
}

for (std::int32_t frame = 0; frame < frameCount; ++frame) {
if (currentWet_ < target) {
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/cpp/DspProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <cstddef>
#include <cstdint>

#include "ParametricEq.h"

namespace lastwave::audio {

class DspProcessor final {
Expand Down Expand Up @@ -212,6 +214,10 @@ class DspProcessor final {
Biquad monoBassFilter_{};
Biquad airExciterFilter_{};
Crossfeed crossfeed_{};
// AutoEQ / manual parametric EQ. Runs first in the chain and, unlike
// the rest of the DSP, also while Bit-Perfect is on: the user asked for
// the correction, so it is applied and the verdict reports "Direct + EQ".
ParametricEq parametricEq_{};

// Rebuilds one trimmed stage from its design spec plus the smoothed
// trim value. Peaking/shelf stages keep filter state (coefficients
Expand Down
Loading