Hello!,
I had made a old resource called BPMDetector a few months ago, right now i bring you the new and improved BPMDetector 2 (v0.0.1) , As always, this lets you detect bpm of any song , ofcourse you would still need to tweak and experiment with the parameters for different song genres, but most of the time is accurate.
This system uses NamedSignal By @nowodev
Most notable improvements :
- frame rate independence
- better queue system
- BPM Clamping
- Robust Median for missing or double beats
- some new arguments
DISCLAIMER : This Only Works with the new AudioApi, so very Basic Understanding of it, it’s needed
Downloads
BPMDetector2v0.0.1.rbxm (9,8 KB) > First Release v0.0.1
Showcase
example code (small camera system on beat, Using AudioGraphV2) :
-- CutsceneCamera
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Modules = ReplicatedStorage:WaitForChild("Modules")
local Audio = require(Modules.Audio)
local BPMDetector = require(Modules.BPMDetectorv2)
local Camera = workspace.CurrentCamera
local player = Players.LocalPlayer
local AudioPlayer = workspace:WaitForChild("AudioPlayer", math.huge)
local character = player.Character or player.CharacterAdded:Wait()
local rootPart = character:WaitForChild("HumanoidRootPart")
----------------------------------------------------------
local AudioDeviceOutput = Instance.new("AudioDeviceOutput")
AudioDeviceOutput.Parent = Camera
local Analyzer = Instance.new("AudioAnalyzer")
Analyzer.Parent = AudioPlayer
local PlayerGraph = Audio.NewGraph(AudioPlayer)
PlayerGraph:ConnectConsumer(AudioDeviceOutput)
PlayerGraph:ConnectConsumer(Analyzer)
PlayerGraph:Create{
--["AudioPitchShifter"] = {
-- Name = "DistortionPitch",
-- WindowSize = "Medium",
-- Pitch = 1,
-- Bypass = false,
--},
["AudioReverb"] = {
Name = "AudioReverb_",
DecayRatio = 0.577,
DecayTime = 0.23,
Density = 0.53,
},
}
AudioPlayer.Asset = "rbxassetid://112559061735366"
AudioPlayer.AutoPlay = false
-- Detector config — tuned for the onset-detection approach.
-- ThresholdMultiplier stays low (sensitive to level).
-- RiseSensitivity is the new gate that stops false fires.
local detector = BPMDetector.new({
Analyzer = Analyzer,
MinGap = 0.27,
WindowSize = 23,
ThresholdMultiplier = 0.14,
RiseSensitivity = 0.18,
MinBPM = 60,
MaxBPM = 200,
})
local ORBIT_RADIUS = 5
local RNG = Random.new()
local ANGLE_POOL = {
0, math.pi / 4, math.pi / 2, 3 * math.pi / 4,
math.pi, -math.pi / 4, -math.pi / 2, -3 * math.pi / 4,
}
local HEIGHT_POOL = { 1.0, 1.6, 2.4, 3.2 }
local orbitAngle = 0
local orbitHeight = 1.6
local fromAngle = 0
local toAngle = 0
local fromHeight = 1.6
local toHeight = 1.6
local blendT = 1
local beatTimer = 0
local secondsPerBeat = 0.5
local function easeInOutSine(t)
return -(math.cos(math.pi * t) - 1) / 2
end
local function pickAngle(current)
local candidates = {}
for _, a in ipairs(ANGLE_POOL) do
if math.abs(a - current) > math.pi / 6 then
table.insert(candidates, a)
end
end
return candidates[RNG:NextInteger(1, #candidates)]
end
detector.OnBeat:Connect(function()
fromAngle = orbitAngle
fromHeight = orbitHeight
toAngle = pickAngle(orbitAngle)
toHeight = HEIGHT_POOL[RNG:NextInteger(1, #HEIGHT_POOL)]
blendT = 0
beatTimer = 0
local bpm = detector:GetBPM()
local rms = Analyzer.RmsLevel
local formula = (bpm / 60) * rms + 1
Camera.FieldOfView = RNG:NextNumber(38, 50) + formula
end)
-- optional secondsPerBeat in sync with detected tempo
detector.OnBPMUpdate:Connect(function(bpm)
secondsPerBeat = 60 / bpm
end)
-- smooth orbit interpolation
RunService.RenderStepped:Connect(function(dt)
beatTimer += dt
local progress = math.clamp(beatTimer / secondsPerBeat, 0, 1)
blendT = easeInOutSine(progress)
orbitAngle = fromAngle + (toAngle - fromAngle) * blendT
orbitHeight = fromHeight + (toHeight - fromHeight) * blendT
local root = rootPart.Position
local camX = root.X + math.cos(orbitAngle) * ORBIT_RADIUS
local camZ = root.Z + math.sin(orbitAngle) * ORBIT_RADIUS
local camPos = Vector3.new(camX, root.Y + orbitHeight, camZ)
Camera.CFrame = CFrame.lookAt(camPos, root + Vector3.new(0, 1.0, 0))
end)
detector:Start()
task.delay(9 ,function()
AudioPlayer:Play()
AudioPlayer.TimePosition = 0
end)
player.CharacterRemoving:Connect(function()
detector:Destroy()
end)
Documentation :
Here’s a table I’ve made explaining and listing some parameters
