Studio

FolioTier 1

sound

Control the SFX channel - play or stop a sound effect.

The sound construct fires SFX and ambient cues on their own channel, separate from music. Reach for it any time you need a discrete effect - a knock, a footstep, a UI blip - or a layered background loop like rain or crickets that should keep running under the dialogue without competing with the music track.

Because every sound channel plays once by default, a single play call is enough for a one-off cue; you only add loop, once, volume, muted, on, or fade when the moment calls for something more deliberate, like an ambient bed that fades in and loops until you stop it later.

Reach for on to keep concerns separate: put one-shot SFX on the default sfx channel and looping ambience on its own named channel (ambient, or whatever you call it) so a door-knock doesn't interrupt the crickets underneath it. Each channel tracks its own playback and volume independently, so volume and stop only affect the channel you name.

volume only changes the channel's ongoing gain; it does not start or stop anything, so pair it with a play or stop line if you also need to change what's audible. On stop, a bare call cuts the channel instantly - use with fade(N) whenever an ambient loop should ease out instead of slamming off. loop and once occupy the same slot and can't be combined; if you omit both, the channel's own default behavior applies.

Parameters

ParameterKindRequiredDefaultNotes
actionidentifieryes-One of `play`, `stop`, or `volume`. `play` triggers a one-shot SFX; `stop` stops the SFX channel; `volume <0-100>` sets the channel volume from that point on without touching playback (Ren'Py's `renpy.sound.set_volume`).
referenceidentifierno-Audio reference registered in the project's audio table. Required for `play`; omit for `stop`. Quoted form accepted for references with non-identifier characters.
onceidentifierno-Optional `play` modifier - `sound play rain-loop once` plays the cue a single time without looping (Ren'Py's `noloop`). Every `sound` channel (`sfx`, `ambient`, any custom name) already plays once by default; `once` pins that explicitly, so the cue stays one-shot even if the channel default ever changes. Exclusive with `loop`.
loopidentifierno-Optional `play` modifier - `sound play crickets loop on ambient` repeats the cue until the channel is stopped, even though `sound` channels play once by default (Ren'Py's `play sound "crickets.ogg" loop` - the ambient crickets / TV hum / boiling-pan idiom). It sits in the same slot as `once`, right after the reference; the two are exclusive (a line with both is an error). Omit both for the channel default. In Studio the audio inspector's Loop control (Channel default / Loop / Play once) writes it.
volumenumberno-Optional `play` modifier - `sound play door-knock volume(60)` sets the per-cue volume to 60%. Accepts 0-100; multiplies with the listener's master volume and the channel's per-source slider. Omit to use the channel default.
mutedidentifierno-Optional `play` flag - `sound play door-knock muted` plays the cue at zero volume. Useful when a creator wants the channel state to follow the script without audible playback. Combines with `volume()`; mute wins.
fadenumberno-Optional `with fade(N)` clause, on both actions. On `play` it ramps the cue up from silence over N seconds (Ren'Py's `fadein N`), which is how a long ambient bed eases in rather than slamming on; on `stop` it fades the SFX/ambient channel gain down over N seconds before releasing it (Ren'Py's `fadeout N`). It rides last, after any `on <channel>`. Bare `play` starts at full volume and bare `stop` hard-cuts; `fade(0)` spells either one explicitly.
levelnumberno-The `volume` action's argument - `sound volume 70 on ambient` sets that channel to 70% from here on. Required for `volume`, bare number 0-100 (out-of-range values clamp with a warning). Playing audio keeps playing at the new level.
onidentifiernosfxOptional channel override. `sound` lines default to the `sfx` channel - pass `on <channel>` to route to an additional channel (e.g. `on ambient` for layered ambient loops separate from one-shot SFX). The channel is a single identifier (letters, digits, `. - _`); it rides after the cue and any `once` / `loop` / `volume()` / `muted` modifiers, and before any `with fade(...)` clause.

Canonical example

Folio
sound play door-knock
sound play heartbeat on ambient
sound play rain-loop once on ambient
sound play crickets loop on ambient
sound play storm-bed on ambient with fade(3)
sound volume 70 on ambient
sound stop on ambient with fade(1)
Ren'Py
play sound "door-knock.ogg"
play audio "heartbeat.ogg" channel "ambient"
play audio "rain-loop.ogg" noloop channel "ambient"
play audio "crickets.ogg" loop channel "ambient"
play audio "storm-bed.ogg" channel "ambient" fadein 3
$ renpy.sound.set_volume(0.7, channel="ambient")
stop audio fadeout 1

sound is the SFX verb. The action grammar mirrors music - play, stop, volume - but the default channel is sfx so one-shot effects don't fight with the music channel. Use a different channel for layered ambient loops that should keep running while other SFX fire (sound play rain-loop on ambient).

Notes

sound and music are intentionally separate verbs even though the parser routes both through the same parseAudioLine function - the split keeps creator intent explicit and lets the runtime apply per-type mixing (SFX ducks under voice, music ducks under SFX, etc.) without inspecting channel names.

Ren'Py's play sound "..." and play audio "..." channel "<name>" both lower to this verb during import. The default channel for sound is sfx; the importer leaves the on clause off when the Ren'Py source used the default channel implicitly.

Modifiers

The same modifiers as music apply (shared parser):

  • once - appended to play. Plays the cue a single time without looping (Ren'Py noloop). Every sound channel already plays once by default; once pins it explicitly. Exclusive with loop.
  • loop - appended to play. Repeats the cue until the channel is stopped, even on a one-shot channel (Ren'Py play sound "..." loop - the ambient crickets / TV hum idiom). Same slot as once; a line with both is an error. In Studio the audio inspector's Loop control (Channel default / Loop / Play once) writes it - Channel default emits neither token.
  • volume(N) - appended to play (where N is 0-100, integer or decimal). Sets the per-cue volume for this cue only - multiplies with the listener's master volume and the channel's per-source slider. Omit to use the channel default (the sfx channel plays at full gain, unlike music's built-in attenuation). Has no Ren'Py equivalent.
  • muted - appended to play. Plays the cue at zero volume while keeping the channel state in sync with the script (so a later sound stop still has something to stop, saves round-trip cleanly, etc.). Combines with volume(); mute wins.
  • with fade(N) - appended to play or stop (seconds, integer or decimal). On play it ramps the cue up from silence (Ren'Py fadein N); on stop it fades the gain down before releasing the channel (Ren'Py fadeout N). Bare sound stop hard-cuts.

The modifiers compose with on <channel> and conditional if tails. Order: <action> <ref> [once|loop] [volume(N)] [muted] [on <channel>] [if <cond>] and stop [on <channel>] [with fade(N)] [if <cond>].

See also

  • music - same action grammar for background tracks
  • voice - character voice clips (placeholder; future construct)