Skip to content

Instantly share code, notes, and snippets.

Created August 17, 2014 14:40
What would you like to do?
A little Godfried Toussaint inspired polyrhythmic drum toy online at
(ns drum-machine.core
(:require [om.core :as om :include-macros true]
[sablono.core :as html :refer-macros [html]]))
;; UI
(def tau 6.2831853071)
(defn build-drum [radius sides]
(mapv #(vector (* radius (.cos js/Math (* tau (/ % sides))))
(* radius (.sin js/Math (* tau (/ % sides))))
%) ; 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]
[: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))]
[: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))))
(.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 []
(reset! interval-id (js/setInterval run-beat 250)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment