]> git.donarmstrong.com Git - lilypond.git/blob - scm/part-combiner.scm
*** empty log message ***
[lilypond.git] / scm / part-combiner.scm
1 ;;;; part-combiner.scm -- Part combining, staff changes.
2 ;;;;
3 ;;;;  source file of the GNU LilyPond music typesetter
4 ;;;; 
5 ;;;; (c)  2004  Han-Wen Nienhuys <hanwen@cs.uu.nl>
6
7 ;; todo: figure out how to make module,
8 ;; without breaking nested ly scopes
9
10 (define-class <Voice-state> ()
11   (event-list #:init-value '() #:accessor events #:init-keyword #:events)
12   (when-moment #:accessor when #:init-keyword #:when)
13   (split-index #:accessor split-index)
14   (vector-index)
15   (state-vector)
16
17
18   ;;;
19   ; spanner-state is an alist
20   ; of (SYMBOL . RESULT-INDEX), which indicates where
21   ; said spanner was started.
22   (spanner-state #:init-value '() #:accessor span-state) )
23   
24 (define-method (write (x <Voice-state> ) file)
25   (display (when x) file)
26   (display " evs = " file)
27   (display (events x) file)
28   (display " active = " file)
29   (display (span-state x) file)
30   (display "\n" file) )
31
32 (define-method (note-events (vs <Voice-state>))
33   (define (f? x)
34     (equal? (ly:get-mus-property  x 'name) 'NoteEvent))
35   (filter f? (events vs)))
36
37 (define-method (previous-voice-state (vs <Voice-state>))
38   (let* ((i (slot-ref vs 'vector-index))
39          (v (slot-ref vs 'state-vector)) )
40     (if (< 0 i)
41         (vector-ref v (1- i))
42         #f)
43   ))
44
45
46 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
47
48 (define-class <Split-state> ()
49   (configuration #:init-value '() #:accessor configuration)
50   (when-moment #:accessor when #:init-keyword #:when)
51   (is #:init-keyword #:voice-states #:accessor voice-states)
52   (synced  #:init-keyword #:synced #:init-value  #f #:getter synced?) )
53                              
54
55
56
57 (define-method (write (x <Split-state> ) f)
58   (display (when x) f)
59   (display " = " f)
60   (display (configuration x) f)
61   (if (synced? x)
62       (display " synced "))
63   (display "\n" f) )
64
65 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
66
67
68 (define (previous-span-state vs)
69          (let*
70              ((p (previous-voice-state vs)))
71
72            (if p (span-state p)
73                '())
74          ))
75
76 (define (make-voice-states evl)
77   (let
78       ((vec
79         (list->vector
80          (map
81           (lambda (v)
82             (make <Voice-state>
83               #:when (car v)
84               #:events (map car (cdr v))
85               ))
86           evl))))
87     
88     (do ( (i 0 (1+ i)) )
89         ( (= i (vector-length vec)) vec)
90       (slot-set! (vector-ref vec i) 'vector-index i)
91       (slot-set! (vector-ref vec i) 'state-vector vec)
92     )))
93
94
95 (define (make-split-state vs1 vs2)
96   "Merge lists VS1 and VS2, containing Voice-state objects into vector
97 of Split-state objects, crosslinking the Split-state vector and
98 Voice-state objects
99 "
100   
101   (define (helper ss-idx ss-list idx1 idx2)
102     (let*
103         ((s1 (if (< idx1 (vector-length vs1)) (vector-ref vs1 idx1) #f))
104          (s2 (if (< idx2 (vector-length vs2)) (vector-ref vs2 idx2) #f))
105          (min (cond ((and s1 s2) (moment-min (when s1) (when s2)))
106                     (s1 (when s1))
107                     (s2 (when s2))
108                     (else #f)
109                     ))
110
111          (inc1 (if (and s1 (equal? min (when s1))) 1 0))
112          (inc2 (if (and s2 (equal? min (when s2))) 1 0))
113          (ss-object
114           (if min
115               (make <Split-state>
116                 #:when min
117                 #:voice-states (cons s1 s2)
118                 #:synced (= inc1 inc2)
119                 ) #f)) )
120       (if s1
121           (set! (split-index s1) ss-idx))
122       (if s2
123           (set! (split-index s2) ss-idx))
124       
125       (if min
126           (helper (1+ ss-idx)
127                   (cons ss-object ss-list)
128                   (+ idx1 inc1)
129                   (+ idx2 inc2))
130           ss-list )
131       ))
132
133     (list->vector
134      (reverse!
135       (helper 0 '() 0  0) '())) )
136       
137
138
139 (define (analyse-spanner-states voice-state-vec)
140
141   (define (helper index active)
142     "Analyse EVS at INDEX, given state ACTIVE."
143     
144     (define (analyse-tie-start active ev)
145       (if (equal? (ly:get-mus-property ev 'name) 'TieEvent)
146           (acons 'tie index active)
147           active
148           ))
149     
150     (define (analyse-tie-end active ev)
151       (if (equal? (ly:get-mus-property ev 'name) 'NoteEvent)
152           (assoc-remove! active 'tie)
153           active) )
154
155     (define (analyse-absdyn-end active ev)
156       (if (equal? (ly:get-mus-property ev 'name) 'AbsoluteDynamicEvent)
157           (assoc-remove!
158            (assoc-remove! active 'cresc)
159            'decr)
160           active) )
161     
162     (define (active<? a b)
163       (cond
164        ((symbol<? (car a) (car b)) #t)
165        ((symbol<? (car b) (car b)) #f)
166        (else
167         (< (cdr a) (cdr b)))
168        ))
169     
170     (define (analyse-span-event active ev)
171       (let*
172           ((name (ly:get-mus-property ev 'name))
173            (key (cond
174                  ((equal? name 'SlurEvent) 'slur)
175                  ((equal? name 'PhrasingSlurEvent) 'tie)
176                  ((equal? name 'BeamEvent) 'beam)
177                  ((equal? name 'CrescendoEvent) 'cresc)
178                  ((equal? name 'DecrescendoEvent) 'decr)
179                  (else #f)) )
180            (sp (ly:get-mus-property ev 'span-direction)) )
181
182         (if (and (symbol? key) (ly:dir? sp))
183             (if (= sp STOP)
184                 (assoc-remove! active key)
185                 (acons key
186                        (split-index (vector-ref voice-state-vec index))
187                        active))
188             active)
189         ))
190
191     (define (analyse-events active evs)
192       "Run all analyzers on ACTIVE and EVS"
193
194       (define (run-analyzer analyzer active evs)
195         (if (pair? evs)
196             (run-analyzer analyzer (analyzer active (car evs)) (cdr evs))
197             active
198             ))
199       (define (run-analyzers analyzers active evs)
200         (if (pair? analyzers)
201             (run-analyzers
202              (cdr analyzers)
203              (run-analyzer (car analyzers) active evs)
204              evs)
205             active
206         ))
207
208       
209
210       (sort
211
212        ;; todo: use fold or somesuch.
213        (run-analyzers
214         (list
215          analyse-absdyn-end
216          analyse-span-event
217               
218          ;; note: tie-start/span comes after tie-end/absdyn.
219          analyse-tie-end analyse-tie-start)
220
221          active evs)
222        
223        active<?))
224
225     ;; must copy, since we use assoc-remove!
226     (if (< index (vector-length voice-state-vec))
227         (begin
228           (set! active (analyse-events active (events (vector-ref voice-state-vec index))))
229           (set! (span-state (vector-ref voice-state-vec index))
230                 (list-copy active))
231
232           (helper (1+ index) active))) )
233
234
235   (helper 0 '()) )
236
237
238         
239 (define noticed '())
240 (define part-combine-listener '())
241 (define-public (set-part-combine-listener x)
242   (set! part-combine-listener x))
243
244 (define-public (notice-the-events-for-pc context lst)
245   (set! noticed (acons (ly:context-id context) lst noticed)))
246
247 (define-public (make-part-combine-music music-list)
248   (let*
249      ((m (make-music-by-name 'PartCombineMusic))
250       (m1 (context-spec-music (car music-list) 'Voice "one"))
251       (m2 (context-spec-music (cadr music-list) 'Voice "two"))
252       (props '((denies Thread)
253                (consists Rest_engraver)
254                (consists Note_heads_engraver)
255                )))
256     
257     (ly:set-mus-property! m 'elements (list m1 m2))
258     (ly:set-mus-property! m1 'property-operations props)
259     (ly:set-mus-property! m2 'property-operations props)
260     (ly:run-translator m2 part-combine-listener)
261     (ly:run-translator m1 part-combine-listener)
262     (ly:set-mus-property! m 'split-list
263                          (determine-split-list (reverse! (cdr (assoc "one" noticed)) '())
264                                                (reverse! (cdr (assoc "two" noticed)) '())))
265     (set! noticed '())
266     
267     m))
268
269
270     
271     
272
273
274
275 (define-public (determine-split-list evl1 evl2)
276   "EVL1 and EVL2 should be ascending"
277
278
279   
280   (let*
281       ((pc-debug #f)
282        (chord-threshold 8)
283        (voice-state-vec1 (make-voice-states evl1))
284        (voice-state-vec2 (make-voice-states evl2))
285        (result (make-split-state voice-state-vec1 voice-state-vec2)) )
286
287
288   (define (analyse-time-step ri)
289     (define (put x . index)
290       "Put the result to X, starting from INDEX backwards.
291
292 Only set if not set previously.
293 "
294       
295       (let
296           ((i (if (pair? index) (car index) ri)))
297
298         (if (and (<= 0 i)
299                  (not (symbol? (configuration (vector-ref result i)))))
300             (begin
301               (set! (configuration (vector-ref result i)) x)
302               (put x (1- i))
303             ))
304         ))
305
306     
307     (define (copy-state-from state-vec vs)
308       (define (copy-one-state key-idx)
309         (let*
310             ((idx (cdr key-idx))
311              (prev-ss (vector-ref result idx))
312              (prev (configuration prev-ss)) )
313           (if (symbol? prev)
314               (put prev))))
315       
316       (map copy-one-state (span-state vs)) )
317
318     (define (analyse-notes now-state) 
319       (let*
320           (
321            (vs1 (car (voice-states now-state)))
322            (vs2 (cdr (voice-states now-state)))
323            
324            (notes1 (note-events vs1))
325            (durs1 (sort (map (lambda (x) (ly:get-mus-property x 'duration)) notes1) ly:duration<?))
326            (pitches1 (sort
327                       (map (lambda (x) (ly:get-mus-property x 'pitch)) notes1) ly:pitch<?))
328            (notes2 (note-events vs2))
329            (durs2     (sort (map (lambda (x) (ly:get-mus-property x 'duration)) notes2) ly:duration<?))
330            (pitches2 (sort
331                       (map (lambda (x) (ly:get-mus-property x 'pitch)) notes2) ly:pitch<?)) )
332         
333         (cond
334          ((> (length notes1) 1) (put 'apart))
335          ((> (length notes2) 1) (put 'apart))
336          ((not (= (length notes1) (length notes2)))
337           (put 'apart))
338          ((and
339            (= (length durs1) 1)
340            (= (length durs2) 1)
341            (not (equal? (car durs1) (car durs2))))
342
343           (put 'apart))
344          (else
345           (if (and (= (length pitches1) (length pitches2)))
346               (if (and (pair? pitches1)
347                        (pair? pitches2)
348                        (< chord-threshold (ly:pitch-steps
349                                            (ly:pitch-diff (car pitches1) (car pitches2)))))
350                   (put 'apart)
351
352                   ;; copy previous split state from spanner state
353                   (begin
354                     (if (previous-voice-state vs1)
355                         (copy-state-from voice-state-vec1
356                                          (previous-voice-state vs1)))
357                     (if (previous-voice-state vs2)
358                         (copy-state-from voice-state-vec2
359                                          (previous-voice-state vs2)))
360                     (if (and (null? (span-state vs1)) (null? (span-state vs2)))
361                         (put 'chords))
362                     
363                     ))))
364          )))
365          
366
367
368     (if (< ri (vector-length result))
369         (let*
370             ((now-state (vector-ref result ri))
371              (vs1 (car (voice-states now-state)))
372              (vs2 (cdr (voice-states now-state))))
373           
374           (cond
375            ((not vs1) (put 'apart))
376            ((not vs2) (put 'apart))
377            (else
378             (let*
379                 (
380                  (active1 (previous-span-state vs1))
381                  (active2 (previous-span-state vs2))
382
383                  (new-active1 (span-state vs1))
384                  (new-active2 (span-state vs2)) )
385               (if
386                pc-debug
387                (display (list (when now-state) ri
388                                     active1 "->" new-active1
389                                     active2 "->" new-active2
390                                     "\n")))
391
392               
393               
394               (if (and (synced? now-state)
395                        (equal? active1 active2)
396                        (equal? new-active1 new-active2))
397
398                   (analyse-notes now-state)
399
400                   ;; active states different:
401                   (put 'apart)
402                   ))
403
404                                         ; go to the next one, if it exists.
405             (analyse-time-step (1+ ri))
406             )))))
407     
408   (define (analyse-a2 ri)
409     (if (< ri (vector-length result))
410         (let*
411             ((now-state (vector-ref result ri))
412              (vs1 (car (voice-states now-state)))
413              (vs2 (cdr (voice-states now-state))) )
414           
415           (if (and (equal? (configuration now-state) 'chords)
416                    vs1 vs2)
417
418               (let*
419                   ((notes1 (note-events vs1)) 
420                    (notes2 (note-events vs2)) )
421                 (cond
422                  ((and
423                    (= 1 (length notes1))
424                    (= 1 (length notes2))
425                    (equal? (ly:get-mus-property (car notes1) 'pitch)
426                            (ly:get-mus-property (car notes2) 'pitch)))
427
428                   (set! (configuration now-state) 'unisono))
429                  ((and
430                    (= 0 (length notes1))
431                    (= 0 (length notes2)))
432                   (set! (configuration now-state) 'unisilence)))
433
434                 ))
435           (analyse-a2 (1+ ri))
436
437           )))
438         
439    (define (analyse-solo12 ri)
440     
441      (define (previous-config vs)
442        (let*  ((pvs (previous-voice-state vs))
443                (spi (if pvs (split-index pvs) #f))
444                (prev-split (if spi (vector-ref result spi) #f)) )
445          
446          (if prev-split
447              (configuration prev-split)
448              'apart)
449                     
450        ))
451      (define (put-range x a b)
452        (display (list "put range "  x a b "\n"))
453        (do
454            ((i a (1+ i)))
455            ((> i b) b)
456          (set! (configuration (vector-ref result i)) x)
457          ))
458      (define (put x)
459       
460        (display (list "putting "  x "\n"))
461       (set! (configuration (vector-ref result ri)) x))
462             
463      (define (try-solo type start-idx current-idx)
464        "Find a maximum stretch that can be marked as solo. Only set
465 the mark when there are no spanners active."
466        (if (< current-idx (vector-length result))
467            (let*
468                ((now-state (vector-ref result current-idx))
469                 (solo-state ((if (equal? type 'solo1) car cdr) (voice-states now-state)))
470                 (silent-state ((if (equal? type 'solo1) cdr car) (voice-states now-state)))
471                 (silent-notes (if silent-state (note-events silent-state) '()))
472                 (solo-notes (if solo-state (note-events solo-state) '()))
473                 (soln (length solo-notes))
474                 (siln (length silent-notes)))
475
476              (cond
477               ((not (equal? (configuration now-state) 'apart))
478                current-idx)
479               ((= soln 0) current-idx)
480               ((> siln 0) current-idx)
481               ((null? (span-state solo-state))
482                (put-range type start-idx current-idx)
483                current-idx)
484               (else
485                (try-solo type start-idx (1+ current-idx)))
486                
487               ))
488            (1- current-idx)))
489               
490      (define (analyse-moment ri)
491        "Analyse 'apart  starting at RI. Return next index. "
492         (let*
493            ((now-state (vector-ref result ri))
494             (vs1 (car (voice-states now-state)))
495             (vs2 (cdr (voice-states now-state)))
496             (notes1 (if vs1 (note-events vs1) '()))
497             (notes2 (if vs2 (note-events vs2) '()))
498             (n1 (length notes1))
499             (n2 (length notes2)) )
500
501           (display (list "analysing "  (when now-state)
502                          "\n1= " vs1 
503                          "\n2= " vs2 
504                          "\n"))
505           
506           (cond
507            ((and (= n1 0) (= n2 0))
508             (put 'apart-silence)
509             (1+ ri) )
510
511            ((and (= n2 0)
512                  (equal? (when vs1) (when now-state))
513                  (null? (previous-span-state vs1)))
514             (try-solo 'solo1 ri ri))
515            ((and (= n1 0)
516                  (equal? (when vs2) (when now-state))
517                  (null? (previous-span-state vs2)))
518             (try-solo 'solo2 ri ri))
519            (else
520             (1+ ri))
521        )))
522           
523      (if (< ri (vector-length result))
524          (if (equal? (configuration (vector-ref result ri)) 'apart)
525              (analyse-solo12 (analyse-moment ri))
526              (analyse-solo12 (1+ ri)))) )
527      
528    
529    (analyse-spanner-states voice-state-vec1)
530    (analyse-spanner-states voice-state-vec2)
531
532    (if #f
533        (begin
534         (display voice-state-vec1)
535         (display "***\n")
536         (display voice-state-vec2)
537         (display "***\n")
538         (display result)
539         (display "***\n")
540         ))
541      
542    (analyse-time-step 0)
543 ;   (display result)
544 ;   (analyse-a2 0)
545 ;   (display result)
546    (analyse-solo12 0)
547    (display result)
548
549    (set! result (map
550                  (lambda (x) (cons (when x) (configuration x)))
551                  (vector->list result)))
552
553    (if pc-debug (display result))
554    result))
555
556
557 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
558 ;; autochange - fairly related to part combining.
559
560 (define-public (make-autochange-music music)
561
562   (define (generate-split-list event-list acc)
563     (if (null? event-list)
564         acc
565         (let*
566             ((evs (map car (cdar event-list)))
567              (now (caar event-list))
568              (notes (filter (lambda (x)
569                               (equal? (ly:get-mus-property  x 'name) 'NoteEvent))
570                               evs))
571              (pitch (if (pair? notes)
572                         (ly:get-mus-property (car notes) 'pitch)
573                         #f)) )
574
575         ;; tail recursive.
576         (if (and pitch (not (= (ly:pitch-steps pitch) 0)))
577             (generate-split-list
578              (cdr event-list)
579              (cons (cons now (sign (ly:pitch-steps pitch))) acc))
580             (generate-split-list (cdr event-list) acc)
581             ))
582         ))
583
584   (set! noticed '())
585   
586   (let*
587       ((m (make-music-by-name 'AutoChangeMusic))
588        (context (ly:run-translator music part-combine-listener))
589        (evs (last-pair noticed))
590        (split
591         (reverse!
592          (generate-split-list (if (pair? evs)
593                                   (reverse! (cdar evs) '()) '())
594                               '())
595          '())
596        ))
597
598     (ly:set-mus-property! m 'element music)
599     (ly:set-mus-property! m 'split-list split)
600     
601     (set! noticed '())
602     m
603   ))
604
605