Skip to content

Revamp audio plugins module#47

Open
luapmartin wants to merge 24 commits into
musescore:mainfrom
luapmartin:luapmartin/revamp-audio_plugins-module
Open

Revamp audio plugins module#47
luapmartin wants to merge 24 commits into
musescore:mainfrom
luapmartin:luapmartin/revamp-audio_plugins-module

Conversation

@luapmartin
Copy link
Copy Markdown
Contributor

@luapmartin luapmartin commented May 21, 2026

Audacity PR: audacity/audacity#10989
Musescore PR: musescore/MuseScore#33535

Revamp of the audioplugins framework module — decouple, version, state lifecycle.

  • Moved the audio-plugin metadata types (AudioResourceId, AudioResourceMeta, AudioResourceType, …) out of muse::audio into the dedicated muse::audioplugins module. muse::audio keeps using aliases, so existing callers compile unchanged.
  • Replaced AudioPluginInfo's enabled boolean with an AudioPluginState lifecycle: Discovered, Validated, Missing, Error.
  • Introduced a versioning system for the shared known_audio_plugins.json cache — a {version, plugins} envelope plus a migration register. The framework auto-registers v0→v1 (structural) and v1→v2 (enabledstate); apps register any later steps. Legacy bare-array files still load.
  • AudioResourceType is now an opaque wire string; each plugin module owns its canonical identifier (vst::AUDIO_RESOURCE_TYPE_NAME = "VstPlugin", …). The engine-side enum was narrowed to the formats the engine actually routes.
  • Made the VST plugin metadata reader fully audio-engine-independent (own attributes header, local PluginType enum).
  • Hardened the scan flow: Discovered placeholders are persisted so an interrupted scan auto-resumes next launch; stopping a scan no longer loses already-validated plugins; registerNewPlugins(paths, validate) can record plugins without validating them.
  • Tests: migration register, wire-string stability, expanded register/scenario coverage.

Note: IRegisterAudioPluginsScenario::unregisterRemovedPlugins() has no callers in either the framework or audacity — the scan flow now marks removed plugins Missing via setPluginsState() instead of hard-deleting. I think that depending on what are musescore needs we could consider dropping that method? or maybe keep it as a cleaner in case the known_audio_plugins.json get's "too big"... to be called from plugin manager.

  • I signed the CLA
  • The title of the PR describes the problem it addresses
  • Each commit's message describes its purpose and effects, and references the issue it resolves
  • If changes are extensive, there is a sequence of easily reviewable commits
  • The code in the PR follows the coding rules
  • There are no unnecessary changes
  • The code compiles and runs on my machine, preferably after each commit individually
  • I created a unit test or vtest to verify the changes I made (if applicable)

Build configuration

audacity: luapmartin/audacity/luapmartin/revamp-audio_plugins-module
audacity platforms: linux_x64 macos windows_x64
musescore: luapmartin/MuseScore/luapmartin/revamp-audio_plugins-module
musescore platforms: linux_x64 linux_arm64 macos windows_x64 windows_portable

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 07e7e72f-50a2-442b-a67a-5a3e32bab2a5

📥 Commits

Reviewing files that changed from the base of the PR and between 550d364 and 491a3c0.

📒 Files selected for processing (2)
  • framework/audioplugins/audiopluginstypes.h
  • framework/audioplugins/internal/knownaudiopluginsregister.cpp

📝 Walkthrough

Walkthrough

This PR replaces enum-based plugin/resource types with string-based audioplugins::AudioResourceType and AudioResourceMeta, adds AudioPluginState and migration/register infrastructure (IKnownAudioPluginsMigrationRegister, KnownAudioPluginsMigrationRegister), versioned known-plugins cache I/O, runtime-only attribute defaults, PluginType and VST attribute constants, refactors scanning/registration to use explicit states and placeholders, updates engine/VST/musesampler plumbing to the new types, and adapts/extends tests and mocks accordingly.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Revamp audio plugins module' directly reflects the main change—a comprehensive refactoring of the audioplugins module covering type movement, state lifecycle, versioning, and wire-format changes.
Description check ✅ Passed The PR description includes all required template sections: references to Audacity/MuseScore PRs, detailed summary of changes, all checklist items addressed, and build configuration with target platforms.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/audioplugins/tests/knownaudiopluginsregistertest.cpp (1)

75-93: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Drop the vestigial RESOURCE_TYPE_TO_STR map.

AudioResourceType is now a wire string, so this map is a single-entry identity for "VstPlugin" and silently rewrites any other type to "Undefined" via the fallback. New tests that introduce e.g. FluidSoundfont or MuseSamplerSoundPack would produce wrong expected JSON without any signal. Set the field directly to info.meta.type.

♻️ Proposed simplification
     ByteArray pluginInfoListToJson(const std::vector<AudioPluginInfo>& infoList) const
     {
-        const std::map<AudioResourceType, std::string> RESOURCE_TYPE_TO_STR {
-            { "VstPlugin", "VstPlugin" },
-        };
-
         JsonArray array;
@@
             JsonObject metaObj;
             metaObj.set("id", info.meta.id);
-            metaObj.set("type", muse::value(RESOURCE_TYPE_TO_STR, info.meta.type, "Undefined"));
+            metaObj.set("type", info.meta.type.toStdString());

(adjust the conversion to match AudioResourceType's underlying string type.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/tests/knownaudiopluginsregistertest.cpp` around lines
75 - 93, Remove the vestigial RESOURCE_TYPE_TO_STR map and stop translating
AudioResourceType via muse::value; instead set the JSON "type" directly from
info.meta.type (adjusting conversion to the underlying string type as needed),
i.e. replace the muse::value(RESOURCE_TYPE_TO_STR, info.meta.type, "Undefined")
call in the metaObj.set("type", ...) expression with a direct use of
info.meta.type (or info.meta.type.toStdString()/equivalent) so new resource
types like FluidSoundfont or MuseSamplerSoundPack are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/audio/engine/internal/audioengineconfiguration.cpp`:
- Line 44: The literal "FluidSoundfont" should be replaced with the canonical
FLUID_SOUNDFONT_TYPE_NAME constant to avoid duplicating the wire-string; update
the code in audioengineconfiguration.cpp to use the constant (e.g.
muse::audio::synth::FLUID_SOUNDFONT_TYPE_NAME or the correct namespace where
FLUID_SOUNDFONT_TYPE_NAME is declared) and ensure the existing include of
soundfonttypes.h provides that symbol or add the proper include/using directive
so the compiler can find FLUID_SOUNDFONT_TYPE_NAME.

In `@framework/audioplugins/audiopluginstypes.h`:
- Around line 81-85: AudioResourceMeta::operator< currently returns id <
other.id || vendor < other.vendor which is not a strict weak ordering; change
the comparator to perform a lexicographical comparison of all relevant members
(e.g., compare id first, if equal compare vendor, if equal compare type, then
attributes) so that the relation is transitive, antisymmetric, and returns false
when all compared fields are equal; implement this either with a sequence of
explicit comparisons or using std::tie(id, vendor, type, attributes) <
std::tie(...).

In `@framework/audioplugins/iknownaudiopluginsmigrationregister.h`:
- Line 32: CURRENT_KNOWN_AUDIO_PLUGINS_VERSION is set to 3 while the PR only
registers migrations 0->1 and 1->2, causing migrate() to fail for caches at
version 2; either implement and register the missing 2->3 migration (add the
migration handler and register it alongside the existing 0->1 and 1->2 entries)
or lower CURRENT_KNOWN_AUDIO_PLUGINS_VERSION to 2 to match the available
migrations so migrate() has a complete migration path. Ensure the change updates
the same symbols: CURRENT_KNOWN_AUDIO_PLUGINS_VERSION and the migration
registration logic used by migrate().

In `@framework/audioplugins/internal/knownaudiopluginsregister.cpp`:
- Around line 131-138: The object-branch currently treats a missing or
wrong-typed "version" or "plugins" field as defaults (fileVersion=0, empty
array) which can silently accept truncated/malformed envelopes; instead validate
the envelope: when json.isObject() fails to contain a numeric "version" and an
array-valued "plugins", log an error mentioning knownAudioPluginsPath and return
a Ret error (same style as the existing Unrecognized branch) rather than
defaulting; update the code around JsonObject root = json.rootObject(),
fileVersion = root.value("version").toInt(), and array =
root.value("plugins").toArray() to explicitly check root.contains("version") &&
root.value("version").isDouble()/isNumber and root.contains("plugins") &&
root.value("plugins").isArray(), extract/assign fileVersion and array only when
valid, otherwise return an error Ret.
- Around line 154-156: Runtime-only defaults are not overriding cached values
because emplace() retains existing entries in info.meta.attributes; change the
loop that iterates configuration()->runtimeAttributeDefaults() so it assigns
into info.meta.attributes (e.g., info.meta.attributes[kv.first] = kv.second)
instead of using emplace(), ensuring runtime defaults overwrite any legacy
cached entries.

In `@framework/audioplugins/internal/registeraudiopluginsscenario.cpp`:
- Around line 104-105: The calls to
knownPluginsRegister()->setPluginsState(result.missingPluginIds,
AudioPluginState::Missing) and
knownPluginsRegister()->setPluginsState(result.rediscoveredPluginIds,
AudioPluginState::Validated) currently discard their Ret values; update
RegisterAudioPluginScenario (or the surrounding function) to capture each
setPluginsState() return, check for failure, and abort/return an error (or
propagate the Ret) before proceeding to registration so the scan does not
continue on partial cache-write failures; ensure you reference the two calls
with result.missingPluginIds and result.rediscoveredPluginIds and
handle/log/propagate the Ret from setPluginsState() appropriately.
- Around line 57-63: The code currently maps each io::path_t to a single
CacheEntry (struct CacheEntry { AudioResourceId id; AudioPluginState state; })
which drops duplicate plugin IDs for the same binary path; change registered
from std::map<io::path_t, CacheEntry> to std::map<io::path_t,
std::vector<CacheEntry>> and, where you populate it from
knownPluginsRegister()->pluginInfoList(), push_back a CacheEntry for each info
(registered[info.path].push_back({ info.meta.id, info.state })). Update all
subsequent logic that formerly accessed a single CacheEntry by key (the blocks
handling Missing/Validated updates around the functions that iterate registered)
to iterate the vector and update each CacheEntry.state for matching
AudioResourceId values so every cached ID for a shared binary path is preserved
and updated.

In `@framework/audioplugins/iregisteraudiopluginsscenario.h`:
- Around line 49-53: The registerNewPlugins method currently returns void but
performs multiple fallible operations; change its signature from void
registerNewPlugins(const io::paths_t& pluginPaths, bool validate = true) to
return Ret and propagate that through implementations (short-circuit and return
the first Ret error encountered during cache write/load or any other failing
step). Update all classes implementing IRegisterAudioPluginsScenario to
implement Ret registerNewPlugins(...), ensure callers check and propagate the
Ret result instead of assuming success, and update the comment above the
declaration to reflect the new return value semantics.

In `@framework/vst/internal/vstmodulesrepository.cpp`:
- Around line 123-133: The code hard-codes u"Instrument" inside
VstModulesRepository::modulesMetaList to distinguish instruments from FX;
extract this literal into a named constant (e.g. INSTRUMENT_CATEGORY) declared
alongside CATEGORIES_ATTRIBUTE (suggest placing in vstpluginattrs.h or next to
PluginCategory) and replace the inline u"Instrument" usage with that constant so
both producer and consumer share the same symbol (update includes/usings as
needed to reference INSTRUMENT_CATEGORY where modulesMetaList reads
info.meta.attributeVal(muse::vst::CATEGORIES_ATTRIBUTE)).

In `@framework/vst/internal/vstpluginmetareader.cpp`:
- Around line 25-27: vstpluginmetareader.cpp relies on symbols
AUDIO_RESOURCE_TYPE_NAME and CATEGORIES_ATTRIBUTE but only pulls them
transitively via vsttypes.h; add an explicit include for "vstpluginattrs.h" at
the top of framework/vst/internal/vstpluginmetareader.cpp so the file directly
declares its dependency (locate the top includes near the existing `#include`
"vsttypes.h" and insert `#include` "vstpluginattrs.h" there).

In `@framework/vst/vstpluginattrs.h`:
- Around line 30-33: The header defines static const String CATEGORIES_ATTRIBUTE
which creates per-translation-unit copies; change its definition to use inline
const (matching AUDIO_RESOURCE_TYPE_NAME's inline constexpr style) so there is a
single shared definition of CATEGORIES_ATTRIBUTE across TUs; update the
declaration for the String named CATEGORIES_ATTRIBUTE to be inline const String
to avoid duplicate construction/destruction and address-inequality issues.

---

Outside diff comments:
In `@framework/audioplugins/tests/knownaudiopluginsregistertest.cpp`:
- Around line 75-93: Remove the vestigial RESOURCE_TYPE_TO_STR map and stop
translating AudioResourceType via muse::value; instead set the JSON "type"
directly from info.meta.type (adjusting conversion to the underlying string type
as needed), i.e. replace the muse::value(RESOURCE_TYPE_TO_STR, info.meta.type,
"Undefined") call in the metaObj.set("type", ...) expression with a direct use
of info.meta.type (or info.meta.type.toStdString()/equivalent) so new resource
types like FluidSoundfont or MuseSamplerSoundPack are preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e5095749-7e36-4068-a444-e9de5e141a23

📥 Commits

Reviewing files that changed from the base of the PR and between b7c2a97 and 554031e.

📒 Files selected for processing (47)
  • framework/audio/common/audiotypes.h
  • framework/audio/common/audioutils.h
  • framework/audio/common/rpc/rpcpacker.h
  • framework/audio/engine/internal/audioengineconfiguration.cpp
  • framework/audio/engine/internal/enginerpccontroller.cpp
  • framework/audio/engine/internal/synthesizers/fluidsynth/fluidresolver.cpp
  • framework/audio/tests/CMakeLists.txt
  • framework/audio/tests/audioresourcetypes_tests.cpp
  • framework/audio/tests/rpcpacker_tests.cpp
  • framework/audioplugins/CMakeLists.txt
  • framework/audioplugins/audiopluginsmodule.cpp
  • framework/audioplugins/audiopluginstypes.h
  • framework/audioplugins/iaudiopluginmetareader.h
  • framework/audioplugins/iaudiopluginsconfiguration.h
  • framework/audioplugins/iknownaudiopluginsmigrationregister.h
  • framework/audioplugins/iknownaudiopluginsregister.h
  • framework/audioplugins/internal/audiopluginsconfiguration.cpp
  • framework/audioplugins/internal/audiopluginsconfiguration.h
  • framework/audioplugins/internal/knownaudiopluginsmigrationregister.cpp
  • framework/audioplugins/internal/knownaudiopluginsmigrationregister.h
  • framework/audioplugins/internal/knownaudiopluginsregister.cpp
  • framework/audioplugins/internal/knownaudiopluginsregister.h
  • framework/audioplugins/internal/registeraudiopluginsscenario.cpp
  • framework/audioplugins/internal/registeraudiopluginsscenario.h
  • framework/audioplugins/iregisteraudiopluginsscenario.h
  • framework/audioplugins/tests/CMakeLists.txt
  • framework/audioplugins/tests/audiopluginsutilstest.cpp
  • framework/audioplugins/tests/knownaudiopluginsmigrationregistertest.cpp
  • framework/audioplugins/tests/knownaudiopluginsregistertest.cpp
  • framework/audioplugins/tests/mocks/audiopluginmetareadermock.h
  • framework/audioplugins/tests/mocks/audiopluginsconfigurationmock.h
  • framework/audioplugins/tests/mocks/knownaudiopluginsmigrationregistermock.h
  • framework/audioplugins/tests/mocks/knownaudiopluginsregistermock.h
  • framework/audioplugins/tests/registeraudiopluginsscenariotest.cpp
  • framework/musesampler/internal/musesamplerresolver.cpp
  • framework/musesampler/musesamplertypes.h
  • framework/vst/CMakeLists.txt
  • framework/vst/internal/fx/vstfxprocessor.cpp
  • framework/vst/internal/synth/vstsynthesiser.cpp
  • framework/vst/internal/vstaudioclient.cpp
  • framework/vst/internal/vstaudioclient.h
  • framework/vst/internal/vstmodulesrepository.cpp
  • framework/vst/internal/vstmodulesrepository.h
  • framework/vst/internal/vstpluginmetareader.cpp
  • framework/vst/internal/vstpluginmetareader.h
  • framework/vst/vstpluginattrs.h
  • framework/vst/vsttypes.h
💤 Files with no reviewable changes (1)
  • framework/audioplugins/tests/audiopluginsutilstest.cpp

Comment thread framework/audio/engine/internal/audioengineconfiguration.cpp Outdated
Comment thread framework/audioplugins/audiopluginstypes.h

namespace muse::audioplugins {
inline AudioPluginType audioPluginTypeFromCategoriesString(const String& categoriesStr)
inline constexpr int CURRENT_KNOWN_AUDIO_PLUGINS_VERSION = 3;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The cache version is one step ahead of the registered migrations.

CURRENT_KNOWN_AUDIO_PLUGINS_VERSION is 3, but the concrete register in this PR only provides 0 -> 1 and 1 -> 2. Any legacy cache upgrade will still require a missing 2 -> 3 step and fail in migrate().

Minimal fix
-inline constexpr int CURRENT_KNOWN_AUDIO_PLUGINS_VERSION = 3;
+inline constexpr int CURRENT_KNOWN_AUDIO_PLUGINS_VERSION = 2;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inline constexpr int CURRENT_KNOWN_AUDIO_PLUGINS_VERSION = 3;
inline constexpr int CURRENT_KNOWN_AUDIO_PLUGINS_VERSION = 2;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/iknownaudiopluginsmigrationregister.h` at line 32,
CURRENT_KNOWN_AUDIO_PLUGINS_VERSION is set to 3 while the PR only registers
migrations 0->1 and 1->2, causing migrate() to fail for caches at version 2;
either implement and register the missing 2->3 migration (add the migration
handler and register it alongside the existing 0->1 and 1->2 entries) or lower
CURRENT_KNOWN_AUDIO_PLUGINS_VERSION to 2 to match the available migrations so
migrate() has a complete migration path. Ensure the change updates the same
symbols: CURRENT_KNOWN_AUDIO_PLUGINS_VERSION and the migration registration
logic used by migrate().

Comment thread framework/audioplugins/internal/knownaudiopluginsregister.cpp
Comment thread framework/audioplugins/internal/knownaudiopluginsregister.cpp
Comment thread framework/audioplugins/internal/registeraudiopluginsscenario.cpp Outdated
Comment thread framework/audioplugins/iregisteraudiopluginsscenario.h
Comment thread framework/vst/internal/vstmodulesrepository.cpp
Comment thread framework/vst/internal/vstpluginmetareader.cpp
Comment thread framework/vst/vstpluginattrs.h
@luapmartin
Copy link
Copy Markdown
Contributor Author

/build

static const String PLAYBACK_SETUP_DATA_ATTRIBUTE(u"playbackSetupData");
static const String CATEGORIES_ATTRIBUTE(u"categories");

using AudioResourceId = muse::audioplugins::AudioResourceId;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you explain the purpose of this change? Not only does it create circular dependencies between the modules, but it also looks conceptually incorrect. AudioResource can be anything (soundfont, MuseSounds, etc.), not only audio plugins

luapmartin added 20 commits May 22, 2026 13:46
…cycle

Decouple the audioplugins module from the audio module so it can host
generic plugin discovery for other apps (e.g. Audacity), and add a
versioned cache schema with app-registered migrations. This commit is
the framework-side half; it is paired with a main-repo commit that
wires the MuseScore-side migrations and bumps the muse/ submodule
pointer.

Decoupling:
- Plugin-shaped types (AudioResourceMeta, AudioResourceAttributes,
  AudioResourceId, etc.) live in muse::audioplugins:: instead of
  muse::audio::. Audio keeps `using` aliases for source compat.
- audioplugins::AudioResourceType becomes an opaque std::string. Apps
  register their own plugin format identifiers; the audio module keeps
  an engine-internal enum and converts at the boundary via
  resourceTypeFromString() / resourceTypeName().
- Runtime-only attributes (skipped on save, re-injected on load) are
  app-registered via IAudioPluginsConfiguration::setRuntimeAttributeDefaults
  instead of hard-coded to audio::PLAYBACK_SETUP_DATA_ATTRIBUTE.
- HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE and CATEGORIES_ATTRIBUTE move out
  of audioplugins (now framework-pure) into audio. meta.hasNativeEditorSupport()
  is replaced by a free function audio::hasNativeEditorSupport(meta).
- AudioPluginType, IAudioPluginTypeDetector and AudioPluginInfo.type are
  dropped from the framework. The Instrument/Fx classification was
  runtime-only, never persisted, and MuseScore-shaped. The VST module
  gains its own vst::PluginType and computes the category from meta on
  demand.

Cache schema (version field added; bare-array legacy treated as v0):
- v0 -> v1: hasNativeEditorSupport moves from a top-level meta field
  into meta.attributes ("true"/"false" strings).
- v1 -> v2: the boolean `enabled` flag becomes a `state` string. The
  state lifecycle has four values:
    * Discovered: scanner found the file but validation hasn't run yet
    * Validated:  validation succeeded; usable
    * Missing:    file no longer found at the previously known path
    * Error:      validation failed (errorCode carries detail)
- Both migrations are registered MuseScore-side via the new
  IKnownAudioPluginsMigrationRegister.

Scanner behaviour:
- scanPlugins() now reports rediscoveredPluginIds (entries that were
  Missing and have come back) alongside missingPluginIds.
- updatePluginsRegistry() uses the new
  IKnownAudioPluginsRegister::setPluginsState() to mark removed
  entries as Missing instead of erasing them, and rediscovered ones
  back to Validated. Already-Missing entries stay Missing without
  churn. unregisterPlugins() is kept for explicit UI-driven removal.

Typed attribute accessors:
- boolAttribute() / intAttribute() free helpers in muse::audioplugins
  encode the on-disk "true"/"1" -> bool and digit-string -> int
  conventions in one place, so callers don't reimplement them. Storage
  stays as map<String, String>; no JSON / RPC / file-format change.

Tests cover migration chaining, legacy v0 array load, the v0->v1 and
v1->v2 transitions, and the Missing/Rediscovered scanner transitions.
Move CATEGORIES_ATTRIBUTE from audio/common/audiotypes.h into a new vst/vstpluginattrs.h header so VST consumers (including hosts that don't link the audio module) can use it without pulling audio. Fix VstPluginMetaReader::metaType() override return type to match the IAudioPluginMetaReader interface (audioplugins::AudioResourceType, not audio::AudioResourceType which is now an engine enum).
vstpluginmetareader's signature and body referenced muse::audio:: types and constants. AudioResourceMeta/AudioResourceMetaList actually live in audioplugins (audio just re-exports them); switch to use audioplugins:: directly. Move HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE alongside CATEGORIES_ATTRIBUTE in vst/vstpluginattrs.h so the file no longer needs muse::audio::. Audio's own makeReverbMeta keeps its constant in audiotypes.h (same string value).
Slims muse::audio::AudioResourceType to the formats the muse audio engine actually routes (FluidSoundfont, VstPlugin, NativeEffect, MuseSamplerSoundPack); the Audacity-only values (Lv2Plugin, AudioUnit, NyquistPlugin) move to Audacity's own EffectFamily enum, leaving the framework enum scoped to its own engine concerns. Each plugin-format module exports a wire-string constant (muse::vst::AUDIO_RESOURCE_TYPE_NAME, muse::musesampler::AUDIO_RESOURCE_TYPE_NAME, audio::FLUID_SOUNDFONT_TYPE_NAME, audio::NATIVE_EFFECT_TYPE_NAME) and producers use it instead of literal strings. New audio::isResourceType helper replaces scattered meta.type == "X" comparisons. A round-trip test (audioresourcetypes_tests) pins the wire strings to their canonical values so cache compatibility cannot drift.
Today's audacity launch tripped registerPlugins's m_loaded assertion because an old cache file with the obsolete 'enabled' field made load() return a migration error silently. Surface the failure where it actually originates: LOGE in KnownAudioPluginsRegister::load() at the JSON parse / unrecognized-root / migration-failure branches, with the cache path and the migration error text. Sharpen the messages produced by KnownAudioPluginsMigrationRegister::migrate() so the LOGE is actionable: future-version files tell the user the file is newer than the build expects, and missing migrators tell developers to register one in their AudioPluginsAppConfigModule. New tests pin the wording so a future regression that drops the actionable hint fails the build.
Wire the previously-reserved AudioPluginState::Discovered into the registration pipeline. registerNewPlugins writes a Discovered placeholder per path before spawning the validation subprocess; on subprocess return, removePluginsAtPath clears the placeholder before registerPlugin (Validated) or registerFailedPlugin (Error) writes a fresh entry. If the host crashes between placeholder-write and subprocess-return, the placeholder survives in the cache and scanPlugins() picks the path back up as 'new' on the next launch, so validation auto-resumes. removePluginsAtPath is the new register API and is mock-friendly. New scenario test covers the auto-resume path.
KnownAudioPluginsMigrationRegister's constructor now pre-registers the
framework-owned migrations so apps don't have to copy-paste them:

  v0 -> v1: structural (envelope intro, no-op callback)
  v1 -> v2: enabled boolean -> state string (AudioPluginState is a
            framework enum, so this transformation belongs here)

Apps register only their own field migrations on top. Bumps
CURRENT_KNOWN_AUDIO_PLUGINS_VERSION 2 -> 3 since the MuseScore-specific
hasNativeEditorSupport migration moves to v2 -> v3 (app-owned).
processPluginsRegistration's per-iteration removePluginsAtPath ran in the
main process and rewrote the JSON from the main's stale in-memory map,
clobbering every Validated entry the subprocesses had accumulated — only
the most recent survived. Move the placeholder removal into registerPlugin
/ registerFailedPlugin (subprocess side, fresh state); the main process
now writes the register only once, via the existing load() at the end of
registerNewPlugins.

As a side benefit, registerPlugin becomes idempotent and a subprocess
crash now leaves the Discovered placeholder intact (re-validated next
launch) instead of pre-deleted.
registerNewPlugins() gains a `bool validate = true` parameter. When false,
the paths are persisted as Discovered placeholders only and the
out-of-process validation step is skipped. scanPlugins() already treats
Discovered as re-validatable, so deferred-validation entries get
re-offered on the next scan.

Lets the app distinguish "user picked Skip on the validate prompt" from
"user has never seen these paths" — Skip now records the plugins instead
of discarding them.
Adds two tests covering the recent scan-flow changes, and updates the
existing tests to reflect the new architecture (placeholder clearing
moved from processPluginsRegistration into registerPlugin /
registerFailedPlugin):

- RegisterNewPlugins_ValidateFalsePersistsDiscoveredOnly: Skip records
  Discovered without spawning subprocesses.
- RegisterNewPlugins_NoPerIterationClobber: main-process loop calls
  removePluginsAtPath once per path, never per iteration.
- RegisterPlugin / RegisterFailedPlugin: assert subprocess-side clearing
  before registerPlugins.
- UpdatePluginsRegistry_LeftoverDiscoveredRevalidates: expected
  removePluginsAtPath count drops 2->1.
The old `id < a.id || vendor < a.vendor` was not antisymmetric (could
report a<b and b<a both true) and ignored type/attributes, so it
mis-ordered AudioResourceMetaSet. Compare all four fields lexicographically.
A shell / multi-effect bundle hosts several plugin IDs at one path. The
path->CacheEntry map kept only the last entry, so when such a binary
disappeared or reappeared only one ID had its state transitioned. Map
each path to a vector of entries and update them all.
Regression guard for the per-path multi-id tracking fix: a shell binary
hosting two plugin ids must transition both to Missing when it disappears
and both back to Validated when rediscovered, not just the last one.
load()'s object branch treated a missing/non-numeric "version" or a
missing/non-array "plugins" as defaults (v0, empty array), so a truncated
file loaded as "no plugins" and could be saved back that way. Validate
the envelope and return an error instead.
load() used emplace() for runtime-only attribute defaults, which keeps a
stale value if a legacy cache still carries that key. Assign instead so
the current default always wins.
Both setPluginsState() calls write the cache and can fail; their Ret was
discarded, so a partial state transition could be reported as success.
Check each and return the error.
- vstpluginattrs.h: CATEGORIES_ATTRIBUTE static const -> inline const
  (one shared definition instead of a per-TU copy).
- Extract the instrument-discriminator literal into vst::INSTRUMENT_CATEGORY
  and use it in vstmodulesrepository (shared producer/consumer symbol).
- vstpluginmetareader.cpp: include vstpluginattrs.h directly (IWYU).
- audioengineconfiguration.cpp: use FLUID_SOUNDFONT_TYPE_NAME instead of
  the raw "FluidSoundfont" wire-string literal.
CURRENT_KNOWN_AUDIO_PLUGINS_VERSION is 3 but the framework shipped only
v0->v1 and v1->v2, so a framework-only consumer loading a v2 cache failed
migrate(). Register a no-op v2->v3 default; apps that need real work
(MuseScore's hasNativeEditorSupport lift) override the slot via
registerMigration(2, ...). audacity's no-op v2->v3 is now redundant.
registerNewPlugins performed fallible cache writes/loads but returned
void, so callers could not tell a partial scan from a clean one. Return
Ret and short-circuit on the first failure; persistDiscoveredPlaceholders
likewise propagates removePluginsAtPath / registerPlugins errors.
updatePluginsRegistry now checks and propagates the result.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
framework/audioplugins/internal/knownaudiopluginsregister.cpp (2)

249-275: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Notify observers after in-memory registry mutations.

setPluginsState() and removePluginsAtPath() both mutate m_pluginInfoMap/m_pluginPaths and persist the result, but neither emits m_pluginInfoListChanged. Subscribers only get notified on load(), so Missing/rediscovered/error transitions can stay invisible until a full reload.

🔔 Minimal fix pattern
-    return writePluginsInfo();
+    Ret ret = writePluginsInfo();
+    if (ret) {
+        m_pluginInfoListChanged.notify();
+    }
+    return ret;

Apply the same pattern to both mutators, or centralize it in a shared post-write helper so all registry mutations notify consistently.

Also applies to: 305-327

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/internal/knownaudiopluginsregister.cpp` around lines
249 - 275, setPluginsState and removePluginsAtPath currently mutate
m_pluginInfoMap / m_pluginPaths and persist via writePluginsInfo but never
notify observers, so update these mutators to emit m_pluginInfoListChanged after
a successful in-memory change + successful write; specifically, in
KnownAudioPluginsRegister::setPluginsState (and the analogous
removePluginsAtPath implementation) call the existing notification/emitter for
m_pluginInfoListChanged (or invoke a shared helper you create, e.g.,
notifyPluginInfoListChangedPostWrite) only when changed is true and
writePluginsInfo() returns success, so subscribers see
Missing/rediscovered/error transitions without requiring a full load.

156-171: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject malformed plugin rows before inserting them.

load() now validates the root envelope, but it still inserts entries even when meta or path is missing. metaFromJson({}) yields empty id/vendor/type, so one truncated row can create an empty-key record and poison later lookups.

🛡️ Proposed fix
         AudioPluginInfo info;
         info.meta = metaFromJson(object.value("meta").toObject());
         for (const auto& kv : configuration()->runtimeAttributeDefaults()) {
             // Assign, don't emplace: runtime-only defaults must override any
             // stale value a legacy cache still carries for the same key.
             info.meta.attributes[kv.first] = kv.second;
         }
         info.path = object.value("path").toString();
         info.state = audioPluginStateFromName(object.value("state").toStdString());
         info.errorCode = object.value("errorCode").toInt();
+
+        if (!info.meta.isValid() || info.path.empty()) {
+            LOGE() << "Malformed known-audio-plugins entry at " << knownAudioPluginsPath;
+            return Ret(static_cast<int>(Ret::Code::UnknownError),
+                       "Malformed known_audio_plugins.json entry");
+        }

         m_pluginPaths.insert(info.path);
         m_pluginInfoMap.emplace(info.meta.id, std::move(info));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/internal/knownaudiopluginsregister.cpp` around lines
156 - 171, The loop currently inserts entries even when meta or path are
missing, which allows empty keys (from metaFromJson({})) to poison lookups;
update the loop that builds AudioPluginInfo (using metaFromJson, info.path,
info.meta.id, m_pluginPaths, m_pluginInfoMap) to validate that info.meta.id and
info.path are non-empty (and optionally that meta.vendor/type are present if
required) and skip the entry (continue) when they are malformed so you do not
call m_pluginPaths.insert(...) or m_pluginInfoMap.emplace(...) for invalid rows.
framework/audioplugins/internal/knownaudiopluginsmigrationregister.cpp (1)

39-58: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use the canonical state-name helper in the v1→v2 migrator.

This migration hard-codes "Validated" and "Error" even though audioPluginStateName() is the canonical wire mapping. If those names ever drift, migrated caches and runtime parsing will diverge.

♻️ Proposed fix
-            obj.set("state", enabled ? std::string("Validated") : std::string("Error"));
+            obj.set("state", audioPluginStateName(enabled
+                                                  ? AudioPluginState::Validated
+                                                  : AudioPluginState::Error));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/internal/knownaudiopluginsmigrationregister.cpp`
around lines 39 - 58, The migrator registered in registerMigration(1, ...)
currently hardcodes "Validated" and "Error"; change it to use the canonical
helper audioPluginStateName(...) so wire names stay consistent. Inside the
lambda that iterates JsonArray plugins, replace the hardcoded strings with
audioPluginStateName(AudioPluginState::Validated) for enabled==true and
audioPluginStateName(AudioPluginState::Error) for enabled==false (keeping the
same logic where "enabled" is removed and "state" set). Make sure the
AudioPluginState symbol and audioPluginStateName function are available in this
translation unit or include the proper header if missing.
framework/vst/internal/vstpluginmetareader.cpp (1)

57-70: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Stop collapsing VST3 modules with multiple audio-effect classes into a single cache entry

  • VstPluginMetaReader::readMeta() emits only one AudioResourceMeta per pluginPath (breaks after the first kVstAudioEffectClass) and assigns a basename-only meta.id via io::completeBasename(pluginPath).toStdString().
  • VstPluginInstance::load() also breaks after the first kVstAudioEffectClass, so it can’t select a specific ClassInfo even if more classes were discovered.
  • This prevents representing/transitioning multiple audio-effect classes from the same VST3 binary under the multi-ID-per-path behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/vst/internal/vstpluginmetareader.cpp` around lines 57 - 70,
VstPluginMetaReader::readMeta() currently stops after the first
kVstAudioEffectClass and uses only the basename for meta.id
(io::completeBasename(pluginPath).toStdString()), collapsing multiple
audio-effect classes into one entry; remove the break and emit one
AudioResourceMeta per ClassInfo in factory.classInfos() that matches
kVstAudioEffectClass, setting meta.id to a unique multi-ID-per-path form (e.g.
include pluginPath plus a class-specific identifier such as classInfo.id() or
classInfo.name()) and preserve attributes/vendor as before; also update
VstPluginInstance::load() to not break on the first class and to select/load a
specific ClassInfo when a particular class ID is requested so the code can
represent multiple audio-effect classes from the same VST3 binary.
framework/audioplugins/internal/registeraudiopluginsscenario.cpp (1)

250-255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check removePluginsAtPath() before writing replacement entries.

Lines 254 and 293 discard the Ret. If that cache update fails, the subprocess continues against stale state and can either trip the same-id guard or leave the stale placeholder on disk while returning the later registerPlugins() result.

Proposed fix
-    knownPluginsRegister()->removePluginsAtPath(pluginPath);
+    Ret ret = knownPluginsRegister()->removePluginsAtPath(pluginPath);
+    if (!ret) {
+        LOGE() << "Failed to clear existing plugin entry at path: "
+               << pluginPath.toStdString() << ", error: " << ret.toString();
+        return ret;
+    }
@@
-    Ret ret = knownPluginsRegister()->registerPlugins(infoList);
-    return ret;
+    return knownPluginsRegister()->registerPlugins(infoList);

Apply the same guard in registerFailedPlugin() before building the Error entry.

Also applies to: 290-293

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/internal/registeraudiopluginsscenario.cpp` around
lines 250 - 255, The call to
knownPluginsRegister()->removePluginsAtPath(pluginPath) is returning a Ret that
is currently ignored, which can leave stale placeholders or trip same-id guards;
update the code around removePluginsAtPath (and the similar call around line
~290) to check the returned result and abort/return early (or propagate the
error) if the cache update fails instead of continuing to write replacement
entries; also apply the same pre-check in registerFailedPlugin() before
constructing/writing the Error entry so you don't write error placeholders when
removePluginsAtPath failed; reference removePluginsAtPath,
knownPluginsRegister(), registerPlugins(), and registerFailedPlugin() when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/audioplugins/internal/registeraudiopluginsscenario.cpp`:
- Around line 80-98: The loop currently only treats AudioPluginState::Discovered
as a reason to re-validate a path and only treats AudioPluginState::Missing as
rediscovered; update the logic to also consider AudioPluginState::Error as a
visible binary so paths with any CacheEntry.state == AudioPluginState::Error are
re-validated (add Error into the hasDiscovered predicate alongside Discovered)
and treat previously Error entries like Missing entries by pushing their
entry.id into result.rediscoveredPluginIds before erasing from registered so
errored plugins seen on disk get retried.

---

Outside diff comments:
In `@framework/audioplugins/internal/knownaudiopluginsmigrationregister.cpp`:
- Around line 39-58: The migrator registered in registerMigration(1, ...)
currently hardcodes "Validated" and "Error"; change it to use the canonical
helper audioPluginStateName(...) so wire names stay consistent. Inside the
lambda that iterates JsonArray plugins, replace the hardcoded strings with
audioPluginStateName(AudioPluginState::Validated) for enabled==true and
audioPluginStateName(AudioPluginState::Error) for enabled==false (keeping the
same logic where "enabled" is removed and "state" set). Make sure the
AudioPluginState symbol and audioPluginStateName function are available in this
translation unit or include the proper header if missing.

In `@framework/audioplugins/internal/knownaudiopluginsregister.cpp`:
- Around line 249-275: setPluginsState and removePluginsAtPath currently mutate
m_pluginInfoMap / m_pluginPaths and persist via writePluginsInfo but never
notify observers, so update these mutators to emit m_pluginInfoListChanged after
a successful in-memory change + successful write; specifically, in
KnownAudioPluginsRegister::setPluginsState (and the analogous
removePluginsAtPath implementation) call the existing notification/emitter for
m_pluginInfoListChanged (or invoke a shared helper you create, e.g.,
notifyPluginInfoListChangedPostWrite) only when changed is true and
writePluginsInfo() returns success, so subscribers see
Missing/rediscovered/error transitions without requiring a full load.
- Around line 156-171: The loop currently inserts entries even when meta or path
are missing, which allows empty keys (from metaFromJson({})) to poison lookups;
update the loop that builds AudioPluginInfo (using metaFromJson, info.path,
info.meta.id, m_pluginPaths, m_pluginInfoMap) to validate that info.meta.id and
info.path are non-empty (and optionally that meta.vendor/type are present if
required) and skip the entry (continue) when they are malformed so you do not
call m_pluginPaths.insert(...) or m_pluginInfoMap.emplace(...) for invalid rows.

In `@framework/audioplugins/internal/registeraudiopluginsscenario.cpp`:
- Around line 250-255: The call to
knownPluginsRegister()->removePluginsAtPath(pluginPath) is returning a Ret that
is currently ignored, which can leave stale placeholders or trip same-id guards;
update the code around removePluginsAtPath (and the similar call around line
~290) to check the returned result and abort/return early (or propagate the
error) if the cache update fails instead of continuing to write replacement
entries; also apply the same pre-check in registerFailedPlugin() before
constructing/writing the Error entry so you don't write error placeholders when
removePluginsAtPath failed; reference removePluginsAtPath,
knownPluginsRegister(), registerPlugins(), and registerFailedPlugin() when
making the change.

In `@framework/vst/internal/vstpluginmetareader.cpp`:
- Around line 57-70: VstPluginMetaReader::readMeta() currently stops after the
first kVstAudioEffectClass and uses only the basename for meta.id
(io::completeBasename(pluginPath).toStdString()), collapsing multiple
audio-effect classes into one entry; remove the break and emit one
AudioResourceMeta per ClassInfo in factory.classInfos() that matches
kVstAudioEffectClass, setting meta.id to a unique multi-ID-per-path form (e.g.
include pluginPath plus a class-specific identifier such as classInfo.id() or
classInfo.name()) and preserve attributes/vendor as before; also update
VstPluginInstance::load() to not break on the first class and to select/load a
specific ClassInfo when a particular class ID is requested so the code can
represent multiple audio-effect classes from the same VST3 binary.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0b7335ce-a62d-496e-8e72-ae56078837a2

📥 Commits

Reviewing files that changed from the base of the PR and between 554031e and 864f1c5.

📒 Files selected for processing (12)
  • framework/audio/engine/internal/audioengineconfiguration.cpp
  • framework/audioplugins/audiopluginstypes.h
  • framework/audioplugins/internal/knownaudiopluginsmigrationregister.cpp
  • framework/audioplugins/internal/knownaudiopluginsregister.cpp
  • framework/audioplugins/internal/registeraudiopluginsscenario.cpp
  • framework/audioplugins/internal/registeraudiopluginsscenario.h
  • framework/audioplugins/iregisteraudiopluginsscenario.h
  • framework/audioplugins/tests/knownaudiopluginsmigrationregistertest.cpp
  • framework/audioplugins/tests/registeraudiopluginsscenariotest.cpp
  • framework/vst/internal/vstmodulesrepository.cpp
  • framework/vst/internal/vstpluginmetareader.cpp
  • framework/vst/vstpluginattrs.h

Comment on lines +80 to +98
// A Discovered placeholder means a prior run was interrupted
// before this path finished validating — re-validate the path.
const bool hasDiscovered = std::any_of(entries.cbegin(), entries.cend(),
[](const CacheEntry& e) {
return e.state == AudioPluginState::Discovered;
});
if (hasDiscovered) {
result.newPluginPaths.push_back(path);
registered.erase(it);
continue;
}

// Every formerly Missing ID under this path is rediscovered.
for (const CacheEntry& entry : entries) {
if (entry.state == AudioPluginState::Missing) {
result.rediscoveredPluginIds.push_back(entry.id);
}
}
registered.erase(it);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 22, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Revalidate Error entries when the binary is seen again.

Lines 80-98 only retry Discovered paths and only transition Missing IDs. A plugin that previously landed in AudioPluginState::Error but is still present on disk gets erased from registered with no follow-up, so it stays permanently stuck in Error until the cache is deleted manually.

Proposed fix
-            const bool hasDiscovered = std::any_of(entries.cbegin(), entries.cend(),
-                                                   [](const CacheEntry& e) {
-                return e.state == AudioPluginState::Discovered;
-            });
-            if (hasDiscovered) {
+            const bool needsRevalidation = std::any_of(entries.cbegin(), entries.cend(),
+                                                       [](const CacheEntry& e) {
+                return e.state == AudioPluginState::Discovered
+                       || e.state == AudioPluginState::Error;
+            });
+            if (needsRevalidation) {
                 result.newPluginPaths.push_back(path);
                 registered.erase(it);
                 continue;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/audioplugins/internal/registeraudiopluginsscenario.cpp` around
lines 80 - 98, The loop currently only treats AudioPluginState::Discovered as a
reason to re-validate a path and only treats AudioPluginState::Missing as
rediscovered; update the logic to also consider AudioPluginState::Error as a
visible binary so paths with any CacheEntry.state == AudioPluginState::Error are
re-validated (add Error into the hasDiscovered predicate alongside Discovered)
and treat previously Error entries like Missing entries by pushing their
entry.id into result.rediscoveredPluginIds before erasing from registered so
errored plugins seen on disk get retried.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

sticky Error on automatic scans is intentional; retry is driven by user rescan (already supported)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@luapmartin, understood — thanks for clarifying the design intent. Keeping Error sticky on automatic scans makes sense as a deliberate throttle to avoid repeatedly hammering a broken plugin on every launch, with the explicit user rescan as the intentional retry path.


✏️ Learnings added
Learnt from: luapmartin
Repo: musescore/muse_framework PR: 47
File: framework/audioplugins/internal/registeraudiopluginsscenario.cpp:80-98
Timestamp: 2026-05-22T15:12:37.428Z
Learning: In `framework/audioplugins/internal/registeraudiopluginsscenario.cpp` (muse_framework, C++), `AudioPluginState::Error` is intentionally sticky during automatic scans in `scanPlugins()`. Plugins in Error state are not re-validated automatically; retry is driven by an explicit user-initiated rescan. Do not flag this as a bug.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

using AudioResourceAttributes = muse::audioplugins::AudioResourceAttributes;
using AudioResourceMeta = muse::audioplugins::AudioResourceMeta;
using AudioResourceMetaList = muse::audioplugins::AudioResourceMetaList;
using AudioResourceMetaSet = muse::audioplugins::AudioResourceMetaSet;
Copy link
Copy Markdown
Member

@igorkorsukov igorkorsukov May 22, 2026

Choose a reason for hiding this comment

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

I think we should simply break the connection between audio and audioplugins
i.e., consider them to have nothing in common.

Maybe it makes sense to rename audioplugins to something else, like userplugins

So, as it was:

  • Audio plugins loaded and created audio resources - the problem here is that Audacity don't use our audio, so they have "different" terms, resources, etc.

  • Now, as I understand it, our audio uses audio plugins, and their types - well, that's also not very clear, why should our audio depend on an auxiliary module...

  • Therefore, I think we need to break this connection completely:

  • MF audio data types exist in our terminology, whatever it may be.

  • There are data types for Audacity audio and effects.

  • There is a module that verifies and loads user plugins and its data types.
    Each application has its own mapping:
    PluginTypes (some meta) - AudioTypes (resource) for MF
    PluginTypes (some meta) - EffectsTypes (...) for Audacity

Audio plugins must have their own types, and we need to convert one type to another

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually, I think right now it would be enough to do something like this:

  • muse::audioplugins::AudioResourceId;
  • muse::audio::AudioResourceId;
  • Find a place to make
audioplugins::AudioResourceId plugId = ...
audio::AudioResourceId audioId = plugId;

or better

audio::AudioResourceId toAudioResourceId(const audioplugins::AudioResourceId& id) 
{
    return id;
}

i.e. make it one to one

This may look strange now, but in essence this is exactly what we need

Then we can continue to:

  • Rename the types to reduce confusion and make them more meaningful
  • Change types independently, we will only need to change the conversion code

load() validated the envelope but still inserted rows with a missing
meta/path, creating empty-key records that poison the lookup map. Reject
the file when an entry's id or path is empty.
…ugin

The subprocess-side placeholder-clear discarded its Ret; a failed cache
write would let stale state through. Capture, log and propagate it.
@luapmartin luapmartin force-pushed the luapmartin/revamp-audio_plugins-module branch from 864f1c5 to 550d364 Compare May 22, 2026 15:33
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/audioplugins/audiopluginstypes.h`:
- Around line 143-162: The current conversion functions return an empty string
for AudioPluginState::Undefined which conflates undefined with unknown names;
update the mapping returned by detail::audioPluginStateNames() to include an
explicit entry for AudioPluginState::Undefined (e.g. "Undefined" or another
clear sentinel) and change audioPluginStateName(AudioPluginState) /
audioPluginStateFromName(const std::string&) to rely on that mapping so the name
lookup returns that explicit string and the reverse lookup maps that exact
string back to AudioPluginState::Undefined, making the conversion bijective and
unambiguous.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 823d4a18-a785-4fe8-887e-01b868c67c76

📥 Commits

Reviewing files that changed from the base of the PR and between 864f1c5 and 550d364.

📒 Files selected for processing (34)
  • framework/audio/common/audiotypes.h
  • framework/audio/common/audioutils.h
  • framework/audio/common/rpc/rpcpacker.h
  • framework/audio/engine/internal/audioengineconfiguration.cpp
  • framework/audio/engine/internal/enginerpccontroller.cpp
  • framework/audio/engine/internal/synthesizers/fluidsynth/fluidresolver.cpp
  • framework/audio/tests/CMakeLists.txt
  • framework/audio/tests/audioresourcetypes_tests.cpp
  • framework/audio/tests/rpcpacker_tests.cpp
  • framework/audioplugins/CMakeLists.txt
  • framework/audioplugins/audiopluginsmodule.cpp
  • framework/audioplugins/audiopluginstypes.h
  • framework/audioplugins/iaudiopluginmetareader.h
  • framework/audioplugins/iaudiopluginsconfiguration.h
  • framework/audioplugins/iknownaudiopluginsmigrationregister.h
  • framework/audioplugins/iknownaudiopluginsregister.h
  • framework/audioplugins/internal/audiopluginsconfiguration.cpp
  • framework/audioplugins/internal/audiopluginsconfiguration.h
  • framework/audioplugins/internal/knownaudiopluginsmigrationregister.cpp
  • framework/audioplugins/internal/knownaudiopluginsmigrationregister.h
  • framework/audioplugins/internal/knownaudiopluginsregister.cpp
  • framework/audioplugins/internal/knownaudiopluginsregister.h
  • framework/audioplugins/internal/registeraudiopluginsscenario.cpp
  • framework/audioplugins/internal/registeraudiopluginsscenario.h
  • framework/audioplugins/iregisteraudiopluginsscenario.h
  • framework/audioplugins/tests/CMakeLists.txt
  • framework/audioplugins/tests/audiopluginsutilstest.cpp
  • framework/audioplugins/tests/knownaudiopluginsmigrationregistertest.cpp
  • framework/audioplugins/tests/knownaudiopluginsregistertest.cpp
  • framework/audioplugins/tests/mocks/audiopluginmetareadermock.h
  • framework/audioplugins/tests/mocks/audiopluginsconfigurationmock.h
  • framework/audioplugins/tests/mocks/knownaudiopluginsmigrationregistermock.h
  • framework/audioplugins/tests/mocks/knownaudiopluginsregistermock.h
  • framework/audioplugins/tests/registeraudiopluginsscenariotest.cpp
💤 Files with no reviewable changes (1)
  • framework/audioplugins/tests/audiopluginsutilstest.cpp

Comment thread framework/audioplugins/audiopluginstypes.h
audioPluginStateName(Undefined) returned "" — indistinguishable from an
unknown name. Add an explicit Undefined<->"Undefined" entry so the
mapping is bijective. writePluginsInfo still omits the state field for
Undefined entries, so the JSON wire format is unchanged.
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