2.0 Plugins

• Jan 13, 2015 - 01:17

Hi,

I'm trying to create a couple of plugins. One is similar to the old retrograde.js, the other simply copies the selection, or if not the whole score and appends it at the end, creating measures if needed.

I'm planning to read the notes in an array and then while iterating through it in a certain order, append it at the end.

In the process of writing the script I'm running into a few issues:
1. How do I read the length of a note? (tickLen in the past I think)
2. How do I figure out how many measures have been selected?
3. Can I append with cursor.add() the note from the array?
4. While fooling around, Musescore crashes now and then but I can't tell what I did wrong.

Do you have some further documentation on scripting - the existing help, while describing the API and models does not go into enough details for me to understand well the scripting model.

Thanks,
/S

btw, this is the code I have so far, the last part being meaningless since I don't know what to do.


import QtQuick 2.0
import MuseScore 1.0

MuseScore {
version: "1.0"
description: "This test plugin reverses a score"
menuPath: "Plugins.Retrograde"

function retrograde() {
var cursor = curScore.newCursor();
cursor.rewind(1);
var startStaff;
var endStaff;
var endTick;
var fullScore = false;
if (!cursor.segment) { // no selection
console.log("No selection")
fullScore = true;
startStaff = 0; // start with 1st staff
endStaff = curScore.nstaves - 1; // and end with last
console.log("curScore.nstaves=" + curScore.nstaves);
} else {
console.log("Processing selection")
startStaff = cursor.staffIdx;
cursor.rewind(2);
if (cursor.tick == 0) {
// this happens when the selection includes
// the last measure of the score.
// rewind(2) 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;
}

var scoreNotes = [];

console.log(startStaff + " - " + endStaff + " - " + endTick)
for (var staff = startStaff; staff <= endStaff; staff++) {
for (var voice = 0; voice < 4; voice++) {
cursor.rewind(1); // sets voice to 0
cursor.voice = voice; //voice has to be set after goTo
cursor.staffIdx = staff;

if (fullScore)
cursor.rewind(0) // if no selection, beginning of score

while (cursor.segment && (fullScore || cursor.tick < endTick)) {
console.log(".");
if (cursor.element && cursor.element.type == Element.CHORD) {
scoreNotes.push(cursor.element);
}
cursor.next();
}
console.log(">>" + scoreNotes.length);

for(var n in scoreNotes) {
console.log("_" + scoreNotes[n].notes[0].pitch);
cursor.setDuration(1,8);
cursor.addNote(scoreNotes[n].notes[0].pitch);
cursor.next();
}
curScore.appendMeasures(1);

}
}
}

onRun: {
if (typeof curScore === 'undefined')
Qt.quit();
console.log("Start processing..")

retrograde();

Qt.quit()
}
}


Comments

1. A Note doesn't have a Duration, but a DurationElement has. ChordRest inherits this and also has a DurationType and Chord inherits either. But there is Cursor.setDuration(int numerator, int denumerator)
2. The number of measure in a Score is in nmeasures, I don't think you can get the number od measures in a selection. Maybe you can get the measure number of the first and last element and from that calculate? Or use Cursor.nextMeasure() and count until false?

In reply to by Jojo-Schmitz

Excellent, thanks! I can use both these now. However it seems that my bigger problem lies elsewhere. How do I append at the end of a score? I can see in random2.qml that to create a score it is:

score.startCmd()
...
var cursor = score.newCursor();
...
score.endCmd()

what's the meaning of these?

Looking through the plugin examples shipped with 2.0 they all fall into these categories:
- create a new score
- modify a property of an existing element.

Trying to add a note to an existing score kills Musescore every single time. I even copied the random & random2 examples and instead of creating a new score I pointed to the curScore. I died right away.

Any idea how to do this? Thanks!

In reply to by SebiF

I don't know much about plugin coding, but did notice that the RANDOM2 plugin usually crashed MS but RANDOM always works for me. In fact, RANDOM2 now doesn't crash but instead a PLUGIN window appears beneath the palettes asking for a number for "octaves", and there is a create button which now creates a random score.

In reply to by Jojo-Schmitz

I have made some progress I think the problem is with my usage of cursor. Let me explain.

If you look at the attached code, it works fine creating a new score with the same name as the currently opened score.

If you uncomment line 123 and comment out line 124 and 133, you can see that the retrograde function produced good results:

var result = retrograde(); // line 123
//var result = m; // line 124
...
//generatenNewScore(curScore.name, result.measures, result.scoreNotes); // line 133

However if you uncomment line 133 which is supposed to write to the new file it kills Musescore.
Since I've tested that each of the components work by themselves I'm thinking that there's some sort of interdependency that creates this, something like cursors. Well, I'm speculating since I could not know what's actually happening.

import QtQuick 2.0
import MuseScore 1.0

MuseScore {
version: "1.0"
description: "This test plugin reverses a score"
menuPath: "Plugins.Retrograde"

function addNote(pitch, cursor) {
cursor.addNote(pitch);
}

function generatenNewScore(name, measures, scoreNotes) {

var numerator = 3;
var denominator = 4;
var octaves = 2;

var score = newScore(name + ".mscz", "piano", measures);

score.addText("title", name);
score.addText("subtitle", "Reverted");

var cursor = score.newCursor();
cursor.track = 0;

cursor.rewind(0);

var ts = newElement(Element.TIMESIG);
ts.setSig(numerator, denominator);
cursor.add(ts);

cursor.rewind(0);
cursor.setDuration(1, denominator);
for (var n in scoreNotes) {
var d = scoreNotes[n].duration/15;
cursor.setDuration(d, 128);
var nv = scoreNotes[n].notes[0];
cursor.addNote(nv.pitch);
console.log(nv + "+")
cursor.next();
}
}

function retrograde() {
var measures = 0;
var scoreNotes = [];

var cursor = curScore.newCursor();
cursor.rewind(1);
var startStaff;
var endStaff;
var endTick;
var fullScore = false;
if (!cursor.segment) { // no selection
console.log("No selection")
fullScore = true;
startStaff = 0; // start with 1st staff
endStaff = curScore.nstaves - 1; // and end with last
console.log("curScore.nstaves=" + curScore.nstaves);
} else {
console.log("Processing selection")
startStaff = cursor.staffIdx;
cursor.rewind(2);
if (cursor.tick == 0) {
// this happens when the selection includes
// the last measure of the score.
// rewind(2) 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;
}

scoreNotes = [];

console.log(startStaff + " - " + endStaff + " - " + endTick)
for (var staff = startStaff; staff <= endStaff; staff++) {
for (var voice = 0; voice < 4; voice++) {
cursor.rewind(1); // sets voice to 0
cursor.voice = voice; //voice has to be set after goTo
cursor.staffIdx = staff;

if (fullScore)
cursor.rewind(0) // if no selection, beginning of score

while (cursor.segment && (fullScore || cursor.tick < endTick)) {
console.log(".");
if (cursor.element && cursor.element.type == Element.CHORD) {
scoreNotes.push(cursor.element);
}
cursor.next();
}

cursor.rewind((fullScore)? 0 : 1) // if no selection, beginning of score
var measures = 0;
while (cursor.segment && (fullScore || cursor.tick < endTick)) {
measures++;
cursor.nextMeasure(measures);
}
curScore.appendMeasures(measures);
console.log(">>" + scoreNotes.length + ", M: " + measures);
}
}
return { "measures":measures, "scoreNotes":scoreNotes};
}

onRun: {
if (typeof curScore === 'undefined')
Qt.quit();
console.log("Start processing..")

var m = {
measures:3,
scoreNotes: [
{notes:[{pitch:72}], duration:30},
{notes:[{pitch:60}], duration:60}
]
};
// var result = retrograde(); // line 123
var result = m; // line 124
console.log("measures:" + result.measures)
for (var n in result.scoreNotes) {
var nv = result.scoreNotes[n].notes;
for (var x in nv) {
console.log("pitch:" + nv[x].pitch)
console.log("duration:" + result.scoreNotes[n].duration/15)
}
}
generatenNewScore(curScore.name, result.measures, result.scoreNotes); // line 133

Qt.quit()
}
}

Attachment Size
retrograde.qml 5.02 KB
test.mscz 4.77 KB

In reply to by Jojo-Schmitz

It crashes randomly, most probably due to too much memory usage.
I'm currently on a Windows 7, 16GB RAM, Intel i7, nVidia Graphics card, using ASIO4ALL and stock Waves MaxxAudio Realtek sound drivers (Tryed using both, but still crashes frequently).
When opening a rather large score, 14 parts, 144 measures, it crashes about once every 20 minutes, and has a consistent lag to everything except the spacebar. When I have the plugin creator open, it crashes about once every 15 minutes, and more so after I leave my computer on idle and return to musescore. It also crashes on startup once every 20 times maybe

Hi,

I just recently started to try MuseScore 2.0. I haven't been following the development discussions between 1.3 and 2.0.
Now I found the plugin system and syntax had changed quite drastically. I trust the developers that this is an improvement, I just can't see which yet. Unfortunately I just was starting to understand how to write my own plugins for MuseScore 1.3. I had some nice scripts already working for me, but I have to port or re-write them now for MuseScore 2.0

Where can I find documentation about the new plugin API and syntax?
The information I can find in the musescore website is all about the old Musescore 1.3 plugin API?

P.S.
One totally unimportant and off-topic remark: why isn't the musescore executable simply named "musescore(.exe)" instead of "mscore"? Or: Why isn't the name of the project Mscore instead of MuseScore.

In reply to by mtarenskeen

For now you could reverse engineer the musescore library code here.. https://github.com/musescore/MuseScore/tree/master/libmscore

Anyways, One important change I found out from Miwarre is that every score object (note, chord, segment, blah) is now a part of the Element class. Meaning that instead of
var someNote = new Note();
you have to write
var something = newElement(enum class Type : char);
where newElement() is a function and Type is basically all the types of score objects.

For Musescore2, you would now use
var someNote = newElement(Element.NOTE);
There's also Element.CHORD, Element.LYRICS etc.. based on the type of Element.

The full list of Element.blah you can use is from line 191 to 285 of this file (as of 24/3/15):
https://github.com/musescore/MuseScore/blob/master/libmscore/element.h

Another significant change would be the cursor.

To create the Cursor, you'd use
curScore.newCursor()
instead of var cursor = new Cursor(score)

And for cursor rewinding,
goToSelectionStart();
is now cursor.rewind(1);
and goToSelectionEnd();
is now cursor.rewind(2);

The best thing about this new plugin thingy though, is that the plugins are now coded in .QML instead of .js. There isn't really a significant change in syntax cuz you can put javascript into the QML files. But the big thing is that now you can have graphical plugins with actual windows that allows for interaction and what not. For more info about QML you can go here:

http://doc.qt.io/qt-5/qml-tutorial1.html

You can actually enter all those examples in the plugin creator, and just add a few lines like this:

import QtQuick 2.0
import MuseScore 1.0

MuseScore {
//Add the next three lines here
pluginType: "dialog";//To make the plugin an actual window
width: 600;//Width of the window in pixels
height: 500;//Height of the window in pixels

and it whatever you put will actually show.

Anyways, hope the Musescore 2 plugin development documentation comes out soon, and good luck with your learning!

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