I've tweaked the autochange code a little more and I've been able
to make some of the improvements suggested. It's still under
construction, but I'm posting it so you guys can play with it.
The easiest way to test it would be to just save the
autochange_revised.scm and autochange.ly attachments in the same
directory and then compile autochange.ly.

Play around with the max-avg-deviation variable in
autochange_revised.scm. Eventually I'll also add a max-deviation
property which will allow fine-tuning of the staff-change
algorithm.

I've attached (in miniature) a png which shows the file compiled
with the current setting on the left, and with my revisions on the
right.

I think there will always be situations that end up not looking
right. One can only automate so much. But I think this is big
improvement over the current function.

Let me know what you guys think.
- Mark


      

<<attachment: autochange.png>>


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; autochange - fairly related to part combining.

; copied from lily-library.scm
(define (sign x)
  (if (= x 0)
      0
      (if (< x 0) -1 1)))

(define (notes-get-pitches notes)
  (map (lambda (x) (ly:event-property x 'pitch))
       notes))

(define (get-avg-pitch-steps pitches)
   (round (apply average (map ly:pitch-steps pitches))))

(define (get-pitch-steps-range pitches)
  (let ((pitch-steps (map ly:pitch-steps pitches)))
    (cons (apply min pitch-steps) (apply max pitch-steps))))

(define-public (make-autochange-music parser music)
  ;; don't let gradually moving chords get stuck in one staff.
  ;; when the absolute-value of a chord's average staff-position
  ;; exceeds this value, allow the chord to change staves.
  ;; at the moment, this does not affect single notes, only chords that
  ;; are close together
  ;; TODO: max-deviation
  (define max-avg-deviation 2)

  (define (generate-split-list change-moment event-list last-profile acc)
    ;; acc is a reversed list of (moment . staff) pairs,
    ;; where staff is 1 or -1.
    ;; last-profile is (last-staff . last-extremity)
    (if (null? event-list)
        acc
        (let* ((now-tun (caar event-list))
               (evs (map car (cdar event-list)))
               (now (car now-tun)) ; a moment
               (notes (filter (lambda (x)
                                (equal? (ly:event-property  x 'class)
                                        'note-event))
                              evs))
               (pitches (notes-get-pitches notes))
               (this-avg (if (pair? notes)
                             (get-avg-pitch-steps pitches)
                              #f))
               (this-range (if (pair? notes)
                               (get-pitch-steps-range pitches)
                               '(0 . 0)))
               (last-staff     (car last-profile))
               (last-extremity (cdr last-profile))
               (is-single-note (= (car this-range) (cdr this-range)))
               (this-staff
                (cond ; don't change staves if this-avg is C.
                      ((= 0 this-avg) last-staff)

                      ;; TODO: this block could be better organized:
                      ((or ; when to force a change during chords.
                           (if is-single-note
                               #f
                               (< max-avg-deviation (abs this-avg)))

                           ; if chord normally goes in the other staff
                           ; and this-avg exceeds last-extremity.
                           (and (not (= last-staff (sign this-avg)))
                                (< (abs last-extremity)
                                   (abs this-avg))
                                (if is-single-note
                                    (< max-avg-deviation (abs this-avg)))
                                    #t))
                         (sign this-avg))
                      (else last-staff))) ; -1 or 1
               (this-extremity (if (positive? this-staff)
                                   (car this-range)
                                   (cdr this-range)))
               (this-profile (cons this-staff this-extremity)))
          ;; tail recursive.
          (if (and this-avg
                   (not (= last-staff this-staff)))
              (generate-split-list #f
                                   (cdr event-list)
                                   this-profile
                                   (cons (cons
                                          (if change-moment
                                              change-moment
                                              now)
                                          (sign this-avg))
                                    acc))
              (generate-split-list
               (if this-avg #f now)
               (cdr event-list)
               this-profile
               acc)))))

  (let* ((m (make-music 'AutoChangeMusic))
        (m1 (make-non-relative-music (context-spec-music music 'Voice "one")))
         (context-list (recording-group-emulate music
                                                (ly:parser-lookup parser 'partCombineListener)))
         (evs (car context-list))
         (rev (reverse! (cdar context-list)))
         (split (reverse! (generate-split-list
                           #f
                           rev
                           '(1 . 0) ; first staff must default to 1.
                           '())
                          '())))
    (set! (ly:music-property m 'element) music)
    (set! (ly:music-property m 'split-list) split)
    m))
\version "2.13.2"

% uncomment to test revised version:
%#(load "autochange_revised.scm")

uppersFirstShort = \relative {
  <b g>8 <c g> <c a> <d a>
  <d b> <e b> <e c> <f c>
  <f c> <e c> <e b> <d b> 
  <d a> <c a> <c g> <b g>
}
lowersFirstShort = \relative {
  <g b>8 <g c> <a c> <a d>
  <b d> <b e> <c e> <c f>
  <c f> <c e> <b e> <b d> 
  <a d> <a c> <g c> <g b>
}

uppersFirstLong = \relative {
  <b g>8 <c g> <c a> <d a>
  <d b> <e b> <e c> <f c>
  <f d> <g d> <g e> <a e>
  <a e> <g e> <g d> <f d>
  <f c> <e c> <e b> <d b> 
  <d a> <c a> <c g> <b g>
}

lowersFirstLong = \relative {
  <g b>8 <g c> <a c> <a d>
  <b d> <b e> <c e> <c f>
  <d f> <d g> <e g> <e a>
  <e a> <e g> <d g> <d f>
  <c f> <c e> <b e> <b d> 
  <a d> <a c> <g c> <g b>
}

\new PianoStaff \autochange \uppersFirstShort
\new PianoStaff \autochange \lowersFirstShort
\new PianoStaff \autochange \uppersFirstLong
\new PianoStaff \autochange \lowersFirstLong

\new PianoStaff \autochange \relative {
 <b, e'>8 <d' f> <b, e'> <d' f>
 <a d'> <g b> <a d'> <g b>
}

\new PianoStaff \autochange \relative {
 c8 d e f g f e d c b a g f g a b c
}

\new PianoStaff \autochange \relative {
 <c, e>8 <d f> <e g> <f a>
 <g b> <a c> <b d> <c e>
 <d f> <e g> <f a> <g b>
 <g b> <f a> <e g> <d f>
 <c e> <b d> <a c> <g b>
 <f a> <e g> <d f> <c e>
}
_______________________________________________
lilypond-devel mailing list
lilypond-devel@gnu.org
http://lists.gnu.org/mailman/listinfo/lilypond-devel

Reply via email to