Audio
Purpose
Audio plays independent voices from immutable clips or streamed files. Playback has its own output clock and application-owned lifetime rather than depending on render-frame publication or a runtime context adapter.
Interface
Types below are in CE::Audio unless another namespace is shown.
Types and operations
API and purpose |
|---|
|
|
|
|
|
Playback values and choices
Type or choice |
Purpose and details |
|---|---|
Initial normalized volume, looping and paused state per voice. Clip/stream submission. |
|
|
|
Device / offline output |
Device output advances independently; offline output advances only through explicit |
Clip / streamed file |
Retained decoded samples or file-backed playback whose source must remain available. Sources and limits. |
Use a decoded clip when several effects should share retained samples without reopening a file. Stream longer tracks when retaining the whole decoded source is undesirable; keep that file available during playback. The miniaudio preparation example shows both entry points.
Usage
Own one selected audio system alongside the game, prepare clips at loading boundaries and retain voice handles for playback needing later control. Audio progresses on its output clock; its optional event pump is a separate owner task.
---
config:
theme: base
themeVariables:
fontFamily: system-ui, sans-serif
fontSize: 14px
flowchart:
htmlLabels: true
curve: linear
nodeSpacing: 22
rankSpacing: 38
padding: 12
---
flowchart TB
accTitle: Audio · application-owned playback and outcomes
accDescr: Local names omit Audio::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices.
k0(["Application<br/>composition"]):::app
k1["<b>Miniaudio::<br/>System</b><br/><br/>open_device(Format, PlaybackNotificationOptions):<br/>Miniaudio::System<br/><br/>publish_events_to(Events::EventBus::Publisher)<br/><br/>pump_with(Runtime::RuntimeServices):<br/>Runtime::ServiceAttachment<br/><br/>publish_notifications()"]:::focal
k2(["Application<br/>loading work"]):::app
k3("Miniaudio::decode_file(file, Miniaudio::DecodeOptions):<br/>Clip"):::focal
k4["<b>Clip</b><br/><br/>from_samples(Format, samples): Clip"]:::focal
k5(["Application<br/>sound controller"]):::app
k6["<b>System</b><br/><br/>play(Clip, PlaybackOptions): Voice<br/><br/>stream(file, PlaybackOptions): Voice<br/><br/>maintain()<br/><br/>close()"]:::focal
k7["PlaybackOptions"]:::focal
k8["<b>Voice</b><br/><br/>snapshot(): VoiceSnapshot<br/><br/>pause()<br/><br/>resume()<br/><br/>stop()"]:::focal
k9["VoiceSnapshot"]:::focal
k10(["Application<br/>listener setup"]):::app
k11["<b>Events::<br/>EventBus</b><br/><br/>register_listener(<br/>Events::EventChannel<PlaybackEnded>,<br/>Events::EventChannel<PlaybackEnded>::Callback,<br/>Events::EventBus::Delivery,<br/>Events::EventBus::ErrorHandler):<br/>Events::EventBus::Registration<br/><br/>publisher(): Events::EventBus::Publisher"]:::neighbor
k12["PlaybackEnded"]:::focal
k13(["Application<br/>init_platform()"]):::app
k14["Runtime::<br/>RuntimeServices"]:::neighbor
k15["<b>Runtime::<br/>ServiceAttachment</b><br/><br/>reset()"]:::neighbor
k16(["Application<br/>cleanup / quiesce()"]):::app
k17["Events::EventBus::<br/>Publisher"]:::neighbor
k0 --> k1
k2 -.-> k4
k5 --> k6
k5 --> k7
k6 --> k4
k6 --> k8
k5 --> k8
k8 --> k9
k10 --> k11
k10 --> k12
k13 --> k1
k1 --> k14
k1 --> k15
k13 --> k15
k16 --> k6
k2 --> k3
k3 --> k4
k6 --> k7
k11 --> k17
k1 --> k17
classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px
classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px
classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3
classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px
linkStyle default stroke:#7e8fa7,stroke-width:1.2px
Local names omit Audio:: where unambiguous. Explore this diagram and its other views →
The runtime maintenance attachment in this map is optional. Audio-only programs need no Context, display or renderer. The engine dependency map shows the neutral contracts and miniaudio implementation boundary.
Uses
Consumer |
Dependency |
Integration boundary |
|---|---|---|
|
|
Retain immutable PCM for independent voices; keep stream files available through playback and restart. The selected module owns decoding/mixing/output. |
Outcome publication |
The designated owner explicitly pumps publication outside audio locks. Immediate listeners run there; queued listeners use their selected delivery adapter. |
|
Miniaudio |
Runtime |
Optional platform-maintenance notification pump. It retains weak audio access and does not own output or voice reclamation. |
Workers can run application-owned CPU file decoding. Audio::System
does not depend on an Engine worker pool, and playback does not wait for game
updates or render-frame publication.
Used by
Consumer |
Audio value/operation |
Responsibility |
|---|---|---|
Application sound/music controller |
|
Trigger prepared sources, control individual voices and explicitly close output after producers stop. Dropping a handle does not stop active playback. |
Application state/UI observation |
|
Observe copied state or logical generation outcomes; select simulation delivery when a platform pump must reach simulation-owned state. |
Offline processors and tests |
|
Explicitly advance the mixer clock with complete output frames; no native device opens. Output selection. |
Playback, maintenance and notifications
Operation |
What it advances or releases |
What the application must arrange |
|---|---|---|
Device output / offline |
Audio playback time. |
Choose real device or explicit offline output; opening and initial source preparation can block. |
|
Reclaims unreferenced inactive voices. |
Call regularly while open. It neither advances audio time nor publishes callbacks. |
|
Observes terminal facts and attempts pending event publication. |
Call on the designated owner, or retain the optional |
|
Stops output and retires voices; surviving handles report |
Settle producers first. Pump final outcomes explicitly before removing subscriptions and releasing the system. |
Observe playback outcomes
The optional Audio::playback_ended_event reports one logical terminal fact per
admitted generation: Completed, Stopped, Replaced, OutputClosed or
Reclaimed. Restart admits another generation while retaining voice identity;
pause/resume and loop iterations do not create generations. Miniaudio supports
this extension; notification_snapshot().system == 0 means unsupported.
Notifications use bounded storage and one publication attempt per retained
record. Use Audio::PlaybackNotifications
to detect loss and a retained voice’s current snapshot().terminal to reconcile
that generation. This does not recover superseded playback history. The
playback outcome contract explains
identity, reason selection and pump ownership; the
miniaudio example supplies
subscription setup and removal.
Contracts
Clips require complete nonempty finite mono/stereo PCM at supported source
rates. Volume is finite normalized linear gain in [0, 1]; mixing loud sources
can still exceed nominal amplitude. A stream file must remain available through
playback. Device opening, decode and initial stream opening can block.
Dropping a voice handle does not stop active playback. Stop and natural finish require explicit restart; toggling looping alone does not restart them. Snapshots observe playback rather than freezing its native cursor.
Lifecycle
Create output before submitting voices. Retain handles for voices needing later
control and call maintain() regularly to reclaim eligible unreferenced voices.
Maintenance does not advance audio time. Settle producers before system
destruction; idempotent close() stops output and retires voices before native
state. Surviving handles report Closed and reject controls. Construct a new
system for another session.
Execution
Controls, submissions, snapshots and maintenance synchronize application producers. Playback advances independently on the backend clock, without calling game callbacks from mixing. Runtime concurrency does not change the audio ownership contract.
Integration
With an application-owned audio system and previously prepared clip:
auto voice = audio.play(clip, {.volume = 0.5f});
// Keep voice if the game needs to pause, stop or change this playback.
audio.maintain();
Create the system and decode clips at loading boundaries. Trigger prepared clips from game state, then close output only after all producers stop.
Limits and further reading
Offline processing is explicit; an unavailable device must not silently succeed inaudibly. Spatial/world/collision behavior belongs to game code or another selected capability. Read audio ownership, miniaudio setup and coverage and the System/Voice interface.