Hello,

I've created this small mockup of how we can store additional information in 
scores and let Lilypond manage it. The idea is: One might have different 
versions of a part of a score, be it different sources or different edits 
within a source.

So instead of selecting some version (and maybe putting the other ones into a 
critical apparatus) we can have Lilypond store this additional information and 
then do the selecting programmatically.

As I said this is a mockup and is missing many important features, like 
detailed overrides, nested edits and such.

Cheers,
Valentin
\version "2.22"

%%% includeable BACKEND, content at line 225

#(define EDIT_DEBUG_MODE 'warning)

%{
Takes a list of edit ids and music and assigns ids to music. ids can be given as pair
'(id . val) where val is the local prevalence of the edit.
%}
edits =
#(define-music-function (ids music) (list? ly:music?)
   (define (get-symbol tag) (if (symbol? tag) tag (car tag)))
   (define (tag-music id music)
      (ly:music-set-property! music 'edit-id (get-symbol id)))
   (map tag-music ids (ly:music-property music 'elements))
   (ly:music-set-property! music 'edit-ids ids)
   music)

%{
Pick specific edits. Collisions are resolved using prevalence value. Triggers a
warning if in some place no specified edit exists. Change the value of
EDIT_DEBUG_MODE to 'suppress to not trigger a warning or to anything else to
trigger an error if this happens.
%}
selectEdits =
#(define-music-function (edit-ids-prevs music) (list? ly:music?)
   (define (get-symbol tag) (if (symbol? tag) tag (car tag)))
   (define (get-prev tag) (if (symbol? tag) 0 (cdr tag)))
   (define (symbol-prev tag) (cons (get-symbol tag) (get-prev tag)))
   (define ((complete-prev prev-vals) tag)
     (if (pair? tag) tag
         (cons tag (assoc-get tag prev-vals 0))))
   (define (handle-hub music)
     (let* ((local-tags (ly:music-property music 'edit-ids))
            (edit-ids (map get-symbol edit-ids-prevs))
            (prev-vals (map symbol-prev edit-ids-prevs))
            (filtered-tags (filter (lambda (x) (member (get-symbol x) edit-ids)) local-tags))
            (preved-tags (map (complete-prev prev-vals) filtered-tags))
            (max-tag (car (reduce (lambda (x y) (if (> (cdr x) (cdr y)) x y)) (cons #f #f) preved-tags)))
            (music-length (ly:music-length music))
            (elements (ly:music-property music 'elements))
            (selected-elements (if max-tag (filter (lambda (x) (equal? (ly:music-property x 'edit-id) max-tag)) elements) '()))
            (tmp-joined-element (make-music 'SimultaneousMusic 'elements selected-elements))
            (selected-length (ly:music-length tmp-joined-element))
            (adjusted-elements (if (equal? selected-length music-length) selected-elements
                                  (append selected-elements (list (skip-of-length music))))))
       (ly:music-set-property! music 'elements adjusted-elements)
       (if (not max-tag)
           (if (equal? EDIT_DEBUG_MODE 'warning)
               (ly:music-warning music "No available edit ID was selected!")
               (if (not (equal? EDIT_DEBUG_MODE 'suppress))
                   (ly:music-error music "No available edit ID was selected!"))))))
   (define (iter music)
     (let ((elt (ly:music-property music 'element))
           (elts (ly:music-property music 'elements))
           (edit-ids (ly:music-property music 'edit-ids)))
       (if (null? edit-ids)
           (begin
            (if (not (null? elt)) (iter elt))
            (map iter elts))
           (handle-hub music))))
   (iter music)
   music)

%{
Color specific edits some way
%}
colorEdits =
#(define-music-function (edit-colors grobs music) (list? list? ly:music?)
   (define (color-tweaks grobs color)
     (if (null? grobs) '()
         (cons (cons (cons (car grobs) 'color) color) (color-tweaks (cdr grobs) color))))
   (define (iter music current-color)
     (let* ((elt (ly:music-property music 'element))
           (elts (ly:music-property music 'elements))
           (edit-id (ly:music-property music 'edit-id))
           (this-color (assoc-get edit-id edit-colors current-color))
           (tweaks (ly:music-property music 'tweaks)))
       (if (not (null? this-color))
           (ly:music-set-property! music 'tweaks (append tweaks (color-tweaks grobs this-color))))
       (if (not (null? elt)) (iter elt this-color))
       (map (lambda (x) (iter x this-color)) elts)))
   (iter music '())
   music)

%{
Add footnotes to edits
%}
annotateEdits =
#(define-music-function (edit-details music) (list? ly:music?)
   (define (iter music carry-edit)
     (let* ((elt (ly:music-property music 'element))
            (elts (ly:music-property music 'elements))
            (edit-id (ly:music-property music 'edit-id))
            (edit-det-raw (assoc-get edit-id edit-details #f))
            (edit-det (if edit-det-raw edit-det-raw carry-edit))
            (tweaks (ly:music-property music 'tweaks))
            (fn-music (cons 'footnote-music
                            (make-music
                             'FootnoteEvent
                             'footnote-text edit-det
                             'text (markup #:null)
                             'automatically-numbered #t
                             'Y-offset -1
                             'X-offset 0)))
            (footnoteable (or (music-is-of-type? music 'note-event) (music-is-of-type? music 'event-chord)))
            (new-carry (if footnoteable #f edit-det)))
       (if (not (null? edit-det))
           (if footnoteable
               (begin (ly:music-set-property! music 'tweaks `(,fn-music . ,tweaks)) #f)
               (let ((new-carry-b (if (not (null? elt)) (iter elt new-carry) new-carry)))
                 (fold (lambda (x y) (if y (iter x y) y)) new-carry-b elts)))
           (begin
             (if (not (null? elt)) (iter elt '()))
             (map (lambda (x) (iter x '())) elts)))))
   (iter music '())
   music)

%{
helper for creating a footnote score
%}
createScore=
#(define-scheme-function (music) (ly:music?)
   #{
     \score {
       \new Staff { #music }
       \layout {
         indent = 0
         \override Score.Clef.space-alist.first-note = #'(minimum-fixed-space . 3.3)
         \context {
           \Staff
           fontSize = #(magnification->font-size 0.55)
           \override StaffSymbol.staff-space = #0.55
           \omit KeySignature
           \omit TimeSignature
         }
       }
     }
   #})

%{
Select some edits and place the rest as footnote
%}
annotateAlternateEdits =
#(define-music-function (edit-ids-prevs edit-details music) (list? list? ly:music?)
   (define (get-symbol tag) (if (symbol? tag) tag (car tag)))
   (define (get-prev tag) (if (symbol? tag) 0 (cdr tag)))
   (define (symbol-prev tag) (cons (get-symbol tag) (get-prev tag)))
   (define ((complete-prev prev-vals) tag)
     (if (pair? tag) tag
         (cons tag (assoc-get tag prev-vals 0))))
   (define (create-footnote-markup elts)
     (if (null? elts) ""
         (let* ((elt (car elts))
                (ed-id (ly:music-property elt 'edit-id))
                (ed-det (assoc-get ed-id edit-details ""))
                (score-m (markup ed-det #:raise 0.9 #:score (createScore elt) #:hspace 3))) ; (ly:make-score elt))))
           (markup score-m (create-footnote-markup (cdr elts))))))
   (define (create-footnote music un-elts todo)
     (let* ((elt (ly:music-property music 'element))
            (elts (ly:music-property music 'elements))
            (tweaks (ly:music-property music 'tweaks))
            (footnoteable (or (music-is-of-type? music 'note-event) (music-is-of-type? music 'event-chord)))
            (fn-music (if footnoteable
                          (cons 'footnote-music
                                (make-music
                                   'FootnoteEvent
                                   'footnote-text (create-footnote-markup un-elts)
                                   'text (markup #:null)
                                   'automatically-numbered #t
                                   'Y-offset -1
                                   'X-offset 0)))))
       (if footnoteable
           (begin (ly:music-set-property! music 'tweaks `(,fn-music . ,tweaks)) #f)
           (fold
            (lambda (x y) (if y (create-footnote x un-elts y) y))
            (if (null? elt) #t (create-footnote elt un-elts #t))
            elts))))
   (define (handle-hub music)
     (let* ((local-tags (ly:music-property music 'edit-ids))
            (edit-ids (map get-symbol edit-ids-prevs))
            (prev-vals (map symbol-prev edit-ids-prevs))
            (filtered-tags (filter (lambda (x) (member (get-symbol x) edit-ids)) local-tags))
            (preved-tags (map (complete-prev prev-vals) filtered-tags))
            (max-tag (car (reduce (lambda (x y) (if (> (cdr x) (cdr y)) x y)) (cons #f #f) preved-tags)))
            (music-length (ly:music-length music))
            (elements (ly:music-property music 'elements))
            (selected-elements (if max-tag (filter (lambda (x) (equal? (ly:music-property x 'edit-id) max-tag)) elements) '()))
            (unselected-elements (if max-tag (filter (lambda (x) (not (equal? (ly:music-property x 'edit-id) max-tag))) elements) '()))
            (tmp-joined-element (make-music 'SimultaneousMusic 'elements selected-elements))
            (selected-length (ly:music-length tmp-joined-element))
            (adjusted-elements (if (equal? selected-length music-length) selected-elements
                                  (append selected-elements (list (skip-of-length music))))))
       (ly:music-set-property! music 'elements adjusted-elements)
       (create-footnote music unselected-elements #t)
       (if (not max-tag)
           (if (equal? EDIT_DEBUG_MODE 'warning)
               (ly:music-warning music "No available edit ID was selected!")
               (if (not (equal? EDIT_DEBUG_MODE 'suppress))
                   (ly:music-error music "No available edit ID was selected!"))))))
   (define (iter music)
     (let ((elt (ly:music-property music 'element))
           (elts (ly:music-property music 'elements))
           (edit-ids (ly:music-property music 'edit-ids)))
       (if (null? edit-ids)
           (begin
            (if (not (null? elt)) (iter elt))
            (map iter elts))
           (handle-hub music))))
   (iter music)
   music)

%%% OTHER STUFF ONLY NESCESSARY FOR FORMATTING THIS DEMONSTRATION

qscore=
#(define-scheme-function (m) (ly:music?)
   #{ \score { #m \layout { indent = 0 } } #})
#(define-markup-command (mscore layout props m) (ly:music?)
   (interpret-markup layout props
    (markup #:raise 0.6 #:score (qscore m))))

\layout { indent = 0 }

%%% REPRESENTATION INTERFACE

music = \new Staff { \edits #'((a . 1) b c) << c'2 { e'4 fis'8 } b4 >> d'2 }

%%% SCRORING INTERFACE

\markup\huge "MUSIC WITH THREE VERSIONS a,b,c"
{ \music }

\markup\column{
  \huge "SELECTING EDITS"
  \vspace #0.5
  \justify-line {
    \line { a: \mscore { \selectEdits #'(a) \music } }
    \line { b: \mscore { \selectEdits #'(b) \music } }
    \line { c: \mscore { \selectEdits #'(c) \music } }
    \line { d: \mscore { \selectEdits #'(d) \music } }
  }
  \vspace #1.5
}

\markup\column{
  \huge "SELECTING MULTIPLE EDITS"
  \vspace #0.5
  \justify-line {
    ""
    \line { a+b: \mscore { \selectEdits #'(a b) \music } }
    \line { a+b with higher priority for b: \mscore { \selectEdits #'(a (b . 2)) \music } }
    ""
  }
  \vspace #1.5
}
  
\markup\huge "COLORING EDITS"
\colorEdits #`((b . ,red) (c . ,green)) #'(NoteHead Stem Flag Beam Accidental) \music

\markup\huge "COLORING + SELECTION"
\colorEdits #`((b . ,red) (c . ,green)) #'(NoteHead Stem Flag Beam Accidental)
   \selectEdits #'(b) \music

\markup\huge "ANNOTATING EDITS"
\annotateEdits #'((a . "Source A") (b . "Source B") (c . "Source C")) \music

\markup\huge "SELECT EDITS AND ANNOTATE AS FOOTNOTE"
\annotateAlternateEdits #'(a) #'((a . "Source A") (b . "Source B") (c . "Source C")) \music

Attachment: edits.pdf
Description: Adobe PDF document

Reply via email to