1

I am creating a piano book in LilyPond, which I am using for the first time. I am separating each piece into its own \bookpart, and if any of them have multiple movements, then each movement is in a different \score block. I also include table of contents entries for each \bookpart, and if there are multiple movements, one entry for each movement. I would like to create a scheme function that would generate the majority of this layout for me, to avoid copying/pasting this layout every time I add a piece to the book. I found the answer here that shows how to do something similar to what I would like, but with the following differences:

  • It needs to allow for multiple scores in the \bookpart
  • It needs to add a \tocItem for the \bookpart, and each \score.

After playing around with the answer in the link above, I reached a point where I had these functions:

defaultBookPart = #(define-scheme-function
    (scores toclabel tocentry title composer)
    (ly:score? (symbol-list-or-symbol? '()) markup? string? string? )
    (toplevel-bookpart-handler #{
        \bookpart {
            \header {
                title = $title
                composer = $composer
            }
            \tocItem $toclabel $tocentry
            #scores
        }
    #})
)

defaultScore = #(define-scheme-function
    (righthand lefthand piece opus)
    (ly:music? ly:music? string? string?)
    (toplevel-score-handler #{
        \score {
            \header {
                piece = #piece
                opus = #opus
            }
            \new PianoStaff <<
                \set PianoStaff.connectArpeggios = ##t
                \accidentalStyle "piano"
                \new Staff \relative {
                    #righthand
                }
                \new Staff \relative {
                    \clef "bass"
                    #lefthand
                }
            >>
        }
    #})
)

defaultMovementScore = #(define-scheme-function
    (righthand lefthand toclabel piece opus)
    (ly:music? ly:music? (symbol-list-or-symbol? '()) string? string?)
    (toplevel-score-handler #{
        \score {
            \header {
                piece = $piece
                opus = $opus
            }
            \new PianoStaff <<
                \set PianoStaff.connectArpeggios = ##t
                \accidentalStyle "piano"
                \new Staff \relative {
                    \tocItem $toclabel \markup $piece
                    #righthand
                }
                \new Staff \relative {
                    \clef "bass"
                    #lefthand
                }
            >>
        }
    #})
)

I am getting bad expression type errors when I try to use these, and in addition I see the following problems with this approach:

  • The first argument to the defaultBookPart function has the wrong type. I was unable to find in the documentation a score-list type, but it looks like there is a built-in function that takes a similar argument: ly:make-book-part. I was unable to find what the correct type to pass in there would be however.
  • I'm not sure that the toclabel arguments have the right type either. I was able to find the type specified above in the toc-init.ly file, but when I try to call my function, it doesn't seem to like the same input as can be used in the tocItem function.

Is there a way to resolve these issues? I am also having a hard time figuring out how to call these, passing the output of the score functions into the bookpart function.

1 Answer 1

0

Regarding your first point: You can simply use the predicate list?. Regarding the second point: This should be correct.

But maybe you want to go for a solution with slightly less complexity like this:

#(define (list-or-music? x)
   (or (ly:music? x) (list? x)))

#(define (format-movement-toc opus part-index)
   (format "Movement ~a" (1+ part-index)))

#(define (format-title-toc composer title opus)
   (markup #:line ( #:concat ( composer ":" ) )
                    #:hspace 1
                    title
                    #:hspace 0.4
                    #:normal-text #:italic #:concat ( "(" opus ")" )))

#(define (create-score composer title opus multi-part? part-label rhs lhs)
   (let ((label (if multi-part?
                    (if (number? part-label)
                        (format-movement-toc opus part-label)
                        part-label)
                    "")))
     #{
       \score {
         \header {
           piece = #label
         }
         \new PianoStaff <<
           \set PianoStaff.connectArpeggios = ##t
           \accidentalStyle "piano"
           \new Staff \relative {
             #(if multi-part?
                  (add-toc-item! 'tocItemMarkup label
                                 (list (string->symbol
                                        (format "~a"
                                                (format-title-toc composer title opus)))
                                       (string->symbol (format "~a" label))))
                  (empty-music))
             #rhs
           }
           \new Staff \relative {
             \clef "bass"
             #lhs
           }
        >>
      }
     #}))

createPiece =
#(define-void-function
  (composer title opus movement-labels music-pairs)
  (markup? markup? markup? list? pair?)
  (if (and (list? music-pairs) (null? movement-labels))
      (set! movement-labels (iota (length music-pairs))))
  (if (and (list? music-pairs) (= (length music-pairs) 1))
      (set! music-pairs (car music-pairs)))
  (toplevel-bookpart-handler #{
    \bookpart {
      \header {
        title = #title
        composer = #composer
        opus = #opus
      }
      \tocItem #(string->symbol (format "~a" (format-title-toc composer title opus)))
               #(format-title-toc composer title opus)
      $@(if (list? music-pairs)
            (map (lambda (l p) (create-score composer title opus #t l (car p) (cdr p)))
                 movement-labels
                 music-pairs)
            (list (create-score composer title opus #f 1 (car music-pairs) (cdr music-pairs))))
    }
                             #}))

makeMusicPair =
#(define-scheme-function (m1 m2) (ly:music? ly:music?)
   (cons m1 m2))

\markuplist\table-of-contents

\createPiece "composerA" "titleA" "opusA"
  #'()
  \makeMusicPair
  { c'' d c d }
  { c d e f }

\createPiece "composerB" "titleB" "opusB"
  #'()
  #(list
    (makeMusicPair #{ c' d e f #} #{ c b c d #})
  )

\createPiece "composerC" "titleC" "opusC"
  #'()
  #(list
    (makeMusicPair #{ c' d e f #} #{ c b c d #})
    (makeMusicPair #{ c'' b a g #} #{ e g f e #})
  )

\createPiece "composerD" "titleD" "opusD"
  #'("Allegro" "Andante")
  #(list
    (makeMusicPair #{ c' d e f #} #{ c b c d #})
    (makeMusicPair #{ c'' b a g #} #{ e g f e #})
  )
4
  • Thanks for the answer! It appears to work correctly if I paste the entire code you had in one file. Is there any way to put each song in a different file? That would keep it a little cleaner the longer the list of songs becomes. I have tried to use \include in various ways with no success so far.
    – Dubl
    Jun 1 at 6:17
  • @Dubl Sure. Just put the definitions into a file called e.g. 0.ly and put the \createPiece calls into files 1.ly, 2.ly, ... (whatever naming you prefer) and then do a master file that includes all of these files. Just make sure to include 0.ly first (so that \createPiece is defined when it is needed). You can also include 0.ly from each .ly file if each file should be compileable.
    – Lazy
    Jun 1 at 9:06
  • Trying that out, I keep getting this error: error: GUILE signaled an error for the expression beginning here $@(if (list? music-pairs) Wrong type (expecting pair): list
    – Dubl
    Jun 1 at 14:56
  • Never mind, it looks like I was not following your examples exactly when calling \createPiece. Thanks a lot for your help!
    – Dubl
    Jun 1 at 16:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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