Skip to content

Instantly share code, notes, and snippets.

@mwitmer
Created May 2, 2012 05:43
Show Gist options
  • Save mwitmer/2574183 to your computer and use it in GitHub Desktop.
Save mwitmer/2574183 to your computer and use it in GitHub Desktop.
A lilypond extension for generating a score and parts from a simple description
#(define paper-size "letter")
\include "ly-score.ly"
#(set-paper-staff-size scorepap 13)
#(ly-score:process "wind-quintet"
`((copyright "Copyright 2012, Mark Witmer") (title "Wind Quintet") (composer "Mark Witmer"))
`((title "Serpents") (tagline "Copyright 2012") (composer "Mark Witmer"))
`(("mvt" ()))
'(StaffGroup "Main Score" flute oboe clarinet-in-b-flat horn bassoon)
#t #t)e
\version "2.14.2"
#(define-markup-command (mainfont layout props text) (markup?) (interpret-markup layout props (markup text)))
#(define-markup-command (secondaryfont layout props text) (markup?) (interpret-markup layout props (markup text)))
#(define ly-score:hide-instrument-names #f)
lyScoreMidi = \midi {}
lyScoreLayout = \layout {
\context{
\Voice
\remove "Forbid_line_break_engraver"
\override Beam #'breakable = ##t
}
}
% A music function that can take a list of parts and combine them into one staff. Still doesn't really work for more than 2 parts
multipartcombine =
#(define-music-function (parser location parts) (ly:music-list?)
(make-part-combine-music parser parts))
% Paper definitions for score and parts
#(define my-papsize (if (defined? 'paper-size) paper-size
(begin (display "Paper size? (l = letter, a = arch a, t = tabloid) ")
(newline)
(let ((papsizechar (read-char)))
(if (eq? papsizechar #\a) "arch a" (if (eq? papsizechar #\l) "letter" "11x17"))))))
scorepap = \paper {
#(set-paper-size my-papsize)
short-indent = 15\mm
two-sided = ##t
top-markup-spacing =
#'((basic-distance . 10)
(minimum-distance . 5)
(padding . 5))
markup-system-spacing =
#'((basic-distance . 30)
(minimum-distance . 20)
(padding . 5))
system-system-spacing =
#'((basic-distance . 25)
(minimum-distance . 20)
(padding . 5))
}
partpap = \paper {
#(set-paper-size my-papsize)
left-margin = 20
system-system-spacing =
#'((basic-distance . 8)
(minimum-distance . 6)
(padding . 5))
}
#(define set-paper-staff-size (lambda (pap sz)
(let ((new-scope (ly:output-def-scope pap)))
(layout-set-absolute-staff-size-in-module new-scope
(* sz (eval 'pt new-scope))))))
#(set-paper-staff-size scorepap 12)
#(set-paper-staff-size partpap 20)
#(define ly-score:transpose (lambda (music note)
(let ((pitch (ly:music-property (car (ly:music-property (car (ly:music-property note 'elements)) 'elements)) 'pitch)))
(ly:music-transpose music (ly:pitch-negate pitch)))))
% Helper functions
#(define ly-score:alist->module (lambda (alist) (let ((mod (make-module))) (map (lambda (list-el) (module-define! mod (car list-el) (cadr list-el))) alist) mod)))
#(define ly-score:combined-part-numbers (lambda (n)
(string-append (number->string (car n)) ", " (number->string (cdr n)))))
#(define ly-score:part-creator (lambda (file key name)
(lambda* (prefix head reversed-movements number)
(let ((filename (string-append prefix "-" (if number (string-append file (number->string number)) file))))
(module-define! head 'instrument (markup #:secondaryfont (if number (string-append name " " (number->string number)) name)))
(ly:book-process (apply ly:make-book partpap head
(map (lambda (el)
(ly-score:make-score (car el) (cadr el) `(Parallel
,(if number (string-append file (number->string number)) file)
,(if number (cons key number) key)) #f #t))
reversed-movements)) partpap lyScoreLayout filename)))))
#(define ly-score:create-file (lambda (key)
(display (string-append "Creating missing file: " key "."))
(newline)
(let ((newfile (open-file key "w")))
(display "" newfile)
(close-port newfile))))
#(define ly-score:include (let ((parsed (make-hash-table)))
(lambda (folder file)
(if (ly:moment? folder)
#{ \mark \markup Tacet #}
(let* (
(key (string-append folder "/" file ".ly"))
(music (hash-ref parsed key)))
(if music
(ly:music-deep-copy music)
(begin (if (not (file-exists? key))
(ly-score:create-file key))
(let ((new-music #{ \include $key #}))
(hash-set! parsed key new-music)
(ly:music-deep-copy new-music)))))))))
#(define ly-score:tacet-staff (lambda ()
#{
\new Staff \with { \remove "Staff_symbol_engraver" }{
\override Staff.TimeSignature #'transparent = ##t
\override Staff.InstrumentName #'transparent = ##t
\override Staff.Clef #'transparent = ##t
{s8^\markup \secondaryfont Tacet}
}
#}))
% Define staff creators. These functions return a new function that takes a symbol as an argument and returns another function corresponding to that symbol
% 'combine returns a function that creates a staff with a number of parts on the instrument combined
% 'staff returns a single staff for a single part
% 'make-part creates the pdf file for a single part
#(define ly-score:piano-staff-creator (lambda (key file name shortName midi)
(lambda (method)
(let* ((staff (lambda* (folder number transpose? is-full-score?)
(let ((music-right (ly-score:include folder (string-append (if number (string-append file (number->string number)) file) "-right")))
(music-left (ly-score:include folder (string-append (if number (string-append file (number->string number)) file) "-left"))))
(if (and (= (ly:moment-main-numerator (ly:music-length music-left)) 0) (= (ly:moment-main-numerator (ly:music-length music-right)) 0))
'()
#{
\new PianoStaff {
$(if (not ly-score:hide-instrument-names) #{
\set PianoStaff.shortInstrumentName = $(markup #:secondaryfont shortName)
\set PianoStaff.instrumentName = $(markup #:secondaryfont name)
#})
\set PianoStaff.midiInstrument = $midi
<<
\new Staff = $(string-append file "-right") \with {
\consists "Span_arpeggio_engraver"
}{
\override Staff.VerticalAxisGroup #'minimum-Y-extent = #'(-5 . 5)
#(set-accidental-style 'neo-modern)
\clef treble $music-right
}
\new Staff = $(string-append file "-left") \with {
\consists "Span_arpeggio_engraver"
}{
\override Staff.VerticalAxisGroup #'minimum-Y-extent = #'(-5 . 5)
#(set-accidental-style 'neo-modern)
\clef bass $music-left
}
>>
}
#}))))
(make-part (ly-score:part-creator file key name)))
(case method
((combine) (ly:error "Cannot combine piano staves into one staff"))
((staff) staff)
((make-part) make-part)
(else (ly:error (string-append "Unknown method " (symbol->string method) " called on staff creator"))))))))
#(define ly-score:single-staff-creator (lambda (key file name shortName midi clef transpose)
(lambda (method)
(let* ((combine (lambda* (folder numbers transpose? is-full-score?)
(let ((combined-music #{ \multipartcombine $(map (lambda (n)
(let ((music (ly-score:include folder (string-append file (number->string n)))))
(if transpose? (ly-score:transpose music transpose) music)))
(list (car numbers) (cdr numbers))) #}))
(if (= (ly:moment-main-numerator (ly:music-length combined-music)) 0)
'()
#{
\new Staff = $file {
\clef $clef
\set Staff.soloText = $(number->string (car numbers))
\set Staff.soloIIText = $(number->string (cdr numbers))
\set Staff.aDueText = $(string-append (number->string (car numbers)) "," (number->string (cdr numbers)))
\set Staff.midiInstrument = $midi
#(if (not ly-score:hide-instrument-names) #{
\set Staff.instrumentName = $(markup #:secondaryfont (string-append name " " (ly-score:combined-part-numbers numbers)))
\set Staff.instrumentName = $(markup #:secondaryfont shortName)
#})
\override Staff.VerticalAxisGroup #'minimum-Y-extent = #'(-5 . 5)
#(set-accidental-style 'modern-cautionary)
#(if is-full-score? #{ \removeWithTag #'part $combined-music #} #{ \keepWithTag #'part $combined-music #})
}
#}))))
(staff (lambda* (folder number transpose? is-full-score?)
(let* (
(no-part-music (ly-score:include folder (if number (string-append file (number->string number)) file)))
(music (if is-full-score? #{ \removeWithTag #'part $no-part-music #} #{ \keepWithTag #'part $no-part-music #})))
(if (= (ly:moment-main-numerator (ly:music-length music)) 0) '()
#{
\new Staff = $(if number (string-append file (number->string number)) file) {
$(if (not ly-score:hide-instrument-names) #{
\set Staff.instrumentName = $(if number (markup #:secondaryfont name " " (number->string number)) (markup #:secondaryfont name))
\set Staff.shortInstrumentName = $(if number (markup #:secondaryfont shortName " " (number->string number)) (markup #:secondaryfont shortName))
#})
\set Staff.midiInstrument = $midi
\clef $clef
\override Staff.VerticalAxisGroup #'minimum-Y-extent = #'(-5 . 5)
#(set-accidental-style 'modern-cautionary)
\compressFullBarRests
$(if transpose? (ly-score:transpose music transpose) music)
}
#}))))
(make-part (ly-score:part-creator file key name)))
(case method
((combine) combine)
((staff) staff)
((make-part) make-part)
(else (ly:error (string-append "Unknown method " (symbol->string method) " called on staff creator"))))))))
% Define a table to store staff creators
#(define ly-score-private:instrument-defs (make-hash-table))
#(define ly-score:register-instrument (lambda (key creator)
(hash-set! ly-score-private:instrument-defs key creator)))
#(define ly-score:instrument-defs-lookup (lambda (key)
(let ((inst (hash-ref ly-score-private:instrument-defs key)))
(if (not inst)
(ly:error (string-append "Missing instrument: " (symbol->string key))))
inst)))
% Define some built-in staff creators
#(map (lambda (l) (ly-score:register-instrument (car l) (cadr l)))
`((violin ,(ly-score:single-staff-creator 'violin "violin" "Violin" "Vl" "violin" "treble" #{ c' #}))
(viola ,(ly-score:single-staff-creator 'viola "viola" "Viola" "Va" "violin" "alto" #{ c' #}))
(cello ,(ly-score:single-staff-creator 'cello "cello" "Cello" "Vc" "cello" "bass" #{ c' #}))
(contrabass ,(ly-score:single-staff-creator 'contrabass "contrabass" "Contrabass" "Cb" "celo" "bass" #{ c' #}))
(piccolo ,(ly-score:single-staff-creator 'piccolo "piccolo" "Piccolo" "Pic" "flute" "treble" #{ c' #}))
(flute ,(ly-score:single-staff-creator 'flute "flute" "Flute" "Fl" "flute" "treble" #{ c' #}))
(alto-flute ,(ly-score:single-staff-creator 'alto-flute "alto-flute" "Alto Flute" "A. Fl" "flute" "treble" #{ g #}))
(clarinet-in-e-flat ,(ly-score:single-staff-creator 'clarinet-in-e-flat "clarinet-in-e-flat" (markup "E" #:flat " Clarinet") (markup "Cl(E" #:flat ")") "clarinet" "treble" #{ ees' #}))
(clarinet-in-b-flat ,(ly-score:single-staff-creator 'clarinet-in-b-flat "clarinet-in-b-flat" (markup "B" #:flat " Clarinet") (markup "Cl(B" #:flat ")") "clarinet" "treble" #{ bes #}))
(clarinet-in-a ,(ly-score:single-staff-creator 'clarinet-in-a "clarinet-in-a" "A Clarinet" "Cl(A)" "clarinet" "treble" #{ a #}))
(bass-clarinet ,(ly-score:single-staff-creator 'bass-clarinet "bass-clarinet" "Bass Clarinet""B. Cl." "clarinet" "treble" #{ bes, #}))
(oboe ,(ly-score:single-staff-creator 'oboe "oboe" "Oboe" "Ob" "oboe" "treble" #{ c' #}))
(english-horn ,(ly-score:single-staff-creator 'english-horn "english-horn" "English Horn""En Hn" "oboe" "treble" #{ f #}))
(bassoon ,(ly-score:single-staff-creator 'bassoon "bassoon" "Bassoon" "Bs" "bassoon" "bass" #{ c' #}))
(contrabassoon ,(ly-score:single-staff-creator 'contrabassoon "contrabassoon" "Contraassoon""Ctrb" "bassoon" "bass" #{ c' #}))
(trumpet-in-d ,(ly-score:single-staff-creator 'trumpet-in-d "trumpet-in-d" "Trumpet in D""Tr(D)" "trumpet" "treble" #{ d' #}))
(trumpet-in-c ,(ly-score:single-staff-creator 'trumpet-in-c "trumpet-in-c" "Trumpet" "Tr" "trumpet" "treble" #{ c' #}))
(horn ,(ly-score:single-staff-creator 'horn "horn" "Horn" "Hn" "french horn" "treble" #{ f #}))
(trombone ,(ly-score:single-staff-creator 'trombone "trombone" "Trombone" "Trb" "trombone" "bass" #{ c' #}))
(tuba ,(ly-score:single-staff-creator 'tuba "tuba" "Tuba" "Tb" "tuba" "bass" #{ c' #}))
(piano ,(ly-score:piano-staff-creator 'piano "piano" "Piano" "Pn" "acoustic grand"))))
% Returns a staff that contains information global to a score. There must be a file called "time_signature.ly" with this information in every movement's folder
#(define ly-score:time-signature (lambda (folder) #{
\new Staff \with { \override VerticalAxisGroup #'remove-empty = ##t \override VerticalAxisGroup #'remove-first = ##t } $(ly-score:include folder "time_signature")
#}))
% Create parallel music or a specified context for a list of instrument specifications
#(define ly-score:make-music (lambda (instruments folder is-transposed? is-full-score?)
(let ((music (ly-score:make-parallel-staves (cddr instruments) folder is-transposed? is-full-score?)))
(if (eq? (car instruments) 'Parallel) music (context-spec-music music (car instruments) (cadr instruments))))))
% Create an individual staff from an instrument specification
#(define ly-score:make-staff (lambda (instrument folder is-transposed? is-full-score?)
(if (pair? instrument)
(if (pair? (cdr instrument))
(((ly-score:instrument-defs-lookup (car instrument)) 'combine) folder (cdr instrument) is-transposed? is-full-score?)
(((ly-score:instrument-defs-lookup (car instrument)) 'staff) folder (cdr instrument) is-transposed? is-full-score?))
(((ly-score:instrument-defs-lookup instrument) 'staff) folder #f is-transposed? is-full-score?))))
% Create parallel music for a list of instrument specifications
#(define ly-score:make-parallel-staves (lambda (instruments folder is-transposed? is-full-score?)
(make-simultaneous-music (filter (lambda (el) (not (null? el))) (map (lambda (instrument)
(if (list? instrument)
(ly-score:make-music instrument folder is-transposed? is-full-score?)
(ly-score:make-staff instrument folder is-transposed? is-full-score?))) instruments)))))
% Create a score for a given instrument specification, folder, and movement title
#(define ly-score:make-score (lambda (folder title instruments is-full-score? is-transposed?)
(if (not (file-exists? folder))
(begin
(display (string-append "Creating directory: " folder))
(newline)
(mkdir folder)))
(let* ((my-time-signature (ly-score:time-signature folder))
(my-music-timeless (ly-score:make-music instruments folder is-transposed? is-full-score?))
(my-length (ly:music-length my-time-signature))
(my-music-with-time
(if (and (= (ly:moment-main-numerator (ly:music-length my-music-timeless)) 0) (not is-full-score?))
(ly-score:tacet-staff) my-music-timeless))
(my-music (make-simultaneous-music (list my-time-signature my-music-with-time)))
(my-midi (ly:output-def-clone lyScoreMidi))
(my-score (ly:make-score my-music)))
(ly:score-set-header! my-score (ly-score:alist->module title))
(if is-full-score? (ly:score-add-output-def! my-score my-midi))
(ly:score-add-output-def! my-score (ly:output-def-clone lyScoreLayout))
my-score)))
% Recursive function to go through the instrument specification and extract parts
#(define ly-score:process-part (lambda (prefix head reversed-movements instrument)
(if (list? instrument)
(for-each (lambda (instr) (ly-score:process-part prefix head reversed-movements instr)) (cddr instrument))
(if (pair? instrument)
(if (pair? (cdr instrument))
(map (lambda (n) (((ly-score:instrument-defs-lookup (car instrument)) 'make-part) prefix head reversed-movements n)) (list (cadr instrument) (cddr instrument)))
(((ly-score:instrument-defs-lookup (car instrument)) 'make-part) prefix head reversed-movements (cdr instrument)))
(((ly-score:instrument-defs-lookup instrument) 'make-part) prefix head reversed-movements #f)))))
#(define adjustvib #t)
% Create a score and parts from the given information
#(define ly-score:process (lambda (prefix scorehead parthead movements instruments transpose? include-parts?)
(ly:book-process (apply ly:make-book scorepap (ly-score:alist->module scorehead) (map (lambda (el) (ly-score:make-score (car el) (cadr el) instruments #t transpose?)) (reverse movements))) partpap lyScoreLayout prefix)
(set! adjustvib #f)
(if include-parts? (ly-score:process-part prefix (ly-score:alist->module parthead) (reverse movements) instruments))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment