| Age | Commit message (Collapse) | Author |
|
points
Replace cubic Bezier with Lagrange interpolation so P1/P2 are actual
points on the curve. Eval uses stored t1/t2 as arbitrary knot positions.
fitBezier keeps least-squares but with Lagrange basis at u=1/3,2/3.
Remove endpoint companion drag (no longer tangent handles).
TODO: support arbitrary number of inner control points.
handoff(Claude): Lagrange interpolation replaces Bezier in mq_editor curve eval/fit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
trackIsoContour() follows constant energy level through STFT frames
instead of peaks. Useful for broad bass areas where peak detector finds
nothing. Preview in cyan, auto-detects spread on commit (naturally large).
Toggle: ≋ Contour button or C key. Mutually exclusive with ⊕ Explore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Pre-compute peak frames after STFT cache is built, so trackFromSeed
works without requiring Extract Partials first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Hover to preview a tracked partial from mouse position (peak-snapped,
forward+backward MQ tracking). Click to commit. Toggle with ⊕ Explore
button or X key. Escape exits explore mode.
handoff(Gemini): explore mode added in mq_extract.trackFromSeed + viewer.js/app.js
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
setPlayheadTime() now updates only the playhead overlay and spectrum panel
instead of triggering a full renderSpectrogram() every rAF tick.
handoff(Gemini): playhead is now an overlay canvas (like cursorCanvas),
no more O(n_frames) redraw during playback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- '+ Partial' button (N key): insert 440Hz/max-amp partial at front
- Undo/Redo buttons (Ctrl+Z/Y): JSON snapshot stack, 50 levels
- Hooks in delete, mute, curve edits, amp drag, freq drag
handoff(Gemini): undo/redo + new-partial added to mq_editor
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
freqCurve now carries a0-a3 (amplitude control values) alongside
v0-v3 (frequency). Both components share the same t0-t3 time
parameterization. evalBezierAmp() added to utils.js.
ampCurve removed from partials and synth pipeline.
Amp panel drag now changes only a_i; t is read-only (shared with freq).
handoff(Claude): unified freq/amp bezier done
|
|
Same P0↔P1 / P3↔P2 coupling as the amp editor, now for the frequency
bezier dragged in the main spectrogram viewer.
handoff(Claude): freq curve coupled drag fixed
|
|
Moving P0 translates P1 rigidly (preserving relative offset).
Moving P3 translates P2 rigidly. Handles P1/P2 remain independently
draggable.
handoff(Claude): bezier coupled anchor drag done
|
|
Extract synth UI params into reusable getSynthParams(). Add Digit3 shortcut
to synthesize and play only the currently selected partial.
handoff(Claude): getSynthParams() now shared by playSynthesized() and Digit3 handler.
|
|
- utils.js (new): evalBezier (robust), getCanvasCoords, buildBandPoints
- app.js (new): extract ~450-line inline script from index.html
- editor.js: generalize _makeJogSlider(inp, options) with onUpdate cb,
eliminate 50-line inline resonator jog duplication, use getCanvasCoords
- mq_extract.js: extract findBestPeak(), replace two identical loop bodies
- viewer.js: remove duplicate evalBezier, use getCanvasCoords/buildBandPoints
- mq_synth.js: remove duplicate evalBezier
- index.html: inline script removed, load order: utils→fft→extract→synth→viewer→editor→app
handoff(Claude): mq_editor refactor complete — no logic changes, browser-ready.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Reorganize extraction parameters into four labeled groups (STFT / Peak
Detection / Tracking / Filter). Expose Birth, Death, Phase Wt and Min
Len as live controls wired to runExtraction(). All labels carry tooltip
descriptions. mq_extract.js now reads these from params instead of
hardcoded constants (same defaults preserved).
handoff(Claude): tracking params exposed, panel grouped, README updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
prediction
- phaseInterp: missing *0.5 on quadratic term made interpolated phase 2x off
- trackPartials: phase advance now scaled by (age+1) to cover frames missed
during a partial gap, preventing stale predictions after consecutive misses
handoff(Claude): two bugs fixed in mq_extract.js phase tracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Implements a more robust partial tracking algorithm by using phase
coherence to validate and link spectral peaks across frames. This
significantly improves tracking accuracy, especially for crossing or
closely-spaced partials.
Key changes:
- : The STFT cache now computes and stores the phase for each
frequency bin alongside the magnitude.
- : The peak detection logic now interpolates the
phase for sub-bin accuracy.
- : The core tracking algorithm was replaced with a
phase-aware model. It predicts the expected phase for a partial in
the next frame and uses a cost function combining both frequency and
phase error to find the best match.
This implementation follows the design outlined in the new document
.
handoff(Gemini): Phase prediction implemented. Further tuning of the cost function weights may be beneficial.
|
|
- Implement Predictive Kinematic Tracking to improve partial tracking during fast glissandos and vibrato.
- Add Peak Prominence Pruning to filter out insignificant local maxima.
- Replace heuristic Bezier fitting with a Least-Squares solver for more accurate trajectories.
- Update UI to include a Prominence parameter input.
- Archive MQ_EXTRACTION_IMPROVEMENTS.md design document.
handoff(Gemini): implemented MQ extraction improvements (kinematic tracking, prominence pruning, least-squares bezier)
|
|
Add r (pole) and gain sliders with force r/gain checkbox in Synthesis
panel, visible when Resonator (all) is active. Restrict r range to
[0.75, 0.9999] for both global and per-partial sliders.
handoff(Claude): global resonator r/gain override controls added
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add k1/k2 range sliders for post-synthesis LP/HP filters. Display maps
coefficients to -3dB cutoff frequency using exact IIR formulas; shows
'bypass' at k=1.0. Uses audioBuffer.sampleRate when loaded, falls back
to 44100 Hz.
handoff(Claude): LP k1 and HP k2 sliders added to Synthesis panel with Hz display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Adds "Resonator (all)" checkbox that forces resonator synthesis for all
partials without modifying per-partial mode settings.
Fixes null deref when partial.resonator is undefined in forceResonator mode.
handoff(Gemini): resonator test-mode checkbox added; no per-partial data mutated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Add first-order IIR low-pass (options.k1) and high-pass (options.k2)
filters applied globally after the synthesis loop and before normalization.
Both are bypassed when the key is absent; LP is applied before HP
(bandpass configuration when both active).
handoff(Claude): LP/HP filters added to synthesizeMQ, no UI yet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
Each partial in the Synth tab now has a Sinusoid/Resonator toggle.
Resonator path: y[n] = 2r·cos(ω₀)·y1 − r²·y2 + A(t)·√(1−r²)·noise,
coefficients recomputed per-sample from the freq Bezier curve.
gainNorm=√(1−r²) normalises steady-state power; gainComp for trim.
UI: mode toggle buttons, r + gain jog sliders, RES badge in header.
Docs updated in tools/mq_editor/README.md.
handoff(Claude): resonator mode complete, coefficients translated from
spread params in README, ready for perceptual comparison testing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
auto-spread
- Add 'f·Power' checkbox: weights spectrum by f before peak detection (f·FFT_Power(f))
to accentuate high-frequency peaks; re-runs extraction on toggle
- Deselect partial after Extract Partials run
- Fix right panel not refreshing after Auto Spread All: re-call editor.onPartialSelect
handoff(Claude): mq_editor UX polish
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Map 'E' key to Extract Partials
- Move extractBtn inline style to CSS rule #extractBtn
- Hoist autoSpreadAllBtn DOM ref to top with other refs
- Drop unused PADY local var in _renderAmpEditor
- Remove redundant comment before extractBtn listener
- README: add E/Esc keys, add editor.js to architecture, mark Phase 3 done
handoff(Gemini): mq_editor UX polish + doc consolidated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
panel refresh after extract
- Clear extractedPartials and editor state when loading a new WAV
- After extract, refresh right panels (re-select if index still valid)
- Synth fields (decay, jitter, spread) get jog sliders: drag to nudge, spring-back on release
- Spread extension limit dashed line: alpha 0.4→0.75, lineWidth 1→1.5, dash [3,4]→[4,3]
handoff(Gemini): mq_editor UX polish — jog sliders, WAV reset, panel refresh, spread line visibility
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- autodetectSpread(): measures half-power (-3dB) peak width in spectrogram
to infer spread_above/below (replaces near-zero bezier residual approach)
- 'Auto' button per partial + 'Auto Spread All' toolbar button
- Spread panel inputs now trigger viewer refresh on change
- 50% drop-off dotted reference lines on spread band (selected partial only)
- Mini-spectrum: use max() instead of mean() over bins per pixel column,
fixing high-frequency amplitude mismatch between original and synthesis
handoff(Gemini): spread autodetection and mini-spectrum fixes done
|
|
- Freq/Amp curve params now in tabs to save vertical space
- Add Synth tab: per-partial decay, jitter, spread_above/below controls
- Visualize spread band on selected partial (filled band + dashed bounds)
- Larger fonts and right panel (14px body, 260px panel width)
- Non-kept partials at 0.12 alpha (was 0.5)
- Default threshold -20dB (was -60dB)
handoff(Claude): mq_editor UI/UX improvements
|
|
- Click-to-select partials on canvas (proximity hit test on bezier)
- Right panel: peak freq/amp, time range, freq/amp bezier text inputs, mute/delete
- Selected partial renders on top with glow + larger control points
- Draggable freq curve control points on main canvas (grab/grabbing cursor)
- Amplitude bezier editor: 120px canvas below spectrogram, time-synced
with main view zoom/scroll via viewer.onRender callback
- Amp edits live-affect synthesis (mq_synth.js already uses ampCurve)
- PartialEditor class in editor.js owns all editing logic; index.html
wires it with 5 calls (setViewer, setPartials, onPartialSelect, onRender,
onPartialDeleted)
handoff(Gemini): partial editing MVP complete. Next: freq curve drag polish
or export (.spec generation).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Document UI layout (right panel, mini-spectrum color coding)
- Add keyboard shortcuts table
- Document viewer.js coordinate API
- Update algorithm section (backward expansion pass)
- Refresh implementation status checklist
handoff(Claude): docs updated
|
|
viewer.js:
- Add timeToX(), freqLogNorm(), normToFreq() coordinate primitives
- freqToY/canvasToFreq now delegate to freqLogNorm/normToFreq
- normalizeDB() replaces duplicated (magDB-(maxDB-80))/80 formula
- partialColor(p) replaces repeated color array
- All inline time/freq→pixel math replaced with API calls
index.html:
- getKeepCount() replaces 3 copies of the same calculation
- playAudioBuffer() replaces duplicated playback setup + RAF loop
handoff(Claude): refactor complete
|
|
- 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
|
|
audio
- Fix incoherent per-sample jitter/spread: seed by partial index only
- Fix || fallback for zero-valued params (use != null checks)
- Phase integration: accumulator (2π*f/SR per sample) replaces 2π*f*t
- Add 'Integrate phase' checkbox to toggle between modes
- Revert Catmull-Rom back to simple bezier (1/3, 2/3 sample points)
- Remove all debug logging, clean up trackPartials, fitBezier
handoff(Gemini): dual-sine test validates full MQ pipeline (extract→track→synth).
Next: real audio loading and partial detection improvements.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Fix mini-spectrum: log-scale frequency axis, per-pixel bin averaging,
red peak bars after extraction
- Fix key handlers swallowing digits in input fields
- Clamp hop size to min 64 to prevent hang
- Store maxDB in STFTCache, use it to normalize both mini-spectrum and
main spectrogram (fixes clipping at 0dB for pure tones)
- Add Test WAV console validation for 440/660Hz peaks
- Grayscale colormap for main spectrogram
handoff(Gemini): mq_editor display fixes complete, tests not affected
|
|
Generates 2s of 440+660Hz (A4+E5) at 32kHz without needing a file.
Refactors WAV load path into shared loadAudioBuffer() used by both
file input and the test button.
handoff(Gemini): test button wired, full UI pipeline testable without assets.
|
|
- tools/mq_editor/test_fft.html: browser test for fft.js (12 tests:
DC impulse, single tone, STFT magnitude, pairs, triplets)
- tools/gen_sine_440.py: generate 1s 440Hz WAV at 32kHz for manual testing
handoff(Gemini): FFT isolation tests added, all passing in browser.
|
|
Replace all inline linear freq→Y math with freqToY()/canvasToFreq() pair.
freqStart set to 20Hz (log-safe). Freq axis ticks now log-spaced.
handoff(Gemini): log scale done, all render paths use freqToY/canvasToFreq
|
|
handoff(Claude): removed full spectrum storage from STFTCache frames;
getFFT() deleted; viewers use squaredAmplitude + 10*log10 directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- evalBezier: guard dt<=0 to avoid NaN on degenerate curves
- fitBezier: replace nearest-neighbor control points with Catmull-Rom
tangents (Hermite->Bezier), curve now passes through endpoints
- key 'a': toggle mini-spectrum between original and synth FFT
handoff(Claude): bezier fix + synth FFT comparison
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
- Sort extracted partials by decreasing peak amplitude
- Add Keep% range slider (1-100%) to toolbar
- Viewer draws omitted partials at 50% opacity (live on slider input)
- synthesizeMQ uses only the top-N% partials
handoff(Claude): partial amplitude filtering complete
|
|
- STFTCache: cache squaredAmplitude (re²+im²) per frame, add getSquaredAmplitude(t)
- getMagnitudeDB uses cached sq amp (10*log10, no sqrt)
- detectPeaks uses squaredAmp from cache instead of recomputing FFT
- extractPartials: use cache frames, return {partials, frames}
- Press 'p' to toggle raw peak overlay in viewer
- threshold input: step=any, change event triggers re-extraction
- runExtraction() shared by button and threshold change
handoff(Claude): mq_partial peaks/cache refactor complete
|
|
|
|
Phase 2 - JS Synthesizer:
- Created mq_synth.js with replica oscillator bank
- Bezier curve evaluation (cubic De Casteljau algorithm)
- Replica synthesis: frequency spread, amplitude decay, phase jitter
- PCM buffer generation from extracted MQ partials
- Normalization to prevent clipping
- Key '1' plays synthesized audio, key '2' plays original
- Playback comparison with animated playhead
STFT Cache Optimization:
- Created STFTCache class in fft.js for pre-computed windowed FFT frames
- Clean interface: getFFT(t), getMagnitudeDB(t, freq), setHopSize()
- Pre-computes all frames on WAV load (eliminates redundant FFT calls)
- Dynamic cache update when hop size changes
- Shared across spectrogram, tooltip, and mini-spectrum viewer
- Significant performance improvement
Mini-Spectrum Viewer:
- Bottom-right overlay (200x100) matching spectral_editor style
- Real-time FFT display at playhead or mouse position
- 100-bar visualization with cyan-to-yellow gradient
- Updates during playback or mouse hover
Files:
- tools/mq_editor/mq_synth.js (new)
- tools/mq_editor/fft.js (STFTCache class added)
- tools/mq_editor/index.html (synthesis playback, cache integration)
- tools/mq_editor/viewer.js (cache-based rendering, spectrum viewer)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
|
Phase 1 deliverables complete:
- MQ extraction with improved tracking
- Spectrogram visualization with zoom/scroll
- Original WAV playback with playhead
- Ready for Phase 2 (JS synthesizer)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
|
Tracking improvements:
- Frequency-dependent threshold (5% of freq, min 20 Hz)
- Candidate system requiring 3-frame persistence before birth
- Extended death tolerance (5 frames) for robust trajectories
- Minimum 10-frame length filter for valid partials
- Result: cleaner, less scattered partial trajectories
Audio playback:
- Web Audio API integration for original WAV playback
- Play/Stop buttons with proper state management
- Animated red playhead bar during playback
- Keyboard shortcuts: '2' plays original, '1' reserved for synthesis
Visualization:
- Power law (gamma=0.3) for improved spectrogram contrast
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
|
- Fixed FFT to 1024 bins for 31.25 Hz resolution (better bass analysis)
- Refactored view state to zoom_factor + t_center for cleaner pan/zoom
- Mousewheel scrolls horizontally, shift+mousewheel zooms (respects deltaX/Y)
- Spectrogram bins now fill complete time/freq buckets at all zoom levels
- Extended dB range to -80→0 dB (80 dB) for better high-amplitude granularity
- Added real-time intensity tooltip in dB
- 50% alpha on spectrogram to reduce clutter over partial trajectories
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
|
|
Implement McAulay-Quatieri sinusoidal analysis tool for audio compression.
New files:
- doc/SPECTRAL_BRUSH_2.md: Complete design doc (MQ algorithm, data format, synthesis, roadmap)
- tools/mq_editor/index.html: Web UI (file loader, params, canvas)
- tools/mq_editor/fft.js: Radix-2 Cooley-Tukey FFT (from spectral_editor)
- tools/mq_editor/mq_extract.js: MQ algorithm (peak detection, tracking, bezier fitting)
- tools/mq_editor/viewer.js: Visualization (spectrogram, partials, zoom, axes)
- tools/mq_editor/README.md: Usage and implementation status
Features:
- Load WAV → extract sinusoidal partials → fit cubic bezier curves
- Time-frequency spectrogram with hot colormap (0-16 kHz)
- Horizontal zoom (mousewheel) around mouse position
- Axis ticks with labels (time: seconds, freq: Hz/kHz)
- Mouse tooltip showing time/frequency coordinates
- Real-time adjustable MQ parameters (FFT size, hop, threshold)
Algorithm:
- STFT with Hann windows (2048 FFT, 512 hop)
- Peak detection with parabolic interpolation
- Birth/death/continuation tracking (50 Hz tolerance)
- Cubic bezier fitting (4 control points per trajectory)
Next: Phase 2 (JS synthesizer for audio preview)
handoff(Claude): MQ editor Phase 1 complete. Ready for synthesis implementation.
|