44 std::array<SampleStreamPageDemand, kSampleStreamVoiceMaxPageDemands>
demands{};
45 std::array<SampleStreamPageView, kSampleStreamVoiceMaxPageDemands>
ready_pages{};
63static_assert(std::is_trivially_copyable_v<SampleStreamVoiceBlockPlan>);
64static_assert(std::is_trivially_copyable_v<SampleStreamVoiceBlockResult>);
72 std::uint32_t output_frames)
noexcept {
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 &&
79 envelope.begin_recovery();
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;
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;
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);
98 envelope.mark_starved(missing_frames);
101 if (result.ready_output_frames == 0)
return;
103 envelope.begin_recovery();
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;
123 if (!asset.valid() || requester.requester_id == 0 ||
124 requester.requester_generation == 0 ||
125 !starvation_envelope_.prepare(starvation)) {
128 asset_ = asset.asset;
129 source_ = asset.source;
130 requester_ = requester;
139 source_position_ = 0.0;
140 starvation_envelope_.reset();
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)) {
150 source_position_ = source_frame;
151 starvation_envelope_.restart_audio();
157 return starvation_envelope_.mode();
160 return starvation_envelope_.stats();
164 std::uint32_t output_frames,
165 double source_frames_per_output,
166 double output_sample_rate)
const noexcept {
168 asset, output_frames, source_frames_per_output, output_sample_rate,
169 {.source_frames_per_second =
170 source_frames_per_output * output_sample_rate});
175 std::uint32_t output_frames,
176 double source_frames_per_output,
177 double output_sample_rate,
180 plan.
asset = asset.asset;
181 plan.
source = asset.source;
186 if (!prepared_ || !asset.valid() || output_frames == 0 ||
187 !positive_finite(source_frames_per_output) ||
188 !positive_finite(output_sample_rate)) {
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) {
205 if (!same_generation(asset)) {
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)) {
215 if (source_position_ >=
static_cast<double>(asset.total_frames)) {
220 const auto consumption_frames_per_second =
221 consumption.source_frames_per_second;
222 if (!positive_finite(consumption_frames_per_second))
return plan;
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) ||
234 plan, asset, upper, consumption_frames_per_second)) {
245 template<std::
size_t InboxCapacity>
249 std::uint32_t start_index = 0) const noexcept {
258 for (std::uint32_t index = start_index; index < plan.
demand_count; ++index) {
274 if (plan.output_frames == 0 ||
275 plan.output_frames > destination.num_samples()) {
278 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel) {
279 if (destination.channel_ptr(channel) ==
nullptr)
return result;
281 clear_from(destination, 0, plan.output_frames);
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)) {
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) {
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)) {
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) {
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) {
347 destination.channel_ptr(channel)[output] =
348 *lower_sample + (*upper_sample - *lower_sample) * fraction;
355 starvation_envelope_, destination, result, plan.output_frames);
356 advance_timeline(asset, plan);
361 struct LocatedFrame {
363 bool preload =
false;
364 std::uint64_t source_frame = 0;
368 static bool positive_finite(
double value)
noexcept {
369 return value > 0.0 && std::isfinite(value);
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;
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;
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;
394 if (plan.demand_count >= plan.demands.size())
return false;
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));
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
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;
432 if (!asset.has_stream_source || asset.stream_source.window ==
nullptr ||
433 asset.stream_source.page_frames == 0) {
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;
448 located.ready = located.page.valid;
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;
460 return asset.stream_source.window->ready_channel_data(located.page, channel);
463 static void clear_from(BufferView<float> destination,
465 std::uint32_t end)
noexcept {
467 for (std::size_t channel = 0; channel < destination.num_channels(); ++channel) {
468 std::fill_n(destination.channel_ptr(channel) + start, count, 0.0f);
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));
480 SampleAssetToken asset_{};
481 SampleStreamSourceToken source_{};
482 SampleStreamRequesterToken requester_{};
483 double source_position_ = 0.0;
484 SampleStarvationEnvelope starvation_envelope_;
485 bool prepared_ =
false;
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
@ 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