(ns unheard.theory (:require [missionary.core :as m] [unheard.time-object :refer [time-object]] [clojure.set :refer [union]])) (defn note [>clock start duration >value] (time-object start duration [(gensym) (m/cp (let [[c v] (m/?< (m/latest vector >clock >value))] (if (<= start c (dec (+ start duration))) #{v} #{})))])) ;; Reducing function that returns diffs :add v :remove v ;; Reducing function that unfolds to values (comment (require '[unheard.time-object :refer [lift phrase timeline point-query]]) (def c (atom 0)) (def >c (m/signal (m/watch c))) (def v (atom 0)) (def >v (m/watch v)) (def n (note >c 4 8 >v)) (def song (phrase (lift n))) (def t (timeline (song 0))) (def r (point-query t >c)) (def r (m/ap (try (m/?< ;; TODO: Simplify (apply m/latest vector (point-query t >c) (vals (m/?< (m/eduction (map #(into {} %)) (point-query t >c)))))) (catch missionary.Cancelled _ (m/amb))))) (def cancel ((m/reduce prn nil r) 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