Plugin API Bug or Feature? Note.accidental and Note.accidentalType not updated in new cursor instance after setting to regular accidental.

• May 27, 2020 - 13:14

Hi all. I have provided a condensed snippet of my situation:

Assuming that the first note in the score on voice = 1, staffIdx = 1, tick = 0 exists, and is any note that is not F#...

// some necessary preliminary code for other parts of the plugin.
cursor.track = 0;

// Setting the first note to F#5
var x = {the first note in the score};
x.line = 0;
x.accidentalType = Accidental.SHARP;
console.log(x.accidental, x.accidentalType); // as expected: Ms::PluginAPI::Element(0x1dbb9fe0) SHARP

// some more necessary code
cursor.track = 0;

// this is my issue
var y = cursor.element.notes[0];
console.log(y.line); // correct: 0
console.log(y.tpc); // correct: 20
console.log(y.accidental); // expected: [Element object], got: null
console.log(y.accidentalType); // expected: SHARP, got: NONE

The unexpected values of y.accidental and y.accidentalType do not occur when I set the x.accidentalType to a non-standard microtonal accidental such as SHARP_ARROW_DOWN or MIRRORED_FLAT, and I assume it is because when I set the accidental to a recognized standard accidental, it updates the tpc of the note, removes any attached accidental and marks the score dirty such that it updates and attaches the new regular accidental only after the plugin runs.

However, I require access to that accidental in order for my plugin to work. Is there any way I can force the score to redraw in the middle of my plugin's execution? Or is there something I'm missing or doing wrong? Or is this a bug that I should report in the Support and Bug Reports forum?

Any help greatly appreciated :)

Thanks in advance!


Depending on your pluginType the framework calls startCmd/endCmd for you to apply the undostack correctly.
You could try calling endCmd() after changing the setting, Then startCmd() again before changing different stuff.

In reply to by jeetee

Hi, I have the same problem: note.accidental stays undefined after changing note.tcp and/or note.accidentalType. endCmd()/startCmd() do not help (and would semantically incorrect). My plugin type is dock.

Also, I noticed that the inspector tool doesn't display a button for accidental. Why? Any ideas?

In reply to by nurfz

First, note.accidental should be null if there is no accidental for this note, not undefined. If the value you see is exactly undefined then it may happen that your note variable points to something else than a note.

Second, changing tpc values directly does not really do the right thing. In order to get a note in a consistent state one should change at least all of tpc1, tpc2 and pitch. This stems from the way it is implemented internally in MuseScore, and plugin API currently does not provide a more convenient way to do this. It may be easier to remove the old note and add a new one instead if it is appropriate.

Changing accidentalType though does work for me, and the accidental is available at the note's accidental property after endCmd()/startCmd(). The latter calls are needed to do a score re-layout which would add the necessary accidental to a note. If you really need to access the accidental object before the re-layout you may try to create an accidental object manually and add it to the note with note.add() call.

In reply to by dmitrio95

Thanks! Oh, I had a bug which explains the undefined!

Ups, I only assigned tcp so far which seems to work unless I don't change MuseScore setting "Concert Pitch". Thank you for the hint.

Does changing accidentalType affects tcpX/pitch? Or is it "safe" to change it just for a different accidental appearance?

Looks like I really have to use note.add(myAccidental); note.accidental = myAccidental to make it always visible (important for my use case). Can you tell me how I can create myAccidental (of type Element). Can it just be {visible: true}?

Thank you!

In reply to by nurfz

Yes, changing note's accidentalType changes pitch and tpc if needed.

As for your other questions, the answer depends on what exactly do you need. If you only need to add some accidental that will always be displayed, even if such accidental is implied by key signature, then setting note's accidentalType seems to work fine.

If you also need to access that accidental in the same command and do something with it then the simplest way to do it I was able to get working is the following:

var acc = newElement(Element.ACCIDENTAL);
var accType = Accidental.SHARP;
acc.accidentalType = accType;
note.accidentalType = accType;
note.accidental.color = "red"; // or do something else with that accidental.

Basically, here I create an accidental with newElement() call, and the rest is something weird. It seems that the accidental gets removed if it does not match the note's accidentalType, but in order to always display the accidental you should only set the note's accidentalType after adding the accidental to the note. In the same time the final accidental seems to be not acc so changing its color does nothing, but if I do not add that acc to a note first then the entire code fails if the note has not had an accidental before. So this is a somewhat hacky but hopefully working way to achieve what you have described (if I understood you correctly).

In reply to by dmitrio95

Thank you for the explanation and examples! I'll keep that in mind. But it's indeed not easy and I have to invest some more hours to get this working. For your information, my plugin is for converting the first note system in this image to one of the other systems (different "dialects" of a tabulature system). The SHARP2 symbols are only used as symbols in the tabulature and the side effects between tcp, pitch, accidental are pretty annoying in this case ^^

In reply to by nurfz

If you only need a symbol without any meaningful side effects then you should probably use exactly symbol elements instead of accidentals. In user interface you can drag a symbol to a note from "Symbols" section in Master Palette, in plugins you can do the same by adding a symbol to a note:

var sym = newElement(Element.SYMBOL);
sym.symbol = SymId.accidentalDoubleSharp;
sym.offsetX = -1.5;

The downside of this solution is that you would have to manually set an offset to make the accidental be displayed at the correct position, but the effect would be exactly as desired: just a visual marking without any side effects.

The list of available symbols can be found here or directly in Master Palette (symbol tooltips seem to contain the relevant symbol names). There are several flavors of double sharp and many other symbols there.

In reply to by dmitrio95

Wow, thank you so much! I should have ask earlier! Your solution is so much nicer than abusing accidental and just perfect for my use case.

Do you happen to know, if I'm allowed to set my own properties on, e.g. a note object like note.myCrossSymbol = sym (in addition to note.add(sym))? It would be helpful to avoid a for-loop over note.elements to find the added symbol later on...

In reply to by nurfz

No, unfortunately it won't work this way. The objects accessible in plugins are actually wrappers for internal MuseScore objects, and those wrappers are created on demand when relevant properties and methods are invoked from plugins. Therefore several wrappers may point to the same internal object (this can be checked with is() method), and while you can add a custom property to some wrapper object, other wrapper objects will not see such property. So this will not work for saving an information between plugin invocations or between different operations in the same plugin instance.

In reply to by dmitrio95

I think, I could solve my last question:

var myAccidental = newElement(Element.ACCIDENTAL)
myAccidental.visible = false

But when endCmd() performs the re-layout, the the accidental on the second note in the measure gets visible again. Is there really no relayout() command that does not commit to the undo history?

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