Positioning tuplet numbers close to kneed beams
If you want to move tuplet numbers close to kneed beams on a single staff or between staves, this workaround automates the process. It ignores tuplets on ordinary beams or with visible brackets.
Important: to work properly, this method must be used with manual beaming.
Two functions are provided. The first moves the number along the Y-axis, and is called like so: \override TupletNumber #'Y-offset = #kneed-beam. The second function centers the number horizontally: \override TupletNumber #'X-offset = #center-on-beam.
\version "2.24.0"
%% http://lsr.di.unimi.it/LSR/Item?id=646
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A function to position tuplet numbers next to kneed beams on a single
%% staff and between staves. Will ignore tuplets on ordinary beams and
%% with visible brackets.
%%
%% Usage: \override TupletNumber.Y-offset = #kneed-beam
%%
%% You must use manual beaming for this function to work properly.
%%
%% An additional function, called with a separate override (see below), will
%% horizontally center the tuplet number on the kneed beam.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(define (kneed-beam tuplet-number)
(let* ((tuplet-bracket (ly:grob-object tuplet-number 'bracket))
(first-note (ly:grob-parent tuplet-bracket X))
(first-stem (ly:grob-object first-note 'stem))
(beam (ly:grob-object first-stem 'beam)))
(if (and (ly:grob? beam) ; beam on first note?
(ly:grob-property beam 'knee) ; is it kneed?
(interval-empty? (ly:grob-property tuplet-bracket 'Y-extent))) ; visible bracket?
(let* ((stems (ly:grob-object beam 'stems))
(closest-stem (nearest tuplet-number stems))
(direction-first-stem (ly:grob-property first-stem 'direction))
(direction-closest-stem (ly:grob-property closest-stem 'direction))
(beaming-near-number (car (ly:grob-property closest-stem 'beaming)))
(beam-multiplier
(if (= direction-closest-stem UP)
(length (filter positive? beaming-near-number))
(length (filter negative? beaming-near-number))))
(beam-ends (ly:grob-property beam 'positions))
(mid-beam-Y (/ (+ (car beam-ends) (cdr beam-ends)) 2)) ; mid-beam Y-coordinate
(number-height (ly:grob::stencil-height tuplet-number))
;; inital value of Y-offset (will cause number to overlap beam slightly)
(correction
(- mid-beam-Y
(if (= direction-closest-stem UP)
(car number-height)
(cdr number-height))))
(beam-width (ly:grob-property beam 'beam-thickness))
(beam-gap (* 0.5 (ly:grob-property beam 'gap)))
(beam-padding 0.2)) ; change to move number closer or farther from beam
;; refinement of initial value of Y-offset
(cond
((= direction-first-stem direction-closest-stem DOWN)
(- correction
(* 0.5 beam-width)
beam-padding))
((= direction-first-stem direction-closest-stem UP)
(+ correction
(* 0.5 beam-width)
beam-padding))
((and (= direction-first-stem DOWN) (= direction-closest-stem UP))
(+ correction
(* beam-multiplier (+ beam-gap beam-width))
(* 0.5 beam-width)
beam-padding))
((and (= direction-first-stem UP) (= direction-closest-stem DOWN))
(- correction
(* beam-multiplier (+ beam-gap beam-width))
(* 0.5 beam-width)
beam-padding)))))))
%% find the stem closest to the tuplet-number
#(define (nearest tuplet-number stems)
(let* ((refp (ly:grob-system tuplet-number))
(X-coord (interval-center (ly:grob-extent tuplet-number refp X)))
(closest (ly:grob-array-ref stems 0)))
(let lp ((x 1))
(if (<= (abs (- X-coord
(ly:grob-relative-coordinate
(ly:grob-array-ref stems x) refp X)))
(abs (- X-coord
(ly:grob-relative-coordinate closest refp X))))
(set! closest (ly:grob-array-ref stems x)))
(if (< x (1- (ly:grob-array-length stems)))
(lp (1+ x))
closest))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% A function which horizontally centers a tuplet number on a kneed beam. May
%% be used in conjunction with the earlier function.
%%
%% Usage: \override TupletNumber.X-offset = #center-on-beam
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(define (center-on-beam tuplet-number)
(let* ((tuplet-bracket (ly:grob-object tuplet-number 'bracket))
(first-note (ly:grob-parent tuplet-bracket X))
(first-stem (ly:grob-object first-note 'stem))
(beam (ly:grob-object first-stem 'beam)))
(if (and (ly:grob? beam)
(ly:grob-property beam 'knee)
(interval-empty? (ly:grob-property tuplet-bracket 'Y-extent)))
(let* ((refp (ly:grob-system tuplet-number))
(number-X (interval-center (ly:grob-extent tuplet-number refp X)))
(beam-center-X (interval-center (ly:grob-extent beam refp X))))
(- beam-center-X number-X)))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% EXAMPLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
top = \change Staff = "1"
bottom = \change Staff = "2"
music = \relative c {
\override Beam.auto-knee-gap = #1
\tupletSpan 4
\tuplet 3/2 {
\bottom c8[ g' \top e']
c'[ e, \bottom g,]
\top e''[ \bottom c,, \top g'']
}
\tuplet 5/4 {
\bottom c,,16[ \top g'' e' \bottom g,, \top c']
}
}
\score {
\new PianoStaff <<
\new Staff = "1" {
s1^"Before:"
s1^"After:"
}
\new Staff = "2" {
\clef bass
\music
\bar "||"
\override TupletNumber.Y-offset = #kneed-beam
\override TupletNumber.X-offset = #center-on-beam
\music
}
>>
}