mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-08 06:14:04 +09:00
Taud: 8 MB sample rom/it and xm resampling too-long samples
This commit is contained in:
73
it2taud.py
73
it2taud.py
@@ -41,7 +41,7 @@ import sys
|
||||
from taud_common import (
|
||||
set_verbose, vprint,
|
||||
TAUD_MAGIC, TAUD_VERSION, TAUD_HEADER_SIZE, TAUD_SONG_ENTRY,
|
||||
SAMPLEBIN_SIZE, INSTBIN_SIZE, SAMPLEINST_SIZE,
|
||||
SAMPLEBIN_SIZE, INSTBIN_SIZE, SAMPLEINST_SIZE, SAMPLE_LEN_LIMIT,
|
||||
PATTERN_ROWS, PATTERN_BYTES, NUM_PATTERNS_MAX, NUM_CUES, CUE_SIZE, NUM_VOICES,
|
||||
NOTE_NOP, NOTE_KEYOFF, NOTE_CUT, TAUD_C4,
|
||||
TOP_NONE, TOP_A, TOP_B, TOP_C, TOP_D, TOP_E, TOP_F, TOP_G, TOP_H, TOP_I,
|
||||
@@ -51,7 +51,8 @@ from taud_common import (
|
||||
EFF_K, EFF_L, EFF_M, EFF_N, EFF_O, EFF_P, EFF_Q, EFF_R, EFF_S, EFF_T,
|
||||
EFF_U, EFF_V, EFF_W, EFF_X, EFF_Y, EFF_Z,
|
||||
J_SEMI_TABLE,
|
||||
d_arg_to_col, resample_linear, rescale_offset_effects, encode_cue, deduplicate_patterns,
|
||||
d_arg_to_col, resample_linear, rescale_offset_effects_per_slot,
|
||||
encode_cue, deduplicate_patterns,
|
||||
normalise_sample, encode_song_entry, nearest_minifloat, compress_blob,
|
||||
CUE_INST_NOP, CUE_INST_HALT, CUE_INST_LEN, cue_instruction_len,
|
||||
)
|
||||
@@ -1090,25 +1091,60 @@ def build_sample_inst_bin_it(samples_or_proxy: list,
|
||||
sample_detune, nna, dct, dca.
|
||||
All optional; missing keys default to neutral values.
|
||||
|
||||
Returns (bin_bytes[SAMPLEINST_SIZE], offsets_dict).
|
||||
Returns (bin_bytes[SAMPLEINST_SIZE], offsets_dict, slot_ratios) where
|
||||
slot_ratios maps Taud slot index → effective TOP_O scale (combined
|
||||
global × per-sample resample ratio).
|
||||
"""
|
||||
pcm_list = [(i, s) for i, s in enumerate(samples_or_proxy)
|
||||
if s is not None and s.sample_data]
|
||||
|
||||
def _scale_sample(s, r):
|
||||
s.sample_data = resample_linear(s.sample_data, r)
|
||||
s.length = len(s.sample_data)
|
||||
s.loop_beg = max(0, int(s.loop_beg * r))
|
||||
s.loop_end = max(0, min(int(s.loop_end * r), s.length))
|
||||
s.sus_beg = max(0, int(s.sus_beg * r))
|
||||
s.sus_end = max(0, min(int(s.sus_end * r), s.length))
|
||||
s.c5_speed = max(1, int(s.c5_speed * r))
|
||||
|
||||
# ── Pass 1: global pool-overflow resample (8 MB cap) ────────────────────
|
||||
total = sum(len(s.sample_data) for _, s in pcm_list)
|
||||
ratio = 1.0
|
||||
global_ratio = 1.0
|
||||
if total > SAMPLEBIN_SIZE:
|
||||
ratio = SAMPLEBIN_SIZE / total
|
||||
vprint(f" info: sample bin overflow ({total} bytes); resampling all by {ratio:.4f}")
|
||||
global_ratio = SAMPLEBIN_SIZE / total
|
||||
vprint(f" info: sample bin overflow ({total} bytes); resampling all by {global_ratio:.4f}")
|
||||
seen_g = set()
|
||||
for _, s in pcm_list:
|
||||
new_data = resample_linear(s.sample_data, ratio)
|
||||
s.sample_data = new_data
|
||||
s.length = len(new_data)
|
||||
s.loop_beg = max(0, int(s.loop_beg * ratio))
|
||||
s.loop_end = max(0, min(int(s.loop_end * ratio), s.length))
|
||||
s.sus_beg = max(0, int(s.sus_beg * ratio))
|
||||
s.sus_end = max(0, min(int(s.sus_end * ratio), s.length))
|
||||
s.c5_speed = max(1, int(s.c5_speed * ratio))
|
||||
if id(s) in seen_g:
|
||||
continue
|
||||
seen_g.add(id(s))
|
||||
_scale_sample(s, global_ratio)
|
||||
|
||||
# ── Pass 2: per-sample u16 cap (each sample must fit in 65535 bytes) ────
|
||||
# The Taud instrument record stores the sample length as u16, and TOP_O
|
||||
# offsets address up to 0xFF00 bytes — anything longer would silently
|
||||
# truncate at load time and over-shoot O-jumps. Resample only the
|
||||
# over-long samples and remember each one's individual ratio so the
|
||||
# caller can rescale TOP_O args per channel rather than globally.
|
||||
per_sample_ratio = {} # id(s) → per-sample ratio (after global)
|
||||
seen_p = set()
|
||||
for _, s in pcm_list:
|
||||
if id(s) in seen_p:
|
||||
continue
|
||||
seen_p.add(id(s))
|
||||
if len(s.sample_data) > SAMPLE_LEN_LIMIT:
|
||||
r = SAMPLE_LEN_LIMIT / len(s.sample_data)
|
||||
vprint(f" info: '{s.name}' exceeds {SAMPLE_LEN_LIMIT}-byte cap "
|
||||
f"({len(s.sample_data)}); resampling by {r:.4f}")
|
||||
_scale_sample(s, r)
|
||||
per_sample_ratio[id(s)] = r
|
||||
|
||||
# Effective slot → ratio for TOP_O rescaling. Slots sharing a sample
|
||||
# object (IT use_instruments mode) get the same ratio.
|
||||
slot_ratios = {}
|
||||
for slot_idx, s in pcm_list:
|
||||
slot_ratios[slot_idx] = global_ratio * per_sample_ratio.get(id(s), 1.0)
|
||||
ratio = slot_ratios
|
||||
|
||||
sample_bin = bytearray(SAMPLEBIN_SIZE)
|
||||
offsets = {}
|
||||
@@ -1719,8 +1755,13 @@ def assemble_taud(h: ITHeader, samples: list, instruments: list,
|
||||
pat_bin += build_pattern_it(cg, ch, default_pans[vi], inst_vols,
|
||||
amiga_mode=not h.linear_slides)
|
||||
|
||||
# Rescale TOP_O sample-offset args if samples were globally downsampled.
|
||||
pat_bin = rescale_offset_effects(bytes(pat_bin), sample_ratio)
|
||||
# Rescale TOP_O sample-offset args per channel using the active slot's
|
||||
# ratio (combined global + per-sample). Walks pat_bin in cue-major /
|
||||
# channel-minor order, tracking the most recent inst byte seen on each
|
||||
# channel — must run before deduplication so the channel state stays
|
||||
# linear.
|
||||
pat_bin = rescale_offset_effects_per_slot(
|
||||
bytes(pat_bin), len(taud_cue_list), C, sample_ratio)
|
||||
|
||||
orig_count = len(taud_cue_list) * C
|
||||
pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(pat_bin, orig_count)
|
||||
|
||||
Reference in New Issue
Block a user