Nitpick: ly:spanner-bound grob name slur -> spanner.
[lilypond.git] / scm / display-lily.scm
blob21ed3a93f65597c1418b71f0d2a5e59f593a9d09
1 ;;; display-lily.scm -- Display music expressions using LilyPond notation
2 ;;;
3 ;;;
4 ;;;
5 ;;; (c) 2005--2009 Nicolas Sceaux  <nicolas.sceaux@free.fr>
6 ;;;
8 ;;; - This file defines the procedures used to define display methods for each
9 ;;; music type: define-display-method and define-extra-display-method.
10 ;;; See scm/define-music-display-methods.scm
11 ;;; Display methods are stored in the `display-methods' property of each music
12 ;;; type.
13 ;;;
14 ;;; - `music->lily-string' return a string describing a music expression using
15 ;;; LilyPond notation. The special variables *indent*, *previous-duration*,
16 ;;; and *force-duration* influence the indentation level and the display of
17 ;;; music durations.
18 ;;;
19 ;;; - `with-music-match' can be used to destructure a music expression, extracting
20 ;;; some interesting music properties.
23 (define-module (scm display-lily)
24   #:use-module (ice-9 optargs)
25   #:use-module (ice-9 format)
26   #:use-module (ice-9 regex)
27   #:use-module (ice-9 pretty-print)
28   #:use-module (srfi srfi-1)
29   #:use-module (srfi srfi-13)
30   #:use-module (srfi srfi-39)
31   #:use-module (lily)
32   #:use-syntax (srfi srfi-39)
33   #:use-syntax (ice-9 optargs))
35 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
36 ;;;
37 ;;; Display method definition and call
38 ;;;
41 (define-macro (define-display-method music-type vars . body)
42   "Define a display method for a music type and store it in the
43 `display-methods' property of the music type entry found in the
44 `music-name-to-property-table' hash table. Print methods previously
45 defined for that music type are lost. 
46 Syntax: (define-display-method MusicType (expression parser)
47           ...body...))"
48   `(let ((type-props (hashq-ref music-name-to-property-table
49                                 ',music-type '()))
50          (method (lambda ,vars
51                    ,@body)))
52      (set! type-props
53            (assoc-set! type-props 'display-methods (list method)))
54      (hashq-set! music-name-to-property-table
55                  ',music-type
56                  type-props)
57      method))
59 (define-macro (define-extra-display-method music-type vars . body)
60   "Add a display method for a music type. A primary display method
61 is supposed to have been previously defined with `define-display-method'.
62 This new method should return a string or #f. If #f is returned, the next
63 display method will be called."
64   `(let* ((type-props (hashq-ref music-name-to-property-table
65                                  ',music-type '()))
66           (methods (assoc-ref type-props 'display-methods))
67           (new-method (lambda ,vars
68                         ,@body)))
69      (set! type-props
70            (assoc-set! type-props
71                        'display-methods
72                        (cons new-method methods)))
73      (hashq-set! music-name-to-property-table
74                  ',music-type
75                  type-props)
76      new-method))
78 (define* (tag->lily-string expr #:optional (post-event? #f))
79   (format #f "~{~a ~}"
80           (map (lambda (tag)
81                  (format #f "~a\\tag #'~a" (if post-event? "-" "") tag))
82                (ly:music-property expr 'tags))))
84 (define-public (music->lily-string expr parser)
85   "Print expr, a music expression, in LilyPond syntax"
86   (if (ly:music? expr)
87       (let* ((music-type (ly:music-property expr 'name))
88              (procs (assoc-ref (hashq-ref music-name-to-property-table
89                                           music-type '())
90                                'display-methods))
91              (result-string (and procs (any (lambda (proc)
92                                               (proc expr parser))
93                                             procs))))
94         (if result-string
95             (format #f "~a~a" 
96                     (tag->lily-string expr (post-event? expr))
97                     result-string)
98             (format #f "%{ Print method not implemented for music type ~a %}"
99                     music-type)))
100       (format #f "%{ expecting a music expression: ~a %}" expr)))
102 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
104 ;;; Music pattern matching
105 ;;; 
107 (define (var? x)
108   (and (symbol? x) (char=? #\? (string-ref (symbol->string x) 0))))
110 (define (music? x)
111   (and (pair? x) (eqv? (car x) 'music)))
113 (define (music-list? x)
114   (and (pair? x)
115        (every music? x)))
117 (define (music-or-var-list? x)
118   (and (pair? x)
119        (every (lambda (e)
120                 (or (music? e) (var? e)))
121               x)))
123 (define (key-val-list->alist lst)
124   (define (key-val-list->alist-aux lst prev-result)
125     (if (null? lst)
126         prev-result
127         (key-val-list->alist-aux (cddr lst)
128                                  (cons (cons (first lst) (second lst))
129                                        prev-result))))
130   (reverse! (key-val-list->alist-aux lst (list))))
132 (define (gen-condition expr pattern)
133   "Helper function for `with-music-match'.
134 Generate an form that checks if the properties of `expr'
135 match thoses described in `pattern'."
136   (let* (;; all (property . value) found at the first depth in pattern,
137          ;; including a (name . <Musictype>) pair.
138          (pat-all-props (cons (cons 'name (second pattern))
139                               (key-val-list->alist (cddr pattern))))
140          ;; all (property . value) pairs found in pattern, where value is not
141          ;; a ?var, a music expression or a music list.
142          (prop-vals (remove (lambda (kons)
143                              (or (var? (cdr kons))
144                                  (music? (cdr kons))
145                                  (music-or-var-list? (cdr kons))))
146                             pat-all-props))
147          ;; list of (property . element) pairs, where element is a music expression
148          (element-list (filter (lambda (kons) (music? (cdr kons)))
149                                pat-all-props))
150          ;; list of (property . (e1 e2 ..)) pairs, where (e1 e2 ...) is a 
151          ;; list a music expressions
152          (elements-list (filter (lambda (kons) (music-or-var-list? (cdr kons)))
153                                 pat-all-props)))
154     `(and 
155       ;; a form that checks that `expr' is a music expression
156       ;; before actually accessing its properties...
157       (ly:music? ,expr)
158       ;; a form that checks that `expr' properties have the same
159       ;; values as those given in `pattern'
160       ,@(map (lambda (prop-val)
161                (let ((prop (car prop-val))
162                      (val (cdr prop-val)))
163                  `(and (not (null? (ly:music-property ,expr ',prop)))
164                        (equal? (ly:music-property ,expr ',prop) ,val))))
165              prop-vals)
166       ;; build the test condition for each element found in a (property . element) pair.
167       ;; (typically, property will be 'element)
168       ,@(map (lambda (prop-element)
169                (gen-condition `(ly:music-property ,expr ',(car prop-element)) (cdr prop-element)))
170              element-list)
171       ;; build the test conditions for each element found in a (property . (e1 e2 ...)) pair.
172       ;; this requires accessing to an element of a list, hence the index.
173       ;; (typically, property will be 'elements)
174       ,@(map (lambda (prop-elements)
175                (let ((ges (gensym))
176                      (index -1))
177                  `(and ,@(map (lambda (e)
178                                 (set! index (1+ index))
179                                 (if (music? e)
180                                     (gen-condition `(and (> (length (ly:music-property ,expr ',(car prop-elements)))
181                                                             ,index)
182                                                          (list-ref (ly:music-property ,expr ',(car prop-elements)) 
183                                                                    ,index))
184                                                    e)
185                                     #t))
186                               (cdr prop-elements)))))
187              elements-list))))
189 (define (gen-bindings expr pattern)
190   "Helper function for `with-music-match'.
191 Generate binding forms by looking for ?var symbol in pattern."
192   (let* (;; all (property . value) found at the first depth of pattern,
193          ;; including a (name . <Musictype>) pair.
194          (pat-all-props (cons (cons 'name (second pattern))
195                               (key-val-list->alist (cddr pattern))))
196          ;; all (property . ?var) pairs
197          (prop-vars (filter (lambda (kons) (var? (cdr kons)))
198                             pat-all-props))
199          ;; list of (property . element) pairs, where element is a music expression
200          (element-list (filter (lambda (kons) (music? (cdr kons)))
201                                pat-all-props))
202          ;; list of (property . (e1 e2 ..)) pairs, where (e1 e2 ...) is a 
203          ;; list a music expressions
204          (elements-list (filter (lambda (kons) (music-or-var-list? (cdr kons)))
205                                 pat-all-props)))
206     (append 
207      ;; the binding form for the ?var variable found in pattern (first depth).
208      ;; ?var is bound to the value of `expr' property
209      (map (lambda (prop-var)
210             `(,(cdr prop-var) (ly:music-property ,expr ',(car prop-var))))
211           prop-vars)
212      ;; generate bindings for each element found in a (property . element) pair.
213      ;; (typically, property will be 'element)
214      (append-map (lambda (prop-element)
215                    (gen-bindings `(ly:music-property ,expr ',(car prop-element))
216                                  (cdr prop-element)))
217                  element-list)
218      ;; generate bindings for each element found in a (property . (e1 e2 ...)) pair
219      ;; (typically, property will be 'elements)
220             (append-map (lambda (prop-elements)
221                           (let ((index -1))
222                             (append-map (lambda (e)
223                                           (set! index (1+ index))
224                                           (if (var? e)
225                                               `((,e (list-ref (ly:music-property ,expr ',(car prop-elements)) ,index)))
226                                               (gen-bindings `(list-ref (ly:music-property ,expr ',(car prop-elements))
227                                                                        ,index)
228                                                             e)))
229                                         (cdr prop-elements))))
230                         elements-list))))
232 (define-macro (with-music-match music-expr+pattern . body)
233   "If `music-expr' matches `pattern', call `body'. `pattern' should look like:
234   '(music <MusicType>
235      property value
236      property ?var1
237      element (music <MusicType> ...)
238      elements ((music <MusicType> ...)
239                ?var2
240                (music <MusicType> ...)))
241 The properties of `music-expr' are checked against the values given in the
242 pattern (the name property being the <MusicType> symbol after the `music'
243 keyword), then all music expression found in its properties (such as 'element
244 or 'elements).
245 When ?var is found instead of a property value, ?var is bound that property value,
246 as read inside `music-expr'. ?var may also be used to refere to a whole music 
247 expression inside an elements list for instance. These bindings are accessible 
248 inside body."
249   (let ((music-expr (first music-expr+pattern))
250         (pattern (second music-expr+pattern))
251         (expr-sym (gensym)))
252     `(let ((,expr-sym ,music-expr))
253        (if ,(gen-condition expr-sym pattern)
254            (let ,(gen-bindings expr-sym pattern)
255              ,@body)
256            #f))))
258 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
260 ;;; Special parameters
263 ;;; indentation
264 (define-public *indent* (make-parameter 0))
266 ;;; set to #t to force duration printing
267 (define-public *force-duration* (make-parameter #f))
269 ;;; last duration found
270 (define-public *previous-duration* (make-parameter (ly:make-duration 2)))
272 ;;; Set to #t to force a line break with some kinds of expressions (eg sequential music)
273 (define *force-line-break* (make-parameter #t))
274 (define *max-element-number-before-break* (make-parameter 6))
276 ;; \times factor (used in durations)
277 (define *time-factor-denominator* (make-parameter #f))
278 (define *time-factor-numerator* (make-parameter #f))
280 (define *current-context* (make-parameter 'Bottom))
282 (define *explicit-mode* (make-parameter #t))
284 (define (new-line->lily-string)
285   (format #f "~%~v_" (max 0 (1- (*indent*)))))
288 ;;; music type predicate maker
291 (define (make-music-type-predicate . music-types)
292   (define ((make-music-type-predicate-aux mtypes) expr)
293     (if (null? mtypes)
294         #f
295         (or (eqv? (car mtypes) (ly:music-property expr 'name))
296             ((make-music-type-predicate-aux (cdr mtypes)) expr))))
297   (make-music-type-predicate-aux music-types))
299 (load "define-music-display-methods.scm")