\version "2.19.2" % Function for adjusting the MIDI volume using the Staff.midiExpression % context property. % % % Known issues: % % % * This function is designed to be used only for generating MIDI % output (that is, in a score which contains only a \midi block). % Calling this function when generating typeset output (in a % score which includes a \layout block) is likely to result in % output which includes a lot of unwanted extra dynamic marks. % % If both typeset and MIDI output are needed, it is therefore % recommended to use separate \score blocks for generating these % different types of output. Ensuring that this function will % then be used only when generating MIDI could then be done, for % example, by using tags to call the function "conditionally" % only when producing MIDI output. % % % * As the purpose of this function is to make changes to the MIDI % volume, it is recommended to avoid using any constructs which % might have influence on MIDI volume in the music expression used % as the function parameter. (Such constructs will likely be % accepted without warnings, but they may have unexpected effects % on the MIDI volume if they interfere with the action of this % function.) % % % * The MIDI volume adjustment is based on the assumption that the % dynamic level (implemented in LilyPond as note velocity) and the % MIDI expression level have equal significance when computing % the actual MIDI output volume in a MIDI output device - that is, % that the total volume will depend on the product of the absolute % dynamic level and the expression level. The volume changes % produced by this function are not likely to be smooth if this % assumption does not hold. adjustExpression = #(define-music-function (parser location step music omit-final-adjustment initial-dynamic final-dynamic dynamic-to-volume-function minimum-absolute-volume maximum-absolute-volume) ((ly:moment? (ly:make-moment 1 64)) ly:music? (boolean? #f) string? string? (procedure? default-dynamic-absolute-volume) number? number?) "Adjusts the MIDI volume starting from @var{initial-dynamic} to @var{final-dynamic} during the music expression @var{music} via a linear sequence of changes to the @code{Staff.midiExpression} context property. The frequency of emitting changes to the @code{Staff.midiExpression} context property can be specified using the optional @var{step} moment argument; by default, changes in the context property during @var{music} will be separated by 64th rests. The duration of @var{step} should not exceed the duration of @var{music}. It is assumed that the @code{Staff.midiExpression} context property has the value 1.0 before calling this function. If the optional @var{omit-final-adjustment} flag has not been set (the default), the current context's MIDI volume will be explicitly set to @var{final-dynamic}, and the value of the @code{Staff.midiExpression} context property to 1.0 at the moment that immediately follows @var{music}. In case there could be competing changes to the MIDI volume or the MIDI expression level occurring at this exact same moment (such as another call to this function), it is recommended to raise this flag to avoid possible anomalies with the MIDI volume at this musical moment. If specified, the optional @var{dynamic-to-volume-function} argument must be a procedure for mapping @var{initial-dynamic} and @var{final-dynamic} into values between 0.0 and 1.0, representing fractions of the total MIDI volume range corresponding to these dynamics. By default, the pre-defined @code{default-dynamic-absolute-volume} function is used. To ensure smooth volume adjustment at the start and end of @var{music}, the mandatory @var{minimum-absolute-volume} and @var{maximum-absolute-volume} arguments should match the bounds for the absolute MIDI volume range effective in the context in which this function is called, as defined, for example, via the @code{midiMinimumVolume} and @code{midiMaximumVolume} context properties, or using a MIDI instrument equalizer function." (let* ((music-duration (ly:music-length music)) (n (floor (ly:moment-main (ly:moment-div music-duration step)))) (silence (ly:music-compress #{ s1 #} step))) (if (<= n 0) (ly:error "adjustExpression: invalid step for adjusting MIDI expression level")) (let* ((get-absolute-volume (lambda (dynamic-string) (let ((absolute-volume-range-width (- maximum-absolute-volume minimum-absolute-volume)) (volume-fraction (dynamic-to-volume-function dynamic-string))) (if (not (number? volume-fraction)) (ly:error "adjustExpression: no MIDI volume found for dynamic ~s" dynamic-string)) (+ minimum-absolute-volume (* volume-fraction absolute-volume-range-width))))) (initial-volume (get-absolute-volume initial-dynamic)) (final-volume (get-absolute-volume final-dynamic)) (crescendo (<= initial-volume final-volume)) (greater-dynamic (if crescendo final-dynamic initial-dynamic)) (expression-range (if crescendo (cons (/ initial-volume final-volume) 1.0) (cons 1.0 (/ final-volume initial-volume)))) (initial-expression (car expression-range)) (unit (/ (- (cdr expression-range) initial-expression) n))) (letrec ((adjust-expression-at-endpoint (lambda (dynamic expression) (list (make-music 'AbsoluteDynamicEvent 'text dynamic) #{ \set Staff.midiExpression = #expression #}))) (adjust-expression-between-endpoints (lambda (i) (if (< i n) (cons #{ \set Staff.midiExpression = #(+ initial-expression (* i unit)) #silence #} (adjust-expression-between-endpoints (+ i 1))) '())))) (make-simultaneous-music (list music (make-sequential-music (append (adjust-expression-at-endpoint greater-dynamic initial-expression) (adjust-expression-between-endpoints 0) (if omit-final-adjustment '() (adjust-expression-at-endpoint final-dynamic 1.0)))))))))) % ----------------------------------------------------------------------------- minVol = 0.2 maxVol = 0.8 \score { \new Staff \with { midiExpression = #1.0 midiInstrument = #"lead 1 (square)" midiMinimumVolume = #minVol midiMaximumVolume = #maxVol } \new Voice \repeat unfold 4 { \adjustExpression { a'2 } "ppppp" "sf" #minVol #maxVol f'4 s4 \adjustExpression { a'2 } "sf" "ppppp" #minVol #maxVol f'4 s4 } \midi { } }