Dark Spawner Script
HomeGamesTrendingExecutors
Dark Spawner Script

The best community driven platform for finding the latest Roblox scripts. Create, share, and discover amazing scripts.

Scripts
675+
Games
434+
Votes cast
8,238+

Quick Links

  • How to Use
  • Games
  • Trending Scripts
  • Executors
  • FAQs

Community

Links coming soon.

Contact

  • [email protected]
  • Report a script

© 2026 Dark Spawner Script. All rights reserved.

About UsPrivacy PolicyDisclaimer
Home›Games›Universal

Universal Script Sound Hub: Audio Search, Playlists

Universal•5 hours ago
1 views
Universal Script Sound Hub: Audio Search, Playlists
No Key
  • Working
  • updated 5 hours ago
  • No key
  • tested on Delta, Xeno
T

Team Darkspawner

Hub: Sound Hub

Description

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!!

Features

  • +Audio Search
  • +Copy Audio ID
  • +Playlists
  • +Audio Visualizer
  • +Play by Audio ID
  • +Recent Tracks
  • +Favorites
  • +Audio Presets

Universal

Explore more scripts for this game (5)

Script

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

Similar Scripts

Universal Script HX: Aimbot, Weapon Spawner

No Key • 3 days ago

Universal Script Legit Hub: Aimbot, ESP Boxes

No Key • 6 days ago

Universal Script Salwyrr: Player ESP, Box ESP

No Key • 1 week ago

Universal Script Moondiety: Silent Aim, Fly

No Key • 1 week ago

Sandworm Playground Script DarkSpawner Hub: Worm Leap, ESP

Key System • 1 hour ago