A Luau SDK for the AudioScape Developer API — search music and sound effects, browse the catalog, sync to beat-level track structure, and track analytics for your Roblox experiences.
Note: This SDK uses
HttpService:RequestAsync()and must run on the server (Script, not LocalScript). You must enable Allow HTTP Requests in your experience's Game Settings → Security.
Add to your wally.toml:
[dependencies]
AudioScape = "this-fifo/audioscape-sdk@0.10.1"Then run:
wally installSince the SDK realm is server, Wally installs it to ServerScriptService.Packages (or your configured server packages location).
Download AudioScape.rbxm from the latest release and drop it into ServerStorage or ServerScriptService in Roblox Studio.
Copy the src/ folder into your project under ServerStorage or ServerScriptService. If using Rojo, add it to your server-side project tree.
- Enable HTTP Requests — In Roblox Studio, go to Game Settings → Security → Allow HTTP Requests and turn it on.
- API Key — Get your key at developer.audioscape.ai. Use the Roblox Secrets Store to securely store your key in production.
local ServerStorage = game:GetService("ServerStorage")
local HttpService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
local AudioScape = require(ServerStorage.AudioScape)
local apiKey = if RunService:IsStudio()
then "your-test-key"
else HttpService:GetSecret("AudioScapeKey")
local client = AudioScape.new(apiKey)
local player = client:createPlayer()
-- Search and play
local result, err = client:search({ query = "chill lo-fi beats", limit = 10 })
if result then
player:queue(result.tracks)
player:play()
endThe AudioScapeMusicPlayer handles Sound lifecycle, queue advancement, and analytics tracking automatically — no manual trackPlay/trackStop calls needed.
The SDK automatically sends your game's Universe ID and Place ID with every request to help you track usage across your experiences. You can also pass an optional playerId to tie requests to specific players:
local result, err = client:search({
query = "epic battle music",
playerId = player.UserId,
})Creates a new client instance.
local client = AudioScape.new("your-api-key")Search the catalog using natural language.
local result, err = client:search({
query = "epic orchestral battle music", -- required
limit = 20, -- optional (default: 20, max: 100)
offset = 0, -- optional
playerId = player.UserId, -- optional
filters = { -- optional
genres = { "electronic", "pop" }, -- Roblox music_genre slugs (lowercase)
duration = { min = 60, max = 180 }, -- seconds
min_play_count = 100000, -- min lifetime Roblox plays
min_likes = 500, -- min lifetime Roblox likes
created_after = "2024-01-01", -- YYYY-MM-DD
},
})
-- result = { tracks, artists, albums, meta }Find tracks that sound similar to a given track.
local result, err = client:similar({
asset_id = "123456789", -- required
limit = 10, -- optional
offset = 0, -- optional
playerId = player.UserId, -- optional
filters = { -- optional
genres = { "electronic" },
duration = { min = 60, max = 180 },
},
})
-- result = { tracks, meta }Browse by artist, album, genre, mood, or trending.
-- List all genres
local result, err = client:browse({ type = "genre" })
-- result = { items, meta }
-- Get tracks for a specific genre
local result, err = client:browse({ type = "genre", name = "electronic", limit = 20 })
-- result = { tracks, meta }
-- Trending music (no name needed — returns the top tracks directly)
local result, err = client:browse({ type = "trending", limit = 50 })
-- result = { tracks, meta }Browse types: artist, album, genre, mood, trending
Trending is a popularity-ranked list of music tracks refreshed daily, capped at 200 entries. Player engagement signals (plays, favorites, votes, queue adds, listen duration, plus custom events) are exponentially decayed over a 60-day window with a 30-day half-life, so recent activity dominates.
Browse the SFX catalog. v1 only supports type = "trending" — a popularity-ranked list of sound effects, refreshed daily on the same schedule as music trending.
local result, err = client:sfxBrowse({ type = "trending", limit = 50 })
-- result = { tracks, meta }
-- result.tracks = { { asset_id, name, description, category, subcategory, tags, duration, ... } }Search the sound effects catalog. Pass a free-text query, or browse a UCS category by passing filters.categories (the API synthesizes the query under the hood).
local result, err = client:sfxSearch({
query = "metal sword impact short", -- optional if filters.categories is set
limit = 20, -- optional (default: 20, max: 100)
offset = 0, -- optional
playerId = player.UserId, -- optional
filters = { -- optional
categories = { "WEAPON" }, -- UCS category names
subcategories = { "SWORD" }, -- UCS subcategories
duration = { min = 0, max = 1 }, -- seconds
min_likes = 100, -- min lifetime Roblox likes
created_after = "2024-01-01", -- YYYY-MM-DD
},
})
-- result = { tracks, categories, subcategories, meta }Find sound effects acoustically similar to a given asset.
local result, err = client:sfxSimilar({
asset_id = "9120386436", -- required
limit = 10, -- optional
offset = 0, -- optional
playerId = player.UserId, -- optional
filters = { -- optional
categories = { "AMBIENCE" },
duration = { min = 0, max = 5 },
},
})
-- result = { tracks, meta }Fetch the full broader_category → category → subcategory hierarchy for building SFX picker UIs. Server-cached for 10 minutes, so polling is cheap.
local taxonomy, err = client:getSfxTaxonomy()
-- taxonomy.taxonomy = { { broader_category, categories = { { category, subcategories = { string } } } } }Fetch the beat grid and section structure for a track. Use this to sync animations, lighting, or VFX to the music.
local structure, err = client:getStructure({
asset_id = "1843209165", -- required
})
-- structure = { asset_id, duration, bpm, track_energy, beat_grid, sections, phrases }
-- structure.beat_grid = { times = { number }, downbeats = { number } }
-- structure.sections = { { start, end, label, energy, bar_start, bar_end, color } }label values come from: Intro, Verse, Chorus, Drop, Bridge, Climax, Outro, Main, Break, Build, Breakdown, Transition, Peak. energy is 1–4.
Locate the closest beat to a time. Useful for snapping animations to the grid. Reuses the cached structure response, so repeat calls are free.
local beat, err = client:beatAtTime("1843209165", currentTime)
-- beat = { time, is_downbeat, bar }Locate the section (or phrase, with level = "phrase") covering a time. Trigger different effects on Verse vs Drop.
local section = client:sectionAtTime("1843209165", currentTime)
if section and section.label == "Drop" then
workspace.CurrentCamera.FieldOfView = 70 + section.energy * 5
endFetch a configured playlist and its tracks. Playlists are created in the Developer Portal.
local result, err = client:getPlaylist({
playlist_id = "station-electronic-1712...", -- required
playerId = player.UserId, -- optional
})
-- result = { playlist, tracks, meta }
-- result.playlist = { id, name, genre, playback_mode, track_count }
-- result.tracks = { { asset_id, name, artist, album, genre, duration, bpm, position, ... } }List all playlists configured for your API key.
local result, err = client:listPlaylists(player.UserId)
-- result = { playlists, meta }
-- result.playlists = { { id, name, genre, playback_mode, track_count } }
if result then
for _, playlist in result.playlists do
print(playlist.name, "-", playlist.genre, "-", playlist.track_count, "tracks")
end
endThe AudioScapeMusicPlayer manages audio playback — queue tracks, play, skip — and automatically fires trackPlay, trackStop, and trackSkip analytics events with accurate listen durations.
Create an AudioScapeMusicPlayer instance.
local player = client:createPlayer({
volume = 0.5, -- optional (default: 0.5)
parent = SoundService, -- optional (default: SoundService)
playerId = player.UserId, -- optional, for per-player analytics
})Add tracks to the end of the play queue.
local result = client:search({ query = "upbeat summer" })
player:queue(result.tracks)Replace the queue with new tracks and stop current playback.
Clear the queue and stop playback.
Start playing from the current position in the queue.
Stop the currently playing track. Fires a stop analytics event with listen duration.
Skip to the next track. Fires a skip analytics event with how long the player listened.
-- Skip fires analytics automatically
player:skip()Play a single track immediately, replacing current playback.
local result = client:search({ query = "epic boss battle", limit = 1 })
player:playTrack(result.tracks[1])Set playback volume (0 to 1).
Set or change the player ID used for analytics.
player.OnTrackChanged = function(track)
if track then
print("Now playing:", track.artist, "—", track.name)
end
end
player.OnQueueFinished = function()
print("Queue finished!")
end| Property | Type | Description |
|---|---|---|
player.NowPlaying |
Track? |
Currently playing track, or nil |
player.IsPlaying |
boolean |
Whether audio is currently playing |
player.Queue |
{ Track } |
Current track queue |
The SDK runs on the server (HttpService requires server context). To call AudioScape from LocalScripts, enable client access on the server and use the AudioScapeClient companion module.
Call enableClientAccess() once on the server to create the RemoteFunctions:
local client = AudioScape.new(apiKey)
client:enableClientAccess()This creates an AudioScapeRemotes folder in ReplicatedStorage with a RemoteFunction for each API method. Requests are rate-limited per player (1 request/second) and playerId is automatically set from the calling player.
Place AudioScapeClient.luau in ReplicatedStorage. Then use it from any LocalScript:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AudioScapeClient = require(ReplicatedStorage.AudioScapeClient)
local client = AudioScapeClient.new()
local result, err = client:search({ query = "chill beats", limit = 10 })Available client methods: search, similar, browse, sfxSearch, sfxSimilar, sfxBrowse, getSfxTaxonomy, getStructure, getPlaylist, listPlaylists
Note:
AudioScapeClient.luaumust be placed inReplicatedStoragemanually (or via the Studio plugin). The server SDK's Wally realm isserver, so it installs toServerScriptService— the client module is distributed separately.
Analytics are collected automatically — events are buffered in memory and flushed every 30 seconds (configurable). On game close, remaining events are flushed via game:BindToClose. No player PII is stored; playerId is used only for unique player counts.
Tip: If you use
client:createPlayer(), play/stop/skip events are tracked automatically. The manual tracking methods below are for custom integrations or events the player can't detect (votes, favorites, etc.).
Configure analytics batching behavior. Call before tracking events.
client:configureAnalytics({
enabled = true, -- default: true
batchInterval = 30, -- seconds between flushes (min: 5)
maxBatchSize = 50, -- events per flush (1-500)
maxQueueSize = 500, -- max buffered events (min: 10)
})Track a song play event.
client:trackPlay("rbxassetid://123456789", player.UserId, 120)Track a song stop event (natural end or user action).
Track a song skip event. Duration is how long the player listened before skipping.
Track a vote. Value must be "up" or "down".
client:trackVote("rbxassetid://123456789", "up", player.UserId)Track a favorite event.
Track an unfavorite event.
Track when a player adds a song to a queue, setlist, or playlist.
Track when a player clicks a search result.
Track a custom event with any type name.
client:trackCustom("song_previewed", assetId, player.UserId, {
source = "browse_genre",
position = 3,
})Force flush all buffered events immediately. Called automatically on game close.
Roblox enforces a limit of 500 HTTP requests per minute per game server. Keep this in mind when designing your integration — consider caching results and debouncing player-triggered searches.
All methods return result, err. On failure, result is nil and err is a descriptive string:
local result, err = client:search({ query = "test" })
if not result then
warn("Search failed:", err)
return
endSee the examples/ folder for complete usage examples:
- MusicPlayerBasic.luau — Search, queue, and play with auto-analytics
- MusicPlayerPlaylist.luau — Play a configured playlist with the AudioScapeMusicPlayer
- ClientSearch.luau — Search from a LocalScript using AudioScapeClient
- SearchBox.luau — Wire search to a TextBox input via RemoteEvent
- BrowseGenres.luau — List genres and play a random track
- SimilarTrack.luau — Auto-playlist using similar tracks
- PlaylistStation.luau — Fetch and play a configured station playlist
- SfxImpactPool.luau — Pre-fetch a variety pool of SFX clips and randomize per swing
- StructureBeatSync.luau — Schedule particle bursts on downbeats and punch the camera FOV on Drops
- TrendingLobbyJukebox.luau — Drop-in lobby music from
browse({ type = "trending" })piped through the music player - TrendingSfxBoard.luau — Lobby sound board built from
sfxBrowse({ type = "trending" })
MIT