ellejohara / newretrograde

A retrograde plugin for MuseScore 3.x.
GNU General Public License v3.0
0 stars 2 forks source link

TypeError going from a measure with 1 voice to a measure with 2 voices #8

Closed elsewhere37 closed 7 months ago

elsewhere37 commented 1 year ago

Select measure 2 alone: everything OK Select measures 1 & 2: You get: 98:-1: TypeError: Cannot read property 'type' of null Entering a note in measure 1 (voice 1), or making the voice 2 note a dotted quarter gives the same error But entering a note in measure 1 (voice 2): everything Ok Select measures 3 & 4 (voice2 note & rests removed): everything OK Ravel3

elsewhere37 commented 1 year ago

If you check that a cursor.element exists (at lines 87 & 97) you don’t get the type error anymore but the resulting retrograde is not correct.

ellejohara commented 1 year ago

I found on the MuseScore QML boilerplates page that it's recommended to set the cursor.track first before setCursorToTick(). So I swapped lines 84-85, 94-95, and 116-117. That got rid of the type error, and the retrograde did "work", but the notes in voice 2 remained in measure 2. But a half-fix isn't a whole fix. I'll keep working on it.

elsewhere37 commented 1 year ago

The problem is (again) that empty inner voices have no segments: cursor.rewindToTick(x) will only work correctly if there is a segment at x. Otherwise it will put the cursor at the next available segment (in the current track) after x. See my comment: https://musescore.org/en/node/345409#comment-1206211 It also describes a very clever trick to put the cursor in the right place in track 0, even though there is no pre-existing segment there, but I’m not sure that would work for you.

ellejohara commented 1 year ago

I did try the 'set the track twice' trick, but it gave me the same result as just setting it once. However when writing a note directly, it did write to the second voice at the cursor selection. So that's weird. cursor.track = 0; setCursorToTick(cursor, startTick); cursor.track = 1; cursor.setDuration(1,8); cursor.addNote(60); I'm trying to figure out a way in the "clear selection" part of my code to write a rest into the second (or higher) voice if there isn't one in that measure. Because if I manually put a second voice in a one-voice measure, the retrograde works fine.

elsewhere37 commented 1 year ago

I think that for some reason (not clear to me) you need to do this with a new cursor

var c=curScore.newCursor() // get a new cursor

elsewhere37 commented 1 year ago

Be careful adding rests to the last measure!! See below or my FillWithRests plugin (https://musescore.org/en/project/fill-voices-rests)

var done = false;

            while (done == false && cursor.segment && cursor.tick < endTick) {
                var duration = inc;
                cursor.setDuration(duration, 1920);
                var curtime = cursor.tick;
                cursor.addRest();
                // see https://musescore.org/en/node/320206
                // at the last measure cursor gets stuck: get out of the loop via done
                if (cursor.tick == curtime) {
                    done = true;
                }
            } // end while segment
ellejohara commented 1 year ago

Okay, I did figure out a way to generate additional voices during the 'remove elements in selection' section of the code. But, as expected, it breaks if the last measure is selected. Here's what I have:

var el = cursor.measure; for (var trackNum in tracks) { cursor.track = 0; setCursorToTick(cursor, startTick); cursor.track = tracks[trackNum]; var done = false; while (cursor.segment && cursor.tick < endTick) { cursor.setDuration(1,el.timesigActual.denominator); var nut = 60 + tracks[trackNum]; cursor.addNote(nut); cursor.prev(); removeElement(cursor.element); cursor.next(); } }

I write an arbitrary note to fill each beat of the measure, then remove the note with the removeElement() function. As long as there's a measure selected with more than one voice, any measures with only one voice will increase to however many there are. Still figuring out how to include your last measure workaround. Ugh. That last measure! Edit: OH MY GLOB why can't I get a simple block of code to format correctly?

elsewhere37 commented 1 year ago

addNote has the same problem as addRest in the last measure, so that does not help.

But I think I found a more elegant solution, with a little caveat (see below) To calculate the measure length

cursor.rewind(1) var tick1 = cursor.tick cursor.nextMeasure(); var duration = cursor.tick - tick1 console.log("duration = ", duration) cursor.rewind(1)

Then add a rest if cursor.element = null, a true sign of a no segment measure:

while (cursor.segment && cursor.tick < endTick) { if (cursor.element == null) { cursor.setDuration(1,duration); cursor.addRest(); } retro.push(cursor.element); cursor.next(); console.log("91 cursor.tick = ", cursor.tick) }

The caveat: the duration found = 1440 = correct, but the plugin puts 3 quarter rests in voice 2 of the second measure. Select measure 1-2 run the plugin, result = measure 4-5. Note1: these are changes on the posted NewRetroGrade (without switching track & tick) Note2:my code formatting happened by accident (I put some code in Word, no distance between lines, but I left in all the tabs

retrotest2 EDIT: the caveat only happens if the empty measure is the first in the selection EDIT2: For duration your trick is better (and more elegant) (I had misread el as cursor.element)

if (cursor.element == null) { var el = cursor.measure; cursor.setDuration(1,el.timesigActual.denominator); cursor.addRest(); }

Where do you find neat tricks like ’el.timesigActual.denominator’? It’s not in the API documentation I have (https://musescore.github.io/MuseScore_PluginAPI_Docs/plugins/html/class_ms_1_1_plugin_a_p_i_1_1_plugin_a_p_i.html) and not found in the code.

elsewhere37 commented 1 year ago

Found the 'caveat bug'. Use: cursor.setDuration(el.timesigActual.numerator,el.timesigActual.denominator); instead of: cursor.setDuration(1,el.timesigActual.denominator); EDIT: But having an empty last measure is still wrong

elsewhere37 commented 1 year ago

The best(?!) solution is to fill the empty tracks with rests first, so that they get properly incorporated into the retro array. How to detect these empty internal tracks? There are 3 different cases: First measure of the selection: cursor.element = null Last measure of the selection: cursor.tick = 0 after nextMeasure Internal measure of the selection: the cursor jumped more than a measure width after nextMeasure

Everything seems to work now I attach the changed section of code (fix.txt) EDIT: line 30: if (tracks[trackNum] % 4 == 0) should say: if (tracks[trackNum] == 0)

fix.txt

ellejohara commented 1 year ago

Deleting the selection by filling with measures in existing voices definitely seems the most logical way to go. Thanks for being on this! I got the flu this weekend and am fully brainfoggy. I'll incorporate your fixes when I can. Thanks again!

elsewhere37 commented 1 year ago

Get well soon!! I take back my EDIT above: the selection may not include track 0. But make sure somehow that mticks is only filled once, even if multiple staves are selected.

ellejohara commented 1 year ago

Okay, check out the 3.0.6. I found an even simpler solution! If the cursor.element is null, then addRest() with the denominator. Otherwise just remove elements as usual.

Oh and I missed one of your questions above. How do I find neat things like timesig.Actual? I use a very simple function to list the content of objects in the console window:

function pre(item) {
    for(var p in item)
    console.log(p + ": " + item[p]);
}

That shows me all kinds of goodies. Then I just tinker. Of course the API documentation does help, even if it doesn't describe absolutely everything.

elsewhere37 commented 1 year ago

Congrats! Looks perfect!