Skip to content

Add Linux GUI, audio, and LV2 plugin port - #272

Open
jopdorp wants to merge 42 commits into
dsharlet:masterfrom
jopdorp:linux-gui-port
Open

Add Linux GUI, audio, and LV2 plugin port#272
jopdorp wants to merge 42 commits into
dsharlet:masterfrom
jopdorp:linux-gui-port

Conversation

@jopdorp

@jopdorp jopdorp commented May 7, 2026

Copy link
Copy Markdown

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

  • Adds LiveSPICE.Avalonia as a Linux desktop editor with schematic loading, editing, property inspection, waveform viewing, and live audio controls.
  • Adds Linux audio backends and configuration flow, including JACK-oriented device naming and a virtual audio mode for validation.
  • Adds LiveSPICE.Avalonia.Tests coverage for editor interaction, launch/open behavior, settings, audio simulation flow, plugin port behavior, and GUI parity checks.
  • Adds LiveSPICE.PluginCore and LiveSPICE.PluginLinux for Linux plugin-facing simulation/control logic without changing the Windows VST UI.
  • Adds native LV2 plugin bundles under Native/LiveSPICE.LV2, including an MXR Phase 90 plugin and a generic schematic-loader plugin shell.
  • Adds a GTK3 LV2 UI with schematic selection, discovered controls, Windows-style plugin presentation, knob scales/ticks/labels, and host smoke-test support.
  • Adds LiveSPICE.Linux.sln so Linux-specific projects can be built independently from the Windows WPF solution.
  • Keeps Windows-facing paths unchanged relative to the linux base after the final cleanup commit.

Validation

  • dotnet build LiveSPICE.Linux.sln --no-restore
  • Focused Avalonia tests for schematic interaction, settings, and menu-open behavior passed: 17 tests.
  • Native LV2 build/test/UI smoke/install flow passed with make -C Native/LiveSPICE.LV2 clean all test ui-smoke install.
  • Carla was able to load the generic LV2 plugin with carla-single lv2 https://livespice.org/plugins/generic.
  • Codacy analysis was clean on edited C# files where supported. .sln, .xaml, and .csproj files 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.sln

The 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 linux branch for LiveSPICE.sln, LiveSPICE/, LiveSPICEVst/, MockVst/, and SchematicControls/.

Copilot AI review requested due to automatic review settings May 7, 2026 09:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and LiveSPICE.Avalonia.Tests.
  • Added Linux-facing plugin components (LiveSPICE.PluginCore, LiveSPICE.PluginLinux) plus a native LV2 bundle under Native/LiveSPICE.LV2.
  • Added/updated solution and documentation for Linux/headless builds, and introduced Circuit.AudioSimulationFactory to 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.

Comment on lines +48 to +52
install: all
mkdir -p $(PREFIX)
rm -rf $(PREFIX)/LiveSPICE-MXR-Phase90.lv2
cp -r $(BUNDLE) $(PREFIX)/

Comment on lines +608 to +616
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);
}
Comment on lines +509 to +513
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);
Comment on lines +15 to +17
lv2:extensionData state:interface ;
lv2:optionalFeature lv2:hardRTCapable ;
livespice:schematicPath "" ;
Comment on lines +211 to +214
wrapper = button.NumPositions == 2
? new DoubleThrowWrapper(button, button.Group)
: new MultiThrowWrapper(button, component.Name);
buttonGroups[button.Group] = wrapper;
Comment on lines +257 to +259
case MultiThrowWrapper multiThrowWrapper:
ComboBox comboBox = CreateOptionBox(new[] { 0, 1, 2 }, multiThrowWrapper.Position);
comboBox.SelectionChanged += (_, _) =>
Comment thread LiveSPICE.sln Outdated
Comment on lines +95 to +98
{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
Comment on lines +264 to +272
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>();

Comment thread LiveSPICE.Avalonia/LinuxAudioDriver.cs Outdated
Comment on lines +104 to +106
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);
ledogar added a commit to ledogar/LiveSPICE that referenced this pull request Jul 29, 2026
Stage 3: Avalonia GUI on macOS (adopt upstream PR dsharlet#272 + CoreAudio wiring)
ledogar pushed a commit to ledogar/LiveSPICE that referenced this pull request Jul 29, 2026
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>
@ledogar

ledogar commented Jul 29, 2026

Copy link
Copy Markdown

Not a maintainer — just a data point that might help this PR, plus some findings.

Your Avalonia port also works on macOS, essentially unmodified

I'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:

  • LiveSPICE.Linux.sln builds clean on macOS arm64, first try — GUI, headless, plugin core and tests. The X11PlatformOptions in Program.cs is inert off X11 because UsePlatformDetect() picks the native backend.
  • The editor runs, loads schematics from the command line, and renders correctly. Here's an Ibanez TS-9 from Tests/Examples, rendered on macOS via your LIVESPICE_SCREENSHOT hook — component palette, symbols, values, pots, the lot.
  • 36 of 39 tests pass when each class is run in isolation, including all 10 SchematicCanvasInteractionTests.
  • It merges cleanly with an independent macOS branch (Core Audio backend + a CLI player), no conflicts.

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. AvaloniaAudioDrivers.Available() falling through to Audio.Driver.Drivers meant my Core Audio driver needed one ProjectReference and one line to force the assembly load — no changes to your code. That reflective fallback was a good call.

Things you'd probably want to know

Found while reviewing before adopting. All still present as of 4af7ef2:

  1. The test suite hangs when run as a whole, and 3 tests fail even per-class. Each test class guards 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 can block forever. Switching to Avalonia.Headless.XUnit — one [AvaloniaTestApplication] per process, [AvaloniaFact] on tests touching controls — got it to 41/41 in ~2s, headless, which also makes it CI-able without a display.

  2. WaveformWindow.cs:221 posts to the Avalonia dispatcher from the audio callback, ~94×/sec. That's the one thing GUI_PORT_PLAN.md:176 explicitly rules out ("Keep audio callback code isolated from Avalonia dispatching"). Per callback it costs a closure allocation, a DispatcherOperation, a lock the UI thread also takes, and a run-loop wakeup — and the queue is unbounded while the UI can't sustain the post rate. A lock-free ring drained on a DispatcherTimer would fix it.

  3. InteractiveComponents is enumerated under lock (sync) on the audio thread but mutated without it by SetCircuit/ClearSchematic from the UI thread. Loading a schematic while live — or the FileSystemWatcher firing on a save — throws Collection was modified out of the audio thread. A Volatile.Write-published array snapshot fixes this and also removes a per-buffer enumerator boxing (ObservableCollection<T> has no struct enumerator).

  4. WaveformWindow.cs:190-193 solves and compiles twice on the UI thread — once at a hardcoded 48000, then again at the device's real rate, discarding the first — while audio is already running from line 192.

  5. Minor: SimulationProcessor's bypass path uses Array.CopyTo (whole source array, ignoring numSamples, throws if input is longer than output) and writes only channel 0.

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.

@jopdorp

jopdorp commented Jul 31, 2026

Copy link
Copy Markdown
Author

@ledogar yeah please do open those pr's! thanks for the find and fixes! :)

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