Spaces:
Sleeping
Sleeping
;;; display-lily.scm -- Display music expressions using LilyPond notation | |
;;; | |
;;; | |
;;; | |
;;; Copyright (C) 2005--2020 Nicolas Sceaux <[email protected]> | |
;;; | |
;;; - This file defines the procedures used to define display methods for each | |
;;; music type: define-display-method and define-extra-display-method. | |
;;; See scm/define-music-display-methods.scm | |
;;; Display methods are stored in the `display-methods' property of each music | |
;;; type. | |
;;; | |
;;; - `music->lily-string' return a string describing a music | |
;;; expression using LilyPond notation. The special variables *indent* | |
;;; and *omit-duration* influence the indentation level and the | |
;;; display of music durations. | |
;;; | |
;;; - `with-music-match' can be used to destructure a music expression, extracting | |
;;; some interesting music properties. | |
(define-module (scm display-lily) | |
#:use-module (ice-9 optargs) | |
#:use-module (ice-9 format) | |
#:use-module (ice-9 regex) | |
#:use-module (ice-9 pretty-print) | |
#:use-module (srfi srfi-1) | |
#:use-module (srfi srfi-13) | |
#:use-module (srfi srfi-39) | |
#:use-module (lily)) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;;; | |
;;; Display method definition and call | |
;;; | |
(define-macro (define-display-method music-type vars . body) | |
"Define a display method for a music type and store it in the | |
`display-methods' property of the music type entry found in the | |
`music-name-to-property-table' hash table. Print methods previously | |
defined for that music type are lost. | |
Syntax: (define-display-method MusicType (expression) | |
...body...))" | |
`(let ((type-props (hashq-ref music-name-to-property-table | |
',music-type '())) | |
(method (lambda ,vars | |
,@body))) | |
(set! type-props | |
(assoc-set! type-props 'display-methods (list method))) | |
(hashq-set! music-name-to-property-table | |
',music-type | |
type-props) | |
method)) | |
(define-macro (define-extra-display-method music-type vars . body) | |
"Add a display method for a music type. A primary display method | |
is supposed to have been previously defined with `define-display-method'. | |
This new method should return a string or #f. If #f is returned, the next | |
display method will be called." | |
`(let* ((type-props (hashq-ref music-name-to-property-table | |
',music-type '())) | |
(methods (assoc-ref type-props 'display-methods)) | |
(new-method (lambda ,vars | |
,@body))) | |
(set! type-props | |
(assoc-set! type-props | |
'display-methods | |
(cons new-method methods))) | |
(hashq-set! music-name-to-property-table | |
',music-type | |
type-props) | |
new-method)) | |
(define* (tag->lily-string expr #:optional (post-event? #f)) | |
(format #f "~{~a ~}" | |
(map (lambda (tag) | |
(format #f "~a\\tag #'~a" (if post-event? "-" "") tag)) | |
(ly:music-property expr 'tags)))) | |
(define* (tweaks->lily-string expr #:optional (post-event? #f)) | |
(format #f "~{~a ~}" | |
(map (lambda (tweak) | |
(let ((addr (car tweak)) | |
(val (cdr tweak))) | |
(format #f "~a\\tweak ~a #~a" | |
(if post-event? "-" "") | |
(cond | |
((symbol? addr) | |
(format #f "~a" addr)) | |
((symbol? (cdr addr)) | |
(format #f "~a.~a" (car addr) (cdr addr))) | |
(else | |
(format #f "~{~a~^.~}" | |
(if (symbol? (car addr)) | |
addr | |
(cdr addr))))) | |
(scheme-expr->lily-string val)))) | |
(ly:music-property expr 'tweaks)))) | |
(define-public (music->lily-string expr) | |
"Print @var{expr}, a music expression, in LilyPond syntax." | |
(if (ly:music? expr) | |
(let* ((music-type (ly:music-property expr 'name)) | |
(procs (assoc-ref (hashq-ref music-name-to-property-table | |
music-type '()) | |
'display-methods)) | |
(result-string (and procs (any (lambda (proc) | |
(proc expr)) | |
procs)))) | |
(if result-string | |
(format #f "~a~a~a" | |
(tag->lily-string expr (post-event? expr)) | |
(tweaks->lily-string expr (post-event? expr)) | |
result-string) | |
(format #f "%{ Print method not implemented for music type ~a %}" | |
music-type))) | |
(format #f "%{ expecting a music expression: ~a %}" expr))) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;;; | |
;;; Music pattern matching | |
;;; | |
(define (var? x) | |
(and (symbol? x) (char=? #\? (string-ref (symbol->string x) 0)))) | |
(define (music? x) | |
(and (pair? x) (eqv? (car x) 'music))) | |
(define (music-list? x) | |
(and (pair? x) | |
(every music? x))) | |
(define (music-or-var-list? x) | |
(and (pair? x) | |
(every (lambda (e) | |
(or (music? e) (var? e))) | |
x))) | |
(define (key-val-list->alist lst) | |
(define (key-val-list->alist-aux lst prev-result) | |
(if (null? lst) | |
prev-result | |
(key-val-list->alist-aux (cddr lst) | |
(cons (cons (first lst) (second lst)) | |
prev-result)))) | |
(reverse! (key-val-list->alist-aux lst (list)))) | |
(define (gen-condition expr pattern) | |
"Helper function for `with-music-match'. | |
Generate an form that checks if the properties of `expr' | |
match thoses described in `pattern'." | |
(let* (;; all (property . value) found at the first depth in pattern, | |
;; including a (name . <Musictype>) pair. | |
(pat-all-props (cons (cons 'name (second pattern)) | |
(key-val-list->alist (cddr pattern)))) | |
;; all (property . value) pairs found in pattern, where value is not | |
;; a ?var, a music expression or a music list. | |
(prop-vals (remove (lambda (kons) | |
(or (var? (cdr kons)) | |
(music? (cdr kons)) | |
(music-or-var-list? (cdr kons)))) | |
pat-all-props)) | |
;; list of (property . element) pairs, where element is a music expression | |
(element-list (filter (lambda (kons) (music? (cdr kons))) | |
pat-all-props)) | |
;; list of (property . (e1 e2 ..)) pairs, where (e1 e2 ...) is a | |
;; list a music expressions | |
(elements-list (filter (lambda (kons) (music-or-var-list? (cdr kons))) | |
pat-all-props))) | |
`(and | |
;; a form that checks that `expr' is a music expression | |
;; before actually accessing its properties... | |
(ly:music? ,expr) | |
;; a form that checks that `expr' properties have the same | |
;; values as those given in `pattern' | |
,@(map (lambda (prop-val) | |
(let ((prop (car prop-val)) | |
(val (cdr prop-val))) | |
`(and (not (null? (ly:music-property ,expr ',prop))) | |
(equal? (ly:music-property ,expr ',prop) ,val)))) | |
prop-vals) | |
;; build the test condition for each element found in a (property . element) pair. | |
;; (typically, property will be 'element) | |
,@(map (lambda (prop-element) | |
(gen-condition `(ly:music-property ,expr ',(car prop-element)) (cdr prop-element))) | |
element-list) | |
;; build the test conditions for each element found in a (property . (e1 e2 ...)) pair. | |
;; this requires accessing to an element of a list, hence the index. | |
;; (typically, property will be 'elements) | |
,@(map | |
(lambda (prop-elements) | |
(let ((ges (gensym)) | |
(len (length (cdr prop-elements)))) | |
`(let ((,ges (ly:music-property ,expr ',(car prop-elements)))) | |
(and (eqv? (length+ ,ges) ,len) | |
,@(filter-map | |
(lambda (e index) | |
(and (music? e) | |
(gen-condition `(list-ref ,ges ,index) e))) | |
(cdr prop-elements) (iota len)))))) | |
elements-list)))) | |
(define (gen-bindings expr pattern) | |
"Helper function for `with-music-match'. | |
Generate binding forms by looking for ?var symbol in pattern." | |
(let* (;; all (property . value) found at the first depth of pattern, | |
;; including a (name . <Musictype>) pair. | |
(pat-all-props (cons (cons 'name (second pattern)) | |
(key-val-list->alist (cddr pattern)))) | |
;; all (property . ?var) pairs | |
(prop-vars (filter (lambda (kons) (var? (cdr kons))) | |
pat-all-props)) | |
;; list of (property . element) pairs, where element is a music expression | |
(element-list (filter (lambda (kons) (music? (cdr kons))) | |
pat-all-props)) | |
;; list of (property . (e1 e2 ..)) pairs, where (e1 e2 ...) is a | |
;; list a music expressions | |
(elements-list (filter (lambda (kons) (music-or-var-list? (cdr kons))) | |
pat-all-props))) | |
(append | |
;; the binding form for the ?var variable found in pattern (first depth). | |
;; ?var is bound to the value of `expr' property | |
(map (lambda (prop-var) | |
`(,(cdr prop-var) (ly:music-property ,expr ',(car prop-var)))) | |
prop-vars) | |
;; generate bindings for each element found in a (property . element) pair. | |
;; (typically, property will be 'element) | |
(append-map (lambda (prop-element) | |
(gen-bindings `(ly:music-property ,expr ',(car prop-element)) | |
(cdr prop-element))) | |
element-list) | |
;; generate bindings for each element found in a (property . (e1 e2 ...)) pair | |
;; (typically, property will be 'elements) | |
(append-map (lambda (prop-elements) | |
(let ((index -1)) | |
(append-map (lambda (e) | |
(set! index (1+ index)) | |
(if (var? e) | |
`((,e (list-ref (ly:music-property ,expr ',(car prop-elements)) ,index))) | |
(gen-bindings `(list-ref (ly:music-property ,expr ',(car prop-elements)) | |
,index) | |
e))) | |
(cdr prop-elements)))) | |
elements-list)))) | |
(defmacro-public with-music-match (music-expr+pattern . body) | |
"If `music-expr' matches `pattern', call `body'. `pattern' should look like: | |
'(music <MusicType> | |
property value | |
property ?var1 | |
element (music <MusicType> ...) | |
elements ((music <MusicType> ...) | |
?var2 | |
(music <MusicType> ...))) | |
The properties of `music-expr' are checked against the values given in the | |
pattern (the name property being the <MusicType> symbol after the `music' | |
keyword), then all music expression found in its properties (such as 'element | |
or 'elements). | |
When ?var is found instead of a property value, ?var is bound that property value, | |
as read inside `music-expr'. ?var may also be used to refere to a whole music | |
expression inside an elements list for instance. These bindings are accessible | |
inside body." | |
(let ((music-expr (first music-expr+pattern)) | |
(pattern (second music-expr+pattern)) | |
(expr-sym (gensym))) | |
`(let ((,expr-sym ,music-expr)) | |
(and ,(gen-condition expr-sym pattern) | |
(let ,(gen-bindings expr-sym pattern) | |
,@body))))) | |
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
;;; | |
;;; Special parameters | |
;;; | |
;;; indentation | |
(define-public *indent* (make-parameter 0)) | |
;;; set to #t to omit duration printing | |
(define-public *omit-duration* (make-parameter #f)) | |
;;; Set to #t to force a line break with some kinds of expressions (eg sequential music) | |
(define *force-line-break* (make-parameter #t)) | |
(define *max-element-number-before-break* (make-parameter 6)) | |
;; \times factor (used in durations) | |
(define *time-scale* (make-parameter 1)) | |
(define *current-context* (make-parameter 'Bottom)) | |
(define *explicit-mode* (make-parameter #t)) | |
(define (new-line->lily-string) | |
(format #f "~%~v_" (max 0 (1- (*indent*))))) | |
;;; | |
;;; music type predicate maker | |
;;; | |
(define (make-music-type-predicate . music-types) | |
(define make-music-type-predicate-aux | |
(lambda (mtypes) | |
(lambda (expr) | |
(if (null? mtypes) | |
#f | |
(or (eqv? (car mtypes) (ly:music-property expr 'name)) | |
((make-music-type-predicate-aux (cdr mtypes)) expr)))))) | |
(make-music-type-predicate-aux music-types)) | |
(ly:load "define-music-display-methods.scm") | |