summaryrefslogtreecommitdiff
path: root/src/notation.clj
blob: 7cf4e86f3cecee141abfc6ac87de6cd4a2bea9b0 (plain)
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)