summaryrefslogtreecommitdiff
path: root/src/notation.clj
blob: 6ec09f85de856d8c3b5f9dc09fd1c80ed0cbdce2 (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
123
124
125
126
127
128
129
(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)
                 dxfn (case (m/?< pulse) :inc inc :dec dec)]
             (println "AT"  (swap! c dxfn))
             (if (< 0 @c duration)
               (m/amb #{value})
               (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)))))

;; We have something pretty cool going. I think chord is actually correct.
;; Line isn't working quite corectly, though.
;; The idea of note storing its internal state is clever, but isn't behaving
;; quite corectly when you try to sequence two chords back to back. Think through
;; how this will work with pulses. You are close!
;;
(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 :dec)
(reset! ctr :inc)