1 ;;; display-lily.scm -- Display music expressions using LilyPond notation
5 ;;; Copyright (C) 2005--2015 Nicolas Sceaux <nicolas.sceaux@free.fr>
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
14 ;;; - `music->lily-string' return a string describing a music
15 ;;; expression using LilyPond notation. The special variables *indent*
16 ;;; and *omit-duration* influence the indentation level and the
17 ;;; display of music durations.
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)
33 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
35 ;;; Display method definition and call
39 (define-macro (define-display-method music-type vars . body)
40 "Define a display method for a music type and store it in the
41 `display-methods' property of the music type entry found in the
42 `music-name-to-property-table' hash table. Print methods previously
43 defined for that music type are lost.
44 Syntax: (define-display-method MusicType (expression)
46 `(let ((type-props (hashq-ref music-name-to-property-table
51 (assoc-set! type-props 'display-methods (list method)))
52 (hashq-set! music-name-to-property-table
57 (define-macro (define-extra-display-method music-type vars . body)
58 "Add a display method for a music type. A primary display method
59 is supposed to have been previously defined with `define-display-method'.
60 This new method should return a string or #f. If #f is returned, the next
61 display method will be called."
62 `(let* ((type-props (hashq-ref music-name-to-property-table
64 (methods (assoc-ref type-props 'display-methods))
65 (new-method (lambda ,vars
68 (assoc-set! type-props
70 (cons new-method methods)))
71 (hashq-set! music-name-to-property-table
76 (define* (tag->lily-string expr #:optional (post-event? #f))
79 (format #f "~a\\tag #'~a" (if post-event? "-" "") tag))
80 (ly:music-property expr 'tags))))
82 (define* (tweaks->lily-string expr #:optional (post-event? #f))
85 (let ((addr (car tweak))
87 (format #f "~a\\tweak ~a #~a"
88 (if post-event? "-" "")
91 (format #f "~a" addr))
93 (format #f "~a.~a" (car addr) (cdr addr)))
95 (format #f "~{~a~^.~}"
96 (if (symbol? (car addr))
99 (scheme-expr->lily-string val))))
100 (ly:music-property expr 'tweaks))))
102 (define-public (music->lily-string expr)
103 "Print @var{expr}, a music expression, in LilyPond syntax."
105 (let* ((music-type (ly:music-property expr 'name))
106 (procs (assoc-ref (hashq-ref music-name-to-property-table
109 (result-string (and procs (any (lambda (proc)
114 (tag->lily-string expr (post-event? expr))
115 (tweaks->lily-string expr (post-event? expr))
117 (format #f "%{ Print method not implemented for music type ~a %}"
119 (format #f "%{ expecting a music expression: ~a %}" expr)))
121 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
123 ;;; Music pattern matching
127 (and (symbol? x) (char=? #\? (string-ref (symbol->string x) 0))))
130 (and (pair? x) (eqv? (car x) 'music)))
132 (define (music-list? x)
136 (define (music-or-var-list? x)
139 (or (music? e) (var? e)))
142 (define (key-val-list->alist lst)
143 (define (key-val-list->alist-aux lst prev-result)
146 (key-val-list->alist-aux (cddr lst)
147 (cons (cons (first lst) (second lst))
149 (reverse! (key-val-list->alist-aux lst (list))))
151 (define (gen-condition expr pattern)
152 "Helper function for `with-music-match'.
153 Generate an form that checks if the properties of `expr'
154 match thoses described in `pattern'."
155 (let* (;; all (property . value) found at the first depth in pattern,
156 ;; including a (name . <Musictype>) pair.
157 (pat-all-props (cons (cons 'name (second pattern))
158 (key-val-list->alist (cddr pattern))))
159 ;; all (property . value) pairs found in pattern, where value is not
160 ;; a ?var, a music expression or a music list.
161 (prop-vals (remove (lambda (kons)
162 (or (var? (cdr kons))
164 (music-or-var-list? (cdr kons))))
166 ;; list of (property . element) pairs, where element is a music expression
167 (element-list (filter (lambda (kons) (music? (cdr kons)))
169 ;; list of (property . (e1 e2 ..)) pairs, where (e1 e2 ...) is a
170 ;; list a music expressions
171 (elements-list (filter (lambda (kons) (music-or-var-list? (cdr kons)))
174 ;; a form that checks that `expr' is a music expression
175 ;; before actually accessing its properties...
177 ;; a form that checks that `expr' properties have the same
178 ;; values as those given in `pattern'
179 ,@(map (lambda (prop-val)
180 (let ((prop (car prop-val))
181 (val (cdr prop-val)))
182 `(and (not (null? (ly:music-property ,expr ',prop)))
183 (equal? (ly:music-property ,expr ',prop) ,val))))
185 ;; build the test condition for each element found in a (property . element) pair.
186 ;; (typically, property will be 'element)
187 ,@(map (lambda (prop-element)
188 (gen-condition `(ly:music-property ,expr ',(car prop-element)) (cdr prop-element)))
190 ;; build the test conditions for each element found in a (property . (e1 e2 ...)) pair.
191 ;; this requires accessing to an element of a list, hence the index.
192 ;; (typically, property will be 'elements)
194 (lambda (prop-elements)
196 (len (length (cdr prop-elements))))
197 `(let ((,ges (ly:music-property ,expr ',(car prop-elements))))
198 (and (eqv? (length+ ,ges) ,len)
202 (gen-condition `(list-ref ,ges ,index) e)))
203 (cdr prop-elements) (iota len))))))
206 (define (gen-bindings expr pattern)
207 "Helper function for `with-music-match'.
208 Generate binding forms by looking for ?var symbol in pattern."
209 (let* (;; all (property . value) found at the first depth of pattern,
210 ;; including a (name . <Musictype>) pair.
211 (pat-all-props (cons (cons 'name (second pattern))
212 (key-val-list->alist (cddr pattern))))
213 ;; all (property . ?var) pairs
214 (prop-vars (filter (lambda (kons) (var? (cdr kons)))
216 ;; list of (property . element) pairs, where element is a music expression
217 (element-list (filter (lambda (kons) (music? (cdr kons)))
219 ;; list of (property . (e1 e2 ..)) pairs, where (e1 e2 ...) is a
220 ;; list a music expressions
221 (elements-list (filter (lambda (kons) (music-or-var-list? (cdr kons)))
224 ;; the binding form for the ?var variable found in pattern (first depth).
225 ;; ?var is bound to the value of `expr' property
226 (map (lambda (prop-var)
227 `(,(cdr prop-var) (ly:music-property ,expr ',(car prop-var))))
229 ;; generate bindings for each element found in a (property . element) pair.
230 ;; (typically, property will be 'element)
231 (append-map (lambda (prop-element)
232 (gen-bindings `(ly:music-property ,expr ',(car prop-element))
235 ;; generate bindings for each element found in a (property . (e1 e2 ...)) pair
236 ;; (typically, property will be 'elements)
237 (append-map (lambda (prop-elements)
239 (append-map (lambda (e)
240 (set! index (1+ index))
242 `((,e (list-ref (ly:music-property ,expr ',(car prop-elements)) ,index)))
243 (gen-bindings `(list-ref (ly:music-property ,expr ',(car prop-elements))
246 (cdr prop-elements))))
249 (defmacro-public with-music-match (music-expr+pattern . body)
250 "If `music-expr' matches `pattern', call `body'. `pattern' should look like:
254 element (music <MusicType> ...)
255 elements ((music <MusicType> ...)
257 (music <MusicType> ...)))
258 The properties of `music-expr' are checked against the values given in the
259 pattern (the name property being the <MusicType> symbol after the `music'
260 keyword), then all music expression found in its properties (such as 'element
262 When ?var is found instead of a property value, ?var is bound that property value,
263 as read inside `music-expr'. ?var may also be used to refere to a whole music
264 expression inside an elements list for instance. These bindings are accessible
266 (let ((music-expr (first music-expr+pattern))
267 (pattern (second music-expr+pattern))
269 `(let ((,expr-sym ,music-expr))
270 (and ,(gen-condition expr-sym pattern)
271 (let ,(gen-bindings expr-sym pattern)
274 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
276 ;;; Special parameters
280 (define-public *indent* (make-parameter 0))
282 ;;; set to #t to omit duration printing
283 (define-public *omit-duration* (make-parameter #f))
285 ;;; Set to #t to force a line break with some kinds of expressions (eg sequential music)
286 (define *force-line-break* (make-parameter #t))
287 (define *max-element-number-before-break* (make-parameter 6))
289 ;; \times factor (used in durations)
290 (define *time-scale* (make-parameter 1))
292 (define *current-context* (make-parameter 'Bottom))
294 (define *explicit-mode* (make-parameter #t))
296 (define (new-line->lily-string)
297 (format #f "~%~v_" (max 0 (1- (*indent*)))))
300 ;;; music type predicate maker
303 (define (make-music-type-predicate . music-types)
304 (define make-music-type-predicate-aux
309 (or (eqv? (car mtypes) (ly:music-property expr 'name))
310 ((make-music-type-predicate-aux (cdr mtypes)) expr))))))
311 (make-music-type-predicate-aux music-types))
313 (ly:load "define-music-display-methods.scm")