Add text line to range

• Feb 2, 2022 - 23:35

I've made a plugin that adjusts note anchored lines to serve as brackets for marking ligatures in early music as encountered in this Facebook post (https://www.facebook.com/groups/musescore/posts/7546201332072786?commen…). I've got the tweaks happening pretty well (attached), but I'd like to be able to create the line itself in the plugin on a range of two or more notes.

I tried:

var bracket = newElement(Element.TEXTLINE);
curScore.selection.elements.add(bracket);

but got a TypeError: Property 'add' of object [object Object] is not a function

What am I missing?

Attachment Size
ligatureBracket.qml 1.24 KB

Comments

A line needs to be applied over a range, not added to a single object.

You could attempt the following:
1. Select the range using the selection API
2. Call cmd('add-noteline');

It could be that the cmd() has to be wrapped within startCmd/endCmd to then correctly further process it (although I'm not sure how you can access that line object then afterwards to adjust properties of it).

In reply to by jeetee

Thanks, I did have a range selected and still the got above error. The cmd() worked without wrapping it, but you're right that then there's no way to refer to the object because the parent of the TextLine item is the system, not the notes.

I tried the following:
var cursor = curScore.newCursor();
var bracket = newElement(Element.TEXTLINE);
cursor.rewind(1);

curScore.selection.select(cursor.element.notes[0]);
cursor.next();
curScore.selection.select(cursor.element.notes[0], true);
curScore.selection.add(bracket);

but I get TypeError: Property 'add' of object Ms::PluginAPI::Selection(0x11822300) is not a function. Do I need to make an array of notes and then call add() on the array? The API says that only the cursor, chord, and note classes have the add function.

In reply to by jonarnold

Since, as you're aware, there are only add methods on single objects, there is no way to add lines in the plugin API besides using the cmd interface for it.

Now I believe I did find a way to reach the line afterwards and it is to make a new range selection after inserting the line. Then you can again loop over the elements in the selection and this time the TextLine element will be included in it; allowing you to further access/modify it.

In reply to by jeetee

Ah thanks! I was able to do it without changing the selection actually. I do get a Warning: Trying to construct an instance of an invalid type, type id: 329084992 warning when I do the cmd, but it still works. Not sure if I should worry about it?

In reply to by jonarnold

If you're now using the cmd upon a range selection, then that might be the origin. A note anchored line is really only supposed to be attached to two selected notes and nothing else, so make sure your selection is exactly that to be on the safe side.
You'll then need to range select that to be able to access the TextLine from what I can see (the line isn't highlighted when you add it manually, so I assume it won't be in the selection directly after adding it).

In reply to by jonarnold

I can confirm that error in 3.6.2 (although it's a different numeric value for me: 959208912).
The error comes from the underlying Qt libraries and have to do with that the constructed element is not convertible into a QVariant type of variable (which indeed is the case, but also not something to worry about for you).

That errors comes from the fact that your selection contains not only notes but also stems, ...
So the primary idea would to isolate the notes from the selection and add the bracket on the note directly.

var sel=curScore.selection.elements;

  // The way it should work
  var bracket = newElement(Element.TEXTLINE);

  for (var i=0;i<sel.length;i++) {
        var el = sel[i];
        console.debug(el.type);
        if (el.type==Element.NOTE) {
              curScore.startCmd();
              el.add(bracket); // STOP : crashes Musescore
              curScore.endCmd();
        }
  }


No error.... But a crash (this is a typical behaviour of MS). So we've got to find another solution.

The idea of jeetee is working fine. After adding the line through the cmd the line is added to the selection.
You just have to retrieve it from the selection in order to modify it:


// The way it works actually
curScore.startCmd();
cmd("add-noteline");
curScore.endCmd();

  var bracket=null;
  for (var i=0;i<sel.length;i++) {
        var el = sel[i];
        console.debug(el.type);
        if (el.type==Element.TEXTLINE) {
              bracket=el;
        }
  }

  if (el==null) {
        console.log("Couldn't find the line");
        } 
  else {
        curScore.startCmd();
        var hookHeight = 4; //desired height of line hook

        bracket.beginHookType = 1; //90 degrees
        bracket.endHookType = 1; //90 degrees
        bracket.beginHookHeight = hookHeight;
        bracket.endHookHeight = hookHeight;

        curScore.endCmd();

  }

In reply to by parkingb

Thanks, I developed a similar solution, and the setting of the beginning and ending hooks worked fine, but oddly the same line has different Y positions when I find it by looping through the selection than when it's the only selection. So, my old code doesn't move the line at all if it's not selected by itself.

In reply to by jonarnold

Honestly I've not been able to select it.
When you select the line manually, its type is "TextLineSegment". Not "TextLine".
But is not a segment. As "TextLineSegment" is not a valid Segment type.
My next attempt has been to iterate through all the elements of the first note measure. The line is NOTthere.
Conclusion: I don't know to programmatically find and select a line...

[EDIT] "The line is NOT there".

In reply to by parkingb

It's an unfortunate fact that MuseScore internally uses the word "segment" to mean to entirely unrelated things.

One is "vertical slice of time". Each note or rest in a score belongs to a segment, and the segments to measures. That's presumably the usage you are thinking of.

But also, a line consists of "segments" in the standard geometric sense of the word - something with a defined start and end point. Most lines have only a single segment, but any line continued from one system to the next - the portion of the line on a given system is a segment. So, each line contains an array of segments.

I finally found some time to look into this and can conclude the following:

1.) There is no need to reselect something the range from within the plugin as the added line using cmd("add-noteline") is automatically included in it.

2.) The line is found as a TEXTLINE element, even if it crosses systems and thus has two underlying SLineSegments internally.

3.) Changing the position of the TEXTLINE via the plugin is not possible (and possibly a bug in the API).
You can assign offset, but it is written to the TextLine tag instead of the child Segment tag. That in itself wouldn't be an issue if it was taken into account when reading the score back in / layouting the textline. But only the offset of the underlying SLineSegment is applied.
Because of not forwarding this property to the SLineSegment assigning userOff2 entirely fails. That property doesn't exist on the TextLine itself and there's no access to the SLineSegment inside to apply it there.

Find the test plugin version attached. When testing out, best to save the example score as mscx so you can open it with a text editor and filter on <Spanner type="TextLine"> to see the effect the plugin has.

Attachment Size
ligatureBracket.qml 4.4 KB

In reply to by jeetee

Further follow-up; you can indeed set offset and userOff2 for a TextLineSegment and that is what is selected if you select the element manually in the score. So you could do a "Select all similar elements" and then loop through all those TextLineSegments to set their offset values as you want.

However I can't find a way to go from a TextLine to the TextLineSegments within the Plugin API.

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