Hello Pondmates,

as I am going to engrave a small mass for a friend I’ve sketched a small set 
of functions that should make engraving pieces with many movements of very 
similar structure much more fun.

Previously when doing such things I would have files for each movement defining 
namesspaced variables, providing different parts, and then combine them in a 
master using \includes.

This sketch here replaces this by a registering interface. Suppose we have a 
mass. Then this allows us to do for each part (possibly in an included file or 
even multiple included files):

\registerPart #'kyrie
\partSetTitle #'kyrie "Kyrie"

\partSetGlobal #'kyrie {  ... global block ... }
\partSetCGlobal #'kyrie { \dynamicUp }

\partSetStaff #'gloria #'flute {
  music
}

... the same for all instruments and voices ...

\partAddLyrics #'kyrie #'soprano \lyricmode { lyrics }

... the same for all lyrics ...

This then registers the content of each part. If we want to print the score we 
can do

\printParts layout

where layout is a function turning a part into a score. These layout functions 
could be defined in included files, allowing us to fully separate content and 
form. Creating instrument parts is then nothing but using a specific layout 
function.

This is something just sketched up now. It works, but it surely could be 
expanded to fit more needs (but it can easily be expanded, modified and 
adapted).

I still hope this might prove useful or educational for some of you.

Cheers,
Valentin
% Given a nested alist sets a value by a path. Does not guaratee to mutate the alist!
#(define (nested-assoc-set! alist path value)
   (if (null? path)
       value
       (assoc-set! alist (car path) (nested-assoc-set! (assoc-get (car path) alist '()) (cdr path) value))))

% Given a nested alist gets a value by a path
#(define* (nested-assoc-get alist path #:optional (default '()))
   (if (null? path)
       alist
       (let ((q (assoc-get (car path) alist default)))
         (if (list? q)
             (nested-assoc-get q (cdr path) default)
             q))))

% Container for our parts. An alist.
#(define parts
   (if (defined? 'parts)
       parts
       '()))

% Assigns a new part to our container. Not strictly necessary, but sets the 'order property
registerPart =
#(define-void-function (partname) (symbol?)
   (set! parts (assoc-set! parts partname (list (cons 'order (length parts))))))

% Assign the title as markup of a given part.
partSetTitle =
#(define-void-function (partname title) (symbol? markup?)
   (set! parts (nested-assoc-set! parts (list partname 'title) title)))

% Return the title of a given part as markup
partGetTitle =
#(define-scheme-function (partname) (symbol?)
   (nested-assoc-get parts (list partname 'title) #f))

% Add a (named) piece of music (such as flute, violin, ...) to a part
partSetStaff =
#(define-void-function (partname staff mus) (symbol? symbol? ly:music?)
   (set! parts (nested-assoc-set! parts (list partname 'staves staff) mus)))

% Returns a (named) piece of music (such as flute, violin, ...) from a part
partGetStaff =
#(define-music-function (partname staff) (symbol? symbol?)
   (nested-assoc-get parts (list partname 'staves staff) (empty-music)))

% Assign an instrument name to a staff
partSetStaffName =
#(define-void-function (partname staff name) (symbol? symbol? markup?)
   (set! parts (nested-assoc-set! parts (list partname 'staff-names staff) name)))

% Returns the instrument name of a staff. May take a default value.
partGetStaffName =
#(define-scheme-function (default partname staff) ((markup? #f) symbol? symbol?)
   (nested-assoc-get parts (list partname 'staff-names staff) default))

% Adds a new line of lyrics to a staff. May optionally specify
% an alist props to provide values such as 'associatedVoice (default: voice-... where ... is
% the name of the music.
partAddLyrics =
#(define-void-function (partname staff props mus) (symbol? symbol? (list? '()) ly:music?)
   (set! parts (nested-assoc-set! parts
                                  (list partname 'lyrics staff)
                                  (cons `((lyrics . ,mus) . ,props)
                                        (partGetLyrics partname staff)))))

% Returns the lyrics associated with staff. This is a list of
% alists with keys 'lyrics and optional keys specified during \partAddLyrics
partGetLyrics =
#(define-scheme-function (partname staff) (symbol? symbol?)
   (nested-assoc-get parts (list partname 'lyrics staff) '()))

#(define (context-mod-or-procedure? x) (or (ly:context-mod? x) (procedure? x)))

% Creates Lyrics contexts for a staff. May optionally specify a
% context mod or a procedure taking the lyrics alist as argument and returning a context mod
partFormatLyrics =
#(define-music-function (with partname staff)
   ((context-mod-or-procedure? (ly:make-context-mod)) symbol? symbol?)
   (make-music 'SimultaneousMusic
               'elements
               (map (lambda (x)
                      (let ((lyr (assoc-get 'lyrics x))
                            (assoc-voice (assoc-get 'associatedVoice x (format #f "voice-~a" staff)))
                            (thiswith (if (ly:context-mod? with) with (with x))))
                        #{ \new Lyrics \with #thiswith \lyricsto #assoc-voice $lyr #}))
                    (reverse (partGetLyrics partname staff)))))

% Assigns a global block to a part
partSetGlobal =
#(define-void-function (partname mus) (symbol? ly:music?)
   (set! parts (nested-assoc-set! parts (list partname 'global) mus)))

% Returns the global block of a part
partGetGlobal =
#(define-music-function (partname) (symbol?)
   (nested-assoc-get parts (list partname 'global) (empty-music)))

% Assigns a (choir) global block to a part
partSetCGlobal =
#(define-void-function (partname mus) (symbol? ly:music?)
   (set! parts (nested-assoc-set! parts (list partname 'cglobal) mus)))

% Returns the (choir) global block of a part
partGetCGlobal =
#(define-music-function (partname) (symbol?)
   (nested-assoc-get parts (list partname 'cglobal) (empty-music)))

% Create a Staff from a staff. May optionally specify a context mod
% and a preparation block. Makes use of the global block
partCreateStaff =
#(define-music-function (with prep partname staffname)
   ((ly:context-mod? (ly:make-context-mod)) (ly:music? (empty-music)) symbol? symbol?)
   (let ((staff (partGetStaff partname staffname)))
     (if (not (null? (ly:music-property staff 'types)))
         #{
           \new Staff = #(format #f "staff-~a" staffname) \with #with
           { \partGetGlobal #partname #prep #staff }
         #}
         (empty-music))))

% Create a (choir) Staff from staff. May optionally specify a context mod
% and a preparation block. Makes use of the (choir) global block
partCreateCStaff =
#(define-music-function (with prep partname staffname)
   ((ly:context-mod? (ly:make-context-mod)) (ly:music? (empty-music)) symbol? symbol?)
   (let ((staff (partGetStaff partname staffname)))
     (if (not (null? (ly:music-property staff 'types)))
         #{
           \new Staff = #(format #f "staff-~a" staffname) \with #with
           \new Voice = #(format #f "voice-~a" staffname)
           { \partGetGlobal #partname \partGetCGlobal #partname #prep #staff }
         #}
         (empty-music))))

% Create a score from a part name and a layout function.
partPrint =
#(define-scheme-function (partname layout) (symbol? procedure?)
   (layout partname))

% Print selection of parts of all parts using a layout function.
printParts =
#(define-void-function (sel layout)
   ((list? #f) procedure?)
   (if (not sel)
       (set! sel
             (map car
                  (sort parts
                        (lambda (x y)
                          (< (assoc-get 'order (cdr x))
                             (assoc-get 'order (cdr y))))))))
   (map (lambda (x) (add-score (partPrint x layout))) sel))


%%%%%% END LIBRARY

% Layout function for a full-score
#(define (full-score partname)
   #{
     \score {
       \header {
         title = \partGetTitle #partname
       }
       <<
         \new StaffGroup <<
           \partCreateStaff
             \with { instrumentName = \partGetStaffName "Flute" #partname #'flute }
             #partname #'flute
           \partCreateStaff
             \with { instrumentName = \partGetStaffName "Oboe" #partname #'oboe }
             #partname #'oboe
         >>
         \new ChoirStaff <<
           \partCreateCStaff
             \with { instrumentName = \partGetStaffName "S" #partname #'soprano }
             #partname #'soprano
           \partFormatLyrics #partname #'soprano
           \partCreateCStaff
             \with { instrumentName = \partGetStaffName "A" #partname #'alto }
             #partname #'alto
           \partFormatLyrics #partname #'alto
           \partCreateCStaff
             \with { instrumentName = \partGetStaffName "T" #partname #'tenor }
             \clef "treble_8" #partname #'tenor
           \partFormatLyrics #partname #'tenor
           \partCreateCStaff
             \with { instrumentName = \partGetStaffName "B" #partname #'bass }
             \clef bass #partname #'bass
           \partFormatLyrics #partname #'bass
         >>
       >>
     }
   #})

% Layout function for a choir score
#(define (choir-score partname)
   #{
     \score {
       \header {
         title = \partGetTitle #partname
       }
       <<
         \new ChoirStaff <<
           \partCreateCStaff \with { instrumentName = "S" } #partname #'soprano
           \partFormatLyrics #partname #'soprano
           \partCreateCStaff \with { instrumentName = "A" } #partname #'alto
           \partFormatLyrics #partname #'alto
           \partCreateCStaff \with { instrumentName = "T" } \clef "treble_8" #partname #'tenor
           \partFormatLyrics #partname #'tenor
           \partCreateCStaff \with { instrumentName = "B" } \clef bass #partname #'bass
           \partFormatLyrics #partname #'bass
         >>
       >>
     }
   #})

% Layout function factory for parts
#(define* ((make-part-layout staff instrument #:optional (with (ly:make-context-mod))) partname)
   #{
     \score {
       \header {
         title = \partGetTitle #partname
         instrument = #instrument
       }
       \partCreateStaff \with #with #partname #staff
     }
   #})

\paper {
  print-all-headers = ##t
}

\registerPart #'kyrie
\partSetTitle #'kyrie "Kyrie"
\partSetStaff #'kyrie #'flute {
  c'4 d' e' f'
}

\partSetStaff #'kyrie #'soprano {
  c'2 d'8 e' f'4
}

\partAddLyrics #'kyrie #'soprano \lyricmode { Ky -- ri -- e e }
\partAddLyrics #'kyrie #'soprano \lyricmode { Chri -- _ ste e }

\partSetStaff #'kyrie #'alto {
  c'2 b
}

\partAddLyrics #'kyrie #'alto \lyricmode { Ky -- rie }
\partAddLyrics #'kyrie #'alto \lyricmode { Chri -- ste }

\markup "Test \partGetTitle:"
\partGetTitle #'kyrie
\markup "Test \partGetStaff:"
\partGetStaff #'kyrie #'flute
\markup "Test \partCreateStaff:"
\partCreateStaff #'kyrie #'flute
\partCreateStaff \with { instrumentName = "Flute" } \clef bass #'kyrie #'flute

\markup "Test \partPrint:"
\partPrint #'kyrie #full-score
\partPrint #'kyrie #choir-score


\registerPart #'gloria
\partSetTitle #'gloria "Gloria"

\partSetGlobal #'gloria { \key cis\minor \time 3/4 }
\partSetCGlobal #'gloria { \dynamicUp }

\partSetStaff #'gloria #'flute {
  cis'4 dis' e' fis'
}

\partSetStaff #'gloria #'soprano {
  cis'2\mp\< dis'8 e' fis'4\mf
}

\partSetStaffName #'gloria #'soprano "Solo"

\partAddLyrics #'gloria #'soprano \lyricmode { Glo -- ri -- a in }


\bookpart {
  \markup "Test \printParts, \partSetGlobal, \partSetCGlobal, \partSetStaffName:"
  \printParts #full-score
}

\bookpart {
  \markup "Test factory:"
  \printParts #(make-part-layout 'flute "Flute")
}

\bookpart {
  \markup "Test factory with with:"
  \printParts #(make-part-layout 'flute "Flute" #{\with { \clef "bass" }#})
}

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to