how to force chords (Harmony) parsing?

• Jun 7, 2015 - 00:10

Hello.

When MuseScore opens a file with Chords annotations, they are not parsed. i.e. the harmony.text element is undefined. (example sheet)
If I simply click on each Chord once, them harmony.text is defined and set to the chord value.

Is there a way to force the harmony to be parsed from the within a plugin?
Or a way to access the Harmony user text from a plugin? harmony.hUserName and harmony._userName are not known to the plugins. (code on github )

Thanks.
Berteh.


Comments

I've never tried doing things with the 2.0 plugin framework, but I can tell you that chord symbol parsing happens during layout - that is what enables MuseScore to display them properly. So somehow you need to get MuseScore to layout the score.

In reply to by Marc Sabatella

Thank you Marc.

Unfortunately I found no way to force the re-layout that would parse the Harmony. Tried to make them "dirty" to be sure they would be "re-layouted" again... but no luck so far.

Where should I look at? What needs to be made dirty for the layout to be effective on the Harmony? it's parent chords/segment/measure/score ?

I tried many option in the following code... but nothing seems to work.

 /** touch/dirty all chords & redo layout to make sure they are all parsed.
          TODO find a way to implement, so far nothing below seems to work */
      function touchChords(){
            console.log("redoing layout of score to parse chords.");
            curScore.doLayout();
            curScore.doReLayout; //no effect
                                          //
            var cursor = curScore.newCursor();
            cursor.rewind(0);
            var segment, harmony, chord;
                                          //
            while (segment = cursor.segment) {                                     
                  harmony = getSegmentHarmony(segment);
                  if (harmony) {
                        harmony.dirty = true;
                        harmony.setDirty;
                        curScore.setPlaylistDirty;
                        curScore.doLayout();
                                         //
                        segment.setDirty;
                        curScore.doLayout();
                  }            
                  cursor.next();
            }
      }

In reply to by berteh

There is no concept of individual elements being "dirty" or not.

I think maybe I misunderstood your original question. Parsing a chord symbol has nothing to do with with the "text'" attribute. Well, they are related, but the cause and effect is the other way - the text is the raw text, once it is parsed there is a data structure built in memory. And my assumption, this *is* happening for your score. You just don't necessarily see the "text" field get populated until the chord is turned into ordinary text in order to edit it.

thank you Marc for your attention. I may have expressed myself not clearly, sorry.

I would like to get, in a (2.0) plugin, the root note, bass note and chord type (either as manual text, or list of degrees, or list of semitones).

When I open any score with chords and directly run the plugin, harmony.text is an empty string (not undefined, just empty, and yes harmony.type == Element.HARMONY).

If I simple double click on the chords in MuseScore, even without modifying them, to make musescore think they changed, then harmony.text returns the chord full name (root+variant+bass).

Moreover harmony.rootTpc and harmony.bassTpc are properly set and accessible at all times (even before any double click), it's just the "text" attribute that is not initialized.

You could test it out with the following simplified code, to be run from plugin creator:

{syntaxhighlighter brush:javascript}
import QtQuick 2.0
import MuseScore 1.0

MuseScore {
menuPath: "Plugins.print Harmonies details on console"

/** return harmony of segment if any, null if none */
function getSegmentHarmony(segment) {
if (segment.segmentType != Segment.ChordRest)
return null;
var aCount = 0;
var annotation = segment.annotations[aCount];
while (annotation) {
if (annotation.type == Element.HARMONY)
return annotation;
annotation = segment.annotations[++aCount];
}
return null;
}

onRun: {
var cursor = curScore.newCursor();
cursor.rewind(0); // beginning of score

var segment, harmony;
while (segment = cursor.segment) {
harmony = getSegmentHarmony(segment);
if (harmony) {
time = cursor.tick;
console.log("got harmony with text:",harmony.text," root:",harmony.rootTpc," bass:",harmony.baseTpc," at time:", time);
}
cursor.next();
}
Qt.quit()
}
}{/syntaxhighlighter}

Calling this plugin on a sample score with chords (Am, D7, G7/A), if I double click (only) on the last chord before running this plugin, the output result shows only the last Harmony has "text" attribute set.

Debug: got harmony with text:   root: 17  bass: -2  at time: 1440
Debug: got harmony with text:   root: 16  bass: -2  at time: 3360
Debug: got harmony with text: G7/A  root: 15  bass: 17  at time: 5280

How can I access the Harmony text without requiring to double click on each chord beforehand? any other attribute/accessor? or any way to initialize it from the plugin?

Thanks!
Berteh.

In reply to by berteh

Currently, I don't think any of the necessary methods are exposed. What you would want would really be the "handle" internally, not the 'text" - the text is raw unparsed. The "handle" is already very nicely broken up into consitutent tokens, like for C-7susb9, it would be (minor)(7)(sus)(b9). And as I mentioned, this parsing takes place on layout. So what you would need is for the "handle" method to be exposed to plugins. It's not actually a method of Harmony but of ChordList, so there would need to be a wrapper in Harmony too I guess.

Or you could consider trying to add this functionality to MuseScore itself rather than making it a plugin - it's a commonly requested feature, after all.

I'll consider to try to implement as core functionality... but it's a bit too time consuming to be really appealing :/ (due my poor C++ knowledge, slow compilation time and dev environment requirements).

I'll look at the Chordlist and consider realizing the chord in a new staff. Do you have any additional (dev) doc on linked staves? -to know what it entails to use them, how to link particular elements, how to handle changes notifications and how to allow to de-link them in user-edits-

Is there a better place to discuss this venture than this forum, or this the right place?

B.

In reply to by berteh

There really isn't documentation aside from the source and what little you see in the Develoepr's Handbook - assuming you've seen that? Click the Development link at right.

I don't think I would recommend trying to do anything with linked staves. I would just create the notes, then let the user worry about if they want to do something else with them.

Now, two years later, is the problem solved? Sibelius gives a means to parse chord text - why doesn't MuseScore2?

I admit that MuseScore2 is a lot better for the ordinary user, but for a plugin developer- it's a nightmare. I spent a week to learn Qt design principles (at least I know JavaScript), and then I found that the MuseScore2 Object Model is not well documented, and there are properties and methods, even whole objects, that are unnecessarily invisible to the programmer .

In reply to by gideonrv

I'm new to MuseScore and I'm using the plugin chordsToNotes (I adjusted it little locally). I haven't find a way to fix this issue from the script but there is a quick way instead of enter all harmonies. Just transpose the whole score up one step and then back again (not using "undo"), then all harmonies are parsed ok. But perhaps you already know that. I fixed shortcuts for transposing up and down so it's quick to dot it.

In reply to by PerD

Interesting. You could probably have your plugin do the transposing as well using cmd("transpose-up");
cmd("transpose-down");
. The only disadvantage I'm aware of is that this probably will change enharmonic spelling in the end result.

So the long workaround thought is then to do the transpose thing, then read in your harmony info; then call cmd("undo");cmd("undo"); to cancel out eventual respelling from the transpose. Then continue with whatever you wanted to do with the harmony information.

In reply to by jeetee

That's interesting, I have searched for what commands you can use in plugins (2.0), but I didn't see "cmd".
I also recently noticed that the writer of the plugin I wrote about have a README in GitHub for the plugin with headline "use transpose to parse all chords.". So it was old news :)
Your workaround means that I have to read in all Harmonies in an Array, or something. It can be problem with memory? I know the problem with "enharmonic" (new word for me, I'm Swedish), and it's annoying, so something has to be done to make a nice plugin.

PS. Is there a list of what commands you can use with "cmd"?

In reply to by jeetee

Thanks again!
But it wasn't working straight forward. For some reason the Chords (notes) wasn't added after "cmd: transpose up/down; cmd undo x 2". I have very detailed logs, it reports that the Chords are added, it's even logging the warning "Segment::add(Chord) there is already a Rest at 1:0:0(0)...", but the Chords are not added.

The solution is to just end the plugin after transpose and undo, and then simply start it again - and no need to read and remember the parsed chords.

In reply to by PerD

You'll need to wait to implement this in version 3 until the plugin framework is fixed. I suspect your efforts in version 2 will not be wasted, because they shouldn't make too big of a change in the way you write a version 2 versus version 3 plugin.

In reply to by mike320

As it happens, I am recently interested in the topic of harmony.text and writing plugins for MuseScore. I saw a tweet about MuseScore 3 being released and thought about writing a chord analysis plugin for jazz lead sheets. There are some minor breaking changes in plugin v3 dev and a bug when you select a whole score vs everything but the last measure. So, I'm also staying with v2 plugin dev for now. Anyway, I am also stuck trying to locate a qml property accessor for the text part of a harmony object. rootTpc is an integer value that can easily be a lookup to get a human friendly note for the chord. But the details about the chord, the suffix, are not accessible using plugin qml code. Here is the lookup function for the rootTpc value. If someone wants to help me out and everyone else here on how to either get a method written to get the suffix text of chords (from the harmony object), please please let me know! :)


function getNoteSpellingFromHarmonyRoot(rootTPC){
var notename = "";
if(rootTPC == -2){
return notename;
}
var spellings = [
[11,"Eb"], [12,"Bb"],[13,"F"],[14,"C"],[15,"G"],[16,"D"],[17,"A"],[18,"E"],[19,"B"],[20,"F#"],[21,"C#"]
];
for(var idx_spellings=0; idx_spellings < spellings.length; idx_spellings++){
if(spellings[idx_spellings][0] == rootTPC){
notename = spellings[idx_spellings][1];
break;
}
}
return notename;
}

In reply to by allenn

I don't understand exactly what you mean. In my modified plugin (from chordsToNotes) I parse the written Harmony. I correct all different ways of writing, e.g. "M", "Ma", "major", "j", "∆", "Δ" or "^" to "maj", "4" (main) or "sus" to "sus4", etc... (and then creating notes from that). It can take all different variants described in https://en.wikipedia.org/wiki/Chord_names_and_symbols_(popular_music)
I also do pitch <-> TPC <-> spelled note. The plugin is logging the generated notes as both pitches (semitones) and letters with alteration.

Is that what you mean?
I have made a HTML file to test it so it's easy to analyse all written harmonies and see what notes that are generated, both in the chosen key and in C-scale, it also shows the "corrected" written harmony.

In reply to by PerD

I'm attempting to have some plugin code read through the selected measure in a score and get the chord notation on each measure. Then I plan to do some analysis on that. But I'm stuck at the part where it would get the text for a chord after the note. So, in a Cm7 chord that appears above a measure, I'd like the plugin code to be able to get the "m7" suffix of the chord. Currently in MuseScore v2, the code I attempted, very much based on your plugin work to get harmony annotation, the code cannot get the suffix value. In the following line of code from the chordsToNotes plugin it looks like this is a bug.

text = harmony.text; // todo fix: where to find chord text if MuseScore did not parse/recognize the Harmony name?

Finally, there is a solution. @jeetee was almost there with the answer at Nov 25, 2018 - 09:31. It was missing startCmd() and endCmd(). And no need to read in and remember the harmonies before undo; undo.
Here is the code that is working for me, MuseScore 2.0, Windows7 x64:

curScore.startCmd(); // unless done at the start of the plugin
cmd("transpose-up");
cmd("transpose-down");
// no need to read in and remember the harmonies
cmd("undo");
cmd("undo");
curScore.endCmd(); //  (don't know if it's necessary to restart cmd)
curScore.startCmd(); // if needed for the rest of the code

In reply to by PerD

That worked! In my plugin code, all I needed were the two cmd lines of code to transpose up and then down. Which it seems allows the harmony.text property to be populated. Which is actually giving me the whole chord text and not just the suffix part that I was expecting. I'm wondering now what are some other cmd options and if there is something that takes less time. The transpose operation takes a few seconds.

Nice workaround! Thanks!!

In reply to by jeetee

Yes, it turns out that I only needed to do one transpose-up and then an undo to get the harmony.text property to populate. I'm getting good results now. I did try looking through the source code at other cmd types and didn't see one that worked. But one optimization I was able to make was to run the cmd("transpose-up")/cmd("undo") before starting to loop through the segment cursor. Instead of on each iteration. (Of course). I still have to figure out the measure number the harmony.text is set in and which beat in the measure. As well as being able to get chord data from more than the first horn part. I have alto, tenor, and vibes as parts that have chord notations on the measures. I may have to do my analysis by hand (perish the thought) if I don't get more data using the plugin code. Thanks again!

In reply to by allenn

If you're browsing the score using a cursor, you can set the track property of it to change to a different staff (track equals a voice within a staff, so you'd have 4 of these for each staff).
Voice 3 on the 2nd staff of a score would be track 6 ((staff-1)*4+(voice-1))

If "beat" equals a quarter note for you, then use the segments tick - the tick of the measure it's in / 480 (ticks/quarter note)

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