Skip to content

OwnAudioVST3 V1.5.20

Choose a tag to compare

@ModernMube ModernMube released this 10 May 19:33
· 112 commits to main since this release

OwnAudioVST3 — v1.5.20

Overview

This release focuses on macOS stability, VST3 plugin state save/restore, and thread-safety improvements. The changes span a major stabilization push covering plugin loading, editor lifecycle, parameter synchronization, and binary state serialization.


New Features

GetState / SetState API

Full VST3 binary state save and restore support via GetStateAsync / SetStateAsync on ThreadedVst3Wrapper. State is returned as a managed byte[] and can be re-applied at any time (e.g. project load).

SetParameterAsync

New task-based parameter setter that executes on the dedicated plugin thread, ensuring immediate visibility to subsequent reads without waiting for an audio processing cycle. Designed for non-realtime use cases such as project state restoration.


Bug Fixes

macOS — Modal / Dropdown Deadlocks

  • Replaced CFRunLoopTimer with dispatch_async_f in DispatchToMainAsync to prevent UI freeze when dropdown menus or popup browsers are opened inside a VST3 editor. CFRunLoopTimer fired during NSEventTrackingRunLoopMode, causing a .NET P/Invoke deadlock on Apple Silicon (macOS 12+).
  • CreateEditor and CloseEditor now dispatch to the macOS main thread via Invoke(). JUCE's MessageManager records the calling thread on first use; calling from a non-main .NET thread caused isThisTheMessageThread() to return false, making runModalLoop() deadlock in callFunctionOnMessageThread.
  • loadPlugin dispatched to macOS main thread so JUCE's MessageManager always initializes on the native main thread.

macOS — Thread Safety & Destruction

  • Plugin destruction now uses a GCD drain-signal pattern instead of SynchronizationContext to avoid deadlocks during shutdown.
  • macOS main queue handle resolved via libdispatch symbol lookup instead of a private API import.
  • setParamNormalized dispatched to the macOS main thread.
  • getState() and setState() dispatched to the macOS main thread (matches existing loadPlugin guard), fixing silent failures with plugins that touch AppKit during serialization (e.g. Classic EQ v6).

State Serialization

  • ISizeableStream implemented in MemoryIBStreamImpl — plugins that query this interface before writing state (e.g. JUCE-based effects) no longer skip their state blob, resolving empty getState() results.
  • getState() result accepted regardless of return code — plugins that return kResultFalse but still write valid state data are now captured correctly.
  • Controller state fallback added: if the processor-side getState() yields no data, the controller state is used instead.
  • Removed spec-violating zero-sample process() call from the getState() path; parameter flushing before serialization is now handled cleanly via the parameter queue.
  • setState() calls updateParameters() after restore to sync the host parameter cache.
  • performEdit() routes editor-driven parameter changes back through the host cache and the audio-thread SPSC queue.
  • getState() calls updateParameters() before reading state so plugins that bypass performEdit() are captured correctly.

Parameter Synchronization

  • Editor UI re-synced with host parameters after view->attached() — plugins that reset their UI to processor state during editor creation now correctly reflect host-set parameter values.
  • restartComponent(kParamValuesChanged) schedules a parameter refresh on the UI thread.
  • updateParameters() now uses operator[] instead of emplace so controller values are always overwritten, not skipped on duplicate keys.
  • PARAM_QUEUE_CAPACITY raised to 8192 to accommodate large parameter sets during state restore.
  • Restored parameters are pushed into the SPSC queue so the audio thread receives state-restored values before the first processAudio() block.
  • activeNoteIds made thread-safe using atomic<int32_t> array.
  • StringCache protected with a mutex for concurrent GetName / GetVendor / GetPluginInfo calls.

Initialization

  • UI thread SynchronizationContext is set before any VST3 plugin load, ensuring correct thread affinity throughout the plugin lifecycle.

Version History (this cycle)

Version Highlight
1.5.20 Thread-safe activeNoteIds/StringCache, restartComponent param refresh, getState cleanup
1.5.19 Flush paramQueue before state serialization
1.5.18 OwnVST3 v1.5.4: state serialization + thread-safe param sync
1.5.17 Remove DSP flush from getState, add controller state fallback
1.5.16 Flush parameters to processor before getState
1.5.15 Accept getState() data regardless of return code
1.5.14 Re-sync editor UI with host parameters after attach
1.5.13 ISizeableStream support in MemoryIBStreamImpl
1.5.12 macOS main thread setParamNormalized
1.5.11 State restore fix: SPSC queue push + updateParameters on setState
1.5.10 GetState/SetState API + SetParameterAsync
1.5.9 macOS main thread dispatching for loadPlugin, CreateEditor, CloseEditor
1.5.8 Fix CFRunLoopTimer dropdown deadlock, GCD drain-signal destruction