Learn/Audio

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.

Time7 min readLevelBeginnerUpdatedUpdated Sep 2026

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.

Sound file_instance.tomlloadsSound objectin Studionot wired yetAudio playerBevy audiono decoderSpeakersoutput device
A Sound's file loads into Studio as an object today. Two links come next: the step that turns the object into a Bevy audio player, and a decoder for its audio file.

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:

PartIn Eustress Engine today
Bevy audioBuilt in. At startup it opens your default audio output device through the rodio and cpal libraries.
DecodersNone built in. Bevy decodes each format only when its feature (vorbis, wav, mp3, flac) is switched on, and none is.
KiraNot used.
Eustress PlayerBuilt 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.

Advanced
A Sound in SoundService is not listed

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:

SoundService/Sound/_instance.toml
[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
KeyDefaultHolds
sound_idemptyThe audio file or asset to play
volume0.5Loudness, from 0 to 1
playingfalseWhether the Sound is playing
loopedfalseWhether it starts again when it ends
playback_speed1.0Speed multiplier; 1 is normal speed
time_position0.0Playback position, in seconds
rolloff_min_distance10.0Within this many meters, a spatial Sound is at full volume
rolloff_max_distance10000.0Beyond this many meters, it is silent
rolloff_modeInverseTaperedHow 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:

ExtensionRecognized in SoundServiceDecoder built inBevy feature that adds it
.oggYesNovorbis (also .oga, .spx)
.mp3YesNomp3
.wavYesNowav
.flacYesNoflac

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.

Luau
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 SoundId
Advanced
The Roblox colon form marks SoundService

PlayLocalSound 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:

SoundBevy playback
LoopedLoop, or play once
VolumeLinear volume, never below 0
Playback speedSpeed, at least 0.01
PlayingPaused while false
SpatialSpatial 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 modePosition scale
Inverse (the default), Logarithmic, Custom1 / min distance
InverseSquared1 / min distance²
Linear1 / (2 × min distance)
None0, 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.

Loading Eustress Engine...