Renderer responsibility and modes
San Andreas Sound renders caller-owned state. It does not choose stations, schedule Dispatch, grant permissions, or own synchronized vehicle state.
| Renderer | Used when | Capabilities | Limitation |
|---|---|---|---|
| webaudio | Direct media permits cross-origin sample access | HRTF, distance, gain, bass shelf, low-pass, compression, limiter, and 12-band spectrum | The media host must allow browser CORS access. |
| html | Direct media plays but cannot enter Web Audio | Playback, transport, distance-shaped volume, and fallback relationship gain | No HRTF, low-pass, bass shelf, compressor, or analyzer. |
| youtube | Supported watch, short, embed, or resolved live URL | Iframe transport, volume, distance-shaped level, and fallback enclosure gain | Embedding may fail; no Web Audio filters, HRTF, or spectrum. |
MediaElementAudioSourceNode
-> GainNode
-> BiquadFilterNode (low shelf)
-> BiquadFilterNode (low pass)
-> DynamicsCompressorNode
-> HRTF panner or direct same-vehicle route
-> shared master limiterOne shared AudioContext is used per client. Listener and active-source transforms are batched; the resource does not create one context or Lua thread per source.
A URL that plays successfully may still use the HTML fallback when CORS prevents Chromium from exposing samples to Web Audio. Inspect diagnostics before treating reduced spatial behavior as a configuration error.
Installation and core configuration
- Keep the exact san_andreas_sound folder name and its config.lua, client, server, web, and manifest files together.
- Start Sound before every native caller, Radio, and every resource depending on xsound or interact-sound.
- Do not start the original xSound or InteractSound resources while Sound provides their aliases.
- Run san_andreas_sound_status in F8, trigger one known sound, and inspect the renderer, started state, context, error, and attachment.
- Destroy the test sound or stop its caller and confirm its diagnostic record disappears.
| Key | Type | Default | Description |
|---|---|---|---|
PositionUpdateMs | number | 50 | Active listener/source transform batch interval in milliseconds. |
IdleUpdateMs | number | 500 | Lower-frequency update interval while no sound is active. |
CompletionCheckMs | number | 500 | Natural-end fallback inspection interval. |
CompletionGraceMs | number | 1500 | Grace before fallback completion is accepted. |
FalloffStartDistance | number | 1.5 | Distance before normal attenuation begins. |
Debug | boolean | false | Enables additional development logging. |
| Key | Type | Default | Description |
|---|---|---|---|
StreamerMode.Enabled | boolean | true | Exposes the client-local managed-audio mute. |
StreamerMode.Command | string | streamermode | Command name without a slash. |
StreamerMode.Persist | boolean | true | Stores the local preference between sessions. |
StreamerMode.DefaultEnabled | boolean | false | Starts new users muted when true. |
StreamerMode.MuteOneShots | boolean | true | Includes InteractSound-compatible one-shot cues. |
Compatibility.XSound.Enabled | boolean | true | Publishes the supported xSound surface and provider alias. |
Compatibility.InteractSound.Enabled | boolean | true | Publishes the supported InteractSound events and exports. |
Compatibility.InteractSound.DefaultVolume | number | 0.30 | One-shot fallback volume when a caller omits it. |
Compatibility.InteractSound.MaxDistance | number | 300.0 | Maximum accepted distance for compatibility calls. |
Compatibility.InteractSound.Extension | string | ogg | Single extension used by the flat compatibility cue library. |
Spatial audio and vehicle acoustics
Tune the caller's source level first, then relationship overrides, Sound profile gain, filtering, bass, compression, and finally the limiter. Change one layer at a time.
| Key | Type | Default | Description |
|---|---|---|---|
SpatialAudio.Enabled | boolean | true | Enables the spatial relationship graph. |
SpatialAudio.PanningModel | string | HRTF | Directional panning model for compatible sources. |
SpatialAudio.DistanceModel | string | linear | Distance attenuation curve. |
SpatialAudio.RolloffFactor | number | 1.0 | Distance rolloff factor. |
SpatialAudio.SmoothingMs | number | 240 | Gain and tone ramp duration. |
SpatialAudio.RelationshipTransitionMs | number | 360 | Direct/HRTF transition duration when listener relationship changes. |
OutputLimiter.ThresholdDb | number | -3.0 | Final direct-output peak threshold. |
OutputLimiter.KneeDb | number | 2.0 | Limiter knee. |
OutputLimiter.Ratio | number | 12.0 | Limiter compression ratio. |
OutputLimiter.Attack | number | 0.001 | Limiter attack in seconds. |
OutputLimiter.Release | number | 0.10 | Limiter release in seconds. |
Tone.BassFrequencyHz | number | 400 | Low-shelf frequency used by relationship shaping. |
Tone.Compressor | table | — | Threshold -24 dB, knee 18 dB, ratio 2.5, attack 0.012, release 0.25. |
| Key | Type | Default | Description |
|---|---|---|---|
Cabin.UpdateMs | number | 200 | Roof, door, window, and trunk enclosure cache interval. |
Cabin.DoorOpenRatio | number | 0.15 | Threshold for treating a door as open. |
Cabin.DoorExposure | number | 0.60 | How strongly open doors reduce enclosure. |
Cabin.WindowExposure | number | 0.40 | How strongly broken/open windows reduce enclosure. |
Cabin.TrunkExposure | number | 0.35 | Trunk contribution for sounds that opt in. |
Cabin.Sealed.Gain | number | 0.65 | Direct Web Audio sealed-cabin gain. |
Cabin.Sealed.FallbackGain | number | 0.35 | Sealed-cabin gain for HTML/YouTube sources that cannot be filtered. |
Cabin.Sealed.LowpassHz | number | 400 | Sealed-cabin cutoff for compatible media. |
Cabin.Sealed.BassGainDb | number | 2.0 | Body-transmission bass shaping. |
Occlusion.UpdateMs | number | 250 | Staggered line-of-sight update interval. |
Occlusion.ChecksPerTick | number | 2 | Maximum staggered occlusion checks per update. |
| Relationship profile | Spatial | Base gain | Low-pass | Role |
|---|---|---|---|---|
| World | Yes | 1.0 | 10000 Hz | General positional playback. |
| Inside | No | 1.0 | 8000 Hz | Same-vehicle direct route with cabin warmth. |
| Outside | Yes | 2.0 | 1000 Hz | Exterior route; caller exterior trim still applies. |
| OtherVehicle | Yes | 0.75 | 2200 Hz | Listener inside a different vehicle. |
| Occluded | Blended | 0.60 | 400 Hz | Additional geometry-blocked shaping. |
| Acoustic class | Intended source | Behavior |
|---|---|---|
| Default or omitted | General positional and compatibility calls | Uses the base cabin and relationship profiles. |
| music | Satellite Radio and Bluetooth | Music-specific cabin and exterior tuning; trunk can opt in. |
| server_music | Locally hosted Server Radio | Peak-controlled profile with reduced exterior bass for hotter local masters. |
| scanner | Dispatch, scanner, and voice traffic | Keeps communication tone independent from entertainment media. |
Native client API
local vehicle = GetVehiclePedIsIn(PlayerPedId(), false)
local netId = VehToNet(vehicle)
local soundName = ('my_resource:vehicle:%s'):format(netId)
exports.san_andreas_sound:PlayPositional({
name = soundName,
url = 'https://media.example.net/authorized-stream.mp3',
volume = 0.20,
position = GetEntityCoords(vehicle),
entityHandle = vehicle,
entityNetId = netId,
syncGroup = 'my_resource:station:example',
acousticClass = 'music',
trunkAffectsEnclosure = true,
relationshipOverrides = {
outside = { volumeMultiplier = 0.18, maxDistance = 18.0 },
other_vehicle = { volumeMultiplier = 0.18, maxDistance = 18.0 }
},
maxDistance = 25.0,
loop = true,
playing = true
})| Key | Type | Default | Accepted values | Required | Description |
|---|---|---|---|---|---|
name | string | — | — | Yes | Stable logical ID. Same-name playback replaces old state without calling the old onPlayEnd callback. |
url | string | — | — | Yes | Direct media URL or supported YouTube form. |
volume | number | 0 | 0.0-1.0 | — | Source volume intent. |
position | vector3 or table | — | — | — | Accepts x/y/z keys or numeric indices. |
entityHandle | client entity handle | — | — | — | Optional local optimization. Never relay it through the server. |
entityNetId | positive number | — | — | — | Network-safe attachment resolved independently by each client. |
global | boolean | false | — | — | Selects a non-spatial route. Otherwise provide position or an entity. |
maxDistance | number | 20 | >= 0.1 | — | Audible radius. |
syncGroup | string | — | Maximum 128 characters | — | Provider hint for sharing matching non-seekable live media. |
acousticClass | string | — | — | — | Selects an inheriting source-specific acoustic profile. |
trunkAffectsEnclosure | boolean | — | — | — | Opts this sound into trunk contribution to enclosure. |
relationshipOverrides | table | — | — | — | Per world, inside, outside, or other_vehicle volume and distance overrides. |
loop | boolean | false | — | — | Repeats compatible media. |
playing | boolean | true | — | — | Initial transport state. |
time | number | 0 | — | — | Initial nonnegative seek position in seconds. |
destroyOnFinish | boolean | — | — | — | Remove direct one-shots after natural completion; helper exports infer true for non-looping media. |
callback fields | functions | — | — | — | onPlayStart, onPlayEnd, onLoading, onPlayPause, and onPlayResume; client-only. |
Case-sensitive Sound client exports
PlayPositional
exportClientCreate or replace a validated named positional or global sound.
PlayPositional(options)- Returns
- boolean; queued locally, not audible confirmation
PlayUrlPos
exportClientxSound-style positional helper.
PlayUrlPos(name, url, volume, position, loop, options)- Returns
- boolean
PlayUrl
exportClientCreate a global, non-spatial sound.
PlayUrl(name, url, volume, loop, options)- Returns
- boolean
Destroy
exportClientStop and remove a sound; explicit destroy calls the current onPlayEnd callback.
Destroy(name)- Returns
- boolean
SetPosition / Position
exportClientMove a detached source and make it positional.
SetPosition(name, position)- Returns
- boolean
SetEntityNetId
exportClientAttach or reattach by network ID. Invalid IDs clear an existing attachment.
SetEntityNetId(name, netId)- Returns
- boolean
SetDistance / Distance
exportClientSet the audible radius, with a 0.1-meter minimum.
SetDistance(name, meters)- Returns
- boolean
SetVolume / setVolume / setVolumeMax
exportClientSet source volume intent, clamped to 0.0-1.0.
SetVolume(name, volume)- Returns
- boolean
SetTime / setTimeStamp
exportClientSeek compatible media to a nonnegative time.
SetTime(name, seconds)- Returns
- boolean
SetPlaying
exportClientSet exact paused/playing transport state.
SetPlaying(name, playing)- Returns
- boolean
Pause / Resume
exportClientTransport convenience exports.
Pause(name) / Resume(name)- Returns
- boolean
SetSpectrumEnabled
exportClientEnable local 12-band frames for compatible Web Audio.
SetSpectrumEnabled(name, enabled)- Returns
- boolean
SoundExists
exportClientTest whether logical sound state exists.
SoundExists(name)- Returns
- boolean
IsReady
exportClientRead NUI readiness.
IsReady()- Returns
- boolean
GetDiagnostics
exportClientReturn active sound diagnostic records.
GetDiagnostics()- Returns
- map keyed by sound name
SetStreamerMode
exportClientSet the local managed-audio mute preference.
SetStreamerMode(enabled)ToggleStreamerMode
exportClientToggle local managed-audio mute.
ToggleStreamerMode()- Returns
- current boolean preference
IsStreamerModeEnabled
exportClientRead the local preference.
IsStreamerModeEnabled()- Returns
- boolean
Callbacks, events, and diagnostics
onPlayStart
callbackClientRuns after the renderer confirms playback began. Use this when audible-start confirmation matters.
onPlayEnd
callbackClientRuns after natural completion or explicit Destroy. Same-name replacement does not call the old callback.
onLoading / onPlayPause / onPlayResume
callbackClientOptional client-only lifecycle callbacks. Functions are not serialized by server relays.
san_andreas_sound:client:playEnded
eventClientLocal integration event emitted for natural completion.
(name, token)san_andreas_sound:client:spectrumFrame
eventClientLocal presentation event with twelve normalized values for compatible Web Audio.
(name, bands)san_andreas_sound:client:streamerModeChanged
eventClientLocal notification after streamer-mode state changes.
(enabled)| Diagnostic field | What to verify |
|---|---|
| renderer | webaudio, html, or youtube; determines available spatial/filter features. |
| started | True only after renderer playback confirmation. |
| audioContextState / playbackError | Normally running and without error while compatible audio is active. |
| profile / acousticClass | Expected listener relationship and source class. |
| enclosure / occluded | Vehicle sealing and geometry-blocked state. |
| effective volume / sourceVolume / muted | Final local shaping versus caller volume and mute state. |
| entityNetId / resolved entity | Attachment exists and resolves locally. |
| syncGroup | Matching live media uses the intended shared transport identity. |
Server relay API
PlayPositional
exportServerRelay serialized positional playback to a validated client.
PlayPositional(target, options)- Returns
- true after TriggerClientEvent
PlayUrlPos
exportServerRelay the positional helper.
PlayUrlPos(target, name, url, volume, position, loop, options)PlayUrl
exportServerRelay global playback.
PlayUrl(target, name, url, volume, loop, options)Destroy
exportServerDestroy one named sound on the validated target client.
Destroy(target, name)SetPosition / Position
exportServerRelay a positional update through the native and xSound-compatible aliases.
SetPosition(target, name, position) / Position(target, name, position)SetEntityNetId
exportServerAttach a named sound to a networked entity on the target client.
SetEntityNetId(target, name, entityNetId)SetDistance / Distance
exportServerRelay the audible-distance update through both supported aliases.
SetDistance(target, name, distance) / Distance(target, name, distance)SetVolume / setVolume / setVolumeMax
exportServerRelay volume through the native name or either case-sensitive xSound alias.
SetVolume(target, name, volume) / setVolume(target, name, volume) / setVolumeMax(target, name, volume)SetTime / setTimeStamp
exportServerRelay a playback-position update through the native or xSound-compatible alias.
SetTime(target, name, seconds) / setTimeStamp(target, name, seconds)SetPlaying
exportServerSet the named sound's playing state using an explicit boolean.
SetPlaying(target, name, playing)Pause / Resume
exportServerRelay explicit transport changes.
Pause(target, name) / Resume(target, name)The server serializes name, URL, volume, position, entityNetId, syncGroup, acoustic and relationship options, global state, loop, distance, time, playing state, and destroyOnFinish. It cannot serialize callback functions or a client-local entityHandle.
Legacy compatibility contracts
| xSound category | Supported names |
|---|---|
| Playback and mutation | PlayUrl, PlayUrlPos, Position, Distance, Destroy, Pause, Resume, setVolume, setVolumeMax, setTimeStamp |
| Information | getLink, getPosition, isLooped, getInfo, soundExists, isPlaying, isPaused, getDistance, getVolume, isDynamic, getTimeStamp, getMaxDuration, getAllAudioInfo, isPlayerCloseToAnySound, isPlayerInStreamerMode |
| Manipulation and callbacks | destroyOnFinish, setSoundLoop, repeatSound, setSoundDynamic, setSoundURL, onPlayStart, onPlayEnd, onLoading, onPlayPause, onPlayResume, fadeIn, fadeOut |
| Legacy state event | xsound:stateSound accepts play, playpos, position, distance, destroy, pause, resume, volume, timestamp, and destroyOnFinish branches |
| Completion event | xSound:songStopPlaying(name) |
InteractSound compatibility
InteractSound_CL:PlayOnOne
eventClientPlay a non-spatial local one-shot.
(file, volume)InteractSound_CL:PlayOnAll
eventClientSame local behavior after a server broadcast.
(file, volume)InteractSound_CL:PlayWithinDistance / PlayWithinDistanceOS
eventClientApply a strict distance gate once, then play a non-spatial one-shot.
(originCoordsOrServerId, maxDistance, file, volume)InteractSound_SV:PlayOnOne
eventServerRelay one cue to one target.
(target, file, volume)InteractSound_SV:PlayOnSource
eventServerRelay to the invoking client.
(file, volume)InteractSound_SV:PlayOnAll
eventServerRelay to every client.
(file, volume)InteractSound_SV:PlayWithinDistance
eventServerRelay source position for a client distance gate.
(maxDistance, file, volume)PlayOneShot / StopOneShot
exportClientAvailable under both canonical and interact-sound resource names.
PlayOneShot(file, volume) / StopOneShot()Performance, verification, and troubleshooting
- Keep the shared AudioContext and batched transform path; do not create a tight Lua thread per sound.
- Prefer entity attachment over repeatedly relaying unchanged positions.
- Enable spectrum only while a display consumes it.
- Keep occlusion checks staggered and use syncGroup only for matching non-seekable live media.
- Measure client frame time, NUI memory, stream traffic, simultaneous decoders, and audible stability with multiple nearby vehicles before changing timing defaults.
The export succeeds but nothing is audible.
Likely causes
- The browser did not start media
- The URL or codec is blocked
- The AudioContext is not running
Checks
- Run san_andreas_sound_status
- Inspect started, renderer, context, URL, and playbackError
- Test the exact media in FiveM CEF
Expected result
The active record reports started=true with no playback error.
There is no HRTF, low-pass, or spectrum.
Likely causes
- CORS forced the HTML renderer
- The source is YouTube
Checks
- Inspect renderer
- Use an authorized CORS-compatible direct media endpoint
Expected result
Renderer reports webaudio for the full graph.
The sound is global instead of positional.
Likely causes
- The caller used PlayUrl
- No valid position or entity attachment was supplied
- global=true was set
Checks
- Use PlayPositional or PlayUrlPos
- Inspect entityNetId and resolved entity
Expected result
Diagnostics show a positional relationship profile.
Doors or trunk do not change enclosure.
Likely causes
- The source is not attached to a valid vehicle
- Cabin processing is disabled
- The trunk was not opted in
Checks
- Verify attachment
- Confirm Cabin.Enabled
- Set trunkAffectsEnclosure=true for music that should use it
Expected result
Enclosure changes cumulatively as openings change.
Compatibility cues are silent.
Likely causes
- Filename case or extension mismatch
- The manifest does not package the format
- The cue is not in the flat sounds directory
Checks
- Check web/sounds, Linux case, configured Extension, manifest patterns, and F8
Expected result
The requested local cue resolves through the compatibility path.
Audio duplicates or survives a restart.
Likely causes
- The original provider is also running
- A caller owns cleanup too broadly or not at all
- Temporary diagnostic sounds remain
Checks
- Run only one provider
- Use resource-prefixed names
- Destroy only owned sounds on the caller's resource stop
Expected result
Independent restarts leave no duplicate or orphaned media.