summaryrefslogtreecommitdiff
path: root/src/main.clj
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.clj')
-rw-r--r--src/main.clj114
1 files changed, 92 insertions, 22 deletions
diff --git a/src/main.clj b/src/main.clj
index fdb306f..eb0e2d2 100644
--- a/src/main.clj
+++ b/src/main.clj
@@ -1,45 +1,115 @@
(ns main
(:require [missionary.core :as m]
- [clojure.set :refer [difference]]))
+ [clojure.set :refer [difference union]]))
;; How many times per second are output continuous values sampled and turned
;; into events?
-(def sample-rate (atom 1))
+(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)))
-(defn play-note [v] (swap! notes-on conj v))
+(def playback-enabled? (atom true))
+
+(defn enable-playback []
+ (reset! playback-enabled? true))
+
+(defn disable-playback []
+ (reset! playback-enabled? false))
-(defn stop-note [v] (swap! notes-on disj v))
+(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)))))
-#_(play-note 1)
-#_(stop-note 1)
-
;; convert the continuous time >notes-on flow to a series of discrete midi note on and off events
(def output
- (m/eduction (map (fn [{:keys [note-on note-off]}] {:note-on note-on :note-off note-off}))
- (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})
- #{}
- (m/sample
- vector
- >notes-on
- clock))))
-
-(def cancel
- ((m/reduce prn output) {} {}))
-
-(cancel)
+ (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))