summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tools/mq_editor/editor.js39
-rw-r--r--tools/mq_editor/mq_extract.js4
-rw-r--r--tools/mq_editor/mq_synth.js13
-rw-r--r--tools/mq_editor/utils.js13
4 files changed, 41 insertions, 28 deletions
diff --git a/tools/mq_editor/editor.js b/tools/mq_editor/editor.js
index 868d3d5..3c07877 100644
--- a/tools/mq_editor/editor.js
+++ b/tools/mq_editor/editor.js
@@ -86,11 +86,11 @@ class PartialEditor {
muteBtn.style.color = partial.muted ? '#fa4' : '';
this._buildCurveGrid(this._freqGrid, partial, 'freqCurve', 'f', index);
- this._buildCurveGrid(this._ampGrid, partial, 'ampCurve', 'a', index);
+ this._buildCurveGrid(this._ampGrid, partial, 'freqCurve', 'a', index, 'a');
this._buildSynthGrid(partial, index);
}
- _buildCurveGrid(grid, partial, curveKey, valueLabel, partialIndex) {
+ _buildCurveGrid(grid, partial, curveKey, valueLabel, partialIndex, valueKey = 'v') {
grid.innerHTML = '';
const curve = partial[curveKey];
if (!curve) { grid.style.color = '#444'; grid.textContent = 'none'; return; }
@@ -108,10 +108,10 @@ class PartialEditor {
const vInput = document.createElement('input');
vInput.type = 'number';
- vInput.value = curveKey === 'freqCurve' ? curve['v' + i].toFixed(2) : curve['v' + i].toFixed(4);
- vInput.step = curveKey === 'freqCurve' ? '1' : '0.0001';
+ vInput.value = valueKey === 'v' ? curve['v' + i].toFixed(2) : curve[valueKey + i].toFixed(4);
+ vInput.step = valueKey === 'v' ? '1' : '0.0001';
vInput.title = valueLabel + i;
- vInput.addEventListener('change', this._makeCurveUpdater(partialIndex, curveKey, 'v', i));
+ vInput.addEventListener('change', this._makeCurveUpdater(partialIndex, curveKey, valueKey, i));
grid.appendChild(lbl);
grid.appendChild(tInput);
@@ -444,7 +444,7 @@ class PartialEditor {
}
// Bezier curve
- const curve = partial.ampCurve;
+ const curve = partial.freqCurve;
if (!curve) return;
const color = this.viewer ? this.viewer.partialColor(this._selectedIndex) : '#f44';
@@ -456,7 +456,7 @@ class PartialEditor {
const t = curve.t0 + (curve.t3 - curve.t0) * i / 120;
const x = this._tToX(t);
if (x < -1 || x > W + 1) { started = false; continue; }
- const y = this._ampToY(evalBezier(curve, t));
+ const y = this._ampToY(evalBezierAmp(curve, t));
if (!started) { ctx.moveTo(x, y); started = true; } else ctx.lineTo(x, y);
}
ctx.stroke();
@@ -464,7 +464,7 @@ class PartialEditor {
// Control points
for (let i = 0; i < 4; ++i) {
const x = this._tToX(curve['t' + i]);
- const y = this._ampToY(curve['v' + i]);
+ const y = this._ampToY(curve['a' + i]);
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, 6, 0, 2 * Math.PI);
@@ -485,17 +485,17 @@ class PartialEditor {
canvas.addEventListener('mousedown', (e) => {
if (this._selectedIndex < 0 || !this.partials) return;
const partial = this.partials[this._selectedIndex];
- if (!partial || !partial.ampCurve) return;
+ if (!partial || !partial.freqCurve) return;
const {x, y} = getCanvasCoords(e, canvas);
- const curve = partial.ampCurve;
+ const curve = partial.freqCurve;
for (let i = 0; i < 4; ++i) {
- if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['v' + i]) - y) <= 8) {
+ if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['a' + i]) - y) <= 8) {
this._dragPointIndex = i;
this._dragCompanionOff = null;
if (i === 0)
- this._dragCompanionOff = { dt: curve.t1 - curve.t0, dv: curve.v1 - curve.v0 };
+ this._dragCompanionOff = { da: curve.a1 - curve.a0 };
else if (i === 3)
- this._dragCompanionOff = { dt: curve.t2 - curve.t3, dv: curve.v2 - curve.v3 };
+ this._dragCompanionOff = { da: curve.a2 - curve.a3 };
canvas.style.cursor = 'grabbing';
e.preventDefault();
return;
@@ -507,14 +507,13 @@ class PartialEditor {
const {x, y} = getCanvasCoords(e, canvas);
if (this._dragPointIndex >= 0) {
- const curve = this.partials[this._selectedIndex].ampCurve;
+ const curve = this.partials[this._selectedIndex].freqCurve;
const i = this._dragPointIndex;
- curve['t' + i] = Math.max(0, Math.min(this.viewer ? this.viewer.t_max : 1e6, this._xToT(x)));
- curve['v' + i] = Math.max(0, this._yToAmp(y));
+ curve['a' + i] = Math.max(0, this._yToAmp(y));
if (this._dragCompanionOff) {
const off = this._dragCompanionOff;
- if (i === 0) { curve.t1 = curve.t0 + off.dt; curve.v1 = curve.v0 + off.dv; }
- else { curve.t2 = curve.t3 + off.dt; curve.v2 = curve.v3 + off.dv; }
+ if (i === 0) { curve.a1 = curve.a0 + off.da; }
+ else { curve.a2 = curve.a3 + off.da; }
}
this._renderAmpEditor();
if (this.viewer) this.viewer.render();
@@ -524,11 +523,11 @@ class PartialEditor {
// Cursor hint
if (this._selectedIndex >= 0 && this.partials) {
- const curve = this.partials[this._selectedIndex]?.ampCurve;
+ const curve = this.partials[this._selectedIndex]?.freqCurve;
if (curve) {
let near = false;
for (let i = 0; i < 4; ++i) {
- if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['v' + i]) - y) <= 8) {
+ if (Math.hypot(this._tToX(curve['t' + i]) - x, this._ampToY(curve['a' + i]) - y) <= 8) {
near = true; break;
}
}
diff --git a/tools/mq_editor/mq_extract.js b/tools/mq_editor/mq_extract.js
index 107b2ac..97191e2 100644
--- a/tools/mq_editor/mq_extract.js
+++ b/tools/mq_editor/mq_extract.js
@@ -22,7 +22,9 @@ function extractPartials(params, stftCache) {
for (const partial of partials) {
partial.freqCurve = fitBezier(partial.times, partial.freqs);
- partial.ampCurve = fitBezier(partial.times, partial.amps);
+ const ac = fitBezier(partial.times, partial.amps);
+ partial.freqCurve.a0 = ac.v0; partial.freqCurve.a1 = ac.v1;
+ partial.freqCurve.a2 = ac.v2; partial.freqCurve.a3 = ac.v3;
}
return {partials, frames};
diff --git a/tools/mq_editor/mq_synth.js b/tools/mq_editor/mq_synth.js
index 4c68056..00867a9 100644
--- a/tools/mq_editor/mq_synth.js
+++ b/tools/mq_editor/mq_synth.js
@@ -8,7 +8,7 @@ function randFloat(seed, min, max) {
}
// Synthesize audio from MQ partials
-// partials: array of {freqCurve, ampCurve, replicas?, resonator?}
+// partials: array of {freqCurve (with a0-a3 for amp), replicas?, resonator?}
// replicas: {offsets, decay_alpha, jitter, spread_above, spread_below}
// resonator: {enabled, r, gainComp} — two-pole resonator mode per partial
// integratePhase: true = accumulate 2π*f/SR per sample (correct for varying freq)
@@ -35,7 +35,6 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
for (let p = 0; p < partials.length; ++p) {
const partial = partials[p];
const fc = partial.freqCurve;
- const ac = partial.ampCurve;
if ((partial.resonator && partial.resonator.enabled) || options.forceResonator) {
// --- Two-pole resonator mode ---
@@ -50,7 +49,7 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
const gainNorm = Math.sqrt(Math.max(0, 1.0 - r * r));
configs.push({
mode: 'resonator',
- fc, ac,
+ fc,
r, gainComp, gainNorm,
y1: 0.0, y2: 0.0,
noiseSeed: ((p * 1664525 + 1013904223) & 0xFFFFFFFF) >>> 0
@@ -70,7 +69,7 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
const initPhase = randFloat(p * 67890 + r, 0.0, 1.0) * (jitter * jitterMult) * 2.0 * Math.PI;
replicaData.push({ratio: offsets[r], spread, phase: initPhase});
}
- configs.push({ mode: 'sinusoid', fc, ac, decay_alpha, replicaData });
+ configs.push({ mode: 'sinusoid', fc, decay_alpha, replicaData });
}
}
@@ -80,13 +79,13 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
for (let p = 0; p < configs.length; ++p) {
const cfg = configs[p];
- const {fc, ac} = cfg;
+ const {fc} = cfg;
if (cfg.mode === 'resonator') {
if (t < fc.t0 || t > fc.t3) { cfg.y1 = 0.0; cfg.y2 = 0.0; continue; }
const f0 = evalBezier(fc, t);
- const A = evalBezier(ac, t);
+ const A = evalBezierAmp(fc, t);
const omega = 2.0 * Math.PI * f0 / sampleRate;
const b1 = 2.0 * cfg.r * Math.cos(omega);
@@ -104,7 +103,7 @@ function synthesizeMQ(partials, sampleRate, duration, integratePhase = true, opt
if (t < fc.t0 || t > fc.t3) continue;
const f0 = evalBezier(fc, t);
- const A0 = evalBezier(ac, t);
+ const A0 = evalBezierAmp(fc, t);
const {decay_alpha, replicaData} = cfg;
for (let r = 0; r < replicaData.length; ++r) {
diff --git a/tools/mq_editor/utils.js b/tools/mq_editor/utils.js
index c38b1f5..96d807c 100644
--- a/tools/mq_editor/utils.js
+++ b/tools/mq_editor/utils.js
@@ -13,6 +13,19 @@ function evalBezier(curve, t) {
u*u*u * curve.v3;
}
+// Evaluate amplitude component of unified bezier curve at time t
+function evalBezierAmp(curve, t) {
+ const dt = curve.t3 - curve.t0;
+ if (dt <= 0) return curve.a0;
+ let u = (t - curve.t0) / dt;
+ u = Math.max(0, Math.min(1, u));
+ const u1 = 1.0 - u;
+ return u1*u1*u1 * curve.a0 +
+ 3*u1*u1*u * curve.a1 +
+ 3*u1*u*u * curve.a2 +
+ u*u*u * curve.a3;
+}
+
// Get canvas-relative {x, y} from a mouse event
function getCanvasCoords(e, canvas) {
const rect = canvas.getBoundingClientRect();