diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-18 06:59:32 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-18 06:59:32 +0100 |
| commit | 105c817021a84bfacffa1553d6bcd536808b9f23 (patch) | |
| tree | 36bc576d4605c5057e1604640f0fa1679866d6b6 /tools/mq_editor/mq_extract.js | |
| parent | 65cd99553cd688c5ad2cfd64d79c6434fe694a33 (diff) | |
feat(mq_editor): UI improvements and partial detection enhancements
- Right panel with synthesis checkboxes (integrate phase, disable jitter, disable spread)
- Style file chooser as button; show filename next to page title
- Second backward pass in extractPartials to recover partial onsets
- Cursor line drawn on overlay canvas (no full redraw on mousemove)
handoff(Claude): UI + algo improvements complete
Diffstat (limited to 'tools/mq_editor/mq_extract.js')
| -rw-r--r-- | tools/mq_editor/mq_extract.js | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/tools/mq_editor/mq_extract.js b/tools/mq_editor/mq_extract.js index 2293d52..8a0ea0e 100644 --- a/tools/mq_editor/mq_extract.js +++ b/tools/mq_editor/mq_extract.js @@ -16,6 +16,9 @@ function extractPartials(params, stftCache) { const partials = trackPartials(frames); + // Second pass: extend partials leftward to recover onset frames + expandPartialsLeft(partials, frames); + for (const partial of partials) { partial.freqCurve = fitBezier(partial.times, partial.freqs); partial.ampCurve = fitBezier(partial.times, partial.amps); @@ -144,6 +147,41 @@ function trackPartials(frames) { return partials; } +// Second pass: extend each partial leftward to recover onset frames missed +// by the birthPersistence requirement in the forward pass. +function expandPartialsLeft(partials, frames) { + const trackingRatio = 0.05; + const minTrackingHz = 20; + + // Build time → frame index map + const timeToIdx = new Map(); + for (let i = 0; i < frames.length; ++i) timeToIdx.set(frames[i].time, i); + + for (const partial of partials) { + let startIdx = timeToIdx.get(partial.times[0]); + if (startIdx == null || startIdx === 0) continue; + + for (let i = startIdx - 1; i >= 0; --i) { + const frame = frames[i]; + const refFreq = partial.freqs[0]; + const tol = Math.max(refFreq * trackingRatio, minTrackingHz); + + let bestIdx = -1, bestDist = Infinity; + for (let j = 0; j < frame.peaks.length; ++j) { + const dist = Math.abs(frame.peaks[j].freq - refFreq); + if (dist < tol && dist < bestDist) { bestDist = dist; bestIdx = j; } + } + + if (bestIdx < 0) break; + + const pk = frame.peaks[bestIdx]; + partial.times.unshift(frame.time); + partial.freqs.unshift(pk.freq); + partial.amps.unshift(pk.amp); + } + } +} + // Fit cubic bezier to trajectory using samples at ~1/3 and ~2/3 as control points function fitBezier(times, values) { const n = times.length - 1; |
