
Universal Script Sound Hub: Audio Search, Playlists
1 views

Use this Sound Hub for Universal Roblox!! Now you can get features like Play by Audio ID, Favorites, Audio Search, Audio Visualizer and Playlists, and More!! The Search tab has a keyword box with a Search button, a Play by audio ID field, and quick buttons for Play All, Play Selected, + Playlist, Favorite and Copy ID. Results load in a scrolling list where every track shows its ID along with Copy ID, Fav and + List buttons. The menu also lists hotkeys: P to play or pause, N for the next track, B for the previous one and O to stop. No key on this one, so paste the loadstring and go!!
Explore more scripts for this game (5)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local HttpService = game:GetService("HttpService")
local SoundService = game:GetService("SoundService")
if _G.SoundHub_Cleanup then
pcall(_G.SoundHub_Cleanup)
end
local player = Players.LocalPlayer
local connections = {}
local function connect(signal, fn)
local c = signal:Connect(fn)
table.insert(connections, c)
return c
end
local THEME = {
Window = Color3.fromRGB(118, 114, 108),
WindowTop = Color3.fromRGB(152, 148, 142),
WindowBottom = Color3.fromRGB(94, 90, 85),
White = Color3.fromRGB(255, 255, 255),
Inset = Color3.fromRGB(58, 55, 51),
Text = Color3.fromRGB(255, 255, 255),
SubText = Color3.fromRGB(228, 225, 219),
Accent = Color3.fromRGB(10, 132, 255),
Green = Color3.fromRGB(48, 209, 88),
Red = Color3.fromRGB(255, 69, 58),
Orange = Color3.fromRGB(255, 159, 10),
Off = Color3.fromRGB(142, 142, 147),
}
local FONT = Enum.Font.GothamMedium
local FONT_BOLD = Enum.Font.GothamBold
local LEFT, CENTER, RIGHT = Enum.TextXAlignment.Left, Enum.TextXAlignment.Center, Enum.TextXAlignment.Right
local FILES = {
playlist = "SoundHub_Playlist.json",
favorites = "SoundHub_Favorites.json",
recent = "SoundHub_Recent.json",
preset = "SoundHub_Preset.json",
config = "SoundHub_Config.json",
}
local CFG = {
masterVolume = 0.6,
playbackSpeed = 1,
fadeTime = 0.5,
soundMode = "2D",
autoNext = true,
shuffle = false,
repeatMode = "off",
uiKey = Enum.KeyCode.RightShift,
}
local playlist, favorites, recent, customPreset = {}, {}, {}, {}
local notify, refreshNowPlaying, cleanup
local renderPlaylist, renderFavorites, renderRecent, renderCustom
local onEnded, playFromQueue
local playlistList, favoritesList, recentList, customList, searchList
local hasFS = type(writefile) == "function" and type(readfile) == "function" and type(isfile) == "function"
local function writeJson(name, data)
if not hasFS then
return
end
pcall(function()
writefile(name, HttpService:JSONEncode(data))
end)
end
local function readJson(name)
if not hasFS then
return nil
end
local ok, result = pcall(function()
if isfile(name) then
return HttpService:JSONDecode(readfile(name))
end
return nil
end)
if ok then
return result
end
return nil
end
local function idStr(value)
if type(value) == "number" then
return string.format("%.0f", value)
end
return tostring(value)
end
local function cleanList(raw)
local out = {}
if type(raw) == "table" then
for _, v in ipairs(raw) do
if type(v) == "table" and v.id then
local id = idStr(v.id)
table.insert(out, { id = id, name = tostring(v.name or ("Audio " .. id)) })
end
end
end
return out
end
local function cfgToTable()
return {
masterVolume = CFG.masterVolume,
playbackSpeed = CFG.playbackSpeed,
fadeTime = CFG.fadeTime,
soundMode = CFG.soundMode,
autoNext = CFG.autoNext,
shuffle = CFG.shuffle,
repeatMode = CFG.repeatMode,
uiKey = CFG.uiKey.Name,
}
end
local function cfgFromTable(t)
if type(t) ~= "table" then
return
end
if type(t.masterVolume) == "number" then
CFG.masterVolume = math.clamp(t.masterVolume, 0, 1)
end
if type(t.playbackSpeed) == "number" then
CFG.playbackSpeed = math.clamp(t.playbackSpeed, 0.5, 2)
end
if type(t.fadeTime) == "number" then
CFG.fadeTime = math.clamp(t.fadeTime, 0, 3)
end
if t.soundMode == "2D" or t.soundMode == "3D" then
CFG.soundMode = t.soundMode
end
if type(t.autoNext) == "boolean" then
CFG.autoNext = t.autoNext
end
if type(t.shuffle) == "boolean" then
CFG.shuffle = t.shuffle
end
if t.repeatMode == "off" or t.repeatMode == "all" or t.repeatMode == "one" then
CFG.repeatMode = t.repeatMode
end
if type(t.uiKey) == "string" then
local ok, key = pcall(function()
return Enum.KeyCode[t.uiKey]
end)
if ok and key then
CFG.uiKey = key
end
end
end
local function savePlaylist()
writeJson(FILES.playlist, playlist)
end
local function saveFavorites()
writeJson(FILES.favorites, favorites)
end
local function saveRecent()
writeJson(FILES.recent, recent)
end
local function savePreset()
writeJson(FILES.preset, customPreset)
end
local function saveConfig()
writeJson(FILES.config, cfgToTable())
end
local function saveAll()
savePlaylist()
saveFavorites()
saveRecent()
savePreset()
saveConfig()
end
local function loadAll()
playlist = cleanList(readJson(FILES.playlist))
favorites = cleanList(readJson(FILES.favorites))
recent = cleanList(readJson(FILES.recent))
customPreset = cleanList(readJson(FILES.preset))
cfgFromTable(readJson(FILES.config))
end
loadAll()
local function renderAll()
if renderPlaylist then
renderPlaylist()
end
if renderFavorites then
renderFavorites()
end
if renderRecent then
renderRecent()
end
if renderCustom then
renderCustom()
end
end
local function copyText(text)
local fn = setclipboard or toclipboard or set_clipboard
if fn then
local ok = pcall(fn, text)
return ok
end
return false
end
local function copyId(id)
if copyText(tostring(id)) then
notify("Copied ID " .. tostring(id), THEME.Green)
else
notify("Clipboard is not supported by your executor", THEME.Red)
end
end
local function hasTrack(list, id)
for i, t in ipairs(list) do
if t.id == id then
return i
end
end
return nil
end
local function addUnique(list, track)
if hasTrack(list, track.id) then
return false
end
table.insert(list, { id = track.id, name = track.name })
return true
end
local function httpGet(url)
local ok, res = pcall(function()
return game:HttpGet(url)
end)
if ok and type(res) == "string" and #res > 0 then
return true, res
end
local req = (syn and syn.request) or http_request or request
if req then
local ok2, r = pcall(req, { Url = url, Method = "GET" })
if ok2 and r then
if r.StatusCode == 200 and r.Body then
return true, r.Body
end
return false, "HTTP " .. tostring(r.StatusCode)
end
return false, tostring(r)
end
return false, tostring(res)
end
local function extractItems(data)
local list = data.data or data.creatorStoreAssets or data.assets or data.items or data
local out = {}
if type(list) ~= "table" then
return out
end
for _, e in ipairs(list) do
if type(e) == "table" then
local a = e.asset or e.assetInfo or e
local rawId = a.id or a.assetId or e.id
if rawId then
local typeId = a.typeId or a.assetTypeId or a.assetType
if typeId == nil or typeId == 3 or typeId == "Audio" then
table.insert(out, { id = idStr(rawId), name = a.name or e.name })
end
end
end
end
return out
end
local function fetchNames(ids)
local map = {}
local i = 1
while i <= #ids do
local batch = {}
for j = i, math.min(i + 29, #ids) do
table.insert(batch, ids[j])
end
local ok, body = httpGet("https://apis.roblox.com/toolbox-service/v1/items/details?assetIds=" .. table.concat(batch, ","))
if ok then
local okD, decoded = pcall(function()
return HttpService:JSONDecode(body)
end)
if okD and type(decoded) == "table" then
local list = decoded.data or decoded
if type(list) == "table" then
for _, e in ipairs(list) do
if type(e) == "table" then
local a = e.asset or e
local rawId = a.id or a.assetId
if rawId and a.name then
map[idStr(rawId)] = tostring(a.name)
end
end
end
end
end
end
i += 30
end
return map
end
local function fillNames(items)
local missing = {}
for _, it in ipairs(items) do
if not it.name then
table.insert(missing, it.id)
end
end
if #missing > 0 then
local map = fetchNames(missing)
for _, it in ipairs(items) do
if not it.name then
it.name = map[it.id]
end
end
end
for _, it in ipairs(items) do
it.name = tostring(it.name or ("Audio " .. it.id))
end
end
local ENDPOINTS = {
function(q, page)
return "https://apis.roblox.com/toolbox-service/v1/marketplace/3?limit=30&pageNumber=" .. page .. "&keyword=" .. HttpService:UrlEncode(q) .. "&sortType=0"
end,
function(q, page)
return "https://apis.roblox.com/toolbox-service/v2/assets:search?searchCategoryType=Audio&query=" .. HttpService:UrlEncode(q) .. "&maxPageSize=30&pageNumber=" .. page
end,
}
local function fetchAudio(query, page)
local errors = {}
for i, builder in ipairs(ENDPOINTS) do
local ok, body = httpGet(builder(query, page))
if ok then
local okD, decoded = pcall(function()
return HttpService:JSONDecode(body)
end)
if okD and type(decoded) == "table" then
local items = extractItems(decoded)
if #items > 0 then
fillNames(items)
return items, nil
end
table.insert(errors, "A" .. i .. ": empty")
else
table.insert(errors, "A" .. i .. ": bad response")
end
else
table.insert(errors, "A" .. i .. ": " .. tostring(body))
end
end
return {}, table.concat(errors, " | ")
end
local playToken = 0
local currentSound, currentTrack = nil, nil
local queue, queueIdx = {}, 0
local failStreak = 0
local muted = false
local fadeTween
local function soundParent()
if CFG.soundMode == "3D" then
local char = player.Character
local root = char and char:FindFirstChild("HumanoidRootPart")
if root then
return root
end
end
return SoundService
end
local function newSound(id)
local s = Instance.new("Sound")
s.Name = "SoundHub_Current"
s.SoundId = "rbxassetid://" .. id
s.Volume = 0
s.Looped = false
s.PlaybackSpeed = CFG.playbackSpeed
s.RollOffMode = Enum.RollOffMode.InverseTapered
s.RollOffMinDistance = 5
s.RollOffMaxDistance = 120
return s
end
local function applyVolume()
if fadeTween then
fadeTween:Cancel()
fadeTween = nil
end
if currentSound then
currentSound.Volume = muted and 0 or CFG.masterVolume
end
end
local function addRecent(track)
local i = hasTrack(recent, track.id)
if i then
table.remove(recent, i)
end
table.insert(recent, 1, { id = track.id, name = track.name })
while #recent > 40 do
table.remove(recent)
end
saveRecent()
if renderRecent then
renderRecent()
end
end
local function pickIndex(dir)
local n = #queue
if n == 0 then
return nil
end
if CFG.shuffle and n > 1 then
local i
repeat
i = math.random(n)
until i ~= queueIdx
return i
end
local i = queueIdx + dir
if i > n then
i = 1
elseif i < 1 then
i = n
end
return i
end
local function stopSound()
playToken += 1
local s = currentSound
currentSound = nil
currentTrack = nil
if fadeTween then
fadeTween:Cancel()
fadeTween = nil
end
if s then
if CFG.fadeTime > 0 then
local tw = TweenService:Create(s, TweenInfo.new(CFG.fadeTime, Enum.EasingStyle.Linear), { Volume = 0 })
tw.Completed:Connect(function()
pcall(function()
s:Destroy()
end)
end)
tw:Play()
else
pcall(function()
s:Destroy()
end)
end
end
if refreshNowPlaying then
refreshNowPlaying()
end
end
local function playTrack(track)
playToken += 1
local token = playToken
if fadeTween then
fadeTween:Cancel()
fadeTween = nil
end
if currentSound then
local old = currentSound
currentSound = nil
pcall(function()
old:Destroy()
end)
end
local s = newSound(track.id)
currentSound = s
currentTrack = track
s.Parent = soundParent()
s:Play()
if CFG.fadeTime > 0 then
fadeTween = TweenService:Create(s, TweenInfo.new(CFG.fadeTime, Enum.EasingStyle.Linear), { Volume = muted and 0 or CFG.masterVolume })
fadeTween:Play()
else
s.Volume = muted and 0 or CFG.masterVolume
end
addRecent(track)
refreshNowPlaying()
s.Ended:Connect(function()
if currentSound == s then
onEnded()
end
end)
task.spawn(function()
local t0 = tick()
while tick() - t0 < 8 do
if playToken ~= token then
return
end
if s.IsLoaded and s.TimeLength > 0 then
failStreak = 0
return
end
task.wait(0.2)
end
if playToken ~= token then
return
end
failStreak += 1
notify("Cannot play (private or removed): " .. track.name, THEME.Red)
if failStreak < 5 and #queue > 1 then
local nextIdx = pickIndex(1)
if nextIdx then
playFromQueue(nextIdx)
end
else
failStreak = 0
stopSound()
end
end)
end
function playFromQueue(i)
if not i or not queue[i] then
return
end
queueIdx = i
playTrack(queue[i])
end
local function playList(items, idx)
if #items == 0 then
return
end
queue = {}
for _, t in ipairs(items) do
table.insert(queue, { id = t.id, name = t.name })
end
playFromQueue(math.clamp(idx or 1, 1, #queue))
end
local function shufflePlay(items)
if #items == 0 then
return
end
local copy = {}
for _, t in ipairs(items) do
table.insert(copy, { id = t.id, name = t.name })
end
for i = #copy, 2, -1 do
local j = math.random(i)
copy[i], copy[j] = copy[j], copy[i]
end
queue = copy
playFromQueue(1)
end
function onEnded()
if not currentSound then
return
end
if CFG.repeatMode == "one" then
currentSound.TimePosition = 0
currentSound:Play()
return
end
if not CFG.autoNext or #queue == 0 then
return
end
if not CFG.shuffle and CFG.repeatMode == "off" and queueIdx >= #queue then
stopSound()
return
end
playFromQueue(pickIndex(1))
end
local function playNext()
if #queue == 0 then
return
end
playFromQueue(pickIndex(1))
end
local function playPrev()
if currentSound and currentSound.TimePosition > 3 then
currentSound.TimePosition = 0
return
end
if #queue == 0 then
return
end
playFromQueue(pickIndex(-1))
end
local function playPause()
if currentSound then
if currentSound.IsPaused then
currentSound:Resume()
elseif currentSound.IsPlaying then
currentSound:Pause()
else
currentSound:Play()
end
elseif #queue > 0 then
playFromQueue(queueIdx > 0 and queueIdx or 1)
elseif #playlist > 0 then
playList(playlist, 1)
end
end
local function reparentSound()
if currentSound then
currentSound.Parent = soundParent()
end
end
local function resolveName(track)
task.spawn(function()
local map = fetchNames({ track.id })
local name = map[track.id]
if not name then
return
end
track.name = name
for _, list in ipairs({ playlist, favorites, recent, customPreset }) do
for _, t in ipairs(list) do
if t.id == track.id and t.name:match("^Audio %d+$") then
t.name = name
end
end
end
saveAll()
renderAll()
if refreshNowPlaying then
refreshNowPlaying()
end
end)
end
local function toggleFavorite(track)
local i = hasTrack(favorites, track.id)
if i then
table.remove(favorites, i)
notify("Removed from favorites", THEME.Orange)
else
addUnique(favorites, track)
notify("Added to favorites", THEME.Green)
end
saveFavorites()
renderFavorites()
end
local function addToPlaylist(track)
if addUnique(playlist, track) then
notify("Added to playlist", THEME.Green)
else
notify("Already in playlist", THEME.Orange)
end
savePlaylist()
renderPlaylist()
end
local function parseId(text)
return tostring(text or ""):match("%d+")
end
local function make(class, props, parent)
local inst = Instance.new(class)
for k, v in pairs(props) do
inst[k] = v
end
inst.Parent = parent
return inst
end
local function corner(inst, r)
return make("UICorner", { CornerRadius = UDim.new(0, r) }, inst)
end
local function stroke(inst, color, transparency, thickness)
return make("UIStroke", {
Color = color,
Transparency = transparency,
Thickness = thickness or 1,
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
}, inst)
end
local function tween(inst, t, props)
local tw = TweenService:Create(inst, TweenInfo.new(t, Enum.EasingStyle.Quad, Enum.EasingDirection.Out), props)
tw:Play()
return tw
end
local function nextOrder(parent)
local n = (parent:GetAttribute("order_n") or 0) + 1
parent:SetAttribute("order_n", n)
return n
end
local function label(parent, props)
local l = Instance.new("TextLabel")
l.BackgroundTransparency = 1
l.BorderSizePixel = 0
l.Font = FONT
l.TextSize = 13
l.TextColor3 = THEME.Text
l.TextXAlignment = LEFT
l.Text = ""
for k, v in pairs(props) do
l[k] = v
end
l.Parent = parent
return l
end
local function button(parent, text, callback, style)
local baseT, color = 0.8, THEME.White
if style == "accent" then
baseT, color = 0.05, THEME.Accent
elseif style == "danger" then
baseT, color = 0.1, THEME.Red
elseif style == "ghost" then
baseT = 1
end
local b = make("TextButton", {
AutoButtonColor = false,
BorderSizePixel = 0,
Font = FONT_BOLD,
TextSize = 12,
Text = text,
TextColor3 = THEME.Text,
BackgroundColor3 = color,
BackgroundTransparency = baseT,
Size = UDim2.new(0, 80, 0, 32),
LayoutOrder = nextOrder(parent),
}, nil)
b:SetAttribute("base", baseT)
corner(b, 999)
b.MouseEnter:Connect(function()
tween(b, 0.12, { BackgroundTransparency = math.max((b:GetAttribute("base") or baseT) - 0.12, 0) })
end)
b.MouseLeave:Connect(function()
tween(b, 0.12, { BackgroundTransparency = b:GetAttribute("base") or baseT })
end)
b.MouseButton1Click:Connect(function()
if callback then
callback(b)
end
end)
b.Parent = parent
return b
end
local function setActive(b, on)
b.BackgroundColor3 = on and THEME.Accent or THEME.White
local t = on and 0.05 or 0.8
b:SetAttribute("base", t)
b.BackgroundTransparency = t
end
local function section(parent, text)
return label(parent, {
Text = string.upper(text),
Font = FONT_BOLD,
TextSize = 11,
TextColor3 = THEME.SubText,
TextTransparency = 0.15,
Size = UDim2.new(1, 0, 0, 18),
LayoutOrder = nextOrder(parent),
})
end
local function card(parent, height)
local c = make("Frame", {
BackgroundColor3 = THEME.White,
BackgroundTransparency = 0.86,
BorderSizePixel = 0,
Size = UDim2.new(1, 0, 0, height),
LayoutOrder = nextOrder(parent),
}, parent)
corner(c, 18)
stroke(c, THEME.White, 0.88, 1)
return c
end
local function textBox(parent, placeholder)
local container = make("Frame", {
BackgroundColor3 = THEME.Inset,
BackgroundTransparency = 0.5,
BorderSizePixel = 0,
Size = UDim2.new(1, 0, 0, 36),
LayoutOrder = nextOrder(parent),
}, parent)
corner(container, 999)
stroke(container, THEME.White, 0.88, 1)
local box = make("TextBox", {
BackgroundTransparency = 1,
BorderSizePixel = 0,
Position = UDim2.new(0, 16, 0, 0),
Size = UDim2.new(1, -32, 1, 0),
Font = FONT,
TextSize = 13,
TextColor3 = THEME.Text,
PlaceholderText = placeholder,
PlaceholderColor3 = Color3.fromRGB(205, 202, 196),
Text = "",
TextXAlignment = LEFT,
ClearTextOnFocus = false,
}, container)
return box, container
end
local function inputRow(parent, placeholder, btnText, btnW, onSubmit, accent)
local row = make("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 36),
LayoutOrder = nextOrder(parent),
}, parent)
local box, container = textBox(row, placeholder)
container.Size = UDim2.new(1, -(btnW + 8), 1, 0)
local b = button(row, btnText, function()
onSubmit(box.Text, box)
end, accent and "accent" or nil)
b.AnchorPoint = Vector2.new(1, 0)
b.Position = UDim2.new(1, 0, 0, 0)
b.Size = UDim2.new(0, btnW, 1, 0)
connect(box.FocusLost, function(enter)
if enter then
onSubmit(box.Text, box)
end
end)
return box, b
end
local function grid(parent, columns, count, rowH)
rowH = rowH or 32
local rows = math.ceil(count / columns)
local pad = 8
local g = make("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, rows * rowH + (rows - 1) * pad),
LayoutOrder = nextOrder(parent),
}, parent)
make("UIGridLayout", {
CellSize = UDim2.new(1 / columns - 0.001, -math.ceil(pad * (columns - 1) / columns), 0, rowH),
CellPadding = UDim2.new(0, pad, 0, pad),
SortOrder = Enum.SortOrder.LayoutOrder,
FillDirection = Enum.FillDirection.Horizontal,
}, g)
return g
end
local function bindDrag(hit, track, onChange, onDone)
local dragging = false
local function update(x)
local a = math.clamp((x - track.AbsolutePosition.X) / math.max(track.AbsoluteSize.X, 1), 0, 1)
onChange(a)
end
hit.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
update(input.Position.X)
end
end)
connect(UserInputService.InputChanged, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
update(input.Position.X)
end
end)
connect(UserInputService.InputEnded, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then
dragging = false
if onDone then
onDone()
end
end
end)
return function()
return dragging
end
end
local function sliderParts(parent, position, size)
local hit = make("Frame", {
BackgroundTransparency = 1,
Position = position,
Size = size,
}, parent)
local track = make("Frame", {
BackgroundColor3 = THEME.Inset,
BackgroundTransparency = 0.35,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.new(0, 0, 0.5, 0),
Size = UDim2.new(1, 0, 0, 8),
}, hit)
corner(track, 999)
local fill = make("Frame", {
BackgroundColor3 = THEME.White,
BackgroundTransparency = 0.05,
BorderSizePixel = 0,
Size = UDim2.new(0, 0, 1, 0),
}, track)
corner(fill, 999)
local knob = make("Frame", {
BackgroundColor3 = THEME.White,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0, 0, 0.5, 0),
Size = UDim2.new(0, 18, 0, 18),
ZIndex = 3,
}, track)
corner(knob, 999)
stroke(knob, Color3.fromRGB(0, 0, 0), 0.85, 1)
return hit, track, fill, knob
end
local function makeSlider(parent, text, minV, maxV, default, fmt, callback)
local c = card(parent, 56)
label(c, { Text = text, Font = FONT_BOLD, Position = UDim2.new(0, 16, 0, 8), Size = UDim2.new(0.6, 0, 0, 18) })
local valueLabel = label(c, {
Position = UDim2.new(0.5, 0, 0, 8),
Size = UDim2.new(0.5, -16, 0, 18),
TextXAlignment = RIGHT,
TextColor3 = THEME.SubText,
})
local hit, track, fill, knob = sliderParts(c, UDim2.new(0, 16, 0, 28), UDim2.new(1, -32, 0, 24))
local value = default
local function visual(v)
local a = (v - minV) / (maxV - minV)
fill.Size = UDim2.new(a, 0, 1, 0)
knob.Position = UDim2.new(a, 0, 0.5, 0)
valueLabel.Text = fmt(v)
end
visual(default)
bindDrag(hit, track, function(a)
local v = minV + (maxV - minV) * a
value = math.floor(v * 100 + 0.5) / 100
visual(value)
callback(value, false)
end, function()
callback(value, true)
end)
return function(v)
value = v
visual(v)
end
end
local function makeToggle(parent, text, default, callback)
local c = card(parent, 44)
label(c, { Text = text, Font = FONT_BOLD, Position = UDim2.new(0, 16, 0, 0), Size = UDim2.new(0.7, 0, 1, 0) })
local sw = make("TextButton", {
AutoButtonColor = false,
Text = "",
BorderSizePixel = 0,
AnchorPoint = Vector2.new(1, 0.5),
Position = UDim2.new(1, -14, 0.5, 0),
Size = UDim2.new(0, 52, 0, 30),
BackgroundColor3 = THEME.Off,
BackgroundTransparency = 0.2,
}, c)
corner(sw, 999)
local knob = make("Frame", {
BackgroundColor3 = THEME.White,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0, 0.5),
Position = UDim2.new(0, 3, 0.5, 0),
Size = UDim2.new(0, 24, 0, 24),
}, sw)
corner(knob, 999)
local state = default
local function apply(animated)
local props1 = { BackgroundColor3 = state and THEME.Green or THEME.Off, BackgroundTransparency = state and 0 or 0.2 }
local props2 = { Position = state and UDim2.new(1, -27, 0.5, 0) or UDim2.new(0, 3, 0.5, 0) }
if animated then
tween(sw, 0.18, props1)
tween(knob, 0.18, props2)
else
for k, v in pairs(props1) do
sw[k] = v
end
for k, v in pairs(props2) do
knob[k] = v
end
end
end
apply(false)
sw.MouseButton1Click:Connect(function()
state = not state
apply(true)
callback(state)
end)
return function(v)
state = v
apply(false)
end
end
local allLists = {}
local function newList(parent, size, opts)
local L = { items = {}, rows = {}, selected = nil }
local holder = make("Frame", {
BackgroundColor3 = THEME.Inset,
BackgroundTransparency = 0.6,
BorderSizePixel = 0,
Size = size,
LayoutOrder = nextOrder(parent),
}, parent)
corner(holder, 20)
stroke(holder, THEME.White, 0.9, 1)
local scroll = make("ScrollingFrame", {
BackgroundTransparency = 1,
BorderSizePixel = 0,
Position = UDim2.new(0, 6, 0, 6),
Size = UDim2.new(1, -12, 1, -12),
CanvasSize = UDim2.new(),
AutomaticCanvasSize = Enum.AutomaticSize.Y,
ScrollBarThickness = 3,
ScrollBarImageColor3 = THEME.White,
ScrollBarImageTransparency = 0.5,
ScrollingDirection = Enum.ScrollingDirection.Y,
}, holder)
make("UIListLayout", { Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder }, scroll)
local empty = label(holder, {
Text = opts.empty or "Nothing here yet",
Size = UDim2.new(1, 0, 1, 0),
TextXAlignment = CENTER,
TextColor3 = THEME.SubText,
TextTransparency = 0.2,
})
L.scroll = scroll
function L.refresh()
local cur = currentTrack and currentTrack.id
for i, r in ipairs(L.rows) do
local item = L.items[i]
local active = opts.active and cur ~= nil and item.id == cur
r.frame.BackgroundColor3 = active and THEME.Accent or THEME.White
r.frame.BackgroundTransparency = active and 0.45 or 0.86
r.stroke.Transparency = (L.selected == i) and 0.1 or 0.9
end
end
function L.add(item)
local idx = #L.items + 1
L.items[idx] = item
local frame = make("Frame", {
BackgroundColor3 = THEME.White,
BackgroundTransparency = 0.86,
BorderSizePixel = 0,
Size = UDim2.new(1, 0, 0, 48),
LayoutOrder = idx,
}, scroll)
corner(frame, 14)
local rowStroke = stroke(frame, THEME.White, 0.9, 1)
local actions = opts.actions or {}
local widths, total = {}, 0
for i, a in ipairs(actions) do
widths[i] = math.max(36, #a.text * 7 + 22)
total += widths[i] + 6
end
local hit = make("TextButton", {
BackgroundTransparency = 1,
Text = "",
AutoButtonColor = false,
Size = UDim2.new(1, 0, 1, 0),
}, frame)
hit.MouseButton1Click:Connect(function()
if opts.select then
L.selected = idx
L.refresh()
end
if opts.click then
opts.click(item, idx)
end
end)
hit.MouseButton2Click:Connect(function()
copyId(item.id)
end)
label(frame, {
Text = item.name,
Font = FONT_BOLD,
Position = UDim2.new(0, 16, 0, 6),
Size = UDim2.new(1, -(total + 30), 0, 18),
TextTruncate = Enum.TextTruncate.AtEnd,
})
label(frame, {
Text = "ID " .. item.id,
TextSize = 11,
TextColor3 = THEME.SubText,
Position = UDim2.new(0, 16, 0, 26),
Size = UDim2.new(1, -(total + 30), 0, 16),
})
local x = -10
for i = #actions, 1, -1 do
local a = actions[i]
local b = button(frame, a.text, function()
a.fn(item, idx)
end)
b.AnchorPoint = Vector2.new(1, 0.5)
b.Position = UDim2.new(1, x, 0.5, 0)
b.Size = UDim2.new(0, widths[i], 0, 28)
x -= widths[i] + 6
end
L.rows[idx] = { frame = frame, stroke = rowStroke }
empty.Visible = false
L.refresh()
end
function L.clear()
for _, r in ipairs(L.rows) do
r.frame:Destroy()
end
L.rows = {}
L.items = {}
L.selected = nil
scroll.CanvasPosition = Vector2.new(0, 0)
empty.Visible = true
end
function L.set(items, keep)
local pos = scroll.CanvasPosition
L.clear()
for _, it in ipairs(items) do
L.add(it)
end
if keep then
task.defer(function()
scroll.CanvasPosition = pos
end)
end
end
table.insert(allLists, L)
return L
end
local guiHolders = {}
if type(gethui) == "function" then
local ok, h = pcall(gethui)
if ok and h then
table.insert(guiHolders, h)
end
end
pcall(function()
table.insert(guiHolders, game:GetService("CoreGui"))
end)
table.insert(guiHolders, player:WaitForChild("PlayerGui"))
for _, holder in ipairs(guiHolders) do
pcall(function()
local old = holder:FindFirstChild("SoundHub_UI")
if old then
old:Destroy()
end
end)
end
local gui = make("ScreenGui", {
Name = "SoundHub_UI",
ResetOnSpawn = false,
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
DisplayOrder = 50,
IgnoreGuiInset = true,
}, nil)
local parented = pcall(function()
gui.Parent = guiHolders[1]
end)
if not parented or not gui.Parent then
gui.Parent = player:WaitForChild("PlayerGui")
end
local main = make("Frame", {
Name = "Main",
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.new(0.5, 0, 0.5, 0),
Size = UDim2.new(0, 680, 0, 560),
BackgroundColor3 = THEME.Window,
BackgroundTransparency = 0.12,
BorderSizePixel = 0,
Active = true,
}, gui)
corner(main, 30)
stroke(main, THEME.White, 0.6, 1.2)
make("UIGradient", {
Color = ColorSequence.new(THEME.WindowTop, THEME.WindowBottom),
Rotation = 90,
}, main)
local uiScale = make("UIScale", {}, main)
local function updateScale()
local cam = workspace.CurrentCamera
if not cam then
return
end
local v = cam.ViewportSize
uiScale.Scale = math.clamp(math.min(v.X / 720, v.Y / 600), 0.5, 1)
end
updateScale()
if workspace.CurrentCamera then
connect(workspace.CurrentCamera:GetPropertyChangedSignal("ViewportSize"), updateScale)
end
local toast = make("Frame", {
AnchorPoint = Vector2.new(0.5, 0),
Position = UDim2.new(0.5, 0, 0, -60),
Size = UDim2.new(0, 0, 0, 38),
AutomaticSize = Enum.AutomaticSize.X,
BackgroundColor3 = Color3.fromRGB(104, 100, 95),
BackgroundTransparency = 0.08,
BorderSizePixel = 0,
ZIndex = 100,
}, gui)
corner(toast, 999)
stroke(toast, THEME.White, 0.6, 1)
make("UIPadding", { PaddingLeft = UDim.new(0, 16), PaddingRight = UDim.new(0, 18) }, toast)
make("UIListLayout", {
FillDirection = Enum.FillDirection.Horizontal,
VerticalAlignment = Enum.VerticalAlignment.Center,
Padding = UDim.new(0, 10),
SortOrder = Enum.SortOrder.LayoutOrder,
}, toast)
local toastDot = make("Frame", {
Size = UDim2.new(0, 8, 0, 8),
BackgroundColor3 = THEME.Green,
BorderSizePixel = 0,
ZIndex = 101,
LayoutOrder = 1,
}, toast)
corner(toastDot, 999)
local toastText = label(toast, {
AutomaticSize = Enum.AutomaticSize.X,
Size = UDim2.new(0, 0, 1, 0),
LayoutOrder = 2,
ZIndex = 101,
Font = FONT_BOLD,
})
local toastToken = 0
function notify(msg, color)
toastToken += 1
local token = toastToken
msg = tostring(msg)
if #msg > 70 then
msg = msg:sub(1, 67) .. "..."
end
toastText.Text = msg
toastDot.BackgroundColor3 = color or THEME.Green
tween(toast, 0.25, { Position = UDim2.new(0.5, 0, 0, 16) })
task.delay(2.2, function()
if token == toastToken then
tween(toast, 0.3, { Position = UDim2.new(0.5, 0, 0, -60) })
end
end)
end
local topBar = make("Frame", {
BackgroundTransparency = 1,
Size = UDim2.new(1, 0, 0, 48),
Active = true,
}, main)
label(topBar, {
Text = "audio player or smth",
Font = FONT_BOLD,
TextSize = 18,
Position = UDim2.new(0, 24, 0, 0),
Size = UDim2.new(0, 200, 1, 0),
})
local floatBtn
local function setVisible(v)
main.Visible = v
if floatBtn then
floatBtn.Visible = not v
end
end
local hideBtn = button(topBar, "Hide", function()
setVisible(false)
end)
hideBtn.AnchorPoint = Vector2.new(1, 0.5)
hideBtn.Position = UDim2.new(1, -86, 0.5, 0)
hideBtn.Size = UDim2.new(0, 62, 0, 28)
local closeBtn = button(topBar, "Close", function()
cleanup()
end, "danger")
closeBtn.AnchorPoint = Vector2.new(1, 0.5)
closeBtn.Position = UDim2.new(1, -18, 0.5, 0)
closeBtn.Size = UDim2.new(0, 62, 0, 28)
floatBtn = button(gui, "Sound Hub", function()
setVisible(true)
end)
floatBtn.AnchorPoint = Vector2.new(0.5, 0)
floatBtn.Position = UDim2.new(0.5, 0, 0, 10)
floatBtn.Size = UDim2.new(0, 110, 0, 32)
floatBtn.BackgroundColor3 = Color3.fromRGB(104, 100, 95)
floatBtn:SetAttribute("base", 0.1)
floatBtn.BackgroundTransparency = 0.1
floatBtn.Visible = false
do
local dragging, dragStart, startPos = false, nil, nil
connect(topBar.InputBegan, function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
dragStart = input.Position
startPos = main.Position
end
end)
connect(UserInputService.InputChanged, function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
local d = input.Position - dragStart
local s = uiScale.Scale
main.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + d.X / s, startPos.Y.Scale, startPos.Y.Offset + d.Y / s)
end
end)
connect(UserInputService.InputEnded, function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = false
end
end)
end
local sidebar = make("Frame", {
BackgroundColor3 = THEME.White,
BackgroundTransparency = 0.9,
BorderSizePixel = 0,
Position = UDim2.new(0, 10, 0, 56),
Size = UDim2.new(0, 150, 0, 390),
}, main)
corner(sidebar, 22)
stroke(sidebar, THEME.White, 0.88, 1)
make("UIPadding", {
PaddingTop = UDim.new(0, 8),
PaddingBottom = UDim.new(0, 8),
PaddingLeft = UDim.new(0, 8),
PaddingRight = UDim.new(0, 8),
}, sidebar)
make("UIListLayout", { Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder }, sidebar)
local content = make("Frame", {
BackgroundTransparency = 1,
Position = UDim2.new(0, 170, 0, 56),
Size = UDim2.new(0, 500, 0, 390),
}, main)
local pages, tabButtons = {}, {}
local TAB_NAMES = { "Search", "Playlist", "Favorites", "Recent", "Presets", "Visualizer", "Settings" }
local function selectTab(name)
for tabName, page in pairs(pages) do
page.Visible = tabName == name
end
for tabName, b in pairs(tabButtons) do
local active = tabName == name
b:SetAttribute("base", active and 0.62 or 1)
b.BackgroundTransparency = active and 0.62 or 1
b.TextColor3 = active and THEME.Text or THEME.SubText
end
end
for _, name in ipairs(TAB_NAMES) do
local b = button(sidebar, name, function()
selectTab(name)
end, "ghost")
b.Size = UDim2.new(1, 0, 0, 38)
b.TextSize = 13
tabButtons[name] = b
end
local function newPage(name, scrolling)
local p
if scrolling then
p = make("ScrollingFrame", {
BackgroundTransparency = 1,
BorderSizePixel = 0,
CanvasSize = UDim2.new(),
AutomaticCanvasSize = Enum.AutomaticSize.Y,
ScrollBarThickness = 3,
ScrollBarImageColor3 = THEME.White,
ScrollBarImageTransparency = 0.5,
}, content)
make("UIPadding", { PaddingRight = UDim.new(0, 8) }, p)
else
p = make("Frame", { BackgroundTransparency = 1 }, content)
end
p.Name = name
p.Size = UDim2.new(1, 0, 1, 0)
p.Visible = false
make("UIListLayout", { Padding = UDim.new(0, 8), SortOrder = Enum.SortOrder.LayoutOrder }, p)
pages[name] = p
return p
end
local bar = make("Frame", {
BackgroundColor3 = THEME.White,
BackgroundTransparency = 0.84,
BorderSizePixel = 0,
Position = UDim2.new(0, 10, 1, -106),
Size = UDim2.new(1, -20, 0, 96),
}, main)
corner(bar, 26)
stroke(bar, THEME.White, 0.8, 1)
local titleLabel = label(bar, {
Text = "Nothing playing",
Font = FONT_BOLD,
TextSize = 14,
Position = UDim2.new(0, 20, 0, 8),
Size = UDim2.new(1, -250, 0, 20),
TextTruncate = Enum.TextTruncate.AtEnd,
})
local idLabel = label(bar, {
TextSize = 11,
TextColor3 = THEME.SubText,
Position = UDim2.new(1, -220, 0, 8),
Size = UDim2.new(0, 200, 0, 20),
TextXAlignment = RIGHT,
})
local timeLeft = label(bar, {
Text = "0:00",
TextSize = 11,
TextColor3 = THEME.SubText,
Position = UDim2.new(0, 20, 0, 34),
Size = UDim2.new(0, 40, 0, 14),
})
local timeRight = label(bar, {
Text = "0:00",
TextSize = 11,
TextColor3 = THEME.SubText,
Position = UDim2.new(1, -60, 0, 34),
Size = UDim2.new(0, 40, 0, 14),
TextXAlignment = RIGHT,
})
local progHit, progTrack, progFill, progKnob = sliderParts(bar, UDim2.new(0, 64, 0, 30), UDim2.new(1, -128, 0, 22))
progTrack.Size = UDim2.new(1, 0, 0, 6)
progKnob.Size = UDim2.new(0, 12, 0, 12)
local seekRatio = 0
local progDragging = bindDrag(progHit, progTrack, function(a)
seekRatio = a
progFill.Size = UDim2.new(a, 0, 1, 0)
progKnob.Position = UDim2.new(a, 0, 0.5, 0)
end, function()
if currentSound and currentSound.TimeLength > 0 then
currentSound.TimePosition = seekRatio * currentSound.TimeLength
end
end)
local barX = 20
local function barButton(text, w, cb)
local b = button(bar, text, cb)
b.Size = UDim2.new(0, w, 0, 30)
b.Position = UDim2.new(0, barX, 0, 56)
barX += w + 6
return b
end
local playBtn, shuffleBtn, repeatBtn, volBtn
barButton("Prev", 46, playPrev)
playBtn = barButton("Play", 62, playPause)
setActive(playBtn, true)
barButton("Next", 46, playNext)
barButton("Stop", 46, stopSound)
shuffleBtn = barButton("Shuffle", 66, function()
CFG.shuffle = not CFG.shuffle
setActive(shuffleBtn, CFG.shuffle)
saveConfig()
end)
local REPEAT_TEXT = { off = "Repeat: Off", all = "Repeat: All", one = "Repeat: One" }
repeatBtn = barButton(REPEAT_TEXT[CFG.repeatMode], 86, function()
if CFG.repeatMode == "off" then
CFG.repeatMode = "all"
elseif CFG.repeatMode == "all" then
CFG.repeatMode = "one"
else
CFG.repeatMode = "off"
end
repeatBtn.Text = REPEAT_TEXT[CFG.repeatMode]
setActive(repeatBtn, CFG.repeatMode ~= "off")
saveConfig()
end)
barButton("Copy ID", 66, function()
if currentTrack then
copyId(currentTrack.id)
else
notify("Nothing is playing", THEME.Orange)
end
end)
setActive(shuffleBtn, CFG.shuffle)
setActive(repeatBtn, CFG.repeatMode ~= "off")
local masterUpdaters = {}
local function setMaster(v, final)
CFG.masterVolume = v
if v > 0 and muted then
muted = false
volBtn.Text = "Vol"
end
applyVolume()
for _, u in ipairs(masterUpdaters) do
u(v)
end
if final then
saveConfig()
end
end
volBtn = button(bar, "Vol", function()
muted = not muted
volBtn.Text = muted and "Muted" or "Vol"
setActive(volBtn, muted)
applyVolume()
end)
volBtn.AnchorPoint = Vector2.new(1, 0)
volBtn.Position = UDim2.new(1, -130, 0, 56)
volBtn.Size = UDim2.new(0, 56, 0, 30)
local miniHit, miniTrack, miniFill, miniKnob = sliderParts(bar, UDim2.new(1, -112, 0, 60), UDim2.new(0, 92, 0, 22))
miniTrack.Size = UDim2.new(1, 0, 0, 6)
miniKnob.Size = UDim2.new(0, 14, 0, 14)
local function miniVisual(v)
miniFill.Size = UDim2.new(v, 0, 1, 0)
miniKnob.Position = UDim2.new(v, 0, 0.5, 0)
end
miniVisual(CFG.masterVolume)
table.insert(masterUpdaters, miniVisual)
local miniValue = CFG.masterVolume
bindDrag(miniHit, miniTrack, function(a)
miniValue = math.floor(a * 100 + 0.5) / 100
setMaster(miniValue, false)
end, function()
setMaster(miniValue, true)
end)
local function formatTime(sec)
sec = math.max(0, sec or 0)
return string.format("%d:%02d", math.floor(sec / 60), math.floor(sec % 60))
end
function refreshNowPlaying()
if currentTrack then
titleLabel.Text = currentTrack.name
idLabel.Text = "ID " .. currentTrack.id
else
titleLabel.Text = "Nothing playing"
idLabel.Text = ""
end
for _, L in ipairs(allLists) do
L.refresh()
end
end
local searchState = { query = "", page = 0, loading = false, hasMore = false, seen = {}, token = 0 }
local pageSearch = newPage("Search", false)
local searchBox = inputRow(pageSearch, "Search audio on the Creator Store", "Search", 90, function(text)
if text:gsub("%s", "") == "" then
return
end
searchState.token += 1
searchState.query = text
searchState.page = 0
searchState.loading = false
searchState.hasMore = false
searchState.seen = {}
searchList.clear()
local token = searchState.token
local statusSetter = pageSearch:FindFirstChild("SearchStatus")
if statusSetter then
statusSetter.Text = "Searching..."
end
searchState.start(token)
end, true)
inputRow(pageSearch, "Play by audio ID", "Play ID", 90, function(text, box)
local id = parseId(text)
if not id then
notify("Enter a valid audio ID", THEME.Red)
return
end
local track = { id = id, name = "Audio " .. id }
playList({ track }, 1)
resolveName(currentTrack or track)
box.Text = ""
end)
local actionGrid = grid(pageSearch, 5, 5, 30)
local searchStatus = label(pageSearch, {
Name = "SearchStatus",
Text = "Type a keyword and press Search",
TextSize = 12,
TextColor3 = THEME.SubText,
Size = UDim2.new(1, 0, 0, 16),
LayoutOrder = nextOrder(pageSearch),
})
local function selectedResult()
local idx = searchList.selected
if idx and searchList.items[idx] then
return searchList.items[idx], idx
end
return nil
end
local function checkScrollLoad()
if searchState.loading or not searchState.hasMore then
return
end
local sc = searchList.scroll
local remaining = sc.AbsoluteCanvasSize.Y - (sc.CanvasPosition.Y + sc.AbsoluteWindowSize.Y)
if remaining < 120 then
searchState.start(searchState.token)
end
end
function searchState.start(token)
if searchState.loading then
return
end
searchState.loading = true
searchStatus.Text = (#searchList.items == 0) and "Searching..." or "Loading more..."
task.spawn(function()
local fresh, lastErr = {}, nil
for _ = 1, 2 do
local items, err = fetchAudio(searchState.query, searchState.page)
searchState.page += 1
if token ~= searchState.token then
return
end
lastErr = err
for _, it in ipairs(items) do
if not searchState.seen[it.id] then
searchState.seen[it.id] = true
table.insert(fresh, it)
end
end
if #fresh > 0 or err then
break
end
end
if token ~= searchState.token then
return
end
searchState.loading = false
for _, it in ipairs(fresh) do
searchList.add(it)
end
searchState.hasMore = #fresh > 0
if #searchList.items == 0 then
searchStatus.Text = lastErr and ("Search failed - " .. lastErr) or "No results found"
else
searchStatus.Text = #searchList.items .. " results" .. (searchState.hasMore and " - scroll for more" or " - end of results")
end
task.defer(checkScrollLoad)
end)
end
local function doSearch(text)
searchBox.Text = text
searchState.token += 1
searchState.query = text
searchState.page = 0
searchState.loading = false
searchState.hasMore = false
searchState.seen = {}
searchList.clear()
searchState.start(searchState.token)
end
searchList = newList(pageSearch, UDim2.new(1, 0, 1, -(36 + 36 + 68 - 38 + 16 + 32)), {
select = true,
active = true,
empty = "Search results will appear here",
click = function(item, idx)
playList(searchList.items, idx)
end,
actions = {
{ text = "Copy ID", fn = function(item) copyId(item.id) end },
{ text = "Fav", fn = function(item) toggleFavorite(item) end },
{ text = "+ List", fn = function(item) addToPlaylist(item) end },
},
})
local function actionBtn(text, fn)
local b = button(actionGrid, text, fn)
b.TextSize = 11
return b
end
actionBtn("Play All", function()
if #searchList.items == 0 then
notify("No results to play", THEME.Orange)
return
end
playList(searchList.items, 1)
end)
actionBtn("Play Selected", function()
local item, idx = selectedResult()
if not item then
notify("Select a result first", THEME.Orange)
return
end
playList(searchList.items, idx)
end)
actionBtn("+ Playlist", function()
local item = selectedResult()
if not item then
notify("Select a result first", THEME.Orange)
return
end
addToPlaylist(item)
end)
actionBtn("Favorite", function()
local item = selectedResult()
if not item then
notify("Select a result first", THEME.Orange)
return
end
toggleFavorite(item)
end)
actionBtn("Copy ID", function()
local item = selectedResult()
if not item then
notify("Select a result first", THEME.Orange)
return
end
copyId(item.id)
end)
connect(searchList.scroll:GetPropertyChangedSignal("CanvasPosition"), checkScrollLoad)
connect(searchList.scroll:GetPropertyChangedSignal("AbsoluteCanvasSize"), checkScrollLoad)
local pagePlaylist = newPage("Playlist", false)
inputRow(pagePlaylist, "Add audio by ID", "Add", 80, function(text, box)
local id = parseId(text)
if not id then
notify("Enter a valid audio ID", THEME.Red)
return
end
local track = { id = id, name = "Audio " .. id }
if addUnique(playlist, track) then
savePlaylist()
renderPlaylist()
resolveName(track)
else
notify("Already in playlist", THEME.Orange)
end
box.Text = ""
end)
local plGrid = grid(pagePlaylist, 4, 4, 30)
button(plGrid, "Play", function()
if #playlist == 0 then
notify("Playlist is empty", THEME.Orange)
return
end
playList(playlist, 1)
end, "accent")
button(plGrid, "Shuffle Play", function()
if #playlist == 0 then
notify("Playlist is empty", THEME.Orange)
return
end
shufflePlay(playlist)
end)
button(plGrid, "Clear", function()
playlist = {}
savePlaylist()
renderPlaylist()
end)
button(plGrid, "Export JSON", function()
if #playlist == 0 then
notify("Playlist is empty", THEME.Orange)
return
end
if copyText(HttpService:JSONEncode(playlist)) then
notify("Playlist JSON copied", THEME.Green)
else
notify("Clipboard is not supported by your executor", THEME.Red)
end
end)
inputRow(pagePlaylist, "Paste playlist JSON to import", "Import", 80, function(text, box)
local ok, decoded = pcall(function()
return HttpService:JSONDecode(text)
end)
if not ok or type(decoded) ~= "table" then
notify("Invalid JSON", THEME.Red)
return
end
local added = 0
for _, t in ipairs(cleanList(decoded)) do
if addUnique(playlist, t) then
added += 1
end
end
savePlaylist()
renderPlaylist()
box.Text = ""
notify("Imported " .. added .. " tracks", THEME.Green)
end)
local function movePlaylist(idx, dir)
local j = idx + dir
if j < 1 or j > #playlist then
return
end
playlist[idx], playlist[j] = playlist[j], playlist[idx]
savePlaylist()
renderPlaylist()
end
playlistList = newList(pagePlaylist, UDim2.new(1, 0, 1, -(36 + 30 + 36 + 24)), {
active = true,
empty = "Your playlist is empty",
click = function(item, idx)
playList(playlist, idx)
end,
actions = {
{ text = "Up", fn = function(item, idx) movePlaylist(idx, -1) end },
{ text = "Down", fn = function(item, idx) movePlaylist(idx, 1) end },
{ text = "Copy ID", fn = function(item) copyId(item.id) end },
{ text = "Remove", fn = function(item, idx)
table.remove(playlist, idx)
savePlaylist()
renderPlaylist()
end },
},
})
function renderPlaylist()
playlistList.set(playlist, true)
end
local pageFav = newPage("Favorites", false)
local favGrid = grid(pageFav, 4, 4, 30)
button(favGrid, "Play All", function()
if #favorites == 0 then
notify("No favorites yet", THEME.Orange)
return
end
playList(favorites, 1)
end, "accent")
button(favGrid, "Shuffle Play", function()
if #favorites == 0 then
notify("No favorites yet", THEME.Orange)
return
end
shufflePlay(favorites)
end)
button(favGrid, "Add To Playlist", function()
local added = 0
for _, t in ipairs(favorites) do
if addUnique(playlist, t) then
added += 1
end
end
savePlaylist()
renderPlaylist()
notify("Added " .. added .. " tracks", THEME.Green)
end)
button(favGrid, "Clear", function()
favorites = {}
saveFavorites()
renderFavorites()
end)
favoritesList = newList(pageFav, UDim2.new(1, 0, 1, -(30 + 8)), {
active = true,
empty = "No favorites yet",
click = function(item, idx)
playList(favorites, idx)
end,
actions = {
{ text = "Copy ID", fn = function(item) copyId(item.id) end },
{ text = "+ List", fn = function(item) addToPlaylist(item) end },
{ text = "Remove", fn = function(item, idx)
table.remove(favorites, idx)
saveFavorites()
renderFavorites()
end },
},
})
function renderFavorites()
favoritesList.set(favorites, true)
end
local pageRecent = newPage("Recent", false)
local recGrid = grid(pageRecent, 3, 3, 30)
button(recGrid, "Play All", function()
if #recent == 0 then
notify("Nothing played yet", THEME.Orange)
return
end
playList(recent, 1)
end, "accent")
button(recGrid, "Add To Playlist", function()
local added = 0
for _, t in ipairs(recent) do
if addUnique(playlist, t) then
added += 1
end
end
savePlaylist()
renderPlaylist()
notify("Added " .. added .. " tracks", THEME.Green)
end)
button(recGrid, "Clear", function()
recent = {}
saveRecent()
renderRecent()
end)
recentList = newList(pageRecent, UDim2.new(1, 0, 1, -(30 + 8)), {
active = true,
empty = "Nothing played yet",
click = function(item, idx)
playList(recent, idx)
end,
actions = {
{ text = "Copy ID", fn = function(item) copyId(item.id) end },
{ text = "Fav", fn = function(item) toggleFavorite(item) end },
{ text = "+ List", fn = function(item) addToPlaylist(item) end },
},
})
function renderRecent()
recentList.set(recent, false)
end
local pagePresets = newPage("Presets", true)
section(pagePresets, "Quick search")
local PRESETS = {
{ "Lofi / Chill", "lofi" },
{ "Phonk", "phonk" },
{ "Trap", "trap beat" },
{ "Ambient", "ambient" },
{ "Nightcore", "nightcore" },
{ "Anime", "anime" },
{ "Rock", "rock" },
{ "Meme Sounds", "meme" },
{ "Vaporwave", "vaporwave" },
{ "Synthwave", "synthwave" },
{ "Jazz", "jazz" },
{ "Piano", "piano" },
{ "Horror", "horror" },
{ "Epic", "epic orchestral" },
{ "EDM", "edm" },
{ "Sound Effects", "sound effect" },
}
local presetGrid = grid(pagePresets, 4, #PRESETS, 32)
for _, p in ipairs(PRESETS) do
button(presetGrid, p[1], function()
selectTab("Search")
doSearch(p[2])
end)
end
section(pagePresets, "Custom presets")
local presetNameBox = textBox(pagePresets, "Preset name")
inputRow(pagePresets, "Audio ID", "Add Preset", 100, function(text, box)
local id = parseId(text)
if not id then
notify("Enter a valid audio ID", THEME.Red)
return
end
local name = presetNameBox.Text:gsub("^%s+", ""):gsub("%s+$", "")
if name == "" then
name = "Audio " .. id
end
local track = { id = id, name = name }
if addUnique(customPreset, track) then
savePreset()
renderCustom()
box.Text = ""
presetNameBox.Text = ""
else
notify("Preset already exists", THEME.Orange)
end
end, true)
customList = newList(pagePresets, UDim2.new(1, 0, 0, 220), {
active = true,
empty = "No custom presets yet",
click = function(item, idx)
playList(customPreset, idx)
end,
actions = {
{ text = "Copy ID", fn = function(item) copyId(item.id) end },
{ text = "Remove", fn = function(item, idx)
table.remove(customPreset, idx)
savePreset()
renderCustom()
end },
},
})
function renderCustom()
customList.set(customPreset, true)
end
local pageViz = newPage("Visualizer", false)
local vizCard = card(pageViz, 250)
local vizHolder = make("Frame", {
BackgroundTransparency = 1,
Position = UDim2.new(0, 16, 0, 16),
Size = UDim2.new(1, -32, 1, -32),
}, vizCard)
local BAR_COUNT = 32
local vizBars, vizValues = {}, {}
local vizHue = 0.585
local function barColor(i)
return Color3.fromHSV((vizHue + (i / BAR_COUNT) * 0.12) % 1, 0.7, 1)
end
local function applyBarColors()
for i, b in ipairs(vizBars) do
b.BackgroundColor3 = barColor(i)
end
end
for i = 1, BAR_COUNT do
local b = make("Frame", {
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0, 1),
Position = UDim2.new((i - 1) / BAR_COUNT, 2, 1, 0),
Size = UDim2.new(1 / BAR_COUNT, -4, 0.04, 0),
}, vizHolder)
corner(b, 5)
vizBars[i] = b
vizValues[i] = 0.04
end
applyBarColors()
local vizGrid = grid(pageViz, 2, 2, 32)
button(vizGrid, "Randomize Colors", function()
vizHue = math.random()
applyBarColors()
end)
button(vizGrid, "Reset Colors", function()
vizHue = 0.585
applyBarColors()
end)
local pageSettings = newPage("Settings", true)
local setMasterCard, setSpeed, setFade, setMode, setAuto
section(pageSettings, "Audio")
setMasterCard = makeSlider(pageSettings, "Master Volume", 0, 1, CFG.masterVolume, function(v)
return math.floor(v * 100 + 0.5) .. "%"
end, function(v, final)
setMaster(v, final)
end)
table.insert(masterUpdaters, setMasterCard)
setSpeed = makeSlider(pageSettings, "Playback Speed", 0.5, 2, CFG.playbackSpeed, function(v)
return string.format("%.2fx", v)
end, function(v, final)
CFG.playbackSpeed = v
if currentSound then
currentSound.PlaybackSpeed = v
end
if final then
saveConfig()
end
end)
setFade = makeSlider(pageSettings, "Fade Time", 0, 3, CFG.fadeTime, function(v)
return string.format("%.1fs", v)
end, function(v, final)
CFG.fadeTime = v
if final then
saveConfig()
end
end)
section(pageSettings, "Playback")
setMode = makeToggle(pageSettings, "3D Audio (sound comes from your character)", CFG.soundMode == "3D", function(on)
CFG.soundMode = on and "3D" or "2D"
reparentSound()
saveConfig()
end)
setAuto = makeToggle(pageSettings, "Auto Next", CFG.autoNext, function(on)
CFG.autoNext = on
saveConfig()
end)
section(pageSettings, "Controls")
local binding = false
local bindCard = card(pageSettings, 44)
label(bindCard, { Text = "UI Toggle Key", Font = FONT_BOLD, Position = UDim2.new(0, 16, 0, 0), Size = UDim2.new(0.5, 0, 1, 0) })
local bindBtn
bindBtn = button(bindCard, CFG.uiKey.Name, function()
binding = true
bindBtn.Text = "Press a key..."
end)
bindBtn.AnchorPoint = Vector2.new(1, 0.5)
bindBtn.Position = UDim2.new(1, -12, 0.5, 0)
bindBtn.Size = UDim2.new(0, 140, 0, 28)
label(pageSettings, {
Text = "Hotkeys: P play/pause, N next, B previous, O stop",
TextSize = 12,
TextColor3 = THEME.SubText,
Size = UDim2.new(1, 0, 0, 18),
LayoutOrder = nextOrder(pageSettings),
})
section(pageSettings, "Data")
local dataGrid = grid(pageSettings, 3, 3, 32)
local function syncSettings()
setMaster(CFG.masterVolume, false)
setSpeed(CFG.playbackSpeed)
setFade(CFG.fadeTime)
setMode(CFG.soundMode == "3D")
setAuto(CFG.autoNext)
setActive(shuffleBtn, CFG.shuffle)
repeatBtn.Text = REPEAT_TEXT[CFG.repeatMode]
setActive(repeatBtn, CFG.repeatMode ~= "off")
bindBtn.Text = CFG.uiKey.Name
if currentSound then
currentSound.PlaybackSpeed = CFG.playbackSpeed
end
end
button(dataGrid, "Save Data", function()
saveAll()
notify("Data saved", THEME.Green)
end, "accent")
button(dataGrid, "Load Data", function()
loadAll()
renderAll()
syncSettings()
notify("Data loaded", THEME.Green)
end)
button(dataGrid, "Delete Data", function()
if hasFS and type(delfile) == "function" then
for _, f in pairs(FILES) do
pcall(function()
if isfile(f) then
delfile(f)
end
end)
end
notify("Saved data deleted", THEME.Orange)
else
notify("File functions are not supported by your executor", THEME.Red)
end
end, "danger")
section(pageSettings, "Interface")
local destroyGrid = grid(pageSettings, 1, 1, 32)
button(destroyGrid, "Destroy UI", function()
cleanup()
end, "danger")
local vizTime, textAccum = 0, 0
connect(RunService.Heartbeat, function(dt)
local s = currentSound
if s then
local len = s.TimeLength
if len > 0 and not progDragging() then
local a = math.clamp(s.TimePosition / len, 0, 1)
progFill.Size = UDim2.new(a, 0, 1, 0)
progKnob.Position = UDim2.new(a, 0, 0.5, 0)
end
elseif not progDragging() then
progFill.Size = UDim2.new(0, 0, 1, 0)
progKnob.Position = UDim2.new(0, 0, 0.5, 0)
end
textAccum += dt
if textAccum >= 0.2 then
textAccum = 0
if s then
timeLeft.Text = formatTime(s.TimePosition)
timeRight.Text = formatTime(s.TimeLength)
playBtn.Text = s.IsPlaying and "Pause" or "Play"
else
timeLeft.Text = "0:00"
timeRight.Text = "0:00"
playBtn.Text = "Play"
end
end
if main.Visible and pageViz.Visible then
vizTime += dt
local level = 0
if s and s.IsPlaying then
level = math.clamp(s.PlaybackLoudness / 500, 0, 1)
end
for i, b in ipairs(vizBars) do
local wave = 0.45 + 0.55 * math.abs(math.sin(vizTime * (1.5 + i * 0.13) + i))
local target = math.max(0.04, level * wave)
vizValues[i] = vizValues[i] + (target - vizValues[i]) * math.clamp(dt * 14, 0, 1)
b.Size = UDim2.new(1 / BAR_COUNT, -4, vizValues[i], 0)
end
end
end)
connect(UserInputService.InputBegan, function(input, processed)
if binding and input.UserInputType == Enum.UserInputType.Keyboard then
binding = false
if input.KeyCode ~= Enum.KeyCode.Escape then
CFG.uiKey = input.KeyCode
saveConfig()
end
bindBtn.Text = CFG.uiKey.Name
return
end
if UserInputService:GetFocusedTextBox() then
return
end
if input.UserInputType ~= Enum.UserInputType.Keyboard then
return
end
if input.KeyCode == CFG.uiKey then
setVisible(not main.Visible)
return
end
if processed then
return
end
if input.KeyCode == Enum.KeyCode.P then
playPause()
elseif input.KeyCode == Enum.KeyCode.N then
playNext()
elseif input.KeyCode == Enum.KeyCode.B then
playPrev()
elseif input.KeyCode == Enum.KeyCode.O then
stopSound()
end
end)
connect(player.CharacterAdded, function(char)
task.spawn(function()
if CFG.soundMode == "3D" then
local root = char:WaitForChild("HumanoidRootPart", 10)
if root and currentSound then
currentSound.Parent = root
end
end
end)
end)
connect(player.CharacterRemoving, function()
if currentSound then
currentSound.Parent = SoundService
end
end)
function cleanup()
for _, c in ipairs(connections) do
pcall(function()
c:Disconnect()
end)
end
connections = {}
playToken += 1
if currentSound then
pcall(function()
currentSound:Destroy()
end)
currentSound = nil
end
pcall(function()
gui:Destroy()
end)
_G.SoundHub_Cleanup = nil
end
_G.SoundHub_Cleanup = cleanup
renderAll()
selectTab("Search")