Audio
Audio in Eustress is built around the Sound object: a Sound records which audio file to play and how loud, how fast and how far it carries, and SoundService is the folder where a Space keeps its audio. Eustress Engine stores and imports Sounds today; playing them is the next step.
01Overview
Audio Today
A Sound is an object that describes a piece of audio: the file it plays, its volume, its speed, whether it loops, and how its loudness falls off with distance. You can insert Sounds in Studio, edit their settings in their files, and bring them in from Roblox places with their settings intact.
Eustress Engine does not play audio yet. The settings a Sound holds are kept with the Space, ready for the playback step described in What's Next.
The Audio Stack
Eustress runs on Bevy, and Bevy's audio plugin is compiled into Eustress Engine. This is what each part of the stack does today:
| Part | In Eustress Engine today |
|---|---|
| Bevy audio | Built in. At startup it opens your default audio output device through the rodio and cpal libraries. |
| Decoders | None built in. Bevy decodes each format only when its feature (vorbis, wav, mp3, flac) is switched on, and none is. |
| Kira | Not used. |
| Eustress Player | Built without Bevy audio. |
02The Sound Object
Adding a Sound
Open the Insert menu and pick Sound under Audio. Studio writes a new Sound folder
with an _instance.toml into the folder of the object you
have selected, or into SoundService when nothing is selected. Sound is the
only audio class the Insert menu offers.
The Explorer shows SoundService as a single row without its contents, so a Sound inserted there does not appear under it. Select a part or a folder first to keep the Sound where you can see it.
Properties and Defaults
A Sound's settings live in the [sound] table of its _instance.toml. This is the table a new Sound starts with:
[sound]
looped = false
playback_speed = 1.0
playing = false
rolloff_max_distance = 10000.0
rolloff_min_distance = 10.0
rolloff_mode = "InverseTapered"
sound_id = ""
time_position = 0.0
volume = 0.5| Key | Default | Holds |
|---|---|---|
sound_id | empty | The audio file or asset to play |
volume | 0.5 | Loudness, from 0 to 1 |
playing | false | Whether the Sound is playing |
looped | false | Whether it starts again when it ends |
playback_speed | 1.0 | Speed multiplier; 1 is normal speed |
time_position | 0.0 | Playback position, in seconds |
rolloff_min_distance | 10.0 | Within this many meters, a spatial Sound is at full volume |
rolloff_max_distance | 10000.0 | Beyond this many meters, it is silent |
rolloff_mode | InverseTapered | How loudness falls between those two distances |
Distances are in meters, like everything else in a Space. A Sound is spatial by default, so its position in the world will decide how loud it is and which ear it favors once playback is wired.
Sounds from Roblox
When you import a Roblox place, each Sound keeps its Volume, Looped,
Playing, PlaybackSpeed, TimePosition, RollOffMinDistance, RollOffMaxDistance
and RollOffMode in the same [sound] table, and its SoundId
is recorded as an asset reference. The whole import workflow is in Importing.
03Files and Formats
Audio Files in a Space
SoundService is the only folder where the Space loader looks for audio
files. When a Space opens, an .ogg, .mp3,
.wav or .flac file there is recognized as
audio and noted in the log as not yet loadable; no object is created for it.
Audio files in any other folder are skipped.
SoundService's own settings, such as AmbientReverb and DopplerScale, are listed on the Services page. No system reads them yet.
Formats
The loader knows four audio extensions by name. None of them can be decoded yet, because no Bevy decoder feature is switched on:
| Extension | Recognized in SoundService | Decoder built in | Bevy feature that adds it |
|---|---|---|---|
.ogg | Yes | No | vorbis (also .oga, .spx) |
.mp3 | Yes | No | mp3 |
.wav | Yes | No | wav |
.flac | Yes | No | flac |
04Scripting
Luau
Instance.new("Sound") returns a Sound table with SoundId (empty), Volume (1), Playing (false) and Looped (false). The SoundService global has one function, PlayLocalSound, which sets a Sound's Playing to true and writes its SoundId to the log. No sound is heard.
local click = Instance.new("Sound")
click.SoundId = "sounds/click.ogg"
click.Volume = 0.8
-- A dot, not a colon: the Sound must be the first argument.
SoundService.PlayLocalSound(click)
print(click.Playing) -- true; the log shows the SoundIdPlayLocalSound treats its first argument as the Sound. Written as SoundService:PlayLocalSound(click), the first argument is
SoundService itself, so SoundService is marked as playing and click is left unchanged.
SoundService is a global in Luau rather than a member of game, so game:GetService("SoundService") raises an error. Scripting covers the
Luau runtime as a whole.
Rune
The eustress module registers a Sound handle type whose
fields (entity_id, sound_id, volume, playing, looped)
scripts can read, and three functions: sound_play and sound_stop set the handle's playing field,
and sound_set_volume sets its volume,
clamped to 0 to 1. No Rune function returns a Sound handle yet, so a script
cannot reach a Sound this way today.
05What's Next
Playback
The code that turns a Sound into a Bevy audio player is already written and registered. It will run once the Space loader creates Sounds through it and a decoder feature is switched on. It loads the SoundId as a Bevy asset path and maps the rest of the Sound like this:
| Sound | Bevy playback |
|---|---|
| Looped | Loop, or play once |
| Volume | Linear volume, never below 0 |
| Playback speed | Speed, at least 0.01 |
| Playing | Paused while false |
| Spatial | Spatial playback, scaled by roll-off (below) |
Spatial Sound
Bevy's spatial audio pans between two ears and fades with distance on its own, with no per-sound falloff setting. The spawner will emulate each roll-off mode by scaling the Sound's position by its minimum distance:
| Roll-off mode | Position scale |
|---|---|
| Inverse (the default), Logarithmic, Custom | 1 / min distance |
| InverseSquared | 1 / min distance² |
| Linear | 1 / (2 × min distance) |
| None | 0, so position has no effect |
Sounds will also need a listener on the camera so they pan with your view; no camera carries one yet.
Author your Sounds now. They will play where you put them.