Saitenwechsel im Glissando vermeiden

Begonnen von eichhofener, Montag, 13. März 2023, 19:08

« vorheriges - nächstes »

eichhofener

Liebe Leser!
Beim Erstellen einer Tabulator für Saiteninstrumente mit Bünden (bei mir Banjo) verwendet man das Glissando für den "Slide".
Beispielsweise für einen Slide von c auf d siehe unten.

Lilypond berücksichtigt bei der Bestimmung der gespielten Saite leider nicht, ob die Note Teil eines Glissando-Events ist. Dadurch kommt es u.U. dazu, dass im Tab (wo man das ja sieht) die Zielnote auf einer anderen Saite als die Ausgangsnote steht.
Aber während eines Slides wird natürlich die Saite nicht gewechselt.

Natürlich gibt es die Möglichkeit bei der Zielnote die Saitenzahl explizit zu setzen (2. Beispiel) aber schöner wäre es, wenn man Lilypond beibringen könnte, die Saite von der Ausgangsnote beizubehalten.

Ich habe schon daran gedacht, für den Slide eine music function zu schreiben, bei der ich der Zielnote einen passenden StringNumberEvent als Artikulation hinzufüge, den man ja aus der ersten Note übernehmen könnte.
Aber in den meisten Fällen hat die Ausgangsnote selbst gar keinen StringNumberEvent, sondern die Saite wird automatisch von Lilypond ermittelt.
Evtl. müsste man dann selbst anhand des stringTunings aus dem TabStaff Kontext die Saitenzahl ermitteln (obwohl der Code dafür in Lilypond ja schon irgendwo vorhanden sein muss). Aber ich weiß nicht, wie ich an diese Information aus einer music function heraus herankommen.

Hat vielleicht jemand einen Tipp?

Vielen Dank
eichhofener


\version "2.25.2"
\new TabStaff \with { stringTunings=\stringTuning <g' d g b d'> }
{
  c'8 \glissando d' c'8 \glissando d'\2
}

juergen74

#1
Hallo eichhofener,

bevor das Thema ohne Antwort auf die zweite Seite rutscht...

Ich spiele Ukulele und beschäftige mich ebenfalls mit der Thematik TabStaff und automatischer Berechnung von Saiten- und Bundnummern.

Mit einer music-function geht das nicht, da zu dem Zeitpunkt der Kontext und damit das string-tuning noch nicht verfügbar sind(?). Wobei ich den zeitlichen Ablauf an der Stelle noch nicht vollständig durchschaut habe. Eventuell könnte man das tuning global definieren und dann selbst die Berechnungen durchführen?

Zitat(obwohl der Code dafür in Lilypond ja schon irgendwo vorhanden sein muss)
(determine-string-fret-finger context notes specified-info rest) in translation-functions.scm


Ich weiß nicht ob das Sinn macht, aber hier ein Ansatz mit einem Engraver. Zumindest sind hier alle benötigten Daten verfügbar.
\version "2.24.0"

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% from translation-functions.scm line 593 (v2.24.0)
#(define (calc-fret pitch string tuning)
   "Calculate the fret to play @var{pitch} on @var{string} with
@var{tuning}."
   (* 2  (- (ly:pitch-tones pitch) (ly:pitch-tones (list-ref tuning (1- string))))))
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

#(define (staff-position->string-number tuning staffPosition)
   "Map @var{staff-position} to string-number. Eg. (4 2 0 -2 -4) to (1 2 3 4 5)"
   (let* ((stringCount (length tuning)))
     (/ (1+ (- stringCount staffPosition)) 2)))



#(define (Post_glissando_engraver context)
   ; in case of a glissando:
   ; check if current tab-note-head is on same line as previous tab-note-head
   ; and move if necessary
   (let (
          (glissandoPending #f)
          (glissando #f)
          (lastStaffPosition #f)
          (currentPitch #f))

     (make-engraver
      (listeners
       ; will be processed in acknowledger
       ((glissando-event engraver event)
        (set! glissandoPending #t))

       ; store current pitch
       ; required for calculating the fret-number if tab-note-head should be moved to different string
       ((note-event engraver event)
        (set! currentPitch (ly:event-property event 'pitch))))

      (acknowledgers
       ((tab-note-head-interface engraver grob source-engraver)
        (let (
               ; get tuning from context
               (tuning (ly:context-property context 'stringTunings))
               (fretNumber -1)
               (currentStaffPosition (ly:grob-property grob 'staff-position)))

          ; glissando-event in previous time step and current tab-note-head on different string?
          (when (and glissando (not (equal? lastStaffPosition currentStaffPosition)))
            ; calculate fret-number of current pitch for previous string
            (set! fretNumber (calc-fret
                              currentPitch
                              (staff-position->string-number tuning lastStaffPosition)
                              tuning))
            ; what about fret-number = 0? glissando or pull-off?
            ; fret-number valid?
            (if (> fretNumber -1)
                (begin
                 ; move grob...
                 (ly:grob-set-property! grob 'staff-position lastStaffPosition)
                 ; ... and change text according to calculated new fret-number
                 (ly:grob-set-property! grob 'text (markup #:vcenter (number->string fretNumber))))
                ; fret-number not valid
                (ly:warning
                 (format #f "Keeping glissando on same string requires negative fret: string ~s pitch ~s."
                   (staff-position->string-number tuning lastStaffPosition)
                   currentPitch))))

          ; glissando processed in this time step, so...
          (set! glissando #f)

          ; glissando in current time step...
          (when glissandoPending
            (begin
             (set! glissandoPending #f)
             ; ... will be processed in next time step
             (set! glissando #t)))

          ; store current staff position
          (set! lastStaffPosition currentStaffPosition)))))))


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Example

mus = {
  % Should the second d' also stay on the same string?
  c'  \glissando d' d'
}

<<
  \new Staff \mus

  \new TabStaff \with {
    \consists #Post_glissando_engraver
    stringTunings=\stringTuning <g' d g b d'>
  }
  \mus
>>

ToDo:
  • Im Beispiel sollte das zweite d sinnvollerweise auch auf der zweiten Saite gespielt werden
  • Akkorde?
  • definierte stringNumber, fingerNumber?
  • das, was ich sonst alles nicht bedacht habe


Grüße, Jürgen.

Edit: Rechtschreibfehler korrigiert.

juergen74

Da pass schonmal was nicht...
mus = {
  % Should the second d' also stay on the same string?
  c'  \glissando < d' e' >
  c'  \glissando < e' d' >
}

\markup "mit custom Engraver"
<<
  \new Staff \mus

  \new TabStaff \with {
    \consists #Post_glissando_engraver
    stringTunings=\stringTuning <g' d g b d'>
  }
  \mus
>>

\markup "ohne custom Engraver"
<<
  \new Staff \mus

  \new TabStaff \with {
    stringTunings=\stringTuning <g' d g b d'>
  }
  \mus
>>
Du darfst diesen Dateianhang nicht ansehen.

eichhofener

Lieber Jürgen,

vielen Dank für Deine wirklich hilfreiche Antwort.

Ich hätte das alleine wohl nicht hingekriegt. Die Doku gibt leider zum Thema user-defined engravers nicht viel her.
Hast Du Dir die Funktionsweise nur aus vorhandenen Beispielen abgeleitet, oder gibt es irgendwo Infos zum Zusammenspiel von listeners und acknowledgers? (Ich finde die Syntax von make-engraver schon undurchsichtig. Das werden ja offenbar callbacks definiert, aber da fehlt doch das Schlüsselwort lambda.. ist da evtl. die scheme-Syntax modifiziert?)

Ich werde den Code noch ein bisschen erweitern, weil ich mit hammer-ons und pull-offs das gleiche Problem habe. Dafür werden ja slurs zweckentfremdet. Das sollte ich jetzt selbst hinkriegen.

Es ist ein bisschen schade (aber das ist natürlich überhaupt keine Kritik an Deinem Code!), dass die Information, aus erster Note, Glissando und zweiter Note von dem Engraver im Prinzip mit einem Zustandsautomaten aus dem Zeitverlauf wieder zusammengetragen werden muss, obwohl ja vor dem engraving die Informationen schon als music-expression strukturiert zur Verfügung stehen.

Das widerspricht halt völlig dem eigentlichen funktionalen Ansatz von Scheme.

Vielleicht wäre es günstig sich schon vor dem Engraving während des Iterator-Prozesses einzuhängen, wo auch schon Kontext-Info vorhanden ist. Aber ich habe keine Ahnung ob und wie das geht. Ich verfolge also erstmal Deinen Vorschlag mit dem Engraver.

Viele Grüße
Uwe



harm6

#4
Hallo,

Doku
Es gibt im CG etwas Doku zum schreiben eines engravers. (Es bezieht sich zwar immer auf C++, aber für guile gilt das Gleiche.)
Die imho beste Doku ist aber hier zu finden:
https://extending-lilypond.gitlab.io/en/extending/translation.html#writing-an-engraver
Ansonsten gibt es zahlreiche Beispiele auf der mailing-list, hier im Forum sowie in /scm/scheme-engravers.scm

Syntax
`make-engraver' ist ein macro. Hauptsächlich zur Vereinfachung.
Die ausführliche Syntax ist aber nach wie vor valide:
Demo_engraver =
#(lambda (ctx)
  `((listeners
      (note-event
      .
      ,(lambda (engraver event)
        (format #t "note-event seen at ~a\n" (ly:context-current-moment ctx)))))
    (acknowledgers
      (note-head-interface
      .
      ,(lambda (engraver grob source-engraver)
        (ly:grob-set-property! grob 'color red))))))
       
\new Staff \with { \consists \Demo_engraver } { b4 c' d' e' }
In obiger Quelle wird das auch kurz thematisiert.

Zum Problem
Zitatobwohl ja vor dem engraving die Informationen schon als music-expression strukturiert zur Verfügung stehen.
Welche Informationen denn? Sofern man keine Saiten explizit angegeben hat, sind doch nur "nackte" Noten da.
Auch muß ein Glissando nicht auf derselben Saite beendet werden. Es gibt in der Literatur zahlreiche Beispiele für:
\new TabStaff { e,\6\glissando a,\5 }Um hier eine Einstellmöglichkeit zu schaffen könnte man eventuell ein context-property erschaffen, etwas wie:
\set TabVoice.sameStringGlissando = ##t/##f

Wenn ich das Problem mal grundsätzlicher betrachte, müßten in folgenden Beispielen alle Noten auf der zweiten Saite dargestellt werden (default Gitarren-Tabulatur):
mus = {
  b\glissando e'\glissando g'2
  b4( e' g'2)
  g'4\glissando e'\glissando b2
  g'4( e' b2)
}

<<
  \new Staff { \clef "G_8" \mus }
  \new TabStaff \mus
>>

Bei Beispiel eins und zwei könnte man die per default für die erste Note richtig gefundene zweite Saite eventuell auslesen, speichern und sie den beiden folgenden Noten "mitgeben".
Aber wie soll das mit Beispiel drei und vier gehen?
Da ist ja erst mit der letzen Note klar, welche Saite gebraucht wird.
Ich bin mir nicht sicher wie das programmiert werden könnte...
(Es sei denn man gibt bei der ersten Note doch die Saite an)


Gruß,
  Harm

EDIT: Tippfehler korrigiert

juergen74

#5
Hallo Uwe!

Zitat von: eichhofener am Freitag, 14. April 2023, 13:06Hast Du Dir die Funktionsweise nur aus vorhandenen Beispielen abgeleitet, oder gibt es irgendwo Infos zum Zusammenspiel von listeners und acknowledgers?
https://extending-lilypond.gitlab.io/en/extending/translation.html
Das Tutorial find ich insgesamt sehr gelungen.

https://www.mail-archive.com/lilypond-user@gnu.org/msg138462.html

Weitere Beispiele in scheme-engravers.scm und den Regression-Tests.

Zitat von: eichhofener am Freitag, 14. April 2023, 13:06ist da evtl. die scheme-Syntax modifiziert?
make-engraver ist ein Macro. Siehe IR make-translator.

Zum allgemeinen Verständnis habe ich noch den Source-Code und den Contributor's Guide heruntergeladen.
https://lilypond.org/development.html

...sowie dieses pdf:
https://lilypond.gitlab.io/static-files/media/thesis-erik-sandberg.pdf

Außerdem natürlich hilfreich: dieses tolle Forum hier, die internationale Mailing-List und das LSR.

Zitat von: eichhofener am Freitag, 14. April 2023, 13:06Vielleicht wäre es günstig sich schon vor dem Engraving während des Iterator-Prozesses einzuhängen, wo auch schon Kontext-Info vorhanden ist. Aber ich habe keine Ahnung ob und wie das geht.
Das habe ich ebenfalls noch nicht so ganz durchschaut - trotz der ganzen Doku die ich schon gelesen habe. Vielleicht werde ich auch langsam zu alt für so kompliziertes Zeugs...  :(


Grüße, Jürgen.

Edit: zu langsam ;-)

harm6

Zitat von: harmIch bin mir nicht sicher wie das programmiert werden könnte...
Hinzu kommt noch das Problem mit Chord-Glissando.
Bei einem event-chord ist das Glissando in den 'elements des event-chords, bei einzelnen note-events in deren 'articulations.
Das führt u.a. dazu, daß `determine-frets' bei Chord-Glissando schlichtweg überhaupt nicht weiss, ob ein (Chord-)Glissando vorliegt.

Ich hatte nämlich mal den Gedanken `determine-frets' zu erweitern, aber da Chord-Glissandi dort nicht bekannt sind, ist das sinnlos...

Gruß,
  Harm

eichhofener

#7
Zitat von: harm6 am Samstag, 15. April 2023, 11:07
Zitatobwohl ja vor dem engraving die Informationen schon als music-expression strukturiert zur Verfügung stehen.
Welche Informationen denn? Sofern man keine Saiten explizit angegeben hat, sind doch nur "nackte" Noten da.
Okay, das war mir nicht klar. Der Gedanke, die Aufgabe schon beim Iterationsprozess (den ich nicht durchschaue) zu behandeln, war reine Spekulation. Es hätte ja sein können, dass dort schon die Saite festgelegt wird. Es ist gut zu wissen, dass das eine Sackgasse ist.

Zitat von: harm6 am Samstag, 15. April 2023, 11:07Auch muß ein Glissando nicht auf derselben Saite beendet werden.
Verstehe, dann ist es offensichtlich, dass es hier nicht um eine allgemeine Forderung ans Glissando gehen kann. Mir geht es ja nur um die Slides beim Banjo. Dort zumindest ist mir bislang kein Saitenwechsel im Slide bekannt (und ich wüsste auch nicht, wie das dort gehen sollte). Konfigurierbarkeit wäre sicher nicht schlecht.

Zitat von: harm6 am Samstag, 15. April 2023, 11:07Um hier eine Einstellmöglichkeit zu schaffen könnte man eventuell ein context-property erschaffen, etwas wie:
\set TabVoice.sameStringGlissando = ##t/##f
Die Erweiterung um ein Kontext-Property ist aber ein tieferer Eingriff in den Code, und nicht leicht möglich, oder?
Ich habe nämlich ein ähnliches Anliegen beim Slur, den ich für Hammer-On und Pull-Off verwende. Da soll auch die Saite der Zielnote beibehalten werden. Andererseits gibt es aber auch Alternate-String-Pull-Offs und -Pull-Ons (sorry, die Banjo-Literatur ist halt amerikanisch :-) ), bei denen das nicht so ist.
Als Workaround habe ich das Property Slur.annotation zweckentfremdet, um die 4 Arten im Engraver zu unterscheiden (wo ich das Property im Grob anschließend auch wieder löschen muss, weil es sonst angezeigt wird).
Ein explizites benutzerdefiniertes Property wäre natürlich viel schöner...


Zitat von: harm6 am Samstag, 15. April 2023, 11:07Bei Beispiel eins und zwei könnte man die per default für die erste Note richtig gefundene zweite Saite eventuell auslesen, speichern und sie den beiden folgenden Noten "mitgeben".
Aber wie soll das mit Beispiel drei und vier gehen?
Da ist ja erst mit der letzen Note klar, welche Saite gebraucht wird.
Ich bin mir nicht sicher wie das programmiert werden könnte...
(Es sei denn man gibt bei er ersten Note doch die Saite an)
Mit Deinen Beispielen hast Du sicher recht. Es wäre die "Kür", wenn der Algorithmus zur Saitenfindung auch sicherstellte, dass der Slide überhaupt durchführbar ist. Für meine Zwecke wäre es akzeptabel, wenn man im Problemfall die Saite bei der ersten Note angeben müsste.



insgesamt vielen Dank für Deine Ausführungen.
Viele Grüße
Uwe

eichhofener

Zitat von: harm6 am Samstag, 15. April 2023, 11:59Hinzu kommt noch das Problem mit Chord-Glissando.
Ja, in dem Falle müsste man auf das bisherige Default-Verhalten zurückfallen (auf der Ebene von music-functions kann ich ja noch erkennen, dass der Ausgangspunkt eine Akkord ist).
Das wäre auch kein großes Problem, weil durch das gleichzeitige Spielen mehrerer Saiten die zu spielenden Saiten meistens schon eindeutig festgelegt sind, und es kaum noch zu "Fehlinterpretationen" der Zielsaite kommt.

Aber ich verstehe natürlich, dass Du als Maintainer auf eine saubere allumfassende Lösung aus bist, während ich eher einen "Hack" im Sinn habe...

Viele Grüße
Uwe

eichhofener

Zitat von: juergen74 am Samstag, 15. April 2023, 11:22...
Das Tutorial find ich insgesamt sehr gelungen.
...
Edit: zu langsam ;-)

Dennoch vielen Dank, das ist ein prima Fingerzeig (und ganz deckungsgleich sind die Informationen ja auch nicht).
Viele Grüße
Uwe

juergen74

#10
Hallo!
Zitat von: harm6 am Samstag, 15. April 2023, 11:07Welche Informationen denn? Sofern man keine Saiten explizit angegeben hat, sind doch nur "nackte" Noten da.

Das ist ja m.E. die ursprüngliche Frage in diesem Thread (unabhängig von Glissando und Saiten-Nummern): An welcher Stelle kann ich mich frühstmöglich einklinken um einen musikalischen Ausdruck - nackte Noten - in Abhängigkeit von Context-Eigenschaften zu bearbeiten? Geht das erst in einem Translator wenn musikalische Ausdrücke in Streaming Events umgewandelt sind?

... und hat sich mein Verständnisproblem vielleicht schon in der Formulierung der Fragen offenbart?  ;)

Wie setze ich zum Beispiel das hier um:
\version "2.24.0"

% intro depending on tuning
intro =
#(define-music-function () ()
   (make-apply-context (lambda (context)
                         (if (equal? (ly:context-property context 'stringTunings) tenor-ukulele-tuning)
                              #{ g a b c' #}
                               #{ f' e' d' c' #}))))


\new TabStaff \with { stringTunings = #tenor-ukulele-tuning }
{
  \intro
  c' d' e'
}

Grüße, Jürgen.

Edit: Syntaxfehler korrigiert