Jump to content

Audio Overview

From OpenBOR
(Redirected from Sound.group resume)

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 -1 when 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:

  1. .ogg
  2. .oga
  3. .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 0 through 63.
  • Indexed commands apply to the selected entry until another sound.index command changes the selection.
  • The selected index resets to 0 after each frame command.
  • 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.
  • none creates an active silent entry. Silent entries can participate in sound.random selections.
  • Failed sample loads do not create active entries.
  • Supplying another sound command 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
  • cache decodes and retains PCM data in memory.
  • stream retains 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 the frame command.
sound.channel_set

Selects the mixer channel used by the selected sound entry.

sound.channel_set {int channel|auto}

# Default
sound.channel_set auto
  • auto uses normal channel allocation.
  • Numeric values force playback onto a channel from 0 through 4095.
  • 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 0 is 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 0 through 4294967295.
  • 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, a through z, and a1 through z1.
  • Multiple arguments combine into one group mask. For example, sound.group a c h1 assigns all three groups.
  • Group names are case-insensitive.
  • Every frame sound defaults to group a.
  • none leaves the playback ungrouped.
  • The command modifies the selected sound.index entry.
  • 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 0 or 1.
  • 0 plays the source once.
  • 1 restarts playback from sound.loop_offset after 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.loop is 1.
  • 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
  • auto derives 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 0 starts 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.chance is 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 0 through 100.
  • Chance is evaluated once when sound.delay expires.
  • Failed rolls release the mixer channel without emitting audio.
  • Playback chance is independent from sound.random selection.
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 0 through 63.
  • Selection is uniform among configured entries inside the range.
  • Unused or failed entries inside the range are ignored.
  • Successfully loaded sources and explicit sound none entries 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.random command supplied before the frame takes precedence.
  • Random selection occurs before sound.delay and sound.chance processing.
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 0 through 4095.
  • 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.index entry.
sound.channel_pause

Pauses playback on a mixer channel without releasing it.

sound.channel_pause {int channel}

# No default
  • Accepts a channel from 0 through 4095.
  • 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.index entry.
sound.channel_resume

Resumes playback on a paused mixer channel.

sound.channel_resume {int channel}

# No default
  • Accepts a channel from 0 through 4095.
  • Has no effect when the selected channel is inactive or already playing.
  • Applies to the animation frame rather than the selected sound.index entry.
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 0 through 4095 and 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.index entry.
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.index entry.
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.index entry.
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.index entry.
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.index entry.

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
);
  • group is one or more SOUND_GROUP_* masks.
  • owner_id selects one entity owner. ENTITY_UNIQUE_ID_ALL selects 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.

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 0 when 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_ALL only 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.