Skip to content

Conversation

@manolo
Copy link
Contributor

@manolo manolo commented Nov 25, 2025

Resolves: #31201

Add a new Score.replaceInstrument(part, instrumentId) method to the Plugin API, allowing plugins to programmatically change instruments on score parts.

Changes

  • Add Q_INVOKABLE Score::replaceInstrument(Part*, QString) to the plugin API
  • Bridge plugin API to existing NotationParts::replaceInstrument() infrastructure
  • Use Part::MAIN_INSTRUMENT_TICK to correctly identify the main instrument vs mid-score instrument changes
  • Add unit tests for instrument replacement with undo/redo support
  • Add test environment setup for API tests

Usage Example

var part = curScore.parts[0];
curScore.replaceInstrument(part, "violin");

The instrumentId parameter should match IDs from instruments.xml.


  • 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)

@manolo manolo force-pushed the pr_replace_instrument branch from 4fb76ec to 30635e5 Compare November 25, 2025 20:57
@manolo manolo marked this pull request as draft November 25, 2025 21:27
@manolo manolo force-pushed the pr_replace_instrument branch 2 times, most recently from 0e22080 to 0a9d2fe Compare November 25, 2025 21:45
@manolo manolo marked this pull request as ready for review November 25, 2025 22:09
@manolo manolo marked this pull request as draft November 25, 2025 22:11
@manolo manolo force-pushed the pr_replace_instrument branch 2 times, most recently from 245bdd8 to 125d62c Compare November 26, 2025 07:26
@cbjeukendrup cbjeukendrup added the plugins Related to MuseScore's QML plugin framework (NOT VST/audio plugins) label Jan 18, 2026
@manolo manolo force-pushed the pr_replace_instrument branch from 125d62c to e8e560a Compare January 19, 2026 06:41
@manolo manolo marked this pull request as ready for review January 19, 2026 06:41

/// Replaces the main instrument for a part.
/// Updates the part name, instrument, and clefs for all staves.
void replacePartInstrument(Score* score, Part* part, const Instrument& newInstrument,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These new funcions might be static like in EditSystemLocks , but not sure about the preferences in MS for these

initialInstrument.setTrackName(u"Flute");
domPart->setInstrument(initialInstrument);

EXPECT_EQ(domPart->instrumentId(), QString("test.flute"));
Copy link
Member

@cbjeukendrup cbjeukendrup Feb 1, 2026

Choose a reason for hiding this comment

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

Suggested change
EXPECT_EQ(domPart->instrumentId(), QString("test.flute"));
EXPECT_EQ(domPart->instrumentId(), u"test.flute");

and similar in all those cases

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

String newInstrumentPartName = formatInstrumentTitle(newInstrument.trackName(), newInstrument.trait());
mu::engraving::replacePartInstrument(score(), part, newInstrument, newStaffType, newInstrumentPartName);
} else {
mu::engraving::InstrumentChange* instrumentChange = findInstrumentChange(part, instrumentKey.tick);
Copy link
Member

Choose a reason for hiding this comment

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

NotationParts::findInstrumentChange can now be deleted

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


// Change the part's instrument and name
String newPartName = partName.isEmpty() ? newInstrument.trackName() : partName;
score->undo(new ChangePart(part, new Instrument(newInstrument), newPartName));
Copy link
Member

Choose a reason for hiding this comment

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

Not a new problem, but it looks like the new Instrument will be leaked, at least on undo. Solution might be to implement ChangePart::cleanup, which deletes the ChangePart::instrument.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


/// Replaces the main instrument for a part.
/// Updates the part name, instrument, and clefs for all staves.
void replacePartInstrument(Score* score, Part* part, const Instrument& newInstrument, const StaffType* newStaffType = nullptr,
Copy link
Member

Choose a reason for hiding this comment

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

We might want to put these functions as static methods in an EditPart class; somehow that feels more structured to me than a bunch of free functions. But it's not a big deal; if you think there are reasons to not do that, then we should not do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@cbjeukendrup
Copy link
Member

I like the new structure! This is indeed the kind of things that suit well in the engraving/editing folder.

I would propose to tweak and merge this PR first with just replaceInstrument, and then potentially add more functions in a later PR.

Expose instrument replacement functionality to plugins through new
Score.replaceInstrument() method. Allows plugins to change a part's
instrument (name, clef, transposition, sound) with full undo/redo support.

Changes:
- Add Q_INVOKABLE Score::replaceInstrument(Part*, QString) to plugin API
- Use Part::MAIN_INSTRUMENT_TICK (not Fraction(0,1)) to correctly identify
  main instrument vs. mid-score instrument changes

Usage example:
  var part = curScore.parts[0];
  curScore.replaceInstrument(part, "violin");
Remove dependency on notation module by using engraving::ChangePart
command directly instead of going through INotationParts. This:
- Eliminates need for NotationMock and NotationPartsMock in tests
- Simplifies test setup (no mock injection required)
- Keeps the implementation purely at the engraving layer
- Makes the API self-contained within the plugin API module
…lugin API

Move instrument replacement logic to editpart.cpp so that both
NotationParts and the Plugin API use the same implementation.

- Add replacePartInstrument() for main instrument replacement
- Add replaceInstrumentAtTick() for instrument changes mid-score
- Update NotationParts::replaceInstrument to use new shared functions
- Update Plugin API Score::replaceInstrument to use new shared functions
- Use EditPart class with static methods instead of free functions
- Add ChangePart::cleanup() to fix instrument pointer leak
- Remove unused NotationParts::findInstrumentChange
- Replace QString with u"..." literals in tests
@manolo manolo force-pushed the pr_replace_instrument branch from 1a55f99 to f6d6fd3 Compare February 8, 2026 21:00
@cbjeukendrup
Copy link
Member

@zacjansheski please regression-check the "Replace instrument" functionality in the UI

@zacjansheski
Copy link
Contributor

Tested on MacOS Tahoe 26.2, Windows 11, Ubuntu 22.04.3. Approved
#31201 FIXED

@cbjeukendrup cbjeukendrup merged commit bdc9b84 into musescore:master Feb 10, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugins Related to MuseScore's QML plugin framework (NOT VST/audio plugins)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add replaceInstrument() or similar method to Plugin API

3 participants