summaryrefslogtreecommitdiff
path: root/src/notation.clj
blob: a4b9488b62f624f0b2515a15976c10ef81c63ef3 (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
120
121
122
(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 ctr (atom nil))
(def pulse (m/signal (m/watch ctr)))

(defn note [value duration]
  (m/eduction (take-while #(not= ::done %))
              (m/ap
               (m/amb #{value}
                      (let [c (atom 0)]
                        (m/?< pulse)
                        (swap! c inc)
                        (if (> @c duration)
                          (m/amb
                           #{}
                           ::done)
                          (m/amb)))))))

(defmacro chord
  [& 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/ap
      (m/amb
       (let ~let-bindings
         (m/amb= ~@reset-forms)
         ~union-form)))))

(defmacro line
  [& notes]
  `(m/ap (m/amb ~@(map (fn [note] `(m/?< ~note)) notes))))

(def melody
  (line
   (chord (note 1 2)
          (note 3 2)
          (note 5 2))
   (chord (note 2 2)
          (note 4 2)
          (note 6 2))))

(def cancel
  ((m/reduce prn #{} melody) {} {}))

(cancel)

(reset! ctr nil)