summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dev/scratch.clj74
-rw-r--r--src/unheard/instrument/minilab3.clj86
-rw-r--r--src/unheard/instrument_utils.clj13
-rw-r--r--src/unheard/midi.clj107
4 files changed, 112 insertions, 168 deletions
diff --git a/dev/scratch.clj b/dev/scratch.clj
index 9775320..8bfa7af 100644
--- a/dev/scratch.clj
+++ b/dev/scratch.clj
@@ -1,9 +1,7 @@
(ns scratch
(:require [unheard.midi :as midi]
[unheard.midi.percussion :refer [kick snare hat]]
- [unheard.instrument.minilab3 :as minilab3]
- [unheard.midi-util :as mu]
- [unheard.clock :refer [clock]]
+ [unheard.instrument.minilab3 :refer [minilab3]]
[unheard.theory :refer [note poly]]
[missionary.core :as m]))
@@ -21,57 +19,53 @@
(note >c 0 32 (m/latest #(+ % 7) >tonic))))
(defn song
- [>c >tonic]
+ [{:keys [clock tonic]}]
(poly
- (triad >c >tonic)
- (triad >c (m/latest #(+ % 12) >tonic))
+ (triad clock tonic)
+ (triad clock (m/latest #(+ % 12) 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 clock 1 1 (m/ap kick))
+ (note clock 9 1 (m/ap kick))
+ (note clock 17 1 (m/ap kick))
+ (note clock 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 clock 1 1 (m/ap hat))
+ (note clock 5 1 (m/ap hat))
+ (note clock 9 1 (m/ap hat))
+ (note clock 13 1 (m/ap hat))
+ (note clock 17 1 (m/ap hat))
+ (note clock 21 1 (m/ap hat))
+ (note clock 25 1 (m/ap hat))
+ (note clock 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))))
+ (note clock 5 1 (m/ap snare))
+ (note clock 13 1 (m/ap snare))
+ (note clock 21 1 (m/ap snare))
+ (note clock 29 1 (m/ap snare))))
+
+;; TODO: Move into /dev
+;; Add logging
+(defn debug-flow [f]
+ ((m/reduce prn nil
+ (m/ap (m/?< f))) prn prn))
(def run
(midi/<bus midi-keyboard
(fn [v]
(m/ap
;; 2 is the number of message types, e.g. :key
- (let [[>c c] (clock)
- tonic (atom 0)
- >tonic (m/signal (m/watch tonic))
- p (song >c >tonic)]
+ (let [controls (midi/controller v minilab3)
+ song-config {:clock (get-in controls [:knob 1])
+ :tonic (get-in controls [:knob 2])}
+ p (song song-config)]
(m/amb=
- (let [[t f] (m/?> 2 (midi/keyboard v))
- [ch k v] (rest (m/?< f))
- chkv [ch k v]]
- (if (mu/control-message? t)
- (m/amb
- (if (minilab3/is-knob 1 chkv)
- (do (reset! c v) (m/amb))
- (m/amb))
- (if (minilab3/is-knob 2 chkv)
- (do (reset! tonic v) (m/amb))
- (m/amb)))
- (m/amb)))
- [:n (m/?< (m/eduction (dedupe) p))]
- [:c (m/?< (m/eduction (dedupe) >c))]))))))
+ [:n (m/?< p)]
+ [:c (m/?< (get-in controls [:knob 1]))]))))))
+#_
(def cancel
(run prn prn))
+#_
(cancel)
diff --git a/src/unheard/instrument/minilab3.clj b/src/unheard/instrument/minilab3.clj
index 11da62e..4973be2 100644
--- a/src/unheard/instrument/minilab3.clj
+++ b/src/unheard/instrument/minilab3.clj
@@ -1,31 +1,61 @@
(ns unheard.instrument.minilab3
- (:require [unheard.instrument-utils :as iu]))
+ (:require [missionary.core :as m])
+ (:import [javax.sound.midi ShortMessage]))
-(def minilab3
- {:knobs
- {1 [0 74]
- 2 [0 71]
- 3 [0 76]
- 4 [0 77]
- 5 [0 93]
- 6 [0 18]
- 7 [0 19]
- 8 [0 16]}
- :faders
- {1 [0 82]
- 2 [0 83]
- 3 [0 85]
- 4 [0 17]}
- :pads
- {1 [9 36]
- 2 [9 37]
- 3 [9 38]
- 4 [9 39]
- 5 [9 40]
- 6 [9 41]
- 7 [9 42]
- 8 [9 43]}})
+(def matching-control-change
+ (filter (fn [^ShortMessage m] (= (.getCommand m) ShortMessage/CONTROL_CHANGE))))
+
+(defn matching-channel [ch]
+ (filter (fn [^ShortMessage m] (= (.getChannel m) ch))))
+
+(defn matching-data-1 [d1]
+ (filter (fn [^ShortMessage m] (= (.getData1 m) d1))))
+
+(def get-data-2
+ (map (fn [^ShortMessage m] (.getData2 m))))
-(def is-knob (iu/is-knob minilab3))
-(def is-fader (iu/is-fader minilab3))
-(def is-pad (iu/is-pad minilab3))
+(defn matching-control
+ "Returns a function filtering flow of ShortMessage `f` down to control
+ change messages where channel is `ch` and data-1 is `k`, and then returning
+ a signal of values for data-2. Initial value of signal will be `init`.
+
+ Though a little esoteric sounding, this is actually quite useful.
+ The signal returned by this is useful for representing knobs, faders, and
+ pads on a midi controller.
+ "
+ [init ch k]
+ (fn [f]
+ (m/signal
+ (m/reductions {} init
+ (m/eduction
+ (comp
+ matching-control-change
+ (matching-channel ch)
+ (matching-data-1 k)
+ get-data-2)
+ f)))))
+
+(def minilab3
+ {:knob
+ {1 (matching-control 0 0 74)
+ 2 (matching-control 0 0 71)
+ 3 (matching-control 0 0 76)
+ 4 (matching-control 0 0 77)
+ 5 (matching-control 0 0 93)
+ 6 (matching-control 0 0 18)
+ 7 (matching-control 0 0 19)
+ 8 (matching-control 0 0 16)}
+ :fader
+ {1 (matching-control 0 0 82)
+ 2 (matching-control 0 0 83)
+ 3 (matching-control 0 0 85)
+ 4 (matching-control 0 0 17)}
+ :pad
+ {1 (matching-control 0 9 36)
+ 2 (matching-control 0 9 37)
+ 3 (matching-control 0 9 38)
+ 4 (matching-control 0 9 39)
+ 5 (matching-control 0 9 40)
+ 6 (matching-control 0 9 41)
+ 7 (matching-control 0 9 42)
+ 8 (matching-control 0 9 43)}})
diff --git a/src/unheard/instrument_utils.clj b/src/unheard/instrument_utils.clj
deleted file mode 100644
index 1ceae6b..0000000
--- a/src/unheard/instrument_utils.clj
+++ /dev/null
@@ -1,13 +0,0 @@
-(ns unheard.instrument-utils)
-
-(defn is-knob [inst]
- (fn [n [ch k _v]]
- (= [ch k] (get-in inst [:knobs n]))))
-
-(defn is-fader [inst]
- (fn [n [ch k _v]]
- (= [ch k] (get-in inst [:faders n]))))
-
-(defn is-pad [inst]
- (fn [n [ch k _v]]
- (= [ch k] (get-in inst [:pads n]))))
diff --git a/src/unheard/midi.clj b/src/unheard/midi.clj
index e3e65d2..174fec2 100644
--- a/src/unheard/midi.clj
+++ b/src/unheard/midi.clj
@@ -188,90 +188,23 @@
(m/? rv)
(recur)))))))))))
-;; CoreMidiSource is TX Device
-;; CoreMidiDestination is RX Device
-
-(defn |short-messages
- "Filter down to midi short messages"
- [>messages]
- (m/eduction (filter #(instance? ShortMessage %)) >messages))
-
-(defn |group-by-channel
- [>messages]
- (m/group-by #(.getChannel ^ShortMessage %) >messages))
-
-(defn |group-by-data-1
- [>messages]
- (m/group-by #(.getData1 ^ShortMessage %) >messages))
-
-(defn |matching-commands
- [>messages commands]
- (m/eduction (filter (fn [^ShortMessage v]
- (contains? commands (.getCommand v)))) >messages))
-
-(def note-commands
- #{ShortMessage/NOTE_OFF
- ShortMessage/NOTE_ON
- ShortMessage/POLY_PRESSURE})
-
-(def control-commands
- #{ShortMessage/CONTROL_CHANGE})
-
-(defn keyboard
- [f]
- (m/relieve
- (m/group-by
- first
- (m/ap
- (let [[ch ch-messages]
- (m/?> 128 (|group-by-channel (|short-messages (m/stream f))))
- ch-messages (m/stream ch-messages)]
- (m/amb=
- (let [[note note-messages]
- (m/?> 128 (-> ch-messages
- (|matching-commands note-commands)
- (|group-by-data-1)))]
- ;; TODO: Where to relieve in here?
- [:key ch note
- (m/?<
- (m/reductions
- (fn [_prev curr]
- (when (some? curr)
- (let [cmd (.getCommand ^ShortMessage curr)]
- (cond
- (= cmd ShortMessage/NOTE_ON)
- (.getData2 ^ShortMessage curr)
- (= cmd ShortMessage/POLY_PRESSURE)
- (.getData2 ^ShortMessage curr)
- (= cmd ShortMessage/NOTE_OFF)
- nil)))) nil note-messages))])
-
- (let [[control-number control-messages]
- (m/?> 128 (-> ch-messages
- (|matching-commands control-commands)
- (|group-by-data-1)))]
- [:control ch control-number (.getData2 ^ShortMessage (m/?< control-messages))])))))))
-
-#_(defn >ch-stream [>device ch]
- (m/cp (m/?< (second (get >device ch)))))
-
-#_{ch {:notes {note aftertouch}
- :pitch v
- :control {controller value}
- :program program}}
-
-;; Goal:
-;; Create function called `(receive-from-bus bus-name)`.
-;; `bus-name` is the name of a midi bus on this machine. If a bus with that name
-;; exists, the signal returned by `bus` will contain a map of {ch val}, where ch
-;; is a midi channel number and val is a signal of sets representing active
-;; notes on that channel.
-;;
-;; Create another function, `(send-to-bus bus-name sigs)`. `bus-name` is the
-;; name of a midi bus on this machine. If a bus with that name exists, it will
-;; start reading note values from `sigs`.
-;;
-;; Here, `sigs` is a map of {ch val}, where `ch` is a midi channel number and
-;; `val` is a signal of sets representing active notes on that channel.
-;;
-
+(defn controller
+ ;; NOTE: The structure of `config` currently assumes a fairly specific
+ ;; structure. It might be better for `config` to be a simple `kv` structure,
+ ;; where `k` can be e.g. a tuple [:knob 1], a single value [:mod-wheel],
+ ;; etc.
+
+ "Given a flow `f` and a controller config `config`, return a map of
+ controller flows taking from `f`.
+
+ A `config` is a map of maps of type `group` -> `id` -> `flow-constructor`.
+ Here, `group` is the name of a control type, e.g. :knob; `id` is a unique
+ identifier for that control, e.g. `1`, and `flow-constructor` is a function
+ accepting a flow of ShortMessages as its sole argument, and returning
+ a flow of values associated with the control."
+ [f config]
+
+ (into {}
+ (map (fn [[group instance]]
+ {group (into {} (map (fn [[id flow]] {id (flow f)}) instance))})
+ config)))