mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-08 09:42:55 -04:00
168 lines
5.6 KiB
Lua
168 lines
5.6 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("chipaudio prewarm queue 2732")
|
|
local check, eq = S.check, S.eq
|
|
|
|
love = require("tests.love_stub")
|
|
local ffi = require("ffi")
|
|
|
|
local SoundData = {}
|
|
SoundData.__index = SoundData
|
|
function SoundData:setSample(index, a, b)
|
|
if b == nil then
|
|
self.buf[index * self.channels] = a * 32767
|
|
else
|
|
self.buf[index * self.channels + (a - 1)] = b * 32767
|
|
end
|
|
end
|
|
function SoundData:getFFIPointer() return self.buf end
|
|
function SoundData:getSampleCount() return self.samples end
|
|
function SoundData:getSampleRate() return self.rate end
|
|
function SoundData:getChannelCount() return self.channels end
|
|
love.sound.newSoundData = function(samples, rate, _, channels)
|
|
return setmetatable({ samples = samples, rate = rate, channels = channels,
|
|
buf = ffi.new("int16_t[?]", samples * channels) }, SoundData)
|
|
end
|
|
love.audio = { newSource = function(sd) return { sd = sd } end }
|
|
|
|
local clock = 0
|
|
local clockStep = 0
|
|
love.timer.getTime = function()
|
|
clock = clock + clockStep
|
|
return clock
|
|
end
|
|
|
|
local channels = {}
|
|
local function channel(name)
|
|
local ch = channels[name]
|
|
if ch then return ch end
|
|
local items = {}
|
|
ch = { items = items }
|
|
function ch:push(v) items[#items + 1] = v end
|
|
function ch:pop() return table.remove(items, 1) end
|
|
function ch:clear() for i = #items, 1, -1 do items[i] = nil end end
|
|
function ch:getCount() return #items end
|
|
channels[name] = ch
|
|
return ch
|
|
end
|
|
love.thread = {
|
|
newThread = function()
|
|
return { start = function() end, getError = function() return nil end }
|
|
end,
|
|
getChannel = channel,
|
|
}
|
|
|
|
local ChipSynth = require("src.core.ChipSynth")
|
|
local ChipAsm = require("src.audio.ChipAsm")
|
|
local ChipAudio = require("src.core.ChipAudio")
|
|
local FixedStep = require("src.core.FixedStep")
|
|
|
|
local beep = ChipAsm.sfx{ channels = { { hw = 1, program = {
|
|
{ squareNote = { len = 4, volume = 15, fade = 1, frequency = 0x600 } },
|
|
} } } }
|
|
local data = { audio = { sfx = { Beep = beep }, cries = {} } }
|
|
|
|
local function workerStep(limit)
|
|
local cmdCh, fxCh = channel("chipaudio_cmd"), channel("chipaudio_fx")
|
|
local n = 0
|
|
while n < (limit or math.huge) do
|
|
local req = cmdCh:pop()
|
|
if not req then break end
|
|
if req.cmd == "effect" then
|
|
n = n + 1
|
|
fxCh:push({ key = req.key, epoch = req.epoch,
|
|
sd = ChipSynth.renderEffectData({ audio = req.audio }, req.header,
|
|
req.options or {}) })
|
|
end
|
|
end
|
|
return n
|
|
end
|
|
|
|
for pitch = 1, 5 do ChipAudio.prewarmSfx(data, "Beep", pitch) end
|
|
local st = ChipAudio._effectStateForTest()
|
|
eq(st.inFlight, 2, "two prewarms handed to the worker at once")
|
|
eq(st.queued, 3, "the rest wait on the main thread")
|
|
eq(#channel("chipaudio_cmd").items, 2, "only two effect commands pushed")
|
|
|
|
ChipAudio.resetSyncStats()
|
|
check(ChipAudio.newSfx(data, "Beep", 5) ~= nil, "a queued sfx still plays")
|
|
eq(ChipAudio._effectStateForTest().queued, 2,
|
|
"playing a queued sfx drops its prewarm")
|
|
eq(ChipAudio.stats().syncRenders, 1, "and is counted as a sync render")
|
|
|
|
workerStep()
|
|
ChipAudio.pumpEffects()
|
|
st = ChipAudio._effectStateForTest()
|
|
eq(st.ready, 2, "finished prewarms are ready")
|
|
eq(st.inFlight, 2, "the queue refills the worker as results land")
|
|
workerStep()
|
|
ChipAudio.pumpEffects()
|
|
st = ChipAudio._effectStateForTest()
|
|
eq(st.ready, 4, "all four left are ready")
|
|
eq(st.queued + st.inFlight, 0, "nothing left queued")
|
|
|
|
ChipAudio.resetSyncStats()
|
|
check(ChipAudio.newSfx(data, "Beep", 1) ~= nil, "prewarmed sfx plays")
|
|
eq(ChipAudio.stats().syncRenders, 0, "a prewarmed play renders nothing")
|
|
eq(ChipAudio.stats().prewarmHits, 1, "and is counted as a hit")
|
|
|
|
ChipAudio.prewarmPinned(function()
|
|
for pitch = 50, 66 do ChipAudio.prewarmSfx(data, "Beep", pitch) end
|
|
end)
|
|
for _ = 1, 20 do
|
|
workerStep()
|
|
ChipAudio.pumpEffects()
|
|
end
|
|
st = ChipAudio._effectStateForTest()
|
|
eq(st.pinned, 17, "a session set of 17 is pinned")
|
|
eq(st.ready, 20, "pinned prewarms are ready")
|
|
|
|
for pitch = 100, 199 do
|
|
ChipAudio.prewarmSfx(data, "Beep", pitch)
|
|
workerStep()
|
|
ChipAudio.pumpEffects()
|
|
end
|
|
for _ = 1, 10 do
|
|
workerStep()
|
|
ChipAudio.pumpEffects()
|
|
end
|
|
st = ChipAudio._effectStateForTest()
|
|
eq(st.unpinnedReady, 64, "unpinned ready prewarms are capped")
|
|
eq(st.ready, 64 + 17, "pinned ones sit outside the cap")
|
|
ChipAudio.resetSyncStats()
|
|
for pitch = 50, 66 do ChipAudio.newSfx(data, "Beep", pitch) end
|
|
eq(ChipAudio.stats().syncRenders, 0,
|
|
"every pinned session sound survives a 100-sound battle prewarm")
|
|
ChipAudio.newSfx(data, "Beep", 199)
|
|
eq(ChipAudio.stats().syncRenders, 0, "the newest unpinned prewarm survives the cap")
|
|
ChipAudio.newSfx(data, "Beep", 100)
|
|
eq(ChipAudio.stats().syncRenders, 1, "the oldest unpinned one was evicted")
|
|
eq(ChipAudio._effectStateForTest().pinned, 17, "the pin budget is unchanged")
|
|
|
|
ChipAudio.prewarmPinned(function()
|
|
for pitch = 400, 440 do ChipAudio.prewarmSfx(data, "Beep", pitch) end
|
|
end)
|
|
eq(ChipAudio._effectStateForTest().pinned, 24, "pins stop at the pin budget")
|
|
for _ = 1, 30 do
|
|
workerStep()
|
|
ChipAudio.pumpEffects()
|
|
end
|
|
check(ChipAudio._effectStateForTest().ready <= 64 + 24,
|
|
"ready PCM never exceeds both budgets together")
|
|
|
|
FixedStep:init(function() end)
|
|
ChipAudio.resetSyncStats()
|
|
clockStep = 0.001
|
|
ChipAudio.newSfx(data, "Beep", 300)
|
|
check(not FixedStep.suppressCatchup, "a short sync render keeps the catch-up")
|
|
clockStep = 0.05
|
|
ChipAudio.newSfx(data, "Beep", 301)
|
|
check(FixedStep.suppressCatchup, "a sync render over a frame drops the catch-up")
|
|
eq(FixedStep.accum, 0, "and clears the accumulator")
|
|
check(ChipAudio.stats().syncRenderMsMax >= 16, "its duration is recorded")
|
|
check(ChipAudio.stats().line:find("sync=2/", 1, true) ~= nil,
|
|
"stats.line carries the counters")
|
|
clockStep = 0
|
|
|
|
S.finish()
|