Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sample_stream_voice_reader.hpp
Go to the documentation of this file.
1#pragma once
2
7
8#include <algorithm>
9#include <array>
10#include <cmath>
11#include <cstddef>
12#include <cstdint>
13#include <limits>
14#include <type_traits>
15
16namespace pulp::audio {
17
18inline constexpr std::size_t kSampleStreamVoiceMaxPageDemands = 16;
19
27
36
40 double start_source_frame = 0.0;
42 std::uint32_t output_frames = 0;
44 std::array<SampleStreamPageDemand, kSampleStreamVoiceMaxPageDemands> demands{};
45 std::array<SampleStreamPageView, kSampleStreamVoiceMaxPageDemands> ready_pages{};
46 std::uint32_t demand_count = 0;
47};
48
55
57 std::uint32_t demand_count = 0;
58 std::uint32_t enqueued = 0;
59 std::uint32_t next_demand_index = 0;
60 bool complete = false;
61};
62
63static_assert(std::is_trivially_copyable_v<SampleStreamVoiceBlockPlan>);
64static_assert(std::is_trivially_copyable_v<SampleStreamVoiceBlockResult>);
65
66namespace detail {
67
70 BufferView<float> destination,
71 const SampleStreamVoiceBlockResult& result,
72 std::uint32_t output_frames) noexcept {
73 if (result.supply == SampleStreamVoiceSupply::Starved) {
74 const auto fade_frames = std::min(
75 result.ready_output_frames, envelope.fade_out_frames());
76 const auto prefix_frames = result.ready_output_frames - fade_frames;
77 if (result.ready_output_frames != 0 &&
78 envelope.mode() == SampleStarvationMode::Silent) {
79 envelope.begin_recovery();
80 }
81 for (std::uint32_t frame = 0; frame < prefix_frames; ++frame) {
82 const auto gain = envelope.next_valid_gain();
83 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel)
84 destination.channel_ptr(channel)[frame] *= gain;
85 }
86 envelope.begin_predicted_fade(fade_frames);
87 for (std::uint32_t frame = prefix_frames;
88 frame < result.ready_output_frames; ++frame) {
89 const auto gain = envelope.next_valid_gain();
90 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel)
91 destination.channel_ptr(channel)[frame] *= gain;
92 }
93 const auto missing_frames = output_frames - result.ready_output_frames;
94 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel) {
95 std::fill_n(destination.channel_ptr(channel) + result.ready_output_frames,
96 missing_frames, 0.0f);
97 }
98 envelope.mark_starved(missing_frames);
99 return;
100 }
101 if (result.ready_output_frames == 0) return;
102 if (envelope.mode() == SampleStarvationMode::Silent)
103 envelope.begin_recovery();
104 if (envelope.mode() != SampleStarvationMode::Recovering) return;
105 for (std::uint32_t frame = 0; frame < result.ready_output_frames; ++frame) {
106 const auto gain = envelope.next_valid_gain();
107 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel)
108 destination.channel_ptr(channel)[frame] *= gain;
109 }
110}
111
112} // namespace detail
113
117public:
118 bool prepare(const SampleAssetView& asset,
122 reset();
123 if (!asset.valid() || requester.requester_id == 0 ||
124 requester.requester_generation == 0 ||
125 !starvation_envelope_.prepare(starvation)) {
126 return false;
127 }
128 asset_ = asset.asset;
129 source_ = asset.source;
130 requester_ = requester;
131 prepared_ = true;
132 return true;
133 }
134
135 void reset() noexcept {
136 asset_ = {};
137 source_ = {};
138 requester_ = {};
139 source_position_ = 0.0;
140 starvation_envelope_.reset();
141 prepared_ = false;
142 }
143
144 bool seek(const SampleAssetView& asset, double source_frame) noexcept {
145 if (!prepared_ || !same_generation(asset) || !asset.valid() ||
146 !std::isfinite(source_frame) || source_frame < 0.0 ||
147 source_frame > static_cast<double>(asset.total_frames)) {
148 return false;
149 }
150 source_position_ = source_frame;
151 starvation_envelope_.restart_audio();
152 return true;
153 }
154
155 double source_position() const noexcept { return source_position_; }
157 return starvation_envelope_.mode();
158 }
160 return starvation_envelope_.stats();
161 }
162
164 std::uint32_t output_frames,
165 double source_frames_per_output,
166 double output_sample_rate) const noexcept {
167 return plan_block(
168 asset, output_frames, source_frames_per_output, output_sample_rate,
169 {.source_frames_per_second =
170 source_frames_per_output * output_sample_rate});
171 }
172
174 const SampleAssetView& asset,
175 std::uint32_t output_frames,
176 double source_frames_per_output,
177 double output_sample_rate,
178 SampleStreamConsumptionDeclaration consumption) const noexcept {
180 plan.asset = asset.asset;
181 plan.source = asset.source;
182 plan.start_source_frame = source_position_;
183 plan.source_frames_per_output = source_frames_per_output;
184 plan.output_frames = output_frames;
185
186 if (!prepared_ || !asset.valid() || output_frames == 0 ||
187 !positive_finite(source_frames_per_output) ||
188 !positive_finite(output_sample_rate)) {
189 return plan;
190 }
191 if (!asset.fully_resident()) {
192 const auto& contract = asset.preload_contract;
193 const auto maximum_source_frames_per_second =
194 static_cast<double>(asset.sample_rate) *
195 contract.maximum_playback_ratio;
196 if (!asset.has_preload_contract ||
197 output_frames > contract.maximum_host_block_frames ||
198 output_sample_rate != contract.host_sample_rate ||
199 !positive_finite(consumption.source_frames_per_second) ||
200 consumption.source_frames_per_second >
201 maximum_source_frames_per_second) {
202 return plan;
203 }
204 }
205 if (!same_generation(asset)) {
207 return plan;
208 }
209 const auto block_advance =
210 static_cast<double>(output_frames) * source_frames_per_output;
211 if (!std::isfinite(block_advance) ||
212 !std::isfinite(source_position_ + block_advance)) {
213 return plan;
214 }
215 if (source_position_ >= static_cast<double>(asset.total_frames)) {
217 return plan;
218 }
219
220 const auto consumption_frames_per_second =
221 consumption.source_frames_per_second;
222 if (!positive_finite(consumption_frames_per_second)) return plan;
223
224 for (std::uint32_t output = 0; output < output_frames; ++output) {
225 const auto position = source_position_ +
226 static_cast<double>(output) *
227 source_frames_per_output;
228 if (position >= static_cast<double>(asset.total_frames)) break;
229 const auto lower = static_cast<std::uint64_t>(std::floor(position));
230 const auto upper = upper_tap(lower, asset.total_frames);
231 if (!append_page_demand(
232 plan, asset, lower, consumption_frames_per_second) ||
233 !append_page_demand(
234 plan, asset, upper, consumption_frames_per_second)) {
235 plan.demand_count = 0;
237 return plan;
238 }
239 }
240
242 return plan;
243 }
244
245 template<std::size_t InboxCapacity>
247 const SampleStreamVoiceBlockPlan& plan,
249 std::uint32_t start_index = 0) const noexcept {
251 result.demand_count = plan.demand_count;
252 result.next_demand_index = start_index;
254 plan.demand_count > plan.demands.size() ||
255 start_index > plan.demand_count) {
256 return result;
257 }
258 for (std::uint32_t index = start_index; index < plan.demand_count; ++index) {
259 if (inbox.demand_page(plan.demands[index]) !=
261 return result;
262 }
263 ++result.enqueued;
264 ++result.next_demand_index;
265 }
266 result.complete = true;
267 return result;
268 }
269
271 const SampleStreamVoiceBlockPlan& plan,
272 BufferView<float> destination) noexcept {
274 if (plan.output_frames == 0 ||
275 plan.output_frames > destination.num_samples()) {
276 return result;
277 }
278 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel) {
279 if (destination.channel_ptr(channel) == nullptr) return result;
280 }
281 clear_from(destination, 0, plan.output_frames);
282
283 if (!prepared_ || !asset.valid() ||
284 destination.num_channels() != asset.channels ||
285 plan.start_source_frame != source_position_ ||
286 !positive_finite(plan.source_frames_per_output)) {
287 return result;
288 }
289 if (plan.supply == SampleStreamVoiceSupply::StaleGeneration) {
292 return result;
293 }
294 if (plan.supply == SampleStreamVoiceSupply::InvalidContract) {
296 return result;
297 }
298 if (plan.supply != SampleStreamVoiceSupply::Ready &&
300 return result;
301 }
302 if (!same_generation(asset) || plan.asset.asset_id != asset.asset.asset_id ||
303 plan.asset.asset_generation != asset.asset.asset_generation ||
304 plan.source.source_id != asset.source.source_id ||
305 plan.source.source_generation != asset.source.source_generation) {
308 return result;
309 }
310 if (plan.supply == SampleStreamVoiceSupply::EndOfSource) {
313 return result;
314 }
315
318 for (std::uint32_t output = 0; output < plan.output_frames; ++output) {
319 const auto position = plan.start_source_frame +
320 static_cast<double>(output) *
321 plan.source_frames_per_output;
322 if (position >= static_cast<double>(asset.total_frames)) {
325 break;
326 }
327
328 const auto lower_frame = static_cast<std::uint64_t>(std::floor(position));
329 const auto upper_frame = upper_tap(lower_frame, asset.total_frames);
330 const auto lower = locate(plan, asset, lower_frame);
331 const auto upper = locate(plan, asset, upper_frame);
332 if (!lower.ready || !upper.ready) {
335 break;
336 }
337
338 const auto fraction = static_cast<float>(position - std::floor(position));
339 for (std::uint32_t channel = 0; channel < asset.channels; ++channel) {
340 const auto* lower_sample = sample_pointer(asset, lower, channel);
341 const auto* upper_sample = sample_pointer(asset, upper, channel);
342 if (lower_sample == nullptr || upper_sample == nullptr) {
345 break;
346 }
347 destination.channel_ptr(channel)[output] =
348 *lower_sample + (*upper_sample - *lower_sample) * fraction;
349 }
350 if (result.supply == SampleStreamVoiceSupply::Starved) break;
351 ++result.ready_output_frames;
352 }
353
355 starvation_envelope_, destination, result, plan.output_frames);
356 advance_timeline(asset, plan);
357 return result;
358 }
359
360private:
361 struct LocatedFrame {
362 bool ready = false;
363 bool preload = false;
364 std::uint64_t source_frame = 0;
366 };
367
368 static bool positive_finite(double value) noexcept {
369 return value > 0.0 && std::isfinite(value);
370 }
371
372 static std::uint64_t upper_tap(std::uint64_t lower,
373 std::uint64_t total_frames) noexcept {
374 return lower >= total_frames - 1 ? lower : lower + 1;
375 }
376
377 bool same_generation(const SampleAssetView& asset) const noexcept {
378 return asset.asset.asset_id == asset_.asset_id &&
379 asset.asset.asset_generation == asset_.asset_generation &&
380 asset.source.source_id == source_.source_id &&
381 asset.source.source_generation == source_.source_generation;
382 }
383
384 bool append_page_demand(SampleStreamVoiceBlockPlan& plan,
385 const SampleAssetView& asset,
386 std::uint64_t source_frame,
387 double consumption_frames_per_second) const noexcept {
388 if (source_frame < asset.preload_frames) return true;
389 if (!asset.has_stream_source || asset.stream_source.page_frames == 0) return false;
390 const auto page_index = source_frame / asset.stream_source.page_frames;
391 for (std::uint32_t index = 0; index < plan.demand_count; ++index) {
392 if (plan.demands[index].page_index == page_index) return true;
393 }
394 if (plan.demand_count >= plan.demands.size()) return false;
395
396 const auto distance = static_cast<double>(source_frame) -
397 plan.start_source_frame;
398 const auto max_frames =
399 static_cast<double>(std::numeric_limits<std::uint64_t>::max());
400 std::uint64_t resident_source_frames = 0;
401 if (distance > 0.0) {
402 resident_source_frames = distance >= max_frames
403 ? std::numeric_limits<std::uint64_t>::max()
404 : static_cast<std::uint64_t>(std::floor(distance));
405 }
406 plan.ready_pages[plan.demand_count] =
407 asset.stream_source.window->ready_page_for_frame(
408 asset.source.source_generation, source_frame);
409 plan.demands[plan.demand_count++] = {
410 .source = asset.source,
411 .requester = requester_,
412 .page_index = page_index,
413 .resident_source_frames = resident_source_frames,
414 .consumption_frames_per_second = consumption_frames_per_second,
415 .demand_class = source_position_ == 0.0
418 };
419 return true;
420 }
421
422 static LocatedFrame locate(const SampleStreamVoiceBlockPlan& plan,
423 const SampleAssetView& asset,
424 std::uint64_t source_frame) noexcept {
425 LocatedFrame located;
426 located.source_frame = source_frame;
427 if (source_frame < asset.preload_frames) {
428 located.ready = true;
429 located.preload = true;
430 return located;
431 }
432 if (!asset.has_stream_source || asset.stream_source.window == nullptr ||
433 asset.stream_source.page_frames == 0) {
434 return located;
435 }
436 const auto page_index = source_frame / asset.stream_source.page_frames;
437 for (std::uint32_t index = 0; index < plan.demand_count; ++index) {
438 if (plan.demands[index].page_index == page_index) {
439 located.page = plan.ready_pages[index];
440 if (located.page.valid &&
441 source_frame >= located.page.start_frame) {
442 located.page.local_offset =
443 source_frame - located.page.start_frame;
444 }
445 break;
446 }
447 }
448 located.ready = located.page.valid;
449 return located;
450 }
451
452 static const float* sample_pointer(const SampleAssetView& asset,
453 const LocatedFrame& located,
454 std::uint32_t channel) noexcept {
455 if (!located.ready || channel >= asset.channels) return nullptr;
456 if (located.preload) {
457 const auto* data = asset.preload_channel_data(channel);
458 return data == nullptr ? nullptr : data + located.source_frame;
459 }
460 return asset.stream_source.window->ready_channel_data(located.page, channel);
461 }
462
463 static void clear_from(BufferView<float> destination,
464 std::uint32_t start,
465 std::uint32_t end) noexcept {
466 const auto count = static_cast<std::size_t>(end - start);
467 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel) {
468 std::fill_n(destination.channel_ptr(channel) + start, count, 0.0f);
469 }
470 }
471
472 void advance_timeline(const SampleAssetView& asset,
473 const SampleStreamVoiceBlockPlan& plan) noexcept {
474 const auto next = plan.start_source_frame +
475 static_cast<double>(plan.output_frames) *
476 plan.source_frames_per_output;
477 source_position_ = std::min(next, static_cast<double>(asset.total_frames));
478 }
479
480 SampleAssetToken asset_{};
481 SampleStreamSourceToken source_{};
482 SampleStreamRequesterToken requester_{};
483 double source_position_ = 0.0;
484 SampleStarvationEnvelope starvation_envelope_;
485 bool prepared_ = false;
486};
487
488} // namespace pulp::audio
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Definition sample_starvation_envelope.hpp:34
Prepared bounded command port from one RT producer to the service owner.
Definition sample_stream_service.hpp:91
SampleStreamCommandPushStatus demand_page(const SampleStreamPageDemand &demand) noexcept
Definition sample_stream_service.hpp:93
Allocation-free linear forward one-shot reader over immutable sample assets.
Definition sample_stream_voice_reader.hpp:116
double source_position() const noexcept
Definition sample_stream_voice_reader.hpp:155
SampleStarvationMode starvation_mode() const noexcept
Definition sample_stream_voice_reader.hpp:156
SampleStreamVoiceBlockResult render_block(const SampleAssetView &asset, const SampleStreamVoiceBlockPlan &plan, BufferView< float > destination) noexcept
Definition sample_stream_voice_reader.hpp:270
SampleStreamVoiceBlockPlan plan_block(const SampleAssetView &asset, std::uint32_t output_frames, double source_frames_per_output, double output_sample_rate, SampleStreamConsumptionDeclaration consumption) const noexcept
Definition sample_stream_voice_reader.hpp:173
SampleStarvationEnvelopeStats starvation_stats() const noexcept
Definition sample_stream_voice_reader.hpp:159
SampleStreamVoiceDemandEnqueueResult enqueue_demands(const SampleStreamVoiceBlockPlan &plan, SampleStreamCommandInbox< InboxCapacity > &inbox, std::uint32_t start_index=0) const noexcept
Definition sample_stream_voice_reader.hpp:246
bool prepare(const SampleAssetView &asset, SampleStreamRequesterToken requester, SampleStarvationEnvelopeConfig starvation=kDefaultSampleStarvationEnvelopeConfig) noexcept
Definition sample_stream_voice_reader.hpp:118
void reset() noexcept
Definition sample_stream_voice_reader.hpp:135
SampleStreamVoiceBlockPlan plan_block(const SampleAssetView &asset, std::uint32_t output_frames, double source_frames_per_output, double output_sample_rate) const noexcept
Definition sample_stream_voice_reader.hpp:163
bool seek(const SampleAssetView &asset, double source_frame) noexcept
Definition sample_stream_voice_reader.hpp:144
Definition impulse_response.hpp:69
void apply_sample_starvation_envelope(SampleStarvationEnvelope &envelope, BufferView< float > destination, const SampleStreamVoiceBlockResult &result, std::uint32_t output_frames) noexcept
Definition sample_stream_voice_reader.hpp:68
Definition analyzer_provider.hpp:12
@ Sustain
Definition sample_stream_scheduler.hpp:17
@ Attack
Definition sample_stream_scheduler.hpp:16
@ None
Definition finite_time_stretch.hpp:26
@ StaleGeneration
Definition sample_stream_decode_pool.hpp:67
SampleStreamVoiceOutcomeClass
Definition sample_stream_voice_reader.hpp:28
@ InvalidPreloadContract
Definition sample_stream_voice_reader.hpp:31
@ ServiceStarvation
Definition sample_stream_voice_reader.hpp:30
@ StaleGeneration
Definition sample_stream_voice_reader.hpp:32
@ NormalEndOfSource
Definition sample_stream_voice_reader.hpp:33
@ None
Definition sample_stream_voice_reader.hpp:29
@ InvalidRenderContract
Definition sample_stream_voice_reader.hpp:34
constexpr std::size_t kSampleStreamVoiceMaxPageDemands
Definition sample_stream_voice_reader.hpp:18
@ Enqueued
Definition sample_stream_service.hpp:77
constexpr SampleStarvationEnvelopeConfig kDefaultSampleStarvationEnvelopeConfig
Definition sample_starvation_envelope.hpp:21
SampleStarvationMode
Definition sample_starvation_envelope.hpp:9
@ Silent
Definition sample_starvation_envelope.hpp:12
@ Recovering
Definition sample_starvation_envelope.hpp:13
@ Ready
Definition sample_starvation_envelope.hpp:10
SampleStreamVoiceSupply
Definition sample_stream_voice_reader.hpp:20
@ EndOfSource
Definition sample_stream_voice_reader.hpp:23
@ Starved
Definition sample_stream_voice_reader.hpp:22
@ StaleGeneration
Definition sample_stream_voice_reader.hpp:24
@ Ready
Definition sample_stream_voice_reader.hpp:21
@ InvalidContract
Definition sample_stream_voice_reader.hpp:25
@ count
sentinel: number of real capabilities (keep LAST)
Definition canvas_capability.hpp:38
@ end
Definition editor_host.hpp:354
@ index
Definition gpu_pass.hpp:140
@ start
Definition geometry.hpp:370
Definition sample_asset.hpp:19
Immutable audio-thread snapshot borrowed from one SampleAsset owner.
Definition sample_asset.hpp:130
Definition sample_starvation_envelope.hpp:16
Definition sample_starvation_envelope.hpp:26
Definition sample_stream_consumption.hpp:62
Definition sample_stream_window.hpp:35
Definition sample_stream_service.hpp:23
Definition sample_stream_service.hpp:18
Definition sample_stream_voice_reader.hpp:37
SampleAssetToken asset
Definition sample_stream_voice_reader.hpp:38
std::array< SampleStreamPageView, kSampleStreamVoiceMaxPageDemands > ready_pages
Definition sample_stream_voice_reader.hpp:45
std::uint32_t output_frames
Definition sample_stream_voice_reader.hpp:42
double source_frames_per_output
Definition sample_stream_voice_reader.hpp:41
SampleStreamVoiceSupply supply
Definition sample_stream_voice_reader.hpp:43
SampleStreamSourceToken source
Definition sample_stream_voice_reader.hpp:39
double start_source_frame
Definition sample_stream_voice_reader.hpp:40
std::uint32_t demand_count
Definition sample_stream_voice_reader.hpp:46
std::array< SampleStreamPageDemand, kSampleStreamVoiceMaxPageDemands > demands
Definition sample_stream_voice_reader.hpp:44
Definition sample_stream_voice_reader.hpp:49
SampleStreamVoiceOutcomeClass outcome
Definition sample_stream_voice_reader.hpp:51
SampleStreamVoiceSupply supply
Definition sample_stream_voice_reader.hpp:50
std::uint32_t ready_output_frames
Definition sample_stream_voice_reader.hpp:53
Definition sample_stream_voice_reader.hpp:56
std::uint32_t enqueued
Definition sample_stream_voice_reader.hpp:58
std::uint32_t next_demand_index
Definition sample_stream_voice_reader.hpp:59
bool complete
Definition sample_stream_voice_reader.hpp:60
std::uint32_t demand_count
Definition sample_stream_voice_reader.hpp:57