Two changes that landed together (the note-off work was uncommitted when the synth
rewrite began):
Synth rewrite — replace the simple NES tone generator with a classic Minimoog-style
voice: 3 oscillators (+ noise) → mixer → resonant 4-pole ladder low-pass (own
contour envelope + key tracking) → amplifier (own envelope), plus a tempo-synced
LFO and a 4-slot modulation matrix.
- DSP: SynthVoice rewritten (PolyBLEP oscillators, ladder filter, two ADSRs, LFO,
mod matrix); new SynthPatch parses a slot into an immutable snapshot per note.
PolyBLEP + control-rate cutoff + precomputed osc tuning keep the RT cost down.
- Model: SynthDefs (shared option lists/defaults), rewritten NES_SYNTH params,
NesSynthPresets (8 chiptune factory presets) seeded into the preset library.
- Editor: NesSynthEditor — swipeable cards (Oscillators / Filter / Amp+LFO / Mod
Matrix) with dot pagination; new rotary Knob control (absolute, fader-like
vertical drag: bottom = empty, top = full); enums via RetroDropdown. The shared
slot-audition keyboard (renamed SoundFontKeyboard -> SlotAuditionKeyboard) is
pinned at the editor bottom.
- Engine builds a SynthPatch per note. The enum id stays NES_SYNTH so existing
projects still route; the old single `wave` param is dropped (amp envelope keys
carry over).
Track-tied note-offs — a note-off ("===") is now tied to its track, not a channel:
it has no velocity/channel of its own (shown as ".." and non-editable) and simply
releases whatever that track last played, on the bus(es) that note routed to
(AudioEngine.trackLastChannel). `.sng` export writes "OFF .. ..".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>