Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
reed_waveguide_loop.hpp
Go to the documentation of this file.
1#pragma once
2
5
10
11#include <algorithm>
12#include <cmath>
13#include <limits>
14#include <type_traits>
15#include <utility>
16
17namespace pulp::signal {
18
26template <typename SampleType = float> class ReedWaveguideLoopT {
27 static_assert(std::is_floating_point_v<SampleType>);
28 using Oversampler = OversamplerT<SampleType>;
29
30 public:
31 static constexpr double default_one_way_seconds = 1.0 / (4.0 * 440.0);
32 static constexpr SampleType maximum_base_rate_bell_loss_pole = SampleType{0.98};
33
34 [[nodiscard]] bool prepare(double base_sample_rate, double max_one_way_seconds,
35 int oversampling_factor = 2) {
36 if (!std::isfinite(base_sample_rate) || !(base_sample_rate > 0.0) ||
37 !std::isfinite(max_one_way_seconds) ||
40 return false;
41 const auto internal_rate = base_sample_rate * oversampling_factor;
42 if (!std::isfinite(internal_rate) ||
43 internal_rate > static_cast<double>(std::numeric_limits<SampleType>::max()) ||
44 max_one_way_seconds * internal_rate <
46 return false;
47
48 Oversampler next_oversampler;
49 if (oversampling_factor != 1) {
50 next_oversampler.set_factor(oversampling_factor == 2 ? Oversampler::Factor::x2
51 : Oversampler::Factor::x4);
52 next_oversampler.set_sample_rate(static_cast<SampleType>(base_sample_rate));
53 next_oversampler.set_quality(Oversampler::Quality::standard);
54 next_oversampler.set_kind(Oversampler::Kind::linear_phase_fir);
55 }
56
58 if (!next_line.prepare(internal_rate, max_one_way_seconds))
59 return false;
60 const auto effective_seconds = clamped_seconds(target_one_way_seconds_, internal_rate,
61 max_one_way_seconds);
62 next_line.set_length_samples(
63 static_cast<SampleType>(effective_seconds * internal_rate));
64
65 oversampler_ = std::move(next_oversampler);
66 line_ = std::move(next_line);
67 base_sample_rate_ = base_sample_rate;
68 internal_sample_rate_ = internal_rate;
69 max_one_way_seconds_ = max_one_way_seconds;
70 target_one_way_seconds_ = effective_seconds;
71 oversampling_factor_ = oversampling_factor;
72 prepared_ = true;
73 configure_internal_bell_pole();
74 reset();
75 return true;
76 }
77
78 void set_one_way_seconds(double seconds) noexcept {
79 if (!std::isfinite(seconds))
80 return;
81 if (!prepared_) {
82 target_one_way_seconds_ = std::max(0.0, seconds);
83 return;
84 }
85 target_one_way_seconds_ =
86 clamped_seconds(seconds, internal_sample_rate_, max_one_way_seconds_);
87 line_.set_length_samples(
88 static_cast<SampleType>(target_one_way_seconds_ * internal_sample_rate_));
89 }
90
91 [[nodiscard]] double target_one_way_seconds() const noexcept {
92 return prepared_ ? static_cast<double>(line_.length_samples()) / internal_sample_rate_
93 : target_one_way_seconds_;
94 }
95
96 [[nodiscard]] double current_one_way_seconds() const noexcept {
97 return prepared_
98 ? static_cast<double>(line_.current_length_samples()) / internal_sample_rate_
99 : 0.0;
100 }
101
102 [[nodiscard]] double round_trip_seconds() const noexcept {
103 return prepared_ ? line_.round_trip_seconds() : 0.0;
104 }
105
106 void set_closing_pressure(SampleType value) noexcept {
107 reed_.set_closing_pressure(value);
108 }
109 void set_flow_gain(SampleType value) noexcept { reed_.set_flow_gain(value); }
110 void set_bore_impedance(SampleType value) noexcept { reed_.set_bore_impedance(value); }
111 void set_bell_reflection_gain(SampleType value) noexcept {
112 bell_.set_reflection_gain(value);
113 }
114
116 void set_bell_loss_pole(SampleType value) noexcept {
117 if (!std::isfinite(static_cast<double>(value)))
118 return;
119 base_rate_bell_loss_pole_ =
120 std::clamp(value, SampleType{}, maximum_base_rate_bell_loss_pole);
121 configure_internal_bell_pole();
122 }
123
124 [[nodiscard]] SampleType bell_loss_pole() const noexcept {
125 return base_rate_bell_loss_pole_;
126 }
127 [[nodiscard]] SampleType internal_bell_loss_pole() const noexcept {
128 return bell_.loss_pole();
129 }
130
133 [[nodiscard]] SampleType process(SampleType mouth_pressure) noexcept {
134 if (!prepared_)
135 return SampleType{};
136 if (!std::isfinite(static_cast<double>(mouth_pressure)))
137 mouth_pressure = last_finite_mouth_pressure_;
138 else {
139 mouth_pressure = std::clamp(mouth_pressure, SampleType{}, SampleType{1});
140 last_finite_mouth_pressure_ = mouth_pressure;
141 }
142
143 auto whole_loop = [this](SampleType oversampled_mouth) noexcept {
144 SampleType reed_arrival{};
145 SampleType bell_arrival{};
146 line_.read_outputs(reed_arrival, bell_arrival);
147 const auto reed_reflection = reed_.process(oversampled_mouth, reed_arrival);
148 const auto bell_reflection = bell_.process(bell_arrival);
149 line_.push_inputs(reed_reflection, bell_reflection);
150 return bell_arrival;
151 };
152
153 const auto output = oversampling_factor_ == 1
154 ? whole_loop(mouth_pressure)
155 : oversampler_.process(mouth_pressure, whole_loop);
156 if (!std::isfinite(static_cast<double>(output))) {
157 // The line already rejects non-finite pushes and reads. Avoid its
158 // size-proportional history clear on the audio thread.
159 oversampler_.reset();
160 reed_.reset();
161 bell_.reset();
162 return SampleType{};
163 }
164 return output;
165 }
166
167 [[nodiscard]] int oversampling_factor() const noexcept { return oversampling_factor_; }
168 [[nodiscard]] int latency_samples() const noexcept {
169 return oversampling_factor_ == 1 ? 0 : oversampler_.latency_samples();
170 }
171 [[nodiscard]] int tail_samples() const noexcept { return -1; }
172 [[nodiscard]] bool prepared() const noexcept { return prepared_; }
173
174 void reset() noexcept {
175 oversampler_.reset();
176 line_.reset();
177 reed_.reset();
178 bell_.reset();
179 last_finite_mouth_pressure_ = SampleType{};
180 }
181
182 private:
183 void configure_internal_bell_pole() noexcept {
184 const auto factor = std::max(1, oversampling_factor_);
185 const auto mapped =
186 base_rate_bell_loss_pole_ == SampleType{}
187 ? SampleType{}
188 : static_cast<SampleType>(std::pow(
189 static_cast<double>(base_rate_bell_loss_pole_), 1.0 / factor));
190 bell_.set_loss_pole(mapped);
191 }
192
193 [[nodiscard]] static double clamped_seconds(double seconds, double internal_rate,
194 double maximum) noexcept {
196 return std::clamp(seconds, minimum, maximum);
197 }
198
199 Oversampler oversampler_{};
200 WaveguideLineT<SampleType> line_{};
201 ReedExciterT<SampleType> reed_{};
202 WaveguideReflectionFilterT<SampleType> bell_{};
203 double base_sample_rate_ = 0.0;
204 double internal_sample_rate_ = 0.0;
205 double max_one_way_seconds_ = 0.0;
206 double target_one_way_seconds_ = default_one_way_seconds;
207 int oversampling_factor_ = 1;
208 SampleType base_rate_bell_loss_pole_ = static_cast<SampleType>(
210 SampleType last_finite_mouth_pressure_{};
211 bool prepared_ = false;
212};
213
216
217} // namespace pulp::signal
Definition oversampling.hpp:78
void set_kind(Kind kind)
Definition oversampling.hpp:121
void set_sample_rate(SampleType sr)
Definition oversampling.hpp:116
void set_factor(Factor f)
RT contract: set_factor(), set_sample_rate(), set_kind(), and reset() are setup/control operations th...
Definition oversampling.hpp:111
void set_quality(Quality quality)
Definition oversampling.hpp:126
Owns and oversamples one complete reed -> bore -> bell feedback loop.
Definition reed_waveguide_loop.hpp:26
void set_bell_loss_pole(SampleType value) noexcept
Sets the bell pole in the base-rate domain.
Definition reed_waveguide_loop.hpp:116
void set_bore_impedance(SampleType value) noexcept
Definition reed_waveguide_loop.hpp:110
double current_one_way_seconds() const noexcept
Definition reed_waveguide_loop.hpp:96
SampleType process(SampleType mouth_pressure) noexcept
Returns the pressure wave arriving at the bell before reflection.
Definition reed_waveguide_loop.hpp:133
int oversampling_factor() const noexcept
Definition reed_waveguide_loop.hpp:167
void set_flow_gain(SampleType value) noexcept
Definition reed_waveguide_loop.hpp:109
static constexpr float maximum_base_rate_bell_loss_pole
Definition reed_waveguide_loop.hpp:32
bool prepare(double base_sample_rate, double max_one_way_seconds, int oversampling_factor=2)
Definition reed_waveguide_loop.hpp:34
double round_trip_seconds() const noexcept
Definition reed_waveguide_loop.hpp:102
SampleType internal_bell_loss_pole() const noexcept
Definition reed_waveguide_loop.hpp:127
void set_closing_pressure(SampleType value) noexcept
Definition reed_waveguide_loop.hpp:106
void reset() noexcept
Definition reed_waveguide_loop.hpp:174
void set_bell_reflection_gain(SampleType value) noexcept
Definition reed_waveguide_loop.hpp:111
double target_one_way_seconds() const noexcept
Definition reed_waveguide_loop.hpp:91
static constexpr double default_one_way_seconds
Definition reed_waveguide_loop.hpp:31
void set_one_way_seconds(double seconds) noexcept
Definition reed_waveguide_loop.hpp:78
int latency_samples() const noexcept
Definition reed_waveguide_loop.hpp:168
int tail_samples() const noexcept
Definition reed_waveguide_loop.hpp:171
bool prepared() const noexcept
Definition reed_waveguide_loop.hpp:172
SampleType bell_loss_pole() const noexcept
Definition reed_waveguide_loop.hpp:124
A passive bidirectional waveguide segment.
Definition waveguide_line.hpp:33
void set_length_samples(SampleType length) noexcept
Definition waveguide_line.hpp:74
static constexpr double minimum_length_samples
Definition waveguide_line.hpp:37
bool prepare(double sample_rate, double max_length_seconds=default_max_length_seconds)
Definition waveguide_line.hpp:43
static constexpr double default_loss_pole
Definition waveguide_reflection_filter.hpp:32
Definition additive_bank.hpp:218
ReedWaveguideLoopT< double > ReedWaveguideLoop64
Definition reed_waveguide_loop.hpp:215
ReedWaveguideLoopT< float > ReedWaveguideLoop
Definition reed_waveguide_loop.hpp:214
@ maximum
Definition spectrum_trace.hpp:34
float minimum(const float *data, std::size_t count) noexcept
Prepared bidirectional traveling-wave delay rails.
Bounded memoryless single-reed valve for waveguide compositions.
Passive one-pole waveguide termination.