68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""Frameless QOA as Enshrouded stores voiceover (SoundResource format FramelessQOA).
|
|
|
|
QOA slices without file or frame headers: one little-endian u64 per 20 samples (4-bit scale factor,
|
|
20 x 3-bit residuals), mono 48 kHz. The LMS state starts at weights [0, 0, -8192, 16384], history 0,
|
|
and runs on through the whole stream.
|
|
"""
|
|
import struct
|
|
|
|
DQ = [0.75, -0.75, 2.5, -2.5, 4.5, -4.5, 7, -7]
|
|
SF = [round((s + 1) ** 2.75) for s in range(16)]
|
|
DEQ = [[(1 if sf * d >= 0 else -1) * int(abs(sf * d) + 0.5) for d in DQ] for sf in SF]
|
|
|
|
|
|
def _clamp(v):
|
|
return -32768 if v < -32768 else 32767 if v > 32767 else v
|
|
|
|
|
|
def decode(data: bytes, nsamples: int) -> list:
|
|
h = [0, 0, 0, 0]
|
|
w = [0, 0, -(1 << 13), 1 << 14]
|
|
out, p = [], 0
|
|
for _ in range((nsamples + 19) // 20):
|
|
if p + 8 > len(data):
|
|
break
|
|
sl, = struct.unpack_from("<Q", data, p)
|
|
p += 8
|
|
dq = DEQ[(sl >> 60) & 15]
|
|
for j in range(20):
|
|
rd = dq[(sl >> (57 - 3 * j)) & 7]
|
|
s = _clamp(((h[0] * w[0] + h[1] * w[1] + h[2] * w[2] + h[3] * w[3]) >> 13) + rd)
|
|
delta = rd >> 4
|
|
for k in range(4):
|
|
w[k] += -delta if h[k] < 0 else delta
|
|
h[0], h[1], h[2], h[3] = h[1], h[2], h[3], s
|
|
out.append(s)
|
|
return out[:nsamples]
|
|
|
|
|
|
def encode(samples: list) -> bytes:
|
|
"""mono int16 samples -> frameless QOA; per slice the scale factor with the least squared error."""
|
|
h = [0, 0, 0, 0]
|
|
w = [0, 0, -(1 << 13), 1 << 14]
|
|
out = bytearray()
|
|
pad = list(samples) + [0] * ((-len(samples)) % 20)
|
|
for si in range(0, len(pad), 20):
|
|
sl, best = pad[si:si + 20], None
|
|
for sf in range(16):
|
|
dq, hh, ww, err, bits = DEQ[sf], h[:], w[:], 0, sf
|
|
for x in sl:
|
|
pred = (hh[0] * ww[0] + hh[1] * ww[1] + hh[2] * ww[2] + hh[3] * ww[3]) >> 13
|
|
res = x - pred
|
|
q = min(range(8), key=lambda k: abs(res - dq[k]))
|
|
rd = dq[q]
|
|
s = _clamp(pred + rd)
|
|
err += (x - s) ** 2
|
|
if best and err >= best[0]:
|
|
break
|
|
de = rd >> 4
|
|
ww = [a - de if b < 0 else a + de for a, b in zip(ww, hh)]
|
|
hh = [hh[1], hh[2], hh[3], s]
|
|
bits = (bits << 3) | q
|
|
else:
|
|
if best is None or err < best[0]:
|
|
best = (err, bits, hh, ww)
|
|
_, bits, h, w = best
|
|
out += struct.pack("<Q", bits)
|
|
return bytes(out)
|