mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 05:32:07 -04:00
218 lines
8.7 KiB
Lua
218 lines
8.7 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local passed, failed = 0, 0
|
|
local function check(cond, name)
|
|
if cond then
|
|
passed = passed + 1
|
|
else
|
|
failed = failed + 1
|
|
print("[FAIL] " .. tostring(name))
|
|
end
|
|
end
|
|
local function near(a, b, eps) return math.abs(a - b) <= (eps or 1e-9) end
|
|
|
|
local function new_sd(n, rate, bits, ch)
|
|
local sd = { n = n, rate = rate, ch = ch or 1, data = {} }
|
|
function sd:setSample(i, a, b)
|
|
if b == nil then self.data[i * self.ch + 1] = a else self.data[i * self.ch + a] = b end
|
|
end
|
|
function sd:getSample(i, c)
|
|
return self.data[i * self.ch + (c or 1)] or 0
|
|
end
|
|
function sd:getSampleCount() return self.n end
|
|
function sd:getChannelCount() return self.ch end
|
|
return sd
|
|
end
|
|
|
|
local sources = {}
|
|
local function new_source(sd)
|
|
local s = { sd = sd, volume = 1, playing = false, pos = 0, looping = false }
|
|
function s:play() self.playing = true end
|
|
function s:stop() self.playing = false end
|
|
function s:isPlaying() return self.playing end
|
|
function s:setVolume(v) self.volume = v end
|
|
function s:getVolume() return self.volume end
|
|
function s:setLooping(v) self.looping = v end
|
|
function s:tell() return self.pos end
|
|
function s:seek(p) self.pos = p end
|
|
sources[#sources + 1] = s
|
|
return s
|
|
end
|
|
|
|
love = {
|
|
sound = { newSoundData = new_sd },
|
|
audio = { newSource = new_source },
|
|
}
|
|
|
|
local Sample = require("src.core.game3.m4a_sample")
|
|
local Mix = require("src.core.game3.m4a_mix")
|
|
local Audio = require("src.core.game3.audio")
|
|
|
|
-- pokefirered/src/sound.c:374
|
|
local pret = {
|
|
[0] = { 140, 0, 15360, 0, false, 120 },
|
|
[1] = { 20, 225, 15360, 0, false, 120 },
|
|
[2] = { 140, 225, 15600, 20, false, 90 },
|
|
[3] = { 50, 200, 15800, 20, false, 90 },
|
|
[4] = { 25, 100, 15600, 192, true, 90 },
|
|
[5] = { 140, 200, 14440, 0, false, 120 },
|
|
[6] = { 140, 220, 15555, 192, false, 90 },
|
|
[7] = { 10, 100, 14848, 0, false, 120 },
|
|
[8] = { 60, 225, 15616, 0, false, 120 },
|
|
[9] = { 15, 125, 15200, 0, true, 120 },
|
|
[10] = { 100, 225, 15200, 0, false, 120 },
|
|
[11] = { 140, 0, 15000, 0, false, 120 },
|
|
[12] = { 20, 225, 15000, 0, false, 120 },
|
|
}
|
|
for mode = 0, 12 do
|
|
local p = Sample.cryParams(mode)
|
|
local e = pret[mode]
|
|
check(p.mode == mode, "mode " .. mode .. " id")
|
|
check(p.length == e[1], "mode " .. mode .. " length")
|
|
check(p.release == e[2], "mode " .. mode .. " release")
|
|
check(p.pitch == e[3], "mode " .. mode .. " pitch")
|
|
check(p.chorus == e[4], "mode " .. mode .. " chorus")
|
|
check(p.reverse == e[5], "mode " .. mode .. " reverse")
|
|
check(p.volume == e[6], "mode " .. mode .. " volume")
|
|
end
|
|
check(Sample.cryParams(99).mode == 0, "unknown mode falls back to normal")
|
|
check(Sample.cryParams(0, 125).volume == 125, "caller volume for normal")
|
|
check(Sample.cryParams(3, 125).volume == 90, "mode volume overrides caller")
|
|
|
|
do
|
|
local k, t = Sample.cryKeyTune(15360)
|
|
check(k == 60 and t == 64, "15360 key/tune")
|
|
check(near(Sample.cryRateMul(k, t), 1.0), "15360 rate 1.0")
|
|
k, t = Sample.cryKeyTune(15800)
|
|
check(k == 62 and t == 28, "15800 key/tune")
|
|
check(near(Sample.cryRateMul(k, t), 2 ^ (92 / 768)), "15800 rate")
|
|
k, t = Sample.cryKeyTune(14440)
|
|
check(k == 56 and t == 116, "14440 key/tune")
|
|
check(near(Sample.cryRateMul(k, t), 2 ^ (-204 / 768)), "14440 rate")
|
|
check(Sample.cryChorusTune(20, 28) == 48, "chorus +20")
|
|
check(Sample.cryChorusTune(192, 56) == 120, "chorus 192 wraps as s8 -64")
|
|
end
|
|
|
|
do
|
|
local env = Sample.cryEnvelope(20, 225)
|
|
check(env.gain(0) == 1 and env.gain(19) == 1, "held for length")
|
|
check(near(env.gain(20), math.floor(255 * 225 / 256) / 255), "first release step")
|
|
local prev = 1
|
|
local mono = true
|
|
for f = 20, env.endFrame - 1 do
|
|
local g = env.gain(f)
|
|
if not (g < prev and g > 0) then mono = false end
|
|
prev = g
|
|
end
|
|
check(mono, "release decays monotonically")
|
|
check(env.gain(env.endFrame) == 0, "release reaches zero")
|
|
check(env.endFrame > 20 and env.endFrame < 80, "release tail length")
|
|
local cut = Sample.cryEnvelope(140, 0)
|
|
check(cut.endFrame == 140 and cut.gain(140) == 0, "release 0 cuts at length")
|
|
end
|
|
|
|
for mode = 0, 12 do
|
|
local v = Sample.cryVoices(Sample.cryParams(mode))
|
|
local want = (mode == 2 or mode == 3 or mode == 4 or mode == 6) and 2 or 1
|
|
check(#v == want, "mode " .. mode .. " voice count")
|
|
end
|
|
|
|
local ramp = {}
|
|
for i = 0, 99 do ramp[#ramp + 1] = string.char(i) end
|
|
ramp = table.concat(ramp)
|
|
|
|
do
|
|
local base = { length = 140, release = 0, pitch = 15360, chorus = 0, reverse = false, volume = 127 }
|
|
local fwd = Sample.renderCryMix(ramp, 1000, base, { outRate = 1000 })
|
|
local rp = { length = 140, release = 0, pitch = 15360, chorus = 0, reverse = true, volume = 127 }
|
|
local rev = Sample.renderCryMix(ramp, 1000, rp, { outRate = 1000 })
|
|
check(#fwd == 100 and #rev == 100, "render length at native rate")
|
|
check(near(fwd[1], 0) and near(fwd[100], 99 / 128), "forward order")
|
|
check(near(rev[1], 99 / 128) and near(rev[100], 0), "reverse order")
|
|
local vp = { length = 140, release = 0, pitch = 15360, chorus = 0, reverse = false, volume = 90 }
|
|
local vol = Sample.renderCryMix(ramp, 1000, vp, { outRate = 1000 })
|
|
check(near(vol[51], (50 / 128) * 90 / 127), "volume scales by vol/127")
|
|
end
|
|
|
|
do
|
|
local long = string.rep(string.char(64), 30000)
|
|
local p = { length = 20, release = 225, pitch = 15360, chorus = 0, reverse = false, volume = 127 }
|
|
local out, info = Sample.renderCryMix(long, 10000, p, { outRate = 10000 })
|
|
local env = Sample.cryEnvelope(20, 225)
|
|
check(info.frames == env.endFrame, "doubles length is envelope-bound")
|
|
local fr = Sample.GBA_FRAME_RATE
|
|
local s19 = math.floor(19.5 * 10000 / fr)
|
|
local s25 = math.floor(25.5 * 10000 / fr)
|
|
check(near(out[s19 + 1], 0.5), "held at full before length")
|
|
check(near(out[s25 + 1], 0.5 * env.gain(25)), "release gain applied")
|
|
check(#out <= math.ceil(env.endFrame * 10000 / fr), "buffer ends with the tail")
|
|
local short = string.rep(string.char(64), 1000)
|
|
local n = { length = 140, release = 0, pitch = 15360, chorus = 0, reverse = false, volume = 127 }
|
|
local _, i2 = Sample.renderCryMix(short, 10000, n, { outRate = 10000 })
|
|
check(i2.frames == math.ceil(1000 / 10000 * fr), "short cry ends at sample end")
|
|
end
|
|
|
|
do
|
|
local p = Sample.cryParams(3)
|
|
local one = Sample.renderCryMix(string.rep(string.char(32), 4000), 8000,
|
|
{ length = p.length, release = p.release, pitch = p.pitch, chorus = 0, reverse = false, volume = 90 },
|
|
{ outRate = 8000 })
|
|
local two = Sample.renderCryMix(string.rep(string.char(32), 4000), 8000, p, { outRate = 8000 })
|
|
check(near(two[10], one[10] * 2, 1e-9), "chorus mixes a second voice at the same volume")
|
|
end
|
|
|
|
do
|
|
local pcm = string.rep(string.char(40), 20000)
|
|
Audio._pack = {
|
|
index = { cryIds = { [25] = 0 }, cries = { [0] = { sampleId = 1 } } },
|
|
samples = { [1] = { offset = 0, size = #pcm, freq = 10000 * 1024 } },
|
|
samplesBin = pcm,
|
|
}
|
|
Audio._ready = true
|
|
Audio._cryClock = 0
|
|
sources = {}
|
|
Audio.playCry(25, 1, 0x3F)
|
|
local src = sources[#sources]
|
|
check(src and src.playing, "cry source plays")
|
|
check(src and src.sd.ch == 2, "panned cry is stereo")
|
|
check(Audio._duck == 1, "doubles does not duck")
|
|
check(Audio._cryUntil == Sample.cryEnvelope(20, 225).endFrame, "cryUntil = length + release")
|
|
Audio.stopCry()
|
|
Audio.playCry(25, { mode = 3, pan = -64 })
|
|
check(Audio._cryParams.mode == 3 and Audio._cryParams.pitch == 15800, "table-form mode")
|
|
check(Audio._duck < 1, "non-doubles ducks BGM")
|
|
check(sources[#sources].sd.ch == 2, "table-form pan")
|
|
Audio._cryClock = Audio._cryUntil
|
|
check(Audio.isCryFinished(), "cry finishes on the rendered length")
|
|
Audio.stopCry()
|
|
Audio._pack = nil
|
|
Audio._ready = false
|
|
end
|
|
|
|
do
|
|
sources = {}
|
|
local L, R = {}, {}
|
|
for i = 1, 100 do L[i] = 0.5; R[i] = 0.5 end
|
|
local sd = Audio._buildSeSoundData(L, R, 1, 0, false)
|
|
local old = new_source(sd)
|
|
old:play()
|
|
old.pos = 40
|
|
Audio._seSources = { old }
|
|
Audio._seByPlayer = { [1] = old }
|
|
Audio._seMeta = { [old] = { id = 5, player = 1, rawL = L, rawR = R, master = 1, pan = 0, mono = false, loop = false } }
|
|
Audio.setSePan(63)
|
|
local src = Audio._seByPlayer[1]
|
|
check(src ~= old and not old.playing and src.playing, "setSePan swaps the playing SE")
|
|
check(src.pos == 40, "setSePan keeps playback position")
|
|
check(Audio._seSources[1] == src and Audio._seMeta[src].pan == 63, "setSePan updates bookkeeping")
|
|
check(near(src.sd:getSample(10, 1), 0.5 / 64) and near(src.sd:getSample(10, 2), 0.5), "pan 63 attenuates left")
|
|
Audio.setSePan(-64)
|
|
src = Audio._seByPlayer[1]
|
|
check(near(src.sd:getSample(10, 1), 0.5) and near(src.sd:getSample(10, 2), 0), "pan -64 silences right")
|
|
check(Audio._seMeta[src].pan == -64, "pan recorded")
|
|
Audio._seSources, Audio._seByPlayer, Audio._seMeta = {}, {}, {}
|
|
end
|
|
|
|
print(string.format("game3_cry_modes_test: %d passed, %d failed", passed, failed))
|
|
if failed > 0 then os.exit(1) end
|