├── .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**: [](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]} (