(ns unheard.theory (:require [missionary.core :as m] [unheard.time-object :refer [time-object lift phrase timeline point-query]] [clojure.set :refer [union]] [unheard.util.missionary :refer [reconcile-merge]])) (defn note [>clock start duration >value] (lift (time-object start duration (m/stream (m/ap (let [[c v] (m/?> (m/relieve (m/latest vector >clock >value)))] v)))))) ;; BUG: 2d7f861 (defn read [>clock timeline] (m/relieve (m/reductions {} nil (m/eduction (map vals) (m/reductions (fn [acc {:keys [id state value]}] (if (= :up state) (assoc acc id value) (dissoc acc id))) {} (reconcile-merge (point-query timeline >clock))))))) (comment (def c (atom 0)) (def >c (m/signal (m/watch c))) (def v (atom 0)) (def >v (m/signal (m/watch v))) (def song (phrase (note >c 4 8 >v) (note >c 6 6 >v))) (def t (timeline song)) (def cancel ((m/reduce prn nil (read >c t)) prn prn)) (cancel) (swap! c dec) (swap! c inc) (swap! v inc) (swap! v dec)) (defn poly [& 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 (defn group [clock start end content] (m/cp (let [content (m/signal content)] (if (m/?< (m/latest #(<= start % end) clock)) (m/?< content) (m/amb #{}))))) ;; TODO: ;; - Note literals turn into numbers ;; - Represent keyboard as byte array of shorts ;; - play a note increments, stop a note decrements ;; - Multiple instruments ;; - Mapping inputs to vars ;; - Inputs get declared at the top of a track ;; - Devices get mapped to declared inputs ;; - Notion of scenes that change mapping of inputs to vars ;; - Loops