Fixing some quirky slur rendering #24939
-
I'd like to start getting familiar with the MuseScore codebase, but I'm not a real coder, and I was hoping I could get a little help. I've contrived a silly project and maybe someone can help direct me. Here is a largely fictional bug report about slur rendering:
Now. This is actually true: However, it's hard to imagine a usage case for either. This is an excuse to dig into the codebase. I am not a coder, but I am a mathematician, and I know a fair amount about splines. I'd like to find where the slur shape and rendering are defined from the control points and see how you've implemented it. I generally expect rendering to be highly optimized, but there's always a chance I can clean up the behavior and make things a bit more efficient... and easily extensible to multi-segment splines, allowing more control over the curvature (especially for long slurs), and allow more complex slur shapes. Can someone help me get oriented? |
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 9 replies
-
I'm expecting the slur render to be deliberate: a simplification that is not exposed under any normal usage, and that avoids the extra calculation needed to (correctly) define the slur as a closed planar figure, and fill it as such. However, the slurs crapping out when the baseline goes vertical makes me suspect the spline is defined in an unfortunate and costly way, and that we might be able to fix the latter, with enough savings left over to correct the former... and still come out ahead. |
Beta Was this translation helpful? Give feedback.
-
Ok well in the meantime… |
Beta Was this translation helpful? Give feedback.
-
Ok my first question is are you committed to all of these atan() and Rotate() calls? All rotations can be performed by direct transformation of the x, y components, with a simple component wise (parallel) MADD, without ever calculating the angle. This will also bulletproof you against any unfortunate behavior if the user moves the control points in a way you don’t expect (atan() can’t tell left from right). |
Beta Was this translation helpful? Give feedback.
-
@RhinoHaggis the slur is defined by a Bezier curve. To be more precise, an outer bezier curve, and an inner bezier curve. The two curves share the same start and end points, but split in the middle as the inner curve has a lower shoulder height. The painter then fills the region between the two curves and that gives us the tapered shape (thick in the middle, thin at the endpoints). This isn't perfect, and it certainly won't work for arbitrary shapes if you go crazy with the shoulder points, but it's also very simple and efficient and it works for all of our reasonable use cases, so I don't think it's worth overengineering that aspect. You are almost certainly correct about the arc tangent though, which obviously breaks down at 90 degrees. It's used in a bunch of different places around slurs. If you want to try improving the math there, you're welcome to! Just be sure that the end result is the same (when you'll make a PR, the vtests will tell us). Also, I have some big changes in the pipeline to improve the slur anti-collision algorithm ( Also, please log an actual issue about that disappearing slur. |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
I'm not planning on touching it, but what is the purpose of the reference orientation slur? There's no need to do this for the spline itself.. .is there a coding or UI reason? It seems to me that if there is one thing that distinguishes Musescore from any other Bezier objects I've used, it's the way the slurs jump about when the app is unhappy with the placement. Strange behavior measuring from offsets. If this invisible "wish you were here" position is the reference frame, it seems like it makes editing a real chore in addition to eating up a ton of code space. Is there some reason the curves can't just live in whatever position they're set? |
Beta Was this translation helpful? Give feedback.
-
Just to be clear, the problem with atan() is not at 90 degrees. As long as +- inf register, it will correctly return +-pi/2. What it can’t do, is detect or return a left-facing angle. So while you might not think the user should make a left-facing slur, at present there is no choice: once pp2 crosses to the left of pp1 the whole implementation is broken by these angle calls, which is a shame because left alone in their natural representation bezier curvez are virtually unbreakable. Certainly the test for broken slur in the code is not a sufficiently strict condition to break a Bézier curve |
Beta Was this translation helpful? Give feedback.
@RhinoHaggis the slur is defined by a Bezier curve. To be more precise, an outer bezier curve, and an inner bezier curve. The two curves share the same start and end points, but split in the middle as the inner curve has a lower shoulder height. The painter then fills the region between the two curves and that gives us the tapered shape (thick in the middle, thin at the endpoints). This isn't perfect, and it certainly won't work for arbitrary shapes if you go crazy with the shoulder points, but it's also very simple and efficient and it works for all of our reasonable use cases, so I don't think it's worth overengineering that aspect.
You are almost certainly correct about the arc tangent…