Stage 3: Avalonia GUI on macOS (adopt upstream PR #272 + CoreAudio wiring) - #3
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
LiveSPICE.Linux.slnbuilds clean on macOS arm64, first try.net8.0, Avalonia 11.3.6, no Linux-only packages. (X11PlatformOptionsinProgram.csis inert off X11.)masteris conflict-free, and the circuit suite still passes 49/49 after it (their onlyCircuit/changes are a factory class and cosmetic resistor label positions).What this adds on top
CoreAudio wiring.
Audio.Driver.Driversreflects over loaded assemblies, and .NET loads lazily — aProjectReferencealone 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 isGC.KeepAlive(typeof(...))because a discardedtypeofgets 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.Setupbehind a class-local flag, but Setup is once-per-process, and desktopSetupWithoutStartingnever starts a dispatcher loop, soDispatcher.UIThread.Invokecould block forever. Replaced withAvalonia.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
LiveSPICE.CLI(also does live playback, device enumeration, loopback) and the mergedLiveSPICE.Headless(render-only). The CLI is the supported tool;LiveSPICE.Headlessis 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.SimulationProcessorlacks the two live-path fixes our CLI'sSimulationHosthas: pre-warming the compiled expression tree off the audio thread, andSimulationDivergedrecovery. The GUI's live path inherits those gaps; porting the fixes into PluginCore (and retiringSimulationHost) is the natural next change.LinuxAudioDriverappears 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.slnandLiveSPICE.Core.slnbuild clean on macOS arm64.🤖 Generated with Claude Code