Layout shift on each layout - courtesy key signature calculation?

• Oct 10, 2014 - 03:30
Type
Functional
Severity
S4 - Minor
Status
closed
Project

Open the attached score created from scratch (MuseScore 7ab8acd on Xubuntu 14.10). The score only has one page.

Right click on a measure and choose "Measure Properties...".

Don't change any settings, just click on "Apply" and don't close the popup dialog.

Result 1: all measures are now wider, so the score has two pages.

Close the popup dialog.

Result 2: all measures take back their original width, and the score now has only one page.

If an element is currently selected, click anywhere to deselect it.

Click one time on any of the following toolbar icons: note duration, augmentation dot, double augmentation dot, rest, accidental, flip direction, voice.

Result 3: all measures are wider, so the score now has two pages.

Click again on one of these icons.

Result 4: all measures take back their original width, and the score now has only one page.

Etc. The measure width keeps changing.

Attachment Size
measure-width.mscz 1.42 KB

Comments

Title Measure width keeps toggling between wide and narrow Layout shift on each layout - courtesy key signature calculation?

Yes, anything that causes a re-layout, including Ctrl+A, will cause the layout to toggle between one page and two.

I think this will turn out to be more or less the same as #30941: Layout changes after Select All, and in particular, the score attached to comment #3. The basic symptom, and I think cause, is similar: MuseScore calculates how many measures it can fit on a system before it adds the courtesy key signature that would be needed, but once that is added, it changes its mind and realizes it really can't fit as many measures as it thought it could. So it uts fewer measures on the line next time, but this measn there is no more courtesy accidental, so hey, there's room for another measure after all. And so on and so on.

Miwarre and I have discussed what would be involved in fixing it. No one is looking forward to dealing with it, though. Hopefully this nice simple example makes the job easier. Time signatures are narrow enough that's it can be kind of hard to reproduce, but the courtesy signature for seven sharps will trigger this much more readily.

Status (old) fixed active

Seems like this is on the right track and may work some of the time, but it still fails on the given file. I also tried deleting the key signature and adding it back (same key, same location), but the effect remains.

For me (on ubuntu linux & Qt5.4 beta) it works (also #30941). The width calculation of cautionary symbols is currently not exact (for performance reasons) so there are still corner cases which will show a different score on second layout (or flip layout).
Unfortunately the calculations may also be platform dependent as Qt uses low level platform routines for handling fonts/glyphs.

More investigation is needed...

As you may know, I'll be with Nicolas at the GSoC Reunion this weekend, but when I return, I'll see if I can see what is happening on my end, if no one else figures anything out meanwhile. Glad to hear it is indeed working in general, and hopefully it is some sort of roundoff error which should at least make it less common than it was before. BTW, I tested on Ubuntu 14.04.

I tried under Linux Mint 17 with Qt 5.4 beta.
I can reproduce the bug with the attached score (it is the one from the first post, minus one measure at the beginning): the layout jumps after each Ctrl+A.
Moreover, one further step is added to the undo queue for each Ctrl+A (and the score is marked as "dirty").
Linux Mint 17, commit fc3ea8e

Attachment Size
measure-width2.mscz 1.41 KB

Hmmm. I can no longer reproduce the problem with the original score (measure-width), but can with the second score posted (measure-width), which is indeed the same score but with one measure removed.

I'm trying to step through the code to see where things might be going wrong. I don't think I fully understand, but let me think out loud here:

I see that layoutSystem() calls cautionaryWidth() for each measure. This normally returns 0. For the measure preceding the key change, it returns a value that represents the maximum width of the key signatures in the following measure, multiplied by 1.5. layoutSystem() then further multiples this value by "stretch" to yield a value "cautionaryW" - the estimated width of the cautionary elements that need to be created.

If there are *already* cautionary elements present (as determined by cautionaryWidth()), then nothing special happens - presumably because this value is already accounted for as part of the minimum width of the last measure. If there is *not* already a cautionary element, however, then "cautionaryW" gets added to the estimated measure width before checking to see if there is any hope of fitting another measure. If we decide we will try to fit another measure, we subtract off the "cautionaryW" value when adding the measure width to the accumulated system width.

My mind is starting to spin at as I try looking at what actually happens for our example file measure-width2, but let me point out the things I see that don't make sense to me right now:

1) in cautionaryWidth(), there is a loop to find the maximum width of key signatures. Within this loop, we set hasCourtesy to false if any given staff does not have a courtesy key signature:

https://github.com/musescore/MuseScore/blob/fc3ea8e43459c2c371352ac9b91…

This seems not right - it seem this would be sensitive to whether the *last* staff had a courtesy key signature or not. On the other hand, I'm not sure how this code would ever be hit - it seems it is hit if there is a key signature for this staff in the next measure, and that key signature says it has a courtesy, but we can't *find* the KeySigAnnounce element for the staff in this measure.

2) I don't get why we mutliple the calculated width by 1.5 and then by stretch. This seems to produce way too conservative an estimate. I'm thinking the 1.5 might be a "fudge factor" - a very conservative one - but stretch doesn't make sense at all to me. Key and time signatures are not stretched, are they?

If I change the 1.5 to 1.1, and get rid of the multiplication by "stretch", and remove setting of hasCourtesy to false within the loop, everything seems to work OK.

On tracing what happens with the example score, I realize the key may be something I overlooked before. In the case where the current layout has the key signature at the line break - so there is an existing courtesy key signature - the courtesy width is built in to to the measure width, as I mentioned, so it isn't *added* in again. But it is still *subtracted* off when accumulating the total system width. And the fact that we are significantly overestimating the cautionary width is causing us to subtract off too much, causing us to think we can fit another measure in when we really shouldn't. That seems to be the main problem here.

Better might be to modify mindWidth1() to simply exclude the cautionary width when reporting the width of the measure, perhaps when passed in some special flag so it still reports it correctly in other contexts. Then we could avoid the need to subtract off our estimate later, which would allow us to be overestimate the width without problems.

I see now also that the stretch facvtor probably is relevant in some way as it may affect the overall measure stretch. More investigation definitely still required.

Status (old) active patch (code needs review)

OK, I think maybe I have it.

I believe we *do* want the stretch factor - not because key or time signatures are actually stretched, but because they are part of the measure and thus affect the calculation of stretched measure width. However, multiplying by 1.5 at the end of cautionaryWidth() still does not seem to make sense. Instead, we should be *adding* in the appropriate margins (timesigLeftMargin and keysigLeftMargin) from the style settings if an element is found.

If I do these two things, then the calculation seems to work out almost perfectly - within the usual margin of error in Qt calculations, anyhow. The estimated value for cautionaryW when no courtesy element exists is almost exactly the difference between the actual values returned by minWidth1() with and without the courtesy element.

As for the resetting of hasCourtesy in the keysig loop in cautionaryWidth(), I found that this code *is* hit any time there is a key signature change that is *not* at the end of a line. But explicitly setting hasCourtesy to false still does not make sense to me, because for all we know there was a valid courtesy time signature, or even valid courtesy key signatures on other staves. The initialization of hasCourtesy to false at the beginning of the function should be good enough.

So here is my proposed fix:

https://github.com/musescore/MuseScore/pull/1407

It may not be perfect, but it *does* come very clsoe to the correct values, and none of the posted examples show problems with this fix in place.