├── .gitignore
├── CONTRIBUTING.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── src
└── main.rs
├── synthesizer-io-core
├── .DS_Store
├── Cargo.lock
├── Cargo.toml
├── benches
│ └── bench.rs
└── src
│ ├── graph.rs
│ ├── lib.rs
│ ├── module.rs
│ ├── modules
│ ├── adsr.rs
│ ├── biquad.rs
│ ├── buzz.rs
│ ├── const_ctrl.rs
│ ├── gain.rs
│ ├── mod.rs
│ ├── note_pitch.rs
│ ├── saw.rs
│ ├── sin.rs
│ ├── smooth_ctrl.rs
│ └── sum.rs
│ ├── queue.rs
│ └── worker.rs
├── synthesizer-io-wasm
├── Cargo.toml
├── README.md
├── index.html
├── index.js
├── package-lock.json
├── package.json
├── src
│ └── lib.rs
└── webpack.config.js
└── web
├── index.html
└── ui.js
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult [GitHub Help] for more
22 | information on using pull requests.
23 |
24 | [GitHub Help]: https://help.github.com/articles/about-pull-requests/
25 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "aho-corasick"
3 | version = "0.6.5"
4 | source = "registry+https://github.com/rust-lang/crates.io-index"
5 | dependencies = [
6 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
7 | ]
8 |
9 | [[package]]
10 | name = "ansi_term"
11 | version = "0.11.0"
12 | source = "registry+https://github.com/rust-lang/crates.io-index"
13 | dependencies = [
14 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
15 | ]
16 |
17 | [[package]]
18 | name = "atty"
19 | version = "0.2.10"
20 | source = "registry+https://github.com/rust-lang/crates.io-index"
21 | dependencies = [
22 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
23 | "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
24 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
25 | ]
26 |
27 | [[package]]
28 | name = "bindgen"
29 | version = "0.32.3"
30 | source = "registry+https://github.com/rust-lang/crates.io-index"
31 | dependencies = [
32 | "cexpr 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
33 | "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
34 | "clang-sys 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
35 | "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
36 | "env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
37 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
38 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
39 | "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
40 | "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
41 | "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
42 | "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
43 | "which 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
44 | ]
45 |
46 | [[package]]
47 | name = "bitflags"
48 | version = "1.0.3"
49 | source = "registry+https://github.com/rust-lang/crates.io-index"
50 |
51 | [[package]]
52 | name = "cexpr"
53 | version = "0.2.3"
54 | source = "registry+https://github.com/rust-lang/crates.io-index"
55 | dependencies = [
56 | "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
57 | ]
58 |
59 | [[package]]
60 | name = "cfg-if"
61 | version = "0.1.4"
62 | source = "registry+https://github.com/rust-lang/crates.io-index"
63 |
64 | [[package]]
65 | name = "clang-sys"
66 | version = "0.21.2"
67 | source = "registry+https://github.com/rust-lang/crates.io-index"
68 | dependencies = [
69 | "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
70 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
71 | "libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
72 | ]
73 |
74 | [[package]]
75 | name = "clap"
76 | version = "2.32.0"
77 | source = "registry+https://github.com/rust-lang/crates.io-index"
78 | dependencies = [
79 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
80 | "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
81 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
82 | "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
83 | "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
84 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
85 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
86 | ]
87 |
88 | [[package]]
89 | name = "core-foundation"
90 | version = "0.2.3"
91 | source = "registry+https://github.com/rust-lang/crates.io-index"
92 | dependencies = [
93 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
94 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
95 | ]
96 |
97 | [[package]]
98 | name = "core-foundation-sys"
99 | version = "0.2.3"
100 | source = "registry+https://github.com/rust-lang/crates.io-index"
101 | dependencies = [
102 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
103 | ]
104 |
105 | [[package]]
106 | name = "coreaudio-rs"
107 | version = "0.9.1"
108 | source = "registry+https://github.com/rust-lang/crates.io-index"
109 | dependencies = [
110 | "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
111 | "coreaudio-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
112 | ]
113 |
114 | [[package]]
115 | name = "coreaudio-sys"
116 | version = "0.2.2"
117 | source = "registry+https://github.com/rust-lang/crates.io-index"
118 | dependencies = [
119 | "bindgen 0.32.3 (registry+https://github.com/rust-lang/crates.io-index)",
120 | ]
121 |
122 | [[package]]
123 | name = "coremidi"
124 | version = "0.3.0"
125 | source = "registry+https://github.com/rust-lang/crates.io-index"
126 | dependencies = [
127 | "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
128 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
129 | "coremidi-sys 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
130 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
131 | ]
132 |
133 | [[package]]
134 | name = "coremidi-sys"
135 | version = "2.0.2"
136 | source = "registry+https://github.com/rust-lang/crates.io-index"
137 | dependencies = [
138 | "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
139 | ]
140 |
141 | [[package]]
142 | name = "env_logger"
143 | version = "0.4.3"
144 | source = "registry+https://github.com/rust-lang/crates.io-index"
145 | dependencies = [
146 | "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
147 | "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
148 | ]
149 |
150 | [[package]]
151 | name = "glob"
152 | version = "0.2.11"
153 | source = "registry+https://github.com/rust-lang/crates.io-index"
154 |
155 | [[package]]
156 | name = "kernel32-sys"
157 | version = "0.2.2"
158 | source = "registry+https://github.com/rust-lang/crates.io-index"
159 | dependencies = [
160 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
161 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
162 | ]
163 |
164 | [[package]]
165 | name = "lazy_static"
166 | version = "1.0.1"
167 | source = "registry+https://github.com/rust-lang/crates.io-index"
168 |
169 | [[package]]
170 | name = "libc"
171 | version = "0.2.42"
172 | source = "registry+https://github.com/rust-lang/crates.io-index"
173 |
174 | [[package]]
175 | name = "libloading"
176 | version = "0.4.3"
177 | source = "registry+https://github.com/rust-lang/crates.io-index"
178 | dependencies = [
179 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
180 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
181 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
182 | ]
183 |
184 | [[package]]
185 | name = "log"
186 | version = "0.3.9"
187 | source = "registry+https://github.com/rust-lang/crates.io-index"
188 | dependencies = [
189 | "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
190 | ]
191 |
192 | [[package]]
193 | name = "log"
194 | version = "0.4.3"
195 | source = "registry+https://github.com/rust-lang/crates.io-index"
196 | dependencies = [
197 | "cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
198 | ]
199 |
200 | [[package]]
201 | name = "memchr"
202 | version = "1.0.2"
203 | source = "registry+https://github.com/rust-lang/crates.io-index"
204 | dependencies = [
205 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
206 | ]
207 |
208 | [[package]]
209 | name = "memchr"
210 | version = "2.0.1"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | dependencies = [
213 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
214 | ]
215 |
216 | [[package]]
217 | name = "nom"
218 | version = "3.2.1"
219 | source = "registry+https://github.com/rust-lang/crates.io-index"
220 | dependencies = [
221 | "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
222 | ]
223 |
224 | [[package]]
225 | name = "peeking_take_while"
226 | version = "0.1.2"
227 | source = "registry+https://github.com/rust-lang/crates.io-index"
228 |
229 | [[package]]
230 | name = "proc-macro2"
231 | version = "0.2.3"
232 | source = "registry+https://github.com/rust-lang/crates.io-index"
233 | dependencies = [
234 | "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
235 | ]
236 |
237 | [[package]]
238 | name = "quote"
239 | version = "0.4.2"
240 | source = "registry+https://github.com/rust-lang/crates.io-index"
241 | dependencies = [
242 | "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
243 | ]
244 |
245 | [[package]]
246 | name = "redox_syscall"
247 | version = "0.1.40"
248 | source = "registry+https://github.com/rust-lang/crates.io-index"
249 |
250 | [[package]]
251 | name = "redox_termios"
252 | version = "0.1.1"
253 | source = "registry+https://github.com/rust-lang/crates.io-index"
254 | dependencies = [
255 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
256 | ]
257 |
258 | [[package]]
259 | name = "regex"
260 | version = "0.2.11"
261 | source = "registry+https://github.com/rust-lang/crates.io-index"
262 | dependencies = [
263 | "aho-corasick 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
264 | "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
265 | "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
266 | "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
267 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
268 | ]
269 |
270 | [[package]]
271 | name = "regex-syntax"
272 | version = "0.5.6"
273 | source = "registry+https://github.com/rust-lang/crates.io-index"
274 | dependencies = [
275 | "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
276 | ]
277 |
278 | [[package]]
279 | name = "strsim"
280 | version = "0.7.0"
281 | source = "registry+https://github.com/rust-lang/crates.io-index"
282 |
283 | [[package]]
284 | name = "synthesizer-io"
285 | version = "0.1.0"
286 | dependencies = [
287 | "coreaudio-rs 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
288 | "coremidi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
289 | "synthesizer-io-core 0.1.0",
290 | "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
291 | ]
292 |
293 | [[package]]
294 | name = "synthesizer-io-core"
295 | version = "0.1.0"
296 | dependencies = [
297 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
298 | ]
299 |
300 | [[package]]
301 | name = "termion"
302 | version = "1.5.1"
303 | source = "registry+https://github.com/rust-lang/crates.io-index"
304 | dependencies = [
305 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
306 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
307 | "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
308 | ]
309 |
310 | [[package]]
311 | name = "textwrap"
312 | version = "0.10.0"
313 | source = "registry+https://github.com/rust-lang/crates.io-index"
314 | dependencies = [
315 | "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
316 | ]
317 |
318 | [[package]]
319 | name = "thread_local"
320 | version = "0.3.5"
321 | source = "registry+https://github.com/rust-lang/crates.io-index"
322 | dependencies = [
323 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
324 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
325 | ]
326 |
327 | [[package]]
328 | name = "time"
329 | version = "0.1.40"
330 | source = "registry+https://github.com/rust-lang/crates.io-index"
331 | dependencies = [
332 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
333 | "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
334 | "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
335 | ]
336 |
337 | [[package]]
338 | name = "ucd-util"
339 | version = "0.1.1"
340 | source = "registry+https://github.com/rust-lang/crates.io-index"
341 |
342 | [[package]]
343 | name = "unicode-width"
344 | version = "0.1.5"
345 | source = "registry+https://github.com/rust-lang/crates.io-index"
346 |
347 | [[package]]
348 | name = "unicode-xid"
349 | version = "0.1.0"
350 | source = "registry+https://github.com/rust-lang/crates.io-index"
351 |
352 | [[package]]
353 | name = "unreachable"
354 | version = "1.0.0"
355 | source = "registry+https://github.com/rust-lang/crates.io-index"
356 | dependencies = [
357 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
358 | ]
359 |
360 | [[package]]
361 | name = "utf8-ranges"
362 | version = "1.0.0"
363 | source = "registry+https://github.com/rust-lang/crates.io-index"
364 |
365 | [[package]]
366 | name = "vec_map"
367 | version = "0.8.1"
368 | source = "registry+https://github.com/rust-lang/crates.io-index"
369 |
370 | [[package]]
371 | name = "void"
372 | version = "1.0.2"
373 | source = "registry+https://github.com/rust-lang/crates.io-index"
374 |
375 | [[package]]
376 | name = "which"
377 | version = "1.0.5"
378 | source = "registry+https://github.com/rust-lang/crates.io-index"
379 | dependencies = [
380 | "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)",
381 | ]
382 |
383 | [[package]]
384 | name = "winapi"
385 | version = "0.2.8"
386 | source = "registry+https://github.com/rust-lang/crates.io-index"
387 |
388 | [[package]]
389 | name = "winapi"
390 | version = "0.3.5"
391 | source = "registry+https://github.com/rust-lang/crates.io-index"
392 | dependencies = [
393 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
394 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
395 | ]
396 |
397 | [[package]]
398 | name = "winapi-build"
399 | version = "0.1.1"
400 | source = "registry+https://github.com/rust-lang/crates.io-index"
401 |
402 | [[package]]
403 | name = "winapi-i686-pc-windows-gnu"
404 | version = "0.4.0"
405 | source = "registry+https://github.com/rust-lang/crates.io-index"
406 |
407 | [[package]]
408 | name = "winapi-x86_64-pc-windows-gnu"
409 | version = "0.4.0"
410 | source = "registry+https://github.com/rust-lang/crates.io-index"
411 |
412 | [metadata]
413 | "checksum aho-corasick 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0ba20154ea1f47ce2793322f049c5646cc6d0fa9759d5f333f286e507bf8080"
414 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
415 | "checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1"
416 | "checksum bindgen 0.32.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8b242e11a8f446f5fc7b76b37e81d737cabca562a927bd33766dac55b5f1177f"
417 | "checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
418 | "checksum cexpr 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "42aac45e9567d97474a834efdee3081b3c942b2205be932092f53354ce503d6c"
419 | "checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e"
420 | "checksum clang-sys 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e414af9726e1d11660801e73ccc7fb81803fb5f49e5903a25b348b2b3b480d2e"
421 | "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e"
422 | "checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67"
423 | "checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d"
424 | "checksum coreaudio-rs 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491"
425 | "checksum coreaudio-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "78fdbabf58d5b1f461e31b94a571c109284f384cec619a3d96e66ec55b4de82b"
426 | "checksum coremidi 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33326bbe31e4f0102c694281998365bf37ecd7ed08d2993ded0c19c57579cbed"
427 | "checksum coremidi-sys 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "07f05827cebb30dcd539ff1ac9bf6764f574a15fa147f8572f99d7617142f95e"
428 | "checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b"
429 | "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
430 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
431 | "checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
432 | "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1"
433 | "checksum libloading 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fd38073de8f7965d0c17d30546d4bb6da311ab428d1c7a3fc71dff7f9d4979b9"
434 | "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
435 | "checksum log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd98ae7f7b754bc53dca7d44b604f733c6bba044ea6f41bc8d89272d8161d2"
436 | "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a"
437 | "checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
438 | "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b"
439 | "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
440 | "checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
441 | "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
442 | "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1"
443 | "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
444 | "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
445 | "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
446 | "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
447 | "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
448 | "checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6"
449 | "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
450 | "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b"
451 | "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d"
452 | "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
453 | "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
454 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
455 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
456 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
457 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
458 | "checksum which 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e84a603e7e0b1ce1aa1ee2b109c7be00155ce52df5081590d1ffb93f4f515cb2"
459 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
460 | "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd"
461 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
462 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
463 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
464 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "synthesizer-io"
3 | version = "0.1.0"
4 | license = "Apache-2.0"
5 | authors = ["Raph Levien "]
6 |
7 | [dependencies]
8 | coreaudio-rs = "0.9"
9 | coremidi = "0.3"
10 | time = "0.1"
11 |
12 | [dependencies.synthesizer-io-core]
13 | path = "./synthesizer-io-core"
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Synthesizer IO
2 |
3 | Development has moved to [https://github.com/raphlinus/synthesizer-io](raphlinus/synthesizer-io).
4 |
5 | ## Disclaimer
6 |
7 | This is not an official Google product.
8 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | extern crate coreaudio;
16 | extern crate coremidi;
17 | extern crate time;
18 |
19 | extern crate synthesizer_io_core;
20 |
21 | use coreaudio::audio_unit::{AudioUnit, IOType, SampleFormat, Scope};
22 | use coreaudio::audio_unit::render_callback::{self, data};
23 |
24 | use synthesizer_io_core::modules;
25 |
26 | use synthesizer_io_core::worker::Worker;
27 | use synthesizer_io_core::queue::Sender;
28 | use synthesizer_io_core::graph::{Node, Message, SetParam, Note};
29 | use synthesizer_io_core::module::N_SAMPLES_PER_CHUNK;
30 |
31 | struct Midi {
32 | tx: Sender,
33 | cur_note: Option,
34 | }
35 |
36 | impl Midi {
37 | fn new(tx: Sender) -> Midi {
38 | Midi {
39 | tx: tx,
40 | cur_note: None,
41 | }
42 | }
43 |
44 | fn send(&self, msg: Message) {
45 | self.tx.send(msg);
46 | }
47 |
48 | fn set_ctrl_const(&mut self, value: u8, lo: f32, hi: f32, ix: usize, ts: u64) {
49 | let value = lo + value as f32 * (1.0/127.0) * (hi - lo);
50 | let param = SetParam {
51 | ix: ix,
52 | param_ix: 0,
53 | val: value,
54 | timestamp: ts,
55 | };
56 | self.send(Message::SetParam(param));
57 | }
58 |
59 | fn send_note(&mut self, ixs: Vec, midi_num: f32, velocity: f32, on: bool,
60 | ts: u64)
61 | {
62 | let note = Note {
63 | ixs: ixs.into_boxed_slice(),
64 | midi_num: midi_num,
65 | velocity: velocity,
66 | on: on,
67 | timestamp: ts,
68 | };
69 | self.send(Message::Note(note));
70 | }
71 |
72 | fn dispatch_midi(&mut self, data: &[u8], ts: u64) {
73 | let mut i = 0;
74 | while i < data.len() {
75 | if data[i] == 0xb0 {
76 | let controller = data[i + 1];
77 | let value = data[i + 2];
78 | match controller {
79 | 1 => self.set_ctrl_const(value, 0.0, 22_000f32.log2(), 3, ts),
80 | 2 => self.set_ctrl_const(value, 0.0, 0.995, 4, ts),
81 | 3 => self.set_ctrl_const(value, 0.0, 22_000f32.log2(), 5, ts),
82 |
83 | 5 => self.set_ctrl_const(value, 0.0, 10.0, 11, ts),
84 | 6 => self.set_ctrl_const(value, 0.0, 10.0, 12, ts),
85 | 7 => self.set_ctrl_const(value, 0.0, 6.0, 13, ts),
86 | 8 => self.set_ctrl_const(value, 0.0, 10.0, 14, ts),
87 | _ => println!("don't have handler for controller {}", controller),
88 | }
89 | i += 3;
90 | } else if data[i] == 0x90 || data[i] == 0x80 {
91 | let midi_num = data[i + 1];
92 | let velocity = data[i + 2];
93 | let on = data[i] == 0x90 && velocity > 0;
94 | if on || self.cur_note == Some(midi_num) {
95 | self.send_note(vec![5, 7], midi_num as f32, velocity as f32, on, ts);
96 | self.cur_note = if on { Some(midi_num) } else { None }
97 | }
98 | i += 3;
99 | } else {
100 | break;
101 | }
102 | }
103 | }
104 | }
105 |
106 | fn main() {
107 | let (mut worker, tx, rx) = Worker::create(1024);
108 |
109 | /*
110 | let module = Box::new(modules::ConstCtrl::new(440.0f32.log2()));
111 | worker.handle_node(Node::create(module, 1, [], []));
112 | let module = Box::new(modules::Sin::new(44_100.0));
113 | worker.handle_node(Node::create(module, 2, [], [(1, 0)]));
114 | let module = Box::new(modules::ConstCtrl::new(880.0f32.log2()));
115 | worker.handle_node(Node::create(module, 3, [], []));
116 | let module = Box::new(modules::Sin::new(44_100.0));
117 | worker.handle_node(Node::create(module, 4, [], [(3, 0)]));
118 | let module = Box::new(modules::Sum);
119 | worker.handle_node(Node::create(module, 0, [(2, 0), (4, 0)], []));
120 | */
121 |
122 | let module = Box::new(modules::Saw::new(44_100.0));
123 | worker.handle_node(Node::create(module, 1, [], [(5, 0)]));
124 | let module = Box::new(modules::SmoothCtrl::new(880.0f32.log2()));
125 | worker.handle_node(Node::create(module, 3, [], []));
126 | let module = Box::new(modules::SmoothCtrl::new(0.5));
127 | worker.handle_node(Node::create(module, 4, [], []));
128 | let module = Box::new(modules::NotePitch::new());
129 | worker.handle_node(Node::create(module, 5, [], []));
130 | let module = Box::new(modules::Biquad::new(44_100.0));
131 | worker.handle_node(Node::create(module, 6, [(1,0)], [(3, 0), (4, 0)]));
132 | let module = Box::new(modules::Adsr::new());
133 | worker.handle_node(Node::create(module, 7, [], vec![(11, 0), (12, 0), (13, 0), (14, 0)]));
134 | let module = Box::new(modules::Gain::new());
135 | worker.handle_node(Node::create(module, 0, [(6, 0)], [(7, 0)]));
136 |
137 | let module = Box::new(modules::SmoothCtrl::new(5.0));
138 | worker.handle_node(Node::create(module, 11, [], []));
139 | let module = Box::new(modules::SmoothCtrl::new(5.0));
140 | worker.handle_node(Node::create(module, 12, [], []));
141 | let module = Box::new(modules::SmoothCtrl::new(4.0));
142 | worker.handle_node(Node::create(module, 13, [], []));
143 | let module = Box::new(modules::SmoothCtrl::new(5.0));
144 | worker.handle_node(Node::create(module, 14, [], []));
145 |
146 | let _audio_unit = run(worker).unwrap();
147 |
148 | let source_index = 0;
149 | if let Some(source) = coremidi::Source::from_index(source_index) {
150 | println!("Listening for midi from {}", source.display_name().unwrap());
151 | let client = coremidi::Client::new("synthesizer-client").unwrap();
152 | let mut last_ts = 0;
153 | let mut last_val = 0;
154 | let mut midi = Midi::new(tx);
155 | let callback = move |packet_list: &coremidi::PacketList| {
156 | for packet in packet_list.iter() {
157 | let data = packet.data();
158 | let delta_t = packet.timestamp() - last_ts;
159 | let speed = 1e9 * (data[2] as f64 - last_val as f64) / delta_t as f64;
160 | println!("{} {:3.3} {} {}", speed, delta_t as f64 * 1e-6, data[2],
161 | time::precise_time_ns() - packet.timestamp());
162 | last_val = data[2];
163 | last_ts = packet.timestamp();
164 | midi.dispatch_midi(&data, last_ts);
165 | }
166 | };
167 | let input_port = client.input_port("synthesizer-port", callback).unwrap();
168 | input_port.connect_source(&source).unwrap();
169 |
170 | println!("Press Enter to exit.");
171 | let mut line = String::new();
172 | ::std::io::stdin().read_line(&mut line).unwrap();
173 | input_port.disconnect_source(&source).unwrap();
174 | } else {
175 | println!("No midi available");
176 | }
177 | }
178 |
179 | fn run(mut worker: Worker) -> Result {
180 |
181 | // Construct an Output audio unit that delivers audio to the default output device.
182 | let mut audio_unit = AudioUnit::new(IOType::DefaultOutput)?;
183 |
184 | let stream_format = audio_unit.stream_format(Scope::Output)?;
185 | //println!("{:#?}", &stream_format);
186 |
187 | // We expect `f32` data.
188 | assert!(SampleFormat::F32 == stream_format.sample_format);
189 |
190 | type Args = render_callback::Args>;
191 | audio_unit.set_render_callback(move |args| {
192 | let Args { num_frames, mut data, .. }: Args = args;
193 | assert!(num_frames % N_SAMPLES_PER_CHUNK == 0);
194 | let mut i = 0;
195 | let mut timestamp = time::precise_time_ns();
196 | while i < num_frames {
197 | // should let the graph generate stereo
198 | let buf = worker.work(timestamp)[0].get();
199 | for j in 0..N_SAMPLES_PER_CHUNK {
200 | for channel in data.channels_mut() {
201 | channel[i + j] = buf[j];
202 | }
203 | }
204 | timestamp += 1451247; // 64 * 1e9 / 44_100
205 | i += N_SAMPLES_PER_CHUNK;
206 | }
207 | Ok(())
208 | })?;
209 | audio_unit.start()?;
210 |
211 | Ok(audio_unit)
212 | }
213 |
--------------------------------------------------------------------------------
/synthesizer-io-core/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/synthesizer-io/86e944d462603309864ccd379a536d071ab6ab90/synthesizer-io-core/.DS_Store
--------------------------------------------------------------------------------
/synthesizer-io-core/Cargo.lock:
--------------------------------------------------------------------------------
1 | [[package]]
2 | name = "lazy_static"
3 | version = "1.0.1"
4 | source = "registry+https://github.com/rust-lang/crates.io-index"
5 |
6 | [[package]]
7 | name = "synthesizer-io-core"
8 | version = "0.1.0"
9 | dependencies = [
10 | "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
11 | ]
12 |
13 | [metadata]
14 | "checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739"
15 |
--------------------------------------------------------------------------------
/synthesizer-io-core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "synthesizer-io-core"
3 | version = "0.1.0"
4 | license = "Apache-2.0"
5 | authors = ["Raph Levien "]
6 | description = "Computational core for sound synthesis."
7 |
8 | [dependencies]
9 | lazy_static = "1.0"
10 |
--------------------------------------------------------------------------------
/synthesizer-io-core/benches/bench.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Benchmarks for various audio processing things.
16 |
17 | #![feature(test)]
18 |
19 | extern crate test;
20 | extern crate synthesizer_io_core;
21 |
22 | #[cfg(test)]
23 | mod bench {
24 | use test::Bencher;
25 | use synthesizer_io_core::module::{Module, Buffer};
26 | use synthesizer_io_core::modules::Sin;
27 | use synthesizer_io_core::modules::Biquad;
28 |
29 | #[bench]
30 | fn sin(b: &mut Bencher) {
31 | let mut buf = [Buffer::default(); 1];
32 | let freq = [440.0f32.log2()];
33 | let mut sin = Sin::new(44_100.0);
34 | b.iter(||
35 | sin.process(&freq[..], &mut[][..], &[][..], &mut buf[..])
36 | )
37 | }
38 |
39 | #[bench]
40 | fn biquad(b: &mut Bencher) {
41 | let buf = Buffer::default();
42 | let bufs = [&buf];
43 | let mut bufo = [Buffer::default(); 1];
44 | let mut biquad = Biquad::new(44_100.0);
45 | let params = [44.0f32.log2(), 0.293];
46 | b.iter(||
47 | biquad.process(¶ms[..], &mut [][..], &bufs[..], &mut bufo[..])
48 | )
49 | }
50 |
51 | #[bench]
52 | fn direct_biquad(b: &mut Bencher) {
53 | // biquad in transposed direct form II architecture
54 | let a0 = 0.2513790015131591f32;
55 | let a1 = 0.5027580030263182f32;
56 | let a2 = 0.2513790015131591f32;
57 | let b1 = -0.17124071441396285f32;
58 | let b2 = 0.1767567204665992f32;
59 | let buf = Buffer::default();
60 | let mut bufo = Buffer::default();
61 | let inb = buf.get();
62 | let outb = bufo.get_mut();
63 | let mut z1 = 0.0f32;
64 | let mut z2 = 0.0f32;
65 | b.iter(||
66 | for i in 0..outb.len() {
67 | let inp = inb[i];
68 | let out = inp * a0 + z1;
69 | z1 = inp * a1 + z2 - b1 * out;
70 | z2 = inp * a2 - b2 * out;
71 | outb[i] = out;
72 | }
73 | )
74 | }
75 |
76 | #[bench]
77 | fn exp2(b: &mut Bencher) {
78 | b.iter(|| {
79 | let mut y = 0.0;
80 | for i in 0..1000 {
81 | y += (0.001 * i as f32).exp2();
82 | }
83 | y
84 | })
85 | }
86 |
87 | #[bench]
88 | fn tan(b: &mut Bencher) {
89 | b.iter(|| {
90 | let mut y = 0.0;
91 | for i in 0..1000 {
92 | y += (0.001 * i as f32).tan();
93 | }
94 | y
95 | })
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/graph.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A graph runner that avoids all blocking operations, suitable for realtime threads.
16 |
17 | use std::ops::DerefMut;
18 | use std::ptr;
19 | use std::mem;
20 |
21 | use queue::Item;
22 | use module::{Module, Buffer};
23 |
24 |
25 | // maximum number of control inputs
26 | const MAX_CTRL: usize = 16;
27 |
28 | // maximum number of buffer inputs
29 | const MAX_BUF: usize = 16;
30 |
31 | const SENTINEL: usize = !0;
32 |
33 | pub struct Graph {
34 | nodes: Box<[Option- >]>,
35 |
36 | // state for topo sort; all have same len
37 | visited: Box<[VisitedState]>,
38 | // TODO: there's no compelling reason for the graph sorting
39 | // to use linked lists, we could just use one vector for the
40 | // stack and another for the result.
41 | link: Box<[usize]>,
42 | }
43 |
44 | #[derive(Copy, Clone, PartialEq)]
45 | enum VisitedState {
46 | NotVisited,
47 | Pushed,
48 | Scanned,
49 | }
50 |
51 | use self::VisitedState::*;
52 |
53 | pub enum Message {
54 | Node(Node),
55 | SetParam(SetParam),
56 | Note(Note),
57 | Quit,
58 | }
59 |
60 | impl Message {
61 | fn get_node(&self) -> Option<&Node> {
62 | match *self {
63 | Message::Node(ref node) => Some(node),
64 | _ => None,
65 | }
66 | }
67 | }
68 |
69 | pub struct Node {
70 | pub ix: usize,
71 |
72 | module: Box,
73 | // module ix and index within its out_buf slice
74 | in_buf_wiring: Box<[(usize, usize)]>,
75 | // module ix and index within its out_ctrl slice
76 | in_ctrl_wiring: Box<[(usize, usize)]>,
77 | out_bufs: Box<[Buffer]>,
78 | out_ctrl: Box<[f32]>,
79 | }
80 |
81 | /// A struct that contains the data for setting a parameter
82 | pub struct SetParam {
83 | pub ix: usize,
84 | pub param_ix: usize,
85 | pub val: f32,
86 | pub timestamp: u64,
87 | }
88 |
89 | /// A struct that represents a note on/off event
90 | pub struct Note {
91 | pub ixs: Box<[usize]>, // list of node ix's affected by this note
92 | pub midi_num: f32, // 69.0 = A4 (440Hz)
93 | pub velocity: f32, // 1 = minimum, 127 = maximum
94 | pub on: bool,
95 | pub timestamp: u64,
96 | }
97 |
98 | pub trait IntoBoxedSlice {
99 | fn into_box(self) -> Box<[T]>;
100 | }
101 |
102 | impl IntoBoxedSlice for Vec {
103 | fn into_box(self) -> Box<[T]> { self.into_boxed_slice() }
104 | }
105 |
106 | impl IntoBoxedSlice for Box<[T]> {
107 | fn into_box(self) -> Box<[T]> { self }
108 | }
109 |
110 | impl<'a, T: Clone> IntoBoxedSlice for &'a [T] {
111 | fn into_box(self) -> Box<[T]> {
112 | let vec: Vec = From::from(self);
113 | vec.into_boxed_slice()
114 | }
115 | }
116 |
117 | impl IntoBoxedSlice for [T; 0] {
118 | fn into_box(self) -> Box<[T]> { Vec::new().into_boxed_slice() }
119 | }
120 |
121 | impl IntoBoxedSlice for [T; 1] {
122 | fn into_box(self) -> Box<[T]> { self[..].into_box() }
123 | }
124 |
125 | impl IntoBoxedSlice for [T; 2] {
126 | fn into_box(self) -> Box<[T]> { self[..].into_box() }
127 | }
128 |
129 | impl Node {
130 | /// Create a new node. The index must be given, as well as the input wiring.
131 | pub fn create,
132 | B2: IntoBoxedSlice<(usize, usize)>>
133 | (module: Box, ix: usize, in_buf_wiring: B1, in_ctrl_wiring: B2) -> Node
134 | {
135 | let n_bufs = module.n_bufs_out();
136 | let mut out_bufs = Vec::with_capacity(n_bufs);
137 | for _ in 0..n_bufs {
138 | out_bufs.push(Buffer::default());
139 | }
140 | let out_bufs = out_bufs.into_boxed_slice();
141 | let out_ctrl = vec![0.0; module.n_ctrl_out()].into_boxed_slice();
142 | Node {
143 | ix: ix,
144 | module: module,
145 | in_buf_wiring: in_buf_wiring.into_box(),
146 | in_ctrl_wiring: in_ctrl_wiring.into_box(),
147 | out_bufs: out_bufs,
148 | out_ctrl: out_ctrl,
149 | }
150 | }
151 | }
152 |
153 | impl Graph {
154 | pub fn new(max_size: usize) -> Graph {
155 | // can't use vec! for nodes because Item isn't Clone
156 | let mut nodes = Vec::with_capacity(max_size);
157 | for _ in 0..max_size {
158 | nodes.push(None);
159 | }
160 | Graph {
161 | nodes: nodes.into_boxed_slice(),
162 | visited: vec![NotVisited; max_size].into_boxed_slice(),
163 | link: vec![0; max_size].into_boxed_slice(),
164 | }
165 | }
166 |
167 | /// Get the output buffers for the specified graph node. Panics if the
168 | /// index is not a valid, populated node. Lock-free.
169 | pub fn get_out_bufs(&self, ix: usize) -> &[Buffer] {
170 | &self.get_node(ix).unwrap().out_bufs
171 | }
172 |
173 | fn get_node(&self, ix: usize) -> Option<&Node> {
174 | self.nodes[ix].as_ref().and_then(|item| item.get_node())
175 | }
176 |
177 | fn get_node_mut(&mut self, ix: usize) -> Option<&mut Node> {
178 | self.nodes[ix].as_mut().and_then(|msg| match *msg.deref_mut() {
179 | Message::Node(ref mut n) => Some(n),
180 | _ => None
181 | })
182 | }
183 |
184 | pub fn get_module_mut(&mut self, ix: usize) -> &mut Module {
185 | self.get_node_mut(ix).unwrap().module.deref_mut()
186 | }
187 |
188 | /// Replace a graph node with a new item, returning the old value.
189 | /// Lock-free.
190 | pub fn replace(&mut self, ix: usize, item: Option
- >) -> Option
- > {
191 | let mut old_item = mem::replace(&mut self.nodes[ix], item);
192 | if let Some(ref mut old) = old_item {
193 | if let Message::Node(ref mut old_node) = *old.deref_mut() {
194 | self.get_node_mut(ix).unwrap().module.migrate(old_node.module.deref_mut());
195 | }
196 | }
197 | old_item
198 | }
199 |
200 | fn run_one_module(&mut self, module_ix: usize, ctrl: &mut [f32; MAX_CTRL],
201 | bufs: &mut [*const Buffer; MAX_BUF], timestamp: u64)
202 | {
203 | {
204 | let this = self.get_node(module_ix).unwrap();
205 | for (i, &(mod_ix, buf_ix)) in this.in_buf_wiring.iter().enumerate() {
206 | // otherwise the transmute would cause aliasing
207 | assert!(module_ix != mod_ix);
208 | bufs[i] = &self.get_out_bufs(mod_ix)[buf_ix];
209 | }
210 | for (i, &(mod_ix, ctrl_ix)) in this.in_ctrl_wiring.iter().enumerate() {
211 | ctrl[i] = self.get_node(mod_ix).unwrap().out_ctrl[ctrl_ix];
212 | }
213 | }
214 | let this = self.get_node_mut(module_ix).unwrap();
215 | let buf_in = unsafe { mem::transmute(&bufs[..this.in_buf_wiring.len()]) };
216 | let ctrl_in = &ctrl[..this.in_ctrl_wiring.len()];
217 | this.module.process_ts(ctrl_in, &mut this.out_ctrl, buf_in, &mut this.out_bufs,
218 | timestamp);
219 | }
220 |
221 | fn topo_sort(&mut self, root: usize) -> usize {
222 | // initially the result linked list is empty
223 | let mut head = SENTINEL;
224 | let mut tail = SENTINEL;
225 |
226 | // initially the stack just contains root
227 | self.link[root] = SENTINEL;
228 | let mut stack = root;
229 | self.visited[root] = Pushed;
230 |
231 | while stack != SENTINEL {
232 | if self.visited[stack] == Pushed {
233 | self.visited[stack] = Scanned;
234 | let node = self.nodes[stack].as_ref().and_then(|item| item.get_node()).unwrap();
235 | for &(ix, _) in node.in_buf_wiring.iter().chain(node.in_ctrl_wiring.iter()) {
236 | if self.visited[ix] == NotVisited {
237 | self.visited[ix] = Pushed;
238 | // push ix on stack
239 | self.link[ix] = stack;
240 | stack = ix;
241 | }
242 | }
243 | }
244 | if self.visited[stack] == Scanned {
245 | let next = self.link[stack];
246 |
247 | // add `stack` to end of result linked list
248 | self.link[stack] = SENTINEL;
249 | if head == SENTINEL {
250 | head = stack;
251 | }
252 | if tail != SENTINEL {
253 | self.link[tail] = stack;
254 | }
255 | tail = stack;
256 |
257 | // pop stack
258 | stack = next;
259 | }
260 | }
261 | head
262 | }
263 |
264 | /// Run the graph. On return, the buffer for the given root node will be
265 | /// filled. Designed to be lock-free.
266 | pub fn run_graph(&mut self, root: usize, timestamp: u64) {
267 | // scratch space, here to amortize the initialization costs
268 | let mut ctrl = [0.0f32; MAX_CTRL];
269 | let mut bufs = [ptr::null(); MAX_BUF];
270 |
271 | // TODO: don't do topo sort every time, reuse if graph hasn't changed
272 | let mut ix = self.topo_sort(root);
273 | while ix != SENTINEL {
274 | self.run_one_module(ix, &mut ctrl, &mut bufs, timestamp);
275 | self.visited[ix] = NotVisited; // reset state for next topo sort
276 | ix = self.link[ix];
277 | }
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Audio processing modules, packaged as a library.
16 |
17 | #[macro_use]
18 | extern crate lazy_static;
19 |
20 | pub mod queue;
21 | pub mod graph;
22 | pub mod module;
23 | pub mod modules;
24 | pub mod worker;
25 |
26 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/module.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! The interface for a module that does some audio processing.
16 |
17 | use std::any::Any;
18 |
19 | pub const N_SAMPLES_PER_CHUNK: usize = 64;
20 |
21 | pub struct Buffer {
22 | // TODO: simd alignment
23 | buf: [f32; N_SAMPLES_PER_CHUNK],
24 | // Will probably get special zero handling
25 | }
26 |
27 | impl Buffer {
28 | pub fn set_zero(&mut self) {
29 | *self = Default::default();
30 | }
31 |
32 | pub fn get(&self) -> &[f32; N_SAMPLES_PER_CHUNK] {
33 | &self.buf
34 | }
35 |
36 | pub fn get_mut(&mut self) -> &mut [f32; N_SAMPLES_PER_CHUNK] {
37 | &mut self.buf
38 | }
39 | }
40 |
41 | impl Default for Buffer {
42 | fn default() -> Buffer {
43 | Buffer {
44 | buf: [0.0; N_SAMPLES_PER_CHUNK]
45 | }
46 | }
47 | }
48 |
49 | pub trait Module: MyToAny {
50 | /// Report the number of buffers this module is expected to generate.
51 | fn n_bufs_out(&self) -> usize { 0 }
52 |
53 | /// Report the number of control values this module is expected to generate.
54 | fn n_ctrl_out(&self) -> usize { 0 }
55 |
56 | /// Support for downcasting
57 | fn to_any(&mut self) -> &mut Any { MyToAny::my_to_any(self) }
58 |
59 | /// Give modules an opportunity to migrate state from the previous module
60 | /// when it is replaced.
61 | #[allow(unused)]
62 | fn migrate(&mut self, old: &mut Module) {}
63 |
64 | /// Process one chunk of audio. Implementations are expected to be lock-free.
65 | fn process(&mut self, control_in: &[f32], control_out: &mut [f32],
66 | buf_in: &[&Buffer], buf_out: &mut [Buffer]);
67 |
68 | /// Process one chunk of audio. Implementations are expected to be lock-free.
69 | /// Implementations should override this method if they require a timestamp,
70 | /// otherwise `process`.
71 | #[allow(unused)]
72 | fn process_ts(&mut self, control_in: &[f32], control_out: &mut [f32],
73 | buf_in: &[&Buffer], buf_out: &mut [Buffer], timestamp: u64)
74 | {
75 | self.process(control_in, control_out, buf_in, buf_out);
76 | }
77 |
78 | /// Set a param (or, in general, accept a control message).
79 | #[allow(unused)]
80 | fn set_param(&mut self, param_ix: usize, val: f32, timestamp: u64) {}
81 |
82 | /// Handle a note on or off message.
83 | #[allow(unused)]
84 | fn handle_note(&mut self, midi_num: f32, velocity: f32, on: bool) {}
85 | }
86 |
87 | pub trait MyToAny {
88 | fn my_to_any(&mut self) -> &mut Any;
89 | }
90 |
91 | impl MyToAny for T {
92 | fn my_to_any(&mut self) -> &mut Any { self }
93 | }
94 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/adsr.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Attack, decay, sustain, release.
16 |
17 | use module::{Module, Buffer};
18 |
19 | pub struct Adsr {
20 | value: f32,
21 | state: State,
22 | }
23 |
24 | enum State {
25 | Quiet,
26 | Attack, // note is on, rising
27 | Decay, // note is on, falling
28 | Sustain, // note is on, steady
29 | Release, // note is off, falling
30 | }
31 |
32 | use self::State::*;
33 |
34 | impl Adsr {
35 | pub fn new() -> Adsr {
36 | Adsr {
37 | value: -24.0,
38 | state: Quiet,
39 | }
40 | }
41 | }
42 |
43 | impl Module for Adsr {
44 | fn n_ctrl_out(&self) -> usize { 1 }
45 |
46 | fn handle_note(&mut self, _midi_num: f32, _velocity: f32, on: bool) {
47 | if on {
48 | self.state = Attack;
49 | } else {
50 | self.state = Release;
51 | }
52 | }
53 |
54 | fn process(&mut self, control_in: &[f32], control_out: &mut [f32],
55 | _buf_in: &[&Buffer], _buf_out: &mut [Buffer])
56 | {
57 | match self.state {
58 | Quiet => (),
59 | Attack => {
60 | let mut l = self.value.exp2();
61 | l += (-control_in[0]).exp2();
62 | if l >= 1.0 {
63 | l = 1.0;
64 | self.state = Decay;
65 | }
66 | self.value = l.log2();
67 | }
68 | Decay => {
69 | let sustain = control_in[2] - 6.0;
70 | self.value -= (-control_in[1]).exp2();
71 | if self.value < sustain {
72 | self.value = sustain;
73 | self.state = Sustain;
74 | }
75 | }
76 | Sustain => {
77 | let sustain = control_in[2] - 6.0;
78 | self.value = sustain;
79 | }
80 | Release => {
81 | self.value -= (-control_in[3]).exp2();
82 | if self.value < -24.0 {
83 | self.value = -24.0;
84 | self.state = Quiet;
85 | }
86 | }
87 | }
88 | control_out[0] = self.value;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/biquad.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! An implementation of biquad filters.
16 |
17 |
18 | use std::f32::consts;
19 |
20 | use module::{Module, Buffer};
21 |
22 | pub struct Biquad {
23 | sr_offset: f32,
24 | state: [f32; 2],
25 | matrix: [f32; 16],
26 | }
27 |
28 | impl Biquad {
29 | pub fn new(sample_rate: f32) -> Biquad {
30 | Biquad {
31 | sr_offset: consts::PI.log2() - sample_rate.log2(),
32 | state: [0.0; 2],
33 | matrix: [0.0; 16],
34 | }
35 | }
36 | }
37 |
38 | struct StateParams {
39 | a: [f32; 4], // 2x2 matrix, column-major order
40 | b: [f32; 2],
41 | c: [f32; 2],
42 | d: f32,
43 | }
44 |
45 | // `log_f` is log2 of frequency relative to sampling rate, e.g.
46 | // -1.0 is the Nyquist frequency.
47 | fn calc_g(log_f: f32) -> f32 {
48 | // TODO: use lut to speed this up
49 | let f = log_f.exp2(); // pi has already been factored into sr_offset
50 | f.tan()
51 | }
52 |
53 | // Compute parameters for low-pass state variable filter.
54 | // `res` ranges from 0 (no resonance) to 1 (self-oscillating)
55 | fn svf_lp(log_f: f32, res: f32) -> StateParams {
56 | let g = calc_g(log_f);
57 | let k = 2.0 - 2.0 * res;
58 | let a1 = 2.0 / (1.0 + g * (g + k));
59 | let a2 = g * a1;
60 | let a3 = g * a2;
61 | let a = [a1 - 1.0, a2, -a2, 1.0 - a3];
62 | let b = [a2, a3];
63 | let c = [0.5 * a2, 1.0 - 0.5 * a3];
64 | let d = 0.5 * a3;
65 | StateParams { a: a, b: b, c: c, d: d }
66 | }
67 |
68 | // See https://github.com/google/music-synthesizer-for-android/blob/master/lab/Second%20order%20sections%20in%20matrix%20form.ipynb
69 | fn raise_matrix(params: StateParams) -> [f32; 16] {
70 | let StateParams { a, b, c, d } = params;
71 | [d, c[0] * b[0] + c[1] * b[1],
72 | a[0] * b[0] + a[2] * b[1], a[1] * b[0] + a[3] * b[1],
73 |
74 | 0.0, d, b[0], b[1],
75 |
76 | c[0], c[0] * a[0] + c[1] * a[1],
77 | a[0] * a[0] + a[2] * a[1], a[1] * a[0] + a[3] * a[1],
78 |
79 | c[1], c[0] * a[2] + c[1] * a[3],
80 | a[0] * a[2] + a[2] * a[3], a[1] * a[2] + a[3] * a[3],
81 | ]
82 | }
83 |
84 | impl Module for Biquad {
85 | fn n_bufs_out(&self) -> usize { 1 }
86 |
87 | fn process(&mut self, control_in: &[f32], _control_out: &mut [f32],
88 | buf_in: &[&Buffer], buf_out: &mut [Buffer])
89 | {
90 | let log_f = control_in[0];
91 | let res = control_in[1];
92 | // TODO: maybe avoid recomputing matrix if params haven't changed
93 | let params = svf_lp(log_f + self.sr_offset, res);
94 | self.matrix = raise_matrix(params);
95 | let inb = buf_in[0].get();
96 | let out = buf_out[0].get_mut();
97 | let m = &self.matrix;
98 | let mut i = 0;
99 | let mut state0 = self.state[0];
100 | let mut state1 = self.state[1];
101 | while i < out.len() {
102 | let x0 = inb[i];
103 | let x1 = inb[i + 1];
104 | let y0 = m[0] * x0 + m[4] * x1 + m[8] * state0 + m[12] * state1;
105 | let y1 = m[1] * x0 + m[5] * x1 + m[9] * state0 + m[13] * state1;
106 | let y2 = m[2] * x0 + m[6] * x1 + m[10] * state0 + m[14] * state1;
107 | let y3 = m[3] * x0 + m[7] * x1 + m[11] * state0 + m[15] * state1;
108 | out[i] = y0;
109 | out[i + 1] = y1;
110 | state0 = y2;
111 | state1 = y3;
112 | i += 2;
113 | }
114 | self.state[0] = state0;
115 | self.state[1] = state1;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/buzz.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A simple module that makes a harsh buzzing noise.
16 |
17 | use module::{Module, Buffer, N_SAMPLES_PER_CHUNK};
18 |
19 | pub struct Buzz;
20 |
21 | impl Module for Buzz {
22 | fn n_bufs_out(&self) -> usize { 1 }
23 |
24 | fn process(&mut self, _control_in: &[f32], _control_out: &mut [f32],
25 | _buf_in: &[&Buffer], buf_out: &mut [Buffer])
26 | {
27 | let out = buf_out[0].get_mut();
28 | for i in 0..out.len() {
29 | out[i] = i as f32 * (2.0 / N_SAMPLES_PER_CHUNK as f32) - 1.0;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/const_ctrl.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A simple module that just sets a constant control parameter.
16 |
17 | use module::{Module, Buffer};
18 |
19 | pub struct ConstCtrl {
20 | value: f32,
21 | }
22 |
23 | impl ConstCtrl {
24 | pub fn new(value: f32) -> ConstCtrl {
25 | ConstCtrl { value: value }
26 | }
27 | }
28 |
29 | impl Module for ConstCtrl {
30 | fn n_ctrl_out(&self) -> usize { 1 }
31 |
32 | fn process(&mut self, _control_in: &[f32], control_out: &mut [f32],
33 | _buf_in: &[&Buffer], _buf_out: &mut [Buffer])
34 | {
35 | control_out[0] = self.value;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/gain.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A simple module that applies gain to the input. Gain is interpreted
16 | //! as log2 of absolute gain. Linear smoothing applied.
17 |
18 | use module::{Module, Buffer};
19 |
20 | pub struct Gain {
21 | last_g: f32,
22 | }
23 |
24 | impl Gain {
25 | pub fn new() -> Gain {
26 | Gain {
27 | last_g: 0.0,
28 | }
29 | }
30 | }
31 |
32 | impl Module for Gain {
33 | fn n_bufs_out(&self) -> usize { 1 }
34 |
35 | fn process(&mut self, control_in: &[f32], _control_out: &mut [f32],
36 | buf_in: &[&Buffer], buf_out: &mut [Buffer])
37 | {
38 | let ctrl = control_in[0];
39 | let g = ctrl.exp2();
40 | let out = buf_out[0].get_mut();
41 | let dg = (g - self.last_g) * (1.0 / out.len() as f32);
42 | let mut y = self.last_g + dg;
43 | self.last_g = g;
44 | let buf = buf_in[0].get();
45 | for i in 0..out.len() {
46 | out[i] = buf[i] * y;
47 | y += dg;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A collection of audio processing modules.
16 |
17 | mod sum;
18 | mod buzz;
19 | mod sin;
20 | mod saw;
21 | mod biquad;
22 | mod const_ctrl;
23 | mod smooth_ctrl;
24 | mod note_pitch;
25 | mod adsr;
26 | mod gain;
27 |
28 | pub use self::sum::Sum;
29 | pub use self::buzz::Buzz;
30 | pub use self::sin::Sin;
31 | pub use self::saw::Saw;
32 | pub use self::biquad::Biquad;
33 | pub use self::const_ctrl::ConstCtrl;
34 | pub use self::smooth_ctrl::SmoothCtrl;
35 | pub use self::note_pitch::NotePitch;
36 | pub use self::adsr::Adsr;
37 | pub use self::gain::Gain;
38 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/note_pitch.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A simple module that just holds a note at a constant pitch.
16 |
17 | use module::{Module, Buffer};
18 |
19 | pub struct NotePitch {
20 | value: f32,
21 | }
22 |
23 | impl NotePitch {
24 | pub fn new() -> NotePitch {
25 | NotePitch { value: 0.0 }
26 | }
27 | }
28 |
29 | impl Module for NotePitch {
30 | fn n_ctrl_out(&self) -> usize { 1 }
31 |
32 | fn handle_note(&mut self, midi_num: f32, _velocity: f32, on: bool) {
33 | if on {
34 | self.value = midi_num * (1.0 / 12.0) + (440f32.log2() - 69.0 / 12.0);
35 | }
36 | }
37 |
38 | fn process(&mut self, _control_in: &[f32], control_out: &mut [f32],
39 | _buf_in: &[&Buffer], _buf_out: &mut [Buffer])
40 | {
41 | control_out[0] = self.value;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/saw.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A module that makes a band-limited sawtooth wave.
16 |
17 | use std::f32::consts;
18 | use std::ops::Deref;
19 | use std::cmp::min;
20 |
21 | use module::{Module, Buffer};
22 |
23 | const LG_N_SAMPLES: usize = 10;
24 | const N_SAMPLES: usize = (1 << LG_N_SAMPLES);
25 | const N_PARTIALS_MAX: usize = N_SAMPLES / 2;
26 |
27 | const LG_SLICES_PER_OCTAVE: usize = 2;
28 | const SLICES_PER_OCTAVE: usize = (1 << LG_SLICES_PER_OCTAVE);
29 | const N_SLICES: usize = 36;
30 | // 0.5 * (log(440./44100) / log(2) + log(440./48000) / log(2) + 2./12) + 1./64 - 3
31 | const SLICE_BASE: f32 = -9.609300863499751;
32 | const SLICE_OVERLAP: f32 = 0.125;
33 |
34 | lazy_static! {
35 | static ref SAWTAB: [[f32; N_SAMPLES + 1]; N_SLICES] = {
36 | let mut t = [[0.0; N_SAMPLES + 1]; N_SLICES];
37 |
38 | let mut lut = [0.0; N_SAMPLES / 2];
39 | let slice_inc = (1.0 / SLICES_PER_OCTAVE as f32).exp2();
40 | let mut f_0 = slice_inc.powi(N_SLICES as i32 - 1) * SLICE_BASE.exp2();
41 | let mut n_partials_last = 0;
42 | for j in (0..N_SLICES).rev() {
43 | let n_partials = (0.5 / f_0) as usize;
44 | let n_partials = min(n_partials, N_PARTIALS_MAX);
45 | for k in n_partials_last + 1 .. n_partials + 1 {
46 | let mut scale = -consts::FRAC_2_PI / k as f32;
47 | if N_PARTIALS_MAX - k <= N_PARTIALS_MAX >> 2 {
48 | scale *= (N_PARTIALS_MAX - k) as f32 * (1.0 / (N_PARTIALS_MAX >> 2) as f32);
49 | }
50 | let dphase = k as f64 * (2.0 * ::std::f64::consts::PI / N_SAMPLES as f64);
51 | let c = dphase.cos();
52 | let s = dphase.sin();
53 | let mut u = scale as f64;
54 | let mut v = 0.0f64;
55 | for i in 0..(N_SAMPLES / 2) {
56 | lut[i] += v;
57 | let t = u * s + v * c;
58 | u = u * c - v * s;
59 | v = t;
60 | }
61 | }
62 | for i in 1..(N_SAMPLES / 2) {
63 | let value = lut[i] as f32;
64 | t[j][i] = value;
65 | t[j][N_SAMPLES - i] = -value;
66 | }
67 | // note: values at 0, N_SAMPLES / 2 and N_SAMPLES all 0
68 | n_partials_last = n_partials;
69 | f_0 *= 1.0 / slice_inc;
70 | }
71 | t
72 | };
73 | }
74 |
75 | pub struct Saw {
76 | sr_offset: f32,
77 | phase: f32,
78 | }
79 |
80 | impl Saw {
81 | pub fn new(sample_rate: f32) -> Saw {
82 | // make initialization happen here so it doesn't happen in process
83 | let _ = SAWTAB.deref();
84 | Saw {
85 | sr_offset: LG_N_SAMPLES as f32 - sample_rate.log2(),
86 | phase: 0.0,
87 | }
88 | }
89 | }
90 |
91 | fn compute(tab_ix: usize, phasefrac: f32) -> f32 {
92 | (tab_ix as f32 + phasefrac) * (2.0 / N_SAMPLES as f32) - 1.0
93 | }
94 |
95 | impl Module for Saw {
96 | fn n_bufs_out(&self) -> usize { 1 }
97 |
98 | fn process(&mut self, control_in: &[f32], _control_out: &mut [f32],
99 | _buf_in: &[&Buffer], buf_out: &mut [Buffer])
100 | {
101 | let logf = control_in[0] + self.sr_offset;
102 | let slice_off = -SLICE_BASE - LG_N_SAMPLES as f32;
103 | let slice = (logf + slice_off) * SLICES_PER_OCTAVE as f32;
104 | //println!("logf={}, slice={}", logf, slice);
105 | let freq = logf.exp2();
106 | let out = buf_out[0].get_mut();
107 | let mut phase = self.phase;
108 | if slice < -SLICE_OVERLAP {
109 | // pure computation
110 | for i in 0..out.len() {
111 | let phaseint = phase as i32;
112 | let tab_ix = phaseint as usize % N_SAMPLES;
113 | let phasefrac = phase - phaseint as f32;
114 | out[i] = compute(tab_ix, phasefrac);
115 | phase += freq;
116 | }
117 | } else if slice < 0.0 {
118 | // interpolate between computation and slice 0
119 | let tab = &SAWTAB[0];
120 | let yi = slice * (-1.0 / SLICE_OVERLAP); // 1 = comp, 0 = lut
121 | for i in 0..out.len() {
122 | let phaseint = phase as i32;
123 | let tab_ix = phaseint as usize % N_SAMPLES;
124 | let phasefrac = phase - phaseint as f32;
125 | let yc = compute(tab_ix, phasefrac);
126 | let y0 = tab[tab_ix];
127 | let y1 = tab[tab_ix + 1];
128 | let yl = y0 + (y1 - y0) * phasefrac;
129 | out[i] = yl + yi * (yc - yl);
130 | phase += freq;
131 | }
132 | } else {
133 | let tab = SAWTAB.deref();
134 | let sliceint = slice as u32;
135 | let slicefrac = slice - sliceint as f32;
136 | if slicefrac < 1.0 - SLICE_OVERLAP || sliceint >= N_SLICES as u32 - 1 {
137 | // do lookup from a single slice
138 | let tab = &tab[min(sliceint as usize, N_SLICES - 1)];
139 | for i in 0..out.len() {
140 | let phaseint = phase as i32;
141 | let tab_ix = phaseint as usize % N_SAMPLES;
142 | let y0 = tab[tab_ix];
143 | let y1 = tab[tab_ix + 1];
144 | out[i] = y0 + (y1 - y0) * (phase - phaseint as f32);
145 | phase += freq;
146 | }
147 | } else {
148 | // interpolate between two slices
149 | let tab0 = &tab[sliceint as usize];
150 | let tab1 = &tab[1 + sliceint as usize];
151 | let yi = (slicefrac - (1.0 - SLICE_OVERLAP)) * (1.0 / SLICE_OVERLAP);
152 | for i in 0..out.len() {
153 | let phaseint = phase as i32;
154 | let tab_ix = phaseint as usize % N_SAMPLES;
155 | let phasefrac = phase - phaseint as f32;
156 | let y00 = tab0[tab_ix];
157 | let y01 = tab0[tab_ix + 1];
158 | let y0 = y00 + (y01 - y00) * phasefrac;
159 | let y10 = tab1[tab_ix];
160 | let y11 = tab1[tab_ix + 1];
161 | let y1 = y10 + (y11 - y10) * phasefrac;
162 | out[i] = y0 + yi * (y1 - y0);
163 | phase += freq;
164 | }
165 | }
166 | }
167 | let phaseint = phase as i32;
168 | self.phase = phase - (phaseint & -(N_SAMPLES as i32)) as f32;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/sin.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A simple module that makes a sine wave.
16 |
17 | use std::f32::consts;
18 | use std::ops::Deref;
19 |
20 | use module::{Module, Buffer};
21 |
22 | const LG_N_SAMPLES: usize = 10;
23 | const N_SAMPLES: usize = (1 << LG_N_SAMPLES);
24 |
25 | lazy_static! {
26 | static ref SINTAB: [f32; N_SAMPLES + 1] = {
27 | let mut t = [0.0; N_SAMPLES + 1];
28 | let dth = 2.0 * consts::PI / (N_SAMPLES as f32);
29 | for i in 0..N_SAMPLES/2 {
30 | let s = (i as f32 * dth).sin();
31 | t[i] = s;
32 | t[i + N_SAMPLES / 2] = -s;
33 | }
34 | // TODO: more optimization is possible
35 | // t[N_SAMPLES] = t[0], but not necessary because it's already 0
36 | t
37 | };
38 | }
39 |
40 | pub struct Sin {
41 | sr_offset: f32,
42 | phase: f32,
43 | }
44 |
45 | impl Sin {
46 | pub fn new(sample_rate: f32) -> Sin {
47 | // make initialization happen here so it doesn't happen in process
48 | let _ = SINTAB.deref();
49 | Sin {
50 | sr_offset: LG_N_SAMPLES as f32 - sample_rate.log2(),
51 | phase: 0.0,
52 | }
53 | }
54 | }
55 |
56 | impl Module for Sin {
57 | fn n_bufs_out(&self) -> usize { 1 }
58 |
59 | // Example of migration, although replacing one Sin module with another
60 | // isn't going to have much use unless the sample rate is changing. But
61 | // if so, at least the phase will be continuous now.
62 | fn migrate(&mut self, old: &mut Module) {
63 | if let Some(old_sin) = old.to_any().downcast_ref::() {
64 | self.phase = old_sin.phase;
65 | }
66 | }
67 |
68 | fn process(&mut self, control_in: &[f32], _control_out: &mut [f32],
69 | _buf_in: &[&Buffer], buf_out: &mut [Buffer])
70 | {
71 | let freq = (control_in[0] + self.sr_offset).exp2();
72 | let tab = SINTAB.deref();
73 | let out = buf_out[0].get_mut();
74 | let mut phase = self.phase;
75 | for i in 0..out.len() {
76 | let phaseint = phase as i32;
77 | let tab_ix = phaseint as usize % N_SAMPLES;
78 | let y0 = tab[tab_ix];
79 | let y1 = tab[tab_ix + 1];
80 | out[i] = y0 + (y1 - y0) * (phase - phaseint as f32);
81 | phase += freq;
82 | }
83 | let phaseint = phase as i32;
84 | self.phase = phase - (phaseint & -(N_SAMPLES as i32)) as f32;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/smooth_ctrl.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A module that smooths parameters (optimized for midi controllers).
16 |
17 | use module::{Module, Buffer};
18 |
19 | pub struct SmoothCtrl {
20 | rate: f32, // smoothed rate (units of updates per ms)
21 | rategoal: f32, // unsmoothed rate
22 | t: u64, // timestamp of current time
23 | last_set_t: u64, // timestamp of last param setting
24 | inp: f32, // raw, unsmoothed value
25 | mid: f32, // result of 1 pole of lowpass filtering
26 | out: f32, // result of 2 poles of lowpass filtering
27 | }
28 |
29 | impl SmoothCtrl {
30 | pub fn new(value: f32) -> SmoothCtrl {
31 | SmoothCtrl {
32 | rate: 0.0,
33 | rategoal: 0.0,
34 | t: 0,
35 | last_set_t: 0,
36 | inp: value,
37 | mid: value,
38 | out: value,
39 | }
40 | }
41 | }
42 |
43 | impl Module for SmoothCtrl {
44 | fn n_ctrl_out(&self) -> usize { 1 }
45 |
46 | // maybe empty impl belongs in Module?
47 | fn process(&mut self, _control_in: &[f32], _control_out: &mut [f32],
48 | _buf_in: &[&Buffer], _buf_out: &mut [Buffer])
49 | {
50 | }
51 |
52 | fn process_ts(&mut self, _control_in: &[f32], control_out: &mut [f32],
53 | _buf_in: &[&Buffer], _buf_out: &mut [Buffer], timestamp: u64)
54 | {
55 | self.advance_to(timestamp);
56 | control_out[0] = self.out;
57 | }
58 |
59 | fn set_param(&mut self, _param_ix: usize, val: f32, timestamp: u64) {
60 | self.advance_to(timestamp);
61 | if timestamp > self.last_set_t {
62 | const SLOWEST_RATE: f32 = 0.005; // 0.2s
63 | let mut rategoal = 1e6 / ((timestamp - self.last_set_t) as f32);
64 | if rategoal <= SLOWEST_RATE {
65 | rategoal = SLOWEST_RATE;
66 | }
67 | self.rategoal = rategoal;
68 | self.last_set_t = timestamp;
69 | }
70 | self.inp = val;
71 | }
72 | }
73 |
74 | impl SmoothCtrl {
75 | // Analytic solutions of the 3 1-pole lowpass filters under step invariant assumption.
76 | fn advance_to(&mut self, t: u64) {
77 | if t <= self.t {
78 | return;
79 | }
80 | let dt = (t - self.t) as f32 * 1e-6; // in ms
81 | const RATE_TC: f32 = 10.0; // in ms
82 | let erate = ((-1.0 / RATE_TC) * dt).exp();
83 | let warped_dt = dt * self.rategoal + RATE_TC * (self.rate - self.rategoal) * (1.0 - erate);
84 | self.rate = self.rategoal + (self.rate - self.rategoal) * erate;
85 | let ewarp = (-warped_dt).exp();
86 | self.out = self.inp + (self.out - self.inp + (self.mid - self.inp) * warped_dt) * ewarp;
87 | self.mid = self.inp + (self.mid - self.inp) * ewarp;
88 | self.t = t;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/modules/sum.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A simple module that just sums the inputs.
16 |
17 | use module::{Module, Buffer};
18 |
19 | pub struct Sum;
20 |
21 | impl Module for Sum {
22 | fn n_bufs_out(&self) -> usize { 1 }
23 |
24 | fn process(&mut self, _control_in: &[f32], _control_out: &mut [f32],
25 | buf_in: &[&Buffer], buf_out: &mut [Buffer])
26 | {
27 | let out = buf_out[0].get_mut();
28 | for i in 0..out.len() {
29 | out[i] = 0.0;
30 | }
31 | for buf in buf_in {
32 | let buf = buf.get();
33 | for i in 0..out.len() {
34 | out[i] += buf[i];
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/queue.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A lock-free queue suitable for real-time audio threads
16 |
17 | use std::sync::atomic::{AtomicPtr, Ordering};
18 | use std::sync::atomic::Ordering::{Relaxed, Release};
19 | use std::sync::Arc;
20 | use std::thread;
21 | use std::ptr;
22 | use std::ops::{Deref, DerefMut};
23 | use std::marker::PhantomData;
24 | use std::time;
25 |
26 | // The implementation is a fairly straightforward Treiber stack.
27 |
28 | struct Node {
29 | payload: T,
30 | child: *mut Node,
31 | }
32 |
33 | impl Node {
34 | // reverse singly-linked list in place
35 | unsafe fn reverse(mut p: *mut Node) -> *mut Node {
36 | let mut q = ptr::null_mut();
37 | while !p.is_null() {
38 | let element = p;
39 | p = (*element).child;
40 | (*element).child = q;
41 | q = element;
42 | }
43 | q
44 | }
45 | }
46 |
47 | /// A structure that owns a value. It acts a lot like `Box`, but has the
48 | /// special property that it can be sent back over a channel with zero
49 | /// allocation.
50 | ///
51 | /// Note: in the current implementation, dropping an `Item` just leaks the
52 | /// storage.
53 | pub struct Item {
54 | ptr: *mut Node,
55 | // TODO: can use NonZero once that stabilizes, for optimization
56 | // TODO: does this need a PhantomData marker?
57 | }
58 | // TODO: it would be great to disable drop
59 |
60 | impl Item {
61 | pub fn make_item(payload: T) -> Item {
62 | let ptr = Box::into_raw(Box::new(Node {
63 | payload: payload,
64 | child: ptr::null_mut(),
65 | }));
66 | Item { ptr: ptr }
67 | }
68 | }
69 |
70 | impl Deref for Item {
71 | type Target = T;
72 |
73 | fn deref(&self) -> &T {
74 | unsafe { &(*self.ptr).payload }
75 | }
76 | }
77 |
78 | impl DerefMut for Item {
79 | fn deref_mut(&mut self) -> &mut T {
80 | unsafe { &mut (*self.ptr).payload }
81 | }
82 | }
83 |
84 | pub struct Queue {
85 | head: AtomicPtr>,
86 | }
87 |
88 | // implement send (so queue can be transferred into worker thread)
89 | // but not sync (to enforce spsc, which avoids ABA)
90 | unsafe impl Send for Sender {}
91 | pub struct Sender {
92 | queue: Arc>,
93 | _marker: PhantomData<*const T>,
94 | }
95 |
96 | unsafe impl Send for Receiver {}
97 | pub struct Receiver {
98 | queue: Arc>,
99 | _marker: PhantomData<*const T>,
100 | }
101 |
102 | impl Sender {
103 | /// Enqueue a value into the queue. Note: this method allocates.
104 | pub fn send(&self, payload: T) {
105 | self.queue.send(payload);
106 | }
107 |
108 | /// Enqueue a value held in an `Item` into the queue. This method does
109 | /// not allocate.
110 | pub fn send_item(&self, item: Item) {
111 | self.queue.send_item(item);
112 | }
113 | }
114 |
115 | impl Receiver {
116 | /// Dequeue all of the values waiting in the queue, and return an iterator
117 | /// that transfers ownership of those values. Note: the iterator
118 | /// will deallocate.
119 | pub fn recv(&self) -> QueueMoveIter {
120 | self.queue.recv()
121 | }
122 |
123 | /// Dequeue all of the values waiting in the queue, and return an iterator
124 | /// that transfers ownership of those values into `Item` structs.
125 | /// Neither this method nor the iterator do any allocation.
126 | pub fn recv_items(&self) -> QueueItemIter {
127 | self.queue.recv_items()
128 | }
129 | }
130 |
131 | impl Queue {
132 | /// Create a new queue, and return endpoints for sending and receiving.
133 | pub fn new() -> (Sender, Receiver) {
134 | let queue = Arc::new(Queue {
135 | head: AtomicPtr::new(ptr::null_mut()),
136 | });
137 | (Sender {
138 | queue: queue.clone(),
139 | _marker: Default::default(),
140 | },
141 | Receiver {
142 | queue: queue,
143 | _marker: Default::default(),
144 | })
145 | }
146 |
147 | fn send(&self, payload: T) {
148 | self.send_item(Item::make_item(payload));
149 | }
150 |
151 | fn recv(&self) -> QueueMoveIter {
152 | unsafe { QueueMoveIter(Node::reverse(self.pop_all())) }
153 | }
154 |
155 | fn send_item(&self, item: Item) {
156 | self.push_raw(item.ptr);
157 | }
158 |
159 | fn recv_items(&self) -> QueueItemIter {
160 | unsafe { QueueItemIter(Node::reverse(self.pop_all())) }
161 | }
162 |
163 | fn push_raw(&self, n: *mut Node) {
164 | let mut old_ptr = self.head.load(Relaxed);
165 | loop {
166 | unsafe { (*n).child = old_ptr; }
167 | match self.head.compare_exchange_weak(old_ptr, n, Release, Relaxed) {
168 | Ok(_) => break,
169 | Err(old) => old_ptr = old,
170 | }
171 | }
172 | }
173 |
174 | // yields linked list in reverse order as sent
175 | fn pop_all(&self) -> *mut Node {
176 | self.head.swap(ptr::null_mut(), Ordering::Acquire)
177 | }
178 | }
179 |
180 | /// An iterator yielding an `Item` for each value dequeued by a `recv_items` call.
181 | pub struct QueueItemIter(*mut Node);
182 |
183 | impl Iterator for QueueItemIter {
184 | type Item = Item;
185 | fn next(&mut self) -> Option
- > {
186 | unsafe {
187 | let result = self.0.as_mut();
188 | if !self.0.is_null() {
189 | self.0 = (*self.0).child;
190 | }
191 | result.map(|ptr| Item{ ptr: ptr })
192 | }
193 | }
194 | }
195 |
196 | /// An iterator yielding the values dequeued by a `recv` call.
197 | pub struct QueueMoveIter(*mut Node);
198 |
199 | impl Iterator for QueueMoveIter {
200 | type Item = T;
201 | fn next(&mut self) -> Option {
202 | unsafe {
203 | if self.0.is_null() {
204 | None
205 | } else {
206 | let result = *Box::from_raw(self.0);
207 | self.0 = result.child;
208 | Some(result.payload)
209 | }
210 | }
211 | }
212 | }
213 |
214 | impl Drop for QueueMoveIter {
215 | fn drop(&mut self) {
216 | self.all(|_| true);
217 | }
218 | }
219 |
220 | // Use case code below, to be worked in a separate module. Would also be
221 | // a good basis for a test.
222 |
223 | struct Worker {
224 | to_worker: Receiver,
225 | from_worker: Sender,
226 | }
227 |
228 | impl Worker {
229 | fn work(&mut self) {
230 | let mut things = Vec::new();
231 |
232 | let start = time::Instant::now();
233 | loop {
234 | for node in self.to_worker.recv_items() {
235 | things.push(node);
236 | }
237 | if things.len() >= 1000 {
238 | break;
239 | }
240 | thread::sleep(time::Duration::new(0, 5000));
241 | }
242 | let elapsed = start.elapsed();
243 | for thing in things {
244 | self.from_worker.send_item(thing);
245 | }
246 | println!("#total time: {:?}", elapsed);
247 | }
248 | }
249 |
250 | pub fn try_queue() {
251 | let (tx, to_worker) = Queue::new();
252 | let (from_worker, rx) = Queue::new();
253 | let mut worker = Worker {
254 | to_worker: to_worker,
255 | from_worker: from_worker,
256 | };
257 | let child = thread::spawn(move || worker.work());
258 | thread::sleep(time::Duration::from_millis(1));
259 | for i in 0..1000 {
260 | tx.send(i.to_string());
261 | //thread::sleep(time::Duration::new(0, 1000));
262 | }
263 | let mut n_recv = 0;
264 | loop {
265 | for s in rx.recv() {
266 | println!("{}", s);
267 | n_recv += 1;
268 | }
269 | if n_recv == 1000 {
270 | break;
271 | }
272 | }
273 | let _ = child.join();
274 | //println!("done");
275 | }
276 |
--------------------------------------------------------------------------------
/synthesizer-io-core/src/worker.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Google Inc. All rights reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A worker, designed to produce audio in a lock-free manner.
16 |
17 | use std::ops::Deref;
18 |
19 | use queue::{Queue, Sender, Receiver, Item};
20 | use module::Buffer;
21 | use graph::{Graph, Node, Message};
22 |
23 | pub struct Worker {
24 | to_worker: Receiver,
25 | from_worker: Sender,
26 | graph: Graph,
27 | root: usize,
28 | }
29 |
30 | impl Worker {
31 | /// Create a new worker, with the specified maximum number of graph nodes,
32 | /// and set up communication channels.
33 | pub fn create(max_size: usize) -> (Worker, Sender, Receiver) {
34 | let (tx, to_worker) = Queue::new();
35 | let (from_worker, rx) = Queue::new();
36 | let graph = Graph::new(max_size);
37 | let worker = Worker {
38 | to_worker: to_worker,
39 | from_worker: from_worker,
40 | graph: graph,
41 | root: 0,
42 | };
43 | (worker, tx, rx)
44 | }
45 |
46 | /// Process a message. In normal operation, messages are sent to the
47 | /// queue, but this function is available to initialize the graph into
48 | /// a good state before starting any work. Allocates.
49 | pub fn handle_message(&mut self, msg: Message) {
50 | self.handle_item(Item::make_item(msg));
51 | }
52 |
53 | /// Convenience function for initializing one node in the graph
54 | pub fn handle_node(&mut self, node: Node) {
55 | self.handle_message(Message::Node(node));
56 | }
57 |
58 | fn handle_item(&mut self, item: Item) {
59 | let ix = match *item.deref() {
60 | Message::Node(ref node) => Some(node.ix),
61 | Message::SetParam(ref param) => {
62 | let module = self.graph.get_module_mut(param.ix);
63 | module.set_param(param.param_ix, param.val, param.timestamp);
64 | None
65 | }
66 | Message::Note(ref note) => {
67 | for &ix in note.ixs.iter() {
68 | let module = self.graph.get_module_mut(ix);
69 | module.handle_note(note.midi_num, note.velocity, note.on);
70 | }
71 | None
72 | }
73 | _ => return, // NYI
74 | };
75 | if let Some(ix) = ix {
76 | let old_item = self.graph.replace(ix, Some(item));
77 | if let Some(old_item) = old_item {
78 | self.from_worker.send_item(old_item);
79 | }
80 | } else {
81 | self.from_worker.send_item(item);
82 | }
83 | }
84 |
85 | /// Process the incoming items, run the graph, and return the rendered audio
86 | /// buffers. Lock-free.
87 | // TODO: leave incoming items in the queue if they have a timestamp in the
88 | // future.
89 | pub fn work(&mut self, timestamp: u64) -> &[Buffer] {
90 | for item in self.to_worker.recv_items() {
91 | self.handle_item(item);
92 | }
93 | self.graph.run_graph(self.root, timestamp);
94 | self.graph.get_out_bufs(self.root)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "synthesizer-io-wasm"
3 | version = "0.1.0"
4 | license = "Apache-2.0"
5 | authors = ["Raph Levien "]
6 | description = "WebAssembly bindings for the synthesizer core."
7 |
8 | [dependencies]
9 | wasm-bindgen = "0.2"
10 |
11 | [dependencies.synthesizer-io-core]
12 | path = "../synthesizer-io-core"
13 |
14 | [lib]
15 | crate-type = ["cdylib"]
16 |
17 | [profile.release]
18 | debug = false
19 | lto = true
20 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/README.md:
--------------------------------------------------------------------------------
1 | Generally following [wasm-bindgen tutorial](https://rustwasm.github.io/wasm-bindgen/basic-usage.html).
2 |
3 | ```
4 | cargo build --target=wasm32-unknown-unknown --release
5 | wasm-bindgen target/wasm32-unknown-unknown/release/synthesizer_io_wasm.wasm --out-dir .
6 | npm install
7 | npm run serve
8 | ```
9 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/index.html:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/index.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | const js = import("./synthesizer_io_wasm");
16 |
17 | js.then(js => {
18 | let synth = js.Synth.new();
19 |
20 | var ctx = new AudioContext();
21 |
22 | let scriptNode = ctx.createScriptProcessor(256, 0, 1);
23 | let bufSize = scriptNode.bufferSize;
24 | synth.setup_saw(8.781);
25 | scriptNode.onaudioprocess = function(audioProcessingEvent) {
26 | let obuf = audioProcessingEvent.outputBuffer.getChannelData(0);
27 | synth.get_samples(obuf);
28 | };
29 | scriptNode.connect(ctx.destination);
30 |
31 | });
32 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "serve": "webpack-dev-server"
4 | },
5 | "devDependencies": {
6 | "webpack": "^4.0.1",
7 | "webpack-cli": "^2.0.10",
8 | "webpack-dev-server": "^3.1.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! The WebAssembly bindings for the synthesizer core.
16 |
17 | #![feature(proc_macro, wasm_import_module, wasm_custom_section)]
18 | extern crate wasm_bindgen;
19 | extern crate synthesizer_io_core;
20 | use wasm_bindgen::prelude::*;
21 |
22 | use std::cell::RefCell;
23 |
24 | use synthesizer_io_core::modules;
25 |
26 | use synthesizer_io_core::worker::Worker;
27 | use synthesizer_io_core::queue::{Receiver, Sender};
28 | use synthesizer_io_core::graph::{Message, Node};
29 | use synthesizer_io_core::module::N_SAMPLES_PER_CHUNK;
30 |
31 | #[wasm_bindgen]
32 | pub struct Synth {
33 | worker: Worker,
34 | tx: Sender,
35 | rx: Receiver,
36 | }
37 |
38 | #[wasm_bindgen]
39 | impl Synth {
40 | pub fn new() -> Synth {
41 | let (worker, tx, rx) = Worker::create(1024);
42 | Synth { worker, tx, rx }
43 | }
44 |
45 | pub fn setup_saw(&mut self, val: f32) {
46 | let mut worker = &mut self.worker;
47 | let module = Box::new(modules::Saw::new(44_100.0));
48 | worker.handle_node(Node::create(module, 0, [], [(1, 0)]));
49 | let module = Box::new(modules::SmoothCtrl::new(val));
50 | worker.handle_node(Node::create(module, 1, [], []));
51 | }
52 |
53 | pub fn get_samples(&mut self, obuf: &mut[f32]) {
54 | let mut worker = &mut self.worker;
55 | let mut i = 0;
56 | let mut timestamp = 0; // TODO: figure this out
57 | while i < obuf.len() {
58 | // should let the graph generate stereo
59 | let buf = worker.work(timestamp)[0].get();
60 | for j in 0..N_SAMPLES_PER_CHUNK {
61 | obuf[i + j] = buf[j];
62 | }
63 | timestamp += 1451247; // 64 * 1e9 / 44_100
64 | i += N_SAMPLES_PER_CHUNK;
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/synthesizer-io-wasm/webpack.config.js:
--------------------------------------------------------------------------------
1 | // webpack.config.js
2 | const path = require('path');
3 |
4 | module.exports = {
5 | entry: "./index.js",
6 | output: {
7 | path: path.resolve(__dirname, "dist"),
8 | filename: "index.js",
9 | },
10 | mode: "development"
11 | };
12 |
--------------------------------------------------------------------------------
/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | synthesizer.io test
19 |
20 |
source
21 |
23 |
24 |
--------------------------------------------------------------------------------
/web/ui.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // https://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | const NS = "http://www.w3.org/2000/svg";
16 |
17 | function xy_of(x, y) {
18 | return (y << 16) | x;
19 | }
20 |
21 | function x_of(xy) {
22 | return xy & 0xffff;
23 | }
24 |
25 | function y_of(xy) {
26 | return xy >> 16;
27 | }
28 |
29 | // create an SVG element with
30 | function svg_el(tag, pe = 'none') {
31 | let el = document.createElementNS(NS, tag);
32 | el.setAttribute('pointer-events', pe);
33 | return el;
34 | }
35 |
36 | class Ui {
37 | constructor(el) {
38 | this.el = el;
39 | let grid = new Grid(48, 32);
40 | this.wireGrid = new WireGrid(grid);
41 | this.moduleGrid = new ModuleGrid(grid);
42 | this.handler = this.wireGrid;
43 | }
44 |
45 | init() {
46 | this.circle = this.addCircle(100, 100, 10, '#444');
47 | var self = this;
48 | this.el.ondragover = function(ev) {
49 | ev.preventDefault();
50 | console.log(ev);
51 | self.circle.setAttribute('cx', ev.offsetX);
52 | self.circle.setAttribute('cy', ev.offsetY);
53 | };
54 |
55 | this.wireGrid.attach(this.el);
56 | this.moduleGrid.attach(this.el);
57 |
58 | let rect = this.wireGrid.rect;
59 | rect.onmousedown = function(ev) {
60 | self.handler.onmousedown(ev);
61 | ev.preventDefault();
62 | }
63 | rect.onmouseup = function(ev) {
64 | self.handler.onmouseup(ev);
65 | ev.preventDefault();
66 | }
67 | rect.onmousemove = function(ev) {
68 | self.handler.onmousemove(ev);
69 | ev.preventDefault();
70 | }
71 |
72 | // set up buttons
73 | let button = new Button(0, 550, 'wire');
74 | button.onclick = function () {
75 | self.handler = self.wireGrid;
76 | };
77 | button.attach(this.el);
78 | let button2 = new Button(60, 550, 'mod');
79 | button2.onclick = function () {
80 | self.handler = self.moduleGrid;
81 | };
82 | button2.attach(this.el);
83 |
84 | }
85 |
86 | addCircle(x, y, r, color) {
87 | let circle = document.createElementNS(NS, 'circle');
88 | circle.setAttribute('cx', x);
89 | circle.setAttribute('cy', y);
90 | circle.setAttribute('r', r);
91 | circle.style.fill = color;
92 | this.el.appendChild(circle);
93 | return circle;
94 | }
95 | }
96 |
97 | class Grid {
98 | constructor(w, h) {
99 | this.w = w;
100 | this.h = h;
101 | // use translate in DOM?
102 | this.x0 = 0;
103 | this.y0 = 0;
104 | this.scale = 10;
105 | }
106 |
107 | x_y_to_xy(x, y) {
108 | let i = Math.floor(x / this.scale);
109 | let j = Math.floor(y / this.scale);
110 | if (i >= 0 && i < this.w && j >= 0 && j < this.h) {
111 | return xy_of(i, j);
112 | } else {
113 | return -1;
114 | }
115 | }
116 |
117 | ev_to_xy(ev) {
118 | return this.x_y_to_xy(ev.offsetX, ev.offsetY);
119 | }
120 | }
121 |
122 | class WireGrid {
123 | constructor(grid) {
124 | this.grid = grid;
125 | this.hset = new Set();
126 | this.vset = new Set();
127 | this.hmap = new Map();
128 | this.vmap = new Map();
129 | this.clickState = 0;
130 | }
131 |
132 | attach(parent) {
133 | this.parent = parent;
134 | let w = this.grid.w;
135 | let h = this.grid.h;
136 | let scale = this.grid.scale;
137 | let x0 = this.grid.x0;
138 | let y0 = this.grid.y0;
139 | let rect = svg_el('rect', 'all');
140 | rect.setAttribute('x', x0);
141 | rect.setAttribute('y', y0);
142 | rect.setAttribute('width', w * scale);
143 | rect.setAttribute('height', h * scale);
144 | rect.setAttribute('fill', '#cdf');
145 | parent.appendChild(rect);
146 |
147 | for (let x = 0; x <= w; x++) {
148 | let line = svg_el('line');
149 | line.setAttribute('x1', x0 + x * scale);
150 | line.setAttribute('y1', y0);
151 | line.setAttribute('x2', x0 + x * scale);
152 | line.setAttribute('y2', y0 + h * scale);
153 | line.setAttribute('stroke', '#8af');
154 | parent.appendChild(line);
155 | }
156 |
157 | for (let y = 0; y <= h; y++) {
158 | let line = svg_el('line');
159 | line.setAttribute('x1', x0);
160 | line.setAttribute('y1', y0 + y * scale);
161 | line.setAttribute('x2', x0 + w * scale);
162 | line.setAttribute('y2', y0 + y * scale);
163 | line.setAttribute('stroke', '#8af');
164 | parent.appendChild(line);
165 | }
166 | this.rect = rect;
167 | }
168 |
169 | isSet(x, y, isVert) {
170 | let xy = xy_of(x, y);
171 | let set = isVert ? this.vset : this.hset;
172 | return set.has(xy);
173 | }
174 |
175 | set(x, y, isVert, val) {
176 | let xy = xy_of(x, y);
177 | let set = isVert ? this.vset : this.hset;
178 | let map = isVert ? this.vmap : this.hmap;
179 | if (val) {
180 | if (set.has(xy)) { return; }
181 | set.add(xy);
182 | let x1 = this.grid.x0 + (x + 0.5) * this.grid.scale;
183 | let y1 = this.grid.y0 + (y + 0.5) * this.grid.scale;
184 | let line = svg_el('line');
185 | line.setAttribute('x1', x1);
186 | line.setAttribute('y1', y1);
187 | line.setAttribute('x2', isVert ? x1 : x1 + this.grid.scale);
188 | line.setAttribute('y2', isVert ? y1 + this.grid.scale : y1);
189 | line.setAttribute('stroke', '#000');
190 | line.setAttribute('stroke-width', 2);
191 | this.parent.appendChild(line);
192 | map[xy] = line;
193 | } else {
194 | if (!set.has(xy)) { return; }
195 | set.delete(xy);
196 | map[xy].remove();
197 | map.delete(xy);
198 | }
199 | }
200 |
201 | onmousedown(ev) {
202 | let xy = this.grid.ev_to_xy(ev);
203 | if (xy >= 0) {
204 | this.clickState = 1;
205 | this.clickXy = xy;
206 | }
207 | }
208 |
209 | onmouseup(ev) {
210 | this.clickState = 0;
211 | }
212 |
213 | onmousemove(ev) {
214 | let xy = this.grid.ev_to_xy(ev);
215 | if (this.clickState) {
216 | let seg = this.computeSegment(this.clickXy, xy);
217 | if (seg) {
218 | if (this.clickState == 1) {
219 | this.clickVal = !this.isSet(seg.x, seg.y, seg.isVert);
220 | this.clickState = 2;
221 | }
222 | this.set(seg.x, seg.y, seg.isVert, this.clickVal);
223 | this.clickXy = xy;
224 | }
225 | }
226 | }
227 |
228 | // Note: this really needs to be Bresenham
229 | computeSegment(xy1, xy2) {
230 | if (xy1 < 0 || xy2 < 0) { return null; }
231 | let x1 = x_of(xy1);
232 | let y1 = y_of(xy1);
233 | let x2 = x_of(xy2);
234 | let y2 = y_of(xy2);
235 | if (x1 == x2) {
236 | if (y2 == y1 + 1) {
237 | return {'x': x1, 'y': y1, 'isVert': true};
238 | } else if (y1 == y2 + 1) {
239 | return {'x': x1, 'y': y2, 'isVert': true};
240 | }
241 | } else if (y1 == y2) {
242 | if (x2 == x1 + 1) {
243 | return {'x': x1, 'y': y1, 'isVert': false};
244 | } else if (x1 == x2 + 1) {
245 | return {'x': x2, 'y': y1, 'isVert': false};
246 | }
247 | }
248 | return null;
249 | }
250 | }
251 |
252 | class Button {
253 | constructor(x, y, label) {
254 | let width = 50;
255 | let rect = svg_el('rect', 'all');
256 | rect.setAttribute('x', x);
257 | rect.setAttribute('y', y);
258 | rect.setAttribute('width', width);
259 | rect.setAttribute('height', 20);
260 | rect.setAttribute('fill', '#ddd');
261 | let text = svg_el('text');
262 | text.setAttribute('x', x + width / 2);
263 | text.setAttribute('y', y + 15);
264 | text.setAttribute('text-anchor', 'middle');
265 | text.setAttribute('fill', '#000');
266 | let textNode = document.createTextNode(label);
267 | text.appendChild(textNode);
268 |
269 | let self = this;
270 | rect.onclick = function(ev) {
271 | self.onclick();
272 | }
273 | this.onclick = function() { console.log('onclick not set!'); }
274 | this.rect = rect;
275 | this.text = text;
276 | }
277 |
278 | attach(parent) {
279 | parent.appendChild(this.rect);
280 | parent.appendChild(this.text);
281 | }
282 | }
283 |
284 | class ModuleGrid {
285 | constructor(grid) {
286 | this.grid = grid;
287 | this.guide = null;
288 | this.guideWidth = 3;
289 | this.guideHeight = 3;
290 | this.modules = [];
291 | }
292 |
293 | attach(parent) {
294 | this.parent = parent;
295 | }
296 |
297 | ev_to_xy(ev) {
298 | let x0 = ev.offsetX - 0.5 * (this.grid.scale * (this.guideWidth - 1));
299 | let y0 = ev.offsetY - 0.5 * (this.grid.scale * (this.guideHeight - 1));
300 | return this.grid.x_y_to_xy(x0, y0);
301 | }
302 |
303 | xy_ok(xy) {
304 | if (xy < 0) { return false; }
305 | let x = x_of(xy);
306 | let y = y_of(xy);
307 | if (x + this.guideWidth > this.grid.w) { return false; }
308 | if (y + this.guideHeight > this.grid.h) { return false; }
309 | for (let module of this.modules) {
310 | if (x + this.guideWidth >= x_of(module.xy)
311 | && x_of(module.xy) + module.w >= x
312 | && y + this.guideHeight >= y_of(module.xy)
313 | && y_of(module.xy) + module.h >= y) { return false; }
314 | }
315 | return true;
316 | }
317 |
318 | onmousedown(ev) {
319 | let xy = this.ev_to_xy(ev);
320 | if (this.xy_ok(xy)) {
321 | let rect = svg_el('rect');
322 | let module = new Module(xy, this.guideWidth, this.guideHeight);
323 | module.render(this.parent, this.grid);
324 | this.modules.push(module);
325 | }
326 | }
327 |
328 | onmouseup(ev) {
329 | console.log('mod up');
330 | }
331 |
332 | onmousemove(ev) {
333 | let xy = this.ev_to_xy(ev);
334 | if (xy >= 0) {
335 | let x0 = this.grid.x0 + x_of(xy) * this.grid.scale;
336 | let y0 = this.grid.y0 + y_of(xy) * this.grid.scale;
337 | if (this.guide === null) {
338 | this.guide = svg_el('rect');
339 | this.guide.setAttribute('width', this.grid.scale * this.guideWidth);
340 | this.guide.setAttribute('height', this.grid.scale * this.guideHeight);
341 | this.guide.setAttribute('fill', '#888');
342 | this.guide.setAttribute('fill-opacity', 0.5);
343 | this.guide.setAttribute('stroke', '#888');
344 | this.parent.appendChild(this.guide)
345 | }
346 | if (this.xy_ok(xy)) {
347 | this.guide.setAttribute('fill', '#0c0');
348 | this.guide.setAttribute('stroke', '#0c0');
349 | } else {
350 | this.guide.setAttribute('fill', '#e00');
351 | this.guide.setAttribute('stroke', '#e00');
352 | }
353 | this.guide.setAttribute('x', x0);
354 | this.guide.setAttribute('y', y0);
355 | }
356 | console.log('mod move');
357 | }
358 | }
359 |
360 | class Module {
361 | constructor(xy, w, h) {
362 | this.xy = xy;
363 | this.w = w;
364 | this.h = h;
365 | }
366 |
367 | render(parent, grid) {
368 | let g = svg_el('g');
369 | let x = grid.x0 + x_of(this.xy) * grid.scale;
370 | let y = grid.y0 + y_of(this.xy) * grid.scale;
371 | g.setAttribute('transform', 'translate(' + x + ' ' + y + ')');
372 | let rect = svg_el('rect');
373 | rect.setAttribute('x', 0);
374 | rect.setAttribute('y', 0);
375 | rect.setAttribute('width', grid.scale * this.w);
376 | rect.setAttribute('height', grid.scale * this.h);
377 | rect.setAttribute('fill', 'none');
378 | rect.setAttribute('stroke', '#000');
379 | g.appendChild(rect);
380 |
381 | for (let y = 0; y < this.h; y++) {
382 | for (let side = 0; side < 2; side++) {
383 | let line = svg_el('line');
384 | if (side == 0) {
385 | line.setAttribute('x1', -0.5 * grid.scale);
386 | line.setAttribute('x2', 0);
387 | } else {
388 | line.setAttribute('x1', this.w * grid.scale);
389 | line.setAttribute('x2', (this.w + 0.5) * grid.scale);
390 | }
391 | line.setAttribute('y1', grid.scale * (0.5 + y));
392 | line.setAttribute('y2', grid.scale * (0.5 + y));
393 | line.setAttribute('stroke', '#000');
394 | g.appendChild(line);
395 | }
396 | }
397 | this.g = g;
398 | parent.appendChild(g);
399 | }
400 | }
401 |
402 | var ui = new Ui(document.getElementById('main'));
403 | ui.init();
404 |
--------------------------------------------------------------------------------