Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sample_heritage_pitch.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <cmath>
8#include <cstddef>
9#include <cstdint>
10#include <limits>
11#include <vector>
12
13namespace pulp::audio {
14
25
33
45public:
51
53 double factor,
54 std::size_t channel_count) noexcept {
55 release();
56 if (!valid_family(family) || !std::isfinite(factor) ||
58 channel_count == 0 || channel_count > kSampleHeritageMaximumChannels) {
60 }
61
62 family_ = family;
63 factor_ = factor;
64 channels_ = channel_count;
65 exact_bypass_ = factor == 1.0;
67
68 if (fixed_clock_) {
69 try {
70 history_.assign(channels_ * kHistoryFrames, 0.0f);
71 } catch (...) {
72 release();
74 }
75 set_step(factor_);
76 }
77
78 prepared_ = true;
79 reset();
81 }
82
83 void release() noexcept {
84 std::vector<float>().swap(history_);
86 factor_ = 0.0;
87 step_fraction_ = 0.0;
88 step_integer_ = 0;
89 channels_ = 0;
90 prepared_ = false;
91 exact_bypass_ = false;
92 fixed_clock_ = false;
93 accepted_frames_ = 0;
94 source_index_ = 0;
95 source_fraction_ = 0.0;
96 history_count_ = 0;
97 history_start_ = 0;
98 }
99
100 void reset() noexcept {
101 if (!prepared_) return;
102 std::fill(history_.begin(), history_.end(), 0.0f);
103 accepted_frames_ = 0;
104 source_index_ = 0;
105 source_fraction_ = 0.0;
106 history_count_ = 0;
107 history_start_ = 0;
108 }
109
110 bool prepared() const noexcept { return prepared_; }
111 bool exact_bypass() const noexcept { return exact_bypass_; }
112 bool fixed_clock_processing() const noexcept {
113 return fixed_clock_ && !exact_bypass_;
114 }
115 SampleHeritagePitchFamily family() const noexcept { return family_; }
116 double factor() const noexcept { return factor_; }
117
123 if (!prepared_) return SampleHeritagePitchStatus::NotPrepared;
124 if (!std::isfinite(factor) || factor < kMinimumFactor ||
127 }
128 factor_ = factor;
129 if (fixed_clock_) set_step(factor_);
130 exact_bypass_ = factor_ == 1.0 &&
131 (!fixed_clock_ ||
132 (source_index_ >= 0 && source_fraction_ == 0.0 &&
133 static_cast<std::uint64_t>(source_index_) == accepted_frames_));
135 }
136
138 double machine_clock_multiplier() const noexcept {
139 return prepared_ && family_ == SampleHeritagePitchFamily::VariableClock
140 ? factor_
141 : 1.0;
142 }
143
145 double source_frames_per_machine_frame() const noexcept {
146 return prepared_ && family_ != SampleHeritagePitchFamily::VariableClock
147 ? factor_
148 : 1.0;
149 }
150
151 SampleHeritagePitchPlan plan(std::size_t output_frames) const noexcept {
153 result.output_frames = output_frames;
154 if (!prepared_) return result;
156 if (!fixed_clock_processing()) {
157 result.input_frames = output_frames;
158 return result;
159 }
160
161 auto index = source_index_;
162 auto fraction = source_fraction_;
163 std::int64_t highest = -1;
164 for (std::size_t frame = 0; frame < output_frames; ++frame) {
165 auto required = index;
166 if (family_ == SampleHeritagePitchFamily::EarlyLinear && fraction > 0.0) {
167 if (required == std::numeric_limits<std::int64_t>::max()) {
169 return result;
170 }
171 ++required;
172 }
173 highest = std::max(highest, required);
174 if (!advance(index, fraction)) {
176 return result;
177 }
178 }
179 if (highest < 0) return result;
180 const auto required_through = static_cast<std::uint64_t>(highest) + 1u;
181 if (required_through > accepted_frames_) {
182 const auto needed = required_through - accepted_frames_;
183 if (needed > std::numeric_limits<std::size_t>::max()) {
185 return result;
186 }
187 result.input_frames = static_cast<std::size_t>(needed);
188 }
189 return result;
190 }
191
193 BufferView<float> output) noexcept {
194 const auto expected = plan(output.num_samples());
195 if (!expected.valid()) return expected.status;
196 if (input.num_channels() != channels_ || output.num_channels() != channels_)
198 if (input.num_samples() != expected.input_frames)
200
201 if (!fixed_clock_processing()) {
202 if (fixed_clock_ &&
203 (input.num_samples() >
204 std::numeric_limits<std::uint64_t>::max() - accepted_frames_ ||
205 input.num_samples() > static_cast<std::size_t>(
206 std::numeric_limits<std::int64_t>::max() - source_index_))) {
208 }
209 for (std::size_t channel = 0; channel < channels_; ++channel)
210 std::copy(input.channel(channel).begin(), input.channel(channel).end(),
211 output.channel(channel).begin());
212 if (fixed_clock_) {
213 append_history(input);
214 accepted_frames_ += input.num_samples();
215 source_index_ += static_cast<std::int64_t>(input.num_samples());
216 }
218 }
219 if (input.num_samples() >
220 std::numeric_limits<std::uint64_t>::max() - accepted_frames_) {
222 }
223
224 const auto new_input_start = accepted_frames_;
225 auto index = source_index_;
226 auto fraction = source_fraction_;
227 if (output.num_samples() != 0 && index >= 0 &&
228 static_cast<std::uint64_t>(index) < new_input_start &&
229 static_cast<std::uint64_t>(index) < history_start_) {
231 }
232
233 for (std::size_t frame = 0; frame < output.num_samples(); ++frame) {
234 for (std::size_t channel = 0; channel < channels_; ++channel) {
235 const auto first = source_sample(channel, index, input, new_input_start);
237 fraction > 0.0) {
238 const auto second = source_sample(channel, index + 1, input,
239 new_input_start);
240 output.channel(channel)[frame] = static_cast<float>(
241 first + (second - first) * fraction);
242 } else {
243 output.channel(channel)[frame] = first;
244 }
245 }
246 if (!advance(index, fraction))
248 }
249
250 append_history(input);
251 accepted_frames_ += input.num_samples();
252 source_index_ = index;
253 source_fraction_ = fraction;
255 }
256
257 static constexpr double kMinimumFactor = 0.015625;
258 static constexpr double kMaximumFactor = 64.0;
259
260private:
261 static constexpr std::size_t kHistoryFrames = 2;
262
263 static bool valid_family(SampleHeritagePitchFamily family) noexcept {
267 }
268
269 void set_step(double factor) noexcept {
270 const auto integer = std::floor(factor);
271 step_integer_ = static_cast<std::int64_t>(integer);
272 step_fraction_ = factor - integer;
273 }
274
275 bool advance(std::int64_t& index, double& fraction) const noexcept {
276 auto next_fraction = fraction + step_fraction_;
277 std::int64_t carry = 0;
278 if (next_fraction >= 1.0) {
279 next_fraction -= 1.0;
280 carry = 1;
281 }
282 if (step_integer_ > std::numeric_limits<std::int64_t>::max() - carry ||
283 index > std::numeric_limits<std::int64_t>::max() -
284 (step_integer_ + carry)) {
285 return false;
286 }
287 index += step_integer_ + carry;
288 fraction = next_fraction;
289 return true;
290 }
291
292 float source_sample(std::size_t channel,
293 std::int64_t source_index,
294 const BufferView<const float>& input,
295 std::uint64_t new_input_start) const noexcept {
296 if (source_index < 0) return 0.0f;
297 const auto absolute = static_cast<std::uint64_t>(source_index);
298 if (absolute >= new_input_start) {
299 const auto offset = absolute - new_input_start;
300 if (offset < input.num_samples())
301 return input.channel(channel)[static_cast<std::size_t>(offset)];
302 return 0.0f;
303 }
304 if (absolute < history_start_ ||
305 absolute - history_start_ >= history_count_) {
306 return 0.0f;
307 }
308 const auto slot = static_cast<std::size_t>(absolute - history_start_);
309 return history_[channel * kHistoryFrames + slot];
310 }
311
312 void append_history(const BufferView<const float>& input) noexcept {
313 const auto retained = std::min(kHistoryFrames, input.num_samples());
314 const auto previous_needed = kHistoryFrames - retained;
315 const auto previous_retained = std::min(previous_needed, history_count_);
316 for (std::size_t channel = 0; channel < channels_; ++channel) {
317 auto* destination = history_.data() + channel * kHistoryFrames;
318 if (previous_retained != 0) {
319 const auto previous_offset = history_count_ - previous_retained;
320 std::move(destination + previous_offset,
321 destination + history_count_, destination);
322 }
323 const auto source_offset = input.num_samples() - retained;
324 std::copy(input.channel(channel).begin() +
325 static_cast<std::ptrdiff_t>(source_offset),
326 input.channel(channel).end(),
327 destination + previous_retained);
328 }
329 history_count_ = previous_retained + retained;
330 history_start_ = accepted_frames_ + input.num_samples() - history_count_;
331 }
332
333 std::vector<float> history_;
335 double factor_ = 0.0;
336 double step_fraction_ = 0.0;
337 std::int64_t step_integer_ = 0;
338 std::size_t channels_ = 0;
339 bool prepared_ = false;
340 bool exact_bypass_ = false;
341 bool fixed_clock_ = false;
342 std::uint64_t accepted_frames_ = 0;
343 std::int64_t source_index_ = 0;
344 double source_fraction_ = 0.0;
345 std::size_t history_count_ = 0;
346 std::uint64_t history_start_ = 0;
347};
348
349} // namespace pulp::audio
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
SampleHeritagePitchProcessor(const SampleHeritagePitchProcessor &)=delete
SampleHeritagePitchProcessor & operator=(const SampleHeritagePitchProcessor &)=delete
void reset() noexcept
Definition sample_heritage_pitch.hpp:100
bool fixed_clock_processing() const noexcept
Definition sample_heritage_pitch.hpp:112
double machine_clock_multiplier() const noexcept
Multiplier applied to the voice machine clock.
Definition sample_heritage_pitch.hpp:138
double source_frames_per_machine_frame() const noexcept
Source advance per fixed-rate machine frame.
Definition sample_heritage_pitch.hpp:145
SampleHeritagePitchProcessor(SampleHeritagePitchProcessor &&)=delete
SampleHeritagePitchStatus prepare(SampleHeritagePitchFamily family, double factor, std::size_t channel_count) noexcept
Definition sample_heritage_pitch.hpp:52
SampleHeritagePitchPlan plan(std::size_t output_frames) const noexcept
Definition sample_heritage_pitch.hpp:151
void release() noexcept
Definition sample_heritage_pitch.hpp:83
bool prepared() const noexcept
Definition sample_heritage_pitch.hpp:110
SampleHeritagePitchStatus process(const BufferView< const float > &input, BufferView< float > output) noexcept
Definition sample_heritage_pitch.hpp:192
SampleHeritagePitchProcessor & operator=(SampleHeritagePitchProcessor &&)=delete
SampleHeritagePitchStatus set_factor(double factor) noexcept
Updates note pitch without reallocating or discontinuously resetting the source cursor.
Definition sample_heritage_pitch.hpp:122
static constexpr double kMaximumFactor
Definition sample_heritage_pitch.hpp:258
SampleHeritagePitchFamily family() const noexcept
Definition sample_heritage_pitch.hpp:115
bool exact_bypass() const noexcept
Definition sample_heritage_pitch.hpp:111
double factor() const noexcept
Definition sample_heritage_pitch.hpp:116
static constexpr double kMinimumFactor
Definition sample_heritage_pitch.hpp:257
Definition analyzer_provider.hpp:12
SampleHeritagePitchFamily
Definition sample_heritage_schema.hpp:36
@ EarlyLinear
Definition sample_heritage_schema.hpp:39
@ DropRepeat
Definition sample_heritage_schema.hpp:38
@ VariableClock
Definition sample_heritage_schema.hpp:37
@ AllocationFailed
Definition finite_time_stretch.hpp:27
SampleHeritagePitchStatus
Definition sample_heritage_pitch.hpp:15
@ SizeOverflow
Definition sample_heritage_pitch.hpp:19
@ HistoryUnavailable
Definition sample_heritage_pitch.hpp:23
@ InvalidDimensions
Definition sample_heritage_pitch.hpp:21
@ NotPrepared
Definition sample_heritage_pitch.hpp:17
@ InputFrameMismatch
Definition sample_heritage_pitch.hpp:22
@ Ok
Definition sample_heritage_pitch.hpp:16
@ AllocationFailed
Definition sample_heritage_pitch.hpp:20
@ InvalidConfiguration
Definition sample_heritage_pitch.hpp:18
@ SizeOverflow
Definition sample_heritage_engine.hpp:44
@ InvalidDimensions
Definition sample_heritage_bus_dsp.hpp:23
@ NotPrepared
Definition sample_heritage_bus_dsp.hpp:21
@ Ok
Definition loop_types.hpp:34
@ InputFrameMismatch
Definition sample_heritage_engine.hpp:86
@ InvalidConfiguration
Definition sample_heritage_live_cyclic.hpp:24
constexpr std::size_t kSampleHeritageMaximumChannels
Definition sample_heritage_schema.hpp:27
@ index
Definition gpu_pass.hpp:140
@ retained
Definition design_update.hpp:10
Definition sample_heritage_pitch.hpp:26
bool valid() const noexcept
Definition sample_heritage_pitch.hpp:31
std::size_t input_frames
Definition sample_heritage_pitch.hpp:29
SampleHeritagePitchStatus status
Definition sample_heritage_pitch.hpp:27
std::size_t output_frames
Definition sample_heritage_pitch.hpp:28