5

I am setting a guitar piece in Lilypond. The piece requires to use a pitch shifter in certain places. Using \set Staff.stringTunings anywhere in the notes leads to a correct tab for pitch-shifted guitar, but I'd also like to see the effective tuning on the tab.

1 Answer 1

3

You might want to use a custom engraver for that:

#(set-object-property! 'stringTuningColumnFormatter
                       'translation-type?
                       procedure?)

#(define (string-tuning-engraver context)
   (let ((current-tuning #f))
     (make-engraver
      ((process-music engraver)
       (let ((tuning (ly:context-property context 'stringTunings))
             (formatter (ly:context-property context 'stringTuningColumnFormatter)))
         (if (not (equal? tuning current-tuning))
             (let* ((mark (ly:engraver-make-grob engraver 'RehearsalMark '())))
               (ly:grob-set-property! mark 'text (formatter tuning current-tuning))
               (ly:grob-set-property! mark 'font-size 0)
               (ly:grob-set-property! mark 'self-alignment-X LEFT)
               (set! current-tuning tuning))))))))

\layout {
  \context {
    \Score
    stringTuningColumnFormatter =
    #(lambda* (tuning #:optional (old-tuning #f))
       (let ((mup
              (map
               (lambda (n o i)
                 #{
                   \markup {
                     \vcenter \circle \dynamic #(number->string (1+ i))
                     \hspace #0.3
                     \vcenter
                     #(if (equal? n o)
                          (note-name->markup n #f)
                          (make-concat-markup
                           (list (note-name->markup o #f)
                                 "→"
                                 (note-name->markup n #f))))
                   }
                 #})
               tuning (if old-tuning old-tuning tuning) (iota (length tuning)))))
         (markup #:override '(baseline-skip . 3) #:fontsize -4 (make-column-markup mup))))
  }
  \context {
    \TabStaff
    \consists #string-tuning-engraver
  }
}

\new TabStaff {
  c4 4 4 4
  \set TabStaff.stringTunings = \stringTuning <e, ais, d g b e'>
  4 4 4 4
}

Note that here the formatting routine can be passed along by the user, which makes it easy to implement different behaviour. To get the tuning within the lines we can do a similar thing. This will implement a special kind of key signature, which does not give the alterations, but the tunings:

#(define (key-signature-interface::tuning-column-print grob)
   (let* ((staff-symbol (ly:grob-object grob 'staff-symbol))
          (pos (ly:grob-property staff-symbol 'line-positions #f))
          ; With 2.25 line-positions will be used by default. Before this might not be set, so
          ; in case we recalculate this here to make the code compatible with 2.24
          (pos (or pos
                   (let* ((count (ly:grob-property staff-symbol 'line-count)))
                     (map (lambda (i) (- count (* 2 i) 1)) (iota count)))))
          (ssp (ly:staff-symbol-staff-space staff-symbol))
          (tuning (assoc-get 'string-tuning (ly:grob-property grob 'details)))
          (mups (map (lambda (p) (markup #:whiteout #:pad-markup 0.1 (note-name->markup p #f))) tuning))
          (stcs (map (lambda (m)
                       (ly:stencil-aligned-to
                         (grob-interpret-markup grob m)
                         Y CENTER))
                     mups))
          (common-center (interval-center (ly:stencil-extent (grob-interpret-markup grob "A") X)))
          (tstcs (map
                  (lambda (s p) (ly:stencil-translate s (cons (- common-center) (* ssp 0.5 p))))
                  stcs pos)))
     (apply ly:stencil-add tstcs)))

#(define (tab-staff-string-tuning-key-engraver context)
   (let ((current-tuning #f))
     (make-engraver
      ((process-music engraver)
       (let ((tuning (ly:context-property context 'stringTunings)))
         (if (not (equal? tuning current-tuning))
             (let* ((mark (ly:engraver-make-grob engraver 'KeySignature '())))
               (ly:grob-set-nested-property! mark '(details string-tuning) tuning)
               (ly:grob-set-property! mark 'stencil key-signature-interface::tuning-column-print)
               (set! current-tuning tuning))))))))

\layout {
  \context {
    \TabStaff
    \consists #tab-staff-string-tuning-key-engraver
    \override KeySignature.font-size = #-4
    \override KeySignature.break-visibility = #all-visible
  }
}

\new TabStaff {
  c4 4 4 4
  \set TabStaff.stringTunings = \stringTuning <e, ais, d g b e'>
  4 4 4 4
}

Here is a big full example of how this behaves:

\version "2.24"

% Used for printing the actual changes. A procedure that takes the tuning and the old tuning
% as optional argument and returns a markup.
#(set-object-property! 'stringTuningColumnFormatter
                       'translation-type?
                       procedure?)

% Create a tuning column reahearsal mark whenever the string tuning changes
#(define (string-tuning-engraver context)
   (let ((current-tuning #f))
     (make-engraver
      ((process-music engraver)
       (let ((tuning (ly:context-property context 'stringTunings))
             (formatter (ly:context-property context 'stringTuningColumnFormatter)))
         (if (not (equal? tuning current-tuning))
             (let* ((mark (ly:engraver-make-grob engraver 'RehearsalMark '())))
               (ly:grob-set-property! mark 'text (formatter tuning current-tuning))
               (ly:grob-set-property! mark 'font-size 0)
               (ly:grob-set-property! mark 'self-alignment-X LEFT)
               (set! current-tuning tuning))))))))

% Print tuning supplied in details.string-tuning on the line positions
#(define (key-signature-interface::tuning-column-print grob)
   (let* ((staff-symbol (ly:grob-object grob 'staff-symbol))
          (pos (ly:grob-property staff-symbol 'line-positions #f))
          ; With 2.25 line-positions will be used by default. Before this might not be set, so
          ; in case we recalculate this here to make the code compatible with 2.24
          (pos (or pos
                   (let* ((count (ly:grob-property staff-symbol 'line-count)))
                     (map (lambda (i) (- count (* 2 i) 1)) (iota count)))))
          (ssp (ly:staff-symbol-staff-space staff-symbol))
          (tuning (assoc-get 'string-tuning (ly:grob-property grob 'details)))
          (mups (map (lambda (p) (markup #:whiteout #:pad-markup 0.1 (note-name->markup p #f))) tuning))
          (stcs (map (lambda (m)
                       (ly:stencil-aligned-to
                         (grob-interpret-markup grob m)
                         Y CENTER))
                     mups))
          (common-center (interval-center (ly:stencil-extent (grob-interpret-markup grob "A") X)))
          (tstcs (map
                  (lambda (s p) (ly:stencil-translate s (cons (- common-center) (* ssp 0.5 p))))
                  stcs pos)))
     (apply ly:stencil-add tstcs)))

% Create a "tuning key signature" whenever tuning changes
#(define (tab-staff-string-tuning-key-engraver context)
   (let ((current-tuning #f))
     (make-engraver
      ((process-music engraver)
       (let ((tuning (ly:context-property context 'stringTunings)))
         (if (not (equal? tuning current-tuning))
             (let* ((mark (ly:engraver-make-grob engraver 'KeySignature '())))
               (ly:grob-set-nested-property! mark '(details string-tuning) tuning)
               (ly:grob-set-property! mark 'stencil key-signature-interface::tuning-column-print)
               (set! current-tuning tuning))))))))

% Print a column of all tunings. Optional argument str-nos specifies tho 0 indexed string numbers.
% Keyword arg show-change specifies if only the tuning should be printed or also the change from
% previous tuning.
#(define* (tuning-column::full-formatter
           tuning
           #:optional (old-tuning #f) (str-nos #f)
           #:key (show-change #f))
   (if (not str-nos)
       (set! str-nos (iota (length tuning))))
   (let ((mup
          (map
           (lambda (n o i)
             #{
               \markup {
                 \vcenter \circle \dynamic #(number->string (1+ i))
                 \hspace #0.3
                 \vcenter
                 #(if (or (equal? n o) (not show-change))
                      (note-name->markup n #f)
                      (make-concat-markup
                       (list (note-name->markup o #f)
                             "→"
                             (note-name->markup n #f))))
               }
             #})
           tuning (if old-tuning old-tuning tuning) str-nos)))
     (markup #:override '(baseline-skip . 3) #:fontsize -4 (make-column-markup mup))))

% Like tuning-column::full-formatter, but with show-change = #t
#(define* (tuning-column::full-formatter-change . args)
   (apply tuning-column::full-formatter (append args (list #:show-change #t))))

% Like tuning-column::full-formatter, but only print strings with changed tuning
#(define* (tuning-column::diff-formatter tuning
                                         #:optional (old-tuning #f) (str-nos #f)
                                         #:key (show-change #f))
   (if (not str-nos)
       (set! str-nos (iota (length tuning))))
   (if old-tuning
       (let* ((triplets (map list tuning old-tuning str-nos))
              (triplets (filter (lambda (x) (not (equal? (car x) (cadr x)))) triplets)))
         (set! tuning (map car triplets))
         (set! old-tuning (map cadr triplets))
         (set! str-nos (map caddr triplets))))
   (tuning-column::full-formatter tuning old-tuning str-nos #:show-change show-change))

% Like tuning-column::diff-formatter, but with show-change = #t
#(define* (tuning-column::diff-formatter-change . args)
   (apply tuning-column::diff-formatter (append args (list #:show-change #t))))

% Set default formatter to diff with change. Add tuning columns to Staff and TabStaff.
% Add tuning key to TabStaff
\layout {
  \context {
    \Score
    stringTuningColumnFormatter = #tuning-column::diff-formatter-change
  }
  \context {
    \Staff
    \consists #string-tuning-engraver
  }
  \context {
    \TabStaff
    \consists #string-tuning-engraver
    \consists #tab-staff-string-tuning-key-engraver
    \override KeySignature.font-size = #-4
    \override KeySignature.break-visibility = #all-visible
  }
}


%%%%%%%%% EXAMPLE %%%%%%%%%%

\layout {
  \revert TabStaff.TimeSignature.stencil
}


\paper {
  ragged-right = ##t
}

music = {
  <c c'>4 4 4 4
  \set Staff.stringTunings = \stringTuning <e, ais, d g c' e'>
  <c c'>4 <c c'>4
  \set Staff.stringTunings = \stringTuning <e, a, d g b e'>
  <c c'>4 4
  \break
  \set Staff.stringTunings = \stringTuning <e, ais, d g c' e'>
  <c c'>4 4 4 4
}

<<
  \new Staff \with {
    stringTuningColumnFormatter = #tuning-column::full-formatter
    \clef "treble_8"
  } \music
  \new Staff \with {
    stringTuningColumnFormatter = #tuning-column::full-formatter-change
    \clef "treble_8"
  } \music
  \new Staff \with {
    stringTuningColumnFormatter = #tuning-column::diff-formatter
    \clef "treble_8"
  } \music
  \new Staff \with {
    stringTuningColumnFormatter = #tuning-column::diff-formatter-change
    \clef "treble_8"
  } \music
  \new TabStaff \music
>>
2
  • Thanks a lot for this answer. Did you write this from scratch as an answer, or is this a common solution which I didn't find? Is there also a trick to write the tunings on the tab lines, not above? Or is there a good starting point to learn how to program Lilypond, so that I can help myself? The documentation is kind of intimidating :-) (I am not a CS noob, but I am mostly a C++ guy and functional programming in Scheme/LISP-like languages hasn't been on my table since the late 90s...)
    – Martin
    Commented Apr 7 at 15:18
  • Yes, I did just whip this up for you. And I’ll extend something for notating this in the strings.
    – Lazy
    Commented Apr 8 at 12:13

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.