lily.scm 5.8 KB

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