Beginner struggling with creating plugins

• May 12, 2025 - 01:55

I've never created plugins before and I'm trying to make a simple plugin to start myself off. I just want to create a C major triad on the cursor. MuseScore recognizes the plugin and I can enable it, but when I use it nothing happens. All of my knowledge comes from reverse engineering other plugins, so I'm sure there's a simple fix I'm missing.

I've attached the file below. I'm using the latest version of musescore, and VScode. I've also tried using the plugin creator in MuseScore 3, with no luck. Thanks.

Attachment Size
triad.qml 1.03 KB

Comments

No idea whether this is a problem or absolutely required (I struggle with plugins even more than you do :-), but i noticed something odd in yours.

import QtQuick 2.2
import MuseScore 3.0

MuseScore {
version: "4.0"

Line 2 says to import MuseScore 3.0, but line 5 says that the MuseScore version is 4.0. Is that a problem?

In reply to by Jojo-Schmitz

I launched MuseScore, opened a small file, ran the plugin, and exited MuseScore. The entire log file is attached below, but it looks like the important part is ...

2025-05-12T14:19:40.545 | INFO | main_thread | ActionsDispatcher::doDispatch | try call action: action://extensions/v1/triad/triad.qml?action=main
2025-05-12T14:19:40.552 | ERROR | main_thread | ExtPluginRunner::run | Failed to load QML file: C:/Users/Owner/Documents/MuseScore4/Plugins/Triad/triad.qml
2025-05-12T14:19:40.552 | ERROR | main_thread | ExtPluginRunner::run | "file:///C:/Users/Owner/Documents/MuseScore4/Plugins/Triad/triad.qml:2 module \"MuseScore\" version 4.0 is not installed\n"

Obviously it failed to load the plugin file. Why? and how does one respond?

Attachment Size
MuseScore_250512_141923.txt 38.24 KB

In reply to by TheHutch

I'm not sure what part of the error message you don't understand... to me it is 100% clear and really can't get clearer.
As mentioned before: that Import MuseScore 4.0 is just plain wrong, use Import MuseScore 3.0 or (as of 4.4) Import MuseScore, that number is not related to the MuseScore Studio version, but to the Plugin API version

In reply to by TheHutch

So, I removed both version numbers. The beginning of the code now goes ...

    import QtQuick 2.2
    import MuseScore

    MuseScore {
        title: "Insert C Major Triad"
        description: "This plugin inserts a C major triad at the current cursor position."
        categoryCode: "composing-arranging-tools"

Repeated the same thing as before: Opened MuseScore, opened a file, selected a position in the score, ran the plugin, closed MuseScore. New logfile attached but the operative lines of the log say:

    2025-05-12T14:31:16.882 | INFO  | main_thread     | ActionsDispatcher::doDispatch | try call action: action://extensions/v1/triad/triad.qml?action=main
    2025-05-12T14:31:16.896 | WARN  | main_thread     | Qt              | file:///C:/Users/Owner/Documents/MuseScore4/Plugins/Triad/triad.qml:22: ReferenceError: Pitch is not defined

In the code, the pitches are defined at line 22 by ...

    cNote.pitch = Pitch.C4;
    eNote.pitch = Pitch.E4;
    gNote.pitch = Pitch.G4;

I take it that's wrong. How do you define them?

Attachment Size
MuseScore_250512_143056.txt 39.01 KB

In reply to by Jojo-Schmitz

I haven't been able to find any other plugins that "do this kind of thing" (and I've looked and asked before). Is there a table of MIDI pitches? and what are "TPC"s?

Well, I advance. I found a table of MIDI pitches and tried ...

    // Assign the pitches
    cNote.pitch = Pitch.60;
    eNote.pitch = Pitch.62;
    gNote.pitch = Pitch.64;

... and got ...

 Unexpected token `numeric literal'\nfile:///C:/Users/Owner/Documents/MuseScore4/Plugins/Triad/triad.qml:24 Unexpected token `numeric literal'\n" 

So I tried ...

    // Assign the pitches
    cNote.pitch = 60;
    eNote.pitch = 62;
    gNote.pitch = 64;

... and this time I got ...

 TypeError: Property 'addNote' of object mu::engraving::apiv1::Chord(0x2720ee9f8e0) is not a function

... which refers to these lines in the code.

    // Create a chord and assign the notes
    var chord = newElement(Element.CHORD);
    chord.addNote(cNote);
    chord.addNote(eNote);
    chord.addNote(gNote);

What's wrong with this one? How do you add a note to the score file?

In reply to by TheHutch

Well, I figured out what was wrong with the last. It should have been

 chord.add(cNote)

I figured out that several of the names I was using for variables were existing class names, so I changed them. So, now it doesn't give me any errors. It just doesn't DO anything. It says ...

 2025-05-13T00:58:08.791 | INFO  | main_thread     | ActionsDispatcher::doDispatch | try call action: action://extensions/v1/triad/triad.qml?action=main

... and nothing more. Obviously, I'm still missing something. I'm guessing that I have to give the chord a duration in order for it to be added, but I haven't found how to do that.

I would really appreciate it if someone could give me a hint here. (Code attached)

Attachment Size
triad.txt 1020 bytes

In reply to by ILPEPITO

Not trying to make something that has any "utility". I'm not that far along. This is just a "Hello world" sort of program. I have tried to "download the simplest plugins and try to understand how they work." There aren't any "simplest plugins", as far as I can see. Or perhaps, this is the "simplest plugin" :-)

Question: exactly what do these three lines mean at the beginning?

1) import QtQuick 2.2
2) import MuseScore 3.0, and
3) version: "4.0"

Someone (Jojo?) said that one or another of them needed to be not present?

In reply to by TheHutch

'import' refers to the libraries, i.e. properties, methods, objects, controls, etc. that will be used to create the program. For this plugin you only need 'MuseScore 3.0', since it has no window (so no buttons, no checkboxes, etc.). I just changed the code, without worrying about these details. 'version' is simply the version number of the plugin, such as MU 3.6.2 or MU 4.5.2, and is optional. The simplest plugins perhaps (but not necessarily) are the 'shorter' ones, such as those that simply change the color of the note head.

In reply to by TheHutch

That version: xx.yy is something you'd see in the plugin manager, along with the name and description. Other than that it is pretty meaningless

The version number after the import statements refer to a certain version of the library to import (and that is is 3.0 for Mu3 and Mu4). As of MuseScore Studio 4.4 (or rather as of Qt 6) you can drop them and always get the latest available version automagically (so need to specify it only if you want an older one).

In reply to by Jojo-Schmitz

I'm not bothered about older versions and I'll update to 4.5.2 soon.

My priority is getting an emulation of the MS3 TAB Ring plugin running in MS4 because of the crippling effect MS4 has on guitar scores without TAB Ring.

I also need my user velocities copied from MS3 scores to their MS4 counterparts since opening an MS3 score in MS4 flattens all velocities to 64. (Why does it do this?) The plugin for this is 50% complete.

In reply to by yonah_ag

> My priority is getting an emulation of the MS3 TAB Ring plugin running in MS4 because of the crippling effect MS4 has on guitar scores without TAB Ring.

MS4 imparts a crippling effect by ignoring Len values, but at least it doesn't "flatten them" as it does with velocity.

> I also need my user velocities copied from MS3 scores to their MS4 counterparts since opening an MS3 score in MS4 flattens all velocities to 64. (Why does it do this?)

https://musescore.org/en/node/368932

I find this behavior patently unjustifiable. MS4 is FAR too willing to discard user data.

For further examples, look that the mixer settings after opening a MS3 score in MS4: the instrument is likely changes and the channel volume and pan are discarded.

I'd like to direct further comment on this point to the following post:

https://musescore.org/en/node/373626

Okay, so I just wrote very large post with a bunch of questions. However, I decided to dig a little and found half of the answers, so I deleted the post. I think I'm over the hump.

I'll still have lots of questions, but I'm starting to figure out some of the answers on my own.

Definitely struggling less, but still struggling :-)

Three questions:

1) How do I get the plugin to "see"/"know" what the current selection is in the score? The user clicks on THIS. How does the plugin identify that selection? It seems like it should be cursor.rewind(Cursor.Selection_Start) but that doesn't seem to work for me?? (which means I'm probably doing it wrong :-)
B) I can see how to read the key signature. How can I write one?
III) So far I have managed to do stuff in an open score. Can I run a plugin from the Home tab that creates a score? How?

In reply to by TheHutch

which means I'm probably doing it wrong

Very probabable. Color notes does this:

   // Apply the given function to all chords/rests in selection
   // or, if nothing is selected, in the entire score
   function applyToChordsAndRestsInSelection(func) {
      var cursor = curScore.newCursor()
      cursor.rewind(Cursor.SELECTION_START)
      var startStaff
      var endStaff
      var endTick
      var fullScore = false
      if (!cursor.segment) { // no selection
         fullScore = true
         startStaff = 0 // start with 1st staff
         endStaff = curScore.nstaves - 1 // and end with last
         }
      else {
         startStaff = cursor.staffIdx
         cursor.rewind(Cursor.SELECTION_END)
         if (cursor.tick === 0) {
            // this happens when the selection includes
            // the last measure of the score.
            // rewind(Cursor.SELECTION_END)) goes behind the last segment
            // (where there's none) and sets tick=0
            endTick = curScore.lastSegment.tick + 1
            }
         else
            endTick = cursor.tick
         endStaff = cursor.staffIdx
         }
      console.log(startStaff + " - " + endStaff + " - " + endTick)
      curScore.startCmd()
      for (var staff = startStaff; staff <= endStaff; staff++) {
         for (var voice = 0; voice < 4; voice++) {
            cursor.rewind(Cursor.SELECTION_START) // sets voice to 0
            cursor.voice = voice //voice has to be set after goTo
            cursor.staffIdx = staff
 
            if (fullScore)
               cursor.rewind(Cursor.SCORE_START) // if no selection, beginning of score
 
            while (cursor.segment && (fullScore || cursor.tick < endTick)) {
               if (cursor.element) {
                  if (cursor.element.type === Element.REST)
                     func(cursor.element, voice)
                  else if (cursor.element.type === Element.CHORD) {
                     func(cursor.element, voice)
                     var graceChords = cursor.element.graceNotes;
                     for (var i = 0; i < graceChords.length; i++) {
                        // iterate through all grace chords
                        func(graceChords[i], voice)
                        var gnotes = graceChords[i].notes
                        for (var j = 0; j < gnotes.length; j++)
                           func(gnotes[j], voice)
                        }
                     var notes = cursor.element.notes
                     for (var k = 0; k < notes.length; k++) {
                        var note = notes[k]
                        func(note, voice)
                        }
                     } // end if CHORD
                  } // end if element
               cursor.next()
               } // end while cursor
            } // end for loop
         } // end for loop
      curScore.endCmd()
      }

Do you still have an unanswered question? Please log in first to post your question.