From baf8e51ffe60ff243cd08869a8d1840e0faa552b Mon Sep 17 00:00:00 2001 From: Ronny Trommer Date: Wed, 13 May 2026 00:38:28 +0200 Subject: [PATCH] feat: setStereoSeparation method on ChiptuneJsPlayer Add a main-thread entry point to update the stereo-separation render param on a running module, and to update the worklet's persisted this.config.stereoSeparation so subsequently-loaded modules inherit the value without a reload. Today the worklet's play() handler already reads this.config.stereoSeparation and stamps it via openmpt_module_set_render_param at module-create time, but consumers have no way to mutate the config from the main thread after the ChiptuneJsPlayer constructor's auto-config post. setStereoSeparation fills that gap by posting a {cmd:'setStereoSeparation', val} message that the worklet handles by mutating this.config and (when a module is loaded) calling set_render_param immediately. The worklet-side handler clamps incoming values to [0, 200] (the libopenmpt documented range; 100 = default, 0 = mono) and falls back to the default 100 on non-finite values, so callers can be liberal in what they send. Use case: an Amiga MOD player UI that exposes a "stereo separation" slider letting listeners gradually collapse the classic hard-panned field toward mono. Without this method the only way to change the value after page load is to recreate the ChiptuneJsPlayer (destroys the AudioContext + reloads the worklet, ~0.5s silence on every slider tick). The .min.js siblings are not regenerated in this PR -- I assume minify.js runs as part of a release cut. Happy to add them if preferred. Assisted-by: ClaudeCode:claude-opus-4-7 Signed-off-by: Ronny Trommer --- chiptune3.js | 5 +++++ chiptune3.worklet.js | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/chiptune3.js b/chiptune3.js index 36e317e..e0f8139 100644 --- a/chiptune3.js +++ b/chiptune3.js @@ -121,6 +121,11 @@ export class ChiptuneJsPlayer { setRepeatCount(val) { this.postMsg('repeatCount', val) } setPitch(val) { this.postMsg('setPitch', val) } setTempo(val) { this.postMsg('setTempo', val) } + // Live-update the stereo-separation render param (0..200 percent; + // 100 = libopenmpt default, 0 = mono). Also mutates the worklet's + // this.config.stereoSeparation so subsequently-loaded modules + // inherit the value without a reload. + setStereoSeparation(val) { this.postMsg('setStereoSeparation', val) } setPos(val) { this.postMsg('setPos', val) } setOrderRow(o,r) { this.postMsg('setOrderRow', {o:o,r:r}) } setVol(val) { this.gain.gain.value = val } diff --git a/chiptune3.worklet.js b/chiptune3.worklet.js index 285b067..c04b48f 100644 --- a/chiptune3.worklet.js +++ b/chiptune3.worklet.js @@ -139,6 +139,15 @@ class MPT extends AudioWorkletProcessor { if (!libopenmpt.stackSave || !this.modulePtr) return libopenmpt._openmpt_module_ctl_set(this.modulePtr, asciiToStack('play.tempo_factor'), asciiToStack(v.toString())) break + case 'setStereoSeparation': { + // Clamp to [0, 200] (libopenmpt's documented range) and + // fall back to the default 100 on non-finite values. + const n = Number.isFinite(v) ? Math.max(0, Math.min(200, Math.trunc(v))) : 100 + this.config.stereoSeparation = n + if (!this.modulePtr) break + libopenmpt._openmpt_module_set_render_param(this.modulePtr, OPENMPT_MODULE_RENDER_STEREOSEPARATION_PERCENT, n) + break + } case 'selectSubsong': if (!this.modulePtr) return libopenmpt._openmpt_module_select_subsong(this.modulePtr, v)