summaryrefslogtreecommitdiff
path: root/tools/mq_editor
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-18 12:13:09 +0100
committerskal <pascal.massimino@gmail.com>2026-02-18 12:13:09 +0100
commit362f862da4fb5d9c666c8ca7b0dc329d4b8d1f7e (patch)
tree9c8c3e7ce50c0bbc49b391aeac527ea0e5cec53e /tools/mq_editor
parentce218b7459314dc9b7a5faf26139b8954a417b97 (diff)
feat(mq_editor): global r/gain overrides for Resonator (all) mode
Add r (pole) and gain sliders with force r/gain checkbox in Synthesis panel, visible when Resonator (all) is active. Restrict r range to [0.75, 0.9999] for both global and per-partial sliders. handoff(Claude): global resonator r/gain override controls added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'tools/mq_editor')
-rw-r--r--tools/mq_editor/editor.js2
-rw-r--r--tools/mq_editor/index.html30
-rw-r--r--tools/mq_editor/mq_synth.js6
3 files changed, 34 insertions, 4 deletions
diff --git a/tools/mq_editor/editor.js b/tools/mq_editor/editor.js
index f57e177..97d8a7a 100644
--- a/tools/mq_editor/editor.js
+++ b/tools/mq_editor/editor.js
@@ -219,7 +219,7 @@ class PartialEditor {
const resObj = partial.resonator || {};
const resParams = [
- { key: 'r', label: 'r (pole)', step: '0.001', min: '0', max: '0.9999',
+ { key: 'r', label: 'r (pole)', step: '0.001', min: '0.75', max: '0.9999',
title: 'Pole radius. r→1 = narrow bandwidth / long ring. r→0 = wide / fast decay.' },
{ key: 'gainComp', label: 'gain', step: '0.01', min: '0', max: '100',
title: 'Output gain multiplier (gainNorm=√(1-r²) normalises power; use this to trim level).' },
diff --git a/tools/mq_editor/index.html b/tools/mq_editor/index.html
index b37eaaf..c663c69 100644
--- a/tools/mq_editor/index.html
+++ b/tools/mq_editor/index.html
@@ -349,6 +349,19 @@
<label><input type="checkbox" id="disableJitter"> Disable jitter</label>
<label><input type="checkbox" id="disableSpread"> Disable spread</label>
<label title="Test mode: force resonator synthesis for all partials (ignores per-partial mode setting)"><input type="checkbox" id="forceResonator"> Resonator (all)</label>
+ <div id="globalResParams" style="display:none;margin-top:4px;padding:4px 0 2px 12px;border-left:2px solid #555;">
+ <label style="display:flex;align-items:center;gap:6px;" title="Global pole radius r in (0,1). Applied to all partials in resonator mode.">
+ r (pole)
+ <input type="range" id="globalR" min="0.75" max="0.9999" step="0.0001" value="0.995" style="flex:1;min-width:0;">
+ <span id="globalRVal" style="width:44px;text-align:right;">0.9950</span>
+ </label>
+ <label style="display:flex;align-items:center;gap:6px;" title="Global gain compensation applied to all partials in resonator mode.">
+ gain
+ <input type="range" id="globalGain" min="0.0" max="4.0" step="0.01" value="1.0" style="flex:1;min-width:0;">
+ <span id="globalGainVal" style="width:44px;text-align:right;">1.00</span>
+ </label>
+ <label title="Override per-partial r/gain with global values during playback"><input type="checkbox" id="forceRGain"> force r/gain</label>
+ </div>
<div style="margin-top:6px;">
<label style="display:flex;align-items:center;gap:6px;" title="LP filter coefficient k1 in (0,1]. 1.0 = bypass.">
LP k1
@@ -403,6 +416,17 @@
const f = k2ToHz(k, getSR());
document.getElementById('hpK2Val').textContent = k >= 1.0 ? 'bypass' : fmtHz(f);
});
+
+ // Show/hide global resonator params when forceResonator toggled
+ document.getElementById('forceResonator').addEventListener('change', function() {
+ document.getElementById('globalResParams').style.display = this.checked ? '' : 'none';
+ });
+ document.getElementById('globalR').addEventListener('input', function() {
+ document.getElementById('globalRVal').textContent = parseFloat(this.value).toFixed(4);
+ });
+ document.getElementById('globalGain').addEventListener('input', function() {
+ document.getElementById('globalGainVal').textContent = parseFloat(this.value).toFixed(2);
+ });
let audioBuffer = null;
let viewer = null;
let audioContext = null;
@@ -676,8 +700,12 @@
const hpK2Raw = parseFloat(document.getElementById('hpK2').value);
const k1 = lpK1Raw < 1.0 ? lpK1Raw : null;
const k2 = hpK2Raw < 1.0 ? hpK2Raw : null;
+ const forceRGain = forceResonator && document.getElementById('forceRGain').checked;
+ const globalR = parseFloat(document.getElementById('globalR').value);
+ const globalGain = parseFloat(document.getElementById('globalGain').value);
const pcm = synthesizeMQ(partialsToUse, audioBuffer.sampleRate, audioBuffer.duration,
- integratePhase, {disableJitter, disableSpread, forceResonator, k1, k2});
+ integratePhase, {disableJitter, disableSpread, forceResonator,
+ forceRGain, globalR, globalGain, k1, k2});
if (viewer) {
viewer.setSynthStftCache(new STFTCache(pcm, audioBuffer.sampleRate, fftSize, parseInt(hopSize.value)));
diff --git a/tools/mq_editor/mq_synth.js b/tools/mq_editor/mq_synth.js
index d85c890..2d4cf1b 100644
--- a/tools/mq_editor/mq_synth.js
+++ b/tools/mq_editor/mq_synth.js
@@ -56,8 +56,10 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
// r controls pole radius (bandwidth): r→1 = narrow, r→0 = wide.
// gainNorm = sqrt(1 - r²) normalises steady-state output power to ~A.
const res = partial.resonator || {};
- const r = res.r != null ? Math.min(0.9999, Math.max(0, res.r)) : 0.995;
- const gainComp = res.gainComp != null ? res.gainComp : 1.0;
+ const r = options.forceRGain ? Math.min(0.9999, Math.max(0, options.globalR))
+ : (res.r != null ? Math.min(0.9999, Math.max(0, res.r)) : 0.995);
+ const gainComp = options.forceRGain ? options.globalGain
+ : (res.gainComp != null ? res.gainComp : 1.0);
const gainNorm = Math.sqrt(Math.max(0, 1.0 - r * r));
configs.push({
mode: 'resonator',