(ns drum-machine.core (:require [om.core :as om :include-macros true] [sablono.core :as html :refer-macros [html]])) (enable-console-print!) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; UI (def tau 6.2831853071) (defn build-drum [radius sides] (mapv #(vector (* radius (.cos js/Math (* tau (/ % sides)))) (* radius (.sin js/Math (* tau (/ % sides)))) false %) ; or: (int (* % (/ tempo sides))) (range (inc sides)))) (def drum-radii [145 95 45]) (defonce app-state (atom {:drums (mapv build-drum drum-radii [8 8 4])})) (defn widget [app] (om/component (html [:div {:id "drum-container"} [:svg {:id "drum-circles" :viewBox "-155 -160 320 320" :preserveAspectRatio "xMaxYmax" :height "100%" :width "100%"} (for [[i vertices] (map-indexed vector (:drums app))] [:g [:polygon {:points (apply str (for [[x y] vertices] (str x "," y " "))) :stroke "#ECE5CE" :stroke-width "2" :fill "#E08E79" :fill-opacity "0.33"}] (for [[x y active n] (butlast vertices)] (let [handler (fn [e] (.preventDefault e) (om/update! app [:drums i n 2] (not active)))] [:circle {:id (str i "-" n) :cx (str x) :cy (str y) :r 8 :fill (if active "#E08E79" "#ECE5CE") :onTouchEnd handler :onClick handler }]))])] (for [i (range 3)] [:input {:id (str "slider" i) :type "range" :value (dec (count (nth (:drums app) i))) :min 3 :max 16 :style {:width "100%"} :on-change #(om/update! app [:drums i] (build-drum (drum-radii i) (-> % .-target .-value js/parseInt)))}])]))) (om/root widget app-state {:target js/document.body}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Play loop (defonce sounds [(js/Howl. (js-obj "urls" (array "sounds/kick.wav"))) (js/Howl. (js-obj "urls" (array "sounds/snare.wav"))) (js/Howl. (js-obj "urls" (array "sounds/closed-hat.wav")))]) (defonce interval-id (atom 0)) (defonce current-beat (atom 0)) (defn run-beat [] (swap! current-beat inc) (doseq [[i vertices] (map-indexed vector (:drums @app-state))] (doseq [[x y active n] (butlast vertices)] (if (= n (mod @current-beat (dec (count vertices)))) (do (.setAttribute (.getElementById js/document (str i "-" n)) "r" "10") (when active (.play (sounds i)))) (.setAttribute (.getElementById js/document (str i "-" n)) "r" "8") )))) (defn stop-beat [] (js/clearInterval @interval-id)) (defn start-beat [] (stop-beat) (reset! interval-id (js/setInterval run-beat 250))) (start-beat)