Created
May 2, 2012 05:43
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#(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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
\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