Deutsches Lilypond Forum (Archiv)

Allgemein => Fragen zu Funktionen => Thema gestartet von: mgd am Montag, 11. Mai 2015, 22:11

Titel: Staccatopunkte unter freiem Text (\markup) GELÖST
Beitrag von: mgd am Montag, 11. Mai 2015, 22:11
Hallo Forum,

im Textteil eines anglican chants werden Punkte unter den Vokalen der Silben dazu verwendet, besondere Betonungen im Text zu notieren. Das folgende Snippet illustriert, wie ich das bisher löse:
\version "2.18.2"

\markup {
  % "let the congre-" mit Punkten darunter
  \line { \concat { l \combine e \translate #'(0.27 . -0.5) . t }
          \concat { th \combine e \translate #'(0.27 . -0.5) . }
          \concat { c \combine o \translate #'(0.27 . -0.5) . ngr
                    \combine e \translate #'(0.27 . -0.5) . - } }
}

Es funktioniert und das Ergebnis sieht auch so aus, wie ich mir das vorstelle. Aber:
1. Die Mengen an \concat und \combine sind sehr unübersichtlich. Geht das auch einfacher ?
2. Wie könnte man ein vergleichbares Ergebnis innerhalb von \lyricmode erzielen ?

Für jede Verbesserung dankbar,
Michael
Titel: Re: Staccatopunkte unter freiem Text (\markup)
Beitrag von: harm6 am Dienstag, 12. Mai 2015, 00:16
Ich hab mal ein underdot-markup-command geschrieben, das hilft schon mal etwas.

\version "2.18.2"

#(define-markup-command (underdot layout props arg)
  (markup?)
  #:category font
    (interpret-markup layout props
      (markup #:override '(baseline-skip . 0.5) #:center-column (arg "."))))
   
\markup {
  % "let the congre-" mit Punkten darunter
  \line { \concat { l \underdot e t }
          \concat { th \underdot e }
          \concat { c \underdot o ngr \underdot e - } }
}


\new Lyrics
\lyricmode {
  \markup \concat { l \underdot e t }
  \markup \concat { th \underdot e }
}

Ich weiß aber nicht, ob Du auf \concat wirst verzichten können:
Es ist ja denkbar den Befehl so zu bauen, daß der Buchstabe Nummer x der folgenden Silbe (oder des Wortes) diesen Punkt bekommt. Allerdings müßte man die Silben (Worte) in einzelne Buchstaben zerlegen (und diese anschließend wieder zusammensetzen).
Das wird sehr schnell ungemütlich sobald Buchstaben oder Zeichen (wenn ich an außereuropäische Sprachen denke) ins Spiel kommen, deren string-length gößer als 1 ist.

Fängt schon bei deutschen Umlauten an:
guile> (string-length "ö")
2


Zuletzt habe ich das hier (https://liarchiv.joonet.de/index.php?topic=1950.msg10678#msg10678) versucht und bin aus unbekannten Gründen gescheitert. :(


Gruß,
  Harm
Titel: Re: Staccatopunkte unter freiem Text (\markup)
Beitrag von: mgd am Dienstag, 12. Mai 2015, 00:56
Klasse !

Ich hatte angefangen ein underdot-markup-command zu schreiben, habe aber an diesem Punkt offenbar noch einiges zu lernen. Mit \underdot werde ich zumindest schon einmal die \combine  nebst \translate los und habe ausserdem eine zentrale Stelle, die Positionierung anzupassen.

Das Fass Text zu parsen und Zeichen zu identifizieren möchte ich nicht aufmachen. Ich weiss zuviel über Zeichensätze, um das ohne Not zu wollen )

Hätten die Tags in lilypond einen expliziten Ende-Marker und nicht nur einen Start-Marker (das '\'), dann wäre es sicher leicht zu implementieren. So ist der Leidensdruck durch die Existenz von \concat wohl überschaubar.

Nein, ich denke du hast recht und ich werde halt \concat einsetzen. \underdot ist ja bereits eine deutliche Verbesserung.

Vielen Dank,
Michael
Titel: Re: Staccatopunkte unter freiem Text (\markup)
Beitrag von: iugin am Dienstag, 12. Mai 2015, 09:12
Hallo Harm

ich habe, wie du schon weisst, keine grosse Ahnung von Scheme (ich werde aber immer besser)...
Könnte man nicht in dem Fall ein Befehl kreieren, welcher ein String als Argument nimmt (z.B. "Hal*lo"), wandelt es in etwas wie char-Array um, und durch Iteration alle Buchstaben ausgibt. Wenn ein bestimmter Zeichen vorkommt (wie in dem Fall '*'), wird das Zeichen übersprungen, dafür aber ein Punkt gesetzt?

Ich konnte es sogar ein bisschen basteln, aber nur mit Output auf die Konsole. Dann wusste ich nicht, wie man dann das ganze in einen markup konvertiert (bin trotzdem stolz auf mich) ;)

Einen lieben Gruss

Eugenio

Titel: Re: Staccatopunkte unter freiem Text (\markup)
Beitrag von: iugin am Dienstag, 12. Mai 2015, 09:13
Ach ja, ich habe deine Antwort wieder gelesen... Das Problem sind die Umlaute und so...
Sorry für das Reinschwatzen :)

Eugenio
Titel: Re: Staccatopunkte unter freiem Text (\markup)
Beitrag von: mgd am Dienstag, 12. Mai 2015, 09:28
Hallo Eugenio,

vielen Dank für deinen Vorschlag. Ich denke, grundsätzlich sollte das klappen können. Vorraussetzung wäre allerdings, dass Guile (oder Scheme) Infrastruktur bereit stellen, über die einzelnen Zeichen einer Zeichenkette zu iterieren. Mit solch einem Iterator könnte man wie von dir vorgeschlagen durch die Zeichenkette wandern und nach UnderdotMarkern suchen und entsprchend umwandeln.

Dann bliebe nur noch der Fall, dass der UnderdotMarker selbst ausgegeben werden soll, aber ich denke, damit könnte ich sehr gut leben. Das könnte man wie in vielen anderen Srachen auch z.B. durch ein zusätzliches Escape Symbol (z.B. '\') ermöglichen.

Bleibt die Frage, ob Guile einen Iterator wie oben bereit stellt und falls nicht, ob das nicht ganz grundsätzlich für Guile eine sinnvolle Erweiterung wäre :)

Liebe Grüße,
Michael
Titel: Re: Staccatopunkte unter freiem Text (\markup)
Beitrag von: harm6 am Mittwoch, 13. Mai 2015, 23:13
Sorry für das Reinschwatzen :)

Kein Grund sich zu entschuldigen!!
Vor allem nicht, wenn Du bedenkenswerte Vorschläge machst!!

Hallo Eugenio,

vielen Dank für deinen Vorschlag. Ich denke, grundsätzlich sollte das klappen können. Vorraussetzung wäre allerdings, dass Guile (oder Scheme) Infrastruktur bereit stellen, über die einzelnen Zeichen einer Zeichenkette zu iterieren. Mit solch einem Iterator könnte man wie von dir vorgeschlagen durch die Zeichenkette wandern und nach UnderdotMarkern suchen und entsprchend umwandeln.

Dann bliebe nur noch der Fall, dass der UnderdotMarker selbst ausgegeben werden soll, aber ich denke, damit könnte ich sehr gut leben. Das könnte man wie in vielen anderen Srachen auch z.B. durch ein zusätzliches Escape Symbol (z.B. '\') ermöglichen.

Bleibt die Frage, ob Guile einen Iterator wie oben bereit stellt und falls nicht, ob das nicht ganz grundsätzlich für Guile eine sinnvolle Erweiterung wäre :)

Es gibt tatsächlich einige Proceduren in guile, die einen string bearbeiten.
Ich habe allerdings nichts gefunden welches direkt gepaßt hätte und/oder mit dem ich zurecht gekommen wäre.

Also habe ich selbst was geschrieben. Kommt mir alles sehr umständlich vor, hat aber zwei große Vorteile:
a) Um David Kastrup frei zu zitieren: Wenn ich es selbst schreibe, weiß ich wenigstens wo die bugs sind.
b) Es funktioniert :D (zumindest soweit ich getestet habe)

\version "2.18.2"

#(define-markup-command (underdot layout props arg)
  (markup?)
"Place a dot below @var{arg} using @code{\\center.column}"
    (interpret-markup layout props
      (markup #:override '(baseline-skip . 0.5) #:center-column (arg "."))))

#(define (get-string-indices strg ref-string)
"Returns a list accumulating the indices of all occurrences of @var{ref-string}
in @var{strg}"
  (define (helper st s l)
    (let ((in (string-contains st s))
          (val (if (null? l) 0 (+ 1 (car l)))))
      (if (or (not in) (string-null? st))
          (reverse l)
          (helper (string-drop st (+ 1 in)) s (cons (+ in val) l)))))   
  (helper strg ref-string '()))

#(define (extract-strgs strg ls)
"Return a list of substrings from @var{strg} using the number-pairs from
@var{ls}"
  (if (number-pair-list? ls)
      (map (lambda (e) (substring strg (car e) (cdr e))) ls)
      (ly:error "ls from extract-strgs needs to be a list of number-pairs")))

#(define-markup-command (emphasize layout props strg)(string?)
#:properties ((func make-underdot-markup)
              (indicator "*"))
"Applies @code{func} to substrings of @var{strg}.  Those substrings have to be
marked with @code{indicator}, which defaults to @code{"*"}

@lilypond[verbatim,quote]
\\markup \\column {
  \\emphasize #"a*ä**ö*de*f**ü*"
  \\override #'(indicator . "_") \\emphasize #"a_ä__ö_de_f__ü_"
  \\override #`(func . ,make-underline-markup) \\emphasize #"a*ä**ö*de*f**ü*"
}
@end lilypond
"
  (let* ((string-indices (get-string-indices strg indicator))
         (paired-string-indices
           (if (odd? (length string-indices))
               (ly:error "indicator not set?")
               (ly:list->offsets '() string-indices)))
         ;; get rid of superfluous indicators
         (offset-car-paired-string-indices
           (map
             (lambda (e)
               (offset-add '(1 . 0) e))
             paired-string-indices))
         (sub-strings (extract-strgs strg offset-car-paired-string-indices))
         (indicator-char
           (let ((ls (string->list indicator)))
             (if (not (= (length ls) 1))
                 (ly:error
                   "indicator needs to be (= (string-length indicator) 1)")
                 (car ls))))
         (strg-ls (string-split strg indicator-char))
         (mrkp
           (make-concat-markup
             (map
               (lambda (x)
                 (if (member x sub-strings)
                     (func x)
                     x))
               strg-ls))))
    (interpret-markup layout props mrkp)))
   
%%%%%%%%%%%%%%%%%%%%%%
%% EXAMPLES
%%%%%%%%%%%%%%%%%%%%%%

\markup
  \rounded-box
  \emphasize #"a*ä**ö*de*f**ü*"
 
\markup
  \rounded-box
  \override #'(indicator . "_")
  \emphasize #"a_ä__ö_de_f__ü_"

\markup
  \rounded-box
  \override #`(func . ,make-underline-markup)
  \emphasize #"a*ä**ö*de*f**ü*"
 
\markup
  \rounded-box
  \override #`(func . ,make-underline-markup)
  \emphasize #"a*äö*de*fü*"
 
\markup
  \rounded-box
  \override #`(func . ,(lambda (s) (make-with-color-markup red s)))
  \emphasize #"a*ä**ö*de*f**ü*"
 
\markup
  \rounded-box
  \override #`(func
               .
               ,(lambda (s)
                  #{
                    \markup
                      \override #'(baseline-skip . 0)
                      \center-column {
                        $s
                        \tied-lyric #"~"
                      }
                  #}))
  \emphasize #"a*äö*de*fü*"
 
\markup \rounded-box \emphasize #"l*e*t th*e* c*o*ngr*e*-"


HTH,
  Harm
Titel: Re: Staccatopunkte unter freiem Text (\markup) GELÖST
Beitrag von: mgd am Montag, 18. Mai 2015, 18:24
Wieder eine tolle Lösung für mein ursprüngliches Problem.

Ich habe auch verstanden, wie sie i.P. funktioniert (einiges an Doku dafür gelesen... ;)

Dabei bin ich auf das für mich grundsätzliche Problem gestoßen, wie werden Scheme Skripte eigentlich debugged. Aber das hat nichts mit diesem Thread zu tun, weshalb ich dafür jetzt einen neuen anelgen werde.

Jedenfalls vielen Dank für die tolle Lösung,
Michael
Titel: Re: Staccatopunkte unter freiem Text (\markup) GELÖST
Beitrag von: mgd am Donnerstag, 21. Mai 2015, 07:51
Hallo Forum,

ich habe mich inzwischen durch die Details von Harm's Code durchgearbeitet und denke, ich habe ihn jetzt verstanden. Darüber hinaus habe ich mich in die regular expressions eingelesen und denke ich habe eine funktionsidentische Version zu der von Harm, jedoch mit regulären Ausdrücken. In einer Guile Kommandozeilenumgebung tun die auch, was ich von ihnen möchte. Aber in lilypond bekomme ich eine Fehlermeldung.

Hier der Code:
\version "2.18.2"

#(define-markup-command (underdot layout props arg)
  (markup?)
"Place a dot below @var{arg} using @code{\\center.column}"
    (interpret-markup layout props
      (markup #:override '(baseline-skip . 0.5) #:center-column (arg "."))))

#(define-markup-command (emphasize layout props strg)(string?)
#:properties ((func make-underdot-markup)
              (indicator "*"))
"Applies @code{func} to substrings of @var{strg}.  Those substrings have to be
marked with @code{indicator}, which defaults to @code{"*"}

@lilypond[verbatim,quote]
\\markup \\column {
  \\emphasize #"a*ä**ö*de*f**ü*"
  \\override #'(indicator . "_") \\emphasize #"a_ä__ö_de_f__ü_"
  \\override #`(func . ,make-underline-markup) \\emphasize #"a*ä**ö*de*f**ü*"
}
@end lilypond
"
  (let* ((indicator-char
           (let ((ls (string->list indicator)))
             (if (not (= (length ls) 1))
                 (ly:error
                   "indicator needs to be (= (string-length indicator) 1)")
                 (car ls))))
         (sub-strings
           (map match:substring
             (list-matches
               (string-concatenate
                 (list (regexp-quote indicator-char) "([^" (regexp-quote indicator-char) "]+)" (regexp-quote indicator-char)))
               strg)))
         ;; der folgende refuläre Ausdruck sollte das gleiche Ergebnis liefern
         ;; und zudem effizienter sein. zumindest auf der Kommandozeile habe ich
         ;; jedoch keine non-greedy regexp zum Laufen bekommen :(
;         (sub-strings
;           (map match:substring
;             (list-matches
;               (string-concatenate
;                 (list (regexp-quote indicator-char) "(.+?)" (regexp-quote indicator-char)))
;               strg))

         (strg-ls (string-split strg indicator-char))
         (mrkp
           (make-concat-markup
             (map
               (lambda (x)
                 (if (member x sub-strings)
                     (func x)
                     x))
               strg-ls))))
;    (ly:warning (string-concatenate (list "indicator='" indicator "', strg='" strg "'" )))
;    (ly:warning (string-concatenate (list "indicator-char='" (string indicator-char) "'")))
;    (ly:warning (string-concatenate (list "sub-strings=(" (string-concatenate (map (lambda (x) (string-concatenate (list "'" x "',"))) sub-strings)) ")" )))
;    (ly:warning (string-concatenate (list "strg-ls=(" (string-concatenate (map (lambda (x) (string-concatenate (list "'" x "',"))) strg-ls)) ")" )))
;    (ly:warning " ")

    (interpret-markup layout props mrkp)))

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

\markup
  \rounded-box
  \emphasize #"a*ä**ö*de*f**ü*"

\markup
  \rounded-box
  \emphasize #"a*äö*de*frü*"

\markup
  \rounded-box
  \emphasize #"aäödefü"

% generate warning-located "number of indicators not even, last indicator will be ignored"
\markup
  \rounded-box
  \emphasize #"a*ä*ö*de*fü*"

\markup
  \rounded-box
  \emphasize #"aä**öde*f****ü*"

\markup
  \rounded-box
  \override #'(indicator . "_")
  \emphasize #"a_ä__ö_de_f__ü_"

\markup
  \rounded-box
  \override #`(func . ,make-underline-markup)
  \emphasize #"a*ä**ö*de*f**ü*"

\markup
  \rounded-box
  \override #`(func . ,make-underline-markup)
  \emphasize #"a*äö*de*fü*"

\markup
  \rounded-box
  \override #`(func . ,(lambda (s) (make-with-color-markup red s)))
  \emphasize #"a*ä**ö*de*f**ü*"

\markup
  \rounded-box
  \override #`(func
               .
               ,(lambda (s)
                  #{
                    \markup
                      \override #'(baseline-skip . 0)
                      \center-column {
                        $s
                        \tied-lyric #"~"
                      }
                  #}))
  \emphasize #"a*äö*de*fü*"

\markup \rounded-box \emphasize #"l*e*t th*e* c*o*ngr*e*-"

Und dies ist die Fehlermeldung:
.../underdot-emphasize2.ly:30:12: Unbound variable: match:substring
Muss ich noch irgendetwas includen, damit match:substring erkannt wird ?

In obigem Code sind kommentiert noch meine debugging Loganweisungen zu sehen.
Ferner ist der verwendete reguläre Ausdruck eigentlich eine ineffiziente Krücke, aber die non-greedy regexp haben bei mir nicht funktioniert.

Wie immer für sachdienliche und sonstige Hinweise dankbar,
Michael
Titel: Re: Staccatopunkte unter freiem Text (\markup) GELÖST
Beitrag von: harm6 am Donnerstag, 21. Mai 2015, 10:57
Du brauchst:

#(use-modules (ice-9 regex))


Gruß,
  Harm

P.S. Bin ein paar Tage off jetzt
Titel: Re: Staccatopunkte unter freiem Text (\markup) GELÖST
Beitrag von: mgd am Donnerstag, 21. Mai 2015, 12:48
Vielen Dank.

Damit sieht die fertige in Bezug auf die Beispiele funktionsidentische (eine Änderung: eine ungerade ANzahl von Indikator Markern führt nicht zum Abbruch, vielmehr wird der letzte Indikator stillschweigend ignoriert) Version mit regexp wie folgt aus:
\version "2.18.2"

#(use-modules (ice-9 regex))

#(define-markup-command (underdot layout props arg)
  (markup?)
"Place a dot below @var{arg} using @code{\\center.column}"
    (interpret-markup layout props
      (markup #:override '(baseline-skip . 0.5) #:center-column (arg "."))))

#(define-markup-command (emphasize layout props strg)(string?)
#:properties ((func make-underdot-markup)
              (indicator "*"))
"Applies @code{func} to substrings of @var{strg}.  Those substrings have to be
marked with @code{indicator}, which defaults to @code{"*"}

@lilypond[verbatim,quote]
\\markup \\column {
  \\emphasize #"a*ä**ö*de*f**ü*"
  \\override #'(indicator . "_") \\emphasize #"a_ä__ö_de_f__ü_"
  \\override #`(func . ,make-underline-markup) \\emphasize #"a*ä**ö*de*f**ü*"
}
@end lilypond
"
  (let* ((indicator-char
           (let ((ls (string->list indicator)))
             (if (not (= (length ls) 1))
                 (ly:error
                   "indicator needs to be (= (string-length indicator) 1)")
                 (car ls))))
         (sub-strings
           (map (lambda(x) (match:substring x 1))
             (list-matches
               (string-concatenate
                 (list (regexp-quote (string indicator-char)) "([^" (regexp-quote (string indicator-char)) "]*)" (regexp-quote (string indicator-char))))
               strg)))
         (strg-ls (string-split strg indicator-char))
         (mrkp
           (make-concat-markup
             (map
               (lambda (x)
                 (if (member x sub-strings)
                     (func x)
                     x))
               strg-ls))))
    (interpret-markup layout props mrkp)))

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

\markup
  \rounded-box
  \emphasize #"a*ä**ö*de*f**ü*"

\markup
  \rounded-box
  \emphasize #"a*äö*de*frü*"

\markup
  \rounded-box
  \emphasize #"aäödefü"

% silently ignore last indicator"
\markup
  \rounded-box
  \emphasize #"a*ä*ö*de*fü*"

\markup
  \rounded-box
  \emphasize #"aä**öde*f****ü*"

\markup
  \rounded-box
  \override #'(indicator . "_")
  \emphasize #"a_ä__ö_de_f__ü_"

\markup
  \rounded-box
  \override #`(func . ,make-underline-markup)
  \emphasize #"a*ä**ö*de*f**ü*"

\markup
  \rounded-box
  \override #`(func . ,make-underline-markup)
  \emphasize #"a*äö*de*fü*"

\markup
  \rounded-box
  \override #`(func . ,(lambda (s) (make-with-color-markup red s)))
  \emphasize #"a*ä**ö*de*f**ü*"

\markup
  \rounded-box
  \override #`(func
               .
               ,(lambda (s)
                  #{
                    \markup
                      \override #'(baseline-skip . 0)
                      \center-column {
                        $s
                        \tied-lyric #"~"
                      }
                  #}))
  \emphasize #"a*äö*de*fü*"

\markup \rounded-box \emphasize #"l*e*t th*e* c*o*ngr*e*-"

Mein besonderer Dank gilt Harm, der die originale Funktion entwickelt hat, bei deren Verständnis ich mal wieder eine Menge habe lernen können. Ferner den diversen hilfreichen Menschen in diesem Forum, deren viele Hinweise zum Debuggen von Scheme in lilypond mir über die zahlreichen Frustrationen auf dem Weg zum Verständnis hinweg geholfen haben (und ich war zwischendurch echt angefressen, weil die Dinge nicht so wollten, wie ich meinte, dass sie müssten :) )

Liebe Grüße,
Michael