%% https://lists.gnu.org/archive/html/lilypond-user/2022-07/msg00353.html %% by Lukas-Fabian Moser %% Change-log Harm %% - drop support for guilev1 %% - exclude NoteHeads with details.tie-me set to #f %% - move some definitions out of engraver %% - reformating \version "2.23.9" % TODO: Rename variables for clarity % TODO: Turn re-implementations of C++ helper functions into exported callbacks #(define (hash-non-empty? hash-table) (positive? (hash-count (const #t) hash-table))) #(define (tie-column::add_tie tie-column tie) ;;; TODO: Make callback from C++ (when (not (grob::has-interface (ly:grob-parent tie Y) 'tie-column-interface)) (when (or (null? (ly:spanner-bound tie-column LEFT)) (> (car (ly:grob-spanned-column-rank-interval tie-column)) ; THINK: is this exactly equivalent to the C++ original? (car (ly:grob-spanned-column-rank-interval tie-column)))) (ly:spanner-set-bound! tie-column LEFT (ly:spanner-bound tie LEFT)) (ly:spanner-set-bound! tie-column RIGHT (ly:spanner-bound tie RIGHT))) (ly:grob-set-parent! tie Y tie-column) (ly:pointer-group-interface::add-grob tie-column 'ties tie))) %{ head-event-alist has the fields: '((end-moment . #f) (tie-stream-event . #f) (tie-articulation-event . #f) (tie-from-chord-created . #f) (tie . #f) ) %} #(define (ly:enharmonic-equivalent? p1 p2) (= (ly:pitch-tones p1) (ly:pitch-tones p2))) #(define (ly:tie::head tie dir) (let ((it (ly:spanner-bound tie dir))) (if (grob::has-interface it 'note-head-interface) it #f))) #(define (report-unterminated-tie notehead alist) ;; give notehead argument in order to simplify use of ;; report-unterminated-tie as a proc in hash-for-each (when (not (assq-ref alist 'tie-from-chord-created)) (ly:warning (G_ "unterminated tie")) ; TODO: Warn with source position (ly:grob-suicide! (assq-ref alist 'tie)))) #(define (typeset-tie her) ;; this seems not to change anything for "her" if both bounds ;; are note heads ??? (let ((left-head (ly:tie::head her LEFT)) (right-head (ly:tie::head her RIGHT))) (when (not (and left-head right-head)) (ly:warning "lonely tie") (if (not left-head) (set! left-head right-head) (set! right-head left-head))) (ly:spanner-set-bound! her LEFT left-head) (ly:spanner-set-bound! her RIGHT right-head))) #(define-public (New_tie_engraver context) (let ((event-processed #f) (tie-stream-event #f) ; corresponds to event_ in C++ (tie-column #f) (now-heads '()) (heads-to-tie (make-hash-table)) (ties '()) (target (ly:context-name context))) (define (tie-notehead engraver head enharmonic?) (let ((found #f)) (hash-for-each (lambda (registered-head alist) (let* ((right-ev (event-cause head)) (left-head registered-head) (left-ev (event-cause left-head))) (when (and (not found) left-ev right-ev) (let ((p1 (ly:event-property left-ev 'pitch)) (p2 (ly:event-property right-ev 'pitch)) (p-equal? (if enharmonic? ly:enharmonic-equivalent? equal?))) (when (and (p-equal? p1 p2) ;; Do not create tie for events split by ;; Completion_heads_engraver (not (ly:event-property left-ev 'autosplit-end #f))) (let* ((tie (assq-ref alist 'tie)) (end (assq-ref alist 'end-moment)) (tie-event (assq-ref alist 'tie-articulation-event)) (cause (if tie-event tie-event (assq-ref alist 'tie-stream-event))) (cause-direction (ly:event-property cause 'direction #f))) (ly:engraver-announce-end-grob engraver tie cause) (ly:spanner-set-bound! tie RIGHT head) (ly:spanner-set-bound! tie LEFT left-head) (if cause-direction (ly:grob-set-property! tie 'direction cause-direction)) (set! ties (cons tie ties)) (set! found #t) (hash-remove! heads-to-tie registered-head) (hash-for-each (lambda (other-head alist) (if (equal? (assq-ref alist 'end-moment) end) (hash-set! heads-to-tie other-head (assq-set! alist 'tie-from-chord-created #t)))) heads-to-tie))))))) heads-to-tie) found)) (make-engraver ((start-translation-timestep translator) (when (and (hash-non-empty? heads-to-tie) (not (ly:context-property context 'tieWaitForNote #f))) (let ((now (ly:context-current-moment context))) (hash-for-each (lambda (head-event alist) (if (ly:moment~ ;; } (reverse now-heads)) (when (and (not wait) (pair? new-heads-to-tie)) (hash-for-each report-unterminated-tie heads-to-tie) (hash-clear! heads-to-tie)) (for-each (lambda (new-head-entry) (hash-set! heads-to-tie (car new-head-entry) (cdr new-head-entry))) new-heads-to-tie) (set! now-heads '()))) ((stop-translation-timestep engraver) ;; Discard event only if it has been processed with at least one ;; appropriate note. (when event-processed (set! tie-stream-event #f)) (set! event-processed #f))))) to = #(define-event-function (id event) (key? ly:event?) (set! (ly:music-property event 'spanner-target) id) event) \layout { \context { \Voice \remove Tie_engraver \consists #New_tie_engraver } } skipMe = \once \override NoteHead.details.tie-me = ##f % -------------------------------------------------------------------- % % { \new Staff \with { \consists #New_tie_engraver } { << \relative { 4 c8 b a g~ 4 } \\ \relative { s4 c'2 e4 } >> } %} \new Staff \with { \consists #New_tie_engraver } << \relative { <%{ tie this %} c'\to Staff ~ c'>2 \skipMe c8 b a g } \\ \relative { s2 %{ to this %} c'2 } \\ \relative { g16 a \skipMe c d e a g e f'4 d } >>