Architecture

Modern Meeter is a WinUI 3 MSIX-packaged app that controls VoiceMeeter (Basic/Banana/Potato) through its native DLL API. It follows MVVM with CommunityToolkit.Mvvm source generators.

Project layout

src/
  ModernMeeter/           App project (WinUI 3 MSIX)
  ModernMeeter.Core/      Core library (Interop, Models, Services — no WinUI)
  ModernMeeter.CmdPal/    Command Palette extension (CommandPalette SDK)
tests/
  ModernMeeter.Tests/     Test project (references Core, shared-compiles app sources)
ModernMeeter.slnx         Solution file
Deploy.ps1                Deployment script (local + remote)

The project was split from a single project into three so the VoiceMeeter API is reusable and the Command Palette SDK dependencies are isolated from the main app.

Layering (bottom-up)

  1. Interop (ModernMeeter.Core) — LibraryImport P/Invoke to VoicemeeterRemote64.dll with a registry-based DLL resolver. Never called directly outside VoiceMeeterClient.
  2. Services
    • CoreIVoiceMeeterClient / VoiceMeeterClient wrap P/Invoke into a typed C# API. AppSettings is the settings POCO.
    • AppLocalSettingsStore (IWritableOptions<AppSettings>) persists settings via ApplicationData.LocalSettings. MidiMapService reads MIDI mapping XML. FileLoggerProvider writes a rotating log file.
  3. Models
    • CoreVoiceMeeterLayout, AudioDevice, enums (pure data, no WinUI).
    • AppMidiMapEntry / MidiMessageItem (require [Bindable] for XAML DataTemplate binding).
  4. ViewModels (ModernMeeter) — MixerViewModel owns the connection and builds StripViewModel/BusViewModel collections; MidiViewModel, SettingsViewModel, VbanStreamViewModel handle their respective pages.
  5. Controls (ModernMeeter) — BusRoutingItem observable data object for bus routing toggles.
  6. Views (ModernMeeter) — MixerPage, SettingsPage, MidiPage, MidiBindingsPage, VbanPage, HelpPage hosted in a NavigationView shell.
  7. Command Palette (ModernMeeter.CmdPal) — ModernMeeterExtension (IExtension) exposes mixer commands; runs as an out-of-process COM server. See Command Palette extension.

Data flow

VoiceMeeter DLL → VoiceMeeterClient → StripViewModel.Refresh()
  → [ObservableProperty] → {Binding} → UI

User input flows in reverse: property setters call SetParameter back into VoiceMeeter. A _isRefreshing guard prevents write-back loops when Refresh() updates properties.

Key design decisions

  • {Binding} with [Bindable] in DataTemplates — {x:Bind} crashes with E_NOINTERFACE for CommunityToolkit.Mvvm source-generated properties in WinUI 3 (a CsWinRT marshalling issue).
  • Code-behind event handlers for page-level controls (Settings toggles, buttons) — DataContext doesn’t propagate reliably into CommunityToolkit SettingsCard content slots in Release builds.
  • Test project references ModernMeeter.Core and shared-compiles the remaining UI-dependent app sources, avoiding the WinAppSDK module auto-initializer that a full app project reference would pull in.
  • FakeVoiceMeeterClient implements IVoiceMeeterClient so every ViewModel can be tested without the real VoiceMeeter DLL.
  • Self-contained deployment — bundles the .NET and Windows App SDK runtimes; a post-build target strips the WinAppRuntime framework dependency from the AppxManifest for zero-dependency installs.
  • Single instance via AppInstance.FindOrRegisterForKey in a custom Program.cs entry point.
  • Computed level properties (LevelLeftPercent / LevelRightPercent) on Strip/Bus ViewModels avoid IValueConverter boxing on the 30fps level-meter hot path.
  • Microsoft.Extensions.* throughout — Options (IWritableOptions<AppSettings> backed by LocalSettingsStore), Logging (FileLoggerProvider), and DependencyInjection (App.Services).