Expansion packs: audio
audio.toml and each map's [audio] table give an expansion pack its soundscape: the music of a map, a zone, a boss phase, a victory or a game mode, the sounds triggers and telegraphs raise, and loops that sound from points on a map. Retail sound is named by coordinate; the modder's own WAV and Ogg Vorbis files play beside it. With no pack active every hook is the identity and the mixer's PCM bus stays empty, so the retail output is bit-identical.
At a glance
- Files
audio.tomlat the pack root (optional);[audio]in any map. Declarestrackandsoundids.- 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'sworld::expansion_audio; the mixer's PCM busengine-audio::pcm_bus; one director,AudioDirector, on the native window and the browser page.- Sample
packs/sample/audio.toml
Music and sound references
| Form | Plays |
|---|---|
{ 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 -> to | What happens |
|---|---|
| Pack track -> pack track | A true cross-fade on the PCM bus. |
| Retail score -> pack track | The score fades out and its sequencer leaves at silence; the track fades in. |
| Pack track -> retail score | The track fades out; the score starts. |
| Retail -> retail | The 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.