Get cursor to score from parts

• Apr 9, 2022 - 04:34

I'm trying to write a plugin that will set the accidentalBracket of an accidental in the score to the existing value in the part (e.g. # is bracketed in the part only, so plugin would then bracket it in the score). I haven't worked with the cursor object much and need some help.

I can get the tick of the accidental (well, the segment) and rewind the cursor to that point, but:
1. How do I create a cursor object in the full score when curScore is a part?
2. How do I get the cursor to point to the same note in the correct staff? I don't have any chords in the project I'm working on, but I could imagine the need for multiple notes in a segment.

Here's my test code so far:

    var element = curScore.selection.elements[0];
    //var bracketSetting = element.accidentalBracket;
    var bracketSetting = 1; //for testing since the cursor is in the same score right now
    while(element.type != Element.SEGMENT) {
         element = element.parent;
    }
    var tick = element.tick;
 
    var cursor = curScore.newCursor();
    cursor.rewindToTick(tick);
 
    cursor.element.notes[0].accidental.accidentalBracket = bracketSetting;

Thanks for the help!


Comments

First, before moving the cursor, you have to specify its track:

var element = curScore.selection.elements[0];
//var bracketSetting = element.accidentalBracket;
var bracketSetting = 1; //for testing since the cursor is in the same score right now
 
var segment=element;        
while (segment.type != Element.SEGMENT) {
    segment = segment.parent;
}
var tick = segment.tick;
var track=element.track;
 
console.log("Analyzing "+tick+"/"+track);
 
var cursor = curScore.newCursor();
cursor.track = element.track;
cursor.rewindToTick(tick);
 
var atSegment = cursor.segment.elementAt(cursor.track);
 
console.log("found at "+tick+"/"+track+": "+(atSegment?atSegment.userName():"null")+" ("+cursor.segment.tick+")");
if (atSegment.type === Element.CHORD) {
    for (var i = 0; i < atSegment.notes.length; i++) {
        var note = atSegment.notes[i];
        console.log("note at pitch "+note.pitch+":  "+note.accidentalType);
        note.accidental.accidentalBracket = bracketSetting;
 
    }
}

In reply to by parkingb

Thanks! So I should maybe take this further by saving the pitch of the note at the beginning and only operating on a matching note in the for loop at the end.

So, how do I modify var cursor = curScore.newCursor(); to direct the cursor to the score when selecting an accidental in the part? Or is there another way to refer to the same element in the score? That's the reason for the plugin.

In reply to by parkingb

Sorry my first post wasn't clear. I want to select an accidental in the linked part and have the properties (at least the bracket style) copied to the corresponding accidental in the score.

I'm entering music in the parts so that I can mirror the original partbooks with the line breaks and having notes display across measure boundaries. If I make an accidental bracketed there, I want to be able to transfer that easily without having to find it.

In reply to by jonarnold

I got your point, and well this puzzled me a lot.
There is a big WORDING issue in MS:
What is called "Part" in the User Interface is actually called "Excerpt" in the API,
While what is called "Part" in the API is actually a class on-top of the "Staff".
What adds to the confusion every excerpt behaves like a separated score which be obtained by the ... partScore property.

So for your plugin forget about the Parts, look for the Excerpts.

Here is a sketch:

// 1) identify our starting note/accidental
var element = curScore.selection.elements[0];
 
var accidental;
var note;
var part;
 
// -note-accidental-
if (element === undefined) {
    console.log("No selection");
    return;
} else if (element.type == Element.ACCIDENTAL) {
    accidental = element;
    note = element.parent;
} else if (element.type == Element.NOTE) {
    note = element;
    accidental = element.accidental;
} else {
    console.log("Fail to identify an accidental " + element.userName() + "/" + element.type);
    return;
}
 
// 2) identifying where is the selected element
// -segment-track-
var segment = note;
while (segment.type != Element.SEGMENT) {
    segment = segment.parent;
}
var tick = segment.tick;
var track = element.track;
 
// -"partScpre" excerpt-
var part = note.staff.part;
 
console.log("we are on " + tick + "/" + track + "/" + part.longName);
 
// 3) Identifying where to apply the change
var scores = [curScore];
for (var i = 0; i < curScore.excerpts.length; i++) {
    scores.push(curScore.excerpts[i].partScore);
}
 
// 4) Defining what to do
//var bracketSetting = accidental.accidentalBracket;
var bracketSetting = 2; //for testing since the cursor is in the same score right now
 
// 5) Applying the change
for (var i = 0; i < scores.length; i++) {
    console.log("~~~~ " + scores[i] + " ~~~~");
    var partscore = scores[i];
 
    // console.log("excerpts: "+partscore.excerpts.length+", tracks: "+partscore.ntracks);
 
    var cursor = partscore.newCursor();
    cursor.track = track;
    cursor.rewindToTick(tick);
 
    var cseg=cursor.segment;
    if (cseg===null) {
    console.log("nothing found at " + tick + "/" + track);
    continue;
    }
    var atSegment = cseg.elementAt(cursor.track);
 
    console.log("found at " + tick + "/" + track + ": " + (atSegment ? atSegment.userName() : "null") + " (" + cursor.segment.tick + ")");
    if (cursor.segment.tick !== tick) {
        console.log("Non matching tick");
        continue;
    }
    if (atSegment.type === Element.CHORD) {
        for (var j = 0; j < atSegment.notes.length; j++) {
            var note = atSegment.notes[j];
            console.log("note at pitch " + note.pitch + ":  " + note.accidentalType);
            if (note.accidental === undefined || note.accidentalType === 0)
                continue;
            note.accidental.accidentalBracket = bracketSetting;
 
        }
    }
 
}

REMARK: this isn't a finished code. Up-to-you to finalize it.

In this code I deduce the "track" from the selected element.
But the "track" you'll get there is based on current excerpt, not on an overall position.
Look at the attached example:
The Eb for the Trumpet is on track 0 on the main score (curScore) and on track 0 on the trumpet's excerpt.
The code will work fine.
The Ab for the Saxophone is on track 4 on the main score (curScore) but on track 0 on the saxophone's excerpt (because that excerpt contains only one staff).
The code will not work correctly in that case.

Up to you to try to identify the right track. Maybe parse all the tracks and select the ones with the same instrument. But I hope that sketch will help you moving on.

Attachment Size
mcveExcerpt.mscz 5.42 KB

In reply to by Jojo-Schmitz

Is there an easy way to identify a same track in different excerpts ? The track number doesn't work as it dependent of its position on the score/excerpt.
Is there some kind of unique ID that is exposed by the API ?
I was thinking to use the Instrument+voice as a kind of a pseudo-unique ID, but the instrument is not unique, thus this approach won't be really error-proof.

In reply to by parkingb

Thanks so much for working on this. It is very helpful for learning, and it's the next piece of what I want to be able to do with the plugin.

However, my first goal is to work in the opposite direction. I want to be able access the "full" score when curScore is an excerpt. The logical equivalent of var cursor = curScore.parent.newCursor(), or in other words, going the opposite direction as curScore.excerpts[i].partScore.

In reply to by jonarnold

You are right. My mistake. I can't help you here. I looked in the datamodel, explored all the curScore properties, explored the various "cmd-xxx" commands. Nothing seems to allow you to retrieve the full score from one of the excerpt score.

I haven't followed this completely as I'm not that up on the plugin API, but just a general comment: normally, this isn't how you'd go about it it at all. Within MuseScore itself, the code almost never tries to find parts assdcociated with a score then hunt through the parts looking for the equivalent of a given element. Instead, each element contains links to the corresponding elements in parts directly - no hunting required. So, if that property - the list of linked elements for a given element - isn't exposed already, that's what should be, as that's what you should be looking at in trying to make an update to score & parts simultaneously.

In reply to by parkingb

The link I am talking about is at the element level itself, not the part level. So, for a note in the score, it contains a list to the linked note in the corresponding part - or if there are multiple parts containing that instrument, the linked note each of the relevant parts. It works the same for linked standard+tab staves - the list of linked elements in cludes both the tab note and the corresponding note(s) in any parts. And the list is equally available whether starting from a note in the score or starting from a note in the part. And again, it's part of the element class (or perhaps scoreelement), so it applies to accidentals, text, articulations, etc - anything that inherits from element.

In reply to by Marc Sabatella

Yes, that would be great to be able to use. I see that there's an elements property that comes up as < [object Object] > for notes. Is that an array? I tried curScore.selection.elements[0].elements[0].visible = false; and got a TypeError

EDIT: It looks like the elements list property is for articulations attached to the note according to the API: https://musescore.github.io/MuseScore_PluginAPI_Docs/plugins/html/class…

I don't see anything in the API for Elements that would help. So should I make a feature request to expose this?

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