Wie kriege ich den Ambitus in die 2. Zeile? (es existiert keine Lösung)

Begonnen von Manuela, Freitag, 13. März 2026, 18:07

Vorheriges Thema - Nächstes Thema

Manuela

\version "2.25.34"

mus = \relative {
  c''4 c c c c c c c, }

\layout {
  \context {
    \Voice
    \name SingVoice
    \alias Voice
    \consists "Ambitus_engraver"
    \ambitusAfter time-signature
  }
  \context {
    \Staff
    \RemoveAllEmptyStaves
    \accepts SingVoice
  }

}

\paper {
  ragged-bottom = ##t
  indent = 0
  top-margin = #20
  tagline = ##f
  line-width = #150
}

\new StaffGroup
<<
  \new Staff
  \new Voice
  {
    s1 * 2 \break
    \mus \mus
  }
  \new PianoStaff
  <<
    \new Staff { \mus \mus \mus }
    \new Staff {
      \clef bass
      \transpose c c,, { \mus \mus \mus }
    }
  >>
>>

\new StaffGroup
<<
  \new Staff
  \new SingVoice
  {
    s1 * 2 \break
    \mus \mus
  }
  \new PianoStaff
  <<
    \new Staff { \mus \mus \mus }
    \new Staff {
      \clef bass
      \transpose c c,, { \mus \mus \mus }
    }
  >>
>>

Wenn ich keinen Ambitus habe, dann erscheint der Staff für die Singstimme erst dann, wenn es Noten in dieser Stimme gibt.
Füge ich einen Ambitus hinzu, habe ich eine leere Zeile. Gibt es einen Möglichkeit, den Ambitus an den Beginn der Singstimme zu setzen?

Ich denke, das Problem wird öfter vorkommen, wenn man Klavierbegleitung hat, die erst ein kleines Intro beinhaltet.
Danke für eure Hilfe
viele Grüße
-- Manuela

harm6

Hallo Manuela,

ich habe keine wirklich gute Lösung für dich, aber ein bisschen Hintergrund und einen workaround.

Der Ambitus ist ein Container-grob, welches u.a. AmbitusNoteHead und AmbitusLine enthält. Der Ambitus_engraver legt den Ambitus ganz am Anfang an und füllt ihn auf ganz am Ende.
Wenn man also den Ambitus_engraver nicht komplett neu schreibt (und dafür fehlt mir sowohl Zeit als auch Motivation), so wird er immer am Anfang erscheinen.

Als workaround:

\version "2.25.34"

mus = \relative {
  c''4 c c c c c c c, }

\layout {
  \context {
    \Voice
    \name SingVoice
    \alias Voice
  }
  \context {
    \Staff
    \RemoveAllEmptyStaves
    \accepts SingVoice
  }

}

\paper {
  ragged-bottom = ##t
  indent = 0
  top-margin = #20
  tagline = ##f
  line-width = #150
}

\new StaffGroup
<<
  \new Staff {
    \new SingVoice = "Intro" { s1 * 2 \break }
    \new SingVoice = "Mel"
      \with {
      \consists "Ambitus_engraver"
      \override AmbitusNoteHead.break-visibility = ##(#f #f #t)
      \override AmbitusLine.break-visibility = ##(#f #f #t)
      \override AmbitusAccidental.break-visibility = ##(#f #f #t)
      %\ambitusAfter time-signature
      }
      { \mus \mus }
    }
  \new Lyrics \lyricsto "Mel" { \repeat unfold 16 bla }
  \new PianoStaff
  <<
    \new Staff { \mus \mus \mus }
    \new Staff {
      \clef bass
      \transpose c c,, { \mus \mus \mus }
    }
  >>
>>

HTH,
  Harm


Manuela

Danke für die Antwort, Harm. Irgendwie hatte ich das schon vermutet, ich hatte gehofft, dass es doch eine überraschende Lösung gibt.
Danke für eure Hilfe
viele Grüße
-- Manuela

harm6

Zitat von: harmWenn man also den Ambitus_engraver nicht komplett neu schreibt (und dafür fehlt mir sowohl Zeit als auch Motivation), so wird er immer am Anfang erscheinen.

Tatsächlich gibt es den Ambitus_engraver in scheme schon:
https://wiki.lilypond.community/wiki/Defining_an_engraver_in_Scheme:_ambitus_engraver

Insoweit ist eine zumindest semi-automatische Codierung möglich (Kommentare im file).

\version "2.24"

%%% This file provides a modified rewrite of the Ambitus_engraver from
%%% https://wiki.lilypond.community/wiki/Defining_an_engraver_in_Scheme:_ambitus_engraver
%%%
%%% The new context-property 'printAmbitus is introduced.
%%%
%%% As opposed to the original behaviour an Ambitus is printed only when
%%% 'printAmbitus is set true. The Ambitus is printed at the musical moment
%%% where 'printAmbitus occurs.
%%% An Ambitus is printed once per context only.
%%%
%%% This may be useful if empty staves are removed.
%%%
%%% NB Only NoteHeads entered after 'printAmbitus are considered for building
%%% the Ambitus.

%% Define new context-property 'printAmbitus
#(define (define-translator-property symbol type? description)
  (if (not (and (symbol? symbol)
                (procedure? type?)
                (string? description)))
      (ly:error "error in call of define-translator-property"))
  (if (not (equal? (object-property symbol 'translation-doc) #f))
      (ly:error (_ "symbol ~S redefined") symbol))

  (set-object-property! symbol 'translation-type? type?)
  (set-object-property! symbol 'translation-doc description)
  symbol)

#(for-each
  (lambda (x)
    (apply define-translator-property x))
    `((printAmbitus
       ,boolean?
       "Print Ambitus.")))


#(use-modules (oop goops))

%%%
%%% Grob utilities
%%%
%%% These are literal rewrites of some C++ methods used by the ambitus
%%% engraver.

#(define (ly:separation-item::add-conditional-item grob grob-item)
   "Add GROB-ITEM to the array of conditional elements of GROB.

This is a rewrite of function `Separation_item::add_conditional_item` from
file `lily/separation-item.cc`."
   (ly:pointer-group-interface::add-grob
    grob 'conditional-elements grob-item))

#(define (ly:accidental-placement::accidental-pitch accidental-grob)
   "Get the pitch from the grob cause of ACCIDENTAL-GROB.

This is a rewrite of function `accidental_pitch` from file
`lily/accidental-placement.cc`."
   (ly:event-property (ly:grob-property
                       (ly:grob-parent accidental-grob Y) 'cause)
                      'pitch))

#(define (ly:accidental-placement::add-accidental grob accidental-grob)
   "Add ACCIDENTAL-GROB to the list of accidentals grobs of GROB.
ACCIDENTAL-GROB is an `Accidental` grob; GROB is an `AccidentalPlacement`
grob.

This is a rewrite of function `Accidental_placement::add_accidental` from
file `lily/accidental-placement.cc`."
   (let ((pitch (ly:accidental-placement::accidental-pitch
                 accidental-grob)))
     (set! (ly:grob-parent accidental-grob X) grob)
     (let* ((accidentals (ly:grob-object grob 'accidental-grobs))
            (handle (assq (ly:pitch-notename pitch) accidentals))
            (entry (if handle (cdr handle) '())))
       (set! (ly:grob-object grob 'accidental-grobs)
             (assq-set! accidentals
                        (ly:pitch-notename pitch)
                        (cons accidental-grob entry))))))

%%%
%%% Ambitus data structures.
%%%

%%% The <ambitus> class holds the various grobs that are created to
%%% print an ambitus:
%%%
%%% - `ambitus-group`: the grob that groups all the components of an
%%%   ambitus (`Ambitus` grob);
%%% - `ambitus-line`: the vertical line between the upper and lower
%%%   ambitus notes (`AmbitusLine` grob);
%%% - `ambitus-up-note` and `ambitus-down-note`: the note head and
%%%   accidental for the lower and upper note of the ambitus (see
%%%   `<ambitus-note>` class below).
%%%
%%% The other slots define the key and clef context of the engraver:
%%%
%%% - `start-c0`: position of middle c at the beginning of the piece.
%%%   It is used to place the ambitus notes according to their pitch;
%%% - `start-key-sig`: the key signature at the beginning of the
%%%   piece.  It is used to determine whether accidentals shall be
%%%   printed next to ambitus notes.

#(define-class <ambitus> ()
   (ambitus-group #:accessor ambitus-group)
   (ambitus-line #:accessor ambitus-line)
   (ambitus-up-note #:getter ambitus-up-note
                    #:init-form (make <ambitus-note>))
   (ambitus-down-note #:getter ambitus-down-note
                      #:init-form (make <ambitus-note>))
   (start-c0 #:accessor ambitus-start-c0
             #:init-value #f)
   (start-key-sig #:accessor ambitus-start-key-sig
                  #:init-value '()))

%%% Accessor for the lower and upper note data of an ambitus.
#(define-method (ambitus-note (ambitus <ambitus>) direction)
   "Return lower or upper note of AMBITUS depending on DIRECTION."
   (if (= direction UP)
       (ambitus-up-note ambitus)
       (ambitus-down-note ambitus)))

%%% The `<ambitus-note>` class holds the grobs that are specific to
%%% ambitus (lower and upper) notes:
%%%
%%% - `head`: an `AmbitusNoteHead` grob;
%%% - `accidental`: an `AmbitusAccidental` grob, to be possibly
%%%   printed next to the ambitus note head.
%%%
%%% Moreover,
%%%
%%% - `pitch` is the absolute pitch of the note;
%%% - `cause` is the note event that causes this ambitus note, i.e.,
%%%   the lower or upper note of the considered music sequence.

#(define-class <ambitus-note> ()
   (head #:accessor ambitus-note-head
         #:init-value #f)
   (accidental #:accessor ambitus-note-accidental
               #:init-value #f)
   (cause #:accessor ambitus-note-cause
          #:init-value #f)
   (pitch #:accessor ambitus-note-pitch
          #:init-value #f))

%%%
%%% Ambitus engraving logic.
%%%
%%% This is rewrite of the code from file `lily/ambitus-engraver.cc`.

#(define (make-ambitus translator)
   "Build an ambitus object: initialize all the grobs and their
relations.

The `Ambitus` grob contains all other grobs:

 Ambitus
  |- AmbitusLine
  |- AmbitusNoteHead    for upper note
  |- AmbitusAccidental  for upper note
  |- AmbitusNoteHead    for lower note
  |- AmbitusAccidental  for lower note

The parent of an accidental is the corresponding note head, and the
accidental is set as the `accidental-grob` property of the note head
so that is printed by the function that prints notes."
   ;; Make the ambitus object.
   (let ((ambitus (make <ambitus>)))
     ;; Build the `Ambitus` grob, which will contain all other grobs.
     (set! (ambitus-group ambitus)
           (ly:engraver-make-grob translator 'Ambitus '()))
     ;; Build the `AmbitusLine` grob (the line between lower and upper
     ;; note).
     (set! (ambitus-line ambitus)
           (ly:engraver-make-grob translator 'AmbitusLine '()))
     ;; Build the upper and lower `AmbitusNoteHead` and
     ;; `AmbitusAccidental`.
     (for-each
      (lambda (direction)
        (let ((head (ly:engraver-make-grob translator
                                           'AmbitusNoteHead '()))
              (accidental (ly:engraver-make-grob translator
                                                 'AmbitusAccidental '()))
              (group (ambitus-group ambitus)))
          ;; The parent of the `AmbitusAccidental` grob is the
          ;; `AmbitusNoteHead` grob.
          (set! (ly:grob-parent accidental Y) head)
          ;; The `AmbitusAccidental` grob is set as the
          ;; `accidental-grob` object of `AmbitusNoteHead`.  This is
          ;; later used by the function that prints notes.
          (set! (ly:grob-object head 'accidental-grob) accidental)
          ;; Both the note head and the accidental grobs are added to
          ;; the main ambitus grob.
          (ly:axis-group-interface::add-element group head)
          (ly:axis-group-interface::add-element group accidental)
          ;; The note head and the accidental grobs are added to the
          ;; ambitus object.
          (set! (ambitus-note-head (ambitus-note ambitus direction))
                head)
          (set! (ambitus-note-accidental (ambitus-note ambitus direction))
                accidental)))
      (list DOWN UP))

     ;; The parent of the ambitus line is the lower ambitus note head.
     (set! (ly:grob-parent (ambitus-line ambitus) X)
           (ambitus-note-head (ambitus-note ambitus DOWN)))
     ;; The ambitus line is added to the ambitus main grob.
     (ly:axis-group-interface::add-element (ambitus-group ambitus)
                                           (ambitus-line ambitus))
     ambitus))

#(define-method (initialize-ambitus-state
                 (ambitus <ambitus>) translator)
   "Initialize the state of AMBITUS by getting the starting position of
middle C and key signature from TRANSLATOR's context."
   (if (not (ambitus-start-c0 ambitus))
       (begin
         (set! (ambitus-start-c0 ambitus)
               (ly:context-property (ly:translator-context translator)
                                    'middleCPosition 0))
         (set! (ambitus-start-key-sig ambitus)
               (ly:context-property (ly:translator-context translator)
                                    'keyAlterations)))))

#(define-method (update-ambitus-notes (ambitus <ambitus>) note-grob)
   "Update upper and lower ambitus pitches of AMBITUS using NOTE-GROB."
   ;; Get the event that caused the `note-grob` creation and check
   ;; that it is a `note-event`.
   (let ((note-event (ly:grob-property note-grob 'cause)))
     (if (ly:in-event-class? note-event 'note-event)
         ;; Get the pitch from the note event.
         (let ((pitch (ly:event-property note-event 'pitch)))
           ;; If this pitch is lower than the current ambitus' lower
           ;; note pitch (or it has not been initialized yet), then
           ;; this pitch is the new ambitus' lower pitch.  The same is
           ;; done for the upper pitch (but in the opposite
           ;; direction).
           (for-each
            (lambda (direction pitch-compare)
              (if (or (not (ambitus-note-pitch
                            (ambitus-note ambitus direction)))
                      (pitch-compare
                       pitch (ambitus-note-pitch
                              (ambitus-note ambitus direction))))
                  (begin
                    (set! (ambitus-note-pitch
                           (ambitus-note ambitus direction))
                          pitch)
                    (set! (ambitus-note-cause
                           (ambitus-note ambitus direction))
                          note-event))))
            (list DOWN UP)
            (list ly:pitch<?
                  (lambda (p1 p2) (ly:pitch<? p2 p1))))))))

#(define-method (typeset-ambitus (ambitus <ambitus>) translator)
   "Typeset AMBITUS.

- Place the lower and upper ambitus notes according to their pitch and
  the position of the middle C.
- Typeset or delete the note accidentals, according to the key
  signature.  An accidental, if it is to be printed, is added to an
  `AccidentalPlacement` grob (a grob dedicated to the placement of
  accidentals near a chord).
- Both note heads are added to the ambitus line grob so that a line
  gets printed between them."
   ;; Check whether there are lower and upper pitches.
   (if (and (ambitus-note-pitch (ambitus-note ambitus UP))
            (ambitus-note-pitch (ambitus-note ambitus DOWN)))
       ;; Make an `AccidentalPlacement` grob, for placement of note
       ;; accidentals.
       (let ((accidental-placement
              (ly:engraver-make-grob
               translator
               'AccidentalPlacement (ambitus-note-accidental
                                     (ambitus-note ambitus DOWN)))))
         ;; For lower and upper ambitus notes.
         (for-each
          (lambda (direction)
            (let ((pitch (ambitus-note-pitch
                          (ambitus-note ambitus direction))))
              ;; Set the cause and the staff position of the ambitus
              ;; note according to the associated pitch.
              (set! (ly:grob-property
                     (ambitus-note-head (ambitus-note ambitus direction))
                     'cause)
                    (ambitus-note-cause (ambitus-note ambitus direction)))
              (set! (ly:grob-property
                     (ambitus-note-head (ambitus-note ambitus direction))
                     'staff-position)
                    (+ (ambitus-start-c0 ambitus)
                       (ly:pitch-steps pitch)))
              ;; Determine whether an accidental shall be printed for
              ;; this note, according to the key signature.
              (let* ((handle
                      (or (assoc (cons (ly:pitch-octave pitch)
                                       (ly:pitch-notename pitch))
                                 (ambitus-start-key-sig ambitus))
                          (assoc (ly:pitch-notename pitch)
                                 (ambitus-start-key-sig ambitus))))
                     (sig-alter (if handle (cdr handle) 0)))
                (cond
                 ((= (ly:pitch-alteration pitch) sig-alter)
                  ;; The note alteration is in the key signature
                  ;; => it does not have to be printed.
                  (ly:grob-suicide! (ambitus-note-accidental
                                     (ambitus-note ambitus direction)))
                  (set! (ly:grob-object (ambitus-note-head
                                         (ambitus-note ambitus direction))
                                        'accidental-grob)
                        '()))
                 (else
                  ;; Otherwise the accidental shall be printed.
                  (set! (ly:grob-property
                         (ambitus-note-accidental
                          (ambitus-note ambitus direction)) 'alteration)
                        (ly:pitch-alteration pitch)))))
              ;; Add the `AccidentalPlacement` grob to the conditional
              ;; items of the `AmbitusNoteHead`.
              (ly:separation-item::add-conditional-item
               (ambitus-note-head (ambitus-note ambitus direction))
               accidental-placement)
              ;; Add the `AmbitusAccidental` to the list of the
              ;; `AccidentalPlacement` grob accidentals.
              (ly:accidental-placement::add-accidental
               accidental-placement
               (ambitus-note-accidental (ambitus-note ambitus direction)))
              ;; Add the `AmbitusNoteHead` grob to the `AmbitusLine` grob.
              (ly:pointer-group-interface::add-grob
               (ambitus-line ambitus)
               'note-heads
                (ambitus-note-head (ambitus-note ambitus direction)))))
          (list DOWN UP))
         ;; Add the `AccidentalPlacement` grob to the main `Ambitus` grob.
         (ly:axis-group-interface::add-element
          (ambitus-group ambitus) accidental-placement))
       ;; No lower and upper pitches => nothing to print.
       (begin
         (for-each
          (lambda (direction)
            (ly:grob-suicide! (ambitus-note-accidental
                               (ambitus-note ambitus direction)))
            (ly:grob-suicide! (ambitus-note-head
                               (ambitus-note ambitus direction))))
          (list DOWN UP))
         (ly:grob-suicide! ambitus-line))))

%%%
%%% Ambitus engraver definition.
%%%
#(define ambitus-engraver
   (lambda (context)
     (let ((ambitus #f))
       ;; When music is processed and the context-property printAmbitus is true,
       ;; make the ambitus object if not already built.
       (make-engraver
        ((process-music translator)
         (if (and (not ambitus) (ly:context-property context 'printAmbitus #f))
             (set! ambitus (make-ambitus translator))))

        ;; Set the ambitus clef and key signature state.
        ((stop-translation-timestep translator)
         (if ambitus
             (initialize-ambitus-state ambitus translator)))

        ;; When a note head grob is built, update the ambitus notes.
        (acknowledgers
         ((note-head-interface engraver grob source-engraver)
          (if ambitus
              (update-ambitus-notes ambitus grob))))

        ;; Finally, typeset the ambitus according to its upper and
        ;; lower notes (if any).
        ((finalize translator)
         (if ambitus
             (typeset-ambitus ambitus translator)))))))

%%%
%%% Example
%%%

mus = \relative {
  c''4 c c c c c c c,
}

\layout {
  \context {
    \Voice
    \name SingVoice
    \alias Voice
    \consists #ambitus-engraver
    \ambitusAfter time-signature
    %% To get Ambitus at the very beginning uncomment next line
    %printAmbitus = ##t
  }
  \context {
    \Staff
    \RemoveAllEmptyStaves
    \accepts SingVoice
  }

}

\paper {
  ragged-bottom = ##t
  indent = 0
  top-margin = #20
  tagline = ##f
  line-width = #150
}

\new StaffGroup
<<
  \new Staff
  \new SingVoice
  {
    s1 * 2 \break

    %% Print Ambitus here.
    \set Staff.printAmbitus = ##t
    %% Do not print Ambitus at the end of previous line.
    \override AmbitusNoteHead.break-visibility = ##(#f #f #t)
    \override AmbitusLine.break-visibility = ##(#f #f #t)
    \override AmbitusAccidental.break-visibility = ##(#f #f #t)
    %% Do not consider Ambitus at line end while removing staves.
    \once \set Staff.keepAliveInterfaces = #'()

    \mus \mus
  }
  \new PianoStaff
  <<
    \new Staff { \mus \mus \mus }
    \new Staff {
      \clef bass
      \transpose c c,, { \mus \mus \mus }
    }
  >>
>>


HTH,
  Harm