;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; property setting music objs.
+(define-safe-public (check-grob-path path #:optional parser location
+ #:key
+ (start 0)
+ default
+ (min 1)
+ max)
+ "Check a grob path specification @var{path}, a symbol list (or a
+single symbol), for validity and possibly complete it. Returns the
+completed specification, or @code{#f} if invalid. If optional
+@var{parser} is given, a syntax error is raised in that case,
+optionally using @var{location}. If an optional keyword argument
+@code{#:start @var{start}} is given, the parsing starts at the given
+index in the sequence @samp{Context.Grob.property.sub-property...},
+with the default of @samp{0} implying the full path.
+
+If there is no valid first element of @var{path} fitting at the given
+path location, an optionally given @code{#:default @var{default}} is
+used as the respective element instead without checking it for
+validity at this position.
+
+The resulting path after possibly prepending @var{default} can be
+constrained in length by optional arguments @code{#:min @var{min}} and
+@code{#:max @var{max}}, defaulting to @samp{1} and unlimited,
+respectively."
+ (let ((path (if (symbol? path) (list path) path)))
+ ;; A Guile 1.x bug specific to optargs precludes moving the
+ ;; defines out of the let
+ (define (unspecial? s)
+ (not (or (object-property s 'is-grob?)
+ (object-property s 'backend-type?))))
+ (define (grob? s)
+ (object-property s 'is-grob?))
+ (define (property? s)
+ (object-property s 'backend-type?))
+ (define (check c p) (c p))
+
+ (let* ((checkers
+ (and (< start 3)
+ (drop (list unspecial? grob? property?) start)))
+ (res
+ (cond
+ ((null? path)
+ ;; tricky. Should we make use of the default when the
+ ;; list is empty? In most cases, this question should be
+ ;; academical as an empty list can only be generated by
+ ;; Scheme and is likely an error. We consider this a case
+ ;; of "no valid first element, and default given".
+ ;; Usually, invalid use cases should be caught later using
+ ;; the #:min argument, and if the user explicitly does not
+ ;; catch this, we just follow through.
+ (if default (list default) '()))
+ ((not checkers)
+ ;; no checkers, so we have a valid first element and just
+ ;; take the path as-is.
+ path)
+ (default
+ (if ((car checkers) (car path))
+ (and (every check (cdr checkers) (cdr path))
+ path)
+ (and (every check (cdr checkers) path)
+ (cons default path))))
+ (else
+ (and (every check checkers path)
+ path)))))
+ (if (and res
+ (if max (<= min (length res) max)
+ (<= min (length res))))
+ res
+ (begin
+ (if parser
+ (ly:parser-error parser
+ (format #f (_ "bad grob property path ~a")
+ path)
+ location))
+ #f)))))
+
(define-public (make-grob-property-set grob gprop val)
"Make a @code{Music} expression that sets @var{gprop} to @var{val} in
@var{grob}. Does a pop first, i.e., this is not an override."
Fingering
LaissezVibrerTie
LigatureBracket
+ MultiMeasureRest
PhrasingSlur
RepeatTie
Rest
(Voice Fingering font-size -8)
(Voice StringNumber font-size -8)))
- (make-grob-property-set 'NoteColumn 'horizontal-shift (quotient n 2))
- (make-grob-property-set 'MultiMeasureRest 'staff-position (if (odd? n) -4 4))))))
+ (make-grob-property-set 'NoteColumn 'horizontal-shift (quotient n 2))))))
+
(define-safe-public (make-voice-props-override n)
(make-sequential-music
(if (ly:music? e)
(set! (ly:music-property m 'element) (voicify-music e)))
(if (and (equal? (ly:music-property m 'name) 'SimultaneousMusic)
- (reduce (lambda (x y ) (or x y)) #f (map music-separator? es)))
+ (any music-separator? es))
(set! m (context-spec-music (voicify-chord m) 'Staff)))
m))
(define-public (music-has-type music type)
(memq type (ly:music-property music 'types)))
-(define-public (music-clone music)
- (define (alist->args alist acc)
- (if (null? alist)
- acc
- (alist->args (cdr alist)
- (cons (caar alist) (cons (cdar alist) acc)))))
-
- (apply
- make-music
- (ly:music-property music 'name)
- (alist->args (ly:music-mutable-properties music) '())))
+(define-public (music-clone music . music-properties)
+ "Clone @var{music} and set properties according to
+@var{music-properties}, a list of alternating property symbols and
+values:
+@example\n(music-clone start-span 'span-direction STOP)
+@end example
+Only properties that are not overriden by @var{music-properties} are
+actually fully cloned."
+ (let ((old-props (list-copy (ly:music-mutable-properties music)))
+ (new-props '())
+ (m (ly:make-music (ly:prob-immutable-properties music))))
+ (define (set-props mus-props)
+ (if (and (not (null? mus-props))
+ (not (null? (cdr mus-props))))
+ (begin
+ (set! old-props (assq-remove! old-props (car mus-props)))
+ (set! new-props
+ (assq-set! new-props
+ (car mus-props) (cadr mus-props)))
+ (set-props (cddr mus-props)))))
+ (set-props music-properties)
+ (for-each
+ (lambda (pair)
+ (set! (ly:music-property m (car pair))
+ (ly:music-deep-copy (cdr pair))))
+ old-props)
+ (for-each
+ (lambda (pair)
+ (set! (ly:music-property m (car pair)) (cdr pair)))
+ new-props)
+ m))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; warn for bare chords at start.
(let* ((entry (car keysig))
(entryoct (key-entry-octave entry))
(entrynn (key-entry-notename entry))
- (oct (ly:pitch-octave pitch))
(nn (ly:pitch-notename pitch)))
(if (and (equal? nn entrynn)
- (or (and accept-global (not entryoct))
- (and accept-local (equal? oct entryoct))))
+ (or (not entryoct)
+ (= entryoct (ly:pitch-octave pitch)))
+ (if (key-entry-bar-number entry)
+ accept-local
+ accept-global))
entry
(find-pitch-entry (cdr keysig) pitch accept-global accept-local)))))
(entry (find-pitch-entry keysig pitch #t #t)))
(if (not entry)
(cons #f #f)
- (let* ((global-entry (find-pitch-entry keysig pitch #f #f))
- (key-acc (key-entry-alteration global-entry))
- (acc (ly:pitch-alteration pitch))
- (entrymp (key-entry-measure-position entry))
+ (let* ((entrymp (key-entry-measure-position entry))
(entrybn (key-entry-bar-number entry)))
- (cons #f (not (or (equal? acc key-acc)
- (and (equal? entrybn barnum) (equal? entrymp measurepos)))))))))
+ (cons #f (not (and (equal? entrybn barnum) (equal? entrymp measurepos))))))))
(define-public (set-accidentals-properties extra-natural
auto-accs auto-cauts
(map (lambda (x) (ly:music-property x 'pitch))
(event-chord-notes event-chord)))
+(defmacro-public make-relative (pitches last-pitch music)
+ "The list of pitch-carrying variables in @var{pitches} is used as a
+sequence for creating relativable music from @var{music}.
+The variables in @var{pitches} are, when considered inside of
+@code{\\relative}, all considered to be specifications to the preceding
+variable. The first variable is relative to the preceding musical
+context, and @var{last-pitch} specifies the pitch passed as relative
+base onto the following musical context."
+
+ ;; 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 pitches p->m p->p) music pitch)
+ (let* ((chord (make-event-chord
+ (map
+ (lambda (p)
+ (make-music 'NoteEvent
+ 'pitch p))
+ pitches)))
+ (pitchout (begin
+ (ly:make-music-relative! chord pitch)
+ (event-chord-pitches chord))))
+ (set! (ly:music-property music 'element)
+ (apply p->m pitchout))
+ (apply p->p pitchout)))
+ `(make-music 'RelativeOctaveMusic
+ 'to-relative-callback
+ (,make-relative::to-relative-callback
+ (list ,@pitches)
+ (lambda ,pitches ,music)
+ (lambda ,pitches ,last-pitch))
+ 'element ,music))
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; The following functions are all associated with the crossStaff
; function
(if (>= (length siblings) 2)
(helper siblings arg)
(car arg))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; measure counter
+
+(define (measure-counter-stencil grob)
+ "Print a number for a measure count. The number is centered using
+the extents of @code{BreakAlignment} grobs associated with
+@code{NonMusicalPaperColumn} grobs. In the case of an unbroken measure, these
+columns are the left and right bounds of a @code{MeasureCounter} spanner.
+Broken measures are numbered in parentheses."
+ (let* ((orig (ly:grob-original grob))
+ (siblings (ly:spanner-broken-into orig)) ; have we been split?
+ (bounds (ly:grob-array->list (ly:grob-object grob 'columns)))
+ (refp (ly:grob-system grob))
+ ; we use the first and/or last NonMusicalPaperColumn grob(s) of
+ ; a system in the event that a MeasureCounter spanner is broken
+ (all-cols (ly:grob-array->list (ly:grob-object refp 'columns)))
+ (all-cols
+ (filter
+ (lambda (col) (eq? #t (ly:grob-property col 'non-musical)))
+ all-cols))
+ (left-bound
+ (if (or (null? siblings) ; spanner is unbroken
+ (eq? grob (car siblings))) ; or the first piece
+ (car bounds)
+ (car all-cols)))
+ (right-bound
+ (if (or (null? siblings)
+ (eq? grob (car (reverse siblings))))
+ (car (reverse bounds))
+ (car (reverse all-cols))))
+ (elts-L (ly:grob-array->list (ly:grob-object left-bound 'elements)))
+ (elts-R (ly:grob-array->list (ly:grob-object right-bound 'elements)))
+ (break-alignment-L
+ (filter
+ (lambda (elt) (grob::has-interface elt 'break-alignment-interface))
+ elts-L))
+ (break-alignment-R
+ (filter
+ (lambda (elt) (grob::has-interface elt 'break-alignment-interface))
+ elts-R))
+ (break-alignment-L-ext (ly:grob-extent (car break-alignment-L) refp X))
+ (break-alignment-R-ext (ly:grob-extent (car break-alignment-R) refp X))
+ (num (markup (number->string (ly:grob-property grob 'count-from))))
+ (num
+ (if (or (null? siblings)
+ (eq? grob (car siblings)))
+ num
+ (make-parenthesize-markup num)))
+ (num (grob-interpret-markup grob num))
+ (num (ly:stencil-aligned-to num X (ly:grob-property grob 'self-alignment-X)))
+ (num
+ (ly:stencil-translate-axis
+ num
+ (+ (interval-length break-alignment-L-ext)
+ (* 0.5
+ (- (car break-alignment-R-ext)
+ (cdr break-alignment-L-ext))))
+ X)))
+ num))