
KeylessUniversal

-- Services
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local ContentProvider = game:GetService("ContentProvider")
local player = Players.LocalPlayer
-- Configuration
local MESH_ID = "rbxassetid://1066606415"
local TEXTURE_ID = "" -- optional: rbxassetid://... if the mesh has a matching texture
local MESH_SCALE = Vector3.new(0.016, 0.016, 0.016) -- adjust if the mesh looks too big/small
local BASE_OFFSET = CFrame.new(0, 0, 1.0) -- forward/back + left/right placement, Y height is adjusted separately below
local SPEED_STEP = 0.1
local SPEED_MIN, SPEED_MAX = 0.5, 2
local PITCH_STEP = 0.1
local PITCH_MIN, PITCH_MAX = 0.5, 2
local COLOR_SWATCHES = {
Color3.fromRGB(40, 40, 40),
Color3.fromRGB(200, 200, 200),
Color3.fromRGB(180, 40, 40),
Color3.fromRGB(40, 90, 180),
Color3.fromRGB(60, 150, 70),
Color3.fromRGB(230, 200, 40),
}
-- State (shared between GUI and boombox)
local boomboxPart = nil
local boomboxWeld = nil
local currentUpperTorso = nil
local sound = nil
local pitchEffect = nil
local currentSoundId = ""
local soundLoadedConn = nil
local statusLabelRef = nil -- set once the GUI exists, used for error feedback
local yOffsetLabelRef = nil
local speedLabelRef = nil
local pitchLabelRef = nil
local yOffset = 0 -- studs, adjustable from the GUI, persists across respawns/recreation
local currentSpeed = 1
local currentPitch = 1
local currentColor = COLOR_SWATCHES[1]
-- Re-welds the boombox at its current upperTorso + yOffset. WeldConstraint
-- locks in the relative CFrame at creation time, so to move it we have to
-- set the CFrame first, then rebuild the weld against the new position.
local function repositionBoombox()
if not boomboxPart or not currentUpperTorso then return end
if boomboxWeld then
boomboxWeld:Destroy()
boomboxWeld = nil
end
boomboxPart.CFrame = currentUpperTorso.CFrame * BASE_OFFSET * CFrame.new(0, yOffset, 0)
local weld = Instance.new("WeldConstraint")
weld.Part0 = currentUpperTorso
weld.Part1 = boomboxPart
weld.Parent = boomboxPart
boomboxWeld = weld
end
local function applySpeed()
if sound then
sound.PlaybackSpeed = currentSpeed
end
end
local function applyPitch()
if pitchEffect then
pitchEffect.Octave = currentPitch
end
end
local function applyColor()
if boomboxPart then
boomboxPart.Color = currentColor
end
end
-- Removes the boombox and stops the sound. It comes back next respawn
-- (createBoombox runs again on CharacterAdded).
local function deleteBoombox()
if boomboxPart then
boomboxPart:Destroy()
end
boomboxPart = nil
boomboxWeld = nil
sound = nil
pitchEffect = nil
end
-- Pull a numeric asset id out of either a bare number or a pasted
-- roblox.com/library/123456/Song-Name style link
local function parseSoundId(text)
local digits = text:match("(%d+)")
return digits and tonumber(digits) or nil
end
-- Plays soundId on the given Sound instance, handling both the "already
-- cached, fires before we can connect" case and genuine load failures
-- (e.g. the asset isn't permitted for use in this experience).
local function playSoundId(soundInstance, soundId, onResult)
if soundLoadedConn then
soundLoadedConn:Disconnect()
soundLoadedConn = nil
end
soundInstance:Stop()
soundInstance.SoundId = soundId
local function attemptPlay()
local ok, err = pcall(function()
soundInstance:Play()
end)
if onResult then
onResult(ok, err)
end
end
if soundInstance.IsLoaded then
-- Already loaded (common with cached/short sounds) - the Loaded
-- event will never fire, so play immediately instead of waiting.
attemptPlay()
return
end
local loaded = false
soundLoadedConn = soundInstance.Loaded:Connect(function()
loaded = true
attemptPlay()
if soundLoadedConn then
soundLoadedConn:Disconnect()
soundLoadedConn = nil
end
end)
-- Fallback in case Loaded never fires (bad id, moderation block, etc.)
task.delay(2, function()
if not loaded and soundInstance and soundInstance.Parent then
attemptPlay()
end
end)
end
-- Small helper to build a plain grey button, matching the rough flat style
local function makeGreyButton(name, size, position, text, parent)
local btn = Instance.new("TextButton")
btn.Name = name
btn.Size = size
btn.Position = position
btn.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
btn.BorderSizePixel = 1
btn.BorderColor3 = Color3.fromRGB(0, 0, 0)
btn.Text = text
btn.TextColor3 = Color3.fromRGB(0, 0, 0)
btn.Font = Enum.Font.SourceSans
btn.TextSize = 14
btn.Parent = parent
return btn
end
-- Small helper to build a +/- adjust row: label on the left, minus/plus on the right
local function makeAdjustRow(y, labelText, parent)
local label = Instance.new("TextLabel")
label.Size = UDim2.new(0, 80, 0, 26)
label.Position = UDim2.new(0, 0, 0, y)
label.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
label.BorderSizePixel = 1
label.BorderColor3 = Color3.fromRGB(0, 0, 0)
label.Text = labelText
label.TextColor3 = Color3.fromRGB(0, 0, 0)
label.Font = Enum.Font.SourceSans
label.TextSize = 14
label.Parent = parent
local minusBtn = makeGreyButton("Minus", UDim2.new(0, 50, 0, 26), UDim2.new(0, 85, 0, y), "-", parent)
local plusBtn = makeGreyButton("Plus", UDim2.new(0, 50, 0, 26), UDim2.new(0, 140, 0, y), "+", parent)
return label, minusBtn, plusBtn
end
-- Create the draggable GUI
local function createGUI()
local playerGui = player:FindFirstChild("PlayerGui")
if not playerGui then return end
local existing = playerGui:FindFirstChild("BoomboxGui")
if existing then
existing:Destroy()
end
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "BoomboxGui"
screenGui.ResetOnSpawn = false
screenGui.Parent = playerGui
local frame = Instance.new("Frame")
frame.Name = "MainFrame"
frame.Size = UDim2.new(0, 200, 0, 250)
frame.Position = UDim2.new(0, 20, 0.5, -125)
frame.BackgroundColor3 = Color3.fromRGB(150, 150, 150)
frame.BorderSizePixel = 1
frame.BorderColor3 = Color3.fromRGB(0, 0, 0)
frame.Parent = screenGui
local title = Instance.new("TextLabel")
title.Name = "Title"
title.Size = UDim2.new(1, 0, 0, 20)
title.BackgroundColor3 = Color3.fromRGB(150, 150, 150)
title.BorderSizePixel = 1
title.BorderColor3 = Color3.fromRGB(0, 0, 0)
title.Text = "Boombox"
title.TextColor3 = Color3.fromRGB(0, 0, 0)
title.Font = Enum.Font.SourceSans
title.TextSize = 14
title.Active = true
title.Parent = frame
-- Tabs
local mainTabButton = makeGreyButton("MainTabButton", UDim2.new(0, 95, 0, 22), UDim2.new(0, 5, 0, 22), "Main", frame)
local colorTabButton = makeGreyButton("ColorTabButton", UDim2.new(0, 90, 0, 22), UDim2.new(0, 100, 0, 22), "Color", frame)
local mainPage = Instance.new("Frame")
mainPage.Name = "MainPage"
mainPage.Size = UDim2.new(1, -10, 0, 172)
mainPage.Position = UDim2.new(0, 5, 0, 46)
mainPage.BackgroundTransparency = 1
mainPage.Visible = true
mainPage.Parent = frame
local colorPage = Instance.new("Frame")
colorPage.Name = "ColorPage"
colorPage.Size = UDim2.new(1, -10, 0, 172)
colorPage.Position = UDim2.new(0, 5, 0, 46)
colorPage.BackgroundTransparency = 1
colorPage.Visible = false
colorPage.Parent = frame
local function showTab(tab)
mainPage.Visible = (tab == "main")
colorPage.Visible = (tab == "color")
end
mainTabButton.MouseButton1Click:Connect(function()
showTab("main")
end)
colorTabButton.MouseButton1Click:Connect(function()
showTab("color")
end)
-- ===== Main page =====
local textBox = Instance.new("TextBox")
textBox.Name = "SongIdInput"
textBox.Size = UDim2.new(1, 0, 0, 26)
textBox.Position = UDim2.new(0, 0, 0, 0)
textBox.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
textBox.BorderSizePixel = 1
textBox.BorderColor3 = Color3.fromRGB(0, 0, 0)
textBox.PlaceholderText = "Enter Sound ID or link..."
textBox.Text = ""
textBox.TextColor3 = Color3.fromRGB(0, 0, 0)
textBox.Font = Enum.Font.SourceSans
textBox.TextSize = 14
textBox.ClearTextOnFocus = true
textBox.Parent = mainPage
local button = makeGreyButton("EnterButton", UDim2.new(1, 0, 0, 26), UDim2.new(0, 0, 0, 28), "Enter", mainPage)
local yLabel, yDownButton, yUpButton = makeAdjustRow(58, "Y: 0.0", mainPage)
yOffsetLabelRef = yLabel
local speedLabel, speedDownButton, speedUpButton = makeAdjustRow(88, "Speed: 1.0", mainPage)
speedLabelRef = speedLabel
local pitchLabel, pitchDownButton, pitchUpButton = makeAdjustRow(118, "Pitch: 1.0", mainPage)
pitchLabelRef = pitchLabel
local deleteButton = makeGreyButton("DeleteButton", UDim2.new(1, 0, 0, 26), UDim2.new(0, 0, 0, 148), "Delete Boombox", mainPage)
-- Small status line so load/permission errors are visible, not silent
local status = Instance.new("TextLabel")
status.Name = "Status"
status.Size = UDim2.new(1, 0, 0, 16)
status.Position = UDim2.new(0, 0, 1, -16)
status.BackgroundTransparency = 1
status.Text = ""
status.TextColor3 = Color3.fromRGB(30, 30, 30)
status.Font = Enum.Font.SourceSans
status.TextSize = 12
status.TextXAlignment = Enum.TextXAlignment.Left
status.Parent = frame
statusLabelRef = status
local Y_STEP = 0.1
local function nudgeY(delta)
yOffset = yOffset + delta
yLabel.Text = "Y: " .. string.format("%.1f", yOffset)
repositionBoombox()
end
yDownButton.MouseButton1Click:Connect(function() nudgeY(-Y_STEP) end)
yUpButton.MouseButton1Click:Connect(function() nudgeY(Y_STEP) end)
local function nudgeSpeed(delta)
currentSpeed = math.clamp(currentSpeed + delta, SPEED_MIN, SPEED_MAX)
speedLabel.Text = "Speed: " .. string.format("%.1f", currentSpeed)
applySpeed()
end
speedDownButton.MouseButton1Click:Connect(function() nudgeSpeed(-SPEED_STEP) end)
speedUpButton.MouseButton1Click:Connect(function() nudgeSpeed(SPEED_STEP) end)
local function nudgePitch(delta)
currentPitch = math.clamp(currentPitch + delta, PITCH_MIN, PITCH_MAX)
pitchLabel.Text = "Pitch: " .. string.format("%.1f", currentPitch)
applyPitch()
end
pitchDownButton.MouseButton1Click:Connect(function() nudgePitch(-PITCH_STEP) end)
pitchUpButton.MouseButton1Click:Connect(function() nudgePitch(PITCH_STEP) end)
deleteButton.MouseButton1Click:Connect(function()
deleteBoombox()
status.Text = "Boombox deleted"
status.TextColor3 = Color3.fromRGB(30, 30, 30)
end)
-- ===== Color page (very simple - just swatch buttons) =====
local swatchSize = 40
local perRow = 4
local gap = 5
for i, color in ipairs(COLOR_SWATCHES) do
local row = math.floor((i - 1) / perRow)
local col = (i - 1) % perRow
local swatch = Instance.new("TextButton")
swatch.Name = "Swatch" .. i
swatch.Size = UDim2.new(0, swatchSize, 0, swatchSize)
swatch.Position = UDim2.new(0, col * (swatchSize + gap), 0, row * (swatchSize + gap))
swatch.BackgroundColor3 = color
swatch.BorderSizePixel = 1
swatch.BorderColor3 = Color3.fromRGB(0, 0, 0)
swatch.Text = ""
swatch.Parent = colorPage
swatch.MouseButton1Click:Connect(function()
currentColor = color
applyColor()
end)
end
-- Custom dragging via the title bar
local dragging = false
local dragStart = nil
local startPos = nil
title.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = true
dragStart = input.Position
startPos = frame.Position
end
end)
title.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
dragging = false
end
end)
UserInputService.InputChanged:Connect(function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
local delta = input.Position - dragStart
frame.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
end
end)
local submitting = false
local function submitSong()
if submitting then return end
submitting = true
local soundIdNum = parseSoundId(textBox.Text)
if soundIdNum then
currentSoundId = "rbxassetid://" .. tostring(soundIdNum)
-- If the boombox exists, update+play immediately. If it doesn't
-- exist yet (character not spawned, or deleted), currentSoundId
-- is still saved and will be picked up next time it's created.
if sound then
playSoundId(sound, currentSoundId, function(ok, err)
if ok then
status.Text = "Playing " .. tostring(soundIdNum)
status.TextColor3 = Color3.fromRGB(20, 120, 40)
else
status.Text = "Can't play this id (check it's a valid, permitted audio asset)"
status.TextColor3 = Color3.fromRGB(150, 30, 30)
warn("[Boombox] Play failed for", currentSoundId, err)
end
end)
end
button.Text = "Enter"
else
status.Text = "Enter a numeric Sound ID"
status.TextColor3 = Color3.fromRGB(150, 30, 30)
button.Text = "Invalid ID!"
end
task.delay(1.5, function()
button.Text = "Enter"
submitting = false
end)
end
button.MouseButton1Click:Connect(submitSong)
textBox.FocusLost:Connect(function(enterPressed)
if enterPressed then
submitSong()
end
end)
end
-- Create the boombox on the character
local function createBoombox(character)
if boomboxPart then
boomboxPart:Destroy()
boomboxPart = nil
end
local upperTorso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
if not upperTorso then
warn("[Boombox] Could not find Torso/UpperTorso on the player's character.")
return
end
-- Plain Part + SpecialMesh (FileMesh) - this is the combo you confirmed
-- actually renders for this mesh id. MeshPart.MeshId is pickier about
-- what it'll accept and can silently show nothing for the same asset.
local part = Instance.new("Part")
part.Name = "ClientBoombox"
part.Size = Vector3.new(1, 1, 1)
part.Transparency = 0
part.CanCollide = false
part.CanQuery = false
part.Massless = true
part.Color = currentColor
local mesh = Instance.new("SpecialMesh")
mesh.MeshType = Enum.MeshType.FileMesh
mesh.MeshId = MESH_ID
if TEXTURE_ID ~= "" then
mesh.TextureId = TEXTURE_ID
end
mesh.Scale = MESH_SCALE
mesh.Parent = part
part.Parent = character
-- Kick off loading the mesh/texture as soon as it's placed
pcall(function()
ContentProvider:PreloadAsync({ part })
end)
boomboxPart = part
currentUpperTorso = upperTorso
repositionBoombox() -- sets CFrame + weld using the current yOffset
sound = Instance.new("Sound")
sound.Volume = 1
sound.Looped = true
sound.RollOffMaxDistance = 60
sound.PlaybackSpeed = currentSpeed
sound.Parent = part
pitchEffect = Instance.new("PitchShiftSoundEffect")
pitchEffect.Octave = currentPitch
pitchEffect.Parent = sound
if currentSoundId ~= "" then
playSoundId(sound, currentSoundId, function(ok, err)
if statusLabelRef then
if ok then
statusLabelRef.Text = "Playing"
statusLabelRef.TextColor3 = Color3.fromRGB(20, 120, 40)
else
statusLabelRef.Text = "Can't play this id (check it's a valid, permitted audio asset)"
statusLabelRef.TextColor3 = Color3.fromRGB(150, 30, 30)
warn("[Boombox] Play failed for", currentSoundId, err)
end
end
end)
end
end
-- Handle character spawns
local function onCharacterAdded(character)
character:WaitForChild("HumanoidRootPart", 5)
createBoombox(character)
end
-- Create the GUI (persists across respawns)
createGUI()
-- Handle current character and future respawns
if player.Character then
onCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(onCharacterAdded)No comments yet for this script.



