From 789c78f1e72d4447ddf6351696d60c62aeacc4da Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 3 May 2026 16:50:24 +0900 Subject: [PATCH] taud: more fixes --- assets/disk0/tvdos/bin/taut.js | 6 +++--- it2taud.py | 5 +++-- mod2taud.py | 6 ++++-- s3m2taud.py | 7 ++++--- terranmon.txt | 2 +- .../src/net/torvald/tsvm/peripheral/AudioAdapter.kt | 10 ++++++---- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/assets/disk0/tvdos/bin/taut.js b/assets/disk0/tvdos/bin/taut.js index c8bfa3b..26c93d2 100644 --- a/assets/disk0/tvdos/bin/taut.js +++ b/assets/disk0/tvdos/bin/taut.js @@ -856,8 +856,8 @@ function drawControlHint() { ['sep'], ['Sp','Edit'], ['sep'], + ['n','Solo'], ['m','Mute'], - ['s','Solo'], ['sep'], ['Tab','Panel'] // ['sep'], @@ -1397,7 +1397,7 @@ function timelineInput(wo, event) { drawVoiceHeaders(); drawSeparators(separatorStyle); drawAlwaysOnElems(); drawVoiceDetail() } else if (keyJustHit && !shiftDown && event.includes(keys.M)) { toggleMute(cursorVox) } - else if (keyJustHit && !shiftDown && event.includes(keys.S)) { toggleSolo(cursorVox) } + else if (keyJustHit && !shiftDown && event.includes(keys.N)) { toggleSolo(cursorVox) } return } @@ -1434,7 +1434,7 @@ function timelineInput(wo, event) { } if (keyJustHit && !shiftDown && event.includes(keys.M)) { toggleMute(cursorVox); return } - if (keyJustHit && !shiftDown && event.includes(keys.S)) { toggleSolo(cursorVox); return } + if (keyJustHit && !shiftDown && event.includes(keys.N)) { toggleSolo(cursorVox); return } if (keysym === "") { cursorRow -= moveDelta; rowMove = true } else if (keysym === "") { cursorRow += moveDelta; rowMove = true } diff --git a/it2taud.py b/it2taud.py index c8312b0..cf93260 100644 --- a/it2taud.py +++ b/it2taud.py @@ -1254,8 +1254,9 @@ def build_sample_inst_bin_it(samples_or_proxy: list, if vol_env: _write_env(inst_bin, base + 21, vol_env) else: - # Single-point: hold at sample default volume. - inst_bin[base + 21] = min(getattr(s, 'vol', 63), 63) + # Single-point envelope held at full-scale; the per-sample level is + # carried by IGV (byte 171), so the envelope must be a unit multiplier. + inst_bin[base + 21] = 63 inst_bin[base + 22] = 0 # Force engine to use this single point. struct.pack_into(' tuple: le = min(s.loop_end, 65535) loop_mode = 1 if (s.flags & 1) else 0 flags_byte = loop_mode & 0x3 - env_vol = min(s.volume, 63) + # Envelope first point is full-scale; per-sample level is carried by + # IGV (byte 171) so the envelope must contribute a unit multiplier. + env_vol = 63 vol_env_flags = 0x0020 # use-envelope bit base = taud_idx * 192 @@ -763,7 +765,7 @@ def assemble_taud(mod: dict) -> bytes: pat_bin_comp_size=len(pat_comp), cue_sheet_comp_size=len(cue_comp), global_vol=0xFF, - mixing_vol=0x7F, + mixing_vol=180, ) assert len(song_table) == TAUD_SONG_ENTRY diff --git a/s3m2taud.py b/s3m2taud.py index 6177b14..9a48d69 100644 --- a/s3m2taud.py +++ b/s3m2taud.py @@ -495,8 +495,9 @@ def build_sample_inst_bin(instruments: list) -> tuple: loop_mode = 1 if (inst.flags & 1) else 0 flags_byte = loop_mode & 0x3 # 0b 0000 00pp - # Volume envelope: hold at instrument volume (clamped to 0x3F). - env_vol = min(inst.volume, 63) + # Volume envelope first point is full-scale; per-sample level is carried + # by IGV (byte 171) so the envelope contributes a unit multiplier. + env_vol = 63 # Vol env-flags: enable use-envelope bit (b=1) so engine reads the single point. vol_env_flags = 0x0020 # b=bit 5 @@ -820,7 +821,7 @@ def assemble_taud(h: S3MHeader, instruments: list, patterns: list) -> bytes: pat_bin_comp_size=len(pat_comp), cue_sheet_comp_size=len(cue_comp), global_vol=0xFF, - mixing_vol=0x7F, + mixing_vol=180, ) assert len(song_table) == TAUD_SONG_ENTRY diff --git a/terranmon.txt b/terranmon.txt index f691a26..f87c88f 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -2111,7 +2111,7 @@ TODO: [x] Fix 4THSYM.it filters [x] 4THSYM.it: pitchbend is wrong, some notes keep playing (loudly!) even if new notes are emitted [x] `*2taud.py`: some notes are emitted with wrong volume-set command. Tested with GSLINGER.mod: on order 0x15 channel 1, mod2taud.py emits volume 8 -- also many of the effects are dropped. Suggested solution: currently all converters write default volume to the voleff when original modules (.mod/.s3m/.it) specify nothing; we should also write nothing and let the engine resolve the value just like other trackers do (also we now have "Instrument Global Volume" on instrument definition unlike the other time). This bug may affecting other formats, not just mod2taud.py, as well - [ ] nearly_there_.mod: `C#5 SD300 / ... / C-5 SD200 / A#4 / G#4 (at tickspeed 4)`: every `C-5 SD200` (there are four occurances) gets skipped + [x] nearly_there_.mod: `C#5 SD300 / ... / C-5 SD200 / A#4 / G#4 (at tickspeed 4)`: every `C-5 SD200` (there are four occurances) gets skipped [ ] scale Oxxxx when samples get resampled [ ] implement bitcrusher and overdrive (eff sym '8' and '9') diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt index a80ae48..1a5f33a 100644 --- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt +++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt @@ -1469,8 +1469,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { if (i0 in ls until inst.sampleLoopEnd && inst.funkBit(i0 - ls)) b0 = b0 xor 0x80 if (i1 in ls until inst.sampleLoopEnd && inst.funkBit(i1 - ls)) b1 = b1 xor 0x80 } - val s0 = (b0 - 128) / 128.0 - val s1 = (b1 - 128) / 128.0 + val s0 = (b0 - 127.5) / 127.5 + val s1 = (b1 - 127.5) / 127.5 val sample = s0 + (s1 - s0) * frac if (voice.forward) { @@ -2371,6 +2371,8 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { } } + private fun Double.sqrt() = Math.sqrt(this) + internal fun generateTrackerAudio(playhead: Playhead): ByteArray? { val ts = playhead.trackerState ?: return null @@ -2408,7 +2410,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { val swingScale = 1.0 + voice.randomVolBias / 255.0 // Volume envelope is bypassed (treated as unity) when S $77 has disabled it. val effEnvVol = if (voice.volEnvOn) voice.envVolume else 1.0 - val vol = effEnvVol * voice.fadeoutVolume * voice.rowVolume / 63.0 * + val vol = effEnvVol.sqrt() * voice.fadeoutVolume * (voice.rowVolume / 63.0).sqrt() * swingScale * gvol * mvol * instGv * playhead.masterVolume / 255.0 val pan = if (voice.hasPanEnv && voice.panEnvOn) { val envPanRaw = (voice.envPan * 255.0).roundToInt().coerceIn(0, 255) @@ -2438,7 +2440,7 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) { val instGv = bgInst.instGlobalVolume / 255.0 val swingScale = 1.0 + bg.randomVolBias / 255.0 val effEnvVol = if (bg.volEnvOn) bg.envVolume else 1.0 - val vol = effEnvVol * bg.fadeoutVolume * bg.rowVolume / 63.0 * + val vol = effEnvVol.sqrt() * bg.fadeoutVolume * (bg.rowVolume / 63.0).sqrt() * swingScale * gvol * mvol * instGv * playhead.masterVolume / 255.0 val pan = if (bg.hasPanEnv && bg.panEnvOn) { val envPanRaw = (bg.envPan * 255.0).roundToInt().coerceIn(0, 255)