I quite often find myself transcribing a part of a big orchestral
score from IMSLP (e.g., putting the horns in F
transposition and the trombones in bass clef -- our brass section is
not too good at exotic sightreading).

For this I put the global structure of a movement in a \global
variable, such as:


global = {
  \time 4/4 \tempo "Al dente ma non troppo"
  s1*47 \mark "A" % bar 48
  s1*38 \mark "B" % bar 86
  ...
}

and cues in another variable:

cues = {
  % I don't know about you, but I like having *a lot* of cues to
  % follow. I suspect that being a former pianist, I find it easier
  % than most brass colleagues to read several lines at once?
  R1*15 % bar 16
  \relative { fis'1^"Fl." } % bar 17
  R1*24
  \relative { c''1^"Ob." } % bar 41
}

Keeping tracks of the bar numbers is a bit tedious, and I find myself
doing a *lot* of additions/subtractions (« sooo this oboe cue is 7
bars before A, and my last cue ended at bar 17, which makes it, let's
see.. 17 measures of rest »). And then spend twice as long fixing the
bugs (and introducing new ones, etc.)



So I wrote a few macros to automate this (and I expect them to pay
for themselves in only 1-2 more symphonies).

Now I can write the previous score in this way:

global = {
  \at-bar 1 { \time 4/4 \tempo "..." }
  \at-bar 48 { \mark "A" }
  \at-bar 86 { \mark "B" }
}

cues = <<
  \relative { \goto 16 fis'1^"Fl." }
  \relative { \goto "A-7" c''1^"Ob." } % we can even make them relative
>>

(In particular, this makes *inserting* a new cue much, much easier).

The functions should work even in the presence of varying time
signatures. A proof of concept (in 608 parentheses) is in the attached file.

Drawbacks (those identified for now):
 - \partial is not recognized yet
   (but this is quite easy to compensate for, by putting the
   appropriate skip at the beginning of \global);
 - this assumes that alternateBarNumbering is 'numbers-with-letters,
   (the other way around is quite easier; I could put some code to
   identify \set Score.alternateBarNumbering commands);
 - it *does not work* (for syntax reasons) in the presence of true
   repeats (they must be simulated using Score.repeatCommands);
 - when a voice uses \at-bar, it must *only* use \at-bar (this is the
   only way to keep track of time; see my previous question).


Oh, and by the way, I have a half-serious bug-report:
negative-length skips don't work.

-- 
        Jérôme
#(begin

(use-modules (ice-9 regex))

(define (print . l) (map display l) (newline) #f)
(define (music-length m)
  "Duration of m, as a rational"
    (ly:moment-main (ly:music-length m)))


(define (rcons e l) (append! l (list e)))

;  alternatives: 
;   VoltaRepeatedMusic 'element [rpt] 'repeat-count 2 'elements [alternatives]
;   - if 'numbers, then sets bar number to the *sum* of all lengths
;   - if 'numbers-with-letters, sets bar number to the *last* length only
; HOWEVER, inserting \atBar syntaxically prevents us from inserting ture
; \repeat volta  { ... } construct; we are stuck with
; score.repeatCommands, which produces
; 'ContextSpeccedMusic 'context-type 'Score 'element
;    ('PropertySet 'symbol 'repeatCommands 'value ... )

; the (bar number ↦ time) function* is piecewise affine.
; - slope is current time signature
; - jumps are introduced by \partial, repeats, or currentBarNumber changes.
; The status of timing is given by a list:
; (current-time, last-bar, last-time, last-sig, last-volta) where:
;  - current-time [rational] = duration since beginning of music
;  - last-bar  = bar number of beginning of current affine function part
;  - last-time = position of same (rational)
;  - last-sig  = signature of same (rational)
;  - last-volta= bar number of current volta (or #f)
; The *bars-affine* global holds only the last 4 values.
(define *bars-affine* '()) ; list of (bar pos sig volta)
(define *bars-marks* '())
(define *bars-current-time* 0)
(define *bar-number-defining* #t)

(define (reset-bar-structures!)
  (set! *bars-marks* '())
  (set! *bars-affine* '((1 0 1 #f)))
  #t)
(reset-bar-structures!)
(define (bar-find-affine-part bar data)
  (if (< bar (caar data)) (bar-find-affine-part bar (cdr data))
    (car data)))

(define (update-bars-affine! bar pos sig volta)
  (set! *bars-affine* (cons (list bar pos sig volta) *bars-affine*)))
(define (update-bars-marks! bar mark)
  (set! *bars-marks* (cons (cons mark bar) *bars-marks*)))

; this appends (if needed) a new state in structures
; and returns the current state
(define (update-bar-structures! music . state);<<<
  (let* (
    (prop (lambda (x . y) (apply ly:music-property music (cons x y))))
    (n (prop 'name))
    (state (if (null? state) (list 0 1 0 1 #f) (car state)))
    (pos (list-ref state 0))
    (last-bar (list-ref state 1))
    (last-time (list-ref state 2))
    (last-sig (list-ref state 3))
    (last-volta (list-ref state 4))
    (this-bar (+ last-bar (/ (- pos last-time) last-sig)))
  ) (cond
  ((eq? n 'SequentialMusic)
    (fold update-bar-structures! state (prop 'elements '())))
  ((eq? n 'SimultaneousMusic)
    (car (last-pair (map
       (lambda (m) (update-bar-structures! m state))
       (prop 'elements '())))))
  ((eq? n 'TimeSignatureMusic)
    (let* (
      (sig (/ (prop 'numerator) (prop 'denominator)))
    )
    (update-bars-affine! this-bar pos sig last-volta)
    (list pos this-bar pos sig last-volta)))
  ((and (eq? n 'PropertySet) (eq? (prop 'symbol) 'repeatCommands))
    (let* (
      (value (prop 'value))
      (volta-list (map
        (lambda (i) (list-ref value (+ 1 i)))
        (filter (lambda (i) (eq? (list-ref value i) 'volta))
                (iota (length value))))) ; list of values attached to 'volta
      (vstart (find string? volta-list))
      (vstop (find boolean? volta-list))
      (new-bar (if (and vstart last-volta) last-volta this-bar))
      (new-volta (if vstart new-bar #f))
    )
    (if (and vstart last-volta) ; volta was ongoing and new one is starting:
      (update-bars-affine! new-bar pos last-sig new-volta)) ; creates a jump
      ; else do nothing
    (list pos new-bar pos sig new-volta)))
  ((and (eq? n 'PropertySet) (eq? (prop 'symbol) 'currentBarNumber))
    (set! this-bar (prop 'value))
    (update-bars-affine this-bar pos last-sig last-volta)
    (list pos this-bar pos last-sig last-volta))
  ((eq? n 'MarkEvent)
    (update-bars-marks! this-bar (prop 'label))
    state)
  ((and (equal? (prop 'symbol) 'whichBar) (equal? (prop 'value) "|."))
    (update-bars-marks! this-bar "end")
    state)
  ((prop 'element #f) => (lambda (e) (update-bar-structures! e state)))
  (else
    (list (+ pos (music-length music)) last-bar last-time last-sig last-volta))
)));>>>
(define (contains-time-sig? . music);<<<
  (and (not (null? music)) (car music)
    (let* (
      (head (car music))
      (prop (lambda (x . y) (apply ly:music-property head (cons x y))))
    ) (or
      (eq? (prop 'name) 'TimeSignatureMusic)
      (apply contains-time-sig? (prop 'elements '()))
      (contains-time-sig? (prop 'element #f))))));>>>
(define (skip-to-bar pos new-bar)
"Returns a SkipEvent long enough to reach bar new-bar
from time position pos (rational)"
  (let* (
    (aff (bar-find-affine-part new-bar *bars-affine*))
    (last-bar (list-ref aff 0))
    (last-pos (list-ref aff 1))
    (last-sig (list-ref aff 2))
    (last-volta (list-ref aff 3))
    ; new-pos = (bar' - bar₀) * sig₀ + pos₀
    (new-pos (+ (* (- new-bar last-bar) last-sig) last-pos))
    (delta (- new-pos pos))
  )
   (make-music 'SkipEvent 'duration
    (ly:make-duration 0 0 (numerator delta) (denominator delta)))
))
(define (at-bar-number mode new-bar music);<<<
  (let* (
    (rest (skip-to-bar *bars-current-time* new-bar))
    (new-music (make-sequential-music (list rest music)))
  )
  (set! *bars-current-time* (+ *bars-current-time* (music-length rest)))
  (if mode
    (update-bar-structures! music
      (cons *bars-current-time* (car *bars-affine*))))
  (set! *bars-current-time* (+ *bars-current-time* (music-length music)))
  new-music
))
(define at-bar-reset (define-void-function (parser location) ()
  (set! *bars-current-time* 0)
  (set! *bar-number-defining* #f)
))
(define (parse-bar-number x);<<<
  (if (number? x) x
  (fold-matches "[-+]?([0-9]+|[A-Z]|end)" x 1
    (lambda (u n) (let* (
      (w (array-ref u 1))
      (s (substring x (car w) (cdr w)))
    ) (cond
    ; TODO: it should not be too hard to allow stuff such as
    ; * A+|| for the next double bar after A
    ; * A-|| for the next *before* A
    ; A = go to mark A
    ((assoc-ref *bars-marks* s) => identity)
    ; E+2 == 2 bars after E
    ((member (string-ref s 0) `(#\+ #\-))
       (+ n (string->number s)))
    ; E2 = 2nd bar *of* E, which means E+1
    ;  (also works for beginning of piece)
    ((string->number s) => (lambda (d) (+ d -1 n)))
    (else (error (string-append "unknown mark: \"" s "\"")))
))))));>>>

(define at-bar (define-music-function (parser location where music);<<<
  (number-or-string? ly:music?)
; the general atBar behaviour is given by an automaton:
; STATE: { defining | inserting }
; * if current position is 0 then
;    - if music contains a TimeSignature event, we enter 'definition' mode
; * if the first music expression contains a \time command, then
;   we pass to state 'defining, else to state 'inserting
  (let* (
    (bar-num (parse-bar-number where))
  )
  (and (= *bars-current-time* 0)
       (contains-time-sig? music)
       reset-bar-structures!)
  (at-bar-number *bar-number-defining* bar-num music)
)));>>>
(define goto (define-music-function (parser location where)
  (number-or-string?)
  (print "parse number " where " = " (parse-bar-number where))
  (skip-to-bar 0 (parse-bar-number where))))

) % begin


foo = \relative {
  \override Score.BarNumber.break-visibility = ##(#t #t #t)
  \at-bar 1 { \time 4/4 }
  \at-bar 3 { \time 2/4 }
  \at-bar 5 { \mark "A" }
  \at-bar 11 { \time 4/4 }
  \at-bar 16 { \bar "|." }
}

qux = \relative {
  \at-bar-reset
  \at-bar 2 { a4 b c d }
  \at-bar "A-1" { bes4 }
  \at-bar 6 { c4 d e f }
  \at-bar "end-1" { fis1 }
}

baz = <<
  \relative { \goto "A+1" c'4 d e f }
  \relative { \goto 2 bes4 }
>>
\void \displayLilyMusic \foo
\void \displayLilyMusic \qux
\void \displayLilyMusic \baz

\score { 
  \new StaffGroup <<
    \new Staff { \foo }
    \new Staff { \qux }
    \new Staff { \baz }
  >> }
_______________________________________________
lilypond-user mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/lilypond-user

Reply via email to