diff options
| author | Jake Zerrer <him@jakezerrer.com> | 2025-11-06 15:25:16 -0500 |
|---|---|---|
| committer | Jake Zerrer <him@jakezerrer.com> | 2025-11-07 13:14:16 -0500 |
| commit | 275450c8c07a4f73e1f6e1da83578cc2ec2248b8 (patch) | |
| tree | 5076790f343ab537d28ff748dea82d2d63a1ef5d /src | |
| parent | c62ed160a04c1ee5d08297d13a26630a590c5d6a (diff) | |
Vastly simplify controller setup
Diffstat (limited to 'src')
| -rw-r--r-- | src/unheard/instrument/minilab3.clj | 86 | ||||
| -rw-r--r-- | src/unheard/instrument_utils.clj | 13 | ||||
| -rw-r--r-- | src/unheard/midi.clj | 107 |
3 files changed, 78 insertions, 128 deletions
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))) |
