├── .gitignore ├── LICENSE ├── README.md ├── dev ├── repl.clj └── user.clj ├── project.clj ├── resources ├── public │ ├── css │ │ └── main.css │ └── index.html └── sounds │ ├── crash.wav │ ├── kick.wav │ ├── kits │ ├── 2step │ │ ├── 808 Kick.wav │ │ ├── hat.wav │ │ ├── kick.wav │ │ ├── shaker.wav │ │ ├── snare.wav │ │ └── tambourine.wav │ └── big_room │ │ ├── big_kick.wav │ │ ├── claps.wav │ │ ├── hat_1.wav │ │ ├── hat_2.wav │ │ ├── hat_3.wav │ │ ├── hat_4.wav │ │ ├── kick.wav │ │ ├── kick_2.wav │ │ ├── kick_3.wav │ │ ├── snap.wav │ │ ├── snare1.wav │ │ ├── snare2.wav │ │ ├── snare3.wav │ │ ├── snare4.wav │ │ └── vocal loop.wav │ ├── samples │ ├── atw_bass.aif │ ├── phoenix.aif │ ├── robot_drive.wav │ ├── technologic.wav │ └── the_one.wav │ ├── shaker.wav │ ├── snare.wav │ └── tom_flat.wav ├── src ├── clj │ └── disclojure_ui │ │ ├── api.clj │ │ ├── main.clj │ │ ├── model.clj │ │ ├── runtime.clj │ │ ├── system.clj │ │ ├── track.clj │ │ └── websocket.clj └── cljs │ └── disclojure_ui │ ├── config.cljs │ ├── core.cljs │ ├── db.cljs │ ├── handlers.cljs │ ├── subs.cljs │ ├── views.cljs │ └── websocket.cljs └── test └── cljs └── disclojure_ui ├── core_test.cljs └── runner.cljs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | /profiles.clj 11 | dev/local.clj 12 | *.iml 13 | .idea 14 | npm-debug.log 15 | figwheel_server.log 16 | compiled/ 17 | work/ 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is Disclojure UI? 2 | 3 | A [re-frame](https://github.com/Day8/re-frame) application to visualize and edit compositions created with [Leipzig](https://github.com/ctford/leipzig) for [Overtone](https://github.com/overtone/overtone). 4 | 5 | 6 | 7 | ## What is Overtone? 8 | [Overtone](https://github.com/overtone/overtone) is a suberb Clojure sound library created by [Sam Aaron](https://github.com/samaaron). It lets you design own synths, play samples, interact with MIDI devices through [SuperCollider](http://supercollider.github.io) platform. Overtone is realy good for sound design and *playing individual notes* but not that good at *modeling complex compositions*. That's why some abstraction layer libraries over it like [Leipzig](https://github.com/ctford/leipzig) and [mud](https://github.com/josephwilk/mud) were created. 9 | 10 | ## What is Leipzig? 11 | [Leipzig](https://github.com/ctford/leipzig) is a composition library for [Overtone](https://github.com/overtone/overtone) created by [Chris Ford](https://github.com/ctford). The main idea behind Leipzig is that you can model (most of) melodies by sequence of notes with durations: 12 | ```clojure 13 | {:time 0 :pitch 67 :duration 1/4 :part :bass} 14 | ``` 15 | 16 | Leipzig provides a DSL which allows you to model these melodies with convenient transformations of Clojure collections. 17 | 18 | Consider classic [Da Funk](https://www.youtube.com/watch?v=mmi60Bd4jSs) track by Daft Punk: 19 | ```clojure 20 | (->> (phrase (concat [2] 21 | (take 12 (cycle [1/2 1/2 1/2 2.5])) 22 | [1 1]) 23 | [7 6 7 9 4 3 4 6 2 1 2 4 0 1 2]) 24 | (where :pitch (comp scale/G scale/minor)) 25 | (all :part :supersaw) 26 | (all :amp 1)) 27 | ``` 28 | 29 | The `phrase` function takes 2 collections - with note durations and pitches - which are `zip`ped to create base melody items with `:time`, `:pitch` and `:duration` entries. You can use standard Clojure collection functions like `cycle`, `take` and `concat` there. The pitches are steps of a given scale, which is provided with `scale` namespace, when you can `comp`ose scale root, type (`minor`, `major`, `pentatonic` and many more), you can go octave down with `low` and octave up with `high`. Then you just specify what instrument (`part`) should play this melody and other properties of the notes, like `:amp`, `:cutoff` and other. So, the resulting Leipzig structure looks like this: 30 | ```clojure 31 | [{:pitch 67, :time 0, :duration 2, :part :da-funk, :amp 1} 32 | {:pitch 65, :time 2, :duration 1/2, :part :da-funk, :amp 1} 33 | {:pitch 67, :time 5/2, :duration 1/2, :part :da-funk, :amp 1} 34 | ...] 35 | ``` 36 | 37 | ## How can Disclojure-UI help working with Leipzig melodies? 38 | As a non-musician it was hard for me to understand the context of the notes without actually seeing them on the screen. So I built Disclojure-UI as a browser-based DAW with a piano-roll like view that visualizes melodies created with Leipzig. The data is synced in both directions - when you change the Leipzig structure in the REPL (via websocket) and when you just edit the notes by clicking in the web GUI (via REST api): 39 | 40 | 41 | 42 | Apart from editing melodies, Disclojure-UI helps you edit beats in a separate `beat` view: 43 | 44 | 45 | 46 | ## How it works? 47 | Frontend is built with [re-frame](https://github.com/Day8/re-frame) and [sente](https://github.com/ptaoussanis/sente) for websockets. 48 | Backend manages state with [component](https://github.com/stuartsierra/component) and uses [system](https://github.com/danielsz/system) and [compojure-api](https://github.com/metosin/compojure-api) to expose REST API, also visible as Swagger spec: 49 | 50 | 51 | 52 | 53 | ## Running 54 | 55 | Compile Clojurescript: 56 | ```clojure 57 | lein cljsbuild once dev 58 | ``` 59 | 60 | Run: 61 | ```clojure 62 | lein repl 63 | (require 'repl) 64 | (reloaded.repl/reset) 65 | ``` 66 | Browse to [http://localhost:3005](http://localhost:3005). 67 | 68 | Interact (better with some IDE like Cursive, vim-clojure, Emacs): 69 | ```clojure 70 | (require '[leipzig.melody :refer :all]) 71 | (require '[leipzig.scale :as scale]) 72 | (require '[leipzig.live :as live]) 73 | (require '[disclojure.live :as l]) 74 | (->> (phrase (concat [2] 75 | (take 12 (cycle [1/2 1/2 1/2 2.5])) 76 | [1 1]) 77 | [7 6 7 9 4 3 4 6 2 1 2 4 0 1 2]) 78 | (where :pitch (comp scale/G scale/minor)) 79 | (all :part :supersaw) 80 | (all :amp 1) 81 | (l/assoc-track :supersaw)) 82 | (live/jam (l/track)) 83 | ... 84 | (live/stop) 85 | ``` 86 | 87 | More info about live coding in [disclojure](https://github.com/pjagielski/disclojure) docs. 88 | 89 | You can see example session in this **live-coding demo**: [![disclojure ui demo](http://img.youtube.com/vi/K98oZPca3Fw/0.jpg)](http://www.youtube.com/watch?v=K98oZPca3Fw) 90 | 91 | ## License 92 | 93 | Copyright © 2016 Piotr Jagielski 94 | 95 | The project name refers to [Disclosure](https://www.youtube.com/watch?v=W_vM8ePGuRM) band. 96 | 97 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 98 | -------------------------------------------------------------------------------- /dev/repl.clj: -------------------------------------------------------------------------------- 1 | (ns repl 2 | (:require [reloaded.repl :refer [system init start stop go reset]] 3 | [disclojure-ui.system :as system] 4 | [leipzig.melody :refer :all])) 5 | 6 | (def config 7 | {:http {:port 3005}, :dev-mode? true}) 8 | 9 | (reloaded.repl/set-init! #(system/new-system config)) 10 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user) 2 | 3 | (defn dev 4 | "Load and switch to the 'dev' namespace." 5 | [] 6 | (require 'repl) 7 | (in-ns 'repl) 8 | :loaded) 9 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject disclojure-ui "0.1.0-SNAPSHOT" 2 | :dependencies [[org.clojure/clojure "1.7.0"] 3 | [org.clojure/clojurescript "1.7.170"] 4 | [org.clojure/core.match "0.3.0-alpha4"] 5 | 6 | [http-kit "2.1.19"] 7 | [com.taoensso/sente "1.8.1"] 8 | [ring/ring-defaults "0.2.0"] 9 | 10 | [com.stuartsierra/component "0.3.0"] 11 | [org.danielsz/system "0.3.0"] 12 | 13 | [pjagielski/disclojure "0.1.4"] 14 | [compojure "1.4.0"] 15 | [metosin/compojure-api "1.1.4"] 16 | 17 | [reagent "0.5.1"] 18 | [re-frame "0.6.0"] 19 | [garden "1.3.0"] 20 | [cljs-http "0.1.39"] 21 | [cljs-ajax "0.5.3"] 22 | [shodan "0.4.2"]] 23 | 24 | :min-lein-version "2.5.3" 25 | 26 | :source-paths ["src/clj"] 27 | 28 | :plugins [[lein-cljsbuild "1.1.1"] 29 | [lein-garden "0.2.6"]] 30 | 31 | :clean-targets ^{:protect false} ["resources/public/js/compiled" "target" 32 | "test/js" 33 | "resources/public/css/compiled"] 34 | 35 | :main disclojure-ui.main 36 | 37 | :garden {:builds [{:id "screen" 38 | :source-paths ["src/clj"] 39 | :stylesheet disclojure-ui.css/screen 40 | :compiler {:output-to "resources/public/css/compiled/screen.css" 41 | :pretty-print? true}}]} 42 | 43 | :profiles 44 | {:dev {:source-paths ["dev"] 45 | :plugins [[lein-figwheel "0.5.0-2"] 46 | [lein-doo "0.1.6"]] 47 | :dependencies [[reloaded.repl "0.2.1"] 48 | [figwheel "0.5.0-2"] 49 | [binaryage/devtools "0.5.2"] 50 | [org.clojars.stumitchell/clairvoyant "0.1.0-SNAPSHOT"] 51 | [day8/re-frame-tracer "0.1.0-SNAPSHOT"]] 52 | :figwheel {:css-dirs ["resources/public/css"]}} 53 | 54 | :repl {:source-paths ["dev" "src/clj"] 55 | :resource-paths ^:replace ["resources" "target/figwheel"] 56 | :prep-tasks ^:replace [["javac"] ["compile"]]} 57 | :uberjar {:aot :all}} 58 | 59 | :cljsbuild {:builds [{:id "dev" 60 | :source-paths ["src/cljs"] 61 | :figwheel {:on-jsload "disclojure-ui.core/mount-root"} 62 | :compiler {:main disclojure-ui.core 63 | :output-to "resources/public/js/compiled/app.js" 64 | :output-dir "resources/public/js/compiled/out" 65 | :asset-path "js/compiled/out" 66 | :source-map-timestamp true}} 67 | 68 | {:id "test" 69 | :source-paths ["src/cljs" "test/cljs"] 70 | :compiler {:output-to "resources/public/js/compiled/test.js" 71 | :main disclojure-ui.runner 72 | :optimizations :none}} 73 | 74 | {:id "min" 75 | :source-paths ["src/cljs"] 76 | :compiler {:main disclojure-ui.core 77 | :output-to "resources/public/js/compiled/app.js" 78 | :optimizations :advanced 79 | :closure-defines {goog.DEBUG false} 80 | :pretty-print false}}]}) 81 | -------------------------------------------------------------------------------- /resources/public/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | } 3 | 4 | .main { 5 | padding: 10px; 6 | margin: 10px; 7 | } 8 | 9 | table.seq { 10 | border-spacing: 0; 11 | max-width: 2016px; 12 | } 13 | 14 | table.seq th, td { 15 | background-color: #eeeeee; 16 | min-width: 30px; 17 | max-width: 30px; 18 | height: 25px; 19 | font-size: 10px; 20 | text-align: center; 21 | border: 1px solid #000000; 22 | } 23 | 24 | table.seq .key { 25 | max-width: 50px; 26 | min-width: 50px; 27 | } 28 | 29 | table.seq td.x { 30 | color: #ffffff; 31 | } 32 | 33 | table.seq td.p { 34 | background-color: #ADFF2F; 35 | } 36 | 37 | table.seq td.x.a7 { 38 | background-color: #EA1308; 39 | } 40 | 41 | table.seq td.x.a5 { 42 | background-color: #FF635B; 43 | } 44 | 45 | table.seq td.x.a3 { 46 | background-color: #FF8B85; 47 | } 48 | 49 | table.seq td.t { 50 | border-left: 2px solid #000000; 51 | } 52 | 53 | table.seq td.c { 54 | border-bottom: 2px solid #000000; 55 | } 56 | 57 | td.b { 58 | background-color: #d8d8d8; 59 | } 60 | 61 | .play-controls { 62 | margin-bottom: 20px; 63 | } 64 | 65 | .update-control .track-control { 66 | width: 30% !important; 67 | 68 | } 69 | .track-control { 70 | width: 30% !important; 71 | } 72 | 73 | .play-controls { 74 | margin-top: 20px; 75 | } 76 | 77 | .track-container { 78 | overflow: scroll; 79 | height: 800px; 80 | } 81 | 82 | .beat-container { 83 | overflow-x: scroll; 84 | } 85 | 86 | .logo { 87 | margin-top: 0; 88 | margin-bottom: 0; 89 | } 90 | 91 | .panel { 92 | padding-bottom: 10px; 93 | margin-bottom: 20px; 94 | } 95 | 96 | .slider { 97 | margin-top: 5px; 98 | } 99 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/sounds/crash.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/crash.wav -------------------------------------------------------------------------------- /resources/sounds/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kick.wav -------------------------------------------------------------------------------- /resources/sounds/kits/2step/808 Kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/2step/808 Kick.wav -------------------------------------------------------------------------------- /resources/sounds/kits/2step/hat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/2step/hat.wav -------------------------------------------------------------------------------- /resources/sounds/kits/2step/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/2step/kick.wav -------------------------------------------------------------------------------- /resources/sounds/kits/2step/shaker.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/2step/shaker.wav -------------------------------------------------------------------------------- /resources/sounds/kits/2step/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/2step/snare.wav -------------------------------------------------------------------------------- /resources/sounds/kits/2step/tambourine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/2step/tambourine.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/big_kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/big_kick.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/claps.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/claps.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/hat_1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/hat_1.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/hat_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/hat_2.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/hat_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/hat_3.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/hat_4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/hat_4.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/kick.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/kick_2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/kick_2.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/kick_3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/kick_3.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/snap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/snap.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/snare1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/snare1.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/snare2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/snare2.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/snare3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/snare3.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/snare4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/snare4.wav -------------------------------------------------------------------------------- /resources/sounds/kits/big_room/vocal loop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/kits/big_room/vocal loop.wav -------------------------------------------------------------------------------- /resources/sounds/samples/atw_bass.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/samples/atw_bass.aif -------------------------------------------------------------------------------- /resources/sounds/samples/phoenix.aif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/samples/phoenix.aif -------------------------------------------------------------------------------- /resources/sounds/samples/robot_drive.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/samples/robot_drive.wav -------------------------------------------------------------------------------- /resources/sounds/samples/technologic.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/samples/technologic.wav -------------------------------------------------------------------------------- /resources/sounds/samples/the_one.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/samples/the_one.wav -------------------------------------------------------------------------------- /resources/sounds/shaker.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/shaker.wav -------------------------------------------------------------------------------- /resources/sounds/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/snare.wav -------------------------------------------------------------------------------- /resources/sounds/tom_flat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pjagielski/disclojure-ui/327d2af8f5a904e4ad66f79dc1682d5d79bea0d2/resources/sounds/tom_flat.wav -------------------------------------------------------------------------------- /src/clj/disclojure_ui/api.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.api 2 | (:require [clojure.java.io :as io] 3 | [compojure.core :as c] 4 | [compojure.route :as route] 5 | [compojure.api.sweet :refer :all] 6 | [ring.util.http-response :refer [ok]] 7 | [disclojure.live :as dl :refer [assoc-track]] 8 | [disclojure-ui.model :as m] 9 | [disclojure-ui.runtime :as r] 10 | [disclojure.play :as pl] 11 | [overtone.live :as o] 12 | [leipzig.live :as live] 13 | [plumbing.core :as p])) 14 | 15 | ; -> cljc 16 | (defn mutate-beat! [current {:keys [time duration amp]} drum add?] 17 | (->> 18 | (if add? 19 | (conj current {:time time :duration duration :drum drum :part :beat :amp amp}) 20 | (remove #(and (= (double time) (double (:time %))) (= drum (:drum %))) current)) 21 | (sort-by :time))) 22 | 23 | (defn mutate-track! [current {:keys [time duration amp pitch]} part add?] 24 | (->> 25 | (if add? 26 | (conj current {:time time :duration duration :pitch pitch :part part :amp amp}) 27 | (remove #(and (= (double time) (double (:time %))) (= pitch (:pitch %))) current)) 28 | (sort-by :time))) 29 | 30 | (defn api-routes [{{:keys [raw-track kit controls]} :state}] 31 | (routes 32 | (route/resources "/") 33 | (c/GET "/" [] 34 | (io/resource "public/index.html")) 35 | (api 36 | {:swagger {:ui "/api-docs" :spec "/swagger.json" 37 | :data {:info {:version "1.0.0" 38 | :title "Disclojure-UI API" 39 | :description "Interact with Overtone via REST"}}}} 40 | (context "/api" [] 41 | (context "/track" [] 42 | (GET "/" [] (ok @raw-track)) 43 | (POST "/play" [] (live/jam (dl/track)) (ok)) 44 | (POST "/stop" [] (live/stop) (ok)) 45 | (PUT "/" [] 46 | :body [body m/TrackMutation] 47 | (let [part (keyword (:part body))] 48 | (as-> 49 | (get @raw-track part) $ 50 | (mutate-track! $ body part (m/add? body)) 51 | (assoc-track part $))) 52 | (ok))) 53 | (context "/instruments" [] 54 | (GET "/" [] 55 | (-> 56 | (symbol "disclojure.inst") 57 | (r/find-instruments) 58 | (ok))) 59 | (POST "/play" [] 60 | :body [body m/PlayNote] 61 | (pl/play (keyword (:instr body)) body) 62 | (ok))) 63 | (GET "/notes/:from/:to" [] 64 | :path-params [from :- Long, to :- Long] 65 | (->> (range from to) 66 | (map (fn [x] {x (o/find-note-name x)})) 67 | (into {}) 68 | ok)) 69 | (context "/kit" [] 70 | (GET "/" [] 71 | (ok (p/map-vals #(select-keys % [:amp]) @kit))) 72 | (POST "/play" [] 73 | :body [body m/PlayBeat] 74 | (apply (-> (get @kit (keyword (:drum body))) :sound) []) 75 | (ok))) 76 | (context "/beat" [] 77 | (PUT "/" [] 78 | :body [body m/BeatMutation] 79 | (as-> 80 | (get @raw-track :beat) $ 81 | (mutate-beat! $ body (keyword (:drum body)) (m/add? body)) 82 | (assoc-track :beat $)) 83 | (ok))) 84 | (context "/controls" [] 85 | (PUT "/" [] 86 | :body [body m/ControlChange] 87 | (let [instr (keyword (:instr body)) 88 | control (keyword (:control body))] 89 | (swap! controls assoc-in [instr control] (:value body))) 90 | (ok))))))) 91 | -------------------------------------------------------------------------------- /src/clj/disclojure_ui/main.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.main 2 | (:gen-class) 3 | (:require [com.stuartsierra.component :as component] 4 | [disclojure-ui.system :refer [new-system]])) 5 | 6 | (def config 7 | {:http {:port 3005}, :dev-mode? false}) 8 | 9 | (defn -main [& args] 10 | (let [system (new-system config)] 11 | (println "Starting HTTP server on port" (-> system :http :port)) 12 | (component/start system))) 13 | -------------------------------------------------------------------------------- /src/clj/disclojure_ui/model.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.model 2 | (:require [schema.core :as s])) 3 | 4 | (s/defschema PlayNote 5 | {:instr String 6 | :duration Double 7 | :note Integer}) 8 | 9 | (s/defschema PlayBeat 10 | {:drum String}) 11 | 12 | (def MutationType (s/enum :add :remove)) 13 | 14 | (s/defschema TrackMutation 15 | {:part String 16 | :time Number 17 | :pitch Long 18 | :duration Number 19 | :amp Number 20 | :type MutationType}) 21 | 22 | (s/defschema BeatMutation 23 | {:time Number 24 | :drum String 25 | :amp Number 26 | :duration Number 27 | :type MutationType}) 28 | 29 | (s/defschema ControlChange 30 | {:instr String 31 | :control String 32 | :value Double}) 33 | 34 | (defn add? [mutation] 35 | (= :add (:type mutation))) 36 | -------------------------------------------------------------------------------- /src/clj/disclojure_ui/runtime.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.runtime) 2 | 3 | (defn find-instruments [ns] 4 | (->> (ns-interns ns) 5 | (filter (fn [e] (= :overtone.studio.inst/instrument 6 | (type (val e))))) 7 | (map key))) -------------------------------------------------------------------------------- /src/clj/disclojure_ui/system.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.system 2 | (:require [com.stuartsierra.component :as component] 3 | [ring.middleware.defaults :refer [wrap-defaults api-defaults]] 4 | [taoensso.sente.server-adapters.http-kit :refer [http-kit-adapter]] 5 | (system.components 6 | [sente :refer [new-channel-socket-server sente-routes]] 7 | [endpoint :refer [new-endpoint]] 8 | [handler :refer [new-handler]] 9 | [http-kit :refer [new-web-server]] 10 | [middleware :refer [new-middleware]]) 11 | (disclojure 12 | [play :refer [controls]] 13 | [live :refer [state reset-track]] 14 | [kit :refer [kit]]) 15 | (disclojure-ui 16 | [track :refer [initial-track]] 17 | [api :refer [api-routes]] 18 | [websocket :refer [websocket-handler new-state-broadcaster]]))) 19 | 20 | (defn new-system [config] 21 | (component/map->SystemMap 22 | 23 | {:state (reify component/Lifecycle 24 | (start [_] 25 | (reset-track initial-track) 26 | {:raw-track (get state :raw-track) 27 | :kit kit 28 | :controls controls})) 29 | 30 | :api (component/using 31 | (new-endpoint api-routes) 32 | [:state]) 33 | 34 | :sente (component/using 35 | (new-channel-socket-server websocket-handler 36 | http-kit-adapter {:wrap-component? true}) 37 | [:state]) 38 | 39 | :sente-endpoint (component/using 40 | (new-endpoint sente-routes) 41 | [:sente]) 42 | 43 | :middleware (new-middleware {:middleware [[wrap-defaults :defaults]] 44 | :defaults api-defaults 45 | :not-found "

The requested page does not exist.

"}) 46 | 47 | :handler (component/using 48 | (new-handler) 49 | [:sente-endpoint :api :middleware]) 50 | 51 | :http (component/using 52 | (new-web-server 53 | (get-in config [:http :port])) 54 | [:handler]) 55 | 56 | :broadcaster (component/using 57 | (new-state-broadcaster) 58 | [:state :sente]) 59 | })) 60 | -------------------------------------------------------------------------------- /src/clj/disclojure_ui/track.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.track 2 | (:require [disclojure.play] 3 | [disclojure.track :as t])) 4 | 5 | (def initial-track {:beat []}) 6 | 7 | (reset! t/metro 120) 8 | -------------------------------------------------------------------------------- /src/clj/disclojure_ui/websocket.clj: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.websocket 2 | (:require [clojure.core.async :refer [go-loop StateBroadcaster {})) 24 | 25 | (defn websocket-handler [{state :state}] 26 | (fn [{:keys [event] :as ev}] 27 | (t/debug "Received" event))) 28 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/config.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.config) 2 | 3 | (def debug? 4 | ^boolean js/goog.DEBUG) 5 | 6 | (when debug? 7 | (enable-console-print!)) 8 | 9 | (def api-base "/api") 10 | 11 | (def res 0.25) 12 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/core.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.core 2 | (:require [reagent.core :as reagent] 3 | [re-frame.core :as re-frame] 4 | [disclojure-ui.handlers] 5 | [disclojure-ui.subs] 6 | [disclojure-ui.websocket] 7 | [disclojure-ui.views :as views] 8 | [disclojure-ui.config :as config])) 9 | 10 | (when config/debug? 11 | (println "dev mode")) 12 | 13 | (defn mount-root [] 14 | (reagent/render [views/main-panel] 15 | (.getElementById js/document "app"))) 16 | 17 | (defn ^:export init [] 18 | (re-frame/dispatch-sync [:initialize-db]) 19 | (mount-root)) 20 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/db.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.db) 2 | 3 | (def default-db 4 | {:configs {:from 24 :to 96 :bars 8} 5 | :controls {:panel "track" :instr "supersaw"} 6 | :instr-controls {"supersaw" {:cutoff 2200}} 7 | :beat-controls {:amp 1} 8 | :editor {:duration 0.25 :amp 1} 9 | :instruments [] 10 | :kit []}) 11 | 12 | (defn duration [db] 13 | (get-in db [:editor :duration])) 14 | 15 | (defn editor [db] 16 | (get db :editor)) 17 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/handlers.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.handlers 2 | (:require-macros [cljs.core.async.macros :refer [go]]) 3 | (:require [re-frame.core :as re-frame] 4 | [plumbing.core :refer [map-vals map-keys]] 5 | [cljs.core.async :refer [clj %1)])}) 25 | db)) 26 | 27 | (defn normalize-names [notes] 28 | (->> notes 29 | (map (fn [{:keys [drum part] :as n}] 30 | (if drum (assoc n :drum (keyword drum)) 31 | (assoc n :part (name part))))))) 32 | 33 | (re-frame/register-handler 34 | :sync-track-response 35 | (fn [db [_ track]] 36 | (->> track 37 | (map-keys name) 38 | (map-vals normalize-names) 39 | (map-vals #(group-by :pitch %)) 40 | (assoc db :track)))) 41 | 42 | (re-frame/register-handler 43 | :push-track 44 | (fn [db [_ track]] 45 | (->> track 46 | (map-keys name) 47 | (map-vals normalize-names) 48 | (map-vals #(group-by :pitch %)) 49 | (assoc db :track)))) 50 | 51 | (re-frame/register-handler 52 | :sync-notes 53 | (fn [db [_ [from to]]] 54 | (GET (str c/api-base "/notes/" from "/" to) 55 | {:response-format :json :keywords? false 56 | :handler #(re-frame/dispatch [:sync-notes-response (js->clj %1)])}) 57 | db)) 58 | 59 | (re-frame/register-handler 60 | :sync-notes-response 61 | (fn [db [_ notes]] 62 | (assoc db :notes notes))) 63 | 64 | (re-frame/register-handler 65 | :sync-kit 66 | (fn [db _] 67 | (GET (str c/api-base "/kit") 68 | {:response-format :json :keywords? false 69 | :handler #(re-frame/dispatch [:sync-kit-response (js->clj %1)])}) 70 | db)) 71 | 72 | (re-frame/register-handler 73 | :sync-kit-response 74 | (fn [db [_ kit]] 75 | (assoc db :kit (into (sorted-map) kit)))) 76 | 77 | (re-frame/register-handler 78 | :sync-instruments 79 | (fn [db _] 80 | (GET (str c/api-base "/instruments") 81 | {:response-format :json 82 | :handler #(re-frame/dispatch [:sync-instruments-response (js->clj %1)])}) 83 | db)) 84 | 85 | (re-frame/register-handler 86 | :sync-instruments-response 87 | (fn [db [_ instruments]] 88 | (assoc db :instruments instruments))) 89 | 90 | (re-frame/register-handler 91 | :play 92 | (fn [db _] 93 | (POST (str c/api-base "/track/play")) 94 | (assoc db :playing true))) 95 | 96 | (re-frame/register-handler 97 | :stop 98 | (fn [db _] 99 | (POST (str c/api-base "/track/stop")) 100 | (assoc db :playing false))) 101 | 102 | (re-frame/register-handler 103 | :change-control 104 | (fn [db [_ [name value]]] 105 | (assoc-in db [:controls name] value))) 106 | 107 | (re-frame/register-handler 108 | :change-editor-control 109 | (fn [db [_ [name value]]] 110 | (assoc-in db [:editor name] value))) 111 | 112 | (re-frame/register-handler 113 | :change-instr-control 114 | (fn [db [_ [instr control value]]] 115 | (go 116 | (PUT (str c/api-base "/controls") 117 | {:params {:instr instr :control control :value value} :format :json})) 118 | (assoc-in db [:instr-controls instr control] value))) 119 | 120 | (re-frame/register-handler 121 | :play-note 122 | (fn [db [_ note]] 123 | (let [params (assoc note :duration (db/duration db))] 124 | (POST (str c/api-base "/instruments/play") 125 | {:params params :format :json})) 126 | db)) 127 | 128 | (re-frame/register-handler 129 | :play-kit 130 | (fn [db [_ drum]] 131 | (POST (str c/api-base "/kit/play") 132 | {:params {:drum drum} :format :json}) 133 | db)) 134 | 135 | (re-frame/register-handler 136 | :edit-beat 137 | (fn [db [_ [instr t-idx has-note?]]] 138 | (let [path [:track "beat" nil] curr-pattern (get-in db path []) 139 | time (* t-idx res) const (:beat-controls db) 140 | new-entry (merge const {:time time :duration (- 8 time) :drum instr})] 141 | (PUT (str c/api-base "/beat") 142 | {:params (merge new-entry {:type (if has-note? :remove :add)}) 143 | :format :json}) 144 | (->> (if has-note? 145 | (remove #(and (= time (:time %)) (= instr (:drum %))) curr-pattern) 146 | (conj curr-pattern new-entry)) 147 | (sort-by :time) 148 | (assoc-in db path))))) 149 | 150 | (re-frame/register-handler 151 | :edit-track 152 | (fn [db [_ [instr t-idx note has-note?]]] 153 | (let [path [:track instr note] curr-pattern (get-in db path []) 154 | time (* t-idx res) const (db/editor db) 155 | new-entry (merge const {:time time :pitch note :part instr})] 156 | (go 157 | (PUT (str c/api-base "/track") 158 | {:params (merge new-entry {:type (if has-note? :remove :add)}) 159 | :format :json})) 160 | (->> (if has-note? 161 | (remove #(and (= time (:time %)) (= note (:pitch %))) curr-pattern) 162 | (conj curr-pattern new-entry)) 163 | (sort-by :time) 164 | (assoc-in db path))))) 165 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/subs.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.subs 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-frame.core :as re-frame] 4 | [plumbing.core :refer [map-vals]] 5 | [disclojure-ui.config :refer [res]])) 6 | 7 | (re-frame/register-sub 8 | :name 9 | (fn [db] 10 | (reaction (:name @db)))) 11 | 12 | (re-frame/register-sub 13 | :track-part 14 | (fn [db [_ instr pitch]] 15 | (reaction (-> (:track @db) (get instr) (get pitch))))) 16 | 17 | (re-frame/register-sub 18 | :beat 19 | (fn [db] 20 | (reaction 21 | (->> (-> (:track @db) (get "beat") vals first) 22 | (map #(assoc % :duration 0.25)) 23 | (group-by :drum))))) 24 | 25 | (re-frame/register-sub 26 | :notes 27 | (fn [db] (reaction (:notes @db)))) 28 | 29 | (re-frame/register-sub 30 | :kit 31 | (fn [db] (reaction (:kit @db)))) 32 | 33 | (re-frame/register-sub 34 | :instruments 35 | (fn [db] (reaction (:instruments @db)))) 36 | 37 | (re-frame/register-sub 38 | :instr-controls 39 | (fn [db] (reaction (:instr-controls @db)))) 40 | 41 | (re-frame/register-sub 42 | :controls 43 | (fn [db] (reaction (:controls @db)))) 44 | 45 | (re-frame/register-sub 46 | :configs 47 | (fn [db] (reaction (:configs @db)))) 48 | 49 | (re-frame/register-sub 50 | :editor 51 | (fn [db] (reaction (:editor @db)))) 52 | 53 | (re-frame/register-sub 54 | :instr 55 | (fn [db] (reaction (:instr @db)))) 56 | 57 | (re-frame/register-sub 58 | :playing 59 | (fn [db] (reaction (:playing @db)))) 60 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/views.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.views 2 | (:require-macros [reagent.ratom :refer [reaction]]) 3 | (:require [re-frame.core :as re-frame] 4 | [clojure.set :refer [union]] 5 | [clojure.string :as s] 6 | [clojure.data :as d] 7 | [disclojure-ui.config :refer [res]] 8 | [shodan.console :include-macros true] 9 | [reagent.core :as reagent])) 10 | 11 | (defn map-note [[idx result] {:keys [time duration amp]}] 12 | (let [time-idx (int (/ time res)) 13 | new-idx (+ time-idx (int (/ duration res)))] 14 | [new-idx (conj result [idx time-idx false amp] [time-idx new-idx true amp])])) 15 | 16 | (defn map-row [times ticks] 17 | (let [[end reduced] (reduce map-note [0 []] times)] 18 | (conj reduced [end ticks false]))) 19 | 20 | (defn amp->class [amp] 21 | (condp > amp 22 | 0.0 "a0" 23 | 0.3 "a3" 24 | 0.5 "a5" 25 | "a7")) 26 | 27 | (defn res->ticks [res] 28 | (int (/ 2 res))) 29 | 30 | (defn sep? [x] (= 0 (mod x (res->ticks res)))) 31 | 32 | (defn would-have-note [note time cursor-pos duration] 33 | (and (= note (:note @cursor-pos)) 34 | (<= (:time @cursor-pos) time) 35 | (< time (+ (:time @cursor-pos) (int (/ duration res)))))) 36 | 37 | (defn ticks [bars res] 38 | (* (res->ticks res) bars)) 39 | 40 | (defn is-black-key? [note-name] 41 | (let [chars (set note-name)] 42 | (and (string? note-name) 43 | (or (contains? chars \#) 44 | (contains? chars \b))))) 45 | 46 | (defn is-C? [note-name] 47 | (and (string? note-name) (re-matches #"C\d" note-name))) 48 | 49 | (defn track-row [instr y bars duration] 50 | (let [part (re-frame/subscribe [:track-part instr y]) 51 | note-names (re-frame/subscribe [:notes]) 52 | cursor-pos (reagent.core/atom {})] 53 | (reagent/create-class 54 | {:should-component-update 55 | (fn [_ _ _] false) 56 | :reagent-render 57 | (fn [instr y bars duration] 58 | (let [mapping (map-row @part (ticks bars res)) 59 | note (get @note-names (str y))] 60 | (into 61 | ^{:key y} [:tr] 62 | (cons 63 | [:td.key {:on-click #(re-frame/dispatch [:play-note {:instr instr :note y}])} 64 | note] 65 | (for [[from to has-note? amp] mapping] 66 | (doall 67 | (for [t (range from to)] 68 | ^{:key (str t y)} 69 | [:td {:class (s/join " " [(when (is-black-key? note) "b") 70 | (when (is-C? note) "c") 71 | (when has-note? "x") (amp->class amp) 72 | (when (would-have-note y t cursor-pos duration) "p") 73 | (when (sep? t) "t")]) 74 | :on-click #(re-frame/dispatch [:edit-track [instr t y has-note?]]) 75 | :on-mouse-enter #(reset! cursor-pos {:note y :time t}) 76 | :on-mouse-leave #(reset! cursor-pos {})} 77 | (if has-note? (when (= t from) note) "-")])))))))}))) 78 | 79 | (defn track-panel [] 80 | (let [configs (re-frame/subscribe [:configs]) 81 | controls (re-frame/subscribe [:controls]) 82 | editor (re-frame/subscribe [:editor])] 83 | (fn [] 84 | (time 85 | [:div.track-container 86 | (let [{:keys [from to bars]} @configs 87 | {:keys [instr]} @controls 88 | {:keys [duration]} @editor] 89 | [:table.seq 90 | (into ^{:key (str instr "-tbody")} [:tbody] 91 | (conj 92 | (for [y (reverse (range from to))] 93 | [track-row instr y bars duration])))])])))) 94 | 95 | (defn beat-panel [] 96 | (let [beat (re-frame/subscribe [:beat]) 97 | kit (re-frame/subscribe [:kit]) 98 | configs (re-frame/subscribe [:configs])] 99 | (fn [] 100 | [:div.beat-container 101 | [:table.seq 102 | (into ^{:key "beat-tbody"} [:tbody] 103 | (for [instr (keys @kit)] 104 | (let [mapping (map-row (get @beat (keyword instr)) (ticks (get @configs :bars) res))] 105 | (into 106 | ^{:key instr} [:tr] 107 | (cons 108 | [:td.key {:on-click #(re-frame/dispatch [:play-kit instr])} 109 | instr] 110 | (for [[from to has-note? amp] mapping] 111 | (doall 112 | (for [t (range from to)] 113 | ^{:key (str t instr)} 114 | [:td {:class (s/join " " [(when has-note? "x") (amp->class amp) 115 | (when (sep? t) "t")]) 116 | :on-click #(re-frame/dispatch [:edit-beat [instr t has-note?]])} 117 | "-"]))))))))]]))) 118 | 119 | (defn slider [key min max step] 120 | (let [instr-controls (re-frame/subscribe [:instr-controls]) 121 | controls (re-frame/subscribe [:controls])] 122 | (fn [] 123 | (let [instr (:instr @controls)] 124 | [:input.slider 125 | {:type "range" 126 | :min min 127 | :max max 128 | :step step 129 | :value (get-in @instr-controls [instr key]) 130 | :on-change (fn [e] (re-frame/dispatch [:change-instr-control 131 | [instr key (js/Number (-> e .-target .-value))]]))}])))) 132 | 133 | (defn step-control [key pred f t controls event-key] 134 | (fn [] 135 | (let [val (get @controls key)] 136 | [:button.btn 137 | {:on-click #(when (pred val) 138 | (re-frame/dispatch [event-key [key (f val)]]))} 139 | t]))) 140 | 141 | (defn select [key values data event-key f] 142 | (into [:select.form-control 143 | {:name (str key) :value (get @data key) 144 | :on-change (fn [e] (re-frame/dispatch [event-key 145 | [key (f (.. e -target -value))]]))}] 146 | (map (fn [x] [:option {:value x} x]) values))) 147 | 148 | (defn step-select [key start end step controls event-key] 149 | (fn [] 150 | [:div.form-inline 151 | [step-control key #(> % start) #(- % step) "-" controls event-key] 152 | [select key (range start end step) controls event-key js/Number] 153 | [step-control key #(< % end) #(+ % step) "+" controls event-key]])) 154 | 155 | (defn track-control-panel [] 156 | (let [controls (re-frame/subscribe [:controls]) 157 | editor (re-frame/subscribe [:editor]) 158 | instruments (re-frame/subscribe [:instruments])] 159 | (fn [] 160 | [:div 161 | [:div.col-sm-3 162 | [select :panel ["track" "beat"] controls :change-control identity]] 163 | [:div.col-sm-3 164 | [select :instr @instruments controls :change-control identity]] 165 | [:div.col-sm-5 166 | [step-select :duration 0.25 4.25 0.25 editor :change-editor-control]]]))) 167 | 168 | (defn play-stop-button [] 169 | (let [playing (re-frame/subscribe [:playing])] 170 | (fn [] 171 | (let [[name event] (if @playing ["stop" :stop] ["play" :play])] 172 | [:button.form-control.btn-small {:on-click #(re-frame/dispatch [event])} name])))) 173 | 174 | (defn play-control-panel [] 175 | (fn [] 176 | [:div.form-inline 177 | [play-stop-button]])) 178 | 179 | (defonce key-listener (reagent.core/atom nil)) 180 | 181 | (defn main-panel [] 182 | (let [controls (re-frame/subscribe [:controls]) 183 | playing (re-frame/subscribe [:playing])] 184 | (reagent/create-class 185 | {:component-did-mount 186 | (fn [] 187 | (when (nil? @key-listener) 188 | (let [handler (fn [e] (if (= 32 (.-keyCode e)) 189 | (re-frame/dispatch [(if @playing :stop :play)])))] 190 | (js/addEventListener "keydown" handler) 191 | (reset! key-listener handler)))) 192 | :component-will-unmount 193 | (fn [] 194 | (js/removeEventListener "keydown" @key-listener) 195 | (reset! key-listener nil)) 196 | :reagent-render 197 | (fn [] 198 | [:div.main 199 | [:div.row.panel 200 | [:div.col-sm-2 201 | [:h2.logo "Disclo" [:em "j"] "ure"]] 202 | [:div.col-sm-1 203 | [play-control-panel]] 204 | [:div.col-sm-6.form-inline 205 | [track-control-panel]]] 206 | [:div.row 207 | [:div.col-sm-11 208 | (condp = (:panel @controls) 209 | "track" [track-panel] 210 | "beat" [beat-panel])] 211 | [:div.col-md-3]]])} 212 | ))) 213 | -------------------------------------------------------------------------------- /src/cljs/disclojure_ui/websocket.cljs: -------------------------------------------------------------------------------- 1 | (ns disclojure-ui.websocket 2 | (:require-macros 3 | [cljs.core.async.macros :refer (go go-loop)]) 4 | (:require 5 | [cljs.core.async :refer (! put! chan)] 6 | [cljs.core.match :refer-macros [match]] 7 | [taoensso.sente :as sente] 8 | [re-frame.core :as re-frame])) 9 | 10 | (defn event-handler [event] 11 | (match [event] 12 | [[:chsk/handshake _]] (.log js/console "Sente handshake") 13 | [[:chsk/recv [:disclojure/track track]]] (re-frame/dispatch [:push-track track]) 14 | :else (.log js/console "Unmatched event: %s" (pr-str event)))) 15 | 16 | (defn event-loop [ch-recv] 17 | (go-loop [] 18 | (let [{:as ev-msg :keys [event]} (