Files
llamamod/tools/asf2wav.py
T

74 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""Decodes Argonaut ASF music (v2.1, 4-bit ADPCM) like the game's asfcodec.adl.
Reimplemented from the disassembly of asfcodec.adl (routines 0x26e01969 / 0x26e01ca1):
Filter 0: s = s1 + (n << shift)
Filter 1: s = 2*s1 - s2 + (n << shift) (+ carry from 'adc', see below)
- no clipping; output is stored as 16 bit (overflow = wrap)
- within a block s1 carries on untruncated in the register,
at the start of a block it is read from the 16-bit buffer
- s2 is always read from the 16-bit buffer
Usage: asf2wav.py input.asf output.wav
"""
import array
import struct
import sys
import wave
M32 = 0xFFFFFFFF
def s32(x):
x &= M32
return x - (1 << 32) if x & 0x80000000 else x
def wrap16(x):
return ((x + 0x8000) & 0xFFFF) - 0x8000
def decode(data):
off = struct.unpack_from('<I', data, 12)[0]
nblocks, _, _, rate, _, flags = struct.unpack_from('<IIIHHI', data, off)
ch = 2 if flags & 2 else 1
p = off + 20
out = array.array('h', bytes(nblocks * 32 * ch * 2))
mem = [[0, 0] for _ in range(ch)] # stored 16-bit values: [second to last, last]
o = 0
for _ in range(nblocks):
for c in range(ch):
ctl = data[p]
blk = data[p + 1:p + 17]
p += 17
cl = (ctl >> 4) + 2
prev2, prev1 = mem[c]
eax = prev1 # block start: s1 from memory
k = o + c
for byte in blk:
for nib in (byte >> 4, byte & 15):
n = nib - 16 if nib & 8 else nib
edx = s32((n * 16) << cl)
if ctl & 4:
ebx = s32(eax << 7)
a = s32(-prev2 << 6)
carry = ((a & M32) + (edx & M32)) >> 32
eax = s32(a + edx + ebx + carry) >> 6
else:
eax = s32((eax << 6) + edx) >> 6
w = wrap16(eax)
out[k] = w
k += ch
prev2, prev1 = prev1, w
mem[c] = [prev2, prev1]
o += 32 * ch
return rate, ch, out
if __name__ == '__main__':
rate, ch, pcm = decode(open(sys.argv[1], 'rb').read())
with wave.open(sys.argv[2], 'wb') as w:
w.setnchannels(ch)
w.setsampwidth(2)
w.setframerate(rate)
w.writeframes(pcm.tobytes())