Who is this for?
This tutorial is geared towards newer developers who may have used Roblox stock audio as background music, but would like to get more sophisticated with music in their game.What this tutorial shows
We show how to take a beat-based song (like a track of dance music), find the loop points of the sections of the song, and play those sections as loops in different areas of your game, allowing gameplay with continuous music that changes when you move from location to location, but with the continuity of the same song. For example, triggering the "intro" in one area, the "verse" in another, and the "chorus" the last. Like this video:In the tutorial we use Audioscape’s generative music, but our code works for Roblox stock music as well. @jackjenningsdev has a great post on finding music in Roblox’s audio library and getting details from the APM site.
Video
Feel free to skip down to the Video below. It has all the information you need.Overview: looping beat-based music
Most beat-based songs like dance music are based on 8 bar phrases. You can figure out the timing of the phrases from the tempo of the song. In this example below, a tempo of 120bpm (beats per minute) results in 8 bar phrases being 16 seconds long. Here is what a song at 120 bpm might look like:The intro section loops from 0 to 16 seconds, the verse from 16 to 32 seconds, and the chorus from 32 to 48 seconds. So in the sample code (see below), if we had 3 versions of the same track and wanted these three different loop points, we would set the appropriate LoopRegions:
tracks[1].LoopRegion = NumberRange.new(0, 16) — Intro
tracks[2].LoopRegion = NumberRange.new(16, 32) — Verse
tracks[3].LoopRegion = NumberRange.new(32, 48) — Chorus
Video Tutorial
Here is the YouTube video that takes you through all the steps (scroll down for an index and our sample code).(skip to 4:37 if you are using Roblox stock music)
00:00 Intro
00:16 Logging into Audioscape
00:19 Making a track in Audioscape
01:00 Downloading the song
01:56 Getting our sample code
02:28 Getting the loop points from our help page
02:37 Roblox Studio – starting a blank Faceplate game
03:05 Uploading your track into Roblox Studio
03:40 Inserting the track using SoundService and making copies
04:37 Grabbing our sample code and pasting it into a new script
05:04 Changing the Audioscape code to use your uploaded track
05:19 Setting up loop regions for your track
06:22 Setting up game triggers (checkpoints) to play the track sections
07:38 Testing our sound triggers in your game
08:10 Making your game triggers transparent
08:48 Conclusion
Sample Code
Sample code for integrating loop-based music in your game
local fullSound = 1 -- Loudest volume a track should play
local minSound = 0.01 -- Volume of a track that is not currently in focus
local transitionSeconds = 5.0 -- How long to fade in/out clips when you touch an object that changes the track
local slowDownSeconds = 5 -- How long to slow the song over (on death, for example)
local fadeInTime = 5 -- How long to start fading in a track with itself (self looped cross-fade)
local fadeOutTime = 5 -- How long to start fading out a track with itself (self looped cross-fade)
-- Initialize the tracks list, setting up each to be looped and all play at the same time
local SoundService = game:GetService("SoundService")
-- List here all of the tracks/versions of tracks you have imported to the SoundService
local tracks = {
SoundService.NAME_OF_YOUR_FIRST_TRACK,
SoundService.NAME_OF_YOUR_SECOND_TRACK,
SoundService.NAME_OF_YOUR_THIRD_TRACK,
}
-- Get the loop regions from our help pages. This is one example for a cinematic track
tracks[1].LoopRegion = NumberRange.new(197, 265) -- Field
tracks[2].LoopRegion = NumberRange.new(0, 88) -- Bridge
tracks[3].LoopRegion = NumberRange.new(0, 195) -- Door 64 sec and 128
for index, track in (tracks) do
track.Volume = minSound
track.Looped = true
track.PlaybackRegionsEnabled = true
track.TimePosition = track.LoopRegion.Min
track.Volume = 0
track:Play()
end
local lastNumberPlayed = 0 -- Prevents re-setting play head when sitting at a block
-- Set a track (by index in tracks) to a target speed over t seconds
function slowDown(soundIndex, targetSpeed, t)
local tweenservice = game:GetService("TweenService") -- getting the tween service
-- Increase/decrease the playback speed to targetSpeed over t seconds
local tween = tweenservice:Create(tracks[soundIndex], TweenInfo.new(t), {PlaybackSpeed = targetSpeed})
tween:Play() -- Starting the background fading
end
-- Used to slow down the sound of all tracks, good to play for death events (like hitting a kill brick)
-- Sets the track speed to target speed over t seconds
function slowAllTracks(targetSpeed, t)
for index, track in tracks do
slowDown(index, targetSpeed, t)
end
end
-- Increase the sound volume of one of the tracks, while decreasing the volume of all other tracks
function increaseSoundVolume(toIncrease, duration)
local tweenservice = game:GetService("TweenService") -- getting the tween service
for index, track in tracks do
local targetVolume = minSound
if (index == toIncrease) then
print("Increasing the volume of track ", index, track)
targetVolume = fullSound
end
-- Increase/decrease the volume over the rampUpTime for the track
local tween = tweenservice:Create(track, TweenInfo.new(transitionSeconds), {Volume = targetVolume})
tween:Play() -- Starting the background fading
end
end
-- Fading a loop in with itself, uses lastNumberPlayed local variable to keep track of the sound that is playing
local runService = game:GetService("RunService")
local isSelfFading = false
local selfFadingNumber = 0
runService.Heartbeat:Connect(function (dt)
if (lastNumberPlayed ~= 0) then
local currentTrack = tracks[lastNumberPlayed]
local timeRemaining = currentTrack.LoopRegion.Max - (currentTrack.TimePosition)
local offset = currentTrack.LoopRegion.Min
local currentTime = currentTrack.TimePosition - offset
local tweenservice = game:GetService("TweenService") -- getting the tween service
if (selfFadingNumber ~= lastNumberPlayed or not isSelfFading) then
-- Fade in
if currentTime <= fadeInTime then
-- Fade in the track for fadeInTime seconds
local tween = tweenservice:Create(currentTrack, TweenInfo.new(fadeInTime), {Volume = fullSound}) -- making the tween a variable for further use
tween.Completed:Connect(function()
isSelfFading = false
print("Tween completed!")
end)
tween:Play() -- playing the tween
elseif timeRemaining <= fadeOutTime then
-- Fade out the track before it ends
local tween = tweenservice:Create(currentTrack, TweenInfo.new(timeRemaining), {Volume = minSound}) -- making the tween a variable for further use
tween.Completed:Connect(function()
isSelfFading = false
print("Tween completed!")
end)
tween:Play() -- playing the tween
end
isSelfFading = true
selfFadingNumber = lastNumberPlayed
end
end
end)
-- Attach tracks to different objects named "Section1", "Section2", etc, for as many tracks/positions you want. Put these in Workspace -> "Checkpoints" folder -> Section1, Section2,......
local workspace = script.Parent
for key, value in (workspace.Checkpoints:GetChildren()) do
local number = tonumber(string.split(value.Name, "Section")[2]) -- Get number from the checkpoint name, Checkpoint1 plays the second section
-- When a player touches the block with that value (SectionX), if that song section isn't the one playing, fade it in
value.Touched:connect(function(hit)
if hit and hit.Parent and hit.Parent:FindFirstChild("Humanoid") then
--slowAllTracks(1, 0.1), use this to slow tracks down over time, like if you hit a kill brick (see "ObbyoScape" example)
if (lastNumberPlayed ~= number) then
increaseSoundVolume(number, transitionSeconds)
end
lastNumberPlayed = number -- Store the value of the current playing track
end
end)
end
More ideas?
I'd love to hear your feedback -- there may be better efficient ways to code this. I can also post a tutorial on how to find the loop points in an audio track that isn't at 120BPM. Let me know.Thanks!
OffGridDude