Spanners in selection
This week I have mostly been focused on spanners and how they interact with selections.
Progress on filters
- Key Signatures
- Time Signatures (?)
- Chord Symbols
- Figured Bass
- Fret Diagrams
- Pedal Lines
- Other Lines
- Bagpipe Embellishments
Spanners are elements in a score that span over multiple elements. The difference from other elements is, that they are not found in a segment, or in notes. They are kept in a spanner map. The spanner map allows operations such as findOverlapping, or findContained to be called, that return all spanners that are in the tick range provided.
Ties are an exception to this, there are the only spanners that are not included in the map, they can be accessed in individual notes at tieFor() and tieBack().
I have managed to solve the issue of slur filtering. The problem was in the fact that the writeSegments function used the score’s spannerMap. One solution was to add the filter as a parameter to the function, but this would cause the filters to be applied in two different places. Instead, I have changed the writeSegments, function to take a spannerMap parameter. When we want to call the function normally, we will call it with score->spanner(). To filter the spanners, we create a copy of the map. With a copy we are free to remove any spanners without affecting the score spanner map.
While adding tests for spanner filters, I have noticed the following behavior. No matter which CR is removed, if the CR is in range of a spanner, the spanner is removed as well. This looked like a bug to me, since most of the time, we actually want the spanner to stay. Take a crescendo for example. Removing any CR in it’s range should not remove it. I have filed an issue here: Removing any range in ottava’s range removes ottava
I have changed the behavior in PR #993. Right now spanners are removed if a chordrest at start segment is also removed. The exception are spanners, that are removed if start or end segments are removed.
I have added test cases to cover the new behavior. This includes tests for removal of CR at start, middle and end segment of slur and spanner.
After rebasing and pushing the selection-window branch, I noticed that the travis build has failed on 1 test case. The test case involved removing a measure, and seeing if spanners are correctly moved back the right amount of ticks. The bug was uncovered by my change to the loop in writeSegments. I used upperBound to find the last spanner that should be written in the segment. It turns out that the undoInsertTick method that is used to move spanners, does not update the keys in the map. So even though the start and end tick of the spanner where updated, a spanner would not be found when searching using the key. I have looked at other usages of the spannerMap, but I don’t think the map was used in that way. Most calls are to findContained or findOverlapping, which use an IntervalTree.
I have created a function fixKeys, which loops through the map, clears, and adds all spanners with the correct ticks. There is one drawback though, the function has to be called when undo is performed. The only solution I have thought of so far, is to create a new undo action that will just call spannerMap.fixKeys(). This undo would have to be added twice, before and after changing of the spanner ticks.
Spanners in filters
Some spanners are really just extensions of other symbols. An example would be hairpins. I do not see a point of creating a separate filter just for them. They should be treated as dynamics, so they should be filtered along with other dynamic symbols. Same goes for trill lines, which are also articulations.
Made a few test scores for ottava, pedals, and other spanners. I will add new tests based on these scores once the spanner PR’s will be merged.
There has been a bug that I have introduced with the selections. When range selecting notes in another staff, an assert has failed. It turned out to be an simple mistake of not correctly assigning the start and end staff when appending to a selection. The issue has been resolved now, along with a minor change in range selection extending.
Before, a selection had an “active segment” that was moved when extending a selection. It might be better to explain this with an example.
Let’s imagine a score with 8 notes.
We create a range selection from 4th to 7th note. Our active segment is the 7th note. If we extend the selection to the 8th or 6th note, the start segment stays as is, we move the active segment. If we extend to the 2nd note, the selection will be inverted, and the 2nd and 3rd note would be in the selection.
The new behavior is to always add to a selection. In the previous example, the new selection would span from the 2nd to 8th note. Different applications solve the range extending differently. You can try it for yourself in your favorite browser/editor.
I have made a PR with the fix to return to the previous behavior in case most users will decide that the previous behavior was better. If so, you can post suggestion in the MuseScore forum which can be found here.
Tasks in the upcoming week
I have an exam on Tuesday, so I don’t think I will have much more work done before that time. In the following days, I will add the remaining filters for spanners. Hopefully I can focus on voice filtering during the rest of the week.