Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds an additive Linux port of LiveSPICE alongside the existing Windows/WPF application: a new Avalonia-based editor, Linux audio backends, a shared plugin-core for Linux plugin experiments, a native LV2 bundle (GTK3 UI + example plugins), and a Linux-focused solution file. It also factors shared audio-simulation construction into Circuit.AudioSimulationFactory and wires it into the existing VST simulation path.
Changes:
- Added
LiveSPICE.Avalonia(GUI editor + simulation scope + Linux audio configuration, including JACK/PipeWire discovery) andLiveSPICE.Avalonia.Tests. - Added Linux-facing plugin components (
LiveSPICE.PluginCore,LiveSPICE.PluginLinux) plus a native LV2 bundle underNative/LiveSPICE.LV2. - Added/updated solution and documentation for Linux/headless builds, and introduced
Circuit.AudioSimulationFactoryto unify audio simulation setup.
Reviewed changes
Copilot reviewed 54 out of 54 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Documents submodule init and headless Linux build/run examples. |
| Native/LiveSPICE.LV2/README.md | Documents native LV2 bundle, build/test/install, and URIs. |
| Native/LiveSPICE.LV2/manifest.ttl | Declares LV2 plugin entries for MXR Phase 90 and Generic shell. |
| Native/LiveSPICE.LV2/Makefile | Build/test/install targets for LV2 plugins and GTK UI. |
| Native/LiveSPICE.LV2/livespice_mxr_phase90.ttl | LV2 metadata for the MXR Phase 90-style plugin. |
| Native/LiveSPICE.LV2/livespice_mxr_phase90.c | Native LV2 DSP + state save/restore for the phaser. |
| Native/LiveSPICE.LV2/livespice_generic.ttl | LV2 metadata for the generic schematic-loader shell + GTK3 UI. |
| Native/LiveSPICE.LV2/livespice_generic.c | Native LV2 passthrough shell with atom event parsing and state. |
| Native/LiveSPICE.LV2/livespice_generic_ui.c | GTK3 LV2 UI for schematic loading and discovered controls. |
| Native/LiveSPICE.LV2/.gitignore | Ignores native LV2 build artifacts. |
| LiveSPICEVst/SimulationProcessor.cs | Uses AudioSimulationFactory for shared audio simulation setup. |
| LiveSPICE.sln | Adds LiveSPICE.Headless project to the main solution. |
| LiveSPICE.PluginLinux/LiveSPICELinuxPlugin.cs | Linux plugin host using shared processor + Avalonia editor window. |
| LiveSPICE.PluginLinux/LiveSPICE.PluginLinux.csproj | New net8 project for Linux plugin experiment (AudioPlugSharp). |
| LiveSPICE.PluginCore/Wrappers/PotWrapper.cs | Wrapper for grouped pot controls with update tracking. |
| LiveSPICE.PluginCore/Wrappers/MultiThrowWrapper.cs | Wrapper for multi-position switches with rebuild tracking. |
| LiveSPICE.PluginCore/Wrappers/IComponentWrapper.cs | Common wrapper interface for interactive components. |
| LiveSPICE.PluginCore/Wrappers/DoubleThrowWrapper.cs | Wrapper for 2-position switch behavior. |
| LiveSPICE.PluginCore/Wrappers/ComponentWrapper.cs | Base wrapper supporting multi-section grouping. |
| LiveSPICE.PluginCore/SimulationProcessor.cs | Shared processor for schematic load + async simulation rebuild/update. |
| LiveSPICE.PluginCore/PluginProgramParameters.cs | State serialization model for schematic path + controls/settings. |
| LiveSPICE.PluginCore/LiveSPICE.PluginCore.csproj | New shared plugin/core library project references. |
| LiveSPICE.Linux.sln | Adds a Linux-centric solution for Avalonia + plugin experiments. |
| LiveSPICE.Headless/WaveFile.cs | WAV IO for headless processing. |
| LiveSPICE.Headless/Program.cs | Console runner for headless simulation (sine or WAV input). |
| LiveSPICE.Headless/LiveSPICE.Headless.csproj | New net8 headless executable project. |
| LiveSPICE.Avalonia/WaveformWindow.cs | Simulation scope UI + live audio streaming integration. |
| LiveSPICE.Avalonia/VirtualAudioDriver.cs | Virtual loopback audio driver for testing/validation. |
| LiveSPICE.Avalonia/SchematicDocument.cs | Document model with undo/redo and dirty/external-change tracking. |
| LiveSPICE.Avalonia/SchematicCanvas.cs | Avalonia schematic renderer/editor interaction layer. |
| LiveSPICE.Avalonia/SavePromptWindow.cs | Unsaved-changes prompt dialog. |
| LiveSPICE.Avalonia/PropertyInspector.cs | Lightweight property editor for selected objects. |
| LiveSPICE.Avalonia/Program.cs | Avalonia app entrypoint and platform configuration. |
| LiveSPICE.Avalonia/PluginEditorWindow.cs | Plugin-style editor window with overlay controls. |
| LiveSPICE.Avalonia/MainWindow.cs | Main Avalonia editor shell: tabs, menus, edit commands, simulation entry. |
| LiveSPICE.Avalonia/LiveSPICE.Avalonia.csproj | New net8 Avalonia GUI project with references. |
| LiveSPICE.Avalonia/LiveAudioProcessor.cs | Live audio processing wrapper around AudioSimulationFactory. |
| LiveSPICE.Avalonia/LinuxAudioDriver.cs | JACK/PipeWire discovery + JACK callback stream implementation. |
| LiveSPICE.Avalonia/ConfirmWindow.cs | Generic yes/no confirmation dialog. |
| LiveSPICE.Avalonia/AvaloniaAudioDrivers.cs | Driver discovery/aggregation for Avalonia build. |
| LiveSPICE.Avalonia/AudioConfigWindow.cs | UI for driver/device/channel selection + test stream. |
| LiveSPICE.Avalonia/AppSettings.cs | Persistent settings (recent files, window size, audio selection). |
| LiveSPICE.Avalonia/App.cs | Avalonia Application initialization. |
| LiveSPICE.Avalonia.Tests/SimulationAndAudioTests.cs | Tests for simulation factory, drivers, and live processing. |
| LiveSPICE.Avalonia.Tests/SchematicCanvasInteractionTests.cs | Tests for selection, move, wire tool, undo/redo, copy/paste. |
| LiveSPICE.Avalonia.Tests/PluginPortTests.cs | Tests for Linux plugin editor + state round-tripping and processing. |
| LiveSPICE.Avalonia.Tests/MenuOpenPathTests.cs | Tests for file URI path decoding and startup doc behavior. |
| LiveSPICE.Avalonia.Tests/LiveSPICE.Avalonia.Tests.csproj | New xUnit test project for Avalonia + plugin components. |
| LiveSPICE.Avalonia.Tests/AssemblyInfo.cs | Disables parallelization for UI-sensitive tests. |
| LiveSPICE.Avalonia.Tests/AppSettingsTests.cs | Tests settings persistence and recent file behavior. |
| GUI_PORT_PLAN.md | Updated plan/notes for cross-platform GUI work. |
| Circuit/Components/Resistor.cs | Adjusts resistor label positioning for rendering parity. |
| Circuit/Circuit.csproj | Adds package references needed by new functionality. |
| Circuit/AudioSimulationFactory.cs | Centralizes “audio simulation” solve/create logic (inputs/outputs). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| install: all | ||
| mkdir -p $(PREFIX) | ||
| rm -rf $(PREFIX)/LiveSPICE-MXR-Phase90.lv2 | ||
| cp -r $(BUNDLE) $(PREFIX)/ | ||
|
|
| uint8_t buffer[4096]; | ||
| lv2_atom_forge_set_buffer(&self->forge, buffer, sizeof(buffer)); | ||
| LV2_Atom_Forge_Ref ref = lv2_atom_forge_path(&self->forge, path, (uint32_t)strlen(path) + 1); | ||
| if (ref == 0) | ||
| return; | ||
|
|
||
| LV2_Atom* atom = lv2_atom_forge_deref(&self->forge, ref); | ||
| self->write(self->controller, PORT_CONTROL_EVENTS, lv2_atom_total_size(atom), self->atom_event_transfer, atom); | ||
| } |
| static gboolean knob_scroll(GtkWidget* widget, GdkEventScroll* event, gpointer data) | ||
| { | ||
| KnobControl* knob = (KnobControl*)data; | ||
| double delta = event->direction == GDK_SCROLL_UP ? 0.025 : -0.025; | ||
| knob->value = clamp_unit(knob->value + delta); |
| lv2:extensionData state:interface ; | ||
| lv2:optionalFeature lv2:hardRTCapable ; | ||
| livespice:schematicPath "" ; |
| wrapper = button.NumPositions == 2 | ||
| ? new DoubleThrowWrapper(button, button.Group) | ||
| : new MultiThrowWrapper(button, component.Name); | ||
| buttonGroups[button.Group] = wrapper; |
| case MultiThrowWrapper multiThrowWrapper: | ||
| ComboBox comboBox = CreateOptionBox(new[] { 0, 1, 2 }, multiThrowWrapper.Position); | ||
| comboBox.SelectionChanged += (_, _) => |
| {51F8BCEC-5C29-46BA-8448-06B516715840}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {51F8BCEC-5C29-46BA-8448-06B516715840}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {51F8BCEC-5C29-46BA-8448-06B516715840}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {51F8BCEC-5C29-46BA-8448-06B516715840}.Release|Any CPU.Build.0 = Release|Any CPU |
| RedirectStandardError = true, | ||
| UseShellExecute = false, | ||
| CreateNoWindow = true | ||
| }; | ||
| using Process process = Process.Start(startInfo)!; | ||
| string output = process.StandardOutput.ReadToEnd(); | ||
| if (!process.WaitForExit(1000) || process.ExitCode != 0) | ||
| return Array.Empty<string>(); | ||
|
|
| JackNative.jack_connect(client, input[i].Name, JackNative.PortName(inputPorts[i])); | ||
| for (int i = 0; i < output.Length; i++) | ||
| JackNative.jack_connect(client, JackNative.PortName(outputPorts[i]), output[i].Name); |
Stage 3: Avalonia GUI on macOS (adopt upstream PR dsharlet#272 + CoreAudio wiring)
Records the known problems in this fork, from the design and documentation adherence review, so they are not rediscovered. Each item is marked as originating in this fork or in upstream PR dsharlet#272, because the latter are worth reporting back rather than only fixing locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Not a maintainer — just a data point that might help this PR, plus some findings. Your Avalonia port also works on macOS, essentially unmodifiedI've been porting LiveSPICE to macOS (Apple Silicon) and evaluated this branch as a starting point rather than writing an Avalonia app from scratch. It went far better than expected:
So the "Linux GUI port" label undersells it — it's a cross-platform GUI port that happens to have been developed on Linux. Might be worth saying so in the PR title/description, since that materially changes its value to the project. A macOS audio backend drops straight in. Things you'd probably want to knowFound while reviewing before adopting. All still present as of
Happy to open PRs against your branch for any of these if useful — the test-isolation fix in particular is self-contained. My working versions are at https://github.com/ledogar/LiveSPICE if you want to lift anything; treat it as reference rather than something to merge, since it's tangled with macOS-specific work. Thanks for doing this — it saved me weeks. |
|
@ledogar yeah please do open those pr's! thanks for the find and fixes! :) |
The test suite hangs when run as a whole, and three tests fail even when each class is run on its own. Both come from how Avalonia is started for the tests. Each class guards AppBuilder.Setup behind its own class-local flag, but Setup is once-per-process, so the second class to run throws "Setup was already called". And the desktop SetupWithoutStarting never starts a dispatcher loop, so a Dispatcher.UIThread.Invoke from a test can wait for a pump that will never run - which is the hang. Avalonia.Headless.XUnit exists for exactly this: [AvaloniaTestApplication] sets up one headless application per process, and [AvaloniaFact] runs a test on the headless dispatcher thread, so UI work has somewhere to run. Tests that touch controls or windows are marked [AvaloniaFact]; the rest stay plain [Fact]. The per-class EnsureAvalonia helpers are no longer needed. Also means the suite needs no display, so it can run in CI. Before: hangs past 120s, 3 failures logged on the way. After: 39/39 in about a second. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The palette lists component *types* - a generic BJT, Diode, JFET - built from a hardcoded array plus a reflection scan. The part libraries in Circuit/Components/*.xml are never read, so the ~87 parts in them cannot be reached from the editor: placing a 2N3904 means dropping a bare BJT and typing IS, BF and BR by hand, and the tube models are effectively unusable. GUI_PORT_PLAN.md:85 lists component library insertion as Phase 3 work, so this looked known-incomplete rather than deliberate. ComponentLibrary reads the same XML the WPF app does: a Library element of Component elements, each handed to Circuit.Component.Deserialize. The palette becomes a tree of collapsible categories, because a flat list would put the generic Diode at the same level as the sixteen specific ones with only a trailing "s" to tell them apart - and 110 entries is too many to scan. The tiers follow the WPF app (ComponentLibrary.xaml.cs builds Common, then Generic, then the libraries), including its names, so the layout is familiar to someone coming from Windows: Common (10) everyday parts, expanded by default Generic (22) the reflection-discovered component classes Diodes (16), Op-Amps (12), Transistors (47), Tubes (12) Both Diode entries earn their place: the generic one is what you need for a part the libraries do not carry, or to hand-tune a model. Headers carry counts, and a filter box narrows the list - filtering expands every category that matched, since results hidden behind a collapsed header would defeat the search. Each LibraryPart keeps its source XElement rather than a deserialized prototype, and deserializes afresh per placement: components are mutable, so a shared instance would let an edit to one placed part appear in the next one. There is a test for that. Also fixes the reason the libraries would not have been found even with this code: Circuit.csproj marked all four XML files as Content but set CopyToOutputDirectory on Tubes.xml alone, so a host looking beside its executable saw one library out of four. Verified on this branch: builds clean, all four libraries reach the output, ComponentLibraryTests 3/3, SchematicCanvasInteractionTests 10/10, AppSettingsTests 3/3, and the palette renders as described. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run the Avalonia tests headless so the suite completes
Show the component libraries in the palette
|
@ledogar thanks, I merged them in! |
Summary
Adds a Linux-focused LiveSPICE port alongside the existing Windows application instead of replacing it.
This branch introduces an Avalonia desktop editor, Linux audio configuration and live simulation support, a shared plugin core for Linux plugin experiments, and native LV2 plugin bundles with a GTK3 UI. The main Windows solution and Windows WPF/VST UI paths are intentionally left unchanged; Linux-specific projects live in
LiveSPICE.Linux.sln.Highlights
LiveSPICE.Avaloniaas a Linux desktop editor with schematic loading, editing, property inspection, waveform viewing, and live audio controls.LiveSPICE.Avalonia.Testscoverage for editor interaction, launch/open behavior, settings, audio simulation flow, plugin port behavior, and GUI parity checks.LiveSPICE.PluginCoreandLiveSPICE.PluginLinuxfor Linux plugin-facing simulation/control logic without changing the Windows VST UI.Native/LiveSPICE.LV2, including an MXR Phase 90 plugin and a generic schematic-loader plugin shell.LiveSPICE.Linux.slnso Linux-specific projects can be built independently from the Windows WPF solution.linuxbase after the final cleanup commit.Validation
dotnet build LiveSPICE.Linux.sln --no-restoremake -C Native/LiveSPICE.LV2 clean all test ui-smoke install.carla-single lv2 https://livespice.org/plugins/generic..sln,.xaml, and.csprojfiles were reported unsupported by the configured Codacy tools.Notes for reviewers
The Linux GUI/plugin work is intentionally additive. Reviewers can focus on:
LiveSPICE.Avalonia/*LiveSPICE.Avalonia.Tests/*LiveSPICE.PluginCore/*LiveSPICE.PluginLinux/*Native/LiveSPICE.LV2/*LiveSPICE.Linux.slnThe native generic LV2 plugin currently provides host-loadable state/UI plumbing and pass-through audio; full schematic DSP execution inside the native LV2 shell remains follow-up work.
The existing Windows WPF application and Windows VST UI are not part of this port and were verified unchanged against the
linuxbranch forLiveSPICE.sln,LiveSPICE/,LiveSPICEVst/,MockVst/, andSchematicControls/.