summaryrefslogtreecommitdiff
path: root/tools/mq_editor/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'tools/mq_editor/index.html')
-rw-r--r--tools/mq_editor/index.html150
1 files changed, 134 insertions, 16 deletions
diff --git a/tools/mq_editor/index.html b/tools/mq_editor/index.html
index 60076b3..c5902a7 100644
--- a/tools/mq_editor/index.html
+++ b/tools/mq_editor/index.html
@@ -73,12 +73,16 @@
border: 1px solid #555;
border-radius: 4px;
padding: 12px;
- min-width: 160px;
+ min-width: 220px;
+ max-width: 220px;
+ max-height: 600px;
+ overflow-y: auto;
display: flex;
flex-direction: column;
- gap: 10px;
+ gap: 6px;
+ box-sizing: border-box;
}
- .right-panel .panel-title {
+ .panel-title {
font-size: 11px;
color: #888;
text-transform: uppercase;
@@ -86,6 +90,9 @@
border-bottom: 1px solid #444;
padding-bottom: 6px;
margin-bottom: 2px;
+ display: flex;
+ align-items: center;
+ gap: 6px;
}
.right-panel label {
display: flex;
@@ -95,6 +102,61 @@
cursor: pointer;
font-size: 13px;
}
+ /* Partial properties */
+ .prop-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ font-size: 12px;
+ padding: 1px 0;
+ }
+ .prop-label { color: #777; font-size: 11px; }
+ .prop-section {
+ font-size: 10px;
+ color: #666;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-top: 6px;
+ margin-bottom: 2px;
+ }
+ .curve-grid {
+ display: grid;
+ grid-template-columns: 18px 1fr 1fr;
+ gap: 2px 3px;
+ align-items: center;
+ }
+ .curve-grid span { color: #666; font-size: 10px; }
+ .curve-grid input[type="number"] {
+ width: 100%;
+ background: #333;
+ color: #ccc;
+ border: 1px solid #444;
+ padding: 1px 3px;
+ border-radius: 2px;
+ font-size: 10px;
+ font-family: monospace;
+ box-sizing: border-box;
+ }
+ .curve-grid input[type="number"]:focus {
+ border-color: #666;
+ outline: none;
+ }
+ .partial-actions {
+ display: flex;
+ gap: 4px;
+ margin-top: 6px;
+ }
+ .partial-actions button {
+ flex: 1;
+ padding: 3px 6px;
+ font-size: 11px;
+ margin: 0;
+ }
+ .synth-section {
+ border-top: 1px solid #444;
+ padding-top: 8px;
+ margin-top: auto;
+ }
#status {
margin-top: 10px;
padding: 8px;
@@ -135,21 +197,62 @@
</div>
<div class="main-area">
- <div style="position: relative; flex-shrink: 0;">
- <canvas id="canvas" width="1400" height="600"></canvas>
- <canvas id="cursorCanvas" width="1400" height="600" style="position:absolute;top:0;left:0;pointer-events:none;"></canvas>
+ <div style="display:flex; flex-direction:column; gap:6px; flex-shrink:0;">
+ <div style="position: relative;">
+ <canvas id="canvas" width="1400" height="600"></canvas>
+ <canvas id="cursorCanvas" width="1400" height="600" style="position:absolute;top:0;left:0;pointer-events:none;"></canvas>
- <!-- Mini spectrum viewer (bottom-right overlay) -->
- <div id="spectrumViewer" style="position: absolute; bottom: 10px; right: 10px; width: 400px; height: 100px; background: rgba(30, 30, 30, 0.9); border: 1px solid #555; border-radius: 3px; pointer-events: none;">
- <canvas id="spectrumCanvas" width="400" height="100"></canvas>
+ <!-- Mini spectrum viewer (bottom-right overlay) -->
+ <div id="spectrumViewer" style="position: absolute; bottom: 10px; right: 10px; width: 400px; height: 100px; background: rgba(30, 30, 30, 0.9); border: 1px solid #555; border-radius: 3px; pointer-events: none;">
+ <canvas id="spectrumCanvas" width="400" height="100"></canvas>
+ </div>
+ </div>
+
+ <!-- Amplitude bezier editor (shown when partial selected) -->
+ <div id="ampEditPanel" style="display:none;">
+ <div style="font-size:10px; color:#555; padding:2px 0 3px 1px; display:flex; align-items:center; gap:10px; text-transform:uppercase; letter-spacing:0.5px;">
+ <span>Amplitude</span>
+ <span id="ampEditTitle" style="color:#777; text-transform:none; letter-spacing:0;"></span>
+ <span style="color:#333; text-transform:none; letter-spacing:0;">drag control points · Esc to deselect</span>
+ </div>
+ <canvas id="ampEditCanvas" width="1400" height="120" style="border:1px solid #2a2a2a; background:#0e0e0e; cursor:crosshair; display:block;"></canvas>
</div>
</div>
<div class="right-panel">
- <div class="panel-title">Synthesis</div>
- <label><input type="checkbox" id="integratePhase" checked> Integrate phase</label>
- <label><input type="checkbox" id="disableJitter"> Disable jitter</label>
- <label><input type="checkbox" id="disableSpread"> Disable spread</label>
+ <!-- Partial properties (visible when a partial is selected) -->
+ <div id="partialProps" style="display:none;">
+ <div class="panel-title">
+ <span id="propTitle">Partial #—</span>
+ <span id="propSwatch" style="display:inline-block;width:10px;height:10px;border-radius:2px;flex-shrink:0;"></span>
+ </div>
+ <div class="prop-row">
+ <span class="prop-label">Peak</span>
+ <span id="propPeak">—</span>
+ </div>
+ <div class="prop-row">
+ <span class="prop-label">Time</span>
+ <span id="propTime">—</span>
+ </div>
+ <div class="prop-section">Freq Curve</div>
+ <div class="curve-grid" id="freqCurveGrid"></div>
+ <div class="prop-section">Amp Curve</div>
+ <div class="curve-grid" id="ampCurveGrid"></div>
+ <div class="partial-actions">
+ <button id="mutePartialBtn">Mute</button>
+ <button id="deletePartialBtn">Delete</button>
+ </div>
+ </div>
+
+ <div id="noSelMsg" style="color:#555;font-size:11px;padding:2px 0;">Click a partial to select</div>
+
+ <!-- Synthesis options (always at bottom) -->
+ <div class="synth-section">
+ <div class="panel-title">Synthesis</div>
+ <label><input type="checkbox" id="integratePhase" checked> Integrate phase</label>
+ <label><input type="checkbox" id="disableJitter"> Disable jitter</label>
+ <label><input type="checkbox" id="disableSpread"> Disable spread</label>
+ </div>
</div>
</div>
@@ -161,6 +264,7 @@
<script src="mq_extract.js"></script>
<script src="mq_synth.js"></script>
<script src="viewer.js"></script>
+ <script src="editor.js"></script>
<script>
let audioBuffer = null;
let viewer = null;
@@ -193,6 +297,13 @@
if (viewer && extractedPartials) viewer.setKeepCount(getKeepCount());
});
+ // --- Editor ---
+ const editor = new PartialEditor();
+ editor.onPartialDeleted = () => {
+ if (viewer && extractedPartials)
+ viewer.setKeepCount(extractedPartials.length > 0 ? getKeepCount() : 0);
+ };
+
// Initialize audio context
function initAudioContext() {
if (!audioContext) {
@@ -213,6 +324,9 @@
stftCache = new STFTCache(signal, audioBuffer.sampleRate, fftSize, Math.max(64, parseInt(hopSize.value) || 64));
setStatus(`${label} — ${audioBuffer.duration.toFixed(2)}s, ${audioBuffer.sampleRate}Hz, ${audioBuffer.numberOfChannels}ch (${stftCache.getNumFrames()} frames cached)`, 'info');
viewer = new SpectrogramViewer(canvas, audioBuffer, stftCache);
+ editor.setViewer(viewer);
+ viewer.onPartialSelect = (i) => editor.onPartialSelect(i);
+ viewer.onRender = () => editor.onRender();
if (label.startsWith('Test WAV')) validateTestWAVPeaks(stftCache);
}, 10);
}
@@ -296,10 +410,12 @@
});
extractedPartials = result.partials;
+ editor.setPartials(result.partials);
viewer.setFrames(result.frames);
setStatus(`Extracted ${result.partials.length} partials`, 'info');
viewer.setPartials(result.partials);
viewer.setKeepCount(getKeepCount());
+ viewer.selectPartial(-1);
} catch (err) {
setStatus('Extraction error: ' + err.message, 'error');
@@ -384,8 +500,8 @@
setStatus('Synthesizing...', 'info');
const keepCount = getKeepCount();
- const partialsToUse = extractedPartials.slice(0, keepCount);
- setStatus(`Synthesizing ${keepCount}/${extractedPartials.length} partials (${keepPct.value}%)...`, 'info');
+ const partialsToUse = extractedPartials.slice(0, keepCount).filter(p => !p.muted);
+ setStatus(`Synthesizing ${partialsToUse.length}/${extractedPartials.length} partials (${keepPct.value}%)...`, 'info');
const integratePhase = document.getElementById('integratePhase').checked;
const disableJitter = document.getElementById('disableJitter').checked;
@@ -399,7 +515,7 @@
const synthBuffer = audioContext.createBuffer(1, pcm.length, audioBuffer.sampleRate);
synthBuffer.getChannelData(0).set(pcm);
- playAudioBuffer(synthBuffer, `Playing synthesized (${keepCount}/${extractedPartials.length} partials, ${keepPct.value}%)...`);
+ playAudioBuffer(synthBuffer, `Playing synthesized (${partialsToUse.length}/${extractedPartials.length} partials, ${keepPct.value}%)...`);
}
// Keyboard shortcuts
@@ -422,6 +538,8 @@
viewer.showSynthFFT = !viewer.showSynthFFT;
viewer.renderSpectrum();
}
+ } else if (e.code === 'Escape') {
+ if (viewer) viewer.selectPartial(-1);
}
});