DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
StereoColor.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
13#include "../FIRFilter.h"
14#include "ContinuousClip.h"
15#include "ContinuousStereoColor.h"
16#include "ConvolutionRate.h"
17#include "RetimedDcBlock.h"
18#include <array>
19#include <memory>
20
21namespace dspark::detail
22{
23template <ClipperCurve C> class OversampledStereoColor final
24{
26 ConvolutionRate rate_;
30 std::array<double, 64> dry_{}, pad_{};
31 int delay_, padding_, latency_, dryWrite_ = 0, padWrite_ = 0;
32
33 public:
34 [[nodiscard]] static int latencyFor(int factor, int sourceFactor) noexcept
35 {
36 const int delay =
37 7 + (factor <= 2 ? 18 : 10) + RetimedDcBlock::filterLength(factor, sourceFactor) / 2;
38 return 256 + 2 * 64 + (delay + factor - 1) / factor;
39 }
40 [[nodiscard]] static std::size_t allocationBound(int factor, int sourceFactor) noexcept
41 {
42 const std::size_t order = factor <= 2 ? 18 : 10, taps = 2 * order + 1;
43 const auto construction = (6 * (order + 1) + (order + 2) * taps) * sizeof(double);
44 const auto filter = taps * (sizeof(std::atomic<double>) + 4 * sizeof(double)) + sizeof(int);
45 return ConvolutionRate::allocationBound(factor, {256, 64, 18.5, .475}, false) +
46 RetimedDcBlock::allocationBound(factor, sourceFactor) + construction + filter +
47 (order + 13) * 64;
48 }
49 OversampledStereoColor(int factor, int sourceFactor)
50 : rate_(factor, {256, 64, 18.5, .475}, false), dc_(factor, sourceFactor),
51 delay_(7 + (factor <= 2 ? 18 : 10)),
52 padding_((factor - (delay_ + dc_.latency()) % factor) % factor),
53 latency_(rate_.latency() + (delay_ + dc_.latency() + padding_) / factor)
54 {
55 assert(factor >= 2);
56 const auto coefficients = continuous_clip::inverseBoxPower(factor <= 2 ? 18 : 10);
57 eq_.prepare(static_cast<int>(coefficients.size()), 1);
58 eq_.setCoefficients(coefficients);
59 residual_.reset(.08);
60 }
61
62 [[nodiscard]] int latency() const noexcept
63 {
64 return latency_;
65 }
66
67 void reset() noexcept
68 {
69 rate_.reset();
70 residual_.reset(.08);
71 eq_.reset();
72 dc_.reset();
73 dry_.fill(0);
74 pad_.fill(0);
75 dryWrite_ = padWrite_ = 0;
76 }
77
78 void process(AudioBufferView<double> input) noexcept
79 {
80 auto high = rate_.upsample(input);
81 auto *values = high.getChannel(0);
82 for (int i = 0; i < high.getNumSamples(); ++i)
83 {
84 const double sample = values[i];
85 double result = eq_.processSample(residual_.process(sample), 0);
86 result += dry_[dryWrite_];
87 dry_[dryWrite_] = sample;
88 dryWrite_ = (dryWrite_ + 1) % delay_;
89 result = dc_.process(result);
90 if (padding_)
91 {
92 const double old = pad_[padWrite_];
93 pad_[padWrite_] = result;
94 padWrite_ = (padWrite_ + 1) % padding_;
95 result = old;
96 }
97 values[i] = result;
98 }
99 rate_.downsample(input);
100 }
101};
102// Select the complete source-rate algorithm at 1x; retain the established
103// explicit 2x/4x/8x/16x paths, including their latency and state arithmetic.
104template <ClipperCurve C> class StereoColor final
105{
106 std::unique_ptr<ContinuousStereoColor<C>> continuous_;
107 std::unique_ptr<OversampledStereoColor<C>> oversampled_;
108
109 public:
110 [[nodiscard]] static int latencyFor(int factor, int sourceFactor) noexcept
111 {
112 return factor == 1 ? ContinuousStereoColor<C>::latency()
113 : OversampledStereoColor<C>::latencyFor(factor, sourceFactor);
114 }
115 [[nodiscard]] static std::size_t allocationBound(int factor, int sourceFactor) noexcept
116 {
118 : sizeof(OversampledStereoColor<C>) +
119 OversampledStereoColor<C>::allocationBound(factor, sourceFactor);
120 }
121 StereoColor(int factor, int sourceFactor)
122 {
123 if (factor == 1)
124 continuous_ = std::make_unique<ContinuousStereoColor<C>>(sourceFactor);
125 else
126 oversampled_ = std::make_unique<OversampledStereoColor<C>>(factor, sourceFactor);
127 }
128 [[nodiscard]] int latency() const noexcept
129 {
130 return continuous_ ? continuous_->latency() : oversampled_->latency();
131 }
132 void reset() noexcept
133 {
134 if (continuous_) continuous_->reset();
135 else oversampled_->reset();
136 }
137 void process(AudioBufferView<double> input) noexcept
138 {
139 if (continuous_) continuous_->process(input);
140 else oversampled_->process(input);
141 }
142};
143} // namespace dspark::detail
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
FIR filter using direct-form convolution with a mirrored delay line.
Definition FIRFilter.h:364
void reset() noexcept
Resets all delay lines to zero, clearing the filter's memory.
Definition FIRFilter.h:452
void prepare(int maxTaps, int numChannels)
Pre-allocates memory and initializes the delay lines.
Definition FIRFilter.h:376
T processSample(T input, int channel) noexcept
Processes a single sample through the FIR filter.
Definition FIRFilter.h:518
void setCoefficients(std::span< const T > coeffs) noexcept
Sets the filter coefficients asynchronously.
Definition FIRFilter.h:418
static constexpr int latency() noexcept
static std::size_t allocationBound(int factor, Settings settings, bool withReference) noexcept
OversampledStereoColor(int factor, int sourceFactor)
Definition StereoColor.h:49
static int latencyFor(int factor, int sourceFactor) noexcept
Definition StereoColor.h:34
static std::size_t allocationBound(int factor, int sourceFactor) noexcept
Definition StereoColor.h:40
void process(AudioBufferView< double > input) noexcept
Definition StereoColor.h:78
double process(double x) noexcept
static std::size_t allocationBound(int factor, int sourceFactor) noexcept
static int filterLength(int factor, int sourceFactor) noexcept
static std::size_t allocationBound(int factor, int sourceFactor) noexcept
void process(AudioBufferView< double > input) noexcept
int latency() const noexcept
static int latencyFor(int factor, int sourceFactor) noexcept
StereoColor(int factor, int sourceFactor)
Streaming cubic B-spline moments of a reconstructed clipping residual.
double process(double x) noexcept
Processes one high-rate sample, returning its integrated residual.
void reset(double ceiling) noexcept
Resets reconstruction history and sets the positive finite ceiling.
std::vector< double > inverseBoxPower(int order)