From 517d0ad9a70ab6a37a686d3787ca5c6ffd79d560 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Mon, 4 May 2026 02:14:19 +0900 Subject: [PATCH] taut.js: fxNames update --- assets/disk0/tvdos/bin/taut.js | 18 +++++++++--------- it2taud.py | 13 ++++++++----- mod2taud.py | 11 +++++++---- s3m2taud.py | 11 +++++++---- taud_common.py | 21 +++++++++++++++++++++ 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index b493301..befbc95 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -116,17 +116,17 @@ P:"UNIMPLEMENTED", // IT: panning slide. Use PanEff instead Q:"Retrigger ", R:"Tremolo ", S:"Special ", -S0:"UNIMPLEMENTED", // PT: Set audio filter. +S0:"UNIMPLEMENTED", // PT: Set audio filter S1:"Gliss. ctrl ", S2:"Sample tune ", S3:"Vibrato LFO ", S4:"Tremolo LFO ", S5:"Panbrello LFO", -S6:"UNIMPLEMENTED", // IT: Fine pattern delay. -S7:"UNIMPLEMENTED", // IT: misc. functions -S8:"Channel pan ", // Taud: 8-bit channel panning. -S9:"UNIMPLEMENTED", // IT: Sound control. -SA:"UNIMPLEMENTED", // SC3: Stereo control. IT: Sample offset high twobyte. +S6:"Fine delay ", +S7:"Note action ", +S8:"Channel pan ", // Taud: 8-bit channel panning +S9:"UNIMPLEMENTED", // IT: Sound control +SA:"UNIMPLEMENTED", // SC3: Stereo control. IT: Sample offset high twobyte (not applicable because Taud has 64k limit) SB:"Pattern loop ", SC:"Note cut ", SD:"Note delay ", @@ -135,10 +135,10 @@ SF:"Funk it ", T:"Tempo ", U:"Fine vibrato ", V:"Global volume", -W:"UNIMPLEMENTED", // IT: Global volume slide. -X:"UNIMPLEMENTED", // IT: 8-bit channel panning. Use PanEff or S80xx instead +W:"G.Vol Slide ", +X:"UNIMPLEMENTED", // IT: 8-bit channel panning. Use S80xx instead Y:"Panbrello ", -Z:"UNIMPLEMENTED", // IT: MIDI macro. +Z:"UNIMPLEMENTED", // IT: MIDI macro } const panFxNames = { 0:"Set to", diff --git a/it2taud.py b/it2taud.py index cf93260..9f1512c 100644 --- a/it2taud.py +++ b/it2taud.py @@ -52,7 +52,7 @@ 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, encode_cue, deduplicate_patterns, + d_arg_to_col, resample_linear, rescale_offset_effects, encode_cue, deduplicate_patterns, normalise_sample, encode_song_entry, ) @@ -1314,7 +1314,7 @@ def build_sample_inst_bin_it(samples_or_proxy: list, vprint(f" instrument[{taud_idx}] '{s.name}' ptr:{ptr} c5spd:{s.c5_speed}") - return bytes(sample_bin) + bytes(inst_bin), offsets + return bytes(sample_bin) + bytes(inst_bin), offsets, ratio # ── Pattern builder ─────────────────────────────────────────────────────────── @@ -1682,7 +1682,7 @@ def assemble_taud(h: ITHeader, samples: list, instruments: list, 'dct': inst.dct, 'dca': inst.dca, } - sampleinst_raw, _ = build_sample_inst_bin_it(proxy, instr_data_by_slot) + sampleinst_raw, _, sample_ratio = build_sample_inst_bin_it(proxy, instr_data_by_slot) else: # Samples referenced directly; proxy is samples list (0-based, slot 0 unused) proxy = [None] + list(samples) @@ -1691,7 +1691,7 @@ def assemble_taud(h: ITHeader, samples: list, instruments: list, for i, s in enumerate(samples) if s is not None } - sampleinst_raw, _ = build_sample_inst_bin_it(proxy) + sampleinst_raw, _, sample_ratio = build_sample_inst_bin_it(proxy) assert len(sampleinst_raw) == SAMPLEINST_SIZE @@ -1723,8 +1723,11 @@ 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) + orig_count = len(taud_cue_list) * C - pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(bytes(pat_bin), orig_count) + pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(pat_bin, orig_count) vprint(f" patterns: {orig_count} → {num_taud_pats} unique " f"({orig_count - num_taud_pats} deduplicated)") diff --git a/mod2taud.py b/mod2taud.py index 579f366..621da27 100644 --- a/mod2taud.py +++ b/mod2taud.py @@ -39,7 +39,7 @@ from taud_common import ( TOP_J, TOP_K, TOP_L, TOP_O, TOP_Q, TOP_R, TOP_S, TOP_T, TOP_U, TOP_V, TOP_Y, SEL_SET, SEL_UP, SEL_DOWN, SEL_FINE, J_SEMI_TABLE, - d_arg_to_col, resample_linear, encode_cue, deduplicate_patterns, + d_arg_to_col, resample_linear, rescale_offset_effects, encode_cue, deduplicate_patterns, encode_song_entry, ) @@ -546,7 +546,7 @@ def build_sample_inst_bin(samples: list) -> tuple: vprint(f" instrument[{taud_idx}] '{s.name}' ptr={ptr} c2spd={s.c2spd} " f"vol={s.volume} loop=({ls},{le},{'on' if loop_mode else 'off'})") - return bytes(sample_bin) + bytes(inst_bin), offsets + return bytes(sample_bin) + bytes(inst_bin), offsets, ratio # ── Pattern build ──────────────────────────────────────────────────────────── @@ -704,7 +704,7 @@ def assemble_taud(mod: dict) -> bytes: relocate_late_note_delays(patterns, order_list, n_channels, init_speed) vprint(" building sample/instrument bin…") - sampleinst_raw, _offsets = build_sample_inst_bin(samples) + sampleinst_raw, _offsets, sample_ratio = build_sample_inst_bin(samples) assert len(sampleinst_raw) == SAMPLEINST_SIZE compressed = gzip.compress(sampleinst_raw, compresslevel=9, mtime=0) @@ -742,9 +742,12 @@ def assemble_taud(mod: dict) -> bytes: pat_bin += build_pattern(grid, ch, default_pan, inst_vols) assert len(pat_bin) == n_patterns * n_channels * PATTERN_BYTES + # Rescale TOP_O sample-offset args if samples were globally downsampled. + pat_bin = rescale_offset_effects(bytes(pat_bin), sample_ratio) + vprint(" deduplicating patterns…") orig_count = n_patterns * n_channels - pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(bytes(pat_bin), orig_count) + pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(pat_bin, orig_count) vprint(f" patterns: {orig_count} → {num_taud_pats} unique " f"({orig_count - num_taud_pats} deduplicated)") diff --git a/s3m2taud.py b/s3m2taud.py index 9a48d69..67cf3b2 100644 --- a/s3m2taud.py +++ b/s3m2taud.py @@ -43,7 +43,7 @@ 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, encode_cue, deduplicate_patterns, + d_arg_to_col, resample_linear, rescale_offset_effects, encode_cue, deduplicate_patterns, normalise_sample, encode_song_entry, ) @@ -528,7 +528,7 @@ def build_sample_inst_bin(instruments: list) -> tuple: if inst.c2spd > 65535: vprint(f" warning: sampling rate of '{inst.name}' exceeds 65535 (got '{inst.c2spd}')") - return bytes(sample_bin) + bytes(inst_bin), offsets + return bytes(sample_bin) + bytes(inst_bin), offsets, ratio def _default_channel_pan(ch_setting: int) -> int: @@ -740,7 +740,7 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes: # Build sample+instrument bin vprint(" building sample/instrument bin…") - sampleinst_raw, _offsets = build_sample_inst_bin(instruments) + sampleinst_raw, _offsets, sample_ratio = build_sample_inst_bin(instruments) assert len(sampleinst_raw) == SAMPLEINST_SIZE # Compress @@ -787,10 +787,13 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes: inst_vols, amiga_mode=not h.linear_slides) assert len(pat_bin) == num_taud_pats * PATTERN_BYTES + # Rescale TOP_O sample-offset args if samples were globally downsampled. + pat_bin = rescale_offset_effects(bytes(pat_bin), sample_ratio) + # Deduplicate identical patterns vprint(" deduplicating patterns…") orig_count = num_taud_pats - pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(bytes(pat_bin), orig_count) + pat_bin, pat_remap, num_taud_pats = deduplicate_patterns(pat_bin, orig_count) vprint(f" patterns: {orig_count} → {num_taud_pats} unique ({orig_count - num_taud_pats} deduplicated)") # Cue sheet (using remapped pattern indices) diff --git a/taud_common.py b/taud_common.py index 975a236..f7ae0db 100644 --- a/taud_common.py +++ b/taud_common.py @@ -131,6 +131,27 @@ def resample_linear(data: bytes, ratio: float) -> bytes: return bytes(out) +def rescale_offset_effects(pat_bin: bytes, ratio: float) -> bytes: + """Scale TOP_O sample-offset args in raw pattern bytes by `ratio`. + + Each row is 8 bytes; byte 5 is the effect opcode, bytes 6-7 are the + little-endian 16-bit arg (= byte offset into the sample). When the + sample bin overflows and every sample is downsampled globally, the + offset commands must shrink the same amount or O-jumps land past + the new end of sample. + """ + if ratio == 1.0 or not pat_bin: + return pat_bin + out = bytearray(pat_bin) + for i in range(0, len(out) - 7, 8): + if out[i + 5] == TOP_O: + arg = out[i + 6] | (out[i + 7] << 8) + arg = max(0, min(0xFFFF, int(arg * ratio + 0.5))) + out[i + 6] = arg & 0xFF + out[i + 7] = (arg >> 8) & 0xFF + return bytes(out) + + def encode_cue(patterns12: list, instruction: int) -> bytearray: """Encode a 32-byte cue entry for up to 20 voices with 12-bit pattern numbers.""" pats = list(patterns12) + [0xFFF] * NUM_VOICES