Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
transfer_curve.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <cstddef>
13#include <optional>
14#include <span>
15#include <type_traits>
16
17namespace pulp::signal {
18
19template <typename SampleType>
21 SampleType input{};
22 SampleType output{};
24};
25
26template <typename SampleType = float, std::size_t MaxPoints = 32>
28 static_assert(MaxPoints >= 2, "a transfer curve needs room for two endpoints");
29
31
32 std::array<Point, MaxPoints> points{};
33 std::array<SampleType, MaxPoints - 1> inverse_input_deltas{};
34 std::size_t point_count = 0;
35 SampleType input_min{};
36 SampleType input_max{};
37 SampleType output_min{};
38 SampleType output_max{};
39};
40
47template <typename SampleType = float, std::size_t MaxPoints = 32>
48std::optional<PreparedTransferCurveT<SampleType, MaxPoints>> prepare_transfer_curve(
49 std::span<const TransferCurvePointT<SampleType>> points,
50 SampleType input_min,
51 SampleType input_max,
52 SampleType output_min,
53 SampleType output_max) noexcept {
54 static_assert(std::is_floating_point_v<SampleType>,
55 "transfer curves require a floating-point sample type");
56
57 if (points.size() < 2 || points.size() > MaxPoints ||
58 !std::isfinite(input_min) || !std::isfinite(input_max) ||
59 !std::isfinite(output_min) || !std::isfinite(output_max) ||
60 !(input_min < input_max) || output_min > output_max ||
61 !std::isfinite(input_max - input_min) ||
62 !std::isfinite(output_max - output_min) ||
63 points.front().input != input_min || points.back().input != input_max)
64 return std::nullopt;
65
67 result.point_count = points.size();
68 result.input_min = input_min;
69 result.input_max = input_max;
70 result.output_min = output_min;
71 result.output_max = output_max;
72
73 for (std::size_t i = 0; i < points.size(); ++i) {
74 const auto point = points[i];
75 if (!std::isfinite(point.input) || !std::isfinite(point.output) ||
76 point.input < input_min || point.input > input_max ||
77 point.output < output_min || point.output > output_max)
78 return std::nullopt;
79
80 result.points[i] = point;
81 result.points[i].curve_to_next = sanitize_modulation_curve(point.curve_to_next);
82 if (i == 0) continue;
83
84 const auto input_delta = point.input - points[i - 1].input;
85 if (!(input_delta > SampleType{}) || !std::isfinite(input_delta) ||
86 !std::isfinite(point.output - points[i - 1].output))
87 return std::nullopt;
88
89 const auto inverse_input_delta = SampleType{1} / input_delta;
90 if (!std::isfinite(inverse_input_delta)) return std::nullopt;
91 result.inverse_input_deltas[i - 1] = inverse_input_delta;
92 }
93
94 return result;
95}
96
100template <typename SampleType, std::size_t MaxPoints>
103 SampleType input) noexcept {
104 if (!std::isfinite(input) || curve.point_count < 2 || curve.point_count > MaxPoints)
105 return SampleType{};
106 if (input <= curve.input_min) return curve.points.front().output;
107 if (input >= curve.input_max) return curve.points[curve.point_count - 1].output;
108
109 std::size_t low = 0;
110 std::size_t high = curve.point_count - 1;
111 while (high - low > 1) {
112 const auto middle = low + (high - low) / 2;
113 if (input < curve.points[middle].input)
114 high = middle;
115 else
116 low = middle;
117 }
118
119 const auto& left = curve.points[low];
120 const auto& right = curve.points[low + 1];
121 const auto progress = (input - left.input) * curve.inverse_input_deltas[low];
122 const auto output = interpolate_modulation_curve(
123 left.output, right.output, progress, left.curve_to_next);
124 if (!std::isfinite(output)) return SampleType{};
125 return std::clamp(output, std::min(left.output, right.output),
126 std::max(left.output, right.output));
127}
128
136template <typename SampleType = float, std::size_t MaxPoints = 32>
138public:
141
142 static constexpr std::size_t max_points = MaxPoints;
143
144 TransferCurveT() noexcept : published_(identity_curve()), active_(identity_curve()) {}
145
146 bool publish_curve(std::span<const Point> points,
147 SampleType input_min,
148 SampleType input_max,
149 SampleType output_min,
150 SampleType output_max) noexcept {
152 points, input_min, input_max, output_min, output_max);
153 if (!prepared) return false;
154 published_.write(*prepared);
155 return true;
156 }
157
158 SampleType process(SampleType input) noexcept {
159 sync_curve();
160 return evaluate_transfer_curve(active_, input);
161 }
162
163 void process(const SampleType* input, SampleType* output, int num_samples) noexcept {
164 if (input == nullptr || output == nullptr || num_samples <= 0) return;
165 sync_curve();
166 for (int i = 0; i < num_samples; ++i)
167 output[i] = evaluate_transfer_curve(active_, input[i]);
168 }
169
170 void process(SampleType* samples, int num_samples) noexcept {
171 process(samples, samples, num_samples);
172 }
173
176 void reset() noexcept { sync_curve(); }
177
178 static constexpr int latency_samples() noexcept { return 0; }
179 static constexpr int tail_samples() noexcept { return 0; }
180
181private:
182 static constexpr PreparedCurve identity_curve() noexcept {
183 PreparedCurve curve{};
184 curve.points[0] = Point{SampleType{-1}, SampleType{-1}};
185 curve.points[1] = Point{SampleType{1}, SampleType{1}};
186 curve.inverse_input_deltas[0] = SampleType{0.5};
187 curve.point_count = 2;
188 curve.input_min = SampleType{-1};
189 curve.input_max = SampleType{1};
190 curve.output_min = SampleType{-1};
191 curve.output_max = SampleType{1};
192 return curve;
193 }
194
195 void sync_curve() noexcept { active_ = published_.read(); }
196
197 pulp::runtime::TripleBuffer<PreparedCurve> published_;
198 PreparedCurve active_{};
199};
200
207
208} // namespace pulp::signal
Audio-thread processor for a fixed-capacity arbitrary transfer curve.
Definition transfer_curve.hpp:137
TransferCurvePointT< SampleType > Point
Definition transfer_curve.hpp:139
static constexpr std::size_t max_points
Definition transfer_curve.hpp:142
void process(SampleType *samples, int num_samples) noexcept
Definition transfer_curve.hpp:170
void process(const SampleType *input, SampleType *output, int num_samples) noexcept
Definition transfer_curve.hpp:163
SampleType process(SampleType input) noexcept
Definition transfer_curve.hpp:158
TransferCurveT() noexcept
Definition transfer_curve.hpp:144
PreparedTransferCurveT< SampleType, MaxPoints > PreparedCurve
Definition transfer_curve.hpp:140
static constexpr int tail_samples() noexcept
Definition transfer_curve.hpp:179
static constexpr int latency_samples() noexcept
Definition transfer_curve.hpp:178
bool publish_curve(std::span< const Point > points, SampleType input_min, SampleType input_max, SampleType output_min, SampleType output_max) noexcept
Definition transfer_curve.hpp:146
void reset() noexcept
A transfer curve has no history.
Definition transfer_curve.hpp:176
A shared, allocation-free curve vocabulary for modulation segments.
Definition additive_bank.hpp:218
TransferCurvePointT< double > TransferCurvePoint64
Definition transfer_curve.hpp:202
TransferCurveT< double, 32 > TransferCurve64
Definition transfer_curve.hpp:206
TransferCurvePointT< float > TransferCurvePoint
Definition transfer_curve.hpp:201
@ right
Definition multi_channel_meter.hpp:67
@ left
Definition multi_channel_meter.hpp:66
SampleType interpolate_modulation_curve(SampleType start, SampleType end, ProgressType progress, ModulationCurve curve={}) noexcept
Definition modulation_curve.hpp:97
ModulationCurve sanitize_modulation_curve(ModulationCurve curve) noexcept
Definition modulation_curve.hpp:42
PreparedTransferCurveT< float, 32 > PreparedTransferCurve
Definition transfer_curve.hpp:203
SampleType evaluate_transfer_curve(const PreparedTransferCurveT< SampleType, MaxPoints > &curve, SampleType input) noexcept
Evaluate a prepared curve.
Definition transfer_curve.hpp:101
TransferCurveT< float, 32 > TransferCurve
Definition transfer_curve.hpp:205
@ i
Definition formant_filter_bank.hpp:20
PreparedTransferCurveT< double, 32 > PreparedTransferCurve64
Definition transfer_curve.hpp:204
std::optional< PreparedTransferCurveT< SampleType, MaxPoints > > prepare_transfer_curve(std::span< const TransferCurvePointT< SampleType > > points, SampleType input_min, SampleType input_max, SampleType output_min, SampleType output_max) noexcept
Validate and prepare a piecewise transfer curve.
Definition transfer_curve.hpp:48
@ prepared
Definition finite_stretch_builder.hpp:19
@ progress
Definition finite_stretch_builder.hpp:30
Curve strength is dimensionless and legal in [0, 1].
Definition modulation_curve.hpp:37
Definition transfer_curve.hpp:27
std::array< Point, MaxPoints > points
Definition transfer_curve.hpp:32
SampleType input_max
Definition transfer_curve.hpp:36
SampleType output_min
Definition transfer_curve.hpp:37
SampleType input_min
Definition transfer_curve.hpp:35
std::size_t point_count
Definition transfer_curve.hpp:34
std::array< SampleType, MaxPoints - 1 > inverse_input_deltas
Definition transfer_curve.hpp:33
TransferCurvePointT< SampleType > Point
Definition transfer_curve.hpp:30
SampleType output_max
Definition transfer_curve.hpp:38
Definition transfer_curve.hpp:20
ModulationCurve curve_to_next
Definition transfer_curve.hpp:23
float output
Definition transfer_curve.hpp:22
float input
Definition transfer_curve.hpp:21
Lock-free latest-value publication between two threads.