Boilerplates, snippets, use cases and QML notes

Päivitetty 2 kuukautta sitten

    To help beginners kickstart their plugin project, this section gathers boilerplates ,tiny reusable code snippets or parts which are too trivial to be used as an individual plugin, along with several specific use cases and notes that demonstrate how to achieve particular tasks in plugin code.

    What goes in here? and why?

    This place centralizes clever code bits which would otherwise be left hidden under waves of forum posts. Anyone can edit. Feel free to rearrange, correct or add anything, provide a source if possible. Only share code that is taken from other's plugin after you make sure you have permission. Musicians who wish to learn to create plugins usually visit here first, it'd be counter-productive to fragmentize MuseScore resource on the web, plus sharing here saves you the trouble of learning git commands and signing up for github. Codebase large or functional enough to be used as an individual plugin could be posted as a project on musescore.org without github knowledge. Tips on QML are also put in here and as subpage, notes on API write in community notes on API

    Knock knock, who's there?

    Wong. Spot a typo? Found codes need fixing? Want to share yours? Please go ahead. Click the ⠇ to the right of the title, choose "Edit".
    Not a musescore.org member yet? Register an account, it's free. Start editing right away, you really do not need to report and wait for permission. In fact, paid employee from MuseScore BVBA tends to concentrate on improving the main program and providing forum support, it is up to fellow passionate musicians like yourself to share findings and correct errors in the plugin section. Discussion about this page itself visit this forum thread.
    capture_001_29082022_145502.jpg

    Marked Default are copied from plugins bundled with MuseScore 3 installation, open them to learn more.

    QML notes on WebEngine

    QML notes on keyboard and mouse input

    use case: Add Fingering Text

    use case: Changing an Existing Note's Pitch

    use case: Element Explorer

    --- BOILERPLATES AND SNIPPETS: ---

    QML boilerplates

    Contains auto layout templates.

    Run this script once. then upon clicking on a element, print all its properties and func on logger

    also try the buildin debugger
    Object Explorer plugin exhaustively print out element info
    see also: use case: Element Explorer

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug20"
        onScoreStateChanged: {
          if (state.selectionChanged && curScore){
            var es=curScore.selection.elements
            for (var i = 0; i < es.length; i++) {
              console.log('\n'+es[i].name)
              for(var p in es[i]){
                console.log(es[i].name+"."+p+" : "+es[i][p])
              }
            }
         }
      }
    }

    run this script once to sync/bind the time position of the Cursor to first note of current selection

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug2"
      id:pluginscope
      property var c
      onRun: {
        pluginscope.c=curScore.newCursor()
        pluginscope.c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
      }
      onScoreStateChanged: {
        if (state.selectionChanged && curScore) console.log( pluginscope.c.tick )
      }
    }

    select everything on the score, then filter by element type

    Note: selectRange() parameter endTick and endStaff are excluded from selection

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug3"
      onRun: {
        curScore.startCmd()
        curScore.selection.selectRange(0,curScore.lastSegment.tick + 1,0,curScore.nstaves);    //cmd("select-all")
        curScore.endCmd()
        var es=curScore.selection.elements
        for (var i = 0; i < es.length; i++) {
          if(es[i].type==Element.NOTE) console.log(es[i].pitch)
        }
      }
    }

    move the Cursor to the beginning of score

    more info: move cursor to start or end of your current selection ,or tick
    see also Default walk.qml

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
          menuPath: "Plugins.debug5"
          onRun: {
            var c= curScore.newCursor();
            c.voice    = 0;
            c.staffIdx = 0;
            c.rewind(Cursor.SCORE_START);
            console.log(c.tick)    
        }
    }

    log type enum string of one selected element

    import MuseScore 3.0
    MuseScore {
      menuPath: "Plugins.debug55"
      property var reverseEnumElement:(function(){ var x={}; for(var i in Element){  if(typeof Element[i]!=='function')  x[Element[i]]=i  }; return x })()
      onRun:{
        console.log(reverseEnumElement[curScore.selection.elements[0].type])
      }
    }

    log selected note's pitch and next note's pitch

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug4"
      onRun: {
        var e=curScore.selection.elements[0]
        if(e.type==Element.NOTE){
          console.log( "This pitch "+e.pitch )
          var track=e.track
          var seg=e.parent
          while(seg&&seg.type!=Element.SEGMENT){ seg=seg.parent }
          if(seg) var tick=seg.tick
          var c=curScore.newCursor()
          c.track=track  //set track first, setting track will reset tick
          c.rewindToTick(tick)
          var segnext=c.segment.next
          if(segnext
            &&segnext.elementAt(track).type==Element.CHORD  // 1 or more notes
            &&segnext.elementAt(track).notes
          ) console.log("Next pitch "+ segnext.elementAt(track).notes[0].pitch )  //0=first entered note
        }
      }
    }

    log selected chord's pitches

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug41"
      onRun: {
        var e=curScore.selection.elements[0]
        var track=e.track
        var seg=e.parent
        while(seg&&seg.type!=Element.SEGMENT){ seg=seg.parent }
        if(seg) var tick=seg.tick
        var c=curScore.newCursor()
        c.track=track  //set track first, setting track will reset tick
        c.rewindToTick(tick)
        if(c.segment.elementAt(track).type==Element.CHORD){
          var notesarray=c.segment.elementAt(track).notes
          if(notesarray&&notesarray.length>0){
            for (var i = 0; i < notesarray.length; i++){ 
              console.log('note # '+i+' pitch '+notesarray[i].pitch) 
            }
          }
        }
      } 
    }

    create new note, assign pitch using addNote( )

    • addNote( ) and addRest( ) advance the cursor whenever possible ,see api reference and notes.
    • They do not return falsy value, using cursor advancement logic as loop conditional statement may leads to infinite loop error.
    • Chord inserted incorrectly when spams multiple measures, see kamilio141414's post
    • see also related snippets below.
    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug7"
      onRun:{
        var c=curScore.newCursor()
        c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
        curScore.startCmd()
        c.addNote(60) // c.addNote(60,true) to create double stops or chord, or insert note into one //addNote( ) advances the cursor
        curScore.endCmd()
      }
    }

    create new chord using add( ) to designate accidental

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug8gpl"
      //
      // create and return a new Note element with given pitch, tpc
      function createNote(pitch, tpc){
        var note = newElement(Element.NOTE);
        note.pitch = pitch;
        note.tpc = tpc;
        note.tpc1 = tpc;
        note.tpc2 = tpc;
        return note;
      }// end CreateNote
    // Use cursor.rewindToTick(cur_time) instead
      function setCursorToTime(cursor, time){
        cursor.rewind(0);
        while (cursor.segment) { 
          var current_time = cursor.tick;
          if(current_time>=time) return true
          cursor.next();
        }
        cursor.rewind(0);
        return false;
      }// end setcursor To Time
      //adds chord at current position. chord_notes is an array with pitch of notes.
      function addChord(cursor, chord_notes, duration){
        if(chord_notes.length==0) return -1;
        var cur_time=cursor.tick;
        cursor.setDuration(duration, 1920);
        cursor.addNote(chord_notes[0]); //add 1st note //addNote( ) advances the cursor
        var next_time=cursor.tick;
        setCursorToTime(cursor, cur_time); //rewind to this note
        var note = cursor.element.notes[0];
        note.tpc = chord_notes[4];
        var chord = cursor.element; //get the chord created when 1st note was inserted
        for(var i=1; i<4; i++){
          var note = createNote(chord_notes[i], chord_notes[i+4]);
          note.tpc = chord_notes[i+4];
          chord.add(note); //add notes to the chord //add( ) does not advance the cursor
          chord.notes[i].tpc = chord_notes[i+4];
        }
        setCursorToTime(cursor, next_time);
        return 0;
      }//end AddChord
      onRun: {
        var cursor = curScore.newCursor(), startStaff, endStaff, endTick, fullScore = false;
        cursor.rewind(1);
        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(2);
          if (cursor.tick === 0) { // this happens when the selection includes the last measure of the score.
            endTick = curScore.lastSegment.tick + 1;
          } else {
            endTick = cursor.tick;
          }
          endStaff = cursor.staffIdx;
        }
        cursor.rewind(1); // beginning of selection
        if (fullScore) { // no selection
          cursor.rewind(0); // beginning of score
        }
        cursor.voice = 0;
        cursor.staffIdx = 1;
        // chords[0]: name
        // chords[1]: pitches lo-hi & tpc lo-hi
        // chords[2]: occurences of this voicing
        // chords[3]: instances of this chord name
        var chords=
        [
          ["C7b13",[48,52,56,58,14,18,10,12],1,1], //c,e,ab,bb
          ["C7b13",[52,56,58,62,18,10,12,16],1,1], //e,ab,bb,d
        ];
        for (var c = 0; c < chords.length; c++){
          cursor.staffIdx = 0;
          var harmony = newElement(Element.HARMONY);
          harmony.text = chords[c][0];
          cursor.add(harmony);
          cursor.staffIdx = 1;
          var chord = chords[c][1];
          var cur_time=cursor.tick;
          addChord(cursor, chord, 1920); // 480 OK for quarter note trips; 240 for eight notes etc
        }
      }
    }

    Change the accidental of currently selected note

    Changing note's accidentalType changes pitch and tpc automatically.
    Creating a new Element.ACCIDENTAL with accidentalType and then adding it to a note does not always work as expected.
    For cosmetic purpose accidental without pitch change, and microtonal symbols, use Element.SYMBOL instead, see dmitrio95's post, also see Tuning systems, microtonal notation system, and playback.
    Source: dmitrio95's post and XiaoMigros's post.
    For possible accidentals, see the AccidentalType enum

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug71"
      onRun:{
        var note=curScore.selection.elements[0]
        curScore.startCmd()
        note.accidentalType = Accidental.SHARP2 // the 'x' double sharp
        curScore.endCmd()
        curScore.startCmd()
        note.accidental.color = "red"
        curScore.endCmd()
      }
    }

    update note's pitch

    see also the create note snippet above
    see also use case: Changing an Existing Note's Pitch
    Two scenarios
    * in Fretted eg guitar TAB: must set 5 values: .pitch, .string, .fret, .tpc1, .tpc2
    * in Fretless eg piano: must set 3 values: .pitch, .tpc1, .tpc2
    source notes

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug8"
     //WIP
    }

    add lyrics at selected element and subsequent non rest elements

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug9"
      onRun: {
        var e=curScore.selection.elements[0]
        var seg=e.parent
        while(seg&&seg.type!=Element.SEGMENT){ seg=seg.parent }
        if(seg) var tick=seg.tick
        var c=curScore.newCursor()
        c.rewindToTick(tick)
        c.track=e.track
        var lyr = newElement(Element.LYRICS)
        lyr.text = "muse"
        lyr.verse = 0
        curScore.startCmd()
        c.add(lyr)
        curScore.endCmd()
     
        var lyricsarray=['score','is','awesome']
        c.next()
        while(c&&c.element&&lyricsarray.length>0){
          if(
            c.element.type==Element.CHORD  // 1 or more notes
            // &&c.element.type!=Element.REST  //redundant
          ){
            var lyr = newElement(Element.LYRICS)
            lyr.text = lyricsarray[0]
            lyr.verse = 0
            curScore.startCmd()
            c.add(lyr)
            curScore.endCmd()
            lyricsarray.shift()
          }
          c.next()
        }
      } 
    }

    log the shown key signature at selected position

    see notes on which methods seem not working
    Note: Musescore 3 interpret key signature's shape and flat symbols to provide tonality logic, it does not understand tonality really. The following code returns the key signature shown on screen, it does not always mean the real key in musical sense . Caution when using an open/atonal keysig and/or on transposing instruments; transposing instruments' return value also varies with user's current "Concert Pitch" display status.
    key signature enum are not exposed as API yet, use this hardcoded schema, ref:
    Num of Sharps = positive number
    No symbols = 0
    Num of Flats = negative number

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug29" 
      onRun:{
        var  c=curScore.newCursor()
        c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
         console.log(c.keySignature)
      }
    }

    get to the title, composer etc frame at start of page

    Did you find a way get to its existing content text eg read title, composer text string ? src ref
    also see metatag snippet which contains score properties set as data field only , which may not be the current visual text

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug12"
      onRun: {
        var c=curScore.newCursor()
        c.rewind(Cursor.SCORE_START)
        curScore.selection.select(c) //tackle weird range select problem
        cmd('prev-element')
        while( curScore.selection.elements[0].type!=Element.VBOX ){
          cmd('prev-element')
        }
        var e=curScore.selection.elements[0]
        console.log(e.name)
      }
    }

    Alternative to modifying existing text: Delete the existing frame, use the addText function:
    curScore.addText("title", "The Park, Op. 44")

    log time signature then create new one at selected position

    some values won't work, always check actual timesig : right click > measure prop see handbook
    See Time Signature Enforcer for a plugin that uses the cmd interface to manipulate the timesigActual of a measure.
    see notes for methods currently not working

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug28"
      onRun: {
        var c=curScore.newCursor()
        c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
        //
        var m=c.measure.timesigActual  //replace with timesigNominal for displayed symbol
        console.log("timesigActual : "+m.numerator)
        console.log("timesigActual : "+m.denominator)
        //
        var ts=newElement(Element.TIMESIG)
        ts.timesig=fraction(12,8) //some values won't work, always check timesigActual
        curScore.startCmd()
        c.add(ts)
        curScore.endCmd()
      }
    }

    log score information entered in File>Properties or new score wizard

    'name' of default score properties

    import MuseScore 3.0
    MuseScore {
      menuPath: "Plugins.debug31"
      onRun:{
        console.log(curScore.metaTag("workTitle")) //https://github.com/fp22june/MuseScoreTag362/blob/master/share/templates/My_First_Score.mscx#L19-L29
      }
    }

    Add text, symbols, and articulation to selected note

    see also TempoChanges Plugin
    sym, SymId enum: see Master palatte > symbols or lookup
    MS3 fontfamily
    lookup codepoints
    everything in musescore.qrc
    only icon image
    source post

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug30"
      pluginType:"dock"  
      onRun: {
        var c=curScore.newCursor()
        c.inputStateMode=Cursor.INPUT_STATE_SYNC_WITH_SCORE
        //
        var s = newElement(Element.ARTICULATION); 
        s.symbol = "articAccentAbove" // see SymId enumeration values
        curScore.startCmd()
        c.add(s)
        curScore.endCmd()
        //
        var sym = newElement(Element.SYMBOL);
        sym.symbol = "miscEyeglasses"
        curScore.startCmd()
        c.add(t)
        curScore.endCmd()
        //
        var t = newElement(Element.STAFF_TEXT); 
        t.fontSize= 20
        t.text = "<sym>miscEyeglasses</sym>" 
        curScore.startCmd()
        c.add(t)
        curScore.endCmd()
      }
      Column{
        Text{
          font.family: 'MScore Text' //also  Bravura  Leland  //https://github.com/musescore/MuseScore/tree/master/fonts
          font.pointSize: 20
          text:"\uE4E5" //https://github.com/w3c/smufl/blob/gh-pages/metadata/glyphnames.json
        }
        Image{
          source:"qrc:///data/icons/note-longa.svg" //https://github.com/musescore/MuseScore/blob/3.x/mscore/icons.cpp#L40
        }
      }
    }

    Save custom data

    The following snippet saves a string as tag property in a score, to save object use JSON.stringify() and parse()
    Also:
    Save data to a time position: add invisible Staff Text, more info study the MuseScore Navigation plugin
    Save data across sessions, use Settings { }, study the Daily Log plugin

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore { 
      menuPath: "Plugins.debug24"
      pluginType: "dock"
      anchors.fill: parent
      onRun:{
        curScore.setMetaTag('customdataname', 'initemptyvalue')
      }
      Column{
        Button{
          text:"load"
          onClicked: console.log( curScore.metaTag('customdataname') )
        }
        Button{
          text:"save"
          onClicked: curScore.setMetaTag('customdataname', 'customdatavalue')
        }
      }
    }

    workaround to gate onScoreStateChanged's cpu-heavy code to the last plugin instance

    Following limits cpu-heavy, blocking synchronous code to last plugin instance (because every onScoreStateChanged continues to run even after its invocation plugin window closed)
    Remember to use unique string in metaTag('PluginLastInstance')
    To test, comment out the comparison line then reopen plugin few times

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug43"
      property string instanceStamp:Date.now()
      Component.onCompleted: curScore.setMetaTag('PluginLastInstance', instanceStamp) //save to score, overwritten by latest plugin
      function blockingSynchronousCode(){
        var x=0
        for(var i=0;i<1e7;i++) x+=i //edit 1e7 according to cpu
      }
      onScoreStateChanged:{
        if(curScore.metaTag('PluginLastInstance')==instanceStamp){   //filter out old instances
          blockingSynchronousCode()
        }
      }
    }

    workaround to relink onScoreStateChanged to current score only

    Using the gate method above, if the plugin window stays open, switching to another score will invalidate onScoreStateChanged because instanceStamp doesn't exist or doesn't equal to plugin's stored value. Click button to reapply stamp so that plugin is affected by current score's onScoreStateChanged
    Remember to use unique string in metaTag('PluginLastInstance')

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore {
      menuPath: "Plugins.debug44"
      pluginType: "dock"
      anchors.fill:parent
      property string instanceStamp:Date.now()
      function maincode(){
        txt.text=instanceStamp+' score called '+Date.now()
      }
      function ownInstance(){
        instanceStamp=Date.now()
        curScore.setMetaTag('PluginLastInstance', instanceStamp)
        maincode()
      }
      Component.onCompleted: ownInstance()
      onScoreStateChanged:{
        if(curScore.metaTag('PluginLastInstance')==instanceStamp){ 
          maincode()
        }
      }
      Column{
        Button{
          text:"relink to current score"
          onClicked:ownInstance()
        }
        Text{
          id: txt
          anchors.fill:parent
        }
      }
    }

    Listview with unidirectional dataflow

    qt ref

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore {
      menuPath: "Plugins.debug22"
      pluginType: "dock"
      anchors.fill: parent
      id:pluginscope
      property var db:[
          { nm : "name1" , btn : true }
        , { nm : "name2" , btn : false }
        , { nm : "name3" , btn : false }
        , { nm : "name4" , btn : true }
      ]
      function gen(payload){
        if(payload && payload.deletenm){
          for (var i=0; i<pluginscope.db.length; i++){
            if(pluginscope.db[i].nm==payload.deletenm){
              pluginscope.db[i].removed=true
            }
          }
        }
        if(payload && payload.renewall){
          pluginscope.db=pluginscope.db.map(function _(r){
            return ({ nm: r.nm, btn: r.btn, removed: false})
          })
        }
        md1.clear()
        pluginscope.db.map(function _(r){
          if(!r.removed) md1.append(r)
        })
        console.log("gen")
      }
      Item{
        anchors.fill: parent
        ListModel { id: md1 }    //ref from delegate use getview1.ListView.view.model
        ListView {
          anchors.fill: parent
          id: listview1         //ref from delegate use getview1.ListView.view
          model: md1  
          delegate: Component {
            Row{
              id: getview1
              width: parent.width
              Text{
                clip:true
                text: nm
              }
              Button { 
                text: btn ? "logmynm" : "deleteme"
                onClicked:{ 
                  if(btn){ 
                    console.log(nm)
                  } else {
                    gen({ deletenm : nm })
                  }
                }
              }
            }
          }
        }
      }
      Button { 
        anchors.bottom: parent.bottom
        text: "renewall"
        onClicked: gen({ renewall : true })
      }
    }

    QML keyboard handler

    more info see this QML notes
    Keys enum list
    focus:true may not mean want you think

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore { 
      menuPath: "Plugins.debug10"
      pluginType: "dock"
      Item {
        focus:true
        Keys.onPressed: {
          console.log("event.key: "+event.key)
          if (event.key == Qt.Key_Space) cmd("play")
        }
      }
    }

    Add components dynamically eg add clickable buttons

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore { 
      menuPath: "Plugins.debug50"
      pluginType: "dock"
      anchors.fill: parent
      Column{
        anchors.fill: parent
        id:btnscontainer 
      }
      Component { id: btntemplate 
        Button { 
          width: parent.width
        }
      }
      Component.onCompleted:{
        btntemplate.createObject(btnscontainer,{text:'log1'})
          .clicked.connect(function(){console.log('logged1')})
        btntemplate.createObject(btnscontainer,{text:'log2'})
          .clicked.connect(function(){console.log('logged2')})
      }
    }

    Add components dynamically with non static var (data binding)

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore {
      menuPath: "Plugins.debug54"
      pluginType: "dock"
      anchors.fill: parent
      implicitHeight: 9999
      Column{
        id:maincontainer
        anchors.fill:parent ;    padding:5;
        Row{ id:rowtoftext }
      }
      Component { id: texttemplate 
        Text{ 
          color:'green'
          topPadding:5
          property var fs   // to dynamically overwrite font.pointSize
          font.pointSize: fs||this.font.pointSize
          font.weight:Font.Medium
        }
      }
      Text{ id:normaltext }   //reference
      Component.onCompleted:{
        texttemplate.createObject(rowtoftext,{
           text:'Extend'
          ,rightPadding:10
          ,leftPadding:Qt.binding(function(){return parent.width*.3 }) // use Qt.binding for non-static var , otherwise assigned at createObject( ) invocation
        })
        texttemplate.createObject(rowtoftext,{
           text:'Musescore'
          ,color:'blue'
          ,topPadding:0
          ,fs: normaltext.font.pointSize*1.5
        })
      }
    }

    Popup a modal window

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    import QtQuick.Dialogs 1.1
    MuseScore { 
      menuPath: "Plugins.debug51"
      pluginType: "dock"
      id: pluginscope
      anchors.fill: parent
      //
      Component { id: msg ; MessageDialog { title: "Confirm"; standardButtons: StandardButton.Ok; onAccepted: console.log("ok") } }
      function confirm(t){ msg.createObject(pluginscope,{ text:t, visible: true}) }
      //
      Button { 
        width: parent.width
        text:"MuseScore"
        onClicked: confirm('Rocks')
      }
    }

    Button color, mouse over color

    more prop like disabled, qmlssed, checked, checkable, focused, highlighted, flat, mirrored, hovered etc
    for more mouse control like detect button, QML use MouseArea , see this QML notes

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore { 
      menuPath: "Plugins.debug6"
      pluginType: "dock"
      anchors.fill: parent
      Button{ 
        text:"clickme"
        onClicked: onbtn1()
        background: Rectangle {
        color: parent.hovered ? "#aaa": "#ddd"
        } 
      }
      function onbtn1(){
        console.log("clicked")
      }
    }

    mouse button, mouse down, mouse up

    Mouse button enum list
    also see this QML notes
    for complex mouse handler logic, you may want to use standard js mouseevent as in the WebEngine snippet, instead of QML mouseevent shown here

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2 
    MuseScore { 
      menuPath: "Plugins.debug90"
      pluginType: "dock"
      anchors.fill: parent
      Row{
        Rectangle { 
          width:  50;
          height: 50;
          color: "red"
          MouseArea{
            id:ma
            anchors.fill: parent
            acceptedButtons: Qt.LeftButton | Qt.RightButton
            onPressed: {
              console.log('MouseArea.pressedButtons: '+ pressedButtons) //pressedButtons is also available ma.pressedButtons
              console.log('mouse.button: '+mouse.button)                //mouse is mouseEvent exist inside onPressed(){} 
            }
          }
        }
        Rectangle { 
          width:  50;
          height: 50;
          color: "blue"
          MouseArea{
            anchors.fill: parent
            acceptedButtons: Qt.LeftButton
            z:2
            onPressed:{
              mouse.accepted=false
              console.log('pressed')
            }
          }
          MouseArea{
            anchors.fill: parent
            acceptedButtons: Qt.LeftButton
            z:-1
            onReleased:{
              console.log('released')
            }
          }
        }
      }
    }

    Setup a timeout countdown timer for delayed function

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore { 
      menuPath: "Plugins.debug52"
      pluginType: "dock"
      id: pluginscope
      anchors.fill: parent
      //
      property var timeout
      Component { id: setTimeout ; Timer {  } }
      //
      Button { 
        width: parent.width
        text:"Delayed"
        onClicked:{
          console.log('promise')
          setTimeout.createObject(pluginscope,{ interval:1000 ,running:true  })
            .triggered.connect(function(){ console.log('delivered') })
        }
      }
    }

    Debounce

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    MuseScore { 
      menuPath: "Plugins.debug53"
      pluginType: "dock"
      id: pluginscope
      anchors.fill: parent
      //
      property var timeout
      Component { id: setTimeout ; Timer {  } }
      function debounced(curryt,curryf){
        return function(obj){
          if(timeout) timeout.stop()
          timeout=setTimeout.createObject(pluginscope,{
             interval:curryt
             //,onTriggered:function(){ curryf(obj) }   //?cannot curry?
            ,running:true
          })
          timeout.triggered.connect(function(){ curryf(obj) })
        }
      }
      //
      property int heavycount:0
      function heavy(){console.log('heavy'+(heavycount++))}
      property var heavydebounced:debounced(500,heavy)
      //
      property int easycount:0
      Button { 
        width: parent.width
        text:"Smash"
        onClicked:{
          console.log('easy'+(easycount++))
          heavydebounced()
        }
      }
    }

    WebEngine = fetch online or local page, or write html, run javascript

    Windows only, see QML notes
    save the following two snippet in the same directory, name the html debug21.html
    also see the other advanced boilerplate below

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    import QtWebEngine 1.5
    MuseScore { 
      menuPath: "Plugins.debug21"
      pluginType: "dock"
      anchors.fill: parent
      Column{
        anchors.fill: parent
        Button{
          id:b
          height:30
          text:"clickme"
          onClicked:{
            v2.runJavaScript('approot.style.background="red"')
            v3.loadHtml('<div style="background:green;width:100%;height:100%"></div>')
          }
        }
        WebEngineView {
          width: parent.width
          height: (parent.height-b.height)/3
          url: "https://example.com"
        }
        WebEngineView {
          id:v2
          width: parent.width
          height: (parent.height-b.height)/3
          url: "debug21.html"
        }
        WebEngineView {
          id:v3
          width: parent.width
          height: (parent.height-b.height)/3
        }
      }
    }

    debug21.html

    <div id="approot" style="width:100%;height:100%"></div>

    WebEngine + WebChannel = hybrid UI html css js

    Windows only, see QML notes
    save the following two snippet in the same directory, name the html debug19.html
    advice: if large project, avoid direct mutations will help prevent nightmare
    also see the other simpler boilerplate above

    import MuseScore 3.0
    import QtQuick 2.9
    import QtQuick.Controls 2.2
    import QtWebEngine 1.5
    import QtWebChannel 1.0
    MuseScore{
      menuPath: "Plugins.debug19"
      pluginType: "dock"
      anchors.fill: parent
      QtObject {
        id: backendobj
        WebChannel.id: "backendid"
        property string backp: "nothing"
        signal post(string payload)
        function backf(payload){
          backconsole.text = "From front f: " + payload +'\n'+ backconsole.text
          return (payload+110)
        }
      }
      Column{
        anchors.top: wv.bottom
        spacing: 6
        Button {
          height: 12
          text: "Cast signal backendobj.post"
          onClicked: { backendobj.post(201319) }
        }
        Button {
          height: 12
          text: "Direct javascript"
          onClicked:{wv.runJavaScript(""
            + "   var t=document.querySelector('textarea').value;    "
            + "   document.querySelector('textarea').value = 'Direct javascript: 394' +'\\n'+ t;   "
          )}
        }
        Button {
          height: 12
          text: "Read backendobj.backp"
          onClicked: { backconsole.text = backendobj.backp +'\n'+ backconsole.text }
        }
        Text {
          id: backconsole
          text: "BackConsole"
          //onTextChanged: backendobj.post("BackConsoleCB")
        }
      }
      WebChannel {
        id: channel
        registeredObjects: [backendobj]
      }
      WebEngineView {
        id: wv
        width: parent.width
        height: parent.height/2
        url: "debug19.html"
        webChannel: channel
        onJavaScriptConsoleMessage: function(w,s,l,i){ console.log('Web console line '+l+' : '+s) } //pipe console
      }
    }

    debug19.html

    <button onclick="frontf()">Front f</button>
    <button onclick="backendobjread()">Direct backendobj.back read</button>
    <button onclick="backendobjwrite()">Direct backendobj.back write</button>
    <textarea id="debug" style="width:100%;height:60%"></textarea>
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <script>
        var backend
        window.onload = function _(){
            new QWebChannel(qt.webChannelTransport, channel=>{
                backend = channel.objects.backendid;
                backend.post.connect(payload=>{
                    debug.value = ("Signal from backendobj.post: "+payload) +'\n'+ debug.value
                })
            })
        }
        // async backend.func and callback
        var frontf = _=>{ 
          backend.backf( 721 , r=>{
            debug.value = ("Back f CB: "+r) +'\n'+ debug.value 
          })
        }
        // direct read/write backend.prop 
        var backendobjread  = _=> debug.value = backend.backp +'\n'+ debug.value
        var backendobjwrite = _=> backend.backp = new Date() 
    </script>

    loop thru all channel, mute all

    see also this note

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      menuPath: "Plugins.debug64"
      function iterCh(cb){
        var parts = curScore.parts
        for (var i = 0; i < parts.length; i++) {
            var instrs = parts[i].instruments
            for (var j = 0; j < instrs.length; j++) {
                var channels = instrs[j].channels
                for (var k = 0; k < channels.length; k++) {
                    cb(parts[i],instrs[j],channels[k], i,j,k)
                }
            }
        }
      }
      onRun:{
        iterCh(function _(p,i,c, n,m,k){ 
          c.mute=true
        })
      }
    }

    log OS name, start another program commandline such as python

    see the BandInMuseScore plugin and receiving / passing MuseScore command line parameter.
    TIPS: use Score's path, see api and Batch Convert

    import MuseScore 3.0
    import QtQuick 2.9
    MuseScore {
      anchors.fill: parent
      menuPath: "Plugins.debug42"
      QProcess { id: proc }
      onRun:{
        console.log('os: '+Qt.platform.os)
        var c
        switch (Qt.platform.os){
          case "windows":
            c = 'cmd /c calc'
            break;
          default:
            c = '/bin/sh -c calc'
        }
        proc.start(c)
        var val=proc.waitForFinished(5000)
        // DEBUG
        try {
          var out=proc.readAllStandardOutput()
          console.log("-- Command output: "+out)
          if (val) {
            console.log('terminated correctly.')
          } else {
            console.log('failure')
          }
        } catch (err) {
          console.log("--" + err.message);
        }
     }
    }

    Identify if the score is displayed in "Concert pitch" or "Score pitch"

    This can be achieved by comparing the .tpc1 and .tpc2 value of a note of a transposing instrument.

    Rational:

    The note.tpc1 is the Concert pitch representation of a note.
    The note.tpc2 is the Score pitch representation of a note.
    The note.tpc is the representation of the note in the current mode.

    For a transposing instrument tpc1 and tpc2 are different. So they can be compared to tpc and the current mode deduced.

    For a non transposing instrument tpc1 and tpc2 are equal. So the current mode cannot be deduced.

    import QtQuick 2.0
    import MuseScore 3.0
     
    MuseScore {
          menuPath: "Plugins.pluginName"
          description: "Description goes here"
          version: "1.0"
          onRun: {
                // assuming a note of a transpoing instrument is selected
                var note=curScore.selection.elements[0];
                console.log((note.tpc===note.tpc1)?"Concert Pitch":"Score pitch");
                }
          }

    Identify an instrument transposition

    This can be achieved by comparing the .tpc and .tpc1 value of a note of a transposing instrument.

    Rational

    The note.tpc1 is the Concert pitch representation of a note.
    The note.tpc2 is the Score pitch representation of a note.

    import QtQuick 2.0
    import MuseScore 3.0
     
    MuseScore {
          menuPath: "Plugins.pluginName"
          description: "Description goes here"
          version: "1.0"
          onRun: {
     
                var note=curScore.selection.elements[0];  // assuming a note is selected
                console.log("Instrument's transposition: %1 semtitones".arg(deltaTpcToPitch(note.tpc1,note.tpc2)));
                }
     
          function deltaTpcToPitch(tpc1, tpc2) {
                var d = ((tpc2 - tpc1) * 5) % 12;
                    //if (d < 0) d += 12;
                return d;
                }
          }

    setup keyboard shortcut in MuseScore from QML

    Better use Plugin Manager's "Define Shortcut" instead, because running the following code more than once will bug out that key until restart.
    Source post.
    Workaround forum post by matt28.

    import MuseScore 3.0
    import QtQuick 2.6
    import QtQuick.Window 2.2
    MuseScore {
        id: plugin
        pluginType: "dock"
        readonly property var window: Window.window
        Item { 
          id: someItem 
          focus:true
        }
        Shortcut {
            sequence: "Ctrl+D"
            context: Qt.ApplicationShortcut
            onActivated: {
                plugin.window.requestActivate();
                someItem.forceActiveFocus();
            }
        }
    }

    External links

    Element Analyser plugin which aims to provide a reusable library

    License and credits for this page only

    License info are for legal purpose of this webpage only.
    These are copied from default plugins and MuseScore files. Some contributor info are missing, please add credits wherever due.
    abc_import.qml
    Based on ABC Import by Nicolas Froment (lasconic)
    Copyright (2013) Stephane Groleau (vgstef)
    colornotes.qml
    Copyright (C) 2012 Werner Schweer
    Copyright (C) 2013-2017 Nicolas Froment, Joachim Schmitz
    Copyright (C) 2014 Jörn Eichler
    notenames-interactive.qml
    Copyright (C) 2012 Werner Schweer
    Copyright (C) 2013 - 2019 Joachim Schmitz
    Copyright (C) 2014 Jörn Eichler
    Copyright (C) 2020 MuseScore BVBA
    notenames.qml
    Copyright (C) 2012 Werner Schweer
    Copyright (C) 2013 - 2020 Joachim Schmitz
    Copyright (C) 2014 Jörn Eichler
    Copyright (C) 2020 Johan Temmerman
    Copyright (C) 2023 Laurent van Roy (parkingb)
    Copyright (C) 2012 Werner Schweer and others
    walk.qml
    jeetee
    Copyright (C) 2012-2017 Werner Schweer
    MuseScore
    Music Composition & Notation
    This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2as 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
    These are for the WebEngine WebChannel boilerplate:
    msfp
    decovar Declaration of VAR
    source
    GPLv3