blob: eb0e2d24dc384eeff3513b787eeb35d2ea292de0 (
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
|
(ns main
(:require [missionary.core :as m]
[clojure.set :refer [difference union]]))
;; How many times per second are output continuous values sampled and turned
;; into events?
(def sample-rate (atom 30))
(defn set-sample-rate [v] (reset! sample-rate v))
;; Temporary atom to explore the concept of note state as a continuous value
(def notes-on (atom #{}))
(def >notes-on (m/signal (m/watch notes-on)))
(def playback-enabled? (atom true))
(defn enable-playback []
(reset! playback-enabled? true))
(defn disable-playback []
(reset! playback-enabled? false))
(defn play-notes [& v]
(when @playback-enabled?
(swap! notes-on union (into #{} v))))
(defn stop-notes [& v]
(when @playback-enabled?
(swap! notes-on difference (into #{} v))))
(def clock
(m/ap
(loop []
(m/amb
;; TODO: This seems to be emitting twice per cycle
;; TODO: Currently, there will be latency when changing the sample rate
;; due to having to wait for the most recent sleep to complete
;; Update clock to be reactive on sample-rate, too
(m/?
(m/sleep (/ 1000 @sample-rate)))
:tick
(recur)))))
;; convert the continuous time >notes-on flow to a series of discrete midi note on and off events
(def output
(m/eduction
(comp (remove #(= (select-keys % [:note-on :note-off]) {:note-on #{} :note-off #{}}))
(dedupe))
(m/reductions (fn [{:keys [active note-on note-off]} [curr _]]
{:note-on (difference (difference curr active) note-on)
:note-off (difference (difference active curr) note-off)
:active curr})
{:note-on #{}
:note-off #{}
:active #{}}
;; This actually does a bunch of unnecessary work
;; What we really want is to sample at _at most_ sample rate,
;; but not at all if nothing has changed
(m/sample
vector
>notes-on
clock))))
(def toggle-playback
(m/ap
(let [local-playback-enabled? (atom nil)
local-active (atom #{})
[tag value] (m/amb=
[:playback-enabled? (m/?< (m/watch playback-enabled?))]
[:note-event (m/?< output)])]
(case tag
:playback-enabled?
(let [playback-enabled? value
active @local-active]
(reset! local-playback-enabled? playback-enabled?)
(if (not playback-enabled?)
(do
(reset! notes-on #{})
(reset! local-active #{})
{:note-on #{}
:note-off active
:active #{}})
(m/amb)))
:note-event
(let [{:keys [active]
:as note-event} value]
(reset! local-active active)
(if @local-playback-enabled?
note-event
(m/amb)))))))
(defonce process (atom nil))
(defn start-process []
(when (not @process)
(reset! process
(do (enable-playback)
((m/reduce prn toggle-playback) {} {})))))
(defn stop-process []
(disable-playback)
(@process)
(reset! process nil))
(comment
(start-process)
(stop-process)
(set-sample-rate 1)
(play-notes 1 2 3 4 5)
(stop-notes 3)
(play-notes 1 2 3 4 5)
(stop-notes 1 2 3 4 5)
(enable-playback)
(disable-playback))
|