├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml ├── src ├── audio │ ├── basic_sampler.rs │ └── mod.rs ├── io │ ├── deseralizer.rs │ ├── exporter.rs │ └── mod.rs ├── lib.rs ├── performance │ ├── mod.rs │ └── performance_engine.rs └── theory │ ├── chords.rs │ ├── composition.rs │ ├── mod.rs │ └── notes.rs └── tests ├── export_test.yaml ├── export_test_missing_patterns.yaml ├── export_test_new.yaml ├── export_test_no_patterns.yaml ├── middle_c.yaml ├── test_interface.rs └── test_template_export.yaml /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | # - name: Install dependencies 12 | # run: sudo apt-get install libsdl2-dev 13 | - uses: actions/checkout@v1 14 | - name: Test 15 | run: cargo test --release --no-default-features 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | *.mid 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "alsa-sys" 5 | version = "0.1.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | dependencies = [ 8 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "autocfg" 14 | version = "1.0.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "backtrace" 19 | version = "0.3.45" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 25 | "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 26 | ] 27 | 28 | [[package]] 29 | name = "backtrace-sys" 30 | version = "0.1.34" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | dependencies = [ 33 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "bindgen" 39 | version = "0.53.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | dependencies = [ 42 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "cexpr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "clang-sys 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [[package]] 57 | name = "bitflags" 58 | version = "1.2.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "byteorder" 63 | version = "1.3.4" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | 66 | [[package]] 67 | name = "cc" 68 | version = "1.0.50" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | 71 | [[package]] 72 | name = "cexpr" 73 | version = "0.4.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | dependencies = [ 76 | "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "cfg-if" 81 | version = "0.1.10" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | 84 | [[package]] 85 | name = "chord-composer" 86 | version = "0.3.1" 87 | dependencies = [ 88 | "ghakuf 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "music-timer 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "stopwatch 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "clang-sys" 100 | version = "0.29.2" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | dependencies = [ 103 | "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 104 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 105 | "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 106 | ] 107 | 108 | [[package]] 109 | name = "claxon" 110 | version = "0.4.2" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | 113 | [[package]] 114 | name = "core-foundation-sys" 115 | version = "0.6.2" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | 118 | [[package]] 119 | name = "coreaudio-rs" 120 | version = "0.9.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | dependencies = [ 123 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 124 | "coreaudio-sys 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 125 | ] 126 | 127 | [[package]] 128 | name = "coreaudio-sys" 129 | version = "0.2.4" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | dependencies = [ 132 | "bindgen 0.53.2 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "cpal" 137 | version = "0.10.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | dependencies = [ 140 | "alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 141 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 142 | "coreaudio-rs 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "stdweb 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 149 | ] 150 | 151 | [[package]] 152 | name = "dtoa" 153 | version = "0.4.5" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | 156 | [[package]] 157 | name = "failure" 158 | version = "0.1.7" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "failure_derive" 167 | version = "0.1.7" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | dependencies = [ 170 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "fuchsia-cprng" 178 | version = "0.1.1" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | 181 | [[package]] 182 | name = "getrandom" 183 | version = "0.1.14" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | dependencies = [ 186 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 187 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 188 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 189 | ] 190 | 191 | [[package]] 192 | name = "ghakuf" 193 | version = "0.5.4" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | dependencies = [ 196 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 197 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 198 | ] 199 | 200 | [[package]] 201 | name = "glob" 202 | version = "0.3.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | 205 | [[package]] 206 | name = "hound" 207 | version = "3.4.0" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | 210 | [[package]] 211 | name = "lazy_static" 212 | version = "1.4.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | 215 | [[package]] 216 | name = "lazycell" 217 | version = "1.2.1" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | 220 | [[package]] 221 | name = "lewton" 222 | version = "0.9.4" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | dependencies = [ 225 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "ogg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 227 | "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", 228 | ] 229 | 230 | [[package]] 231 | name = "libc" 232 | version = "0.2.67" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | 235 | [[package]] 236 | name = "libloading" 237 | version = "0.5.2" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | dependencies = [ 240 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 241 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 242 | ] 243 | 244 | [[package]] 245 | name = "linked-hash-map" 246 | version = "0.5.2" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | 249 | [[package]] 250 | name = "log" 251 | version = "0.4.8" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | dependencies = [ 254 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 255 | ] 256 | 257 | [[package]] 258 | name = "mach" 259 | version = "0.3.2" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | dependencies = [ 262 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 263 | ] 264 | 265 | [[package]] 266 | name = "maybe-uninit" 267 | version = "2.0.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | 270 | [[package]] 271 | name = "memchr" 272 | version = "2.3.3" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | 275 | [[package]] 276 | name = "minimp3" 277 | version = "0.3.5" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | dependencies = [ 280 | "minimp3-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 281 | "slice-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 282 | ] 283 | 284 | [[package]] 285 | name = "minimp3-sys" 286 | version = "0.3.2" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | dependencies = [ 289 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 290 | ] 291 | 292 | [[package]] 293 | name = "music-timer" 294 | version = "0.1.6" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | 297 | [[package]] 298 | name = "nom" 299 | version = "5.1.1" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | dependencies = [ 302 | "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 303 | "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 304 | ] 305 | 306 | [[package]] 307 | name = "num" 308 | version = "0.1.42" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | dependencies = [ 311 | "num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", 312 | "num-complex 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", 313 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 314 | "num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 315 | "num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 316 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 317 | ] 318 | 319 | [[package]] 320 | name = "num-bigint" 321 | version = "0.1.44" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | dependencies = [ 324 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 325 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", 327 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 328 | ] 329 | 330 | [[package]] 331 | name = "num-complex" 332 | version = "0.1.43" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | dependencies = [ 335 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 337 | ] 338 | 339 | [[package]] 340 | name = "num-integer" 341 | version = "0.1.42" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | dependencies = [ 344 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 346 | ] 347 | 348 | [[package]] 349 | name = "num-iter" 350 | version = "0.1.40" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | dependencies = [ 353 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 354 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 355 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 356 | ] 357 | 358 | [[package]] 359 | name = "num-rational" 360 | version = "0.1.42" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | dependencies = [ 363 | "num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", 364 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 365 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 366 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 367 | ] 368 | 369 | [[package]] 370 | name = "num-traits" 371 | version = "0.2.11" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | dependencies = [ 374 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 375 | ] 376 | 377 | [[package]] 378 | name = "ogg" 379 | version = "0.7.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | dependencies = [ 382 | "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 383 | ] 384 | 385 | [[package]] 386 | name = "peeking_take_while" 387 | version = "0.1.2" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | 390 | [[package]] 391 | name = "pkg-config" 392 | version = "0.3.17" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | 395 | [[package]] 396 | name = "ppv-lite86" 397 | version = "0.2.6" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | 400 | [[package]] 401 | name = "proc-macro2" 402 | version = "1.0.9" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | dependencies = [ 405 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 406 | ] 407 | 408 | [[package]] 409 | name = "quote" 410 | version = "1.0.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 414 | ] 415 | 416 | [[package]] 417 | name = "rand" 418 | version = "0.4.6" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | dependencies = [ 421 | "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 422 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 423 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 424 | "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 425 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 426 | ] 427 | 428 | [[package]] 429 | name = "rand" 430 | version = "0.7.3" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | dependencies = [ 433 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 434 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 435 | "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 436 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 437 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 438 | ] 439 | 440 | [[package]] 441 | name = "rand_chacha" 442 | version = "0.2.2" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | dependencies = [ 445 | "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 447 | ] 448 | 449 | [[package]] 450 | name = "rand_core" 451 | version = "0.3.1" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | dependencies = [ 454 | "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 455 | ] 456 | 457 | [[package]] 458 | name = "rand_core" 459 | version = "0.4.2" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | 462 | [[package]] 463 | name = "rand_core" 464 | version = "0.5.1" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | dependencies = [ 467 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 468 | ] 469 | 470 | [[package]] 471 | name = "rand_hc" 472 | version = "0.2.0" 473 | source = "registry+https://github.com/rust-lang/crates.io-index" 474 | dependencies = [ 475 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 476 | ] 477 | 478 | [[package]] 479 | name = "rdrand" 480 | version = "0.4.0" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | dependencies = [ 483 | "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 484 | ] 485 | 486 | [[package]] 487 | name = "regex" 488 | version = "1.3.5" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | dependencies = [ 491 | "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)", 492 | ] 493 | 494 | [[package]] 495 | name = "regex-syntax" 496 | version = "0.6.17" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | 499 | [[package]] 500 | name = "rodio" 501 | version = "0.10.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | dependencies = [ 504 | "claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 505 | "cpal 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 506 | "hound 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 508 | "lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", 509 | "minimp3 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 510 | ] 511 | 512 | [[package]] 513 | name = "rustc-demangle" 514 | version = "0.1.16" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | 517 | [[package]] 518 | name = "rustc-hash" 519 | version = "1.1.0" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | 522 | [[package]] 523 | name = "rustc-serialize" 524 | version = "0.3.24" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | 527 | [[package]] 528 | name = "serde" 529 | version = "1.0.104" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | 532 | [[package]] 533 | name = "serde_derive" 534 | version = "1.0.104" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | dependencies = [ 537 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 538 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 539 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 540 | ] 541 | 542 | [[package]] 543 | name = "serde_yaml" 544 | version = "0.8.11" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | dependencies = [ 547 | "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 548 | "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 549 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 550 | "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 551 | ] 552 | 553 | [[package]] 554 | name = "shlex" 555 | version = "0.1.1" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | 558 | [[package]] 559 | name = "slice-deque" 560 | version = "0.3.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | dependencies = [ 563 | "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)", 564 | "mach 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 565 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 566 | ] 567 | 568 | [[package]] 569 | name = "smallvec" 570 | version = "0.6.13" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | dependencies = [ 573 | "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 574 | ] 575 | 576 | [[package]] 577 | name = "stdweb" 578 | version = "0.1.3" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | 581 | [[package]] 582 | name = "stopwatch" 583 | version = "0.0.7" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | dependencies = [ 586 | "num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 587 | ] 588 | 589 | [[package]] 590 | name = "syn" 591 | version = "1.0.16" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | dependencies = [ 594 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 595 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 596 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 597 | ] 598 | 599 | [[package]] 600 | name = "synstructure" 601 | version = "0.12.3" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | dependencies = [ 604 | "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", 605 | "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", 606 | "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", 607 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 608 | ] 609 | 610 | [[package]] 611 | name = "unicode-xid" 612 | version = "0.2.0" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | 615 | [[package]] 616 | name = "version_check" 617 | version = "0.9.1" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | 620 | [[package]] 621 | name = "wasi" 622 | version = "0.9.0+wasi-snapshot-preview1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | 625 | [[package]] 626 | name = "winapi" 627 | version = "0.3.8" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | dependencies = [ 630 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 631 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 632 | ] 633 | 634 | [[package]] 635 | name = "winapi-i686-pc-windows-gnu" 636 | version = "0.4.0" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | 639 | [[package]] 640 | name = "winapi-x86_64-pc-windows-gnu" 641 | version = "0.4.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | 644 | [[package]] 645 | name = "yaml-rust" 646 | version = "0.4.3" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | dependencies = [ 649 | "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 650 | ] 651 | 652 | [metadata] 653 | "checksum alsa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58" 654 | "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 655 | "checksum backtrace 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8" 656 | "checksum backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69" 657 | "checksum bindgen 0.53.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6bb26d6a69a335b8cb0e7c7e9775cd5666611dc50a37177c3f2cedcfc040e8c8" 658 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 659 | "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 660 | "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 661 | "checksum cexpr 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" 662 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 663 | "checksum clang-sys 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f92986241798376849e1a007827041fed9bb36195822c2049d18e174420e0534" 664 | "checksum claxon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f86c952727a495bda7abaf09bafdee1a939194dd793d9a8e26281df55ac43b00" 665 | "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 666 | "checksum coreaudio-rs 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491" 667 | "checksum coreaudio-sys 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e81f1c165c33ffab90a03077ac3b03462b34d5947145dfa48102e063d581502c" 668 | "checksum cpal 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ded070249be850b5b59e1e3a44a70b8ae395e0e5c65b487131d8909a8208120" 669 | "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" 670 | "checksum failure 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b8529c2421efa3066a5cbd8063d2244603824daccb6936b079010bb2aa89464b" 671 | "checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231" 672 | "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 673 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 674 | "checksum ghakuf 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aa13a54a7c0471e3e04c68d07cec2c1e77bd5ad2546a60ebaa1fa67a29039a" 675 | "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" 676 | "checksum hound 3.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549" 677 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 678 | "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 679 | "checksum lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" 680 | "checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" 681 | "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 682 | "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" 683 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 684 | "checksum mach 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 685 | "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 686 | "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 687 | "checksum minimp3 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "dce0cff6a0bfd3f8b6b2350819bbddd63bc65cc45e53888bdd0ff49dde16d2d5" 688 | "checksum minimp3-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90" 689 | "checksum music-timer 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bbc698c76ac2742a300ae94e907e9dedae141e7768488f36cf15d095ca5d64af" 690 | "checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" 691 | "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" 692 | "checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1" 693 | "checksum num-complex 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656" 694 | "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 695 | "checksum num-iter 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "dfb0800a0291891dd9f4fe7bd9c19384f98f7fbe0cd0f39a2c6b88b9868bbc00" 696 | "checksum num-rational 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e" 697 | "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 698 | "checksum ogg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d79f1db9148be9d0e174bb3ac890f6030fcb1ed947267c5a91ee4c91b5a91e15" 699 | "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 700 | "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 701 | "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 702 | "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" 703 | "checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 704 | "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 705 | "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 706 | "checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 707 | "checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 708 | "checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 709 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 710 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 711 | "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 712 | "checksum regex 1.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8900ebc1363efa7ea1c399ccc32daed870b4002651e0bed86e72d501ebbe0048" 713 | "checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" 714 | "checksum rodio 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0e0dfa7c8b17c6428f6e992a22ea595922cc86f946191b6b59e7ce96b77262" 715 | "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 716 | "checksum rustc-hash 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 717 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 718 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 719 | "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" 720 | "checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" 721 | "checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 722 | "checksum slice-deque 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25" 723 | "checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" 724 | "checksum stdweb 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e" 725 | "checksum stopwatch 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3d04b5ebc78da44d3a456319d8bc2783e7d8cc7ccbb5cb4dc3f54afbd93bf728" 726 | "checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" 727 | "checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" 728 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 729 | "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 730 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 731 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 732 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 733 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 734 | "checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" 735 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = 'chord-composer' 3 | version = '0.3.1' 4 | authors = ['cj '] 5 | edition = '2018' 6 | license = 'MIT' 7 | readme = 'README.md' 8 | repository = 'https://github.com/unsignedbytebite/chord-composer' 9 | description = ''' 10 | A music composition tool for structuring chord progressions and patterns. 11 | ''' 12 | keywords = [ 13 | 'music', 14 | 'composing', 15 | 'chords', 16 | ] 17 | categories = ['multimedia::audio'] 18 | autobenches = true 19 | 20 | [features] 21 | default = ['with-sound'] 22 | with-sound = ['rodio'] 23 | 24 | [dependencies] 25 | stopwatch = '0.0.7' 26 | serde = '1.0.104' 27 | serde_yaml = '0.8.11' 28 | serde_derive = '1.0.104' 29 | ghakuf = '0.5.4' 30 | rand = '0.7.3' 31 | music-timer = '0.1.6' 32 | 33 | [dependencies.rodio] 34 | version = '0.10.0' 35 | optional = true 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 cj 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # chord-composer 2 | 3 | > A music composition tool for structuring chord progressions and patterns, written in Rust. 4 | 5 | The philosophy behind **Chord Composer** is to make a lightweight, portable, and accessible tool to structure chord patterns and progressions for music composition ideas. It must fit into common digital music writing workflows and not hinder the creative process of the user. 6 | 7 | **A CLI for this library found [here](https://github.com/unsignedbytebite/chord-composer-interface).** 8 | 9 | ## Features 10 | 11 | - Describe compositions with patterns in `YAML` or via the **API**. 12 | - Export _composition patterns_ to `MIDI` clips. 13 | - Playback _composition patterns_ with audio samples. 14 | 15 | ## Future Work 16 | 17 | - Develop better audio engine and instrument sampler. 18 | - Support `MIDI` routing. 19 | - Explore the need to support common music trackers. 20 | 21 | ## Composition Parameters `YAML` file 22 | 23 | The schema for the _composition parameters_ `YAML` file are outlined below in this template. 24 | 25 | ```yaml 26 | # Name of the composition 27 | name: default_composition 28 | 29 | # The default master parameters of the composition. 30 | # New master pattern can be assigned to a pattern that overrides 31 | # the default master values. 32 | master: 33 | # The musical key to transpose the chords. 34 | # Supported values: C, C#, D, D#, E, F, F#, G, G#, A, A#, B 35 | key: F# 36 | 37 | # The beats per minute of the composition. 38 | time: 120 39 | 40 | # The time signature of the composition. 41 | # Beat numerator supported values: must be > 0. 42 | # Beat denominator supported values: 2, 4, 8, 16, 32, 64 43 | # e.g 3/8 is supported, 0/7 is not supported. 44 | signature: [4, 4] 45 | 46 | # Composition defined chords. 47 | chords: 48 | # [chord_name, [chord intervals]]. 49 | - [custom1, [0, 3, 8]] 50 | - [custom2, [0, 5]] 51 | 52 | # The composition's chord patterns/progressions. 53 | patterns: 54 | - name: part_a 55 | # Each pattern event = [bar, beat, beat interval, chord name, chord transpose]. 56 | pattern: 57 | - [1, 1, 1, MAJOR_SEVENTH, 0] 58 | - [1, 3, 1, custom1, 0] 59 | - [2, 1, 1, MAJOR_NINTH, 0] 60 | - [2, 3, 1, custom1, 0] 61 | - [3, 1, 1, MAJOR_SEVENTH, 3] 62 | - [3, 2, 1, custom1, 0] 63 | - [4, 1, 1, MAJOR_NINTH, -3] 64 | - [4, 2, 1, ?, 0] # ? = Select a random user defined chord. 65 | 66 | - name: part_b 67 | master: 68 | signature: [4, 8] 69 | key: C# 70 | time: 69 71 | # Each pattern event = [bar, beat, beat interval, chord name, chord transpose]. 72 | pattern: 73 | - [1, 1, 1, MAJOR_SEVENTH, 0] 74 | - [1, 2, 1, custom1, 0] 75 | - [2, 1, 1, MAJOR_NINTH, 0] 76 | - [2, 2, 1, custom1, 0] 77 | - [3, 1, 1, MAJOR_SEVENTH, 3] 78 | - [3, 2, 1, custom1, 0] 79 | - [4, 1, 1, MAJOR_NINTH, -3] 80 | - [4, 2, 1, ??, 0] #?? = Select a random chord from user defined and internal defined chord. 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 -------------------------------------------------------------------------------- /src/audio/basic_sampler.rs: -------------------------------------------------------------------------------- 1 | use rodio::Source; //TODO: replace rodio with something better 2 | use std::{fs, io}; 3 | 4 | type AudioBuffers = Vec>>>; 5 | 6 | pub struct SamplerPlayer { 7 | device: Option, 8 | clip_buffers: AudioBuffers, 9 | } 10 | 11 | impl SamplerPlayer { 12 | pub fn new(audio_clip_paths: &Vec) -> Result { 13 | if cfg!(feature = "no-audio") { 14 | Ok(SamplerPlayer { 15 | device: None, 16 | clip_buffers: Vec::new(), 17 | }) 18 | } else { 19 | let mut clip_buffers: AudioBuffers = Vec::new(); 20 | 21 | for path in audio_clip_paths { 22 | clip_buffers.push({ 23 | let buffer = { 24 | let file = match fs::File::open(path) { 25 | Ok(file) => file, 26 | Err(_) => return Err(()), 27 | }; 28 | 29 | io::BufReader::new(file) 30 | }; 31 | 32 | match rodio::Decoder::new(buffer) { 33 | Ok(decoder) => decoder.buffered(), 34 | Err(_) => return Err(()), 35 | } 36 | }); 37 | } 38 | 39 | let device = match rodio::default_output_device() { 40 | Some(device) => device, 41 | _ => return Err(()), 42 | }; 43 | 44 | Ok(SamplerPlayer { 45 | device: Some(device), 46 | clip_buffers, 47 | }) 48 | } 49 | } 50 | 51 | pub fn play(&self, sample_index: usize) { 52 | if sample_index >= self.clip_buffers.len() { 53 | return; 54 | } 55 | 56 | match &self.device { 57 | Some(device) => { 58 | rodio::play_raw( 59 | device, 60 | self.clip_buffers[sample_index].clone().convert_samples(), 61 | ); 62 | } 63 | _ => {} 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/audio/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod basic_sampler; 2 | -------------------------------------------------------------------------------- /src/io/deseralizer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | pub type CustomChords = Vec<(String, Vec)>; 4 | pub type PatternObject = (u16, u8, u8, String, i8); 5 | 6 | pub fn deserialize_file(file_name: &str) -> Result { 7 | match std::fs::read_to_string(file_name) { 8 | Ok(stream) => Ok(deserialize_string(&stream)?), 9 | _ => Err(crate::FailResult::Deserialize), 10 | } 11 | } 12 | 13 | pub fn deserialize_string(string: &str) -> Result { 14 | match serde_yaml::from_str(&string) { 15 | Ok(deserialize) => Ok(deserialize), 16 | _ => Err(crate::FailResult::Deserialize), 17 | } 18 | } 19 | 20 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 21 | pub struct CompositionParameters { 22 | name: Option, 23 | master: Option, 24 | chords: Option, 25 | patterns: Option>, 26 | } 27 | 28 | impl CompositionParameters { 29 | pub fn new() -> Self { 30 | Self { 31 | name: None, 32 | master: None, 33 | chords: None, 34 | patterns: None, 35 | } 36 | } 37 | 38 | pub fn get_name(&self) -> String { 39 | match &self.name { 40 | Some(name) => name.clone(), 41 | None => "Unnamed composition".to_string(), 42 | } 43 | } 44 | 45 | pub fn get_master(&self) -> &Option { 46 | &self.master 47 | } 48 | 49 | pub fn get_custom_chords(&self) -> &Option { 50 | &self.chords 51 | } 52 | 53 | pub fn get_patterns(&self) -> &Option> { 54 | &self.patterns 55 | } 56 | } 57 | 58 | #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] 59 | pub struct MasterParameters { 60 | key: Option, 61 | time: Option, 62 | signature: Option<(u8, u8)>, 63 | } 64 | 65 | impl MasterParameters { 66 | const DEFAULT_KEY: &'static str = "C"; 67 | const DEFAULT_TIME: u8 = 120; 68 | const DEFAULT_SIGNATURE: (u8, u8) = (4, 4); 69 | const DEFAULT_METRONOME: bool = true; 70 | const DEFAULT_VERBOSE: u8 = 4; 71 | 72 | pub fn from_overrides(defaults: &MasterParameters, overrides: &MasterParameters) -> Self { 73 | let key = Some(match &overrides.key { 74 | Some(key) => key.clone(), 75 | None => defaults.get_key_or_default(), 76 | }); 77 | 78 | let time = Some(match overrides.time { 79 | Some(time) => time, 80 | None => defaults.get_time_or_default(), 81 | }); 82 | 83 | let signature = Some(match overrides.signature { 84 | Some(signature) => signature, 85 | None => defaults.get_signature_or_default(), 86 | }); 87 | 88 | Self { 89 | key, 90 | time, 91 | signature, 92 | } 93 | } 94 | 95 | pub fn get_key(&self) -> Option { 96 | match &self.key { 97 | Some(key) => Some(key.clone()), 98 | None => None, 99 | } 100 | } 101 | pub fn get_key_or_default(&self) -> String { 102 | match &self.key { 103 | Some(key) => key.clone(), 104 | None => MasterParameters::DEFAULT_KEY.to_string(), 105 | } 106 | } 107 | pub fn get_time(&self) -> Option { 108 | match self.time { 109 | Some(time) => Some(time), 110 | None => None, 111 | } 112 | } 113 | pub fn get_time_or_default(&self) -> u8 { 114 | match self.time { 115 | Some(time) => time, 116 | None => MasterParameters::DEFAULT_TIME, 117 | } 118 | } 119 | pub fn get_signature(&self) -> Option<(u8, u8)> { 120 | match self.signature { 121 | Some(signature) => Some(signature), 122 | None => None, 123 | } 124 | } 125 | pub fn get_signature_or_default(&self) -> (u8, u8) { 126 | match self.signature { 127 | Some(signature) => signature, 128 | None => MasterParameters::DEFAULT_SIGNATURE, 129 | } 130 | } 131 | pub fn get_all_or_defaults(&self) -> (String, u8, (u8, u8)) { 132 | ( 133 | self.get_key_or_default(), 134 | self.get_time_or_default(), 135 | self.get_signature_or_default(), 136 | ) 137 | } 138 | } 139 | impl Default for MasterParameters { 140 | fn default() -> Self { 141 | Self { 142 | key: Some(MasterParameters::DEFAULT_KEY.to_string()), 143 | time: Some(MasterParameters::DEFAULT_TIME), 144 | signature: Some(MasterParameters::DEFAULT_SIGNATURE), 145 | } 146 | } 147 | } 148 | #[derive(Debug, PartialEq, Serialize, Deserialize)] 149 | pub struct PatternParameters { 150 | name: Option, 151 | master: Option, 152 | pattern: Option>, 153 | } 154 | 155 | impl PatternParameters { 156 | pub fn get_name(&self) -> &Option { 157 | &self.name 158 | } 159 | 160 | pub fn get_pattern(&self) -> &Option> { 161 | &self.pattern 162 | } 163 | 164 | pub fn get_master(&self) -> &Option { 165 | &self.master 166 | } 167 | } 168 | 169 | mod tests { 170 | 171 | #[test] 172 | fn test_yaml_import() { 173 | use crate::io::deseralizer::*; 174 | 175 | let params = deserialize_string( 176 | r#" 177 | name: bc_000_a 178 | 179 | # Can be overridden by patterns 180 | master: 181 | key: D 182 | time: 128 183 | signature: [3, 4] 184 | 185 | structure: 186 | - part_a 187 | - part_b 188 | 189 | chords: 190 | - [custom1, [0, 3, 8]] 191 | 192 | patterns: 193 | - name: part_a 194 | pattern: 195 | - [1,1,1, MAJOR_SEVENTH, 0] 196 | - [1,3,1, custom1, 0] 197 | - [2,1,1, MAJOR_NINTH, 0] 198 | - [2,3,1, custom1, 0] 199 | - [3,1,1, MAJOR_SEVENTH, 3] 200 | - [2,2,1, custom1, 0] 201 | - [3,1,1, MAJOR_NINTH, -3] 202 | - [3,2,1, custom1, 0] 203 | 204 | - name: part_b 205 | master: 206 | signature: [4, 8] 207 | key: C# 208 | time: 69 209 | pattern: 210 | - [1,1,1, MAJOR_SEVENTH, 0] 211 | - [1,2,1, custom1, 0] 212 | - [2,1,1, MAJOR_NINTH, 0] 213 | - [2,2,1, custom1, 0] 214 | - [3,1,1, MAJOR_SEVENTH, 3] 215 | - [2,2,1, custom1, 0] 216 | - [3,1,1, MAJOR_NINTH, -3] 217 | - [3,2,1, custom1, 0] 218 | 219 | "#, 220 | ) 221 | .unwrap(); 222 | 223 | assert_eq!(params.get_name(), "bc_000_a"); 224 | 225 | match params.get_master() { 226 | Some(master) => { 227 | assert_eq!(master.get_key_or_default(), "D"); 228 | assert_eq!(master.get_time_or_default(), 128); 229 | assert_eq!(master.get_signature_or_default(), (3, 4)); 230 | } 231 | None => assert!(false), 232 | }; 233 | 234 | match params.get_custom_chords() { 235 | Some(chords) => { 236 | assert_eq!(chords, &[("custom1".to_string(), vec![0, 3, 8])]); 237 | } 238 | None => assert!(false), 239 | }; 240 | 241 | match params.get_patterns() { 242 | Some(patterns) => { 243 | let pattern = &patterns[0]; 244 | assert_eq!(pattern.get_name(), &Some("part_a".to_string())); 245 | assert_eq!(pattern.get_master(), &None); 246 | match pattern.get_pattern() { 247 | Some(events) => assert_eq!(&events.len(), &8usize), 248 | None => assert!(false), 249 | } 250 | 251 | let pattern = &patterns[1]; 252 | assert_eq!(pattern.get_name(), &Some("part_b".to_string())); 253 | 254 | match pattern.get_master() { 255 | Some(master) => { 256 | assert_eq!(master.get_key(), Some("C#".to_string())); 257 | assert_eq!(master.get_time(), Some(69)); 258 | assert_eq!(master.get_signature(), Some((4, 8))); 259 | } 260 | None => assert!(false), 261 | }; 262 | 263 | match pattern.get_pattern() { 264 | Some(events) => assert_eq!(&events.len(), &8usize), 265 | None => assert!(false), 266 | } 267 | } 268 | None => assert!(false), 269 | }; 270 | } 271 | 272 | #[test] 273 | fn test_yaml_import_missing() { 274 | use crate::io::deseralizer::*; 275 | 276 | // Parse parameters 277 | let params = deserialize_string( 278 | r#" 279 | # Empty 280 | empty: empty 281 | "#, 282 | ) 283 | .unwrap(); 284 | 285 | assert_eq!(params.get_name(), "Unnamed composition"); 286 | assert_eq!(params.get_master(), &None); 287 | assert_eq!(params.get_custom_chords(), &None); 288 | assert_eq!(params.get_patterns(), &None); 289 | } 290 | 291 | #[test] 292 | fn test_yaml_import_none() { 293 | use crate::io::deseralizer::*; 294 | 295 | assert_eq!( 296 | deserialize_file("not a path"), 297 | Err(crate::FailResult::Deserialize) 298 | ); 299 | assert_eq!( 300 | deserialize_string("not a path"), 301 | Err(crate::FailResult::Deserialize) 302 | ); 303 | } 304 | 305 | #[test] 306 | fn test_master_defaults() { 307 | use crate::io::deseralizer::MasterParameters; 308 | 309 | let defaults = MasterParameters::default(); 310 | 311 | const DEFAULT_KEY: &'static str = "C"; 312 | const DEFAULT_TIME: u8 = 120; 313 | const DEFAULT_SIGNATURE: (u8, u8) = (4, 4); 314 | 315 | assert_eq!(defaults.get_key_or_default(), DEFAULT_KEY); 316 | assert_eq!(defaults.get_time_or_default(), DEFAULT_TIME); 317 | assert_eq!(defaults.get_signature_or_default(), DEFAULT_SIGNATURE); 318 | 319 | let (key, time, signature) = defaults.get_all_or_defaults(); 320 | assert_eq!(key, DEFAULT_KEY); 321 | assert_eq!(time, DEFAULT_TIME); 322 | assert_eq!(signature, DEFAULT_SIGNATURE); 323 | } 324 | 325 | #[test] 326 | fn test_master_overrides() { 327 | use crate::io::deseralizer::MasterParameters; 328 | 329 | let defaults = MasterParameters { 330 | key: None, 331 | time: Some(130), 332 | signature: Some((4, 4)), 333 | }; 334 | 335 | assert_eq!(defaults.get_key_or_default(), "C"); 336 | assert_eq!(defaults.get_time_or_default(), 130); 337 | assert_eq!(defaults.get_signature_or_default(), (4, 4)); 338 | 339 | let overrides = MasterParameters { 340 | key: Some("E".to_string()), 341 | time: None, 342 | signature: Some((3, 4)), 343 | }; 344 | 345 | assert_eq!(overrides.get_key(), Some("E".to_string())); 346 | assert_eq!(overrides.get_time(), None); 347 | assert_eq!(overrides.get_signature(), Some((3, 4))); 348 | 349 | let master = MasterParameters::from_overrides(&defaults, &overrides); 350 | assert_eq!(master.get_key_or_default(), "E"); 351 | assert_eq!(master.get_time_or_default(), 130); 352 | assert_eq!(master.get_signature_or_default(), (3, 4)); 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/io/exporter.rs: -------------------------------------------------------------------------------- 1 | use crate::composition; 2 | use ghakuf::{ 3 | messages::{Message, MetaEvent, MidiEvent}, 4 | writer::Writer, 5 | }; 6 | use music_timer::music_time; 7 | use std::path; 8 | 9 | pub fn export_composition( 10 | composition: &composition::Composition, 11 | parent_directory: &str, 12 | ) -> Result { 13 | let mut pattern_midi = Vec::with_capacity(composition.len()); 14 | for pattern in composition.get_patterns() { 15 | let pattern_messages = { 16 | let pattern_name = pattern.get_name().to_string(); 17 | let mut messages = pattern_to_midi_meta(pattern); 18 | messages.append(&mut pattern_to_midi_messages(pattern)); 19 | (pattern_name, messages) 20 | }; 21 | 22 | pattern_midi.push(pattern_messages); 23 | } 24 | 25 | let name = composition.get_name(); 26 | export_midi_patterns(name, &pattern_midi, parent_directory) 27 | } 28 | 29 | pub fn export_midi_patterns( 30 | composition_name: &str, 31 | patterns: &Vec<(String, Vec)>, 32 | parent_directory: &str, 33 | ) -> Result { 34 | let target_dir = format!("{}/{}", parent_directory, composition_name); 35 | 36 | // Flush directory 37 | let _ = std::fs::remove_dir_all(&target_dir).is_ok(); 38 | std::fs::create_dir(&target_dir).unwrap(); 39 | 40 | // Export patterns 41 | let mut export_paths = Vec::with_capacity(patterns.len()); 42 | for (pattern_name, midi_messages) in patterns { 43 | let path = format!("{}/{}.mid", target_dir, pattern_name); 44 | export_midi_messages(&path, midi_messages)?; 45 | export_paths.push(path); 46 | } 47 | 48 | Ok(crate::SuccessResult::Export(export_paths)) 49 | } 50 | 51 | fn export_midi_messages(path: &str, midi_messages: &Vec) -> Result<(), crate::FailResult> { 52 | let path = path::Path::new(path); 53 | let mut writer = Writer::new(); 54 | writer.running_status(false); 55 | 56 | for message in midi_messages { 57 | writer.push(message); 58 | } 59 | 60 | match writer.write(&path) { 61 | Err(_) => Err(crate::FailResult::ExportMIDI), 62 | _ => Ok(()), 63 | } 64 | } 65 | 66 | fn pattern_to_midi_meta(pattern: &composition::Pattern) -> Vec { 67 | let mut messages = Vec::with_capacity(4); 68 | let tempo = to_tempo_samples(pattern.get_bpm()); 69 | messages.push(Message::MetaEvent { 70 | delta_time: 0, 71 | event: MetaEvent::SetTempo, 72 | data: vec![(tempo >> 16) as u8, (tempo >> 8) as u8, tempo as u8], 73 | }); 74 | 75 | let time_signature = time_signature_to_data(pattern.get_time_signature().as_tuple()); 76 | 77 | messages.push(Message::MetaEvent { 78 | delta_time: 0, 79 | event: MetaEvent::TimeSignature, 80 | data: time_signature, 81 | }); 82 | 83 | messages.push(Message::MetaEvent { 84 | delta_time: 0, 85 | event: MetaEvent::EndOfTrack, 86 | data: Vec::new(), 87 | }); 88 | 89 | messages.push(Message::TrackChange); 90 | 91 | messages 92 | } 93 | 94 | fn pattern_to_midi_messages(pattern: &composition::Pattern) -> Vec { 95 | let mut messages = Vec::new(); 96 | let mut total_time = 0; 97 | 98 | messages.push(Message::MetaEvent { 99 | delta_time: 0, 100 | event: MetaEvent::SequenceOrTrackName, 101 | data: pattern.get_name().as_bytes().to_vec(), 102 | }); 103 | 104 | for i in 0..pattern.get_events().len() { 105 | let (music_time, intervals) = pattern.get(i); 106 | 107 | // Skip empty intervals 108 | if intervals.is_empty() { 109 | continue; 110 | } 111 | 112 | let mut push_event = |smf_note: u8, time: u32, velocity: u8| { 113 | messages.push(Message::MidiEvent { 114 | delta_time: time, 115 | event: MidiEvent::NoteOn { 116 | ch: 0, 117 | note: smf_note, 118 | velocity: velocity, 119 | }, 120 | }); 121 | }; 122 | 123 | let mut push_events = |velocity: u8, bar: u16, beat: u8, beat_interval: u8| { 124 | let delta_time = { 125 | let numerator = pattern.get_time_signature().get_numerator(); 126 | let tick_time = to_tick_time(numerator, bar, beat, beat_interval); 127 | let delta_time = tick_time - total_time; 128 | total_time = to_tick_time(numerator, bar, beat, beat_interval); 129 | delta_time 130 | }; 131 | // Push notes into the chord 132 | push_event(intervals[0] as u8, delta_time, velocity); 133 | for i in 1..intervals.len() { 134 | push_event(intervals[i], 0, velocity); 135 | } 136 | }; 137 | 138 | // Note on 139 | const NOTE_ON: u8 = 64; 140 | push_events( 141 | NOTE_ON, 142 | music_time.get_bar(), 143 | music_time.get_beat(), 144 | music_time.get_beat_interval(), 145 | ); 146 | 147 | // Note off 148 | let legato_end = { 149 | let is_last_element = i == pattern.get_events().len() - 1; 150 | if is_last_element { 151 | music_time::MusicTime::new(music_time.get_bar() + 1, 1, 1) 152 | } else { 153 | let (next_music_time, _next_intervals) = pattern.get(i + 1); 154 | next_music_time.clone() 155 | } 156 | }; 157 | 158 | const NOTE_OFF: u8 = 0; 159 | push_events( 160 | NOTE_OFF, 161 | legato_end.get_bar(), 162 | legato_end.get_beat(), 163 | legato_end.get_beat_interval(), 164 | ); 165 | } 166 | 167 | // End track 168 | messages.push(Message::MetaEvent { 169 | delta_time: 0, 170 | event: MetaEvent::EndOfTrack, 171 | data: Vec::new(), 172 | }); 173 | 174 | messages 175 | } 176 | fn time_signature_to_data(time_signature: (u8, u8)) -> Vec { 177 | let (numerator, denominator) = time_signature; 178 | let dd = match denominator { 179 | 2 => 1, 180 | 4 => 2, 181 | 8 => 3, 182 | 16 => 4, 183 | 32 => 5, 184 | 64 => 6, 185 | _ => 0, 186 | }; 187 | 188 | vec![numerator, dd, 8, 24] 189 | } 190 | 191 | fn to_tick_time(time_numerator: u8, bar: u16, beat: u8, beat_interval: u8) -> u32 { 192 | const BEAT_INTERVAL_SAMPLE: u32 = 60; 193 | const BEAT_SAMPLE: u32 = BEAT_INTERVAL_SAMPLE * 8; 194 | 195 | let bar_corrected = bar as u32 - 1; 196 | let beat_corrected = beat as u32 - 1; 197 | let beat_interval_corrected = beat_interval as u32 - 1; 198 | 199 | (bar_corrected * time_numerator as u32 * BEAT_SAMPLE) 200 | + (beat_corrected * BEAT_SAMPLE) 201 | + (beat_interval_corrected * BEAT_INTERVAL_SAMPLE) 202 | } 203 | 204 | fn to_tempo_samples(bpm: u8) -> u32 { 205 | (60.0 * 1000000.0 / bpm as f64) as u32 206 | } 207 | 208 | mod test { 209 | 210 | #[test] 211 | fn test_time_signature_convert() { 212 | use crate::io::exporter::*; 213 | let time_signature = time_signature_to_data((4, 4)); 214 | assert_eq!(time_signature, [4, 2, 8, 24]); 215 | 216 | let time_signature = time_signature_to_data((2, 4)); 217 | assert_eq!(time_signature, [2, 2, 8, 24]); 218 | let time_signature = time_signature_to_data((2, 2)); 219 | assert_eq!(time_signature, [2, 1, 8, 24]); 220 | 221 | let time_signature = time_signature_to_data((2, 5)); 222 | assert_eq!(time_signature, [2, 0, 8, 24]); 223 | 224 | let time_signature = time_signature_to_data((7, 8)); 225 | assert_eq!(time_signature, [7, 3, 8, 24]); 226 | } 227 | 228 | #[test] 229 | fn test_correct_tick_time() { 230 | use crate::io::exporter::*; 231 | const BEAT_INTERVAL_SAMPLE: u32 = 60; 232 | const BEAT_SAMPLE: u32 = BEAT_INTERVAL_SAMPLE * 8; 233 | assert_eq!(to_tick_time(4, 1, 1, 1), 0); 234 | assert_eq!(to_tick_time(4, 1, 2, 1), BEAT_SAMPLE); 235 | assert_eq!(to_tick_time(4, 1, 3, 1), BEAT_SAMPLE * 2); 236 | assert_eq!(to_tick_time(4, 1, 4, 1), BEAT_SAMPLE * 3); 237 | assert_eq!(to_tick_time(4, 2, 1, 1), BEAT_SAMPLE * 4); 238 | assert_eq!(to_tick_time(4, 2, 2, 1), BEAT_SAMPLE * 5); 239 | assert_eq!(to_tick_time(4, 2, 3, 1), BEAT_SAMPLE * 6); 240 | assert_eq!(to_tick_time(4, 2, 4, 1), BEAT_SAMPLE * 7); 241 | assert_eq!(to_tick_time(4, 2, 5, 1), BEAT_SAMPLE * 8); 242 | 243 | assert_eq!(to_tick_time(3, 1, 1, 1), 0); 244 | assert_eq!(to_tick_time(3, 1, 2, 1), BEAT_SAMPLE); 245 | assert_eq!(to_tick_time(3, 1, 3, 1), BEAT_SAMPLE * 2); 246 | assert_eq!(to_tick_time(3, 2, 1, 1), BEAT_SAMPLE * 3); 247 | assert_eq!(to_tick_time(3, 2, 2, 1), BEAT_SAMPLE * 4); 248 | assert_eq!(to_tick_time(3, 2, 3, 1), BEAT_SAMPLE * 5); 249 | assert_eq!(to_tick_time(3, 3, 1, 1), BEAT_SAMPLE * 6); 250 | assert_eq!(to_tick_time(3, 3, 2, 1), BEAT_SAMPLE * 7); 251 | assert_eq!(to_tick_time(3, 3, 3, 1), BEAT_SAMPLE * 8); 252 | 253 | assert_eq!(to_tick_time(4, 1, 1, 1), 0); 254 | assert_eq!(to_tick_time(4, 1, 1, 2), BEAT_INTERVAL_SAMPLE); 255 | assert_eq!(to_tick_time(4, 1, 1, 3), BEAT_INTERVAL_SAMPLE * 2); 256 | assert_eq!(to_tick_time(4, 1, 1, 4), BEAT_INTERVAL_SAMPLE * 3); 257 | assert_eq!(to_tick_time(4, 1, 1, 5), BEAT_INTERVAL_SAMPLE * 4); 258 | assert_eq!(to_tick_time(4, 1, 1, 6), BEAT_INTERVAL_SAMPLE * 5); 259 | assert_eq!(to_tick_time(4, 1, 1, 7), BEAT_INTERVAL_SAMPLE * 6); 260 | assert_eq!(to_tick_time(4, 1, 1, 8), BEAT_INTERVAL_SAMPLE * 7); 261 | assert_eq!(to_tick_time(4, 1, 2, 1), BEAT_INTERVAL_SAMPLE * 8); 262 | assert_eq!(to_tick_time(4, 1, 2, 2), BEAT_INTERVAL_SAMPLE * 9); 263 | assert_eq!(to_tick_time(4, 1, 2, 3), BEAT_INTERVAL_SAMPLE * 10); 264 | assert_eq!(to_tick_time(4, 1, 2, 4), BEAT_INTERVAL_SAMPLE * 11); 265 | assert_eq!(to_tick_time(4, 1, 2, 5), BEAT_INTERVAL_SAMPLE * 12); 266 | assert_eq!(to_tick_time(4, 1, 2, 6), BEAT_INTERVAL_SAMPLE * 13); 267 | assert_eq!(to_tick_time(4, 1, 2, 7), BEAT_INTERVAL_SAMPLE * 14); 268 | assert_eq!(to_tick_time(4, 1, 2, 8), BEAT_INTERVAL_SAMPLE * 15); 269 | } 270 | 271 | #[test] 272 | fn test_event_delta() { 273 | use crate::io::exporter::*; 274 | let mut total_time = 0; 275 | 276 | let mut to_delta_time = |bar: u16, beat: u8, beat_interval: u8| -> u32 { 277 | let delta_time = to_tick_time(4, bar, beat, beat_interval) - total_time; 278 | total_time = to_tick_time(4, bar, beat, beat_interval); 279 | delta_time 280 | }; 281 | 282 | assert_eq!(to_delta_time(1, 1, 1), 0); 283 | assert_eq!(to_delta_time(1, 2, 1), 480); 284 | assert_eq!(to_delta_time(2, 1, 1), 1440); 285 | assert_eq!(to_delta_time(2, 2, 1), 480); 286 | assert_eq!(to_delta_time(3, 1, 1), 1440); 287 | assert_eq!(to_delta_time(3, 1, 2), 60); 288 | assert_eq!(to_delta_time(3, 1, 3), 60); 289 | assert_eq!(to_delta_time(3, 1, 6), 180); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod deseralizer; 2 | pub mod exporter; 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | 4 | mod audio; 5 | mod io; 6 | pub mod performance; 7 | pub mod theory; 8 | 9 | use music_timer::{music_time, time_signature}; 10 | use performance::performance_engine; 11 | use std::{io::Write, path::Path}; 12 | use theory::{chords, composition, notes}; 13 | 14 | /// Possible failures. 15 | #[derive(Debug, PartialEq)] 16 | pub enum FailResult { 17 | Deserialize, 18 | ExportMIDI, 19 | ExportTemplate, 20 | NoPatterns, 21 | NoFoundPattern(String), 22 | EmptyPatterns, 23 | TimeReverse(music_time::MusicTime, usize, String), 24 | UnreachableTime(music_time::MusicTime, usize, String), 25 | TimeSignature(time_signature::TimeSignature), 26 | LoadSampler, 27 | } 28 | 29 | /// Possible successes. 30 | #[derive(Debug, PartialEq)] 31 | pub enum SuccessResult { 32 | Export(Vec), 33 | ExportTemplate, 34 | Playback, 35 | } 36 | 37 | /// Returns all the internally supported chord keywords. 38 | /// * Interpreted when parsing composition YAML. 39 | /// * Case sensitive. 40 | /// * Considered for future removal. 41 | pub fn get_chord_keywords() -> Vec<&'static str> { 42 | theory::chords::chord_to_string_array() 43 | } 44 | 45 | /// Export a composition to a midi files. Each pattern will be 46 | /// exported as a different midi file. 47 | /// 48 | /// # Arguments 49 | /// * `composition` - The composition to export from. 50 | /// * `export_path` - The path to export the midi patterns. 51 | pub fn export_to_midi_file( 52 | composition: &composition::Composition, 53 | export_path: &str, 54 | ) -> Result { 55 | let parent_directory = Path::new(export_path) 56 | .parent() 57 | .unwrap_or(Path::new("./")) 58 | .to_str() 59 | .unwrap_or("./"); 60 | 61 | io::exporter::export_composition(&composition, parent_directory) 62 | } 63 | 64 | /// Load a composition then export it to midi files. Each pattern will be 65 | /// exported as a different midi file. 66 | /// 67 | /// # Arguments 68 | /// * `composition_path` - The file path to the composition YAML file and the export path 69 | /// of the midi files. 70 | pub fn export_file_to_midi(composition_path: &str) -> Result { 71 | let composition_parameters = io::deseralizer::deserialize_file(composition_path)?; 72 | let composition = parameters_to_composition(&composition_parameters)?; 73 | 74 | export_to_midi_file(&composition, composition_path) 75 | } 76 | 77 | /// Helper to build music events. Chord intervals will be transposed and 78 | /// converted to midi key values in the returned `PatternEvent`. 79 | /// 80 | /// # Arguments 81 | /// * `bar` - The bar for the music event. 82 | /// * `beat` - The beat of the msuic event. 83 | /// * `beat_interval` - The interval value between beats. There are 8 84 | /// * `beat_intervals` in a `beat`, this is the same as a 16th. 85 | /// * `chord_intervals` - The note intervals in the chord. 86 | /// * `transpose` - The transpose of all the chord intervals. 87 | pub fn build_event( 88 | bar: u16, 89 | beat: u8, 90 | beat_interval: u8, 91 | chord_intervals: Vec, 92 | transpose: i8, 93 | ) -> composition::PatternEvent { 94 | ( 95 | music_time::MusicTime::new(bar, beat, beat_interval), 96 | chords::IntervalChord::new(chord_intervals, transpose) 97 | .transpose_octave(3) 98 | .to_midi(), 99 | ) 100 | } 101 | 102 | /// Play a composition starting from the composition's pattern index 103 | /// and time. 104 | /// 105 | /// # Arguments 106 | /// * `composition` - The composition to play. 107 | /// * `performance_state` - The state performance with callbacks to be triggered. 108 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 109 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 110 | /// and the compiler feature `with-sound` is used. 111 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 112 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 113 | /// * `playback_start` - The `MusicTime` to begin playback at. 114 | /// * `pattern_start_index` - The index of the `MusicPattern` to begin from. 115 | pub fn play_from_index( 116 | composition: &composition::Composition, 117 | performance_state: &mut State, 118 | is_metronome_enabled: bool, 119 | sample_paths_metronome: &Vec, 120 | sample_paths_piano: &Vec, 121 | playback_start: &music_timer::music_time::MusicTime, 122 | pattern_start_index: usize, 123 | ) -> Result { 124 | let mut performance_engine = performance_engine::PerformanceEngine::new( 125 | &composition, 126 | performance_state, 127 | sample_paths_metronome, 128 | sample_paths_piano, 129 | )?; 130 | 131 | performance_engine.set_metronome_enabled(is_metronome_enabled); 132 | performance_engine.run_from(playback_start, pattern_start_index); 133 | Ok(SuccessResult::Playback) 134 | } 135 | 136 | /// Play a composition starting from a composition's pattern name 137 | /// and time. 138 | /// 139 | /// # Arguments 140 | /// * `composition` - The composition to play. 141 | /// * `performance_state` - The state performance with callbacks to be triggered. 142 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 143 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 144 | /// and the compiler feature `with-sound` is used. 145 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 146 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 147 | /// * `playback_start` - The `MusicTime` to begin playback at. 148 | /// * `pattern_start_name` - The name of the `MusicPattern` to begin from. 149 | /// 150 | /// # Example 151 | /// ``` 152 | /// use chord_composer::{ 153 | /// performance::performance_engine::PerformanceState, 154 | /// theory::composition::{Composition, Pattern, PatternEvent}, 155 | /// FailResult, SuccessResult, 156 | /// }; 157 | /// use music_timer::{music_time::MusicTime, time_signature::TimeSignature}; 158 | /// 159 | /// struct MyState { 160 | /// events: u16, 161 | /// current_time: MusicTime, 162 | /// } 163 | /// impl PerformanceState for MyState { 164 | /// fn on_ready(&mut self, _composition: &Composition) { 165 | /// println!("on_ready"); 166 | /// } 167 | /// fn on_beat_interval_change(&mut self, current_time: &MusicTime) { 168 | /// self.current_time = current_time.clone(); 169 | /// println!("on_beat_interval_change: {:?}", current_time); 170 | /// } 171 | /// fn on_beat_change(&mut self, current_time: &MusicTime) { 172 | /// println!("on_beat_change: {:?}", current_time); 173 | /// } 174 | /// fn on_bar_change(&mut self, current_time: &MusicTime) { 175 | /// println!("on_bar_change: {:?}", current_time); 176 | /// } 177 | /// fn on_event(&mut self, event: &PatternEvent) { 178 | /// self.events += 1; 179 | /// 180 | /// if self.events == 1 { 181 | /// assert_eq!(event, &chord_composer::build_event(2, 1, 1, vec![1], -1)); 182 | /// } else if self.events == 2 { 183 | /// assert_eq!(event, &chord_composer::build_event(3, 5, 1, vec![2], -2)); 184 | /// } 185 | /// println!("on_event"); 186 | /// } 187 | /// fn on_pattern_playback_begin(&mut self, _pattern: &Pattern) { 188 | /// println!("on_pattern_playback_begin"); 189 | /// } 190 | /// fn on_pattern_playback_end(&mut self, _pattern: &Pattern) { 191 | /// println!("on_pattern_playback_end"); 192 | /// } 193 | /// fn on_completed(&mut self, composition: &Composition) { 194 | /// assert_eq!(composition.get_name(), "test compo"); 195 | /// println!("on_completed"); 196 | /// } 197 | /// } 198 | /// 199 | /// let composition = Composition::new_with_patterns( 200 | /// "test compo", 201 | /// vec![ 202 | /// Pattern::new_with_events( 203 | /// "a", 204 | /// 100, 205 | /// TimeSignature::default(), 206 | /// vec![ 207 | /// chord_composer::build_event(1, 1, 1, vec![0, 3, 7], 0), 208 | /// chord_composer::build_event(2, 1, 1, vec![0, 3, 7], 1), 209 | /// chord_composer::build_event(3, 5, 1, vec![0, 3, 7], 2), 210 | /// ], 211 | /// ), 212 | /// Pattern::new_with_events( 213 | /// "b", 214 | /// 150, 215 | /// TimeSignature::new(7, 4), 216 | /// vec![ 217 | /// chord_composer::build_event(3, 5, 1, vec![2], -2), 218 | /// chord_composer::build_event(2, 1, 1, vec![1], -1), 219 | /// chord_composer::build_event(1, 1, 1, vec![0], 0), 220 | /// ], 221 | /// ), 222 | /// ], 223 | /// ); 224 | /// 225 | /// let mut my_state = MyState { 226 | /// events: 0, 227 | /// current_time: MusicTime::default(), 228 | /// }; 229 | /// 230 | /// assert_eq!( 231 | /// chord_composer::play_from( 232 | /// &composition, 233 | /// &mut my_state, 234 | /// false, 235 | /// &Vec::new(), 236 | /// &Vec::new(), 237 | /// &MusicTime::new(2, 1, 1), 238 | /// "b" 239 | /// ), 240 | /// Ok(SuccessResult::Playback), 241 | /// ); 242 | /// 243 | /// assert_eq!(my_state.events, 2); 244 | /// assert_eq!(my_state.current_time, MusicTime::new(3, 7, 8)); 245 | /// 246 | /// assert_eq!( 247 | /// chord_composer::play_from( 248 | /// &composition, 249 | /// &mut my_state, 250 | /// false, 251 | /// &Vec::new(), 252 | /// &Vec::new(), 253 | /// &MusicTime::new(2, 1, 1), 254 | /// "c" 255 | /// ), 256 | /// Err(FailResult::NoFoundPattern("c".to_owned())), 257 | /// ); 258 | /// ``` 259 | pub fn play_from( 260 | composition: &composition::Composition, 261 | performance_state: &mut State, 262 | is_metronome_enabled: bool, 263 | sample_paths_metronome: &Vec, 264 | sample_paths_piano: &Vec, 265 | playback_start: &music_timer::music_time::MusicTime, 266 | pattern_start_name: &str, 267 | ) -> Result { 268 | let patterns_playback_index = composition 269 | .get_patterns() 270 | .iter() 271 | .position(|pattern| pattern.get_name() == pattern_start_name); 272 | 273 | match patterns_playback_index { 274 | Some(pattern_index) => play_from_index( 275 | composition, 276 | performance_state, 277 | is_metronome_enabled, 278 | sample_paths_metronome, 279 | sample_paths_piano, 280 | playback_start, 281 | pattern_index, 282 | ), 283 | None => Err(FailResult::NoFoundPattern(pattern_start_name.to_owned())), 284 | } 285 | } 286 | 287 | /// Play a composition and all it's patterns from the start. 288 | /// 289 | /// # Arguments 290 | /// * `composition` - The composition to play. 291 | /// * `performance_state` - The state performance with callbacks to be triggered. 292 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 293 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 294 | /// and the compiler feature `with-sound` is used. 295 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 296 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 297 | pub fn play( 298 | composition: &composition::Composition, 299 | performance_state: &mut State, 300 | is_metronome_enabled: bool, 301 | sample_paths_metronome: &Vec, 302 | sample_paths_piano: &Vec, 303 | ) -> Result { 304 | play_from_index( 305 | composition, 306 | performance_state, 307 | is_metronome_enabled, 308 | sample_paths_metronome, 309 | sample_paths_piano, 310 | &music_time::MusicTime::default(), 311 | 0, 312 | ) 313 | } 314 | 315 | /// Load a YAML file of a composition then play all it's patterns from the start. 316 | /// 317 | /// # Arguments 318 | /// * `composition_path` - Path to the composition YAML file. 319 | /// * `performance_state` - The state performance with callbacks to be triggered. 320 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 321 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 322 | /// and the compiler feature `with-sound` is used. 323 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 324 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 325 | pub fn play_file( 326 | composition_path: &str, 327 | performance_state: &mut State, 328 | is_metronome_enabled: bool, 329 | sample_paths_metronome: &Vec, 330 | sample_paths_piano: &Vec, 331 | ) -> Result { 332 | let composition_parameters = io::deseralizer::deserialize_file(composition_path)?; 333 | let composition = parameters_to_composition(&composition_parameters)?; 334 | play( 335 | &composition, 336 | performance_state, 337 | is_metronome_enabled, 338 | sample_paths_metronome, 339 | sample_paths_piano, 340 | ) 341 | } 342 | 343 | /// Parse YAML of a composition then play all it's patterns from the start. 344 | /// 345 | /// # Arguments 346 | /// * `composition_yaml` - Composition YAML. 347 | /// * `performance_state` - The state performance with callbacks to be triggered. 348 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 349 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 350 | /// and the compiler feature `with-sound` is used. 351 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 352 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 353 | pub fn play_yaml( 354 | composition_yaml: &str, 355 | performance_state: &mut State, 356 | is_metronome_enabled: bool, 357 | sample_paths_metronome: &Vec, 358 | sample_paths_piano: &Vec, 359 | ) -> Result { 360 | let composition_parameters = io::deseralizer::deserialize_string(composition_yaml)?; 361 | let composition = parameters_to_composition(&composition_parameters)?; 362 | play( 363 | &composition, 364 | performance_state, 365 | is_metronome_enabled, 366 | sample_paths_metronome, 367 | sample_paths_piano, 368 | ) 369 | } 370 | 371 | /// Load a YAML file of a composition then play it's patterns starting from a composition's pattern name 372 | /// and time. 373 | /// 374 | /// # Arguments 375 | /// * `composition_path` - Path to the composition YAML file. 376 | /// * `performance_state` - The state performance with callbacks to be triggered. 377 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 378 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 379 | /// and the compiler feature `with-sound` is used. 380 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 381 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 382 | /// * `playback_start` - The `MusicTime` to begin playback at. 383 | /// * `pattern_start_name` - The name of the `MusicPattern` to begin from. 384 | pub fn play_file_from( 385 | composition_path: &str, 386 | performance_state: &mut State, 387 | is_metronome_enabled: bool, 388 | sample_paths_metronome: &Vec, 389 | sample_paths_piano: &Vec, 390 | playback_start: &music_timer::music_time::MusicTime, 391 | pattern_start_name: &str, 392 | ) -> Result { 393 | let composition_parameters = io::deseralizer::deserialize_file(composition_path)?; 394 | let composition = parameters_to_composition(&composition_parameters)?; 395 | play_from( 396 | &composition, 397 | performance_state, 398 | is_metronome_enabled, 399 | sample_paths_metronome, 400 | sample_paths_piano, 401 | playback_start, 402 | pattern_start_name, 403 | ) 404 | } 405 | 406 | /// Parse YAML of a composition then play all it's patterns starting from a composition's pattern name 407 | /// and time. 408 | /// 409 | /// # Arguments 410 | /// * `composition_yaml` - Path to the composition YAML file. 411 | /// * `performance_state` - The state performance with callbacks to be triggered. 412 | /// * `is_metronome_enabled` - Set if the metronome is to be played during playback. 413 | /// This is only relevant when `sample_paths_metronome` contains audio file paths 414 | /// and the compiler feature `with-sound` is used. 415 | /// * `sample_paths_metronome` - Paths to the 2 metronome audio files, tick and tock. 416 | /// * `sample_paths_piano` - Paths to the playback instrument audio files. 417 | /// * `playback_start` - The `MusicTime` to begin playback at. 418 | /// * `pattern_start_name` - The name of the `MusicPattern` to begin from. 419 | pub fn play_yaml_from( 420 | composition_yaml: &str, 421 | performance_state: &mut State, 422 | is_metronome_enabled: bool, 423 | sample_paths_metronome: &Vec, 424 | sample_paths_piano: &Vec, 425 | playback_start: &music_timer::music_time::MusicTime, 426 | pattern_start_name: &str, 427 | ) -> Result { 428 | let composition_parameters = io::deseralizer::deserialize_string(composition_yaml)?; 429 | let composition = parameters_to_composition(&composition_parameters)?; 430 | play_from( 431 | &composition, 432 | performance_state, 433 | is_metronome_enabled, 434 | sample_paths_metronome, 435 | sample_paths_piano, 436 | playback_start, 437 | pattern_start_name, 438 | ) 439 | } 440 | 441 | /// Export a template of a composition YAML file to a path. 442 | /// 443 | /// # Arguments 444 | /// * `path` - The path to export the template to. 445 | pub fn export_template(path: &str) -> Result { 446 | let mut file = match std::fs::File::create(path) { 447 | Ok(file) => Ok(file), 448 | _ => Err(FailResult::ExportTemplate), 449 | }?; 450 | 451 | let write_out = file.write_all( 452 | br#" 453 | # Name of the composition 454 | name: default_composition 455 | 456 | # The default master parameters of the composition. 457 | # New master pattern can be assigned to a pattern that overrides 458 | # the default master values. 459 | master: 460 | # The musical key to transpose the chords. 461 | # Supported values: C, C#, D, D#, E, F, F#, G, G#, A, A#, B 462 | key: F# 463 | 464 | # The beats per minute of the composition. 465 | time: 120 466 | 467 | # The time signature of the composition. 468 | # Beat numerator supported values: must be > 0. 469 | # Beat denominator supported values: 2, 4, 8, 16, 32, 64 470 | # e.g 3/8 is supported, 0/7 is not supported. 471 | signature: [4, 4] 472 | 473 | # Composition defined chords. 474 | chords: 475 | # [chord_name, [chord intervals]]. 476 | - [custom1, [0, 3, 8]] 477 | - [custom2, [0, 5]] 478 | 479 | # The composition's chord patterns/progressions. 480 | patterns: 481 | - name: part_a 482 | # Each pattern event = [bar, beat, beat interval, chord name, chord transpose]. 483 | pattern: 484 | - [1, 1, 1, MAJOR_SEVENTH, 0] 485 | - [1, 3, 1, custom1, 0] 486 | - [2, 1, 1, MAJOR_NINTH, 0] 487 | - [2, 3, 1, custom1, 0] 488 | - [3, 1, 1, MAJOR_SEVENTH, 3] 489 | - [3, 2, 1, custom1, 0] 490 | - [4, 1, 1, MAJOR_NINTH, -3] 491 | - [4, 2, 1, ?, 0] # ? = Select a random user defined chord. 492 | 493 | - name: part_b 494 | master: 495 | signature: [3, 4] 496 | key: C# 497 | time: 69 498 | # Each pattern event = [bar, beat, beat interval, chord name, chord transpose]. 499 | pattern: 500 | - [1, 1, 1, MAJOR_SEVENTH, 0] 501 | - [1, 2, 1, custom1, 0] 502 | - [2, 1, 5, MAJOR_NINTH, 0] 503 | - [2, 2, 1, custom1, 0] 504 | - [3, 1, 5, MAJOR_SEVENTH, 3] 505 | - [3, 2, 1, custom1, 0] 506 | - [4, 1, 1, MAJOR_NINTH, -3] 507 | - [4, 2, 1, ??, 0] #?? = Select a random chord from user defined and internal defined chord. 508 | "#, 509 | ); 510 | 511 | match write_out { 512 | Ok(()) => Ok(SuccessResult::ExportTemplate), 513 | _ => Err(FailResult::ExportTemplate), 514 | } 515 | } 516 | 517 | /// Convert YAML deserialized composition parameters to a `Composition` data type. 518 | /// 519 | /// # Arguments 520 | /// * `params` - The `CompositionParameters` to convert into a `Composition`. 521 | fn parameters_to_composition( 522 | params: &io::deseralizer::CompositionParameters, 523 | ) -> Result { 524 | let default_master: io::deseralizer::MasterParameters = match params.get_master() { 525 | Some(master) => master.clone(), 526 | None => io::deseralizer::MasterParameters::default(), 527 | }; 528 | 529 | let mut composition_result = Ok(composition::Composition::new(¶ms.get_name())); 530 | let mut count = 0; 531 | 532 | match params.get_patterns() { 533 | None => Err(crate::FailResult::NoPatterns), 534 | Some(patterns) => { 535 | if patterns.is_empty() { 536 | Err(crate::FailResult::EmptyPatterns) 537 | } else { 538 | for pattern in patterns { 539 | if composition_result.is_err() { 540 | break; 541 | } 542 | 543 | match pattern.get_pattern() { 544 | Some(pattern_pattern) => { 545 | let pattern_master = match pattern.get_master() { 546 | Some(pattern_master) => { 547 | io::deseralizer::MasterParameters::from_overrides(&default_master, pattern_master) 548 | } 549 | _ => default_master.clone(), 550 | }; 551 | 552 | let name = match pattern.get_name() { 553 | Some(name) => name.to_string(), 554 | _ => format!("unnamed_pattern_{}", count), 555 | }; 556 | let composition_pattern = { 557 | let mut pattern = { 558 | let bpm = pattern_master.get_time_or_default(); 559 | let (numerator, denominator) = pattern_master.get_signature_or_default(); 560 | let time_signature = time_signature::TimeSignature::new(numerator, denominator); 561 | 562 | if !time_signature.is_valid() { 563 | composition_result = Err(FailResult::TimeSignature(time_signature)); 564 | break; 565 | } 566 | 567 | composition::Pattern::new(&name, bpm, time_signature) 568 | }; 569 | 570 | let additional_chords = match params.get_custom_chords() { 571 | Some(custom_chords) => custom_chords.clone(), 572 | None => Vec::new(), 573 | }; 574 | 575 | for (bar, beat, beat_interval, chord_string, transpose) in pattern_pattern { 576 | let chord_notes = { 577 | let mut chord_intervals = chords::IntervalChord::from_string_with_custom( 578 | chord_string, 579 | &additional_chords, 580 | ); 581 | 582 | let key_offset = { 583 | let key_string = pattern_master.get_key_or_default(); 584 | let key = notes::string_to_key(&key_string); 585 | notes::key_to_index(key) as i8 586 | }; 587 | chord_intervals 588 | .transpose(key_offset) 589 | .transpose(*transpose) 590 | .transpose_octave(3) 591 | .to_midi() 592 | }; 593 | 594 | let time = music_time::MusicTime::new(*bar, *beat, *beat_interval); 595 | 596 | const INTERVAL_RESOLUTION: u8 = 16; 597 | let unreachable_beat_interval = time.get_beat_interval() 598 | > INTERVAL_RESOLUTION / 2 599 | || time.get_beat_interval() == 0; 600 | let unreachable_beat = time.get_beat() 601 | > pattern.get_time_signature().get_numerator() 602 | || time.get_beat() == 0; 603 | let unreachable_bar = time.get_bar() == 0; 604 | 605 | if unreachable_bar || unreachable_beat_interval || unreachable_beat { 606 | composition_result = Err(FailResult::UnreachableTime( 607 | time, 608 | pattern.len(), 609 | chord_string.to_string(), 610 | )); 611 | } else if pattern.len() != 0 { 612 | let previous_time = pattern.get(pattern.len() - 1).0; 613 | let time_does_not_advance = time == previous_time; 614 | 615 | if time_does_not_advance { 616 | composition_result = Err(FailResult::UnreachableTime( 617 | time, 618 | pattern.len(), 619 | chord_string.to_string(), 620 | )); 621 | } else { 622 | let reverse_time_flow = time < previous_time; 623 | if reverse_time_flow { 624 | composition_result = Err(FailResult::TimeReverse( 625 | time, 626 | pattern.len(), 627 | chord_string.to_string(), 628 | )); 629 | } 630 | } 631 | } 632 | 633 | pattern.push_event(time, chord_notes); 634 | } 635 | pattern 636 | }; 637 | 638 | match &mut composition_result { 639 | Ok(composition) => composition.push_pattern(composition_pattern), 640 | _ => break, 641 | } 642 | 643 | count += 1; 644 | } 645 | None => composition_result = Err(FailResult::NoPatterns), 646 | } 647 | } 648 | 649 | composition_result 650 | } 651 | } 652 | } 653 | } 654 | 655 | #[test] 656 | fn test_new_composition() { 657 | let params = io::deseralizer::deserialize_string( 658 | r#" 659 | name: bc_000_a 660 | 661 | # Can be overridden by patterns 662 | master: 663 | key: D 664 | time: 128 665 | signature: [3, 4] 666 | 667 | chords: 668 | - [custom1, [0, 3, 8]] 669 | 670 | patterns: 671 | - name: part_a 672 | pattern: 673 | - [1,1,1, MAJOR_SEVENTH, 0] 674 | - [1,3,1, custom1, 0] 675 | - [2,1,1, MAJOR_NINTH, 0] 676 | - [2,3,1, custom1, 0] 677 | - [3,1,1, MAJOR_SEVENTH, 3] 678 | - [3,2,1, custom1, 0] 679 | - [4,1,1, MAJOR_NINTH, -3] 680 | - [4,2,1, custom1, -3] 681 | 682 | - name: part_b 683 | master: 684 | signature: [4, 8] 685 | key: C# 686 | time: 69 687 | pattern: 688 | - [1,1,1, MAJOR_SEVENTH, 0] 689 | - [1,2,1, custom1, 0] 690 | - [2,1,1, MAJOR_NINTH, 0] 691 | - [2,2,1, custom1, 0] 692 | - [3,1,1, MAJOR_SEVENTH, 3] 693 | - [3,2,1, custom1, 0] 694 | - [4,1,1, MAJOR_NINTH, -3] 695 | - [4,2,1, custom1, 0] 696 | 697 | "#, 698 | ); 699 | 700 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 701 | 702 | let compo = parameters_to_composition(¶ms.unwrap()).unwrap(); 703 | 704 | assert_eq!(compo.len(), 2); 705 | assert_eq!(compo.get(0).len(), 8); 706 | assert_eq!(compo.get(1).len(), 8); 707 | 708 | assert_eq!(compo.get(0).get_bpm(), 128); 709 | assert_eq!( 710 | compo.get(0).get_time_signature(), 711 | time_signature::TimeSignature::new(3, 4) 712 | ); 713 | 714 | assert_eq!(compo.get(1).get_bpm(), 69); 715 | assert_eq!( 716 | compo.get(1).get_time_signature(), 717 | time_signature::TimeSignature::new(4, 8) 718 | ); 719 | 720 | let (time, notes) = compo.get(0).get(0); 721 | assert_eq!(time, &music_time::MusicTime::new(1, 1, 1)); 722 | assert_eq!(notes, &vec![62, 66, 69, 73]); 723 | 724 | let (time, notes) = compo.get(0).get(1); 725 | assert_eq!(time, &music_time::MusicTime::new(1, 3, 1)); 726 | assert_eq!(notes, &vec![62, 65, 70]); 727 | 728 | let (time, notes) = compo.get(0).get(2); 729 | assert_eq!(time, &music_time::MusicTime::new(2, 1, 1)); 730 | assert_eq!(notes, &vec![62, 66, 69, 73, 64]); 731 | 732 | let (time, notes) = compo.get(0).get(7); 733 | assert_eq!(time, &music_time::MusicTime::new(4, 2, 1)); 734 | assert_eq!(notes, &vec![59, 62, 67]); 735 | 736 | let (time, notes) = compo.get(1).get(0); 737 | assert_eq!(time, &music_time::MusicTime::new(1, 1, 1)); 738 | assert_eq!(notes, &vec![61, 65, 68, 72]); 739 | 740 | let (time, notes) = compo.get(1).get(1); 741 | assert_eq!(time, &music_time::MusicTime::new(1, 2, 1)); 742 | assert_eq!(notes, &vec![61, 64, 69]); 743 | 744 | let (time, notes) = compo.get(1).get(2); 745 | assert_eq!(time, &music_time::MusicTime::new(2, 1, 1)); 746 | assert_eq!(notes, &vec![61, 65, 68, 72, 63]); 747 | 748 | let (time, notes) = compo.get(1).get(7); 749 | assert_eq!(time, &music_time::MusicTime::new(4, 2, 1)); 750 | assert_eq!(notes, &vec![61, 64, 69]); 751 | } 752 | 753 | #[test] 754 | fn test_flow_reverse() { 755 | let params = io::deseralizer::deserialize_string( 756 | r#" 757 | patterns: 758 | - name: part_a 759 | pattern: 760 | - [1,3,1, MAJOR_SEVENTH, 0] 761 | - [1,2,1, custom1, 0] 762 | "#, 763 | ); 764 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 765 | 766 | let compo = parameters_to_composition(¶ms.unwrap()); 767 | 768 | match compo { 769 | Err(FailResult::TimeReverse(music_time, index, chord)) => { 770 | assert_eq!(music_time, music_time::MusicTime::new(1, 2, 1)); 771 | assert_eq!(index, 1); 772 | assert_eq!(chord, "custom1".to_string()); 773 | } 774 | _ => assert_eq!(false, true), 775 | } 776 | } 777 | 778 | #[test] 779 | fn test_unreachable_time() { 780 | { 781 | let params = io::deseralizer::deserialize_string( 782 | r#" 783 | patterns: 784 | - name: part_a 785 | pattern: 786 | - [1,3,1, MAJOR_SEVENTH, 0] 787 | - [1,2,9, custom1, 0] 788 | "#, 789 | ); 790 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 791 | 792 | let compo = parameters_to_composition(¶ms.unwrap()); 793 | 794 | match compo { 795 | Err(FailResult::UnreachableTime(music_time, index, chord)) => { 796 | assert_eq!(music_time, music_time::MusicTime::new(1, 2, 9)); 797 | assert_eq!(index, 1); 798 | assert_eq!(chord, "custom1".to_string()); 799 | } 800 | _ => assert_eq!(false, true), 801 | } 802 | } 803 | { 804 | let params = io::deseralizer::deserialize_string( 805 | r#" 806 | patterns: 807 | - name: part_a 808 | pattern: 809 | - [1,3,1, MAJOR_SEVENTH, 0] 810 | - [1,9,1, custom1, 0] 811 | "#, 812 | ); 813 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 814 | 815 | let compo = parameters_to_composition(¶ms.unwrap()); 816 | 817 | match compo { 818 | Err(FailResult::UnreachableTime(music_time, index, chord)) => { 819 | assert_eq!(music_time, music_time::MusicTime::new(1, 9, 1)); 820 | assert_eq!(index, 1); 821 | assert_eq!(chord, "custom1".to_string()); 822 | } 823 | _ => assert_eq!(false, true), 824 | } 825 | } 826 | { 827 | let params = io::deseralizer::deserialize_string( 828 | r#" 829 | patterns: 830 | - name: part_a 831 | pattern: 832 | - [1,3,1, MAJOR_SEVENTH, 0] 833 | - [1,9,0, custom1, 0] 834 | "#, 835 | ); 836 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 837 | 838 | let compo = parameters_to_composition(¶ms.unwrap()); 839 | 840 | match compo { 841 | Err(FailResult::UnreachableTime(music_time, index, chord)) => { 842 | assert_eq!(music_time, music_time::MusicTime::new(1, 9, 0)); 843 | assert_eq!(index, 1); 844 | assert_eq!(chord, "custom1".to_string()); 845 | } 846 | _ => assert_eq!(false, true), 847 | } 848 | } 849 | { 850 | let params = io::deseralizer::deserialize_string( 851 | r#" 852 | patterns: 853 | - name: part_a 854 | pattern: 855 | - [1,0,1, custom1, 0] 856 | "#, 857 | ); 858 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 859 | 860 | let compo = parameters_to_composition(¶ms.unwrap()); 861 | 862 | match compo { 863 | Err(FailResult::UnreachableTime(music_time, index, chord)) => { 864 | assert_eq!(music_time, music_time::MusicTime::new(1, 0, 1)); 865 | assert_eq!(index, 0); 866 | assert_eq!(chord, "custom1".to_string()); 867 | } 868 | _ => assert_eq!(false, true), 869 | } 870 | } 871 | { 872 | let params = io::deseralizer::deserialize_string( 873 | r#" 874 | patterns: 875 | - name: part_a 876 | pattern: 877 | - [0,1,1, custom1, 0] 878 | "#, 879 | ); 880 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 881 | 882 | let compo = parameters_to_composition(¶ms.unwrap()); 883 | 884 | match compo { 885 | Err(FailResult::UnreachableTime(music_time, index, chord)) => { 886 | assert_eq!(music_time, music_time::MusicTime::new(0, 1, 1)); 887 | assert_eq!(index, 0); 888 | assert_eq!(chord, "custom1".to_string()); 889 | } 890 | _ => assert!(false, true), 891 | } 892 | } 893 | { 894 | let params = io::deseralizer::deserialize_string( 895 | r#" 896 | patterns: 897 | - name: part_a 898 | pattern: 899 | - [1,1,1, MINOR, 0] 900 | - [1,1,1, custom1, 0] 901 | "#, 902 | ); 903 | assert_ne!(params, Err(crate::FailResult::Deserialize)); 904 | 905 | let compo = parameters_to_composition(¶ms.unwrap()); 906 | 907 | match compo { 908 | Err(FailResult::UnreachableTime(music_time, index, chord)) => { 909 | assert_eq!(music_time, music_time::MusicTime::new(1, 1, 1)); 910 | assert_eq!(index, 1); 911 | assert_eq!(chord, "custom1".to_string()); 912 | } 913 | _ => assert!(false, true), 914 | } 915 | } 916 | } 917 | -------------------------------------------------------------------------------- /src/performance/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod performance_engine; 2 | 3 | mod tests { 4 | #[test] 5 | fn test_performance_engine() { 6 | use crate::{ 7 | performance_engine::{PerformanceEngine, PerformanceState}, 8 | theory::composition, 9 | }; 10 | use music_timer::{music_time, time_signature}; 11 | 12 | struct MyState { 13 | callback_calls: u8, 14 | current_time: music_time::MusicTime, 15 | } 16 | 17 | impl PerformanceState for MyState { 18 | fn on_ready(&mut self, composition: &composition::Composition) { 19 | self.callback_calls += 1; 20 | assert_eq!(composition.get_name(), "test compo"); 21 | } 22 | fn on_beat_interval_change(&mut self, current_time: &music_time::MusicTime) { 23 | self.callback_calls += 1; 24 | self.current_time = current_time.clone(); 25 | println!("on_beat_interval_change: {:?}", current_time); 26 | } 27 | fn on_beat_change(&mut self, current_time: &music_time::MusicTime) { 28 | self.callback_calls += 1; 29 | println!("on_beat_change: {:?}", current_time); 30 | } 31 | fn on_bar_change(&mut self, current_time: &music_time::MusicTime) { 32 | self.callback_calls += 1; 33 | println!("on_bar_change: {:?}", current_time); 34 | } 35 | fn on_event(&mut self, event: &composition::PatternEvent) { 36 | self.callback_calls += 1; 37 | 38 | let (time, notes) = event; 39 | 40 | if time == &music_time::MusicTime::new(1, 3, 1) { 41 | let test_notes: Vec = Vec::new(); 42 | assert_eq!(*notes, test_notes); 43 | } 44 | println!("event: {:?}", event); 45 | } 46 | fn on_pattern_playback_begin(&mut self, pattern: &composition::Pattern) { 47 | self.callback_calls += 1; 48 | 49 | if self.callback_calls < 80 { 50 | assert_eq!(pattern.get_name(), "pattern_z"); 51 | assert_eq!( 52 | pattern.get_time_signature(), 53 | time_signature::TimeSignature::default() 54 | ); 55 | assert_eq!(pattern.get_bpm(), 140); 56 | assert_eq!(pattern.len(), 2); 57 | } else { 58 | assert_eq!(pattern.get_name(), "pattern_y"); 59 | assert_eq!( 60 | pattern.get_time_signature(), 61 | time_signature::TimeSignature::default() 62 | ); 63 | assert_eq!(pattern.get_bpm(), 130); 64 | assert_eq!(pattern.len(), 2); 65 | } 66 | } 67 | fn on_pattern_playback_end(&mut self, pattern: &composition::Pattern) { 68 | self.callback_calls += 1; 69 | if self.callback_calls < 119 { 70 | assert_eq!(pattern.get_name(), "pattern_z"); 71 | } else { 72 | assert_eq!(pattern.get_name(), "pattern_y"); 73 | } 74 | } 75 | fn on_completed(&mut self, composition: &composition::Composition) { 76 | self.callback_calls += 1; 77 | assert_eq!(composition.get_name(), "test compo"); 78 | } 79 | } 80 | 81 | let composition = { 82 | let mut composition = composition::Composition::new("test compo"); 83 | let mut pattern = 84 | composition::Pattern::new("pattern_z", 140, time_signature::TimeSignature::default()); 85 | 86 | pattern.push_event(music_time::MusicTime::new(1, 3, 1), Vec::new()); 87 | pattern.push_event(music_time::MusicTime::new(3, 1, 1), Vec::new()); 88 | composition.push_pattern(pattern); 89 | 90 | let mut pattern = 91 | composition::Pattern::new("pattern_y", 130, time_signature::TimeSignature::default()); 92 | 93 | pattern.push_event(music_time::MusicTime::new(1, 3, 1), Vec::new()); 94 | pattern.push_event(music_time::MusicTime::new(3, 1, 1), Vec::new()); 95 | composition.push_pattern(pattern); 96 | 97 | composition 98 | }; 99 | 100 | let mut my_state = MyState { 101 | callback_calls: 0, 102 | current_time: music_time::MusicTime::default(), 103 | }; 104 | 105 | let performance_engine = 106 | PerformanceEngine::new(&composition, &mut my_state, &Vec::new(), &Vec::new()); 107 | 108 | match performance_engine { 109 | Ok(mut performance) => { 110 | performance.set_metronome_enabled(true); 111 | performance.run(); 112 | } 113 | _ => assert!(false, "Cannot create performance engine"), 114 | } 115 | assert_eq!(my_state.callback_calls, 232); 116 | assert_eq!(my_state.current_time, music_time::MusicTime::new(3, 4, 8)); 117 | } 118 | 119 | #[test] 120 | fn test_performance_engine_run_from() { 121 | use crate::{ 122 | performance_engine::{PerformanceEngine, PerformanceState}, 123 | theory::composition, 124 | }; 125 | use music_timer::{music_time, time_signature}; 126 | 127 | struct MyState { 128 | beat_interval_count: u8, 129 | current_time: music_time::MusicTime, 130 | } 131 | 132 | impl PerformanceState for MyState { 133 | fn on_ready(&mut self, _composition: &composition::Composition) {} 134 | fn on_beat_interval_change(&mut self, current_time: &music_time::MusicTime) { 135 | self.beat_interval_count += 1; 136 | self.current_time = current_time.clone(); 137 | println!("on_beat_interval_change: {:?}", current_time); 138 | } 139 | fn on_beat_change(&mut self, _current_time: &music_time::MusicTime) {} 140 | fn on_bar_change(&mut self, _current_time: &music_time::MusicTime) {} 141 | fn on_event(&mut self, _event: &composition::PatternEvent) {} 142 | fn on_pattern_playback_begin(&mut self, _pattern: &composition::Pattern) {} 143 | fn on_pattern_playback_end(&mut self, _pattern: &composition::Pattern) {} 144 | fn on_completed(&mut self, _composition: &composition::Composition) {} 145 | } 146 | 147 | let composition = { 148 | let mut composition = composition::Composition::new("test compo"); 149 | let mut pattern = 150 | composition::Pattern::new("pattern_z", 140, time_signature::TimeSignature::default()); 151 | 152 | pattern.push_event(music_time::MusicTime::new(1, 3, 1), Vec::new()); 153 | pattern.push_event(music_time::MusicTime::new(5, 1, 1), Vec::new()); 154 | composition.push_pattern(pattern); 155 | 156 | composition 157 | }; 158 | 159 | let mut my_state = MyState { 160 | beat_interval_count: 0, 161 | current_time: music_time::MusicTime::default(), 162 | }; 163 | 164 | let performance_engine = 165 | PerformanceEngine::new(&composition, &mut my_state, &Vec::new(), &Vec::new()); 166 | 167 | match performance_engine { 168 | Ok(mut performance) => { 169 | performance.set_metronome_enabled(true); 170 | performance.run_from(&music_time::MusicTime::new(4, 3, 5), 0); 171 | } 172 | _ => assert!(false, "Cannot create performance engine"), 173 | } 174 | assert_eq!(my_state.beat_interval_count, 44); 175 | assert_eq!(my_state.current_time, music_time::MusicTime::new(5, 4, 8)); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/performance/performance_engine.rs: -------------------------------------------------------------------------------- 1 | use crate::audio::basic_sampler; 2 | use crate::{theory::composition, FailResult}; 3 | use music_timer::{music_time, music_timer_engine}; 4 | use std::{thread, time::Duration}; 5 | 6 | pub trait PerformanceState { 7 | fn on_ready(&mut self, composition: &composition::Composition); 8 | fn on_beat_interval_change(&mut self, current_time: &music_time::MusicTime); 9 | fn on_beat_change(&mut self, current_time: &music_time::MusicTime); 10 | fn on_bar_change(&mut self, current_time: &music_time::MusicTime); 11 | fn on_event(&mut self, event: &composition::PatternEvent); 12 | fn on_pattern_playback_begin(&mut self, pattern: &composition::Pattern); 13 | fn on_pattern_playback_end(&mut self, pattern: &composition::Pattern); 14 | fn on_completed(&mut self, composition: &composition::Composition); 15 | } 16 | 17 | pub struct PerformanceEngine<'a, State: PerformanceState> { 18 | sampler_metronome: basic_sampler::SamplerPlayer, 19 | sampler_piano: basic_sampler::SamplerPlayer, 20 | event_head: usize, 21 | current_pattern: &'a composition::Pattern, 22 | is_playing: bool, 23 | composition: &'a composition::Composition, 24 | state: &'a mut State, 25 | is_metronome_enabled: bool, 26 | } 27 | 28 | impl<'a, State: PerformanceState> PerformanceEngine<'a, State> { 29 | pub fn new( 30 | composition: &'a composition::Composition, 31 | state: &'a mut State, 32 | sample_paths_metronome: &Vec, 33 | sample_paths_piano: &Vec, 34 | ) -> Result { 35 | if composition.len() > 0 { 36 | let sampler_metronome = basic_sampler::SamplerPlayer::new(sample_paths_metronome); 37 | let sampler_piano = basic_sampler::SamplerPlayer::new(&sample_paths_piano); 38 | let error_loading = sampler_metronome.is_err() || sampler_piano.is_err(); 39 | if error_loading { 40 | Err(FailResult::LoadSampler) 41 | } else { 42 | Ok(PerformanceEngine { 43 | sampler_metronome: sampler_metronome.unwrap(), 44 | sampler_piano: sampler_piano.unwrap(), 45 | event_head: 0, 46 | current_pattern: &composition.get(0), 47 | is_playing: false, 48 | composition, 49 | state, 50 | is_metronome_enabled: false, 51 | }) 52 | } 53 | } else { 54 | Err(FailResult::NoPatterns) 55 | } 56 | } 57 | 58 | pub fn run(&mut self) { 59 | self.run_from(&music_time::MusicTime::default(), 0); 60 | } 61 | 62 | pub fn run_from(&mut self, start_time: &music_time::MusicTime, starting_pattern_index: usize) { 63 | self.state.on_ready(self.composition); 64 | for pattern_index in starting_pattern_index..self.composition.get_patterns().len() { 65 | let pattern = &self.composition.get_patterns()[pattern_index]; 66 | self.state.on_pattern_playback_begin(pattern); 67 | self.is_playing = true; 68 | 69 | // Create new music timer 70 | let mut music_timer = music_timer::create_performance_engine( 71 | pattern.get_time_signature().get_numerator(), 72 | pattern.get_time_signature().get_denominator(), 73 | pattern.get_bpm() as f32, 74 | ); 75 | 76 | // Set the current time for playback and 77 | // advance events to that time 78 | self.event_head = pattern.find_next_event_index(start_time); 79 | music_timer.set_music_timer(*start_time); 80 | 81 | // Assign current pattern 82 | self.current_pattern = pattern; 83 | 84 | // Loop while playback enabled 85 | while self.is_playing { 86 | music_timer.pulse(self); 87 | const PULSE_RESOLUTION: Duration = Duration::from_millis(16); 88 | thread::sleep(PULSE_RESOLUTION); 89 | } 90 | self.state.on_pattern_playback_end(pattern); 91 | } 92 | 93 | self.state.on_completed(self.composition); 94 | } 95 | 96 | pub fn set_metronome_enabled(&mut self, is_enabled: bool) { 97 | self.is_metronome_enabled = is_enabled; 98 | } 99 | } 100 | 101 | impl<'a, State: PerformanceState> music_timer_engine::MusicTimerState 102 | for PerformanceEngine<'a, State> 103 | { 104 | fn on_beat_interval(&mut self, current_time: &music_time::MusicTime) { 105 | let events_complete = self.event_head == self.current_pattern.len(); 106 | 107 | const MAX_BEAT_INTERVALS: u8 = 8; 108 | self.is_playing = !(events_complete 109 | && current_time.get_beat() == self.current_pattern.get_time_signature().get_numerator() 110 | && current_time.get_beat_interval() == MAX_BEAT_INTERVALS); 111 | 112 | self.state.on_beat_interval_change(current_time); 113 | if !events_complete { 114 | let current_event = self.current_pattern.get(self.event_head); 115 | 116 | let (event_time, event_notes) = current_event; 117 | 118 | let is_event_trigger_time = current_time == event_time; 119 | if is_event_trigger_time { 120 | self.state.on_event(¤t_event); 121 | self.event_head += 1; 122 | 123 | for note in event_notes { 124 | let sample_index = { 125 | const MIDI_OFFSET: usize = 24; 126 | *note as usize - MIDI_OFFSET 127 | }; 128 | 129 | self.sampler_piano.play(sample_index); 130 | } 131 | } 132 | } 133 | } 134 | 135 | fn on_beat(&mut self, current_time: &music_time::MusicTime) { 136 | if !self.is_playing { 137 | return; 138 | } 139 | 140 | self.state.on_beat_change(current_time); 141 | if self.is_metronome_enabled && current_time.get_beat() != 1 { 142 | self.sampler_metronome.play(0); 143 | } 144 | } 145 | 146 | fn on_bar(&mut self, current_time: &music_time::MusicTime) { 147 | if !self.is_playing { 148 | return; 149 | } 150 | 151 | self.state.on_bar_change(current_time); 152 | if self.is_metronome_enabled { 153 | self.sampler_metronome.play(1); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/theory/chords.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use super::notes; 4 | 5 | pub type CustomChords = Vec<(String, Vec)>; 6 | 7 | // TODO: remove chords? Allow user created chords only? 8 | pub const AUGMENTED: [i8; 3] = [0, 4, 8]; 9 | pub const AUGMENTED_ELEVENTH: [i8; 6] = [0, 4, 7, 10, 2, 6]; 10 | pub const AUGMENTED_MAJOR_SEVENTH: [i8; 4] = [0, 4, 8, 11]; 11 | pub const AUGMENTED_SEVENTH: [i8; 4] = [0, 4, 8, 10]; 12 | pub const AUGMENTED_SIXTH: [i8; 3] = [0, 6, 8]; 13 | pub const DIMINISHED: [i8; 3] = [0, 3, 6]; 14 | pub const DIMINISHED_MAJOR_SEVENTH: [i8; 4] = [0, 3, 6, 11]; 15 | pub const DIMINISHED_SEVENTH: [i8; 4] = [0, 3, 6, 9]; 16 | pub const DOMINANT: [i8; 3] = [0, 4, 7]; 17 | pub const DOMINANT_ELEVENTH: [i8; 6] = [0, 4, 7, 10, 2, 5]; 18 | pub const DOMINANT_MINOR_NINTH: [i8; 5] = [0, 4, 7, 10, 1]; 19 | pub const DOMINANT_NINTH: [i8; 5] = [0, 4, 7, 10, 2]; 20 | pub const DOMINANT_PARALLEL: [i8; 3] = [0, 3, 7]; 21 | pub const DOMINANT_SEVENTH: [i8; 4] = [0, 4, 7, 10]; 22 | pub const DOMINANT_SEVENTH_FLAT_FIVE: [i8; 4] = [0, 4, 6, 10]; 23 | pub const DOMINANT_SEVENTH_RAISED_NINTH: [i8; 5] = [0, 4, 7, 10, 3]; 24 | pub const DOMINANT_THIRTEENTH: [i8; 7] = [0, 4, 7, 10, 2, 5, 9]; 25 | pub const DREAM: [i8; 4] = [0, 5, 6, 7]; 26 | pub const ELEKTRA: [i8; 5] = [0, 7, 9, 1, 4]; 27 | pub const FARBEN: [i8; 5] = [0, 8, 11, 4, 9]; 28 | pub const HALF_DIMINISHED_SEVENTH: [i8; 4] = [0, 3, 6, 10]; 29 | pub const HARMONIC_SEVENTH: [i8; 4] = [0, 4, 7, 10]; 30 | pub const AUGMENTED_NINTH: [i8; 5] = [0, 4, 7, 10, 3]; 31 | pub const LEADING_TONE: [i8; 3] = [0, 3, 6]; 32 | pub const LYDIAN: [i8; 5] = [0, 4, 7, 11, 6]; 33 | pub const MAGIC: [i8; 8] = [0, 1, 5, 6, 10, 0, 3, 5]; 34 | pub const MAJOR: [i8; 3] = [0, 4, 7]; 35 | pub const MAJOR_ELEVENTH: [i8; 6] = [0, 4, 7, 11, 2, 5]; 36 | pub const MAJOR_SEVENTH: [i8; 4] = [0, 4, 7, 11]; 37 | pub const MAJOR_SEVENTH_SHARP_ELEVENTH: [i8; 5] = [0, 4, 8, 11, 6]; 38 | pub const MAJOR_SIXTH: [i8; 4] = [0, 4, 7, 9]; 39 | pub const MAJOR_SIXTH_NINTH: [i8; 5] = [0, 4, 7, 9, 2]; 40 | pub const MAJOR_NINTH: [i8; 5] = [0, 4, 7, 11, 2]; 41 | pub const MAJOR_THIRTEENTH: [i8; 7] = [0, 4, 7, 11, 2, 6, 9]; 42 | pub const MEDIANT: [i8; 3] = [0, 3, 7]; 43 | pub const MINOR: [i8; 3] = [0, 3, 7]; 44 | pub const MINOR_ELEVENTH: [i8; 6] = [0, 3, 7, 10, 2, 5]; 45 | pub const MINOR_MAJOR_SEVENTH: [i8; 4] = [0, 3, 7, 11]; 46 | pub const MINOR_NINTH: [i8; 5] = [0, 3, 7, 10, 2]; 47 | pub const MINOR_SEVENTH: [i8; 4] = [0, 3, 7, 10]; 48 | pub const MINOR_SIXTH: [i8; 4] = [0, 3, 7, 9]; 49 | pub const MINOR_SIXTH_NINTH: [i8; 5] = [0, 3, 7, 9, 2]; 50 | pub const MINOR_THIRTEENTH: [i8; 7] = [0, 3, 7, 10, 2, 5, 9]; 51 | pub const MU: [i8; 4] = [0, 2, 4, 7]; 52 | pub const MYSTIC: [i8; 6] = [0, 6, 10, 4, 9, 2]; 53 | pub const NEAPOLITAN: [i8; 3] = [1, 5, 8]; 54 | pub const NINTH_AUGMENTED_FIFTH: [i8; 5] = [0, 4, 8, 10, 2]; 55 | pub const NINTH_FLAT_FIFTH: [i8; 5] = [0, 4, 6, 10, 2]; 56 | pub const NORTHERN_LIGHTS: [i8; 11] = [1, 2, 8, 0, 3, 6, 7, 10, 11, 4, 7]; 57 | pub const ODE_TO_NAPOLEON_HEXACHORD: [i8; 6] = [0, 1, 4, 5, 8, 9]; 58 | pub const PETRUSHKA: [i8; 6] = [0, 1, 4, 6, 7, 10]; 59 | pub const POWER: [i8; 2] = [0, 7]; 60 | pub const PSALMS: [i8; 3] = [0, 3, 7]; 61 | pub const SECONDARY_DOMINANT: [i8; 3] = [0, 4, 7]; 62 | pub const SECONDARY_LEADING_TONE: [i8; 3] = [0, 3, 6]; 63 | pub const SECONDARY_SUPERTONIC: [i8; 3] = [0, 3, 7]; 64 | pub const SEVEN_SIX: [i8; 5] = [0, 4, 7, 9, 10]; 65 | pub const SEVENTH_FLAT_NINE: [i8; 5] = [0, 4, 7, 10, 1]; 66 | pub const SEVENTH_SUSPENSION_FOUR: [i8; 4] = [0, 5, 7, 10]; 67 | pub const SO_WHAT: [i8; 5] = [0, 5, 10, 3, 7]; 68 | pub const SUSPENDED: [i8; 3] = [0, 5, 7]; 69 | pub const SUBDOMINANT: [i8; 3] = [0, 4, 7]; 70 | pub const SUBDOMINANT_PARALLEL: [i8; 3] = [0, 3, 7]; 71 | pub const SUBMEDIANT: [i8; 3] = [0, 3, 7]; 72 | pub const SUBTONIC: [i8; 3] = [0, 4, 7]; 73 | pub const SUPERTONIC: [i8; 3] = [0, 3, 7]; 74 | pub const THIRTEENTH_FLAT_NINTH: [i8; 7] = [0, 4, 7, 10, 1, 12, 9]; 75 | pub const THIRTEENTH_FLAT_NINTH_FLAT_FIFTH: [i8; 7] = [0, 4, 6, 10, 1, 12, 9]; 76 | pub const TONIC_COUNTER_PARALLEL: [i8; 3] = [0, 3, 7]; 77 | pub const TONIC: [i8; 3] = [0, 4, 7]; 78 | pub const TONIC_PARALLEL: [i8; 3] = [0, 3, 7]; 79 | pub const TRISTAN: [i8; 4] = [0, 3, 6, 10]; 80 | pub const VIENNESE_TRICHORD: [i8; 4] = [0, 1, 6, 7]; 81 | 82 | pub fn string_to_chord(name: &str) -> Vec { 83 | match name { 84 | "AUGMENTED" => AUGMENTED.to_vec(), 85 | "AUGMENTED_ELEVENTH" => AUGMENTED_ELEVENTH.to_vec(), 86 | "AUGMENTED_MAJOR_SEVENTH" => AUGMENTED_MAJOR_SEVENTH.to_vec(), 87 | "AUGMENTED_SEVENTH" => AUGMENTED_SEVENTH.to_vec(), 88 | "AUGMENTED_SIXTH" => AUGMENTED_SIXTH.to_vec(), 89 | "DIMINISHED" => DIMINISHED.to_vec(), 90 | "DIMINISHED_MAJOR_SEVENTH" => DIMINISHED_MAJOR_SEVENTH.to_vec(), 91 | "DIMINISHED_SEVENTH" => DIMINISHED_SEVENTH.to_vec(), 92 | "DOMINANT" => DOMINANT.to_vec(), 93 | "DOMINANT_ELEVENTH" => DOMINANT_ELEVENTH.to_vec(), 94 | "DOMINANT_MINOR_NINTH" => DOMINANT_MINOR_NINTH.to_vec(), 95 | "DOMINANT_NINTH" => DOMINANT_NINTH.to_vec(), 96 | "DOMINANT_PARALLEL" => DOMINANT_PARALLEL.to_vec(), 97 | "DOMINANT_SEVENTH" => DOMINANT_SEVENTH.to_vec(), 98 | "DOMINANT_SEVENTH_FLAT_FIVE" => DOMINANT_SEVENTH_FLAT_FIVE.to_vec(), 99 | "DOMINANT_SEVENTH_RAISED_NINTH" => DOMINANT_SEVENTH_RAISED_NINTH.to_vec(), 100 | "DOMINANT_THIRTEENTH" => DOMINANT_THIRTEENTH.to_vec(), 101 | "DREAM" => DREAM.to_vec(), 102 | "ELEKTRA" => ELEKTRA.to_vec(), 103 | "FARBEN" => FARBEN.to_vec(), 104 | "HALF_DIMINISHED_SEVENTH" => HALF_DIMINISHED_SEVENTH.to_vec(), 105 | "HARMONIC_SEVENTH" => HARMONIC_SEVENTH.to_vec(), 106 | "AUGMENTED_NINTH" => AUGMENTED_NINTH.to_vec(), 107 | "LEADING_TONE" => LEADING_TONE.to_vec(), 108 | "LYDIAN" => LYDIAN.to_vec(), 109 | "MAGIC" => MAGIC.to_vec(), 110 | "MAJOR" => MAJOR.to_vec(), 111 | "MAJOR_ELEVENTH" => MAJOR_ELEVENTH.to_vec(), 112 | "MAJOR_SEVENTH" => MAJOR_SEVENTH.to_vec(), 113 | "MAJOR_SEVENTH_SHARP_ELEVENTH" => MAJOR_SEVENTH_SHARP_ELEVENTH.to_vec(), 114 | "MAJOR_SIXTH" => MAJOR_SIXTH.to_vec(), 115 | "MAJOR_SIXTH_NINTH" => MAJOR_SIXTH_NINTH.to_vec(), 116 | "MAJOR_NINTH" => MAJOR_NINTH.to_vec(), 117 | "MAJOR_THIRTEENTH" => MAJOR_THIRTEENTH.to_vec(), 118 | "MEDIANT" => MEDIANT.to_vec(), 119 | "MINOR" => MINOR.to_vec(), 120 | "MINOR_ELEVENTH" => MINOR_ELEVENTH.to_vec(), 121 | "MINOR_MAJOR_SEVENTH" => MINOR_MAJOR_SEVENTH.to_vec(), 122 | "MINOR_NINTH" => MINOR_NINTH.to_vec(), 123 | "MINOR_SEVENTH" => MINOR_SEVENTH.to_vec(), 124 | "MINOR_SIXTH" => MINOR_SIXTH.to_vec(), 125 | "MINOR_SIXTH_NINTH" => MINOR_SIXTH_NINTH.to_vec(), 126 | "MINOR_THIRTEENTH" => MINOR_THIRTEENTH.to_vec(), 127 | "MU" => MU.to_vec(), 128 | "MYSTIC" => MYSTIC.to_vec(), 129 | "NEAPOLITAN" => NEAPOLITAN.to_vec(), 130 | "NINTH_AUGMENTED_FIFTH" => NINTH_AUGMENTED_FIFTH.to_vec(), 131 | "NINTH_FLAT_FIFTH" => NINTH_FLAT_FIFTH.to_vec(), 132 | "NORTHERN_LIGHTS" => NORTHERN_LIGHTS.to_vec(), 133 | "ODE_TO_NAPOLEON_HEXACHORD" => ODE_TO_NAPOLEON_HEXACHORD.to_vec(), 134 | "PETRUSHKA" => PETRUSHKA.to_vec(), 135 | "POWER" => POWER.to_vec(), 136 | "PSALMS" => PSALMS.to_vec(), 137 | "SECONDARY_DOMINANT" => SECONDARY_DOMINANT.to_vec(), 138 | "SECONDARY_LEADING_TONE" => SECONDARY_LEADING_TONE.to_vec(), 139 | "SECONDARY_SUPERTONIC" => SECONDARY_SUPERTONIC.to_vec(), 140 | "SEVEN_SIX" => SEVEN_SIX.to_vec(), 141 | "SEVENTH_FLAT_NINE" => SEVENTH_FLAT_NINE.to_vec(), 142 | "SEVENTH_SUSPENSION_FOUR" => SEVENTH_SUSPENSION_FOUR.to_vec(), 143 | "SO_WHAT" => SO_WHAT.to_vec(), 144 | "SUSPENDED" => SUSPENDED.to_vec(), 145 | "SUBDOMINANT" => SUBDOMINANT.to_vec(), 146 | "SUBDOMINANT_PARALLEL" => SUBDOMINANT_PARALLEL.to_vec(), 147 | "SUBMEDIANT" => SUBMEDIANT.to_vec(), 148 | "SUBTONIC" => SUBTONIC.to_vec(), 149 | "SUPERTONIC" => SUPERTONIC.to_vec(), 150 | "THIRTEENTH_FLAT_NINTH" => THIRTEENTH_FLAT_NINTH.to_vec(), 151 | "THIRTEENTH_FLAT_NINTH_FLAT_FIFTH" => THIRTEENTH_FLAT_NINTH_FLAT_FIFTH.to_vec(), 152 | "TONIC_COUNTER_PARALLEL" => TONIC_COUNTER_PARALLEL.to_vec(), 153 | "TONIC" => TONIC.to_vec(), 154 | "TONIC_PARALLEL" => TONIC_PARALLEL.to_vec(), 155 | "TRISTAN" => TRISTAN.to_vec(), 156 | "VIENNESE_TRICHORD" => VIENNESE_TRICHORD.to_vec(), 157 | _ => Vec::new(), 158 | } 159 | } 160 | 161 | pub fn chords_to_vector() -> Vec> { 162 | vec![ 163 | AUGMENTED.to_vec(), 164 | AUGMENTED_ELEVENTH.to_vec(), 165 | AUGMENTED_MAJOR_SEVENTH.to_vec(), 166 | AUGMENTED_SEVENTH.to_vec(), 167 | AUGMENTED_SIXTH.to_vec(), 168 | DIMINISHED.to_vec(), 169 | DIMINISHED_MAJOR_SEVENTH.to_vec(), 170 | DIMINISHED_SEVENTH.to_vec(), 171 | DOMINANT.to_vec(), 172 | DOMINANT_ELEVENTH.to_vec(), 173 | DOMINANT_MINOR_NINTH.to_vec(), 174 | DOMINANT_NINTH.to_vec(), 175 | DOMINANT_PARALLEL.to_vec(), 176 | DOMINANT_SEVENTH.to_vec(), 177 | DOMINANT_SEVENTH_FLAT_FIVE.to_vec(), 178 | DOMINANT_SEVENTH_RAISED_NINTH.to_vec(), 179 | DOMINANT_THIRTEENTH.to_vec(), 180 | DREAM.to_vec(), 181 | ELEKTRA.to_vec(), 182 | FARBEN.to_vec(), 183 | HALF_DIMINISHED_SEVENTH.to_vec(), 184 | HARMONIC_SEVENTH.to_vec(), 185 | AUGMENTED_NINTH.to_vec(), 186 | LEADING_TONE.to_vec(), 187 | LYDIAN.to_vec(), 188 | MAGIC.to_vec(), 189 | MAJOR.to_vec(), 190 | MAJOR_ELEVENTH.to_vec(), 191 | MAJOR_SEVENTH.to_vec(), 192 | MAJOR_SEVENTH_SHARP_ELEVENTH.to_vec(), 193 | MAJOR_SIXTH.to_vec(), 194 | MAJOR_SIXTH_NINTH.to_vec(), 195 | MAJOR_NINTH.to_vec(), 196 | MAJOR_THIRTEENTH.to_vec(), 197 | MEDIANT.to_vec(), 198 | MINOR.to_vec(), 199 | MINOR_ELEVENTH.to_vec(), 200 | MINOR_MAJOR_SEVENTH.to_vec(), 201 | MINOR_NINTH.to_vec(), 202 | MINOR_SEVENTH.to_vec(), 203 | MINOR_SIXTH.to_vec(), 204 | MINOR_SIXTH_NINTH.to_vec(), 205 | MINOR_THIRTEENTH.to_vec(), 206 | MU.to_vec(), 207 | MYSTIC.to_vec(), 208 | NEAPOLITAN.to_vec(), 209 | NINTH_AUGMENTED_FIFTH.to_vec(), 210 | NINTH_FLAT_FIFTH.to_vec(), 211 | NORTHERN_LIGHTS.to_vec(), 212 | ODE_TO_NAPOLEON_HEXACHORD.to_vec(), 213 | PETRUSHKA.to_vec(), 214 | POWER.to_vec(), 215 | PSALMS.to_vec(), 216 | SECONDARY_DOMINANT.to_vec(), 217 | SECONDARY_LEADING_TONE.to_vec(), 218 | SECONDARY_SUPERTONIC.to_vec(), 219 | SEVEN_SIX.to_vec(), 220 | SEVENTH_FLAT_NINE.to_vec(), 221 | SEVENTH_SUSPENSION_FOUR.to_vec(), 222 | SO_WHAT.to_vec(), 223 | SUSPENDED.to_vec(), 224 | SUBDOMINANT.to_vec(), 225 | SUBDOMINANT_PARALLEL.to_vec(), 226 | SUBMEDIANT.to_vec(), 227 | SUBTONIC.to_vec(), 228 | SUPERTONIC.to_vec(), 229 | THIRTEENTH_FLAT_NINTH.to_vec(), 230 | THIRTEENTH_FLAT_NINTH_FLAT_FIFTH.to_vec(), 231 | TONIC_COUNTER_PARALLEL.to_vec(), 232 | TONIC.to_vec(), 233 | TONIC_PARALLEL.to_vec(), 234 | TRISTAN.to_vec(), 235 | VIENNESE_TRICHORD.to_vec(), 236 | ] 237 | } 238 | 239 | pub fn chord_to_string_array() -> Vec<&'static str> { 240 | vec![ 241 | "AUGMENTED = [0, 4, 8]", 242 | "AUGMENTED_ELEVENTH = [0, 4, 7, 10, 2, 6]", 243 | "AUGMENTED_MAJOR_SEVENTH = [0, 4, 8, 11]", 244 | "AUGMENTED_SEVENTH = [0, 4, 8, 10]", 245 | "AUGMENTED_SIXTH = [0, 6, 8]", 246 | "DIMINISHED = [0, 3, 6]", 247 | "DIMINISHED_MAJOR_SEVENTH = [0, 3, 6, 11]", 248 | "DIMINISHED_SEVENTH = [0, 3, 6, 9]", 249 | "DOMINANT = [0, 4, 7]", 250 | "DOMINANT_ELEVENTH = [0, 4, 7, 10, 2, 5]", 251 | "DOMINANT_MINOR_NINTH = [0, 4, 7, 10, 1]", 252 | "DOMINANT_NINTH = [0, 4, 7, 10, 2]", 253 | "DOMINANT_PARALLEL = [0, 3, 7]", 254 | "DOMINANT_SEVENTH = [0, 4, 7, 10]", 255 | "DOMINANT_SEVENTH_FLAT_FIVE = [0, 4, 6, 10]", 256 | "DOMINANT_SEVENTH_RAISED_NINTH = [0, 4, 7, 10, 3]", 257 | "DOMINANT_THIRTEENTH = [0, 4, 7, 10, 2, 5, 9]", 258 | "DREAM = [0, 5, 6, 7]", 259 | "ELEKTRA = [0, 7, 9, 1, 4]", 260 | "FARBEN = [0, 8, 11, 4, 9]", 261 | "HALF_DIMINISHED_SEVENTH = [0, 3, 6, 10]", 262 | "HARMONIC_SEVENTH = [0, 4, 7, 10]", 263 | "AUGMENTED_NINTH = [0, 4, 7, 10, 3]", 264 | "LEADING_TONE = [0, 3, 6]", 265 | "LYDIAN = [0, 4, 7, 11, 6]", 266 | "MAGIC = [0, 1, 5, 6, 10, 0, 3, 5]", 267 | "MAJOR = [0, 4, 7]", 268 | "MAJOR_ELEVENTH = [0, 4, 7, 11, 2, 5]", 269 | "MAJOR_SEVENTH = [0, 4, 7, 11]", 270 | "MAJOR_SEVENTH_SHARP_ELEVENTH = [0, 4, 8, 11, 6]", 271 | "MAJOR_SIXTH = [0, 4, 7, 9]", 272 | "MAJOR_SIXTH_NINTH = [0, 4, 7, 9, 2]", 273 | "MAJOR_NINTH = [0, 4, 7, 11, 2]", 274 | "MAJOR_THIRTEENTH = [0, 4, 7, 11, 2, 6, 9]", 275 | "MEDIANT = [0, 3, 7]", 276 | "MINOR = [0, 3, 7]", 277 | "MINOR_ELEVENTH = [0, 3, 7, 10, 2, 5]", 278 | "MINOR_MAJOR_SEVENTH = [0, 3, 7, 11]", 279 | "MINOR_NINTH = [0, 3, 7, 10, 2]", 280 | "MINOR_SEVENTH = [0, 3, 7, 10]", 281 | "MINOR_SIXTH = [0, 3, 7, 9]", 282 | "MINOR_SIXTH_NINTH = [0, 3, 7, 9, 2]", 283 | "MINOR_THIRTEENTH = [0, 3, 7, 10, 2, 5, 9]", 284 | "MU = [0, 2, 4, 7]", 285 | "MYSTIC = [0, 6, 10, 4, 9, 2]", 286 | "NEAPOLITAN = [1, 5, 8]", 287 | "NINTH_AUGMENTED_FIFTH = [0, 4, 8, 10, 2]", 288 | "NINTH_FLAT_FIFTH = [0, 4, 6, 10, 2]", 289 | "NORTHERN_LIGHTS = [1, 2, 8, 0, 3, 6, 7, 10, 11, 4, 7]", 290 | "ODE_TO_NAPOLEON_HEXACHORD = [0, 1, 4, 5, 8, 9]", 291 | "PETRUSHKA = [0, 1, 4, 6, 7, 10]", 292 | "POWER = [0, 7]", 293 | "PSALMS = [0, 3, 7]", 294 | "SECONDARY_DOMINANT = [0, 4, 7]", 295 | "SECONDARY_LEADING_TONE = [0, 3, 6]", 296 | "SECONDARY_SUPERTONIC = [0, 3, 7]", 297 | "SEVEN_SIX = [0, 4, 7, 9, 10]", 298 | "SEVENTH_FLAT_NINE = [0, 4, 7, 10, 1]", 299 | "SEVENTH_SUSPENSION_FOUR = [0, 5, 7, 10]", 300 | "SO_WHAT = [0, 5, 10, 3, 7]", 301 | "SUSPENDED = [0, 5, 7]", 302 | "SUBDOMINANT = [0, 4, 7]", 303 | "SUBDOMINANT_PARALLEL = [0, 3, 7]", 304 | "SUBMEDIANT = [0, 3, 7]", 305 | "SUBTONIC = [0, 4, 7]", 306 | "SUPERTONIC = [0, 3, 7]", 307 | "THIRTEENTH_FLAT_NINTH = [0, 4, 7, 10, 1, 12, 9]", 308 | "THIRTEENTH_FLAT_NINTH_FLAT_FIFTH = [0, 4, 6, 10, 1, 12, 9]", 309 | "TONIC_COUNTER_PARALLEL = [0, 3, 7]", 310 | "TONIC = [0, 4, 7]", 311 | "TONIC_PARALLEL = [0, 3, 7]", 312 | "TRISTAN = [0, 3, 6, 10]", 313 | "VIENNESE_TRICHORD = [0, 1, 6, 7]", 314 | ] 315 | } 316 | 317 | pub struct IntervalChord { 318 | intervals: Vec, 319 | transpose: i8, 320 | } 321 | 322 | impl IntervalChord { 323 | pub fn from_string(interval_chord_string: &str) -> Self { 324 | Self { 325 | intervals: string_to_chord(interval_chord_string.trim()), 326 | transpose: 0, 327 | } 328 | } 329 | 330 | pub fn from_string_with_custom( 331 | interval_chord_string: &str, 332 | custom_chords: &CustomChords, 333 | ) -> Self { 334 | use rand::Rng; 335 | 336 | if interval_chord_string.trim() == "?" { 337 | let random_chord_intervals = { 338 | let index = rand::thread_rng().gen_range(0, custom_chords.len()); 339 | custom_chords[index].1.clone() 340 | }; 341 | 342 | Self { 343 | intervals: random_chord_intervals, 344 | transpose: 0, 345 | } 346 | } else if interval_chord_string.trim() == "??" { 347 | let random_chord_intervals = { 348 | let mut custom_chords_intervals = Vec::with_capacity(custom_chords.len()); 349 | 350 | for (_name, intervals) in custom_chords { 351 | custom_chords_intervals.push(intervals.clone()); 352 | } 353 | 354 | let mut all_chords = chords_to_vector(); 355 | all_chords.append(&mut custom_chords_intervals); 356 | 357 | let index = rand::thread_rng().gen_range(0, all_chords.len()); 358 | all_chords[index].clone() 359 | }; 360 | 361 | Self { 362 | intervals: random_chord_intervals, 363 | transpose: 0, 364 | } 365 | } else { 366 | let mut chord = IntervalChord::from_string(interval_chord_string); 367 | 368 | let look_for_custom_chords = chord.intervals.is_empty(); 369 | if look_for_custom_chords { 370 | for (name, intervals) in custom_chords { 371 | if name.trim() == interval_chord_string.trim() { 372 | chord.intervals = intervals.clone(); 373 | break; 374 | } 375 | } 376 | } 377 | 378 | chord 379 | } 380 | } 381 | 382 | pub fn new(intervals: Vec, transpose: i8) -> Self { 383 | Self { 384 | intervals, 385 | transpose, 386 | } 387 | } 388 | 389 | pub fn to_midi(&self) -> Vec { 390 | let mut resolved_notes = Vec::with_capacity(self.intervals.len()); 391 | for interval in &self.intervals { 392 | let midi_note = notes::to_midi_note(interval + self.transpose); 393 | resolved_notes.push(midi_note); 394 | } 395 | resolved_notes 396 | } 397 | 398 | pub fn transpose(&mut self, transpose_delta: i8) -> &mut Self { 399 | self.transpose += transpose_delta; 400 | self 401 | } 402 | 403 | pub fn transpose_octave(&mut self, octave_delta: i8) -> &mut Self { 404 | const NOTES_IN_OCTAVE_COUNT: i8 = 12; 405 | self.transpose += octave_delta * NOTES_IN_OCTAVE_COUNT; 406 | self 407 | } 408 | 409 | pub fn is_empty(&self) -> bool { 410 | self.intervals.is_empty() 411 | } 412 | 413 | pub fn get_interval(&self, index: usize) -> i8 { 414 | self.intervals[index] + self.transpose 415 | } 416 | 417 | pub fn len(&self) -> usize { 418 | self.intervals.len() 419 | } 420 | } 421 | 422 | mod tests { 423 | #[test] 424 | fn test_interval_chord() { 425 | use crate::theory::chords::*; 426 | let mut chord = IntervalChord::from_string("POWER"); 427 | assert_eq!(chord.intervals, vec![0, 7]); 428 | chord.transpose(-1); 429 | chord.transpose_octave(1); 430 | assert_eq!(chord.transpose, 11); 431 | 432 | let chord = IntervalChord::from_string(" MINOR "); 433 | assert_eq!(chord.intervals, vec![0, 3, 7]); 434 | 435 | let chord = IntervalChord::from_string(" POsdsdWER >"); 436 | assert_eq!(chord.intervals.len(), 0); 437 | 438 | let chord = IntervalChord::from_string(" MINOR -5 "); 439 | assert_eq!(chord.intervals.len(), 0); 440 | assert_eq!(chord.transpose, 0); 441 | } 442 | 443 | #[test] 444 | fn test_interval_random() { 445 | use crate::theory::chords::*; 446 | let custom_chords = vec![("customA".to_string(), vec![0, 1, 2])]; 447 | 448 | let chord = IntervalChord::from_string_with_custom("?", &custom_chords); 449 | assert_eq!(chord.intervals, vec![0, 1, 2]); 450 | 451 | let custom_chords = vec![ 452 | ("customA".to_string(), vec![0, 1, 2, 5, 6]), 453 | ("customB".to_string(), vec![3, 4, 5, 8, 9]), 454 | ]; 455 | 456 | let chord = IntervalChord::from_string_with_custom("?", &custom_chords); 457 | assert_eq!(chord.intervals.len(), 5); 458 | 459 | let chord = IntervalChord::from_string_with_custom("??", &custom_chords); 460 | assert_ne!(chord.intervals.len(), 0); 461 | } 462 | 463 | #[test] 464 | fn test_interval_chord_custom() { 465 | use crate::theory::chords::*; 466 | let custom_chords = vec![ 467 | ("customA".to_string(), vec![0, 1, 2]), 468 | ("customB".to_string(), vec![3, 4, 5]), 469 | ]; 470 | 471 | let chord = IntervalChord::from_string_with_custom("POWER", &custom_chords); 472 | assert_eq!(chord.intervals, vec![0, 7]); 473 | 474 | let chord = IntervalChord::from_string_with_custom(" customA", &custom_chords); 475 | assert_eq!(chord.intervals, vec![0, 1, 2]); 476 | 477 | let chord = IntervalChord::from_string_with_custom("customB ", &custom_chords); 478 | assert_eq!(chord.intervals, vec![3, 4, 5]); 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /src/theory/composition.rs: -------------------------------------------------------------------------------- 1 | use music_timer::{music_time::MusicTime, time_signature::TimeSignature}; 2 | 3 | pub type PatternEvent = (MusicTime, Vec); 4 | 5 | #[derive(Debug)] 6 | pub struct Pattern { 7 | name: String, 8 | bpm: u8, 9 | signature: TimeSignature, 10 | events: Vec, 11 | } 12 | 13 | impl Pattern { 14 | pub fn new(name: &str, bpm: u8, signature: TimeSignature) -> Self { 15 | Pattern { 16 | name: name.to_owned(), 17 | bpm, 18 | signature, 19 | events: Vec::new(), 20 | } 21 | } 22 | 23 | pub fn new_with_events( 24 | name: &str, 25 | bpm: u8, 26 | signature: TimeSignature, 27 | events: Vec, 28 | ) -> Self { 29 | let mut pattern = Pattern { 30 | name: name.to_owned(), 31 | bpm, 32 | signature, 33 | events, 34 | }; 35 | pattern.sort_events(); 36 | pattern 37 | } 38 | 39 | pub fn push_event(&mut self, time: MusicTime, notes: Vec) -> &Self { 40 | self.events.push((time, notes)); 41 | self 42 | } 43 | 44 | pub fn len(&self) -> usize { 45 | self.events.len() 46 | } 47 | 48 | pub fn get_bpm(&self) -> u8 { 49 | self.bpm 50 | } 51 | 52 | pub fn get_time_signature(&self) -> TimeSignature { 53 | self.signature 54 | } 55 | 56 | pub fn get(&self, index: usize) -> &PatternEvent { 57 | &self.events[index] 58 | } 59 | 60 | pub fn get_events(&self) -> &Vec { 61 | &self.events 62 | } 63 | 64 | pub fn get_name(&self) -> &str { 65 | &self.name 66 | } 67 | 68 | pub fn find_next_event_index(&self, time: &MusicTime) -> usize { 69 | self 70 | .events 71 | .iter() 72 | .position(|event| &event.0 >= time) 73 | .unwrap_or(0) 74 | } 75 | 76 | pub fn sort_events(&mut self) { 77 | self.events.sort_by(|a, b| a.0.cmp(&b.0)); 78 | } 79 | } 80 | 81 | #[derive(Debug)] 82 | pub struct Composition { 83 | name: String, 84 | patterns: Vec, 85 | } 86 | 87 | impl Composition { 88 | pub fn new(name: &str) -> Self { 89 | Composition { 90 | name: name.to_string(), 91 | patterns: Vec::new(), 92 | } 93 | } 94 | 95 | pub fn new_with_patterns(name: &str, patterns: Vec) -> Self { 96 | Composition { 97 | name: name.to_string(), 98 | patterns, 99 | } 100 | } 101 | 102 | pub fn push_new_pattern(&mut self, name: String, bpm: u8, signature: TimeSignature) { 103 | self.patterns.push(Pattern::new(&name, bpm, signature)); 104 | } 105 | 106 | pub fn push_pattern(&mut self, pattern: Pattern) { 107 | self.patterns.push(pattern); 108 | } 109 | 110 | pub fn len(&self) -> usize { 111 | self.patterns.len() 112 | } 113 | 114 | pub fn get(&self, index: usize) -> &Pattern { 115 | &self.patterns[index] 116 | } 117 | 118 | pub fn get_mut(&mut self, index: usize) -> &mut Pattern { 119 | &mut self.patterns[index] 120 | } 121 | 122 | pub fn get_patterns(&self) -> &Vec { 123 | &self.patterns 124 | } 125 | 126 | pub fn get_name(&self) -> &str { 127 | &self.name 128 | } 129 | } 130 | 131 | mod tests { 132 | #[test] 133 | fn test_create() { 134 | use crate::composition::*; 135 | let mut compo = Composition::new("test"); 136 | assert_eq!(compo.get_name(), "test"); 137 | 138 | compo.push_new_pattern("a".to_string(), 120, TimeSignature::default()); 139 | compo 140 | .get_mut(0) 141 | .push_event(MusicTime::new(1, 1, 1), vec![0, 1, 2]); 142 | compo 143 | .get_mut(0) 144 | .push_event(MusicTime::new(2, 1, 1), vec![2, 3, 4]); 145 | 146 | compo.push_new_pattern("a".to_string(), 54, TimeSignature::new(3, 4)); 147 | compo 148 | .get_mut(1) 149 | .push_event(MusicTime::new(1, 3, 1), vec![51, 51, 52]); 150 | compo 151 | .get_mut(1) 152 | .push_event(MusicTime::new(2, 4, 1), vec![52, 53, 54]); 153 | 154 | assert_eq!(compo.len(), 2); 155 | assert_eq!(compo.get(0).len(), 2); 156 | assert_eq!(compo.get(1).len(), 2); 157 | 158 | assert_eq!(compo.get(0).get_bpm(), 120); 159 | assert_eq!(compo.get(0).get_time_signature(), TimeSignature::new(4, 4)); 160 | 161 | assert_eq!(compo.get(1).get_bpm(), 54); 162 | assert_eq!(compo.get(1).get_time_signature(), TimeSignature::new(3, 4)); 163 | 164 | let (time, notes) = compo.get(0).get(0); 165 | assert_eq!(time, &MusicTime::new(1, 1, 1)); 166 | assert_eq!(notes, &vec![0, 1, 2]); 167 | 168 | let (time, notes) = compo.get(0).get(1); 169 | assert_eq!(time, &MusicTime::new(2, 1, 1)); 170 | assert_eq!(notes, &vec![2, 3, 4]); 171 | 172 | let (time, notes) = compo.get(1).get(0); 173 | assert_eq!(time, &MusicTime::new(1, 3, 1)); 174 | assert_eq!(notes, &vec![51, 51, 52]); 175 | 176 | let (time, notes) = compo.get(1).get(1); 177 | assert_eq!(time, &MusicTime::new(2, 4, 1)); 178 | assert_eq!(notes, &vec![52, 53, 54]); 179 | } 180 | 181 | #[test] 182 | fn test_find_next_event() { 183 | use crate::composition::Pattern; 184 | use music_timer::{music_time::MusicTime, time_signature::TimeSignature}; 185 | 186 | let pattern = Pattern::new_with_events( 187 | "test pattern", 188 | 85, 189 | TimeSignature::default(), 190 | vec![ 191 | (MusicTime::new(1, 1, 1), vec![0]), 192 | (MusicTime::new(2, 1, 1), vec![0]), 193 | (MusicTime::new(3, 1, 1), vec![0]), 194 | (MusicTime::new(3, 4, 1), vec![0]), 195 | (MusicTime::new(4, 1, 1), vec![0]), 196 | ], 197 | ); 198 | 199 | let time = MusicTime::new(3, 3, 2); 200 | let result = Pattern::find_next_event_index(&pattern, &time); 201 | assert_eq!(result, 3); 202 | } 203 | 204 | #[test] 205 | fn test_event_order() { 206 | use crate::composition::Pattern; 207 | use music_timer::{music_time::MusicTime, time_signature::TimeSignature}; 208 | 209 | let pattern = Pattern::new_with_events( 210 | "test pattern", 211 | 85, 212 | TimeSignature::default(), 213 | vec![ 214 | (MusicTime::new(2, 1, 1), vec![0]), 215 | (MusicTime::new(1, 1, 1), vec![0]), 216 | (MusicTime::new(4, 1, 1), vec![0]), 217 | (MusicTime::new(3, 1, 1), vec![0]), 218 | (MusicTime::new(3, 4, 1), vec![0]), 219 | ], 220 | ); 221 | 222 | assert_eq!( 223 | pattern.get_events(), 224 | &vec![ 225 | (MusicTime::new(1, 1, 1), vec![0]), 226 | (MusicTime::new(2, 1, 1), vec![0]), 227 | (MusicTime::new(3, 1, 1), vec![0]), 228 | (MusicTime::new(3, 4, 1), vec![0]), 229 | (MusicTime::new(4, 1, 1), vec![0]), 230 | ] 231 | ) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/theory/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chords; 2 | pub mod composition; 3 | pub mod notes; 4 | -------------------------------------------------------------------------------- /src/theory/notes.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | #[derive(Debug, PartialEq)] 4 | pub enum Key { 5 | C, 6 | Cs, 7 | D, 8 | Ds, 9 | E, 10 | F, 11 | Fs, 12 | G, 13 | Gs, 14 | A, 15 | As, 16 | B, 17 | } 18 | 19 | pub fn key_to_index(key: Key) -> u8 { 20 | match key { 21 | Key::C => 0, 22 | Key::Cs => 1, 23 | Key::D => 2, 24 | Key::Ds => 3, 25 | Key::E => 4, 26 | Key::F => 5, 27 | Key::Fs => 6, 28 | Key::G => 7, 29 | Key::Gs => 8, 30 | Key::A => 9, 31 | Key::As => 10, 32 | Key::B => 11, 33 | } 34 | } 35 | 36 | pub fn key_to_string(key: Key) -> &'static str { 37 | match key { 38 | Key::C => "C", 39 | Key::Cs => "C#", 40 | Key::D => "D", 41 | Key::Ds => "D#", 42 | Key::E => "E", 43 | Key::F => "F", 44 | Key::Fs => "F#", 45 | Key::G => "G", 46 | Key::Gs => "G#", 47 | Key::A => "A", 48 | Key::As => "A#", 49 | Key::B => "B", 50 | } 51 | } 52 | 53 | pub fn string_to_key(key: &str) -> Key { 54 | match key { 55 | "C" => Key::C, 56 | "C#" => Key::Cs, 57 | "D" => Key::D, 58 | "D#" => Key::Ds, 59 | "E" => Key::E, 60 | "F" => Key::F, 61 | "F#" => Key::Fs, 62 | "G" => Key::G, 63 | "G#" => Key::Gs, 64 | "A" => Key::A, 65 | "A#" => Key::As, 66 | "B" => Key::B, 67 | _ => Key::C, 68 | } 69 | } 70 | 71 | pub fn index_to_key(key: i8) -> Key { 72 | match key { 73 | 0 => Key::C, 74 | 1 => Key::Cs, 75 | 2 => Key::D, 76 | 3 => Key::Ds, 77 | 4 => Key::E, 78 | 5 => Key::F, 79 | 6 => Key::Fs, 80 | 7 => Key::G, 81 | 8 => Key::Gs, 82 | 9 => Key::A, 83 | 10 => Key::As, 84 | 11 => Key::B, 85 | _ => Key::C, 86 | } 87 | } 88 | 89 | pub fn to_midi_note(note_interval: i8) -> u8 { 90 | const MIN_VALUE: i8 = 24; 91 | const MAX_VALUE: i8 = 107; 92 | 93 | if MIN_VALUE + note_interval <= MIN_VALUE { 94 | MIN_VALUE as u8 95 | } else if MIN_VALUE + note_interval >= MAX_VALUE { 96 | MAX_VALUE as u8 97 | } else { 98 | (MIN_VALUE + note_interval) as u8 99 | } 100 | } 101 | 102 | pub fn midi_to_note(value: u8) -> (u8, Key) { 103 | const MIN_VALUE: u8 = 24; 104 | const NOTES_IN_OCTAVE_COUNT: u8 = 12; 105 | let corrected_value = value - MIN_VALUE; 106 | let note = corrected_value % NOTES_IN_OCTAVE_COUNT; 107 | let octave = corrected_value / NOTES_IN_OCTAVE_COUNT; 108 | 109 | (octave + 1, index_to_key(note as i8)) 110 | } 111 | 112 | mod tests { 113 | #[test] 114 | fn test_midi_note() { 115 | use crate::theory::notes::*; 116 | let note = to_midi_note(0); 117 | assert_eq!(note, 24); 118 | assert_eq!(midi_to_note(note), (1, Key::C)); 119 | 120 | assert_eq!(midi_to_note(60), (4, Key::C)); 121 | assert_eq!(midi_to_note(60 - 12 + 3), (3, Key::Ds)); 122 | } 123 | 124 | #[test] 125 | fn test_conversions() { 126 | use crate::theory::notes::*; 127 | let result = key_to_index(Key::G); 128 | assert_eq!(result, 7); 129 | 130 | let result = key_to_string(Key::As); 131 | assert_eq!(result, "A#"); 132 | 133 | let result = string_to_key("B"); 134 | assert_eq!(result, Key::B); 135 | 136 | let result = index_to_key(2); 137 | assert_eq!(result, Key::D); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /tests/export_test.yaml: -------------------------------------------------------------------------------- 1 | name: bc_000_a 2 | 3 | # Can be overridden by patterns 4 | master: 5 | key: D 6 | time: 128 7 | signature: [3, 4] 8 | 9 | chords: 10 | - [custom1, [0, 3, 8]] 11 | - [empty, []] 12 | 13 | patterns: 14 | - name: part_a 15 | pattern: 16 | - [1, 1, 1, MAJOR_SEVENTH, 0] 17 | - [1, 3, 1, custom1, 0] 18 | - [2, 1, 1, MAJOR_NINTH, 0] 19 | - [2, 3, 1, custom1, 0] 20 | - [3, 1, 1, MAJOR_SEVENTH, 3] 21 | - [3, 2, 1, custom1, 0] 22 | - [3, 3, 1, empty, 0] 23 | - [4, 1, 1, MAJOR_NINTH, -3] 24 | - [4, 2, 1, custom1, 0] 25 | 26 | - name: part_b 27 | master: 28 | signature: [4, 8] 29 | key: C# 30 | time: 69 31 | pattern: 32 | - [1, 1, 1, MAJOR_SEVENTH, 0] 33 | - [1, 2, 1, custom1, 0] 34 | - [2, 1, 1, MAJOR_NINTH, 0] 35 | - [2, 2, 1, custom1, 0] 36 | - [3, 1, 1, MAJOR_SEVENTH, 3] 37 | - [3, 2, 1, custom1, 0] 38 | - [4, 1, 1, MAJOR_NINTH, -3] 39 | - [4, 2, 1, custom1, 0] 40 | -------------------------------------------------------------------------------- /tests/export_test_missing_patterns.yaml: -------------------------------------------------------------------------------- 1 | # Empty 2 | patterns: 3 | - 4 | - 5 | -------------------------------------------------------------------------------- /tests/export_test_new.yaml: -------------------------------------------------------------------------------- 1 | name: bc_000_a 2 | 3 | # Can be overridden by patterns 4 | master: 5 | key: D 6 | time: 128 7 | signature: [3, 5] 8 | 9 | chords: 10 | - [custom1, [0, 3, 8]] 11 | 12 | patterns: 13 | - name: part_a 14 | pattern: 15 | - [1,1,1, MAJOR_SEVENTH, 0] 16 | - [1,3,1, custom1, 0] 17 | - [2,1,1, MAJOR_NINTH, 0] 18 | - [2,3,1, custom1, 0] 19 | - [3,1,1, MAJOR_SEVENTH, 3] 20 | - [2,2,1, custom1, 0] 21 | - [3,1,1, MAJOR_NINTH, -3] 22 | - [3,2,1, custom1, 0] 23 | 24 | - name: part_b 25 | master: 26 | signature: [4, 8] 27 | key: Cs 28 | time: 69 29 | pattern: 30 | - [1,1,1, MAJOR_SEVENTH, 0] 31 | - [1,2,1, custom1, 0] 32 | - [2,1,1, MAJOR_NINTH, 0] 33 | - [2,2,1, custom1, 0] 34 | - [3,1,1, MAJOR_SEVENTH, 3] 35 | - [2,2,1, custom1, 0] 36 | - [3,1,1, MAJOR_NINTH, -3] 37 | - [3,2,1, custom1, 0] 38 | -------------------------------------------------------------------------------- /tests/export_test_no_patterns.yaml: -------------------------------------------------------------------------------- 1 | # Empty 2 | empty: empty 3 | -------------------------------------------------------------------------------- /tests/middle_c.yaml: -------------------------------------------------------------------------------- 1 | name: middle_c 2 | 3 | # Can be overridden by patterns 4 | 5 | chords: 6 | - [single_note, [0]] 7 | 8 | patterns: 9 | - name: part_a 10 | pattern: 11 | - [1, 1, 1, single_note, 0] 12 | -------------------------------------------------------------------------------- /tests/test_interface.rs: -------------------------------------------------------------------------------- 1 | extern crate chord_composer; 2 | 3 | use chord_composer::{ 4 | performance::performance_engine::PerformanceState, 5 | theory::composition::{Composition, Pattern, PatternEvent}, 6 | FailResult, SuccessResult, 7 | }; 8 | use music_timer::{music_time::MusicTime, time_signature::TimeSignature}; 9 | 10 | struct MiddleCState { 11 | callback_calls: u16, 12 | current_time: MusicTime, 13 | } 14 | 15 | impl PerformanceState for MiddleCState { 16 | fn on_ready(&mut self, composition: &Composition) { 17 | self.callback_calls += 1; 18 | assert_eq!(composition.get_name(), "middle_c"); 19 | println!("on_ready"); 20 | } 21 | fn on_beat_interval_change(&mut self, current_time: &MusicTime) { 22 | self.callback_calls += 1; 23 | self.current_time = current_time.clone(); 24 | println!("on_beat_interval_change: {:?}", current_time); 25 | } 26 | fn on_beat_change(&mut self, current_time: &MusicTime) { 27 | self.callback_calls += 1; 28 | println!("on_beat_change: {:?}", current_time); 29 | } 30 | fn on_bar_change(&mut self, current_time: &MusicTime) { 31 | self.callback_calls += 1; 32 | println!("on_bar_change: {:?}", current_time); 33 | } 34 | fn on_event(&mut self, event: &PatternEvent) { 35 | self.callback_calls += 1; 36 | let (event_time, event_notes) = event; 37 | assert_eq!(event_time, &MusicTime::default()); 38 | assert_eq!(event_notes, &vec![60]); 39 | println!("on_event"); 40 | } 41 | fn on_pattern_playback_begin(&mut self, _pattern: &Pattern) { 42 | self.callback_calls += 1; 43 | println!("on_pattern_playback_begin"); 44 | } 45 | fn on_pattern_playback_end(&mut self, _pattern: &Pattern) { 46 | self.callback_calls += 1; 47 | println!("on_pattern_playback_end"); 48 | } 49 | fn on_completed(&mut self, composition: &Composition) { 50 | self.callback_calls += 1; 51 | assert_eq!(composition.get_name(), "middle_c"); 52 | println!("on_completed"); 53 | } 54 | } 55 | 56 | #[test] 57 | fn chord_to_string_array() { 58 | let chords = chord_composer::get_chord_keywords(); 59 | assert_eq!(chords.len(), 73); 60 | assert_eq!(chords[0], "AUGMENTED = [0, 4, 8]"); 61 | } 62 | 63 | #[test] 64 | fn export_midi_missing_file() { 65 | let file = "./tests/no_file.gone"; 66 | 67 | assert_eq!( 68 | chord_composer::export_file_to_midi(file), 69 | Err(chord_composer::FailResult::Deserialize) 70 | ); 71 | } 72 | 73 | #[test] 74 | fn export_midi_no_patterns() { 75 | let file = "./tests/export_test_no_patterns.yaml"; 76 | 77 | assert_eq!( 78 | chord_composer::export_file_to_midi(file), 79 | Err(chord_composer::FailResult::NoPatterns) 80 | ); 81 | } 82 | 83 | #[test] 84 | fn test_export_template() { 85 | assert_eq!( 86 | chord_composer::export_template("./tests/test_template_export.yaml"), 87 | Ok(chord_composer::SuccessResult::ExportTemplate) 88 | ); 89 | } 90 | 91 | #[test] 92 | fn export_midi() { 93 | let file = "./tests/export_test.yaml"; 94 | 95 | let files = vec![ 96 | "./tests/bc_000_a/part_a.mid".to_string(), 97 | "./tests/bc_000_a/part_b.mid".to_string(), 98 | ]; 99 | 100 | assert_eq!( 101 | chord_composer::export_file_to_midi(file), 102 | Ok(chord_composer::SuccessResult::Export(files.clone())), 103 | ); 104 | 105 | use std::fs::File; 106 | use std::io::prelude::*; 107 | 108 | let file1_bin = vec![ 109 | 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x02, 0x01, 0xE0, 0x4D, 0x54, 110 | 0x72, 0x6B, 0x00, 0x00, 0x00, 0x13, 0x00, 0xFF, 0x51, 0x03, 0x07, 0x27, 0x0E, 0x00, 0xFF, 0x58, 111 | 0x04, 0x03, 0x02, 0x08, 0x18, 0x00, 0xFF, 0x2F, 0x00, 0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x01, 112 | 0x07, 0x00, 0xFF, 0x03, 0x06, 0x70, 0x61, 0x72, 0x74, 0x5F, 0x61, 0x00, 0x90, 0x3E, 0x40, 0x00, 113 | 0x90, 0x42, 0x40, 0x00, 0x90, 0x45, 0x40, 0x00, 0x90, 0x49, 0x40, 0x87, 0x40, 0x90, 0x3E, 0x00, 114 | 0x00, 0x90, 0x42, 0x00, 0x00, 0x90, 0x45, 0x00, 0x00, 0x90, 0x49, 0x00, 0x00, 0x90, 0x3E, 0x40, 115 | 0x00, 0x90, 0x41, 0x40, 0x00, 0x90, 0x46, 0x40, 0x83, 0x60, 0x90, 0x3E, 0x00, 0x00, 0x90, 0x41, 116 | 0x00, 0x00, 0x90, 0x46, 0x00, 0x00, 0x90, 0x3E, 0x40, 0x00, 0x90, 0x42, 0x40, 0x00, 0x90, 0x45, 117 | 0x40, 0x00, 0x90, 0x49, 0x40, 0x00, 0x90, 0x40, 0x40, 0x87, 0x40, 0x90, 0x3E, 0x00, 0x00, 0x90, 118 | 0x42, 0x00, 0x00, 0x90, 0x45, 0x00, 0x00, 0x90, 0x49, 0x00, 0x00, 0x90, 0x40, 0x00, 0x00, 0x90, 119 | 0x3E, 0x40, 0x00, 0x90, 0x41, 0x40, 0x00, 0x90, 0x46, 0x40, 0x83, 0x60, 0x90, 0x3E, 0x00, 0x00, 120 | 0x90, 0x41, 0x00, 0x00, 0x90, 0x46, 0x00, 0x00, 0x90, 0x41, 0x40, 0x00, 0x90, 0x45, 0x40, 0x00, 121 | 0x90, 0x48, 0x40, 0x00, 0x90, 0x4C, 0x40, 0x83, 0x60, 0x90, 0x41, 0x00, 0x00, 0x90, 0x45, 0x00, 122 | 0x00, 0x90, 0x48, 0x00, 0x00, 0x90, 0x4C, 0x00, 0x00, 0x90, 0x3E, 0x40, 0x00, 0x90, 0x41, 0x40, 123 | 0x00, 0x90, 0x46, 0x40, 0x83, 0x60, 0x90, 0x3E, 0x00, 0x00, 0x90, 0x41, 0x00, 0x00, 0x90, 0x46, 124 | 0x00, 0x83, 0x60, 0x90, 0x3B, 0x40, 0x00, 0x90, 0x3F, 0x40, 0x00, 0x90, 0x42, 0x40, 0x00, 0x90, 125 | 0x46, 0x40, 0x00, 0x90, 0x3D, 0x40, 0x83, 0x60, 0x90, 0x3B, 0x00, 0x00, 0x90, 0x3F, 0x00, 0x00, 126 | 0x90, 0x42, 0x00, 0x00, 0x90, 0x46, 0x00, 0x00, 0x90, 0x3D, 0x00, 0x00, 0x90, 0x3E, 0x40, 0x00, 127 | 0x90, 0x41, 0x40, 0x00, 0x90, 0x46, 0x40, 0x87, 0x40, 0x90, 0x3E, 0x00, 0x00, 0x90, 0x41, 0x00, 128 | 0x00, 0x90, 0x46, 0x00, 0x00, 0xFF, 0x2F, 0x00, 129 | ]; 130 | 131 | let buffer = { 132 | let mut f = File::open(&files[0]).unwrap(); 133 | let mut buffer = Vec::new(); 134 | f.read_to_end(&mut buffer).unwrap(); 135 | buffer 136 | }; 137 | 138 | assert_eq!(buffer, file1_bin); 139 | 140 | let file2_bin = vec![ 141 | 0x4D, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x02, 0x01, 0xE0, 0x4D, 0x54, 142 | 0x72, 0x6B, 0x00, 0x00, 0x00, 0x13, 0x00, 0xFF, 0x51, 0x03, 0x0D, 0x44, 0xBD, 0x00, 0xFF, 0x58, 143 | 0x04, 0x04, 0x03, 0x08, 0x18, 0x00, 0xFF, 0x2F, 0x00, 0x4D, 0x54, 0x72, 0x6B, 0x00, 0x00, 0x01, 144 | 0x06, 0x00, 0xFF, 0x03, 0x06, 0x70, 0x61, 0x72, 0x74, 0x5F, 0x62, 0x00, 0x90, 0x3D, 0x40, 0x00, 145 | 0x90, 0x41, 0x40, 0x00, 0x90, 0x44, 0x40, 0x00, 0x90, 0x48, 0x40, 0x83, 0x60, 0x90, 0x3D, 0x00, 146 | 0x00, 0x90, 0x41, 0x00, 0x00, 0x90, 0x44, 0x00, 0x00, 0x90, 0x48, 0x00, 0x00, 0x90, 0x3D, 0x40, 147 | 0x00, 0x90, 0x40, 0x40, 0x00, 0x90, 0x45, 0x40, 0x8B, 0x20, 0x90, 0x3D, 0x00, 0x00, 0x90, 0x40, 148 | 0x00, 0x00, 0x90, 0x45, 0x00, 0x00, 0x90, 0x3D, 0x40, 0x00, 0x90, 0x41, 0x40, 0x00, 0x90, 0x44, 149 | 0x40, 0x00, 0x90, 0x48, 0x40, 0x00, 0x90, 0x3F, 0x40, 0x83, 0x60, 0x90, 0x3D, 0x00, 0x00, 0x90, 150 | 0x41, 0x00, 0x00, 0x90, 0x44, 0x00, 0x00, 0x90, 0x48, 0x00, 0x00, 0x90, 0x3F, 0x00, 0x00, 0x90, 151 | 0x3D, 0x40, 0x00, 0x90, 0x40, 0x40, 0x00, 0x90, 0x45, 0x40, 0x8B, 0x20, 0x90, 0x3D, 0x00, 0x00, 152 | 0x90, 0x40, 0x00, 0x00, 0x90, 0x45, 0x00, 0x00, 0x90, 0x40, 0x40, 0x00, 0x90, 0x44, 0x40, 0x00, 153 | 0x90, 0x47, 0x40, 0x00, 0x90, 0x4B, 0x40, 0x83, 0x60, 0x90, 0x40, 0x00, 0x00, 0x90, 0x44, 0x00, 154 | 0x00, 0x90, 0x47, 0x00, 0x00, 0x90, 0x4B, 0x00, 0x00, 0x90, 0x3D, 0x40, 0x00, 0x90, 0x40, 0x40, 155 | 0x00, 0x90, 0x45, 0x40, 0x8B, 0x20, 0x90, 0x3D, 0x00, 0x00, 0x90, 0x40, 0x00, 0x00, 0x90, 0x45, 156 | 0x00, 0x00, 0x90, 0x3A, 0x40, 0x00, 0x90, 0x3E, 0x40, 0x00, 0x90, 0x41, 0x40, 0x00, 0x90, 0x45, 157 | 0x40, 0x00, 0x90, 0x3C, 0x40, 0x83, 0x60, 0x90, 0x3A, 0x00, 0x00, 0x90, 0x3E, 0x00, 0x00, 0x90, 158 | 0x41, 0x00, 0x00, 0x90, 0x45, 0x00, 0x00, 0x90, 0x3C, 0x00, 0x00, 0x90, 0x3D, 0x40, 0x00, 0x90, 159 | 0x40, 0x40, 0x00, 0x90, 0x45, 0x40, 0x8B, 0x20, 0x90, 0x3D, 0x00, 0x00, 0x90, 0x40, 0x00, 0x00, 160 | 0x90, 0x45, 0x00, 0x00, 0xFF, 0x2F, 0x00, 161 | ]; 162 | 163 | let buffer = { 164 | let mut f = File::open(&files[1]).unwrap(); 165 | let mut buffer = Vec::new(); 166 | f.read_to_end(&mut buffer).unwrap(); 167 | buffer 168 | }; 169 | 170 | assert_eq!(buffer, file2_bin); 171 | } 172 | 173 | #[test] 174 | fn play_empty_composition() { 175 | let composition = Composition::new_with_patterns("middle_c", vec![]); 176 | 177 | let mut my_state = MiddleCState { 178 | callback_calls: 0, 179 | current_time: MusicTime::default(), 180 | }; 181 | 182 | assert_eq!( 183 | chord_composer::play(&composition, &mut my_state, false, &Vec::new(), &Vec::new()), 184 | Err(FailResult::NoPatterns), 185 | ); 186 | } 187 | 188 | #[test] 189 | fn play_middle_c_yaml() { 190 | use music_timer::music_time::MusicTime; 191 | let yaml = r#" 192 | name: middle_c 193 | chords: 194 | - [single_note, [0]] 195 | patterns: 196 | - name: part_a 197 | pattern: 198 | - [1, 1, 1, single_note, 0] 199 | "#; 200 | 201 | let mut my_state = MiddleCState { 202 | callback_calls: 0, 203 | current_time: MusicTime::default(), 204 | }; 205 | 206 | assert_eq!( 207 | chord_composer::play_yaml(yaml, &mut my_state, false, &Vec::new(), &Vec::new()), 208 | Ok(chord_composer::SuccessResult::Playback) 209 | ); 210 | 211 | assert_eq!(my_state.callback_calls, 42); 212 | assert_eq!(my_state.current_time, MusicTime::new(1, 4, 8)); 213 | } 214 | 215 | #[test] 216 | fn play_middle_c_file() { 217 | use music_timer::music_time::MusicTime; 218 | let file = "./tests/middle_c.yaml"; 219 | let mut my_state = MiddleCState { 220 | callback_calls: 0, 221 | current_time: MusicTime::default(), 222 | }; 223 | 224 | assert_eq!( 225 | chord_composer::play_file(file, &mut my_state, false, &Vec::new(), &Vec::new()), 226 | Ok(chord_composer::SuccessResult::Playback) 227 | ); 228 | 229 | assert_eq!(my_state.callback_calls, 42); 230 | assert_eq!(my_state.current_time, MusicTime::new(1, 4, 8)); 231 | } 232 | 233 | #[test] 234 | fn play_composition_from() { 235 | struct MyState { 236 | events: u16, 237 | current_time: MusicTime, 238 | } 239 | impl PerformanceState for MyState { 240 | fn on_ready(&mut self, _composition: &Composition) { 241 | println!("on_ready"); 242 | } 243 | fn on_beat_interval_change(&mut self, current_time: &MusicTime) { 244 | self.current_time = current_time.clone(); 245 | println!("on_beat_interval_change: {:?}", current_time); 246 | } 247 | fn on_beat_change(&mut self, current_time: &MusicTime) { 248 | println!("on_beat_change: {:?}", current_time); 249 | } 250 | fn on_bar_change(&mut self, current_time: &MusicTime) { 251 | println!("on_bar_change: {:?}", current_time); 252 | } 253 | fn on_event(&mut self, event: &PatternEvent) { 254 | self.events += 1; 255 | 256 | if self.events == 1 { 257 | assert_eq!(event, &chord_composer::build_event(2, 1, 1, vec![1], -1)); 258 | } else if self.events == 2 { 259 | assert_eq!(event, &chord_composer::build_event(3, 5, 1, vec![2], -2)); 260 | } 261 | println!("on_event"); 262 | } 263 | fn on_pattern_playback_begin(&mut self, _pattern: &Pattern) { 264 | println!("on_pattern_playback_begin"); 265 | } 266 | fn on_pattern_playback_end(&mut self, _pattern: &Pattern) { 267 | println!("on_pattern_playback_end"); 268 | } 269 | fn on_completed(&mut self, composition: &Composition) { 270 | assert_eq!(composition.get_name(), "test compo"); 271 | println!("on_completed"); 272 | } 273 | } 274 | 275 | let composition = Composition::new_with_patterns( 276 | "test compo", 277 | vec![ 278 | Pattern::new_with_events( 279 | "a", 280 | 100, 281 | TimeSignature::default(), 282 | vec![ 283 | chord_composer::build_event(1, 1, 1, vec![0, 3, 7], 0), 284 | chord_composer::build_event(2, 1, 1, vec![0, 3, 7], 1), 285 | chord_composer::build_event(3, 5, 1, vec![0, 3, 7], 2), 286 | ], 287 | ), 288 | Pattern::new_with_events( 289 | "b", 290 | 150, 291 | TimeSignature::new(7, 4), 292 | vec![ 293 | chord_composer::build_event(3, 5, 1, vec![2], -2), 294 | chord_composer::build_event(2, 1, 1, vec![1], -1), 295 | chord_composer::build_event(1, 1, 1, vec![0], 0), 296 | ], 297 | ), 298 | ], 299 | ); 300 | 301 | let mut my_state = MyState { 302 | events: 0, 303 | current_time: MusicTime::default(), 304 | }; 305 | 306 | assert_eq!( 307 | chord_composer::play_from( 308 | &composition, 309 | &mut my_state, 310 | false, 311 | &Vec::new(), 312 | &Vec::new(), 313 | &MusicTime::new(2, 1, 1), 314 | "b" 315 | ), 316 | Ok(SuccessResult::Playback), 317 | ); 318 | 319 | assert_eq!(my_state.events, 2); 320 | assert_eq!(my_state.current_time, MusicTime::new(3, 7, 8)); 321 | 322 | assert_eq!( 323 | chord_composer::play_from( 324 | &composition, 325 | &mut my_state, 326 | false, 327 | &Vec::new(), 328 | &Vec::new(), 329 | &MusicTime::new(2, 1, 1), 330 | "c" 331 | ), 332 | Err(FailResult::NoFoundPattern("c".to_owned())), 333 | ); 334 | } 335 | -------------------------------------------------------------------------------- /tests/test_template_export.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Name of the composition 3 | name: default_composition 4 | 5 | # The default master parameters of the composition. 6 | # New master pattern can be assigned to a pattern that overrides 7 | # the default master values. 8 | master: 9 | # The musical key to transpose the chords. 10 | # Supported values: C, C#, D, D#, E, F, F#, G, G#, A, A#, B 11 | key: F# 12 | 13 | # The beats per minute of the composition. 14 | time: 120 15 | 16 | # The time signature of the composition. 17 | # Beat numerator supported values: must be > 0. 18 | # Beat denominator supported values: 2, 4, 8, 16, 32, 64 19 | # e.g 3/8 is supported, 0/7 is not supported. 20 | signature: [4, 4] 21 | 22 | # Composition defined chords. 23 | chords: 24 | # [chord_name, [chord intervals]]. 25 | - [custom1, [0, 3, 8]] 26 | - [custom2, [0, 5]] 27 | 28 | # The composition's chord patterns/progressions. 29 | patterns: 30 | - name: part_a 31 | # Each pattern event = [bar, beat, beat interval, chord name, chord transpose]. 32 | pattern: 33 | - [1, 1, 1, MAJOR_SEVENTH, 0] 34 | - [1, 3, 1, custom1, 0] 35 | - [2, 1, 1, MAJOR_NINTH, 0] 36 | - [2, 3, 1, custom1, 0] 37 | - [3, 1, 1, MAJOR_SEVENTH, 3] 38 | - [3, 2, 1, custom1, 0] 39 | - [4, 1, 1, MAJOR_NINTH, -3] 40 | - [4, 2, 1, ?, 0] # ? = Select a random user defined chord. 41 | 42 | - name: part_b 43 | master: 44 | signature: [3, 4] 45 | key: C# 46 | time: 69 47 | # Each pattern event = [bar, beat, beat interval, chord name, chord transpose]. 48 | pattern: 49 | - [1, 1, 1, MAJOR_SEVENTH, 0] 50 | - [1, 2, 1, custom1, 0] 51 | - [2, 1, 5, MAJOR_NINTH, 0] 52 | - [2, 2, 1, custom1, 0] 53 | - [3, 1, 5, MAJOR_SEVENTH, 3] 54 | - [3, 2, 1, custom1, 0] 55 | - [4, 1, 1, MAJOR_NINTH, -3] 56 | - [4, 2, 1, ??, 0] #?? = Select a random chord from user defined and internal defined chord. 57 | --------------------------------------------------------------------------------