Files
enshrouded-ru-voice/ensvoice/qoa.py
T

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)