Select Git revision
orchestrallily.ly
orchestrallily.ly 28.54 KiB
\version "2.11.41"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% OrchestralLily
% ==============
% Desciption: Lilypond package to make writing large orchestral scores easier.
% Documentation: http://wiki.kainhofer.com/lilypond/orchestrallily
% Version: 0.01, 2008-03-02
% Author: Reinhold Kainhofer, reinhold@kainhofer.com
% Copyright: (C) 2008 by Reinhold Kainhofer
% License: GPL v3.0, http://www.gnu.org/licenses/gpl.html
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(use-modules (ice-9 match))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% SCORE STRUCTURE AND AUTOMATIC GENERATION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Helper functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Helper function to filter all non-null entries
#(define (not-null? x) (not (null? x)))
% Helper function to extract a given variable, built from [Piece][Instrument]Identifier
#(define (namedPieceInstrObject piece instr name)
(let* (
(fullname (string->symbol (string-append piece instr name)))
(instrname (string->symbol (string-append instr name)))
(piecename (string->symbol (string-append piece name)))
)
(cond
((defined? fullname) (primitive-eval fullname))
((defined? instrname) (primitive-eval instrname))
((defined? piecename) (primitive-eval piecename))
(else '())
)
)
)
%% Print text as a justified paragraph, taken from the lilypond Notation Reference
#(define-markup-list-command (paragraph layout props args) (markup-list?)
(let ((indent (chain-assoc-get 'par-indent props 2)))
(interpret-markup-list layout props
(make-justified-lines-markup-list (cons (make-hspace-markup indent)
args)))))
conditionalBreak = #(define-music-function (parser location) ()
#{ \tag #'instrumental-score \pageBreak #}
)
#(define (oly:piece-title-markup title) (markup #:column (#:line (#:fontsize #'3 #:bold title))) )
#(define-markup-command (piece-title layout props title) (markup?)
; (toc-item title)
(interpret-markup layout props (oly:piece-title-markup title))
)
#(define (oly:generate_object_name piece instr obj )
(if (and (string? piece) (string? instr) (string? obj))
(string-append piece instr obj)
#f )
)
#(define (oly:generate_staff_name piece instr) (oly:generate_object_name piece instr "St"))
#(define (set-context-property context property value)
(set! (ly:music-property context property) value)
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Score structure
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#(define oly:orchestral_score_structure '())
#(define (oly:set_score_structure struct)
(if (list? struct)
(set! oly:orchestral_score_structure struct)
(ly:warning (_ "oly:set_score_structure needs an association list as argument!"))
)
)
orchestralScoreStructure = #(define-music-function (parser location structure) (list?)
(oly:set_score_structure structure)
(make-music 'Music 'void #t)
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Automatic staff and group generation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Retrieve all music definitions for the given
#(define (oly:get_music_objects piece name instruments)
(filter not-null? (map (lambda (i) (namedPieceInstrObject piece i "Music")) instruments))
)
#(define (oly:make_staff_internal piece name music)
(let* (
(tempo (namedPieceInstrObject piece name "Tempo"))
(lyrics (namedPieceInstrObject piece name "Lyrics"))
(musiccontent '())
)
;(ly:message "Music in make_staff_internal (piece=~S, name=~S)" piece name)
(if (ly:music? lyrics)
(set! musiccontent (append musiccontent (list dynamicUp)))
(if (not-null? lyrics) (ly:warning (_ "Wrong type (no lyrics) for lyrics for instrument ~S in piece ~S") name piece))
)
; Append the settings, key and clef (if defined)
(map
(lambda (type)
(let* ((object (namedPieceInstrObject piece name type)))
(if (ly:music? object)
(set! musiccontent (append musiccontent (list object)))
(if (not-null? object) (ly:warning (_ "Wrong type (no ly:music) for ~S for instrument ~S in piece ~S") type name piece))
)
)
)
'("Settings" "Key" "Clef")
)
(if (ly:music? music)
(begin
(set! musiccontent (append musiccontent (list music)))
;(ly:message "Generating staff for ~a" name)
(let* (
(voicename (oly:generate_object_name piece name "Voice" ))
(staffname (oly:generate_staff_name piece name)) (voice (context-spec-music (make-simultaneous-music musiccontent) 'Voice voicename))
(staff '())
(staffcont (list voice))
(propops (oly:make_staff_properties piece name))
)
; If we have lyrics, create a lyrics context containing LyricCombineMusic
; and add that as second element to the staff's elements list...
(if (ly:music? lyrics)
(let* (
(lyricsname (oly:generate_object_name piece name "Lyrics" ))
(lyricscont (make-music 'LyricCombineMusic 'element lyrics 'associated-context voicename))
)
(set! staffcont (append staffcont
(list (context-spec-music lyricscont 'Lyrics lyricsname))))
)
)
(set! staff (context-spec-music (make-simultaneous-music staffcont) 'Staff staffname))
(if (not-null? propops)
(set! (ly:music-property staff 'property-operations) propops)
)
staff
)
)
; For empty music, return empty
'()
)
)
)
% TODO: Implement one-staff with multiple voices (\voiceOne and \voiceTwo),
% possibly with lyrics attached
#(define (oly:make_multi_voice_staff parser piece instr voices)
(ly:message "Generating a staff with multiple voices is not yet implemented, using 'SimultaneousMusic instead. Staff ID: ~a, voices: ~a" instr voices)
(oly:make_parallel_staves parser piece instr voices)
)
#(define (oly:make_staff parser piece instr)
(let* ( (music (namedPieceInstrObject piece instr "Music")) )
;(ly:message "make_staff: ~S ~S" piece instr)
(if (not-null? music)
(oly:make_staff_internal piece instr music)
'()
)
)
)
#(define (oly:make_parallel_staves parser piece instr instruments)
(let* (
(staves (map (lambda (i) (oly:create_staff_or_group parser piece i)) instruments))
(nonemptystaves (filter not-null? staves))
)
(if (not-null? nonemptystaves)
(make-simultaneous-music nonemptystaves)
'()
)
)
)
#(define (oly:make_part_combined_staff parser piece instr instruments)
(let* ((music (oly:get_music_objects piece instr instruments)))
;(ly:message "make_part_combined_staff: ~S ~S ~a" piece instr instruments)
(cond
((and (pair? music) (ly:music? (car music)) (not-null? (cdr music)) (ly:music? (cadr music)))
;(ly:message "Part-combine with two music expressions")
(oly:make_staff_internal piece instr (make-part-combine-music parser music)))
((null? music)
;(ly:message "Part-combine without any music expressions")
'())
; exactly one is a music expression, simply use that by joining
((list? music) ;(ly:message "Part-combine with only one music expressions")
(oly:make_staff_internal piece instr (apply append music)))
(else
;(ly:message "make_part_combined_staff: ~S ~S ~a" piece instr instruments)
'() )
)
)
)
#(define (oly:create_staff_or_group parser piece instr)
(let* ( (staff (namedPieceInstrObject piece instr "Staff")) )
;(if (not-null? staff)
; (ly:message "Found staff variable for instrument ~a in piece ~a" instr piece)
; (ly:message "Staff variable for instrument ~a in piece ~a NOT FOUND" instr piece)
;)
(if (not-null? staff)
; Explicit staff variable, use that
staff
; no staff defined => check hierarchy definition and match a possible entry
; against the four allowed forms
(match (assoc-ref oly:orchestral_score_structure instr)
; type (ParallelMusic|SimultaneousMusic ("sub" "instruments"...))
; generate << ... >>
( ((or 'ParallelMusic 'SimultaneousMusic) (? list? subinstr))
(oly:make_parallel_staves parser piece instr subinstr) )
; type (StaffGroupType ("sub" "instruments"...)), generate staff group
( ((? symbol? stafftype) (? list? subinstr))
(oly:make_staff_group parser piece instr stafftype subinstr) )
; type (#t ("sub" "instruments")) => part-combined staff
( (#t ((? string? instone) (? string? insttwo)))
(oly:make_part_combined_staff parser piece instr (list instone insttwo)) )
; type (#f ("sub" "instruments")) => staff with multiple voices
( (#f (? list? subinstr))
(oly:make_multi_voice_staff parser piece instr subinstr) )
; not found in score history -> simple staff
( #f
(oly:make_staff parser piece instr) )
; Any other type is invalid, treat it like a simple staff!
( invalidform
(ly:warning (_ "Encountered illegal entry ~S in score structure for instrument ~a") invalidform instr)
(ly:warning (_ "Trying to interpret ~S as a simple instrument") instr)
(oly:make_staff parser piece instr) )
)
)
)
)
% Given a property name and the extensions, either generate the pair to set
% the property or an empty list, if no pre-defined variable could be found
#(define (oly:generate_property_pair prop piece instr type)
(let* ((val (namedPieceInstrObject piece instr type)))
(if (not-null? val) (list 'assign prop val) '() )
)
)
% Generate the properties for the staff for piece and instr. Typically, these
% are the instrument name and the short instrument name (if defined).
% return a (possibly empty) list of all assignments.
#(define (oly:make_staff_properties piece instr)
(let* (
(mapping '(
(instrumentName . "InstrumentName")
(shortInstrumentName . "ShortInstrumentName")
(midiInstrument . "MidiInstrument")
))
(assignments (map (lambda (pr)
(oly:generate_property_pair (car pr) piece instr (cdr pr))
)
mapping))
(props (filter not-null? assignments))
)
(display props)
props
)
)
#(define (oly:make_staff_group parser piece instr stafftype instruments)
(let* (
(staves (oly:make_parallel_staves parser piece instr instruments))
)
(if (not-null? staves)
(let* (
(staffname (oly:generate_staff_name piece instr))
(group (context-spec-music staves stafftype staffname))
(propops (oly:make_staff_properties piece instr))
)
(set! (ly:music-property group 'property-operations) propops)
group
)
; Return empty list if no staves are generated
'()
)
)
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Automatic score generation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% \setUserBook ##t/##f sets a flat to determine whether the calls to createScore
% are from within a book block or not
#(define oly:score_handler collect-scores-for-book)
setUseBook = #(define-music-function (parser location usebook) (boolean?)
(if usebook
(set! oly:score_handler book-score-handler)
(set! oly:score_handler toplevel-score-handler)
)
(make-music 'Music 'void #t)
)
% Two functions to handle midi-blocks: Either don't set one, or set an empty
% one so that MIDI is generated
#(define (oly:set_no_midi_block score) '())
#(define (oly:set_midi_block score)
(let* ((midiblock (if (defined? '$defaultmidi)
(ly:output-def-clone $defaultmidi)
(ly:make-output-def))))
(ly:output-def-set-variable! midiblock 'is-midi #t)
(ly:score-add-output-def! score midiblock)
)
)
% \setCreateMidi ##t/##f sets a flag to determine wheter MIDI output should
% be generated
#(define oly:apply_score_midi oly:set_no_midi_block)
setCreateMIDI = #(define-music-function (parser location createmidi) (boolean?)
(if createmidi
(set! oly:apply_score_midi oly:set_midi_block)
(set! oly:apply_score_midi oly:set_no_midi_block)
)
(make-music 'Music 'void #t)
)
% Two functions to handle layout-blocks: Either don't set one, or set an empty
% one so that a PDF is generated
#(define (oly:set_no_layout_block score) '())
#(define (oly:set_layout_block score)
(let* ((layoutblock (if (defined? '$defaultlayout)
(ly:output-def-clone $defaultlayout)
(ly:make-output-def))))
(ly:output-def-set-variable! layoutblock 'is-layout #t)
(ly:score-add-output-def! score layoutblock)
)
)
% \setCreatePDF ##t/##f sets a flag to determine wheter PDF output should
% be generated
#(define oly:apply_score_layout oly:set_no_layout_block)
setCreatePDF = #(define-music-function (parser location createlayout) (boolean?)
(if createlayout
(set! oly:apply_score_layout oly:set_layout_block)
(set! oly:apply_score_layout oly:set_no_layout_block)
)
(make-music 'Music 'void #t)
)
% Set the piece title in a new header block.
#(define (oly:set_piece_header score piecename)
(if (not-null? piecename)
(let* ((header (make-module)))
(module-define! header 'piece piecename)
(ly:score-set-header! score header)
)
;#f
)
)
% post-filter functions. By default, no filtering is done. However,
% for the *NoCues* function, the cue notes should be killed
identity = #(define-music-function (parser location music) (ly:music?) music)
cuefilter = #(define-music-function (parser location music) (ly:music?)
((ly:music-function-extract removeWithTag) parser location 'cued ((ly:music-function-extract killCues) parser location music))
)
% The helper function to build a score.
#(define (oly:createScoreHelper parser location part instr func)
(let* (
(staves (oly:make_parallel_staves parser part "topLevel" instr))
(music (if (not-null? staves)
((ly:music-function-extract func) parser location staves)
'()
))
(score '())
(piecename (namedPieceInstrObject part (car instr) "PieceName"))
(piecenametacet (namedPieceInstrObject part (car instr) "PieceNameTacet"))
(header '())
)
(if (null? music)
; No staves, print tacet
(begin
(if (not-null? piecenametacet) (set! piecename piecenametacet))
(if (not-null? piecename)
(collect-scores-for-book parser (list (oly:piece-title-markup piecename)))
(ly:warning (_ "No music and no score title found for part ~a and instrument ~a") part instr)
)
)
; we have staves, apply the piecename to the score and add layout/midi blocks if needed
(begin
(set! score (scorify-music music parser))
(oly:set_piece_header score piecename) (oly:apply_score_midi score)
(oly:apply_score_layout score)
; Schedule the score for typesetting
(collect-scores-for-book parser score)
)
)
)
; This is a void function, the score has been schedulled for typesetting already
(make-music 'Music 'void #t)
)
createScore = #(define-music-function (parser location piece instr) (string? list?)
(oly:createScoreHelper parser location piece instr identity)
)
createNoCuesScore = #(define-music-function (parser location piece instr) (string? list?)
(oly:createScoreHelper parser location piece instr cuefilter)
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% CUE NOTES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% set the cue instrument name
setCue = #(define-music-function (parser location instr) (string?)
#{ \set Voice.instrumentCueName = $instr #} )
% generate a cue music section with instrument names
% Parameters: \namedCueDuring NameOfQuote CueDirection CueInstrument OriginalInstrument music
% -) NameOfQuote CueDirection music are the parameters for \cueDuring
% -) CueInstrument and OriginalInstrument are the displayed instrument names
% typical call:
% \namedCueDuring #"vIQuote" #UP #"V.I" #"Sop." { R1*3 }
% This adds the notes from vIQuote (defined via \addQuote) to three measures, prints "V.I" at
% the beginning of the cue notes and "Sop." at the end
namedCueDuring = #(define-music-function (parser location cuevoice direction instrcue instr cuemusic) (string? number? string? string? ly:music?)
#{
\cueDuring #$cuevoice #$direction { \tag #'cued \setCue #$instrcue $cuemusic \tag #'cued \setCue #$instr }
% \tag #'uncued $cuemusic
#}
)
namedTransposedCueDuring = #(define-music-function (parser location cuevoice direction instrcue instr trans cuemusic) (string? number? string? string? ly:music? ly:music?)
#{
\transposedCueDuring #$cuevoice #$direction $trans { \tag #'cued \setCue #$instrcue $cuemusic \tag #'cued \setCue #$instr }
% \tag #'uncued $cuemusic
#}
)
% set the cue instrument name and clef
setClefCue = #(define-music-function (parser location instr clef)
(string? ly:music?)
#{
\once \override Staff.Clef #'font-size = #-3 $clef
\set Voice.instrumentCueName = $instr
#} )
% generate a cue music section with instrument names and clef changes
% Parameters: \cleffedCueDuring NameOfQuote CueDirection CueInstrument CueClef OriginalInstrument OriginalClef music
% -) NameOfQuote CueDirection music are the parameters for \cueDuring
% -) CueInstrument and OriginalInstrument are the displayed instrument names
% -) CueClef and OriginalClef are the clefs for the the cue notes and the clef of the containing voice
% typical call:
% \cleffedCueDuring #"vIQuote" #UP #"V.I" #"treble" #"Basso" #"bass" { R1*3 }
% This adds the notes from vIQuote (defined via \addQuote) to three measures, prints "V.I" at
% the beginning of the cue notes and "Basso" at the end. The clef is changed to treble at the % beginning of the cue notes and reset to bass at the end
cleffedCueDuring = #(define-music-function (parser location cuevoice direction instrcue clefcue instr clefinstr cuemusic)
(string? number? string? ly:music? string? ly:music? ly:music?)
#{
\cueDuring #$cuevoice #$direction { \tag #'cued \setClefCue #$instrcue $clefcue $cuemusic \tag #'cued \setClefCue #$instr $clefinstr }
% \tag #'uncued $cuemusic
#}
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% DYNAMICS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
tempoMark = #(define-music-function (parser location padding marktext) (number? string?)
#{
\once \override Score . RehearsalMark #'padding = $padding
\mark \markup { \bold \smaller $marktext }
#})
shiftDynamics = #(define-music-function (parser location xshift yshift) (number? number?)
#{
\once \override DynamicTextSpanner #'padding = $yshift
\once\override DynamicText #'extra-offset = #(cons $xshift $yshift)
#})
ffz = #(make-dynamic-script "ffz")
pf = #(make-dynamic-script "pf")
sempp = #(make-dynamic-script (markup #:line( #:with-dimensions '(0 . 0)
'(0 . 0) #:right-align #:normal-text #:italic "sempre" #:dynamic "pp")))
parenf = #(make-dynamic-script (markup #:line(#:normal-text #:italic #:fontsize 2 "(" #:dynamic "f" #:normal-text #:italic #:fontsize 2 ")" )))
parenp = #(make-dynamic-script (markup #:line(#:normal-text #:italic #:fontsize 2 "(" #:dynamic "p" #:normal-text #:italic #:fontsize 2 ")" )))
dim = #(make-span-event 'DecrescendoEvent START)
enddim = #(make-span-event 'DecrescendoEvent STOP)
decresc = #(make-span-event 'DecrescendoEvent START)
enddecresc = #(make-span-event 'DecrescendoEvent STOP)
cresc = #(make-span-event 'CrescendoEvent START)
endcresc = #(make-span-event 'CrescendoEvent STOP)
setCresc = {
\set crescendoText = \markup { \italic "cresc." }
\set crescendoSpanner = #'dashed-line
}
setDecresc = {
\set decrescendoText = \markup { \italic "decresc." }
\set decrescendoSpanner = #'dashed-line
}
setDim = {
\set decrescendoText = \markup { \italic "dim." }
\set decrescendoSpanner = #'dashed-line
}
newOrOldClef = #(define-music-function (parser location new old ) (string? string?)
(if (ly:get-option 'old-clefs) #{ \clef $old #} #{ \clef $new #})
)
%%% Thanks to "Gilles THIBAULT" <gilles.thibault@free.fr>, there is a way
% to remove also the fermata from R1-\fermataMarkup: By filtering the music
% and removing the corresponding events.
% Documented as an LSR snippet: http://lsr.dsi.unimi.it/LSR/Item?id=372
#(define (filterOneEventsMarkup event)( let ( (eventname (ly:music-property event 'name)) )
(not
(or ;; add here event name you do NOT want
(eq? eventname 'MultiMeasureTextEvent)
(eq? eventname 'AbsoluteDynamicEvent)
(eq? eventname 'TextScriptEvent)
(eq? eventname 'ArticulationEvent)
(eq? eventname 'CrescendoEvent)
(eq? eventname 'DecrescendoEvent)
)
)
))
filterArticulations = #(define-music-function (parser location music) (ly:music?)
(music-filter filterOneEventsMarkup music)
)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% REST COMBINATION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% REST COMBINING, TAKEN FROM http://lsr.dsi.unimi.it/LSR/Item?id=336
%%
%% Usage:
%% \new Staff \with {
%% \override RestCollision #'positioning-done = #merge-rests-on-positioning
%% } << \somevoice \\ \othervoice >>
%% or (globally):
%% \layout {
%% \context {
%% \Staff
%% \override RestCollision #'positioning-done = #merge-rests-on-positioning
%% }
%% }
%%
%% Limitations:
%% - only handles two voices
%% - does not handle multi-measure/whole-measure rests
#(define (rest-score r)
(let ((score 0)
(yoff (ly:grob-property-data r 'Y-offset))
(sp (ly:grob-property-data r 'staff-position)))
(if (number? yoff)
(set! score (+ score 2))
(if (eq? yoff 'calculation-in-progress)
(set! score (- score 3))))
(and (number? sp)
(<= 0 2 sp)
(set! score (+ score 2))
(set! score (- score (abs (- 1 sp)))))
score))
#(define (merge-rests-on-positioning grob)
(let* ((can-merge #f)
(elts (ly:grob-object grob 'elements)) (num-elts (and (ly:grob-array? elts)
(ly:grob-array-length elts)))
(two-voice? (= num-elts 2)))
(if two-voice?
(let* ((v1-grob (ly:grob-array-ref elts 0))
(v2-grob (ly:grob-array-ref elts 1))
(v1-rest (ly:grob-object v1-grob 'rest))
(v2-rest (ly:grob-object v2-grob 'rest)))
(and
(ly:grob? v1-rest)
(ly:grob? v2-rest)
(let* ((v1-duration-log (ly:grob-property v1-rest 'duration-log))
(v2-duration-log (ly:grob-property v2-rest 'duration-log))
(v1-dot (ly:grob-object v1-rest 'dot))
(v2-dot (ly:grob-object v2-rest 'dot))
(v1-dot-count (and (ly:grob? v1-dot)
(ly:grob-property v1-dot 'dot-count -1)))
(v2-dot-count (and (ly:grob? v2-dot)
(ly:grob-property v2-dot 'dot-count -1))))
(set! can-merge
(and
(number? v1-duration-log)
(number? v2-duration-log)
(= v1-duration-log v2-duration-log)
(eq? v1-dot-count v2-dot-count)))
(if can-merge
;; keep the rest that looks best:
(let* ((keep-v1? (>= (rest-score v1-rest)
(rest-score v2-rest)))
(rest-to-keep (if keep-v1? v1-rest v2-rest))
(dot-to-kill (if keep-v1? v2-dot v1-dot)))
;; uncomment if you're curious of which rest was chosen:
;;(ly:grob-set-property! v1-rest 'color green)
;;(ly:grob-set-property! v2-rest 'color blue)
(ly:grob-suicide! (if keep-v1? v2-rest v1-rest))
(if (ly:grob? dot-to-kill)
(ly:grob-suicide! dot-to-kill))
(ly:grob-set-property! rest-to-keep 'direction 0)
(ly:rest::y-offset-callback rest-to-keep)))))))
(if can-merge
#t
(ly:rest-collision::calc-positioning-done grob))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% TITLE PAGE / HEADER
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\paper {
scoreTitleMarkup = \markup \piece-title \fromproperty #'header:piece
bookTitleMarkup = \markup {
\override #'(baseline-skip . 3.5)
\column {
\override #'(baseline-skip . 3.5)
\column {
\huge \bigger \bold
\fill-line {
\bigger \fromproperty #'header:title
}
\fill-line {
\large \smaller \bold \bigger \fromproperty #'header:subtitle
}
\fill-line {
\smaller \bold
\fromproperty #'header:subsubtitle
}
\fill-line {
{ \large \bold \fromproperty #'header:instrument }
}
\fill-line {
\fromproperty #'header:poet
\fromproperty #'header:composer
}
\fill-line {
\fromproperty #'header:meter
\fromproperty #'header:arranger
}
}
}
}
}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%% SCORE (HEADER / LAYOUT) SETTINGS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\paper {
left-margin = 2\cm
right-margin = 2\cm
line-width = 17\cm
% after-title-space = 0.5\cm
ragged-bottom = ##f
ragged-last-bottom = ##f
}
\layout {
\context {
\ChoirStaff
% If only one non-empty staff in a system exists, still print the backet
\override SystemStartBracket #'collapse-height = #1
}
\context {
\StaffGroup
% If only one non-empty staff in a system exists, still print the backet
\override SystemStartBracket #'collapse-height = #1
}
\context {
\Score
% Force multi-measure rests to be written as one span
\override MultiMeasureRest #'expand-limit = #3
skipBars = ##t
autoBeaming = ##f
hairpinToBarline = ##f
\override BarNumber #'break-visibility = #end-of-line-invisible
\override CombineTextScript #'avoid-slur = #'outside
barNumberVisibility = #(every-nth-bar-number-visible 5)
\override DynamicTextSpanner #'dash-period = #-1.0
\override InstrumentSwitch #'font-size = #-1
% Rest collision
\override RestCollision #'positioning-done = #merge-rests-on-positioning
% Auto-Accidentals: Use modern-cautionary style...
extraNatural = ##f
autoAccidentals = #'(Staff (same-octave . 0)) autoCautionaries = #'(Staff (any-octave . 0) (same-octave . 1))
printKeyCancellation = ##t
}
\context {
\RemoveEmptyStaffContext
}
\context {
\Lyrics
\override VerticalAxisGroup #'minimum-Y-extent = #'(0.5 . 0.5)
}
\context {
\StaffGroup
\consists "Instrument_name_engraver"
}
}