lilypond-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [GLISS] Syntax of \parallelMusic


From: Alexander Kobel
Subject: Re: [GLISS] Syntax of \parallelMusic
Date: Sun, 29 Nov 2009 17:57:31 +0100
User-agent: Thunderbird 2.0.0.23 (X11/20090817)

[With forward to -user, since this may be interesting now for everybody.]

Nicolas Sceaux wrote:
Le 29 nov. 2009 à 16:15, Alexander Kobel a écrit :

I propose using a different separator for \parallelMusic than "|", and allow to 
include bar checks in there.

\parallelMusic as is is very fine and handy, but sometimes you want to enter a 
whole phrase of two or three measures in a single go, and include the bar 
checks in there for readability and checking later on.
It somewhat disturbs the workflow if you always have to look up where the 
voiceN ended up in the last measure, especially if you have larger intervals in 
the voices, or reentering the durations if one voice stays in 16, the next in 8 
and the third in 2.

What about, e.g., ":"? It's fast to type, and AFAICS, it only is used for chord 
mode and figured bass. And I don't see a use for those two in \parallelMusic, since - 
well, you usually only have one chord at a time, right?
Another idea: "||", to serve both as bar check and as delimiter. And there's 
definitely no use conflict against two bar checks at the same point in time, right?

Currently, \parallelMusic highjacks the usual meaning of the pipe symbol: [...]
This was done so that \parallelMusic has no impact on the parser.

Thanks, Nicolas,

this was exactly the hint I needed... It never occured to me that parallelMusic might be a simple music function; I figured it's low-level and pretty involved in parser.
"I can see clearly now, the rain is gone..."

Which allows for a quite simple modification, to make the delimiter be "||", combining a bar check and a voice cycle: Just count how often the existing voice change has been called, and only do some work if it's two times in a row.
The modification is attached, and seems to work flawlessly.

Of course, this still leaves the question whether the delimiter should be changed, and whether it's good to overload the bar check. But it seems to me that the convert-ly rule should be so simple that even I might figure out how to write it, and I'm perfectly satiesfied with not having to remember the position of yet another symbol on the US keyboard layout... ;-)


Cheers,
Alexander
parallelMusic =
#(define-music-function (parser location voice-ids music) (list? ly:music?)
  (_i "Define parallel music sequences, separated by '||' (double bar check 
signs),
and assign them to the identifiers provided in @var{voice-ids}.

@var{voice-ids}: a list of music identifiers (symbols containing only letters)

@var{music}: a music sequence, containing double BarChecks as limiting 
expressions.

Example:

@verbatim
  \\parallelMusic #'(A B C) {
    c c | d d | e e ||
    d d | e e | f f ||
    e e | f f | g g ||
  }
<==>
  A = { c c | d d | e e | }
  B = { d d | e e | f f | }
  C = { e e | f f | g g | }
@end verbatim
")
  (let* ((voices (apply circular-list (make-list (length voice-ids) (list))))
         (current-voices voices)
         (current-sequence (list))
         (change-voice-call-count 0))
   ;;
   ;; utilities
   (define (push-music m)
    "Push the music expression into the current sequence"
    (set! current-sequence (cons m current-sequence)))
   (define (change-voice)
    "Stores the previously built sequence into the current voice and
       change to the following voice."
    ;; only do the actual work if change-voice is called the second time in a 
row
    (if (odd? change-voice-call-count)
     (begin
      (list-set! current-voices 0 (cons (make-music 'SequentialMusic
                                         'elements (reverse! current-sequence))
                                   (car current-voices)))
      (set! current-sequence (list))
      (set! current-voices (cdr current-voices))))
    ;; increase the call counter
    (set! change-voice-call-count (- 1 change-voice-call-count)))
   (define (bar-check? m)
    "Checks whether m is a bar check."
    (eq? (ly:music-property m 'name) 'BarCheck))
   (define (music-origin music)
    "Recursively search an origin location stored in music."
    (cond ((null? music) #f)
     ((not (null? (ly:music-property music 'origin)))
      (ly:music-property music 'origin))
     (else (or (music-origin (ly:music-property music 'element))
            (let ((origins (remove not (map music-origin
                                                      (ly:music-property music 
'elements)))))
             (and (not (null? origins)) (car origins)))))))
   ;;
   ;; first, split the music and fill in voices
   (map-in-order (lambda (m)
                  (push-music m)
                  (if (bar-check? m)
                   (change-voice)
                   (set! change-voice-call-count 0))) ;; reset the call counter
    (ly:music-property music 'elements))
   (if (not (null? current-sequence)) (begin (set! change-voice-call-count 1) 
(change-voice)))
   ;; un-circularize `voices' and reorder the voices
   (set! voices (map-in-order (lambda (dummy seqs)
                               (reverse! seqs))
                 voice-ids voices))
   ;;
   ;; set origin location of each sequence in each voice
   ;; for better type error tracking
   (for-each (lambda (voice)
              (for-each (lambda (seq)
                         (set! (ly:music-property seq 'origin)
                          (or (music-origin seq) location)))
               voice))
    voices)
   ;;
   ;; check sequence length
   (apply for-each (lambda* (#:rest seqs)
                    (let ((moment-reference (ly:music-length (car seqs))))
                     (for-each (lambda (seq moment)
                                (if (not (equal? moment moment-reference))
                                 (ly:music-message seq
                                  "Sections in parallel music don't have the 
same length")))
                      seqs (map-in-order ly:music-length seqs))))
    voices)
   ;;
   ;; bind voice identifiers to the voices
   (map (lambda (voice-id voice)
         (ly:parser-define! parser voice-id
          (make-music 'SequentialMusic
           'origin location
           'elements voice)))
    voice-ids voices))
  ;; Return an empty sequence. this function is actually a "void" function.
  (make-music 'SequentialMusic 'void #t))
\version "2.12.2"

\include "parallelMusic.ily"

\parallelMusic #'(A B C) {
  c4 c c c  | c c c c  | c c c c  ||
  a2   a    | a   a    | a   a    ||
  f1        | f        | f        ||
}

\score {
  <<
    \new Staff \relative c'' \A
    \new Staff \relative c'' \B
    \new Staff \relative c'  \C
  >>
}

reply via email to

[Prev in Thread] Current Thread [Next in Thread]