Accessing the Currently-Selected Duration in a Plugin

• Feb 27, 2023 - 09:25

Hi, I'm thinking of making a plugin, never made one before, and I've been reading through other people's code and what documentation I can find to figure out how to do it.
My question is, how do I access the currently-selected duration in my code? As in, how do I figure out which one of those little duration-buttons at the top of the score is currently selected? I've found the setDuration function in the doxygen API docs, which takes two integers to set a Cursors duration, but I'm trying to get access to the those two integers themselves.

Also, I'm curious as to how the Fraction class is used for representing durations: I assume that a half note would be numerator = 1, denominator = 2, but would numerator = 2, denominator=4 also be a half note?
If I took this half note and added the numerator of a quarter note (which I assume is 1/4) would it give me a dotted half note (3/4, presumably)? the .txt file shows what I'm trying to do (don't know if it's actually valid qml but you get the idea).
(In the doxygen doc it lists 'actualDuration' and 'globalDuration' as fractions but 'duration' as a QVariant, which is sorta like a void pointer, right? But if I recall correctly people were referencing it like a fraction.)

Attachment Size
musescorequestion.txt 375 bytes

Comments

This is a starting point:

// Retrieve the selection
var selection = curScore.selection;
var elements = selection.elements;
var all = [];
var prevChord = null;

// Keep only the CHORD and the RESTS elements.
// For the NOTE elements, retrieve their CHORD parent, and ensure it hasn't yet been added
for (var i = 0; i < elements.length; i++) {
    var element = elements[i];
    if (element.type === Element.NOTE) {
        // Got a note, need to find the parent chord
        var chord = element.parent;
        if (!prevChord || (prevChord !== chord)) {
            all.push(chord);
        }
        prevChord = chord;
    } else if ((element.type == Element.REST) || (element.type === Element.CHORD)) {

        all.push(element);

    }

}
console.log("found %1 elements".arg(all.length));

// Sum all the duration
// For the ease of the summuing, put all durations in x/64
var total = 0;
for (var j = 0; j < all.length; j++) {
    var duration = all[j].duration;
    var d = 64 * duration.numerator / duration.denominator;
    console.log("%1): %2 - %3".arg(j).arg(duration.str).arg(d));
    total = total + d;
}
console.log("total duration %1/%2".arg(total).arg(64));

I took a few assumptions (for the ease of the code readability):

  • There are no tuplets: if there were tuplets, the .duration is the nominal element's duration. To get the effective (aka "real") duration, you must access the element's tuplet properties (its ratio).
  • all the elements are on the same track. If multiple tracks were involved, you probably have to work by tracks and segments

Other approaches could go via the Cursor object:

var cursor = score.newCursor();

var firstTick, firstStaff, lastTick, lastStaff;
// start
cursor.rewind(Cursor.SELECTION_START);
firstTick = cursor.tick;
firstStaff = cursor.track;
// end
cursor.rewind(Cursor.SELECTION_END);
lastTick = cursor.tick;
if (lastTick == 0) { // dealing with some bug when selecting to end.
    lastTick = score.lastSegment.tick + 1;
}
lastStaff = cursor.track;
var chords = [];

cursor.rewind(Cursor.SELECTION_START);
var segment = cursor.segment;
while (segment && (segment.tick < lastTick)) {
    for (var track = firstStaff; track <= lastStaff; track++) {
        var element;
        element = segment.elementAt(track);
        if (element && (element.type == Element.CHORD || element.type == Element.REST)) {
            debugSegment(segment, track);
            chords[chords.length] = element;
        }
    }
    // cursor.next();
    // segment = cursor.segment;
    segment = segment.next; // 18/6/22 : looping thru all segments, and only defined at the curso level (which is bound to track)
}

Check which one works the best for your case in different use cases (in terms of selection), such as :
UC1.png UC3.png UC2.png

In reply to by parkingb

OK, thanks for the example code. Ultimately I'm trying to make it so I can press a specified key and it will lengthen the last-inputted note/chord by the currently-selected duration. So, enter an eighth note, press a key and it lengthens to a quarter note, press it again and it becomes a dotted quarter note, etc. I either need to know the current active duration (which would make this simple), or be able to set up an event listener, which I'm not sure if plugins can do.

In reply to by J_Bowden

Yes, THIS. I thought I invented the idea today, but happy to see someone already figured it out.

How is it coming? I am going to look into this directly. Surely you can query the state of toolbar items???

It sounds like plugins can create gui's so worst case I suppose you could create an artificial note value for the plugin to use, and ignore the setting in the toolbar.

In reply to by woodslanding

Oh now I wouldn't be able to even start on this project until at least like halfway through this year, so if you can figure it out please do.
If you can query the state of the toolbar duration, I think should be pretty easy. Something like: 1) get current toolbar duration; 2) make a cursor set to the currently selected note; 3) get the duration of that note; 4) new duration = toolbar duration + selected duration; 5) rewrite all the notes in the chord of the currently selected note to the new duration. Then, shortcut this to one of the keys (like Enter, it ain't doing anything important) so that every-time you want to lengthen a note you just hit enter. Of course it wouldn't work in MS4 but I mean the plugin system there is still in-dev.

In reply to by J_Bowden

It looks from the code above like you can easily get the length of the currently selected note. You could just store that value when you enable the feature, and ignore the values in the toolbar, which will then update with changes of note length normally.

You will have to reset the length of newly created notes to the stored length every time the toolbar value changes, but once again, it doesn't require querying the toolbar, just changing the selection. But maybe that means you'd have to toggle the script on and off to change the notestretch value.

It's not clear to me that the toolbar would actually change its note length though, if already created notes are having their lengths changed via script. I guess that needs to be tested. If it doesn't, then it would in fact be very elegant to use the toolbar values to set the notestretch amount in the script. Then you wouldn't have to toggle the script on and off.

Sorry, thinking out loud here. So now I see where you might need a listener... hmmm. I'll have to read the docs some more. I guess if you run a query of the toolbar in a loop that runs periodically as long as the script is enabled, that's effectively a listener, right? But you still have to know the active duration. Doesn't look like the retrograde script ever gets that, it deals exclusively with the selection. I guess I'll grub around and see if I can find an example of something referencing toolbar values.

Which I guess is exactly where we started.

Well, I'm still going to leave this up, in case I forget, and start going through this whole reasoning process again. ;)

In reply to by woodslanding

I'm always open to add features to my plugin as long as it fits into the plugin philosophy.
Feel free to describe precisely what you are trying to achieve here. A User Story is always a good way to describe a feature request. Once you've done it, I will look at it and tell you if it is feasible or not and if I can find some spare time to work on it.

In reply to by J_Bowden

This plugin is definitely not the easiest to understand. As it uses concepts most plugin don't use (such as transposing instruments or multi -voices edition) and as it tweaks heavily Musescore's note modification paradigm.
However most of the logic that could interest you is located in "NoteHelper js" library.

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