Shared sample stream service¶
pulp::audio::SampleStreamCacheService is the deterministic service core for
sharing decoded sample pages across voices. It canonicalizes page geometry from
registered source metadata, coalesces voice demands for the same source page,
publishes through SampleStreamWindow, and rejects source-generation mismatches.
Use this service for shared polyphony, random access, reverse playback, or
looping across paged assets. For one ordered consumer, use
StreamingSampleSource; for a decoded or
offline-rendered resident generation, use PublishedSampleStore with
LoopRenderer. The sampler playback chooser compares
the complete set of surfaces.
Page storage is always leased through SampleMemoryGovernor. Without an
external governor handle, page_memory_budget_bytes creates an internal
page-only cap. With memory_governor, that field is ignored and the shared
preload-plus-page capacity is authoritative. Admission uses checked arithmetic:
add_source() fails before playback when the page lease would exceed the
active governor. Source metadata, reader state, and control-thread containers
remain outside that capacity.
source_identity_capacity separately bounds the retained highest-generation
history used for ABA rejection. A new source ID fails with
SourceIdentityCapacityExceeded once that table is full; an existing ID may
continue only with a strictly newer non-zero generation. The current count,
capacity, and rejection total are exposed in SampleStreamCacheServiceStats.
Thread boundary¶
The cache core remains caller-driven. Production users can compose it with
SampleStreamDecodePool through SampleStreamAsyncService: one non-audio owner
registers sources, drains SampleStreamCommandInbox, dispatches reservations,
publishes completions, and collects retired sources. The audio callback may
read prepared SampleStreamWindow views, push bounded demand/cancellation
commands, and call only the RT-safe begin_audio_page_read() /
complete_audio_page_read() lifetime-barrier pair. It must not call the file
reader or any mutating service operation. Inbox overflow is explicit and never
blocks; the producer retries commands according to voice/source generation.
If a requester or pending-demand cancellation cannot be queued, its owner keeps
the token and retries before reuse; failure may waste bounded decode work but
cannot make an older requester generation satisfy a newer voice. Source memory
reclamation uses the non-RT retire_source_after_asset_unpublish() watermark
path, not a lossy inbox command. The asset publisher must first stop issuing
the old borrowed view; the service then captures the current audio generation
instead of accepting a caller-invented retirement number. PulpSampler waits
for the callback's monotonic selection acknowledgement before scheduling the
old source, so that generation covers the last callback which could have read
the unpublished asset even when the owner thread was preempted.
Page reuse has a separate retirement barrier because the owner thread's sampled audio generation can be stale across multiple callbacks. The service first removes a victim from Ready visibility and then publishes a monotonic page epoch. Each callback captures that epoch before its first page lookup and acknowledges the captured value only after its final page access. A callback that borrowed the old page therefore cannot release the newer retirement; at least one callback entering after retirement must complete before the storage is cleared. This contract assumes the normal plugin render model: one non-overlapping audio callback that completes in entry order. Prepare and release remain quiescent lifecycle operations.
service_once() decodes at most one canonical page. Multiple requesters for
that page produce one read. A requester cancellation removes only that voice's
future demand; another voice's demand remains. When no Empty page is available,
the request remains pending and NoPageAvailable is returned.
Async integration and current limits¶
SampleStreamDecodePool supplies fixed workers, preallocated planar scratch,
bounded outstanding reservations/jobs per source, one concurrent reader call
per source, bounded SPSC mailboxes, cooperative stop propagation, and leased
completion audio. SampleStreamAsyncService binds those workers to the cache
with registration epochs and reservation serials. Queue
pressure retains the exact Filling reservation for retry, other sources may
still dispatch, and stale/error completions cancel only their matching fill
before the scratch lease is released. Release joins decode workers before cache
windows are destroyed.
PulpSampler is the first production-shaped integration: strict ranged WAV and
uncompressed AIFF, pitched one-shots and forward/reverse crossfade loops,
two decode workers, and a bounded prepared-page demand footprint for each of
eight independently positioned voices. Its two bundle slots assign fixed IDs
to the base source and each possible mip member, then advance a non-zero
generation whenever that physical identity is reused. This keeps the service's
ABA history bounded for the plugin lifetime; generation exhaustion fails closed
instead of wrapping to an old token. Its
service thread owns file/source/cache mutation; the callback owns only voice
state and the SPSC producer. File admission prepares the certified tail horizon
before it publishes the asset, which gives reverse entry a latency-safe attack
neighborhood. Because those pages remain ordinary cache entries, a later
reverse note rechecks the full horizon and holds its cursor and envelope at
time zero until the first render plan owns valid snapshots for every attack
page. Admission deadlines scale with the number of sequential page decodes in
that horizon; timeout and shutdown paths cancel and reclaim unpublished
registrations. The
resident renderer now delegates traversal to the storage-independent
LoopPlaybackCursor, whose plans are checked against an independent loop
oracle. SampleStreamLoopVoiceReader snapshots every primary, blend, and
interpolation page needed by a block, advances the musical cursor through an
explicit miss, and supplies a cursor-based lookahead scheduler whose initial
lead is derived from the certified service, block, interpolation, and loop
guards. Forward notes demand their first nonresident boundary directly, so
one-frame host blocks retain the full service interval without enumerating the
resident horizon. Lookahead scanning has a fixed eight-plan work budget per
callback, and page urgency includes the accumulated distance
from the live render cursor rather than only the offset inside one plan. That
distance remains signed under command-queue backpressure, so lookahead catches
up from a real lag instead of claiming a false zero lead. Partial queue retries
refresh their accepted prefix at the current distance before adding the suffix.
Reverse entry, loop policy, and interpolation quality are exposed by
PulpSampler. Hold, true nearest, linear, cubic Hermite, cubic Lagrange, and a
ratio-tracking windowed-sinc tier share one prepared footprint/evaluation
policy across resident and paged playback. The sinc tier uses immutable,
off-callback Kaiser tables whose cutoff narrows with source consumption and
blends adjacent cutoff tables during modulation. If a resident playback ratio
exceeds the prepared table range, PulpSampler falls back to cubic Hermite
instead of dropping the voice. Inspect
PulpSamplerDiagnostics::interpolation.sinc_fallback_selections to detect how
often rendering selected that fallback since the current prepare. Each phase
row is normalized for DC unity, and
the streamed preload contract must cover the selected kernel's complete tap
guard. Persisted streamed octave mips are loaded from strict .pulpmip
sidecars, and the voice reader applies a 64-frame equal-power fade to silence
and recovery ramp around starvation. See the
PulpSampler example for the current policy and
integration limits.
SampleAsset accepts streamed tails only through a service-issued registration
proof whose source identity and page geometry match the prepared cache. The
borrowed asset and source views remain valid only until their owners cross the
documented audio-generation retirement watermark. SampleStreamVoiceReader
provides the narrow linear forward path; SampleStreamLoopVoiceReader adds
allocation-free cursor-driven one-shot, reverse, prepared interpolation-tap, and
wrap-crossfade page planning. Both return explicit ready/starved/end/stale
results. Starvation gain policy remains a separate layer, implemented by
SampleStarvationEnvelope in the PulpSampler integration.
StreamingSampleSource remains the simpler preload-plus-ring utility for
sequential one-shot playback. It is not instantiated once per sampler voice.