3

In a score I am working on recently, I need to add brackets to certain notation elements such as rests, accidentals and stems. To achieve this purpose I found a quite useful snippet on LSR. This snippet achives the purpose by calling the bracketify-stencil Scheme command.

However I am using an SMuFL library called esmuflily that replaces all the notation elements with their SMuFL equivalences. The command in the snippet modifies the stencil based on Lilypond's default stencil, not my currently using stencil. I want to modify the stencil based on the currently using stencil, no matter whether I am using SMuFL or not. So how can I do it?

In my case of adding bracket to rests, for example, the following code produces a bracketified Lilypond default rest.

brkRest = {
  \once\override Voice.Rest.stencil = #(lambda (grob)
    (bracketify-stencil (ly:rest::print grob) Y 0.1 0.2 0.1))
}

and I must manually change ly:rest::print (which is Lilypond's default rest stencil) to my actually using stencil in esmuflily: ekm-rest.

brkRest = {
  \once\override Voice.Rest.stencil = #(lambda (grob)
    (bracketify-stencil (ekm-rest grob) Y 0.1 0.2 0.1))
}

However, what if I am using another SMuFL library or not using SMuFL at all? I need a method to automatically get the currently using stencil and call bracketify-stencil based on itself. I have tried this, but resulted in a "cyclic dependency" error.

brkRest = {
  \once\override Voice.Rest.stencil = #(lambda (grob)
    (bracketify-stencil ((ly:grob-property grob 'stencil) grob) Y 0.1 0.2 0.1))
}

So what is the correct method of doing this? I am new to Scheme programming in Lilypond.

2 Answers 2

2

You can use something like

brackify =
  \once \propertyTweak stencil
  #(grob-transformer 'stencil (lambda (grob original)
    (bracketify-stencil original Y 0.1 0.2 0.1))) \etc

\new Staff {
  \brackify \time 3/4
  \brackify r4
  \brackify c'2
  \brackify Staff.BarLine
}

This refers to the original stencil definition (as you can see, it works for a variety of music and properties) via grob-transformer. grob-transformer is actually very far from being "syntactical sugar" but goes to extraordinary lengths for retrieving the original property evaluated in a suitable context (if necessary creating an appropriate callback structure for it) and applying the given callback function in exactly such a suitable context.

Note that to actually get at that definition, it must actually be still in the property stack and not overwritten. That means that if you use grob-transformer in an override, it should be either a \once or a \temporary override since those keep the old definition in order to revert to it once the timestep is over or with an explicit revert, respectively. An ordinary \override would replace any preexisting override that has been applied in this particular context (it won't affect definitions inherited from higher-level contexts).

\etc as a shorthand for creating simple music functions is of course documented in the manual.

More complex usage is, of course, possible by going to Scheme. Here is an example that can optionally specify arguments for the \bracketify-stencil function.

brackify =
#(define-music-function (thick protruding padding item)
  ((real? 0.1) (real? 0.2) (real? 0.1) key-list-or-music?)
  (once (propertyTweak 'stencil
     (grob-transformer 'stencil
      (lambda (grob original)
       (bracketify-stencil original Y thick protruding padding)))
     item)))

\new Staff {
  \brackify \time 3/4
  \brackify 0.3 r4
  \brackify 0.1 0.2 0 c'2
  \brackify Accidental <cis' gis'>2.
  <\single \brackify Accidental cis' gis'>2. 
  \brackify 0.2 0.2 0.4 Staff.BarLine
}
4
  • I stand corrected. It very much is syntactic sugar (11 lines of code with maybe 4 really relevant lines), but I thoght the ly:grob-basic-properties would not include the whole property scope.
    – Lazy
    Commented Oct 30 at 19:38
  • This method is indeed simpler, but contains certain limitations that I am currently unsure about. For example I want to dynamically modify the padding of bracket so I defined this command as a music function. But this won't work. Commented Oct 31 at 2:33
  • Even if I add a pair of braces to the original command an error will occur. So What does the \etc command actually do? How can I dynamically modify the brackets' padding. And is this method applicable to accidentals? Commented Oct 31 at 2:37
  • Update: Now I can say that I understand what the \propertyTweak actually does. It is just what I am looking for. In my recent project imitating the engraving effect of SCORE I used \propertyTweak and grob-transformer frequently to change stencils and make them SCORE-like. Thanks very much! Commented Nov 7 at 13:29
1

Side note: Instead of doing #(lambda (grob) (something (defaultthing grob))) or whatever you can use the syntactic sugar #(make-grob-transformer 'stencil (lambda (grob original) (something original))). But that is not going to help you here:

You cannot use the stencil property in the stencil callback for obvious reasons. The problem here is that you actually do want to get the thing you are changing (so you’d need to get this before changing the property).

One way would be to use a different hook such as before-line-breaking:

{
  \override Rest.stencil = #ly:text-interface::print
  \override Rest.text = "rest"
  r1
  \override Rest.before-line-breaking =
  #(lambda (grob)
     (ly:grob-set-property!
      grob 'stencil
      (bracketify-stencil (ly:grob-property grob 'stencil) Y 0.1 0.2 0.1)))
  r
}

So this avoids setting the stencil directly and instead replaces the stencil at a later point. Now, a different method would be to actually before overriding take the previous value and use it in the new value. This is a bit complicated, because this value is not available at parsing time, but only at translation time. Luckily Lilypond offers a nice handle called applyContext, which allows us to execute any function of the context at translation time. So we can do this:

{
  \override Rest.stencil = #ly:text-interface::print
  \override Rest.text = "rest"
  r1
  \applyContext
  #(lambda (context)
     (let* ((prevstc (assoc-ref (ly:context-grob-definition context 'Rest) 'stencil)))
       (ly:context-pushpop-property
        context 'Rest 'stencil
        (lambda (grob)
          (bracketify-stencil
           (if (procedure? prevstc) (prevstc grob)
               prevstc)
           Y 0.1 0.2 0.1)))))
  r
  \revert Rest.stencil
  r
}

We can even plug this into a function and spam this multiple times on one thing:

bracketify =
#(define-music-function (grobsym) (symbol?)
   #{
     \applyContext
     #(lambda (context)
        (let* ((prevstc (assoc-ref (ly:context-grob-definition context grobsym) 'stencil)))
          (ly:context-pushpop-property
           context grobsym 'stencil
           (lambda (grob)
             (bracketify-stencil
              (if (procedure? prevstc) (prevstc grob)
                  prevstc)
              Y 0.1 0.2 0.1)))))
   #})

{
  \override Rest.stencil = #ly:text-interface::print
  \override Rest.text = "rest"
  r1
  \bracketify Rest
  r
  \revert Rest.stencil
  r
  \bracketify Rest
  \bracketify Rest
  r
  \revert Rest.stencil
  r
  \revert Rest.stencil
  r
}

We could even abstract this further and create something similar to grob-transformer:

transformProperty =
#(define-music-function (grobsym prop proc) (symbol? symbol? procedure?)
   #{
     \applyContext
     #(lambda (context)
        (let* ((orig (assoc-ref (ly:context-grob-definition context grobsym) prop)))
          (ly:context-pushpop-property
           context grobsym prop
           (lambda (grob)
             (proc grob (if (procedure? orig) (orig grob) orig))))))
   #})

bracketify =
#(define-music-function (grobsym) (symbol?)
   #{
     \transformProperty #grobsym stencil
     #(lambda (grob orig)
        (bracketify-stencil orig Y 0.1 0.2 0.1))
   #})

{
  \override Rest.stencil = #ly:text-interface::print
  \override Rest.text = "rest"
  r1
  \bracketify Rest
  r
  \revert Rest.stencil
  r
  \bracketify Rest
  \bracketify Rest
  r
  \revert Rest.stencil
  r
  \revert Rest.stencil
  r
}
4
  • Thanks a lot! Working for my example now. I wonder whether it is possible to make the self-defined \bracketify command take effect only once without needing to manually \revert the stencil like the \once\override command. Commented Oct 30 at 14:16
  • @VanLeeF.X.P. Theoretically yes, although it would need some hacks, as there is no proper scheme access to the finalizations context property (except through engravers).
    – Lazy
    Commented Oct 30 at 16:31
  • @VanLeeF.X.P. Another user rightfully pointed out that in fact ly:grob-basic-properties can be used as long as one uses the \temporary keyword. Please consider accepting that answer, so this one can be deleted.
    – Lazy
    Commented Oct 30 at 19:40
  • I see the second answer, but after trying some examples I found that it contains certain limitations compared to your solution. So can you keep your answer and do not delete it right now until I get clear with the other method? thanks Commented Oct 31 at 2:43

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.