Task.wait() is very inconsistent

I’m not sure if there’s a solution to this. The solution I would have thought of is something you’re already using.

If you’re talking about my sonic-pi code, then yes. It doesn’t have to compensate for time, because sonic-pi’s sleep implementation is very accurate in time.

If you’re talking about the command-line code at the bottom, that simply plays the sample for a second, then stops, which can be run multiple times, which reveals the occasional clipping/general time inaccuracies.

The sound is cloned, then destroyed, to allow for it to play without having copies of the same sample spawn over and over, preventing any reading of its variables, though, I could try to see how it would work under that implementation.

1 Like

I’d change the way it’s implemented and not delete the sound, while getting the current TimePosition of the sound to accurately tell where in the audio length it’s being emitted yk

I modified your piece of code and seems to sync up fine.

--!strict

local SoundService = game:GetService("SoundService")

local sound: Sound = script.Sound
local expectedInterval: number = 0.5
local lastTime: number = os.clock()

while true do
	local currentTime: number = os.clock()
	local elapsedTime: number = currentTime - lastTime

	local waitTime: number = expectedInterval - elapsedTime
	
	if waitTime > 0 then
		local actualTime: number = task.wait(waitTime)
		lastTime += actualTime
	end

	local sample = sound:Clone()
	sample.Parent = SoundService
	sample:Destroy()
end

Apparently, TimePosition doesn’t actually fire any events upon change. :sad:

1 Like

I’ve tried your code, and the same issue takes place (I would’ve recorded the difference, but it sounds basically identical to what I recorded in the top post).

Maybe it’s an issue with the sound itself, though I’d doubt it. It seems fine; by itself it loops perfectly with Looped = true.

I also probably should’ve linked the audio itself, so here it is:

1 Like

you don’t need to get it every time it changes, all you need is to check the value once it’s playing to get the position the sound is at and use that to figure out when the next sound is supposed to start playing yk?

Always returns a fat zero.
image

local SoundService = game:GetService("SoundService")

local Samples = SoundService.Samples

local expectedInterval = 0.5
local lastTime = tick()

local function createSound()
	local c = Samples.loop_mika:Clone()
	c.Parent = Samples
	c:Play()
	local d = c.Changed:Connect(function()
		print(c.TimePosition)
		if c.TimePosition >= 1 then d:Disconnect() createSound() end
	end)
end

createSound()

don’t do it in a .Changed connection, just do it in a loop or wait until the sound is loaded and playing and then get the TimePosition

I may be a bit off here but, from working with many languages there is a unwritten rule more or less not to call 0 time waits … wait(), wait(0) or the task.wait version. Roblox uses this in some templates so I assume your 0 time calls are not truly 0 time. If I need to go low, I will always use 0.33 or even 0.033. these are old assembler tricks on faking one FPS wait. The fact you are finding a 0 wait not consistent isn’t shocking. Many languages would just lock up with a call like that.

Edit: It would be interesting to see your test using this technique. I got a feeling it will pass with flying colors with a task.wait …

Like this? The wording was a little confusing.
Either way, the issue persists.

local SoundService = game:GetService("SoundService")
local Samples = SoundService.Samples

local offset

while true do
	local c = Samples.loop_mika:Clone()
	c.Parent = Samples
	c:Play() -- doesnt seem to make a difference if its before or after

	if not c.IsLoaded then c.Loaded:Wait() end
	
	local nextOffset = c.TimePosition
	print(nextOffset) -- still returns 0
	if offset then c.TimePosition += offset end

	task.wait(math.max(0.5 - nextOffset, 0))
	offset = nextOffset
end

Removing the 0 waits doesn’t seem to change anything.

local SoundService = game:GetService("SoundService")

local Samples = SoundService.Samples

local expectedInterval = 0.5
local lastTime = tick()

while true do
	local currentTime = tick()
	local elapsedTime = currentTime - lastTime
	lastTime = currentTime

	local waitTime = expectedInterval - elapsedTime
	lastElapsed = (waitTime > 0 and task.wait(waitTime) or 0.5)

	local c = Samples.loop_mika:Clone()
	c.Parent = SoundService
	c:Destroy()
end

If you read the documentation, task.wait will wait until the heartbeat after the time is up. It also says that task.wait() will default to task.wait(0) and that it is equivalent to RunService.Heartbeat:Wait()

Yes. In the top post, I was asking for any possible alternatives to task.wait, or any way to mitigate its desync well enough, to where it doesn’t sound awful.

I didn’t say remove it. Anyways you’re using this to find an expectedInterval/elapsedTime. Why do that when you can just read the real position of the music being played.

I figured that is what it was doing. You call a real wait 0, you’re heading for a crash … lol

Because it is being destroyed in that code example.
Using an alternative version where it isn’t being destroyed, the result (unless I did it wrong) is also the same issue.

local SoundService = game:GetService("SoundService")
local Samples = SoundService.Samples

local offset

while true do
	local c = Samples.loop_mika:Clone()
	c.Name = "-"
	c.Parent = Samples
	c:Play()

	if not c.IsLoaded then c.Loaded:Wait() end

	local nextOffset = c.TimePosition
	print(nextOffset)
	if offset then c.TimePosition += offset end
	
	local calc = 0.5 - nextOffset
	if calc > 0 then task.wait(calc) end
	offset = nextOffset
end

I was just pointing out, I could see how a wait(0) could be inconstant. There is no such a call … It’s like dividing by 0 … one of the things you just don’t do in a program or it crashes.

Going with your post … how does this work out?

local SoundService = game:GetService("SoundService")
local RunService = game:GetService("RunService")

local Samples = SoundService.Samples
local expectedInterval = 0.5
local lastTime = tick()

local function createSound()
	local c = Samples.loop_mika:Clone()
	c.Parent = Samples
	c:Play()
	lastTime = tick()
end

RunService.Heartbeat:Connect(function()
	if tick() - lastTime >= expectedInterval then
		createSound()
	end
end)

createSound()