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.html175
1 files changed, 175 insertions, 0 deletions
diff --git a/tools/mq_editor/index.html b/tools/mq_editor/index.html
new file mode 100644
index 0000000..d44f19b
--- /dev/null
+++ b/tools/mq_editor/index.html
@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>MQ Spectral Editor</title>
+ <style>
+ body {
+ font-family: monospace;
+ margin: 20px;
+ background: #1a1a1a;
+ color: #ddd;
+ }
+ .toolbar {
+ margin-bottom: 10px;
+ padding: 10px;
+ background: #2a2a2a;
+ border-radius: 4px;
+ }
+ button {
+ background: #3a3a3a;
+ color: #ddd;
+ border: 1px solid #555;
+ padding: 8px 16px;
+ margin-right: 8px;
+ cursor: pointer;
+ border-radius: 4px;
+ }
+ button:hover { background: #4a4a4a; }
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
+ input[type="file"] { margin-right: 16px; }
+ .params {
+ display: inline-block;
+ margin-left: 20px;
+ }
+ label {
+ margin-right: 8px;
+ }
+ input[type="number"], select {
+ width: 80px;
+ background: #3a3a3a;
+ color: #ddd;
+ border: 1px solid #555;
+ padding: 4px;
+ border-radius: 3px;
+ }
+ #canvas {
+ border: 1px solid #555;
+ background: #000;
+ cursor: crosshair;
+ display: block;
+ margin-top: 10px;
+ }
+ #status {
+ margin-top: 10px;
+ padding: 8px;
+ background: #2a2a2a;
+ border-radius: 4px;
+ min-height: 20px;
+ }
+ .info {
+ color: #4af;
+ }
+ .warn {
+ color: #fa4;
+ }
+ .error {
+ color: #f44;
+ }
+ </style>
+</head>
+<body>
+ <h2>MQ Spectral Editor</h2>
+
+ <div class="toolbar">
+ <input type="file" id="wavFile" accept=".wav">
+ <button id="extractBtn" disabled>Extract Partials</button>
+
+ <div class="params">
+ <label>FFT Size:</label>
+ <select id="fftSize">
+ <option value="1024">1024</option>
+ <option value="2048" selected>2048</option>
+ <option value="4096">4096</option>
+ </select>
+
+ <label>Hop:</label>
+ <input type="number" id="hopSize" value="512" min="64" max="2048" step="64">
+
+ <label>Threshold (dB):</label>
+ <input type="number" id="threshold" value="-60" min="-80" max="-20" step="5">
+ </div>
+ </div>
+
+ <canvas id="canvas" width="1400" height="600"></canvas>
+
+ <div id="tooltip" style="position: fixed; display: none; background: #2a2a2a; padding: 4px 8px; border: 1px solid #555; border-radius: 3px; pointer-events: none; font-size: 11px; z-index: 1000;"></div>
+
+ <div id="status">Load a WAV file to begin...</div>
+
+ <script src="fft.js"></script>
+ <script src="mq_extract.js"></script>
+ <script src="viewer.js"></script>
+ <script>
+ let audioBuffer = null;
+ let viewer = null;
+
+ const wavFile = document.getElementById('wavFile');
+ const extractBtn = document.getElementById('extractBtn');
+ const canvas = document.getElementById('canvas');
+ const status = document.getElementById('status');
+
+ const fftSize = document.getElementById('fftSize');
+ const hopSize = document.getElementById('hopSize');
+ const threshold = document.getElementById('threshold');
+
+ // Load WAV file
+ wavFile.addEventListener('change', async (e) => {
+ const file = e.target.files[0];
+ if (!file) return;
+
+ setStatus('Loading WAV...', 'info');
+ try {
+ const arrayBuffer = await file.arrayBuffer();
+ const audioContext = new AudioContext();
+ audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
+
+ extractBtn.disabled = false;
+ setStatus(`Loaded: ${audioBuffer.duration.toFixed(2)}s, ${audioBuffer.sampleRate}Hz, ${audioBuffer.numberOfChannels}ch`, 'info');
+
+ // Create viewer
+ viewer = new SpectrogramViewer(canvas, audioBuffer);
+ } catch (err) {
+ setStatus('Error loading WAV: ' + err.message, 'error');
+ console.error(err);
+ }
+ });
+
+ // Extract partials
+ extractBtn.addEventListener('click', () => {
+ if (!audioBuffer) return;
+
+ setStatus('Extracting partials...', 'info');
+ extractBtn.disabled = true;
+
+ setTimeout(() => {
+ try {
+ const params = {
+ fftSize: parseInt(fftSize.value),
+ hopSize: parseInt(hopSize.value),
+ threshold: parseFloat(threshold.value),
+ sampleRate: audioBuffer.sampleRate
+ };
+
+ const partials = extractPartials(audioBuffer, params);
+
+ setStatus(`Extracted ${partials.length} partials`, 'info');
+
+ // Update viewer
+ viewer.setPartials(partials);
+
+ } catch (err) {
+ setStatus('Extraction error: ' + err.message, 'error');
+ console.error(err);
+ }
+ extractBtn.disabled = false;
+ }, 50);
+ });
+
+ function setStatus(msg, type = '') {
+ status.innerHTML = msg;
+ status.className = type;
+ }
+ </script>
+</body>
+</html>