diff options
Diffstat (limited to 'tools/mq_editor/index.html')
| -rw-r--r-- | tools/mq_editor/index.html | 91 |
1 files changed, 85 insertions, 6 deletions
diff --git a/tools/mq_editor/index.html b/tools/mq_editor/index.html index 1a07b61..84abd1c 100644 --- a/tools/mq_editor/index.html +++ b/tools/mq_editor/index.html @@ -86,7 +86,14 @@ </div> </div> - <canvas id="canvas" width="1400" height="600"></canvas> + <div style="position: relative;"> + <canvas id="canvas" width="1400" height="600"></canvas> + + <!-- Mini spectrum viewer (bottom-right overlay) --> + <div id="spectrumViewer" style="position: absolute; bottom: 10px; right: 10px; width: 200px; height: 100px; background: rgba(30, 30, 30, 0.9); border: 1px solid #555; border-radius: 3px; pointer-events: none;"> + <canvas id="spectrumCanvas" width="200" height="100"></canvas> + </div> + </div> <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> @@ -94,12 +101,15 @@ <script src="fft.js"></script> <script src="mq_extract.js"></script> + <script src="mq_synth.js"></script> <script src="viewer.js"></script> <script> let audioBuffer = null; let viewer = null; let audioContext = null; let currentSource = null; + let extractedPartials = null; + let stftCache = null; const wavFile = document.getElementById('wavFile'); const extractBtn = document.getElementById('extractBtn'); @@ -133,16 +143,36 @@ initAudioContext(); extractBtn.disabled = false; playBtn.disabled = false; - setStatus(`Loaded: ${audioBuffer.duration.toFixed(2)}s, ${audioBuffer.sampleRate}Hz, ${audioBuffer.numberOfChannels}ch`, 'info'); + setStatus('Computing STFT cache...', 'info'); - // Create viewer - viewer = new SpectrogramViewer(canvas, audioBuffer); + // Compute STFT cache + setTimeout(() => { + const signal = audioBuffer.getChannelData(0); + stftCache = new STFTCache(signal, audioBuffer.sampleRate, fftSize, parseInt(hopSize.value)); + + setStatus(`Loaded: ${audioBuffer.duration.toFixed(2)}s, ${audioBuffer.sampleRate}Hz, ${audioBuffer.numberOfChannels}ch (${stftCache.getNumFrames()} frames cached)`, 'info'); + + // Create viewer with cache + viewer = new SpectrogramViewer(canvas, audioBuffer, stftCache); + }, 10); } catch (err) { setStatus('Error loading WAV: ' + err.message, 'error'); console.error(err); } }); + // Update cache when hop size changes + hopSize.addEventListener('change', () => { + if (stftCache) { + setStatus('Updating STFT cache...', 'info'); + setTimeout(() => { + stftCache.setHopSize(parseInt(hopSize.value)); + setStatus(`Cache updated (${stftCache.getNumFrames()} frames)`, 'info'); + if (viewer) viewer.render(); + }, 10); + } + }); + // Extract partials extractBtn.addEventListener('click', () => { if (!audioBuffer) return; @@ -161,6 +191,7 @@ const partials = extractPartials(audioBuffer, params); + extractedPartials = partials; setStatus(`Extracted ${partials.length} partials`, 'info'); // Update viewer @@ -235,12 +266,60 @@ status.className = type; } + // Play synthesized audio + function playSynthesized() { + if (!extractedPartials || extractedPartials.length === 0) { + setStatus('No partials extracted yet', 'warn'); + return; + } + if (!audioBuffer || !audioContext) return; + + stopAudio(); + + setStatus('Synthesizing...', 'info'); + + // Synthesize PCM from partials + const sampleRate = audioBuffer.sampleRate; + const duration = audioBuffer.duration; + const pcm = synthesizeMQ(extractedPartials, sampleRate, duration); + + // Create audio buffer + const synthBuffer = audioContext.createBuffer(1, pcm.length, sampleRate); + synthBuffer.getChannelData(0).set(pcm); + + const startTime = audioContext.currentTime; + currentSource = audioContext.createBufferSource(); + currentSource.buffer = synthBuffer; + currentSource.connect(audioContext.destination); + currentSource.start(); + + currentSource.onended = () => { + currentSource = null; + playBtn.disabled = false; + stopBtn.disabled = true; + viewer.setPlayheadTime(-1); + setStatus('Stopped', 'info'); + }; + + playBtn.disabled = true; + stopBtn.disabled = false; + setStatus('Playing synthesized...', 'info'); + + // Animate playhead + function updatePlayhead() { + if (!currentSource) return; + const elapsed = audioContext.currentTime - startTime; + viewer.setPlayheadTime(elapsed); + requestAnimationFrame(updatePlayhead); + } + updatePlayhead(); + } + // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.code === 'Digit1') { e.preventDefault(); - // TODO: Play synthesized (Phase 2) - setStatus('Synthesized playback not yet implemented', 'warn'); + playSynthesized(); } else if (e.code === 'Digit2') { e.preventDefault(); if (!playBtn.disabled) { |
