Concert Pitch switching does not preserve key spelling if the transposition crosses between sharp and flat keys
Steps to reproduce:
1. Create new score for C Trumpet and Bb Trumpet
2. Drag F# major (6 sharps) key signature to first measure of C Trumpet staff
3. Switch to Concert Pitch
Expected behavior: Both staves should be in F# major (6 sharps)
Actual behavior: Bb Trumpet staff is in Gb major (6 flats).
Discussion: This defeats the purpose of the Concert Pitch button (letting the composer/arranger think in a single key).
GIT commit: a315216
Windows 7
Comments
Corollary bug:
4. Remaining in Concert Pitch, drag F# major key signature to first measure of Bb Trumpet staff
5. Switch off Concert Pitch
6. Switch back to Concert Pitch
Expected behavior: Both staves should be in F# major (6 sharps)
Actual behavior: Bb Trumpet staff is in Gb major (6 flats).
Discussion: MuseScore should respect the user's request for F# major.
GIT commit: a315216
Windows 7
I was actually about to have a look at a bug you reported three years ago http://musescore.org/en/node/6281 that I ran into myself testing out some new code. They might be related. I'll keep a lookout as I go through the code.
Mike
For a related discussion of preference for certain keys, see http://musescore.org/en/node/11841
#20418: incorrect key signature in concert pitch mode for transposing instruments marked as a duplicate of this report.
This bugged me, esp when writing in sharp keys with instruments that are flat key transposing (primarily Eb/Bb Saxes & Trumpets), because the final parts becomes effectively unreadable. I'm just making a note that this bug is still present in 75b66fd which is the latest 2.1 testing nightly.
I'm fairly certain the cause is that enum Key only goes from -7 (Cb) to +7 (C#): https://github.com/musescore/MuseScore/blob/2.1/libmscore/key.h#L27-L36
(There is the function KeySig::enforceLimits(), which ensures that they key is within -7 and +7 limits: https://github.com/musescore/MuseScore/blob/2.1/libmscore/key.cpp#L34-L… but searching github codebase reveals that enforceLimits() is never called anywhere. Seems like that function should be deleted from the code.)
Without looking deeper into the code, I'm sure that somewhere else in the codebase MuseScore is enforcing this -7 to +7 limit. And that enforcement will cause a problem when toggling between concert pitch (for both flat key transposing instruments written in sharp concert keys, as well as for sharp key transposing instruments written in flat concert keys), since the change of key signature is not a reversible operation.
Now I see two possible ways to resolve:
I think both options are valid, and I think MuseScore should allow for both with option in preferences. I think could do 2b safely with minimal changes.
There is another semi-serious concern I want to bring up, but which might be implemented in a seperate issue. Currently when toggling concert pitch, I've discovered that even though the key signature might flip between the sharp/flat realms, that unfortunately the transposion of the individual notes are not obeying that flip. For instance if write an F# Major scale in key of F# Major for a Bb & Eb instrument while in ConcertPitch:
Then after I toggle Concert Pitch OFF, I unfortuantely get this confusing mess:
Same mess for the individual part ("excerpt") child scores. So clearly if MuseScore is having key signature flip realms, then the transposition of the notes and harmonies needs to be aware of this flip.
I'm trying to implement 2b now...
The last comment about having notes and harmonies obey the flip, seems like need to handle this in Score::cmdConcertPitchChanged(), Excerpt::createExcerpt(), and Score:: Score::transpositionChanged(), which all call Score::transposeKeys()...would be nice to somehow merge the logic handling that into one function so easier to ensure consistent behavior...
I wanted to add another related corollary minor bug occuring when *changing a staff to a different transposing instrument* while ConcertPitch is OFF:
Expected behavior: The key should have returned back to F# major (6 sharps).
Actual behavior: The key morphed into Gb major (6 flats).
Also note that if I originally started with notes, then upon doing these transformations they similar get messed up just as I described at the end of comment #5:
While this is honestly not as serious as the originally described bug, I think it makes sense to fix it at the same time since the code changes are very related, although these changes would involve Score::transpositionChanged().
(I guess a similar issue would also occur when adding Change Instrument text element before a keysignature and then deleting that key signature. Although the Change Instrument text change doesn't seem to be doing anything in 2.1)
BTW, if you haven't thought of this already, keep in mind #39176: Option to convert transposed instrument key signatures into enharmonic equivalent when number of accidentals exceeds limit. Ideally there should be some simple control over how the transposition affects keys in the first place.
Also, the behavior you show where changing the key from sharps to flats or vice versa doesn't affect the pitches is perfectly normal taken at face value. That is, even in music for piano, if you enter notes in the key of F# and then change the key signature to Gb, it doesn't and shouldn't automatically respell the notes. If you *manually* change keys, you need to *manually* respell notes as well. The trick is making sure any *automatic* conversions handle both. Do keep in mind we store separate TPC's for all notes in concert versus transposed pitches, specifically to allow for different enharmonic spellings according to context (which is an important and necessary feature). But we don't do anything like that for key signatures. Or chord symbols.
thanks for pointing out #39176: Option to convert transposed instrument key signatures into enharmonic equivalent when number of accidentals exceeds limit...I was unaware of that issue, and it makes sense to provide the option for switching sharp/flat even if not exceeding the limit.
R.e. your second paragraph, the pngs I was showing were not from manual key signature, but those were automatic keysig changes when toggling ConcertPitch, so I do think MuseScore needs to transpose the notes according to the same way the automatic keysig changes. But of course if user manually changes the key sig then there should be no expectation for MuseScore changing the note spellings.
Aha, was not aware of that, thanks. Sounds like storing seperate TPC's for key sigs and chord symbols is probably the way I should go...
(Side Note: If not in concert pitch and add a Change Instrument Text set to a transposing instrument, MuseScore does not seem to change keysigs after that change insrument text (but only changes note pitches) as of current 3.0dev 2274353)
(or I guess something equivalent to TPC's for key sigs)Well looks like class Note stores int _tpc[2] as a simple 2-element array,
so I'm going to try doing that same thing for class KeySigEvent by turning Key _key into Key _key[2] to be analogous....edit: honestly when looking at note.cpp I find the use of _tpc[0] to hold non-transposed and _tpc[1] to hold transposed to be a very confusing naming system, so for KeySigEvent I'm just going to name them instead: _keyConcertPitch and _keyWritten for the time being (where _keyWritten is how this particular keysig is currently displayed) until a better name comes along.BTW, I mentioned the thing about manual versus automatic key changes mostly just to make sure you didn't inadvertently change the behavior for manual changes. But also, I should point out that some *would* want a command to change key signature and respell notes simultaneously. So it's worth thinking about the possibility of a keyboard modifier (like the way "Ctrl" marks a key signature local to one staff) to respell notes. Anyhow, the bottom line is just making sure the two concepts remain separable.
re
I can see how that might be useful for switching from flat to sharp or from sharp to flat keys. Would that also be used for switching keys in general?
I was making the change of having KeySigEvent contain _keyConcertPitch and _keyWritten, but I'm stopping myself now before I go too far down the rabbit hole because it is seems that I am going to have to change a lot of code in a lot of files in order to keep those two values properly in sync with one another.
I'm am going to try an alternative method with is a bit simpler: I'm going to add a simple boolean to the KeySigEvent data struct, named something like _transposedKeyExceededAccidentalLimit (any better names welcomed), which is normally false but gets set true for the case when the calculation of the non-ConcertPitch key has exceeded the limits of the maximum number of accidentals. So now hopefully the only changes to code will be around the concert pitch toggling (and that boolean will be saved/read from the xml). I think this will be a much simpler patch compared to keeping track of concertpitch key (which seemed to require a complete overhall, and would likely not get accepted).
Oh and as I said in #39176: Option to convert transposed instrument key signatures into enharmonic equivalent when number of accidentals exceeds limit I'm adding two styles:
I currently have the boolean _transposedKeyExceededAccidentalLimit successfully get set when an instrument transposition causes the key to exceed those Styles I defined, and can see it in the debugger (which now has my boolean variable displayed as checkbox):
Just making a note that this is the way I'm going forward with this issue, because it seems much easier. I am basically having a separate transpose key function instead of extern Key transposeKey(Key oldKey, const Interval&); which I'm calling:
void KeySigEvent::setTransposedInstrumentKey(Key key, const Interval& interval, int sharpLimit, int flatLimit);
And that I'm using specifically for dealing with transposing instruments handling concertpitch setting, but not for any other transposing. It sets the flag if necessary.
Now I need to finish up for when returning to concert pitch...
Oh, I've ended up treating the transposedKyeExceededAccidentalLimit in KeySigEvent as a 3-state enum:
enum class TransposedInstrumentKeyExceededAccidentalLimit {
EXCEEDED_FLAT_LIMIT = -1,
HAS_NOT_EXCEEDED_LIMIT = 0,
EXCEEDED_SHARP_LIMIT = 1
};
and am storing those when writing or reading from xml if they are set. Turns out that makes the code easier. Anyone may feel free to suggest a shorter name for "_transposedInstrumentKeyExceededAccidentalLimit" in the code.
I have it working for basic case of if I insert a bunch of keysigs in concert pitch and then toggle concert pitch off, I can see the key sigs properly obey the transition:
And then upon re-enabling concert pitch, I can see that each key signature returns to their original spelling:
So now I need to handle the various other times when transposing occurs (I haven't handled dropping keysigs while already in concert pitch mode or excerpts...but I'll handle that next) and write some tests... One question is should I go ahead and handle the situation when user adds Instrument Change staff text before a key signature? MuseScore currently doesn't seem to transpose the later key signatures, but I would think it should... Should I go ahead and implement that?
here's the current state of my basic changes: https://github.com/musescore/MuseScore/compare/master...ericfont:22687-…
When investigating how to handle when transposition changes from one key to another (for instance when the staff was previous set to one transposing instrument but then user changes that staff to another transposing instrument) I've been careful to break the operation of transposing the key signatures into two discreet steps:
The previous code for Score::transpositionChanged() had that as one combined step using the difference between the two transpositions as the interval. But that caused problems because for some cases the flags were set but needed to be unset.
I'm bringing this up now because the rest of Score::transpositionChanged() uses the combined difference interval for transposing notes and chord symbols. Now I don't know if for this PR I should take care of handling all the proper conversions for that to. Maybe I'll make a seperate issue request to have chord symbols also obey this new behavior of the keysignature...
Seems this came up again in https://musescore.org/en/node/225816