mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-08 14:24:05 +09:00
2taud.py: fix: stereo samples not converting correctly
This commit is contained in:
29
it2taud.py
29
it2taud.py
@@ -294,11 +294,11 @@ def _it214_decompress_block(payload: bytes, num_samples: int,
|
|||||||
|
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def it214_decompress(blob: bytes, smp_offset: int, num_samples: int,
|
def _it214_decompress_channel(blob: bytes, pos: int, num_samples: int,
|
||||||
is_16bit: bool, is_it215: bool) -> bytes:
|
is_16bit: bool, is_it215: bool) -> tuple:
|
||||||
"""Decode IT2.14/IT2.15 compressed sample data. Returns raw PCM bytes (signed)."""
|
"""Decode one channel of IT2.14/IT2.15 compressed data. Returns
|
||||||
|
(raw PCM bytes, next position after consumed blocks)."""
|
||||||
block_size = 0x4000 if is_16bit else 0x8000
|
block_size = 0x4000 if is_16bit else 0x8000
|
||||||
pos = smp_offset
|
|
||||||
out_samples = []
|
out_samples = []
|
||||||
|
|
||||||
while len(out_samples) < num_samples:
|
while len(out_samples) < num_samples:
|
||||||
@@ -318,9 +318,24 @@ def it214_decompress(blob: bytes, smp_offset: int, num_samples: int,
|
|||||||
result = bytearray(len(out_samples) * 2)
|
result = bytearray(len(out_samples) * 2)
|
||||||
for i, s in enumerate(out_samples):
|
for i, s in enumerate(out_samples):
|
||||||
struct.pack_into('<h', result, i * 2, max(-32768, min(32767, s)))
|
struct.pack_into('<h', result, i * 2, max(-32768, min(32767, s)))
|
||||||
return bytes(result)
|
return bytes(result), pos
|
||||||
else:
|
else:
|
||||||
return bytes(s & 0xFF for s in out_samples)
|
return bytes(s & 0xFF for s in out_samples), pos
|
||||||
|
|
||||||
|
|
||||||
|
def it214_decompress(blob: bytes, smp_offset: int, num_samples: int,
|
||||||
|
is_16bit: bool, is_it215: bool,
|
||||||
|
is_stereo: bool = False) -> bytes:
|
||||||
|
"""Decode IT2.14/IT2.15 compressed sample data. Returns raw PCM bytes
|
||||||
|
(signed). For stereo samples, returns the left channel block followed
|
||||||
|
by the right channel block (matching IT's on-disk SF_SS layout)."""
|
||||||
|
left, pos = _it214_decompress_channel(blob, smp_offset, num_samples,
|
||||||
|
is_16bit, is_it215)
|
||||||
|
if not is_stereo:
|
||||||
|
return left
|
||||||
|
right, _ = _it214_decompress_channel(blob, pos, num_samples,
|
||||||
|
is_16bit, is_it215)
|
||||||
|
return left + right
|
||||||
|
|
||||||
|
|
||||||
# ── IT sample parser ──────────────────────────────────────────────────────────
|
# ── IT sample parser ──────────────────────────────────────────────────────────
|
||||||
@@ -384,7 +399,7 @@ def parse_samples(data: bytes, h: ITHeader, decompress: bool) -> list:
|
|||||||
try:
|
try:
|
||||||
is_it215 = bool(s.cvt & 0x04)
|
is_it215 = bool(s.cvt & 0x04)
|
||||||
raw = it214_decompress(data, s.smp_point, s.length,
|
raw = it214_decompress(data, s.smp_point, s.length,
|
||||||
s.is_16bit, is_it215)
|
s.is_16bit, is_it215, s.is_stereo)
|
||||||
s.sample_data = normalise_sample(raw, True,
|
s.sample_data = normalise_sample(raw, True,
|
||||||
s.is_16bit, s.is_stereo, s.name)
|
s.is_16bit, s.is_stereo, s.name)
|
||||||
s.length = len(s.sample_data)
|
s.length = len(s.sample_data)
|
||||||
|
|||||||
@@ -592,7 +592,7 @@ def build_sample_inst_bin(samples: list) -> tuple:
|
|||||||
# PT hard-pans channels in LRRL order: 0=L 1=R 2=R 3=L (and tile for >4).
|
# PT hard-pans channels in LRRL order: 0=L 1=R 2=R 3=L (and tile for >4).
|
||||||
def _default_channel_pan(ch_idx: int) -> int:
|
def _default_channel_pan(ch_idx: int) -> int:
|
||||||
side = (ch_idx % 4)
|
side = (ch_idx % 4)
|
||||||
return 16 if side in (0, 3) else 47
|
return 8 if side in (0, 3) else 55
|
||||||
|
|
||||||
|
|
||||||
def build_pattern(grid: list, ch_idx: int, default_pan: int,
|
def build_pattern(grid: list, ch_idx: int, default_pan: int,
|
||||||
|
|||||||
@@ -614,31 +614,44 @@ def build_project_data(*, project_name: str = '',
|
|||||||
|
|
||||||
def normalise_sample(raw: bytes, signed: bool, is_16bit: bool,
|
def normalise_sample(raw: bytes, signed: bool, is_16bit: bool,
|
||||||
is_stereo: bool, name: str) -> bytes:
|
is_stereo: bool, name: str) -> bytes:
|
||||||
"""Return unsigned 8-bit mono sample bytes, downmixing/depthing as needed."""
|
"""Return unsigned 8-bit mono sample bytes, downmixing/depthing as needed.
|
||||||
|
|
||||||
|
Stereo samples are stored as a split (non-interleaved) layout — the full
|
||||||
|
left channel block followed by the full right channel block — matching the
|
||||||
|
on-disk format used by IT, S3M, and XM (Schism's SF_SS).
|
||||||
|
"""
|
||||||
out = []
|
out = []
|
||||||
stride = (2 if is_16bit else 1) * (2 if is_stereo else 1)
|
bps = 2 if is_16bit else 1
|
||||||
i = 0
|
chans = 2 if is_stereo else 1
|
||||||
while i + stride <= len(raw):
|
n_frames = len(raw) // (bps * chans)
|
||||||
|
chan_bytes = n_frames * bps
|
||||||
|
|
||||||
|
for i in range(n_frames):
|
||||||
if is_16bit:
|
if is_16bit:
|
||||||
if is_stereo:
|
if is_stereo:
|
||||||
l16 = struct.unpack_from('<h', raw, i)[0]
|
l16 = struct.unpack_from('<h', raw, i*2)[0]
|
||||||
r16 = struct.unpack_from('<h', raw, i+2)[0]
|
r16 = struct.unpack_from('<h', raw, chan_bytes + i*2)[0]
|
||||||
s = (l16 + r16) >> 1
|
s = (l16 + r16) >> 1
|
||||||
else:
|
else:
|
||||||
s = struct.unpack_from('<h', raw, i)[0]
|
s = struct.unpack_from('<h', raw, i*2)[0]
|
||||||
v = (s >> 8) + 128
|
v = (s >> 8) + 128
|
||||||
else:
|
else:
|
||||||
if is_stereo:
|
if is_stereo:
|
||||||
l8 = raw[i]; r8 = raw[i+1]
|
l8 = raw[i]
|
||||||
raw_s = (l8 + r8) // 2
|
r8 = raw[chan_bytes + i]
|
||||||
|
if signed:
|
||||||
|
l_s = l8 - 256 if l8 >= 0x80 else l8
|
||||||
|
r_s = r8 - 256 if r8 >= 0x80 else r8
|
||||||
|
v = ((l_s + r_s) >> 1) + 128
|
||||||
|
else:
|
||||||
|
v = (l8 + r8) >> 1
|
||||||
else:
|
else:
|
||||||
raw_s = raw[i]
|
raw_s = raw[i]
|
||||||
if signed:
|
if signed:
|
||||||
v = (raw_s ^ 0x80) & 0xFF
|
v = (raw_s ^ 0x80) & 0xFF
|
||||||
else:
|
else:
|
||||||
v = raw_s
|
v = raw_s
|
||||||
out.append(v & 0xFF)
|
out.append(v & 0xFF)
|
||||||
i += stride
|
|
||||||
if is_16bit or is_stereo:
|
if is_16bit or is_stereo:
|
||||||
vprint(f" info: '{name}' converted to unsigned 8-bit mono ({len(out)} samples)")
|
vprint(f" info: '{name}' converted to unsigned 8-bit mono ({len(out)} samples)")
|
||||||
return bytes(out)
|
return bytes(out)
|
||||||
|
|||||||
Reference in New Issue
Block a user