4

A piece of sheet music uses two separated bars as coda. These bars also have a SystemStartBrace and a normal bar line.

When recreating this with Lilypond I have not figured out yet, how to put these elements into the sheet music. For this example, I followed the snippet from the Lilypond documentation Positioning segno and coda (with line break) with some minor adjustments and without line break.

If I understand it, the staves are interrupted for the "D.S. con rep." text and then continue for the coda. So it is the same PianoStaff.

Is there a way to draw a system start brace and a bar line at the beginning of the coda?

Original:

enter image description here

Lilypond:

enter image description here

Edit 1:
Adding a snippet showing non-compiling code using @jeanas solution.

\version "2.24.1"
\include "replicate-delims.ly"

\new PianoStaff <<
  \new Staff \relative c' {
    r2 c8 d e4 ~
    e r8 b8 b c d d ~
    4 c r4 a8 b
    c4 a8 c f e d e ~
    4 r4 <c e>8 <d f> <e g>4 ~
    4 r8 <b d>8 8 <c e> <d f>8 8 ~  %% comment this line
    4 <c e> ~ 4 r4          %% and this, too, then it compiles
    \stopStaff 
    s1*2
    \replicateDelims
    \startStaff
    \clef violin
    <>^\markup { \musicglyph "scripts.coda" }
    c'1  \bar "|."
  }
  \new Staff {
    \clef bass
    R1*7        %% may reduce to 5 after commenting above lines
    \stopStaff
    s1*2
    \startStaff
    \clef bass % <-- not showing
    c'1
  }
>>
5
  • 1
    This is quite tricky to do. Perhaps look into mail-archive.com/[email protected]/msg139604.html for ideas. Commented Feb 23, 2023 at 12:08
  • I will have a closer look at it. Looks really complicated though. Another idea is to use a second score for the coda. This will have the brace, but the system starts on a new line. I have not yet figured out, how to suppress the line break.
    – Andreas N
    Commented Feb 23, 2023 at 16:30
  • Believe me, there's really no way to suppress the line break at the end of a \score. It's deeply hardwired in LilyPond's brain. Commented Feb 23, 2023 at 18:17
  • In reply to the bounty request: what do you mean by a “pure LilyPond” solution? A solution not involving Scheme? There are solutions that are much shorter than the very general solution I linked to, but with my LilyPond developer hat on, I don't believe there is a solution without Scheme code. Commented Feb 25, 2023 at 12:59
  • With 'pure' I mean everything that the Lilypond compiler can process. So, Scheme is fine, too. Non-pure would be using LaTeX to stitch stuff together, e.g. inserting a second PDF containing the coda.
    – Andreas N
    Commented Feb 25, 2023 at 16:55

2 Answers 2

2
+100

This is the simplest way I can think of:

\version "2.24.1"

%% probably put this in an \include file

#(define replicate-stil
   (grob-transformer
    'stencil
    (lambda (grob original)
      (let* ((replicate (ly:grob-array->list (ly:grob-object grob 'replicate-on-cols)))
             (left (ly:spanner-bound grob LEFT))
             (own (interval-start (ly:paper-column::break-align-width left 'clef)))
             (sts (map (lambda (col)
                         (let ((tr (interval-start (ly:paper-column::break-align-width col 'clef))))
                           (ly:stencil-outline
                            (ly:stencil-translate-axis original (- tr own) X)
                            empty-stencil)))
                       replicate)))
        (apply ly:stencil-add (cons original sts))))))

\layout {
  \context {
    \Score
    \consists
      #(lambda (context)
         (let ((delims '())
               (cols '()))
           (make-engraver
            (acknowledgers
             ((system-start-delimiter-interface engraver grob source-engraver)
              (set! delims (cons grob delims))))
            ((stop-translation-timestep engraver)
             (let ((col (ly:context-property context 'currentCommandColumn)))
               (when (assoc-get 'replicate-delims (ly:grob-property col 'details))
                 (set! cols (cons col cols)))))
            ((finalize engraver)
             (for-each (lambda (delim)
                         (ly:grob-set-object! delim 'replicate-on-cols (ly:grob-list->grob-array cols)))
                       delims)))))
    \override SystemStartBar.stencil = #replicate-stil
    \override SystemStartBracket.stencil = #replicate-stil
    \override SystemStartBrace.stencil = #replicate-stil
    \override SystemStartSquare.stencil = #replicate-stil
  }
}

replicateDelims = {
  \once \override Score.NonMusicalPaperColumn.details.replicate-delims = ##t
  \once \set Staff.forceClef = ##t
  \once \override Staff.Clef.full-size-change = ##t
  \once \set Score.measureBarType = ##f
  \once \override Score.BreakAlignment.X-extent =
    #(lambda (grob)
       (match-let (((a . b) (ly:axis-group-interface::width grob)))
         (cons (- a 0.8) b)))
}


%% main file

\new PianoStaff <<
  \new Staff \relative c' {
    r2 c8 d e4 ~
    e r8 b8 b c d d ~
    4 c r4 a8 b
    c4 a8 c f e d e ~
    4 r4 <c e>8 <d f> <e g>4 ~
    4 r8 <b d>8 8 <c e> <d f>8 8 ~
    4 <c e> ~ 4 r4
    \stopStaff
    s1*2
    \replicateDelims
    \startStaff
    \clef violin
    <>^\markup { \musicglyph "scripts.coda" }
    c'1  \bar "|."
  }
  \new Staff {
    \clef bass
    R1*7
    \stopStaff
    s1*2
    \startStaff
    \replicateDelims
    \clef bass
    c'1
  }
>>
7
  • 1
    I tried this approach with my score, but the compiler aborts with error at init.ly:65:2: Guile signaled an error for the expression beginning here: # (let ((book-handler (if (defined? 'default-toplevel-book-handler) In procedure ly:grob-extent: Wrong type argument in position 1 (expecting Grob): #<unspecified> After commenting the \replicateDelims command, the file compiles fine. Commenting all music following a specific position does the same with the \replicateDelims command active, but I don't see anything wrong with the music itself.
    – Andreas N
    Commented Feb 27, 2023 at 23:52
  • As an additional note: when adding clefs in the above minimal example they will be smaller and an additional bar line is added after them and before the coda music. This can be fixed by overriding Staff.Clef attributes and omitting BarLines as shown in @Lazy answer. However, I need to use \clef bass at the beginning of the left hand staff and then the bass clef is no longer shown at the start of the coda.
    – Andreas N
    Commented Feb 27, 2023 at 23:57
  • Can you share your code, or a snippet reproducing the problem? Commented Feb 28, 2023 at 9:23
  • 1
    I have added a snippet to my question labelled as "Edit 1".
    – Andreas N
    Commented Feb 28, 2023 at 12:10
  • Pff, I made a stupid mistake. I’ll correct it when I’m back home. Commented Feb 28, 2023 at 13:17
0

This is not something Lilypond supports natively, so we have to somehow get this done ourselves. This code here tries to replicate the default delimiters behaviour. Currently this has Brace, Bracket and Bar implemented. This could then be expanded to allow for in Staff Instrument names and such.

#(define (inside-staff-linkage-gen)
   (let ((tree '()) (cc #f) (cc2 #f))
     (define (merge-branch-to-tree branch tree)
       (if (eq? (assoc-get 'context (car branch)) (assoc-get 'context (car tree)))
           (let* ((sub (map (lambda (x) (merge-branch-to-tree (cadr branch) x)) (cdr tree)))
                  (anysub (reduce (lambda (x y) (or x y)) #t (map car sub))))
             (if (not anysub)
                 (cons #t (cons (car tree) (append (cdr branch) (cdr tree))))
                 (cons #t (cons (car tree) (map cdr sub)))))
           (cons #f tree)))
     (define (add-to-tree el)
       (if (null? tree)
           (set! tree el)
           (set! tree (cdr (merge-branch-to-tree el tree)))))
     (define (tree-get-refpoints tree)
       (if (null? tree)
           '()
           (cons (assoc-get 'ref (car tree)) (apply append (map tree-get-refpoints (cdr tree))))))
     (define (tree-make-delimiter tree support)
       (let* ((engraver (assoc-get 'engraver (car tree)))
              (type (assoc-get 'type (car tree)))
              (refp (tree-get-refpoints tree))
              (grob #f))
         (cond ((eq? type 'SystemStartBrace)
                (set! grob (ly:engraver-make-spanner engraver 'InlineStaffBrace '())))
               ((eq? type 'SystemStartBracket)
                (set! grob (ly:engraver-make-spanner engraver 'InlineStaffBracket '())))
               ((eq? type 'SystemStartBar)
                (set! grob (ly:engraver-make-spanner engraver 'InlineStaffBar '()))))
         (if grob
             (begin
              (ly:grob-set-property! grob 'details `((ref . ,refp)))
              (ly:spanner-set-bound! grob LEFT cc)
              (ly:spanner-set-bound! grob RIGHT cc2)
              (ly:grob-set-object! grob 'side-support-elements (ly:grob-list->grob-array (cons cc2 support)))
              (cons grob support))
             support)))
     (define (tree-make-all-delimiters tree support)
       (if (pair? tree)
           (let ((sup2 (tree-make-delimiter tree support)))
             (map (lambda (t) (tree-make-all-delimiters t sup2)) (cdr tree)))))
     (lambda (action . args)
       (cond ((equal? action 'add-to-tree) (set! cc (cadr args)) (add-to-tree (car args)))
             ((equal? action 'make-delimiters) (set! cc2 (car args)) (tree-make-all-delimiters tree '()))
             ((equal? action 'clear-tree) (set! tree '()))))))

#(define inside-staff-linkage (inside-staff-linkage-gen))

#(define-event-class 'inline-staff-event 'music-event)

#(define (define-event! type properties)
   (set-object-property! type
                         'music-description
                         (cdr (assq 'description properties)))
   (set! properties (assoc-set! properties 'name type))
   (set! properties (assq-remove! properties 'description))
   (hashq-set! music-name-to-property-table type properties)
   (set! music-descriptions
         (sort (cons (cons type properties)
                     music-descriptions)
               alist<?)))

#(define-event!
  'InlineStaffEvent
  '((types . (inline-staff-event event))
    (description . "")))

#(set-object-property! 'InlineRef 'is-grob? #t)
#(set-object-property! 'InlineRef 'translation-type? ly:grob-properties?) 
#(set-object-property! 'InlineStaffBrace 'is-grob? #t)
#(set-object-property! 'InlineStaffBrace 'translation-type? ly:grob-properties?)
#(set-object-property! 'InlineStaffBracket 'is-grob? #t)
#(set-object-property! 'InlineStaffBracket 'translation-type? ly:grob-properties?) 
#(set-object-property! 'InlineStaffBar 'is-grob? #t)
#(set-object-property! 'InlineStaffBar 'translation-type? ly:grob-properties?) 

#(define (inline-staff-brace::print grob)
   (define (font-get-brace font name)
     (ly:font-get-glyph font (format #f "brace~a" name)))
   (define (font-brace-exists font name)
     (not (ly:stencil-empty? (font-get-brace font name))))
   (define (internal-font-get-upper-limit font u)
     (if (font-brace-exists font u)
         (internal-font-get-upper-limit font (* 2 u))
         u))
   (define (font-get-upper-limit font) (internal-font-get-upper-limit font 1))
   (define (internal-font-get-count font l u)
     (if (< (1+ l) u)
         (let ((m (quotient (+ l u) 2)))
           (if (font-brace-exists font m)
               (internal-font-get-count font m u)
               (internal-font-get-count font l m)))
         l))
   (define (font-get-count font) (internal-font-get-count font 0 (font-get-upper-limit font)))
   (define (internal-font-fit-brace font l stc1 u stc2 height)
     (if (< (1+ l) u)
         (let* ((m (quotient (+ l u) 2)) (stcm (font-get-brace font m)))
           (if (> (interval-length (ly:stencil-extent stcm Y)) height)
               (internal-font-fit-brace font l stc1 m stcm height)
               (internal-font-fit-brace font m stcm u stc2 height)))
         stc1))
   (define (font-fit-brace font height)
     (let ((l 0) (u (font-get-count font)))
       (internal-font-fit-brace font l (font-get-brace font l) u (font-get-brace font u) height)))
   (define (grob-is-parent? grob axis par)
     (if (null? grob)
         #f
         (if (eq? grob par)
             #t
             (grob-is-parent? (ly:grob-parent grob axis) axis par))))
   (let* ((det (ly:grob-property grob 'details))
          (refs (assoc-get 'ref det))
          (refs (filter (lambda (x) x) refs))
          (stsym (map (lambda (r) (ly:grob-object r 'staff-symbol)) refs))
          (refp (ly:grob-common-refpoint-of-array grob (ly:grob-list->grob-array stsym) Y))
          ; If check is false this means that at least one staff symbol is on a different system
          ; This does most likely mean that the system is broken, in which case we do not want to print
          ; the delimiter
          (check (not (any (lambda (ss) (not (grob-is-parent? ss Y refp))) stsym)))
          (exts (if check (map (lambda (ss) (ly:grob-extent ss refp Y)) stsym) '()))
          (ext (reduce interval-union '(0 . 0) exts))
          (height (interval-length ext)))
     (if (or (not check) (< height (ly:grob-property grob 'collapse-height 5.0)))
         (ly:grob-suicide! grob)
         (let* ((font (ly:paper-get-font (ly:grob-layout grob) '(((font-encoding . fetaBraces)))))
                (bracstc (font-fit-brace font (interval-length ext))))
           (ly:stencil-translate-axis bracstc (interval-center ext) Y)))))

#(define (inline-staff-bracket::print grob)
   (define (grob-is-parent? grob axis par)
     (if (null? grob)
         #f
         (if (eq? grob par)
             #t
             (grob-is-parent? (ly:grob-parent grob axis) axis par))))
   (let* ((det (ly:grob-property grob 'details))
          (refs (assoc-get 'ref det))
          (refs (filter (lambda (x) x) refs))
          (stsym (map (lambda (r) (ly:grob-object r 'staff-symbol)) refs))
          (refp (ly:grob-common-refpoint-of-array grob (ly:grob-list->grob-array stsym) Y))
          ; If check is false this means that at least one staff symbol is on a different system
          ; This does most likely mean that the system is broken, in which case we do not want to print
          ; the delimiter
          (check (not (any (lambda (ss) (not (grob-is-parent? ss Y refp))) stsym)))
          (exts (if check (map (lambda (ss) (ly:grob-extent ss refp Y)) stsym) '()))
          (ext (reduce interval-union '(0 . 0) exts))
          (height (interval-length ext)))
     (if (or (not check) (< height (ly:grob-property grob 'collapse-height 5.0)))
         (ly:grob-suicide! grob)
         (let* ((font (ly:paper-get-font (ly:grob-layout grob) '(((font-encoding . fetaMusic)))))
                (tipsd (ly:font-get-glyph font "brackettips.down"))
                (tipsu (ly:font-get-glyph font "brackettips.up"))
                (thick (ly:grob-property grob 'thickness 0.25))
                (overlap (* 0.1 thick))
                (height2 (+ (/ height 2) overlap))
                (bracket-line (make-filled-box-stencil (cons 0 thick) (cons (- height2) height2)))
                (stc (stack-stencils Y DOWN 0 (list tipsu bracket-line tipsd)))
                (stc (ly:stencil-aligned-to stc Y CENTER))
                (stc (ly:make-stencil (ly:stencil-expr stc)
                                      (ly:stencil-extent bracket-line X)
                                      (ly:stencil-extent stc Y))))
           (ly:stencil-translate-axis stc (interval-center ext) Y)))))

#(define (inline-staff-bar::print grob)
   (define (grob-is-parent? grob axis par)
     (if (null? grob)
         #f
         (if (eq? grob par)
             #t
             (grob-is-parent? (ly:grob-parent grob axis) axis par))))
   (let* ((det (ly:grob-property grob 'details))
          (refs (assoc-get 'ref det))
          (refs (filter (lambda (x) x) refs))
          (stsym (map (lambda (r) (ly:grob-object r 'staff-symbol)) refs))
          (refp (ly:grob-common-refpoint-of-array grob (ly:grob-list->grob-array stsym) Y))
          ; If check is false this means that at least one staff symbol is on a different system
          ; This does most likely mean that the system is broken, in which case we do not want to print
          ; the delimiter
          (check (not (any (lambda (ss) (not (grob-is-parent? ss Y refp))) stsym)))
          (exts (if check (map (lambda (ss) (ly:grob-extent ss refp Y)) stsym) '()))
          (ext (reduce interval-union '(0 . 0) exts))
          (height (interval-length ext)))
     (if (or (not check) (< height (ly:grob-property grob 'collapse-height 5.0)))
         (ly:grob-suicide! grob)
         (let* ((lt (ly:output-def-lookup (ly:grob-layout grob) 'line-thickness))
                (thick (ly:grob-property grob 'thickness 1))
                (w (* lt thick))
                (height2 (/ height 2))
                (stc (ly:round-filled-box (cons 0 w) (cons (- height2) height2) lt)))
           (ly:stencil-translate-axis stc (interval-center ext) Y)))))


#(define (inline-spanner::minimum-length grob)
   (ly:output-def-lookup (ly:grob-layout grob) 'indent))

\layout {
  \context {
    \Global
    \grobdescriptions #`((InlineRef
                          . (
                              (meta
                                (name . InlineRef)
                                (classes Item)
                                (interfaces grob-interface)
                              )
                            )
                          )
                         (InlineStaffBrace
                          . (
                              (stencil . ,inline-staff-brace::print)
                              (direction . -1)
                              (X-offset . ,ly:side-position-interface::x-aligned-side)
                              (padding . 0.3)
                              (springs-and-rods . ,ly:spanner::set-spacing-rods)
                              (minimum-length . ,inline-spanner::minimum-length)
                              (meta
                                (name . InlineStaffBrace)
                                (classes Item Spanner)
                                (interfaces spanner-interface
                                            font-interface
                                            side-position-interface)
                              )
                            )
                         )
                         (InlineStaffBracket
                          . (
                              (stencil . ,inline-staff-bracket::print)
                              (direction . -1)
                              (extra-spacing-width . (-10 . 10))
                              (X-offset . ,ly:side-position-interface::x-aligned-side)
                              (padding . 0.8)
                              (thickness . 0.45)
                              (springs-and-rods . ,ly:spanner::set-spacing-rods)
                              (minimum-length . ,inline-spanner::minimum-length)
                              (meta
                                (name . InlineStaffBrace)
                                (classes Item Spanner)
                                (interfaces spanner-interface
                                            font-interface
                                            side-position-interface)
                              )
                            )
                         )
                         (InlineStaffBar
                          . (
                              (stencil . ,inline-staff-bar::print)
                              (direction . -1)
                              (X-offset . ,ly:side-position-interface::x-aligned-side)
                              (padding . -0.1)
                              (thickness . 1.6)
                              (springs-and-rods . ,ly:spanner::set-spacing-rods)
                              (minimum-length . ,inline-spanner::minimum-length)
                              (meta
                                (name . InlineStaffBrace)
                                (classes Item Spanner)
                                (interfaces spanner-interface
                                            font-interface
                                            side-position-interface)
                              )
                            )
                          )
                         )
  }
}

inline = #(make-music 'InlineStaffEvent)

#(define (inline-staff-engraver context)
   (define (make-branch context engraver childr ref)
     (let* ((par (ly:context-parent context))
            (type (ly:context-property context 'systemStartDelimiter))
            (typecontext (ly:context-property-where-defined context 'systemStartDelimiter))
            (type (if (eq? context typecontext) type '()))
            (leaf `((context . ,context) (type . ,type) (engraver . ,engraver) (ref . ,ref))))
       (if par
           (make-branch par engraver (cons leaf (list childr)) #f)
           (cons leaf (list childr)))))
   (define (get-column context allow-musical?)
     (if (and (ly:moment<? (ly:make-moment 0 0) (ly:context-property context 'measurePosition))
              allow-musical?)
         (ly:context-property context 'currentMusicalColumn)
         (ly:context-property context 'currentCommandColumn)))
   (make-engraver
    (listeners
     ((inline-staff-event engraver event)
      (let* ((ref (ly:engraver-make-grob engraver 'InlineRef '()))
             (branch (make-branch context engraver '() ref)))
        (inside-staff-linkage 'add-to-tree branch (get-column context #t)))))
    ((process-music engraver)
     (inside-staff-linkage 'make-delimiters
                           (get-column context #t))
     (inside-staff-linkage 'clear-tree))))

\layout {
  \context {
    \Staff
    \consists #inline-staff-engraver
  }
}

\new PianoStaff \new PianoStaff <<
  \new Staff { c'4 4 4 4 \inline 4 4  }
  \new Staff { c'4 4 4 4 \inline 4 4 }
>>

\new PianoStaff \new PianoStaff <<
  \new Staff { c'4 4 4 4 \break \inline 4 4  }
  \new Staff { c'4 4 4 4 \inline 4 4 }
>>

\new StaffGroup \new StaffGroup <<
  \new Staff { c'4 4 4 4 \inline 4 4  }
  \new Staff { c'4 4 4 4 \inline 4 4 }
>>

\new PianoStaff <<
  \new Staff {
    c'4 4 4 4
    \cadenzaOn
    \stopStaff s1
    \cadenzaOff
    \startStaff
    \once\override Staff.Clef.full-size-change = ##t
    \once\override Staff.Clef.extra-spacing-width = #'(0 . 2)
    \once\override Staff.Clef.extra-offset = #'(0.5 . 0)
    \once \omit Staff.BarLine
    \once \omit PianoStaff.SpanBar
    \set Staff.forceClef = ##t
    \inline 4 4
  }
  \new Staff {
    c'4 4 4 4
    \cadenzaOn
    \stopStaff s1
    \cadenzaOff
    \startStaff
    \once\override Staff.Clef.full-size-change = ##t
    \once\override Staff.Clef.extra-spacing-width = #'(0 . 2)
    \once\override Staff.Clef.extra-offset = #'(0.5 . 0)
    \once \omit Staff.BarLine
    \set Staff.forceClef = ##t
    \inline 4 4
  }
>>
5
  • Seems like we raced… That works, although TBH it seems needlessly complicated to me. FWIW, I think we should eventually turn system start delimiters into items so that having separate grobs will not be needed. Commented Feb 26, 2023 at 5:07
  • @ABOUSAMRAJean Hello Jean, I suppose so, but arguing like in the old russian joke I got second place while you only got second last ... What my version goes for is to allow having the System preamble print anywhere within the System. If I can get this to print after the BarLine, with potential Instrument names and automatic hiding of the Staff symbol this could implement the whole "inside Staff start" thing while at the same time being break friendly.
    – Lazy
    Commented Feb 26, 2023 at 9:33
  • @ABOUSAMRAJean I think potentially the whole System start preamble could be placed in this manner in the first place with a Clef like behaviour so that be can force a system start placed by a context option.
    – Lazy
    Commented Feb 26, 2023 at 9:34
  • This works quite well, but as mentioned in the other answer from @jeanas the bass clef is not shown in the coda, if it was already used before in the staff. The violin clef works as I don't have to declare it before the coda.
    – Andreas N
    Commented Feb 27, 2023 at 23:58
  • 1
    @AndreasN Interesting, it seems like at some point a change was implemented that repeated \clef something calls are omitted. Instead use \set Staff.forceClef = ##t to force a repeat of the current clef.
    – Lazy
    Commented Feb 28, 2023 at 11:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.