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.html91
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) {