Boilerplates, snippets, use cases and QML notes
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.
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&¬esarray.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
- This snippet utilize both addNote( ) and add( ). addNote( ) and addRest( ) advance the cursor whenever possible,
- add( ) does not, see above example for more.
- src elsewhere's post and Emmanuel Roussel (rousselmanu)'s github GPL2.
- Chord inserted incorrectly when spams multiple measures, see kamilio141414's post
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
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