1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
(ns notation
"Experimental notation"
(:require [missionary.core :as m]
[clojure.set :refer [union]]))
(comment
;; Parallel groups
;; Notes 1, 2, and 3 simultaneously
;; = should remind you of amb=
;; implicit duration of 1
[= 1 2 3]
;; Compiles to?
;; Same as above, but with duration 3
([= 1 2 3] 3)
;; Notes 1, 2, and 3 all with different durations
[=
(1 2)
(2 3)
(3 4)]
;; Inner values override outer values
;; In this chord, 1 would have a duration of 3 while 2 and 3 would have a duration of 2
([= (1 3) 2 3] 2)
;; Notes 1, 2, and 3 all with different durations and velocities
[=
(1 2 100)
(2 3 110)
(3 4 123)]
;; Sequential groups
;; Note 1, then note 2, then note 3
[1 2 3]
;; Note 1 duration 1, then note 2 duration 2, then note 3 duration 1
[(1 1)
(2 2)
(3 1)]
;; Three chords played sequentially
[[= 1 2 3]
[= 1 2 3]
[= 1 2 3]]
;; Note 1, followed by a rest, followed by note 3
[1 (r) 3]
;; Unlike notes, rests are at most 2-tuples
;; (Think about it: Rests never have a note value)
;; Assign the note sequence 1 2 3 to the name loop1
;; The first argument is always the name; the last argument is always either
;; a sequential or parallel group
(=loop1 [1 2 3])
;; Use loop1
[1 (loop1) 2 3]
;; Middle arguments are variable names
(=loop2 dur ([1 2 3] dur))
;; TODO:
;; - Note literals turn into numbers
;; - Represent keyboard as byte array of shorts
;; - play a note increments, stop a note decrements
;; - Multiple instruments
;; - Mapping inputs to vars
;; - Inputs get declared at the top of a track
;; - Devices get mapped to declared inputs
;; - Notion of scenes that change mapping of inputs to vars
;; - Loops
)
(def clock (atom 0))
(def >clock (m/signal (m/watch clock)))
(defn note [clock start duration value]
(m/cp
(if (m/?< (m/latest #(<= start % (dec (+ start duration))) clock))
#{value}
#{})))
(defmacro poly
[& notes]
(let [atoms (repeatedly (count notes) gensym)
let-bindings (vec (mapcat (fn [atom] [atom `(atom #{})]) atoms))
reset-forms (map (fn [atom note] `(m/amb (reset! ~atom (m/?< ~note)))) atoms notes)
union-form (cons `union (map (fn [atom] `(deref ~atom)) atoms))]
`(m/relieve {}
(m/ap
(let ~let-bindings
(m/amb= ~@reset-forms)
~union-form)))))
;; TODO: Group could actually wrap note, rather than using explicitly
;; WIll introduce a lot of GC churn, though
(defn group
[clock start end content]
(m/cp
(let [content (m/signal content)]
(if (m/?< (m/latest #(<= start % end) clock))
(m/?< content)
(m/amb #{})))))
(def melody
(m/signal
(poly (note >clock 0 4 1)
(note >clock 0 5 3)
(note >clock 0 3 5))))
#_(def cancel
((m/reduce prn #{} melody) {} {}))
#_(cancel)
#_(reset! clock 0)
#_(swap! clock inc)
#_(swap! clock dec)
|