31 std::array<SampleStreamPageDemand, kSampleStreamLoopMaxPageDemands>
demands{};
32 std::array<SampleStreamPageView, kSampleStreamLoopMaxPageDemands>
ready_pages{};
36static_assert(std::is_trivially_copyable_v<SampleStreamLoopBlockPlan>);
47 double playback_rate)
noexcept {
48 return prepare(asset, requester, region, playback_rate,
57 return prepare(asset, requester, region, playback_rate,
69 if (!asset.valid() || requester.requester_id == 0 ||
70 requester.requester_generation == 0 ||
71 !positive_finite(playback_rate) ||
72 !
interpolation.valid() || !starvation_envelope_.prepare(starvation) ||
73 !cursor_.set_region(region, asset.total_frames)) {
76 cursor_.set_playback_rate(playback_rate);
78 std::abs(cursor_.step()))) {
84 source_ = asset.source;
85 admission_asset_ = asset;
86 requester_ = requester;
95 admission_asset_ = {};
99 starvation_envelope_.reset();
100 timeline_serial_ = 1;
105 if (!prepared_ || !positive_finite(playback_rate))
return false;
106 auto candidate = cursor_;
107 candidate.set_playback_rate(playback_rate);
108 if (!plan_capacity_accepts(admission_asset_, candidate.region(),
109 interpolation_, std::abs(candidate.step()))) {
118 double playback_rate)
noexcept {
119 return prepared_ && same_generation(asset) &&
126 if (!prepared_ || !same_generation(asset) || !
interpolation.valid() ||
127 !plan_capacity_accepts(asset, cursor_.region(),
interpolation,
128 std::abs(cursor_.step()))) {
139 return interpolation_;
141 bool active() const noexcept {
return prepared_ && cursor_.active(); }
143 return starvation_envelope_.mode();
146 return starvation_envelope_.stats();
153 starvation_envelope_.mark_starved(frames);
158 if (!prepared_ || !same_generation(asset) ||
160 !plan_capacity_accepts(asset,
cursor.region(), interpolation_,
161 std::abs(
cursor.step()))) {
171 std::uint32_t output_frames,
172 double output_sample_rate,
175 asset, output_frames, output_sample_rate,
176 {.source_frames_per_second =
177 std::abs(cursor_.step()) * output_sample_rate},
183 std::uint32_t output_frames,
184 double output_sample_rate,
189 plan.
asset = asset.asset;
190 plan.
source = asset.source;
197 if (!prepared_ || !asset.valid() || output_frames == 0 ||
198 !positive_finite(output_sample_rate)) {
201 if (!same_generation(asset)) {
205 const auto step = std::abs(cursor_.step());
206 if (!positive_finite(step) ||
207 !contract_accepts(asset, interpolation_, output_frames, step,
209 consumption.source_frames_per_second)) {
212 if (!cursor_.active()) {
218 const auto consumption_frames_per_second =
219 consumption.source_frames_per_second;
220 for (std::uint32_t output = 0; output < output_frames && scan.active(); ++output) {
221 const auto frame_plan = scan.frame_read_plan();
222 if (!append_position(plan, asset, scan.region(), interpolation_,
223 frame_plan.read_position,
224 output, step, consumption_frames_per_second,
227 !append_position(plan, asset, scan.region(), interpolation_,
228 frame_plan.blend_position,
229 output, step, consumption_frames_per_second,
235 (void) scan.advance();
242 template<std::
size_t InboxCapacity>
246 std::uint32_t start_index = 0,
247 std::uint64_t resident_source_frame_offset = 0,
248 std::uint32_t end_index =
249 std::numeric_limits<std::uint32_t>::max()) const noexcept {
253 const auto range_end = std::min(end_index, plan.
demand_count);
258 for (std::uint32_t index = start_index; index < range_end; ++index) {
259 auto demand = plan.
demands[index];
260 demand.resident_source_frames =
261 resident_source_frame_offset >
262 std::numeric_limits<std::uint64_t>::max() -
263 demand.resident_source_frames
264 ? std::numeric_limits<std::uint64_t>::max()
265 : resident_source_frame_offset + demand.resident_source_frames;
279 if (!prepared_ || !same_plan_generation(asset, plan) ||
297 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel) {
298 if (destination.channel_ptr(channel) ==
nullptr)
return result;
299 std::fill_n(destination.channel_ptr(channel), plan.
output_frames, 0.0f);
301 if (!prepared_ || !asset.valid() || destination.num_channels() != asset.channels) {
304 if (!same_plan_generation(asset, plan)) {
328 for (std::uint32_t output = 0; output < plan.
output_frames && replay.active(); ++output) {
329 const auto frame_plan = replay.frame_read_plan();
330 ResolvedRead primary;
332 const bool primary_ready = resolve_read(plan, asset, replay.region(),
333 frame_plan.read_position, primary);
334 const bool blend_ready = !frame_plan.blend ||
335 resolve_read(plan, asset, replay.region(), frame_plan.blend_position, blend);
336 if (!primary_ready || !blend_ready) {
341 for (std::uint32_t channel = 0; channel < asset.channels; ++channel) {
342 const auto a = interpolate(asset, primary, channel);
348 double sample = a.sample;
349 if (frame_plan.blend) {
350 const auto b = interpolate(asset, blend, channel);
356 sample = sample * frame_plan.primary_gain +
357 static_cast<double>(b.sample) * frame_plan.blend_gain;
359 destination.channel_ptr(channel)[output] =
static_cast<float>(sample);
363 (void) replay.advance();
367 starvation_envelope_, destination, result, plan.
output_frames);
378 struct LocatedFrame {
379 std::uint64_t source_frame;
385 struct ResolvedRead {
386 std::array<LocatedFrame, kMaximumSampleInterpolationTaps> taps;
389 std::uint32_t tap_count;
392 struct InterpolatedSample {
float sample = 0.0f;
bool ready =
false; };
394 static bool positive_finite(
double value)
noexcept {
395 return value > 0.0 && std::isfinite(value);
398 bool same_generation(
const SampleAssetView& asset)
const noexcept {
399 return asset.asset.asset_id == asset_.asset_id &&
400 asset.asset.asset_generation == asset_.asset_generation &&
401 asset.source.source_id == source_.source_id &&
402 asset.source.source_generation == source_.source_generation;
405 bool same_plan_generation(
const SampleAssetView& asset,
406 const SampleStreamLoopBlockPlan& plan)
const noexcept {
407 return same_generation(asset) && plan.
asset.
asset_id == asset.asset.asset_id &&
413 static bool contract_accepts(
const SampleAssetView& asset,
415 std::uint32_t output_frames,
417 double output_sample_rate,
418 double consumption_frames_per_second)
noexcept {
419 if (!positive_finite(consumption_frames_per_second))
return false;
420 if (asset.fully_resident())
return true;
421 if (!asset.has_preload_contract)
return false;
422 const auto& contract = asset.preload_contract;
423 const auto maximum_consumption =
424 static_cast<double>(asset.sample_rate) *
425 contract.maximum_playback_ratio;
426 return output_frames <= contract.maximum_host_block_frames &&
427 output_sample_rate == contract.host_sample_rate &&
428 positive_finite(step) &&
429 consumption_frames_per_second <= maximum_consumption &&
431 contract.interpolation_guard_frames;
434 static bool plan_capacity_accepts(
435 const SampleAssetView& asset,
436 const LoopRegion& region,
438 double step)
noexcept {
439 if (asset.fully_resident())
return true;
440 if (!asset.has_preload_contract || !asset.has_stream_source ||
441 asset.stream_source.page_frames == 0 || !positive_finite(step)) {
445 if (footprint.tap_count == 0 ||
449 const auto advance = std::ceil(
450 step *
static_cast<double>(
451 asset.preload_contract.maximum_host_block_frames));
452 if (!std::isfinite(advance) || advance < 0.0 ||
453 advance >=
static_cast<double>(
454 std::numeric_limits<std::uint64_t>::max())) {
457 const auto span =
static_cast<std::uint64_t
>(advance) +
459 const auto page_frames = asset.stream_source.page_frames;
460 const auto pages_per_region = span / page_frames +
461 (span % page_frames == 0 ? 0
u : 1u) + 2u;
462 const auto crossfaded_loop = region.crossfade_frames != 0 &&
465 const auto read_regions = crossfaded_loop ? 2u : 1u;
466 if (pages_per_region >
467 std::numeric_limits<std::uint64_t>::max() / read_regions) {
470 const auto first_stream_frame =
471 std::max(region.start_frame, asset.preload_frames);
472 if (first_stream_frame >= region.end_frame)
return true;
473 const auto first_stream_page = first_stream_frame / page_frames;
474 const auto last_stream_page = (region.end_frame - 1) / page_frames;
475 const auto total_stream_pages = last_stream_page - first_stream_page + 1;
476 const auto maximum_demands = std::min(
477 total_stream_pages, pages_per_region * read_regions);
481 bool append_position(SampleStreamLoopBlockPlan& plan,
482 const SampleAssetView& asset,
483 const LoopRegion& region,
486 std::uint32_t first_output,
488 double consumption_frames_per_second,
493 if (!resolved.valid)
return false;
494 for (std::uint32_t tap = 0; tap < resolved.footprint.tap_count; ++tap) {
496 region, asset.total_frames,
497 resolved.base_frame + resolved.footprint.first_offset + tap);
498 if (!append_page(plan, asset, frame, first_output, step,
499 consumption_frames_per_second, demand_class)) {
506 bool append_page(SampleStreamLoopBlockPlan& plan,
507 const SampleAssetView& asset,
508 std::uint64_t source_frame,
509 std::uint32_t first_output,
511 double consumption_frames_per_second,
513 if (source_frame < asset.preload_frames)
return true;
514 if (!asset.has_stream_source || asset.stream_source.page_frames == 0 ||
515 asset.stream_source.window ==
nullptr)
return false;
516 const auto page_index = source_frame / asset.stream_source.page_frames;
517 const auto distance =
static_cast<double>(first_output) * step;
518 const auto resident = distance >=
static_cast<double>(std::numeric_limits<std::uint64_t>::max())
519 ? std::numeric_limits<std::uint64_t>::max()
520 :
static_cast<std::uint64_t
>(std::floor(distance));
522 if (plan.
demands[index].page_index != page_index)
continue;
524 std::min(plan.
demands[index].resident_source_frames, resident);
531 asset.stream_source.window->ready_page_for_frame(
532 asset.source.source_generation, source_frame);
534 .source = asset.source,
535 .requester = requester_,
536 .page_index = page_index,
537 .resident_source_frames = resident,
538 .consumption_frames_per_second = consumption_frames_per_second,
539 .demand_class = demand_class,
544 static LocatedFrame locate(
const SampleStreamLoopBlockPlan& plan,
545 const SampleAssetView& asset,
546 std::uint64_t frame)
noexcept {
547 LocatedFrame located{.source_frame = frame};
548 if (frame < asset.preload_frames) {
549 located.ready =
true;
550 located.preload =
true;
553 if (!asset.has_stream_source || asset.stream_source.window ==
nullptr ||
554 asset.stream_source.page_frames == 0)
return located;
555 const auto page_index = frame / asset.stream_source.page_frames;
557 if (plan.
demands[index].page_index != page_index)
continue;
559 if (located.page.valid && frame >= located.page.start_frame)
560 located.page.local_offset = frame - located.page.start_frame;
561 located.ready = located.page.valid;
567 static bool resolve_read(
const SampleStreamLoopBlockPlan& plan,
568 const SampleAssetView& asset,
569 const LoopRegion& region,
571 ResolvedRead& resolved)
noexcept {
575 normalized, resolved.interpolation);
576 if (!position_plan.valid ||
577 position_plan.footprint.tap_count > resolved.taps.size())
return false;
578 resolved.fraction = position_plan.fraction;
579 resolved.tap_count = position_plan.footprint.tap_count;
580 for (std::uint32_t tap = 0; tap < resolved.tap_count; ++tap) {
582 region, asset.total_frames,
583 position_plan.base_frame +
584 position_plan.footprint.first_offset + tap);
585 resolved.taps[tap] = locate(plan, asset, frame);
586 if (!resolved.taps[tap].ready)
return false;
591 static const float* sample_pointer(
const SampleAssetView& asset,
592 const LocatedFrame& located,
593 std::uint32_t channel)
noexcept {
594 if (!located.ready || channel >= asset.channels)
return nullptr;
595 if (located.preload) {
596 const auto* data = asset.preload_channel_data(channel);
597 return data ==
nullptr ? nullptr : data + located.source_frame;
599 return asset.stream_source.window->ready_channel_data(located.page, channel);
602 static InterpolatedSample interpolate(
const SampleAssetView& asset,
603 const ResolvedRead& read,
604 std::uint32_t channel)
noexcept {
605 if (
read.tap_count == 0 ||
read.tap_count >
read.taps.size())
return {};
606 std::array<float, kMaximumSampleInterpolationTaps> samples;
607 for (std::uint32_t tap = 0; tap <
read.tap_count; ++tap) {
608 const auto* sample = sample_pointer(asset,
read.taps[tap], channel);
609 if (sample ==
nullptr)
return {};
610 samples[tap] = *sample;
612 return {
read.interpolation.evaluate(
614 std::span<const float>(samples.data(),
read.tap_count)),
true};
617 SampleAssetToken asset_{};
618 SampleStreamSourceToken source_{};
619 SampleAssetView admission_asset_{};
620 SampleStreamRequesterToken requester_{};
621 PreparedSampleInterpolation interpolation_{};
622 LoopPlaybackCursor cursor_{};
623 std::uint64_t timeline_serial_ = 1;
624 SampleStarvationEnvelope starvation_envelope_;
625 bool prepared_ =
false;
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Allocation-free loop traversal state independent of sample storage.
Definition loop_playback_cursor.hpp:27
bool active() const noexcept
Definition loop_playback_cursor.hpp:43
double position() const noexcept
Definition loop_playback_cursor.hpp:44
static double normalize_position(const LoopRegion ®ion, double position) noexcept
static std::uint64_t source_frame_for_tap(const LoopRegion ®ion, std::uint64_t source_frames, std::int64_t signed_frame) noexcept
Resolves an interpolation tap according to the traversal boundary: bounded modes clamp,...
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 paged adapter for LoopPlaybackCursor.
Definition sample_stream_loop_voice_reader.hpp:42
bool set_interpolation(const SampleAssetView &asset, const PreparedSampleInterpolation &interpolation) noexcept
Definition sample_stream_loop_voice_reader.hpp:123
bool synchronize_cursor(const SampleAssetView &asset, const LoopPlaybackCursor &cursor) noexcept
Definition sample_stream_loop_voice_reader.hpp:156
bool prepare(const SampleAssetView &asset, SampleStreamRequesterToken requester, const LoopRegion ®ion, double playback_rate, const PreparedSampleInterpolation &interpolation, SampleStarvationEnvelopeConfig starvation=kDefaultSampleStarvationEnvelopeConfig) noexcept
Definition sample_stream_loop_voice_reader.hpp:61
bool prepare(const SampleAssetView &asset, SampleStreamRequesterToken requester, const LoopRegion ®ion, double playback_rate, SampleInterpolationPolicy interpolation) noexcept
Definition sample_stream_loop_voice_reader.hpp:52
bool set_playback_rate(const SampleAssetView &asset, double playback_rate) noexcept
Definition sample_stream_loop_voice_reader.hpp:117
SampleStarvationMode starvation_mode() const noexcept
Definition sample_stream_loop_voice_reader.hpp:142
bool commit_planned_timeline(const SampleAssetView &asset, const SampleStreamLoopBlockPlan &plan) noexcept
Definition sample_stream_loop_voice_reader.hpp:277
bool set_playback_rate(double playback_rate) noexcept
Definition sample_stream_loop_voice_reader.hpp:104
const PreparedSampleInterpolation & interpolation() const noexcept
Definition sample_stream_loop_voice_reader.hpp:138
void mark_held_starvation(std::uint64_t frames) noexcept
Records an externally held all-silent block without advancing the cursor.
Definition sample_stream_loop_voice_reader.hpp:152
SampleStarvationEnvelopeStats starvation_stats() const noexcept
Definition sample_stream_loop_voice_reader.hpp:145
const LoopPlaybackCursor & cursor() const noexcept
Definition sample_stream_loop_voice_reader.hpp:137
SampleStreamLoopBlockPlan plan_block(const SampleAssetView &asset, std::uint32_t output_frames, double output_sample_rate, SampleStreamConsumptionDeclaration consumption, SampleStreamDemandClass demand_class=SampleStreamDemandClass::Sustain) const noexcept
Definition sample_stream_loop_voice_reader.hpp:181
bool prepare(const SampleAssetView &asset, SampleStreamRequesterToken requester, const LoopRegion ®ion, double playback_rate) noexcept
Definition sample_stream_loop_voice_reader.hpp:44
void reset() noexcept
Definition sample_stream_loop_voice_reader.hpp:92
SampleStreamLoopBlockPlan plan_block(const SampleAssetView &asset, std::uint32_t output_frames, double output_sample_rate, SampleStreamDemandClass demand_class=SampleStreamDemandClass::Sustain) const noexcept
Definition sample_stream_loop_voice_reader.hpp:169
SampleStreamVoiceDemandEnqueueResult enqueue_demands(const SampleStreamLoopBlockPlan &plan, SampleStreamCommandInbox< InboxCapacity > &inbox, std::uint32_t start_index=0, std::uint64_t resident_source_frame_offset=0, std::uint32_t end_index=std::numeric_limits< std::uint32_t >::max()) const noexcept
Definition sample_stream_loop_voice_reader.hpp:243
SampleStreamVoiceBlockResult render_block(const SampleAssetView &asset, const SampleStreamLoopBlockPlan &plan, BufferView< float > destination) noexcept
Definition sample_stream_loop_voice_reader.hpp:291
bool active() const noexcept
Definition sample_stream_loop_voice_reader.hpp:141
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
bool same_sample_interpolation(const PreparedSampleInterpolation &left, const PreparedSampleInterpolation &right) noexcept
Definition sample_interpolation.hpp:188
SampleStreamDemandClass
Definition sample_stream_scheduler.hpp:15
@ Sustain
Definition sample_stream_scheduler.hpp:17
@ Attack
Definition sample_stream_scheduler.hpp:16
LoopValidationResult validate_loop_region(const LoopRegion ®ion, std::uint64_t source_frames, std::uint64_t min_loop_frames=1) noexcept
constexpr std::size_t kSampleStreamLoopMaxPageDemands
Definition sample_stream_loop_voice_reader.hpp:20
@ Forward
Definition loop_types.hpp:9
@ Reverse
Definition loop_types.hpp:10
@ 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
@ Enqueued
Definition sample_stream_service.hpp:77
constexpr SampleStarvationEnvelopeConfig kDefaultSampleStarvationEnvelopeConfig
Definition sample_starvation_envelope.hpp:21
SampleStarvationMode
Definition sample_starvation_envelope.hpp:9
SampleInterpolationPolicy
Definition sample_interpolation.hpp:21
ResolvedSampleInterpolationPosition resolve_sample_interpolation_position(double normalized_position, const PreparedSampleInterpolation &interpolation) noexcept
Definition sample_interpolation.hpp:210
constexpr std::uint32_t kMaximumSampleInterpolationTaps
Definition sample_interpolation.hpp:19
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
constexpr SampleInterpolationPolicy sample_interpolation_policy(LoopInterpolationMode mode) noexcept
Definition sample_interpolation.hpp:244
@ index
Definition gpu_pass.hpp:140
std::uint64_t read(std::span< const std::uint8_t > b, std::size_t p, unsigned n) noexcept
Definition spectral_frame_codec.hpp:111
@ u
Definition formant_filter_bank.hpp:20
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
Definition loop_types.hpp:42
bool ok
Definition loop_types.hpp:63
Definition sample_interpolation.hpp:148
Definition sample_asset.hpp:19
std::uint64_t asset_generation
Definition sample_asset.hpp:21
std::uint64_t asset_id
Definition sample_asset.hpp:20
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_loop_voice_reader.hpp:22
SampleStreamSourceToken source
Definition sample_stream_loop_voice_reader.hpp:24
std::uint32_t demand_count
Definition sample_stream_loop_voice_reader.hpp:33
std::array< SampleStreamPageView, kSampleStreamLoopMaxPageDemands > ready_pages
Definition sample_stream_loop_voice_reader.hpp:32
SampleStreamVoiceSupply supply
Definition sample_stream_loop_voice_reader.hpp:29
SampleAssetToken asset
Definition sample_stream_loop_voice_reader.hpp:23
PreparedSampleInterpolation interpolation
Definition sample_stream_loop_voice_reader.hpp:30
std::array< SampleStreamPageDemand, kSampleStreamLoopMaxPageDemands > demands
Definition sample_stream_loop_voice_reader.hpp:31
std::uint64_t timeline_serial
Definition sample_stream_loop_voice_reader.hpp:27
LoopPlaybackCursor start_cursor
Definition sample_stream_loop_voice_reader.hpp:25
std::uint32_t output_frames
Definition sample_stream_loop_voice_reader.hpp:28
LoopPlaybackCursor end_cursor
Definition sample_stream_loop_voice_reader.hpp:26
Definition sample_stream_window.hpp:35
Definition sample_stream_service.hpp:23
Definition sample_stream_service.hpp:18
std::uint64_t source_generation
Definition sample_stream_service.hpp:20
std::uint64_t source_id
Definition sample_stream_service.hpp:19
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