1

After setting the a context property for a MIDI effect, for example \set Staff.midiChorusLevel = #2, how would I save this value into a variable inside a Scheme function, should I want to temporarily change this value in the function and then reset it to the value that was there before?

3 Answers 3

2

First let me give you a short overview of how Lilypond thinks: When you invoke Lilypond on a file it is first parsed. The parser essentially transforms the text file containing Lilypond code, scheme code &c. into scheme objects representing music, organized in books &c.

This is then translated into a set of graphic objects (grobs) which are then spaced and output.

Now we need to keep in mind that the whole concept of contexts only exists during this translation step, but most of our code is executed in the parsing step. This means we do not actually have access to the value of these context properties at the time our code is executed.

One solution to this would be to define your own variables to keep track of the value, but this is dangerous, as the setting will not be done in score order but in parsing order.

But instead we can use the hooks Lilypond provides to us to execute code at a later time. In fact, if we use commands like \set or \override these actually create special music objects that will then be interpreted in the translation phase. You can use \displayMusic to actually see what these things turn into.

Now Lilypond does also provide a hook to let us execute any function of a context within the current context using \applyContext fun or if we want a specific parent context \context ... \applyContext fun or even \context ... = ... \applyContext fun.

This way we can write a function that stores previous values and allows step by step unsetting of values like this:

#(set-object-property! 'store 'translation-type? list?)

#(define ((tset sym val) context)
   (if (eq? sym 'store) (ly:warning "Do not use tset to set 'store Property!")
       (let* ((store (ly:context-property context 'store))
              (this-store (assoc-get sym store '()))
              (current-val (ly:context-property context sym))
              (new-store (cons current-val this-store)))
         (ly:context-set-property! context 'store (assoc-set! store sym new-store))
         (ly:context-set-property! context sym val))))

#(define ((rset sym) context)
   (if (eq? sym 'store) (ly:warning "Do not use rset to reset 'store Property!")
       (let* ((store (ly:context-property context 'store))
              (this-store (assoc-get sym store '())))
         (if (null? this-store)
             (ly:context-unset-property context sym)
             (begin
              (ly:context-set-property! context sym (car this-store))
              (ly:context-set-property! context 'store
                                        (assoc-set! store sym (cdr this-store))))))))

\new Staff {
  % Remove beam exceptions so we see an effect
  \set beamExceptions = #'()
  8^"Default" 8 8 8 
  \applyContext #(tset 'beatStructure '(2 2))
  8^"Override" 8 8 8 |
  \applyContext #(rset 'beatStructure)
  8^"Reset" 8 8 8 8 8 8 8 
  
  \break
  
  \set beatStructure = 2,1,1
  8^"Different default" 8 8 8 8 8 8 8
  \applyContext #(tset 'beatStructure '(2 2))
  8^"Override" 8 8 8 8 8 8 8
  \applyContext #(rset 'beatStructure)
  8^"Reset" 8 8 8 8 8 8 8 
  
  \break

  s1
  \applyContext #(tset 'clefPosition 2)
  2^"Voice context (does not work)"
  \context Staff \applyContext #(tset 'clefPosition 2)
  2^"Staff context, does work"
}

Of course we could turn this into Music functions to get syntax more similar to \set Staff.something = something. By the way you can also use this to create differential functions for midi, like let’s say \faster or \faster perc.

1

As far as I have understood your problem, you have

\set Staff.midiChorusLevel = #something
...
\customFunction { ... }
...

and you would like \customFunction to set midiChorusLevel temporarily and reset it to the previous value afterwards.

Now, LilyPond does not provide a ready-made tool for that, but depending on what you're trying to do exactly, there could be simple solutions.

  • If you just need it to set midiChorusLevel at one point in time and reset it just afterwards, use \once \set midiChorusLevel = ....

  • If you want a general default for midiChorusLevel on the staff context, i.e. the value to restore will always be the same, use

\new Staff \with {
  midiChorusLevel = #something
}
{
  ...
  \set midiChorusLevel = ...
  ...
  \unset midiChorusLevel
  ...
}

This \unset construct resets the property to its default in the context, which is established (in priority order) by \with blocks, \layout { \context { ... } } blocks, and LilyPond defaults.

In the general case, LilyPond provides no way to restore the previous setting, because it does not remember it. (It does remember settings using a stack for grob properties, and there is a \temporary command for those, but not for context properties.) However, as @Lazy demonstrated, you can use the \applyContext hook to implement it. My approach would be slightly different from his solution, using a closure to store the property value:

\version "2.22.2"

#(use-modules (ice-9 match)
              (ice-9 receive))

resetAfter =
#(define-music-function (path mus) (symbol-list? ly:music?)
   (receive (context property)
     (match path
       ((c p) (values c p))
       ((p) (values 'Bottom p)))
     (let ((val #f))
       #{
         \context #context
           \applyContext
             #(lambda (context)
                (set! val (ly:context-property context property)))
         #mus
         \context #context
           \applyContext
             #(lambda (context)
                (ly:context-set-property! context property val))
       #})))


\new Staff {
  % Remove beam exceptions so we see an effect
  \set beamExceptions = #'()
  8^"Default" 8 8 8 8 8 8 8
  \resetAfter beatStructure {
    \set beatStructure = #'(2 2)
    8^"Set" 8 8 8 8 8 8 8 |
  }
  8^"Reset" 8 8 8 8 8 8 8 
  
  \break
  
  \set beatStructure = 2,1,1
  8^"Different default" 8 8 8 8 8 8 8
  \resetAfter beatStructure  {
    \set beatStructure = #'(2 2)
    8^"Set" 8 8 8 8 8 8 8
  }
  8^"Reset" 8 8 8 8 8 8 8 
}
0

Take a look at the StackOverflow post Static Variables in Scheme/Racket, which explains how to simulate static variables in Scheme.

The gist of the solution is that within your scheme function, there is a lambda expression that stores the original value of the midi property and returns that value when invoked.

1
  • That is not really what the question is about.
    – Lazy
    Sep 3, 2022 at 12:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.