├── .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 | 22 | 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 | --------------------------------------------------------------------------------