Haiku Game Kit Sound: Preload, Stream, and Control Playback
Choose Haiku Game Kit sound classes by latency and memory needs, then manage initialization, playback, looping, pause, and teardown safely.
Haiku’s Game Kit offers a compact API for sound effects and game-oriented playback. Its central abstraction, BGameSound, represents a sound associated with a game sound device; concrete classes supply the data source and buffering strategy. BFileGameSound can be constructed from an entry reference, path, or BDataIO, and supports preloading. Streaming classes let applications avoid loading every long asset into memory before playback. These facilities complement, rather than replace, the Media Kit’s real-time graph architecture.
The choice is not simply “small file versus large file.” It is a trade between first-play latency, memory residency, streaming I/O risk, resource ownership, and how the game schedules audio. A short effect that must start immediately is a good candidate for preload. A long ambient track may benefit from streaming. Neither class can compensate for an application that blocks its main thread or destroys a sound object while playback is still active.
Game Kit versus Media Kit
The Media Kit models media nodes, time sources, and connections in a graph. It is the general system for media pipelines and synchronized real-time audio/video. The Game Kit narrows the application surface for game sound playback: create sound objects, start and stop them, set gain or pan, and manage the object lifecycle. Pick based on the problem. Do not add a full media graph merely to play a short effect, but do not assume Game Kit is a complete replacement for media recording, codec, video, or graph-routing APIs.
This separation matters operationally. A failure in Game Kit playback may involve the sound object, decoded asset, mixer/device, or application lifecycle. A failure in a Media Kit node chain may instead involve node discovery, format negotiation, or graph timing. Capture which API family is in use before changing system-wide audio settings.
Select the loading strategy deliberately
BFileGameSound accepts a filesystem reference, pathname, or BDataIO source and has a looping parameter. Its public API also exposes Preload(), StartPlaying(), StopPlaying(), and pause controls. Preloading moves file reads and decoding-related preparation out of the latency-sensitive moment when the effect is needed. It consumes memory, and it can fail due to unsupported/corrupt data or resource limits; check InitCheck() and Preload() rather than assuming success.
For a short UI or gameplay effect, preload during a loading screen or another phase where a bounded wait is acceptable. Keep frequently reused sounds ready, but avoid preloading an unbounded asset library. Track approximate decoded memory, not merely compressed file size: an encoded asset can expand substantially after conversion to the playback format.
For long or variable-length data, a streaming sound can reduce upfront memory but introduces refill timing. The source must remain valid for the sound’s lifetime, reads must not stall an audio callback, and the buffer design must tolerate storage latency. Do not pass a temporary BDataIO object and then destroy it immediately unless the class explicitly documents that it copies the data. Confirm ownership and lifetime rules in the current class header.
Check initialization and use the device contract
Construct the concrete sound object, then check InitCheck() before playback. This catches invalid paths, unsupported formats, allocation failures, and device initialization problems. The base constructor accepts a BGameSoundDevice*; the current class documentation notes that the device argument is not currently a portable selection mechanism and should be left at its documented default/null value unless the target API explicitly says otherwise. Do not assume passing a device name redirects output.
A robust call sequence is conceptually:
BFileGameSound* sound = new BFileGameSound(&effectRef, false);
if (sound == NULL || sound->InitCheck() != B_OK) {
delete sound;
ReportAssetFailure();
return;
}
if (sound->Preload() != B_OK) {
delete sound;
ReportPreloadFailure();
return;
}
status_t status = sound->StartPlaying();
if (status != B_OK)
ReportPlaybackFailure(status);
This example uses public constructor and method signatures from current headers; ReportAssetFailure and the reference lifetime are application-specific. In real code, prefer RAII ownership and do not call delete on a null pointer out of habit when a smart pointer is available. If the sound is not preloaded, the playback path may perform streaming work instead, and should be measured under realistic disk load.
Playback control is not sample-accurate game timing
BGameSound exposes StartPlaying(), StopPlaying(), IsPlaying(), SetGain(), and SetPan(). Gain and pan calls can be ramped over a duration where supported by the API. These are useful controls, but a game should not confuse a control message with a sample-accurate synchronization primitive. If several sounds must align exactly with gameplay simulation or MIDI/media time, understand the clock and timing model before using independent start calls.
Avoid polling IsPlaying() in a tight loop. Use application events, a timer with a sensible cadence, or the sound lifecycle needed by the game. A busy loop consumes CPU and can compete with the mixer. Calls from a window thread should be short; long asset preparation belongs before playback or on a worker.
Looping behavior should be chosen at construction or through the exact supported API rather than emulated by repeatedly restarting after completion. Repeated start/stop from an imprecise timer can create gaps or clicks. Test loop boundaries with the actual encoded file; a file with nonmatching start/end waveforms can click even if the API loops correctly.
Resource ownership and teardown
Sound objects manage resources such as stream buffers and media handles. The application owns the object lifetime, so stop playback before releasing an object and ensure no worker or callback can still access it. If a game scene unloads while an effect is active, either stop and join/observe completion or transfer ownership to a sound manager whose lifetime is longer than the scene.
Do not trust Clone() without checking the class-specific documentation. The base class declares cloning, but support differs by subclass; some current Haiku Game Kit docs explicitly describe clone behavior as unimplemented for a class. A null clone is not a recoverable playback object. Construct a new object from a stable source when that is supported, or reuse a managed instance with a defined concurrency policy.
Define what happens when the same sound is triggered repeatedly: ignore duplicates, restart, overlap, or allocate a voice pool. The API’s object semantics do not automatically enforce your game’s concurrency model. A pool needs bounds and a reclamation policy so rapid events cannot create unlimited voices or memory growth.
Diagnose silence by layers
First verify the exact asset path, format, and read permissions. Then check InitCheck() and the return status of preload/start. Verify system output volume and device state independently. Test a small known-supported file and compare with a Media Kit player to separate an asset failure from a system output failure. Record Haiku revision, architecture, asset codec/container, and whether the failure occurs for both preloaded and streaming playback.
If playback begins late, measure construction/preload separately from StartPlaying() and from the time the first sample is heard. Do not log or allocate in a latency-sensitive callback. If playback drops out under load, inspect buffer refill and disk I/O behavior; simply increasing every buffer can conceal scheduling problems and increase memory consumption.
For a click at start or loop boundary, inspect the source waveform, loop point, gain transition, and resampling. Use a short gain ramp if appropriate, but ensure it does not mask an invalid sample or create delayed feedback. For wrong channel balance, test pan/gain with a known mono/stereo source and check actual output routing.
A focused test plan
Test a short preloaded effect, the same effect streamed, a long track, a looping asset, pause/resume, repeated triggering, and teardown during playback. Include missing, unreadable, empty, and malformed assets. Measure peak memory after preload, first-audible latency, and behavior while storage is busy. Confirm the app remains responsive during preparation and that unloading a scene stops or transfers sounds according to policy.
Haiku’s Game Kit is a useful purpose-built layer when its lifecycle matches the application. The reliable approach is to select preload or streaming from measured constraints, validate every initialization result, treat source and object lifetimes as explicit, and separate game sound playback from the broader Media Kit graph instead of blending their responsibilities.
Related:
- The Media Kit: Real-Time Audio and Video in Haiku
- Haiku BMediaFile and BMediaTrack: Container and Track Workflows
Sources: