diff --git a/TODO.md b/TODO.md index 59923b1f4..7eef6c4b2 100644 --- a/TODO.md +++ b/TODO.md @@ -68,3 +68,15 @@ to ugens as arguments and their :id property will be used (currently there is just :append-seq) ## Ugens + +## SC Tweets: + +{loop{play{GVerb.ar(LeakDC.ar(Crackle.ar([a=Phasor.ar(1,Line.kr(0.0020.rand,0,9),0,1),a],0.5,0)))*0.05*EnvGen.kr(Env.sine(9))};5.wait}}.fork + +{loop{play{|q|c=EnvGen.kr(Env.sine(6),b=(6..1));Pan2.ar(PMOsc.ar(a=40*b*b.rand,a/2-1,1+q*c*90,c,0.01).sum.tan,2.0.rand-1)*c*5};1.wait}}.fork + +play{b=LocalBuf(2**20,2).clear;n=LFNoise2.ar(Rand(500)!2);w=Warp1.ar(2,b,n+1/2,[a=0.5,1,2,4]).sum/50;RecordBuf.ar(LeakDC.ar(w+n),b,0,a,a);w} + +{loop{play{GVerb.ar(Pan2.ar(HenonC.ar(11025/(16.rand+1),0.4.rand+1,0.3.rand),2.rand-1).tanh)*0.01*EnvGen.kr(Env.sine(4))};0.8.wait}}.fork + +play{n=12;Splay.ar(Ringz.ar(Decay2.ar(Impulse.ar({2.0.rand.round(0.25)}!n),0.01,0.1,0.1),{|i|50+(i*50)+4.0.rand2}!n,{8.0.rand}!n),0.1)} diff --git a/examples/blues.clj b/examples/blues.clj index a5d52429f..7daf778dd 100644 --- a/examples/blues.clj +++ b/examples/blues.clj @@ -38,7 +38,7 @@ (play-blues instr pitch)) (at (+ time (* 0.5 dur)) - (c-hat 0.1)) + (closed-hat 0.1)) (apply-at n-time #'play-seq [(mod (inc count) 4) instr (next notes) (next durs) n-time odds])))) diff --git a/examples/sample_looper.clj b/examples/sample_looper.clj index a8c0e41c2..e06492cd1 100644 --- a/examples/sample_looper.clj +++ b/examples/sample_looper.clj @@ -75,7 +75,7 @@ vals (range from to step)] (dorun (map #(do (ctl loop-synth :rate (reset! rate* %)) (Thread/sleep 35)) vals)))) -;;(tempo-slide sdlkfjsf lsdjfsdlkjf 0) +;;(tempo-slide 2) ;;(volume 1.5) ;;(reset-samples!) ;;(trigger 1 1) diff --git a/project.clj b/project.clj index 0b386d4ce..9b00a8b34 100644 --- a/project.clj +++ b/project.clj @@ -11,6 +11,5 @@ [overtone/midi-clj "0.2.1"] [clj-glob "1.0.0"] [org.clojure/core.match "0.2.0-alpha6"] - [seesaw "1.3.1-SNAPSHOT"] - [lacij "0.7.0-SNAPSHOT"]] + [seesaw "1.4.0"]] :jvm-opts ["-Xms256m" "-Xmx1g" "-XX:+UseConcMarkSweepGC"]) diff --git a/src/overtone/gui/sequencer.clj b/src/overtone/gui/sequencer.clj index 068add5f4..8c8a433e4 100644 --- a/src/overtone/gui/sequencer.clj +++ b/src/overtone/gui/sequencer.clj @@ -4,12 +4,11 @@ [overtone.gui.control :only [synth-controller]] [seesaw core] [seesaw.color :only [color]] - [seesaw.graphics :only [style update-style draw rounded-rect line]] + [seesaw.graphics :only [style update-style draw rect rounded-rect line]] [seesaw.mig :only [mig-panel]]) (:require [seesaw.bind :as bind])) -(defn- make-initial-state - [metro steps instruments init-vals] +(defn- make-initial-state [metro steps instruments init-vals] {:playing? false :metronome metro :steps steps @@ -19,93 +18,43 @@ (let [i-vals (or (get init-vals (:name i)) (vec (repeat steps false)))] {:inst i - :param (-> i :params first :name) - :mute false - :solo false :value i-vals}))) }) -(def ^{:private true} NEW_ENTRY {:on true}) - -(defn- toggle-playing - [state] +(defn- toggle-playing [state] (update-in state [:playing?] not)) -(defn- get-entry - [state row col] +(defn- get-entry [state row col] (get-in state [:rows row :value col])) -(defn- set-entry - [state row col v] +(defn- set-entry [state row col v] (if (< row (count (:rows state))) (assoc-in state [:rows row :value col] v) state)) -(defn- update-entry - [state row col v] - (if (< row (count (:rows state))) - (update-in state [:rows row :value col] merge NEW_ENTRY v) - state)) +(defn- toggle-entry [state row col] + (update-in state [:rows row :value col] not)) -(defn- mute-entry - [state row col] - (update-in state [:rows row :value col] - (fn [v] - (if (associative? v) - (assoc-in v [:on] false) - v)))) - -(defn- toggle-entry - [state row col] - (update-in state [:rows row :value col] - (fn [v] - (if (associative? v) - (update-in v [:on] not) - NEW_ENTRY)))) - -(defn- delete-entry - [state row col] - (set-entry state row col false)) - -(defn- toggle-row-mute - [state row] - (update-in state [:rows row :mute] not)) - -(defn- toggle-row-solo - [state row] - (update-in state [:rows row :solo] not)) - -(defn- get-row-solo - [state row] - (get-in state [:rows row :solo])) - -(defn- get-row-param - [state row] - (get-in state [:rows row :param])) - -(defn- set-row-param - [state row val] - (assoc-in state [:rows row :param] val)) - -(defn- get-param-info - [state row] - (let [p-name (get-row-param state row)] - (first (filter #(= (:name %) p-name) - (get-in state [:rows row :inst :params]))))) +(defn- add-column-to-row [row] + (update-in row [:value] #(conj % false))) -(defn- clear-row [state row] - (assoc-in state [:rows row :value] (vec (repeat (:steps state) false)))) +(defn- add-column [state] + (-> state + (update-in [:rows] #(vec (map add-column-to-row %))) + (update-in [:steps] inc))) -(defn- play-step - [row index] - (let [{:keys [inst value]} row - step-val (nth value index)] +(defn- remove-column-from-row [row] + (update-in row [:value] pop)) - (when (:on step-val) - (apply inst (-> step-val - (dissoc :on) - seq - flatten))))) +(defn- remove-column [state] + (if (> (:steps state) 2) + (-> state + (update-in [:rows] #(vec (map remove-column-from-row %))) + (update-in [:steps] dec)) + state)) + +(defn- clear-row [state row] + (assoc-in state [:rows row :value] (vec (repeat (:steps state) false)))) (defn- step-player [state-atom beat] @@ -118,9 +67,9 @@ (swap! state-atom assoc-in [:step] index) - (doseq [row (:rows state)] - (when-not (:mute row) - (at (metro beat) (play-step row index)))) + (doseq [{:keys [inst value]} (:rows state)] + (when (value index) + (at (metro beat) (inst)))) (apply-at (metro next-beat) #'step-player [state-atom next-beat]))))) @@ -146,21 +95,7 @@ :background (color 128 128 224 200) :foreground (color 0 150 0))) -(defn- scaled-entry-style - [n] - (let [g (int (+ (* n 200) 55)) - bg (color 0 g 0 200)] - (update-style enabled-entry-style :background bg))) - -(defn- get-param-factor - [p-info val] - (let [{:keys [max min name default]} p-info - p-val (or ((keyword name) val) - default)] - (double (/ (- p-val min) (- max min))))) - -(defn- paint-grid - [state ^javax.swing.JComponent c g] +(defn- paint-grid [state ^javax.swing.JComponent c g] (let [w (width c) h (height c) rows (count (:rows state)) @@ -172,30 +107,16 @@ (let [x (* step dx)] (draw g (rounded-rect x 0 dx h) current-step-style))) (dotimes [r rows] - (let [y (* r dy) - p (get-param-info state r)] + (let [y (* r dy)] (draw g (line 0 y w y) grid-line-style) (dotimes [c cols] (let [x (* c dx)] (draw g (line x 0 x h) grid-line-style) - (when-let [val (get-entry state r c)] - (let [paint-cell - #(draw g - (rounded-rect (+ x 2) (+ y 2) (- dx 3) (- dy 3) 3 3) - %)] - (if (:on val) - (let [p-fact (get-param-factor p val)] - (paint-cell (scaled-entry-style p-fact))) - (paint-cell muted-entry-style)))))))))) - -(defn- scaled-param-map - [state row val] - (let [{:keys [max min name]} (get-param-info state row) - p-name (keyword name) - p-val (-> val - (* (- max min)) - (+ min))] - {p-name p-val})) + (when (get-entry state r c) + (draw g + (rounded-rect (+ x 2) (+ y 2) (- dx 4) (- dy 4) 8 8) + enabled-entry-style)))))) + (draw g (rect 0 0 (dec w) (dec h)) grid-line-style))) (defn- parse-grid-click [state e] @@ -207,61 +128,24 @@ r-size (/ (height grid) n-rows) c-size (/ (width grid) n-cols) r (int (/ y r-size)) - c (int (/ x c-size)) - r-top (* r-size r) - r-btm (+ r-top r-size) - y-val (-> y (max r-top) (min r-btm) (- r-top)) - y-val (- 1 (double (/ y-val r-size))) - param (scaled-param-map state r y-val)] - {:row r :col c :r-size r-size :c-size c-size :y-val y-val :param param})) - -(defn- on-grid-clicked - [state e] - (let [{:keys [row col param]} (parse-grid-click state e) - new-state (cond (.isControlDown e) (delete-entry state row col) - (.isAltDown e) (update-entry state row col param) - :else (toggle-entry state row col))] + c (int (/ x c-size))] + { :row r :col c })) + +(defn- on-grid-clicked [state e] + (let [{:keys [row col]} (parse-grid-click state e) + new-state (toggle-entry state row col)] ;;when they enable a cell, play the sample. - (when (not (:playing? new-state)) - (let [row (get-in new-state [:rows row])] - (play-step row col))) + (when-not (:playing? new-state) + ((get-in new-state [:rows row :inst]))) new-state)) -(defn- on-grid-drag - [state e] - (let [{:keys [row col r-size c-size y-val param]} (parse-grid-click state e)] - (cond (.isControlDown e) (delete-entry state row col) - (.isShiftDown e) (mute-entry state row col) - (.isAltDown e) (update-entry state row col param) - :else (update-entry state row col {})))) - -(defn- inst->index - [rows inst] - (first (keep-indexed - (fn [index item] - (when (= inst (:inst item)) - index)) - rows))) - -(defn- on-param-selection - [state inst e] - (let [r (inst->index (:rows state) inst)] - (set-row-param state r (selection e)))) - -(defn- on-mute-toggle - [state inst e] - (let [r (inst->index (:rows state) inst)] - (toggle-row-mute state r))) - -(defn- on-solo-toggle - [state inst e] - (let [r (inst->index (:rows state) inst)] - (toggle-row-solo state r))) - -(defn- step-grid - [state-atom] +(defn- on-grid-drag [state e] + (let [{:keys [row col]} (parse-grid-click state e)] + (set-entry state row col (not (.isShiftDown e))))) + +(defn- step-grid [state-atom] (let [state @state-atom c (canvas :background :darkgrey @@ -277,54 +161,25 @@ (defn- inst-button [inst] (button :text (:name inst) - :listen [:action (fn [e] (synth-controller inst))])) - -(defn- inst-param - [inst] - (combobox :model (map :name (:params inst)) - :class :param)) - -(defn- inst-mute - [inst] - (toggle :text "Mute" :class :mute)) - -(defn- inst-solo - [inst] - (toggle :text "Solo" :class :solo)) + :listen [:action (fn [_] (synth-controller inst))])) (defn- inst-panel [state-atom inst] - (let [panel (mig-panel :constraints ["wrap 2" - "grow"] - :items [[(inst-button inst) "span, growx"] - [(inst-param inst) "span, growx"] - [(inst-mute inst) "growx"] - [(inst-solo inst) "growx"]])] - (listen (select panel [:.param]) - :selection #(swap! state-atom on-param-selection inst %)) - (listen (select panel [:.mute]) - :selection #(swap! state-atom on-mute-toggle inst %)) - (listen (select panel [:.solo]) - :selection #(swap! state-atom on-solo-toggle inst %)) - - panel)) + (inst-button inst)) (defn step-sequencer [metro steps instruments & [init-vals]] (invoke-now (let [state-atom (atom (make-initial-state metro steps instruments init-vals)) - play-btn (button :text "play") + play-btn (button :text "Play") bpm-spinner (spinner :model (spinner-model (metro :bpm) :from 1 :to 10000 :by 1) :maximum-size [60 :by 100]) - controls-btn (button :text "controls") + controls-btn (button :text "Controls" :tip "Show controls for all insts") + plus-btn (button :text "+" :tip "Add a column") + minus-btn (button :text "-" :tip "Remove a column") control-pane (toolbar :floatable? false - :items [play-btn - :separator - bpm-spinner - [:fill-h 5] - "bpm" - :separator - controls-btn]) + :items [play-btn [:fill-h 5] + bpm-spinner [:fill-h 5] "bpm"]) grid (step-grid state-atom) inst-panels (map (partial inst-panel state-atom) instruments) @@ -334,22 +189,22 @@ :north control-pane :west (grid-panel :columns 1 :items inst-panels) - :center grid) + :center grid + :south (toolbar :floatable? false + :items [controls-btn :fill-h plus-btn minus-btn])) :on-close :dispose)] (bind/bind bpm-spinner (bind/b-do [v] (metro :bpm v))) (bind/bind state-atom (bind/b-do [v] (repaint! grid))) - (swap! state-atom assoc-in [:solo-bg] - (button-group :buttons (select f [:.solo]))) - (listen play-btn :action (fn [e] (let [playing? (:playing? (swap! state-atom toggle-playing))] (config! play-btn :text (if playing? "stop" "play")) (if playing? (step-player state-atom (metro)))))) - + (listen plus-btn :action (fn [_] (swap! state-atom add-column))) + (listen minus-btn :action (fn [_] (swap! state-atom remove-column))) (listen controls-btn :action (fn [e] (apply synth-controller instruments))) @@ -371,5 +226,4 @@ (use 'overtone.gui.control) (use 'overtone.inst.drum) (def m (metronome 128)) - (step-sequencer m 8 [kick closed-hat snare])) - ) + (step-sequencer m 8 [kick closed-hat snare]))) diff --git a/src/overtone/gui/stepinator.clj b/src/overtone/gui/stepinator.clj index af7626fc9..504731029 100644 --- a/src/overtone/gui/stepinator.clj +++ b/src/overtone/gui/stepinator.clj @@ -32,13 +32,12 @@ :background (theme-color :background-fill) :stroke 1.0)) -(def ^{:private true} NUM_SLICES 20) - (defn- create-init-state "create the intitial state with the number of possible steps, width and height of the widget" - [num-steps width height] + [num-steps num-slices width height] {:steps (vec (repeat num-steps 0)) :num-steps num-steps + :num-slices num-slices :width width :height height}) @@ -50,13 +49,13 @@ x-res (:num-steps state) step-width (/ w (:num-steps state)) y (/ h 2) - tick-height (/ h NUM_SLICES) + tick-height (/ h (:num-slices state)) num-steps (:num-steps state) line-padding 0] (draw g (rect 0 0 w h) background-fill) - (dotimes [i 20] + (dotimes [i (:num-slices state)] (draw g (line 0 (* i tick-height) w (* i tick-height)) background-stroke)) (dotimes [i num-steps] @@ -78,11 +77,12 @@ (defn- on-press-drag [state e] - (let [ - x-cell (int (/ (.getX e) (/ (.getWidth (.getComponent e)) (:num-steps state)))) - y-cell (int (/ (.getY e) (/ (.getHeight (.getComponent e)) 2))) - tick-height (/ (.getHeight (.getComponent e)) NUM_SLICES) - current-value (- (int (/ (.getY e) tick-height)) 10)] + (let [x (.getX e) + y (.getY e) + x-cell (quot x (quot (width e) (:num-steps state))) + y-cell (quot y (quot (height e) 2)) + tick-height (quot (height e) (:num-slices state)) + current-value (- (quot y tick-height) (quot (:num-slices state) 2))] (assoc (assoc-in state [:steps x-cell] current-value) :current-value current-value))) @@ -99,19 +99,42 @@ c)) (defn stepinator - [& {:keys [steps width height] - :or {steps 16 width 300 height 150}}] + "Creates a simple UI for building a sequence of step values. + + A grid is displayed which is :steps columns wide and :slices rows tall. + Click and drag on the ui to change the value at each step. The range of + values is [-slices/2, slices/2] with 0 at the center, vertically of the + window. + + Returns a map with keys: + + :frame The ui frame + :state The state *atom* which is a map with a :steps entry which is a + vector of step values + + The optional :stepper parameter is a function which takes a vector of + step values. If present, a 'Stepinate' button will be creates on the ui + which, when clicked, will invoke the function with the curent state of + stepinator. + " + [& {:keys [steps slices width height stepper] + :or {steps 16 slices 20 width 300 height 150}}] (invoke-now (with-error-log "stepinator" - (let [state-atom (atom (create-init-state steps width height)) + (let [state-atom (atom (create-init-state steps slices width height)) stepinator (stepinator-panel state-atom) f (frame :title "Stepinator" :content (border-panel + :id :content :size [width :by height] :center stepinator)) state-bindable (bind/bind state-atom (bind/transform #(:current-value %))) ] + (if stepper + (config! (select f [:#content]) + :south (action :name "Stepinate" + :handler (fn [_] (stepper (:steps @state-atom)))))) (bind/bind state-atom (bind/b-do [_] (repaint! stepinator))) (adjustment-popup :widget f :label "Value:" :bindable state-bindable) (with-meta {:frame (-> f pack! show!) @@ -128,4 +151,19 @@ (dseq (map #(+ 60 %) (:steps @(:state pstep))))) src (saw (midicps note))] (* [0.2 0.2] src))) + +; Or give the stepinator a func to call and it will show a Stepinate button +(stepinator + :steps 32 + :slices 50 + :width 640 + :height 480 + :stepper (fn [steps] + (demo 5 + (let [note (duty (dseq [0.2 0.1] INF) + 0 + (dseq (map #(+ 60 %) steps))) + a (saw (midicps note)) + b (sin-osc (midicps (+ note 7)))] + [(* 0.2 a) (* 0.2 b)])))) ) diff --git a/src/overtone/gui/surface.clj b/src/overtone/gui/surface.clj index 72d35dd63..40ef645de 100644 --- a/src/overtone/gui/surface.clj +++ b/src/overtone/gui/surface.clj @@ -2,27 +2,8 @@ (:use [seesaw.core] [seesaw.graphics :only [draw circle style string-shape]] [seesaw.color :only [color]] - [overtone.sc.node :only [ctl]])) - -; TODO move to Seesaw. -(def ^ {:private true} input-modifier-table - {:left java.awt.event.InputEvent/BUTTON1_DOWN_MASK - :center java.awt.event.InputEvent/BUTTON2_DOWN_MASK - :right java.awt.event.InputEvent/BUTTON3_DOWN_MASK}) - -(def ^ {:private true} mouse-button-table - {java.awt.event.MouseEvent/BUTTON1 :left - java.awt.event.MouseEvent/BUTTON2 :center - java.awt.event.MouseEvent/BUTTON3 :right - java.awt.event.MouseEvent/NOBUTTON :none }) - -(defn- mouse-button-down? - [^java.awt.event.InputEvent e btn] - (let [mask (input-modifier-table btn 0)] - (not= 0 (bit-and mask (.getModifiersEx e))))) - -(defn- mouse-button [^java.awt.event.MouseEvent e] - (mouse-button-table (.getButton e))) + [overtone.sc.node :only [ctl]]) + (:require [seesaw.mouse :as mouse])) ;;; @@ -69,8 +50,8 @@ (style :foreground :darkgrey))) (defn- handle-move-event [state e] - (if (or (mouse-button-down? e :left) - (mouse-button-down? e :right)) + (if (or (mouse/button-down? e :left) + (mouse/button-down? e :right)) (let [x (min (max (.getX e) 0) (width e)) y (min (max (.getY e) 0) (height e))] ((get-in state [:x :set-value]) (to-model (:x state) (width e) x)) @@ -79,12 +60,12 @@ (defn- handle-press-event [state timer e] (handle-move-event state e) - (if (= :right (mouse-button e)) + (if (= :right (mouse/button e)) (reset! (:dir state) +) (repaint! e))) (defn- handle-release-event [state timer e] - (if (= :right (mouse-button e)) + (if (= :right (mouse/button e)) (reset! (:dir state) -) (repaint! e))) @@ -100,10 +81,22 @@ (repaint! c))) c) +(defn- nil-param + [] + (let [v (atom 0.0)] + {:name "" + :get-value (fn [] @v) + :set-value (fn [new-val] (reset! v new-val)) + :min 0.0 + :max 1.0})) + (defn surface-panel [x y z] (let [state {:color :blue - :x x :y y :z z :dir (atom nil)} - c (canvas :preferred-size [480 :by 480] + :x (or x (nil-param)) + :y (or y (nil-param)) + :z (or z (nil-param)) + :dir (atom nil)} + c (canvas :preferred-size [400 :by 300] :cursor :crosshair :paint (fn [c g] (paint-surface state c g))) t (timer (partial handle-timer state) @@ -115,14 +108,6 @@ :mouse-released (partial handle-release-event state t)) [c t])) -(defn- nil-param - [] - (let [v (atom 0.0)] - {:name "" - :get-value (fn [] @v) - :set-value (fn [new-val] (reset! v new-val)) - :min 0.0 - :max 1.0})) (defn surface "Create a 'surface' for controlling params. Takes three @@ -139,15 +124,36 @@ " [x y z] (invoke-now - (let [[panel timer] (surface-panel (or x (nil-param)) - (or y (nil-param)) - (or z (nil-param))) + (let [[panel timer] (surface-panel x y z) f (frame :title "Surface" :content panel :on-close :dispose)] (listen f :window-closed (fn [_] (.stop timer))) (-> f pack! show!)))) +(defn surface-grid + "Create a grid of surfaces. params is a sequence of [x y z] triples passed + to (surface). Returns the frame that displays the grid. If you get an + empty surface, it probably means you've got an inst without sufficient + metadata. + + See: + (surface) + " + [& params] + (let [p-and-t (for [p params] (apply surface-panel p)) + panels (map first p-and-t) + timers (map second p-and-t) + f (frame :title "Surfaces" + :content (grid-panel + :columns (int (Math/ceil (Math/sqrt (count panels)))) + :border 5 :hgap 5 :vgap 5 + :background :black + :items panels))] + (listen f :window-closed (fn [_] + (doseq [t timers] (.stop t)))) + (-> f pack! show!))) + (defn synth-param "Create a surface param for a synth or instrument. @@ -171,23 +177,27 @@ :max (:max param)}))) (comment + ; Set up a sequencer with some drum sounds. Hook some drum params + ; to a couple surfaces. (do (use 'overtone.live) (use 'overtone.gui.sequencer) (use 'overtone.gui.surface) (use 'overtone.inst.drum) (def m (metronome 128)) - (step-sequencer m 11 [kick closed-hat snare]) - (surface (synth-param snare "freq") - (synth-param kick "freq") - (synth-param snare "sustain"))) + (step-sequencer m 11 [kick closed-hat open-hat snare]) + (surface-grid [(synth-param snare "freq") (synth-param kick "freq") (synth-param snare "sustain")] + [(synth-param closed-hat "low") (synth-param closed-hat "hi") (synth-param closed-hat "t")] + [(synth-param open-hat "low") (synth-param open-hat "hi") (synth-param open-hat "t")] + [(synth-param kick "freq-decay") (synth-param kick "amp-decay") (synth-param snare "decay")])) + ; Set up two sin waves and control their frequencies with a surface. (do (use 'overtone.live) (use 'overtone.gui.surface) (defsynth harmony [freq1 {:default 440 :min 40 :max 1800 :step 1} freq2 {:default 880 :min 40 :max 1800 :step 1} - amp {:default 0.1 :min 0.1 :max 1 :step 0.01} ] + amp {:default 0.1 :min 0.1 :max 1 :step 0.01} ] (out 0 (* amp (sin-osc [freq1 freq2])))) (def h (harmony)) (surface (synth-param harmony h "freq1") diff --git a/src/overtone/inst/synth.clj b/src/overtone/inst/synth.clj index ff4bc3493..745f1f3d7 100644 --- a/src/overtone/inst/synth.clj +++ b/src/overtone/inst/synth.clj @@ -11,10 +11,10 @@ (definst ping [note {:default 72 :min 0 :max 120 :step 1} - a {:default 0.02 :min 0.001 :max 1 :step 0.001} - b {:default 0.3 :min 0.001 :max 1 :step 0.001}] + attack {:default 0.02 :min 0.001 :max 1 :step 0.001} + decay {:default 0.3 :min 0.001 :max 1 :step 0.001}] (let [snd (sin-osc (midicps note)) - env (env-gen (perc a b) :action FREE)] + env (env-gen (perc attack decay) :action FREE)] (* 0.1 env snd))) (definst tb303 diff --git a/src/overtone/music/rhythm.clj b/src/overtone/music/rhythm.clj index e3ead670e..7c2b738a2 100644 --- a/src/overtone/music/rhythm.clj +++ b/src/overtone/music/rhythm.clj @@ -49,7 +49,7 @@ (bpm [this new-bpm] (let [cur-beat (beat this) new-tick (beat-ms 1 new-bpm) - new-start (- (now) (* new-tick cur-beat))] + new-start (- (beat this cur-beat) (* new-tick cur-beat))] (reset! start new-start) (reset! bpm new-bpm)) [:bpm new-bpm])