At a glance

Files
audio.toml at the pack root (optional); [audio] in any map. Declares track and sound ids.
Assets
.wav (8/16/24/32-bit PCM, 32-bit float) and .ogg (Vorbis), mono or stereo, 8-96 kHz, at most 360 s.
Parser
legaia_expansion_format::{audio, audio_clip}.
Runtime
legaia_engine_expansion::audio; engine-core's world::expansion_audio; the mixer's PCM bus engine-audio::pcm_bus; one director, AudioDirector, on the native window and the browser page.
Sample
packs/sample/audio.toml

Music and sound references

FormPlays
{ kind = "bgm", id = 30 }A retail music cue by sound-test id (music tracks).
{ kind = "track", id = "tower_hum" }A pack track.
{ kind = "silence" }Nothing.
{ kind = "sfx", id = 0x21 }A retail sound cue (static table 0x000..0x100, or the base scene's runtime bank 0x200..=0x2FF) through the field's cue ring.
{ kind = "sound", id = "chime" }A pack sound on the PCM bus.

audio.toml

[tracks.echo_drone]
file = "audio/tower_drone.wav"   # relative to assets/
gain = 0.8                       # 0..=4 on top of normalisation
loop_start = 0                   # frames; the file's own loop, else 0
loop_end = 132300                # exclusive; the file's own loop, else its end

[sounds.chime]
file = "audio/chime.wav"

[music]
battle = { kind = "bgm", id = 26 }
boss = { kind = "bgm", id = 28 }
victory = { kind = "track", id = "fanfare" }
boss_victory = { kind = "bgm", id = 65 }

[music.modes]
poker = { kind = "bgm", id = 18 }

A track loops unless loop = false; a sound plays once unless loop = true. Loop points default to the file's own loop - a WAV smpl chunk, an Ogg LOOPSTART / LOOPLENGTH comment - then to the whole clip. music.battle scores fights on the pack's maps, music.boss a boss with no music of its own, music.victory / boss_victory a won fight's results; music.modes is keyed by mode instance id or mode kind, the instance winning.

A map's [audio]

[audio]
music = { kind = "track", id = "echo_drone" }   # wins over the map's bgm
fade_frames = 40

[[audio.zones]]
rect = [8, 7, 14, 10]
music = { kind = "bgm", id = 18 }

[[audio.ambient]]
sound = { kind = "sound", id = "torch" }
tile = [13, 4]
radius = 512        # silent from here (a tile is 128)
inner = 96          # full volume within
volume = 0.5

The first zone holding the player's tile plays, else the map's music, else its bgm; a change cross-fades over fade_frames, and the first pick on entry cuts. A trigger may raise a sound: event = { kind = "sound", sound = ... }. A map places at most 16 ambient sources, 4 of them retail cues.

Boss music, phases and telegraphs

A boss's music may be a pack track. A boss-script phase takes music, cross-fading in as it begins, and sound; a telegraphed move takes charge_sound, played on its charge turn. The battle's end restores the stashed field track as for any fight.

How music reaches the director

Every change is an ordinary field BGM start with the world's current track kept in step, so the battle swap stashes and restores a zone's track as it does a retail scene's. A pack track rides music id 0xE000 + n; the engine's sub-op 0xFC asks for a cross-fade.

From -> toWhat happens
Pack track -> pack trackA true cross-fade on the PCM bus.
Retail score -> pack trackThe score fades out and its sequencer leaves at silence; the track fades in.
Pack track -> retail scoreThe track fades out; the score starts.
Retail -> retailThe old score fades out first; the new one starts at silence, since its bank is uploaded over the old one's samples.

Sounds and ambient sources

Each tick on a pack map the world recomputes every ambient source's level: full within inner, a smooth fall to silence at radius, an equal-power pan by where the source sits across the camera's view. It is presentation only. A pack sound loops on the PCM bus; a retail cue keys one of the voices 20-23 through the same positional-emitter path the retail river uses, its volume written each tick and released out of range.

Levels

With normalize on (the default) a clip's gain brings its RMS to 0.07 of full scale (about -23 dBFS), the level the retail field scores measure at through the port's mixer, before gain. Tracks follow the BGM volume option and the battle duck; sounds and ambient loops follow the SFX volume. Mute, master volume and the mono options cover the bus too.

Decoders

WAV is read by the format crate; Ogg Vorbis goes through lewton, a small pure-Rust decoder. One decoder on both hosts keeps their output identical and lets the validator check a file the way the engine plays it; the workbench previews files with the browser's own decoder.

Validation

Errors: a missing, mistyped or undecodable file, gain outside 0..=4, a loop that ends before it starts or past the clip, an undeclared track or sound, a bgm id outside the sound test, a cue outside the two cue ranges, a zone or source off the map, a radius not past inner, a volume outside 0..=1, too many sources. Warnings: a loop shorter than 50 ms, a music.modes key that names no mode.

The World API

For cutscene steps and scripts: World::expansion_play_sound(&SoundRef), World::expansion_set_music(Some(&MusicRef), fade_frames) (held over the zones until cleared with None) and World::expansion_music_id.

The sample pack

The sample pack keeps each floor's retail music and adds a games-room zone on 5F (the Sol casino's theme over the Rest's bar tune), the pack's own drone in 7F's inner sanctum, crackling crystals, braziers and hearth, the Warden's Stone hum and running water, casino music at the poker table, the Hollow Sovereign's phase music with a chime on its ultimate's telegraph, and the title theme over every tower boss's results. Its WAV files are synthesised from sine partials and seeded noise by a script in the repository - no disc audio.