instrumentName an StaffGroup innerhalb einer Partitur ändern

Begonnen von martinmagtenor, Samstag, 12. April 2025, 18:03

« vorheriges - nächstes »

martinmagtenor

Hallo zusammen,

ich brauche mal wieder Hilfe. Vorab ein klein wenig Kontextinformation.

Es geht um Orgelnoten. Häufig werden vierstimmige Kompositionen auf drei Systeme verteilt: Sopran und Alt teilen sich das Oberste, Tenor und Bass darunter jeweils in einem eigenen System. Wenn es dem Komponisten wichtig ist werden auch Angaben zu den Manualen gemacht, für die Hände I und II bzw. Ped für die Füße.

Jetzt liegt mir ein Sonderfall vor, dass der Komponist einen Teil (hier ein Refrain) vom Rest dadurch absetzen will, dass die drei oberen Stimmen auf einem Manual, hier I, gespielt werden soll.

Hier ist ein minimalistisches Beispiel:

\version "2.24.4"

#(set-default-paper-size "a6")

musicPone = { c4 c c c | }
musicPtwo = { d4 d d d | }
\score {
<<
\time 4/4
\new StaffGroup \with { \remove System_start_delimiter_engraver }
<<
\new GrandStaff = "Orgel"
<<
\new Staff = "rh" \with { instrumentName = "I" }
<<
\clef treble
\new Voice = "ru" { \relative c'' { \musicPone \bar "||" \musicPtwo \bar "|." } }
>>
\new Staff = "lh" \with { instrumentName = "II" }
<<
\clef bass
\new Voice = "lu" { \relative c { \musicPone \musicPtwo } }
>>
>>
\new Staff = "pd" \with { instrumentName = "Ped" }
<<
\clef bass
\new Voice = "pd" { \relative c { \musicPone \musicPtwo } }
>>
>>
>>
}

An der Stelle, die mit \bar "||" markiert wird, soll eine neue Akkoladenklammer stehen, der dann als Instrumentenname die I zugewiesen wird.

Für mich stellt sich jetzt die Frage, ob und wie man das von "innerhalb" der Musik überhaupt auslösen kann!?




harm6

Hallo,

ich habe noch keine Vorstellung wie es nachher aussehen soll. Kannst Du einen scan vom Original oder auch eine handschriftliche Skizze posten?
Wenn Du von neuer Akkoladenklammer sprichst, welche Art meinst Du:
    \markup { bracket: \bar-line "[" oder brace: \bar-line "{" }

Gruß,
  Harm

P.S.
Du verwendest folgende Konstruktion:
    \new Staff << \clef bass \new Voice { b } >>
Das macht dich anfällig für issue 34:
    \new Staff << \clef bass \new Voice { \grace b4 b } >>
Besser wäre:
    \new Staff { \clef bass \new Voice { \grace b4 b } }
Oder gleich:
    \new Staff \new Voice { \clef bass \grace b4 b }

martinmagtenor

Hallo Harm,

Danke für die Hinweise zur Vermeidung des Issue 34.

Beigefügt ein Scan der Handschrift. Ich hoffe, die Qualität ist ausreichend ...

harm6

Danke!

Eine Akkolade in der Mitte einer Zeile ist in LilyPond nicht vorgesehen.
Beim InstrumentName ist es sogar explizit ausgeschlossen ihn woanders als am Zeilenanfang zu schreiben.

Insoweit wäre zu überlegen, ob Du das ganze nicht per markup hinfummelst.

Falls nicht, eine neue Akkolade innerhalb einer Zeile ist etwas was man manchmal schlichtweg haben möchte, insbesondere wenn es mit einem white-space Zwischenraum einhergeht.
Deshalb habe ich vor einiger Zeit mal code dafür entwickelt.
Der kann zwar auch nicht alles, aber doch einiges mehr als für Dein Beispiel erforderlich. Es ist somit ein ziemliches Monster geworden. Er setzt auch die Notenschlüssel neu, was Du hier rückgängig machen musst (siehe unten).
Getriggert wird das Ganze mit \space <number>, wobei jeder Staff diesen Befehl braucht.

Die Definition für InstrumentName.stencil habe ich abgändert, damit es mid-line funktioniert
InstrumentName.X/Y-offset bin ich nicht angegangen, vielleicht jemand anderer? Momentan also ausprobieren...

Gruß,
  Harm

\version "2.21.1"

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Workaround to insert one or more gaps mid-line of a score
%% Fakes a system-start-delimiter at the end of every gap.
%% Usage: put \space <some-number> in every context at the place where you
%% want the gap.
%% The argument of \space determines the width of the gap.
%% Needs issue 5899 and issue 5919
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Redefine "|" with a different name to allow `print-certain-span-bars?' to
%% differentiate between "|" and "|-g"
#(define-bar-line "|-g" "|" #f "|")

space =
#(define-music-function (span-bar-before width) ((boolean? #t) number?)
"Adding a gap of width @var{width} into a @code{StaffSymbol}.
Only works in the middle of a @code{Staff}.
Before the gap starts a bar-line with span-bars covering all staves is printed
unless optional @var{span-bar-before} is set false.

After the gap a faked SystemStartBar covering all staves is done, i.e.
@code{SpanBar} is always printed."
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; General TODO
;;   Should we care about SpanBar here at all?
;;   Isn't it more due to user preferences?
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  #{
  %% cadenzaOn/Off will mostly prevent line-breaks, better be paranoid
     \noBreak
     \cadenzaOn
     \stopStaff
     %% no BarNumber before gap
     \once \override Score.BarNumber.break-visibility = ##(#f #f #f)
     %% Print SpanBars relying on `span-bar-before' and
     %% `print-certain-span-bars'
     \once \override Staff.BarLine.allow-span-bar = #span-bar-before
     %% Insert a very short skip-event, with a stretched TextScript
     \once \textLengthOn
     $(if (zero? width)
          #{ #}
          #{
            s1*1/1000000
              -\markup \with-dimensions #(cons 0 width) #'(0 . 0) \null
          #})
     %% Order items after \startStaff
     \once \override Score.BreakAlignment.break-align-orders =
       #(make-vector 3
         '(left-edge
           ambitus
           breathing-sign
           staff-bar
           clef
           key-cancellation
           key-signature
           time-signature
           custos))
     %% Some trickery: set subproperty `new-staff' #t in order to flag this
     %% BarLine for the 'move-system-start'-procedure
     \once \override Score.BarLine.details.new-staff = ##t
     %\once \override StaffGroup.BarLine.details.new-staff = ##t
     %\once \override ChoirStaff.BarLine.details.new-staff = ##t
     %\once \override PianoStaff.BarLine.details.new-staff = ##f
     %% Use the newly defined "|-g" (apart from the name, it's the same as "|")
     %% and let `print-certain-span-bars' work on it, if wished.
     %% \bar "|-g" mimics the SystemStartBar, thus adjust thickness
     %% TODO We assume a new mid-staff SystemStartBar should cover all staves
     %%      Are there othere use-cases?
     \once \override Staff.BarLine.hair-thickness = 1.6
     \bar "|-g"
     %% Force printing full-size Clef
     %% NB setting a different Clef is not prevented
     \once \override Score.Clef.full-size-change = ##t
     \set Staff.forceClef = ##t
     %% Print BarNumber after gap (for debugging purpose)
     %\once \override Score.BarNumber.break-visibility = ##(#f #t #t)
     \startStaff
     \cadenzaOff
     \noBreak
  #})

#(define (print-certain-span-bars? glyphes)
  (lambda (grob)
  "Predicate whether to print @code{SpanBar} at line-end/start, and to print
selected span-bars from @var{glyphes} mid-line as well."
    (or (member (ly:grob-property grob 'glyph-name) glyphes)
        (not (zero? (ly:item-break-dir grob))))))
       
#(define (delete-duplicate-cdr lst)
  "Goes through @var{lst} from right to left, deletes every element which has
the same @code{cdr} as the one to the right.
@var{lst} should be sorted, having adjacent elements with equal @code{cdr}."
  (if (pair? lst)
      (fold-right
        (lambda (elem ret)
          (if (equal? (cdr elem) (cdr (first ret)))
              ret
              (cons elem ret)))
        (list (last lst))
        lst)
      '()))
         
#(define (move-system-start style)
  (lambda (grob)
    (let* (;; system-start-grobs are spanners, thus we need to go through
           ;; the broken parts
           (orig (if (ly:spanner? grob)
                     (ly:grob-original grob)
                     #f))
           (siblings (if (ly:grob? orig)
                         (ly:spanner-broken-into orig)
                         '()))
           ;; padding is set to different values for different
           ;; system-start-grobs or not set at all
           ;; TODO find values programmatically
           (padding
             (case style
               ((bracket) 0.8)
               ((line-bracket) 0)
               ((bar-line) 0)
               ((brace) -0.8)))
           (spanner-id (ly:grob-property grob 'spanner-id))
           ;; Get indent and short-indent from \paper, these values need to
           ;; be respected, while moving
           (indent (ly:output-def-lookup $defaultpaper 'indent))
           (short-indent (ly:output-def-lookup $defaultpaper 'short-indent))
           ;; Get output-scale from grob-layout, above (short-)indent must
           ;; be scaled with this value
           (output-scale
             (ly:output-def-lookup (ly:grob-layout grob) 'output-scale)))
             
      ;; Walk through all siblings
      ;; For each sibling find the bar-lines which indicate a mid-staff gap,
      ;; determine their x-coordinates and move the sibling to the selected
      ;; bar-line (relying on spanner-id.
      ;; Drop superfluous siblings.
      (if (pair? siblings)
          (for-each
            (lambda (sibling)
              (if (equal? grob sibling)
                  (let* ((sys (ly:grob-system sibling))
                         (all-elts-array
                           (ly:grob-object sys 'all-elements))
                         (all-elts-list
                           (ly:grob-array->list all-elts-array))
                         ;; Get all bar-lines, with set details.new-staff
                         (bar-lines
                           (filter
                             (lambda (elt)
                               (and
                                 (grob::has-interface elt 'bar-line-interface)
                                 (assoc-get
                                   'new-staff
                                   (ly:grob-property elt 'details))))
                            all-elts-list))
                         ;; Assign the x-coordinate to each found bar-line.
                         ;; Returns a list of pairs.
                         (bar-line-candidates
                           (map
                             (lambda (bl)
                               (cons bl (ly:grob-relative-coordinate bl sys X)))
                             bar-lines))
                         ;; Needed? - better be paranoid
                         (sorted-bar-line-candidates
                           (sort
                             bar-line-candidates
                             (lambda (p q) (< (cdr p) (cdr q)))))
                         ;; Keep only one bar-line for each x-coordinate
                         (relevant-bar-lines
                           (delete-duplicate-cdr
                             sorted-bar-line-candidates))
                         ;; Get the relevant bar-line-x-coord, by selecting
                         ;; from relevant-bar-lines, taking spanner-id as
                         ;; index. Return #f for spanner-id exceeding the
                         ;; list-length.
                         (bar-line-coord
                           (if (> spanner-id (length relevant-bar-lines))
                               #f
                               (cdr
                                 (list-ref
                                   relevant-bar-lines
                                   (1- spanner-id))))))
                               
                    ;; Set the style of the system-start-grob to move.
                    ;; Move it in front of the relevant bar-line.
                    ;; Drop superfluous ones.
                    (if (and (equal? grob sibling) bar-line-coord)
                        (let ((x-off
                                ;; Take proper scaled (short-)indent into
                                ;; account.
                                ;; Adjust padding.
                                (- bar-line-coord
                                   (/ padding 2)
                                   (/ (if (equal? grob (car siblings))
                                          indent
                                          short-indent)
                                      output-scale))))
                           (ly:grob-set-property! grob 'style style)
                           (ly:grob-set-property! sibling 'X-offset x-off))
                        (ly:grob-suicide! sibling)))))
            siblings)))))
         
#(define* (fake-mid-staff-system-start #:optional (desired-system-starts 5))
  (lambda (ctx)
  "Constructs @code{systemStartDelimiterHierarchy} for @var{ctx}, with the
default @code{systemStartDelimiter} and @var{desired-system-starts} instances
of nested @code{SystemStartSquare}s.
@var{desired-system-starts} will determine how many faked system-starts are
possible.
Collects those @code{SystemStartSquare}s and assigns a @code{spanner-id} to
them.
Finally assigns @code{move-system-start} to @code{after-line-breaking} of each
@code{SystemStartSquare}.
Limitations:
  - @code{SystemStartSquare}s can't be used for other things any more
  - A user provided @code{systemStartDelimiterHierarchy} will be dropped
  - Can't be reasonably consisted in score-context."

    (if desired-system-starts
        (let* ((squares '())
               (system-start-delimiter
                 (ly:context-property ctx 'systemStartDelimiter))
               (style
                 (case system-start-delimiter
                   ((SystemStartBracket) 'bracket)
                   ((SystemStartBrace) 'brace)
                   ((SystemStartBar) 'bar-line)
                   ((SystemStartSquare) 'line-bracket))))
   
          (define
            (construct-system-start-delimiter main name counter init)
            ;; Returns a nested list like:
            ;;  '(main
            ;;     (name
            ;;       (name
            ;;         ...
            ;;           ...
            ;;             init)))
            ;; The nesting-level relies on `counter'. A negative value will
            ;; return an emty list.  Zero returns '(main init). 
            ;;
            ;; Used to construct a list for `systemStartDelimiterHierarchy'

              (cond ((zero? counter) (list main init))
                    ((negative? counter) '())
                    (else
                     (construct-system-start-delimiter
                       main
                       name
                       (1- counter)
                       (cons name (list init))))))

          (ly:context-set-property! ctx 'systemStartDelimiterHierarchy
            (construct-system-start-delimiter
              ;; Use default for starting
              system-start-delimiter
              ;; Which system-start-grob do we use for the inner lists
              'SystemStartSquare
              ;; The nesting level, determines how many mid-staff-gaps get a
              ;; faked SystemStartDelimiter.
              ;; NB For zero one gap is possible etc
              ;; Negative values here will return an empty list
              (1- desired-system-starts)
              ;; The most inner list contains the system-start-grob to use as
              ;; before.
              ;; The numerical value determines the maximium of how many staves
              ;; are covered.
              ;; See discussion:
              ;;
              ;; TODO test with Aaron's engraver
              ;; DONE result: works nicely and makes it possible to reduce the
              ;;              here provided number
              ;;              Probably better to move this functionality out of 
              ;;              the engraver.
              (cons 'SystemStartSquare (iota 100))))
             
          (make-engraver
            (acknowledgers
              ((system-start-delimiter-interface engraver grob source-engraver)
                ;; Always set SystemStartSquare.thickness to 0.45 to ensure
                ;; a sufficient thickness, if style is set 'bracket. The value
                ;; 0.45 is taken from IR for SystemStartBracket
                ;;
                ;; Accumulate all SystemStartSquare-grobs in `squares'.
                (if (eq? (grob::name grob) 'SystemStartSquare)
                    (begin
                      (ly:grob-set-property! grob 'thickness 0.45)
                      (set! squares (cons grob squares))))))
            ((start-translation-timestep trans)
              ;; Assign spanner-id to each collected SystemStartSquare
              (if (pair? squares)
                  (for-each
                    (lambda (i sq)
                      (ly:grob-set-property! sq 'spanner-id i))
                    (iota (length squares) 1 1)
                    squares)))
            ((finalize trans)
              ;; Set each SystemStartSquare.after-line-breaking to
              ;; `move-system-start'-procedure
              (for-each
                (lambda (sq)
                  (ly:grob-set-property! sq 'after-line-breaking
                    (move-system-start style)))
                squares)
              ;; Clean up
              (set! squares '()))))
        ;; don't do anything if `desired-system-starts' is #f
        '())))

sysStart = \with { \consists #(fake-mid-staff-system-start) }


\layout {
  \context {
    \StaffGroup
    \sysStart
  }
  \context {
    \GrandStaff
    \sysStart
  }
  \context {
    \PianoStaff
    \sysStart
  }
  \context {
    \ChoirStaff
    \override BarLine.allow-span-bar =
      #(print-certain-span-bars? '("|." ".|:" ":|." "|-g"))
    \sysStart
  }
  \context {
    \Score
    %% adding Span_bar_engraver
    \consists "Span_bar_engraver"
    %% \sysStart does not work if consisted in Score-context
  }
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%
%% Tests
%%%%%%%%%%%%%%%%%%%%%%

\paper {
  indent = 15
  short-indent = 8
}

#(define (system-start-text::print-mid-line grob)
  (let* ((left-bound (ly:spanner-bound grob LEFT))
         (left-mom (ly:grob-property left-bound 'when))
         (name (if (moment<=? left-mom ZERO-MOMENT)
                   (ly:grob-property grob 'long-text)
                   (ly:grob-property grob 'text))))
    (grob-interpret-markup grob name)))

{
  <<
    \new GrandStaff = "staff-group"
      \with { instrumentName = "St-Gr" shortInstrumentName = "St-Gr" }
      <<
      \new Staff = "1" { b1 1 \once \omit Staff.Clef \space #0 }
      \new Staff = "2" { b1 1 \once \omit Staff.Clef \space #0 }
      >>
  \new Staff = "3" { b1 1 \omit Staff.Clef \space #0 }
  >>
  <<
    \context GrandStaff = "staff-group"
      <<
      \context Staff = "1" {
      \once \override GrandStaff.InstrumentName.stencil =
        #system-start-text::print-mid-line
      \once \override GrandStaff.InstrumentName.Y-offset = #-9
      \once \override GrandStaff.InstrumentName.X-offset = #-2.5
      \once \set GrandStaff.shortInstrumentName = "I"
      c'1 1
      \break
      d'1 1
      }
      \context Staff = "2" { c'1 1 d'1 1}
      >>
    \context Staff = "3" { c'1 1 d'1 1}
  >>
}


martinmagtenor

Hallo Harm,

wow!

Vielen Dank, das klingt ja vielversprechend.

Ich knöpfe mir das mal vor ...

Martin

harm6

Nachtrag:
Der finale Test-Code ist übermäßig kompliziert. Das war noch einer früheren Idee geschuldet. Es geht viel simpler:

<<
  \new GrandStaff = "staff-group"
    \with { instrumentName = "St-Gr" shortInstrumentName = "St-Gr" }
    <<
      \new Staff = "1" {
        b1 1
        \once \omit Staff.Clef
        \space #0
        \once \override GrandStaff.InstrumentName.stencil =
          #system-start-text::print-mid-line
        \once \override GrandStaff.InstrumentName.Y-offset = #-9
        \once \override GrandStaff.InstrumentName.X-offset = #-2.5
        \once \set GrandStaff.shortInstrumentName = "I"
        c'1 1
        \break
        d'1 1
      }
      \new Staff = "2" { b1 1 \once \omit Staff.Clef \space #0 c'1 1 d'1 1 }
    >>
  \new Staff = "3" { b1 1 \omit Staff.Clef \space #0 c'1 1 d'1 1 }
>>

martinmagtenor

Hallo Harm,
nochmal DANKE.
Das Beispiel kompiliert ohne Hand anzulegen auch mit 2.24.4.
Martin

Lilysetter

#7
Kompiliert schon, aber zumindest unter 2.25.6 nahm ich folgende Änderungen vor, um ein ansprechendes Layout zu bekommen:        \once \override GrandStaff.InstrumentName.Y-offset = #-10.5
        \once \override GrandStaff.InstrumentName.X-offset = #-2.5
        \once\set GrandStaff.shortInstrumentName = "I"    das \once weglassen, es soll ja in den nächsten Zeilen auch wirken.
Ansonsten: vielen Dank für Deine sehr hilfreichen Lösungen, Harm. Was täten wir ohne Dich?