AU plays HEXAWE voices as factory presets; fix crashes on load and state restore #6

Merged
peterswimm merged 1 commit from feat/hexawe-runtime-voice into main 2026-10-14 23:11:08 +00:00
Owner

Summary

The AU now plays the HEXAWE patches. Each one shows up as a factory preset and plays its LittleGPTracker voice from #5. This PR also fixes two crashes that are already on main: the AU couldn't be created, and it couldn't restore saved state.

Voices as presets

  • Bundling: presets/hexawe is copied into the extension as a folder.
  • Preset list: Init (the wavetable engine) comes first, then all 212 voices.
    • Names come from each voice's credit, in the form <release>[ / <song>] <instrument> · <artist>.
    • For example: hogtypet 06 · k9d n BuZ or LIVE @ SAVE FILE / STeP 0A · KEFF.
    • Every name is unique.
  • Choosing a preset loads its voice. fullState saves the voice's path and restores it. If the saved voice is missing or won't decode, the AU falls back to the wavetable engine rather than keeping the previous voice.
  • Host tempo (from musicalContextBlock) drives the voice's tables. With no host tempo, the voice keeps its song's own tempo.
  • Loading while audio runs is safe. The kernel takes up a new voice through a try-lock hand-off at its next note or block, and voices it replaces are released off the render thread.

Crashes fixed (already on main)

  • The AU threw on creation. Every parameter identifier (sample.index, …) contained ., which AudioToolbox reserves as a delimiter.
    • Fix: each parameter is now identified by the part after its section, so index lives in group sample.
    • Its key path is still the OPSL identifier sample.index, which opsl_parity and udg_parity depend on.
  • Restoring any saved state overflowed the stack. The tree's value provider returned param.value, and reading that calls the provider again.
    • Fix: the AU now keeps each parameter's value by address, and the provider reads it from there.

Testing

tests/lgpt/au_check.swift creates the real AU and checks each of the following:

  • Every parameter's key path is its OPSL identifier, and every parameter reports its default.
  • The presets are Init then all 212 voices, numbered in order, each named from its credit, with no duplicate names.
  • A chosen voice, rendered through internalRenderBlock at a host tempo of 97 BPM, matches the same voice rendered directly, sample for sample.
  • fullState restores both the voice and parameter values. Init and a missing saved voice both return to the wavetable engine.
  • Control: expecting the song's own tempo instead of the host's makes the test fail, as it should.

tests/lgpt/run.sh runs this check too. Its wavetable regression baseline now builds main's kernel together with main's LGPTVoice.swift.

Other checks:

  • tests/lgpt/run.sh (full): all checks pass, and LGPT still matches exactly on 418 of 418 comparable renders.
  • CLAP parity: the CLAP and AU render the same output.
  • udg_parity and opsl_parity both pass.
  • Build: xcodebuild for the iOS Simulator succeeds, and the .appex contains hexawe/.

Not covered

  • The AU's own view has no preset picker. Presets are chosen from the host's preset menu.
  • AU parameters still don't affect the LGPT voice.
  • The CLAP doesn't have LGPT voices yet.
## Summary The AU now plays the HEXAWE patches. Each one shows up as a factory preset and plays its LittleGPTracker voice from #5. This PR also fixes two crashes that are already on `main`: the AU couldn't be created, and it couldn't restore saved state. ### Voices as presets - **Bundling:** `presets/hexawe` is copied into the extension as a folder. - **Preset list:** Init (the wavetable engine) comes first, then all 212 voices. - Names come from each voice's credit, in the form `<release>[ / <song>] <instrument> · <artist>`. - For example: `hogtypet 06 · k9d n BuZ` or `LIVE @ SAVE FILE / STeP 0A · KEFF`. - Every name is unique. - **Choosing a preset** loads its voice. `fullState` saves the voice's path and restores it. If the saved voice is missing or won't decode, the AU falls back to the wavetable engine rather than keeping the previous voice. - **Host tempo** (from `musicalContextBlock`) drives the voice's tables. With no host tempo, the voice keeps its song's own tempo. - **Loading while audio runs is safe.** The kernel takes up a new voice through a try-lock hand-off at its next note or block, and voices it replaces are released off the render thread. ### Crashes fixed (already on main) - **The AU threw on creation.** Every parameter identifier (`sample.index`, …) contained `.`, which AudioToolbox reserves as a delimiter. - Fix: each parameter is now identified by the part after its section, so `index` lives in group `sample`. - Its key path is still the OPSL identifier `sample.index`, which `opsl_parity` and `udg_parity` depend on. - **Restoring any saved state overflowed the stack.** The tree's value provider returned `param.value`, and reading that calls the provider again. - Fix: the AU now keeps each parameter's value by address, and the provider reads it from there. ## Testing `tests/lgpt/au_check.swift` creates the real AU and checks each of the following: - Every parameter's key path is its OPSL identifier, and every parameter reports its default. - The presets are Init then all 212 voices, numbered in order, each named from its credit, with no duplicate names. - A chosen voice, rendered through `internalRenderBlock` at a host tempo of 97 BPM, matches the same voice rendered directly, sample for sample. - `fullState` restores both the voice and parameter values. Init and a missing saved voice both return to the wavetable engine. - **Control:** expecting the song's own tempo instead of the host's makes the test fail, as it should. `tests/lgpt/run.sh` runs this check too. Its wavetable regression baseline now builds `main`'s kernel together with `main`'s `LGPTVoice.swift`. Other checks: - **`tests/lgpt/run.sh` (full):** all checks pass, and LGPT still matches exactly on 418 of 418 comparable renders. - **CLAP parity:** the CLAP and AU render the same output. - **`udg_parity`** and **`opsl_parity`** both pass. - **Build:** `xcodebuild` for the iOS Simulator succeeds, and the `.appex` contains `hexawe/`. ## Not covered - The AU's own view has no preset picker. Presets are chosen from the host's preset menu. - AU parameters still don't affect the LGPT voice. - The CLAP doesn't have LGPT voices yet.
The AU now plays the HEXAWE patches' LGPT voices:
- presets/hexawe is bundled into the extension; every *.lgpt.json is a
  factory preset after Init (the wavetable engine), named from its credit.
- Choosing a preset loads its voice; fullState saves the voice's path and
  restores it, falling back to the wavetable engine if it is missing.
- The host tempo (musicalContextBlock) drives the voice's tables; with no
  host tempo the voice keeps its song's tempo.
- The kernel takes up a new voice through a try-lock hand-off at its next
  note or block, so loading while audio runs never swaps the voice
  mid-render, and replaced voices are released off the render thread.

Two bugs already on main, found by instantiating the AU in a test:
- Every AUParameter identifier ("sample.index") contained ".", which
  AudioToolbox reserves, so creating the AU threw. Parameters now use the
  part after their section ("index" in group "sample"), so their key
  paths are still the OPSL identifiers.
- The tree's value provider returned param.value, which calls the
  provider again; restoring any saved state overflowed the stack. Values
  are now kept per address and the provider reads those.

tests/lgpt/au_check.swift checks all of this through the real AU and render
block (control: expecting the song's tempo must fail); run.sh runs it and
now builds main's kernel with main's LGPTVoice for the regression baseline.

Co-Authored-By: Claude <noreply@anthropic.com>
peterswimm deleted branch feat/hexawe-runtime-voice 2026-10-14 23:11:08 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
peterswimm/01nk!6
No description provided.