summaryrefslogtreecommitdiff
path: root/tools/mq_editor/editor.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mq_editor/editor.js')
-rw-r--r--tools/mq_editor/editor.js62
1 files changed, 31 insertions, 31 deletions
diff --git a/tools/mq_editor/editor.js b/tools/mq_editor/editor.js
index 0854ec0..b07664e 100644
--- a/tools/mq_editor/editor.js
+++ b/tools/mq_editor/editor.js
@@ -25,8 +25,7 @@ class PartialEditor {
// Private state
this._selectedIndex = -1;
- this._dragPointIndex = -1;
- this._dragCompanionOff = null; // {dt, dv} offset of companion handle relative to anchor
+ this._dragPointIndex = -1;
this._amp = { tMin: 0, tMax: 1, ampTop: 1 };
this._setupButtons();
@@ -125,8 +124,8 @@ class PartialEditor {
const grid = this._synthGrid;
grid.innerHTML = '';
- const repDefaults = { decay_alpha: 0.1, jitter: 0.05, spread_above: 0.02, spread_below: 0.02 };
- const resDefaults = { r: 0.995, gainComp: 1.0 };
+ const harmDefaults = { decay: 0.0, freq_mult: 2.0, jitter: 0.05, spread_above: 0.02, spread_below: 0.02 };
+ const resDefaults = { r: 0.995, gainComp: 1.0 };
const isResonator = !!(partial.resonator && partial.resonator.enabled);
@@ -157,16 +156,17 @@ class PartialEditor {
sinSection.style.cssText = 'display:contents;';
sinSection.dataset.section = 'sinusoid';
- const rep = partial.replicas || {};
+ const harm = partial.harmonics || {};
const sinParams = [
- { key: 'decay_alpha', label: 'decay', step: '0.001' },
+ { key: 'decay', label: 'h.decay', step: '0.01', max: '0.90' },
+ { key: 'freq_mult', label: 'h.freq', step: '0.01' },
{ key: 'jitter', label: 'jitter', step: '0.001' },
{ key: 'spread_above', label: 'spread ↑', step: '0.001' },
{ key: 'spread_below', label: 'spread ↓', step: '0.001' },
];
const sinInputs = {};
for (const p of sinParams) {
- const val = rep[p.key] != null ? rep[p.key] : repDefaults[p.key];
+ const val = harm[p.key] != null ? harm[p.key] : harmDefaults[p.key];
const lbl = document.createElement('span');
lbl.textContent = p.label;
const inp = document.createElement('input');
@@ -174,22 +174,25 @@ class PartialEditor {
inp.value = val.toFixed(3);
inp.step = p.step;
inp.min = '0';
+ if (p.max) inp.max = p.max;
inp.addEventListener('change', (e) => {
if (!this.partials) return;
- const v = parseFloat(e.target.value);
+ let v = parseFloat(e.target.value);
if (isNaN(v)) return;
- if (!this.partials[index].replicas) this.partials[index].replicas = { ...repDefaults };
- this.partials[index].replicas[p.key] = v;
+ if (p.max) v = Math.min(v, parseFloat(p.max));
+ if (!this.partials[index].harmonics) this.partials[index].harmonics = { ...harmDefaults };
+ this.partials[index].harmonics[p.key] = v;
if (this.viewer) this.viewer.render();
});
sinInputs[p.key] = inp;
const jog = this._makeJogSlider(inp, {
step: parseFloat(p.step),
+ max: p.max ? parseFloat(p.max) : undefined,
onUpdate: (newVal) => {
if (!this.partials || !this.partials[index]) return;
- if (!this.partials[index].replicas) this.partials[index].replicas = { ...repDefaults };
- this.partials[index].replicas[p.key] = newVal;
+ if (!this.partials[index].harmonics) this.partials[index].harmonics = { ...harmDefaults };
+ this.partials[index].harmonics[p.key] = newVal;
if (this.viewer) this.viewer.render();
}
});
@@ -214,9 +217,9 @@ class PartialEditor {
const sr = this.viewer ? this.viewer.audioBuffer.sampleRate : 44100;
const fs = sc ? sc.fftSize : 2048;
const {spread_above, spread_below} = autodetectSpread(p, sc, fs, sr);
- if (!p.replicas) p.replicas = { ...repDefaults };
- p.replicas.spread_above = spread_above;
- p.replicas.spread_below = spread_below;
+ if (!p.harmonics) p.harmonics = { ...harmDefaults };
+ p.harmonics.spread_above = spread_above;
+ p.harmonics.spread_below = spread_below;
sinInputs['spread_above'].value = spread_above.toFixed(4);
sinInputs['spread_below'].value = spread_below.toFixed(4);
});
@@ -323,10 +326,10 @@ class PartialEditor {
if (!dragging) return;
const dx = e.clientX - startX;
const half = slider.offsetWidth / 2;
- const clamped = Math.max(-half, Math.min(half, dx));
+ const clamped = clamp(dx, -half, half);
thumb.style.transition = 'none';
thumb.style.left = `calc(50% - 3px + ${clamped}px)`;
- const newVal = Math.max(min, Math.min(max, startVal + dx * sensitivity));
+ const newVal = clamp(startVal + dx * sensitivity, min, max);
inp.value = newVal.toFixed(decimals);
onUpdate(newVal);
};
@@ -496,13 +499,8 @@ class PartialEditor {
for (let i = 0; i < 4; ++i) {
if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['a' + i]) - y) <= 8) {
if (this.onBeforeChange) this.onBeforeChange();
- this._dragPointIndex = i;
- this._dragCompanionOff = null;
- if (i === 0)
- this._dragCompanionOff = { da: curve.a1 - curve.a0 };
- else if (i === 3)
- this._dragCompanionOff = { da: curve.a2 - curve.a3 };
- canvas.style.cursor = 'grabbing';
+ this._dragPointIndex = i;
+ canvas.style.cursor = (i === 1 || i === 2) ? 'move' : 'ns-resize';
e.preventDefault();
return;
}
@@ -516,10 +514,11 @@ class PartialEditor {
const curve = this.partials[this._selectedIndex].freqCurve;
const i = this._dragPointIndex;
curve['a' + i] = Math.max(0, this._yToAmp(y));
- if (this._dragCompanionOff) {
- const off = this._dragCompanionOff;
- if (i === 0) { curve.a1 = curve.a0 + off.da; }
- else { curve.a2 = curve.a3 + off.da; }
+ // Inner control points are also horizontally draggable
+ if (i === 1) {
+ curve.t1 = clamp(this._xToT(x), curve.t0 + 1e-4, curve.t2 - 1e-4);
+ } else if (i === 2) {
+ curve.t2 = clamp(this._xToT(x), curve.t1 + 1e-4, curve.t3 - 1e-4);
}
this._renderAmpEditor();
if (this.viewer) this.viewer.render();
@@ -531,13 +530,14 @@ class PartialEditor {
if (this._selectedIndex >= 0 && this.partials) {
const curve = this.partials[this._selectedIndex]?.freqCurve;
if (curve) {
- let near = false;
+ let cursor = 'crosshair';
for (let i = 0; i < 4; ++i) {
if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['a' + i]) - y) <= 8) {
- near = true; break;
+ cursor = (i === 1 || i === 2) ? 'move' : 'ns-resize';
+ break;
}
}
- canvas.style.cursor = near ? 'grab' : 'crosshair';
+ canvas.style.cursor = cursor;
}
}
});