Jump to content

Indenting individual systems

From LilyPond wiki
Revision as of 07:49, 6 January 2026 by Lemzwerg (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

LilyPond scores are indented similar to text paragraphs: all systems except the first one are indented by the same amount (the value of the short-indent property). This layout constraint is applied in advance and cannot be modified later. In other words, indenting on a system-by-system basis is not possible with this mechanism.

Users who need a differently indented system can arrange for that system to start a new score, as demonstrated in the snippet Coda ahead of a line of its own. That may be a reasonable workaround at a more or less natural break such as a coda, but in general the structure clash introduces severe fragmentation requiring compensatory splicing and fudging; see this discussion of problems with this approach.

This snippet demonstrates a pseudo-indent workaround that superimposes an additional, system-wise indentation on the score's unmodified layout indentation, avoiding the abovementioned score change problems. It allows to change the left and right (pseudo) indentation independently; optionally, the instrument name can be changed, too.

Some caveats still apply, though.

  • The indenting mechanism is based on an arcane misuse of the LeftEdge grob (see Issue #3761) and may therefore become temporarily or permanently broken by side effects of future development changes in the LilyPond code base.
  • The code is not protected against multiple calls per system break.

\version "2.24"

%%%%%%%% HEADER %%%%%%%%
%
% This code was prompted by
%
%   https://lists.gnu.org/archive/html/lilypond-user/2019-07/msg00139.html
%
% and offers a pseudo-indent hack suitable for general use.
%
% keywords:
%
%   indent short-indent indentation system line
%   mid-score temporarily arbitrary individual single just only once
%   coda margin
%
% Put the code before the examples into a file `pseudo-indent.ily` that
% you can include later on in your code with
%
%   \include "pseudo-indent.ily"


%%%%%%%% PSEUDO-INDENT FUNCTIONS START %%%%%%%%

% Two functions are provided for indenting individual systems.
%
% - To left-indent a system, apply `\pseudoIndent` before the music
%   continues.
% - `\pseudoIndents` does the same but lets you also indent on the
%   right.
%
% Both commands accept an optional argument for changing the affected
% system's instrument name(s).
%
% The following conditions must be met to make the functions work.
%
% - It is assumed that the score is not using a ragged-right layout
%   (which is the default for multi-system scores).
% - A manual `\break` must be used to call the functions at the start
%   of a system.
% - The functions misbehave if called more than once at the same
%   system start.
%
% The syntax forms are as follows.
%
%   \pseudoIndent [<name-tweaks>] <left-indent>
%   \pseudoIndent [<name-tweaks>] <left-indent> <right-indent>
%
% <name-tweaks>
%      An optional argument holding a markup list for instrument names
%      as an ordered list.  If an element is `*`, the previous
%      instrument name stays unchanged.
% <left-indent>
%      Additional left indentation, in staff-space units.  This value
%      can be negative with the restriction that the sum
%
%        <left-indent> + `short-indent`
%
%      must be larger or equal to zero to avoid unsupported
%      stretching.
% <right-indent>
%      Additional right indentation, in staff-space units; can be
%      negative.
%
% Examples:
%
%   \pseudoIndent 11
%   \pseudoIndents 11 22
%   \pseudoIndents \markuplist { "foo" * \myMarkup } 11 22


pseudoIndents =
#(define-music-function (name-tweaks left-indent right-indent)
   ((markup-list? '()) number? number?)

   (define (warn-stretched p1 p2)
     (ly:input-warning
      (*location*)
      (G_
       " \\pseudoIndents ~s ~s is stretching staff; expect distorted layout")
      p1 p2))

   (let*
       ((narrowing (+ left-indent right-indent))
        (set-staffsymbol!
         (lambda (staffsymbol-grob) ; change staff to new width
           (let*
               ((left-bound (ly:spanner-bound staffsymbol-grob LEFT))
                (left-moment (ly:grob-property left-bound 'when))
                ;; in first system of score?
                (capo? (moment<=? left-moment ZERO-MOMENT))
                (layout (ly:grob-layout staffsymbol-grob))
                ;; debugging info
                (lw (ly:output-def-lookup layout 'line-width))
                (indent (ly:output-def-lookup
                         layout
                         (if capo? 'indent 'short-indent)))
                (old-stil (ly:staff-symbol::print staffsymbol-grob))
                (staffsymbol-x-ext (ly:stencil-extent old-stil X))
                ;; For LilyPond versions >=2.19.16 the first system has
                ;; `old-stil` already narrowed.  Compensate for this
                ;; (i.e., being not pristine) when calculating.
                ;;
                ;; - old leftmost-x (its value is needed when setting
                ;;   so-called 'width)
                ;; - the new width and position (via local variable
                ;;   `narrowing_`)
                (ss-t (ly:staff-symbol-line-thickness
                       staffsymbol-grob))
                ;; would expect half
                (pristine? (<= 0 (car staffsymbol-x-ext) ss-t))
                (leftmost-x (+ indent (if pristine? 0 narrowing)))
                ;; uses 0 if already narrowed
                (narrowing_ (if pristine? narrowing 0))
                (old-width (+ (interval-length staffsymbol-x-ext)
                              ss-t))
                (new-width (- old-width narrowing_))
                ;; and set! this immediately
                (new-rightmost-x (+ leftmost-x new-width))
                (junk (ly:grob-set-property! staffsymbol-grob
                                             'width new-rightmost-x))
                (in-situ-stil (ly:staff-symbol::print
                               staffsymbol-grob))
                (new-stil (ly:stencil-translate-axis in-situ-stil
                                                     narrowing_
                                                     X))
                ;; for debugging
                ;; (new-stil (stencil-with-color new-stil red))
                (new-x-ext (ly:stencil-extent new-stil X)))
             (ly:grob-set-property! staffsymbol-grob 'stencil
                                    new-stil)
             (ly:grob-set-property! staffsymbol-grob 'X-extent
                                    new-x-ext))))

        ;; move grob across to line start
        (set-X-offset!
         (lambda (margin-grob)
           (let* ((old (ly:grob-property-data margin-grob 'X-offset))
                  (new (lambda (grob)
                         (+ (if (procedure? old) (old grob) old)
                            narrowing))))
             (ly:grob-set-property! margin-grob 'X-offset new))))

        ;; tweak both instrument name texts
        (tweak-text!
         (lambda (i-name-grob mkup)
           (when (and (markup? mkup)
                      (not (string=? (markup->string mkup) "*")))
             (ly:grob-set-property! i-name-grob 'long-text mkup)
             (ly:grob-set-property! i-name-grob 'text mkup))))

        ;; on staves, + adapt left margin
        (install-narrowing
         (lambda (leftedge-grob)
           (let*
               ((sys (ly:grob-system leftedge-grob))
                (all-grobs (ly:grob-array->list
                            (ly:grob-object sys 'all-elements)))
                (grobs-named
                 (lambda (name)
                   (filter (lambda (x)
                             (eq? name (grob::name x))) all-grobs)))
                (first-leftedge-grob (list-ref
                                      (grobs-named 'LeftEdge) 0))
                (relsys-x-of (lambda (g)
                               (ly:grob-relative-coordinate g sys X)))
                (leftedge-x (relsys-x-of first-leftedge-grob))
                (leftedged? (lambda (g)
                              (= (relsys-x-of g) leftedge-x)))
                (leftedged-ss (filter leftedged?
                                      (grobs-named 'StaffSymbol))))
             ;; ignore other left-edges
             (when (eq? leftedge-grob first-leftedge-grob)
               (for-each set-staffsymbol! leftedged-ss)
               (for-each set-X-offset! (grobs-named 'SystemStartBar))
               (for-each set-X-offset! (grobs-named 'InstrumentName))
               (for-each tweak-text! (grobs-named 'InstrumentName)
                         name-tweaks))))))

     (when (negative? narrowing)
       (warn-stretched left-indent right-indent))
     ;; and continue anyway
     #{
       % ensure that these overrides are applied only at begin-of-line
       % (but this does not protect against unsupported multiple
       % application)
       \break
       % give the spacing engine notice regarding the loss of width
       % for music
       \once \override Score.LeftEdge.X-extent =
         #(cons narrowing narrowing)
       % discard line start region of staff and reassemble left-margin
       % elements
       \once \override Score.LeftEdge.after-line-breaking =
         #install-narrowing
       % shift the system to partition the narrowing between left and
       % right
       \overrideProperty Score.NonMusicalPaperColumn
                         .line-break-system-details
                         .X-offset #(- right-indent)
       % prevent a leftmost bar number entering a stretched staff
       \once \override Score.BarNumber.horizon-padding =
         #(max 1 (- 1 narrowing))
     #}))

pseudoIndent =
#(define-music-function (name-tweaks left-indent)
   ((markup-list? '()) number?)
   #{
     \pseudoIndents $name-tweaks $left-indent 0
   #})
%%%%%%%% PSEUDO-INDENT FUNCTIONS END %%%%%%%%


%%%%%%%% SETUP FOR WIKI EXAMPLES %%%%%%%%

\paper {
  indent = 55\mm
  short-indent = 40\mm
  system-system-spacing.basic-distance = 10\mm
  tagline = ##f
}

m = { f'4 f'4 f'4 f'4 } % one measure

showInfo =
#(define-music-function (info) (markup?)
   #{
     \once \override Score.LeftEdge.break-visibility =
       #begin-of-line-visible
     \once \override Score.LeftEdge.stencil =
       #(lambda (grob) (grob-interpret-markup grob info))
   #})


%%%%%%%% BASIC EXAMPLE %%%%%%%%

% trace short indentation
basic-info-line = \markup \translate #'(0 . -0.2)
                            \with-color #blue
                            \draw-dashed-line #'(0 . -49)

v-gap = \markup \vspace #2.25

basic-info-text = \markup \translate #'(-24 . 8.8) \column {
  \italic "(indent)"
  \v-gap
  \italic "(short-indent)"
  \v-gap
  "\pseudoIndent 22"
  \v-gap
  "\pseudoIndent 44"
  \v-gap
  "\pseudoIndents 22 22"
  \v-gap
  \line { "\pseudoIndents 0 44"
          \translate #'(53 . 0)
            \italic "with  ragged-right  ##f      (default)" }
  \v-gap
  \line { "\pseudoIndents 0 20"
          \translate #'(72 . 0)
            \italic \right-column { "ragged-last  ##f" "(default)" } }
}

basic = {
  \omit Score.BarNumber
  \m \m \m \m \m \m
  \break % a short-indented system for reference    (also info anchor:)
  \showInfo \markup \combine \basic-info-text \basic-info-line
  \m \m \m \m \m \m \m
  \pseudoIndent 22
  \m \m \m \m \m
  \pseudoIndent 44
  \m \m \m
  \pseudoIndents 22 22
  \m \m \m
  \pseudoIndents 0 44
  \m \m \m
  \pseudoIndents 0 20
  \m \m \m \m \m \bar "|."
}

\markup \huge \bold \column { " " "        Basic usage"  " "}

\score {
  \new Staff { \basic }

  \layout {}
}


%%%%%%%% DEMO EXAMPLE %%%%%%%%

% trace right margin
demo-info-line = \markup \translate #'(85.3 . 20.3)
                           \with-color #blue
                           \draw-dashed-line #'(0 . -56)

demo-info-text = \markup \translate #'(-24 . 17.3) \column \italic {
  "right-indent at head"
  " "
  "stop/startStaff OK"
  " "
  " "
  " "
  " "
  "(short-indent)"
  " "
  " "
  " "
  " "
  "outdent without stretching"
  " "
  "name-tweaks displaying"
  "how its list is ordered"
  " "
  " "
  \line { "using name-tweaks   "
          \column \upright { " "  "\"\""  "\\mkCoda"  * }
          " to    "
          \column { " " "hide name"
                    "change name"  "(leave name as it is)" } }
}

% See https://wiki.lilypond.community/wiki/Coda_ahead_of_a_line_of_its_own
mkCoda = \markup \vcenter {
  \bold "coda" \fontsize #3 \musicglyph "scripts.coda" }

demo = {
  \pseudoIndents 0 22 % no additional left-indenting applied
  s1*4
  \break % a short-indented system for reference    (also info anchor:)
  \showInfo \markup \combine \demo-info-text \demo-info-line
  s1*7
  % display the instrument name ordering
  \pseudoIndents \markuplist { 1 2 3 } 11 -4
  s1*6
  % hide 1; leave 2; change 3
  \pseudoIndent \markuplist { "" * \mkCoda } 55
  s1*2 \bar "|."
}

demoHi = {
  \m \m \m \m
  \m \m \m \m \m \m \m
  \m \m \m \m \m \m
  \m \m
}

demoLo = {
  \m \m \stopStaff s1 \startStaff \m
  \m \m \m \m \m \m \m
  \m \m \m \m \m \m
  \m \m
}


\markup \huge \bold \column { " "  "      Further possibilities"  " " }

\score {
  \new StaffGroup \with {
    instrumentName = "SYS"
    shortInstrumentName = "sys" }
  <<
    \new Staff \with {
      instrumentName = "HI"
      shortInstrumentName = "hi" }
    << \demo \demoHi >>
    \new RhythmicStaff \with {
      instrumentName = "LO"
      shortInstrumentName = "lo" }
    { \demoLo }
  >>

  \layout {
    \override Score.InstrumentName.self-alignment-X = #RIGHT
    \override Score.InstrumentName.padding = 2
  }
}

%%%%%%%% END %%%%%%%%