Audio Overview
OpenBOR uses a unified sample, stream, and channel system for sound effects, ambience, voice, music, and WebM audio. WAV and Ogg Vorbis files may be retained in memory or streamed from module data. Music commands route WAV, or Ogg playback to soft-reserved channel 0.
The audio system has three main parts:
- Sample - Loaded audio data or streaming metadata identified by a sample ID.
- Playback - One use of a sample, identified by a unique play ID and optionally associated with an entity owner and sound groups.
- Channel - Mixer slot that owns the current playback state.
Keeping these concepts separate is important. One loaded sample may play on several channels at once, and every playback receives its own play ID.
Formats
| Format | Channels | Source quality | Resident sample | Streamed sample |
|---|---|---|---|---|
| PCM WAV | Mono or stereo | 8, 16, or 24-bit PCM 11.025 through 48 kHz supported |
Yes | Yes |
Ogg Vorbis (.ogg or .oga)
|
Mono or stereo | 11.025 through 48 kHz Decoded to 16-bit PCM |
Yes | Yes |
WAV, and Ogg, sample types are identified by their file signatures rather than the filename extension alone. Valid WAV data begins with RIFF, and valid Ogg data begins with OggS. This permits alternate extensions or extensionless paths when the containing command supports them.
Output
OpenBOR outputs stereo audio at 24-bit and 48 kHz. Source files up to these limits retain their native precision and sample rate through the mixer without mandatory downsampling. When a device cannot open the preferred format, OpenBOR automatically selects the closest compatible output.
Recommendations
- Use WAV for short effects, low decoding overhead, or exact uncompressed PCM.
- Use Ogg for long sounds where compressed module size is useful.
- Use 22.05 kHz for compact general-purpose effects when high-frequency detail is unnecessary.
- Use 44.1 or 48 kHz for music, voice, or effects that benefit from higher-frequency detail.
- Use mono when a sound will be positioned with separate left and right channel volumes.
- Use stereo when the source itself contains important left and right information.
24-bit
Don’t use 24-bit files merely to have 24-bit files. Most playback electronics can process 24-bit audio, but few speaker systems can physically reproduce its full dynamic range. Typical human hearing and normal listening environments make the difference effectively inaudible, particularly within a busy game mix.
OpenBOR fully supports 24-bit assets, but getting the benefit from them requires extremely high-fidelity equipment, a quiet listening environment, and a sparse audio mix. For nearly all in-game use cases, CD-quality audio (16-bit, 44.1 kHz) provides the highest practical level of fidelity.
Resident and streamed samples
WAV and Ogg samples support resident or streamed storage. The stream argument of loadsample() selects the mode.
| Mode | stream
|
Load behavior | Playback behavior | Best use |
|---|---|---|---|---|
| Resident | 0
|
Loads PCM into memory. Ogg data is decoded during loading. | Reads directly from the sample cache. | Short or frequently reused effects. |
| Streamed | 1
|
Loads and validates metadata only. | Reads or decodes incrementally through rotating PCM buffers. | Music, ambience, speech, and long or infrequently used effects. |
The same WAV or Ogg source file may exist in both modes at once. Resident and streamed forms have separate cache identities and therefore separate sample IDs. Loading the same file repeatedly in the same mode returns its existing sample ID.
Stream behavior
Each active streamed playback owns its file position and, for Ogg, its decoder state. Several channels may therefore stream the same or different files independently.
Each streamed channel retains four rotating buffers of 16 KiB each. At the largest supported WAV input format - 48 kHz, stereo, 24-bit - each buffer holds approximately 57 milliseconds of audio. Total reserve is therefore approximately 228 milliseconds per active stream at that format.
Producer-fed streams use the same rotating queue when the final frame count is not known in advance. WebM audio follows this path: its decoder thread publishes 16-bit PCM blocks while the ordinary channel mixer consumes them.
File reads, seeks, decoding setup, allocation, and cleanup occur outside the audio callback. Streamed channels are refilled fairly in round-robin order under a per-update work budget. If storage cannot supply data in time, the mixer outputs silence without advancing past unheard audio.
Stream capacity is determined by available memory, storage throughput, platform file resources, and the sound channel pool.
Capacity
With the modern PAK64 format, OpenBOR does not impose a universal fixed size limit on sound assets. Internal stream positions, frame counts, and file offsets use 64-bit values wherever the source decoder supports them, providing theoretical bounds measured in exabytes - far beyond practical hardware limits. Actual capacity is therefore determined by the source format, decoder interface, available memory, storage space, and platform resources.
Cached samples are decoded and retained in memory, so their practical size is limited by available RAM. Streamed samples retain only metadata and a fixed set of rotating PCM buffers, allowing very large assets to play without loading the entire file into memory.
Classic RIFF/WAVE format uses 32-bit chunk sizes, limiting WAV files to just under 4 GiB.
OGG and OGA files have a platform-dependent limit. As of Windows 11, the Vorbis decoder reports file positions through a 32-bit long. This limits OGG and OGA assets to 2,147,483,647 bytes - one byte under 2 GiB. The 2 GiB Vorbis limit does not apply to LP64 targets such as 64-bit Linux, where long is 64-bit.
OpenBOR imposes no limit on the combined size of all sound assets; total capacity is constrained only by available RAM and storage hardware.
Sound channels
OpenBOR exposes channel numbers from 0 through 4095. Internally, channels use 64 banks of 64 channels each.
- Bank 0, containing channels 0 through 63, is allocated during sound startup.
- Additional banks are allocated in groups of 64 when existing automatic channels are occupied.
- Allocated banks remain available until sound shutdown.
- Finished or stopped playback immediately frees its channel for reuse.
- 64-bit state masks allow the mixer to skip inactive banks and channels.
Ordinary workloads therefore pay for active playback rather than the full 4,096-channel capacity.
Channel allocation
Automatic playback selects the lowest available non-reserved channel. If every allocated bank is full, OpenBOR allocates the next bank and continues. Priority replacement is considered only when no further automatic channel can be supplied, including allocation failure or exhaustion of the 4,096-channel address space.
When replacement is required, OpenBOR finds the lowest-priority active non-reserved channel:
- Playback replaces that channel when the new priority is equal to or greater than the existing priority.
- Playback fails and returns
-1when every replaceable channel has a greater priority.
Priority normally has no effect because almost all projects remain below the available channel capacity.
Channel 0 and music
Channel 0 is soft-reserved as conventional music and media space.
- Automatic sample allocation skips channel 0.
- Priority replacement skips channel 0.
- WAV, and Ogg music explicitly target channel 0.
- Starting new music replaces the current channel 0 playback.
- WebM files with an audio track stop channel 0 and all active non-reserved sample channels before opening a producer-fed PCM stream on channel 0.
- WebM files without an audio track leave existing channel 0 playback running.
- Stopping music leaves channel 0 idle but still reserved from automatic allocation.
- Engine playback paths that explicitly target a channel may use channel 0 because the reservation is an allocator exclusion, not ownership protection.
- The next music request or WebM audio stream replaces anything explicitly placed on channel 0.
Bank 0 therefore provides one conventional music channel plus 63 ordinary automatic channels.
WAV, Ogg, and WebM audio all use the general channel mixer. WebM supplies decoded PCM through a generic live-producer queue.
Practical Limits
The 4,096-channel capacity was neither chosen as a game-design goal nor intended to represent a realistic level of polyphonic mixing. It is simply a byproduct of OpenBOR's highly efficient, mask-friendly organization of 64 banks with 64 channels each. This layout maps naturally to CPU-friendly 64-bit state masks and also happens to produce an absurdly large sound-channel ceiling.
Practical polyphonic mixing is inherently constrained by frequency overlap, auditory masking, finite output headroom, and the limits of human hearing. Once even a few dozen sounds play simultaneously, they may collapse into an indecipherable, noise-like audio slurry.
This is not a limitation of OpenBOR's mixer, but an inherent property of sound reproduction and human perception. The large channel pool eliminates arbitrary channel exhaustion and accommodates unusual workloads, while effective sound design should still prioritize clarity.
Music
Music commands use streamed playback on channel 0. OpenBOR first tries the supplied path exactly. If no playable file is found, it appends extensions in this order:
.ogg.oga.wav
Providing an extension explicitly avoids fallback ambiguity.
Loop offsets
Music offsets select the position used when automatic looping returns from the end of the track. The initial pass still begins at the start.
| Music format | Offset unit | Behavior |
|---|---|---|
| WAV or Ogg | PCM frame | Restarts automatic looping at the selected frame. |
One PCM frame contains every spatial sample for one moment in time. Mono contains one scalar sample per frame, while stereo contains a left and right pair. To convert seconds to a PCM-frame offset:
frame offset = seconds * sample rate
For example, one second into a 48 kHz WAV or Ogg file is frame 48000.
Native sound playback
Native model and engine sounds use the unified sample loader. Animation frame sounds may cache or stream WAV and Ogg sources.
Predefined sounds
OpenBOR loads the following conventional sound paths during startup and plays them in response to game conditions. Replace a file to customize the effect. You may provide a blank file or no file at all if you do not wish the predefined sound to play.
| Path | Event |
|---|---|
data/sounds/beat1.wav
|
Default attack impact. Playback speed may vary with damage unless noslowfx is enabled.
|
data/sounds/block.wav
|
Default blocked-attack impact. |
data/sounds/fall.wav
|
Entity landing after a knockdown. |
data/sounds/get.wav
|
Normal item pickup. |
data/sounds/money.wav
|
Score or money pickup. |
data/sounds/jump.wav
|
Jump action. |
data/sounds/indirect.wav
|
Indirect collision caused by a thrown or blasted entity. |
data/sounds/punch.wav
|
Default standing attack-chain sound, commonly heard on a miss. |
data/sounds/1up.wav
|
Player gains an extra life. |
data/sounds/go.wav
|
Wait completion and movement prompt. |
data/sounds/timeover.wav
|
Timer expiration or loss of all credits. |
data/sounds/beep.wav
|
Menu navigation. |
data/sounds/beep2.wav
|
Menu selection. |
data/sounds/pause.wav
|
Pause action. beep2.wav is used as fallback when unavailable.
|
data/sounds/bike.wav
|
Biker type entity spawns. |
Tip: If it isn't obvious, many of these are for legacy support and quick start boilerplate. You will probably want to forgo some of them (ex: punch, fall) and use model commands or script to play your own effects instead.
Model commands
diesound
diesound <path>
# Default
diesound none
Sets the resident sample played when the entity is defeated by damage, including special defeat sources such as pits or lifespan expiration.
Animation frame sounds
Animation frames support up to 64 indexed sound entries. Sound commands placed before a frame configure that frame only. Entering the frame applies frame-level sound actions in declaration order, then processes every configured entry according to its channel, priority, group, loading, offset, looping, delay, chance, and random-selection settings.
Each frame sound receives the unique ID of the entity entering the frame. This ownership remains attached to the mixer channel even if the entity changes animation or the sound continues playing after the frame ends. Group actions can therefore control related sounds from one entity without affecting sounds in the same groups that belong to other entities.
Sound groups use a 64-bit mask with 52 creator groups arranged as a through z and a1 through z1. One sound may belong to several groups. Group actions match when at least one requested group overlaps the channel's group mask.
| Name | Script constant | Description |
|---|---|---|
none
|
SOUND_GROUP_NONE
|
No group. |
a through z
|
SOUND_GROUP_A through SOUND_GROUP_Z
|
First bank of 26 creator groups. |
a1 through z1
|
SOUND_GROUP_A1 through SOUND_GROUP_Z1
|
Second bank of 26 creator groups. |
all0
|
SOUND_GROUP_ALL_0
|
Every group from a through z.
|
all1
|
SOUND_GROUP_ALL_1
|
Every group from a1 through z1.
|
all
|
SOUND_GROUP_ALL
|
All 52 creator groups. |
sound.index
Selects the sound entry modified by subsequent indexed sound commands.
sound.index {int index}
# Default
sound.index 0
- Accepts an integer index from
0through63. - Indexed commands apply to the selected entry until another
sound.indexcommand changes the selection. - The selected index resets to
0after eachframecommand. - Repeating a property command for the same entry replaces that property.
sound
Assigns the sample source for the selected sound entry.
sound {string path|none}
# Default
# No active sound entry
- Accepts WAV and Ogg sources.
nonecreates an active silent entry. Silent entries can participate insound.randomselections.- Failed sample loads do not create active entries.
- Supplying another
soundcommand replaces the selected entry's source.
sound.loading
Selects how OpenBOR stores and supplies the selected sound entry.
sound.loading {cache|stream}
# Default
sound.loading cache
cachedecodes and retains PCM data in memory.streamretains source metadata and reads or decodes through rotating PCM buffers during playback.- The command may appear before or after
sound. OpenBOR resolves the source and loading mode when it reaches theframecommand.
sound.channel_set
Selects the mixer channel used by the selected sound entry.
sound.channel_set {int channel|auto}
# Default
sound.channel_set auto
autouses normal channel allocation.- Numeric values force playback onto a channel from
0through4095. - Forced playback remains subject to
sound.channel_priority. An active channel is replaced only when the new sound's priority is equal to or greater than the current priority. - Channel
0is soft-reserved for music and may be targeted explicitly. Its usual music priority prevents replacement by lower-priority frame sounds. - Replacing an active channel stops its current playback and releases any stream resources before the new sound begins.
sound.channel_priority
Sets the allocation and replacement priority of the selected sound entry.
sound.channel_priority {uint priority}
# Default
sound.channel_priority 0
- Accepts an unsigned integer from
0through4294967295. - Forced channels reject the new sound when the active occupant has a higher priority.
- Equal priority permits replacement.
- Automatic allocation uses priority when every eligible channel is occupied. The new sound may replace the lowest-priority channel when its priority is equal or greater.
sound.group
Assigns one or more groups to the selected sound entry.
sound.group {group...}
# Default
sound.group a
- Accepts
none,all,all0,all1,athroughz, anda1throughz1. - Multiple arguments combine into one group mask. For example,
sound.group a c h1assigns all three groups. - Group names are case-insensitive.
- Every frame sound defaults to group
a. noneleaves the playback ungrouped.- The command modifies the selected
sound.indexentry. - Group membership and the acting entity's unique ID are stored on the acquired mixer channel when playback is submitted.
sound.loop
Selects whether the selected sound entry stops at the source end or restarts automatically.
sound.loop {int enable}
# Default
sound.loop 0
- Accepts
0or1. 0plays the source once.1restarts playback fromsound.loop_offsetafter reaching the source end.- Looping belongs to the mixer channel after the frame is entered. Changing the entity's animation or removing the entity does not stop the channel.
- Entering the frame again starts another playback. Use a one-time frame trigger unless overlapping loops are intended.
- Scripts may stop or modify playback through the ordinary channel and sound-object APIs.
sound.loop_offset
Sets the PCM frame used when automatic looping restarts the selected sound entry.
sound.loop_offset {uint64 frame}
# Default
sound.loop_offset 0
- Applies only when
sound.loopis1. - Uses complete PCM-frame indexes. Each PCM frame contains every spatial sample for one moment in time.
- Accepts an unsigned 64-bit value within the source's frame count.
- The selected frame must identify a seekable position.
- Playback does not start when an applicable loop offset is outside the source or cannot be represented by its decoder.
sound.start_offset
Sets the PCM frame used once when playback of the selected sound entry begins.
sound.start_offset {uint64 frame|auto}
# Default
sound.start_offset auto
autoderives the initial PCM frame from the acquired mixer channel, the source's spatial channel count, and its frame count. This phase staggering helps prevent otherwise identical simultaneous sounds from beginning at exactly the same sample position.- Supplying
0starts playback at the first PCM frame. - Numeric values use complete PCM-frame indexes and apply only to the initial pass. Automatic looping restarts from
sound.loop_offset. - Numeric values must fall within the source's frame count and identify a seekable position.
- Playback does not start when a numeric start offset is outside the source or cannot be represented by its decoder.
sound.delay
Sets how long the selected sound entry waits before becoming eligible to play.
sound.delay {uint64 ticks}
# Default
sound.delay 0
- Accepts an unsigned 64-bit number of logical clock ticks.
- The mixer channel owns the delay after the animation frame is entered.
sound.chanceis evaluated after the delay expires.
sound.chance
Sets the probability that the selected sound entry plays after its delay expires.
sound.chance {int chance}
# Default
sound.chance 100
- Accepts an integer percentage from
0through100. - Chance is evaluated once when
sound.delayexpires. - Failed rolls release the mixer channel without emitting audio.
- Playback chance is independent from
sound.randomselection.
sound.random
Selects one configured sound entry from an inclusive index range when the animation frame is entered.
sound.random {int minimum index} {int maximum index}
# Default
# No random range
- Accepts ascending indexes from
0through63. - Selection is uniform among configured entries inside the range.
- Unused or failed entries inside the range are ignored.
- Successfully loaded sources and explicit
sound noneentries count as configured candidates. - Sounds outside the range play normally and apply their own delay and chance.
- The command applies to the whole animation frame and does not depend on
sound.index. - The last
sound.randomcommand supplied before the frame takes precedence. - Random selection occurs before
sound.delayandsound.chanceprocessing.
sound.channel_stop
Stops playback on a mixer channel and frees the channel for reuse.
sound.channel_stop {int channel}
# No default
- Accepts a channel from
0through4095. - Closes resident or streamed playback and releases stream resources.
- Has no effect when the selected channel is inactive.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.channel_pause
Pauses playback on a mixer channel without releasing it.
sound.channel_pause {int channel}
# No default
- Accepts a channel from
0through4095. - Preserves the current sample position and channel properties.
- Has no effect when the selected channel is inactive.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.channel_resume
Resumes playback on a paused mixer channel.
sound.channel_resume {int channel}
# No default
- Accepts a channel from
0through4095. - Has no effect when the selected channel is inactive or already playing.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.channel_offset
Seeks an active mixer channel to a PCM frame without changing its loop offset.
sound.channel_offset {int channel} {uint64 offset}
# No default
- Accepts a channel from
0through4095and an unsigned 64-bit PCM-frame offset. - Supports resident and streamed playback.
- The offset must fall within the source's frame count and identify a seekable position.
- Has no effect when the channel is inactive or the requested offset is invalid for its source.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.group_stop
Stops active channels in one or more groups that belong to the entity entering the animation frame.
sound.group_stop {group...}
# No default
- Accepts one or more sound group names.
- Stops and frees every owned channel sharing at least one requested group.
- Releases streaming resources for matching streamed playback.
- Does not affect matching groups owned by other entities.
- Has no effect when no active owned channel matches.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.group_pause
Pauses active channels in one or more groups that belong to the entity entering the animation frame.
sound.group_pause {group...}
# No default
- Accepts one or more sound group names.
- Pauses every owned channel sharing at least one requested group.
- Preserves playback position, looping, and channel properties.
- Does not affect matching groups owned by other entities.
- Has no effect when no active owned channel matches.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.group_resume
Resumes paused channels in one or more groups that belong to the entity entering the animation frame.
sound.group_resume {group...}
# No default
- Accepts one or more sound group names.
- Resumes every owned channel sharing at least one requested group.
- Does not affect matching groups owned by other entities.
- Has no effect when no active owned channel matches.
- Applies to the animation frame rather than the selected
sound.indexentry.
sound.group_offset
Seeks active channels in one or more groups belonging to the entity entering the animation frame.
sound.group_offset {group...} {uint64 offset}
# No default
- Accepts one or more sound group names followed by an unsigned 64-bit PCM-frame offset.
- Seeks every owned channel sharing at least one requested group.
- Supports resident and sample-backed streamed playback.
- The offset must fall within each source's frame count and identify a seekable position.
- Invalid or non-seekable playback remains unchanged.
- Does not affect matching groups owned by other entities.
- Applies to the animation frame rather than the selected
sound.indexentry.
Channel and group actions retain declaration order. OpenBOR applies every action before submitting new sounds for the frame. This allows a frame to stop an old channel occupant and then replace it through sound.channel_set, or stop an entity's previous grouped sound before starting a replacement. Explicit channel actions may control soft-reserved channel 0, including music or WebM audio routed there. Group actions remain restricted to matching group and owner IDs.
All sound entries, properties, frame-level actions, random-selection data, and the selected index reset after the frame command.
Examples
Forced channel replacement
This example stops the previous occupant of channel 12 before playing the selected sound on that channel. The explicit stop action runs before new frame sounds are submitted.
anim channel_replace
sound.channel_stop 12
sound.index 0
sound data/sounds/announcement.ogg
sound.loading stream
sound.channel_set 12
sound.channel_priority 100
frame data/chars/example/channel_replace_0.png
Existing channel control
This example pauses channel 12, seeks it to PCM frame 48000, and resumes playback. Channel actions run in the order supplied.
anim channel_seek
sound.channel_pause 12
sound.channel_offset 12 48000
sound.channel_resume 12
frame data/chars/example/channel_seek_0.png
Multiple sounds
Both sounds are processed when frame 0 is entered. Index 0 plays immediately from cached PCM. Index 1 streams its source, waits 10 logical ticks, then has a 50 percent chance to play.
anim attack1
sound.index 0
sound data/sounds/swing.wav
sound.index 1
sound data/sounds/voice.ogg
sound.loading stream
sound.delay 10
sound.chance 50
frame data/chars/example/attack1_0.png
Streamed loop
This example starts a 48 kHz ambience source at its first PCM frame. When playback reaches the end, it returns to frame 48000 - one second into the source.
anim ambience
sound.index 0
sound data/sounds/ambience.ogg
sound.loading stream
sound.loop 1
sound.start_offset 0
sound.loop_offset 48000
frame data/chars/example/ambience_0.png
Random sound
One configured entry from indexes 0 through 3 is selected whenever the frame is entered. Each of the four entries has equal selection weight, so index 1 provides a 25 percent silent result. Index 4 lies outside the random range and always plays.
sound.random 0 3
sound.index 0
sound data/sounds/step1.wav
sound.index 1
sound none
sound.index 2
sound data/sounds/step2.wav
sound.index 3
sound data/sounds/step3.wav
sound.index 4
sound data/sounds/cloth.wav
frame data/chars/example/walk_0.png
Entity-owned voice interruption
This attack voice is assigned to group b. Entering the pain animation stops group b sounds owned by the injured entity without silencing voices from other entities.
anim attack1
sound.index 0
sound data/sounds/attack_voice.ogg
sound.loading stream
sound.group b
frame data/chars/example/attack1_0.png
anim pain1
sound.group_stop b
frame data/chars/example/pain1_0.png
hitfx and blockfx
hitfx <path>
blockfx <path>
These commands assign resident samples to attack collision data. hitfx plays when the attack hits, while blockfx plays when the attack is blocked. Collision-property command names expose the same sound assignments.
Other native systems may select sounds for their own events. Relevant feature articles document those event-specific commands.
Playing hit sounds through a flash entity provides centralized control when several attacks share the same effects.
Script playback
loadsample()
Loads a resident sample or streaming metadata and returns its sample ID.
int sample_id = loadsample(
string filename,
int log_errors,
int stream
);
| Argument | Required | Default | Description |
|---|---|---|---|
filename
|
Yes | None | Full module path to a WAV or Ogg, source. |
log_errors
|
No | 0
|
Nonzero writes failed load attempts to the log. |
stream
|
No | 0
|
0 loads resident PCM. Nonzero loads metadata for streamed playback.
|
Returns -1 when loading fails. Valid sample IDs are zero or greater.
Repeated calls for the same filename and storage mode return the existing sample ID. Resident and streamed forms of the same WAV or Ogg filename receive different IDs.
playsample()
Starts a sample and returns the selected channel.
int channel = playsample(
int sample_id,
unsigned int priority,
int volume_left,
int volume_right,
unsigned int speed,
int loop,
mixed start_offset,
mixed loop_offset
);
| Argument | Required | Default | Description |
|---|---|---|---|
sample_id
|
Yes | None | Sample ID returned by loadsample() or another engine source.
|
priority
|
No | 0
|
Replacement priority used only when no automatic channel can be supplied. |
volume_left
|
No | Current effect volume | Left output level. Values are clamped from 0 through 100. |
volume_right
|
No | Current effect volume | Right output level. Values are clamped from 0 through 100. |
speed
|
No | 100
|
Playback speed and pitch percentage. Values below 1 use 100. |
loop
|
No | 0
|
Nonzero repeats playback until stopped. |
start_offset
|
No | Channel-based start | PCM frame used once when playback begins. |
loop_offset
|
No | 0
|
PCM frame used when automatic looping restarts after the end. |
Returns -1 when playback cannot start. Otherwise, the result is a flattened channel number from 0 through 4095.
Offsets apply equally to resident and streamed WAV or Ogg samples. Every playsample() offset uses PCM frames. start_offset never changes later loop behavior, while loop_offset never changes the initial start. Passing an explicit start offset of 0 guarantees playback begins at the first frame. Omitting it uses channel-based phase staggering.
The loop offset matters only when loop is nonzero. Both offsets must be smaller than the sample's PCM frame count.
Resident example
int sample_id = loadsample("data/sounds/voice.ogg", 1, 0);
int channel = playsample(sample_id, 0, 100, 100, 100, 0, 0, 0);
Streamed loop example
This example begins at frame zero and loops to one second into a 48 kHz ambience track.
int sample_id = loadsample("data/sounds/ambience.ogg", 1, 1);
int channel = playsample(sample_id, 0, 100, 100, 100, 1, 0, 48000);
Playback and channel control
isactivesample()
int active = isactivesample(int channel);
Returns 1 when the channel contains active playback, or 0 otherwise.
sampleid()
int play_id = sampleid(int channel);
Returns the channel's unique play ID, or -1 when the channel is inactive. Use SOUND_PROPERTY_SAMPLE when the cached sample ID is required.
querychannel()
int channel = querychannel(int play_id);
Returns the active channel containing the supplied play ID, or -1 when that playback is no longer active. This is the inverse lookup for sampleid().
stopchannel()
stopchannel(int channel);
Stops playback on the supplied channel. Stream handles and decoder state are closed outside the audio callback.
pausesample()
pausesample(int toggle, int channel);
Pauses a single active channel when toggle is nonzero and resumes it when zero.
pausesamples()
pausesamples(int toggle);
Pauses or resumes all active non-reserved sample channels. Soft-reserved channel 0 is controlled explicitly with pausemusic() or the sound object API.
unloadsample()
unloadsample(int sample_id);
Releases resident PCM or streamed metadata for the supplied sample ID. The cache identity remains available, so a later loadsample() call for the same filename and mode restores the sample under the same ID when possible.
Avoid unloading a sample while it is still needed by active playback.
Sound group control
Sound group functions operate on active mixer channels by group mask and owner ID. A channel matches when it shares at least one requested group and its owner ID equals the supplied value. Passing ENTITY_UNIQUE_ID_ALL selects matching groups from every owner.
Frame sounds store the unique ID of the entity that submitted them. Playback started without entity ownership uses owner ID 0 and group SOUND_GROUP_NONE. Scripts may read an entity's unique ID through ENTITY_PROPERTY_UNIQUE_ID and may inspect a channel through SOUND_PROPERTY_OWNER_ID and SOUND_PROPERTY_GROUP.
Group arguments use the unsigned 64-bit SOUND_GROUP_* constants. Combine several groups with the bitwise OR operator. Masks containing bits outside SOUND_GROUP_ALL are rejected.
| Constant | Description |
|---|---|
SOUND_GROUP_NONE
|
No group. Group functions receive no matches from this mask. |
SOUND_GROUP_DEFAULT
|
Default frame-sound group. Equivalent to SOUND_GROUP_A.
|
SOUND_GROUP_A through SOUND_GROUP_Z
|
Individual groups corresponding to model command names a through z.
|
SOUND_GROUP_A1 through SOUND_GROUP_Z1
|
Individual groups corresponding to model command names a1 through z1.
|
SOUND_GROUP_ALL_0
|
Every group from SOUND_GROUP_A through SOUND_GROUP_Z.
|
SOUND_GROUP_ALL_1
|
Every group from SOUND_GROUP_A1 through SOUND_GROUP_Z1.
|
SOUND_GROUP_ALL
|
All 52 creator groups. |
ENTITY_UNIQUE_ID_ALL
|
Owner wildcard. Matches every owner ID, including unowned playback. |
sound_group_stop()
Stops and frees every active channel matching the supplied groups and owner.
int affected = sound_group_stop(
uint64 group,
uint64 owner_id
);
groupis one or moreSOUND_GROUP_*masks.owner_idselects one entity owner.ENTITY_UNIQUE_ID_ALLselects every owner.- Streaming resources belonging to stopped channels are released.
- Returns the number of channels stopped.
sound_group_pause()
Pauses every active channel matching the supplied groups and owner.
int affected = sound_group_pause(
uint64 group,
uint64 owner_id
);
- Preserves playback position, looping, and channel properties.
- Returns the number of matching active channels processed.
sound_group_resume()
Resumes every active channel matching the supplied groups and owner.
int affected = sound_group_resume(
uint64 group,
uint64 owner_id
);
- Uses the same group and owner filtering rules as
sound_group_pause(). - Returns the number of matching active channels processed.
sound_group_offset()
Seeks every matching channel to the supplied PCM frame without changing its loop offset.
int affected = sound_group_offset(
uint64 group,
uint64 owner_id,
uint64 offset
);
- Supports resident and sample-backed streamed playback.
- Each source must contain the requested PCM frame and provide a seekable position.
- Producer-fed streams such as WebM cannot seek and remain unchanged.
- Returns the number of channels successfully repositioned.
Entity-owned group example
This example stops groups b and c for the current entity only. Other entities using either group continue playing.
void entity = getlocalvar("self");
int owner_id = get_entity_property(
entity,
openborconstant("ENTITY_PROPERTY_UNIQUE_ID")
);
int groups =
openborconstant("SOUND_GROUP_B") |
openborconstant("SOUND_GROUP_C");
int stopped = sound_group_stop(groups, owner_id);
Global group example
This example pauses group a1 regardless of ownership.
int group = openborconstant("SOUND_GROUP_A1");
int every_owner = openborconstant("ENTITY_UNIQUE_ID_ALL");
int paused = sound_group_pause(group, every_owner);
Music script interface
Music functions control streamed WAV and Ogg playback on channel 0.
playmusic()
playmusic(string filename, int loop, int loop_offset);
Starts music using the extension search order described above. Calling playmusic() without arguments stops current music.
The optional offset is an automatic-loop position using PCM frames.
fademusic()
fademusic(float fade);
fademusic(float fade, string next_music, int loop, int loop_offset);
Reduces the current music volume by fade during each music fade update. The four-argument form begins the supplied music after volume reaches zero.
setmusicvolume()
setmusicvolume(int volume_left, int volume_right);
Sets music left and right volume. Omitting volume_right uses the left value for both sides. Normal project values are 0 through 100, while supplied values are clamped from 0 through 800.
setmusictempo()
setmusictempo(int tempo);
Sets music playback speed and pitch as a percentage. 100 is normal playback.
pausemusic()
pausemusic(int toggle);
Pauses current music when toggle is nonzero and resumes it when zero.
WebM audio
WebM files with an embedded Vorbis audio track decode into a generic producer-fed PCM stream on channel 0. Before opening the stream, WebM stops current channel 0 playback and all active non-reserved sample channels.
Each WebM stream receives a play ID. Queue publication and cleanup verify that ID so a finishing decoder cannot close later replacement playback on channel 0.
WebM files without an audio track leave existing channel 0 playback running and continue its stream updates during video playback. WebM audio targets channel 0 automatically.
Sound object script API
Advanced scripts may inspect the channel pool, obtain a stable sound object pointer, and read or change playback properties.
Music and WebM playback use ordinary sound channel objects. Scripts inspect conventional music space with get_sound_channel_object(0).
Channel banks are retained after allocation, so a sound object pointer remains structurally valid until sound shutdown. The channel may be reused for later playback, however. Store the play ID when a script must verify that a pointer still represents the same playback instance.
Use named constants from openborconstant(). Raw numeric property and mask IDs are implementation details.
Bank masks
mixed mask = get_sound_channel_bank_mask(int mask_type);
Each returned bit represents one bank. Bit 0 represents bank 0, bit 1 represents bank 1, and so on.
| Mask constant | Description |
|---|---|
SOUND_CHANNEL_BANK_MASK_ALLOCATED
|
Banks whose 64-channel storage has been allocated. |
SOUND_CHANNEL_BANK_MASK_ACTIVE
|
Banks containing at least one active channel. |
SOUND_CHANNEL_BANK_MASK_AVAILABLE
|
Allocated banks containing at least one channel available to automatic playback. |
SOUND_CHANNEL_BANK_MASK_STREAMING
|
Banks containing at least one channel with live streaming state. |
Channel masks
mixed mask = get_sound_channel_mask(
int bank,
int mask_type
);
Each returned bit represents one slot in the selected 64-channel bank. Convert a bank and local bit position to the public channel number with:
channel = bank * 64 + bit
Unallocated banks return zero for every channel mask.
| Mask constant | Description |
|---|---|
SOUND_CHANNEL_MASK_ACTIVE
|
Channels currently playing or looping. |
SOUND_CHANNEL_MASK_PAUSED
|
Active channels currently paused. |
SOUND_CHANNEL_MASK_RESERVED
|
Channels excluded from automatic allocation and priority replacement. Channel 0 is reserved by default. |
SOUND_CHANNEL_MASK_STREAMING
|
Channels owning a live stream queue, handle, or decoder. A finished stream may retain this bit briefly until safe main-thread cleanup. |
Mask example
int active_id = openborconstant("SOUND_CHANNEL_MASK_ACTIVE");
mixed bank_zero_active = get_sound_channel_mask(0, active_id);
Channel objects
get_sound_channel_object()
void sound = get_sound_channel_object(int channel);
Returns the stable sound object at a flattened channel index. Valid indexes are 0 through 4095. An unallocated channel returns an empty value.
Allocated but inactive channels still have objects. Test SOUND_PROPERTY_ACTIVE or the active mask before treating an object as current playback.
get_sound_channel_index()
int channel = get_sound_channel_index(void sound);
Validates a sound object pointer and returns its flattened channel index.
Property access
mixed value = get_sound_property(
void sound,
int property
);
set_sound_property(
void sound,
int property,
mixed value
);
Playback-sensitive changes use synchronization helpers. Changing the position or loop offset of an active sample-backed stream discards and rebuilds prefetched buffers so playback resumes from coherent data.
Producer-fed streams such as WebM do not expose a cached sample or seekable source buffer. Their common channel state remains readable, but sample-dependent position or loop writes are rejected. Internal rotating buffers are never exposed through the script API.
| Property constant | Type | Access | Description |
|---|---|---|---|
SOUND_PROPERTY_ACTIVE
|
Integer | Read | Activity state: 0 inactive, 1 one-shot playback, 2 looping playback.
|
SOUND_PROPERTY_CHANNEL
|
Integer | Read | Flattened channel index from 0 through 4095. |
SOUND_PROPERTY_CHANNELS
|
Integer | Read | Source channel count: 1 mono or 2 stereo.
|
SOUND_PROPERTY_GROUP
|
Unsigned 64-bit integer | Read | Sound group mask assigned when playback was submitted. Zero indicates ungrouped playback. |
SOUND_PROPERTY_LOOP_OFFSET
|
Unsigned 64-bit integer | Read/write | PCM frame used when automatic looping restarts. New values must be within a sample-backed source. Producer-fed streams reject writes. |
SOUND_PROPERTY_OWNER_ID
|
Unsigned 64-bit integer | Read | Unique ID of the entity that submitted the playback. Zero indicates no entity owner. |
SOUND_PROPERTY_PAUSED
|
Integer | Read/write | Pause state. Writes update the authoritative paused mask. |
SOUND_PROPERTY_PERIOD
|
Unsigned 64-bit integer | Read/write | Raw fixed-point PCM frames advanced per output frame, with 16 fractional bits. Zero is rejected. This is lower-level than the playsample() speed percentage.
|
SOUND_PROPERTY_PLAY_ID
|
Integer | Read | Unique ID for the current playback instance. |
SOUND_PROPERTY_PRIORITY
|
Unsigned 64-bit integer | Read/write | Nonnegative replacement priority. Writes must fit the channel's unsigned integer priority storage. |
SOUND_PROPERTY_SAMPLE
|
Integer | Read | Cached sample ID used by the channel, or -1 for a producer-fed stream such as WebM.
|
SOUND_PROPERTY_SAMPLE_POSITION
|
Unsigned 64-bit integer | Read/write | Current PCM frame. Writing seeks resident or sample-backed streamed playback without changing the loop offset. Producer-fed streams reject writes. |
SOUND_PROPERTY_VOLUME_DIVISOR
|
Integer | Read/write | Gain divisor. Values must be 1 or greater. Lower values increase gain and may cause clipping. |
SOUND_PROPERTY_VOLUME_LEFT
|
Integer | Read/write | Left volume. Property writes clamp values from 0 through 800. |
SOUND_PROPERTY_VOLUME_RIGHT
|
Integer | Read/write | Right volume. Property writes clamp values from 0 through 800. |
Property example
void sound = get_sound_channel_object(channel);
if(sound)
{
int active = get_sound_property(
sound,
openborconstant("SOUND_PROPERTY_ACTIVE")
);
if(active)
{
set_sound_property(
sound,
openborconstant("SOUND_PROPERTY_VOLUME_LEFT"),
30
);
set_sound_property(
sound,
openborconstant("SOUND_PROPERTY_VOLUME_RIGHT"),
100
);
}
}
Channel 0 access
Use get_sound_channel_object(0) and the SOUND_PROPERTY_* API to inspect or control current channel 0 playback. This applies equally to WAV, Ogg, and producer-fed WebM audio where the requested property is applicable.
Generic stream buffers remain engine-internal rather than script-visible playback state.
Related global variants
The following values are available through openborvariant():
| Variant | Description |
|---|---|
effectvol
|
Current effect volume used as the default left and right value for playsample().
|
musicvol
|
Current music volume. |
soundvol
|
Current master sound volume. |
maxsoundchannels
|
Maximum flattened sound channel capacity. Current value is 4096.
|
Practical guidance
- Preload frequently used resident effects before timing-sensitive gameplay.
- Stream long voice, ambience, and music that would waste memory when retained as decoded PCM.
- Keep short, frequently repeated effects resident to avoid unnecessary file and decoder work.
- Use explicit start offset
0when exact synchronization to the first PCM frame matters. - Store both channel and play ID when later logic must confirm the same playback is still present.
- Assign related frame sounds to groups when entity events should stop, pause, resume, or reposition them together.
- Use entity-owned group controls for voices, sustained attacks, movement loops, and similar sounds that should react to later animation states without affecting other entities.
- Use
ENTITY_UNIQUE_ID_ALLonly when a group action should intentionally cross entity ownership boundaries. - Prefer
stopchannel(),pausesample(), and property setters over direct assumptions about channel record state. - Treat channel masks as fast state summaries. Treat sound objects as reusable channel slots rather than permanent playback objects.
- Remember that channel 0 is conventional music space, not protected music space.