From 887c2fbfbaa34a4b6e3adf355497b2fb05caa6fd Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 23 Apr 2026 13:35:38 +0900 Subject: [PATCH] s3m to taud fix (not emitting volcmd on note retrigger) --- s3m2taud.py | 20 +++++++++++++++++++- terranmon.txt | 6 +++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/s3m2taud.py b/s3m2taud.py index 2c6383d..b2714b0 100644 --- a/s3m2taud.py +++ b/s3m2taud.py @@ -660,7 +660,9 @@ def build_pattern(s3m_grid: list, ch_idx: int, default_pan: int, inst_vols = {} out = bytearray(PATTERN_BYTES) rows = s3m_grid[ch_idx] if ch_idx < len(s3m_grid) else [S3MRow()] * PATTERN_ROWS - last_inst = 0 # 1-based; tracks which instrument is loaded on this channel + last_inst = 0 # 1-based; tracks which instrument is loaded on this channel + last_note = S3M_NOTE_EMPTY # last raw S3M note byte that was a real pitch + last_vol = None # last SEL_SET volume value (0-63), for retrigger recall for r, row in enumerate(rows[:PATTERN_ROWS]): note = encode_note(row.note) inst = row.inst # S3M 1-based → Taud 1-based @@ -668,6 +670,12 @@ def build_pattern(s3m_grid: list, ch_idx: int, default_pan: int, if row.inst > 0: last_inst = row.inst + # ── Instrument-only retrigger ── + # Instrument-only row: recall the last volume without touching the note. + retrigger = (row.inst > 0 + and row.note == S3M_NOTE_EMPTY + and last_note not in (S3M_NOTE_EMPTY, S3M_NOTE_OFF)) + op, arg, vol_override, pan_override = encode_effect( row.effect, row.effect_arg, ch_idx, r) @@ -683,11 +691,21 @@ def build_pattern(s3m_grid: list, ch_idx: int, default_pan: int, # so prior channel-vol state doesn't bleed through. vol_sel = SEL_SET vol_value = inst_vols.get(last_inst, 0x3F) + elif retrigger and last_vol is not None: + # Instrument-only row: re-emit the last known volume so the sample + # restarts at the correct level without an explicit note trigger. + vol_sel, vol_value = SEL_SET, last_vol elif vol_override is not None: vol_sel, vol_value = vol_override else: vol_sel, vol_value = SEL_FINE, 0 # no-op fine slide + # Track note and volume for future retrigger lookups. + if row.note not in (S3M_NOTE_EMPTY, S3M_NOTE_OFF): + last_note = row.note + if vol_sel == SEL_SET: + last_vol = vol_value + # ── Pan column ── if pan_override is not None: pan_sel, pan_value = pan_override diff --git a/terranmon.txt b/terranmon.txt index b17dd1c..7d4dcd2 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -2210,9 +2210,9 @@ Rows of 16 bytes: Uint16 Number of patterns (0 is invalid. pattern bin length = numPats * 8 bytes) Uint8 Initial BPM (bias of -24. 0x00=24, 0xFF=279) Uint8 Initial Tickrate (0 is invalid) - Uint16 Current Tuning base note (0-4095), assuming octave 3. C3 (the default value) is 0x4000 - Float32 Frequency at the base note. Default (A440) is 261.6255653 - Byte[7] Reserved for future versions + Uint16 Current Tuning base note (1-4094), assuming octave 3. C3 (the default value) is 0x4000. If zero, assume the default value + Float32 Frequency at the base note. Default (A440) is 261.6255653. If zero, assume the default value + Byte[1] Reserved for future versions Taud device can queue up to 2 "playdata" in its buffer, which can be interpreted as a song.