DigiScore / neoscore

A python library for notating music in a graphics-first paradigm
https://neoscore.org
BSD 3-Clause "New" or "Revised" License
108 stars 9 forks source link

`Chordrest` accidental layout is sub-par #32

Open ajyoon opened 2 years ago

ajyoon commented 2 years ago

Chordrest currently uses a very basic algorithm for laying out accidentals - it works through the accidentals from top to bottom, and if a bounding rect collision with the previous accidental is found (and that previous accidental is not shifted), it shifts the accidental to the left edge of the previous accidental's bounding rect. This works for many cases, but causes bad results for some common cases like these:

accidentals

Users can currently work around this by manually adjusting X positions of accidental glyphs with something like:

staff = Staff(ORIGIN, None, Mm(100))
Clef(ZERO, staff, "treble")
c = Chordrest(Mm(10), staff, ["gb", "an", "bb"], (1, 8))
c.accidentals[0].x -= ...

But this is very tedious and modifications don't survive chord rebuilds (which can be triggered by things like beaming). We should implement an accidental layout algorithm according to standard notation conventions. From what I've seen the rules are pretty complicated, so it may be an involved fix. A proper solution should also take into account glyph cutouts provided by SMuFL for this purpose.

ajyoon commented 2 years ago

We might want to consider checking out Musescore's accidental layout algorithm since they also use SMuFL

ajyoon commented 2 years ago

See MuseScore's implementation here - https://github.com/musescore/MuseScore/blob/751c0866abbceb011d2747d9992a584f0675a562/src/engraving/layout/layoutchords.cpp#L621 - it is indeed pretty complicated

ajyoon commented 2 years ago

I've improved the situation considerably with fdbd1c3d461299311 but it still leaves much to be desired. Issue summary has been updated accordingly

Xavman42 commented 1 year ago

image Just noticed that naturals are too close to noteheads in chordrests - I know it doesn't look like it, but this actually is a chordrest with a special table: table = NoteheadTable(double_whole='noteheadBlack', whole='noteheadBlack', half='noteheadBlack', short='noteheadBlack')