Audio Overview
OpenBOR uses a unified sample and channel system for sound effects, ambience, voice, and most music playback. WAV and Ogg Vorbis files may be retained in memory or streamed from module data. Music commands remain available as a legacy convenience interface and route WAV or Ogg playback through the ordinary sound channel system.
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.
- 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 | Legacy music |
|---|---|---|---|---|---|
| PCM WAV | Mono or stereo | 8, 16, or 24-bit PCM 11.025 through 48 kHz supported |
Yes | Yes | Yes |
Ogg Vorbis (.ogg or .oga)
|
Mono or stereo | 11.025 through 48 kHz Decoded to 16-bit PCM |
Yes | Yes | Yes |
BOR ADPCM (.bor)
|
Mono or stereo | 11.025 through 48 kHz | No | Legacy music producer | 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 currently outputs stereo audio at 16-bit and 44.1 kHz. Source files up to 24-bit and 48 kHz are retained at their source quality when loaded, then converted by the mixer during output. Higher input quality therefore remains available for future output upgrades without another asset conversion.
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.
Resident and streamed samples
WAV and Ogg samples support two storage modes. 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 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.
Current streamed channels retain 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 approximately 57 milliseconds multiplied by the buffer count.
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 not limited by the former eight-handle packfile table. Practical limits are available memory, storage throughput, platform file resources, and the sound channel pool.
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 for WAV and Ogg music played through the legacy music interface.
- Automatic sample allocation skips channel 0.
- Priority replacement skips channel 0.
- Legacy WAV and Ogg music explicitly targets channel 0.
- Starting new WAV or Ogg music replaces the current channel 0 playback.
- 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 legacy music request replaces anything explicitly placed on channel 0.
Bank 0 therefore provides one conventional music channel plus 63 ordinary automatic channels. Total physical capacity remains 4,096 channels.
BOR ADPCM music retains its legacy producer and compatibility channel. WAV and Ogg music use the general channel and streaming systems.
Music
Music commands are compatibility helpers over the audio system. OpenBOR first tries the supplied path exactly. If no playable file is found, it appends extensions in this order:
.bor.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. |
| BOR ADPCM | Encoded data byte | Restarts the legacy ADPCM stream at the selected byte offset. |
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.
BOR conversion
BOR is a legacy ADPCM music format retained for compatibility and low decoding cost. The wav2bor utility included with the OpenBOR development tools converts WAV sources to BOR music.
Use 16-bit PCM, mono or stereo source WAV files. A 22.05 kHz source remains a practical compatibility choice, while the current decoder accepts 11.025 through 48 kHz.
New projects may generally use Ogg instead unless BOR compatibility or its lower decoding overhead is specifically useful.
Native sound playback
Native model and engine sounds load as resident samples. WAV and Ogg sources are accepted through the same sample loader.
Predefined sounds
OpenBOR loads the following conventional sound paths during startup. Replace a file to customize the effect. Use a silent file when an engine-triggered sound should remain inaudible.
| Path | Typical use |
|---|---|
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
|
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 engine effect. |
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.
sound
sound <path>
# Default
sound none
Place sound immediately before an animation frame. The resident sample plays when the next frame is reached. One native sound is supported per frame. Script may start additional sounds when needed.
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. Newer 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.
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 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 | Legacy start behavior | 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. 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 preserves OpenBOR's legacy 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. Despite the legacy function name, this is not the cached sample ID. 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 sample channels.
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.
Music script interface
Legacy music functions remain available. WAV and Ogg calls use streamed sample playback on channel 0. BOR calls use the legacy ADPCM producer.
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. WAV and Ogg use PCM frames, while BOR uses encoded data bytes.
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 the compatibility path clamps extreme values to 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.
Sound object script API
Advanced scripts may inspect the channel pool, obtain a stable sound object pointer, and read or change playback properties. This API follows the same object and property pattern used by other OpenBOR script objects.
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 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 streamed sound discards and rebuilds prefetched buffers so playback resumes from coherent data.
| 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_LOOP_OFFSET
|
Unsigned 64-bit integer | Read/write | PCM frame used when automatic looping restarts. New values must be within the source. |
SOUND_PROPERTY_PAUSED
|
Integer | Read/write | Pause state. Writes update the authoritative paused mask. |
SOUND_PROPERTY_PERIOD
|
Unsigned 64-bit integer | Read/write | Raw 16.16 fixed-point PCM frames advanced per output frame. 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
|
Integer | Read/write | Nonnegative replacement priority. |
SOUND_PROPERTY_SAMPLE
|
Integer | Read | Cached sample ID used by the channel. |
SOUND_PROPERTY_SAMPLE_POSITION
|
Unsigned 64-bit integer | Read/write | Current PCM frame. Writing seeks resident or streamed playback without changing the loop offset. |
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. Writes use normal sample volume clamping from 0 through 100. |
SOUND_PROPERTY_VOLUME_RIGHT
|
Integer | Read/write | Right volume. Writes use normal sample volume clamping from 0 through 100. |
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
);
}
}
Legacy music channel object
The music_channel openborvariant and get_music_channel_property()/set_music_channel_property() remain available for compatibility. WAV and Ogg music synchronize a compatibility view from sound channel 0, while BOR ADPCM and video audio still use the legacy music producer.
New scripts controlling WAV or Ogg music should normally use get_sound_channel_object(0) and the SOUND_PROPERTY_* API. Legacy buffer pointers do not represent the rotating buffers owned by a generic streamed sound channel.
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.
|
music_channel
|
Pointer to the legacy music compatibility object. |
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.
- 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.