VCA fix:
- Add cvMod scaler (like oscillator/filter have) so envelope (0-1)
is scaled by the gain param before modulating VCA
- Zero base gain when CV is connected (in rebuildGraph) so envelope
= 0 produces silence instead of falling back to base gain
- updateParam keeps cvMod in sync with gain knob
New module: CV→Gate (⚡) in Utility category:
- Converts continuous CV signal (e.g. LFO) to gate on/off
- Threshold knob (0-1, default 0.5): signal above = gate on
- Reads analyser on master clock tick for threshold comparison
- Triggers/releases connected envelopes automatically
- Use case: LFO → CV→Gate → Envelope → VCA for rhythmic gating
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The periodic audio glitches were caused by main thread starvation:
~840 events/sec during playback starved the audio buffer.
Changes:
- Master clock 480→120 Hz (still 6x headroom for 300 BPM sixteenths)
- Connection cache: replace O(n) reduce hash with dirty flag (zero work
on cache hit, flag set only when connections actually change)
- Tone.js lookAhead: 100ms→50ms for tighter scheduling
- ModuleNode LFO visualization RAF: 60fps→15fps (every 4th frame)
- ScopeDisplay RAF: 60fps→30fps (every 2nd frame)
Net effect: ~840 events/sec → ~200 events/sec during playback.
Audio processing gets 4x more main thread headroom.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _masterTicks++ counter fell behind when Tone.Clock callbacks were
delayed by GC pauses, UI interactions, or tab throttling. The counter
never recovered, causing cumulative drift between sequencers.
Now ticks are derived from the callback's time parameter (which comes
from AudioContext.currentTime — hardware clock, always precise):
ticks = Math.round((time - startTime) * MASTER_TICK_RATE)
If a callback is delayed by 50ms, the time is still correct and ticks
jump ahead to the right value. No accumulation, no drift, self-healing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: floor(elapsed * rateA) vs floor(elapsed * rateB) where
rateB = 2*rateA doesn't maintain exact 2:1 ratio due to floating-point
multiplication errors. This creates a beat/aliasing pattern where
sequencers at 80 and 160 BPM periodically go in and out of phase.
Fix: Master clock now uses an integer tick counter (_masterTicks++)
instead of floating-point elapsed time. Sequencers derive steps via:
stepIdx = floor(ticks / ticksPerStep) % numSteps
where ticks is an integer — no floating-point accumulation possible.
Also bumped master clock to 480 Hz for cleaner division at common BPMs:
80 BPM: 480*60/320 = 90 ticks/step (exact)
120 BPM: 480*60/480 = 60 ticks/step (exact)
160 BPM: 480*60/640 = 45 ticks/step (exact)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _masterTime was captured from Tone.now() BEFORE the clock started,
but the time parameter in Tone.Clock callbacks comes from a different
scheduler timeline. This caused elapsed to drift systematically.
Now _masterTime is set from the first callback's own time parameter,
guaranteeing both are on the exact same clock source. Zero drift.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Master clock 960→240 Hz: reduces CPU/GC pressure by 4x while still
providing 12x headroom for 300 BPM sixteenths
- Remove Tone.getContext().setTimeout() for note-off scheduling —
these accumulated over time causing periodic hiccups
- Note-off now happens at step boundary: previous gate turned off
at the start of each new step (cleaner, zero accumulation)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace independent Tone.Clock per sequencer/pianoroll with a single
shared master clock running at 960 Hz in audioEngine.
Architecture:
- Master clock starts/stops with audio engine (startAudio/stopAudio)
- Widgets subscribe via subscribeTick(id, callback) receiving
(audioTime, elapsed) on every tick
- Each widget derives its own step/position from elapsed time and
its own BPM, so different BPMs stay perfectly in sync
- BPM/steps/bars changes are read from refs (no clock restart needed)
Benefits:
- All timing derived from one clock source = zero relative drift
- No clock recreation on param changes = no glitches
- 960 Hz tick rate ≈ 1ms precision (plenty for musical timing)
- Sequencer at 80 BPM and 160 BPM maintain perfect 1:2 ratio forever
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keyboard fullscreen:
- Double-tap keyboard widget to enter fullscreen piano mode
- 2-octave touch-friendly piano with labeled keys
- Active key highlights cyan, close button to exit
Drum Pad module (🥁):
- New module type with 4x4 colored pad grid
- Each pad triggers a unique frequency (C2-D4 range)
- Outputs freq + gate signals (same as keyboard)
- Double-tap for fullscreen pad mode with large touch targets
- Color-coded pads with hit animation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix pianoroll/sequencer Transport not resetting on stop/restart (notes
were scheduled in the past and never fired)
- Stop and cancel Transport in stopAudio() to prevent stale events
- Add zoom +/- buttons to scope widget (6 levels, 64–2048 samples)
- Increase scope analyser buffer from 256 to 2048 for wider time view
- Add vertical grid lines to scope display
- Add "Limpiar" clear canvas button to PuzzleView
- Skip audio-graph connection for keyboard/seq/pianoroll freq→osc freq
(direct frequency setting prevents inaudible ultrasonic values)
- Auto-trigger envelopes without gate connections for noise/ambient levels
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix LFO→Filter cutoff: add scaling Gain nodes so LFO (-1..1) maps to
meaningful Hz modulation (±cutoff value). Same fix for LFO→Osc freq.
Mod scale updates dynamically when user changes the base param value.
- Visual modulation indicator: knobs receiving LFO/modulation show a
pulsing dashed ring animation (spin + pulse) around the knob arc
- Persist hint usage per level: using a hint permanently caps that level
at 2 stars — survives reload/restart. No more cheating by restarting!
- Hint state stored in separate localStorage key (synthquest-hints)
- Admin reset also clears hint history
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add step sequencer (16-step with note/gate editing) and piano roll
(canvas-based MIDI editor with draw/erase tools). Includes a Megaman-style
melody in C minor. Chiptune preset now uses piano roll instead of keyboard.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>