Skip to content

Stage 3: Avalonia GUI on macOS (adopt upstream PR #272 + CoreAudio wiring) - #3

Merged
ledogar merged 44 commits into
masterfrom
avalonia-gui
Jul 29, 2026
Merged

Stage 3: Avalonia GUI on macOS (adopt upstream PR #272 + CoreAudio wiring)#3
ledogar merged 44 commits into
masterfrom
avalonia-gui

Conversation

@ledogar

@ledogar ledogar commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Adopts upstream PR #272 (jopdorp's Avalonia editor, plugin core, LV2 plugins) and makes it a working macOS GUI. Two commits: a faithful merge of the PR branch, then our wiring and fixes on top.

Why adopt rather than write

Evaluated before merging — every claim measured on this machine:

  • The whole LiveSPICE.Linux.sln builds clean on macOS arm64, first try. net8.0, Avalonia 11.3.6, no Linux-only packages. (X11PlatformOptions in Program.cs is inert off X11.)
  • The GUI launches and runs on macOS.
  • The merge against our master is conflict-free, and the circuit suite still passes 49/49 after it (their only Circuit/ changes are a factory class and cosmetic resistor label positions).

What this adds on top

CoreAudio wiring. Audio.Driver.Drivers reflects over loaded assemblies, and .NET loads lazily — a ProjectReference alone does nothing. AvaloniaAudioDrivers.Available() now forces the load, so the GUI's audio config sees every Core Audio device (verified: all 6 on this machine, including the aggregate). One subtlety: the load-touch is GC.KeepAlive(typeof(...)) because a discarded typeof gets elided in Release builds — the naive version passed Debug and failed Release. A discovery test pins the behaviour, asserting on the driver rather than its devices so it also holds on CI runners with no audio hardware.

Test suite actually runs. Upstream's suite hung indefinitely as a whole and failed 3 tests even per-class: each class guarded AppBuilder.Setup behind a class-local flag, but Setup is once-per-process, and desktop SetupWithoutStarting never starts a dispatcher loop, so Dispatcher.UIThread.Invoke could block forever. Replaced with Avalonia.Headless.XUnit — one [AvaloniaTestApplication] per process, [AvaloniaFact] for tests touching controls/windows. 41/41 pass in ~2 s, headless, now wired into the macOS CI job.

Decisions and follow-ups

  • Two offline renderers now exist: our LiveSPICE.CLI (also does live playback, device enumeration, loopback) and the merged LiveSPICE.Headless (render-only). The CLI is the supported tool; LiveSPICE.Headless is deliberately untouched to avoid merge friction while upstream Add Linux GUI, audio, and LV2 plugin port dsharlet/LiveSPICE#272 is still open. Folding them together is a follow-up.
  • LiveSPICE.PluginCore.SimulationProcessor lacks the two live-path fixes our CLI's SimulationHost has: pre-warming the compiled expression tree off the audio thread, and SimulationDiverged recovery. The GUI's live path inherits those gaps; porting the fixes into PluginCore (and retiring SimulationHost) is the natural next change.
  • Cosmetic: their LinuxAudioDriver appears via reflection as an empty "PipeWire/JACK" driver on macOS, exactly as our CoreAudio driver would on Linux. Symmetric, harmless, left alone.

Verification

  • LiveSPICE.Linux.sln and LiveSPICE.Core.sln build clean on macOS arm64.
  • Avalonia suite 41/41 (~2 s), including the new discovery tests.
  • Circuit suite 49/49 against baselines post-merge.
  • GUI launches and stays alive with CoreAudio wired.

🤖 Generated with Claude Code

jopdorp and others added 14 commits May 7, 2026 09:45
Brings in jopdorp's linux-gui-port branch (upstream PR dsharlet#272): an Avalonia
schematic editor, a shared LiveSPICE.PluginCore, a LiveSPICE.Headless
renderer, and native LV2 plugin bundles. Verified before merging: the whole
LiveSPICE.Linux.sln builds and the GUI runs on macOS arm64, and the merge
is conflict-free against our macOS work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Avalonia GUI from PR dsharlet#272 discovers audio backends through
Audio.Driver.Drivers, which reflects over loaded assemblies - and .NET
loads assemblies lazily, so a ProjectReference alone is not enough. Add
the reference and force the load in AvaloniaAudioDrivers.Available (via
GC.KeepAlive: a discarded typeof gets elided in Release builds). With
that, the GUI's audio configuration sees every Core Audio device on
macOS. A discovery test asserts the driver is present, on the driver
rather than its devices so it also holds on CI runners with no audio
hardware.

The test suite previously hung when run as a whole and had 3 failures
even per class: each test class guarded AppBuilder.Setup with its own
class-local flag, but Setup is once-per-process ("Setup was already
called"), and desktop SetupWithoutStarting never starts a dispatcher
loop, so Dispatcher.UIThread.Invoke could hang forever. Replace the
per-class setup with Avalonia.Headless.XUnit: one headless application
per process via [AvaloniaTestApplication], and [AvaloniaFact] on the
tests that touch controls or windows so they run on the headless
dispatcher thread. The full suite now passes 41/41 in ~2 s, needs no
display, and runs in the macOS CI job.

Both LiveSPICE.CLI and the merged LiveSPICE.Headless can render offline;
the CLI is the supported tool (it also does live playback and device
enumeration). LiveSPICE.Headless is kept untouched to avoid merge
friction while upstream PR dsharlet#272 is still open; folding them together is
a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ledogar
ledogar merged commit e5fbf73 into master Jul 29, 2026
4 checks passed
@ledogar
ledogar deleted the avalonia-gui branch July 29, 2026 04:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants