Am Fr., 22. Okt. 2021 um 15:45 Uhr schrieb Thomas Morley
<[email protected]>:
>
> Am Fr., 22. Okt. 2021 um 14:48 Uhr schrieb Thomas Morley
> <[email protected]>:
> >
> > Am Fr., 22. Okt. 2021 um 14:33 Uhr schrieb Dimitris Marinakis
> > <[email protected]>:
> > >
> > > I'd really like to test this version but I'm getting an error :
> > > In procedure ly:grob-object in expression (ly:grob-object stem (quote
> > > glissandi) ...):
> > > Wrong number of arguments to #<primitive-procedure ly:grob-object>
> >
> > You need a more recent version, 2.23.3 works here, because I make
> > frequent use of that new feature, thanks Jean.
> >
> > I had not updated the \version
> >
> > Sorry for that,
> > Harm
>
> Next iteration attached, it solves the most annoying issue with
> wrongly placed Script.
>
> Thanks Jean !
>
> Now tackling issue 03 (see resulting pdf) with cross-staff glissando-stems ...
>
> Cheers,
> Harm
Attachment missing...
\version "2.23.3"
#(define glissando-stems
(lambda (grob)
"If @code{NoteColumn.glissando-skip} is set to this procedure, a glissando
skips this @code{NoteColumn} grob and the @code{details.glissando-stems}
property is set @code{#t}."
(ly:grob-set-nested-property! grob '(details glissando-stems) #t)
#t))
#(define-public (line-spanner-gradient stencil grob)
"Takes a line-spanner @var{grob} grob and returns the gradient of the line
looking at @var{stencil}."
(if (and (grob::has-interface grob 'line-interface)
(grob::has-interface grob 'line-spanner-interface))
(let* ((left-bound-info (ly:grob-property grob 'left-bound-info))
(Y-left (assoc-get 'Y left-bound-info))
(X-left (assoc-get 'X left-bound-info))
(left-padding (assoc-get 'padding left-bound-info))
(right-bound-info (ly:grob-property grob 'right-bound-info))
(Y-right (assoc-get 'Y right-bound-info))
(X-right (assoc-get 'X right-bound-info))
(right-padding (assoc-get 'padding right-bound-info))
(x-ext (ly:stencil-extent stencil X))
(y-ext (ly:stencil-extent stencil Y))
(line-thickness (ly:staff-symbol-line-thickness grob))
(grob-thickness (ly:grob-property grob 'thickness #f))
(thick (or grob-thickness line-thickness)))
(/
(- (interval-length y-ext) thick)
(- (interval-length x-ext) thick)
(if (>= (- Y-right Y-left) 0) 1 -1)))
(begin
(ly:warning "Grob ~a is not a line-spanner, returning zero" grob)
0)))
#(define-public (line-spanner-stem-intersection-points refp note-columns grob)
"Takes the @code{Stem} grobs of @var{note-columns} and calculates the
intersection points of @var{grob}, supposed to be a line-spanner, with Y-axis of
the @code{Stem} grobs."
(let* ((grob-relative-coord (ly:grob-relative-coordinate grob refp X))
;; TODO use thicknesses at all? See next TODO
;(line-thickness (ly:staff-symbol-line-thickness grob))
;(half-line-thick (/ line-thickness 2))
;(grob-thickness (ly:grob-property grob 'thickness #f))
;(thick (or grob-thickness line-thickness))
;; stencil-stuff
;; NB ly:line-spanner::print adds half-line-thick to both sides of
;; the line
(stil (ly:line-spanner::print grob))
(stil-x-ext (ly:stencil-extent stil X))
(stil-y-ext (ly:stencil-extent stil Y))
;; left-bound-stuff
(left-bound-info (ly:grob-property grob 'left-bound-info))
(X-left (assoc-get 'X left-bound-info))
(gradient (line-spanner-gradient stil grob))
;; stem-stuff
(stems
(filter-map
(lambda (nc) (ly:grob-object nc 'stem #f))
note-columns))
(stems-x-coords
(map
(lambda (stem) (ly:grob-relative-coordinate stem refp X))
stems)))
;; resulting intersection-points
(map
(lambda (stem-x-coord)
(cons
;; X-coord
(- stem-x-coord
grob-relative-coord)
;; Y-coord
(+
;; Calculate and add the Y-value of the line-spanner at
;; the given stem
;; NB The result is relative to zero-staff-line.
(*
gradient
(- stem-x-coord
grob-relative-coord
(car stil-x-ext)))
;; Thus add the relevant value of line-spanner's y-extent:
;; If the line-spanner points down, use top y-extent,
;; if it points up use bottom y-extent.
;; TODO corrected by half thick? I.e.:
;(if (negative? gradient)
; (- (cdr stil-y-ext) (/ thick 2))
; (+ (car stil-y-ext) (/ thick 2)))
(if (negative? gradient)
(cdr stil-y-ext)
(car stil-y-ext)))))
stems-x-coords)))
#(define adjust-stems-beams
(lambda (grob)
"Moves @code{NoteHead} grobs passed by one or more glissando lines to the most
distant glissando line, viewed from the @code{Stem} side.
The @code{Stem.details} subproperties for @code{lengths} (for unbeamed
@code{Stem} grobs) and @code{beamed-minimum-free-lengths} (for beamed
@code{Stem} grobs) are adjusted to avoid most collisions with glissando lines."
(let* ((raw-stems
(cond ((grob::has-interface grob 'stem-interface) (list grob))
((grob::has-interface grob 'beam-interface)
(let ((stems-array (ly:grob-object grob 'stems)))
(if (ly:grob-array? stems-array)
(ly:grob-array->list stems-array)
'())))
(else '())))
(stems
(filter
(lambda (stem)
(ly:grob-object stem 'glissandi #f))
raw-stems)))
;; Proceed only with stems with a passing Glissando, stop if there
;; arent' any.
(if (pair? stems)
(let* (;; We search all Stems, because sometimes a Glissando starting
;; at a beamed Stem may be overlooked.
;; Delete duplicate findings.
(glissandi
(delete-duplicates
(append-map
(lambda (stem)
(let ((glissandi-array
(ly:grob-object stem 'glissandi #f)))
(if glissandi-array
(ly:grob-array->list glissandi-array)
'())))
stems)))
;; Get the bounds of a Glissando. We may not wish to move these
;; NoteHeads, see below
(gliss-bounds
(append-map
(lambda (gliss)
(list
(ly:spanner-bound gliss LEFT)
(ly:spanner-bound gliss RIGHT)))
glissandi))
;; All NoteColumns: start, end and in-between
(parent-ncs
(map
(lambda (stem) (ly:grob-parent stem X))
stems))
(nhds-lists
(map
(lambda (nc)
(let* ((nhds-array (ly:grob-object nc 'note-heads #f)))
(if nhds-array
(ly:grob-array->list nhds-array)
'())))
parent-ncs))
;; All NoteHeads with their 'staff-position
;; NB It is a nested list with sublists of note-heads per
;; note-column
(nhds-staff-pos-lists
(map
(lambda (nhds)
(map
(lambda (nhd)
(cons nhd (ly:grob-property nhd 'staff-position)))
nhds))
nhds-lists))
(stem-gliss-intersection-points
(map
(lambda (gliss)
(line-spanner-stem-intersection-points
(ly:grob-system grob)
parent-ncs
gliss))
glissandi))
;; We are interested in the y-coords of each intersection point
;; y-coords per Glissando
(stem-gliss-y-list
(map
(lambda (l) (map cdr l))
stem-gliss-intersection-points))
;; y-coords per Stem
(y-list
(if (pair? stem-gliss-y-list)
(apply zip stem-gliss-y-list)
'()))
(max-ys
(if (pair? y-list)
(map (lambda (x) (apply max x)) y-list)
'()))
(min-ys
(if (pair? y-list)
(map (lambda (x) (apply min x)) y-list)
'()))
(max-diffs
(map
(lambda (y1 y2) (- y1 y2))
max-ys
min-ys))
(dir (ly:grob-property grob 'direction))
(relevant-y-vals
(if (positive? dir)
min-ys
max-ys)))
#t
;; move note-heads to glissando line
(for-each
(lambda (nhds-staff-pos-list val)
(for-each
(lambda (nhd-staff-pos)
(if (and (pair? nhd-staff-pos)
(not (member (car nhd-staff-pos) gliss-bounds)))
(begin
(ly:grob-translate-axis!
(car nhd-staff-pos)
;; If NoteHead.Y-offset is set zero before, its
;; sufficient to use `val', otherwise take
;; half of staff-position into account
val
;(+ (/ (cdr nhd-staff-pos) -2) val)
Y))))
nhds-staff-pos-list))
nhds-staff-pos-lists
relevant-y-vals)
;; adjust Stem.details.lengths by the current distance between
;; top and bottom glissandi lines, will be zero if only one
;; glissando is present
(if (grob::has-interface grob 'stem-interface)
(ly:grob-set-nested-property! grob
'(details lengths)
(map
(lambda (x) (+ (car max-diffs) x))
(assoc-get 'lengths (ly:grob-property grob 'details)))))
;; adjust beamed-minimum-free-lengths
(if (grob::has-interface grob 'beam-interface)
(for-each
(lambda (stem val)
(let ((details (ly:grob-property stem 'details)))
(ly:grob-set-nested-property! stem
'(details beamed-minimum-free-lengths)
(map
(lambda (x) (+ x val))
(assoc-get
'beamed-minimum-free-lengths details)))))
stems
max-diffs)))))))
#(define stem::adjust-for-glissando
(lambda (grob)
"Moves @code{NoteHead} grobs of an unbeamed @code{Stem} grob to a glissando
line. This procedure is supposed to be the value of the @code{Stem.stencil}
property."
;; If Beam is present simply return default stencil.
;; Else move NoteHead grobs to sit upon the most distant glissando line
;; (viewed from Stem).
;; If only one glissando line is present, take that line.
;; Stem.details.lengths for multiple glissando lines is adjusted.
;; Finally return (new) default stencil.
(let* ((beam (ly:grob-object grob 'beam)))
(if (ly:grob? beam)
(ly:stem::print grob)
(begin
;; Move NoteHeads to glissando line
(adjust-stems-beams grob)
(ly:grob-set-property! grob 'positioning-done #t)
;; return default stencil
(ly:stem::print grob))))))
#(define beam::adjust-for-glissando
(lambda (grob)
"Moves @code{NoteHead} grobs of a beamed @code{Stem} grob to a glissando line.
Respects user overrides for @code{Beam.details.glissando-positions}, let them
replace the default @code{(beam::place-broken-parts-individually grob)}.
This procedure is supposed to be the value of the @code{Beam.positions}
property."
;; Overriding Beam.positions with this procedure needs to be applied
;; before the Beam is started.
;; Thus we put it as general setting in the engraver, see below.
(let* ((positions (assoc-get 'positions (ly:grob-basic-properties grob))))
;; Move NoteHeads to glissando line
;; Stem.details.beamed-minimum-free-lengths is adjusted for multiple
;; glissando lines
(adjust-stems-beams grob)
;; Respect user-override for Beam.positions
(if (procedure? positions)
(positions grob)
;(beam::place-broken-parts-individually grob)
positions))))
#(define Glissando_pointers_engraver
(lambda (context)
"Adds pointers from @code{Glissando} grobs to @code{NoteColumn} grobs and
from @code{Stem} grobs to passing @code{Glissando} grobs.
Sets @code{Beam.positions} to @code{beam::adjust-for-glissando}."
(let ((start-glissando #f)
(note-columns '())
(glissandi '())
(passed-stems '()))
(make-engraver
(acknowledgers
((beam-interface engraver grob source-engraver)
(ly:grob-set-property! grob 'positions beam::adjust-for-glissando))
((glissando-interface engraver grob source-engraver)
(set! start-glissando #t)
(set! glissandi (cons grob glissandi)))
((note-column-interface engraver grob source-engraver)
(let* ((details (ly:grob-property grob 'details))
(glissando-stems? (assoc-get 'glissando-stems details #f)))
(if (and start-glissando glissando-stems?)
(let* ((nhds (ly:grob-object grob 'note-heads))
(nhds-list
(if (ly:grob-array? nhds)
(ly:grob-array->list nhds)
'()))
(stem (ly:grob-object grob 'stem)))
(set! passed-stems (cons stem passed-stems))
(set! note-columns (cons grob note-columns)))))))
(end-acknowledgers
((glissando-interface engraver grob source-engraver)
(let* ((left-bound (ly:spanner-bound grob LEFT))
(right-bound (ly:spanner-bound grob RIGHT))
(left-nc
(if (grob::has-interface
left-bound
'note-head-interface)
(list (ly:grob-parent left-bound X))
'()))
(right-nc
;; check for right-bound being a grob, for glissandi
;; between unequal amounts of notes it may be not
(if (and (ly:grob? right-bound)
(grob::has-interface
right-bound
'note-head-interface))
(list (ly:grob-parent right-bound X))
'())))
;; put NoteColumn grobs into a 'note-columns-pointer of
;; Glissando, technically not needed for stemmed glissandi, but
;; nice to have
(for-each
(lambda (nc)
(ly:pointer-group-interface::add-grob
grob 'note-columns nc))
(append left-nc (reverse note-columns) right-nc))
;; add a pointer named 'glissandi to every passed Stem,
;; containing the Glissando grobs passing this Stem
;; exclude those glissandi without proper bounds, see above
(for-each
(lambda (gliss)
(for-each
(lambda (stem)
(if (and (ly:grob? left-bound) (ly:grob? right-bound))
(ly:pointer-group-interface::add-grob
stem 'glissandi gliss)))
passed-stems))
glissandi)
(set! start-glissando #f)
(set! passed-stems '())
(set! glissandi
(remove (lambda (gliss) (equal? grob gliss)) glissandi))
(if (null? glissandi)
(set! note-columns '())))))))))
#(ly:register-translator
Glissando_pointers_engraver 'Glissando_pointers_engraver
'((grobs-created . ())
(events-accepted . ())
(properties-read . ())
(properties-written . ())
(description . "\
Engraver to set pointers from @code{Glissando} grobs to @code{NoteColumns} grobs
and from @code{Stem} grobs to @code{Glissando} grobs.
Setting @code{Beam.positions} to @code{beam::adjust-for-glissando}")))
glissandoStemsOn =
#(define-music-function (mus) (ly:music?)
"@var{mus} is supposed to be a @code{note-event} or @code{event-chord} with
a starting glissando.
Applies certain overrides to get stems at the glissando."
;; We do it as a music-function to gain the possibility to set it before `mus'.
;; This way it is more in line with the usage of commands like
;; \arpeggioArrowUp etc
#{
$mus
\temporary \override NoteColumn.glissando-skip = #glissando-stems
\temporary \override NoteHead.stem-attachment = #'(0 . 0)
%% TODO keep this one?
%\temporary \override Stem.no-stem-extend = ##t
\temporary \override Stem.stencil = #stem::adjust-for-glissando
\temporary \override Stem.cross-staff = ##t
\temporary \override NoteHead.no-ledgers = ##t
\temporary \override Accidental.stencil = ##f
%% Don't confuse LilyPond with suspended NoteHeads
\temporary \override NoteHead.X-offset = ##f
% \temporary \override NoteHead.color = #grey
% \temporary \override NoteHead.layer = -1000
%% Make NoteHead transparent, omitting stencil or using point-stencil
%% will sometimes result in bad horizontal spacing
\temporary \override NoteHead.transparent = ##t
\temporary \override NoteHead.Y-offset = 0
#})
glissandoStemsOff = {
\revert NoteColumn.glissando-skip
\revert NoteColumn.details.glissando-stems
\revert NoteHead.stem-attachment
\revert NoteHead.X-offset
\revert Stem.cross-staff
% \revert Stem.no-stem-extend
\revert Stem.stencil
\revert NoteHead.no-ledgers
\revert Accidental.stencil
\revert NoteHead.color
\revert NoteHead.layer
\revert NoteHead.transparent
\revert NoteHead.Y-offset
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% USAGE
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(ly:set-option 'debug-skylines #t)
\paper {
indent = 0
ragged-right = ##t
}
\layout {
\context {
\Voice
\override Glissando.breakable = ##t
\consists "Glissando_pointers_engraver"
}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ISSUES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\markup
\rounded-box \rounded-box
\fill-line { \fontsize #3 \bold "Issues" \null }
%% Script
\markup
\pad-around #2
\fill-line {
"01 Script-grobs etc may be placed wrongly (with workaround)"
\bold "solved"
}
%{
correctScripts =
#(define-music-function (val)(number?)
#{
\once \override Script.quantize-position = ##f
\once \override Script.staff-padding = $val
\once \override Fingering.staff-padding = $val
\once \override StringNumber.staff-padding = $val
\once \override StrokeFinger.staff-padding = $val
#})
<<
{
\glissandoStemsOn
b''4\glissando
g''-.-!->
\glissandoStemsOff
b'''2
}
{
\glissandoStemsOn
b''4\glissando
\correctScripts 3.6
g''-.-!->
\glissandoStemsOff b'''2
}
>>
%}
% {
%% Beam.direction
\markup
\column {
\draw-hline
\pad-around #2
"02 Direction of Beams differ with stemmed glissando. Work around with
\\voiceXxx, \\stemUp ..."
}
<<
\relative c'' {
c16b a g
f e d c
b a g f
e d c b
}
\relative c'' {
\glissandoStemsOn
c16\glissando b a g
f e d c
b a g f
e d c \glissandoStemsOff b
}
>>
%}
% {
%% cross-staff glissando-stems don't work
\markup
\column {
\draw-hline
\pad-around #2 "03 cross-staff glissando-stems doesn't work"
}
\new PianoStaff
<<
\new Staff = "top"
\relative e'' {
\voiceOne
\glissandoStemsOn
c16\glissando
b a g f e d c
\change Staff = "bottom"
b a g f e d c b
a g f e d c b a
\glissandoStemsOff
g2
}
\new Staff = "bottom" { \clef "bass" s1*2 }
>>
%}
%%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%%
% {
\markup
\rounded-box \rounded-box
\fill-line { \fontsize #3 \bold "Examples" \null }
\markup \pad-around #2 "Multiple Glissandi, chords, Beam over Rest, line breaks"
\transpose c c' {
%\voiceTwo
\glissandoStemsOn
<ces e'>4\glissando
<ees c'''' >128*32[-!-.->
f''''16*2-!-.->
r
<cis'' e''>8*2]-!-.->
\glissandoStemsOff
\glissandoStemsOn
<e'' gis''>2-!
\glissando
<e'' g''>4
\glissandoStemsOff
q
\break
\glissandoStemsOn
<ces' e'>1\glissando
<c'' e''>4 <c'' e''''>8[-!-.-> <c'' e''''>]-!-.-> q4 q
\break
<c'' e''>8
r
<cis'' e''>4
\glissandoStemsOff
\set glissandoMap = #'((0 . 0) (0 . 1))
\glissandoStemsOn
e''2\glissando
gis''8*8
\glissandoStemsOff
<e' gis''>1
}
%}
% {
\markup
\pad-around #2
"Restricted to current Staff, user override for Beam.positions is respected"
<<
\new Staff \transpose c c {
\voiceOne
\glissandoStemsOn
b8\glissando
c'16 d
\once \override Beam.positions = #'(5 . 10)
e' f' g' c'16 d' e' f' g'
c'16 d'
\glissandoStemsOff
b'8
}
\new Staff \fixed c'' {
d'8-> g-> c-> f,-> bes,,-> ees,,->r4
}
>>
%}
% {
\markup \pad-around #2 "Start/end inside Beam"
{
g16[
ais
\glissandoStemsOn
b\glissando
b
d'' e bis bes \glissandoStemsOff a'
b']
}
%}
% {
\markup \pad-around #2 "Steep Glissandi"
{
\set beamExceptions = #'()
\voiceTwo
\glissandoStemsOn d'''8\glissando[ g'' c'' f'] \glissandoStemsOff bes2
\glissandoStemsOn d'''8 \glissando g'' c'' f' \glissandoStemsOff bes2
}
%}