Permalink
Cannot retrieve contributors at this time
| (ns | |
| ^{:doc "The ugen functions create a data structure representing a synthesizer | |
| graph that can be executed on the synthesis server. This is the logic | |
| to \"compile\" these clojure data structures into a form that can be | |
| serialized by the byte-spec defined in synthdef.clj." | |
| :author "Jeff Rose"} | |
| overtone.sc.synth | |
| (:use [overtone.helpers lib old-contrib synth] | |
| [overtone.libs event counters] | |
| [overtone.music time] | |
| [overtone.sc.machinery.ugen fn-gen defaults common specs sc-ugen] | |
| [overtone.sc.machinery synthdef] | |
| [overtone.sc bindings ugens server node foundation-groups dyn-vars] | |
| [overtone.helpers seq] | |
| [clojure.pprint] | |
| [overtone.helpers.string :only [hash-shorten]]) | |
| (:require [overtone.config.log :as log] | |
| [clojure.set :as set] | |
| [overtone.sc.cgens.env :refer [hold]] | |
| [overtone.sc.protocols :as protocols])) | |
| (declare synth-player) | |
| (defonce ^{:private true} __RECORDS__ | |
| (do | |
| (defrecord-ifn Synth [name ugens sdef args params instance-fn] | |
| (partial synth-player sdef params)))) | |
| (defn- valid-control-proxy-rate? | |
| [rate] | |
| (some #{rate} CONTROL-PROXY-RATES)) | |
| ;; ### Synth | |
| ;; | |
| ;; A Synth is a collection of unit generators that run together. They can be | |
| ;; addressed and controlled by commands to the synthesis engine. They read | |
| ;; input and write output to global audio and control buses. Synths can have | |
| ;; their own local controls that are set via commands to the server. | |
| (defn- ugen-index [ugens ugen] | |
| (ffirst (filter (fn [[i v]] | |
| (= (:id v) (:id ugen))) | |
| (indexed ugens)))) | |
| ; Gets the group number (source) and param index within the group (index) | |
| ; from the params that are grouped by rate like this: | |
| ; | |
| ;[[{:name :freq :default 440.0 :rate 1} {:name :amp :default 0.4 :rate 1}] | |
| ; [{:name :adfs :default 20.23 :rate 2} {:name :bar :default 8.6 :rate 2}]] | |
| (defn- param-input-spec [grouped-params param-proxy] | |
| (let [param-name (:name param-proxy) | |
| ctl-filter (fn [[idx ctl]] (= param-name (:name ctl))) | |
| [[src group] foo] (take 1 (filter | |
| (fn [[src grp]] | |
| (not (empty? | |
| (filter ctl-filter (indexed grp))))) | |
| (indexed grouped-params))) | |
| [[idx param] bar] (take 1 (filter ctl-filter (indexed group)))] | |
| (if (or (nil? src) (nil? idx)) | |
| (throw (IllegalArgumentException. (str "Invalid parameter name: " param-name ". Please make sure you have named all parameters in the param map in order to use them inside the synth definition.")))) | |
| {:src src :index idx})) | |
| (defn- inputs-from-outputs [src src-ugen] | |
| (for [i (range (count (:outputs src-ugen)))] | |
| {:src src :index i})) | |
| ; NOTES: | |
| ; * *All* inputs must refer to either a constant or the output of another | |
| ; UGen that is higher up in the list. | |
| (defn- with-inputs | |
| "Returns ugen object with its input ports connected to constants and | |
| upstream ugens according to the arguments in the initial definition." | |
| [ugen ugens constants grouped-params] | |
| (when-not (contains? ugen :args) | |
| (if-not (sc-ugen? ugen) | |
| (throw (IllegalArgumentException. | |
| (str "Error: synth expected a ugen. Got: " ugen))) | |
| (throw (IllegalArgumentException. | |
| (format "The %s ugen does not have any arguments." | |
| (:name ugen)))))) | |
| (when-not (every? #(or (sc-ugen? %) (number? %) (string? %)) (:args ugen)) | |
| (throw (IllegalArgumentException. | |
| (format "The %s ugen has an invalid argument: %s" | |
| (:name ugen) | |
| (first (filter | |
| #(not (or (sc-ugen? %) (number? %))) | |
| (:args ugen))))))) | |
| (let [inputs (flatten | |
| (map (fn [arg] | |
| (cond | |
| ; constant | |
| (number? arg) | |
| {:src -1 :index (index-of constants (float arg))} | |
| ; control | |
| (control-proxy? arg) | |
| (param-input-spec grouped-params arg) | |
| ; output proxy | |
| (output-proxy? arg) | |
| (let [src (ugen-index ugens (:ugen arg))] | |
| {:src src :index (:index arg)}) | |
| ; child ugen | |
| (sc-ugen? arg) | |
| (let [src (ugen-index ugens arg) | |
| updated-ugen (nth ugens src)] | |
| (inputs-from-outputs src updated-ugen)))) | |
| (:args ugen))) | |
| ugen (assoc ugen :inputs inputs)] | |
| (when-not (every? (fn [{:keys [src index]}] | |
| (and (not (nil? src)) | |
| (not (nil? index)))) | |
| (:inputs ugen)) | |
| (throw (Exception. | |
| (format "Cannot connect ugen arguments for %s ugen with args: %s" (:name ugen) (str (seq (:args ugen))))))) | |
| ;;Add link back to MaxLocalBufs ugen (always at root of tree) if | |
| ;;ugen is a local-buf. | |
| (if (= "LocalBuf" (:name ugen)) | |
| (assoc ugen :inputs (concat (:inputs ugen) [{:src 0 :index 0}])) | |
| ugen))) | |
| ; TODO: Currently the output rate is hard coded to be the same as the | |
| ; computation rate of the ugen. We probably need to have some meta-data | |
| ; capabilities for supporting varying output rates... | |
| (defn- with-outputs | |
| "Returns a ugen with its output port connections setup according to | |
| the spec." | |
| [ugen] | |
| {:post [(every? (fn [val] (not (nil? val))) (:outputs %))]} | |
| (if (contains? ugen :outputs) | |
| ugen | |
| (let [spec (get-ugen (:name ugen)) | |
| num-outs (or (:n-outputs ugen) 1) | |
| outputs (take num-outs (repeat {:rate (:rate ugen)}))] | |
| (assoc ugen :outputs outputs)))) | |
| ; IMPORTANT NOTE: We need to add outputs before inputs, so that multi-channel | |
| ; outputs can be correctly connected. | |
| (defn- detail-ugens | |
| "Fill in all the input and output specs for each ugen." | |
| [ugens constants grouped-params] | |
| (let [constants (map float constants) | |
| outs (map with-outputs ugens) | |
| ins (map #(with-inputs %1 outs constants grouped-params) outs)] | |
| (doall ins))) | |
| (defn- make-control-ugens | |
| "Controls are grouped by rate, so that a single Control ugen | |
| represents each rate present in the params. The Control ugens are | |
| always the top nodes in the graph, so they can be prepended to the | |
| topologically sorted tree. | |
| Specifically handles control proxies at :tr, :ar, :kr and :ir" | |
| [grouped-params] | |
| (loop [done {} | |
| todo grouped-params | |
| offset 0] | |
| (if (empty? todo) | |
| (filter #(not (nil? %)) | |
| [(:ir done) (:tr done) (:ar done) (:kr done)]) | |
| (let [group (first todo) | |
| group-rate (:rate (first group)) | |
| group-size (count group) | |
| ctl-proxy (case group-rate | |
| :tr (trig-control-ugen group-size offset) | |
| :ar (audio-control-ugen group-size offset) | |
| :kr (control-ugen group-size offset) | |
| :ir (inst-control-ugen group-size offset))] | |
| (recur (assoc done group-rate ctl-proxy) (rest todo) (+ offset group-size)))))) | |
| (defn- group-params | |
| "Groups params by rate. Groups a list of parameters into a list of | |
| lists, one per rate." | |
| [params] | |
| (let [by-rate (reduce (fn [mem param] | |
| (let [rate (:rate param) | |
| rate-group (get mem rate [])] | |
| (assoc mem rate (conj rate-group param)))) | |
| {} params)] | |
| (filter #(not (nil? %1)) | |
| [(:ir by-rate) (:tr by-rate) (:ar by-rate) (:kr by-rate)]))) | |
| (def DEFAULT-RATE :kr) | |
| (defn- ensure-param-keys! | |
| "Throws an error if map m doesn't contain the correct | |
| keys: :name, :default and :rate" | |
| [m] | |
| (when-not (and | |
| (contains? m :name) | |
| (contains? m :default) | |
| (contains? m :rate)) | |
| (throw (IllegalArgumentException. (str "Invalid synth param map. Expected to find the keys :name, :default, :rate, got: " m))))) | |
| (defn- ensure-paired-params! | |
| "Throws an error if list l does not contain an even number of | |
| elements" | |
| [l] | |
| (when-not (even? (count l)) | |
| (throw (IllegalArgumentException. (str "A synth requires either an even number of arguments in the form [control default]* i.e. [freq 440 amp 0.5] or a list of maps. You passed " (count l) " args: " l))))) | |
| (defn- ensure-vec! | |
| "Throws an error if list l is not a vector" | |
| [l] | |
| (when-not (vector? l) | |
| (throw (IllegalArgumentException. (str "Your synth argument list is not a vector. Instead I found " (type l) ": " l))))) | |
| (defn- ensure-valid-control-proxy-vec! | |
| [val] | |
| (when-not (= 2 (count val)) | |
| (throw (IllegalArgumentException. (str "Control Proxy vector must have only 2 elements i.e. [0 :tr]")))) | |
| (when-not (number? (first val)) | |
| (throw (IllegalArgumentException. (str "Control Proxy vector must have a number as the first element i.e. [0 :tr]")))) | |
| (when-not (valid-control-proxy-rate? (second val)) | |
| (throw (IllegalArgumentException. (str "Control Proxy rate " (second val) " not valid. Expecting one of " CONTROL-PROXY-RATES)))) | |
| val) | |
| (defn- mapify-params | |
| "Converts a list of param name val pairs to a param map. If the val of | |
| a param is a vector, it assumes it's a pair of [val rate] and sets the | |
| rate of the param accordingly. If the val is a plain number, it sets | |
| the rate to DEFAULT-RATE. All names are converted to strings" | |
| [params] | |
| (for [[p-name p-val] (partition 2 params)] | |
| (let [param-map | |
| (cond | |
| (vector? p-val) (do | |
| (ensure-valid-control-proxy-vec! p-val) | |
| {:name (str p-name) | |
| :default (first p-val) | |
| :rate (second p-val)}) | |
| (associative? p-val) (merge | |
| {:name (str p-name) | |
| :rate DEFAULT-RATE} p-val) | |
| :else {:name (str p-name) | |
| :default `(float (to-id ~p-val)) | |
| :rate DEFAULT-RATE})] | |
| (ensure-param-keys! param-map) | |
| param-map))) | |
| (defn- stringify-names | |
| "Takes a map and converts the val of key :name to a string" | |
| [m] | |
| (into {} (for [[k v] m] (if (= :name k) [k (str v)] [k v])))) | |
| ;; TODO: Figure out a better way to specify rates for synth parameters | |
| ;; perhaps using name post-fixes such as [freq:kr 440] | |
| (defn- parse-params | |
| "Used by defsynth to parse the param list. Accepts either a vector of | |
| name default pairs, name [default rate] pairs or a vector of maps: | |
| (defsynth foo [freq 440] ...) | |
| (defsynth foo [freq [440 :ar]] ...) | |
| (defsynth foo [freq {:default 440 :rate :ar}] ...) | |
| Returns a vec of param maps" | |
| [params] | |
| (ensure-vec! params) | |
| (ensure-paired-params! params) | |
| (vec (mapify-params params))) | |
| (defn- make-params | |
| "Create the param value vector and parameter name vector." | |
| [grouped-params] | |
| (let [param-list (flatten grouped-params) | |
| pvals (map #(:default %1) param-list) | |
| pnames (map (fn [[idx param]] | |
| {:name (to-str (:name param)) | |
| :index idx}) | |
| (indexed param-list))] | |
| [pvals pnames])) | |
| (defn- ugen-form? [form] | |
| (and (seq? form) | |
| (= 'ugen (first form)))) | |
| (defn- fastest-rate [rates] | |
| (REVERSE-RATES (first (reverse (sort (map RATES rates)))))) | |
| (defn- special-op-args? [args] | |
| (some #(or (sc-ugen? %1) (keyword? %1)) args)) | |
| (defn- find-rate [args] | |
| (fastest-rate (map #(cond | |
| (sc-ugen? %1) (REVERSE-RATES (:rate %1)) | |
| (keyword? %1) :kr) | |
| args))) | |
| ;; For greatest efficiency: | |
| ;; | |
| ;; * Unit generators should be listed in an order that permits efficient reuse | |
| ;; of connection buffers, so use a depth first topological sort of the graph. | |
| ; NOTES: | |
| ; * The ugen tree is turned into a ugen list that is sorted by the order in | |
| ; which nodes should be processed. (Depth first, starting at outermost leaf | |
| ; of the first branch. | |
| ; | |
| ; * params are sorted by rate, and then a Control ugen per rate is created | |
| ; and prepended to the ugen list | |
| ; | |
| ; * finally, ugen inputs are specified using their index | |
| ; in the sorted ugen list. | |
| ; | |
| ; * No feedback loops are allowed. Feedback must be accomplished via delay lines | |
| ; or through buses. | |
| ; | |
| (defn synthdef | |
| "Transforms a synth definition (ugen-graph) into a form that's ready | |
| to save to disk or send to the server. | |
| (synthdef \"pad-z\" [ | |
| " | |
| [sname params ugens constants] | |
| (let [grouped-params (group-params params) | |
| [params pnames] (make-params grouped-params) | |
| with-ctl-ugens (concat (make-control-ugens grouped-params) ugens) | |
| detailed (detail-ugens with-ctl-ugens constants grouped-params) | |
| full-name (if (add-current-namespace-to-synth-name?) | |
| (hash-shorten 31 (ns-name *ns*) (str "/" sname)) | |
| sname)] | |
| (with-meta {:name full-name | |
| :constants constants | |
| :params params | |
| :pnames pnames | |
| :ugens detailed} | |
| {:type :overtone.sc.machinery.synthdef/synthdef}))) | |
| (defn- control-proxies | |
| "Returns a list of param name symbols and control proxies" | |
| [params] | |
| (mapcat (fn [param] [(symbol (:name param)) | |
| `(control-proxy ~(:name param) ~(:default param) ~(:rate param))]) | |
| params)) | |
| (defn- gen-synth-name | |
| "Auto generate an anonymous synth name. Intended for use in synths | |
| that have not been defined with an explicit name. Has the form | |
| \"anon-id\" where id is a unique integer across all anonymous | |
| synths." | |
| [] | |
| (str "anon-" (next-id ::anonymous-synth))) | |
| (defn normalize-synth-args | |
| "Pull out and normalize the synth name, parameters, control proxies | |
| and the ugen form from the supplied arglist resorting to defaults if | |
| necessary." | |
| [args] | |
| (let [[sname args] (if (or (string? (first args)) | |
| (symbol? (first args))) | |
| [(str (first args)) (rest args)] | |
| [(gen-synth-name) args]) | |
| [params ugen-form] (if (vector? (first args)) | |
| [(first args) (rest args)] | |
| [[] args]) | |
| param-proxies (control-proxies params)] | |
| [sname params param-proxies ugen-form])) | |
| (defn gather-ugens-and-constants | |
| "Traverses a ugen tree and returns a vector of two sets [#{ugens} | |
| #{constants}]." | |
| [root] | |
| (if (seq? root) | |
| (reduce | |
| (fn [[ugens constants] ugen] | |
| (let [[us cs] (gather-ugens-and-constants ugen)] | |
| [(set/union ugens us) | |
| (set/union constants cs)])) | |
| [#{} #{}] | |
| root) | |
| (let [args (:args root) | |
| cur-ugens (filter sc-ugen? args) | |
| cur-ugens (filter (comp not control-proxy?) cur-ugens) | |
| cur-ugens (map #(if (output-proxy? %) | |
| (:ugen %) | |
| %) | |
| cur-ugens) | |
| cur-consts (filter number? args) | |
| [child-ugens child-consts] (gather-ugens-and-constants cur-ugens) | |
| ugens (conj (set child-ugens) root) | |
| constants (set/union (set cur-consts) child-consts)] | |
| [ugens (vec constants)]))) | |
| (defn- ugen-children | |
| "Returns the children (arguments) of this ugen that are themselves | |
| upstream ugens." | |
| [ug] | |
| (mapcat | |
| #(cond | |
| (seq? %) % | |
| (output-proxy? %) [(:ugen %)] | |
| :default [%]) | |
| (filter | |
| (fn [arg] | |
| (and (not (control-proxy? arg)) | |
| (or (sc-ugen? arg) | |
| (and (seq? arg) | |
| (every? sc-ugen? arg))))) | |
| (:args ug)))) | |
| (defn topological-sort-ugens | |
| "Sort into a vector where each node in the directed graph of ugens | |
| will always be preceded by its upstream dependencies. Depth first, | |
| from: | |
| http://en.wikipedia.org/wiki/Topological_sorting, | |
| following the advice here: | |
| http://supercollider.svn.sourceforge.net/viewvc/supercollider/trunk/common/build/Help/ServerArchitecture/Synth-Definition-File-Format.html | |
| 'For greatest efficiency: | |
| Unit generators should be listed in an order that permits efficient | |
| reuse of connection buffers, which means that a depth first | |
| topological sort of the graph is preferable to breadth first.'" | |
| [ugens] | |
| (let [visit (fn visit [[ret visited path :as acc] ug] | |
| (cond | |
| (visited ug) acc | |
| (path ug) (throw (Exception. "ugen graph contains cycle")) | |
| :else | |
| (let [[ret visited path :as acc] | |
| (reduce visit [ret visited (conj path ug)] (ugen-children ug))] | |
| [(conj ret ug) (conj visited ug) path])))] | |
| (first (reduce visit [[] #{} #{}] ugens)))) | |
| (comment | |
| ; Some test synths, while shaking out the bugs... | |
| (defsynth foo [] (out 0 (rlpf (saw [220 663]) (x-line:kr 20000 2 1 FREE)))) | |
| (defsynth bar [freq 220] (out 0 (rlpf (saw [freq (* 3.013 freq)]) (x-line:kr 20000 2 1 FREE)))) | |
| (definst faz [] (rlpf (saw [220 663]) (x-line:kr 20000 2 1 FREE))) | |
| (definst baz [freq 220] (rlpf (saw [freq (* 3.013 freq)]) (x-line:kr 20000 2 1 FREE))) | |
| (run 1 (out 184 (saw (x-line:kr 10000 10 1 FREE))))) | |
| (defn count-ugens | |
| [ug-tree ug-name] | |
| (let [ugens (flatten ug-tree) | |
| local-bufs (filter #(= ug-name (:name %)) ugens) | |
| ids (set (map :id local-bufs))] | |
| (count ids))) | |
| (defmacro pre-synth | |
| "Resolve a synth def to a list of its name, params, ugens (nested if | |
| necessary) and constants. Sets the lexical bindings of the param | |
| names to control proxies within the synth definition" | |
| [& args] | |
| (let [[sname params param-proxies ugen-form] (normalize-synth-args args)] | |
| `(let [~@param-proxies] | |
| (binding [*ugens* [] | |
| *constants* #{}] | |
| (let [[ugens# consts#] (gather-ugens-and-constants | |
| (with-overloaded-ugens ~@ugen-form)) | |
| ugens# (topological-sort-ugens ugens#) | |
| main-tree# (set ugens#) | |
| side-tree# (filter #(not (main-tree# %)) *ugens*) | |
| ugens# (concat ugens# side-tree#) | |
| n-local-bufs# (count-ugens ugens# "LocalBuf") | |
| ugens# (if (> n-local-bufs# 0) | |
| (cons (max-local-bufs n-local-bufs#) ugens#) | |
| ugens#) | |
| consts# (if (> n-local-bufs# 0) | |
| (cons n-local-bufs# consts#) | |
| consts#) | |
| consts# (into [] (set (concat consts# *constants*)))] | |
| [~sname ~params ugens# consts#]))))) | |
| (defn synth-player | |
| [sdef params this & args] | |
| "Returns a player function for a named synth. Used by (synth ...) | |
| internally, but can be used to generate a player for a pre-compiled | |
| synth. The function generated will accept a target and position | |
| vector of two values that must come first (see the node function | |
| docs). | |
| ;; call foo player with default args: | |
| (foo) | |
| ;; call foo player specifyign node should be at the tail of group 0 | |
| (foo [:tail 0]) | |
| ;; call foo player with positional arguments | |
| (foo 440 0.3) | |
| ;; target node to be at the tail of group 0 with positional args | |
| (foo [:tail 0] 440 0.3) | |
| ;; or target node to be at the head of group 2 | |
| (foo [:head 2] 440 0.3) | |
| ;; you may also use keyword args | |
| (foo :freq 440 :amp 0.3) | |
| ;; which allows you to re-order the args | |
| (foo :amp 0.3 :freq 440 ) | |
| ;; you can also combine a target vector with keyword args | |
| (foo [:head 2] :amp 0.3 :freq 440) | |
| ;; finally, you can combine target vector, keywords args and | |
| ;; positional args. Positional args must go first. | |
| (foo [:head 2] 440 :amp 0.3) | |
| " | |
| (let [arg-names (map keyword (map :name params)) | |
| args (or args []) | |
| [target pos args] (extract-target-pos-args args (foundation-default-group) :tail) | |
| args (idify args) | |
| args (map (fn [arg] (if-let [id (:id arg)] | |
| id | |
| arg)) | |
| args) | |
| defaults (into {} (map (fn [{:keys [name value]}] | |
| [(keyword name) @value]) | |
| params)) | |
| arg-map (arg-mapper args arg-names defaults) | |
| synth-node (node (:name sdef) arg-map {:position pos :target target} sdef) | |
| synth-node (if (:instance-fn this) | |
| ((:instance-fn this) synth-node) | |
| synth-node)] | |
| (when (:instance-fn this) | |
| (swap! active-synth-nodes* assoc (:id synth-node) synth-node)) | |
| synth-node)) | |
| (defn update-tap-data | |
| [msg] | |
| (let [[node-id label-id val] (:args msg) | |
| node (get @active-synth-nodes* node-id) | |
| label (get (:tap-labels node) label-id) | |
| tap-atom (get (:taps node) label)] | |
| (reset! tap-atom val))) | |
| (on-event "/overtone/tap" #'update-tap-data ::handle-incoming-tap-data) | |
| (defmacro synth | |
| "Define a SuperCollider synthesizer using the library of ugen | |
| functions provided by overtone.sc.ugen. This will return callable | |
| record which can be used to trigger the synthesizer. | |
| " | |
| [& args] | |
| `(let [[sname# params# ugens# constants#] (pre-synth ~@args) | |
| sdef# (synthdef sname# params# ugens# constants#) | |
| arg-names# (map :name params#) | |
| params-with-vals# (map #(assoc % :value (atom (:default %))) params#) | |
| instance-fn# (apply comp (map :instance-fn (filter :instance-fn (map meta ugens#)))) | |
| smap# (with-meta | |
| (map->Synth | |
| {:name sname# | |
| :sdef sdef# | |
| :args arg-names# | |
| :params params-with-vals# | |
| :instance-fn instance-fn#}) | |
| {:overtone.live/to-string #(str (name (:type %)) ":" (:name %))})] ;; TODO what on earth is this? | |
| (load-synthdef sdef#) | |
| (event :new-synth :synth smap#) | |
| smap#)) | |
| (defn synth-form | |
| "Internal function used to prepare synth meta-data." | |
| [s-name s-form] | |
| (let [[s-name s-form] (name-with-attributes s-name s-form) | |
| _ (when (not (symbol? s-name)) | |
| (throw (IllegalArgumentException. (str "You need to specify a name for your synth using a symbol")))) | |
| params (first s-form) | |
| params (parse-params params) | |
| ugen-form (concat '(do) (next s-form)) | |
| param-names (list (vec (map #(symbol (:name %)) params))) | |
| md (assoc (meta s-name) | |
| :name s-name | |
| :type ::synth | |
| :arglists (list 'quote param-names))] | |
| [(with-meta s-name md) params ugen-form])) | |
| (defmacro defsynth | |
| "Define a synthesizer and return a player function. The synth | |
| definition will be loaded immediately, and a :new-synth event will be | |
| emitted. Expects a name, an optional doc-string, a vector of synth | |
| params, and a ugen-form as its arguments. | |
| (defsynth foo [freq 440] | |
| (out 0 (sin-osc freq))) | |
| is equivalent to: | |
| (def foo | |
| (synth [freq 440] (out 0 (sin-osc freq)))) | |
| Params can also be given rates. By default, they are :kr, however | |
| another rate can be specified by using either a pair of [default rate] | |
| or a map with keys :default and rate: | |
| (defsynth foo [freq [440 :kr] gate [0 :tr]] ...) | |
| (defsynth foo [freq {:default 440 :rate :kr}] ...) | |
| A doc string can also be included: | |
| (defsynth bar | |
| \"The phatest space pad ever!\" | |
| [] (...)) | |
| The function generated will accept a target vector argument that | |
| must come first, containing position and target as elements (see the | |
| node function docs). | |
| ;; call foo player with default args: | |
| (foo) | |
| ;; call foo player specifying node should be at the tail of group 0 | |
| (foo [:tail 0]) | |
| ;; call foo player with positional arguments | |
| (foo 440 0.3) | |
| ;; target node to be at the tail of group 0 with positional args | |
| (foo [:tail 0] 440 0.3) | |
| ;; or target node to be at the head of group 2 | |
| (foo [:head 2] 440 0.3) | |
| ;; you may also use keyword args | |
| (foo :freq 440 :amp 0.3) | |
| ;; which allows you to re-order the args | |
| (foo :amp 0.3 :freq 440 ) | |
| ;; you can also combine a target vector with keyword args | |
| (foo [:head 2] :amp 0.3 :freq 440) | |
| ;; finally, you can combine target vector, keywords args and | |
| ;; positional args. Positional args must go first. | |
| (foo [:head 2] 440 :amp 0.3)" | |
| [s-name & s-form] | |
| {:arglists '([name doc-string? params ugen-form])} | |
| (let [[s-name params ugen-form] (synth-form s-name s-form)] | |
| `(def ~s-name (synth ~s-name ~params ~ugen-form)))) | |
| (defn synth? | |
| "Returns true if s is a synth, false otherwise." | |
| [s] | |
| (= overtone.sc.synth.Synth (type s))) | |
| (def ^{:dynamic true} *demo-time* 2000) | |
| (defmacro run | |
| "Run an anonymous synth definition for a fixed period of time. | |
| Useful for experimentation. Does NOT add an out ugen - see #'demo for | |
| that. You can specify a timeout in seconds as the first argument | |
| otherwise it defaults to *demo-time* ms. | |
| (run (send-reply (impulse 1) \"/foo\" [1] 43)) ;=> send OSC messages" | |
| [& body] | |
| (let [[demo-time body] (if (number? (first body)) | |
| [(* 1000 (first body)) (second body)] | |
| [*demo-time* (first body)])] | |
| `(let [s# (synth "audition-synth" ~body) | |
| note# (s#)] | |
| (after-delay ~demo-time #(node-free note#)) | |
| note#))) | |
| (defmacro demo | |
| "Listen to an anonymous synth definition for a fixed period of time. | |
| Useful for experimentation. If the root node is not an out ugen, then | |
| it will add one automatically. You can specify a timeout in seconds | |
| as the first argument otherwise it defaults to *demo-time* ms. See | |
| #'run for a version of demo that does not add an out ugen. | |
| (demo (sin-osc 440)) ;=> plays a sine wave for *demo-time* ms | |
| (demo 0.5 (sin-osc 440)) ;=> plays a sine wave for half a second" | |
| [& body] | |
| (let [[demo-time body] (if (number? (first body)) | |
| [(first body) (second body)] | |
| [(* 0.001 *demo-time*) (first body)]) | |
| [out-bus body] (if (= 'out (first body)) | |
| [(second body) (nth body 2)] | |
| [0 body]) | |
| body (list `out out-bus (list `hold body demo-time :done `FREE))] | |
| `((synth "audition-synth" ~body)))) | |
| (defn active-synths | |
| "Return a seq of the actively running synth nodes. If a synth or inst | |
| are passed as the filter it will only return nodes of that type. | |
| (active-synths) ;=> [{:type synth :name \"mixer\" :id 12} {:type | |
| synth :name \"my-synth\" :id 24}] | |
| (active-synths my-synth) ;=>[{:type synth :name \"my-synth\" :id 24}] | |
| " | |
| [& [synth-filter]] | |
| (let [active-nodes (filter #(= overtone.sc.node.SynthNode (type %)) | |
| (vals @active-synth-nodes*))] | |
| (if synth-filter | |
| (filter #(= (:name synth-filter) (:name %)) active-nodes) | |
| active-nodes))) | |
| (defmethod print-method ::synth [syn w] | |
| (let [info (meta syn)] | |
| (.write w (format "#<synth: %s>" (:name info))))) | |
| ; TODO: pull out the default param atom stuff into a separate mechanism | |
| (defn modify-synth-params | |
| "Update synth parameter value atoms storing the current default | |
| settings." | |
| [s & params-vals] | |
| (let [params (:params s)] | |
| (for [[param value] (partition 2 params-vals)] | |
| (let [val-atom (:value (first (filter #(= (:name %) (name param)) params)))] | |
| (if val-atom | |
| (reset! val-atom value) | |
| (throw (IllegalArgumentException. (str "Invalid control parameter: " param)))))))) | |
| (defn reset-synth-defaults | |
| "Reset a synth to its default settings defined at definition time." | |
| [synth] | |
| (doseq [param (:params synth)] | |
| (reset! (:value param) (:default param)))) | |
| (defmacro with-no-ugen-checks [& body] | |
| `(binding [overtone.sc.machinery.ugen.specs/*checking* false] | |
| ~@body)) | |
| (defmacro with-ugen-debugging [& body] | |
| `(binding [overtone.sc.machinery.ugen.specs/*debugging* true] | |
| ~@body)) | |
| (defn synth-args | |
| "Returns a seq of the synth's args as keywords" | |
| [synth] | |
| (map keyword (:args synth))) | |
| (defn synth-arg-index | |
| "Returns an integer index of synth's argument with arg-name. | |
| For example: | |
| (defsynth foo [freq 440 amp 0.5] (out 0 (* amp (sin-osc freq)))) | |
| (synth-arg-index foo :amp) #=> 1 | |
| (synth-arg-index foo \"freq\") #=> 0 | |
| (synth-arg-index foo :baz) #=> nil" | |
| [synth arg-name] | |
| (let [arg-name (name arg-name)] | |
| (index-of (:args synth) arg-name))) | |
| (extend Synth | |
| protocols/IKillable | |
| {:kill* (fn [s] | |
| (kill (node-tree-matching-synth-ids (:name (:sdef s)))))}) | |
| (extend java.util.regex.Pattern | |
| protocols/IKillable | |
| {:kill* (fn [s] | |
| (kill (node-tree-matching-synth-ids (:name (:sdef s)))))}) |