Skip to content

ServerAudio

import { ServerAudio, Sound } from "@sagl/server";

server.audio: sounds from client files (MP3, WAV, FLAC) on players' games. A sound with a place is heard from it, fading out with distance, and turning with the player's camera, at their sound effects volume.

play(file, options?)

play(file: string, options?: SoundOptions): Sound
Option Type Description
at Vector3 | Ped | Player | Vehicle Heard from this spot, or from them (following). Left out: everywhere, as is (music, a jingle).
range number How far it carries (m). Default 30.
loop boolean Plays until stop().
volume number 0-1. Default 1.
to Player[] Who hears it. Default everyone, players joining later too (while it loops).
speech boolean Dialogue or a phone call: the radio goes quiet while it plays near enough to hear.
effects AudioEffects Through audio effects: a preset ("phone", "hall"...) or stages layered.
realism boolean Positioned: its highs fade with distance, and it's muffled with a building between it and the listener. See Realism. Default server.audio.realism.

Returns a Sound:

Member Description
duration How long the file plays (ms), read from the server's copy in clientFiles; undefined if it can't be read.
done A promise that resolves when it's over: played through (by its duration, from when the server sent it) or stopped. A looping one, when stopped.
playing False once it has played through or been stopped.
stop() Stops it.
await server.audio.play("audio/caller.mp3", { to: [player], effects: "phone" }).done;
player.subtitle("The line goes dead.");
const ring = server.audio.play("audio/payphone-ring.mp3", { at: { x: 2069, y: -1766.7, z: 13.2 }, loop: true, range: 30 });
server.audio.play("audio/stinger.mp3", { to: [player] });
ring.stop();

Client scripts play their own with sagl.audio.play().

speak(file, options?)

speak(file: string, options?: SoundOptions & { subtitle?: string | string[]; subtitleTo?: Player[]; prefix?: string; muffled?: boolean }): Speech

A recorded line with its subtitles timed to it, as speech (the radio ducks under it). A string subtitle is split into sentence-sized subtitles (splitSubtitles); each shows for its share of the recording. They show to subtitleTo, else to, else everyone within range of at. muffled with at a vehicle: from inside it, through the glass. Returns { sound, done, stop() }; done resolves when it's been said. In a mission, m.say is the same, for the party.

await server.audio.speak("audio/benny-my-car.mp3", { at: benny, subtitle: "My car! That's my car!" }).done;

duration(file)

duration(file: string): number | undefined

How long a client file plays (ms), from the server's copy; undefined if it isn't there or isn't a sound.

playGame(sound, options?)

playGame(sound: number, options?: { at?: Vector3; to?: Player[] }): void

One of the game's own sounds, no file needed: a frontend sound (heard by the player, from nowhere: GameSound has the pickups', the Pay 'n' Spray's...), or, with at, one of the mission script's sounds at a spot. to picks who hears it (default everyone).

server.audio.playGame(GameSound.CarRespray, { to: [player] });

Pickups play the game's pickup sounds themselves.

playMission(id, options?)

playMission(id: number, options?: { at?: Vector3 | Player | Ped | Vehicle; range?: number; to?: Player[]; effects?: AudioEffects }): void

One of the game's mission audio, by its audio id: the recordings and effects its missions and machines load (a vending machine's clunk is 42600, a snack machine's 42601). From a player, ped or vehicle (following them, heard up to range m, default 30), a spot, or (no at) as is. to picks who hears it (default everyone). Each player's game reads the recording from its own copy of the game and plays it as the server's sounds are, so effects work on it too.

server.audio.playMission(42600, { at: player });

Audio effects

Sounds of the server's and client scripts' (files, the game's mission audio, dialogue lines) can go through effects: filters, distortion, echo, reverb, layered in order, each as strong as you like. The game's own world sounds (engines, guns, footsteps) aren't affected.

effects takes a preset, or a list of presets and stages:

server.audio.play("audio/caller.mp3", { to: [player], effects: "phone" });
server.audio.play("audio/announce.mp3", { at: speaker, effects: ["megaphone", { type: "echo", delay: 0.4, mix: 0.3 }] });
server.audio.play("audio/choir.mp3", { at: church, effects: [{ preset: "hall", strength: 0.5 }] });
Preset
phone A phone line: thin, a little crackly.
radio A two-way radio, a police scanner.
megaphone A megaphone, a PA.
muffled Through a wall or a closed door.
underwater Underwater.
room A small room.
hall A large hall, a warehouse.
cave A cave.
tunnel A tunnel, a car park.
canyon A long, clear echo.

{ preset, strength } is a preset at a strength (0-1). AudioPresets has each preset's stages, to start your own from.

A stage is one of these; mix (0-1) is how much of it you hear, the rest being the sound as it came in:

Stage Settings
{ type: "lowpass", cutoff, mix? } Cuts the highs above cutoff Hz: muffled.
{ type: "highpass", cutoff, mix? } Cuts the lows below cutoff Hz: thin, tinny.
{ type: "bandpass", low, high, mix? } Keeps only low-high Hz: a phone, a radio.
{ type: "distortion", drive, mix? } Clipping, harsher with drive (0-1).
{ type: "echo", delay, decay?, mix? } Repeats after delay s, each decay (0-1, default 0.4) as loud. Default mix 0.4.
{ type: "reverb", size?, damping?, mix? } A room's reverb: size (0-1) small room to cathedral, damping (0-1) bright to dull. Default mix 0.35.
{ type: "gain", volume } Louder or quieter (1: as is, up to 4).

Up to 8 stages a sound (a preset counts its own). Echo and reverb ring on after the sound ends.

Realism

server.audio.realism = true;   // the default for every positioned sound
server.audio.play("audio/payphone-ring.mp3", { at: phone, range: 30, realism: true });

Off by default. With it on, a positioned sound reaches each listener as it would: duller the further away it is (its highs fade, all the way down at its range, as well as its volume), and muffled and quieter as a building comes between it and them (a phone ringing round the corner): gradually, as more of it is hidden, so rounding a corner fades it rather than cutting it. Checked a few times a second as they move. For sounds from files and the game's mission audio (playMission, realism there too); client scripts' take realism as well.

setEnvironment(player, effects)

setEnvironment(player: Player, effects: AudioEffects | null): void

Effects every sound of ours passes through for this player now, on top of each sound's own: where they are (a hall's reverb, a tunnel's echo). null: none.

setZoneEnvironment(zone, effects)

setZoneEnvironment(zone: Zone, effects: AudioEffects | null): void

An environment for everyone inside a zone while they're in it (where zones overlap, the one entered last). One set with setEnvironment wins.

const tunnel = server.createZone({ ... });
server.audio.setZoneEnvironment(tunnel, "tunnel");