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")