+
+(define-public (event-chord-reduce music)
+ "Reduces event chords in @var{music} to their first note event,
+retaining only the chord articulations. Returns the modified music."
+ (map-some-music
+ (lambda (m)
+ (and (music-is-of-type? m 'event-chord)
+ (let*-values (((notes arts) (partition
+ (lambda (mus)
+ (music-is-of-type? mus 'rhythmic-event))
+ (ly:music-property m 'elements)))
+ ((dur) (ly:music-property m 'duration))
+ ((full-arts) (append arts
+ (ly:music-property m 'articulations)))
+ ((first-note) (and (pair? notes) (car notes))))
+ (cond (first-note
+ (set! (ly:music-property first-note 'articulations)
+ full-arts)
+ first-note)
+ ((ly:duration? dur)
+ ;; A repeat chord. Produce an unpitched note.
+ (make-music 'NoteEvent
+ 'duration dur
+ 'articulations full-arts))
+ (else
+ (ly:music-error m (_ "Missing duration"))
+ (make-music 'NoteEvent
+ 'duration (ly:make-duration 2 0 0)
+ 'articulations full-arts))))))
+ music))
+
+
+(defmacro-public make-relative (variables reference music)
+ "The list of pitch or music variables in @var{variables} is used as
+a sequence for creating relativable music from @var{music}.
+
+When the constructed music is used outside of @code{\\relative}, it
+just reflects plugging in the @var{variables} into @var{music}.
+
+The action inside of @code{\\relative}, however, is determined by
+first relativizing the surrogate @var{reference} with the variables
+plugged in and then using the variables relativized as a side effect
+of relativizing @var{reference} for evaluating @var{music}.
+
+Since pitches don't have the object identity required for tracing the
+effect of the reference call, they are replaced @emph{only} for the
+purpose of evaluating @var{reference} with simple pitched note events.
+
+The surrogate @var{reference} expression has to be written with that
+in mind. In addition, it must @emph{not} contain @emph{copies} of
+music that is supposed to be relativized but rather the
+@emph{originals}. This @emph{includes} the pitch expressions. As a
+rule, inside of @code{#@{@dots{}#@}} variables must @emph{only} be
+introduced using @code{#}, never via the copying construct @code{$}.
+The reference expression will usually just be a sequential or chord
+expression naming all variables in sequence, implying that following
+music will be relativized according to the resulting pitch of the last
+or first variable, respectively.
+
+Since the usual purpose is to create more complex music from general
+arguments and since music expression parts must not occur more than
+once, one @emph{does} generally need to use copying operators in the
+@emph{replacement} expression @var{music} when using an argument more
+than once there. Using an argument more than once in @var{reference},
+in contrast, does not make sense.
+
+There is another fine point to mind: @var{music} must @emph{only}
+contain freshly constructed elements or copied constructs. This will
+be the case anyway for regular LilyPond code inside of
+@code{#@{@dots{}#@}}, but any other elements (apart from the
+@var{variables} themselves which are already copied) must be created
+or copied as well.
+
+The reason is that it is usually permitted to change music in-place as
+long as one does a @var{ly:music-deep-copy} on it, and such a copy of
+the whole resulting expression will @emph{not} be able to copy
+variables/values inside of closures where the information for
+relativization is being stored.
+"
+
+ ;; pitch and music generator might be stored instead in music
+ ;; properties, and it might make sense to create a music type of its
+ ;; own for this kind of construct rather than using
+ ;; RelativeOctaveMusic
+ (define ((make-relative::to-relative-callback variables music-call ref-call)
+ music pitch)
+ (let* ((ref-vars (map (lambda (v)
+ (if (ly:pitch? v)
+ (make-music 'NoteEvent 'pitch v)
+ (ly:music-deep-copy v)))
+ variables))
+ (after-pitch (ly:make-music-relative! (apply ref-call ref-vars) pitch))
+ (actual-vars (map (lambda (v r)
+ (if (ly:pitch? v)
+ (ly:music-property r 'pitch)
+ r))
+ variables ref-vars))
+ (rel-music (apply music-call actual-vars)))
+ (set! (ly:music-property music 'element) rel-music)
+ after-pitch))
+ `(make-music 'RelativeOctaveMusic
+ 'to-relative-callback
+ (,make-relative::to-relative-callback
+ (list ,@variables)
+ (lambda ,variables ,music)
+ (lambda ,variables ,reference))
+ 'element ,music))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; The following functions are all associated with the crossStaff
+;; function
+
+(define (close-enough? x y)
+ "Values are close enough to ignore the difference"
+ (< (abs (- x y)) 0.0001))
+
+(define (extent-combine extents)
+ "Combine a list of extents"
+ (if (pair? (cdr extents))
+ (interval-union (car extents) (extent-combine (cdr extents)))
+ (car extents)))
+
+(define ((stem-connectable? ref root) stem)
+ "Check if the stem is connectable to the root"
+ ;; The root is always connectable to itself
+ (or (eq? root stem)
+ (and
+ ;; Horizontal positions of the stems must be almost the same
+ (close-enough? (car (ly:grob-extent root ref X))
+ (car (ly:grob-extent stem ref X)))
+ ;; The stem must be in the direction away from the root's notehead
+ (positive? (* (ly:grob-property root 'direction)
+ (- (car (ly:grob-extent stem ref Y))
+ (car (ly:grob-extent root ref Y))))))))
+
+(define (stem-span-stencil span)
+ "Connect stems if we have at least one stem connectable to the root"
+ (let* ((system (ly:grob-system span))
+ (root (ly:grob-parent span X))
+ (stems (filter (stem-connectable? system root)
+ (ly:grob-object span 'stems))))
+ (if (<= 2 (length stems))
+ (let* ((yextents (map (lambda (st)
+ (ly:grob-extent st system Y)) stems))
+ (yextent (extent-combine yextents))
+ (layout (ly:grob-layout root))
+ (blot (ly:output-def-lookup layout 'blot-diameter)))
+ ;; Hide spanned stems
+ (for-each (lambda (st)
+ (set! (ly:grob-property st 'stencil) #f))
+ stems)
+ ;; Draw a nice looking stem with rounded corners
+ (ly:round-filled-box (ly:grob-extent root root X) yextent blot))
+ ;; Nothing to connect, don't draw the span
+ #f)))
+
+(define ((make-stem-span! stems trans) root)
+ "Create a stem span as a child of the cross-staff stem (the root)"
+ (let ((span (ly:engraver-make-grob trans 'Stem '())))
+ (ly:grob-set-parent! span X root)
+ (set! (ly:grob-object span 'stems) stems)
+ ;; Suppress positioning, the stem code is confused by this weird stem
+ (set! (ly:grob-property span 'X-offset) 0)
+ (set! (ly:grob-property span 'stencil) stem-span-stencil)))
+
+(define-public (cross-staff-connect stem)
+ "Set cross-staff property of the stem to this function to connect it to
+other stems automatically"
+ #t)
+
+(define (stem-is-root? stem)
+ "Check if automatic connecting of the stem was requested. Stems connected
+to cross-staff beams are cross-staff, but they should not be connected to
+other stems just because of that."
+ (eq? cross-staff-connect (ly:grob-property-data stem 'cross-staff)))
+
+(define (make-stem-spans! ctx stems trans)
+ "Create stem spans for cross-staff stems"
+ ;; Cannot do extensive checks here, just make sure there are at least
+ ;; two stems at this musical moment
+ (if (<= 2 (length stems))
+ (let ((roots (filter stem-is-root? stems)))
+ (for-each (make-stem-span! stems trans) roots))))
+
+(define-public (Span_stem_engraver ctx)
+ "Connect cross-staff stems to the stems above in the system"
+ (let ((stems '()))
+ (make-engraver
+ ;; Record all stems for the given moment
+ (acknowledgers
+ ((stem-interface trans grob source)
+ (set! stems (cons grob stems))))
+ ;; Process stems and reset the stem list to empty
+ ((process-acknowledged trans)
+ (make-stem-spans! ctx stems trans)
+ (set! stems '())))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; The following is used by the alterBroken function.
+
+(define ((value-for-spanner-piece arg) grob)
+ "Associate a piece of broken spanner @var{grob} with an element
+of list @var{arg}."
+ (let* ((orig (ly:grob-original grob))
+ (siblings (ly:spanner-broken-into orig)))
+
+ (define (helper sibs arg)
+ (if (null? arg)
+ arg
+ (if (eq? (car sibs) grob)
+ (car arg)
+ (helper (cdr sibs) (cdr arg)))))
+
+ (if (>= (length siblings) 2)
+ (helper siblings arg)
+ (car arg))))
+(export value-for-spanner-piece)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; The following are used by the \offset function
+
+(define (find-value-to-offset prop self alist)
+ "Return the first value of the property @var{prop} in the property
+alist @var{alist} -- after having found @var{self}. If @var{self} is
+not found, return the first value of @var{prop}."
+ (let ((segment (member (cons prop self) alist)))
+ (if (not segment)
+ (assoc-get prop alist)
+ (assoc-get prop (cdr segment)))))
+
+(define (offset-multiple-types arg offsets)
+ "Displace @var{arg} by @var{offsets} if @var{arg} is a number, a
+number pair, or a list of number pairs. If @var{offsets} is an empty
+list or if there is a type-mismatch, @var{arg} will be returned."
+ (cond
+ ((and (number? arg) (number? offsets))
+ (+ arg offsets))
+ ((and (number-pair? arg)
+ (or (number? offsets)
+ (number-pair? offsets)))
+ (coord-translate arg offsets))
+ ((and (number-pair-list? arg) (number-pair-list? offsets))
+ (map
+ (lambda (x y) (coord-translate x y))
+ arg offsets))
+ (else arg)))
+
+(define-public (offsetter property offsets)
+ "Apply @var{offsets} to the default values of @var{property} of @var{grob}.
+Offsets are restricted to immutable properties and values of type @code{number},
+@code{number-pair}, or @code{number-pair-list}."
+ (define (self grob)
+ (let* ((immutable (ly:grob-basic-properties grob))
+ ; We need to search the basic-properties alist for our property to
+ ; obtain values to offset. Our search is complicated by the fact that
+ ; calling the music function `offset' as an override conses a pair to
+ ; the head of the alist. This pair must be discounted. The closure it
+ ; contains is named `self' so it can be easily recognized. If `offset'
+ ; is called as a tweak, the basic-property alist is unaffected.
+ (target (find-value-to-offset property self immutable))
+ ; if target is a procedure, we need to apply it to our grob to calculate
+ ; values to offset.
+ (vals
+ (if (procedure? target)
+ (target grob)
+ target))
+ (can-type-be-offset?
+ (or (number? vals)
+ (number-pair? vals)
+ (number-pair-list? vals))))
+
+ (if can-type-be-offset?
+ ; '(+inf.0 . -inf.0) would offset to itself. This will be confusing to a
+ ; user unaware of the default value of the property, so issue a warning.
+ (if (equal? empty-interval vals)
+ (ly:warning "default '~a of ~a is ~a and can't be offset"
+ property grob vals)
+ (let* ((orig (ly:grob-original grob))
+ (siblings
+ (if (ly:spanner? grob)
+ (ly:spanner-broken-into orig)
+ '()))
+ (total-found (length siblings))
+ ; Since there is some flexibility in input syntax,
+ ; structure of `offsets' is normalized.
+ (offsets
+ (if (or (not (pair? offsets))
+ (number-pair? offsets)
+ (and (number-pair-list? offsets)
+ (number-pair-list? vals)))
+ (list offsets)
+ offsets)))
+
+ (define (helper sibs offs)
+ ; apply offsets to the siblings of broken spanners
+ (if (pair? offs)
+ (if (eq? (car sibs) grob)
+ (offset-multiple-types vals (car offs))
+ (helper (cdr sibs) (cdr offs)))
+ vals))
+
+ (if (>= total-found 2)
+ (helper siblings offsets)
+ (offset-multiple-types vals (car offsets)))))
+
+ (begin
+ (ly:warning "the property '~a of ~a cannot be offset" property grob)
+ vals))))
+ ; return the closure named `self'
+ self)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; \magnifyMusic and \magnifyStaff
+
+;; defined as a function instead of a list because the
+;; all-grob-descriptions alist is not available yet
+(define-public (find-named-props prop-name grob-descriptions)
+ "Used by @code{\\magnifyMusic} and @code{\\magnifyStaff}. When
+@var{grob-descriptions} is equal to the @code{all-grob-descriptions}
+alist (defined in @file{scm/define-grobs.scm}), this will find all grobs
+that can have a value for the @var{prop-name} property, and return them
+as a list in the following format:
+@example
+'((grob prop-name)
+ (grob prop-name)
+ ...)
+@end example"
+ (define (find-grobs-with-interface interface grob-descriptions)
+ (define (has-this-interface? grob-desc)
+ (let* ((meta (ly:assoc-get 'meta (cdr grob-desc)))
+ (interfaces (ly:assoc-get 'interfaces meta '())))
+ (memq interface interfaces)))
+ (let* ((grob-descriptions-with-this-interface
+ (filter has-this-interface? grob-descriptions))
+ (grob-names-with-this-interface
+ (map car grob-descriptions-with-this-interface)))
+ grob-names-with-this-interface))
+ (let* ((interface
+ (case prop-name
+ ((baseline-skip word-space) 'text-interface)
+ ((space-alist) 'break-aligned-interface)
+ (else (ly:programming-error
+ "find-named-props: no interface associated with ~s"
+ prop-name))))
+ (grobs-with-this-prop
+ (find-grobs-with-interface interface grob-descriptions)))
+ (map (lambda (x) (list x prop-name))
+ grobs-with-this-prop)))
+
+
+(define (magnifyStaff-is-set? context mag)
+ (let* ((Staff (ly:context-find context 'Staff))
+ (old-mag (ly:context-property Staff 'magnifyStaffValue)))
+ (not (null? old-mag))))
+
+(define (staff-magnification-is-changing? context mag)
+ (let* ((Staff (ly:context-find context 'Staff))
+ (old-mag (ly:context-property Staff 'magnifyStaffValue 1)))
+ (not (= old-mag mag))))
+
+(define-public (scale-fontSize func-name mag)
+ "Used by @code{\\magnifyMusic} and @code{\\magnifyStaff}. Look up the
+current @code{fontSize} in the appropriate context and scale it by the
+magnification factor @var{mag}. @var{func-name} is either
+@code{'magnifyMusic} or @code{'magnifyStaff}."
+ (make-apply-context
+ (lambda (context)
+ (if (or (eq? func-name 'magnifyMusic)
+ ;; for \magnifyStaff, only scale the fontSize
+ ;; if staff magnification is changing
+ ;; and does not equal 1
+ (and (staff-magnification-is-changing? context mag)
+ (not (= mag 1))))
+ (let* ((where (case func-name
+ ((magnifyMusic) context)
+ ((magnifyStaff) (ly:context-find context 'Staff))))
+ (fontSize (ly:context-property where 'fontSize 0))
+ (new-fontSize (+ fontSize (magnification->font-size mag))))
+ (ly:context-set-property! where 'fontSize new-fontSize))))))
+
+(define-public (revert-fontSize func-name mag)
+ "Used by @code{\\magnifyMusic} and @code{\\magnifyStaff}. Calculate
+the previous @code{fontSize} value (before scaling) by factoring out the
+magnification factor @var{mag} (if @var{func-name} is
+@code{'magnifyMusic}), or by factoring out the context property
+@code{magnifyStaffValue} (if @var{func-name} is @code{'magnifyStaff}).
+Revert the @code{fontSize} in the appropriate context accordingly.
+
+With @code{\\magnifyMusic}, the scaling is reverted after the music
+block it operates on. @code{\\magnifyStaff} does not operate on a music
+block, so the scaling from a previous call (if there is one) is reverted
+before the new scaling takes effect."
+ (make-apply-context
+ (lambda (context)
+ (if (or (eq? func-name 'magnifyMusic)
+ ;; for \magnifyStaff...
+ (and
+ ;; don't revert the user's fontSize choice
+ ;; the first time \magnifyStaff is called
+ (magnifyStaff-is-set? context mag)
+ ;; only revert the previous fontSize
+ ;; if staff magnification is changing
+ (staff-magnification-is-changing? context mag)))
+ (let* ((where
+ (case func-name
+ ((magnifyMusic) context)
+ ((magnifyStaff) (ly:context-find context 'Staff))))
+ (old-mag
+ (case func-name
+ ((magnifyMusic) mag)
+ ((magnifyStaff)
+ (ly:context-property where 'magnifyStaffValue 1))))
+ (fontSize (ly:context-property where 'fontSize 0))
+ (old-fontSize (- fontSize (magnification->font-size old-mag))))
+ (ly:context-set-property! where 'fontSize old-fontSize))))))
+
+(define-public (scale-props func-name mag allowed-to-shrink? props)
+ "Used by @code{\\magnifyMusic} and @code{\\magnifyStaff}. For each
+prop in @var{props}, find the current value of the requested prop, scale
+it by the magnification factor @var{mag}, and do the equivalent of a
+@code{\\temporary@tie{}\\override} with the new value in the appropriate
+context. If @var{allowed-to-shrink?} is @code{#f}, don't let the new
+value be less than the current value. @var{func-name} is either
+@code{'magnifyMusic} or @code{'magnifyStaff}. The @var{props} list is
+formatted like:
+@example
+'((Stem thickness)
+ (Slur line-thickness)
+ ...)
+@end example"
+ (make-apply-context
+ (lambda (context)
+ (define (scale-prop grob-prop-list)
+ (let* ((grob (car grob-prop-list))
+ (prop (cadr grob-prop-list))
+ (where (if (eq? grob 'SpacingSpanner)
+ (ly:context-find context 'Score)
+ (case func-name
+ ((magnifyMusic) context)
+ ((magnifyStaff) (ly:context-find context 'Staff)))))
+ (grob-def (ly:context-grob-definition where grob)))
+ (if (eq? prop 'space-alist)
+ (let* ((space-alist (ly:assoc-get prop grob-def))
+ (scale-spacing-tuple (lambda (x)
+ (cons (car x)
+ (cons (cadr x)
+ (* mag (cddr x))))))
+ (scaled-tuples (if space-alist
+ (map scale-spacing-tuple space-alist)
+ '()))
+ (new-alist (append scaled-tuples space-alist)))
+ (ly:context-pushpop-property where grob prop new-alist))
+ (let* ((val (ly:assoc-get prop grob-def (case prop
+ ((baseline-skip) 3)
+ ((word-space) 0.6)
+ (else 1))))
+ (proc (lambda (x)
+ (if allowed-to-shrink?
+ (* x mag)
+ (* x (max 1 mag)))))
+ (new-val (if (number-pair? val)
+ (cons (proc (car val))
+ (proc (cdr val)))
+ (proc val))))
+ (ly:context-pushpop-property where grob prop new-val)))))
+ (if (or (eq? func-name 'magnifyMusic)
+ ;; for \magnifyStaff, only scale the properties
+ ;; if staff magnification is changing
+ ;; and does not equal 1
+ (and (staff-magnification-is-changing? context mag)
+ (not (= mag 1))))
+ (for-each scale-prop props)))))
+
+(define-public (revert-props func-name mag props)
+ "Used by @code{\\magnifyMusic} and @code{\\magnifyStaff}. Revert each
+prop in @var{props} in the appropriate context. @var{func-name} is
+either @code{'magnifyMusic} or @code{'magnifyStaff}. The @var{props}
+list is formatted like:
+@example
+'((Stem thickness)
+ (Slur line-thickness)
+ ...)
+@end example"
+ (make-apply-context
+ (lambda (context)
+ (define (revert-prop grob-prop-list)
+ (let* ((grob (car grob-prop-list))
+ (prop (cadr grob-prop-list))
+ (where (if (eq? grob 'SpacingSpanner)
+ (ly:context-find context 'Score)
+ (case func-name
+ ((magnifyMusic) context)
+ ((magnifyStaff) (ly:context-find context 'Staff))))))
+ (ly:context-pushpop-property where grob prop)))
+ (if (or (eq? func-name 'magnifyMusic)
+ ;; for \magnifyStaff...
+ (and
+ ;; don't revert the user's property overrides
+ ;; the first time \magnifyStaff is called
+ (magnifyStaff-is-set? context mag)
+ ;; revert the overrides from the previous \magnifyStaff,
+ ;; but only if staff magnification is changing
+ (staff-magnification-is-changing? context mag)))
+ (for-each revert-prop props)))))
+
+;; \magnifyMusic only
+(define-public (scale-beam-thickness mag)
+ "Used by @code{\\magnifyMusic}. Scaling @code{Beam.beam-thickness}
+exactly to the @var{mag} value will not work. This uses two reference
+values for @code{beam-thickness} to determine an acceptable value when
+scaling, then does the equivalent of a
+@code{\\temporary@tie{}\\override} with the new value."
+ (make-apply-context
+ (lambda (context)
+ (let* ((grob-def (ly:context-grob-definition context 'Beam))
+ (val (ly:assoc-get 'beam-thickness grob-def 0.48))
+ (ratio-to-default (/ val 0.48))
+ ;; gives beam-thickness=0.48 when mag=1 (like default),
+ ;; gives beam-thickness=0.35 when mag=0.63 (like CueVoice)
+ (scaled-default (+ 119/925 (* mag 13/37)))
+ (new-val (* scaled-default ratio-to-default)))
+ (ly:context-pushpop-property context 'Beam 'beam-thickness new-val)))))
+
+;; tag management
+;;
+
+(define tag-groups (make-hash-table))
+(call-after-session (lambda () (hash-clear! tag-groups)))
+
+(define-public (define-tag-group tags)
+ "Define a tag-group consisting of the given @var{tags}, a@tie{}list
+of symbols. Returns @code{#f} if successful, and an error message if
+there is a conflicting tag group definition."
+ (cond ((not (symbol-list? tags)) (format #f (_ "not a symbol list: ~a") tags))
+ ((any (lambda (tag) (hashq-ref tag-groups tag)) tags)
+ => (lambda (group) (and (not (lset= eq? group tags))
+ (format #f (_ "conflicting tag group ~a") group))))
+ (else
+ (for-each
+ (lambda (elt) (hashq-set! tag-groups elt tags))
+ tags)
+ #f)))
+
+(define-public (tag-group-get tag)
+ "Return the tag group (as a list of symbols) that the given
+@var{tag} symbol belongs to, @code{#f} if none."
+ (hashq-ref tag-groups tag))
+
+(define-public (tags-remove-predicate tags)
+ "Returns a predicate that returns @code{#f} for any music that is to
+be removed by @{\\removeWithTag} on the given symbol or list of
+symbols @var{tags}."
+ (if (symbol? tags)
+ (lambda (m)
+ (not (memq tags (ly:music-property m 'tags))))
+ (lambda (m)
+ (not (any (lambda (t) (memq t tags))
+ (ly:music-property m 'tags))))))
+
+(define-public (tags-keep-predicate tags)
+ "Returns a predicate that returns @code{#f} for any music that is to
+be removed by @{\\keepWithTag} on the given symbol or list of symbols
+@var{tags}."
+ (if (symbol? tags)
+ (let ((group (tag-group-get tags)))
+ (lambda (m)
+ (let ((music-tags (ly:music-property m 'tags)))
+ (or
+ (null? music-tags) ; redundant but very frequent
+ ;; We know of only one tag to keep. Either we find it in
+ ;; the music tags, or all music tags must be from a
+ ;; different group
+ (memq tags music-tags)
+ (not (any (lambda (t) (eq? (tag-group-get t) group)) music-tags))))))
+ (let ((groups (delete-duplicates (map tag-group-get tags) eq?)))
+ (lambda (m)
+ (let ((music-tags (ly:music-property m 'tags)))
+ (or
+ (null? music-tags) ; redundant but very frequent
+ (any (lambda (t) (memq t tags)) music-tags)
+ ;; if no tag matches, no tag group should match either
+ (not (any (lambda (t) (memq (tag-group-get t) groups)) music-tags))))))))