26 static_assert(std::is_floating_point_v<SampleType>);
34 update_derived_parameters();
42 update_derived_parameters();
61 if (!std::isfinite(amount01))
63 amount_ = std::clamp(amount01, 0.0, 1.0);
78 update_delay_samples();
84 return delay_samples_;
92 requested_cutoff_hz_ = std::clamp(
cutoff_hz, 100.0, 20000.0);
107 return enabled_ && amount_ > 0.0 ? -1 : 0;
111 left_delay_.fill(SampleType{0});
112 right_delay_.fill(SampleType{0});
115 filtered_left_ = SampleType{0};
116 filtered_right_ = SampleType{0};
119 void process_sample(SampleType input_left, SampleType input_right, SampleType& output_left,
120 SampleType& output_right)
noexcept {
121 if (!std::isfinite(
static_cast<double>(input_left)) ||
122 !std::isfinite(
static_cast<double>(input_right))) {
124 output_left = SampleType{0};
125 output_right = SampleType{0};
129 left_delay_[write_position_] = input_left;
130 right_delay_[write_position_] = input_right;
131 write_position_ = (write_position_ + 1) % kDelayCapacity;
132 valid_samples_ = std::min(valid_samples_ + 1, kDelayCapacity);
134 const SampleType delayed_left = read_delay(left_delay_);
135 const SampleType delayed_right = read_delay(right_delay_);
136 filtered_left_ =
snap_to_zero(filter_coefficient_ * filtered_left_ +
137 filter_input_coefficient_ * delayed_left);
138 filtered_right_ =
snap_to_zero(filter_coefficient_ * filtered_right_ +
139 filter_input_coefficient_ * delayed_right);
141 if (!enabled_ || amount_ == 0.0) {
142 output_left = input_left;
143 output_right = input_right;
147 output_left = direct_weight_ * input_left + cross_weight_ * filtered_right_;
148 output_right = direct_weight_ * input_right + cross_weight_ * filtered_left_;
149 if (!std::isfinite(
static_cast<double>(output_left)) ||
150 !std::isfinite(
static_cast<double>(output_right))) {
152 output_left = SampleType{0};
153 output_right = SampleType{0};
157 void process_block(
const SampleType* input_left,
const SampleType* input_right,
158 SampleType* output_left, SampleType* output_right,
159 std::size_t sample_count)
noexcept {
160 if (input_left ==
nullptr || input_right ==
nullptr || output_left ==
nullptr ||
161 output_right ==
nullptr)
163 for (std::size_t
i = 0;
i < sample_count; ++
i) {
164 const SampleType
left = input_left[
i];
165 const SampleType
right = input_right[
i];
171 static constexpr std::size_t kDelayCapacity =
173 using DelayBuffer = std::array<SampleType, kDelayCapacity>;
175 void update_derived_parameters() noexcept {
176 update_delay_samples();
181 void update_delay_samples() noexcept {
182 delay_samples_ = std::clamp(delay_ms_ * sample_rate_ / 1000.0, 0.0,
183 static_cast<double>(kDelayCapacity - 2));
186 void update_filter() noexcept {
187 cutoff_hz_ = std::min(requested_cutoff_hz_, 0.45 * sample_rate_);
188 filter_coefficient_ =
static_cast<SampleType
>(
189 std::exp(-2.0 * std::numbers::pi * cutoff_hz_ / sample_rate_));
190 filter_input_coefficient_ = SampleType{1} - filter_coefficient_;
193 void update_mix() noexcept {
195 const double denominator = 1.0 + gain;
196 direct_weight_ =
static_cast<SampleType
>(1.0 / denominator);
197 cross_weight_ =
static_cast<SampleType
>(gain / denominator);
200 SampleType read_integer(
const DelayBuffer&
buffer, std::size_t
delay)
const noexcept {
201 if (
delay >= valid_samples_)
202 return SampleType{0};
203 const std::size_t
index =
204 (write_position_ + kDelayCapacity - 1 -
delay) % kDelayCapacity;
208 SampleType read_delay(
const DelayBuffer&
buffer)
const noexcept {
209 const auto younger =
static_cast<std::size_t
>(std::floor(delay_samples_));
210 const SampleType fraction =
211 static_cast<SampleType
>(delay_samples_ -
static_cast<double>(younger));
212 const SampleType
a = read_integer(
buffer, younger);
213 const SampleType
b = read_integer(
buffer, younger + 1);
214 return (SampleType{1} - fraction) *
a + fraction * b;
217 double sample_rate_ = 48000.0;
218 double amount_ = 0.5;
219 double delay_ms_ = 0.25;
220 double delay_samples_ = 12.0;
221 double requested_cutoff_hz_ = 700.0;
222 double cutoff_hz_ = 700.0;
223 bool enabled_ =
true;
225 SampleType filter_coefficient_{};
226 SampleType filter_input_coefficient_{};
227 SampleType direct_weight_{};
228 SampleType cross_weight_{};
229 DelayBuffer left_delay_{};
230 DelayBuffer right_delay_{};
231 std::size_t write_position_ = 0;
232 std::size_t valid_samples_ = 0;
233 SampleType filtered_left_{};
234 SampleType filtered_right_{};
Mixes a delayed, low-pass-filtered copy of each channel into the opposite ear.
Definition headphone_crossfeed.hpp:24
void set_enabled(bool enabled) noexcept
Definition headphone_crossfeed.hpp:51
static constexpr double kMinimumSampleRate
Definition headphone_crossfeed.hpp:28
static constexpr int latency_samples() noexcept
Exact zero algorithmic latency: only the opposite-channel contribution is delayed; the direct paths a...
Definition headphone_crossfeed.hpp:101
double crossfeed_gain() const noexcept
Definition headphone_crossfeed.hpp:69
void process_sample(SampleType input_left, SampleType input_right, SampleType &output_left, SampleType &output_right) noexcept
Definition headphone_crossfeed.hpp:119
double sample_rate() const noexcept
Definition headphone_crossfeed.hpp:47
HeadphoneCrossfeedT() noexcept
Definition headphone_crossfeed.hpp:33
void reset() noexcept
Definition headphone_crossfeed.hpp:110
double delay_ms() const noexcept
Definition headphone_crossfeed.hpp:80
void process_block(const SampleType *input_left, const SampleType *input_right, SampleType *output_left, SampleType *output_right, std::size_t sample_count) noexcept
Definition headphone_crossfeed.hpp:157
bool prepare(double sample_rate) noexcept
Definition headphone_crossfeed.hpp:37
void set_amount(double amount01) noexcept
Normalized crossfeed amount.
Definition headphone_crossfeed.hpp:60
static constexpr double kMaximumCrossfeedGain
Definition headphone_crossfeed.hpp:31
double cutoff_hz() const noexcept
Definition headphone_crossfeed.hpp:95
int tail_samples() const noexcept
The one-pole feed decays asymptotically whenever crossfeed is active.
Definition headphone_crossfeed.hpp:106
static constexpr double kMaximumSampleRate
Definition headphone_crossfeed.hpp:29
double delay_samples() const noexcept
Definition headphone_crossfeed.hpp:83
void set_cutoff_hz(double cutoff_hz) noexcept
Crossfeed low-pass cutoff.
Definition headphone_crossfeed.hpp:89
double amount() const noexcept
Definition headphone_crossfeed.hpp:66
void set_delay_ms(double delay_ms) noexcept
Interaural delay in milliseconds, clamped to [0, 1].
Definition headphone_crossfeed.hpp:74
static constexpr double kMaximumDelayMs
Definition headphone_crossfeed.hpp:30
bool enabled() const noexcept
Definition headphone_crossfeed.hpp:54
@ b
Definition pitch.hpp:31
@ index
Definition gpu_pass.hpp:140
Definition additive_bank.hpp:218
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
HeadphoneCrossfeedT< float > HeadphoneCrossfeed
Definition headphone_crossfeed.hpp:237
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
@ delay
Definition envelope.hpp:57
HeadphoneCrossfeedT< double > HeadphoneCrossfeed64
Definition headphone_crossfeed.hpp:238
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ a
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20