Die erste Notendauer eines Musikausdrucks per Scheme ermitteln

Begonnen von Manuela, Sonntag, 15. September 2019, 18:31

« vorheriges - nächstes »

Manuela

Der folgende Code liefert alle Notendauern:

\version "2.19.82"
\language "deutsch"

#(define (display-dauer m)
   (if (ly:duration? (ly:music-property m 'duration))
       (begin
        (display (ly:music-property m 'name))(display "*")
        (display (ly:duration->string (ly:music-property m 'duration)))(display "*")
        (display (ly:duration-log (ly:music-property m 'duration)))(display "*")
        (display (ly:duration-length (ly:music-property m 'duration)))(display "*")
        (display (ly:duration-dot-count (ly:music-property m 'duration)))(display "*")
        (display (ly:duration-scale (ly:music-property m 'duration)))
        (newline)
        (ly:music-property m 'duration)
        )
       (ly:music-property m 'name)
       ))

#(define (display-dauern m)
   (music-map
    (lambda (x)
      (display-dauer x))
    (ly:music-deep-copy m)))

mus = { c2. d4 e8 f8. }

#(display (display-dauern mus))


Aber wie kriege ich den ersten? Oder überhaupt irgendeinen? Die erste Funktion liefert auf einen Musikausdruck angewendet keine Notendauer, obwohl in der Ausgabe zuerst die Notendauern aufgelistet werden.
Danke für eure Hilfe
viele Grüße
-- Manuela

harm6

#1
Hallo Manuela,

was hier passiert ist folgendes:
music-map rekursiert über alle Elemente des Arguments, also alle Noten und den gesamten Ausdruck.
Im Beispiel in genau dieser Reihenfolge.
Bei einer Note wird diese durch ihre Dauer ersetzt.
Beim gesamten Ausdruck durch den Namen.
Deshalb die finale Ausgabe von 'SequentialMusic.
Es wird ja immer der letzte evaluierte Ausdruck zurückgegeben.


Du kannst music-map nicht wirklich davon abhalten zu rekursieren.
Stattdessen versuchs mit map-some-music oder möglicherweise auch mit for-some-music und füge eine Bedingung ein zu stoppen, sobald eine erste Dauer erkannt wird.


mus = { c2. d4 e8 f8. }

#(define (print-first-dur mus)
  (let ((first-dur #f))
    (if (not first-dur)
        (begin
          (map-some-music
            (lambda (m)
              (and (ly:duration? (ly:music-property m 'duration))
                   (if (not first-dur)
                       (set! first-dur (ly:music-property m 'duration)))))
            (ly:music-deep-copy mus))
          first-dur)
        first-dur)))
     
#(write (print-first-dur mus))



Das kann man je nach Anwendung aber bestimmt noch besser machen. Wofür willst Du es haben?


Gruß,
  Harm

EDIT
So richtig gut ist das nicht :(
Es läuft immer noch über den gesamten Ausdruck und funktioniert auch mit music-map ...


Manuela

#2
Ich möchte in dieser Funktion
\version "2.19.82"
\language "deutsch"

#(define (split xs n)
   (let loop ((i n) (first '()) (second xs))
     (if (or (zero? i) (null? second))
         (list (reverse first) second)
         (loop (- i 1) (cons (car second) first) (cdr second)))))

#(define (rotate xs n)
   (if (< n 0)
       (reverse (rotate (reverse xs) (- n)))
       (let ((parts (split xs n)))
         (append (cadr parts) (car parts)))))

ShiftScale =
#(define-music-function (the-scale n)(ly:music? number?)
   (let* ((scpi (music-pitches the-scale))
          (m (length scpi))
          (x (modulo n m))
          (rot (rotate scpi x)))
     (make-music 'SequentialMusic 'elements
       (map (lambda (p)
              (make-music
               'NoteEvent 'duration (ly:make-duration 0)
               'pitch p))
         rot))))

mus = \relative c'  { c2. d4 e8 f8. }
#'()
\mus
\ShiftScale \mus #2


statt (ly:make-duration 0) die tatsächliche Notenlänge (wenn möglich der 1. vorkommenden Note, ansonsten irgendeine) verwenden. Außerdem sollen die Töne aufsteigend verlaufen, ich muss daher die zu niedrigen Töne um eine Oktave anheben. Aber das schaffe ich, glaube ich zumindest  ;)
Danke für eure Hilfe
viele Grüße
-- Manuela

Arnold

Hallo Manuela,

ich vermute, der beste Weg wäre eine eigene Rekursion zu programmieren. Die kann dann auch die weiteren Aufrufe stoppen, wenn bereits ein Ergebnis gefunden wurde.
Dazu muß man sich aber zuvor Gedanken darüber machen, was genau man Auswerten will.

Die Events, deren Dauer uns hier wohl interessiert, sind sicher: NoteEvent, EventChord, RestEvent, SkipEvent - alle enthalten »in sich« eine Längendefinition. Willst Du nur von Noten die Länge, oder auch von Pausen, oder gar von Skips? Dann aber vermute ich, daß s4*0 und ähnliches aber ausgeklammert wird.
Die »sammelnden Events« (ich nenne sie einmal so) enthalten ihre »untergeordneten Events« entweder als ein einzelnes im Property 'element, eine SCHEME-Liste von Elementen im Property 'elements, oder sogar in beiden. Die erste Art, mit nur einem untergeordneten Element in 'element, ist quasi ein »darüberstülpen« - wenn ich mich recht entsinne fällt auch die GraceMusic darunter, welche in deiner Auswertungsfunktion vermutlich besonders behandelt werden muß.
Die häufigsten »sammelnden Events sind:
SequentialMusic - dort enthält 'elements die SCHEME-Liste der untergeordneten Elemente. Es kann auch geschachtelt vorkommen, z. Bsp. in { \clef treble \key c  \major { c'4 d' e'2 } c'1 } möchtest Du sicher c'4 auswerten.
SimultaneousMusic - dort enthält ebenfalls 'elements die SCHEME-Liste der untergeordneten Elemente. Willst Du hier den »zeitlich ersten« Ton (und dann welche Dauer bei unterschiedlich langen Tönen, die zur gleichen Zeit beginnen), oder den in der »ersten aufgeführten Spur«?
...RepeatedMusic (Volta..., Percent..., Unfolded..., ...) - enthält »meistens« unter 'element eine SequentialMusic (der Rumpf), und unter 'elements eine SCHEME-Liste von weiteren SequentialMusic-Events (die Wiederholungsalternativen). Hier wird dich wahrscheinlich nur der Rumpf interessieren. Für einenen Spezialfall habe ich aber schon einmal einen leeren Rumpf verwendet (dort sollte ein Instrument in der Wiederholung etwas komplett anderes spielen, und mit diesem Konstrukt konnte ich die Taktzahlen zu den anderen Stimmen synchronisieren).

Ich glaube, bei meinem »experimental-grace-syncer« habe ich diesen Ansatz zum Ermitteln der »leading grace length« schon einmal ausgeführt.
Dort habe ich in der Rekursionsroutine explizit auch alle Events angegeben, die einfach übersprungen werden können, damit ich eine Warnung ausgeben kann, wenn ein mir noch unbekanntes Event erscheint.

Arnold

P.S.: Wieder viel Theorie, aber kein praktisches Beispiel.

Manuela

Danke für deine Überlegungen, Arnold.

Ich will nur eine Abfolge von Tönen der Art \relative c'' { c4 d e f g a h } umordnen:

\shiftMus \relative c'' { c4 d e f g a h } #2 ==> \relative c'' { e4 f g a h c d }

Keine Skips, rests, gleichzeitige Musik oder ähnliche Feinheiten sind erforderlich.

Danke für eure Hilfe
viele Grüße
-- Manuela

Arnold

Hallo Manuela,

in diesem von Dir genannten Beispiel:
\shiftMus \relative c'' { c4 d e f g a h } #2 ==> \relative c'' { e4 f g a h c d }

  • wird an das SCHEME-Programm shiftMus ein RelativeOctaveMusic-Event übergeben.
  • Darin ist unter 'element (ohne Mehrzahl-S) ein SequentialMusic-Event zu finden.
  • In diesem wiederum findet man unter 'elements (mit Mehrzahl-S) eine SCHEME-Liste.
  • Diese SCHEME-Liste enthält alle deine angegebenen Töne als NoteEvent
  • Und dort findest du unter 'duration, wonach Du gefragt hast. Und das ist hoffentlich auch, was du suchst ;)

Diese Tondauer (duration) steht in allen NoteEvent von diesem Beispiel, obwohl sie nur für den ersten Ton angegeben wurde.
Das hat der Parser in die Töne ohne angegebene Dauer übertragen.

Arnold

Manuela

Danke Arnold für deine Ausführungen.

Bei mir scheitert es an der konkreten Durchführung. Mir sind schon einige exotische Programmiersprachen in meinem Berufsleben untergekommen, aber die Denkweise von Scheme will immer noch nicht in meinen Kopf.
Danke für eure Hilfe
viele Grüße
-- Manuela

Arnold

Hallo Manuela,

zuerst einmal: Mir scheint, es geht dir in Wirklichkeit bei deinem Problem gar nicht darum, die erste Notendauer zu ermitteln.
Bei deiner Problembeschreibung
Zitat von: Manuela am Montag, 16. September 2019, 12:35
...
Ich will nur eine Abfolge von Tönen der Art \relative c'' { c4 d e f g a h } umordnen:
\shiftMus \relative c'' { c4 d e f g a h } #2 ==> \relative c'' { e4 f g a h c d }
...
geht es eigentlich darum, Datenstrukturen zu modifizieren.
Also sollte man dazu sein erstes Augenmerk daruf legen, die Datenstrukturen zu verstehen. Die Programmiersprachen sind dann nur noch dazu da, diese Datenstrukturen »in Bits und Bytes zu gießen«, und natürlich um diese Datenstrukturen auswerten und modifizieren zu können.

Zuerst noch, der Parser von Lilypond macht aus
\relative c'' { c4 d e f g a b }  ==>  \relative c'' { c4 d4 e4 f4 g4 a4 b4 }
d.h. in allen Tönen, bei denen keine Tondauer angegeben wurde, wird die zuletzt angegebene Tondauer hineingeschrieben.

Diese Datenstruktur besteht sozusagen aus Event-Datensätzen, und später beim Aufbau des Notensatzes soll auf diese Events ein Engraver reagieren.
Die Event-Datensätze (ly:music?) können auch untergeordnet weitere Event-Datensätze besitzen, üblicherweise in den »Feldern«" 'element und 'elements.
In jedem dieser Event-Datensätze (ly:music?) ist auch der Typ des Events abgelegt und auslesbar, steht im Feld 'name. Deshalb wundert es nicht, daß die meisten \musicMap-Bearbeitungsfunktion in der Art
#(define-public (user-function-for-musicMap mus)
  (let ((eventtype (ly:music-property mus 'name)))
   (if (eq? eventtype 'SequentialMusic)
     (let ... ) ; hier die Bearbeitung SequentialMusic und Rückgabe einer abgeänderten SequentialMusic
     mus)))

beginnen, um daran entscheiden zu können, ob dieses Event bearbeitet werden soll, oder ob es unverändert durchgereicht werden soll.

Dein Beispiel ist also, wie in meiner vorigen Antwort dargestellt:
ein RelativeOctaveMusic-Event, in dem sich ein SequentialMusic-Event befindet, und darin dann eine Liste mit lauter NoteEvents.

In deinem Fall könntest Du eine musicMap-Funktion verwenden, welche alle vorkommenden SequentialMusic-Events bearbeitet (die darin enthaltene Liste umsortiert), und in allen anderen Fällen einfach das Originalevent zurückgibt. Wären in dem übergebenen ly:music?-Argument aber mehrere SequentialMusic enthalten, dann würde natürlich auch jede dieser Listen umsortiert. Außerdem, durch den Ausgangspunkt in RelativeOctaveMusic wird bei entsprechender Rotation sich auch die (absolut) dargestellte Oktave der einzelnen Töne ändern.
z. Bsp.
\relative c'' { c4 d e f g a b }  ==\musicMap #(shiftSeqMus 2)==>  \relative c'' { e4 f4 g4 a4 b4 c4 d4 }  ==äquivalent==>  { e''4 f''4 g''4 a''4 b''4 c'''4 d'''4 }
\relative c'' { f4 g a b c d e}  ==\musicMap #(shiftSeqMus 2)==>  \relative c'' { a4 b4 c4 d4 e4 f4 g4 }  ==äquivalent==>  { a'4 b'4 c''4 d''4 e''4 f''4 g''4 }
\relative c'' { { c4 d e } { f g a } { b c d } }  ==\musicMap #(shiftSeqMus 2)==> \relative c'' { { d4 b4 c4 } { e4 c4 d4 } { a4 f4 g4 } }  ==äquivalent==> { d''4 b'4 c''4 e''4 c''4 d''4 a'4 f'4 g'4 }



Arnold

Manuela

Zitat von: Arnold am Dienstag, 17. September 2019, 13:29
\relative c'' { c4 d e f g a b }  ==\musicMap #(shiftSeqMus 2)==>  \relative c'' { e4 f4 g4 a4 b4 c4 d4 }  ==äquivalent==>  { e''4 f''4 g''4 a''4 b''4 c'''4 d'''4 }
\relative c'' { f4 g a b c d e}  ==\musicMap #(shiftSeqMus 2)==>  \relative c'' { a4 b4 c4 d4 e4 f4 g4 }  ==äquivalent==>  { a'4 b'4 c''4 d''4 e''4 f''4 g''4 }
\relative c'' { { c4 d e } { f g a } { b c d } }  ==\musicMap #(shiftSeqMus 2)==> \relative c'' { { d4 b4 c4 } { e4 c4 d4 } { a4 f4 g4 } }  ==äquivalent==> { d''4 b'4 c''4 e''4 c''4 d''4 a'4 f'4 g'4 }



Genau das was die fiktive Prozedur shiftSeqMus macht, hätte ich gerne. Oder gibt es diese Funktion schon irgendwo?
Danke für eure Hilfe
viele Grüße
-- Manuela

Manuela

So, ich habe jetzt eine halbwegs zufriedenstellende Lösung gefunden:
\version "2.19.82"
\language "deutsch"

#(define (split xs n)
   (let loop ((i n) (first '()) (second xs))
     (if (or (zero? i) (null? second))
         (list (reverse first) second)
         (loop (- i 1) (cons (car second) first) (cdr second)))))

#(define (rotate xs n)
   (if (< n 0)
       (reverse (rotate (reverse xs) (- n)))
       (let ((parts (split xs n)))
         (append (cadr parts) (car parts)))))

#(define (shift n)
   (lambda (music)
     (let* ((elts (ly:music-property music 'elements))
            (l (length elts))
            (eventtype (ly:music-property music 'name))
            (pitches (map (lambda (x) (ly:music-property x 'pitch))
                       (filter
                        (lambda (y)
                          (music-is-of-type? y 'note-event))
                        elts)))
            ;; ... and put them in order.
            (rotated (rotate pitches n)))
       (if (and (eq? eventtype 'SequentialMusic)
                (>= l n))
           (begin
            (map
             (lambda (e p)
               (ly:music-set-property! e 'pitch p))
             elts rotated)
            ;; first apply the rotated pitches
            ;; to the actual notes.
            (map (lambda (x)
                   (list-set! elts (- (- l x) 1)
                     (ly:music-transpose
                      (list-ref elts (- (- l x) 1))
                      (ly:make-pitch 1 0))))
              (iota n))
            ))
       music)))

shiftSeqMus =
#(define-music-function ( music n)
   (ly:music? integer?)
   #{ \musicMap #(shift n) $music #})

mus = \relative c' { c d g e f }
#'()
\mus
\shiftSeqMus \mus #2


Verbesserungs- und Vereinfachungsvorschläge sind herzlich willkommen  :)
Danke für eure Hilfe
viele Grüße
-- Manuela