Rewrite the NES synth as a 3-oscillator subtractive synth; track-tied note-offs (v0.4.1)

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>
This commit is contained in:
Reactorcoremeltdown
2026-07-18 01:05:17 +02:00
parent 9ab7660012
commit 63cad6897f
15 changed files with 915 additions and 182 deletions

View File

@@ -22,8 +22,8 @@ android {
// (android.media.midi) and AAudio low-latency audio we rely on.
minSdk = 26
targetSdk = 34
versionCode = 5
versionName = "0.3.2"
versionCode = 8
versionName = "0.4.1"
// We provide our own instrumentation runner if/when tests are added.
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"