Skip to content

Correct automatic beam subdivision, even under tuplets

Jason Yip requested to merge ljyip/lilypond:merge_request into master

As my Google Summer of Code project of summer 2023, these patches try to fix defects of the current automatic beam subdivision algorithm in many corner cases and complex beaming patterns. Some of these corner cases include unusual baseMoments and beatStructures, but most notably under tuplets and layers of nested tuplets. The current algorithm relies on the value of baseMoment and beatStructure to tune parameters. However, these values are intended for Auto_beam_engraver to decide whether to start or end beams, not necessarily for subdivision. As a result, parameter tuning of automatic subdivision is heavily limited with the current algorithm. See https://gitlab.com/lilypond/lilypond/uploads/9f968b929162a0d70d212ac2e99ec841/beam-subdivisions--notes-for-reimplementation--urs-liska.pdf

Algorithm details

With these patches, the automatic subdivision algorithm no longer relies on those values and may be tuned using two new Moment properties: minimumBeamSubdivisionInterval and maximumBeamSubdivisionInterval; and boolean respectIncompleteBeams. minimumBeamSubdivisionInterval has default value of 0, maximumBeamSubdivisionInterval has default value of infinity. If we want to subdivide a beam of 8 consecutive 32nd notes, they would be subdivided by 8th and 16th note levels, under the default values, meaning that the beamlets between the 1st and 2nd note, and 6th and 7th note are chipped by 1, and the beamlets between 4th and 5th note are chipped by 2. If maximumBeamSubdivisionInterval is set to 1/16, the 8th note level would not be subdivided (beamlets between 4th and 5th note are chipped by just 1). If minimumBeamSubdivisionInterval is set to 1/8, the only beamlet chipping occurs between 4th and 5th note by 2. All examples in the PDF linked above, which show the flaws of the current beam subdivision algorithm versus a correct rendering, can be correctly reproduced with these patches. Another flaw of the current beam subdivision algorithm limits the amount of beams removed from a subdividision when the remaining beam length cannot complete the metric value of the subdivision. These patches remove this special rule, unless property respectIncompleteBeams, whose purpose is to preserve compatibility with this special rule, is set.

As for the C++ source files edited, beaming-pattern.cc has been reworked. One notable change is that the start moments of stems provided to Beaming_pattern are absolute, rather than relative to the start of the current measure. This change is needed to allow the possibility of the first stem being part of a tuplet span that started in an earlier measure.

Engravers Beam_engraver, and Auto_beam_engraver now inherit from a new class Template_engraver_for_beams that I made to reduce code redundancy between the two. Tuplet_engraver has been heavily modified as these patches transform Tuplet_description into a smob with which the aforementioned engravers can interact (through internal context property currentTupletDescription).

Stem_engraver no longer sets property tuplet-start of Stem grobs as my patches no longer depend on it.

Changes other than code

This patch introudces a couple of regression tests to test beam subdivision and its quirks. 2 regression tests--beam-subdivision and beam-subdivide-quarter-notes--, without any modifications, fail with my patches due to their reliance of baseMoment as a limit on beam subdivision. This patch modifies those 2 regression tests by replacing baseMoment with minimumBeamSubdivideInterval (I went ahead and expanded beam-subdivision to additionally test my 2 new properties.

One convert-ly rule has been added to warn people using subdividingBeams; Writing a convert-ly rule that automatically sets minimumBeamSubdivisionInterval to the value of baseMoment seems unfeasible, if not impossible.

The Rhythm section 1.2.4 of the Notation documentation has been edited.

Edited by Jason Yip

Merge request reports