From f0b809d1663024db5924f1cc4d1749fe8d65b0f3 Mon Sep 17 00:00:00 2001 From: Igor Korsukov Date: Wed, 13 May 2026 11:38:40 +0200 Subject: [PATCH 1/4] [ci] updated check codestyle on CI --- muse | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/muse b/muse index 8b0f8a8b8a30b..550d36429c5fe 160000 --- a/muse +++ b/muse @@ -1 +1 @@ -Subproject commit 8b0f8a8b8a30bc04b05794544655a1eb1636fefd +Subproject commit 550d36429c5fe8ca73ff5666a273f668479b09c1 From df6fcc476dfe561163fa7662a47036c91d1b55cc Mon Sep 17 00:00:00 2001 From: Paul MARTIN Date: Tue, 12 May 2026 18:21:18 +0200 Subject: [PATCH 2/4] [audioplugins] adapt MuseScore to framework audioplugins revamp Companion to the muse_framework audioplugins revamp: - New AudioPluginsAppConfigModule wires v0->v1 (hasNativeEditorSupport into attributes) and v1->v2 (enabled boolean -> state string) cache migrations plus the runtime attribute defaults. - Adopts the new typed plugin format helpers (audio::isResourceType, audio::resourceTypeFromString, audio::hasNativeEditorSupport) at call sites in playback, project, notationscene, converter. - Consumes vst::CATEGORIES_ATTRIBUTE (moved out of the audio module). --- src/app/CMakeLists.txt | 3 + src/app/appfactory.cpp | 4 + .../internal/audiopluginsappconfigmodule.cpp | 111 ++++++++++++++++++ .../internal/audiopluginsappconfigmodule.h | 33 ++++++ .../internal/compat/notationmeta.cpp | 4 +- .../percussionpanel/percussionpanelmodel.cpp | 4 +- src/playback/internal/drumsetloader.cpp | 4 +- src/playback/internal/playbackcontroller.cpp | 2 +- .../internal/soundprofilesrepository.cpp | 4 +- .../MuseScore/Playback/inputresourceitem.cpp | 29 +++-- .../MuseScore/Playback/outputresourceitem.cpp | 5 +- src/project/internal/projectaudiosettings.cpp | 20 ++-- src/project/internal/projectaudiosettings.h | 2 +- 13 files changed, 194 insertions(+), 31 deletions(-) create mode 100644 src/app/internal/audiopluginsappconfigmodule.cpp create mode 100644 src/app/internal/audiopluginsappconfigmodule.h diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index cc087e68b9833..7a916f1d9432b 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -215,6 +215,9 @@ set(APP_SRC appfactory.cpp appfactory.h + internal/audiopluginsappconfigmodule.cpp + internal/audiopluginsappconfigmodule.h + internal/guiapp.cpp internal/guiapp.h ) diff --git a/src/app/appfactory.cpp b/src/app/appfactory.cpp index 3eb0fb0dd2e57..01300bbaed150 100644 --- a/src/app/appfactory.cpp +++ b/src/app/appfactory.cpp @@ -24,6 +24,7 @@ #ifdef MUSE_MODULE_AUDIOPLUGINS #include "framework/audioplugins/audiopluginsmodule.h" +#include "internal/audiopluginsappconfigmodule.h" #endif #ifdef MUSE_MODULE_CLOUD @@ -314,6 +315,7 @@ std::shared_ptr AppFactory::newGuiApp(const std::shared_ptr< app->addModule(new muse::audio::AudioModule()); #ifdef MUSE_MODULE_AUDIOPLUGINS app->addModule(new muse::audioplugins::AudioPluginsModule()); + app->addModule(new mu::app::AudioPluginsAppConfigModule()); #endif app->addModule(new muse::automation::AutomationModule()); app->addModule(new muse::draw::DrawModule()); @@ -455,6 +457,7 @@ static void addConsoleModules(std::shared_ptr app) app->addModule(new muse::audio::AudioModule()); #ifdef MUSE_MODULE_AUDIOPLUGINS app->addModule(new muse::audioplugins::AudioPluginsModule()); + app->addModule(new mu::app::AudioPluginsAppConfigModule()); #endif app->addModule(new muse::draw::DrawModule()); app->addModule(new muse::midi::MidiModule()); @@ -547,6 +550,7 @@ static void addAudioPluginRegistrationModules(std::shared_ptraddModule(new muse::audioplugins::AudioPluginsModule()); + app->addModule(new mu::app::AudioPluginsAppConfigModule()); #endif #ifdef MUSE_MODULE_VST diff --git a/src/app/internal/audiopluginsappconfigmodule.cpp b/src/app/internal/audiopluginsappconfigmodule.cpp new file mode 100644 index 0000000000000..40c90068bb352 --- /dev/null +++ b/src/app/internal/audiopluginsappconfigmodule.cpp @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "audiopluginsappconfigmodule.h" + +#include "modularity/ioc.h" + +#include "global/serialization/json.h" + +#include "audio/common/audiotypes.h" +#include "mpe/playbacksetupdata.h" + +#include "audioplugins/iaudiopluginsconfiguration.h" +#include "audioplugins/iknownaudiopluginsmigrationregister.h" + +using namespace mu::app; +using namespace muse::audioplugins; + +static const std::string mname("audiopluginsappconfig"); + +std::string AudioPluginsAppConfigModule::moduleName() const +{ + return mname; +} + +void AudioPluginsAppConfigModule::resolveImports() +{ + auto configuration = muse::modularity::globalIoc()->resolve(moduleName()); + if (configuration) { + // MuseScore's audio engine routes synthesis via the playbackSetupData + // attribute. It is runtime-only — the engine re-injects the generic + // default at every load, so the cache file should not persist it. + AudioResourceAttributes runtimeDefaults; + runtimeDefaults.emplace(muse::audio::PLAYBACK_SETUP_DATA_ATTRIBUTE, + muse::mpe::GENERIC_SETUP_DATA_STRING); + configuration->setRuntimeAttributeDefaults(runtimeDefaults); + } + + auto migrations = muse::modularity::globalIoc()->resolve(moduleName()); + if (migrations) { + // v0 → v1: hasNativeEditorSupport moved from a top-level meta field + // into meta.attributes (string "true"/"false"). + migrations->registerMigration(0, [](const muse::JsonArray& plugins) { + muse::JsonArray out; + for (size_t i = 0; i < plugins.size(); ++i) { + muse::JsonObject obj = plugins.at(i).toObject(); + muse::JsonObject meta = obj.value("meta").toObject(); + if (meta.contains("hasNativeEditorSupport")) { + muse::JsonObject attrs; + if (meta.contains("attributes")) { + attrs = meta.value("attributes").toObject(); + } + const bool b = meta.value("hasNativeEditorSupport").toBool(); + attrs.set("hasNativeEditorSupport", b ? std::string("true") : std::string("false")); + meta.set("attributes", attrs); + + // JsonObject has no remove(); rebuild without the legacy key. + muse::JsonObject metaWithoutLegacy; + for (const std::string& k : meta.keys()) { + if (k == "hasNativeEditorSupport") { + continue; + } + metaWithoutLegacy.set(k, meta.value(k)); + } + obj.set("meta", metaWithoutLegacy); + } + out << obj; + } + return out; + }); + + // v1 → v2: replace the boolean `enabled` flag with a `state` string + // ("Validated" / "Error"). errorCode is preserved. + migrations->registerMigration(1, [](const muse::JsonArray& plugins) { + muse::JsonArray out; + for (size_t i = 0; i < plugins.size(); ++i) { + muse::JsonObject obj = plugins.at(i).toObject(); + const bool enabled = obj.value("enabled").toBool(); + obj.set("state", enabled ? std::string("Validated") : std::string("Error")); + + muse::JsonObject rebuilt; + for (const std::string& k : obj.keys()) { + if (k == "enabled") { + continue; + } + rebuilt.set(k, obj.value(k)); + } + out << rebuilt; + } + return out; + }); + } +} diff --git a/src/app/internal/audiopluginsappconfigmodule.h b/src/app/internal/audiopluginsappconfigmodule.h new file mode 100644 index 0000000000000..c5c959f81adb6 --- /dev/null +++ b/src/app/internal/audiopluginsappconfigmodule.h @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2026 MuseScore Limited and others + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include "modularity/imodulesetup.h" + +namespace mu::app { +class AudioPluginsAppConfigModule : public muse::modularity::IModuleSetup +{ +public: + std::string moduleName() const override; + void resolveImports() override; +}; +} diff --git a/src/converter/internal/compat/notationmeta.cpp b/src/converter/internal/compat/notationmeta.cpp index 104e899a3040f..1db7154f31c65 100644 --- a/src/converter/internal/compat/notationmeta.cpp +++ b/src/converter/internal/compat/notationmeta.cpp @@ -378,9 +378,9 @@ QJsonArray NotationMeta::tracksJsonArray(notation::INotationPtr notation) QJsonObject jsonTrack; jsonTrack.insert("instrumentId", trackId.instrumentId.toQString()); jsonTrack.insert("partId", trackId.partId.toQString()); - jsonTrack.insert("type", audioResourceTypeToString(inputParams.resourceMeta.type).toQString()); + jsonTrack.insert("type", audio::audioResourceTypeToString(inputParams.resourceMeta.type).toQString()); - audio::AudioSourceType sourceType = sourceTypeFromResourceType(inputParams.resourceMeta.type); + audio::AudioSourceType sourceType = audio::sourceTypeFromResourceType(inputParams.resourceMeta.type); if (sourceType != audio::AudioSourceType::Fluid) { if (sourceType == audio::AudioSourceType::MuseSampler) { jsonTrack.insert("vendor", QString::fromStdString(inputParams.resourceMeta.attributeVal( diff --git a/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp b/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp index 97eef8565d070..4257490166748 100644 --- a/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp +++ b/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp @@ -554,7 +554,7 @@ void PercussionPanelModel::resetLayout() } const muse::audio::AudioResourceMeta& resourceMeta = audioSettings()->trackInputParams(currentTrackId()).resourceMeta; - const bool isMuseSamplerDrumset = resourceMeta.type == muse::audio::AudioResourceType::MuseSamplerSoundPack; + const bool isMuseSamplerDrumset = resourceMeta.type == "MuseSamplerSoundPack"; Drumset defaultDrumset = isMuseSamplerDrumset ? museSamplerDefaultDrumset() : standardDefaultDrumset(); @@ -594,7 +594,7 @@ Drumset PercussionPanelModel::museSamplerDefaultDrumset() const const muse::audio::AudioResourceMeta& resourceMeta = audioSettings()->trackInputParams(currentTrackId()).resourceMeta; - const int instrumentId = resourceMeta.attributeVal(u"museUID").toInt(); + const int instrumentId = muse::audioplugins::intAttribute(resourceMeta, u"museUID"); const muse::ByteArray drumMapping = museSampler()->drumMapping(instrumentId); IF_ASSERT_FAILED(!drumMapping.empty()) { diff --git a/src/playback/internal/drumsetloader.cpp b/src/playback/internal/drumsetloader.cpp index 3e4d336b1cc1c..c94d1c2c314ce 100644 --- a/src/playback/internal/drumsetloader.cpp +++ b/src/playback/internal/drumsetloader.cpp @@ -41,7 +41,7 @@ void DrumsetLoader::loadDrumset(INotationPtr notation, const InstrumentTrackId& } // restore the default drumset when changing from MuseSounds to MS Basic / VST - if (resourceMeta.type != AudioResourceType::MuseSamplerSoundPack) { + if (resourceMeta.type != "MuseSamplerSoundPack") { const InstrumentTemplate& templ = instrumentsRepository()->instrumentTemplate(trackId.instrumentId); if (!templ.useDrumset) { return; @@ -56,7 +56,7 @@ void DrumsetLoader::loadDrumset(INotationPtr notation, const InstrumentTrackId& return; } - int instrumentId = resourceMeta.attributeVal(u"museUID").toInt(); + int instrumentId = muse::audioplugins::intAttribute(resourceMeta, u"museUID"); auto it = m_drumsetCache.find(instrumentId); if (it != m_drumsetCache.end()) { diff --git a/src/playback/internal/playbackcontroller.cpp b/src/playback/internal/playbackcontroller.cpp index 54dd5ba15c4f4..fc7b853ba9cda 100644 --- a/src/playback/internal/playbackcontroller.cpp +++ b/src/playback/internal/playbackcontroller.cpp @@ -492,7 +492,7 @@ bool PlaybackController::shouldLoadDrumset(const engraving::InstrumentTrackId& i return false; } - return oldMeta.type == AudioResourceType::MuseSamplerSoundPack || newMeta.type == AudioResourceType::MuseSamplerSoundPack; + return oldMeta.type == "MuseSamplerSoundPack" || newMeta.type == "MuseSamplerSoundPack"; } void PlaybackController::addSoundFlagsIfNeed(const std::vector& selection) diff --git a/src/playback/internal/soundprofilesrepository.cpp b/src/playback/internal/soundprofilesrepository.cpp index 7e6eca6cd6800..25a96823ce63a 100644 --- a/src/playback/internal/soundprofilesrepository.cpp +++ b/src/playback/internal/soundprofilesrepository.cpp @@ -67,11 +67,11 @@ void SoundProfilesRepository::refresh() continue; } - if (resource.type == AudioResourceType::FluidSoundfont) { + if (resource.type == "FluidSoundfont") { basicProfile.data.emplace(mpe::PlaybackSetupData::fromString(setup->second), resource); } - if (resource.type == AudioResourceType::MuseSamplerSoundPack) { + if (resource.type == "MuseSamplerSoundPack") { museProfile.data.emplace(mpe::PlaybackSetupData::fromString(setup->second), resource); } } diff --git a/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp b/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp index 7b2b68dc41b1c..ff0f24c034153 100644 --- a/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp +++ b/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp @@ -87,9 +87,11 @@ void InputResourceItem::requestAvailableResources() if (!isBlank()) { QString currentResourceId = QString::fromStdString(m_currentInputParams.resourceMeta.id); - result << buildMenuItem(makeMenuResourceItemId(m_currentInputParams.resourceMeta.type, currentResourceId), - title(), - /*checked*/ true, /*subItems*/ QVariantList(), /*includeInFilteredLists*/ false); + result << + buildMenuItem(makeMenuResourceItemId(muse::audio::resourceTypeFromString(m_currentInputParams.resourceMeta.type), + currentResourceId), + title(), + /*checked*/ true, /*subItems*/ QVariantList(), /*includeInFilteredLists*/ false); result << buildSeparator(); } @@ -208,7 +210,7 @@ bool InputResourceItem::isActive() const bool InputResourceItem::hasNativeEditorSupport() const { - return m_currentInputParams.resourceMeta.hasNativeEditorSupport; + return muse::audio::hasNativeEditorSupport(m_currentInputParams.resourceMeta); } QVariantMap InputResourceItem::buildMuseMenuItem(const ResourceByVendorMap& resourcesByVendor) const @@ -300,7 +302,7 @@ QVariantMap InputResourceItem::buildMuseMenuItem(const ResourceByVendorMap& reso return buildMenuItem(MUSE_MENU_ITEM_ID, MUSE_MENU_ITEM_ID, - m_currentInputParams.resourceMeta.type == AudioResourceType::MuseSamplerSoundPack, + m_currentInputParams.resourceMeta.type == "MuseSamplerSoundPack", subItemsByType, /*includeInFilteredLists*/ false); } @@ -314,7 +316,7 @@ QVariantMap InputResourceItem::buildVstMenuItem(const ResourceByVendorMap& resou for (const AudioResourceMeta& resourceMeta : pair.second) { QString resourceId = QString::fromStdString(resourceMeta.id); - subItemsByVendor << buildMenuItem(makeMenuResourceItemId(resourceMeta.type, resourceId), + subItemsByVendor << buildMenuItem(makeMenuResourceItemId(muse::audio::resourceTypeFromString(resourceMeta.type), resourceId), resourceId, m_currentInputParams.resourceMeta.id == resourceMeta.id); } @@ -330,7 +332,7 @@ QVariantMap InputResourceItem::buildVstMenuItem(const ResourceByVendorMap& resou return buildMenuItem(VST_MENU_ITEM_ID, VST_MENU_ITEM_ID, - m_currentInputParams.resourceMeta.type == AudioResourceType::VstPlugin, + m_currentInputParams.resourceMeta.type == "VstPlugin", subItemsByType, /*includeInFilteredLists*/ false); } @@ -391,7 +393,7 @@ QVariantMap InputResourceItem::buildSoundFontsMenuItem(const ResourceByVendorMap return buildMenuItem(SOUNDFONTS_MENU_ITEM_ID, muse::qtrc("playback", "SoundFonts"), - m_currentInputParams.resourceMeta.type == AudioResourceType::FluidSoundfont, + m_currentInputParams.resourceMeta.type == "FluidSoundfont", soundFontItems, /*includeInFilteredLists*/ false); } @@ -436,7 +438,8 @@ QVariantMap InputResourceItem::buildMsBasicMenuItem(const AudioResourceMetaList& presetName = muse::qtrc("playback", "Bank %1, preset %2").arg(item.preset.bank).arg(item.preset.program); } - return buildMenuItem(makeMenuResourceItemId(resourceMeta.type, QString::fromStdString(resourceMeta.id)), + return buildMenuItem(makeMenuResourceItemId(muse::audio::resourceTypeFromString(resourceMeta.type), + QString::fromStdString(resourceMeta.id)), presetName, isCurrent); } @@ -487,7 +490,8 @@ QVariantMap InputResourceItem::buildMsBasicMenuItem(const AudioResourceMetaList& // Prepend the "Choose automatically" item categoryItems.prepend(buildSeparator()); - categoryItems.prepend(buildMenuItem(makeMenuResourceItemId(chooseAutomaticMeta.type, QString::fromStdString(chooseAutomaticMeta.id)), + categoryItems.prepend(buildMenuItem(makeMenuResourceItemId(muse::audio::resourceTypeFromString(chooseAutomaticMeta.type), + QString::fromStdString(chooseAutomaticMeta.id)), muse::qtrc("playback", "Choose automatically"), isCurrentSoundFont && !currentPreset.has_value(), /*subItems*/ QVariantList(), @@ -552,7 +556,8 @@ QVariantMap InputResourceItem::buildSoundFontMenuItem(const muse::String& soundF // Prepend the "Choose automatically" item bankItems.prepend(buildSeparator()); - bankItems.prepend(buildMenuItem(makeMenuResourceItemId(chooseAutomaticMeta.type, QString::fromStdString(chooseAutomaticMeta.id)), + bankItems.prepend(buildMenuItem(makeMenuResourceItemId(muse::audio::resourceTypeFromString(chooseAutomaticMeta.type), + QString::fromStdString(chooseAutomaticMeta.id)), muse::qtrc("playback", "Choose automatically"), isCurrentSoundFont && !currentPreset.has_value(), /*subItems*/ QVariantList(), @@ -571,7 +576,7 @@ void InputResourceItem::updateAvailableResources(const AudioResourceMetaList& av m_availableResourceMap.clear(); for (const AudioResourceMeta& meta : availableResources) { - ResourceByVendorMap& resourcesByVendor = m_availableResourceMap[meta.type]; + ResourceByVendorMap& resourcesByVendor = m_availableResourceMap[muse::audio::resourceTypeFromString(meta.type)]; AudioResourceMetaList& resourcesMetaList = resourcesByVendor[meta.vendor]; resourcesMetaList.push_back(meta); } diff --git a/src/playback/qml/MuseScore/Playback/outputresourceitem.cpp b/src/playback/qml/MuseScore/Playback/outputresourceitem.cpp index 7b97dd0121341..9518e939c34e8 100644 --- a/src/playback/qml/MuseScore/Playback/outputresourceitem.cpp +++ b/src/playback/qml/MuseScore/Playback/outputresourceitem.cpp @@ -3,6 +3,7 @@ #include #include "audio/common/audioutils.h" +#include "vst/vstpluginattrs.h" #include "log.h" #include "translation.h" @@ -183,7 +184,7 @@ void OutputResourceItem::updateCurrentFxParams(const AudioResourceMeta& newMeta) requestToCloseNativeEditorView(); audio::AudioFxParams newParams = m_currentFxParams; - newParams.categories = audio::audioFxCategoriesFromString(newMeta.attributeVal(audio::CATEGORIES_ATTRIBUTE)); + newParams.categories = audio::audioFxCategoriesFromString(newMeta.attributeVal(vst::CATEGORIES_ATTRIBUTE)); newParams.resourceMeta = newMeta; newParams.configuration.clear(); newParams.active = newMeta.isValid(); @@ -213,5 +214,5 @@ bool OutputResourceItem::isBlank() const bool OutputResourceItem::hasNativeEditorSupport() const { - return m_currentFxParams.resourceMeta.hasNativeEditorSupport; + return muse::audio::hasNativeEditorSupport(m_currentFxParams.resourceMeta); } diff --git a/src/project/internal/projectaudiosettings.cpp b/src/project/internal/projectaudiosettings.cpp index fb33b56003a20..28789b25227db 100644 --- a/src/project/internal/projectaudiosettings.cpp +++ b/src/project/internal/projectaudiosettings.cpp @@ -29,6 +29,7 @@ #include "types/bytearray.h" #include "audio/common/audioutils.h" +#include "vst/vstpluginattrs.h" using namespace mu::project; using namespace muse; @@ -45,7 +46,7 @@ static const std::map SOURCE_TYPE_MAP = { static void doCompatibilityConversions(AudioResourceMeta& meta) { - if (meta.type == AudioResourceType::MuseSamplerSoundPack) { + if (meta.type == "MuseSamplerSoundPack") { // MS 4.5: resource name and category have been excluded from ID // Old format: category\\name\\uid if (meta.id.find("\\") == std::string::npos) { @@ -380,7 +381,7 @@ AudioFxParams ProjectAudioSettings::fxParamsFromJson(const QJsonObject& object) result.chainOrder = static_cast(object.value("chainOrder").toInt()); result.resourceMeta = resourceMetaFromJson(object.value("resourceMeta").toObject()); result.configuration = unitConfigFromJson(object.value("unitConfiguration").toObject()); - result.categories = audioFxCategoriesFromString(result.resourceMeta.attributeVal(audio::CATEGORIES_ATTRIBUTE)); + result.categories = audioFxCategoriesFromString(result.resourceMeta.attributeVal(vst::CATEGORIES_ATTRIBUTE)); return result; } @@ -410,11 +411,17 @@ AudioResourceMeta ProjectAudioSettings::resourceMetaFromJson(const QJsonObject& { AudioResourceMeta result; result.id = object.value("id").toString().toStdString(); - result.hasNativeEditorSupport = object.value("hasNativeEditorSupport").toBool(); result.vendor = object.value("vendor").toString().toStdString(); result.type = resourceTypeFromString(object.value("type").toString()); result.attributes = attributesFromJson(object.value("attributes").toObject()); + // Migrate the legacy top-level "hasNativeEditorSupport" boolean into attributes + // for project files written before audio plugin cache version 1. + if (object.contains("hasNativeEditorSupport") && !result.attributes.count(audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE)) { + const bool b = object.value("hasNativeEditorSupport").toBool(); + result.attributes.emplace(audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE, b ? u"true" : u"false"); + } + return result; } @@ -519,7 +526,6 @@ QJsonObject ProjectAudioSettings::resourceMetaToJson(const AudioResourceMeta& me { QJsonObject result; result.insert("id", QString::fromStdString(meta.id)); - result.insert("hasNativeEditorSupport", meta.hasNativeEditorSupport); result.insert("vendor", QString::fromStdString(meta.vendor)); result.insert("type", audioResourceTypeToString(meta.type).toQString()); result.insert("attributes", attributesToJson(meta.attributes)); @@ -561,15 +567,15 @@ AudioSourceType ProjectAudioSettings::sourceTypeFromString(const QString& string return AudioSourceType::Undefined; } -AudioResourceType ProjectAudioSettings::resourceTypeFromString(const QString& string) const +audioplugins::AudioResourceType ProjectAudioSettings::resourceTypeFromString(const QString& string) const { for (const auto& pair : RESOURCE_TYPE_MAP) { if (pair.second == string) { - return pair.first; + return audio::resourceTypeName(pair.first); } } - return AudioResourceType::Undefined; + return audioplugins::AudioResourceType(); } QString ProjectAudioSettings::sourceTypeToString(const AudioSourceType& type) const diff --git a/src/project/internal/projectaudiosettings.h b/src/project/internal/projectaudiosettings.h index eda61bf16ed54..81c89e3f0a36d 100644 --- a/src/project/internal/projectaudiosettings.h +++ b/src/project/internal/projectaudiosettings.h @@ -101,7 +101,7 @@ class ProjectAudioSettings : public IProjectAudioSettings, public muse::Contexta QJsonObject attributesToJson(const muse::audio::AudioResourceAttributes& attributes) const; muse::audio::AudioSourceType sourceTypeFromString(const QString& string) const; - muse::audio::AudioResourceType resourceTypeFromString(const QString& string) const; + muse::audioplugins::AudioResourceType resourceTypeFromString(const QString& string) const; QString sourceTypeToString(const muse::audio::AudioSourceType& type) const; From 9319275e44d9d8444fefb6a904b4f122203a970f Mon Sep 17 00:00:00 2001 From: Paul MARTIN Date: Mon, 11 May 2026 15:38:56 +0200 Subject: [PATCH 3/4] [playback,project,notationscene] use audio::isResourceType helper Replaces the remaining literal-string plugin-type comparisons (e.g. meta.type == "MuseSamplerSoundPack") with audio::isResourceType(meta, AudioResourceType::X). Single source of truth for the wire strings now lives in the framework enum's bridge, plus a unit test pinning them. Bumps muse/ to pick up the framework changes. --- .../percussionpanel/percussionpanelmodel.cpp | 3 ++- src/playback/internal/drumsetloader.cpp | 2 +- src/playback/internal/playbackcontroller.cpp | 3 ++- src/playback/internal/soundprofilesrepository.cpp | 4 ++-- .../qml/MuseScore/Playback/inputresourceitem.cpp | 9 ++++++--- src/project/internal/projectaudiosettings.cpp | 2 +- 6 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp b/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp index 4257490166748..e09babcf0d982 100644 --- a/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp +++ b/src/notationscene/qml/MuseScore/NotationScene/percussionpanel/percussionpanelmodel.cpp @@ -554,7 +554,8 @@ void PercussionPanelModel::resetLayout() } const muse::audio::AudioResourceMeta& resourceMeta = audioSettings()->trackInputParams(currentTrackId()).resourceMeta; - const bool isMuseSamplerDrumset = resourceMeta.type == "MuseSamplerSoundPack"; + const bool isMuseSamplerDrumset = muse::audio::isResourceType(resourceMeta, + muse::audio::AudioResourceType::MuseSamplerSoundPack); Drumset defaultDrumset = isMuseSamplerDrumset ? museSamplerDefaultDrumset() : standardDefaultDrumset(); diff --git a/src/playback/internal/drumsetloader.cpp b/src/playback/internal/drumsetloader.cpp index c94d1c2c314ce..b249a662025e5 100644 --- a/src/playback/internal/drumsetloader.cpp +++ b/src/playback/internal/drumsetloader.cpp @@ -41,7 +41,7 @@ void DrumsetLoader::loadDrumset(INotationPtr notation, const InstrumentTrackId& } // restore the default drumset when changing from MuseSounds to MS Basic / VST - if (resourceMeta.type != "MuseSamplerSoundPack") { + if (!isResourceType(resourceMeta, AudioResourceType::MuseSamplerSoundPack)) { const InstrumentTemplate& templ = instrumentsRepository()->instrumentTemplate(trackId.instrumentId); if (!templ.useDrumset) { return; diff --git a/src/playback/internal/playbackcontroller.cpp b/src/playback/internal/playbackcontroller.cpp index fc7b853ba9cda..3e26a5d9ce3eb 100644 --- a/src/playback/internal/playbackcontroller.cpp +++ b/src/playback/internal/playbackcontroller.cpp @@ -492,7 +492,8 @@ bool PlaybackController::shouldLoadDrumset(const engraving::InstrumentTrackId& i return false; } - return oldMeta.type == "MuseSamplerSoundPack" || newMeta.type == "MuseSamplerSoundPack"; + return isResourceType(oldMeta, AudioResourceType::MuseSamplerSoundPack) + || isResourceType(newMeta, AudioResourceType::MuseSamplerSoundPack); } void PlaybackController::addSoundFlagsIfNeed(const std::vector& selection) diff --git a/src/playback/internal/soundprofilesrepository.cpp b/src/playback/internal/soundprofilesrepository.cpp index 25a96823ce63a..890793692e21c 100644 --- a/src/playback/internal/soundprofilesrepository.cpp +++ b/src/playback/internal/soundprofilesrepository.cpp @@ -67,11 +67,11 @@ void SoundProfilesRepository::refresh() continue; } - if (resource.type == "FluidSoundfont") { + if (isResourceType(resource, AudioResourceType::FluidSoundfont)) { basicProfile.data.emplace(mpe::PlaybackSetupData::fromString(setup->second), resource); } - if (resource.type == "MuseSamplerSoundPack") { + if (isResourceType(resource, AudioResourceType::MuseSamplerSoundPack)) { museProfile.data.emplace(mpe::PlaybackSetupData::fromString(setup->second), resource); } } diff --git a/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp b/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp index ff0f24c034153..2c73bb53d60d8 100644 --- a/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp +++ b/src/playback/qml/MuseScore/Playback/inputresourceitem.cpp @@ -302,7 +302,8 @@ QVariantMap InputResourceItem::buildMuseMenuItem(const ResourceByVendorMap& reso return buildMenuItem(MUSE_MENU_ITEM_ID, MUSE_MENU_ITEM_ID, - m_currentInputParams.resourceMeta.type == "MuseSamplerSoundPack", + muse::audio::isResourceType(m_currentInputParams.resourceMeta, + muse::audio::AudioResourceType::MuseSamplerSoundPack), subItemsByType, /*includeInFilteredLists*/ false); } @@ -332,7 +333,8 @@ QVariantMap InputResourceItem::buildVstMenuItem(const ResourceByVendorMap& resou return buildMenuItem(VST_MENU_ITEM_ID, VST_MENU_ITEM_ID, - m_currentInputParams.resourceMeta.type == "VstPlugin", + muse::audio::isResourceType(m_currentInputParams.resourceMeta, + muse::audio::AudioResourceType::VstPlugin), subItemsByType, /*includeInFilteredLists*/ false); } @@ -393,7 +395,8 @@ QVariantMap InputResourceItem::buildSoundFontsMenuItem(const ResourceByVendorMap return buildMenuItem(SOUNDFONTS_MENU_ITEM_ID, muse::qtrc("playback", "SoundFonts"), - m_currentInputParams.resourceMeta.type == "FluidSoundfont", + muse::audio::isResourceType(m_currentInputParams.resourceMeta, + muse::audio::AudioResourceType::FluidSoundfont), soundFontItems, /*includeInFilteredLists*/ false); } diff --git a/src/project/internal/projectaudiosettings.cpp b/src/project/internal/projectaudiosettings.cpp index 28789b25227db..86b098660861e 100644 --- a/src/project/internal/projectaudiosettings.cpp +++ b/src/project/internal/projectaudiosettings.cpp @@ -46,7 +46,7 @@ static const std::map SOURCE_TYPE_MAP = { static void doCompatibilityConversions(AudioResourceMeta& meta) { - if (meta.type == "MuseSamplerSoundPack") { + if (isResourceType(meta, AudioResourceType::MuseSamplerSoundPack)) { // MS 4.5: resource name and category have been excluded from ID // Old format: category\\name\\uid if (meta.id.find("\\") == std::string::npos) { From a37d4916fb8fba0ed8883bf53d40425dec1fe02d Mon Sep 17 00:00:00 2001 From: Paul MARTIN Date: Wed, 13 May 2026 12:50:24 +0200 Subject: [PATCH 4/4] [audioplugins] framework auto-registers its own cache migrations 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). --- muse | 2 +- .../internal/audiopluginsappconfigmodule.cpp | 40 ++++++------------- src/project/internal/projectaudiosettings.cpp | 5 ++- 3 files changed, 16 insertions(+), 31 deletions(-) diff --git a/muse b/muse index 550d36429c5fe..491a3c0e2a4fc 160000 --- a/muse +++ b/muse @@ -1 +1 @@ -Subproject commit 550d36429c5fe8ca73ff5666a273f668479b09c1 +Subproject commit 491a3c0e2a4fcc9a95c14ceb3dcda0ffe344103d diff --git a/src/app/internal/audiopluginsappconfigmodule.cpp b/src/app/internal/audiopluginsappconfigmodule.cpp index 40c90068bb352..810407c101571 100644 --- a/src/app/internal/audiopluginsappconfigmodule.cpp +++ b/src/app/internal/audiopluginsappconfigmodule.cpp @@ -54,28 +54,33 @@ void AudioPluginsAppConfigModule::resolveImports() configuration->setRuntimeAttributeDefaults(runtimeDefaults); } + // MuseScore-specific audioplugins migrations only — framework-owned + // steps (v0->v1 structural, v1->v2 enabled->state) are pre-registered + // by the framework's AudioPluginsModule. auto migrations = muse::modularity::globalIoc()->resolve(moduleName()); if (migrations) { - // v0 → v1: hasNativeEditorSupport moved from a top-level meta field - // into meta.attributes (string "true"/"false"). - migrations->registerMigration(0, [](const muse::JsonArray& plugins) { + // v2 → v3: hasNativeEditorSupport moved from a top-level meta field + // into meta.attributes (string "true"/"false"). MuseScore-specific: + // audacity has no native-editor concept. + migrations->registerMigration(2, [](const muse::JsonArray& plugins) { + const std::string nativeEditorKey = muse::audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE.toStdString(); muse::JsonArray out; for (size_t i = 0; i < plugins.size(); ++i) { muse::JsonObject obj = plugins.at(i).toObject(); muse::JsonObject meta = obj.value("meta").toObject(); - if (meta.contains("hasNativeEditorSupport")) { + if (meta.contains(nativeEditorKey)) { muse::JsonObject attrs; if (meta.contains("attributes")) { attrs = meta.value("attributes").toObject(); } - const bool b = meta.value("hasNativeEditorSupport").toBool(); - attrs.set("hasNativeEditorSupport", b ? std::string("true") : std::string("false")); + const bool b = meta.value(nativeEditorKey).toBool(); + attrs.set(nativeEditorKey, b ? std::string("true") : std::string("false")); meta.set("attributes", attrs); // JsonObject has no remove(); rebuild without the legacy key. muse::JsonObject metaWithoutLegacy; for (const std::string& k : meta.keys()) { - if (k == "hasNativeEditorSupport") { + if (k == nativeEditorKey) { continue; } metaWithoutLegacy.set(k, meta.value(k)); @@ -86,26 +91,5 @@ void AudioPluginsAppConfigModule::resolveImports() } return out; }); - - // v1 → v2: replace the boolean `enabled` flag with a `state` string - // ("Validated" / "Error"). errorCode is preserved. - migrations->registerMigration(1, [](const muse::JsonArray& plugins) { - muse::JsonArray out; - for (size_t i = 0; i < plugins.size(); ++i) { - muse::JsonObject obj = plugins.at(i).toObject(); - const bool enabled = obj.value("enabled").toBool(); - obj.set("state", enabled ? std::string("Validated") : std::string("Error")); - - muse::JsonObject rebuilt; - for (const std::string& k : obj.keys()) { - if (k == "enabled") { - continue; - } - rebuilt.set(k, obj.value(k)); - } - out << rebuilt; - } - return out; - }); } } diff --git a/src/project/internal/projectaudiosettings.cpp b/src/project/internal/projectaudiosettings.cpp index 86b098660861e..d30dbfe2aeec0 100644 --- a/src/project/internal/projectaudiosettings.cpp +++ b/src/project/internal/projectaudiosettings.cpp @@ -417,8 +417,9 @@ AudioResourceMeta ProjectAudioSettings::resourceMetaFromJson(const QJsonObject& // Migrate the legacy top-level "hasNativeEditorSupport" boolean into attributes // for project files written before audio plugin cache version 1. - if (object.contains("hasNativeEditorSupport") && !result.attributes.count(audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE)) { - const bool b = object.value("hasNativeEditorSupport").toBool(); + const QString nativeEditorKey = audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE.toQString(); + if (object.contains(nativeEditorKey) && !result.attributes.count(audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE)) { + const bool b = object.value(nativeEditorKey).toBool(); result.attributes.emplace(audio::HAS_NATIVE_EDITOR_SUPPORT_ATTRIBUTE, b ? u"true" : u"false"); }