lily.scm 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. (require note)
  2. (export lilyscore->file
  3. #!optional
  4. chord-display
  5. lilyscore-display)
  6. (define (display-items port l-of-str)
  7. (for-each (lambda (str)
  8. (display str port))
  9. l-of-str))
  10. (define (displayl port . l-of-str)
  11. (display-items port l-of-str))
  12. (define (keyword->string k)
  13. (symbol->string (keyword->symbol k)))
  14. (define (=symbol? a)
  15. (let* ((s (symbol->string a))
  16. (l (string-length s)))
  17. (and (>= l 2)
  18. (eq? (string-ref s 0) #\=))))
  19. (define (=symbol->string a)
  20. (let* ((s (symbol->string a))
  21. (l (string-length s)))
  22. (and (>= l 2)
  23. (substring s 1 l))))
  24. (def (note->string note) -> string?
  25. ((pmatch note
  26. (integer? integer->lilynote)
  27. (symbol? scientificnote->lilynote)
  28. ;; could pass through strings, too
  29. (string? identity
  30. ;; just delete the next
  31. ;; form to enable them
  32. (error "strings not OK for now")))
  33. note))
  34. (define (note-display note port)
  35. (display-items port (list (note->string note)
  36. "1" ;; XX for now
  37. )))
  38. (define (chord-display l port)
  39. (display "<" port)
  40. (display-items port
  41. (list-join (map note->string l)
  42. " "))
  43. (display ">1" port) ;; XX for now
  44. (newline port))
  45. (define (lilyscore-display l port)
  46. (let ld ((l l))
  47. (pmatch l
  48. (pair?
  49. (define (process-rest)
  50. ;; Example: `(new: Staff (textLengthOn: (1 3 4)))` translated
  51. ;; to `\new Staff { \textLengthOn <c' cs' e'>1 }`. We have
  52. ;; already processed the first of l, `\new`; of the
  53. ;; remainder, the last one is a scope "{ }" or otherwise
  54. ;; something to recurse into; the values inbetween are
  55. ;; options, like `Staff`.
  56. (let* ((r (cdr l))
  57. (v (last r))
  58. (options (butlast r)))
  59. (for-each (lambda (option)
  60. ;; simplified; for more see
  61. ;; http://lilypond.org/doc/v2.18/Documentation
  62. ;; /notation/creating-and-referencing-contexts
  63. ;; #index-new-contexts
  64. (pmatch option
  65. (symbol?
  66. ;; e.g. `Staff`
  67. (display " " port)
  68. (display (symbol->string option) port))))
  69. options)
  70. (pmatch v
  71. (list?
  72. (display " {\n" port)
  73. (if (and (pair? v)
  74. (integer? (first v)))
  75. ;; v is a single chord
  76. (ld v)
  77. ;; v contains sublists (= chords)
  78. (for-each (lambda (l)
  79. (display " " port)
  80. (ld l))
  81. v))
  82. (display "}" port))
  83. (else
  84. (display " " port)
  85. (ld v))))
  86. (newline port))
  87. (let ((a (car l)))
  88. (pmatch a
  89. (keyword?
  90. (displayl port "\\" (keyword->string a))
  91. (process-rest))
  92. (symbol?
  93. (pmatch a
  94. (=symbol?
  95. (displayl port (=symbol->string a) " =")
  96. (process-rest))
  97. ('lexps
  98. (displayln "{" port)
  99. (for-each (lambda (chord)
  100. (chord-display chord port))
  101. (cdr l))
  102. (displayln "}" port))
  103. ('annotation
  104. ;; (lily '((1 4 5 6 7 4) (annotation below "hello")))
  105. (let-list ;;(([symbol? updown] [string? txt]) (cdr l))
  106. ((updown txt . chords) (cdr l))
  107. (-> symbol? updown)
  108. (-> string? txt)
  109. (unless (null? chords)
  110. (lilyscore-display chords port))
  111. (display
  112. (case updown
  113. ((above up ^) "^")
  114. ((below down _) "_")
  115. (else
  116. (error "annotation needs ^ or _ as first argument"
  117. l)))
  118. port)
  119. (write txt port)))
  120. (else
  121. ;; should be a note symbol => a single chord
  122. (chord-display l port))))
  123. (integer?
  124. ;; abspitch => a single chord
  125. (chord-display l port))
  126. (else
  127. ;; treat l as an actual list, not as an "AST" node
  128. (for-each ld l)))))
  129. (string?
  130. (write l port))
  131. (integer?
  132. (note-display l port)))))
  133. (define (lilyscore->file l path)
  134. (call-with-output-file path
  135. (cut lilyscore-display l <>)))
  136. (TEST
  137. > (def (tst v)
  138. (call-with-output-string ""
  139. (cut lilyscore-display v <>)))
  140. > (tst '(#:version "2.3.4"))
  141. "\\version \"2.3.4\"\n"
  142. > (tst '(#:header ((=tagline "foo") (=author "bar"))))
  143. "\\header {
  144. tagline = \"foo\"
  145. author = \"bar\"
  146. }\n")
  147. (TEST
  148. > (define tval
  149. ;; "lilyscore"
  150. `((#:version "2.17.2")
  151. (#:glanguage "english")
  152. (#:header ((=tagline "foo")
  153. (=author "bar")))
  154. (lexps (C4 E4 G4)
  155. (A3 F2)
  156. (10 44 23)) ;; ok bogus to have 2 scores, but for our test...
  157. (#:score
  158. ((#:new Staff
  159. ((C4 E4 G4)
  160. (A3 F2) (annotation down "Foo")
  161. (10 44 23)
  162. (annotation up "Bar"
  163. (11 44 23)
  164. (10 44 23))))
  165. (#:layout ())
  166. (#:midi ())))))
  167. > (tst tval)
  168. "\\version \"2.17.2\"
  169. \\glanguage \"english\"
  170. \\header {
  171. tagline = \"foo\"
  172. author = \"bar\"
  173. }
  174. {
  175. <c' e' g'>1
  176. <a f,>1
  177. <as' gs'''' b''>1
  178. }
  179. \\score {
  180. \\new Staff {
  181. <c' e' g'>1
  182. <a f,>1
  183. _\"Foo\" <as' gs'''' b''>1
  184. <b' gs'''' b''>1
  185. <as' gs'''' b''>1
  186. ^\"Bar\"}
  187. \\layout {
  188. }
  189. \\midi {
  190. }
  191. }
  192. ")