summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Zerrer <him@jakezerrer.com>2025-11-06 13:26:52 -0500
committerJake Zerrer <him@jakezerrer.com>2025-11-06 13:39:18 -0500
commita1e2543142761bde35d9d3a2cfb46da952a8d064 (patch)
tree7e2d47ef955e2723caa511f1f54be24c6ef07c36
parent576bfd688fc8a65de176d4f1fbf9bf71505953a9 (diff)
Create second instrument (keyboard) with separate input
-rw-r--r--dev/scratch.clj63
-rw-r--r--src/unheard/theory.clj13
2 files changed, 49 insertions, 27 deletions
diff --git a/dev/scratch.clj b/dev/scratch.clj
index ef7241a..cb9a249 100644
--- a/dev/scratch.clj
+++ b/dev/scratch.clj
@@ -9,36 +9,57 @@
(def midi-keyboard "CoreMIDI4J - Minilab3 MIDI")
+(defn song
+ [>c >tonic]
+ (poly
+ (poly
+ ;; This is a major cord,
+ ;; held 32 32nd notes.
+ ;; The tonic can vary.
+ (note >c 0 32 >tonic)
+ (note >c 0 32 (m/latest #(+ % 4) >tonic))
+ (note >c 0 32 (m/latest #(+ % 7) >tonic)))
+
+ ;; The rest of the "song" is a drum pattern.
+ (note >c 1 1 (m/ap kick))
+ (note >c 9 1 (m/ap kick))
+ (note >c 17 1 (m/ap kick))
+ (note >c 25 1 (m/ap kick))
+
+ (note >c 1 1 (m/ap hat))
+ (note >c 5 1 (m/ap hat))
+ (note >c 9 1 (m/ap hat))
+ (note >c 13 1 (m/ap hat))
+ (note >c 17 1 (m/ap hat))
+ (note >c 21 1 (m/ap hat))
+ (note >c 25 1 (m/ap hat))
+ (note >c 29 1 (m/ap hat))
+
+ (note >c 5 1 (m/ap snare))
+ (note >c 13 1 (m/ap snare))
+ (note >c 21 1 (m/ap snare))
+ (note >c 29 1 (m/ap snare))))
+
(def run
(midi/<bus midi-keyboard
(fn [v]
(m/ap
;; 2 is the number of message types, e.g. :key
(let [[>c c] (clock)
- p (poly
- (note >c 0 1 kick)
- (note >c 8 1 kick)
- (note >c 16 1 kick)
- (note >c 24 1 kick)
-
- (note >c 0 1 hat)
- (note >c 4 1 hat)
- (note >c 8 1 hat)
- (note >c 12 1 hat)
- (note >c 16 1 hat)
- (note >c 20 1 hat)
- (note >c 24 1 hat)
- (note >c 28 1 hat)
-
- (note >c 4 1 snare)
- (note >c 12 1 snare)
- (note >c 20 1 snare)
- (note >c 28 1 snare))]
+ tonic (atom 0)
+ >tonic (m/signal (m/watch tonic))
+ p (song >c >tonic)]
(m/amb=
(let [[t f] (m/?> 2 (midi/keyboard v))
[ch k v] (rest (m/?< f))]
- (if (and (= t :control) (= ch 0) (= k 74))
- (do (reset! c v) (m/amb))
+ (if (= t :control)
+ (m/amb
+ (if (and (= ch 0) (= k 74))
+ (do (reset! c v) (m/amb))
+ (m/amb))
+ (if (and (= ch 0) (= k 71))
+ (do (reset! tonic v) (m/amb))
+ (m/amb)))
(m/amb)))
[:n (m/?< (m/eduction (dedupe) p))]
[:c (m/?< (m/eduction (dedupe) >c))]))))))
diff --git a/src/unheard/theory.clj b/src/unheard/theory.clj
index 0fbc6d5..d63532f 100644
--- a/src/unheard/theory.clj
+++ b/src/unheard/theory.clj
@@ -2,16 +2,17 @@
(:require [missionary.core :as m]
[clojure.set :refer [union]]))
-(defn note [clock start duration value]
+(defn note [>clock start duration >value]
(m/cp
- (let [v (m/?< clock)]
- (if (<= start v (dec (+ start duration)))
- #{value}
+ (let [[c v] (m/?< (m/latest vector >clock >value))]
+ (if (<= start c (dec (+ start duration)))
+ #{v}
#{}))))
(defn poly [& notes]
- (m/cp
- (apply union (m/?< (apply m/latest vector notes)))))
+ (m/signal
+ (m/cp
+ (apply union (m/?< (apply m/latest vector notes))))))
;; TODO: Group could actually wrap note, rather than using explicitly
;; WIll introduce a lot of GC churn, though