mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
123feef3a1
CLOSES #2425, CLOSES #2428, CLOSES #2431, CLOSES #2432, CLOSES #2433, CLOSES #2435, CLOSES #2436, CLOSES #2437, CLOSES #2440
136 lines
4.6 KiB
Lua
136 lines
4.6 KiB
Lua
#!/usr/bin/env luajit
|
|
-- pokefirered/src/battle_anim_special.c:1200
|
|
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local failed = 0
|
|
local function check(cond, msg)
|
|
if cond then
|
|
print("[ok] " .. msg)
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. msg)
|
|
end
|
|
end
|
|
|
|
local function find_pack_root()
|
|
local root = require("tests.game3_cache").root("audio/index.lua")
|
|
return root and (root .. "/audio")
|
|
end
|
|
|
|
local root = find_pack_root()
|
|
if not root then
|
|
print("[skip] no imported FireRed audio pack; nothing to measure")
|
|
os.exit(0)
|
|
end
|
|
print("[test] pack at " .. root)
|
|
|
|
local cache = { read = function(_, rel)
|
|
local f = io.open(rel, "rb")
|
|
if not f then return nil end
|
|
local d = f:read("*a")
|
|
f:close()
|
|
return d
|
|
end }
|
|
|
|
local Player = require("src.core.game3.m4a_player")
|
|
local Mix = require("src.core.game3.m4a_mix")
|
|
local Audio = require("src.core.game3.audio")
|
|
|
|
local pack, err = Player.loadPack(cache, root)
|
|
if not pack then
|
|
print("[skip] loadPack failed: " .. tostring(err))
|
|
os.exit(0)
|
|
end
|
|
|
|
local function bake_seconds(id, maxSec)
|
|
local slot = { voices = {} }
|
|
if not Player.start(pack, cache, slot, id, { forceSeq = true }) then return nil end
|
|
local L = Player.bakeSlot(slot, { raw = true, maxSec = maxSec, stopOnGoto = false })
|
|
return #L / Mix.SAMPLE_RATE
|
|
end
|
|
|
|
-- pokefirered/src/m4a_1.s:751
|
|
local longest, longestId = 0, nil
|
|
for id = 0, 400 do
|
|
local info = Player.songInfo(pack, id)
|
|
if info and info.kind == "se" and not info.hasGoto then
|
|
local s = bake_seconds(id, 60)
|
|
if s and s > longest then longest, longestId = s, id end
|
|
end
|
|
end
|
|
check(longest > 0, "measured the pack's one-shot SE cues")
|
|
check((tonumber(Audio.SE_ONESHOT_MAX_SEC) or 0) > longest,
|
|
string.format("SE_ONESHOT_MAX_SEC %.1fs clears the longest one-shot (id %s, %.3fs)",
|
|
Audio.SE_ONESHOT_MAX_SEC or -1, tostring(longestId), longest))
|
|
check(Audio.SE_LOOP_MAX_SEC == 2.5, "looping cues keep pret's 2.5s slice")
|
|
|
|
Audio._ready = true
|
|
Audio._pack = pack
|
|
Audio._cache = cache
|
|
local realBake = Player.bakeSlot
|
|
local function production_max(id)
|
|
local seen = nil
|
|
Player.bakeSlot = function(slot, opts) seen = opts and opts.maxSec; return { 0 }, { 0 } end
|
|
Audio._seRawClear()
|
|
pcall(Audio.playSe, id)
|
|
Player.bakeSlot = realBake
|
|
Audio._seRawClear()
|
|
return seen
|
|
end
|
|
local prodMax = production_max(319)
|
|
check(type(prodMax) == "number", "Audio.playSe passes a numeric maxSec (" .. tostring(prodMax) .. ")")
|
|
|
|
-- pokefirered/src/battle_anim_special.c:1200
|
|
local SE = require("src.core.game3.se_ids")
|
|
check(production_max(SE.SE_EXP) == Audio.SE_LOOP_MAX_SEC,
|
|
"SE_EXP keeps the 2.5s slice (" .. tostring(production_max(SE.SE_EXP)) .. ")")
|
|
check(production_max(SE.SE_LOW_HEALTH) == Audio.SE_LOOP_MAX_SEC,
|
|
"SE_LOW_HEALTH keeps the 2.5s slice (" .. tostring(production_max(SE.SE_LOW_HEALTH)) .. ")")
|
|
|
|
-- pokefirered/include/constants/songs.h:89, :102, :284
|
|
local CUES = { 319, 165, 204, 227, 249, 276, 85, 98 }
|
|
for _, id in ipairs(CUES) do
|
|
local natural = bake_seconds(id, 60)
|
|
local idMax = production_max(id)
|
|
local production = idMax and bake_seconds(id, idMax)
|
|
if not natural then
|
|
print("[skip] id " .. id .. " not in this pack")
|
|
else
|
|
check(production and math.abs(production - natural) <= 1 / 60,
|
|
string.format("id %d bakes in full (%.3fs production vs %.3fs natural)",
|
|
id, production or -1, natural))
|
|
end
|
|
end
|
|
|
|
local bakes = 0
|
|
Player.bakeSlot = function(slot, opts) bakes = bakes + 1; return realBake(slot, opts) end
|
|
Audio._seRawClear()
|
|
for _ = 1, 3 do pcall(Audio.playSe, 319) end
|
|
check(bakes == 1, "MUS_CAUGHT_INTRO bakes once across three plays (" .. bakes .. ")")
|
|
local entry = Audio._seRaw[319]
|
|
check(entry and entry.frames / Mix.SAMPLE_RATE >= 4.5,
|
|
string.format("memoized buffer is the full cue (%.3fs)", entry and entry.frames / Mix.SAMPLE_RATE or -1))
|
|
bakes = 0
|
|
pcall(Audio.playSe, 319, { maxSec = 1 })
|
|
check(bakes == 1 and Audio._seRaw[319] == entry, "an explicit maxSec bypasses the memo")
|
|
local budget = Audio.SE_RAW_MAX_FRAMES
|
|
Audio.SE_RAW_MAX_FRAMES = entry and entry.frames + 1 or budget
|
|
pcall(Audio.playSe, 5)
|
|
check(Audio._seRaw[5] ~= nil and Audio._seRaw[319] == nil
|
|
and Audio._seRawFrames <= Audio.SE_RAW_MAX_FRAMES,
|
|
"memo evicts least-recent cue at the frame budget (" .. tostring(Audio._seRawFrames) .. ")")
|
|
Audio.SE_RAW_MAX_FRAMES = budget
|
|
Audio._seRawClear()
|
|
Player.bakeSlot = realBake
|
|
|
|
local caught = bake_seconds(319, prodMax)
|
|
check(caught and caught >= 4.5,
|
|
string.format("MUS_CAUGHT_INTRO is at least 4.5s (%.3fs)", caught or -1))
|
|
|
|
if failed > 0 then
|
|
print(string.format("\n%d check(s) failed", failed))
|
|
os.exit(1)
|
|
end
|
|
print("\nall SE length checks passed")
|