From f5b96dd15641d57bcfd80d184f9cc219797b4bfd Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Mon, 28 Dec 2020 19:35:38 +0100 Subject: [PATCH 01/47] arpegiator --- Cargo.lock | 461 +++++++++++++++++- Cargo.toml | 5 +- arpegiator/Cargo.toml | 23 + arpegiator/build.rs | 5 + arpegiator/src/change.rs | 82 ++++ arpegiator/src/device.rs | 212 ++++++++ arpegiator/src/device_out.rs | 77 +++ arpegiator/src/expressive_note.rs | 36 ++ arpegiator/src/lib.rs | 289 +++++++++++ arpegiator/src/note.rs | 61 +++ arpegiator/src/parameters.rs | 123 +++++ arpegiator/src/pattern.rs | 72 +++ arpegiator/src/pattern_device.rs | 117 +++++ arpegiator/src/socket.rs | 117 +++++ arpegiator/src/timed_event.rs | 4 + arpegiator_pattern_receiver/Cargo.toml | 21 + arpegiator_pattern_receiver/build.rs | 5 + arpegiator_pattern_receiver/src/lib.rs | 173 +++++++ arpegiator_pattern_receiver/src/parameters.rs | 123 +++++ arpegiator_pattern_receiver/src/socket.rs | 40 ++ audio_data/Cargo.toml | 18 + audio_data/build.rs | 5 + audio_data/src/lib.rs | 116 +++++ note_off_delay/Cargo.toml | 1 + note_off_delay/src/lib.rs | 3 +- osx_vst_bundler.sh | 3 +- rustfmt.toml | 1 + util/Cargo.toml | 13 +- util/build.rs | 5 + util/src/constants.rs | 2 +- util/src/lib.rs | 4 + util/src/logging.rs | 28 ++ util/src/messages.rs | 37 +- util/src/midi_message_with_delta.rs | 24 + util/src/parameter_value_conversion.rs | 4 +- util/src/pattern_payload.rs | 9 + util/src/transmute_buffer.rs | 22 + 37 files changed, 2315 insertions(+), 26 deletions(-) create mode 100644 arpegiator/Cargo.toml create mode 100644 arpegiator/build.rs create mode 100644 arpegiator/src/change.rs create mode 100644 arpegiator/src/device.rs create mode 100644 arpegiator/src/device_out.rs create mode 100644 arpegiator/src/expressive_note.rs create mode 100644 arpegiator/src/lib.rs create mode 100644 arpegiator/src/note.rs create mode 100644 arpegiator/src/parameters.rs create mode 100644 arpegiator/src/pattern.rs create mode 100644 arpegiator/src/pattern_device.rs create mode 100644 arpegiator/src/socket.rs create mode 100644 arpegiator/src/timed_event.rs create mode 100644 arpegiator_pattern_receiver/Cargo.toml create mode 100644 arpegiator_pattern_receiver/build.rs create mode 100644 arpegiator_pattern_receiver/src/lib.rs create mode 100644 arpegiator_pattern_receiver/src/parameters.rs create mode 100644 arpegiator_pattern_receiver/src/socket.rs create mode 100644 audio_data/Cargo.toml create mode 100644 audio_data/build.rs create mode 100644 audio_data/src/lib.rs create mode 100644 rustfmt.toml create mode 100644 util/build.rs create mode 100644 util/src/logging.rs create mode 100644 util/src/midi_message_with_delta.rs create mode 100644 util/src/pattern_payload.rs create mode 100644 util/src/transmute_buffer.rs diff --git a/Cargo.lock b/Cargo.lock index 9e6e79f..9a2d894 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0929d69e78dd9bf5408269919fcbcaeb2e35e5d43e5815517cdc6a8e11a423" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + [[package]] name = "ansi_term" version = "0.11.0" @@ -15,12 +30,172 @@ version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" +[[package]] +name = "arpegiator" +version = "0.1.0" +dependencies = [ + "async-net", + "bincode", + "build-info", + "build-info-build", + "itertools", + "log", + "num-traits", + "smol", + "util", + "vst", +] + +[[package]] +name = "arpegiator_pattern_receiver" +version = "0.1.0" +dependencies = [ + "arpegiator", + "bincode", + "build-info", + "build-info-build", + "log", + "serde", + "util", + "vst", +] + +[[package]] +name = "async-channel" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "vec-arena", +] + +[[package]] +name = "async-fs" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd" +dependencies = [ + "concurrent-queue", + "fastrand", + "futures-lite", + "libc", + "log", + "nb-connect", + "once_cell", + "parking", + "polling", + "vec-arena", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-net" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06de475c85affe184648202401d7622afb32f0f74e02192857d0201a16defbe5" +dependencies = [ + "async-io", + "blocking", + "fastrand", + "futures-lite", +] + +[[package]] +name = "async-process" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda" +dependencies = [ + "async-io", + "blocking", + "cfg-if 0.1.10", + "event-listener", + "futures-lite", + "once_cell", + "signal-hook", + "winapi", +] + +[[package]] +name = "async-task" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" + +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + +[[package]] +name = "audio_data" +version = "0.1.0" +dependencies = [ + "build-info", + "build-info-build", + "log", + "util", + "vst", +] + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598" +dependencies = [ + "addr2line", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.12.3" @@ -43,6 +218,20 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "blocking" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + [[package]] name = "build-info" version = "0.0.20" @@ -117,6 +306,12 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +[[package]] +name = "cache-padded" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" + [[package]] name = "cargo_metadata" version = "0.11.4" @@ -143,6 +338,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "chrono" version = "0.4.19" @@ -166,6 +367,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "ctor" version = "0.1.16" @@ -193,6 +403,27 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "event-listener" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" + +[[package]] +name = "fastrand" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +dependencies = [ + "instant", +] + [[package]] name = "filter_out_non_note" version = "0.1.0" @@ -217,6 +448,39 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7aea5a5909a74969507051a3b17adc84737e31a5f910559892aedce026f4d53" +[[package]] +name = "futures-core" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "847ce131b72ffb13b6109a221da9ad97a64cbe48feb1028356b836b47b8f1748" + +[[package]] +name = "futures-io" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" + +[[package]] +name = "futures-lite" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "gimli" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce" + [[package]] name = "git2" version = "0.13.12" @@ -257,6 +521,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "instant" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itertools" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d572918e350e82412fe766d24b15e6682fb2ed2bbe018280caa810397cb319" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.6" @@ -333,7 +615,17 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", +] + +[[package]] +name = "log-panics" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0136257df209261daa18d6c16394757c63e032e27aafd8b07788b051082bef" +dependencies = [ + "backtrace", + "log", ] [[package]] @@ -363,6 +655,12 @@ dependencies = [ "vst", ] +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + [[package]] name = "midi_delay" version = "0.1.0" @@ -371,6 +669,26 @@ dependencies = [ "vst", ] +[[package]] +name = "miniz_oxide" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "nb-connect" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "note_fan_out" version = "0.1.0" @@ -393,6 +711,7 @@ version = "0.1.0" dependencies = [ "build-info", "build-info-build", + "log", "util", "vst", ] @@ -427,6 +746,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" + [[package]] name = "once_cell" version = "1.5.2" @@ -442,6 +767,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.10.2" @@ -458,7 +789,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "cloudabi", "libc", "redox_syscall", @@ -472,12 +803,31 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project-lite" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" + [[package]] name = "pkg-config" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +[[package]] +name = "polling" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "log", + "wepoll-sys", + "winapi", +] + [[package]] name = "pretty_assertions" version = "0.6.1" @@ -544,6 +894,12 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "rustc-demangle" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" + [[package]] name = "rustc_version" version = "0.2.3" @@ -592,18 +948,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2", "quote", @@ -621,12 +977,60 @@ dependencies = [ "serde", ] +[[package]] +name = "signal-hook" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +dependencies = [ + "libc", +] + +[[package]] +name = "simplelog" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720" +dependencies = [ + "chrono", + "log", + "termcolor", +] + [[package]] name = "smallvec" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" +[[package]] +name = "smol" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4" +dependencies = [ + "async-channel", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-net", + "async-process", + "blocking", + "futures-lite", + "once_cell", +] + [[package]] name = "syn" version = "1.0.51" @@ -638,6 +1042,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "time" version = "0.1.44" @@ -704,7 +1117,13 @@ dependencies = [ name = "util" version = "0.1.0" dependencies = [ + "build-info", + "build-info-build", "global_counter", + "log", + "log-panics", + "serde", + "simplelog", "vst", ] @@ -714,6 +1133,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +[[package]] +name = "vec-arena" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" + [[package]] name = "version_check" version = "0.9.2" @@ -732,12 +1157,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wepoll-sys" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" @@ -754,6 +1194,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 477f8eb..139403f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,8 @@ members = [ "filter_out_non_note", "note_fan_out", "midi_delay", - "max_note_duration" + "max_note_duration", + "audio_data", + "arpegiator", + "arpegiator_pattern_receiver" ] diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml new file mode 100644 index 0000000..c3904cc --- /dev/null +++ b/arpegiator/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "arpegiator" +version = "0.1.0" +authors = ["Vincent Alsteen "] +edition = "2018" + +[dependencies] +vst = { git = "https://github.com/rustaudio/vst-rs" } +util = { path = "../util" } +build-info = "0.0.20" +log = "0.4.11" +num-traits = "0.2.14" +itertools = "0.10.0" +smol = "1.2.5" +async-net = "1.5.0" +bincode = "1.3.1" + +[build-dependencies] +build-info-build = "0.0.20" + +[lib] +name = "arpegiator" +crate-type = ["cdylib", "lib"] diff --git a/arpegiator/build.rs b/arpegiator/build.rs new file mode 100644 index 0000000..a49ab18 --- /dev/null +++ b/arpegiator/build.rs @@ -0,0 +1,5 @@ +fn main() { + // Calling `build_info_build::build_script` collects all data and makes it available to `build_info::build_info!` + // and `build_info::format!` in the main program. + build_info_build::build_script(); +} diff --git a/arpegiator/src/change.rs b/arpegiator/src/change.rs new file mode 100644 index 0000000..122e32c --- /dev/null +++ b/arpegiator/src/change.rs @@ -0,0 +1,82 @@ +use core::cmp::{Ordering, PartialEq, PartialOrd}; +use core::option::Option; +use crate::device::DeviceChange; +use crate::pattern_device::PatternDeviceChange; +use crate::timed_event::TimedEvent; + +pub enum SourceChange { + NoteChange(DeviceChange), + PatternChange(PatternDeviceChange) +} + + +impl PartialOrd for SourceChange { + fn partial_cmp(&self, other: &Self) -> Option { + // note come first, in order to start the pattern with the intended note + match self { + SourceChange::PatternChange(pattern) => { + match other { + SourceChange::PatternChange(other_pattern) => { + pattern.partial_cmp(other_pattern) + } + SourceChange::NoteChange(_) => Option::Some(Ordering::Greater) + } + } + SourceChange::NoteChange(note) => { + match other { + SourceChange::PatternChange(_) => Option::Some(Ordering::Less), + SourceChange::NoteChange(other_note) => { + note.partial_cmp(other_note) + } + } + } + } + } +} + + +impl PartialEq for SourceChange { + fn eq(&self, other: &Self) -> bool { + match self { + SourceChange::PatternChange(pattern) => { + match other { + SourceChange::PatternChange(other_pattern) => pattern.eq(other_pattern), + SourceChange::NoteChange(_) => false + } + } + SourceChange::NoteChange(note) => { + match other { + SourceChange::PatternChange(_) => false, + SourceChange::NoteChange(other_note) => note.eq(other_note) + } + } + } + } +} + + +impl Ord for SourceChange { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl Eq for SourceChange { + +} + +impl TimedEvent for SourceChange { + fn timestamp(&self) -> usize { + match self { + SourceChange::NoteChange(note) => note.timestamp(), + SourceChange::PatternChange(pattern) => pattern.timestamp() + } + } + + fn id(&self) -> usize { + match self { + SourceChange::NoteChange(note) => note.id(), + SourceChange::PatternChange(pattern) => pattern.id() + } + } +} diff --git a/arpegiator/src/device.rs b/arpegiator/src/device.rs new file mode 100644 index 0000000..14feb4c --- /dev/null +++ b/arpegiator/src/device.rs @@ -0,0 +1,212 @@ +use log::info; +use std::collections::HashMap; + +use util::constants::TIMBRECC; +use util::midi_message_type::MidiMessageType; + +use crate::note::{CCIndex, Note, NoteIndex}; +use util::messages::CC; +use crate::timed_event::TimedEvent; +use std::cmp::Ordering; +use util::midi_message_with_delta::MidiMessageWithDelta; + +pub struct Device { + pub notes: HashMap, + pub cc: HashMap, + pub channels: [Channel; 16], + pub note_index: usize, +} + +impl Default for Device { + fn default() -> Self { + Device { + notes: Default::default(), + cc: Default::default(), + channels: [Channel { + pressure: 0, + pitchbend: 0, + timbre: 0, + }; 16], + note_index: 0, + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Channel { + pub pressure: u8, + // in millisemitones + pub pitchbend: i32, + pub timbre: u8, +} + +pub enum Expression { + Timbre, + Pressure, + PitchBend, +} + +pub enum DeviceChange { + AddNote { time: usize, note: Note }, + RemoveNote { time: usize, note: Note }, + NoteExpressionChange { time: usize, expression: Expression, note: Note }, + ReplaceNote { time: usize, old_note: Note, new_note: Note }, + CCChange { time: usize, cc: CC }, + None { time: usize }, +} + + +impl TimedEvent for DeviceChange { + fn timestamp(&self) -> usize { + match self { + DeviceChange::AddNote { time, .. } => *time, + DeviceChange::RemoveNote { time, .. } => *time, + DeviceChange::NoteExpressionChange { time, .. } => *time, + DeviceChange::ReplaceNote { time, .. } => *time, + DeviceChange::CCChange { time, .. } => *time, + DeviceChange::None { time, .. } => *time + } + } + + fn id(&self) -> usize { + // used to order events that happen at the same time. Doesn't matter on CCs, in any case they'll be sorted + // by time already + match self { + DeviceChange::AddNote { note, .. } => note.id, + DeviceChange::RemoveNote { note, .. } => note.id, + DeviceChange::NoteExpressionChange { note, .. } => note.id, + DeviceChange::ReplaceNote { new_note: note, .. } => note.id, + DeviceChange::CCChange { .. } => 0, + DeviceChange::None { .. } => 0 + } + } +} + +impl PartialOrd for DeviceChange { + fn partial_cmp(&self, other: &Self) -> Option { + let timestamp_cmp = self.timestamp().cmp(&other.timestamp()); + if timestamp_cmp == Ordering::Equal { + Some(self.id().cmp(&other.id())) + } else { + Some(timestamp_cmp) + } + } +} + +impl PartialEq for DeviceChange { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} + + +impl Device { + pub fn update(&mut self, + midi_message: MidiMessageWithDelta, current_time: usize, id: Option) -> DeviceChange { + let time = current_time + midi_message.delta_frames as usize; + + match MidiMessageType::from(&midi_message.data) { + MidiMessageType::NoteOnMessage(note) => { + let note_id = match id { + None => { + let note_id = self.note_index; + self.note_index += 1; + note_id + } + Some(id) => id + }; + let index = NoteIndex { channel: note.channel, pitch: note.pitch }; + let new_note = Note { + id: note_id, + pressed_at: time, + released_at: 0, + channel: note.channel, + pitch: note.pitch, + velocity: note.velocity, + velocity_off: 0, + pressure: self.channels[note.channel as usize].pressure, + timbre: self.channels[note.channel as usize].timbre, + pitchbend: self.channels[note.channel as usize].pitchbend, + }; + + match self.notes.insert(index, new_note) { + None => { + DeviceChange::AddNote { time, note: new_note } + } + Some(old_note) => { + DeviceChange::ReplaceNote { time, old_note, new_note } + } + } + } + MidiMessageType::NoteOffMessage(note) => { + let index = NoteIndex { channel: note.channel, pitch: note.pitch }; + + match self.notes.remove(&index) { + None => { + info!("Attempt to remove note, but it was not found {:02X?}", index); + DeviceChange::None { time } + } + Some(mut old_note) => { + //info!("Removed note {:02X?}", index); + old_note.released_at = time; + old_note.velocity_off = note.velocity; + DeviceChange::RemoveNote { time, note: old_note } + } + } + } + MidiMessageType::CCMessage(cc) => { + self.cc.insert(CCIndex { channel: cc.channel, index: cc.cc }, cc.value); + if cc.cc == TIMBRECC { + self.channels[cc.channel as usize].timbre = cc.value; + for (_, note) in self.notes.iter_mut() { + if note.channel == cc.channel { + note.timbre = cc.value; + // note: per design simplification, having several notes running on the same channel + // is not supported. only the first note found on the channel is updated + return DeviceChange::NoteExpressionChange { + time, + expression: Expression::Timbre, + note: *note, + }; + } + } + } + DeviceChange::CCChange { time, cc } + } + MidiMessageType::PressureMessage(message) => { + self.channels[message.channel as usize].pressure = message.value; + for (_, note) in self.notes.iter_mut() { + if note.channel == message.channel { + // note: per design simplification, having several notes running on the same channel + // is not supported. only the first note found on the channel is updated + note.pressure = message.value; + return DeviceChange::NoteExpressionChange { + time, + expression: Expression::Pressure, + note: *note, + }; + } + } + DeviceChange::None { time } + } + MidiMessageType::PitchBendMessage(message) => { + self.channels[message.channel as usize].pitchbend = message.millisemitones; + for (_, note) in self.notes.iter_mut() { + if note.channel == message.channel { + // note: per design simplification, having several notes running on the same channel + // is not supported. only the first note found on the channel is updated + note.pitchbend = message.millisemitones; + return DeviceChange::NoteExpressionChange { + time, + expression: Expression::PitchBend, + note: *note, + }; + } + } + DeviceChange::None { time } + } + MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::None { time }, + MidiMessageType::Unsupported => DeviceChange::None { time } + } + } +} diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/device_out.rs new file mode 100644 index 0000000..d2ac21d --- /dev/null +++ b/arpegiator/src/device_out.rs @@ -0,0 +1,77 @@ +use log::{error, info}; +use vst::buffer::SendEventBuffer; +use vst::plugin::HostCallback; + +use util::messages::NoteOff; +use util::raw_message::RawMessage; + +use crate::device::Device; +use crate::expressive_note::ExpressiveNote; +use crate::note::Note; +use crate::pattern::Pattern; +use util::midi_message_with_delta::MidiMessageWithDelta; + + +#[derive(Default)] +pub struct DeviceOut { + pub device: Device, + queue: Vec, +} + + +impl DeviceOut { + pub fn update(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) { + self.queue.push(midi_message); + self.device.update(midi_message, current_time, id); + } + + pub fn flush_to(&mut self, send_buffer: &mut SendEventBuffer, host: &mut HostCallback) { + send_buffer.send_events(self.queue.drain(..).map(|message| message.new_midi_event()), host); + } + + pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { + let note = match self.device.notes.values().find(|note| note.id == note_id) { + None => { + // info!("Cannot find note to stop: {:02x?}", note_id); + return + } + Some(note) => note + }; + + let raw_message : RawMessage = NoteOff { + channel: note.channel, + pitch: note.pitch, + velocity: velocity_off + }.into(); + + self.update(MidiMessageWithDelta { + delta_frames, + data: raw_message.into(), + }, current_time, None); + } + + pub fn push_note_on(&mut self, pattern: &Pattern, note: &Note, current_time: usize) { + let pitch = match pattern.transpose(note.pitch) { + None => { return } + Some(pitch) => pitch + }; + + for raw_message in (ExpressiveNote { + channel: pattern.channel, + pitch, + velocity: pattern.velocity, + pressure: pattern.pressure, + timbre: pattern.timbre, + pitchbend: pattern.pitchbend, + }).into_rawmessages() { + self.update( + MidiMessageWithDelta { + delta_frames: (pattern.pressed_at - current_time) as u16, + data: raw_message.into(), + }, + current_time, + Some(pattern.id) + ); + } + } +} diff --git a/arpegiator/src/expressive_note.rs b/arpegiator/src/expressive_note.rs new file mode 100644 index 0000000..52e4a3a --- /dev/null +++ b/arpegiator/src/expressive_note.rs @@ -0,0 +1,36 @@ +use util::raw_message::RawMessage; +use util::messages::{NoteOn, Timbre, Pressure, PitchBend}; + + +pub struct ExpressiveNote { + pub channel: u8, + pub pitch: u8, + pub velocity: u8, + pub pressure: u8, + pub timbre: u8, + pub pitchbend: i32, +} + +impl ExpressiveNote { + pub fn into_rawmessages(self) -> Vec { + vec![ + Timbre { + channel: self.channel, + value: self.timbre, + }.into(), + Pressure { + channel: self.channel, + value: self.pressure, + }.into(), + PitchBend { + channel: self.channel, + millisemitones: self.pitchbend, + }.into(), + NoteOn { + channel: self.channel, + pitch: self.pitch, + velocity: self.velocity, // todo mixing between pattern and note + }.into(), + ] + } +} diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs new file mode 100644 index 0000000..a7339d5 --- /dev/null +++ b/arpegiator/src/lib.rs @@ -0,0 +1,289 @@ +use itertools::Itertools; +use log::{error, info}; +use vst::api; +use vst::buffer::{AudioBuffer, SendEventBuffer}; +use vst::event::{Event, MidiEvent}; +use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; + +use device_out::DeviceOut; +use util::logging::logging_setup; +use util::messages::{PitchBend, Pressure, Timbre}; +use util::raw_message::RawMessage; + +use crate::change::SourceChange; +use crate::device::{Device, Expression}; +use crate::pattern_device::{PatternDevice, PatternDeviceChange}; +use crate::timed_event::TimedEvent; +use util::midi_message_with_delta::MidiMessageWithDelta; +use std::sync::Arc; +use crate::parameters::ArpegiatorParameters; +use crate::socket::{SocketChannels, SocketCommand, create_socket_thread}; +use std::thread::JoinHandle; +use std::mem::take; + +pub mod pattern; +mod note; +mod device; +mod pattern_device; +mod timed_event; +mod change; +mod expressive_note; +mod device_out; +mod parameters; +mod socket; + + +#[macro_use] +extern crate vst; + + +plugin_main!(ArpegiatorPlugin); + + +pub struct ArpegiatorPlugin { + events: Vec, + send_buffer: SendEventBuffer, + host: HostCallback, + pattern_device_in: Device, + notes_device_in: Device, + pattern_device: PatternDevice, + current_time: usize, + device_out: DeviceOut, + parameters: Arc, + socket_channels: Option, + thread_handle: Option> +} + + +impl ArpegiatorPlugin { + fn close_socket(&mut self) { + if let Some(socket_channels) = self.socket_channels.as_ref() { + if let Err(e) = socket_channels.command_sender.try_send(SocketCommand::Stop) { + error!("Error while closing note receiver channel : {:?}", e) + } + } + + if let Some(thread_handle) = take(&mut self.thread_handle) { + thread_handle.join().unwrap(); + } + + self.socket_channels = None; // so the channel is not dropped before the thread is joined + } +} + + +impl Default for ArpegiatorPlugin { + fn default() -> Self { + ArpegiatorPlugin { + events: vec![], + send_buffer: Default::default(), + host: Default::default(), + pattern_device_in: Default::default(), + notes_device_in: Default::default(), + pattern_device: PatternDevice::default(), + current_time: 0, + device_out: DeviceOut::default(), + parameters: Arc::new(ArpegiatorParameters::new()), + socket_channels: None, + thread_handle: None + } + } +} + + +impl Plugin for ArpegiatorPlugin { + fn get_info(&self) -> Info { + Info { + name: "Arpegiator".to_string(), + vendor: "DJ Crontab".to_string(), + unique_id: 342111721, + parameters: 1, + category: Category::Synth, + initial_delay: 0, + version: 1, + inputs: 0, + outputs: 0, + midi_inputs: 1, + f64_precision: false, + presets: 1, + midi_outputs: 1, + preset_chunks: true, + silent_when_stopped: true, + } + } + + fn new(host: HostCallback) -> Self { + logging_setup(); + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, + $.compiler, $.timestamp)); + ArpegiatorPlugin { + events: vec![], + send_buffer: Default::default(), + host, + pattern_device_in: Default::default(), + notes_device_in: Default::default(), + pattern_device: Default::default(), + current_time: 0, + device_out: DeviceOut::default(), + parameters: Arc::new(ArpegiatorParameters::new()), + socket_channels: None, + thread_handle: None + } + } + + fn resume(&mut self) { + self.close_socket(); + + self.current_time = 0 ; + + let (join_handle, socket_channels) = create_socket_thread(); + self.thread_handle = Some(join_handle) ; + + socket_channels.command_sender.try_send(SocketCommand::SetPort(self.parameters.get_port())).unwrap(); + + if let Ok(mut socket_command) = self.parameters.socket_command.lock() { + *socket_command = Some(socket_channels.command_sender.clone()); + } + + self.socket_channels = Some(socket_channels); + } + + fn suspend(&mut self) { + self.close_socket() + } + + fn get_parameter_object(&mut self) -> Arc { + Arc::clone(&self.parameters) as Arc + } + + fn can_do(&self, can_do: CanDo) -> vst::api::Supported { + use vst::api::Supported::*; + use vst::plugin::CanDo::*; + + match can_do { + SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline => Yes, + Other(s) => { + if s == "MPE" { + Yes + } else { + + Maybe + } + } + _ => No, + } + } + + fn process(&mut self, buffer: &mut AudioBuffer) { + let messages = match self.socket_channels.as_ref() { + None => vec![], + Some(socket_channels) => { + match socket_channels.notes_receiver.try_recv() { + Ok(payload) => { + //info!("[{}] received patterns : {:02X?}", self.current_time, payload); + payload.messages + } + Err(_) => vec![] + } + } + }; + + // this avoids borrowing self + let current_time = self.current_time; + let pattern_device_in = &mut self.pattern_device_in; + let pattern_device = &mut self.pattern_device; + let notes_device_in = &mut self.notes_device_in; + + let pattern_changes = messages.into_iter().map(|message| { + let change = pattern_device_in.update(message, current_time, None); + let change = pattern_device.update(change); + SourceChange::PatternChange(change) + }); + + let note_changes = self.events.iter().map(|event| { + let midi_message_with_delta = MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: event.data, + }; + + let change = notes_device_in.update(midi_message_with_delta, current_time, None); + SourceChange::NoteChange(change) + }); + + // merge() gets a sorted output. If a note is triggered at the same time as a pattern, note comes first in order + // to set the pitch + for change in pattern_changes.sorted().merge(note_changes.sorted()) { + let delta_frames = (change.timestamp() - self.current_time) as u16; + + match change { + SourceChange::NoteChange(_) => { + // TODO note changed. for now we don't change anything, it's only when a pattern starts or ends + // that we trigger anything + } + SourceChange::PatternChange(change) => { + match change { + PatternDeviceChange::AddPattern { pattern, .. } => { + match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { + None => {} + Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time) + } + } + + PatternDeviceChange::PatternExpressionChange { expression, pattern, .. } => { + let raw_message: RawMessage = match expression { + Expression::Timbre => { + Timbre { channel: pattern.channel, value: pattern.timbre }.into() + } + Expression::Pressure => { + Pressure { channel: pattern.channel, value: pattern.pressure }.into() + } + Expression::PitchBend => { + PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into() + } + }; + + self.device_out.update(MidiMessageWithDelta { delta_frames, data: raw_message.into() }, + current_time, None); + } + PatternDeviceChange::RemovePattern { pattern, .. } => { + self.device_out.push_note_off(pattern.id, pattern.velocity_off, + delta_frames, current_time); + } + PatternDeviceChange::ReplacePattern { old_pattern, new_pattern, .. } => { + self.device_out.push_note_off(old_pattern.id, old_pattern.velocity_off, + delta_frames, current_time); + + let note = match self.notes_device_in.notes.values().sorted().nth(new_pattern.index as usize) { + None => { continue; } + Some(note) => note + }; + + self.device_out.push_note_on(&new_pattern, note, current_time); + } + PatternDeviceChange::None { .. } => {} + } + } + } + + self.device_out.flush_to(&mut self.send_buffer, &mut self.host) + } + + self.events.clear(); + + self.current_time += buffer.samples() + } + + fn process_events(&mut self, events: &api::Events) { + for e in events.events() { + if let Event::Midi(e) = e { + self.events.push(e); + } + } + } +} + +impl Drop for ArpegiatorPlugin { + fn drop(&mut self) { + self.close_socket(); + } +} diff --git a/arpegiator/src/note.rs b/arpegiator/src/note.rs new file mode 100644 index 0000000..97cc142 --- /dev/null +++ b/arpegiator/src/note.rs @@ -0,0 +1,61 @@ +use std::cmp::Ordering; + +#[derive(Clone, Copy, Debug)] +pub struct Note { + pub id: usize, + + pub pressed_at: usize, + pub released_at: usize, + + pub channel: u8, + pub pitch: u8, + pub velocity: u8, + pub velocity_off: u8, + + pub pressure: u8, + pub timbre: u8, + pub pitchbend: i32, // in millisemitones +} + + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct NoteIndex { + pub channel: u8, + pub pitch: u8 +} + + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct CCIndex { + pub channel: u8, + pub index: u8 +} + + +impl PartialOrd for Note { + fn partial_cmp(&self, other: &Self) -> Option { + let cmp = self.pitch.cmp(&other.pitch); + + if cmp != Ordering::Equal { + Some(cmp) + } else { + // unlikely to produce any interesting result + Some(self.id.cmp(&other.id)) + } + } +} + + +impl PartialEq for Note { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Ord for Note { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + +impl Eq for Note {} diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs new file mode 100644 index 0000000..48bb90b --- /dev/null +++ b/arpegiator/src/parameters.rs @@ -0,0 +1,123 @@ +use vst::plugin::PluginParameters; +use vst::util::ParameterTransfer; + +use util::parameters::ParameterConversion; +use util::parameter_value_conversion::f32_to_byte; +use crate::socket::SocketCommand; +use std::sync::Mutex; +use smol::channel::Sender; + +const PARAMETER_COUNT: usize = 1; +const BASE_PORT: u16 = 6000; + +pub struct ArpegiatorParameters { + pub transfer: ParameterTransfer, + pub socket_command: Mutex>> +} + +impl ArpegiatorParameters { + pub fn get_port(&self) -> u16 { + BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 + } + + pub fn update_port(&self) { + let port = self.get_byte_parameter(Parameter::PortIndex); + self.socket_command.lock().unwrap().as_ref().unwrap().try_send( + SocketCommand::SetPort(BASE_PORT + port as u16) + ).unwrap(); + } +} + + +#[repr(i32)] +pub enum Parameter { + PortIndex = 0, +} + +impl From for Parameter { + fn from(i: i32) -> Self { + match i { + 0 => Parameter::PortIndex, + _ => panic!("no such parameter {}", i), + } + } +} + + +impl Into for Parameter { + fn into(self) -> i32 { + self as i32 + } +} + +impl ParameterConversion for ArpegiatorParameters { + fn get_parameter_transfer(&self) -> &ParameterTransfer { + &self.transfer + } + + fn get_parameter_count() -> usize { + PARAMETER_COUNT + } +} + + +impl ArpegiatorParameters { + pub fn new() -> Self { + ArpegiatorParameters { + transfer: ParameterTransfer::new(PARAMETER_COUNT), + socket_command: Mutex::new(None) + } + } +} + + +impl PluginParameters for ArpegiatorParameters { + fn get_parameter_text(&self, index: i32) -> String { + match index.into() { + Parameter::PortIndex => { + self.get_port().to_string() + } + } + } + + fn get_parameter_name(&self, index: i32) -> String { + match index.into() { + Parameter::PortIndex => "Port", + }.to_string() + } + + fn get_parameter(&self, index: i32) -> f32 { + self.get_parameter_transfer().get_parameter(index as usize) + } + + fn set_parameter(&self, index: i32, value: f32) { + match index.into() { + Parameter::PortIndex => { + let new_value = f32_to_byte(value); + let old_value = self.get_byte_parameter(Parameter::PortIndex); + if old_value != new_value { + self.transfer.set_parameter(index as usize, value); + self.update_port() + } + } + } + } + + fn get_preset_data(&self) -> Vec { + self.serialize_state() + } + + fn get_bank_data(&self) -> Vec { + self.serialize_state() + } + + fn load_preset_data(&self, data: &[u8]) { + self.deserialize_state(data); + self.update_port() + } + + fn load_bank_data(&self, data: &[u8]) { + self.deserialize_state(data); + self.update_port() + } +} diff --git a/arpegiator/src/pattern.rs b/arpegiator/src/pattern.rs new file mode 100644 index 0000000..44fbe60 --- /dev/null +++ b/arpegiator/src/pattern.rs @@ -0,0 +1,72 @@ +use crate::note::Note; +use crate::timed_event::TimedEvent; + +pub const C3: u8 = 60 ; + +#[derive(Copy, Clone, Debug)] +pub struct Pattern { + pub id: usize, + + pub channel: u8, + pub index: u8, + pub octave: i8, + + pub pressed_at: usize, + pub released_at: usize, + + pub velocity: u8, + pub velocity_off: u8, + + pub pressure: u8, + pub timbre: u8, + pub pitchbend: i32, // in millisemitones +} + +impl Pattern { + pub fn transpose(&self, pitch: u8) -> Option { + let pitch = pitch as i16 + self.octave as i16 * 12; + if !(0..=127).contains(&pitch) { + // can't play. to fix if we want to change the pitch of a pattern that started + None + } else { + Some(pitch as u8) + } + } +} + + +impl TimedEvent for Pattern { + fn timestamp(&self) -> usize { + if self.released_at > 0 { + self.released_at + } else { + self.pressed_at + } + } + + fn id(&self) -> usize { + self.id + } +} + + +impl From for Pattern { + fn from(note: Note) -> Self { + let index = note.pitch % 12; + let octave = (((note.pitch - index) as i16 - C3 as i16) / 12) as i8; + + Pattern { + channel: note.channel, + id: note.id, + index, + velocity_off: 0, + pressure: 0, + timbre: 0, + octave, + pressed_at: note.pressed_at, + released_at: 0, + velocity: note.velocity, + pitchbend: 0 + } + } +} diff --git a/arpegiator/src/pattern_device.rs b/arpegiator/src/pattern_device.rs new file mode 100644 index 0000000..221bc0a --- /dev/null +++ b/arpegiator/src/pattern_device.rs @@ -0,0 +1,117 @@ +use log::error; +use std::collections::HashMap; + + +use crate::pattern::Pattern; +use crate::device::{DeviceChange, Expression}; +use crate::timed_event::TimedEvent; +use std::cmp::Ordering; + + +#[derive(Default)] +pub struct PatternDevice { + pub patterns: HashMap +} + + +pub enum PatternDeviceChange { + AddPattern { time: usize, pattern: Pattern }, + PatternExpressionChange { time: usize, expression: Expression, pattern: Pattern }, + RemovePattern { time: usize, pattern: Pattern }, + ReplacePattern { time: usize, old_pattern: Pattern, new_pattern: Pattern }, + None { time: usize }, +} + + +impl PartialOrd for PatternDeviceChange { + fn partial_cmp(&self, other: &Self) -> Option { + let timestamp_cmp = self.timestamp().cmp(&other.timestamp()); + if timestamp_cmp == Ordering::Equal { + Some(self.id().cmp(&other.id())) + } else { + Some(timestamp_cmp) + } + } +} + +impl PartialEq for PatternDeviceChange { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} + +impl TimedEvent for PatternDeviceChange { + fn timestamp(&self) -> usize { + match self { + PatternDeviceChange::AddPattern { time, .. } => *time, + PatternDeviceChange::PatternExpressionChange { time, .. } => *time, + PatternDeviceChange::RemovePattern { time, .. } => *time, + PatternDeviceChange::ReplacePattern { time, .. } => *time, + PatternDeviceChange::None { time, .. } => *time + } + } + + fn id(&self) -> usize { + match self { + PatternDeviceChange::AddPattern { pattern, .. } => pattern.id, + PatternDeviceChange::PatternExpressionChange { pattern, .. } => pattern.id, + PatternDeviceChange::RemovePattern { pattern, .. } => pattern.id, + PatternDeviceChange::ReplacePattern { new_pattern: pattern, .. } => pattern.id, + PatternDeviceChange::None { .. } => 0 + } + } +} + + +impl PatternDevice { + pub fn update(&mut self, change: DeviceChange) -> PatternDeviceChange { + match change { + DeviceChange::AddNote { time, note } => { + let new_pattern = Pattern::from(note); + let pattern = self.patterns.insert(note.id, new_pattern); + + match pattern { + None => PatternDeviceChange::AddPattern { time, pattern: new_pattern }, + Some(old_pattern) => { + PatternDeviceChange::ReplacePattern { time, old_pattern, new_pattern } + } + } + } + DeviceChange::RemoveNote { time, note } => { + match self.patterns.remove(¬e.id) { + None => PatternDeviceChange::None { time }, + Some(mut pattern) => { + pattern.velocity_off = note.velocity_off; + pattern.released_at = note.released_at; + PatternDeviceChange::RemovePattern { time, pattern } + } + } + } + DeviceChange::NoteExpressionChange { time, expression, note } => { + match self.patterns.get_mut(¬e.id) { + None => PatternDeviceChange::None { time }, + Some(pattern) => { + pattern.pressure = note.pressure; + pattern.timbre = note.timbre; + pattern.pitchbend = note.pitchbend; + PatternDeviceChange::PatternExpressionChange { time, expression, pattern: *pattern } + } + } + } + DeviceChange::ReplaceNote { time, old_note, new_note } => { + let new_pattern = Pattern::from(new_note); + match self.patterns.insert(new_pattern.id, new_pattern) { + None => { + error!("Expected to replace a pattern matching, found nothing {:?}", old_note); + PatternDeviceChange::None { time } + } + Some(old_pattern) => { + PatternDeviceChange::ReplacePattern { time, old_pattern, new_pattern } + } + } + } + DeviceChange::CCChange { time, .. } => PatternDeviceChange::None { time }, + DeviceChange::None { time } => PatternDeviceChange::None { time } + } + } +} diff --git a/arpegiator/src/socket.rs b/arpegiator/src/socket.rs new file mode 100644 index 0000000..f15d281 --- /dev/null +++ b/arpegiator/src/socket.rs @@ -0,0 +1,117 @@ +use log::{error, info}; +use util::pattern_payload::PatternPayload; +use std::thread::JoinHandle; +use std::thread; +use smol::channel::{unbounded, Sender, Receiver}; +use smol::future::{race, pending}; +use async_net::UdpSocket; + +pub enum SocketCommand { + Stop, + SetPort(u16) +} + +pub struct SocketChannels { + pub command_sender: Sender, + pub notes_receiver: Receiver +} + +enum FutureResult { + Command(SocketCommand), + Payload(PatternPayload), + PayloadError, + ChannelError, + SocketError +} + +pub fn create_socket_thread() -> (JoinHandle<()>, SocketChannels) { + let (command_sender, command_receiver) = unbounded::(); + let (notes_sender, notes_receiver) = unbounded::(); + + let handle = thread::spawn(move || { + let mut socket : Option = None; + let mut buf = vec![0u8; 1024]; + + smol::block_on( async { + loop { + let channel_future = async { + match command_receiver.recv().await { + Ok(command) => { + FutureResult::Command(command) + } + Err(err) => { + error!("Error on command channel while receiving {:?} {}", err, err); + FutureResult::ChannelError + } + } + }; + + let socket_future = async { + match socket.as_ref() { + None => pending().await, + Some(socket) => { + match socket.recv(&mut buf).await { + Ok(len) => { + match bincode::deserialize::(&buf[..len]) { + Ok(payload) => { + FutureResult::Payload(payload) + } + Err(err) => { + error!("Could not deserialize: {:?}", err); + FutureResult::PayloadError + } + } + } + Err(err) => { + error!("Socket error: {:?}", err); + FutureResult::SocketError + } + } + } + } + }; + + match race(channel_future, socket_future).await { + FutureResult::Command(command) => { + match command { + SocketCommand::Stop => { + return + } + SocketCommand::SetPort(port) => { + socket = None; + + match UdpSocket::bind(format!("127.0.0.1:{}", port)).await { + Ok(le_socket) => { + info!("Listening on port {}", port); + socket = Some(le_socket) + } + Err(err) => { + error!("Cannot bind on port {} : {:?}", port, err) + } + } + } + } + } + FutureResult::Payload(payload) => { + notes_sender.send(payload).await.unwrap(); + } + FutureResult::ChannelError => { + // unrecoverable + return + } + FutureResult::SocketError => { + socket = None + } + FutureResult::PayloadError => { + // noop, just loop over + } + } + } + }); + }); + + (handle, SocketChannels { + command_sender, + notes_receiver + }) +} diff --git a/arpegiator/src/timed_event.rs b/arpegiator/src/timed_event.rs new file mode 100644 index 0000000..5d1fab9 --- /dev/null +++ b/arpegiator/src/timed_event.rs @@ -0,0 +1,4 @@ +pub trait TimedEvent { + fn timestamp(&self) -> usize ; + fn id(&self) -> usize ; +} diff --git a/arpegiator_pattern_receiver/Cargo.toml b/arpegiator_pattern_receiver/Cargo.toml new file mode 100644 index 0000000..70c18e5 --- /dev/null +++ b/arpegiator_pattern_receiver/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "arpegiator_pattern_receiver" +version = "0.1.0" +authors = ["Vincent Alsteen "] +edition = "2018" + +[dependencies] +vst = { git = "https://github.com/rustaudio/vst-rs" } +util = { path = "../util" } +arpegiator = { path = "../arpegiator" } +build-info = "0.0.20" +log = "0.4.11" +serde = "1.0.118" +bincode = "1.3.1" + +[build-dependencies] +build-info-build = "0.0.20" + +[lib] +name = "arpegiator_pattern_receiver" +crate-type = ["cdylib", "lib"] diff --git a/arpegiator_pattern_receiver/build.rs b/arpegiator_pattern_receiver/build.rs new file mode 100644 index 0000000..a49ab18 --- /dev/null +++ b/arpegiator_pattern_receiver/build.rs @@ -0,0 +1,5 @@ +fn main() { + // Calling `build_info_build::build_script` collects all data and makes it available to `build_info::build_info!` + // and `build_info::format!` in the main program. + build_info_build::build_script(); +} diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs new file mode 100644 index 0000000..fe36def --- /dev/null +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -0,0 +1,173 @@ +use std::sync::mpsc::Sender; +use std::thread::JoinHandle; + +use log::{info, error}; +use vst::api; +use vst::buffer::AudioBuffer; +use vst::event::Event; +use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; + +use util::logging::logging_setup; +use util::midi_message_with_delta::MidiMessageWithDelta; +use crate::socket::{SenderSocketCommand, create_socket_thread}; +use std::mem::take; +use crate::parameters::ArpegiatorPatternReceiverParameters; +use std::sync::Arc; +use util::pattern_payload::PatternPayload; + +mod parameters; +mod socket; + +#[macro_use] +extern crate vst; + +plugin_main!(ArpegiatorPatternReceiver); + + +struct ArpegiatorPatternReceiver { + host: HostCallback, + socket_thread_handle: Option>, + socket_channel_sender: Option>, + messages: Vec, + current_time: usize, + parameters: Arc +} + + +impl Default for ArpegiatorPatternReceiver { + fn default() -> Self { + ArpegiatorPatternReceiver { + host: Default::default(), + socket_thread_handle: None, + socket_channel_sender: None, + messages: vec![], + current_time: 0, + parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) + } + } +} + +impl ArpegiatorPatternReceiver { + fn close_socket(&mut self) { + if let Some(sender) = take(&mut self.socket_channel_sender) { + if let Err(e) = sender.send(SenderSocketCommand::Stop) { + error!("Error while closing sender channel : {:?} {}", e, e) + } + } + + if let Some(thread_handle) = take(&mut self.socket_thread_handle) { + thread_handle.join().unwrap(); + } + } +} + + +impl Plugin for ArpegiatorPatternReceiver { + fn get_info(&self) -> Info { + Info { + name: "Arpegiator Pattern Receiver".to_string(), + vendor: "DJ Crontab".to_string(), + unique_id: 342112720, + parameters: 1, + category: Category::Synth, + initial_delay: 0, + version: 2, + inputs: 0, + outputs: 0, + midi_inputs: 1, + f64_precision: false, + presets: 1, + midi_outputs: 1, + preset_chunks: true, + silent_when_stopped: true, + } + } + + fn resume(&mut self) { + self.current_time = 0 ; + + let (join_handle, sender) = create_socket_thread(); + self.socket_thread_handle = Some(join_handle) ; + self.socket_channel_sender = Some(sender.clone()); + sender.send(SenderSocketCommand::SetPort(self.parameters.get_port())).unwrap(); + + if let Ok(mut socket_command) = self.parameters.socket_command.lock() { + *socket_command = Some(sender); + } + } + + fn suspend(&mut self) { + self.close_socket() + } + + fn new(host: HostCallback) -> Self { + logging_setup(); + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, + $.compiler, $.timestamp)); + + ArpegiatorPatternReceiver { + host, + socket_thread_handle: None, + socket_channel_sender: None, + messages: vec![], + current_time: 0, + parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) + } + } + + fn can_do(&self, can_do: CanDo) -> vst::api::Supported { + use vst::api::Supported::*; + use vst::plugin::CanDo::*; + + match can_do { + SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline => Yes, + Other(s) => { + if s == "MPE" { + Yes + } else { + + Maybe + } + } + _ => No, + } + } + + fn process(&mut self, buffer: &mut AudioBuffer) { + if !self.messages.is_empty() { + if let Some(sender) = &self.socket_channel_sender { + let payload = PatternPayload { + time: self.current_time, + messages: take(&mut self.messages) + } ; + sender.send(SenderSocketCommand::Send(payload)).unwrap() + } + self.messages.clear(); + } + + self.current_time += buffer.samples() + } + + fn process_events(&mut self, events: &api::Events) { + if self.socket_channel_sender.is_some() { + for e in events.events() { + if let Event::Midi(e) = e { + self.messages.push( MidiMessageWithDelta { + delta_frames: e.delta_frames as u16, + data: e.data + }); + } + } + } + } + + fn get_parameter_object(&mut self) -> Arc { + Arc::clone(&self.parameters) as Arc + } +} + +impl Drop for ArpegiatorPatternReceiver { + fn drop(&mut self) { + self.close_socket(); + } +} diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs new file mode 100644 index 0000000..1d49a01 --- /dev/null +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -0,0 +1,123 @@ +use vst::plugin::PluginParameters; +use vst::util::ParameterTransfer; + +use util::parameters::ParameterConversion; +use util::parameter_value_conversion::f32_to_byte; +use std::sync::mpsc::Sender; +use crate::socket::SenderSocketCommand; +use std::sync::Mutex; + +const PARAMETER_COUNT: usize = 1; +const BASE_PORT: u16 = 6000; + +pub struct ArpegiatorPatternReceiverParameters { + pub transfer: ParameterTransfer, + pub socket_command: Mutex>> +} + +impl ArpegiatorPatternReceiverParameters { + pub fn get_port(&self) -> u16 { + BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 + } + + fn update_port(&self) { + let port = self.get_byte_parameter(Parameter::PortIndex); + self.socket_command.lock().unwrap().as_ref().unwrap().send( + SenderSocketCommand::SetPort(BASE_PORT + port as u16) + ).unwrap(); + } +} + + +#[repr(i32)] +pub enum Parameter { + PortIndex = 0, +} + +impl From for Parameter { + fn from(i: i32) -> Self { + match i { + 0 => Parameter::PortIndex, + _ => panic!("no such parameter {}", i), + } + } +} + + +impl Into for Parameter { + fn into(self) -> i32 { + self as i32 + } +} + +impl ParameterConversion for ArpegiatorPatternReceiverParameters { + fn get_parameter_transfer(&self) -> &ParameterTransfer { + &self.transfer + } + + fn get_parameter_count() -> usize { + PARAMETER_COUNT + } +} + + +impl ArpegiatorPatternReceiverParameters { + pub fn new() -> Self { + ArpegiatorPatternReceiverParameters { + transfer: ParameterTransfer::new(PARAMETER_COUNT), + socket_command: Mutex::new(None) + } + } +} + + +impl PluginParameters for ArpegiatorPatternReceiverParameters { + fn get_parameter_text(&self, index: i32) -> String { + match index.into() { + Parameter::PortIndex => { + self.get_port().to_string() + } + } + } + + fn get_parameter_name(&self, index: i32) -> String { + match index.into() { + Parameter::PortIndex => "Port", + }.to_string() + } + + fn get_parameter(&self, index: i32) -> f32 { + self.get_parameter_transfer().get_parameter(index as usize) + } + + fn set_parameter(&self, index: i32, value: f32) { + match index.into() { + Parameter::PortIndex => { + let new_value = f32_to_byte(value); + let old_value = self.get_byte_parameter(Parameter::PortIndex); + if old_value != new_value { + self.transfer.set_parameter(index as usize, value); + self.update_port(); + } + } + } + } + + fn get_preset_data(&self) -> Vec { + self.serialize_state() + } + + fn get_bank_data(&self) -> Vec { + self.serialize_state() + } + + fn load_preset_data(&self, data: &[u8]) { + self.deserialize_state(data); + self.update_port() + } + + fn load_bank_data(&self, data: &[u8]) { + self.deserialize_state(data); + self.update_port() + } +} diff --git a/arpegiator_pattern_receiver/src/socket.rs b/arpegiator_pattern_receiver/src/socket.rs new file mode 100644 index 0000000..12f3c50 --- /dev/null +++ b/arpegiator_pattern_receiver/src/socket.rs @@ -0,0 +1,40 @@ +use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; +use std::sync::mpsc::{channel, Sender}; +use std::thread; +use std::thread::JoinHandle; + +use util::pattern_payload::PatternPayload; + +pub enum SenderSocketCommand { + Stop, + SetPort(u16), + Send(PatternPayload), +} + + +pub fn create_socket_thread() -> (JoinHandle<()>, Sender) { + let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); + let (sender, receiver) = channel::(); + + let handle = thread::spawn(move || { + let mut to: Option = None; + + while let Ok(command) = receiver.recv() { + match command { + SenderSocketCommand::Stop => { + return; + } + SenderSocketCommand::SetPort(port) => { + to = format!("127.0.0.1:{}", port).to_socket_addrs().unwrap().next() + } + SenderSocketCommand::Send(payload) => { + if let Some(port_to) = to { + socket.send_to(&*bincode::serialize(&payload).unwrap(), port_to).unwrap(); + } + } + } + } + }); + + (handle, sender) +} diff --git a/audio_data/Cargo.toml b/audio_data/Cargo.toml new file mode 100644 index 0000000..41eeaf9 --- /dev/null +++ b/audio_data/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "audio_data" +version = "0.1.0" +authors = ["Vincent Alsteen "] +edition = "2018" + +[dependencies] +vst = { git = "https://github.com/rustaudio/vst-rs" } +util = { path = "../util" } +build-info = "0.0.20" +log = "0.4.11" + +[build-dependencies] +build-info-build = "0.0.20" + +[lib] +name = "audio_data" +crate-type = ["cdylib", "lib"] diff --git a/audio_data/build.rs b/audio_data/build.rs new file mode 100644 index 0000000..a49ab18 --- /dev/null +++ b/audio_data/build.rs @@ -0,0 +1,5 @@ +fn main() { + // Calling `build_info_build::build_script` collects all data and makes it available to `build_info::build_info!` + // and `build_info::format!` in the main program. + build_info_build::build_script(); +} diff --git a/audio_data/src/lib.rs b/audio_data/src/lib.rs new file mode 100644 index 0000000..ff35350 --- /dev/null +++ b/audio_data/src/lib.rs @@ -0,0 +1,116 @@ +#[macro_use] +extern crate vst; + +use log::info; + +use vst::api; +use vst::buffer::{AudioBuffer, SendEventBuffer}; +use vst::event::{Event, MidiEvent}; +use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; +use std::time::SystemTime; +use util::logging::logging_setup; +use util::transmute_buffer::{transmute_raw_buffer, transmute_raw_buffer_mut}; + + + +plugin_main!(AudioData); + +pub struct AudioData { + events: Vec, + send_buffer: SendEventBuffer, + host: HostCallback, + last_was: u128, + last_now: u128 +} + +impl Default for AudioData { + fn default() -> Self { + AudioData { + events: vec![], + send_buffer: Default::default(), + host: Default::default(), + last_was: 0, + last_now: 0 + } + } +} + +impl AudioData { + fn send_midi(&mut self) { + self.send_buffer.send_events(&self.events, &mut self.host); + self.events.clear(); + } +} + + +impl Plugin for AudioData { + fn get_info(&self) -> Info { + Info { + name: "Audio data".to_string(), + vendor: "DJ Crontab".to_string(), + unique_id: 342131710, + parameters: 0, + category: Category::Synth, + initial_delay: 0, + version: 1, + inputs: 2, + outputs: 2, + midi_inputs: 1, + f64_precision: false, + presets: 1, + midi_outputs: 1, + preset_chunks: true, + silent_when_stopped: true, + } + } + + + fn new(host: HostCallback) -> Self { + logging_setup(); + AudioData { + events: vec![], + send_buffer: Default::default(), + host, + last_was: 0, + last_now: 0 + } + } + + fn can_do(&self, can_do: CanDo) -> vst::api::Supported { + use vst::api::Supported::*; + use vst::plugin::CanDo::*; + + match can_do { + SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent => Yes, + _ => No, + } + } + + fn process(&mut self, buffer: &mut AudioBuffer) { + self.send_midi(); + let (inputs, mut outputs) = buffer.split(); + let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_micros(); + + let input_buffer = transmute_raw_buffer(inputs.get(0)); + let was = input_buffer[0]; + + if self.last_was != was { + self.last_was = was; + info!("difference = {} microseconds. Since last check: diff={} now={} was={}", now - was, now - + self.last_now, now, was); + } + + self.last_now = now; + + let output_buffer = transmute_raw_buffer_mut(outputs.get_mut(0)); + output_buffer[0] = now + } + + fn process_events(&mut self, events: &api::Events) { + for e in events.events() { + if let Event::Midi(e) = e { + self.events.push(e); + } + } + } +} diff --git a/note_off_delay/Cargo.toml b/note_off_delay/Cargo.toml index 944cedc..3fd68ba 100644 --- a/note_off_delay/Cargo.toml +++ b/note_off_delay/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } build-info = "0.0.20" +log = "0.4.11" [build-dependencies] build-info-build = "0.0.20" diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 8bcccc6..d6fe992 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -78,8 +78,7 @@ impl NoteOffDelayPlugin { } fn increase_time_in_samples(&mut self, samples: usize) { - let new_time_in_samples = self.current_time_in_samples + samples; - self.current_time_in_samples = new_time_in_samples; + self.current_time_in_samples += samples; } } diff --git a/osx_vst_bundler.sh b/osx_vst_bundler.sh index 69cbb15..9de2e4f 100755 --- a/osx_vst_bundler.sh +++ b/osx_vst_bundler.sh @@ -4,7 +4,8 @@ ARTEFACT_DIRECTORY=artefact LIB_PATH_PREFIX=target/release/ for BUNDLE_LIB in "NoteGenerator note_generator" "NoteOffDelay note_off_delay" "FilterOutNonNote filter_out_non_note" \ - " NoteFanOut note_fan_out" "MidiDelay midi_delay" "MaxNoteDuration max_note_duration"; do + " NoteFanOut note_fan_out" "MidiDelay midi_delay" "MaxNoteDuration max_note_duration" "AudioData audio_data" \ + " ArpegiatorPatternReceiver arpegiator_pattern_receiver" "Arpegiator arpegiator" ; do BUNDLE_NAME=$(echo $BUNDLE_LIB | cut -f1 -d" ") LIBNAME=$(echo $BUNDLE_LIB | cut -f2 -d" ") LIB_PATH=${LIB_PATH_PREFIX}lib${LIBNAME}.dylib diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..866c756 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 \ No newline at end of file diff --git a/util/Cargo.toml b/util/Cargo.toml index 1e27629..b47829d 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -6,4 +6,15 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } -global_counter = "0.2.1" \ No newline at end of file +global_counter = "0.2.1" +log = "0.4.11" +simplelog = "0.9.0" +build-info = "0.0.20" +serde = "1.0.118" + +[dependencies.log-panics] +version = "2.0.0" +features = ["with-backtrace"] + +[build-dependencies] +build-info-build = "0.0.20" diff --git a/util/build.rs b/util/build.rs new file mode 100644 index 0000000..a49ab18 --- /dev/null +++ b/util/build.rs @@ -0,0 +1,5 @@ +fn main() { + // Calling `build_info_build::build_script` collects all data and makes it available to `build_info::build_info!` + // and `build_info::format!` in the main program. + build_info_build::build_script(); +} diff --git a/util/src/constants.rs b/util/src/constants.rs index ed8b366..4160606 100644 --- a/util/src/constants.rs +++ b/util/src/constants.rs @@ -2,7 +2,7 @@ pub const PRESSURE: u8 = 0xD0; pub const PITCHBEND: u8 = 0xE0; pub const ZEROVALUE: u8 = 0x40; pub const CC: u8 = 0xB0; -pub const TIMBRECC: u8 = 0x4A; +pub const TIMBRECC: u8 = 0x4A; // 74 pub const NOTE_OFF: u8 = 0x80; pub const NOTE_ON: u8 = 0x90; pub const AFTERTOUCH: u8 = 0x90; diff --git a/util/src/lib.rs b/util/src/lib.rs index 54353a6..f1f91e1 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -13,6 +13,10 @@ pub mod midi_message_type; pub mod raw_message; pub mod absolute_time_midi_message_vector; pub mod delayed_message_consumer; +pub mod transmute_buffer; +pub mod logging; +pub mod midi_message_with_delta; +pub mod pattern_payload; #[derive(Default)] pub struct HostCallbackLock { diff --git a/util/src/logging.rs b/util/src/logging.rs new file mode 100644 index 0000000..55ebc71 --- /dev/null +++ b/util/src/logging.rs @@ -0,0 +1,28 @@ +// i.e. pass RUSTFLAGS='--cfg enable_logging' to enable +#[cfg(not(enable_logging))] +pub fn logging_setup() {} + + +#[cfg(enable_logging)] +pub fn logging_setup() { + use log::info; + use simplelog::*; + use std::fs::OpenOptions; + + unsafe { if LOGGING_SETUP { return } } + + if let Ok(file) = OpenOptions::new().append(true).create(true).open("/tmp/plugin.log") { + let mut config = ConfigBuilder::new(); + config.set_time_format("%+".to_string()); + WriteLogger::init( + LevelFilter::Info, config.build(), file, + ).unwrap(); + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp)) + } + + log_panics::init(); + + unsafe { LOGGING_SETUP = true } +} + +static mut LOGGING_SETUP: bool = false; diff --git a/util/src/messages.rs b/util/src/messages.rs index eb9dca1..be18c40 100644 --- a/util/src/messages.rs +++ b/util/src/messages.rs @@ -3,6 +3,7 @@ use vst::event::{Event, MidiEvent}; use super::constants::{NOTE_ON, NOTE_OFF, PRESSURE, PITCHBEND}; use super::raw_message::RawMessage; +use crate::constants::TIMBRECC; pub fn format_midi_event(e: &MidiEvent) -> String { format!( @@ -139,8 +140,8 @@ impl NoteMessage for NoteOff { } pub struct Pressure { - channel: u8, - value: u8 + pub channel: u8, + pub value: u8 } impl Into for Pressure { @@ -165,9 +166,8 @@ impl ChannelMessage for Pressure { } pub struct PitchBend { - channel: u8, - semitones: u8, - millisemitones: u8 + pub channel: u8, + pub millisemitones: i32 } impl ChannelMessage for PitchBend { @@ -180,8 +180,7 @@ impl Into for PitchBend { fn into(self) -> RawMessage { // 96000 millisemitones are expressed over the possible values of 14 bits ( 16384 ) // which never gets us an exact integer amount of semitones - let millisemitones = (self.semitones as i32 * 1000) + self.millisemitones as i32 ; - let value = ((millisemitones + 48000) * 16383) / 96000; + let value = ((self.millisemitones + 48000) * 16383) / 96000; let msb = value >> 7; let lsb = value & 0x7F; [self.channel + PITCHBEND, lsb as u8, msb as u8].into() @@ -197,16 +196,15 @@ impl From for PitchBend { PitchBend { channel: data[0] & 0x0F, - semitones: (millisemitones / 1000) as u8, - millisemitones: (millisemitones % 1000) as u8 + millisemitones } } } pub struct CC { - channel: u8, - cc: u8, - value: u8 + pub channel: u8, + pub cc: u8, + pub value: u8 } impl Into for CC { @@ -251,3 +249,18 @@ impl From<&[u8; 3]> for GenericChannelMessage { GenericChannelMessage(RawMessage::from(*data)) } } + +pub struct Timbre { + pub channel: u8, + pub value: u8 +} + +impl Into for Timbre { + fn into(self) -> RawMessage { + CC { + channel: self.channel, + cc: TIMBRECC, + value: self.value + }.into() + } +} diff --git a/util/src/midi_message_with_delta.rs b/util/src/midi_message_with_delta.rs new file mode 100644 index 0000000..b6ca3f8 --- /dev/null +++ b/util/src/midi_message_with_delta.rs @@ -0,0 +1,24 @@ +use serde::{Serialize, Deserialize}; +use vst::event::MidiEvent; + + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct MidiMessageWithDelta { + pub delta_frames: u16, + pub data: [u8; 3], +} + + +impl MidiMessageWithDelta { + pub fn new_midi_event(&self) -> MidiEvent { + MidiEvent { + data: self.data, + delta_frames: self.delta_frames as i32, + live: true, + note_length: None, + note_offset: None, + detune: 0, + note_off_velocity: 0, + } + } +} diff --git a/util/src/parameter_value_conversion.rs b/util/src/parameter_value_conversion.rs index e660416..cb0c7f0 100644 --- a/util/src/parameter_value_conversion.rs +++ b/util/src/parameter_value_conversion.rs @@ -25,12 +25,12 @@ pub fn f32_to_byte(value: f32) -> u8 { #[inline] pub fn u14_to_f32(value: u16) -> f32 { // pitchbend goes from 0x0000 ( -48 semitones ) to 0x3FFF ( +48 semitones ) - value as f32 / (0x3FFF as usize) as f32 + value as f32 / (0x3FFF_usize) as f32 } #[inline] pub fn f32_to_u14(value: f32) -> u16 { - (value * (0x3FFF as usize) as f32) as u16 + (value * (0x3FFF_usize) as f32) as u16 } diff --git a/util/src/pattern_payload.rs b/util/src/pattern_payload.rs new file mode 100644 index 0000000..5f1e343 --- /dev/null +++ b/util/src/pattern_payload.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +use crate::midi_message_with_delta::MidiMessageWithDelta; + +#[derive(Debug, Serialize, Deserialize)] +pub struct PatternPayload { + pub time: usize, + pub messages: Vec, +} diff --git a/util/src/transmute_buffer.rs b/util/src/transmute_buffer.rs new file mode 100644 index 0000000..3e4552b --- /dev/null +++ b/util/src/transmute_buffer.rs @@ -0,0 +1,22 @@ +pub fn transmute_raw_buffer_mut(buffer: &mut [F]) -> &mut [T] { + use std::slice; + use std::mem::size_of; + unsafe { + slice::from_raw_parts_mut( + buffer.as_mut_ptr() as *mut T, + buffer.len() * size_of::() / size_of::() + ) + } +} + +pub fn transmute_raw_buffer(buffer: & [F]) -> &[T] { + use std::slice; + use std::mem::size_of; + + unsafe { + slice::from_raw_parts_mut( + buffer.as_ptr() as *mut T, + buffer.len() * size_of::() / size_of::() + ) + } +} From 0c6c39731c465b73208b9fedd6fd486c1525c98c Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 2 Jan 2021 16:26:34 +0100 Subject: [PATCH 02/47] map pressure as aftertouch, that is properly handled --- arpegiator/src/device.rs | 23 +++++++++++++++-- arpegiator/src/expressive_note.rs | 39 +++++++++++++++++++++-------- arpegiator/src/lib.rs | 41 ++++++++++++++++++++++++------- util/src/constants.rs | 8 +++--- util/src/messages.rs | 34 +++++++++++++++++++++++-- util/src/midi_message_type.rs | 2 ++ util/src/raw_message.rs | 2 +- 7 files changed, 121 insertions(+), 28 deletions(-) diff --git a/arpegiator/src/device.rs b/arpegiator/src/device.rs index 14feb4c..fada7ec 100644 --- a/arpegiator/src/device.rs +++ b/arpegiator/src/device.rs @@ -44,6 +44,7 @@ pub enum Expression { Timbre, Pressure, PitchBend, + AfterTouch } pub enum DeviceChange { @@ -188,7 +189,24 @@ impl Device { } } DeviceChange::None { time } - } + }, + MidiMessageType::AfterTouchMessage(message) => { + // redundant with pressure, but that's the message that bitwig will properly handle for by-note + // expressions + for (_, note) in self.notes.iter_mut() { + if note.channel == message.channel && note.pitch == message.pitch { + note.pressure = message.value; + // since aftertouch is assigned by pitch and channel, contrary to channel pressure + // we are sure it's only affecting one note + return DeviceChange::NoteExpressionChange { + time, + expression: Expression::Pressure, + note: *note, + }; + } + } + DeviceChange::None { time } + }, MidiMessageType::PitchBendMessage(message) => { self.channels[message.channel as usize].pitchbend = message.millisemitones; for (_, note) in self.notes.iter_mut() { @@ -206,7 +224,8 @@ impl Device { DeviceChange::None { time } } MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::None { time }, - MidiMessageType::Unsupported => DeviceChange::None { time } + MidiMessageType::Unsupported => DeviceChange::None { time }, + } } } diff --git a/arpegiator/src/expressive_note.rs b/arpegiator/src/expressive_note.rs index 52e4a3a..f9d5eca 100644 --- a/arpegiator/src/expressive_note.rs +++ b/arpegiator/src/expressive_note.rs @@ -1,5 +1,7 @@ +use log::info; + use util::raw_message::RawMessage; -use util::messages::{NoteOn, Timbre, Pressure, PitchBend}; +use util::messages::{NoteOn, Timbre, Pressure, PitchBend, AfterTouch}; pub struct ExpressiveNote { @@ -11,26 +13,43 @@ pub struct ExpressiveNote { pub pitchbend: i32, } + impl ExpressiveNote { + #[cfg(not(use_channel_pressure))] + #[inline] + fn get_pressure_note(&self) -> RawMessage { + AfterTouch { + channel: self.channel, + pitch: self.pitch, + value: self.pressure, + }.into() + } + + #[cfg(use_channel_pressure)] + #[inline] + fn get_pressure_note(&self) -> RawMessage { + Pressure { + channel: self.channel, + value: self.pressure, + }.into() + } + pub fn into_rawmessages(self) -> Vec { vec![ - Timbre { + NoteOn { channel: self.channel, - value: self.timbre, + pitch: self.pitch, + velocity: self.velocity, // todo mixing between pattern and note }.into(), - Pressure { + self.get_pressure_note(), + Timbre { channel: self.channel, - value: self.pressure, + value: self.timbre, }.into(), PitchBend { channel: self.channel, millisemitones: self.pitchbend, }.into(), - NoteOn { - channel: self.channel, - pitch: self.pitch, - velocity: self.velocity, // todo mixing between pattern and note - }.into(), ] } } diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index a7339d5..d61ac03 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -7,7 +7,7 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use device_out::DeviceOut; use util::logging::logging_setup; -use util::messages::{PitchBend, Pressure, Timbre}; +use util::messages::{PitchBend, Pressure, Timbre, AfterTouch}; use util::raw_message::RawMessage; use crate::change::SourceChange; @@ -20,6 +20,7 @@ use crate::parameters::ArpegiatorParameters; use crate::socket::{SocketChannels, SocketCommand, create_socket_thread}; use std::thread::JoinHandle; use std::mem::take; +use crate::note::Note; pub mod pattern; mod note; @@ -230,20 +231,42 @@ impl Plugin for ArpegiatorPlugin { } PatternDeviceChange::PatternExpressionChange { expression, pattern, .. } => { - let raw_message: RawMessage = match expression { + let raw_message: Option = match expression { Expression::Timbre => { - Timbre { channel: pattern.channel, value: pattern.timbre }.into() - } - Expression::Pressure => { - Pressure { channel: pattern.channel, value: pattern.pressure }.into() + Some(Timbre { channel: pattern.channel, value: pattern.timbre }.into()) } Expression::PitchBend => { - PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into() + Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend } + .into()) + } + Expression::Pressure | Expression::AfterTouch => { + #[cfg(use_channel_pressure)] + { + Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) + } + + #[cfg(not(use_channel_pressure))] + match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { + None => None, + Some(note) => { + if let Some(pitch) = pattern.transpose(note.pitch) { + Some(AfterTouch { + channel: pattern.channel, + pitch, + value: pattern.pressure + }.into()) + } else { + None + } + } + } } }; - self.device_out.update(MidiMessageWithDelta { delta_frames, data: raw_message.into() }, - current_time, None); + if let Some(raw_message) = raw_message { + self.device_out.update(MidiMessageWithDelta { delta_frames, data: raw_message.into() }, + current_time, None); + } } PatternDeviceChange::RemovePattern { pattern, .. } => { self.device_out.push_note_off(pattern.id, pattern.velocity_off, diff --git a/util/src/constants.rs b/util/src/constants.rs index 4160606..11f72b5 100644 --- a/util/src/constants.rs +++ b/util/src/constants.rs @@ -1,11 +1,11 @@ -pub const PRESSURE: u8 = 0xD0; -pub const PITCHBEND: u8 = 0xE0; pub const ZEROVALUE: u8 = 0x40; -pub const CC: u8 = 0xB0; pub const TIMBRECC: u8 = 0x4A; // 74 pub const NOTE_OFF: u8 = 0x80; pub const NOTE_ON: u8 = 0x90; -pub const AFTERTOUCH: u8 = 0x90; +pub const AFTERTOUCH: u8 = 0xA0; +pub const CC: u8 = 0xB0; +pub const PRESSURE: u8 = 0xD0; +pub const PITCHBEND: u8 = 0xE0; pub const C0: i8 = 0x18; pub static NOTE_NAMES: &[&str; 12] = &[ diff --git a/util/src/messages.rs b/util/src/messages.rs index be18c40..965f4d1 100644 --- a/util/src/messages.rs +++ b/util/src/messages.rs @@ -3,7 +3,7 @@ use vst::event::{Event, MidiEvent}; use super::constants::{NOTE_ON, NOTE_OFF, PRESSURE, PITCHBEND}; use super::raw_message::RawMessage; -use crate::constants::TIMBRECC; +use crate::constants::{TIMBRECC, AFTERTOUCH}; pub fn format_midi_event(e: &MidiEvent) -> String { format!( @@ -201,6 +201,36 @@ impl From for PitchBend { } } + +#[derive(Debug)] +pub struct AfterTouch { + pub channel: u8, + pub pitch: u8, + pub value: u8 +} + +impl ChannelMessage for AfterTouch { + fn get_channel(&self) -> u8 { + self.channel + } +} + +impl Into for AfterTouch { + fn into(self) -> RawMessage { + [self.channel + AFTERTOUCH, self.pitch, self.value].into() + } +} + +impl From for AfterTouch { + fn from(data: RawMessage) -> Self { + AfterTouch { + channel: data[0] & 0x0F, + pitch: data[1], + value: data[2] + } + } +} + pub struct CC { pub channel: u8, pub cc: u8, @@ -209,7 +239,7 @@ pub struct CC { impl Into for CC { fn into(self) -> RawMessage { - [self.channel, self.cc, self.value].into() + [0xB0 + self.channel, self.cc, self.value].into() } } diff --git a/util/src/midi_message_type.rs b/util/src/midi_message_type.rs index ccce44e..129b8bb 100644 --- a/util/src/midi_message_type.rs +++ b/util/src/midi_message_type.rs @@ -2,6 +2,7 @@ use core::convert::From; use super::raw_message::RawMessage; use super::absolute_time_midi_message::AbsoluteTimeMidiMessage; use super::messages::{NoteOn, NoteOff, CC, Pressure, PitchBend, GenericChannelMessage}; +use crate::messages::AfterTouch; pub enum MidiMessageType { @@ -10,6 +11,7 @@ pub enum MidiMessageType { CCMessage(CC), PressureMessage(Pressure), PitchBendMessage(PitchBend), + AfterTouchMessage(AfterTouch), UnsupportedChannelMessage(GenericChannelMessage), Unsupported } diff --git a/util/src/raw_message.rs b/util/src/raw_message.rs index 4f2a9ca..0c979d9 100644 --- a/util/src/raw_message.rs +++ b/util/src/raw_message.rs @@ -3,7 +3,7 @@ use core::convert::{From, Into}; use core::ops::Index; use super::messages::ChannelMessage; -#[derive(Copy)] +#[derive(Copy, Debug)] pub struct RawMessage([u8; 3]); impl ChannelMessage for RawMessage { From 4f41a8d02fc31111da14f9ff73f9002bcfee9768 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 2 Jan 2021 17:23:49 +0100 Subject: [PATCH 03/47] silent clippy warnings --- arpegiator/src/device_out.rs | 2 ++ arpegiator/src/expressive_note.rs | 6 +++++- arpegiator/src/lib.rs | 7 +++++-- arpegiator_pattern_receiver/src/lib.rs | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/device_out.rs index d2ac21d..97d9767 100644 --- a/arpegiator/src/device_out.rs +++ b/arpegiator/src/device_out.rs @@ -1,4 +1,6 @@ +#[allow(unused_imports)] use log::{error, info}; + use vst::buffer::SendEventBuffer; use vst::plugin::HostCallback; diff --git a/arpegiator/src/expressive_note.rs b/arpegiator/src/expressive_note.rs index f9d5eca..d391d39 100644 --- a/arpegiator/src/expressive_note.rs +++ b/arpegiator/src/expressive_note.rs @@ -1,7 +1,11 @@ +#[allow(unused_imports)] use log::info; use util::raw_message::RawMessage; -use util::messages::{NoteOn, Timbre, Pressure, PitchBend, AfterTouch}; +use util::messages::{NoteOn, Timbre, PitchBend, AfterTouch}; + +#[cfg(use_channel_pressure)] +use util::messages::Pressure; pub struct ExpressiveNote { diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index d61ac03..ca4b8a2 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -7,7 +7,11 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use device_out::DeviceOut; use util::logging::logging_setup; -use util::messages::{PitchBend, Pressure, Timbre, AfterTouch}; +use util::messages::{PitchBend, Timbre, AfterTouch}; + +#[cfg(use_channel_pressure)] +use util::messages::Pressure; + use util::raw_message::RawMessage; use crate::change::SourceChange; @@ -20,7 +24,6 @@ use crate::parameters::ArpegiatorParameters; use crate::socket::{SocketChannels, SocketCommand, create_socket_thread}; use std::thread::JoinHandle; use std::mem::take; -use crate::note::Note; pub mod pattern; mod note; diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index fe36def..7dd2112 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -25,6 +25,7 @@ plugin_main!(ArpegiatorPatternReceiver); struct ArpegiatorPatternReceiver { + #[allow(dead_code)] host: HostCallback, socket_thread_handle: Option>, socket_channel_sender: Option>, From 6b440094aef5aee532bda8a453a50b9938e4f84d Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 2 Jan 2021 22:51:30 +0100 Subject: [PATCH 04/47] choose b/w channel pressure and aftertouch --- arpegiator/Cargo.toml | 5 +++++ arpegiator/src/expressive_note.rs | 12 ++++++------ arpegiator/src/lib.rs | 31 +++++++++++++++---------------- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index c3904cc..7ef1051 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -21,3 +21,8 @@ build-info-build = "0.0.20" [lib] name = "arpegiator" crate-type = ["cdylib", "lib"] + +[features] +#default = [] +default = ["use_channel_pressure"] +use_channel_pressure = [] diff --git a/arpegiator/src/expressive_note.rs b/arpegiator/src/expressive_note.rs index d391d39..5eb5857 100644 --- a/arpegiator/src/expressive_note.rs +++ b/arpegiator/src/expressive_note.rs @@ -40,19 +40,19 @@ impl ExpressiveNote { pub fn into_rawmessages(self) -> Vec { vec![ - NoteOn { + PitchBend { channel: self.channel, - pitch: self.pitch, - velocity: self.velocity, // todo mixing between pattern and note + millisemitones: self.pitchbend, }.into(), - self.get_pressure_note(), Timbre { channel: self.channel, value: self.timbre, }.into(), - PitchBend { + self.get_pressure_note(), + NoteOn { channel: self.channel, - millisemitones: self.pitchbend, + pitch: self.pitch, + velocity: self.velocity, // todo mixing between pattern and note }.into(), ] } diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index ca4b8a2..fb54281 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -55,7 +55,7 @@ pub struct ArpegiatorPlugin { device_out: DeviceOut, parameters: Arc, socket_channels: Option, - thread_handle: Option> + thread_handle: Option>, } @@ -89,7 +89,7 @@ impl Default for ArpegiatorPlugin { device_out: DeviceOut::default(), parameters: Arc::new(ArpegiatorParameters::new()), socket_channels: None, - thread_handle: None + thread_handle: None, } } } @@ -118,8 +118,10 @@ impl Plugin for ArpegiatorPlugin { fn new(host: HostCallback) -> Self { logging_setup(); - info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, - $.compiler, $.timestamp)); + info!("{} use_channel_pressure: {}", + build_info::format!("{{{} v{} built with {} at {}}} ", $.crate_info.name, $.crate_info.version, $ + .compiler, $.timestamp), if cfg!(feature = "use_channel_pressure") { true } else { false }); + ArpegiatorPlugin { events: vec![], send_buffer: Default::default(), @@ -131,17 +133,17 @@ impl Plugin for ArpegiatorPlugin { device_out: DeviceOut::default(), parameters: Arc::new(ArpegiatorParameters::new()), socket_channels: None, - thread_handle: None + thread_handle: None, } } fn resume(&mut self) { self.close_socket(); - self.current_time = 0 ; + self.current_time = 0; let (join_handle, socket_channels) = create_socket_thread(); - self.thread_handle = Some(join_handle) ; + self.thread_handle = Some(join_handle); socket_channels.command_sender.try_send(SocketCommand::SetPort(self.parameters.get_port())).unwrap(); @@ -165,12 +167,12 @@ impl Plugin for ArpegiatorPlugin { use vst::plugin::CanDo::*; match can_do { - SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline => Yes, + SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline | MidiSingleNoteTuningChange | MidiKeyBasedInstrumentControl => Yes, Other(s) => { if s == "MPE" { Yes } else { - + info!("Cando : {}", s); Maybe } } @@ -239,24 +241,21 @@ impl Plugin for ArpegiatorPlugin { Some(Timbre { channel: pattern.channel, value: pattern.timbre }.into()) } Expression::PitchBend => { - Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend } - .into()) + Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into()) } Expression::Pressure | Expression::AfterTouch => { - #[cfg(use_channel_pressure)] - { + #[cfg(use_channel_pressure)] { Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) } - #[cfg(not(use_channel_pressure))] - match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { + #[cfg(not(use_channel_pressure))] match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { None => None, Some(note) => { if let Some(pitch) = pattern.transpose(note.pitch) { Some(AfterTouch { channel: pattern.channel, pitch, - value: pattern.pressure + value: pattern.pressure, }.into()) } else { None From 0d2adf54d53ab6a7722ae6db0cc3082554567bed Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 3 Jan 2021 17:46:34 +0100 Subject: [PATCH 05/47] add controller, redo workers --- Cargo.lock | 186 ++++++++++++++++ arpegiator/Cargo.toml | 2 + arpegiator/src/device_out.rs | 24 ++- arpegiator/src/lib.rs | 49 ++--- arpegiator/src/midi_controller_worker.rs | 53 +++++ arpegiator/src/parameters.rs | 10 +- arpegiator/src/socket.rs | 117 ---------- arpegiator/src/worker.rs | 200 ++++++++++++++++++ arpegiator_pattern_receiver/src/lib.rs | 2 +- arpegiator_pattern_receiver/src/parameters.rs | 3 +- arpegiator_pattern_receiver/src/socket.rs | 2 +- 11 files changed, 489 insertions(+), 159 deletions(-) create mode 100644 arpegiator/src/midi_controller_worker.rs delete mode 100644 arpegiator/src/socket.rs create mode 100644 arpegiator/src/worker.rs diff --git a/Cargo.lock b/Cargo.lock index 9a2d894..99a32a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,6 +15,28 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "alsa" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb213f6b3e4b1480a60931ca2035794aa67b73103d254715b1db7b70dcb3c934" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -35,11 +57,13 @@ name = "arpegiator" version = "0.1.0" dependencies = [ "async-net", + "async-task", "bincode", "build-info", "build-info-build", "itertools", "log", + "midir", "num-traits", "smol", "util", @@ -300,6 +324,12 @@ dependencies = [ "xz2", ] +[[package]] +name = "bumpalo" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" + [[package]] name = "byteorder" version = "1.3.4" @@ -376,6 +406,46 @@ dependencies = [ "cache-padded", ] +[[package]] +name = "core-foundation" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" +dependencies = [ + "libc", +] + +[[package]] +name = "coremidi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99f92de5534f182bad5f91cad85611ab222cb6e237a0555e06c65a24936c3173" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "coremidi-sys", + "time", +] + +[[package]] +name = "coremidi-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f05827cebb30dcd539ff1ac9bf6764f574a15fa147f8572f99d7617142f95e" +dependencies = [ + "core-foundation-sys", +] + [[package]] name = "ctor" version = "0.1.16" @@ -554,6 +624,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -655,6 +734,12 @@ dependencies = [ "vst", ] +[[package]] +name = "memalloc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" + [[package]] name = "memchr" version = "2.3.4" @@ -669,6 +754,24 @@ dependencies = [ "vst", ] +[[package]] +name = "midir" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25df1e7836ca65078a121efed77049084a434838562b9892d0401bdfb59244cc" +dependencies = [ + "alsa", + "bitflags", + "coremidi", + "js-sys", + "libc", + "memalloc", + "nix", + "wasm-bindgen", + "web-sys", + "winapi", +] + [[package]] name = "miniz_oxide" version = "0.4.3" @@ -689,6 +792,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "nix" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" +dependencies = [ + "bitflags", + "cc", + "cfg-if 0.1.10", + "libc", + "void", +] + [[package]] name = "note_fan_out" version = "0.1.0" @@ -1145,6 +1261,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "vst" version = "0.2.1" @@ -1169,6 +1291,70 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasm-bindgen" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" + +[[package]] +name = "web-sys" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wepoll-sys" version = "3.0.1" diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 7ef1051..d3b0527 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -14,6 +14,8 @@ itertools = "0.10.0" smol = "1.2.5" async-net = "1.5.0" bincode = "1.3.1" +midir = "0.7.0" +async-task = "4.0.3" [build-dependencies] build-info-build = "0.0.20" diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/device_out.rs index 97d9767..534070f 100644 --- a/arpegiator/src/device_out.rs +++ b/arpegiator/src/device_out.rs @@ -1,9 +1,6 @@ #[allow(unused_imports)] use log::{error, info}; -use vst::buffer::SendEventBuffer; -use vst::plugin::HostCallback; - use util::messages::NoteOff; use util::raw_message::RawMessage; @@ -12,6 +9,8 @@ use crate::expressive_note::ExpressiveNote; use crate::note::Note; use crate::pattern::Pattern; use util::midi_message_with_delta::MidiMessageWithDelta; +use smol::channel::Sender; +use crate::midi_controller_worker::ControllerCommand; #[derive(Default)] @@ -27,23 +26,28 @@ impl DeviceOut { self.device.update(midi_message, current_time, id); } - pub fn flush_to(&mut self, send_buffer: &mut SendEventBuffer, host: &mut HostCallback) { - send_buffer.send_events(self.queue.drain(..).map(|message| message.new_midi_event()), host); + pub fn flush_to(&mut self, midi_controller_sender: &Sender) { + for message in self.queue.drain(..) { + if let Err(err) = midi_controller_sender.try_send( + ControllerCommand::RawMessage(RawMessage::from(message.data))) { + error!("Could not send to the controller worker {}", err) + } + } } pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { let note = match self.device.notes.values().find(|note| note.id == note_id) { None => { // info!("Cannot find note to stop: {:02x?}", note_id); - return + return; } Some(note) => note }; - let raw_message : RawMessage = NoteOff { + let raw_message: RawMessage = NoteOff { channel: note.channel, pitch: note.pitch, - velocity: velocity_off + velocity: velocity_off, }.into(); self.update(MidiMessageWithDelta { @@ -54,7 +58,7 @@ impl DeviceOut { pub fn push_note_on(&mut self, pattern: &Pattern, note: &Note, current_time: usize) { let pitch = match pattern.transpose(note.pitch) { - None => { return } + None => { return; } Some(pitch) => pitch }; @@ -72,7 +76,7 @@ impl DeviceOut { data: raw_message.into(), }, current_time, - Some(pattern.id) + Some(pattern.id), ); } } diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index fb54281..96f285a 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -1,7 +1,7 @@ use itertools::Itertools; use log::{error, info}; use vst::api; -use vst::buffer::{AudioBuffer, SendEventBuffer}; +use vst::buffer::AudioBuffer; use vst::event::{Event, MidiEvent}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; @@ -21,7 +21,7 @@ use crate::timed_event::TimedEvent; use util::midi_message_with_delta::MidiMessageWithDelta; use std::sync::Arc; use crate::parameters::ArpegiatorParameters; -use crate::socket::{SocketChannels, SocketCommand, create_socket_thread}; +use crate::worker::{WorkerChannels, WorkerCommand, create_worker_thread}; use std::thread::JoinHandle; use std::mem::take; @@ -34,7 +34,8 @@ mod change; mod expressive_note; mod device_out; mod parameters; -mod socket; +mod worker; +mod midi_controller_worker; #[macro_use] @@ -46,23 +47,22 @@ plugin_main!(ArpegiatorPlugin); pub struct ArpegiatorPlugin { events: Vec, - send_buffer: SendEventBuffer, - host: HostCallback, + _host: HostCallback, pattern_device_in: Device, notes_device_in: Device, pattern_device: PatternDevice, current_time: usize, device_out: DeviceOut, parameters: Arc, - socket_channels: Option, + worker_channels: Option, thread_handle: Option>, } impl ArpegiatorPlugin { fn close_socket(&mut self) { - if let Some(socket_channels) = self.socket_channels.as_ref() { - if let Err(e) = socket_channels.command_sender.try_send(SocketCommand::Stop) { + if let Some(worker_channels) = self.worker_channels.as_ref() { + if let Err(e) = worker_channels.command_sender.try_send(WorkerCommand::Stop) { error!("Error while closing note receiver channel : {:?}", e) } } @@ -71,7 +71,7 @@ impl ArpegiatorPlugin { thread_handle.join().unwrap(); } - self.socket_channels = None; // so the channel is not dropped before the thread is joined + self.worker_channels = None; // so the channel is not dropped before the thread is joined } } @@ -80,15 +80,14 @@ impl Default for ArpegiatorPlugin { fn default() -> Self { ArpegiatorPlugin { events: vec![], - send_buffer: Default::default(), - host: Default::default(), + _host: Default::default(), pattern_device_in: Default::default(), notes_device_in: Default::default(), pattern_device: PatternDevice::default(), current_time: 0, device_out: DeviceOut::default(), parameters: Arc::new(ArpegiatorParameters::new()), - socket_channels: None, + worker_channels: None, thread_handle: None, } } @@ -120,19 +119,18 @@ impl Plugin for ArpegiatorPlugin { logging_setup(); info!("{} use_channel_pressure: {}", build_info::format!("{{{} v{} built with {} at {}}} ", $.crate_info.name, $.crate_info.version, $ - .compiler, $.timestamp), if cfg!(feature = "use_channel_pressure") { true } else { false }); + .compiler, $.timestamp), cfg!(feature = "use_channel_pressure")); ArpegiatorPlugin { events: vec![], - send_buffer: Default::default(), - host, + _host: host, pattern_device_in: Default::default(), notes_device_in: Default::default(), pattern_device: Default::default(), current_time: 0, device_out: DeviceOut::default(), parameters: Arc::new(ArpegiatorParameters::new()), - socket_channels: None, + worker_channels: None, thread_handle: None, } } @@ -142,16 +140,15 @@ impl Plugin for ArpegiatorPlugin { self.current_time = 0; - let (join_handle, socket_channels) = create_socket_thread(); - self.thread_handle = Some(join_handle); + let worker_channels = create_worker_thread(); - socket_channels.command_sender.try_send(SocketCommand::SetPort(self.parameters.get_port())).unwrap(); + worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port())).unwrap(); - if let Ok(mut socket_command) = self.parameters.socket_command.lock() { - *socket_command = Some(socket_channels.command_sender.clone()); + if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { + *worker_commands = Some(worker_channels.command_sender.clone()); } - self.socket_channels = Some(socket_channels); + self.worker_channels = Some(worker_channels); } fn suspend(&mut self) { @@ -181,7 +178,7 @@ impl Plugin for ArpegiatorPlugin { } fn process(&mut self, buffer: &mut AudioBuffer) { - let messages = match self.socket_channels.as_ref() { + let messages = match self.worker_channels.as_ref() { None => vec![], Some(socket_channels) => { match socket_channels.notes_receiver.try_recv() { @@ -290,7 +287,11 @@ impl Plugin for ArpegiatorPlugin { } } - self.device_out.flush_to(&mut self.send_buffer, &mut self.host) + if let Some(worker_channels) = self.worker_channels.as_ref() { + self.device_out.flush_to(&worker_channels.midi_controller_sender) + } + + } self.events.clear(); diff --git a/arpegiator/src/midi_controller_worker.rs b/arpegiator/src/midi_controller_worker.rs new file mode 100644 index 0000000..dfdb86c --- /dev/null +++ b/arpegiator/src/midi_controller_worker.rs @@ -0,0 +1,53 @@ +#[allow(unused_imports)] +use log::{error, info}; + +use util::raw_message::RawMessage; +use midir::MidiOutput; +use midir::os::unix::VirtualOutput; +use util::constants::PRESSURE; +use smol::channel::Receiver; + +pub enum ControllerCommand { + RawMessage(RawMessage), + Stop, +} + +pub async fn midi_controller_worker(name: String, control_channel: Receiver) { + let midi_out = match MidiOutput::new(&*name) { + Ok(midi_out) => midi_out, + Err(err) => { + error!("Could not create midi port {}", err); + return + } + }; + let mut midi_connection = match midi_out.create_virtual(&*name) { + Ok(midi_connection) => midi_connection, + Err(err) => { + error!("Could not get a midi connection {}", err); + return + } + }; + + loop { + match control_channel.recv().await { + Ok(command) => { + match command { + ControllerCommand::RawMessage(raw_message) => { + let message = Into::<[u8; 3]>::into(raw_message); + // ugly hack originally with the intent of moving around a fixed amount of u8 + let len = if message[0] & 0xF0 == PRESSURE { 2 } else { 3 }; + if let Err(err) = midi_connection.send(&message[..len]) { + error!("Error while sending midi message: {}", err); + return + } + } + ControllerCommand::Stop => { return } + } + } + Err(err) => { + error!("Error while fetching a command from the channel: {}", err); + return + } + } + } +} diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 48bb90b..64847ad 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -3,7 +3,7 @@ use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; use util::parameter_value_conversion::f32_to_byte; -use crate::socket::SocketCommand; +use crate::worker::WorkerCommand; use std::sync::Mutex; use smol::channel::Sender; @@ -12,7 +12,7 @@ const BASE_PORT: u16 = 6000; pub struct ArpegiatorParameters { pub transfer: ParameterTransfer, - pub socket_command: Mutex>> + pub worker_commands: Mutex>> } impl ArpegiatorParameters { @@ -22,8 +22,8 @@ impl ArpegiatorParameters { pub fn update_port(&self) { let port = self.get_byte_parameter(Parameter::PortIndex); - self.socket_command.lock().unwrap().as_ref().unwrap().try_send( - SocketCommand::SetPort(BASE_PORT + port as u16) + self.worker_commands.lock().unwrap().as_ref().unwrap().try_send( + WorkerCommand::SetPort(BASE_PORT + port as u16) ).unwrap(); } } @@ -65,7 +65,7 @@ impl ArpegiatorParameters { pub fn new() -> Self { ArpegiatorParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), - socket_command: Mutex::new(None) + worker_commands: Mutex::new(None) } } } diff --git a/arpegiator/src/socket.rs b/arpegiator/src/socket.rs deleted file mode 100644 index f15d281..0000000 --- a/arpegiator/src/socket.rs +++ /dev/null @@ -1,117 +0,0 @@ -use log::{error, info}; -use util::pattern_payload::PatternPayload; -use std::thread::JoinHandle; -use std::thread; -use smol::channel::{unbounded, Sender, Receiver}; -use smol::future::{race, pending}; -use async_net::UdpSocket; - -pub enum SocketCommand { - Stop, - SetPort(u16) -} - -pub struct SocketChannels { - pub command_sender: Sender, - pub notes_receiver: Receiver -} - -enum FutureResult { - Command(SocketCommand), - Payload(PatternPayload), - PayloadError, - ChannelError, - SocketError -} - -pub fn create_socket_thread() -> (JoinHandle<()>, SocketChannels) { - let (command_sender, command_receiver) = unbounded::(); - let (notes_sender, notes_receiver) = unbounded::(); - - let handle = thread::spawn(move || { - let mut socket : Option = None; - let mut buf = vec![0u8; 1024]; - - smol::block_on( async { - loop { - let channel_future = async { - match command_receiver.recv().await { - Ok(command) => { - FutureResult::Command(command) - } - Err(err) => { - error!("Error on command channel while receiving {:?} {}", err, err); - FutureResult::ChannelError - } - } - }; - - let socket_future = async { - match socket.as_ref() { - None => pending().await, - Some(socket) => { - match socket.recv(&mut buf).await { - Ok(len) => { - match bincode::deserialize::(&buf[..len]) { - Ok(payload) => { - FutureResult::Payload(payload) - } - Err(err) => { - error!("Could not deserialize: {:?}", err); - FutureResult::PayloadError - } - } - } - Err(err) => { - error!("Socket error: {:?}", err); - FutureResult::SocketError - } - } - } - } - }; - - match race(channel_future, socket_future).await { - FutureResult::Command(command) => { - match command { - SocketCommand::Stop => { - return - } - SocketCommand::SetPort(port) => { - socket = None; - - match UdpSocket::bind(format!("127.0.0.1:{}", port)).await { - Ok(le_socket) => { - info!("Listening on port {}", port); - socket = Some(le_socket) - } - Err(err) => { - error!("Cannot bind on port {} : {:?}", port, err) - } - } - } - } - } - FutureResult::Payload(payload) => { - notes_sender.send(payload).await.unwrap(); - } - FutureResult::ChannelError => { - // unrecoverable - return - } - FutureResult::SocketError => { - socket = None - } - FutureResult::PayloadError => { - // noop, just loop over - } - } - } - }); - }); - - (handle, SocketChannels { - command_sender, - notes_receiver - }) -} diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs new file mode 100644 index 0000000..139e461 --- /dev/null +++ b/arpegiator/src/worker.rs @@ -0,0 +1,200 @@ +use log::{error, info}; +use util::pattern_payload::PatternPayload; +use std::thread::JoinHandle; +use std::thread; +use smol::channel::{unbounded, Sender, Receiver, RecvError}; +use smol::net::UdpSocket; +use crate::midi_controller_worker::{midi_controller_worker, ControllerCommand}; +use smol::io::Error; +use smol::Task; + + +pub enum WorkerCommand { + Stop, + SetPort(u16), +} + +pub struct WorkerChannels { + pub command_sender: Sender, + pub midi_controller_sender: Sender, + pub notes_receiver: Receiver, + pub worker: JoinHandle<()>, +} + +enum WorkerResult { + Command(WorkerCommand), + PayloadError(bincode::Error), + ChannelError(RecvError), + SocketError(Error), + MidiControllerStopped, +} + +async fn spawn_socket_worker(port: u16, notes_sender: Sender) -> WorkerResult { + let mut buf = vec![0u8; 1024]; + + let socket = match UdpSocket::bind(format!("127.0.0.1:{}", port)).await { + Ok(socket) => { + info!("Listening on port {}", port); + socket + } + Err(err) => { + error!("Cannot bind on port {} : {:?}", port, err); + return WorkerResult::SocketError(err); + } + }; + + loop { + match socket.recv(&mut buf).await { + Ok(len) => { + match bincode::deserialize::(&buf[..len]) { + Ok(payload) => { + notes_sender.send(payload).await.unwrap(); + } + Err(err) => { + error!("Could not deserialize: {:?}", err); + return WorkerResult::PayloadError(err); + } + } + } + Err(err) => { + error!("Socket error: {:?}", err); + return WorkerResult::SocketError(err); + } + } + } +} + + +async fn spawn_controller_worker(name: String, control_channel: Receiver) -> WorkerResult { + midi_controller_worker(name, control_channel).await; + WorkerResult::MidiControllerStopped +} + +async fn command_reader(command_receiver: Receiver) -> WorkerResult { + match command_receiver.recv().await { + Ok(command) => WorkerResult::Command(command), + Err(err) => WorkerResult::ChannelError(err) + } +} + + +macro_rules! schedule { + ($future:expr, $task_queue:expr, $result_queue:expr) => {{ + let result_queue = $result_queue.clone(); + let task_queue = $task_queue.clone(); + let future = async move { + result_queue.send($future.await).await.unwrap(); + }; + let (runnable, task) = async_task::spawn_local(future, + move |runnable| task_queue.try_send(runnable).unwrap() + ); + runnable.schedule(); + task + }} +} + +pub fn create_worker_thread() -> WorkerChannels { + let (command_sender, command_receiver) = unbounded::(); + let (notes_sender, notes_receiver) = unbounded::(); + let (midi_controller_sender, midi_controller_receiver) = unbounded::(); + let (worker_result_sender, worker_result_receiver) = unbounded(); + let (worker_task_sender, worker_task_receiver) = unbounded(); + + { + let command_receiver = command_receiver.clone(); + let task = schedule!(command_reader(command_receiver), worker_task_sender, worker_result_sender); + task.detach(); + } + + let main = { + let midi_controller_sender = midi_controller_sender.clone(); + + async move { + let mut socket_worker_task: Option> = None; + let mut midi_controller_task: Option> = None; + + loop { + let runnable = worker_task_receiver.try_recv().unwrap(); + runnable.run(); + + let worker_result = worker_result_receiver.recv().await.unwrap(); + + match worker_result { + WorkerResult::Command(command) => { + match command { + WorkerCommand::Stop => { + return; + } + WorkerCommand::SetPort(port) => { + if let Some(socket_worker_task) = socket_worker_task { + socket_worker_task.cancel().await; + } + socket_worker_task = { + let notes_sender = notes_sender.clone(); + Some(schedule!(spawn_socket_worker(port, notes_sender), worker_task_sender, + worker_result_sender)) + }; + + if let Some(midi_controller_task) = midi_controller_task { + midi_controller_sender.send(ControllerCommand::Stop).await.unwrap(); + midi_controller_task.cancel().await; + } + + midi_controller_task = { + let midi_controller_receiver = midi_controller_receiver.clone(); + Some( + schedule!( + spawn_controller_worker(format!("Arpegiator {}", port), midi_controller_receiver), + worker_task_sender, worker_result_sender) + ) + }; + + { + let command_receiver = command_receiver.clone(); + let task = schedule!(command_reader(command_receiver), worker_task_sender, worker_result_sender); + task.detach(); + } + } + } + } + WorkerResult::PayloadError(err) => { + error!("Invalid payload received. Data received from wrong service ? ( {} )", err); + socket_worker_task = None + } + WorkerResult::SocketError(_) => { + // don't respawn, wait until user chooses another port + socket_worker_task = None + } + WorkerResult::ChannelError(err) => { + error!("Command channel error, quitting worker ({})", err); + + if let Some(socket_worker_task) = socket_worker_task { + socket_worker_task.cancel().await; + } + + if let Some(midi_controller_task) = midi_controller_task { + midi_controller_sender.send(ControllerCommand::Stop).await.unwrap(); + midi_controller_task.cancel().await; + } + return; + } + + WorkerResult::MidiControllerStopped => { + // controller worker quit. Incompatibility or resource already taken, wait until the user + // chooses another port + midi_controller_task = None + } + } + } + } + }; + + let handle = thread::spawn(move || smol::block_on(main)); + + WorkerChannels { + command_sender, + notes_receiver, + midi_controller_sender, + worker: handle, + } +} diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index 7dd2112..f3342cd 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -1,4 +1,3 @@ -use std::sync::mpsc::Sender; use std::thread::JoinHandle; use log::{info, error}; @@ -14,6 +13,7 @@ use std::mem::take; use crate::parameters::ArpegiatorPatternReceiverParameters; use std::sync::Arc; use util::pattern_payload::PatternPayload; +use std::sync::mpsc::Sender; mod parameters; mod socket; diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs index 1d49a01..a9d1ded 100644 --- a/arpegiator_pattern_receiver/src/parameters.rs +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -3,9 +3,10 @@ use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; use util::parameter_value_conversion::f32_to_byte; -use std::sync::mpsc::Sender; use crate::socket::SenderSocketCommand; use std::sync::Mutex; +use std::sync::mpsc::Sender; + const PARAMETER_COUNT: usize = 1; const BASE_PORT: u16 = 6000; diff --git a/arpegiator_pattern_receiver/src/socket.rs b/arpegiator_pattern_receiver/src/socket.rs index 12f3c50..f43d16a 100644 --- a/arpegiator_pattern_receiver/src/socket.rs +++ b/arpegiator_pattern_receiver/src/socket.rs @@ -1,9 +1,9 @@ use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; -use std::sync::mpsc::{channel, Sender}; use std::thread; use std::thread::JoinHandle; use util::pattern_payload::PatternPayload; +use std::sync::mpsc::{channel, Sender}; pub enum SenderSocketCommand { Stop, From eb5d0b1ee065d36cd451f5175b29344f03489926 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 3 Jan 2021 21:11:28 +0100 Subject: [PATCH 06/47] debugging stuck worker --- arpegiator/src/device_out.rs | 5 +- arpegiator/src/lib.rs | 4 +- arpegiator/src/midi_controller_worker.rs | 7 +- arpegiator/src/parameters.rs | 12 +- arpegiator/src/worker.rs | 148 ++++++++++++++++++----- 5 files changed, 133 insertions(+), 43 deletions(-) diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/device_out.rs index 534070f..2fb1c31 100644 --- a/arpegiator/src/device_out.rs +++ b/arpegiator/src/device_out.rs @@ -11,6 +11,7 @@ use crate::pattern::Pattern; use util::midi_message_with_delta::MidiMessageWithDelta; use smol::channel::Sender; use crate::midi_controller_worker::ControllerCommand; +use crate::worker::WorkerCommand; #[derive(Default)] @@ -26,10 +27,10 @@ impl DeviceOut { self.device.update(midi_message, current_time, id); } - pub fn flush_to(&mut self, midi_controller_sender: &Sender) { + pub fn flush_to(&mut self, midi_controller_sender: &Sender) { for message in self.queue.drain(..) { if let Err(err) = midi_controller_sender.try_send( - ControllerCommand::RawMessage(RawMessage::from(message.data))) { + WorkerCommand::SendToController(ControllerCommand::RawMessage(RawMessage::from(message.data)))) { error!("Could not send to the controller worker {}", err) } } diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 96f285a..c324f48 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -288,10 +288,8 @@ impl Plugin for ArpegiatorPlugin { } if let Some(worker_channels) = self.worker_channels.as_ref() { - self.device_out.flush_to(&worker_channels.midi_controller_sender) + self.device_out.flush_to(&worker_channels.command_sender) } - - } self.events.clear(); diff --git a/arpegiator/src/midi_controller_worker.rs b/arpegiator/src/midi_controller_worker.rs index dfdb86c..36c10ab 100644 --- a/arpegiator/src/midi_controller_worker.rs +++ b/arpegiator/src/midi_controller_worker.rs @@ -7,12 +7,14 @@ use midir::os::unix::VirtualOutput; use util::constants::PRESSURE; use smol::channel::Receiver; +#[derive(Debug)] pub enum ControllerCommand { RawMessage(RawMessage), Stop, } pub async fn midi_controller_worker(name: String, control_channel: Receiver) { + info!("Creating midi device {}", name); let midi_out = match MidiOutput::new(&*name) { Ok(midi_out) => midi_out, Err(err) => { @@ -41,7 +43,10 @@ pub async fn midi_controller_worker(name: String, control_channel: Receiver { return } + ControllerCommand::Stop => { + midi_connection.close(); + return + } } } Err(err) => { diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 64847ad..94cc829 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -1,3 +1,6 @@ +#[allow(unused_imports)] +use log::{error, info}; + use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; @@ -12,7 +15,7 @@ const BASE_PORT: u16 = 6000; pub struct ArpegiatorParameters { pub transfer: ParameterTransfer, - pub worker_commands: Mutex>> + pub worker_commands: Mutex>>, } impl ArpegiatorParameters { @@ -21,9 +24,10 @@ impl ArpegiatorParameters { } pub fn update_port(&self) { - let port = self.get_byte_parameter(Parameter::PortIndex); + let port = self.get_byte_parameter(Parameter::PortIndex) as u16 + BASE_PORT; + info!("Applying parameter change: port={}", port); self.worker_commands.lock().unwrap().as_ref().unwrap().try_send( - WorkerCommand::SetPort(BASE_PORT + port as u16) + WorkerCommand::SetPort(port) ).unwrap(); } } @@ -65,7 +69,7 @@ impl ArpegiatorParameters { pub fn new() -> Self { ArpegiatorParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), - worker_commands: Mutex::new(None) + worker_commands: Mutex::new(None), } } } diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs index 139e461..c5da279 100644 --- a/arpegiator/src/worker.rs +++ b/arpegiator/src/worker.rs @@ -7,16 +7,18 @@ use smol::net::UdpSocket; use crate::midi_controller_worker::{midi_controller_worker, ControllerCommand}; use smol::io::Error; use smol::Task; +use smol::future::FutureExt; +#[derive(Debug)] pub enum WorkerCommand { Stop, SetPort(u16), + SendToController(ControllerCommand) } pub struct WorkerChannels { pub command_sender: Sender, - pub midi_controller_sender: Sender, pub notes_receiver: Receiver, pub worker: JoinHandle<()>, } @@ -26,10 +28,18 @@ enum WorkerResult { PayloadError(bincode::Error), ChannelError(RecvError), SocketError(Error), + SocketStopped, MidiControllerStopped, } -async fn spawn_socket_worker(port: u16, notes_sender: Sender) -> WorkerResult { +enum SocketResult { + Recv(usize), + Stop, + Error(Error) +} + +async fn spawn_socket_worker(port: u16, notes_sender: Sender, socket_stop_channel: Receiver) -> + WorkerResult { let mut buf = vec![0u8; 1024]; let socket = match UdpSocket::bind(format!("127.0.0.1:{}", port)).await { @@ -44,8 +54,22 @@ async fn spawn_socket_worker(port: u16, notes_sender: Sender) -> }; loop { - match socket.recv(&mut buf).await { - Ok(len) => { + let socket_receive = async { + match socket.recv(&mut buf).await { + Ok(len) => SocketResult::Recv(len), + Err(err) => SocketResult::Error(err) + } + }; + + let stop_receive = async { + match socket_stop_channel.recv().await { + Ok(_) => SocketResult::Stop, + Err(_) => SocketResult::Stop + } + }; + + match socket_receive.race(stop_receive).await { + SocketResult::Recv(len) => { match bincode::deserialize::(&buf[..len]) { Ok(payload) => { notes_sender.send(payload).await.unwrap(); @@ -56,9 +80,13 @@ async fn spawn_socket_worker(port: u16, notes_sender: Sender) -> } } } - Err(err) => { - error!("Socket error: {:?}", err); - return WorkerResult::SocketError(err); + SocketResult::Stop => { + info!("Quitting socket port {}", port); + return WorkerResult::SocketStopped + }, + SocketResult::Error(err) => { + info!("Error {} : quitting socket port {}", err, port); + return WorkerResult::SocketError(err) } } } @@ -70,10 +98,17 @@ async fn spawn_controller_worker(name: String, control_channel: Receiver) -> WorkerResult { match command_receiver.recv().await { - Ok(command) => WorkerResult::Command(command), - Err(err) => WorkerResult::ChannelError(err) + Ok(command) => { + info!("Received command {:?}", command); + WorkerResult::Command(command) + }, + Err(err) => { + error!("Error while reading command channel: {}", err); + WorkerResult::ChannelError(err) + } } } @@ -93,67 +128,115 @@ macro_rules! schedule { }} } + +struct MidiControllerChannels { + sender: Sender, + receiver: Receiver, +} + +struct SocketStopChannels { + sender: Sender, + receiver: Receiver, +} + + pub fn create_worker_thread() -> WorkerChannels { let (command_sender, command_receiver) = unbounded::(); - let (notes_sender, notes_receiver) = unbounded::(); - let (midi_controller_sender, midi_controller_receiver) = unbounded::(); let (worker_result_sender, worker_result_receiver) = unbounded(); let (worker_task_sender, worker_task_receiver) = unbounded(); - - { - let command_receiver = command_receiver.clone(); - let task = schedule!(command_reader(command_receiver), worker_task_sender, worker_result_sender); - task.detach(); - } + let (notes_sender, notes_receiver) = unbounded::(); let main = { - let midi_controller_sender = midi_controller_sender.clone(); - async move { + let (sender, receiver) = unbounded::(); + let mut midi_controller_channels = MidiControllerChannels { + sender, + receiver, + }; + + let (sender, receiver) = unbounded::(); + let mut socket_stop_channels = SocketStopChannels { + sender, + receiver + } ; + let mut socket_worker_task: Option> = None; let mut midi_controller_task: Option> = None; + let mut command_receiver_task : Option> = None; loop { + if command_receiver_task.is_none() { + info!("spawning command receiver"); + command_receiver_task = { + let command_receiver = command_receiver.clone(); + let worker_result_sender = worker_result_sender.clone(); + Some(schedule!(command_reader(command_receiver), worker_task_sender, worker_result_sender)) + }; + } + let runnable = worker_task_receiver.try_recv().unwrap(); + info!("Executing runnable {:?}", runnable); runnable.run(); let worker_result = worker_result_receiver.recv().await.unwrap(); match worker_result { WorkerResult::Command(command) => { + command_receiver_task = None; + match command { WorkerCommand::Stop => { + if let Some(socket_worker_task) = socket_worker_task { + socket_stop_channels.sender.send(true).await.unwrap(); + socket_worker_task.cancel().await; + } + + if let Some(midi_controller_task) = midi_controller_task { + midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); + midi_controller_task.cancel().await; + } return; } WorkerCommand::SetPort(port) => { + info!("Switching to port {}" , port); if let Some(socket_worker_task) = socket_worker_task { + socket_stop_channels.sender.send(true).await.unwrap(); socket_worker_task.cancel().await; } + + let (sender, receiver) = unbounded::(); + socket_stop_channels.sender = sender; + socket_stop_channels.receiver = receiver; + + info!("socket worker stopped"); socket_worker_task = { let notes_sender = notes_sender.clone(); - Some(schedule!(spawn_socket_worker(port, notes_sender), worker_task_sender, - worker_result_sender)) + let socket_stop_receiver = socket_stop_channels.receiver.clone(); + Some(schedule!(spawn_socket_worker(port, notes_sender, socket_stop_receiver), + worker_task_sender, worker_result_sender)) }; + info!("stopping controller worker"); if let Some(midi_controller_task) = midi_controller_task { - midi_controller_sender.send(ControllerCommand::Stop).await.unwrap(); + midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); midi_controller_task.cancel().await; } + info!("controller worker stopped"); + let (sender, receiver) = unbounded::(); + midi_controller_channels.sender = sender ; + midi_controller_channels.receiver = receiver; midi_controller_task = { - let midi_controller_receiver = midi_controller_receiver.clone(); + let midi_controller_receiver = midi_controller_channels.receiver.clone(); Some( schedule!( spawn_controller_worker(format!("Arpegiator {}", port), midi_controller_receiver), worker_task_sender, worker_result_sender) ) }; - - { - let command_receiver = command_receiver.clone(); - let task = schedule!(command_reader(command_receiver), worker_task_sender, worker_result_sender); - task.detach(); - } + } + WorkerCommand::SendToController(controller_command) => { + midi_controller_channels.sender.send(controller_command).await.unwrap(); } } } @@ -163,7 +246,6 @@ pub fn create_worker_thread() -> WorkerChannels { } WorkerResult::SocketError(_) => { // don't respawn, wait until user chooses another port - socket_worker_task = None } WorkerResult::ChannelError(err) => { error!("Command channel error, quitting worker ({})", err); @@ -173,7 +255,7 @@ pub fn create_worker_thread() -> WorkerChannels { } if let Some(midi_controller_task) = midi_controller_task { - midi_controller_sender.send(ControllerCommand::Stop).await.unwrap(); + midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); midi_controller_task.cancel().await; } return; @@ -182,7 +264,8 @@ pub fn create_worker_thread() -> WorkerChannels { WorkerResult::MidiControllerStopped => { // controller worker quit. Incompatibility or resource already taken, wait until the user // chooses another port - midi_controller_task = None + } + WorkerResult::SocketStopped => { } } } @@ -194,7 +277,6 @@ pub fn create_worker_thread() -> WorkerChannels { WorkerChannels { command_sender, notes_receiver, - midi_controller_sender, worker: handle, } } From 1e22187e71ae64064ac65c870c29e4b2478ce906 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 3 Jan 2021 21:22:15 +0100 Subject: [PATCH 07/47] ok --- arpegiator/src/worker.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs index c5da279..35ce687 100644 --- a/arpegiator/src/worker.rs +++ b/arpegiator/src/worker.rs @@ -23,6 +23,7 @@ pub struct WorkerChannels { pub worker: JoinHandle<()>, } +#[derive(Debug)] enum WorkerResult { Command(WorkerCommand), PayloadError(bincode::Error), @@ -179,6 +180,7 @@ pub fn create_worker_thread() -> WorkerChannels { runnable.run(); let worker_result = worker_result_receiver.recv().await.unwrap(); + info!("Runnable executed, got {:?}", worker_result); match worker_result { WorkerResult::Command(command) => { From f8a63ddb3d68d23a0750b43127397ccf9699a6ef Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Mon, 4 Jan 2021 09:54:59 +0100 Subject: [PATCH 08/47] switched to async-std --- Cargo.lock | 184 ++++++++++++++--------- arpegiator/Cargo.toml | 7 +- arpegiator/src/device_out.rs | 2 +- arpegiator/src/midi_controller_worker.rs | 4 +- arpegiator/src/parameters.rs | 3 +- arpegiator/src/worker.rs | 173 ++++++++------------- 6 files changed, 187 insertions(+), 186 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99a32a3..add2596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,16 +56,16 @@ checksum = "bf8dcb5b4bbaa28653b647d8c77bd4ed40183b48882e130c1f1ffb73de069fd7" name = "arpegiator" version = "0.1.0" dependencies = [ - "async-net", - "async-task", + "async-channel", + "async-std", "bincode", "build-info", "build-info-build", + "futures-lite", "itertools", "log", "midir", "num-traits", - "smol", "util", "vst", ] @@ -110,14 +110,16 @@ dependencies = [ ] [[package]] -name = "async-fs" -version = "1.5.0" +name = "async-global-executor" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3ca4f8ff117c37c278a2f7415ce9be55560b846b5bc4412aaa5d29c1c3dae2" +checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04" dependencies = [ - "async-lock", - "blocking", + "async-executor", + "async-io", "futures-lite", + "num_cpus", + "once_cell", ] [[package]] @@ -141,40 +143,40 @@ dependencies = [ ] [[package]] -name = "async-lock" -version = "2.3.0" +name = "async-mutex" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" dependencies = [ "event-listener", ] [[package]] -name = "async-net" -version = "1.5.0" +name = "async-std" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06de475c85affe184648202401d7622afb32f0f74e02192857d0201a16defbe5" -dependencies = [ - "async-io", - "blocking", - "fastrand", - "futures-lite", -] - -[[package]] -name = "async-process" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8cea09c1fb10a317d1b5af8024eeba256d6554763e85ecd90ff8df31c7bbda" +checksum = "8f9f84f1280a2b436a2c77c2582602732b6c2f4321d5494d6e799e6c367859a8" dependencies = [ + "async-channel", + "async-global-executor", "async-io", + "async-mutex", "blocking", - "cfg-if 0.1.10", - "event-listener", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", "once_cell", - "signal-hook", - "winapi", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", ] [[package]] @@ -446,6 +448,17 @@ dependencies = [ "core-foundation-sys", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" +dependencies = [ + "autocfg", + "cfg-if 1.0.0", + "lazy_static", +] + [[package]] name = "ctor" version = "0.1.16" @@ -518,6 +531,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7aea5a5909a74969507051a3b17adc84737e31a5f910559892aedce026f4d53" +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b7109687aa4e177ef6fe84553af6280ef2778bdb7783ba44c9dc3399110fe64" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.8" @@ -580,6 +602,28 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "gloo-timers" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hermit-abi" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" +dependencies = [ + "libc", +] + [[package]] name = "idna" version = "0.2.0" @@ -633,6 +677,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -862,6 +915,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.22.0" @@ -925,6 +988,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b063f57ec186e6140e2b8b6921e5f1bd89c7356dda5b33acc5401203ca6131c" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.19" @@ -1093,25 +1162,6 @@ dependencies = [ "serde", ] -[[package]] -name = "signal-hook" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" -dependencies = [ - "libc", -] - [[package]] name = "simplelog" version = "0.9.0" @@ -1124,28 +1174,16 @@ dependencies = [ ] [[package]] -name = "smallvec" -version = "1.5.1" +name = "slab" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] -name = "smol" -version = "1.2.5" +name = "smallvec" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cf3b5351f3e783c1d79ab5fc604eeed8b8ae9abd36b166e8b87a089efd85e4" -dependencies = [ - "async-channel", - "async-executor", - "async-fs", - "async-io", - "async-lock", - "async-net", - "async-process", - "blocking", - "futures-lite", - "once_cell", -] +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" [[package]] name = "syn" @@ -1316,6 +1354,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.69" diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index d3b0527..6f7a490 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -11,11 +11,11 @@ build-info = "0.0.20" log = "0.4.11" num-traits = "0.2.14" itertools = "0.10.0" -smol = "1.2.5" -async-net = "1.5.0" bincode = "1.3.1" midir = "0.7.0" -async-task = "4.0.3" +async-std = "1.8.0" +async-channel = "1.5.1" +futures-lite = "1.11.3" [build-dependencies] build-info-build = "0.0.20" @@ -28,3 +28,4 @@ crate-type = ["cdylib", "lib"] #default = [] default = ["use_channel_pressure"] use_channel_pressure = [] +worker_debug = [] diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/device_out.rs index 2fb1c31..6855b45 100644 --- a/arpegiator/src/device_out.rs +++ b/arpegiator/src/device_out.rs @@ -9,9 +9,9 @@ use crate::expressive_note::ExpressiveNote; use crate::note::Note; use crate::pattern::Pattern; use util::midi_message_with_delta::MidiMessageWithDelta; -use smol::channel::Sender; use crate::midi_controller_worker::ControllerCommand; use crate::worker::WorkerCommand; +use async_channel::Sender; #[derive(Default)] diff --git a/arpegiator/src/midi_controller_worker.rs b/arpegiator/src/midi_controller_worker.rs index 36c10ab..4cf91b8 100644 --- a/arpegiator/src/midi_controller_worker.rs +++ b/arpegiator/src/midi_controller_worker.rs @@ -5,7 +5,8 @@ use util::raw_message::RawMessage; use midir::MidiOutput; use midir::os::unix::VirtualOutput; use util::constants::PRESSURE; -use smol::channel::Receiver; +use async_channel::Receiver; + #[derive(Debug)] pub enum ControllerCommand { @@ -44,6 +45,7 @@ pub async fn midi_controller_worker(name: String, control_channel: Receiver { + info!("Stopping controller {}", name); midi_connection.close(); return } diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 94cc829..2a48be1 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -8,7 +8,8 @@ use util::parameters::ParameterConversion; use util::parameter_value_conversion::f32_to_byte; use crate::worker::WorkerCommand; use std::sync::Mutex; -use smol::channel::Sender; +use async_channel::Sender; + const PARAMETER_COUNT: usize = 1; const BASE_PORT: u16 = 6000; diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs index 35ce687..3e8ad53 100644 --- a/arpegiator/src/worker.rs +++ b/arpegiator/src/worker.rs @@ -2,12 +2,14 @@ use log::{error, info}; use util::pattern_payload::PatternPayload; use std::thread::JoinHandle; use std::thread; -use smol::channel::{unbounded, Sender, Receiver, RecvError}; -use smol::net::UdpSocket; +use async_channel::{unbounded, Sender, Receiver, RecvError}; +use async_std::net::UdpSocket; +use async_std::task; +use async_std::io::Error; +use futures_lite::FutureExt; + use crate::midi_controller_worker::{midi_controller_worker, ControllerCommand}; -use smol::io::Error; -use smol::Task; -use smol::future::FutureExt; + #[derive(Debug)] @@ -28,9 +30,7 @@ enum WorkerResult { Command(WorkerCommand), PayloadError(bincode::Error), ChannelError(RecvError), - SocketError(Error), - SocketStopped, - MidiControllerStopped, + SocketError(Error) } enum SocketResult { @@ -39,8 +39,11 @@ enum SocketResult { Error(Error) } -async fn spawn_socket_worker(port: u16, notes_sender: Sender, socket_stop_channel: Receiver) -> - WorkerResult { +async fn spawn_socket_worker(port: u16, + notes_sender: Sender, + socket_stop_channel: Receiver, + worker_result_sender: Sender +) { let mut buf = vec![0u8; 1024]; let socket = match UdpSocket::bind(format!("127.0.0.1:{}", port)).await { @@ -50,7 +53,8 @@ async fn spawn_socket_worker(port: u16, notes_sender: Sender, so } Err(err) => { error!("Cannot bind on port {} : {:?}", port, err); - return WorkerResult::SocketError(err); + worker_result_sender.send(WorkerResult::SocketError(err)).await.unwrap(); + return; } }; @@ -77,59 +81,42 @@ async fn spawn_socket_worker(port: u16, notes_sender: Sender, so } Err(err) => { error!("Could not deserialize: {:?}", err); - return WorkerResult::PayloadError(err); + worker_result_sender.send(WorkerResult::PayloadError(err)).await.unwrap(); + return; } } } SocketResult::Stop => { info!("Quitting socket port {}", port); - return WorkerResult::SocketStopped + return; }, SocketResult::Error(err) => { info!("Error {} : quitting socket port {}", err, port); - return WorkerResult::SocketError(err) + worker_result_sender.send(WorkerResult::SocketError(err)).await.unwrap(); + return; } } } } -async fn spawn_controller_worker(name: String, control_channel: Receiver) -> WorkerResult { - midi_controller_worker(name, control_channel).await; - WorkerResult::MidiControllerStopped -} - - -async fn command_reader(command_receiver: Receiver) -> WorkerResult { - match command_receiver.recv().await { - Ok(command) => { - info!("Received command {:?}", command); - WorkerResult::Command(command) - }, - Err(err) => { - error!("Error while reading command channel: {}", err); - WorkerResult::ChannelError(err) +async fn command_reader(command_receiver: Receiver, worker_result_sender: Sender) { + loop { + match command_receiver.recv().await { + Ok(command) => { + #[cfg(worker_debug)] + info!("Received command {:?}", command); + worker_result_sender.send(WorkerResult::Command(command)).await.unwrap() + }, + Err(err) => { + error!("Error while reading command channel: {}", err); + worker_result_sender.send(WorkerResult::ChannelError(err)).await.unwrap(); + return + } } } } - -macro_rules! schedule { - ($future:expr, $task_queue:expr, $result_queue:expr) => {{ - let result_queue = $result_queue.clone(); - let task_queue = $task_queue.clone(); - let future = async move { - result_queue.send($future.await).await.unwrap(); - }; - let (runnable, task) = async_task::spawn_local(future, - move |runnable| task_queue.try_send(runnable).unwrap() - ); - runnable.schedule(); - task - }} -} - - struct MidiControllerChannels { sender: Sender, receiver: Receiver, @@ -144,7 +131,6 @@ struct SocketStopChannels { pub fn create_worker_thread() -> WorkerChannels { let (command_sender, command_receiver) = unbounded::(); let (worker_result_sender, worker_result_receiver) = unbounded(); - let (worker_task_sender, worker_task_receiver) = unbounded(); let (notes_sender, notes_receiver) = unbounded::(); let main = { @@ -161,82 +147,58 @@ pub fn create_worker_thread() -> WorkerChannels { receiver } ; - let mut socket_worker_task: Option> = None; - let mut midi_controller_task: Option> = None; - let mut command_receiver_task : Option> = None; + info!("spawning command receiver"); + { + let command_receiver = command_receiver.clone(); + let worker_result_sender = worker_result_sender.clone(); + task::spawn(command_reader(command_receiver, worker_result_sender)); + } loop { - if command_receiver_task.is_none() { - info!("spawning command receiver"); - command_receiver_task = { - let command_receiver = command_receiver.clone(); - let worker_result_sender = worker_result_sender.clone(); - Some(schedule!(command_reader(command_receiver), worker_task_sender, worker_result_sender)) - }; - } - - let runnable = worker_task_receiver.try_recv().unwrap(); - info!("Executing runnable {:?}", runnable); - runnable.run(); - + #[cfg(worker_debug)] + info!("waiting for a command"); let worker_result = worker_result_receiver.recv().await.unwrap(); - info!("Runnable executed, got {:?}", worker_result); + #[cfg(worker_debug)] + info!("Got {:02X?}", worker_result); match worker_result { WorkerResult::Command(command) => { - command_receiver_task = None; - match command { WorkerCommand::Stop => { - if let Some(socket_worker_task) = socket_worker_task { - socket_stop_channels.sender.send(true).await.unwrap(); - socket_worker_task.cancel().await; - } - - if let Some(midi_controller_task) = midi_controller_task { - midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); - midi_controller_task.cancel().await; - } + socket_stop_channels.sender.send(true).await.unwrap(); + midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); return; } WorkerCommand::SetPort(port) => { info!("Switching to port {}" , port); - if let Some(socket_worker_task) = socket_worker_task { - socket_stop_channels.sender.send(true).await.unwrap(); - socket_worker_task.cancel().await; - } + socket_stop_channels.sender.send(true).await.unwrap(); let (sender, receiver) = unbounded::(); socket_stop_channels.sender = sender; socket_stop_channels.receiver = receiver; info!("socket worker stopped"); - socket_worker_task = { + { let notes_sender = notes_sender.clone(); let socket_stop_receiver = socket_stop_channels.receiver.clone(); - Some(schedule!(spawn_socket_worker(port, notes_sender, socket_stop_receiver), - worker_task_sender, worker_result_sender)) - }; + let worker_result_sender = worker_result_sender.clone(); + task::spawn(spawn_socket_worker(port, notes_sender, socket_stop_receiver, + worker_result_sender)); + } info!("stopping controller worker"); - if let Some(midi_controller_task) = midi_controller_task { - midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); - midi_controller_task.cancel().await; - } + + midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); + info!("controller worker stopped"); let (sender, receiver) = unbounded::(); midi_controller_channels.sender = sender ; midi_controller_channels.receiver = receiver; - midi_controller_task = { - let midi_controller_receiver = midi_controller_channels.receiver.clone(); - Some( - schedule!( - spawn_controller_worker(format!("Arpegiator {}", port), midi_controller_receiver), - worker_task_sender, worker_result_sender) - ) - }; + task::spawn(midi_controller_worker(format!("Arpegiator {}", port), + midi_controller_channels.receiver.clone())); } + WorkerCommand::SendToController(controller_command) => { midi_controller_channels.sender.send(controller_command).await.unwrap(); } @@ -244,37 +206,22 @@ pub fn create_worker_thread() -> WorkerChannels { } WorkerResult::PayloadError(err) => { error!("Invalid payload received. Data received from wrong service ? ( {} )", err); - socket_worker_task = None } WorkerResult::SocketError(_) => { // don't respawn, wait until user chooses another port } WorkerResult::ChannelError(err) => { error!("Command channel error, quitting worker ({})", err); - - if let Some(socket_worker_task) = socket_worker_task { - socket_worker_task.cancel().await; - } - - if let Some(midi_controller_task) = midi_controller_task { - midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); - midi_controller_task.cancel().await; - } + socket_stop_channels.sender.send(true).await.unwrap(); + midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); return; } - - WorkerResult::MidiControllerStopped => { - // controller worker quit. Incompatibility or resource already taken, wait until the user - // chooses another port - } - WorkerResult::SocketStopped => { - } } } } }; - let handle = thread::spawn(move || smol::block_on(main)); + let handle = thread::spawn(move || task::block_on(main)); WorkerChannels { command_sender, From 505e226733a4cb7bb548cb4439c37b120b18f39d Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Mon, 4 Jan 2021 13:49:02 +0100 Subject: [PATCH 09/47] sort out feature flag use, add input --- arpegiator/src/expressive_note.rs | 10 +++--- arpegiator/src/lib.rs | 12 ++++--- arpegiator/src/midi_controller_worker.rs | 46 +++++++++++++++++------- arpegiator/src/worker.rs | 6 ++-- util/Cargo.toml | 4 +++ util/src/logging.rs | 5 ++- 6 files changed, 56 insertions(+), 27 deletions(-) diff --git a/arpegiator/src/expressive_note.rs b/arpegiator/src/expressive_note.rs index 5eb5857..3af9db4 100644 --- a/arpegiator/src/expressive_note.rs +++ b/arpegiator/src/expressive_note.rs @@ -2,11 +2,13 @@ use log::info; use util::raw_message::RawMessage; -use util::messages::{NoteOn, Timbre, PitchBend, AfterTouch}; +use util::messages::{NoteOn, Timbre, PitchBend}; -#[cfg(use_channel_pressure)] +#[cfg(feature="use_channel_pressure")] use util::messages::Pressure; +#[cfg(not(feature="use_channel_pressure"))] +use util::messages::AfterTouch; pub struct ExpressiveNote { pub channel: u8, @@ -19,7 +21,7 @@ pub struct ExpressiveNote { impl ExpressiveNote { - #[cfg(not(use_channel_pressure))] + #[cfg(not(feature="use_channel_pressure"))] #[inline] fn get_pressure_note(&self) -> RawMessage { AfterTouch { @@ -29,7 +31,7 @@ impl ExpressiveNote { }.into() } - #[cfg(use_channel_pressure)] + #[cfg(feature="use_channel_pressure")] #[inline] fn get_pressure_note(&self) -> RawMessage { Pressure { diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index c324f48..b911b9a 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -7,11 +7,14 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use device_out::DeviceOut; use util::logging::logging_setup; -use util::messages::{PitchBend, Timbre, AfterTouch}; +use util::messages::{PitchBend, Timbre}; -#[cfg(use_channel_pressure)] +#[cfg(feature="use_channel_pressure")] use util::messages::Pressure; +#[cfg(not(feature="use_channel_pressure"))] +use util::messages::AfterTouch; + use util::raw_message::RawMessage; use crate::change::SourceChange; @@ -241,11 +244,12 @@ impl Plugin for ArpegiatorPlugin { Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into()) } Expression::Pressure | Expression::AfterTouch => { - #[cfg(use_channel_pressure)] { + #[cfg(feature="use_channel_pressure")] { Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) } - #[cfg(not(use_channel_pressure))] match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { + #[cfg(not(feature="use_channel_pressure"))] match self.notes_device_in.notes + .values().sorted().nth(pattern.index as usize) { None => None, Some(note) => { if let Some(pitch) = pattern.transpose(note.pitch) { diff --git a/arpegiator/src/midi_controller_worker.rs b/arpegiator/src/midi_controller_worker.rs index 4cf91b8..447afc1 100644 --- a/arpegiator/src/midi_controller_worker.rs +++ b/arpegiator/src/midi_controller_worker.rs @@ -2,8 +2,8 @@ use log::{error, info}; use util::raw_message::RawMessage; -use midir::MidiOutput; -use midir::os::unix::VirtualOutput; +use midir::{MidiOutput, MidiInput}; +use midir::os::unix::{VirtualOutput, VirtualInput}; use util::constants::PRESSURE; use async_channel::Receiver; @@ -19,18 +19,38 @@ pub async fn midi_controller_worker(name: String, control_channel: Receiver midi_out, Err(err) => { - error!("Could not create midi port {}", err); - return + error!("Could not create midi out port {}", err); + return; } }; - let mut midi_connection = match midi_out.create_virtual(&*name) { - Ok(midi_connection) => midi_connection, + let mut midi_out_connection = match midi_out.create_virtual(&*name) { + Ok(midi_out_connection) => midi_out_connection, Err(err) => { - error!("Could not get a midi connection {}", err); - return + error!("Could not get a midi out connection {}", err); + return; } }; + let midi_in = match MidiInput::new(&*name) { + Ok(midi_in) => midi_in, + Err(err) => { + error!("Could not create midi in port {}", err); + return; + } + }; + + // create input device just to ease setup. returned connection must not be dropped in order to keep the device alive + let mut _midi_in_connection = match midi_in.create_virtual(&*name, |_ime, _data,_| { + // noop for now + }, ()) { + Ok(midi_in_connection) => midi_in_connection, + Err(err) => { + error!("Could not get a midi in connection {}", err); + return; + } + }; + + loop { match control_channel.recv().await { Ok(command) => { @@ -39,21 +59,21 @@ pub async fn midi_controller_worker(name: String, control_channel: Receiver::into(raw_message); // ugly hack originally with the intent of moving around a fixed amount of u8 let len = if message[0] & 0xF0 == PRESSURE { 2 } else { 3 }; - if let Err(err) = midi_connection.send(&message[..len]) { + if let Err(err) = midi_out_connection.send(&message[..len]) { error!("Error while sending midi message: {}", err); - return + return; } } ControllerCommand::Stop => { info!("Stopping controller {}", name); - midi_connection.close(); - return + midi_out_connection.close(); + return; } } } Err(err) => { error!("Error while fetching a command from the channel: {}", err); - return + return; } } } diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs index 3e8ad53..c6da618 100644 --- a/arpegiator/src/worker.rs +++ b/arpegiator/src/worker.rs @@ -104,7 +104,7 @@ async fn command_reader(command_receiver: Receiver, worker_result loop { match command_receiver.recv().await { Ok(command) => { - #[cfg(worker_debug)] + #[cfg(feature="worker_debug")] info!("Received command {:?}", command); worker_result_sender.send(WorkerResult::Command(command)).await.unwrap() }, @@ -155,10 +155,10 @@ pub fn create_worker_thread() -> WorkerChannels { } loop { - #[cfg(worker_debug)] + #[cfg(feature="worker_debug")] info!("waiting for a command"); let worker_result = worker_result_receiver.recv().await.unwrap(); - #[cfg(worker_debug)] + #[cfg(feature="worker_debug")] info!("Got {:02X?}", worker_result); match worker_result { diff --git a/util/Cargo.toml b/util/Cargo.toml index b47829d..970628a 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -18,3 +18,7 @@ features = ["with-backtrace"] [build-dependencies] build-info-build = "0.0.20" + +[features] +default = ["enable_logging"] +enable_logging = [] diff --git a/util/src/logging.rs b/util/src/logging.rs index 55ebc71..16af9f1 100644 --- a/util/src/logging.rs +++ b/util/src/logging.rs @@ -1,9 +1,8 @@ -// i.e. pass RUSTFLAGS='--cfg enable_logging' to enable -#[cfg(not(enable_logging))] +#[cfg(not(feature="enable_logging"))] pub fn logging_setup() {} -#[cfg(enable_logging)] +#[cfg(feature="enable_logging")] pub fn logging_setup() { use log::info; use simplelog::*; From 11201cb69c21dc7e3af2b031138f25d9105032fc Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Mon, 4 Jan 2021 22:29:06 +0100 Subject: [PATCH 10/47] add cc forwarding --- arpegiator/Cargo.toml | 4 ++- arpegiator/src/lib.rs | 46 +++++++++++++++++++++++++++++--- arpegiator/src/pattern.rs | 2 +- arpegiator/src/pattern_device.rs | 10 ++++--- 4 files changed, 54 insertions(+), 8 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 6f7a490..2a0a61e 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -26,6 +26,8 @@ crate-type = ["cdylib", "lib"] [features] #default = [] -default = ["use_channel_pressure"] +default = ["use_channel_pressure", "forward_pattern_cc"] use_channel_pressure = [] worker_debug = [] +forward_note_cc = [] +forward_pattern_cc = [] diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index b911b9a..99d9361 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -18,7 +18,7 @@ use util::messages::AfterTouch; use util::raw_message::RawMessage; use crate::change::SourceChange; -use crate::device::{Device, Expression}; +use crate::device::{Device, Expression, DeviceChange}; use crate::pattern_device::{PatternDevice, PatternDeviceChange}; use crate::timed_event::TimedEvent; use util::midi_message_with_delta::MidiMessageWithDelta; @@ -27,6 +27,7 @@ use crate::parameters::ArpegiatorParameters; use crate::worker::{WorkerChannels, WorkerCommand, create_worker_thread}; use std::thread::JoinHandle; use std::mem::take; +use std::os::raw::c_void; pub mod pattern; mod note; @@ -118,6 +119,15 @@ impl Plugin for ArpegiatorPlugin { } } + fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { + // according to MPE specifications a vendor specific call should occur in order to signal VST + // support ( page 15 ). As it seems all bitwig does is setting "MPE support" to true by default + // when CanDo replies to "MPE", and it's just sending pitchwheel/pressure. + // https://d30pueezughrda.cloudfront.net/campaigns/mpe/mpespec.pdf + info!("vendor_specific {:?} {:?} {:?} {:?}", index, value, ptr, opt); + 0 + } + fn new(host: HostCallback) -> Self { logging_setup(); info!("{} use_channel_pressure: {}", @@ -222,9 +232,28 @@ impl Plugin for ArpegiatorPlugin { let delta_frames = (change.timestamp() - self.current_time) as u16; match change { - SourceChange::NoteChange(_) => { + SourceChange::NoteChange(change) => { // TODO note changed. for now we don't change anything, it's only when a pattern starts or ends - // that we trigger anything + // that we trigger anything. Forwarding CC optional + match change { + DeviceChange::AddNote { .. } => {} + DeviceChange::RemoveNote { .. } => {} + DeviceChange::NoteExpressionChange { .. } => {} + DeviceChange::ReplaceNote { .. } => {} + + DeviceChange::CCChange { cc: _cc, time: _time } => { + #[cfg(feature="forward_note_cc")] + { + let message = MidiMessageWithDelta { + delta_frames, + data: Into::::into(_cc).into() + }; + + let _ = self.device_out.update(message, current_time, None); + } + } + DeviceChange::None { .. } => {} + } } SourceChange::PatternChange(change) => { match change { @@ -286,6 +315,17 @@ impl Plugin for ArpegiatorPlugin { self.device_out.push_note_on(&new_pattern, note, current_time); } + PatternDeviceChange::CC { cc: _cc, time: _time } => { + #[cfg(feature="forward_pattern_cc")] + { + let message = MidiMessageWithDelta { + delta_frames, + data: Into::::into(_cc).into() + }; + + let _ = self.device_out.update(message, current_time, None); + } + } PatternDeviceChange::None { .. } => {} } } diff --git a/arpegiator/src/pattern.rs b/arpegiator/src/pattern.rs index 44fbe60..fc799b7 100644 --- a/arpegiator/src/pattern.rs +++ b/arpegiator/src/pattern.rs @@ -61,7 +61,7 @@ impl From for Pattern { index, velocity_off: 0, pressure: 0, - timbre: 0, + timbre: 64, octave, pressed_at: note.pressed_at, released_at: 0, diff --git a/arpegiator/src/pattern_device.rs b/arpegiator/src/pattern_device.rs index 221bc0a..04bda6b 100644 --- a/arpegiator/src/pattern_device.rs +++ b/arpegiator/src/pattern_device.rs @@ -6,6 +6,7 @@ use crate::pattern::Pattern; use crate::device::{DeviceChange, Expression}; use crate::timed_event::TimedEvent; use std::cmp::Ordering; +use util::messages::CC; #[derive(Default)] @@ -19,6 +20,7 @@ pub enum PatternDeviceChange { PatternExpressionChange { time: usize, expression: Expression, pattern: Pattern }, RemovePattern { time: usize, pattern: Pattern }, ReplacePattern { time: usize, old_pattern: Pattern, new_pattern: Pattern }, + CC { cc: CC, time: usize }, None { time: usize }, } @@ -47,7 +49,8 @@ impl TimedEvent for PatternDeviceChange { PatternDeviceChange::PatternExpressionChange { time, .. } => *time, PatternDeviceChange::RemovePattern { time, .. } => *time, PatternDeviceChange::ReplacePattern { time, .. } => *time, - PatternDeviceChange::None { time, .. } => *time + PatternDeviceChange::None { time, .. } => *time, + PatternDeviceChange::CC { time, .. } => *time } } @@ -57,7 +60,8 @@ impl TimedEvent for PatternDeviceChange { PatternDeviceChange::PatternExpressionChange { pattern, .. } => pattern.id, PatternDeviceChange::RemovePattern { pattern, .. } => pattern.id, PatternDeviceChange::ReplacePattern { new_pattern: pattern, .. } => pattern.id, - PatternDeviceChange::None { .. } => 0 + PatternDeviceChange::CC { .. } => 0, + PatternDeviceChange::None { .. } => 0, } } } @@ -110,7 +114,7 @@ impl PatternDevice { } } } - DeviceChange::CCChange { time, .. } => PatternDeviceChange::None { time }, + DeviceChange::CCChange { time, cc } => PatternDeviceChange::CC { cc, time }, DeviceChange::None { time } => PatternDeviceChange::None { time } } } From 70e33090570058591b5c33de73afb4f8020b48f7 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Tue, 5 Jan 2021 09:35:05 +0100 Subject: [PATCH 11/47] add optional debug log on devices --- arpegiator/Cargo.toml | 1 + arpegiator/src/device.rs | 11 +++++++++-- arpegiator/src/device_out.rs | 8 +++++++- arpegiator/src/lib.rs | 12 ++++++------ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 2a0a61e..6d18693 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -31,3 +31,4 @@ use_channel_pressure = [] worker_debug = [] forward_note_cc = [] forward_pattern_cc = [] +device_debug = [] diff --git a/arpegiator/src/device.rs b/arpegiator/src/device.rs index fada7ec..309c01c 100644 --- a/arpegiator/src/device.rs +++ b/arpegiator/src/device.rs @@ -11,15 +11,18 @@ use std::cmp::Ordering; use util::midi_message_with_delta::MidiMessageWithDelta; pub struct Device { + pub _name: String, pub notes: HashMap, pub cc: HashMap, pub channels: [Channel; 16], pub note_index: usize, } -impl Default for Device { - fn default() -> Self { + +impl Device { + pub fn new (name: String) -> Self { Device { + _name: name, notes: Default::default(), cc: Default::default(), channels: [Channel { @@ -32,6 +35,7 @@ impl Default for Device { } } + #[derive(Copy, Clone, Debug)] pub struct Channel { pub pressure: u8, @@ -104,6 +108,9 @@ impl PartialEq for DeviceChange { impl Device { pub fn update(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) -> DeviceChange { + #[cfg(feature="device_debug")] + info!("[{}] Got event: {:?} {:?} {:02X?}", self._name, id, current_time, midi_message); + let time = current_time + midi_message.delta_frames as usize; match MidiMessageType::from(&midi_message.data) { diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/device_out.rs index 6855b45..d394918 100644 --- a/arpegiator/src/device_out.rs +++ b/arpegiator/src/device_out.rs @@ -14,7 +14,6 @@ use crate::worker::WorkerCommand; use async_channel::Sender; -#[derive(Default)] pub struct DeviceOut { pub device: Device, queue: Vec, @@ -22,6 +21,13 @@ pub struct DeviceOut { impl DeviceOut { + pub fn new(name: String) -> Self { + Self { + device: Device::new(name), + queue: vec![] + } + } + pub fn update(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) { self.queue.push(midi_message); self.device.update(midi_message, current_time, id); diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 99d9361..6ec4699 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -85,11 +85,11 @@ impl Default for ArpegiatorPlugin { ArpegiatorPlugin { events: vec![], _host: Default::default(), - pattern_device_in: Default::default(), - notes_device_in: Default::default(), + pattern_device_in: Device::new("Patterns".to_string()), + notes_device_in: Device::new("Notes".to_string()), pattern_device: PatternDevice::default(), current_time: 0, - device_out: DeviceOut::default(), + device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), worker_channels: None, thread_handle: None, @@ -137,11 +137,11 @@ impl Plugin for ArpegiatorPlugin { ArpegiatorPlugin { events: vec![], _host: host, - pattern_device_in: Default::default(), - notes_device_in: Default::default(), + pattern_device_in: Device::new("Pattern".to_string()), + notes_device_in: Device::new("Notes".to_string()), pattern_device: Default::default(), current_time: 0, - device_out: DeviceOut::default(), + device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), worker_channels: None, thread_handle: None, From 1f707a1b75a5fab295a555558eacf4874f2d56da Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Tue, 5 Jan 2021 21:01:39 +0100 Subject: [PATCH 12/47] avoid crashes related to plugin shutdown --- arpegiator/src/parameters.rs | 6 ++++-- arpegiator/src/worker.rs | 5 +++-- arpegiator_pattern_receiver/src/parameters.rs | 9 +++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 2a48be1..85a75e0 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -27,9 +27,11 @@ impl ArpegiatorParameters { pub fn update_port(&self) { let port = self.get_byte_parameter(Parameter::PortIndex) as u16 + BASE_PORT; info!("Applying parameter change: port={}", port); - self.worker_commands.lock().unwrap().as_ref().unwrap().try_send( + if let Err(error) = self.worker_commands.lock().unwrap().as_ref().unwrap().try_send( WorkerCommand::SetPort(port) - ).unwrap(); + ) { + info!("main worker is shutdown - ignoring port change ({})", error); + } } } diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs index c6da618..7a61eaf 100644 --- a/arpegiator/src/worker.rs +++ b/arpegiator/src/worker.rs @@ -53,7 +53,9 @@ async fn spawn_socket_worker(port: u16, } Err(err) => { error!("Cannot bind on port {} : {:?}", port, err); - worker_result_sender.send(WorkerResult::SocketError(err)).await.unwrap(); + if let Err(error) = worker_result_sender.send(WorkerResult::SocketError(err)).await { + error!("Main worker is shutdown - leaving socket worker"); + } return; } }; @@ -110,7 +112,6 @@ async fn command_reader(command_receiver: Receiver, worker_result }, Err(err) => { error!("Error while reading command channel: {}", err); - worker_result_sender.send(WorkerResult::ChannelError(err)).await.unwrap(); return } } diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs index a9d1ded..1b47b6a 100644 --- a/arpegiator_pattern_receiver/src/parameters.rs +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -1,3 +1,6 @@ +#[allow(unused_imports)] +use log::{info, error}; + use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; @@ -23,9 +26,11 @@ impl ArpegiatorPatternReceiverParameters { fn update_port(&self) { let port = self.get_byte_parameter(Parameter::PortIndex); - self.socket_command.lock().unwrap().as_ref().unwrap().send( + if self.socket_command.lock().unwrap().as_ref().unwrap().send( SenderSocketCommand::SetPort(BASE_PORT + port as u16) - ).unwrap(); + ).is_err() { + error!("Socket thread is shutdown, ignoring port change") + } } } From fe317564f13a5f1ecd119a8e8519c43dc9cfa73e Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 9 Jan 2021 14:50:56 +0100 Subject: [PATCH 13/47] refactorings, switch to IPC --- Cargo.lock | 293 +++++++++++++++++- arpegiator/Cargo.toml | 8 +- arpegiator/src/lib.rs | 195 +++++++----- arpegiator/src/midi_controller_worker.rs | 80 ----- arpegiator/src/{ => midi_messages}/change.rs | 6 +- arpegiator/src/{ => midi_messages}/device.rs | 13 +- .../src/{ => midi_messages}/device_out.rs | 37 ++- .../{ => midi_messages}/expressive_note.rs | 0 arpegiator/src/midi_messages/mod.rs | 8 + arpegiator/src/{ => midi_messages}/note.rs | 0 arpegiator/src/{ => midi_messages}/pattern.rs | 4 +- .../src/{ => midi_messages}/pattern_device.rs | 8 +- .../src/{ => midi_messages}/timed_event.rs | 0 arpegiator/src/parameters.rs | 87 +++++- arpegiator/src/system.rs | 8 + arpegiator/src/worker.rs | 232 -------------- arpegiator/src/workers/ipc_worker.rs | 156 ++++++++++ arpegiator/src/workers/main_worker.rs | 126 ++++++++ arpegiator/src/workers/midi_output_worker.rs | 120 +++++++ arpegiator/src/workers/mod.rs | 3 + arpegiator_pattern_receiver/Cargo.toml | 9 +- arpegiator_pattern_receiver/src/ipc_worker.rs | 91 ++++++ arpegiator_pattern_receiver/src/lib.rs | 83 +++-- arpegiator_pattern_receiver/src/parameters.rs | 34 +- arpegiator_pattern_receiver/src/socket.rs | 40 --- midi_delay/src/lib.rs | 2 +- util/Cargo.toml | 2 + util/src/ipc_payload.rs | 22 ++ util/src/lib.rs | 2 +- util/src/midi_message_with_delta.rs | 5 +- util/src/pattern_payload.rs | 9 - util/src/raw_message.rs | 18 +- 32 files changed, 1142 insertions(+), 559 deletions(-) delete mode 100644 arpegiator/src/midi_controller_worker.rs rename arpegiator/src/{ => midi_messages}/change.rs (93%) rename arpegiator/src/{ => midi_messages}/device.rs (98%) rename arpegiator/src/{ => midi_messages}/device_out.rs (73%) rename arpegiator/src/{ => midi_messages}/expressive_note.rs (100%) create mode 100644 arpegiator/src/midi_messages/mod.rs rename arpegiator/src/{ => midi_messages}/note.rs (100%) rename arpegiator/src/{ => midi_messages}/pattern.rs (94%) rename arpegiator/src/{ => midi_messages}/pattern_device.rs (96%) rename arpegiator/src/{ => midi_messages}/timed_event.rs (100%) create mode 100644 arpegiator/src/system.rs delete mode 100644 arpegiator/src/worker.rs create mode 100644 arpegiator/src/workers/ipc_worker.rs create mode 100644 arpegiator/src/workers/main_worker.rs create mode 100644 arpegiator/src/workers/midi_output_worker.rs create mode 100644 arpegiator/src/workers/mod.rs create mode 100644 arpegiator_pattern_receiver/src/ipc_worker.rs delete mode 100644 arpegiator_pattern_receiver/src/socket.rs create mode 100644 util/src/ipc_payload.rs delete mode 100644 util/src/pattern_payload.rs diff --git a/Cargo.lock b/Cargo.lock index add2596..8046080 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -61,9 +61,12 @@ dependencies = [ "bincode", "build-info", "build-info-build", + "coremidi", "futures-lite", + "ipc-channel", "itertools", "log", + "mach", "midir", "num-traits", "util", @@ -74,11 +77,16 @@ dependencies = [ name = "arpegiator_pattern_receiver" version = "0.1.0" dependencies = [ - "arpegiator", + "async-channel", + "async-std", "bincode", "build-info", "build-info-build", + "futures-lite", + "ipc-channel", + "itertools", "log", + "mach", "serde", "util", "vst", @@ -139,7 +147,7 @@ dependencies = [ "polling", "vec-arena", "waker-fn", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -162,7 +170,7 @@ dependencies = [ "async-io", "async-mutex", "blocking", - "crossbeam-utils", + "crossbeam-utils 0.8.1", "futures-channel", "futures-core", "futures-io", @@ -387,7 +395,7 @@ dependencies = [ "num-traits", "serde", "time", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -448,6 +456,27 @@ dependencies = [ "core-foundation-sys", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils 0.7.2", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if 0.1.10", + "lazy_static", +] + [[package]] name = "crossbeam-utils" version = "0.8.1" @@ -515,6 +544,12 @@ dependencies = [ "vst", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.0.0" @@ -531,6 +566,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7aea5a5909a74969507051a3b17adc84737e31a5f910559892aedce026f4d53" +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + [[package]] name = "futures-channel" version = "0.3.8" @@ -567,6 +618,17 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "gimli" version = "0.23.0" @@ -644,6 +706,33 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipc-channel" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3698b8affd5656032a074a7d40b3c2a29b71971f3e1ff6042b9d40724e20d97c" +dependencies = [ + "bincode", + "crossbeam-channel", + "fnv", + "lazy_static", + "libc", + "mio", + "rand", + "serde", + "tempfile", + "uuid", +] + [[package]] name = "itertools" version = "0.10.0" @@ -677,6 +766,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -717,7 +816,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" dependencies = [ "cc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -771,6 +870,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matches" version = "0.1.8" @@ -787,6 +895,12 @@ dependencies = [ "vst", ] +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memalloc" version = "0.1.0" @@ -822,7 +936,7 @@ dependencies = [ "nix", "wasm-bindgen", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -835,6 +949,37 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + [[package]] name = "nb-connect" version = "1.0.2" @@ -842,7 +987,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8123a81538e457d44b933a02faf885d3fe8408806b23fa700e8f01c6c3a98998" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", ] [[package]] @@ -943,7 +1099,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -973,7 +1129,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1010,9 +1166,15 @@ dependencies = [ "libc", "log", "wepoll-sys", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "pretty_assertions" version = "0.6.1" @@ -1073,12 +1235,62 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -1196,6 +1408,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "rand", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -1212,8 +1438,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi", - "winapi", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", ] [[package]] @@ -1271,9 +1497,11 @@ dependencies = [ name = "util" version = "0.1.0" dependencies = [ + "async-channel", "build-info", "build-info-build", "global_counter", + "ipc-channel", "log", "log-panics", "serde", @@ -1281,6 +1509,15 @@ dependencies = [ "vst", ] +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand", +] + [[package]] name = "vcpkg" version = "0.2.10" @@ -1323,6 +1560,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" @@ -1414,6 +1657,12 @@ dependencies = [ "cc", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -1424,6 +1673,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1436,7 +1691,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1445,6 +1700,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "xz2" version = "0.1.6" diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 6d18693..33ce4aa 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -16,6 +16,7 @@ midir = "0.7.0" async-std = "1.8.0" async-channel = "1.5.1" futures-lite = "1.11.3" +ipc-channel = "0.14.1" [build-dependencies] build-info-build = "0.0.20" @@ -25,10 +26,15 @@ name = "arpegiator" crate-type = ["cdylib", "lib"] [features] -#default = [] default = ["use_channel_pressure", "forward_pattern_cc"] use_channel_pressure = [] worker_debug = [] forward_note_cc = [] forward_pattern_cc = [] device_debug = [] + +[target.'cfg(target_os = "macos")'.dependencies.coremidi] +version = "0.4.0" + +[target.'cfg(target_os = "macos")'.dependencies.mach] +version = "0.3" diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 6ec4699..7c9091a 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -1,3 +1,7 @@ +use std::mem::take; +use std::os::raw::c_void; +use std::sync::Arc; + use itertools::Itertools; use log::{error, info}; use vst::api; @@ -5,41 +9,27 @@ use vst::buffer::AudioBuffer; use vst::event::{Event, MidiEvent}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; -use device_out::DeviceOut; +use midi_messages::device::{Device, DeviceChange, Expression}; +use midi_messages::device_out::DeviceOut; use util::logging::logging_setup; use util::messages::{PitchBend, Timbre}; - -#[cfg(feature="use_channel_pressure")] -use util::messages::Pressure; - -#[cfg(not(feature="use_channel_pressure"))] +#[cfg(not(feature = "use_channel_pressure"))] use util::messages::AfterTouch; - +#[cfg(feature = "use_channel_pressure")] +use util::messages::Pressure; +use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; +use workers::main_worker::{create_worker_thread, WorkerChannels, WorkerCommand}; -use crate::change::SourceChange; -use crate::device::{Device, Expression, DeviceChange}; -use crate::pattern_device::{PatternDevice, PatternDeviceChange}; -use crate::timed_event::TimedEvent; -use util::midi_message_with_delta::MidiMessageWithDelta; -use std::sync::Arc; -use crate::parameters::ArpegiatorParameters; -use crate::worker::{WorkerChannels, WorkerCommand, create_worker_thread}; -use std::thread::JoinHandle; -use std::mem::take; -use std::os::raw::c_void; +use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT}; +use crate::midi_messages::change::SourceChange; +use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; +use crate::midi_messages::timed_event::TimedEvent; -pub mod pattern; -mod note; -mod device; -mod pattern_device; -mod timed_event; -mod change; -mod expressive_note; -mod device_out; +mod midi_messages; +mod workers; +mod system; mod parameters; -mod worker; -mod midi_controller_worker; #[macro_use] @@ -55,27 +45,25 @@ pub struct ArpegiatorPlugin { pattern_device_in: Device, notes_device_in: Device, pattern_device: PatternDevice, - current_time: usize, + current_time_in_samples: usize, + sample_rate: f32, + block_size: i64, device_out: DeviceOut, parameters: Arc, worker_channels: Option, - thread_handle: Option>, } impl ArpegiatorPlugin { - fn close_socket(&mut self) { - if let Some(worker_channels) = self.worker_channels.as_ref() { + fn close_worker(&mut self) { + if let Some(worker_channels) = take(&mut self.worker_channels) { if let Err(e) = worker_channels.command_sender.try_send(WorkerCommand::Stop) { - error!("Error while closing note receiver channel : {:?}", e) + error!("Error while closing worker channel : {:?}", e) + } + if let Err(err) = worker_channels.worker.join() { + error!("Error while waiting for worker thread to finish {:?}", err) } } - - if let Some(thread_handle) = take(&mut self.thread_handle) { - thread_handle.join().unwrap(); - } - - self.worker_channels = None; // so the channel is not dropped before the thread is joined } } @@ -88,11 +76,12 @@ impl Default for ArpegiatorPlugin { pattern_device_in: Device::new("Patterns".to_string()), notes_device_in: Device::new("Notes".to_string()), pattern_device: PatternDevice::default(), - current_time: 0, + current_time_in_samples: 0, + sample_rate: 44100.0, + block_size: 64, device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), worker_channels: None, - thread_handle: None, } } } @@ -104,9 +93,13 @@ impl Plugin for ArpegiatorPlugin { name: "Arpegiator".to_string(), vendor: "DJ Crontab".to_string(), unique_id: 342111721, - parameters: 1, + parameters: PARAMETER_COUNT as i32, category: Category::Synth, - initial_delay: 0, + // this device must start slightly later than the pattern receiver, so it receives patterns that + // apply to the same buffer + // one sample would be 0.2ms down to 0.05ms. increase amount of samples if the delay between plugins + // is greater than 0.05ms + initial_delay: 1, version: 1, inputs: 0, outputs: 0, @@ -140,22 +133,24 @@ impl Plugin for ArpegiatorPlugin { pattern_device_in: Device::new("Pattern".to_string()), notes_device_in: Device::new("Notes".to_string()), pattern_device: Default::default(), - current_time: 0, + current_time_in_samples: 0, + sample_rate: 44100., + block_size: 64, device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), worker_channels: None, - thread_handle: None, } } fn resume(&mut self) { - self.close_socket(); + self.close_worker(); - self.current_time = 0; + self.current_time_in_samples = 0; let worker_channels = create_worker_thread(); worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port())).unwrap(); + worker_channels.command_sender.try_send(WorkerCommand::SetSampleRate(self.sample_rate)).unwrap(); if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { *worker_commands = Some(worker_channels.command_sender.clone()); @@ -165,7 +160,7 @@ impl Plugin for ArpegiatorPlugin { } fn suspend(&mut self) { - self.close_socket() + self.close_worker() } fn get_parameter_object(&mut self) -> Arc { @@ -191,12 +186,33 @@ impl Plugin for ArpegiatorPlugin { } fn process(&mut self, buffer: &mut AudioBuffer) { - let messages = match self.worker_channels.as_ref() { + #[cfg(target_os = "macos")] + let local_time = unsafe { + mach::mach_time::mach_absolute_time() + }; + + #[cfg(target_os = "linux")] + let local_time = 0; + + let pattern_messages = match self.worker_channels.as_ref() { None => vec![], Some(socket_channels) => { - match socket_channels.notes_receiver.try_recv() { + match socket_channels.pattern_receiver.try_recv() { Ok(payload) => { - //info!("[{}] received patterns : {:02X?}", self.current_time, payload); + #[cfg(target_os = "macos")] + { + let diff_milliseconds = (local_time - payload.time) as f64 / 10e6; + + // TODO following will device if the initial_delay is enough + info!("Received time: {:?} current time: {:?} = {} milliseconds", + payload.time, + local_time, + diff_milliseconds); + }; + + #[cfg(feature = "device_debug")] + info!("[{}] received patterns : {:02X?}", self.current_time_in_samples, payload); + payload.messages } Err(_) => vec![] @@ -204,14 +220,18 @@ impl Plugin for ArpegiatorPlugin { } }; + // from here we cannot accurately tell when the buffer we're building will actually play + // so our best guest will be the earliest : + // current_time + (delta_frame + buffer_size) * sample_to_second + // this avoids borrowing self - let current_time = self.current_time; + let current_time_in_samples = self.current_time_in_samples; let pattern_device_in = &mut self.pattern_device_in; let pattern_device = &mut self.pattern_device; let notes_device_in = &mut self.notes_device_in; - let pattern_changes = messages.into_iter().map(|message| { - let change = pattern_device_in.update(message, current_time, None); + let pattern_changes = pattern_messages.into_iter().map(|message| { + let change = pattern_device_in.update(message, current_time_in_samples, None); let change = pattern_device.update(change); SourceChange::PatternChange(change) }); @@ -219,22 +239,21 @@ impl Plugin for ArpegiatorPlugin { let note_changes = self.events.iter().map(|event| { let midi_message_with_delta = MidiMessageWithDelta { delta_frames: event.delta_frames as u16, - data: event.data, + data: event.data.into(), }; - let change = notes_device_in.update(midi_message_with_delta, current_time, None); + let change = notes_device_in.update(midi_message_with_delta, current_time_in_samples, None); SourceChange::NoteChange(change) }); // merge() gets a sorted output. If a note is triggered at the same time as a pattern, note comes first in order // to set the pitch for change in pattern_changes.sorted().merge(note_changes.sorted()) { - let delta_frames = (change.timestamp() - self.current_time) as u16; + let delta_frames = (change.timestamp() - self.current_time_in_samples) as u16; match change { SourceChange::NoteChange(change) => { - // TODO note changed. for now we don't change anything, it's only when a pattern starts or ends - // that we trigger anything. Forwarding CC optional + // TODO note change should trigger pitchbend events match change { DeviceChange::AddNote { .. } => {} DeviceChange::RemoveNote { .. } => {} @@ -242,14 +261,13 @@ impl Plugin for ArpegiatorPlugin { DeviceChange::ReplaceNote { .. } => {} DeviceChange::CCChange { cc: _cc, time: _time } => { - #[cfg(feature="forward_note_cc")] - { + #[cfg(feature = "forward_note_cc")] { let message = MidiMessageWithDelta { delta_frames, - data: Into::::into(_cc).into() + data: Into::::into(_cc).into(), }; - let _ = self.device_out.update(message, current_time, None); + let _ = self.device_out.update(message, current_time_in_samples, None); } } DeviceChange::None { .. } => {} @@ -258,9 +276,10 @@ impl Plugin for ArpegiatorPlugin { SourceChange::PatternChange(change) => { match change { PatternDeviceChange::AddPattern { pattern, .. } => { + // TODO "hold notes" logic match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { None => {} - Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time) + Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time_in_samples) } } @@ -270,15 +289,17 @@ impl Plugin for ArpegiatorPlugin { Some(Timbre { channel: pattern.channel, value: pattern.timbre }.into()) } Expression::PitchBend => { + // TODO should change the pitch as is, meaning it's the pitchbend is just added to + // the result, independently from the notes we're supposed to match + // the result should be: target note + pattern pitchbend Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into()) } Expression::Pressure | Expression::AfterTouch => { - #[cfg(feature="use_channel_pressure")] { + #[cfg(feature = "use_channel_pressure")] { Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) } - #[cfg(not(feature="use_channel_pressure"))] match self.notes_device_in.notes - .values().sorted().nth(pattern.index as usize) { + #[cfg(not(feature = "use_channel_pressure"))] match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { None => None, Some(note) => { if let Some(pitch) = pattern.transpose(note.pitch) { @@ -296,34 +317,33 @@ impl Plugin for ArpegiatorPlugin { }; if let Some(raw_message) = raw_message { - self.device_out.update(MidiMessageWithDelta { delta_frames, data: raw_message.into() }, - current_time, None); + self.device_out.update(MidiMessageWithDelta { delta_frames, data: raw_message }, + current_time_in_samples, None); } } PatternDeviceChange::RemovePattern { pattern, .. } => { self.device_out.push_note_off(pattern.id, pattern.velocity_off, - delta_frames, current_time); + delta_frames, current_time_in_samples); } PatternDeviceChange::ReplacePattern { old_pattern, new_pattern, .. } => { self.device_out.push_note_off(old_pattern.id, old_pattern.velocity_off, - delta_frames, current_time); + delta_frames, current_time_in_samples); let note = match self.notes_device_in.notes.values().sorted().nth(new_pattern.index as usize) { None => { continue; } Some(note) => note }; - self.device_out.push_note_on(&new_pattern, note, current_time); + self.device_out.push_note_on(&new_pattern, note, current_time_in_samples); } PatternDeviceChange::CC { cc: _cc, time: _time } => { - #[cfg(feature="forward_pattern_cc")] - { + #[cfg(feature = "forward_pattern_cc")] { let message = MidiMessageWithDelta { delta_frames, - data: Into::::into(_cc).into() + data: _cc.into(), }; - let _ = self.device_out.update(message, current_time, None); + let _ = self.device_out.update(message, current_time_in_samples, None); } } PatternDeviceChange::None { .. } => {} @@ -332,13 +352,28 @@ impl Plugin for ArpegiatorPlugin { } if let Some(worker_channels) = self.worker_channels.as_ref() { - self.device_out.flush_to(&worker_channels.command_sender) + self.device_out.flush_to(local_time,&worker_channels.command_sender) } - } + }; + self.events.clear(); - self.current_time += buffer.samples() + self.current_time_in_samples += buffer.samples() + } + + fn set_sample_rate(&mut self, rate: f32) { + if let Some(workers_channel) = &self.worker_channels { + workers_channel.command_sender.try_send(WorkerCommand::SetSampleRate(rate)).unwrap() + }; + self.sample_rate = rate + } + + fn set_block_size(&mut self, size: i64) { + self.block_size = size; + if let Some(workers_channel) = &self.worker_channels { + workers_channel.command_sender.try_send(WorkerCommand::SetBlockSize(size)).unwrap() + }; } fn process_events(&mut self, events: &api::Events) { @@ -352,6 +387,6 @@ impl Plugin for ArpegiatorPlugin { impl Drop for ArpegiatorPlugin { fn drop(&mut self) { - self.close_socket(); + self.close_worker(); } } diff --git a/arpegiator/src/midi_controller_worker.rs b/arpegiator/src/midi_controller_worker.rs deleted file mode 100644 index 447afc1..0000000 --- a/arpegiator/src/midi_controller_worker.rs +++ /dev/null @@ -1,80 +0,0 @@ -#[allow(unused_imports)] -use log::{error, info}; - -use util::raw_message::RawMessage; -use midir::{MidiOutput, MidiInput}; -use midir::os::unix::{VirtualOutput, VirtualInput}; -use util::constants::PRESSURE; -use async_channel::Receiver; - - -#[derive(Debug)] -pub enum ControllerCommand { - RawMessage(RawMessage), - Stop, -} - -pub async fn midi_controller_worker(name: String, control_channel: Receiver) { - info!("Creating midi device {}", name); - let midi_out = match MidiOutput::new(&*name) { - Ok(midi_out) => midi_out, - Err(err) => { - error!("Could not create midi out port {}", err); - return; - } - }; - let mut midi_out_connection = match midi_out.create_virtual(&*name) { - Ok(midi_out_connection) => midi_out_connection, - Err(err) => { - error!("Could not get a midi out connection {}", err); - return; - } - }; - - let midi_in = match MidiInput::new(&*name) { - Ok(midi_in) => midi_in, - Err(err) => { - error!("Could not create midi in port {}", err); - return; - } - }; - - // create input device just to ease setup. returned connection must not be dropped in order to keep the device alive - let mut _midi_in_connection = match midi_in.create_virtual(&*name, |_ime, _data,_| { - // noop for now - }, ()) { - Ok(midi_in_connection) => midi_in_connection, - Err(err) => { - error!("Could not get a midi in connection {}", err); - return; - } - }; - - - loop { - match control_channel.recv().await { - Ok(command) => { - match command { - ControllerCommand::RawMessage(raw_message) => { - let message = Into::<[u8; 3]>::into(raw_message); - // ugly hack originally with the intent of moving around a fixed amount of u8 - let len = if message[0] & 0xF0 == PRESSURE { 2 } else { 3 }; - if let Err(err) = midi_out_connection.send(&message[..len]) { - error!("Error while sending midi message: {}", err); - return; - } - } - ControllerCommand::Stop => { - info!("Stopping controller {}", name); - midi_out_connection.close(); - return; - } - } - } - Err(err) => { - error!("Error while fetching a command from the channel: {}", err); - return; - } - } - } -} diff --git a/arpegiator/src/change.rs b/arpegiator/src/midi_messages/change.rs similarity index 93% rename from arpegiator/src/change.rs rename to arpegiator/src/midi_messages/change.rs index 122e32c..78aaf96 100644 --- a/arpegiator/src/change.rs +++ b/arpegiator/src/midi_messages/change.rs @@ -1,8 +1,8 @@ use core::cmp::{Ordering, PartialEq, PartialOrd}; use core::option::Option; -use crate::device::DeviceChange; -use crate::pattern_device::PatternDeviceChange; -use crate::timed_event::TimedEvent; +use crate::midi_messages::device::DeviceChange; +use crate::midi_messages::pattern_device::PatternDeviceChange; +use crate::midi_messages::timed_event::TimedEvent; pub enum SourceChange { NoteChange(DeviceChange), diff --git a/arpegiator/src/device.rs b/arpegiator/src/midi_messages/device.rs similarity index 98% rename from arpegiator/src/device.rs rename to arpegiator/src/midi_messages/device.rs index 309c01c..3585a6b 100644 --- a/arpegiator/src/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -1,15 +1,16 @@ use log::info; +use std::cmp::Ordering; use std::collections::HashMap; use util::constants::TIMBRECC; -use util::midi_message_type::MidiMessageType; - -use crate::note::{CCIndex, Note, NoteIndex}; use util::messages::CC; -use crate::timed_event::TimedEvent; -use std::cmp::Ordering; +use util::midi_message_type::MidiMessageType; use util::midi_message_with_delta::MidiMessageWithDelta; +use crate::midi_messages::note::{NoteIndex, Note, CCIndex}; +use crate::midi_messages::timed_event::TimedEvent; + + pub struct Device { pub _name: String, pub notes: HashMap, @@ -113,7 +114,7 @@ impl Device { let time = current_time + midi_message.delta_frames as usize; - match MidiMessageType::from(&midi_message.data) { + match MidiMessageType::from(&midi_message.data.into()) { MidiMessageType::NoteOnMessage(note) => { let note_id = match id { None => { diff --git a/arpegiator/src/device_out.rs b/arpegiator/src/midi_messages/device_out.rs similarity index 73% rename from arpegiator/src/device_out.rs rename to arpegiator/src/midi_messages/device_out.rs index d394918..57aa1b6 100644 --- a/arpegiator/src/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -1,20 +1,21 @@ #[allow(unused_imports)] use log::{error, info}; +use async_channel::Sender; +use std::mem::take; + use util::messages::NoteOff; +use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; -use crate::device::Device; -use crate::expressive_note::ExpressiveNote; -use crate::note::Note; -use crate::pattern::Pattern; -use util::midi_message_with_delta::MidiMessageWithDelta; -use crate::midi_controller_worker::ControllerCommand; -use crate::worker::WorkerCommand; -use async_channel::Sender; +use crate::midi_messages::device::Device; +use crate::midi_messages::expressive_note::ExpressiveNote; +use crate::midi_messages::pattern::Pattern; +use crate::midi_messages::note::Note; +use crate::workers::main_worker::WorkerCommand; -pub struct DeviceOut { +pub(crate) struct DeviceOut { pub device: Device, queue: Vec, } @@ -33,13 +34,15 @@ impl DeviceOut { self.device.update(midi_message, current_time, id); } - pub fn flush_to(&mut self, midi_controller_sender: &Sender) { - for message in self.queue.drain(..) { - if let Err(err) = midi_controller_sender.try_send( - WorkerCommand::SendToController(ControllerCommand::RawMessage(RawMessage::from(message.data)))) { - error!("Could not send to the controller worker {}", err) - } + pub fn flush_to(&mut self, buffer_start_time: u64, midi_output_sender: &Sender) { + if self.queue.is_empty() { + return } + midi_output_sender.try_send( + WorkerCommand::SendToMidiOutput { buffer_start_time, messages: take(&mut self.queue) + }).unwrap_or_else( + |err| error!("Could not send to the controller worker {}", err) + ); } pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { @@ -59,7 +62,7 @@ impl DeviceOut { self.update(MidiMessageWithDelta { delta_frames, - data: raw_message.into(), + data: raw_message, }, current_time, None); } @@ -80,7 +83,7 @@ impl DeviceOut { self.update( MidiMessageWithDelta { delta_frames: (pattern.pressed_at - current_time) as u16, - data: raw_message.into(), + data: raw_message, }, current_time, Some(pattern.id), diff --git a/arpegiator/src/expressive_note.rs b/arpegiator/src/midi_messages/expressive_note.rs similarity index 100% rename from arpegiator/src/expressive_note.rs rename to arpegiator/src/midi_messages/expressive_note.rs diff --git a/arpegiator/src/midi_messages/mod.rs b/arpegiator/src/midi_messages/mod.rs new file mode 100644 index 0000000..298a5aa --- /dev/null +++ b/arpegiator/src/midi_messages/mod.rs @@ -0,0 +1,8 @@ +pub(crate) mod change; +pub(crate) mod device; +pub(crate) mod device_out; +pub(crate) mod expressive_note; +pub(crate) mod pattern; +pub(crate) mod note; +pub(crate) mod pattern_device; +pub(crate) mod timed_event; diff --git a/arpegiator/src/note.rs b/arpegiator/src/midi_messages/note.rs similarity index 100% rename from arpegiator/src/note.rs rename to arpegiator/src/midi_messages/note.rs diff --git a/arpegiator/src/pattern.rs b/arpegiator/src/midi_messages/pattern.rs similarity index 94% rename from arpegiator/src/pattern.rs rename to arpegiator/src/midi_messages/pattern.rs index fc799b7..2b87584 100644 --- a/arpegiator/src/pattern.rs +++ b/arpegiator/src/midi_messages/pattern.rs @@ -1,5 +1,5 @@ -use crate::note::Note; -use crate::timed_event::TimedEvent; +use crate::midi_messages::note::Note; +use crate::midi_messages::timed_event::TimedEvent; pub const C3: u8 = 60 ; diff --git a/arpegiator/src/pattern_device.rs b/arpegiator/src/midi_messages/pattern_device.rs similarity index 96% rename from arpegiator/src/pattern_device.rs rename to arpegiator/src/midi_messages/pattern_device.rs index 04bda6b..c673c73 100644 --- a/arpegiator/src/pattern_device.rs +++ b/arpegiator/src/midi_messages/pattern_device.rs @@ -1,12 +1,12 @@ use log::error; +use std::cmp::Ordering; use std::collections::HashMap; -use crate::pattern::Pattern; -use crate::device::{DeviceChange, Expression}; -use crate::timed_event::TimedEvent; -use std::cmp::Ordering; use util::messages::CC; +use crate::midi_messages::device::{DeviceChange, Expression}; +use crate::midi_messages::pattern::Pattern; +use crate::midi_messages::timed_event::TimedEvent; #[derive(Default)] diff --git a/arpegiator/src/timed_event.rs b/arpegiator/src/midi_messages/timed_event.rs similarity index 100% rename from arpegiator/src/timed_event.rs rename to arpegiator/src/midi_messages/timed_event.rs diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 85a75e0..7061890 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -5,16 +5,46 @@ use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; -use util::parameter_value_conversion::f32_to_byte; -use crate::worker::WorkerCommand; +use util::parameter_value_conversion::{f32_to_byte, f32_to_bool}; +use crate::workers::main_worker::WorkerCommand; use std::sync::Mutex; use async_channel::Sender; +use util::duration_display; -const PARAMETER_COUNT: usize = 1; +pub const PARAMETER_COUNT: usize = 4; const BASE_PORT: u16 = 6000; -pub struct ArpegiatorParameters { +// 1 = Immediate - TODO : must be default +// 0 = Off +// highest = faster +// lowest = slower +// choose or configurable: +// fixed time between start/end pitch +// fixed time per semitone +/* +think about those cases: + large difference / small interval + small difference / large interval => weirdest + + thus fixed time sounds weird, but the player can keep changing the target note, thus has the possibility to + influence the speed. + but then we need to reset the time at each change + + also : velocity would influence pressure + + */ + +// note: if pitchbend is not off, it makes sense to consume note pitchbend as is +// ideally while a note eposes its pitch and a pitchbend value, a method should directly tell the pitch in +// millisemitones relative to 0 ( C-2 ) +enum PitchBendValues { + Off, // no pitchbend, means same pitch until pattern ends + DurationToReachTarget(f32), + Immediate +} + +pub(crate) struct ArpegiatorParameters { pub transfer: ParameterTransfer, pub worker_commands: Mutex>>, } @@ -39,12 +69,18 @@ impl ArpegiatorParameters { #[repr(i32)] pub enum Parameter { PortIndex = 0, + HoldNotes, // a started pattern will find a note to play, even if no note is playing for that index + PatternLegato, // pattern is not restarted if start/end match, and note is thus held + Pitchbend } impl From for Parameter { fn from(i: i32) -> Self { match i { 0 => Parameter::PortIndex, + 1 => Parameter::HoldNotes, + 2 => Parameter::PatternLegato, + 3 => Parameter::Pitchbend, _ => panic!("no such parameter {}", i), } } @@ -70,26 +106,52 @@ impl ParameterConversion for ArpegiatorParameters { impl ArpegiatorParameters { pub fn new() -> Self { - ArpegiatorParameters { + let parameters = ArpegiatorParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), worker_commands: Mutex::new(None), - } + }; + parameters.set_parameter(Parameter::PatternLegato.into(), 1.); + parameters } } impl PluginParameters for ArpegiatorParameters { fn get_parameter_text(&self, index: i32) -> String { - match index.into() { + let parameter = index.into(); + match parameter { Parameter::PortIndex => { self.get_port().to_string() } + Parameter::HoldNotes | Parameter::PatternLegato => { + match self.get_bool_parameter(parameter) { + true => "On", + false => "Off" + }.to_string() + } + Parameter::Pitchbend => { // TODO set default to 1 + match self.get_parameter(index) { + x if x <= 0. => { + "Immediate".into() + } + x if x >= 1. => { + "Off".into() + } + _ => { + let value = self.get_exponential_scale_parameter(Parameter::Pitchbend, 1., 80.); + duration_display(value) + } + } + } } } fn get_parameter_name(&self, index: i32) -> String { match index.into() { Parameter::PortIndex => "Port", + Parameter::HoldNotes => "Hold notes", + Parameter::PatternLegato => "Pattern Legato", + Parameter::Pitchbend => "Use pitchbend" }.to_string() } @@ -98,7 +160,8 @@ impl PluginParameters for ArpegiatorParameters { } fn set_parameter(&self, index: i32, value: f32) { - match index.into() { + let parameter = index.into(); + match parameter { Parameter::PortIndex => { let new_value = f32_to_byte(value); let old_value = self.get_byte_parameter(Parameter::PortIndex); @@ -107,6 +170,14 @@ impl PluginParameters for ArpegiatorParameters { self.update_port() } } + Parameter::HoldNotes | Parameter::PatternLegato => { + let new_value = f32_to_bool(value); + let old_value = self.get_bool_parameter(parameter); + if old_value != new_value { + self.transfer.set_parameter(index as usize, value); + } + } + Parameter::Pitchbend => self.transfer.set_parameter(index as usize, value) } } diff --git a/arpegiator/src/system.rs b/arpegiator/src/system.rs new file mode 100644 index 0000000..a510229 --- /dev/null +++ b/arpegiator/src/system.rs @@ -0,0 +1,8 @@ +#[cfg(target_os = "macos")] +#[inline] +pub fn second_to_mach_timebase() -> f64 { + let mut timebase_info = mach::mach_time::mach_timebase_info { numer: 0, denom: 0 } ; + unsafe { mach::mach_time::mach_timebase_info(&mut timebase_info) ;} + + 10e9 * timebase_info.denom as f64 / timebase_info.numer as f64 +} diff --git a/arpegiator/src/worker.rs b/arpegiator/src/worker.rs deleted file mode 100644 index 7a61eaf..0000000 --- a/arpegiator/src/worker.rs +++ /dev/null @@ -1,232 +0,0 @@ -use log::{error, info}; -use util::pattern_payload::PatternPayload; -use std::thread::JoinHandle; -use std::thread; -use async_channel::{unbounded, Sender, Receiver, RecvError}; -use async_std::net::UdpSocket; -use async_std::task; -use async_std::io::Error; -use futures_lite::FutureExt; - -use crate::midi_controller_worker::{midi_controller_worker, ControllerCommand}; - - - -#[derive(Debug)] -pub enum WorkerCommand { - Stop, - SetPort(u16), - SendToController(ControllerCommand) -} - -pub struct WorkerChannels { - pub command_sender: Sender, - pub notes_receiver: Receiver, - pub worker: JoinHandle<()>, -} - -#[derive(Debug)] -enum WorkerResult { - Command(WorkerCommand), - PayloadError(bincode::Error), - ChannelError(RecvError), - SocketError(Error) -} - -enum SocketResult { - Recv(usize), - Stop, - Error(Error) -} - -async fn spawn_socket_worker(port: u16, - notes_sender: Sender, - socket_stop_channel: Receiver, - worker_result_sender: Sender -) { - let mut buf = vec![0u8; 1024]; - - let socket = match UdpSocket::bind(format!("127.0.0.1:{}", port)).await { - Ok(socket) => { - info!("Listening on port {}", port); - socket - } - Err(err) => { - error!("Cannot bind on port {} : {:?}", port, err); - if let Err(error) = worker_result_sender.send(WorkerResult::SocketError(err)).await { - error!("Main worker is shutdown - leaving socket worker"); - } - return; - } - }; - - loop { - let socket_receive = async { - match socket.recv(&mut buf).await { - Ok(len) => SocketResult::Recv(len), - Err(err) => SocketResult::Error(err) - } - }; - - let stop_receive = async { - match socket_stop_channel.recv().await { - Ok(_) => SocketResult::Stop, - Err(_) => SocketResult::Stop - } - }; - - match socket_receive.race(stop_receive).await { - SocketResult::Recv(len) => { - match bincode::deserialize::(&buf[..len]) { - Ok(payload) => { - notes_sender.send(payload).await.unwrap(); - } - Err(err) => { - error!("Could not deserialize: {:?}", err); - worker_result_sender.send(WorkerResult::PayloadError(err)).await.unwrap(); - return; - } - } - } - SocketResult::Stop => { - info!("Quitting socket port {}", port); - return; - }, - SocketResult::Error(err) => { - info!("Error {} : quitting socket port {}", err, port); - worker_result_sender.send(WorkerResult::SocketError(err)).await.unwrap(); - return; - } - } - } -} - - -async fn command_reader(command_receiver: Receiver, worker_result_sender: Sender) { - loop { - match command_receiver.recv().await { - Ok(command) => { - #[cfg(feature="worker_debug")] - info!("Received command {:?}", command); - worker_result_sender.send(WorkerResult::Command(command)).await.unwrap() - }, - Err(err) => { - error!("Error while reading command channel: {}", err); - return - } - } - } -} - -struct MidiControllerChannels { - sender: Sender, - receiver: Receiver, -} - -struct SocketStopChannels { - sender: Sender, - receiver: Receiver, -} - - -pub fn create_worker_thread() -> WorkerChannels { - let (command_sender, command_receiver) = unbounded::(); - let (worker_result_sender, worker_result_receiver) = unbounded(); - let (notes_sender, notes_receiver) = unbounded::(); - - let main = { - async move { - let (sender, receiver) = unbounded::(); - let mut midi_controller_channels = MidiControllerChannels { - sender, - receiver, - }; - - let (sender, receiver) = unbounded::(); - let mut socket_stop_channels = SocketStopChannels { - sender, - receiver - } ; - - info!("spawning command receiver"); - { - let command_receiver = command_receiver.clone(); - let worker_result_sender = worker_result_sender.clone(); - task::spawn(command_reader(command_receiver, worker_result_sender)); - } - - loop { - #[cfg(feature="worker_debug")] - info!("waiting for a command"); - let worker_result = worker_result_receiver.recv().await.unwrap(); - #[cfg(feature="worker_debug")] - info!("Got {:02X?}", worker_result); - - match worker_result { - WorkerResult::Command(command) => { - match command { - WorkerCommand::Stop => { - socket_stop_channels.sender.send(true).await.unwrap(); - midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); - return; - } - WorkerCommand::SetPort(port) => { - info!("Switching to port {}" , port); - socket_stop_channels.sender.send(true).await.unwrap(); - - let (sender, receiver) = unbounded::(); - socket_stop_channels.sender = sender; - socket_stop_channels.receiver = receiver; - - info!("socket worker stopped"); - { - let notes_sender = notes_sender.clone(); - let socket_stop_receiver = socket_stop_channels.receiver.clone(); - let worker_result_sender = worker_result_sender.clone(); - task::spawn(spawn_socket_worker(port, notes_sender, socket_stop_receiver, - worker_result_sender)); - } - - info!("stopping controller worker"); - - midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); - - info!("controller worker stopped"); - let (sender, receiver) = unbounded::(); - midi_controller_channels.sender = sender ; - midi_controller_channels.receiver = receiver; - - task::spawn(midi_controller_worker(format!("Arpegiator {}", port), - midi_controller_channels.receiver.clone())); - } - - WorkerCommand::SendToController(controller_command) => { - midi_controller_channels.sender.send(controller_command).await.unwrap(); - } - } - } - WorkerResult::PayloadError(err) => { - error!("Invalid payload received. Data received from wrong service ? ( {} )", err); - } - WorkerResult::SocketError(_) => { - // don't respawn, wait until user chooses another port - } - WorkerResult::ChannelError(err) => { - error!("Command channel error, quitting worker ({})", err); - socket_stop_channels.sender.send(true).await.unwrap(); - midi_controller_channels.sender.send(ControllerCommand::Stop).await.unwrap(); - return; - } - } - } - } - }; - - let handle = thread::spawn(move || task::block_on(main)); - - WorkerChannels { - command_sender, - notes_receiver, - worker: handle, - } -} diff --git a/arpegiator/src/workers/ipc_worker.rs b/arpegiator/src/workers/ipc_worker.rs new file mode 100644 index 0000000..4c0f6e1 --- /dev/null +++ b/arpegiator/src/workers/ipc_worker.rs @@ -0,0 +1,156 @@ +#[allow(unused_imports)] +use log::{error, info}; + +use async_channel::Sender; +use async_std::net::UdpSocket; +use async_std::task; +use futures_lite::io::{Error, ErrorKind}; +use ipc_channel::ipc::{IpcSender, IpcReceiver, IpcReceiverSet}; + +use util::ipc_payload::{PatternPayload, IPCCommand}; + +use crate::workers::main_worker::WorkerCommand; +use std::{thread, error}; +use std::mem::take; + + +pub(crate) enum IPCWorkerCommand { + SocketReceive(IpcReceiver), + Stop, + IPCDisconnect, + PayloadReceived(PatternPayload), +} + + +async fn udp_receive_worker(socket: UdpSocket, sender: Sender) { + let mut buf = vec![0u8; 1024]; + + while let Ok(len) = socket.recv(&mut buf).await { + let ipc_worker_command = match bincode::deserialize::>(&buf[..len]) { + Ok(ipc_receiver) => { + info!("Received IPC Receiver via UDP"); + IPCWorkerCommand::SocketReceive(ipc_receiver) + } + Err(err) => { + error!("Ignoring invalid UDP payload ({}) - expected ICP receiver", err); + continue; + } + }; + + if let Err(err) = sender.send(ipc_worker_command).await { + error!("UDP Worker: error while trying to send worker command {}, quitting UDP Worker", err); + break; + } + }; +} + + +fn spawn_ipc_receiver_thread(ipc_receiver: IpcReceiver, + ipc_worker_sender: Sender) -> Result, Box> { + let mut set = IpcReceiverSet::new()?; + set.add(ipc_receiver)?; + + let (sender, receiver) = ipc_channel::ipc::channel::()?; + set.add(receiver)?; + + thread::spawn(|| task::block_on(async move { + while let Ok(results) = set.select() { + for result in results { + let (_, opaque_message) = result.unwrap(); + match opaque_message.to::().unwrap() { + IPCCommand::PatternPayload(payload) => { + if let Err(err) = ipc_worker_sender.send(IPCWorkerCommand::PayloadReceived(payload)).await { + error!("could not send payload to ipc worker {}", err); + break; + } + } + IPCCommand::Ping => { + info!("Received ping from peer") + } + IPCCommand::Stop(ack_channel_sender) => { + ack_channel_sender.send(()).unwrap_or_else(|err| { + error!("Could not signal ipc thread stop {}", err); + }); + info!("Stopping IPC worker thread"); + break; + } + } + } + }; + ipc_worker_sender.try_send(IPCWorkerCommand::IPCDisconnect).unwrap_or_else(|err| { + error!("Error {} while signaling IPC receiver quitting", err); + }); + })); + + Ok(sender) +} + + +pub(crate) fn spawn_ipc_worker(port: u16, + pattern_sender: Sender, + worker_command_sender: Sender, +) -> Result, Box> { + let (ipc_worker_sender, ipc_worker_receiver) = async_channel::unbounded::(); + + let returned_ipc_worker_sender = ipc_worker_sender.clone(); + + let socket = task::block_on(UdpSocket::bind(format!("127.0.0.1:{}", port)))?; + + thread::spawn(move || task::block_on(async move { + let mut ipc_receiver_sender: Option> = None; + let udp_worker_handle = task::spawn( + udp_receive_worker(socket, ipc_worker_sender.clone()) + ); + + while let Ok(command) = ipc_worker_receiver.recv().await { + match command { + IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket) => { + if ipc_receiver_sender.is_some() { + close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap()); + } + + let ipc_worker_sender = ipc_worker_sender.clone(); + + if let Ok(sender) = spawn_ipc_receiver_thread(ipc_receiver_from_socket, ipc_worker_sender) { + ipc_receiver_sender = Some(sender); + }; + } + IPCWorkerCommand::Stop => { + info!("Quitting socket port {}", port); + break; + } + IPCWorkerCommand::IPCDisconnect => { + ipc_receiver_sender = None; + error!("IPC Receiver disconnected") + } + IPCWorkerCommand::PayloadReceived(payload) => { + if let Err(err) = pattern_sender.send(payload).await { + error!("IPC worker: notes sender channel error, quitting ({})", err); + break; + } + } + } + } + + udp_worker_handle.cancel().await; + if let Some(sender) = take(&mut ipc_receiver_sender) { + close_ipc_receiver_thread(sender).unwrap_or_else(|err| { + info!("ipc receiver thread did not quit gracefully: {}", err); + }) + } + + if let Err(err) = worker_command_sender.send(WorkerCommand::IPCWorkerStopped).await { + info!("Could not signal main worker {}", err); + } + })); + + Ok(returned_ipc_worker_sender) +} + + +fn close_ipc_receiver_thread(ipc_receiver_sender: IpcSender) -> Result<(), Box> { + let (ack_sender, ack_receiver) = ipc_channel::ipc::channel::<()>()?; + ipc_receiver_sender.send(IPCCommand::Stop(ack_sender))?; + ack_receiver.try_recv().map_err(|x| Error::new(ErrorKind::Other, format!("{:?}", x)))?; + Ok(()) +} diff --git a/arpegiator/src/workers/main_worker.rs b/arpegiator/src/workers/main_worker.rs new file mode 100644 index 0000000..005d5c8 --- /dev/null +++ b/arpegiator/src/workers/main_worker.rs @@ -0,0 +1,126 @@ +use std::thread; +use std::thread::JoinHandle; + +use async_channel::{Receiver, Sender, unbounded}; +use async_std::task; +use log::{error, info}; + +use util::midi_message_with_delta::MidiMessageWithDelta; +use util::ipc_payload::PatternPayload; + +use crate::workers::ipc_worker::{spawn_ipc_worker, IPCWorkerCommand}; +use crate::workers::midi_output_worker::{MidiOutputWorkerCommand, spawn_midi_output_worker}; +use std::mem::take; + + +#[derive(Debug)] +pub(crate) enum WorkerCommand { + Stop, + SetPort(u16), + SetSampleRate(f32), + SetBlockSize(i64), + SendToMidiOutput { buffer_start_time: u64, messages: Vec }, + IPCWorkerStopped, +} + +pub(crate) struct WorkerChannels { + pub command_sender: Sender, + pub pattern_receiver: Receiver, + pub worker: JoinHandle<()>, +} + + +pub(crate) fn create_worker_thread() -> WorkerChannels { + let (command_sender, command_receiver) = unbounded::(); + let (pattern_sender, pattern_receiver) = unbounded::(); + + let returned_command_sender = command_sender.clone(); + + let main = { + async move { + let mut midi_out_worker_sender : Option> = None; + let mut ipc_worker_sender : Option> = None; + + while let Ok(command) = command_receiver.recv().await { + match command { + WorkerCommand::SetPort(port) => { + info!("Switching to port {}", port); + + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + + { + let pattern_sender = pattern_sender.clone(); + let command_sender = command_sender.clone(); + + match spawn_ipc_worker(port, pattern_sender, command_sender) { + Ok(sender) => { + ipc_worker_sender = Some(sender); + } + Err(err) => { + error!("Cannot start ipc worker: {}", err); + continue; + } + } + } + + match spawn_midi_output_worker(format!("Arpegiator {}", port)) { + Ok(sender) => { + midi_out_worker_sender = Some(sender); + } + Err(_) => { + error!("Could not spawn midi output worker"); + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + } + } + } + + WorkerCommand::SendToMidiOutput { buffer_start_time, messages } => { + if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { + midi_out_worker_sender.send( + MidiOutputWorkerCommand::SendToController { buffer_start_time, messages } + ).await.unwrap(); + } + } + WorkerCommand::SetSampleRate(rate) => { + if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { + midi_out_worker_sender.send(MidiOutputWorkerCommand::SetSampleRate(rate)).await.unwrap(); + } + } + WorkerCommand::SetBlockSize(_size) => { + // not used + } + WorkerCommand::IPCWorkerStopped => { + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + } + WorkerCommand::Stop => { + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + } + } + } + } + }; + + let worker = thread::spawn(move || task::block_on(main)); + + WorkerChannels { + command_sender: returned_command_sender, + pattern_receiver, + worker + } +} + +async fn close_workers( + midi_out_worker_sender: &mut Option>, + ipc_worker_sender: &mut Option>) { + if let Some(ipc_worker_sender) = take(ipc_worker_sender) { + ipc_worker_sender.send(IPCWorkerCommand::Stop).await.unwrap_or_else(|err| { + error!("Could not contact worker sender for shutdown : {}", err); + }) + }; + + if let Some(midi_out_worker_sender) = take(midi_out_worker_sender) { + midi_out_worker_sender.send(MidiOutputWorkerCommand::Stop).await.unwrap_or_else(|err| { + error!("Could not contact midi output worker for shutdown : {}", err); + }) + }; +} diff --git a/arpegiator/src/workers/midi_output_worker.rs b/arpegiator/src/workers/midi_output_worker.rs new file mode 100644 index 0000000..562d8b9 --- /dev/null +++ b/arpegiator/src/workers/midi_output_worker.rs @@ -0,0 +1,120 @@ +#[allow(unused_imports)] +use log::{error, info}; + +use async_channel::{Sender, unbounded}; +use async_std::task; + +#[cfg(target_os = "macos")] +use { + coremidi::PacketBuffer +}; + +use midir::MidiInput; +#[cfg(target_os = "linux")] +use { + midir::os::unix::VirtualOutput, + midir::MidiOutput +}; +use midir::os::unix::VirtualInput; + +use util::midi_message_with_delta::MidiMessageWithDelta; + + +#[cfg(target_os = "macos")] +use crate::system::second_to_mach_timebase; +use async_std::io::ErrorKind; +use std::io::Error; +use std::error; + + +#[derive(Debug)] +pub(crate) enum MidiOutputWorkerCommand { + SendToController { buffer_start_time: u64, messages: Vec }, + Stop, + SetSampleRate(f32) +} + + +pub(crate) fn spawn_midi_output_worker(name: String) -> + Result, Box> { + + #[cfg(target_os = "macos")] + let (second_to_mach, mut sample_to_mach) : (f64, u64) = { + (second_to_mach_timebase(), 0) + }; + + #[cfg(target_os = "linux")] + #[allow(unused_mut)] + let mut midi_out_connection = { + info!("Creating midi device {}", name); + let midi_out = MidiOutput::new(&*name)?; + midi_out.create_virtual(&*name)? + }; + + #[cfg(target_os = "macos")] + let (_client, source) = { + let client = coremidi::Client::new(&*name).map_err( + |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) + ))?; + let source = client.virtual_source(&*name).map_err( + |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) + ))?; + (client, source) + }; + + let midi_in = MidiInput::new(&*name)?; + + // create input device just to ease setup. returned connection must not be dropped in order to keep the device alive + let mut _midi_in_connection = midi_in.create_virtual(&*name, |_time, _data, _| { + // noop for now + }, ()).map_err( + |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) + ))?; + + let (sender, receiver) = unbounded::(); + + task::spawn(async move { + + while let Ok(command) = receiver.recv().await { + match command { + MidiOutputWorkerCommand::SendToController { buffer_start_time, mut messages } => { + #[cfg(target_os = "linux")] + for message in messages { + // TODO timing is lost, we should actually wait until buffer_start_time and wait for the + // time corresponding to delta frame for each message + if let Err(err) = midi_out_connection.send(message.data.get_bytes()) { + error!("Error while sending midi message: {}", err); + break; + } + } + #[cfg(target_os = "macos")] + { + let message = messages.remove(0); + let mut buffer = PacketBuffer::new( + message.delta_frames as u64 * sample_to_mach + buffer_start_time, + message.data.get_bytes(), + ); + for message in messages { + buffer.push_data(message.delta_frames as u64 * sample_to_mach + buffer_start_time, + &message.data.get_bytes()); + } + source.received(&buffer).unwrap(); + } + } + MidiOutputWorkerCommand::Stop => { + info!("Stopping controller {}", name); + return; + } + MidiOutputWorkerCommand::SetSampleRate(rate) => { + #[cfg(target_os = "macos")] + { + let sample_to_second = 1.0 / rate as f64; + sample_to_mach = (sample_to_second * second_to_mach) as u64 + } + } + } + } + }); + + Ok(sender) +} diff --git a/arpegiator/src/workers/mod.rs b/arpegiator/src/workers/mod.rs new file mode 100644 index 0000000..86509ff --- /dev/null +++ b/arpegiator/src/workers/mod.rs @@ -0,0 +1,3 @@ +pub mod ipc_worker; +pub mod main_worker; +mod midi_output_worker; diff --git a/arpegiator_pattern_receiver/Cargo.toml b/arpegiator_pattern_receiver/Cargo.toml index 70c18e5..2bbeaca 100644 --- a/arpegiator_pattern_receiver/Cargo.toml +++ b/arpegiator_pattern_receiver/Cargo.toml @@ -7,11 +7,15 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } -arpegiator = { path = "../arpegiator" } build-info = "0.0.20" log = "0.4.11" serde = "1.0.118" bincode = "1.3.1" +async-std = "1.8.0" +async-channel = "1.5.1" +futures-lite = "1.11.3" +ipc-channel = "0.14.1" +itertools = "0.10.0" [build-dependencies] build-info-build = "0.0.20" @@ -19,3 +23,6 @@ build-info-build = "0.0.20" [lib] name = "arpegiator_pattern_receiver" crate-type = ["cdylib", "lib"] + +[target.'cfg(target_os = "macos")'.dependencies.mach] +version = "0.3" diff --git a/arpegiator_pattern_receiver/src/ipc_worker.rs b/arpegiator_pattern_receiver/src/ipc_worker.rs new file mode 100644 index 0000000..92f8421 --- /dev/null +++ b/arpegiator_pattern_receiver/src/ipc_worker.rs @@ -0,0 +1,91 @@ +#[allow(unused_imports)] +use log::{error, info}; + +use std::{thread, error}; +use async_channel::{Sender, Receiver}; +use async_std::net::UdpSocket; +use ipc_channel::ipc::IpcSender; + +use util::ipc_payload::{PatternPayload, IPCCommand}; +use std::net::ToSocketAddrs; +use async_std::task; +use std::time::Duration; + + +pub(crate) enum IPCWorkerCommand { + Stop, + SetPort(u16), + Send(PatternPayload), +} + + +async fn try_udp_send_receiver(port: u16) -> Result, Box> { + let socket = UdpSocket::bind("127.0.0.1:0").await?; + let to = format!("127.0.0.1:{}", port).to_socket_addrs()?.next().ok_or("empty list")?; + + let (ipc_sender, ipc_receiver) = ipc_channel::ipc::channel::()?; + + let serialized_ipc_receiver = bincode::serialize(&ipc_receiver)?; + socket.send_to(&*serialized_ipc_receiver, to).await?; + task::sleep(Duration::new(1, 0)).await; + + ipc_sender.send(IPCCommand::Ping)?; + + Ok(ipc_sender) +} + + +async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_receiver: Receiver) { + let mut port = None; + let mut ipc_sender: Option> = None; + + while let Ok(command) = ipc_worker_receiver.recv().await { + match command { + IPCWorkerCommand::Stop => { + break; + } + IPCWorkerCommand::SetPort(new_port) => { + port = Some(new_port); + ipc_sender = match try_udp_send_receiver(new_port).await { + Ok(ipc_sender) => Some(ipc_sender), + Err(err) => { + error!("Error while connecting to arpegiator on port {} : {}", port.unwrap(), err); + let ipc_worker_sender = ipc_worker_sender.clone(); + task::spawn(async move { + task::sleep(Duration::new(1,0)).await; + ipc_worker_sender.send(IPCWorkerCommand::SetPort(port.unwrap())).await.unwrap(); + }); + None + } + }; + } + IPCWorkerCommand::Send(payload) => { + let ipc_sender_ref = match ipc_sender.as_ref() { + None => { + error!("IPC not ready, ignoring {:?}", payload); + continue; + } + Some(ipc_sender) => ipc_sender + }; + + if let Err(err) = ipc_sender_ref.send(IPCCommand::PatternPayload(payload)) { + error!("IPC failed ; will attempt to reconnect ({})", err); + ipc_sender = None; + ipc_worker_sender.send(IPCWorkerCommand::SetPort(port.unwrap())).await.unwrap(); + }; + } + } + } +} + + +pub(crate) fn spawn_ipc_worker() -> Sender { + let (worker_sender, worker_receiver) = async_channel::unbounded(); + { + let worker_sender = worker_sender.clone(); + thread::spawn(move || task::block_on( + ipc_worker(worker_sender, worker_receiver) + )) + }; + worker_sender +} diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index f3342cd..3007cb0 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -1,6 +1,9 @@ -use std::thread::JoinHandle; - +use std::mem::take; +use std::sync::Arc; use log::{info, error}; + +use async_channel::Sender; + use vst::api; use vst::buffer::AudioBuffer; use vst::event::Event; @@ -8,15 +11,13 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use util::logging::logging_setup; use util::midi_message_with_delta::MidiMessageWithDelta; -use crate::socket::{SenderSocketCommand, create_socket_thread}; -use std::mem::take; +use util::ipc_payload::PatternPayload; + +use crate::ipc_worker::{IPCWorkerCommand, spawn_ipc_worker}; use crate::parameters::ArpegiatorPatternReceiverParameters; -use std::sync::Arc; -use util::pattern_payload::PatternPayload; -use std::sync::mpsc::Sender; mod parameters; -mod socket; +mod ipc_worker; #[macro_use] extern crate vst; @@ -27,8 +28,7 @@ plugin_main!(ArpegiatorPatternReceiver); struct ArpegiatorPatternReceiver { #[allow(dead_code)] host: HostCallback, - socket_thread_handle: Option>, - socket_channel_sender: Option>, + ipc_worker_sender: Option>, messages: Vec, current_time: usize, parameters: Arc @@ -39,8 +39,7 @@ impl Default for ArpegiatorPatternReceiver { fn default() -> Self { ArpegiatorPatternReceiver { host: Default::default(), - socket_thread_handle: None, - socket_channel_sender: None, + ipc_worker_sender: None, messages: vec![], current_time: 0, parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) @@ -49,15 +48,11 @@ impl Default for ArpegiatorPatternReceiver { } impl ArpegiatorPatternReceiver { - fn close_socket(&mut self) { - if let Some(sender) = take(&mut self.socket_channel_sender) { - if let Err(e) = sender.send(SenderSocketCommand::Stop) { - error!("Error while closing sender channel : {:?} {}", e, e) - } - } - - if let Some(thread_handle) = take(&mut self.socket_thread_handle) { - thread_handle.join().unwrap(); + fn stop_worker(&mut self) { + if let Some(sender) = take(&mut self.ipc_worker_sender) { + sender.try_send(IPCWorkerCommand::Stop).unwrap_or_else(|err| { + error!("Error while closing sender channel : {}", err) + }); } } } @@ -87,18 +82,17 @@ impl Plugin for ArpegiatorPatternReceiver { fn resume(&mut self) { self.current_time = 0 ; - let (join_handle, sender) = create_socket_thread(); - self.socket_thread_handle = Some(join_handle) ; - self.socket_channel_sender = Some(sender.clone()); - sender.send(SenderSocketCommand::SetPort(self.parameters.get_port())).unwrap(); + let sender= spawn_ipc_worker(); + self.ipc_worker_sender = Some(sender.clone()); + sender.try_send(IPCWorkerCommand::SetPort(self.parameters.get_port())).unwrap(); - if let Ok(mut socket_command) = self.parameters.socket_command.lock() { + if let Ok(mut socket_command) = self.parameters.ipc_worker_sender.lock() { *socket_command = Some(sender); } } fn suspend(&mut self) { - self.close_socket() + self.stop_worker() } fn new(host: HostCallback) -> Self { @@ -108,8 +102,7 @@ impl Plugin for ArpegiatorPatternReceiver { ArpegiatorPatternReceiver { host, - socket_thread_handle: None, - socket_channel_sender: None, + ipc_worker_sender: None, messages: vec![], current_time: 0, parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) @@ -136,29 +129,33 @@ impl Plugin for ArpegiatorPatternReceiver { fn process(&mut self, buffer: &mut AudioBuffer) { if !self.messages.is_empty() { - if let Some(sender) = &self.socket_channel_sender { + if let Some(ipc_worker_sender) = &self.ipc_worker_sender { let payload = PatternPayload { - time: self.current_time, + #[cfg(target_os = "macos")] + time: unsafe { mach::mach_time::mach_absolute_time() }, messages: take(&mut self.messages) } ; - sender.send(SenderSocketCommand::Send(payload)).unwrap() + ipc_worker_sender.try_send(IPCWorkerCommand::Send(payload)).unwrap() + } else { + self.messages.clear(); } - self.messages.clear(); } self.current_time += buffer.samples() } fn process_events(&mut self, events: &api::Events) { - if self.socket_channel_sender.is_some() { - for e in events.events() { - if let Event::Midi(e) = e { - self.messages.push( MidiMessageWithDelta { - delta_frames: e.delta_frames as u16, - data: e.data - }); - } - } + if self.ipc_worker_sender.is_some() { + self.messages.extend(events.events().map(|event| match event { + Event::Midi(event) => Ok(MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: event.data.into() + }), + Event::SysEx(_) => Err(()), + Event::Deprecated(_) => Err(()) + }).filter(|item| item.is_ok()).map(|item| item.unwrap())); + + //|midi_event| midi_event.unwrap()) } } @@ -169,6 +166,6 @@ impl Plugin for ArpegiatorPatternReceiver { impl Drop for ArpegiatorPatternReceiver { fn drop(&mut self) { - self.close_socket(); + self.stop_worker(); } } diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs index 1b47b6a..6f2da7e 100644 --- a/arpegiator_pattern_receiver/src/parameters.rs +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -6,17 +6,18 @@ use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; use util::parameter_value_conversion::f32_to_byte; -use crate::socket::SenderSocketCommand; +use crate::ipc_worker::IPCWorkerCommand; use std::sync::Mutex; -use std::sync::mpsc::Sender; +use async_channel::Sender; +use std::error; const PARAMETER_COUNT: usize = 1; const BASE_PORT: u16 = 6000; -pub struct ArpegiatorPatternReceiverParameters { +pub(crate) struct ArpegiatorPatternReceiverParameters { pub transfer: ParameterTransfer, - pub socket_command: Mutex>> + pub ipc_worker_sender: Mutex>> } impl ArpegiatorPatternReceiverParameters { @@ -24,13 +25,12 @@ impl ArpegiatorPatternReceiverParameters { BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 } - fn update_port(&self) { + fn update_port(&self) -> Result<(), Box> { let port = self.get_byte_parameter(Parameter::PortIndex); - if self.socket_command.lock().unwrap().as_ref().unwrap().send( - SenderSocketCommand::SetPort(BASE_PORT + port as u16) - ).is_err() { - error!("Socket thread is shutdown, ignoring port change") - } + self.ipc_worker_sender.lock()?.as_ref(). + ok_or("cannot unlock ipc worker sender mutex")?. + try_send(IPCWorkerCommand::SetPort(BASE_PORT + port as u16))?; + Ok(()) } } @@ -71,7 +71,7 @@ impl ArpegiatorPatternReceiverParameters { pub fn new() -> Self { ArpegiatorPatternReceiverParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), - socket_command: Mutex::new(None) + ipc_worker_sender: Mutex::new(None) } } } @@ -103,7 +103,9 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { let old_value = self.get_byte_parameter(Parameter::PortIndex); if old_value != new_value { self.transfer.set_parameter(index as usize, value); - self.update_port(); + self.update_port().unwrap_or_else(|err| { + error!("Could not update port: {}", err); + }); } } } @@ -119,11 +121,15 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { fn load_preset_data(&self, data: &[u8]) { self.deserialize_state(data); - self.update_port() + self.update_port().unwrap_or_else(|err| { + error!("Could not update port: {}", err); + }); } fn load_bank_data(&self, data: &[u8]) { self.deserialize_state(data); - self.update_port() + self.update_port().unwrap_or_else(|err| { + error!("Could not update port: {}", err); + }); } } diff --git a/arpegiator_pattern_receiver/src/socket.rs b/arpegiator_pattern_receiver/src/socket.rs deleted file mode 100644 index f43d16a..0000000 --- a/arpegiator_pattern_receiver/src/socket.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::net::{SocketAddr, ToSocketAddrs, UdpSocket}; -use std::thread; -use std::thread::JoinHandle; - -use util::pattern_payload::PatternPayload; -use std::sync::mpsc::{channel, Sender}; - -pub enum SenderSocketCommand { - Stop, - SetPort(u16), - Send(PatternPayload), -} - - -pub fn create_socket_thread() -> (JoinHandle<()>, Sender) { - let socket = UdpSocket::bind("127.0.0.1:0").unwrap(); - let (sender, receiver) = channel::(); - - let handle = thread::spawn(move || { - let mut to: Option = None; - - while let Ok(command) = receiver.recv() { - match command { - SenderSocketCommand::Stop => { - return; - } - SenderSocketCommand::SetPort(port) => { - to = format!("127.0.0.1:{}", port).to_socket_addrs().unwrap().next() - } - SenderSocketCommand::Send(payload) => { - if let Some(port_to) = to { - socket.send_to(&*bincode::serialize(&payload).unwrap(), port_to).unwrap(); - } - } - } - } - }); - - (handle, sender) -} diff --git a/midi_delay/src/lib.rs b/midi_delay/src/lib.rs index 35d4f4a..78300ad 100644 --- a/midi_delay/src/lib.rs +++ b/midi_delay/src/lib.rs @@ -50,7 +50,7 @@ impl MidiDelay { } #[allow(dead_code)] - fn seconds_per_sample(&self) -> f32 { + fn samples_to_seconds(&self) -> f32 { 1.0 / self.sample_rate } diff --git a/util/Cargo.toml b/util/Cargo.toml index 970628a..cdcdfaa 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -11,6 +11,8 @@ log = "0.4.11" simplelog = "0.9.0" build-info = "0.0.20" serde = "1.0.118" +ipc-channel = "0.14.1" +async-channel = "1.5.1" [dependencies.log-panics] version = "2.0.0" diff --git a/util/src/ipc_payload.rs b/util/src/ipc_payload.rs new file mode 100644 index 0000000..ace9222 --- /dev/null +++ b/util/src/ipc_payload.rs @@ -0,0 +1,22 @@ +use serde::{Deserialize, Serialize}; + +use crate::midi_message_with_delta::MidiMessageWithDelta; +use ipc_channel::ipc::IpcSender; + + +#[derive(Debug, Serialize, Deserialize)] +pub struct PatternPayload { + #[cfg(target_os = "macos")] + pub time: u64, + pub messages: Vec, +} + + +#[derive(Debug, Serialize, Deserialize)] +pub enum IPCCommand { + PatternPayload(PatternPayload), + // stop is only used locally, but send over an IPC channel so the worker can listen both on the remote IPC + // for new patterns, and on local IPC for stopping the worker + Stop(IpcSender<()>), + Ping +} diff --git a/util/src/lib.rs b/util/src/lib.rs index f1f91e1..9607564 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -16,7 +16,7 @@ pub mod delayed_message_consumer; pub mod transmute_buffer; pub mod logging; pub mod midi_message_with_delta; -pub mod pattern_payload; +pub mod ipc_payload; #[derive(Default)] pub struct HostCallbackLock { diff --git a/util/src/midi_message_with_delta.rs b/util/src/midi_message_with_delta.rs index b6ca3f8..2e5d378 100644 --- a/util/src/midi_message_with_delta.rs +++ b/util/src/midi_message_with_delta.rs @@ -1,18 +1,19 @@ use serde::{Serialize, Deserialize}; use vst::event::MidiEvent; +use crate::raw_message::RawMessage; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct MidiMessageWithDelta { pub delta_frames: u16, - pub data: [u8; 3], + pub data: RawMessage, } impl MidiMessageWithDelta { pub fn new_midi_event(&self) -> MidiEvent { MidiEvent { - data: self.data, + data: self.data.into(), delta_frames: self.delta_frames as i32, live: true, note_length: None, diff --git a/util/src/pattern_payload.rs b/util/src/pattern_payload.rs deleted file mode 100644 index 5f1e343..0000000 --- a/util/src/pattern_payload.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::midi_message_with_delta::MidiMessageWithDelta; - -#[derive(Debug, Serialize, Deserialize)] -pub struct PatternPayload { - pub time: usize, - pub messages: Vec, -} diff --git a/util/src/raw_message.rs b/util/src/raw_message.rs index 0c979d9..15011cd 100644 --- a/util/src/raw_message.rs +++ b/util/src/raw_message.rs @@ -1,11 +1,26 @@ use core::clone::Clone; use core::convert::{From, Into}; use core::ops::Index; +use serde::{Serialize, Deserialize}; use super::messages::ChannelMessage; +use crate::constants::PRESSURE; -#[derive(Copy, Debug)] +#[derive(Copy, Debug, Serialize, Deserialize)] pub struct RawMessage([u8; 3]); + +impl RawMessage { + pub fn get_bytes(&self) -> &[u8] { + // RawMessage is 3 bytes long to keep moving around a known-size message, but some actually are 2 bytes long + if self.0[0] & 0xF0 == PRESSURE { + &self.0[..2] + } else { + &self.0 + } + + } +} + impl ChannelMessage for RawMessage { fn get_channel(&self) -> u8 { self.0[0] & 0x0F @@ -30,6 +45,7 @@ impl Into<[u8;3]> for RawMessage { } } + impl Index for RawMessage { type Output = u8; From 3dc23852fa6781110d96cc92795fa74cfb5e369b Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 9 Jan 2021 16:14:53 +0100 Subject: [PATCH 14/47] unhandled error --- arpegiator/src/workers/ipc_worker.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arpegiator/src/workers/ipc_worker.rs b/arpegiator/src/workers/ipc_worker.rs index 4c0f6e1..dc442eb 100644 --- a/arpegiator/src/workers/ipc_worker.rs +++ b/arpegiator/src/workers/ipc_worker.rs @@ -106,7 +106,9 @@ pub(crate) fn spawn_ipc_worker(port: u16, match command { IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket) => { if ipc_receiver_sender.is_some() { - close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap()); + if let Err(err) = close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap()) { + error!("Error while shutting down ipc receiver worker {}", err) + } } let ipc_worker_sender = ipc_worker_sender.clone(); From cbaa31cdd7a57b71251d5bd839851c14a7c2539f Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 9 Jan 2021 20:58:25 +0100 Subject: [PATCH 15/47] add more event traces to debug worker hangup --- Cargo.lock | 2 + arpegiator/Cargo.toml | 3 +- arpegiator/src/lib.rs | 61 ++++++++--- arpegiator/src/parameters.rs | 19 ++-- arpegiator/src/workers/ipc_worker.rs | 100 +++++++++++++------ arpegiator/src/workers/main_worker.rs | 85 ++++++++++++---- arpegiator/src/workers/midi_output_worker.rs | 10 +- util/Cargo.toml | 1 + util/src/ipc_payload.rs | 3 +- util/src/lib.rs | 1 + util/src/system.rs | 30 ++++++ 11 files changed, 237 insertions(+), 78 deletions(-) create mode 100644 util/src/system.rs diff --git a/Cargo.lock b/Cargo.lock index 8046080..2557acf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,6 +70,7 @@ dependencies = [ "midir", "num-traits", "util", + "uuid", "vst", ] @@ -1506,6 +1507,7 @@ dependencies = [ "log-panics", "serde", "simplelog", + "uuid", "vst", ] diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 33ce4aa..064ee16 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -17,6 +17,7 @@ async-std = "1.8.0" async-channel = "1.5.1" futures-lite = "1.11.3" ipc-channel = "0.14.1" +uuid = "0.8.1" [build-dependencies] build-info-build = "0.0.20" @@ -26,7 +27,7 @@ name = "arpegiator" crate-type = ["cdylib", "lib"] [features] -default = ["use_channel_pressure", "forward_pattern_cc"] +default = ["use_channel_pressure", "forward_pattern_cc", "worker_debug"] use_channel_pressure = [] worker_debug = [] forward_note_cc = [] diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 7c9091a..15826b5 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -25,6 +25,7 @@ use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT}; use crate::midi_messages::change::SourceChange; use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; use crate::midi_messages::timed_event::TimedEvent; +use util::system::Uuid; mod midi_messages; mod workers; @@ -51,17 +52,19 @@ pub struct ArpegiatorPlugin { device_out: DeviceOut, parameters: Arc, worker_channels: Option, + resumed: bool } impl ArpegiatorPlugin { - fn close_worker(&mut self) { + fn close_worker(&mut self, event_id: Uuid) { if let Some(worker_channels) = take(&mut self.worker_channels) { - if let Err(e) = worker_channels.command_sender.try_send(WorkerCommand::Stop) { - error!("Error while closing worker channel : {:?}", e) + #[cfg(feature = "worker_debug")] info!("[{}] stopping workers", event_id); + if let Err(e) = worker_channels.command_sender.try_send(WorkerCommand::Stop(event_id)) { + error!("[{}] Error while closing worker channel : {:?}", event_id, e) } if let Err(err) = worker_channels.worker.join() { - error!("Error while waiting for worker thread to finish {:?}", err) + error!("[{}] Error while waiting for worker thread to finish {:?}", event_id, err) } } } @@ -82,6 +85,7 @@ impl Default for ArpegiatorPlugin { device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), worker_channels: None, + resumed: false } } } @@ -139,28 +143,57 @@ impl Plugin for ArpegiatorPlugin { device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), worker_channels: None, + resumed: false } } fn resume(&mut self) { - self.close_worker(); + if self.resumed { + info!("Already resumed"); + return; + } + self.resumed = true; + + let event_id = Uuid::new_v4() ; + + #[cfg(feature = "worker_debug")] info!("[{}] resume: enter", event_id); + self.close_worker(event_id); self.current_time_in_samples = 0; let worker_channels = create_worker_thread(); - - worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port())).unwrap(); + worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port(), event_id)).unwrap(); worker_channels.command_sender.try_send(WorkerCommand::SetSampleRate(self.sample_rate)).unwrap(); - if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { - *worker_commands = Some(worker_channels.command_sender.clone()); - } + self.worker_channels = match self.parameters.worker_commands.lock() { + Ok(mut worker_commands) => { + *worker_commands = Some(worker_channels.command_sender.clone()); + Some(worker_channels) + } + Err(err) => { + error!("[{}] Could not get parameters lock: {}", event_id, err); + None + } + }; - self.worker_channels = Some(worker_channels); + #[cfg(feature = "worker_debug")] info!("[{}] resume: exit", event_id); } fn suspend(&mut self) { - self.close_worker() + if !self.resumed { + info!("Already suspended"); + return; + } + let event_id = Uuid::new_v4(); + + self.resumed = false; + #[cfg(feature = "worker_debug")] info!("[{}] suspend enter", event_id); + if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { + *worker_commands = None + } + + self.close_worker(event_id); + #[cfg(feature = "worker_debug")] info!("[{}] suspend exit", event_id); } fn get_parameter_object(&mut self) -> Arc { @@ -387,6 +420,8 @@ impl Plugin for ArpegiatorPlugin { impl Drop for ArpegiatorPlugin { fn drop(&mut self) { - self.close_worker(); + let event_id = Uuid::new_v4(); + info!("[{}] Dropping plugin", event_id); + self.close_worker(event_id); } } diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 7061890..9f7c49f 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -10,6 +10,7 @@ use crate::workers::main_worker::WorkerCommand; use std::sync::Mutex; use async_channel::Sender; use util::duration_display; +use util::system::Uuid; pub const PARAMETER_COUNT: usize = 4; @@ -54,13 +55,13 @@ impl ArpegiatorParameters { BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 } - pub fn update_port(&self) { + pub fn update_port(&self, event_id: Uuid) { let port = self.get_byte_parameter(Parameter::PortIndex) as u16 + BASE_PORT; info!("Applying parameter change: port={}", port); if let Err(error) = self.worker_commands.lock().unwrap().as_ref().unwrap().try_send( - WorkerCommand::SetPort(port) + WorkerCommand::SetPort(port, event_id) ) { - info!("main worker is shutdown - ignoring port change ({})", error); + info!("[{}] main worker is shutdown - ignoring port change ({})", event_id, error); } } } @@ -166,8 +167,10 @@ impl PluginParameters for ArpegiatorParameters { let new_value = f32_to_byte(value); let old_value = self.get_byte_parameter(Parameter::PortIndex); if old_value != new_value { + let event_id = Uuid::new_v4(); + info!("[{}] set parameter port {}", event_id, BASE_PORT + new_value as u16); self.transfer.set_parameter(index as usize, value); - self.update_port() + self.update_port(event_id) } } Parameter::HoldNotes | Parameter::PatternLegato => { @@ -190,12 +193,16 @@ impl PluginParameters for ArpegiatorParameters { } fn load_preset_data(&self, data: &[u8]) { + let event_id = Uuid::new_v4(); + info!("[{}] Load present data", event_id); self.deserialize_state(data); - self.update_port() + self.update_port(event_id) } fn load_bank_data(&self, data: &[u8]) { + let event_id = Uuid::new_v4(); + info!("[{}] Load bank data", event_id); self.deserialize_state(data); - self.update_port() + self.update_port(event_id) } } diff --git a/arpegiator/src/workers/ipc_worker.rs b/arpegiator/src/workers/ipc_worker.rs index dc442eb..01d95dd 100644 --- a/arpegiator/src/workers/ipc_worker.rs +++ b/arpegiator/src/workers/ipc_worker.rs @@ -12,36 +12,44 @@ use util::ipc_payload::{PatternPayload, IPCCommand}; use crate::workers::main_worker::WorkerCommand; use std::{thread, error}; use std::mem::take; +use util::system::Uuid; pub(crate) enum IPCWorkerCommand { - SocketReceive(IpcReceiver), - Stop, - IPCDisconnect, - PayloadReceived(PatternPayload), + SocketReceive(IpcReceiver, Uuid), + Stop(Sender<()>, Uuid), + IPCDisconnect(Uuid), + PayloadReceived(PatternPayload, Uuid), } async fn udp_receive_worker(socket: UdpSocket, sender: Sender) { let mut buf = vec![0u8; 1024]; + let mut exit_event_id = Uuid::default(); while let Ok(len) = socket.recv(&mut buf).await { + exit_event_id = Uuid::new_v4(); + let ipc_worker_command = match bincode::deserialize::>(&buf[..len]) { Ok(ipc_receiver) => { - info!("Received IPC Receiver via UDP"); - IPCWorkerCommand::SocketReceive(ipc_receiver) + info!("[{}] Received IPC Receiver via UDP", exit_event_id); + IPCWorkerCommand::SocketReceive(ipc_receiver, exit_event_id) } Err(err) => { - error!("Ignoring invalid UDP payload ({}) - expected ICP receiver", err); + error!("[{}] Ignoring invalid UDP payload ({}) - expected ICP receiver", exit_event_id, err); continue; } }; if let Err(err) = sender.send(ipc_worker_command).await { - error!("UDP Worker: error while trying to send worker command {}, quitting UDP Worker", err); + error!("[{}] UDP Worker: error while trying to send worker command {}, quitting UDP Worker", + exit_event_id, err); break; } }; + + #[cfg(feature = "worker_debug")] + info!("[{}] Leaving udp receiver worker", exit_event_id) } @@ -54,31 +62,37 @@ fn spawn_ipc_receiver_thread(ipc_receiver: IpcReceiver, set.add(receiver)?; thread::spawn(|| task::block_on(async move { - while let Ok(results) = set.select() { + let mut exit_event_id = Uuid::default(); + + #[cfg(feature = "worker_debug")] info!("started ipc worker"); + 'mainloop: while let Ok(results) = set.select() { for result in results { let (_, opaque_message) = result.unwrap(); match opaque_message.to::().unwrap() { IPCCommand::PatternPayload(payload) => { - if let Err(err) = ipc_worker_sender.send(IPCWorkerCommand::PayloadReceived(payload)).await { - error!("could not send payload to ipc worker {}", err); - break; + let event_id = Uuid::new_v4(); + if let Err(err) = ipc_worker_sender.send(IPCWorkerCommand::PayloadReceived(payload, event_id)).await { + exit_event_id = event_id; + error!("[{}] could not send payload to ipc worker {}", exit_event_id, err); + break 'mainloop; } } IPCCommand::Ping => { info!("Received ping from peer") } - IPCCommand::Stop(ack_channel_sender) => { + IPCCommand::Stop(ack_channel_sender, event_id) => { + exit_event_id = event_id; + #[cfg(feature = "worker_debug")] info!("[{}] Stopping IPC worker thread", exit_event_id); ack_channel_sender.send(()).unwrap_or_else(|err| { - error!("Could not signal ipc thread stop {}", err); + error!("[{}] Could not signal ipc thread stop {}", err, exit_event_id); }); - info!("Stopping IPC worker thread"); - break; + break 'mainloop; } } } }; - ipc_worker_sender.try_send(IPCWorkerCommand::IPCDisconnect).unwrap_or_else(|err| { - error!("Error {} while signaling IPC receiver quitting", err); + ipc_worker_sender.try_send(IPCWorkerCommand::IPCDisconnect(exit_event_id)).unwrap_or_else(|err| { + error!("[{}] Error {} while signaling IPC receiver quitting", exit_event_id, err); }); })); @@ -102,12 +116,14 @@ pub(crate) fn spawn_ipc_worker(port: u16, udp_receive_worker(socket, ipc_worker_sender.clone()) ); + let mut exit_event_id : Uuid = Uuid::default(); + while let Ok(command) = ipc_worker_receiver.recv().await { match command { - IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket) => { + IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket, event_id) => { if ipc_receiver_sender.is_some() { - if let Err(err) = close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap()) { - error!("Error while shutting down ipc receiver worker {}", err) + if let Err(err) = close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap(), event_id) { + error!("[{}] Error while shutting down ipc receiver worker {}", event_id, err) } } @@ -117,42 +133,60 @@ pub(crate) fn spawn_ipc_worker(port: u16, ipc_receiver_sender = Some(sender); }; } - IPCWorkerCommand::Stop => { - info!("Quitting socket port {}", port); + IPCWorkerCommand::Stop(sender, event_id) => { + exit_event_id = event_id; + match sender.send(()).await { + Ok(_) => { info!("[{}] Quitting socket port {}", exit_event_id, port); } + Err(err) => { info!("[{}] Error while quitting socket port {}: {}", exit_event_id, port, err); } + } break; } - IPCWorkerCommand::IPCDisconnect => { + IPCWorkerCommand::IPCDisconnect(event_id) => { ipc_receiver_sender = None; - error!("IPC Receiver disconnected") + error!("[{}] IPC Receiver disconnected", event_id) } - IPCWorkerCommand::PayloadReceived(payload) => { + IPCWorkerCommand::PayloadReceived(payload, event_id) => { if let Err(err) = pattern_sender.send(payload).await { - error!("IPC worker: notes sender channel error, quitting ({})", err); + exit_event_id = event_id; + error!("[{}] IPC worker: notes sender channel error, quitting ({})", exit_event_id, err); break; } } } } + #[cfg(feature = "worker_debug")] info!("[{}] stopping udp worker on port {}", exit_event_id, port); udp_worker_handle.cancel().await; + #[cfg(feature = "worker_debug")] info!("[{}] udp worker on port {} stopped", exit_event_id, port); + if let Some(sender) = take(&mut ipc_receiver_sender) { - close_ipc_receiver_thread(sender).unwrap_or_else(|err| { - info!("ipc receiver thread did not quit gracefully: {}", err); + close_ipc_receiver_thread(sender, exit_event_id).unwrap_or_else(|err| { + info!("[{}] ipc receiver thread did not quit gracefully: {}", exit_event_id, err); }) } - if let Err(err) = worker_command_sender.send(WorkerCommand::IPCWorkerStopped).await { - info!("Could not signal main worker {}", err); + if let Err(err) = worker_command_sender.send(WorkerCommand::IPCWorkerStopped(exit_event_id, port)).await { + info!("[{}] Could not signal main worker {}", err, exit_event_id); } + + #[cfg(feature = "worker_debug")] info!("[{}] exiting ipc worker ( port {} )", exit_event_id, port); })); Ok(returned_ipc_worker_sender) } -fn close_ipc_receiver_thread(ipc_receiver_sender: IpcSender) -> Result<(), Box> { +fn close_ipc_receiver_thread(ipc_receiver_sender: IpcSender, event_id: Uuid) -> Result<(), Box> { + #[cfg(feature = "worker_debug")] info!("[{}] close_ipc_receiver_thread: enter", event_id); let (ack_sender, ack_receiver) = ipc_channel::ipc::channel::<()>()?; - ipc_receiver_sender.send(IPCCommand::Stop(ack_sender))?; + + #[cfg(feature = "worker_debug")] info!("[{}] stopping ipc receiver", event_id); + ipc_receiver_sender.send(IPCCommand::Stop(ack_sender, event_id))?; + + #[cfg(feature = "worker_debug")] info!("[{}] waiting for ack from ipc receiver", event_id); ack_receiver.try_recv().map_err(|x| Error::new(ErrorKind::Other, format!("{:?}", x)))?; + + #[cfg(feature = "worker_debug")] info!("[{}] stopped ipc receiver", event_id); Ok(()) } diff --git a/arpegiator/src/workers/main_worker.rs b/arpegiator/src/workers/main_worker.rs index 005d5c8..9090551 100644 --- a/arpegiator/src/workers/main_worker.rs +++ b/arpegiator/src/workers/main_worker.rs @@ -11,16 +11,17 @@ use util::ipc_payload::PatternPayload; use crate::workers::ipc_worker::{spawn_ipc_worker, IPCWorkerCommand}; use crate::workers::midi_output_worker::{MidiOutputWorkerCommand, spawn_midi_output_worker}; use std::mem::take; +use util::system::Uuid; #[derive(Debug)] pub(crate) enum WorkerCommand { - Stop, - SetPort(u16), + Stop(Uuid), + SetPort(u16, Uuid), SetSampleRate(f32), SetBlockSize(i64), SendToMidiOutput { buffer_start_time: u64, messages: Vec }, - IPCWorkerStopped, + IPCWorkerStopped(Uuid, u16), } pub(crate) struct WorkerChannels { @@ -37,16 +38,25 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { let returned_command_sender = command_sender.clone(); let main = { + #[cfg(feature = "worker_debug")] info!("starting workers"); + async move { + let mut current_port : Option = None; let mut midi_out_worker_sender : Option> = None; let mut ipc_worker_sender : Option> = None; + let mut exit_event_id = Uuid::default(); + let mut exit_reason = ""; while let Ok(command) = command_receiver.recv().await { match command { - WorkerCommand::SetPort(port) => { - info!("Switching to port {}", port); + WorkerCommand::SetPort(port, event_id) => { + if current_port.is_some() && current_port.unwrap() == port { + continue; + } + info!("[{}] Switching to port {}", event_id, port); + current_port = Some(port); - close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender, event_id).await; { let pattern_sender = pattern_sender.clone(); @@ -57,8 +67,12 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { ipc_worker_sender = Some(sender); } Err(err) => { - error!("Cannot start ipc worker: {}", err); - continue; + exit_event_id = event_id; + exit_reason = "Cannot start ipc worker"; + + #[cfg(feature = "worker_debug")] + error!("[{}] Cannot start ipc worker: {}", exit_event_id, err); + break; } } } @@ -68,8 +82,13 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { midi_out_worker_sender = Some(sender); } Err(_) => { - error!("Could not spawn midi output worker"); - close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + exit_event_id = event_id; + exit_reason = "Could not spawn midi output worker"; + + #[cfg(feature = "worker_debug")] + error!("[{}] Could not spawn midi output worker", exit_event_id); + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender, exit_event_id).await; + break; } } } @@ -89,14 +108,26 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { WorkerCommand::SetBlockSize(_size) => { // not used } - WorkerCommand::IPCWorkerStopped => { - close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + WorkerCommand::IPCWorkerStopped(event_id, ipc_worker_port) => { + if current_port.is_none() || current_port.unwrap() != ipc_worker_port { + continue; + } + exit_reason = "IPC worker stopped"; + exit_event_id = event_id; + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender, event_id).await; + break; } - WorkerCommand::Stop => { - close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender).await; + WorkerCommand::Stop(event_id) => { + exit_reason = "Stop received"; + exit_event_id = event_id; + close_workers(&mut midi_out_worker_sender, &mut ipc_worker_sender, event_id).await; + break; } } } + + #[cfg(feature = "worker_debug")] + info!("[{}] exiting main worker: {}", exit_event_id, exit_reason) } }; @@ -111,16 +142,28 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { async fn close_workers( midi_out_worker_sender: &mut Option>, - ipc_worker_sender: &mut Option>) { + ipc_worker_sender: &mut Option>, + event_id: Uuid +) { if let Some(ipc_worker_sender) = take(ipc_worker_sender) { - ipc_worker_sender.send(IPCWorkerCommand::Stop).await.unwrap_or_else(|err| { - error!("Could not contact worker sender for shutdown : {}", err); - }) + let (ack_sender, ack_receiver) = async_channel::bounded(1); + ipc_worker_sender.send(IPCWorkerCommand::Stop(ack_sender, event_id)).await.unwrap_or_else(|err| { + error!("[{}] Could not contact worker sender for shutdown : {}", event_id, err); + }); + match ack_receiver.recv().await { + Ok(_) => { error!("[{}] ipc worker exit ack", event_id) } + Err(err) => { error!("[{}] ipc worker did not ack exit: {}", event_id, err) } + } }; if let Some(midi_out_worker_sender) = take(midi_out_worker_sender) { - midi_out_worker_sender.send(MidiOutputWorkerCommand::Stop).await.unwrap_or_else(|err| { - error!("Could not contact midi output worker for shutdown : {}", err); - }) + let (ack_sender, ack_receiver) = async_channel::bounded(1); + midi_out_worker_sender.send(MidiOutputWorkerCommand::Stop(ack_sender, event_id)).await.unwrap_or_else(|err| { + error!("[{}] Could not contact midi output worker for shutdown : {}", event_id, err); + }); + match ack_receiver.recv().await { + Ok(_) => { info!("[{}] midi out worker exit ack", event_id) } + Err(err) => { error!("[{}] midi out did not ack exit: {}", event_id, err) } + } }; } diff --git a/arpegiator/src/workers/midi_output_worker.rs b/arpegiator/src/workers/midi_output_worker.rs index 562d8b9..66988a1 100644 --- a/arpegiator/src/workers/midi_output_worker.rs +++ b/arpegiator/src/workers/midi_output_worker.rs @@ -25,12 +25,13 @@ use crate::system::second_to_mach_timebase; use async_std::io::ErrorKind; use std::io::Error; use std::error; +use util::system::Uuid; #[derive(Debug)] pub(crate) enum MidiOutputWorkerCommand { SendToController { buffer_start_time: u64, messages: Vec }, - Stop, + Stop(Sender<()>, Uuid), SetSampleRate(f32) } @@ -101,8 +102,11 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> source.received(&buffer).unwrap(); } } - MidiOutputWorkerCommand::Stop => { - info!("Stopping controller {}", name); + MidiOutputWorkerCommand::Stop(sender, event_id) => { + match sender.send(()).await { + Ok(_) => { info!("[{}] Stopping controller {}", event_id, name); } + Err(err) => { info!("[{}] Error while quitting midi out {}: {}", event_id, name, err); } + } return; } MidiOutputWorkerCommand::SetSampleRate(rate) => { diff --git a/util/Cargo.toml b/util/Cargo.toml index cdcdfaa..f0dcb6d 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -13,6 +13,7 @@ build-info = "0.0.20" serde = "1.0.118" ipc-channel = "0.14.1" async-channel = "1.5.1" +uuid = "0.8.1" [dependencies.log-panics] version = "2.0.0" diff --git a/util/src/ipc_payload.rs b/util/src/ipc_payload.rs index ace9222..f92d0c6 100644 --- a/util/src/ipc_payload.rs +++ b/util/src/ipc_payload.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::midi_message_with_delta::MidiMessageWithDelta; use ipc_channel::ipc::IpcSender; +use crate::system::Uuid; #[derive(Debug, Serialize, Deserialize)] @@ -17,6 +18,6 @@ pub enum IPCCommand { PatternPayload(PatternPayload), // stop is only used locally, but send over an IPC channel so the worker can listen both on the remote IPC // for new patterns, and on local IPC for stopping the worker - Stop(IpcSender<()>), + Stop(IpcSender<()>, Uuid), Ping } diff --git a/util/src/lib.rs b/util/src/lib.rs index 9607564..a5814c8 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -17,6 +17,7 @@ pub mod transmute_buffer; pub mod logging; pub mod midi_message_with_delta; pub mod ipc_payload; +pub mod system; #[derive(Default)] pub struct HostCallbackLock { diff --git a/util/src/system.rs b/util/src/system.rs new file mode 100644 index 0000000..128b304 --- /dev/null +++ b/util/src/system.rs @@ -0,0 +1,30 @@ +use std::fmt::{Debug, Display}; +use serde::{Serialize, Deserialize}; +use std::fmt; + +// generate unique IDs on top of Uuid, which does not implement Serialize/Deserialize + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Uuid(u128); + +impl Default for Uuid { + fn default() -> Self { + Self { + 0: uuid::Uuid::default().to_u128_le() + } + } +} + +impl Uuid { + pub fn new_v4() -> Self { + Self { + 0: uuid::Uuid::new_v4().to_u128_le() + } + } +} + +impl Display for Uuid { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + std::fmt::Display::fmt(&uuid::Uuid::from_u128_le(self.0), f) + } +} From 1a3e0fe15222121dccf7fc016d04b28cdf999ac9 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 10 Jan 2021 11:17:42 +0100 Subject: [PATCH 16/47] fixed IPC --- arpegiator/src/lib.rs | 23 +---- arpegiator/src/midi_messages/device_out.rs | 5 +- arpegiator/src/workers/ipc_worker.rs | 65 +++++++++++--- arpegiator/src/workers/main_worker.rs | 14 +-- arpegiator/src/workers/midi_output_worker.rs | 68 +++++++++----- arpegiator_pattern_receiver/src/ipc_worker.rs | 88 +++++++++++++++---- arpegiator_pattern_receiver/src/lib.rs | 22 ++++- util/src/ipc_payload.rs | 10 ++- 8 files changed, 212 insertions(+), 83 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 15826b5..5e78f76 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -24,8 +24,9 @@ use workers::main_worker::{create_worker_thread, WorkerChannels, WorkerCommand}; use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT}; use crate::midi_messages::change::SourceChange; use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; -use crate::midi_messages::timed_event::TimedEvent; use util::system::Uuid; +#[cfg(target_os = "macos")] use mach::mach_time::mach_absolute_time; +use crate::midi_messages::timed_event::TimedEvent; mod midi_messages; mod workers; @@ -219,30 +220,14 @@ impl Plugin for ArpegiatorPlugin { } fn process(&mut self, buffer: &mut AudioBuffer) { - #[cfg(target_os = "macos")] - let local_time = unsafe { - mach::mach_time::mach_absolute_time() - }; - - #[cfg(target_os = "linux")] - let local_time = 0; + #[cfg(target_os = "macos")] let local_time = unsafe { mach_absolute_time() }; + #[cfg(target_os = "linux")] let local_time = 0; let pattern_messages = match self.worker_channels.as_ref() { None => vec![], Some(socket_channels) => { match socket_channels.pattern_receiver.try_recv() { Ok(payload) => { - #[cfg(target_os = "macos")] - { - let diff_milliseconds = (local_time - payload.time) as f64 / 10e6; - - // TODO following will device if the initial_delay is enough - info!("Received time: {:?} current time: {:?} = {} milliseconds", - payload.time, - local_time, - diff_milliseconds); - }; - #[cfg(feature = "device_debug")] info!("[{}] received patterns : {:02X?}", self.current_time_in_samples, payload); diff --git a/arpegiator/src/midi_messages/device_out.rs b/arpegiator/src/midi_messages/device_out.rs index 57aa1b6..804da2a 100644 --- a/arpegiator/src/midi_messages/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -34,12 +34,13 @@ impl DeviceOut { self.device.update(midi_message, current_time, id); } - pub fn flush_to(&mut self, buffer_start_time: u64, midi_output_sender: &Sender) { + pub fn flush_to(&mut self, reception_time: u64, midi_output_sender: &Sender) { if self.queue.is_empty() { return } midi_output_sender.try_send( - WorkerCommand::SendToMidiOutput { buffer_start_time, messages: take(&mut self.queue) + WorkerCommand::SendToMidiOutput { + reception_time, messages: take(&mut self.queue) }).unwrap_or_else( |err| error!("Could not send to the controller worker {}", err) ); diff --git a/arpegiator/src/workers/ipc_worker.rs b/arpegiator/src/workers/ipc_worker.rs index 01d95dd..cda8f39 100644 --- a/arpegiator/src/workers/ipc_worker.rs +++ b/arpegiator/src/workers/ipc_worker.rs @@ -4,10 +4,9 @@ use log::{error, info}; use async_channel::Sender; use async_std::net::UdpSocket; use async_std::task; -use futures_lite::io::{Error, ErrorKind}; -use ipc_channel::ipc::{IpcSender, IpcReceiver, IpcReceiverSet}; +use ipc_channel::ipc::{IpcSender, IpcReceiver, IpcReceiverSet, IpcSelectionResult}; -use util::ipc_payload::{PatternPayload, IPCCommand}; +use util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}; use crate::workers::main_worker::WorkerCommand; use std::{thread, error}; @@ -22,6 +21,13 @@ pub(crate) enum IPCWorkerCommand { PayloadReceived(PatternPayload, Uuid), } +fn bootstrap_ipc(ipc_server_name: String) -> Result, Box> { + let ipc_bootstrap_sender = IpcSender::connect(ipc_server_name)?; + let (ipc_sender, ipc_receiver) = ipc_channel::ipc::channel()?; + ipc_bootstrap_sender.send(BootstrapPayload::Channel(ipc_sender))?; + Ok(ipc_receiver) +} + async fn udp_receive_worker(socket: UdpSocket, sender: Sender) { let mut buf = vec![0u8; 1024]; @@ -30,13 +36,23 @@ async fn udp_receive_worker(socket: UdpSocket, sender: Sender) while let Ok(len) = socket.recv(&mut buf).await { exit_event_id = Uuid::new_v4(); - let ipc_worker_command = match bincode::deserialize::>(&buf[..len]) { - Ok(ipc_receiver) => { - info!("[{}] Received IPC Receiver via UDP", exit_event_id); - IPCWorkerCommand::SocketReceive(ipc_receiver, exit_event_id) + info!("[{}] Received {} bytes", exit_event_id, len); + + let ipc_worker_command = match bincode::deserialize::(&buf[..len]) { + Ok(ipc_server_name) => { + match bootstrap_ipc(ipc_server_name) { + Ok(ipc_receiver) => { + info!("[{}] Received IPC Receiver via UDP", exit_event_id); + IPCWorkerCommand::SocketReceive(ipc_receiver, exit_event_id) + } + Err(err) => { + info!("[{}] failed to get an IPC Receiver via UDP: {}", exit_event_id, err); + continue; + } + } } Err(err) => { - error!("[{}] Ignoring invalid UDP payload ({}) - expected ICP receiver", exit_event_id, err); + error!("[{}] Ignoring invalid UDP payload ({}) - expected a name string", exit_event_id, err); continue; } }; @@ -67,7 +83,14 @@ fn spawn_ipc_receiver_thread(ipc_receiver: IpcReceiver, #[cfg(feature = "worker_debug")] info!("started ipc worker"); 'mainloop: while let Ok(results) = set.select() { for result in results { - let (_, opaque_message) = result.unwrap(); + let opaque_message = match result { + IpcSelectionResult::MessageReceived(_, opaque_message) => opaque_message, + IpcSelectionResult::ChannelClosed(_) => { + exit_event_id = Uuid::new_v4(); + error!("[{}] ipc channel closed", exit_event_id); + break 'mainloop; + } + }; match opaque_message.to::().unwrap() { IPCCommand::PatternPayload(payload) => { let event_id = Uuid::new_v4(); @@ -77,8 +100,12 @@ fn spawn_ipc_receiver_thread(ipc_receiver: IpcReceiver, break 'mainloop; } } - IPCCommand::Ping => { - info!("Received ping from peer") + IPCCommand::Ping(sender) => { + info!("Received ping from peer"); + match sender.send(()) { + Ok(_) => { info!("pong sent") } + Err(err) => { info!("error while sending pong: {}", err) } + } } IPCCommand::Stop(ack_channel_sender, event_id) => { exit_event_id = event_id; @@ -122,7 +149,8 @@ pub(crate) fn spawn_ipc_worker(port: u16, match command { IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket, event_id) => { if ipc_receiver_sender.is_some() { - if let Err(err) = close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap(), event_id) { + if let Err(err) = close_ipc_receiver_thread( + take(&mut ipc_receiver_sender).unwrap(), event_id) { error!("[{}] Error while shutting down ipc receiver worker {}", event_id, err) } } @@ -146,11 +174,22 @@ pub(crate) fn spawn_ipc_worker(port: u16, error!("[{}] IPC Receiver disconnected", event_id) } IPCWorkerCommand::PayloadReceived(payload, event_id) => { + let _payload_time = payload.time; if let Err(err) = pattern_sender.send(payload).await { exit_event_id = event_id; error!("[{}] IPC worker: notes sender channel error, quitting ({})", exit_event_id, err); break; } + + #[cfg(feature = "device_debug")] + { + let local_time = unsafe { mach::mach_time::mach_absolute_time() }; + let diff_nanoseconds = local_time - _payload_time; + info!("Received time: {:?} current time: {:?} = {} nanoseconds", + payload_time, + local_time, + diff_nanoseconds); + } } } } @@ -185,7 +224,7 @@ error::Error>> { ipc_receiver_sender.send(IPCCommand::Stop(ack_sender, event_id))?; #[cfg(feature = "worker_debug")] info!("[{}] waiting for ack from ipc receiver", event_id); - ack_receiver.try_recv().map_err(|x| Error::new(ErrorKind::Other, format!("{:?}", x)))?; + ack_receiver.try_recv().map_err(|x| format!("Error while receiving ack from ipc receiver {:?}", x))?; #[cfg(feature = "worker_debug")] info!("[{}] stopped ipc receiver", event_id); Ok(()) diff --git a/arpegiator/src/workers/main_worker.rs b/arpegiator/src/workers/main_worker.rs index 9090551..b3482df 100644 --- a/arpegiator/src/workers/main_worker.rs +++ b/arpegiator/src/workers/main_worker.rs @@ -20,7 +20,7 @@ pub(crate) enum WorkerCommand { SetPort(u16, Uuid), SetSampleRate(f32), SetBlockSize(i64), - SendToMidiOutput { buffer_start_time: u64, messages: Vec }, + SendToMidiOutput { reception_time: u64, messages: Vec }, IPCWorkerStopped(Uuid, u16), } @@ -93,10 +93,10 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { } } - WorkerCommand::SendToMidiOutput { buffer_start_time, messages } => { + WorkerCommand::SendToMidiOutput { reception_time, messages } => { if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { midi_out_worker_sender.send( - MidiOutputWorkerCommand::SendToController { buffer_start_time, messages } + MidiOutputWorkerCommand::SendToController { reception_time, messages } ).await.unwrap(); } } @@ -105,8 +105,10 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { midi_out_worker_sender.send(MidiOutputWorkerCommand::SetSampleRate(rate)).await.unwrap(); } } - WorkerCommand::SetBlockSize(_size) => { - // not used + WorkerCommand::SetBlockSize(size) => { + if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { + midi_out_worker_sender.send(MidiOutputWorkerCommand::SetBlockSize(size)).await.unwrap(); + } } WorkerCommand::IPCWorkerStopped(event_id, ipc_worker_port) => { if current_port.is_none() || current_port.unwrap() != ipc_worker_port { @@ -151,7 +153,7 @@ async fn close_workers( error!("[{}] Could not contact worker sender for shutdown : {}", event_id, err); }); match ack_receiver.recv().await { - Ok(_) => { error!("[{}] ipc worker exit ack", event_id) } + Ok(_) => { info!("[{}] ipc worker exit ack", event_id) } Err(err) => { error!("[{}] ipc worker did not ack exit: {}", event_id, err) } } }; diff --git a/arpegiator/src/workers/midi_output_worker.rs b/arpegiator/src/workers/midi_output_worker.rs index 66988a1..33b9f4f 100644 --- a/arpegiator/src/workers/midi_output_worker.rs +++ b/arpegiator/src/workers/midi_output_worker.rs @@ -9,12 +9,13 @@ use { coremidi::PacketBuffer }; -use midir::MidiInput; + #[cfg(target_os = "linux")] use { - midir::os::unix::VirtualOutput, - midir::MidiOutput + midir::MidiOutput, + midir::os::unix::VirtualOutput }; +use midir::MidiInput; use midir::os::unix::VirtualInput; use util::midi_message_with_delta::MidiMessageWithDelta; @@ -30,9 +31,10 @@ use util::system::Uuid; #[derive(Debug)] pub(crate) enum MidiOutputWorkerCommand { - SendToController { buffer_start_time: u64, messages: Vec }, + SendToController { reception_time: u64, messages: Vec }, Stop(Sender<()>, Uuid), - SetSampleRate(f32) + SetSampleRate(f32), + SetBlockSize(i64) } @@ -40,33 +42,34 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> Result, Box> { #[cfg(target_os = "macos")] - let (second_to_mach, mut sample_to_mach) : (f64, u64) = { - (second_to_mach_timebase(), 0) + let (second_to_mach, mut sample_to_mach, mut _block_size, mut block_duration) : (f64, u64, u64, u64) = { + (second_to_mach_timebase(), 0, 0, 0) }; #[cfg(target_os = "linux")] - #[allow(unused_mut)] let mut midi_out_connection = { info!("Creating midi device {}", name); let midi_out = MidiOutput::new(&*name)?; - midi_out.create_virtual(&*name)? + midi_out.create_virtual(&*name).map_err(|e| format!("Cannot create midi out: {}", e))? }; #[cfg(target_os = "macos")] - let (_client, source) = { - let client = coremidi::Client::new(&*name).map_err( - |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) - ))?; + let (client, source) = { + let client = coremidi::Client::new(&*name) + .map_err( + |x| format!("os error: {:?}", x) + )?; let source = client.virtual_source(&*name).map_err( - |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) - ))?; + |x| format!("os error: {:?}", x) + )?; + info!("Created virtual port {}", name); (client, source) }; let midi_in = MidiInput::new(&*name)?; // create input device just to ease setup. returned connection must not be dropped in order to keep the device alive - let mut _midi_in_connection = midi_in.create_virtual(&*name, |_time, _data, _| { + let midi_in_connection = midi_in.create_virtual(&*name, |_time, _data, _| { // noop for now }, ()).map_err( |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) @@ -75,10 +78,17 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> let (sender, receiver) = unbounded::(); task::spawn(async move { + // move the client inside the task, otherwise it will be dropped and the device won't appear + #[cfg(target_os = "macos")] let _client = client ; + + // move it here to keep the device visible. no use for now, just helps to have a consistent in/out setup + let _midi_in_connection = midi_in_connection; while let Ok(command) = receiver.recv().await { match command { - MidiOutputWorkerCommand::SendToController { buffer_start_time, mut messages } => { + #[allow(unused_mut)] + #[allow(unused_variables)] + MidiOutputWorkerCommand::SendToController { reception_time, mut messages } => { #[cfg(target_os = "linux")] for message in messages { // TODO timing is lost, we should actually wait until buffer_start_time and wait for the @@ -88,17 +98,23 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> break; } } + #[cfg(target_os = "macos")] { + // we cannot accurately tell when those notes should be played, we just know when it was + // received and the earliest time being reception + time that represents a buffer + let play_time = reception_time + block_duration; let message = messages.remove(0); let mut buffer = PacketBuffer::new( - message.delta_frames as u64 * sample_to_mach + buffer_start_time, + message.delta_frames as u64 * sample_to_mach + play_time, message.data.get_bytes(), ); + for message in messages { - buffer.push_data(message.delta_frames as u64 * sample_to_mach + buffer_start_time, + buffer.push_data(message.delta_frames as u64 * sample_to_mach + play_time, &message.data.get_bytes()); } + source.received(&buffer).unwrap(); } } @@ -109,11 +125,19 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> } return; } - MidiOutputWorkerCommand::SetSampleRate(rate) => { + MidiOutputWorkerCommand::SetSampleRate(_rate) => { + #[cfg(target_os = "macos")] + { + let sample_to_second = 1.0 / _rate as f64; + sample_to_mach = (sample_to_second * second_to_mach) as u64; + block_duration = sample_to_mach * _block_size; + } + } + MidiOutputWorkerCommand::SetBlockSize(_size) => { #[cfg(target_os = "macos")] { - let sample_to_second = 1.0 / rate as f64; - sample_to_mach = (sample_to_second * second_to_mach) as u64 + _block_size = _size as u64; + block_duration = sample_to_mach * _block_size; } } } diff --git a/arpegiator_pattern_receiver/src/ipc_worker.rs b/arpegiator_pattern_receiver/src/ipc_worker.rs index 92f8421..66f3d67 100644 --- a/arpegiator_pattern_receiver/src/ipc_worker.rs +++ b/arpegiator_pattern_receiver/src/ipc_worker.rs @@ -6,58 +6,109 @@ use async_channel::{Sender, Receiver}; use async_std::net::UdpSocket; use ipc_channel::ipc::IpcSender; -use util::ipc_payload::{PatternPayload, IPCCommand}; +use util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}; use std::net::ToSocketAddrs; use async_std::task; use std::time::Duration; +use std::io::{Error, ErrorKind}; pub(crate) enum IPCWorkerCommand { Stop, SetPort(u16), Send(PatternPayload), + TryConnect } -async fn try_udp_send_receiver(port: u16) -> Result, Box> { +async fn try_udp_send_receiver(port: u16) -> Result, Box> { let socket = UdpSocket::bind("127.0.0.1:0").await?; let to = format!("127.0.0.1:{}", port).to_socket_addrs()?.next().ok_or("empty list")?; - let (ipc_sender, ipc_receiver) = ipc_channel::ipc::channel::()?; + let (one_shot, name) = ipc_channel::ipc::IpcOneShotServer::new()?; + let serialized_name = bincode::serialize(&name)?; + socket.send_to(&*serialized_name, to).await?; - let serialized_ipc_receiver = bincode::serialize(&ipc_receiver)?; - socket.send_to(&*serialized_ipc_receiver, to).await?; - task::sleep(Duration::new(1, 0)).await; + let (bootstrap_result_sender, bootstrap_result_receiver) = async_channel::unbounded(); - ipc_sender.send(IPCCommand::Ping)?; + thread::spawn(move || { + let (_, result) = one_shot.accept().unwrap(); + match result { + BootstrapPayload::Channel(ipc_sender) => { + info!("Returning ipc_sender"); + bootstrap_result_sender.try_send(ipc_sender).unwrap() + } + BootstrapPayload::Timeout => { + error!("timed out while waiting for a connection"); + } + } + }); + + task::sleep(Duration::new(1,0 )).await; + + let ipc_sender = match bootstrap_result_receiver.try_recv() { + Ok(ipc_sender) => { + ipc_sender + } + Err(_) => { + IpcSender::::connect(name).unwrap().send(BootstrapPayload::Timeout).unwrap(); + return Err(Box::new(Error::new(ErrorKind::Other, "Connection timeout"))); + } + }; + + let (ping_sender, ping_receiver) = ipc_channel::ipc::channel::<()>()?; + info!("sending ping"); + ipc_sender.send(IPCCommand::Ping(ping_sender))?; + task::sleep(Duration::new(1,0)).await; - Ok(ipc_sender) + match ping_receiver.try_recv() { + Ok(_) => { + info!("pong received"); + Ok(ipc_sender) + } + Err(e) => { + error!("pong not received"); + Err(Box::new(Error::new(ErrorKind::Other, format!("{:?}", e)))) + } + } } async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_receiver: Receiver) { let mut port = None; let mut ipc_sender: Option> = None; + let mut retry_scheduled = false; while let Ok(command) = ipc_worker_receiver.recv().await { match command { IPCWorkerCommand::Stop => { break; } - IPCWorkerCommand::SetPort(new_port) => { - port = Some(new_port); - ipc_sender = match try_udp_send_receiver(new_port).await { + IPCWorkerCommand::TryConnect => { + retry_scheduled = false; + if ipc_sender.is_some() || port.is_none() { + // already connected or not configured + continue; + } + ipc_sender = match try_udp_send_receiver(port.unwrap()).await { Ok(ipc_sender) => Some(ipc_sender), Err(err) => { error!("Error while connecting to arpegiator on port {} : {}", port.unwrap(), err); - let ipc_worker_sender = ipc_worker_sender.clone(); - task::spawn(async move { - task::sleep(Duration::new(1,0)).await; - ipc_worker_sender.send(IPCWorkerCommand::SetPort(port.unwrap())).await.unwrap(); - }); + task::sleep(Duration::new(1,0)).await; + retry_scheduled = true; + ipc_worker_sender.send(IPCWorkerCommand::TryConnect).await.unwrap(); None } }; + }, + IPCWorkerCommand::SetPort(new_port) => { + ipc_sender = None; + port = Some(new_port); + if !retry_scheduled { + ipc_worker_sender.send(IPCWorkerCommand::TryConnect).await.unwrap(); + retry_scheduled = true + } + } IPCWorkerCommand::Send(payload) => { let ipc_sender_ref = match ipc_sender.as_ref() { @@ -71,7 +122,10 @@ async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_rece if let Err(err) = ipc_sender_ref.send(IPCCommand::PatternPayload(payload)) { error!("IPC failed ; will attempt to reconnect ({})", err); ipc_sender = None; - ipc_worker_sender.send(IPCWorkerCommand::SetPort(port.unwrap())).await.unwrap(); + if !retry_scheduled { + ipc_worker_sender.send(IPCWorkerCommand::TryConnect).await.unwrap(); + retry_scheduled = true + } }; } } diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index 3007cb0..4c3914f 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -31,6 +31,7 @@ struct ArpegiatorPatternReceiver { ipc_worker_sender: Option>, messages: Vec, current_time: usize, + resumed: bool, parameters: Arc } @@ -42,6 +43,7 @@ impl Default for ArpegiatorPatternReceiver { ipc_worker_sender: None, messages: vec![], current_time: 0, + resumed: false, parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) } } @@ -53,6 +55,7 @@ impl ArpegiatorPatternReceiver { sender.try_send(IPCWorkerCommand::Stop).unwrap_or_else(|err| { error!("Error while closing sender channel : {}", err) }); + sender.close(); } } } @@ -80,9 +83,17 @@ impl Plugin for ArpegiatorPatternReceiver { } fn resume(&mut self) { + if self.resumed { + return; + } + self.resumed = true; + self.current_time = 0 ; + self.stop_worker(); + let sender= spawn_ipc_worker(); + self.ipc_worker_sender = Some(sender.clone()); sender.try_send(IPCWorkerCommand::SetPort(self.parameters.get_port())).unwrap(); @@ -92,6 +103,10 @@ impl Plugin for ArpegiatorPatternReceiver { } fn suspend(&mut self) { + if !self.resumed { + return; + } + self.resumed = false; self.stop_worker() } @@ -105,6 +120,7 @@ impl Plugin for ArpegiatorPatternReceiver { ipc_worker_sender: None, messages: vec![], current_time: 0, + resumed: false, parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) } } @@ -131,8 +147,10 @@ impl Plugin for ArpegiatorPatternReceiver { if !self.messages.is_empty() { if let Some(ipc_worker_sender) = &self.ipc_worker_sender { let payload = PatternPayload { - #[cfg(target_os = "macos")] - time: unsafe { mach::mach_time::mach_absolute_time() }, + time: { + #[cfg(target_os = "macos")] unsafe { mach::mach_time::mach_absolute_time() } + #[cfg(target_os = "linux")] 0 + }, messages: take(&mut self.messages) } ; ipc_worker_sender.try_send(IPCWorkerCommand::Send(payload)).unwrap() diff --git a/util/src/ipc_payload.rs b/util/src/ipc_payload.rs index f92d0c6..904e719 100644 --- a/util/src/ipc_payload.rs +++ b/util/src/ipc_payload.rs @@ -7,7 +7,6 @@ use crate::system::Uuid; #[derive(Debug, Serialize, Deserialize)] pub struct PatternPayload { - #[cfg(target_os = "macos")] pub time: u64, pub messages: Vec, } @@ -19,5 +18,12 @@ pub enum IPCCommand { // stop is only used locally, but send over an IPC channel so the worker can listen both on the remote IPC // for new patterns, and on local IPC for stopping the worker Stop(IpcSender<()>, Uuid), - Ping + Ping(IpcSender<()>) +} + + +#[derive(Serialize, Deserialize)] +pub enum BootstrapPayload { + Channel(IpcSender), + Timeout } From d9b93b219285828557e853c3e67ae05568b5fdfa Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 10 Jan 2021 13:00:19 +0100 Subject: [PATCH 17/47] simplify error propagation --- arpegiator/src/workers/midi_output_worker.rs | 6 ++-- arpegiator_pattern_receiver/src/ipc_worker.rs | 28 ++++++------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/arpegiator/src/workers/midi_output_worker.rs b/arpegiator/src/workers/midi_output_worker.rs index 33b9f4f..5c70364 100644 --- a/arpegiator/src/workers/midi_output_worker.rs +++ b/arpegiator/src/workers/midi_output_worker.rs @@ -23,8 +23,6 @@ use util::midi_message_with_delta::MidiMessageWithDelta; #[cfg(target_os = "macos")] use crate::system::second_to_mach_timebase; -use async_std::io::ErrorKind; -use std::io::Error; use std::error; use util::system::Uuid; @@ -72,8 +70,8 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> let midi_in_connection = midi_in.create_virtual(&*name, |_time, _data, _| { // noop for now }, ()).map_err( - |x| Error::new(ErrorKind::Other, format!("os error: {:?}", x) - ))?; + |x| format!("os error: {:?}", x) + )?; let (sender, receiver) = unbounded::(); diff --git a/arpegiator_pattern_receiver/src/ipc_worker.rs b/arpegiator_pattern_receiver/src/ipc_worker.rs index 66f3d67..6c4799a 100644 --- a/arpegiator_pattern_receiver/src/ipc_worker.rs +++ b/arpegiator_pattern_receiver/src/ipc_worker.rs @@ -10,7 +10,6 @@ use util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}; use std::net::ToSocketAddrs; use async_std::task; use std::time::Duration; -use std::io::{Error, ErrorKind}; pub(crate) enum IPCWorkerCommand { @@ -46,31 +45,20 @@ async fn try_udp_send_receiver(port: u16) -> Result, Box { - ipc_sender - } - Err(_) => { - IpcSender::::connect(name).unwrap().send(BootstrapPayload::Timeout).unwrap(); - return Err(Box::new(Error::new(ErrorKind::Other, "Connection timeout"))); - } - }; + let ipc_sender = bootstrap_result_receiver.try_recv().or_else(|e| { + IpcSender::::connect(name).or(Err("Cannot connect to signal timeout on ipc bootstrap"))? + .send(BootstrapPayload::Timeout).or(Err("Cannot send timeout to ipc bootstrapper"))?; + Err(format!("Connection timeout {}", e)) + })?; let (ping_sender, ping_receiver) = ipc_channel::ipc::channel::<()>()?; info!("sending ping"); ipc_sender.send(IPCCommand::Ping(ping_sender))?; task::sleep(Duration::new(1,0)).await; - match ping_receiver.try_recv() { - Ok(_) => { - info!("pong received"); - Ok(ipc_sender) - } - Err(e) => { - error!("pong not received"); - Err(Box::new(Error::new(ErrorKind::Other, format!("{:?}", e)))) - } - } + ping_receiver.try_recv().or(Err("Pong not received"))?; + info!("Ping received"); + Ok(ipc_sender) } From 7d7a230287cefa956e26eb30157420c33aa8026d Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 10 Jan 2021 19:26:28 +0100 Subject: [PATCH 18/47] put every IPC feature behind a feature flag before implementing midi hack --- arpegiator/Cargo.toml | 4 +- arpegiator/src/lib.rs | 137 ++++++++++++------ arpegiator/src/midi_messages/device_out.rs | 29 ++-- arpegiator/src/parameters.rs | 52 +++++-- arpegiator/src/workers/midi_output_worker.rs | 3 +- arpegiator/src/workers/mod.rs | 6 +- arpegiator_pattern_receiver/Cargo.toml | 7 + arpegiator_pattern_receiver/src/ipc_worker.rs | 26 ++-- arpegiator_pattern_receiver/src/lib.rs | 68 ++++++--- arpegiator_pattern_receiver/src/parameters.rs | 61 +++++--- osx_vst_bundler.sh | 1 + 11 files changed, 258 insertions(+), 136 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 064ee16..e25d21f 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -27,12 +27,14 @@ name = "arpegiator" crate-type = ["cdylib", "lib"] [features] -default = ["use_channel_pressure", "forward_pattern_cc", "worker_debug"] +default = ["use_channel_pressure", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] use_channel_pressure = [] worker_debug = [] forward_note_cc = [] forward_pattern_cc = [] device_debug = [] +midi_hack_transmission = [] + [target.'cfg(target_os = "macos")'.dependencies.coremidi] version = "0.4.0" diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 5e78f76..10cbb46 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -1,11 +1,15 @@ -use std::mem::take; +#[allow(unused_imports)] +use { + std::mem::take, + log::{error, info} +}; + use std::os::raw::c_void; use std::sync::Arc; use itertools::Itertools; -use log::{error, info}; use vst::api; -use vst::buffer::AudioBuffer; +use vst::buffer::{AudioBuffer, SendEventBuffer}; use vst::event::{Event, MidiEvent}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; @@ -19,18 +23,27 @@ use util::messages::AfterTouch; use util::messages::Pressure; use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; -use workers::main_worker::{create_worker_thread, WorkerChannels, WorkerCommand}; +#[cfg(not(feature="midi_hack_transmission"))] +use { + workers::main_worker::{create_worker_thread, WorkerChannels, WorkerCommand}, +}; + +#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(target_os = "macos")] +use { + mach::mach_time::mach_absolute_time +}; use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT}; use crate::midi_messages::change::SourceChange; use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; use util::system::Uuid; -#[cfg(target_os = "macos")] use mach::mach_time::mach_absolute_time; use crate::midi_messages::timed_event::TimedEvent; +#[cfg(not(feature="midi_hack_transmission"))] mod workers; +#[cfg(not(feature="midi_hack_transmission"))] mod system; + mod midi_messages; -mod workers; -mod system; mod parameters; @@ -44,6 +57,7 @@ plugin_main!(ArpegiatorPlugin); pub struct ArpegiatorPlugin { events: Vec, _host: HostCallback, + send_buffer: SendEventBuffer, pattern_device_in: Device, notes_device_in: Device, pattern_device: PatternDevice, @@ -52,12 +66,14 @@ pub struct ArpegiatorPlugin { block_size: i64, device_out: DeviceOut, parameters: Arc, + #[cfg(not(feature = "midi_hack_transmission"))] worker_channels: Option, - resumed: bool + resumed: bool, } impl ArpegiatorPlugin { + #[cfg(not(feature = "midi_hack_transmission"))] fn close_worker(&mut self, event_id: Uuid) { if let Some(worker_channels) = take(&mut self.worker_channels) { #[cfg(feature = "worker_debug")] info!("[{}] stopping workers", event_id); @@ -77,6 +93,7 @@ impl Default for ArpegiatorPlugin { ArpegiatorPlugin { events: vec![], _host: Default::default(), + send_buffer: Default::default(), pattern_device_in: Device::new("Patterns".to_string()), notes_device_in: Device::new("Notes".to_string()), pattern_device: PatternDevice::default(), @@ -85,8 +102,9 @@ impl Default for ArpegiatorPlugin { block_size: 64, device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), + #[cfg(not(feature = "midi_hack_transmission"))] worker_channels: None, - resumed: false + resumed: false, } } } @@ -104,7 +122,7 @@ impl Plugin for ArpegiatorPlugin { // apply to the same buffer // one sample would be 0.2ms down to 0.05ms. increase amount of samples if the delay between plugins // is greater than 0.05ms - initial_delay: 1, + initial_delay: 0, version: 1, inputs: 0, outputs: 0, @@ -135,6 +153,7 @@ impl Plugin for ArpegiatorPlugin { ArpegiatorPlugin { events: vec![], _host: host, + send_buffer: Default::default(), pattern_device_in: Device::new("Pattern".to_string()), notes_device_in: Device::new("Notes".to_string()), pattern_device: Default::default(), @@ -143,8 +162,9 @@ impl Plugin for ArpegiatorPlugin { block_size: 64, device_out: DeviceOut::new("Out".to_string()), parameters: Arc::new(ArpegiatorParameters::new()), + #[cfg(not(feature = "midi_hack_transmission"))] worker_channels: None, - resumed: false + resumed: false, } } @@ -155,27 +175,30 @@ impl Plugin for ArpegiatorPlugin { } self.resumed = true; - let event_id = Uuid::new_v4() ; + let event_id = Uuid::new_v4(); #[cfg(feature = "worker_debug")] info!("[{}] resume: enter", event_id); - self.close_worker(event_id); self.current_time_in_samples = 0; - let worker_channels = create_worker_thread(); - worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port(), event_id)).unwrap(); - worker_channels.command_sender.try_send(WorkerCommand::SetSampleRate(self.sample_rate)).unwrap(); - - self.worker_channels = match self.parameters.worker_commands.lock() { - Ok(mut worker_commands) => { - *worker_commands = Some(worker_channels.command_sender.clone()); - Some(worker_channels) - } - Err(err) => { - error!("[{}] Could not get parameters lock: {}", event_id, err); - None - } - }; + #[cfg(not(feature = "midi_hack_transmission"))] + { + self.close_worker(event_id); + let worker_channels = create_worker_thread(); + worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port(), event_id)).unwrap(); + worker_channels.command_sender.try_send(WorkerCommand::SetSampleRate(self.sample_rate)).unwrap(); + + self.worker_channels = match self.parameters.worker_commands.lock() { + Ok(mut worker_commands) => { + *worker_commands = Some(worker_channels.command_sender.clone()); + Some(worker_channels) + } + Err(err) => { + error!("[{}] Could not get parameters lock: {}", event_id, err); + None + } + }; + } #[cfg(feature = "worker_debug")] info!("[{}] resume: exit", event_id); } @@ -188,12 +211,16 @@ impl Plugin for ArpegiatorPlugin { let event_id = Uuid::new_v4(); self.resumed = false; - #[cfg(feature = "worker_debug")] info!("[{}] suspend enter", event_id); - if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { - *worker_commands = None - } - self.close_worker(event_id); + #[cfg(not(feature = "midi_hack_transmission"))] + { + #[cfg(feature = "worker_debug")] info!("[{}] suspend enter", event_id); + if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { + *worker_commands = None + } + + self.close_worker(event_id); + } #[cfg(feature = "worker_debug")] info!("[{}] suspend exit", event_id); } @@ -220,23 +247,26 @@ impl Plugin for ArpegiatorPlugin { } fn process(&mut self, buffer: &mut AudioBuffer) { - #[cfg(target_os = "macos")] let local_time = unsafe { mach_absolute_time() }; - #[cfg(target_os = "linux")] let local_time = 0; - - let pattern_messages = match self.worker_channels.as_ref() { - None => vec![], - Some(socket_channels) => { - match socket_channels.pattern_receiver.try_recv() { - Ok(payload) => { - #[cfg(feature = "device_debug")] - info!("[{}] received patterns : {:02X?}", self.current_time_in_samples, payload); - - payload.messages + #[cfg(not(feature = "midi_hack_transmission"))] + { + #[cfg(target_os = "macos")] let local_time = unsafe { mach_absolute_time() }; + #[cfg(target_os = "linux")] let local_time = 0; + let pattern_messages = match self.worker_channels.as_ref() { + None => vec![], + Some(socket_channels) => { + match socket_channels.pattern_receiver.try_recv() { + Ok(payload) => { + #[cfg(feature = "device_debug")] + info!("[{}] received patterns : {:02X?}", self.current_time_in_samples, payload); + + payload.messages + } + Err(_) => vec![] } - Err(_) => vec![] } - } - }; + }; + } + // from here we cannot accurately tell when the buffer we're building will actually play // so our best guest will be the earliest : @@ -248,6 +278,9 @@ impl Plugin for ArpegiatorPlugin { let pattern_device = &mut self.pattern_device; let notes_device_in = &mut self.notes_device_in; + #[cfg(feature = "midi_hack_transmission")] + let pattern_messages = vec![] ; // TODO + let pattern_changes = pattern_messages.into_iter().map(|message| { let change = pattern_device_in.update(message, current_time_in_samples, None); let change = pattern_device.update(change); @@ -369,11 +402,14 @@ impl Plugin for ArpegiatorPlugin { } } + #[cfg(not(feature = "midi_hack_transmission"))] if let Some(worker_channels) = self.worker_channels.as_ref() { - self.device_out.flush_to(local_time,&worker_channels.command_sender) + self.device_out.flush_to(local_time, &worker_channels.command_sender) } }; + // TODO + //self.send_buffer.send_events(events, &mut self._host); self.events.clear(); @@ -381,6 +417,7 @@ impl Plugin for ArpegiatorPlugin { } fn set_sample_rate(&mut self, rate: f32) { + #[cfg(not(feature="midi_hack_transmission"))] if let Some(workers_channel) = &self.worker_channels { workers_channel.command_sender.try_send(WorkerCommand::SetSampleRate(rate)).unwrap() }; @@ -389,6 +426,8 @@ impl Plugin for ArpegiatorPlugin { fn set_block_size(&mut self, size: i64) { self.block_size = size; + + #[cfg(not(feature="midi_hack_transmission"))] if let Some(workers_channel) = &self.worker_channels { workers_channel.command_sender.try_send(WorkerCommand::SetBlockSize(size)).unwrap() }; @@ -397,12 +436,14 @@ impl Plugin for ArpegiatorPlugin { fn process_events(&mut self, events: &api::Events) { for e in events.events() { if let Event::Midi(e) = e { + // TODO separate patterns and notes self.events.push(e); } } } } +#[cfg(not(feature="midi_hack_transmission"))] impl Drop for ArpegiatorPlugin { fn drop(&mut self) { let event_id = Uuid::new_v4(); diff --git a/arpegiator/src/midi_messages/device_out.rs b/arpegiator/src/midi_messages/device_out.rs index 804da2a..3c882bc 100644 --- a/arpegiator/src/midi_messages/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -1,8 +1,9 @@ #[allow(unused_imports)] -use log::{error, info}; - -use async_channel::Sender; -use std::mem::take; +use { + log::{error, info}, + async_channel::Sender, + std::mem::take +}; use util::messages::NoteOff; use util::midi_message_with_delta::MidiMessageWithDelta; @@ -12,12 +13,12 @@ use crate::midi_messages::device::Device; use crate::midi_messages::expressive_note::ExpressiveNote; use crate::midi_messages::pattern::Pattern; use crate::midi_messages::note::Note; -use crate::workers::main_worker::WorkerCommand; +#[cfg(not(feature="midi_hack_transmission"))] use crate::workers::main_worker::WorkerCommand; pub(crate) struct DeviceOut { pub device: Device, - queue: Vec, + pub queue: Vec, } @@ -34,16 +35,20 @@ impl DeviceOut { self.device.update(midi_message, current_time, id); } + #[cfg(not(feature="midi_hack_transmission"))] pub fn flush_to(&mut self, reception_time: u64, midi_output_sender: &Sender) { if self.queue.is_empty() { return } - midi_output_sender.try_send( - WorkerCommand::SendToMidiOutput { - reception_time, messages: take(&mut self.queue) - }).unwrap_or_else( - |err| error!("Could not send to the controller worker {}", err) - ); + + { + midi_output_sender.try_send( + WorkerCommand::SendToMidiOutput { + reception_time, messages: take(&mut self.queue) + }).unwrap_or_else( + |err| error!("Could not send to the controller worker {}", err) + ); + } } pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 9f7c49f..6a1f15b 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -1,19 +1,27 @@ #[allow(unused_imports)] -use log::{error, info}; +use { + log::{error, info}, + util::parameter_value_conversion::{f32_to_byte, f32_to_bool}, + std::sync::Mutex, + async_channel::Sender +}; use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; -use util::parameter_value_conversion::{f32_to_byte, f32_to_bool}; -use crate::workers::main_worker::WorkerCommand; -use std::sync::Mutex; -use async_channel::Sender; +#[cfg(not(feature="midi_hack_transmission"))] use crate::workers::main_worker::WorkerCommand; use util::duration_display; use util::system::Uuid; +#[cfg(not(feature="midi_hack_transmission"))] pub const PARAMETER_COUNT: usize = 4; + +#[cfg(feature="midi_hack_transmission")] +pub const PARAMETER_COUNT: usize = 3; + +#[cfg(not(feature="midi_hack_transmission"))] const BASE_PORT: u16 = 6000; // 1 = Immediate - TODO : must be default @@ -47,14 +55,16 @@ enum PitchBendValues { pub(crate) struct ArpegiatorParameters { pub transfer: ParameterTransfer, - pub worker_commands: Mutex>>, + #[cfg(not(feature="midi_hack_transmission"))] pub worker_commands: Mutex>>, } impl ArpegiatorParameters { + #[cfg(not(feature="midi_hack_transmission"))] pub fn get_port(&self) -> u16 { BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 } + #[cfg(not(feature="midi_hack_transmission"))] pub fn update_port(&self, event_id: Uuid) { let port = self.get_byte_parameter(Parameter::PortIndex) as u16 + BASE_PORT; info!("Applying parameter change: port={}", port); @@ -69,19 +79,21 @@ impl ArpegiatorParameters { #[repr(i32)] pub enum Parameter { - PortIndex = 0, - HoldNotes, // a started pattern will find a note to play, even if no note is playing for that index + HoldNotes = 0, // a started pattern will find a note to play, even if no note is playing for that index PatternLegato, // pattern is not restarted if start/end match, and note is thus held - Pitchbend + Pitchbend, + #[cfg(not(feature="midi_hack_transmission"))] + PortIndex, } impl From for Parameter { fn from(i: i32) -> Self { match i { - 0 => Parameter::PortIndex, - 1 => Parameter::HoldNotes, - 2 => Parameter::PatternLegato, - 3 => Parameter::Pitchbend, + 0 => Parameter::HoldNotes, + 1 => Parameter::PatternLegato, + 2 => Parameter::Pitchbend, + #[cfg(not(feature="midi_hack_transmission"))] + 3 => Parameter::PortIndex, _ => panic!("no such parameter {}", i), } } @@ -109,6 +121,7 @@ impl ArpegiatorParameters { pub fn new() -> Self { let parameters = ArpegiatorParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), + #[cfg(not(feature="midi_hack_transmission"))] worker_commands: Mutex::new(None), }; parameters.set_parameter(Parameter::PatternLegato.into(), 1.); @@ -121,6 +134,7 @@ impl PluginParameters for ArpegiatorParameters { fn get_parameter_text(&self, index: i32) -> String { let parameter = index.into(); match parameter { + #[cfg(not(feature="midi_hack_transmission"))] Parameter::PortIndex => { self.get_port().to_string() } @@ -149,6 +163,7 @@ impl PluginParameters for ArpegiatorParameters { fn get_parameter_name(&self, index: i32) -> String { match index.into() { + #[cfg(not(feature="midi_hack_transmission"))] Parameter::PortIndex => "Port", Parameter::HoldNotes => "Hold notes", Parameter::PatternLegato => "Pattern Legato", @@ -163,6 +178,7 @@ impl PluginParameters for ArpegiatorParameters { fn set_parameter(&self, index: i32, value: f32) { let parameter = index.into(); match parameter { + #[cfg(not(feature="midi_hack_transmission"))] Parameter::PortIndex => { let new_value = f32_to_byte(value); let old_value = self.get_byte_parameter(Parameter::PortIndex); @@ -196,13 +212,19 @@ impl PluginParameters for ArpegiatorParameters { let event_id = Uuid::new_v4(); info!("[{}] Load present data", event_id); self.deserialize_state(data); - self.update_port(event_id) + #[cfg(not(feature="midi_hack_transmission"))] + { + self.update_port(event_id) + } } fn load_bank_data(&self, data: &[u8]) { let event_id = Uuid::new_v4(); info!("[{}] Load bank data", event_id); self.deserialize_state(data); - self.update_port(event_id) + #[cfg(not(feature="midi_hack_transmission"))] + { + self.update_port(event_id) + } } } diff --git a/arpegiator/src/workers/midi_output_worker.rs b/arpegiator/src/workers/midi_output_worker.rs index 5c70364..acca3c9 100644 --- a/arpegiator/src/workers/midi_output_worker.rs +++ b/arpegiator/src/workers/midi_output_worker.rs @@ -101,7 +101,8 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> { // we cannot accurately tell when those notes should be played, we just know when it was // received and the earliest time being reception + time that represents a buffer - let play_time = reception_time + block_duration; + //let play_time = reception_time + block_duration; + let play_time = reception_time ; let message = messages.remove(0); let mut buffer = PacketBuffer::new( message.delta_frames as u64 * sample_to_mach + play_time, diff --git a/arpegiator/src/workers/mod.rs b/arpegiator/src/workers/mod.rs index 86509ff..0f751d0 100644 --- a/arpegiator/src/workers/mod.rs +++ b/arpegiator/src/workers/mod.rs @@ -1,3 +1,3 @@ -pub mod ipc_worker; -pub mod main_worker; -mod midi_output_worker; +#[cfg(not(feature="midi_hack_transmission"))] pub mod ipc_worker; +#[cfg(not(feature="midi_hack_transmission"))] pub mod main_worker; +#[cfg(not(feature="midi_hack_transmission"))] mod midi_output_worker; diff --git a/arpegiator_pattern_receiver/Cargo.toml b/arpegiator_pattern_receiver/Cargo.toml index 2bbeaca..247b3d6 100644 --- a/arpegiator_pattern_receiver/Cargo.toml +++ b/arpegiator_pattern_receiver/Cargo.toml @@ -26,3 +26,10 @@ crate-type = ["cdylib", "lib"] [target.'cfg(target_os = "macos")'.dependencies.mach] version = "0.3" + +[features] +# this transmits midi via the vst output instead of using an IPC, setting the highest bit to 0 so we transmit +# midi messages in the range 0x00-0x7F and it does not interfer with any other message. Somehow bitwig just passes +# those messages while sysex are being blocked. +default = ["midi_hack_transmission"] +midi_hack_transmission = [] diff --git a/arpegiator_pattern_receiver/src/ipc_worker.rs b/arpegiator_pattern_receiver/src/ipc_worker.rs index 6c4799a..16a987a 100644 --- a/arpegiator_pattern_receiver/src/ipc_worker.rs +++ b/arpegiator_pattern_receiver/src/ipc_worker.rs @@ -1,16 +1,14 @@ -#[allow(unused_imports)] -use log::{error, info}; - -use std::{thread, error}; -use async_channel::{Sender, Receiver}; -use async_std::net::UdpSocket; -use ipc_channel::ipc::IpcSender; - -use util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}; -use std::net::ToSocketAddrs; -use async_std::task; -use std::time::Duration; - +use { + log::{error, info}, + std::{thread, error}, + async_channel::{Sender, Receiver}, + async_std::net::UdpSocket, + ipc_channel::ipc::IpcSender, + util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}, + std::net::ToSocketAddrs, + async_std::task, + std::time::Duration +}; pub(crate) enum IPCWorkerCommand { Stop, @@ -61,7 +59,6 @@ async fn try_udp_send_receiver(port: u16) -> Result, Box, ipc_worker_receiver: Receiver) { let mut port = None; let mut ipc_sender: Option> = None; @@ -120,7 +117,6 @@ async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_rece } } - pub(crate) fn spawn_ipc_worker() -> Sender { let (worker_sender, worker_receiver) = async_channel::unbounded(); { diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index 4c3914f..b8c5032 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -1,23 +1,29 @@ -use std::mem::take; +#[allow(unused_imports)] +use { + std::mem::take, + log::{info, error}, + async_channel::Sender, + vst::event::Event +}; use std::sync::Arc; -use log::{info, error}; - -use async_channel::Sender; use vst::api; use vst::buffer::AudioBuffer; -use vst::event::Event; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use util::logging::logging_setup; use util::midi_message_with_delta::MidiMessageWithDelta; -use util::ipc_payload::PatternPayload; -use crate::ipc_worker::{IPCWorkerCommand, spawn_ipc_worker}; -use crate::parameters::ArpegiatorPatternReceiverParameters; +#[cfg(not(feature="midi_hack_transmission"))] +use { + util::ipc_payload::PatternPayload, + crate::ipc_worker::{IPCWorkerCommand, spawn_ipc_worker} +}; + +use crate::parameters::{ArpegiatorPatternReceiverParameters, PARAMETER_COUNT}; mod parameters; -mod ipc_worker; +#[cfg(not(feature="midi_hack_transmission"))] mod ipc_worker; #[macro_use] extern crate vst; @@ -28,6 +34,7 @@ plugin_main!(ArpegiatorPatternReceiver); struct ArpegiatorPatternReceiver { #[allow(dead_code)] host: HostCallback, + #[cfg(not(feature="midi_hack_transmission"))] ipc_worker_sender: Option>, messages: Vec, current_time: usize, @@ -40,6 +47,7 @@ impl Default for ArpegiatorPatternReceiver { fn default() -> Self { ArpegiatorPatternReceiver { host: Default::default(), + #[cfg(not(feature="midi_hack_transmission"))] ipc_worker_sender: None, messages: vec![], current_time: 0, @@ -50,6 +58,7 @@ impl Default for ArpegiatorPatternReceiver { } impl ArpegiatorPatternReceiver { + #[cfg(not(feature="midi_hack_transmission"))] fn stop_worker(&mut self) { if let Some(sender) = take(&mut self.ipc_worker_sender) { sender.try_send(IPCWorkerCommand::Stop).unwrap_or_else(|err| { @@ -67,7 +76,7 @@ impl Plugin for ArpegiatorPatternReceiver { name: "Arpegiator Pattern Receiver".to_string(), vendor: "DJ Crontab".to_string(), unique_id: 342112720, - parameters: 1, + parameters: PARAMETER_COUNT as i32, category: Category::Synth, initial_delay: 0, version: 2, @@ -90,15 +99,18 @@ impl Plugin for ArpegiatorPatternReceiver { self.current_time = 0 ; - self.stop_worker(); + #[cfg(not(feature="midi_hack_transmission"))] + { + self.stop_worker(); - let sender= spawn_ipc_worker(); + let sender= spawn_ipc_worker(); - self.ipc_worker_sender = Some(sender.clone()); - sender.try_send(IPCWorkerCommand::SetPort(self.parameters.get_port())).unwrap(); + self.ipc_worker_sender = Some(sender.clone()); + sender.try_send(IPCWorkerCommand::SetPort(self.parameters.get_port())).unwrap(); - if let Ok(mut socket_command) = self.parameters.ipc_worker_sender.lock() { - *socket_command = Some(sender); + if let Ok(mut socket_command) = self.parameters.ipc_worker_sender.lock() { + *socket_command = Some(sender); + } } } @@ -107,7 +119,11 @@ impl Plugin for ArpegiatorPatternReceiver { return; } self.resumed = false; - self.stop_worker() + + #[cfg(not(feature="midi_hack_transmission"))] + { + self.stop_worker() + } } fn new(host: HostCallback) -> Self { @@ -117,6 +133,7 @@ impl Plugin for ArpegiatorPatternReceiver { ArpegiatorPatternReceiver { host, + #[cfg(not(feature="midi_hack_transmission"))] ipc_worker_sender: None, messages: vec![], current_time: 0, @@ -145,6 +162,7 @@ impl Plugin for ArpegiatorPatternReceiver { fn process(&mut self, buffer: &mut AudioBuffer) { if !self.messages.is_empty() { + #[cfg(not(feature="midi_hack_transmission"))] if let Some(ipc_worker_sender) = &self.ipc_worker_sender { let payload = PatternPayload { time: { @@ -157,12 +175,18 @@ impl Plugin for ArpegiatorPatternReceiver { } else { self.messages.clear(); } + + #[cfg(feature="midi_hack_transmission")] + { + // TODO + } } self.current_time += buffer.samples() } fn process_events(&mut self, events: &api::Events) { + #[cfg(not(feature="midi_hack_transmission"))] if self.ipc_worker_sender.is_some() { self.messages.extend(events.events().map(|event| match event { Event::Midi(event) => Ok(MidiMessageWithDelta { @@ -172,8 +196,11 @@ impl Plugin for ArpegiatorPatternReceiver { Event::SysEx(_) => Err(()), Event::Deprecated(_) => Err(()) }).filter(|item| item.is_ok()).map(|item| item.unwrap())); + } - //|midi_event| midi_event.unwrap()) + #[cfg(feature="midi_hack_transmission")] + { + // TODO } } @@ -184,6 +211,9 @@ impl Plugin for ArpegiatorPatternReceiver { impl Drop for ArpegiatorPatternReceiver { fn drop(&mut self) { - self.stop_worker(); + #[cfg(not(feature="midi_hack_transmission"))] + { + self.stop_worker(); + } } } diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs index 6f2da7e..6d196d9 100644 --- a/arpegiator_pattern_receiver/src/parameters.rs +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -1,23 +1,33 @@ #[allow(unused_imports)] -use log::{info, error}; +use { + log::{info, error}, + std::error, + util::parameter_value_conversion::f32_to_byte +}; use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; -use util::parameter_value_conversion::f32_to_byte; -use crate::ipc_worker::IPCWorkerCommand; -use std::sync::Mutex; -use async_channel::Sender; -use std::error; +#[cfg(not(feature="midi_hack_transmission"))] +use { + crate::ipc_worker::IPCWorkerCommand, + async_channel::Sender, + std::sync::Mutex +}; -const PARAMETER_COUNT: usize = 1; + +#[cfg(feature="midi_hack_transmission")] +pub const PARAMETER_COUNT: usize = 0; +#[cfg(not(feature="midi_hack_transmission"))] +pub const PARAMETER_COUNT: usize = 1; + const BASE_PORT: u16 = 6000; pub(crate) struct ArpegiatorPatternReceiverParameters { pub transfer: ParameterTransfer, - pub ipc_worker_sender: Mutex>> + #[cfg(not(feature="midi_hack_transmission"))] pub ipc_worker_sender: Mutex>> } impl ArpegiatorPatternReceiverParameters { @@ -25,6 +35,7 @@ impl ArpegiatorPatternReceiverParameters { BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 } + #[cfg(not(feature="midi_hack_transmission"))] fn update_port(&self) -> Result<(), Box> { let port = self.get_byte_parameter(Parameter::PortIndex); self.ipc_worker_sender.lock()?.as_ref(). @@ -71,7 +82,7 @@ impl ArpegiatorPatternReceiverParameters { pub fn new() -> Self { ArpegiatorPatternReceiverParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), - ipc_worker_sender: Mutex::new(None) + #[cfg(not(feature="midi_hack_transmission"))] ipc_worker_sender: Mutex::new(None) } } } @@ -99,13 +110,15 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { fn set_parameter(&self, index: i32, value: f32) { match index.into() { Parameter::PortIndex => { - let new_value = f32_to_byte(value); - let old_value = self.get_byte_parameter(Parameter::PortIndex); - if old_value != new_value { - self.transfer.set_parameter(index as usize, value); - self.update_port().unwrap_or_else(|err| { - error!("Could not update port: {}", err); - }); + #[cfg(not(feature="midi_hack_transmission"))] { + let new_value = f32_to_byte(value); + let old_value = self.get_byte_parameter(Parameter::PortIndex); + if old_value != new_value { + self.transfer.set_parameter(index as usize, value); + self.update_port().unwrap_or_else(|err| { + error!("Could not update port: {}", err); + }); + } } } } @@ -121,15 +134,19 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { fn load_preset_data(&self, data: &[u8]) { self.deserialize_state(data); - self.update_port().unwrap_or_else(|err| { - error!("Could not update port: {}", err); - }); + #[cfg(not(feature = "midi_hack_transmission"))] { + self.update_port().unwrap_or_else(|err| { + error!("Could not update port: {}", err); + }); + } } fn load_bank_data(&self, data: &[u8]) { self.deserialize_state(data); - self.update_port().unwrap_or_else(|err| { - error!("Could not update port: {}", err); - }); + #[cfg(not(feature = "midi_hack_transmission"))] { + self.update_port().unwrap_or_else(|err| { + error!("Could not update port: {}", err); + }); + } } } diff --git a/osx_vst_bundler.sh b/osx_vst_bundler.sh index 9de2e4f..c10f040 100755 --- a/osx_vst_bundler.sh +++ b/osx_vst_bundler.sh @@ -2,6 +2,7 @@ ARTEFACT_DIRECTORY=artefact LIB_PATH_PREFIX=target/release/ +#LIB_PATH_PREFIX=target/debug/ for BUNDLE_LIB in "NoteGenerator note_generator" "NoteOffDelay note_off_delay" "FilterOutNonNote filter_out_non_note" \ " NoteFanOut note_fan_out" "MidiDelay midi_delay" "MaxNoteDuration max_note_duration" "AudioData audio_data" \ From 63c380cb3af90d21ad07f42de10b2c290074f754 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 10 Jan 2021 22:28:55 +0100 Subject: [PATCH 19/47] fix feature switch --- arpegiator/src/lib.rs | 46 ++++++++++++++++-------- arpegiator_pattern_receiver/src/lib.rs | 50 +++++++++++++++----------- util/src/parameters.rs | 2 +- 3 files changed, 63 insertions(+), 35 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 10cbb46..81aa42b 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -57,6 +57,7 @@ plugin_main!(ArpegiatorPlugin); pub struct ArpegiatorPlugin { events: Vec, _host: HostCallback, + #[cfg(feature = "midi_hack_transmission")] send_buffer: SendEventBuffer, pattern_device_in: Device, notes_device_in: Device, @@ -93,6 +94,7 @@ impl Default for ArpegiatorPlugin { ArpegiatorPlugin { events: vec![], _host: Default::default(), + #[cfg(feature = "midi_hack_transmission")] send_buffer: Default::default(), pattern_device_in: Device::new("Patterns".to_string()), notes_device_in: Device::new("Notes".to_string()), @@ -153,6 +155,7 @@ impl Plugin for ArpegiatorPlugin { ArpegiatorPlugin { events: vec![], _host: host, + #[cfg(feature = "midi_hack_transmission")] send_buffer: Default::default(), pattern_device_in: Device::new("Pattern".to_string()), notes_device_in: Device::new("Notes".to_string()), @@ -248,10 +251,14 @@ impl Plugin for ArpegiatorPlugin { fn process(&mut self, buffer: &mut AudioBuffer) { #[cfg(not(feature = "midi_hack_transmission"))] - { - #[cfg(target_os = "macos")] let local_time = unsafe { mach_absolute_time() }; - #[cfg(target_os = "linux")] let local_time = 0; - let pattern_messages = match self.worker_channels.as_ref() { + let local_time = { + #[cfg(target_os = "macos")] unsafe { mach_absolute_time() } + #[cfg(target_os = "linux")] 0 + }; + + #[cfg(not(feature = "midi_hack_transmission"))] + let pattern_messages = { + match self.worker_channels.as_ref() { None => vec![], Some(socket_channels) => { match socket_channels.pattern_receiver.try_recv() { @@ -264,8 +271,10 @@ impl Plugin for ArpegiatorPlugin { Err(_) => vec![] } } - }; - } + } + }; + #[cfg(not(feature = "midi_hack_transmission"))] + let events = &self.events; // from here we cannot accurately tell when the buffer we're building will actually play @@ -279,7 +288,17 @@ impl Plugin for ArpegiatorPlugin { let notes_device_in = &mut self.notes_device_in; #[cfg(feature = "midi_hack_transmission")] - let pattern_messages = vec![] ; // TODO + let (events, pattern_messages) : (Vec, Vec) = { + let (mut events, mut patterns) : (Vec, Vec) = self.events.drain(..).partition( + |item| item.data[0] < 0x80); + events.sort_by_key(|x| x.delta_frames); + patterns.sort_by_key(|x| x.delta_frames); + let patterns = patterns.iter().map(|event| MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: RawMessage::from([event.data[0] + 0x80, event.data[1], event.data[2]]) + }).collect_vec(); + (events, patterns) + }; let pattern_changes = pattern_messages.into_iter().map(|message| { let change = pattern_device_in.update(message, current_time_in_samples, None); @@ -287,7 +306,7 @@ impl Plugin for ArpegiatorPlugin { SourceChange::PatternChange(change) }); - let note_changes = self.events.iter().map(|event| { + let note_changes = events.iter().map(|event| { let midi_message_with_delta = MidiMessageWithDelta { delta_frames: event.delta_frames as u16, data: event.data.into(), @@ -401,13 +420,13 @@ impl Plugin for ArpegiatorPlugin { } } } - - #[cfg(not(feature = "midi_hack_transmission"))] - if let Some(worker_channels) = self.worker_channels.as_ref() { - self.device_out.flush_to(local_time, &worker_channels.command_sender) - } }; + #[cfg(not(feature = "midi_hack_transmission"))] + if let Some(worker_channels) = self.worker_channels.as_ref() { + self.device_out.flush_to(local_time, &worker_channels.command_sender) + } + // TODO //self.send_buffer.send_events(events, &mut self._host); @@ -436,7 +455,6 @@ impl Plugin for ArpegiatorPlugin { fn process_events(&mut self, events: &api::Events) { for e in events.events() { if let Event::Midi(e) = e { - // TODO separate patterns and notes self.events.push(e); } } diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index b8c5032..a3d0f8f 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -3,21 +3,23 @@ use { std::mem::take, log::{info, error}, async_channel::Sender, - vst::event::Event + vst::event::Event, + vst::buffer::SendEventBuffer, + vst::api::MidiEvent }; use std::sync::Arc; use vst::api; -use vst::buffer::AudioBuffer; +use vst::buffer::{AudioBuffer}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use util::logging::logging_setup; -use util::midi_message_with_delta::MidiMessageWithDelta; #[cfg(not(feature="midi_hack_transmission"))] use { util::ipc_payload::PatternPayload, - crate::ipc_worker::{IPCWorkerCommand, spawn_ipc_worker} + crate::ipc_worker::{IPCWorkerCommand, spawn_ipc_worker}, + util::midi_message_with_delta::MidiMessageWithDelta }; use crate::parameters::{ArpegiatorPatternReceiverParameters, PARAMETER_COUNT}; @@ -34,9 +36,14 @@ plugin_main!(ArpegiatorPatternReceiver); struct ArpegiatorPatternReceiver { #[allow(dead_code)] host: HostCallback, + #[cfg(feature = "midi_hack_transmission")] + send_buffer: SendEventBuffer, #[cfg(not(feature="midi_hack_transmission"))] ipc_worker_sender: Option>, + #[cfg(not(feature="midi_hack_transmission"))] messages: Vec, + #[cfg(feature="midi_hack_transmission")] + messages: Vec, current_time: usize, resumed: bool, parameters: Arc @@ -52,7 +59,9 @@ impl Default for ArpegiatorPatternReceiver { messages: vec![], current_time: 0, resumed: false, - parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) + parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()), + #[cfg(feature="midi_hack_transmission")] + send_buffer: Default::default() } } } @@ -138,7 +147,9 @@ impl Plugin for ArpegiatorPatternReceiver { messages: vec![], current_time: 0, resumed: false, - parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()) + parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()), + #[cfg(feature="midi_hack_transmission")] + send_buffer: Default::default() } } @@ -178,7 +189,8 @@ impl Plugin for ArpegiatorPatternReceiver { #[cfg(feature="midi_hack_transmission")] { - // TODO + self.send_buffer.send_events(&self.messages, &mut self.host); + self.messages.clear() } } @@ -187,21 +199,19 @@ impl Plugin for ArpegiatorPatternReceiver { fn process_events(&mut self, events: &api::Events) { #[cfg(not(feature="midi_hack_transmission"))] - if self.ipc_worker_sender.is_some() { - self.messages.extend(events.events().map(|event| match event { - Event::Midi(event) => Ok(MidiMessageWithDelta { - delta_frames: event.delta_frames as u16, - data: event.data.into() - }), - Event::SysEx(_) => Err(()), - Event::Deprecated(_) => Err(()) - }).filter(|item| item.is_ok()).map(|item| item.unwrap())); + if self.ipc_worker_sender.is_none() { + return; } - #[cfg(feature="midi_hack_transmission")] - { - // TODO - } + self.messages.extend(events.events().map(|event| match event { + #[allow(unused_mut)] + Event::Midi(mut event) => Ok(MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: event.data.into() + }), + Event::SysEx(_) => Err(()), + Event::Deprecated(_) => Err(()) + }).filter(|item| item.is_ok()).map(|item| item.unwrap())); } fn get_parameter_object(&mut self) -> Arc { diff --git a/util/src/parameters.rs b/util/src/parameters.rs index d132edb..84a0709 100644 --- a/util/src/parameters.rs +++ b/util/src/parameters.rs @@ -70,7 +70,7 @@ pub trait ParameterConversion } fn deserialize_state(&self, data: &[u8]) { - for (i, item) in data.iter().enumerate() { + for (i, item) in data.iter().enumerate().take_while(|(i,_)| *i < Self::get_parameter_count()) { self.set_byte_parameter((i as i32).into(), *item); } } From 20cee7af6ac07ef67f152a57189046921d837715 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 10 Jan 2021 23:06:55 +0100 Subject: [PATCH 20/47] reorder --- arpegiator/src/lib.rs | 56 +++++++++++++------------- arpegiator_pattern_receiver/src/lib.rs | 52 ++++++++++++++---------- 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 81aa42b..fc7780b 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -137,15 +137,6 @@ impl Plugin for ArpegiatorPlugin { } } - fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { - // according to MPE specifications a vendor specific call should occur in order to signal VST - // support ( page 15 ). As it seems all bitwig does is setting "MPE support" to true by default - // when CanDo replies to "MPE", and it's just sending pitchwheel/pressure. - // https://d30pueezughrda.cloudfront.net/campaigns/mpe/mpespec.pdf - info!("vendor_specific {:?} {:?} {:?} {:?}", index, value, ptr, opt); - 0 - } - fn new(host: HostCallback) -> Self { logging_setup(); info!("{} use_channel_pressure: {}", @@ -171,6 +162,23 @@ impl Plugin for ArpegiatorPlugin { } } + fn set_sample_rate(&mut self, rate: f32) { + #[cfg(not(feature="midi_hack_transmission"))] + if let Some(workers_channel) = &self.worker_channels { + workers_channel.command_sender.try_send(WorkerCommand::SetSampleRate(rate)).unwrap() + }; + self.sample_rate = rate + } + + fn set_block_size(&mut self, size: i64) { + self.block_size = size; + + #[cfg(not(feature="midi_hack_transmission"))] + if let Some(workers_channel) = &self.worker_channels { + workers_channel.command_sender.try_send(WorkerCommand::SetBlockSize(size)).unwrap() + }; + } + fn resume(&mut self) { if self.resumed { info!("Already resumed"); @@ -227,8 +235,13 @@ impl Plugin for ArpegiatorPlugin { #[cfg(feature = "worker_debug")] info!("[{}] suspend exit", event_id); } - fn get_parameter_object(&mut self) -> Arc { - Arc::clone(&self.parameters) as Arc + fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { + // according to MPE specifications a vendor specific call should occur in order to signal VST + // support ( page 15 ). As it seems all bitwig does is setting "MPE support" to true by default + // when CanDo replies to "MPE", and it's just sending pitchwheel/pressure. + // https://d30pueezughrda.cloudfront.net/campaigns/mpe/mpespec.pdf + info!("vendor_specific {:?} {:?} {:?} {:?}", index, value, ptr, opt); + 0 } fn can_do(&self, can_do: CanDo) -> vst::api::Supported { @@ -435,23 +448,6 @@ impl Plugin for ArpegiatorPlugin { self.current_time_in_samples += buffer.samples() } - fn set_sample_rate(&mut self, rate: f32) { - #[cfg(not(feature="midi_hack_transmission"))] - if let Some(workers_channel) = &self.worker_channels { - workers_channel.command_sender.try_send(WorkerCommand::SetSampleRate(rate)).unwrap() - }; - self.sample_rate = rate - } - - fn set_block_size(&mut self, size: i64) { - self.block_size = size; - - #[cfg(not(feature="midi_hack_transmission"))] - if let Some(workers_channel) = &self.worker_channels { - workers_channel.command_sender.try_send(WorkerCommand::SetBlockSize(size)).unwrap() - }; - } - fn process_events(&mut self, events: &api::Events) { for e in events.events() { if let Event::Midi(e) = e { @@ -459,6 +455,10 @@ impl Plugin for ArpegiatorPlugin { } } } + + fn get_parameter_object(&mut self) -> Arc { + Arc::clone(&self.parameters) as Arc + } } #[cfg(not(feature="midi_hack_transmission"))] diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index a3d0f8f..5f6d05d 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -100,6 +100,24 @@ impl Plugin for ArpegiatorPatternReceiver { } } + fn new(host: HostCallback) -> Self { + logging_setup(); + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, + $.compiler, $.timestamp)); + + ArpegiatorPatternReceiver { + host, + #[cfg(not(feature="midi_hack_transmission"))] + ipc_worker_sender: None, + messages: vec![], + current_time: 0, + resumed: false, + parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()), + #[cfg(feature="midi_hack_transmission")] + send_buffer: Default::default() + } + } + fn resume(&mut self) { if self.resumed { return; @@ -135,24 +153,6 @@ impl Plugin for ArpegiatorPatternReceiver { } } - fn new(host: HostCallback) -> Self { - logging_setup(); - info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, - $.compiler, $.timestamp)); - - ArpegiatorPatternReceiver { - host, - #[cfg(not(feature="midi_hack_transmission"))] - ipc_worker_sender: None, - messages: vec![], - current_time: 0, - resumed: false, - parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()), - #[cfg(feature="midi_hack_transmission")] - send_buffer: Default::default() - } - } - fn can_do(&self, can_do: CanDo) -> vst::api::Supported { use vst::api::Supported::*; use vst::plugin::CanDo::*; @@ -205,10 +205,18 @@ impl Plugin for ArpegiatorPatternReceiver { self.messages.extend(events.events().map(|event| match event { #[allow(unused_mut)] - Event::Midi(mut event) => Ok(MidiMessageWithDelta { - delta_frames: event.delta_frames as u16, - data: event.data.into() - }), + Event::Midi(mut event) => { + #[cfg(not(feature = "midi_hack_transmission"))] { + Ok(MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: event.data.into(), + }) + } + #[cfg(feature = "midi_hack_transmission")] { + event.data[0] -= 0x80; + Ok(event) + } + }, Event::SysEx(_) => Err(()), Event::Deprecated(_) => Err(()) }).filter(|item| item.is_ok()).map(|item| item.unwrap())); From 3a62582579c591c8853b73ba8965612de7108519 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Tue, 12 Jan 2021 12:35:58 +0100 Subject: [PATCH 21/47] output notes again --- arpegiator/src/lib.rs | 16 +++++++++------- util/src/midi_message_with_delta.rs | 15 +++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index fc7780b..d7275a1 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -301,16 +301,16 @@ impl Plugin for ArpegiatorPlugin { let notes_device_in = &mut self.notes_device_in; #[cfg(feature = "midi_hack_transmission")] - let (events, pattern_messages) : (Vec, Vec) = { - let (mut events, mut patterns) : (Vec, Vec) = self.events.drain(..).partition( + let (pattern_messages, notes) : (Vec, Vec) = { + let (mut patterns, mut notes) : (Vec, Vec) = self.events.drain(..).partition( |item| item.data[0] < 0x80); - events.sort_by_key(|x| x.delta_frames); + notes.sort_by_key(|x| x.delta_frames); patterns.sort_by_key(|x| x.delta_frames); let patterns = patterns.iter().map(|event| MidiMessageWithDelta { delta_frames: event.delta_frames as u16, data: RawMessage::from([event.data[0] + 0x80, event.data[1], event.data[2]]) }).collect_vec(); - (events, patterns) + (patterns, notes) }; let pattern_changes = pattern_messages.into_iter().map(|message| { @@ -319,7 +319,7 @@ impl Plugin for ArpegiatorPlugin { SourceChange::PatternChange(change) }); - let note_changes = events.iter().map(|event| { + let note_changes = notes.iter().map(|event| { let midi_message_with_delta = MidiMessageWithDelta { delta_frames: event.delta_frames as u16, data: event.data.into(), @@ -440,8 +440,9 @@ impl Plugin for ArpegiatorPlugin { self.device_out.flush_to(local_time, &worker_channels.command_sender) } - // TODO - //self.send_buffer.send_events(events, &mut self._host); + #[cfg(feature = "midi_hack_transmission")] { + self.send_buffer.send_events(take(&mut self.device_out.queue), &mut self._host); + } self.events.clear(); @@ -451,6 +452,7 @@ impl Plugin for ArpegiatorPlugin { fn process_events(&mut self, events: &api::Events) { for e in events.events() { if let Event::Midi(e) = e { + #[cfg(feature = "device_debug")] info!("Received {:2X?}", e.data); self.events.push(e); } } diff --git a/util/src/midi_message_with_delta.rs b/util/src/midi_message_with_delta.rs index 2e5d378..7f22d6e 100644 --- a/util/src/midi_message_with_delta.rs +++ b/util/src/midi_message_with_delta.rs @@ -1,6 +1,7 @@ use serde::{Serialize, Deserialize}; use vst::event::MidiEvent; use crate::raw_message::RawMessage; +use vst::buffer::PlaceholderEvent; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] @@ -10,6 +11,20 @@ pub struct MidiMessageWithDelta { } +impl vst::buffer::WriteIntoPlaceholder for MidiMessageWithDelta { + fn write_into(&self, out: &mut PlaceholderEvent) { + MidiEvent { + data: self.data.into(), + delta_frames: self.delta_frames as i32, + live: false, + note_length: None, + note_offset: None, + detune: 0, + note_off_velocity: 0 + }.write_into(out) + } +} + impl MidiMessageWithDelta { pub fn new_midi_event(&self) -> MidiEvent { MidiEvent { From 3d303ec0120e273566c1a9e32aa5fda8ab1a0318 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Tue, 12 Jan 2021 13:00:23 +0100 Subject: [PATCH 22/47] redefine pressure_as_aftertouch/pressure_as_channel_pressure --- arpegiator/Cargo.toml | 8 ++++++-- arpegiator/src/lib.rs | 9 +++++---- arpegiator/src/midi_messages/expressive_note.rs | 8 ++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index e25d21f..0fbd98f 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -27,8 +27,12 @@ name = "arpegiator" crate-type = ["cdylib", "lib"] [features] -default = ["use_channel_pressure", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] -use_channel_pressure = [] +default = ["pressure_as_channel_pressure", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] + +pressure_as_channel_pressure = [] +pressure_as_aftertouch = [] +pressure_as_cc7 = [] + worker_debug = [] forward_note_cc = [] forward_pattern_cc = [] diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index d7275a1..656d3e3 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -17,9 +17,9 @@ use midi_messages::device::{Device, DeviceChange, Expression}; use midi_messages::device_out::DeviceOut; use util::logging::logging_setup; use util::messages::{PitchBend, Timbre}; -#[cfg(not(feature = "use_channel_pressure"))] +#[cfg(feature = "pressure_as_aftertouch")] use util::messages::AfterTouch; -#[cfg(feature = "use_channel_pressure")] +#[cfg(feature = "pressure_as_channel_pressure")] use util::messages::Pressure; use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; @@ -378,11 +378,12 @@ impl Plugin for ArpegiatorPlugin { Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into()) } Expression::Pressure | Expression::AfterTouch => { - #[cfg(feature = "use_channel_pressure")] { + #[cfg(feature = "pressure_as_channel_pressure")] { Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) } - #[cfg(not(feature = "use_channel_pressure"))] match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { + #[cfg(feature = "pressure_as_aftertouch")] match self.notes_device_in.notes.values() + .sorted().nth(pattern.index as usize) { None => None, Some(note) => { if let Some(pitch) = pattern.transpose(note.pitch) { diff --git a/arpegiator/src/midi_messages/expressive_note.rs b/arpegiator/src/midi_messages/expressive_note.rs index 3af9db4..3ebcecd 100644 --- a/arpegiator/src/midi_messages/expressive_note.rs +++ b/arpegiator/src/midi_messages/expressive_note.rs @@ -4,10 +4,10 @@ use log::info; use util::raw_message::RawMessage; use util::messages::{NoteOn, Timbre, PitchBend}; -#[cfg(feature="use_channel_pressure")] +#[cfg(feature= "pressure_as_channel_pressure")] use util::messages::Pressure; -#[cfg(not(feature="use_channel_pressure"))] +#[cfg(feature= "pressure_as_aftertouch")] use util::messages::AfterTouch; pub struct ExpressiveNote { @@ -21,7 +21,7 @@ pub struct ExpressiveNote { impl ExpressiveNote { - #[cfg(not(feature="use_channel_pressure"))] + #[cfg(feature= "pressure_as_aftertouch")] #[inline] fn get_pressure_note(&self) -> RawMessage { AfterTouch { @@ -31,7 +31,7 @@ impl ExpressiveNote { }.into() } - #[cfg(feature="use_channel_pressure")] + #[cfg(feature= "pressure_as_channel_pressure")] #[inline] fn get_pressure_note(&self) -> RawMessage { Pressure { From 25a90dbc84970dd7021588936e1732a50089a780 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Tue, 12 Jan 2021 20:37:44 +0100 Subject: [PATCH 23/47] implement pressure_as_cc7 --- arpegiator/src/lib.rs | 52 +++++++++++++------ arpegiator/src/midi_messages/device.rs | 6 +++ .../src/midi_messages/expressive_note.rs | 13 +++++ arpegiator_pattern_receiver/src/lib.rs | 7 ++- util/src/logging.rs | 3 +- 5 files changed, 63 insertions(+), 18 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 656d3e3..45071b6 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -21,6 +21,8 @@ use util::messages::{PitchBend, Timbre}; use util::messages::AfterTouch; #[cfg(feature = "pressure_as_channel_pressure")] use util::messages::Pressure; +#[cfg(feature = "pressure_as_cc7")] +use util::messages::CC; use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; #[cfg(not(feature="midi_hack_transmission"))] @@ -139,9 +141,19 @@ impl Plugin for ArpegiatorPlugin { fn new(host: HostCallback) -> Self { logging_setup(); - info!("{} use_channel_pressure: {}", - build_info::format!("{{{} v{} built with {} at {}}} ", $.crate_info.name, $.crate_info.version, $ - .compiler, $.timestamp), cfg!(feature = "use_channel_pressure")); + info!("{} \ + pressure_as_cc7: {} \ + pressure_as_aftertouch: {} \ + pressure_as_channel_pressure: {} \ + midi_hack_transmission {} \ + ", + build_info::format!("{{{} v{} built with {} at {}}} ", + $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp), + cfg!(feature = "pressure_as_cc7"), + cfg!(feature = "pressure_as_aftertouch"), + cfg!(feature = "pressure_as_channel_pressure"), + cfg!(feature = "midi_hack_transmission"), + ); ArpegiatorPlugin { events: vec![], @@ -382,18 +394,28 @@ impl Plugin for ArpegiatorPlugin { Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) } - #[cfg(feature = "pressure_as_aftertouch")] match self.notes_device_in.notes.values() - .sorted().nth(pattern.index as usize) { - None => None, - Some(note) => { - if let Some(pitch) = pattern.transpose(note.pitch) { - Some(AfterTouch { - channel: pattern.channel, - pitch, - value: pattern.pressure, - }.into()) - } else { - None + #[cfg(any(feature = "pressure_as_aftertouch", feature="pressure_as_cc7"))] { + match self.notes_device_in.nth(pattern.index as usize) { + None => None, + Some(note) => { + if let Some(pitch) = pattern.transpose(note.pitch) { + #[cfg(feature="pressure_as_aftertouch")] { + Some(AfterTouch { + channel: pattern.channel, + pitch, + value: pattern.pressure, + }.into()) + } + #[cfg(feature="pressure_as_cc7")] { + Some(CC { + channel: pattern.channel, + cc: 7, + value: pattern.pressure, + }.into()) + } + } else { + None + } } } } diff --git a/arpegiator/src/midi_messages/device.rs b/arpegiator/src/midi_messages/device.rs index 3585a6b..52ad02c 100644 --- a/arpegiator/src/midi_messages/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -9,6 +9,7 @@ use util::midi_message_with_delta::MidiMessageWithDelta; use crate::midi_messages::note::{NoteIndex, Note, CCIndex}; use crate::midi_messages::timed_event::TimedEvent; +use itertools::Itertools; pub struct Device { @@ -34,6 +35,11 @@ impl Device { note_index: 0, } } + + #[inline] + pub fn nth(&self, n: usize) -> Option<&Note> { + self.notes.values().sorted().nth(n) + } } diff --git a/arpegiator/src/midi_messages/expressive_note.rs b/arpegiator/src/midi_messages/expressive_note.rs index 3ebcecd..3926086 100644 --- a/arpegiator/src/midi_messages/expressive_note.rs +++ b/arpegiator/src/midi_messages/expressive_note.rs @@ -10,6 +10,9 @@ use util::messages::Pressure; #[cfg(feature= "pressure_as_aftertouch")] use util::messages::AfterTouch; +#[cfg(feature= "pressure_as_cc7")] +use util::messages::CC; + pub struct ExpressiveNote { pub channel: u8, pub pitch: u8, @@ -31,6 +34,16 @@ impl ExpressiveNote { }.into() } + #[cfg(feature= "pressure_as_cc7")] + #[inline] + fn get_pressure_note(&self) -> RawMessage { + CC { + channel: self.channel, + value: self.pressure, + cc: 7 + }.into() + } + #[cfg(feature= "pressure_as_channel_pressure")] #[inline] fn get_pressure_note(&self) -> RawMessage { diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index 5f6d05d..7ed9ff7 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -102,8 +102,11 @@ impl Plugin for ArpegiatorPatternReceiver { fn new(host: HostCallback) -> Self { logging_setup(); - info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, - $.compiler, $.timestamp)); + info!("{} midi_hack_transmission={}", + build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, + $.crate_info.version, + $.compiler, $.timestamp), + cfg!(feature = "midi_hack_transmission")); ArpegiatorPatternReceiver { host, diff --git a/util/src/logging.rs b/util/src/logging.rs index 16af9f1..dbf9da7 100644 --- a/util/src/logging.rs +++ b/util/src/logging.rs @@ -16,7 +16,8 @@ pub fn logging_setup() { WriteLogger::init( LevelFilter::Info, config.build(), file, ).unwrap(); - info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp)) + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $ + .compiler, $.timestamp)) } log_panics::init(); From 8811094e3ce3c2289f20dae8f2c929c290930d5f Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Wed, 13 Jan 2021 22:52:30 +0100 Subject: [PATCH 24/47] implement pitchbend --- arpegiator/Cargo.toml | 2 +- arpegiator/src/lib.rs | 27 +++++++++++---- arpegiator/src/midi_messages/device.rs | 19 ++++++----- arpegiator/src/midi_messages/device_out.rs | 33 ++++++++++++++----- .../src/midi_messages/pattern_device.rs | 15 +++++++-- 5 files changed, 68 insertions(+), 28 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 0fbd98f..1eb821f 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -27,7 +27,7 @@ name = "arpegiator" crate-type = ["cdylib", "lib"] [features] -default = ["pressure_as_channel_pressure", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] +default = ["pressure_as_cc7", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] pressure_as_channel_pressure = [] pressure_as_aftertouch = [] diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 45071b6..fc2e750 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -348,9 +348,22 @@ impl Plugin for ArpegiatorPlugin { match change { SourceChange::NoteChange(change) => { - // TODO note change should trigger pitchbend events match change { - DeviceChange::AddNote { .. } => {} + DeviceChange::AddNote { note , .. } => { + let note_position = self.notes_device_in.notes.values().position(|n| { + n.pitch < note.pitch + }); + + if let Some(position) = note_position { + for pattern in self.pattern_device.at(position as u8) { + // careful here, the output note can have an octave difference + if let Some(target_pitch) = pattern.transpose(note.pitch) { + self.device_out.update_pitch(pattern.id, target_pitch, delta_frames, + current_time_in_samples); + } + } + } + } DeviceChange::RemoveNote { .. } => {} DeviceChange::NoteExpressionChange { .. } => {} DeviceChange::ReplaceNote { .. } => {} @@ -365,14 +378,14 @@ impl Plugin for ArpegiatorPlugin { let _ = self.device_out.update(message, current_time_in_samples, None); } } - DeviceChange::None { .. } => {} + DeviceChange::Ignored { .. } => {} } } SourceChange::PatternChange(change) => { match change { PatternDeviceChange::AddPattern { pattern, .. } => { // TODO "hold notes" logic - match self.notes_device_in.notes.values().sorted().nth(pattern.index as usize) { + match self.notes_device_in.nth(pattern.index as usize) { None => {} Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time_in_samples) } @@ -398,11 +411,11 @@ impl Plugin for ArpegiatorPlugin { match self.notes_device_in.nth(pattern.index as usize) { None => None, Some(note) => { - if let Some(pitch) = pattern.transpose(note.pitch) { + if let Some(_pitch) = pattern.transpose(note.pitch) { #[cfg(feature="pressure_as_aftertouch")] { Some(AfterTouch { channel: pattern.channel, - pitch, + _pitch, value: pattern.pressure, }.into()) } @@ -464,7 +477,7 @@ impl Plugin for ArpegiatorPlugin { } #[cfg(feature = "midi_hack_transmission")] { - self.send_buffer.send_events(take(&mut self.device_out.queue), &mut self._host); + self.send_buffer.send_events(take(&mut self.device_out.output_queue), &mut self._host); } self.events.clear(); diff --git a/arpegiator/src/midi_messages/device.rs b/arpegiator/src/midi_messages/device.rs index 52ad02c..19cfd49 100644 --- a/arpegiator/src/midi_messages/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -62,9 +62,10 @@ pub enum DeviceChange { AddNote { time: usize, note: Note }, RemoveNote { time: usize, note: Note }, NoteExpressionChange { time: usize, expression: Expression, note: Note }, + // replacing happens when a note on is triggered for a note and channel that is already on ReplaceNote { time: usize, old_note: Note, new_note: Note }, CCChange { time: usize, cc: CC }, - None { time: usize }, + Ignored { time: usize }, } @@ -76,7 +77,7 @@ impl TimedEvent for DeviceChange { DeviceChange::NoteExpressionChange { time, .. } => *time, DeviceChange::ReplaceNote { time, .. } => *time, DeviceChange::CCChange { time, .. } => *time, - DeviceChange::None { time, .. } => *time + DeviceChange::Ignored { time, .. } => *time } } @@ -89,7 +90,7 @@ impl TimedEvent for DeviceChange { DeviceChange::NoteExpressionChange { note, .. } => note.id, DeviceChange::ReplaceNote { new_note: note, .. } => note.id, DeviceChange::CCChange { .. } => 0, - DeviceChange::None { .. } => 0 + DeviceChange::Ignored { .. } => 0 } } } @@ -159,7 +160,7 @@ impl Device { match self.notes.remove(&index) { None => { info!("Attempt to remove note, but it was not found {:02X?}", index); - DeviceChange::None { time } + DeviceChange::Ignored { time } } Some(mut old_note) => { //info!("Removed note {:02X?}", index); @@ -202,7 +203,7 @@ impl Device { }; } } - DeviceChange::None { time } + DeviceChange::Ignored { time } }, MidiMessageType::AfterTouchMessage(message) => { // redundant with pressure, but that's the message that bitwig will properly handle for by-note @@ -219,7 +220,7 @@ impl Device { }; } } - DeviceChange::None { time } + DeviceChange::Ignored { time } }, MidiMessageType::PitchBendMessage(message) => { self.channels[message.channel as usize].pitchbend = message.millisemitones; @@ -235,10 +236,10 @@ impl Device { }; } } - DeviceChange::None { time } + DeviceChange::Ignored { time } } - MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::None { time }, - MidiMessageType::Unsupported => DeviceChange::None { time }, + MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::Ignored { time }, + MidiMessageType::Unsupported => DeviceChange::Ignored { time }, } } diff --git a/arpegiator/src/midi_messages/device_out.rs b/arpegiator/src/midi_messages/device_out.rs index 3c882bc..bc021a6 100644 --- a/arpegiator/src/midi_messages/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -5,7 +5,7 @@ use { std::mem::take }; -use util::messages::NoteOff; +use util::messages::{NoteOff, PitchBend}; use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; @@ -17,8 +17,8 @@ use crate::midi_messages::note::Note; pub(crate) struct DeviceOut { - pub device: Device, - pub queue: Vec, + device: Device, + pub output_queue: Vec, } @@ -26,35 +26,52 @@ impl DeviceOut { pub fn new(name: String) -> Self { Self { device: Device::new(name), - queue: vec![] + output_queue: vec![] } } pub fn update(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) { - self.queue.push(midi_message); + self.output_queue.push(midi_message); self.device.update(midi_message, current_time, id); } #[cfg(not(feature="midi_hack_transmission"))] pub fn flush_to(&mut self, reception_time: u64, midi_output_sender: &Sender) { - if self.queue.is_empty() { + if self.output_queue.is_empty() { return } { midi_output_sender.try_send( WorkerCommand::SendToMidiOutput { - reception_time, messages: take(&mut self.queue) + reception_time, messages: take(&mut self.output_queue) }).unwrap_or_else( |err| error!("Could not send to the controller worker {}", err) ); } } + pub fn update_pitch(&mut self, note_id: usize, target_pitch: u8, delta_frames: u16, current_time: usize) { + match self.device.notes.values().find(|note| note.id == note_id) { + None => {} + Some(note) => { + let raw_message: RawMessage = PitchBend { + channel: note.channel, + millisemitones: (target_pitch - note.pitch) as i32 * 1000 + }.into(); + self.update(MidiMessageWithDelta { + delta_frames, + data: raw_message + }, current_time, None); + } + }; + } + pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { let note = match self.device.notes.values().find(|note| note.id == note_id) { None => { - // info!("Cannot find note to stop: {:02x?}", note_id); + #[cfg(feature="device_debug")] + info!("Cannot find note to stop: {:02x?}", note_id); return; } Some(note) => note diff --git a/arpegiator/src/midi_messages/pattern_device.rs b/arpegiator/src/midi_messages/pattern_device.rs index c673c73..1ee4033 100644 --- a/arpegiator/src/midi_messages/pattern_device.rs +++ b/arpegiator/src/midi_messages/pattern_device.rs @@ -1,7 +1,9 @@ use log::error; + +use core::iter::Filter; use std::cmp::Ordering; use std::collections::HashMap; - +use std::collections::hash_map::Values; use util::messages::CC; use crate::midi_messages::device::{DeviceChange, Expression}; @@ -9,9 +11,10 @@ use crate::midi_messages::pattern::Pattern; use crate::midi_messages::timed_event::TimedEvent; + #[derive(Default)] pub struct PatternDevice { - pub patterns: HashMap + patterns: HashMap } @@ -115,7 +118,13 @@ impl PatternDevice { } } DeviceChange::CCChange { time, cc } => PatternDeviceChange::CC { cc, time }, - DeviceChange::None { time } => PatternDeviceChange::None { time } + DeviceChange::Ignored { time } => PatternDeviceChange::None { time } } } + + pub fn at(&self, index: u8) -> Filter, PatternIteratorClosure> { + self.patterns.values().filter(Box::new(move |pattern| pattern.index == index)) + } } + +type PatternIteratorClosure = Box bool>; From 5e7eb831e4e7b550dfa295322fc539f6ffba1560 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 14 Jan 2021 20:31:12 +0100 Subject: [PATCH 25/47] pitchbend at fixed semitones --- arpegiator/src/lib.rs | 43 ++++++++++++++++------ arpegiator/src/midi_messages/device_out.rs | 12 +++++- arpegiator/src/midi_messages/note.rs | 13 +++++++ arpegiator/src/parameters.rs | 30 ++++++++------- note_generator/src/parameters.rs | 2 +- util/src/messages.rs | 7 +++- 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index fc2e750..e134e9a 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -36,11 +36,12 @@ use { mach::mach_time::mach_absolute_time }; -use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT}; +use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT, PitchBendValues}; use crate::midi_messages::change::SourceChange; use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; use util::system::Uuid; use crate::midi_messages::timed_event::TimedEvent; +use std::cmp::Ordering; #[cfg(not(feature="midi_hack_transmission"))] mod workers; #[cfg(not(feature="midi_hack_transmission"))] mod system; @@ -349,18 +350,36 @@ impl Plugin for ArpegiatorPlugin { match change { SourceChange::NoteChange(change) => { match change { - DeviceChange::AddNote { note , .. } => { - let note_position = self.notes_device_in.notes.values().position(|n| { - n.pitch < note.pitch - }); - - if let Some(position) = note_position { - for pattern in self.pattern_device.at(position as u8) { - // careful here, the output note can have an octave difference - if let Some(target_pitch) = pattern.transpose(note.pitch) { - self.device_out.update_pitch(pattern.id, target_pitch, delta_frames, - current_time_in_samples); + DeviceChange::AddNote { .. } => { + match self.parameters.get_pitchbend() { + PitchBendValues::Off => {} + PitchBendValues::DurationToReachTarget(_) => {} + PitchBendValues::Immediate => { + // incoming note can move before other notes, so we have to recalculate pitches of + // all playing notes + for (position, note) in self.notes_device_in.notes.values().sorted_by(|item1, + item2| { + let pitch_cmp = item1.pitch.cmp(&item2.pitch); + match pitch_cmp { + Ordering::Equal => { + item1.channel.cmp(&item2.channel) + } + _ => pitch_cmp + } + }).enumerate() { + #[cfg(feature="device_debug")] + info!("note will be applied to pattern {}", position); + for pattern in self.pattern_device.at(position as u8) { + if let Some(target_pitch) = pattern.transpose(note.pitch) { + #[cfg(feature="device_debug")] + info!("Applying pitchbend to pattern {}, at position {}", + pattern.id, target_pitch); + self.device_out.update_pitch(pattern.id, target_pitch, delta_frames, + current_time_in_samples); + } + } } + } } } diff --git a/arpegiator/src/midi_messages/device_out.rs b/arpegiator/src/midi_messages/device_out.rs index bc021a6..91ce26c 100644 --- a/arpegiator/src/midi_messages/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -33,6 +33,10 @@ impl DeviceOut { pub fn update(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) { self.output_queue.push(midi_message); self.device.update(midi_message, current_time, id); + if !self.device.notes.is_empty() { + #[cfg(feature="device_debug")] + info!("Device out state after update: {:2X?}", self.device.notes) + } } #[cfg(not(feature="midi_hack_transmission"))] @@ -53,11 +57,15 @@ impl DeviceOut { pub fn update_pitch(&mut self, note_id: usize, target_pitch: u8, delta_frames: u16, current_time: usize) { match self.device.notes.values().find(|note| note.id == note_id) { - None => {} + None => { + info!("Cannot find note to pitchbend. Required note_id: {}. Current notes: {:02X?}", + note_id, self.device.notes.values() + ) + } Some(note) => { let raw_message: RawMessage = PitchBend { channel: note.channel, - millisemitones: (target_pitch - note.pitch) as i32 * 1000 + millisemitones: (target_pitch as i32 - note.pitch as i32) * 1000 }.into(); self.update(MidiMessageWithDelta { delta_frames, diff --git a/arpegiator/src/midi_messages/note.rs b/arpegiator/src/midi_messages/note.rs index 97cc142..5bc8047 100644 --- a/arpegiator/src/midi_messages/note.rs +++ b/arpegiator/src/midi_messages/note.rs @@ -46,6 +46,19 @@ impl PartialOrd for Note { } +impl PartialOrd for NoteIndex { + fn partial_cmp(&self, other: &Self) -> Option { + let cmp = self.pitch.cmp(&other.pitch); + + if cmp != Ordering::Equal { + Some(cmp) + } else { + Some(self.channel.cmp(&other.channel)) + } + } +} + + impl PartialEq for Note { fn eq(&self, other: &Self) -> bool { self.id == other.id diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 6a1f15b..d7a84e2 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -47,7 +47,7 @@ think about those cases: // note: if pitchbend is not off, it makes sense to consume note pitchbend as is // ideally while a note eposes its pitch and a pitchbend value, a method should directly tell the pitch in // millisemitones relative to 0 ( C-2 ) -enum PitchBendValues { +pub(crate) enum PitchBendValues { Off, // no pitchbend, means same pitch until pattern ends DurationToReachTarget(f32), Immediate @@ -127,6 +127,17 @@ impl ArpegiatorParameters { parameters.set_parameter(Parameter::PatternLegato.into(), 1.); parameters } + + pub fn get_pitchbend(&self) -> PitchBendValues { + match self.get_parameter(Parameter::Pitchbend.into()) { + x if x <= 0. => PitchBendValues::Immediate, + x if x >= 1. => PitchBendValues::Off, + _ => { + let value = self.get_exponential_scale_parameter(Parameter::Pitchbend, 1., 80.); + PitchBendValues::DurationToReachTarget(value) + } + } + } } @@ -144,18 +155,11 @@ impl PluginParameters for ArpegiatorParameters { false => "Off" }.to_string() } - Parameter::Pitchbend => { // TODO set default to 1 - match self.get_parameter(index) { - x if x <= 0. => { - "Immediate".into() - } - x if x >= 1. => { - "Off".into() - } - _ => { - let value = self.get_exponential_scale_parameter(Parameter::Pitchbend, 1., 80.); - duration_display(value) - } + Parameter::Pitchbend => { + match self.get_pitchbend() { + PitchBendValues::Off => "Off".into(), + PitchBendValues::DurationToReachTarget(value) => duration_display(value), + PitchBendValues::Immediate => "Immediate".into() } } } diff --git a/note_generator/src/parameters.rs b/note_generator/src/parameters.rs index ced0982..93e3f2f 100644 --- a/note_generator/src/parameters.rs +++ b/note_generator/src/parameters.rs @@ -90,7 +90,7 @@ impl NoteGeneratorPluginParameters { #[inline] fn get_pitchbend_label(&self) -> String { let semitones = self.get_u14_parameter(Parameter::PitchBend); - format!("{:.2} semitones", ((semitones as i32 * 96000 / 16383) - 48000) as f32 / 1000.) + format!("{:.2} semitones", ((semitones as i32 * 96000 / 16384) - 48000) as f32 / 1000.) } #[inline] diff --git a/util/src/messages.rs b/util/src/messages.rs index 965f4d1..440666f 100644 --- a/util/src/messages.rs +++ b/util/src/messages.rs @@ -1,3 +1,6 @@ +#[allow(unused_imports)] +use log::{info,error}; + use vst::event::Event::Midi; use vst::event::{Event, MidiEvent}; @@ -180,7 +183,7 @@ impl Into for PitchBend { fn into(self) -> RawMessage { // 96000 millisemitones are expressed over the possible values of 14 bits ( 16384 ) // which never gets us an exact integer amount of semitones - let value = ((self.millisemitones + 48000) * 16383) / 96000; + let value = ((self.millisemitones + 48000) * 16384) / 96000; let msb = value >> 7; let lsb = value & 0x7F; [self.channel + PITCHBEND, lsb as u8, msb as u8].into() @@ -192,7 +195,7 @@ impl From for PitchBend { let lsb : i32 = data[1] as i32; let msb : i32 = data[2] as i32; let value = lsb + (msb << 7); - let millisemitones = (value * 96000 / 16383) - 48000; + let millisemitones = (value * 96000 / 16384) - 48000; PitchBend { channel: data[0] & 0x0F, From 8ef64877d4df7c51fdaa8263bbbcb0ec464a4ba2 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 14 Jan 2021 20:35:12 +0100 Subject: [PATCH 26/47] reformat --- arpegiator/src/lib.rs | 339 +++++++++++------- arpegiator/src/midi_messages/change.rs | 61 ++-- arpegiator/src/midi_messages/device.rs | 98 +++-- arpegiator/src/midi_messages/device_out.rs | 83 +++-- .../src/midi_messages/expressive_note.rs | 35 +- arpegiator/src/midi_messages/mod.rs | 2 +- arpegiator/src/midi_messages/note.rs | 11 +- arpegiator/src/midi_messages/pattern.rs | 8 +- .../src/midi_messages/pattern_device.rs | 117 +++--- arpegiator/src/midi_messages/timed_event.rs | 4 +- arpegiator/src/parameters.rs | 99 ++--- arpegiator/src/system.rs | 6 +- arpegiator/src/workers/ipc_worker.rs | 308 +++++++++------- arpegiator/src/workers/main_worker.rs | 88 +++-- arpegiator/src/workers/midi_output_worker.rs | 114 +++--- arpegiator/src/workers/mod.rs | 9 +- arpegiator_pattern_receiver/src/ipc_worker.rs | 47 +-- arpegiator_pattern_receiver/src/lib.rs | 135 +++---- arpegiator_pattern_receiver/src/parameters.rs | 53 ++- audio_data/src/lib.rs | 30 +- max_note_duration/src/lib.rs | 34 +- max_note_duration/src/parameters.rs | 9 +- midi_delay/src/lib.rs | 36 +- midi_delay/src/parameters.rs | 12 +- note_fan_out/src/lib.rs | 58 ++- note_fan_out/src/parameters.rs | 26 +- note_generator/src/bin/host.rs | 13 +- note_generator/src/lib.rs | 12 +- note_generator/src/parameters.rs | 51 ++- note_off_delay/src/bin/note_off_host.rs | 17 +- note_off_delay/src/lib.rs | 36 +- note_off_delay/src/parameters.rs | 19 +- util/src/absolute_time_midi_message.rs | 15 +- util/src/absolute_time_midi_message_vector.rs | 27 +- util/src/constants.rs | 4 +- util/src/debug.rs | 2 +- util/src/delayed_message_consumer.rs | 140 +++++--- util/src/ipc_payload.rs | 9 +- util/src/lib.rs | 18 +- util/src/logging.rs | 22 +- util/src/messages.rs | 58 +-- util/src/midi_message_type.rs | 17 +- util/src/midi_message_with_delta.rs | 11 +- util/src/parameter_value_conversion.rs | 1 - util/src/parameters.rs | 20 +- util/src/raw_message.rs | 13 +- util/src/system.rs | 8 +- util/src/transmute_buffer.rs | 10 +- 48 files changed, 1283 insertions(+), 1062 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index e134e9a..8979e96 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -1,7 +1,7 @@ #[allow(unused_imports)] use { + log::{error, info}, std::mem::take, - log::{error, info} }; use std::os::raw::c_void; @@ -16,47 +16,42 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use midi_messages::device::{Device, DeviceChange, Expression}; use midi_messages::device_out::DeviceOut; use util::logging::logging_setup; -use util::messages::{PitchBend, Timbre}; #[cfg(feature = "pressure_as_aftertouch")] use util::messages::AfterTouch; #[cfg(feature = "pressure_as_channel_pressure")] use util::messages::Pressure; #[cfg(feature = "pressure_as_cc7")] use util::messages::CC; +use util::messages::{PitchBend, Timbre}; use util::midi_message_with_delta::MidiMessageWithDelta; use util::raw_message::RawMessage; -#[cfg(not(feature="midi_hack_transmission"))] -use { - workers::main_worker::{create_worker_thread, WorkerChannels, WorkerCommand}, -}; +#[cfg(not(feature = "midi_hack_transmission"))] +use workers::main_worker::{create_worker_thread, WorkerChannels, WorkerCommand}; -#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(not(feature = "midi_hack_transmission"))] #[cfg(target_os = "macos")] -use { - mach::mach_time::mach_absolute_time -}; +use mach::mach_time::mach_absolute_time; -use crate::parameters::{ArpegiatorParameters, PARAMETER_COUNT, PitchBendValues}; use crate::midi_messages::change::SourceChange; use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; -use util::system::Uuid; use crate::midi_messages::timed_event::TimedEvent; +use crate::parameters::{ArpegiatorParameters, PitchBendValues, PARAMETER_COUNT}; use std::cmp::Ordering; +use util::system::Uuid; -#[cfg(not(feature="midi_hack_transmission"))] mod workers; -#[cfg(not(feature="midi_hack_transmission"))] mod system; +#[cfg(not(feature = "midi_hack_transmission"))] +mod system; +#[cfg(not(feature = "midi_hack_transmission"))] +mod workers; mod midi_messages; mod parameters; - #[macro_use] extern crate vst; - plugin_main!(ArpegiatorPlugin); - pub struct ArpegiatorPlugin { events: Vec, _host: HostCallback, @@ -75,23 +70,25 @@ pub struct ArpegiatorPlugin { resumed: bool, } - impl ArpegiatorPlugin { #[cfg(not(feature = "midi_hack_transmission"))] fn close_worker(&mut self, event_id: Uuid) { if let Some(worker_channels) = take(&mut self.worker_channels) { - #[cfg(feature = "worker_debug")] info!("[{}] stopping workers", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] stopping workers", event_id); if let Err(e) = worker_channels.command_sender.try_send(WorkerCommand::Stop(event_id)) { error!("[{}] Error while closing worker channel : {:?}", event_id, e) } if let Err(err) = worker_channels.worker.join() { - error!("[{}] Error while waiting for worker thread to finish {:?}", event_id, err) + error!( + "[{}] Error while waiting for worker thread to finish {:?}", + event_id, err + ) } } } } - impl Default for ArpegiatorPlugin { fn default() -> Self { ArpegiatorPlugin { @@ -114,7 +111,6 @@ impl Default for ArpegiatorPlugin { } } - impl Plugin for ArpegiatorPlugin { fn get_info(&self) -> Info { Info { @@ -142,19 +138,20 @@ impl Plugin for ArpegiatorPlugin { fn new(host: HostCallback) -> Self { logging_setup(); - info!("{} \ + info!( + "{} \ pressure_as_cc7: {} \ pressure_as_aftertouch: {} \ pressure_as_channel_pressure: {} \ midi_hack_transmission {} \ ", - build_info::format!("{{{} v{} built with {} at {}}} ", + build_info::format!("{{{} v{} built with {} at {}}} ", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp), - cfg!(feature = "pressure_as_cc7"), - cfg!(feature = "pressure_as_aftertouch"), - cfg!(feature = "pressure_as_channel_pressure"), - cfg!(feature = "midi_hack_transmission"), - ); + cfg!(feature = "pressure_as_cc7"), + cfg!(feature = "pressure_as_aftertouch"), + cfg!(feature = "pressure_as_channel_pressure"), + cfg!(feature = "midi_hack_transmission"), + ); ArpegiatorPlugin { events: vec![], @@ -176,9 +173,12 @@ impl Plugin for ArpegiatorPlugin { } fn set_sample_rate(&mut self, rate: f32) { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] if let Some(workers_channel) = &self.worker_channels { - workers_channel.command_sender.try_send(WorkerCommand::SetSampleRate(rate)).unwrap() + workers_channel + .command_sender + .try_send(WorkerCommand::SetSampleRate(rate)) + .unwrap() }; self.sample_rate = rate } @@ -186,9 +186,12 @@ impl Plugin for ArpegiatorPlugin { fn set_block_size(&mut self, size: i64) { self.block_size = size; - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] if let Some(workers_channel) = &self.worker_channels { - workers_channel.command_sender.try_send(WorkerCommand::SetBlockSize(size)).unwrap() + workers_channel + .command_sender + .try_send(WorkerCommand::SetBlockSize(size)) + .unwrap() }; } @@ -201,7 +204,8 @@ impl Plugin for ArpegiatorPlugin { let event_id = Uuid::new_v4(); - #[cfg(feature = "worker_debug")] info!("[{}] resume: enter", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] resume: enter", event_id); self.current_time_in_samples = 0; @@ -209,8 +213,14 @@ impl Plugin for ArpegiatorPlugin { { self.close_worker(event_id); let worker_channels = create_worker_thread(); - worker_channels.command_sender.try_send(WorkerCommand::SetPort(self.parameters.get_port(), event_id)).unwrap(); - worker_channels.command_sender.try_send(WorkerCommand::SetSampleRate(self.sample_rate)).unwrap(); + worker_channels + .command_sender + .try_send(WorkerCommand::SetPort(self.parameters.get_port(), event_id)) + .unwrap(); + worker_channels + .command_sender + .try_send(WorkerCommand::SetSampleRate(self.sample_rate)) + .unwrap(); self.worker_channels = match self.parameters.worker_commands.lock() { Ok(mut worker_commands) => { @@ -224,7 +234,8 @@ impl Plugin for ArpegiatorPlugin { }; } - #[cfg(feature = "worker_debug")] info!("[{}] resume: exit", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] resume: exit", event_id); } fn suspend(&mut self) { @@ -238,14 +249,16 @@ impl Plugin for ArpegiatorPlugin { #[cfg(not(feature = "midi_hack_transmission"))] { - #[cfg(feature = "worker_debug")] info!("[{}] suspend enter", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] suspend enter", event_id); if let Ok(mut worker_commands) = self.parameters.worker_commands.lock() { *worker_commands = None } self.close_worker(event_id); } - #[cfg(feature = "worker_debug")] info!("[{}] suspend exit", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] suspend exit", event_id); } fn vendor_specific(&mut self, index: i32, value: isize, ptr: *mut c_void, opt: f32) -> isize { @@ -262,7 +275,13 @@ impl Plugin for ArpegiatorPlugin { use vst::plugin::CanDo::*; match can_do { - SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline | MidiSingleNoteTuningChange | MidiKeyBasedInstrumentControl => Yes, + SendEvents + | SendMidiEvent + | ReceiveEvents + | ReceiveMidiEvent + | Offline + | MidiSingleNoteTuningChange + | MidiKeyBasedInstrumentControl => Yes, Other(s) => { if s == "MPE" { Yes @@ -278,31 +297,35 @@ impl Plugin for ArpegiatorPlugin { fn process(&mut self, buffer: &mut AudioBuffer) { #[cfg(not(feature = "midi_hack_transmission"))] let local_time = { - #[cfg(target_os = "macos")] unsafe { mach_absolute_time() } - #[cfg(target_os = "linux")] 0 + #[cfg(target_os = "macos")] + unsafe { + mach_absolute_time() + } + #[cfg(target_os = "linux")] + 0 }; #[cfg(not(feature = "midi_hack_transmission"))] let pattern_messages = { match self.worker_channels.as_ref() { None => vec![], - Some(socket_channels) => { - match socket_channels.pattern_receiver.try_recv() { - Ok(payload) => { - #[cfg(feature = "device_debug")] - info!("[{}] received patterns : {:02X?}", self.current_time_in_samples, payload); - - payload.messages - } - Err(_) => vec![] + Some(socket_channels) => match socket_channels.pattern_receiver.try_recv() { + Ok(payload) => { + #[cfg(feature = "device_debug")] + info!( + "[{}] received patterns : {:02X?}", + self.current_time_in_samples, payload + ); + + payload.messages } - } + Err(_) => vec![], + }, } }; #[cfg(not(feature = "midi_hack_transmission"))] let events = &self.events; - // from here we cannot accurately tell when the buffer we're building will actually play // so our best guest will be the earliest : // current_time + (delta_frame + buffer_size) * sample_to_second @@ -314,15 +337,18 @@ impl Plugin for ArpegiatorPlugin { let notes_device_in = &mut self.notes_device_in; #[cfg(feature = "midi_hack_transmission")] - let (pattern_messages, notes) : (Vec, Vec) = { - let (mut patterns, mut notes) : (Vec, Vec) = self.events.drain(..).partition( - |item| item.data[0] < 0x80); + let (pattern_messages, notes): (Vec, Vec) = { + let (mut patterns, mut notes): (Vec, Vec) = + self.events.drain(..).partition(|item| item.data[0] < 0x80); notes.sort_by_key(|x| x.delta_frames); patterns.sort_by_key(|x| x.delta_frames); - let patterns = patterns.iter().map(|event| MidiMessageWithDelta { - delta_frames: event.delta_frames as u16, - data: RawMessage::from([event.data[0] + 0x80, event.data[1], event.data[2]]) - }).collect_vec(); + let patterns = patterns + .iter() + .map(|event| MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: RawMessage::from([event.data[0] + 0x80, event.data[1], event.data[2]]), + }) + .collect_vec(); (patterns, notes) }; @@ -357,29 +383,37 @@ impl Plugin for ArpegiatorPlugin { PitchBendValues::Immediate => { // incoming note can move before other notes, so we have to recalculate pitches of // all playing notes - for (position, note) in self.notes_device_in.notes.values().sorted_by(|item1, - item2| { - let pitch_cmp = item1.pitch.cmp(&item2.pitch); - match pitch_cmp { - Ordering::Equal => { - item1.channel.cmp(&item2.channel) + for (position, note) in self + .notes_device_in + .notes + .values() + .sorted_by(|item1, item2| { + let pitch_cmp = item1.pitch.cmp(&item2.pitch); + match pitch_cmp { + Ordering::Equal => item1.channel.cmp(&item2.channel), + _ => pitch_cmp, } - _ => pitch_cmp - } - }).enumerate() { - #[cfg(feature="device_debug")] + }) + .enumerate() + { + #[cfg(feature = "device_debug")] info!("note will be applied to pattern {}", position); for pattern in self.pattern_device.at(position as u8) { - if let Some(target_pitch) = pattern.transpose(note.pitch) { - #[cfg(feature="device_debug")] - info!("Applying pitchbend to pattern {}, at position {}", - pattern.id, target_pitch); - self.device_out.update_pitch(pattern.id, target_pitch, delta_frames, - current_time_in_samples); + if let Some(target_pitch) = pattern.transpose(note.pitch) { + #[cfg(feature = "device_debug")] + info!( + "Applying pitchbend to pattern {}, at position {}", + pattern.id, target_pitch + ); + self.device_out.update_pitch( + pattern.id, + target_pitch, + delta_frames, + current_time_in_samples, + ); } } } - } } } @@ -388,14 +422,15 @@ impl Plugin for ArpegiatorPlugin { DeviceChange::ReplaceNote { .. } => {} DeviceChange::CCChange { cc: _cc, time: _time } => { - #[cfg(feature = "forward_note_cc")] { - let message = MidiMessageWithDelta { - delta_frames, - data: Into::::into(_cc).into(), - }; - - let _ = self.device_out.update(message, current_time_in_samples, None); - } + #[cfg(feature = "forward_note_cc")] + { + let message = MidiMessageWithDelta { + delta_frames, + data: Into::::into(_cc).into(), + }; + + let _ = self.device_out.update(message, current_time_in_samples, None); + } } DeviceChange::Ignored { .. } => {} } @@ -406,44 +441,72 @@ impl Plugin for ArpegiatorPlugin { // TODO "hold notes" logic match self.notes_device_in.nth(pattern.index as usize) { None => {} - Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time_in_samples) + Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time_in_samples), } } - PatternDeviceChange::PatternExpressionChange { expression, pattern, .. } => { + PatternDeviceChange::PatternExpressionChange { + expression, pattern, .. + } => { let raw_message: Option = match expression { - Expression::Timbre => { - Some(Timbre { channel: pattern.channel, value: pattern.timbre }.into()) - } + Expression::Timbre => Some( + Timbre { + channel: pattern.channel, + value: pattern.timbre, + } + .into(), + ), Expression::PitchBend => { // TODO should change the pitch as is, meaning it's the pitchbend is just added to // the result, independently from the notes we're supposed to match // the result should be: target note + pattern pitchbend - Some(PitchBend { channel: pattern.channel, millisemitones: pattern.pitchbend }.into()) + Some( + PitchBend { + channel: pattern.channel, + millisemitones: pattern.pitchbend, + } + .into(), + ) } Expression::Pressure | Expression::AfterTouch => { - #[cfg(feature = "pressure_as_channel_pressure")] { - Some(Pressure { channel: pattern.channel, value: pattern.pressure }.into()) + #[cfg(feature = "pressure_as_channel_pressure")] + { + Some( + Pressure { + channel: pattern.channel, + value: pattern.pressure, + } + .into(), + ) } - #[cfg(any(feature = "pressure_as_aftertouch", feature="pressure_as_cc7"))] { + #[cfg(any(feature = "pressure_as_aftertouch", feature = "pressure_as_cc7"))] + { match self.notes_device_in.nth(pattern.index as usize) { None => None, Some(note) => { if let Some(_pitch) = pattern.transpose(note.pitch) { - #[cfg(feature="pressure_as_aftertouch")] { - Some(AfterTouch { - channel: pattern.channel, - _pitch, - value: pattern.pressure, - }.into()) + #[cfg(feature = "pressure_as_aftertouch")] + { + Some( + AfterTouch { + channel: pattern.channel, + _pitch, + value: pattern.pressure, + } + .into(), + ) } - #[cfg(feature="pressure_as_cc7")] { - Some(CC { - channel: pattern.channel, - cc: 7, - value: pattern.pressure, - }.into()) + #[cfg(feature = "pressure_as_cc7")] + { + Some( + CC { + channel: pattern.channel, + cc: 7, + value: pattern.pressure, + } + .into(), + ) } } else { None @@ -455,24 +518,51 @@ impl Plugin for ArpegiatorPlugin { }; if let Some(raw_message) = raw_message { - self.device_out.update(MidiMessageWithDelta { delta_frames, data: raw_message }, - current_time_in_samples, None); + self.device_out.update( + MidiMessageWithDelta { + delta_frames, + data: raw_message, + }, + current_time_in_samples, + None, + ); } } PatternDeviceChange::RemovePattern { pattern, .. } => { - self.device_out.push_note_off(pattern.id, pattern.velocity_off, - delta_frames, current_time_in_samples); + self.device_out.push_note_off( + pattern.id, + pattern.velocity_off, + delta_frames, + current_time_in_samples, + ); } - PatternDeviceChange::ReplacePattern { old_pattern, new_pattern, .. } => { - self.device_out.push_note_off(old_pattern.id, old_pattern.velocity_off, - delta_frames, current_time_in_samples); - - let note = match self.notes_device_in.notes.values().sorted().nth(new_pattern.index as usize) { - None => { continue; } - Some(note) => note + PatternDeviceChange::ReplacePattern { + old_pattern, + new_pattern, + .. + } => { + self.device_out.push_note_off( + old_pattern.id, + old_pattern.velocity_off, + delta_frames, + current_time_in_samples, + ); + + let note = match self + .notes_device_in + .notes + .values() + .sorted() + .nth(new_pattern.index as usize) + { + None => { + continue; + } + Some(note) => note, }; - self.device_out.push_note_on(&new_pattern, note, current_time_in_samples); + self.device_out + .push_note_on(&new_pattern, note, current_time_in_samples); } PatternDeviceChange::CC { cc: _cc, time: _time } => { #[cfg(feature = "forward_pattern_cc")] { @@ -488,15 +578,17 @@ impl Plugin for ArpegiatorPlugin { } } } - }; + } #[cfg(not(feature = "midi_hack_transmission"))] if let Some(worker_channels) = self.worker_channels.as_ref() { self.device_out.flush_to(local_time, &worker_channels.command_sender) } - #[cfg(feature = "midi_hack_transmission")] { - self.send_buffer.send_events(take(&mut self.device_out.output_queue), &mut self._host); + #[cfg(feature = "midi_hack_transmission")] + { + self.send_buffer + .send_events(take(&mut self.device_out.output_queue), &mut self._host); } self.events.clear(); @@ -507,7 +599,8 @@ impl Plugin for ArpegiatorPlugin { fn process_events(&mut self, events: &api::Events) { for e in events.events() { if let Event::Midi(e) = e { - #[cfg(feature = "device_debug")] info!("Received {:2X?}", e.data); + #[cfg(feature = "device_debug")] + info!("Received {:2X?}", e.data); self.events.push(e); } } @@ -518,7 +611,7 @@ impl Plugin for ArpegiatorPlugin { } } -#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(not(feature = "midi_hack_transmission"))] impl Drop for ArpegiatorPlugin { fn drop(&mut self) { let event_id = Uuid::new_v4(); diff --git a/arpegiator/src/midi_messages/change.rs b/arpegiator/src/midi_messages/change.rs index 78aaf96..f8649de 100644 --- a/arpegiator/src/midi_messages/change.rs +++ b/arpegiator/src/midi_messages/change.rs @@ -1,82 +1,65 @@ -use core::cmp::{Ordering, PartialEq, PartialOrd}; -use core::option::Option; use crate::midi_messages::device::DeviceChange; use crate::midi_messages::pattern_device::PatternDeviceChange; use crate::midi_messages::timed_event::TimedEvent; +use core::cmp::{Ordering, PartialEq, PartialOrd}; +use core::option::Option; pub enum SourceChange { NoteChange(DeviceChange), - PatternChange(PatternDeviceChange) + PatternChange(PatternDeviceChange), } - impl PartialOrd for SourceChange { fn partial_cmp(&self, other: &Self) -> Option { // note come first, in order to start the pattern with the intended note match self { - SourceChange::PatternChange(pattern) => { - match other { - SourceChange::PatternChange(other_pattern) => { - pattern.partial_cmp(other_pattern) - } - SourceChange::NoteChange(_) => Option::Some(Ordering::Greater) - } - } - SourceChange::NoteChange(note) => { - match other { - SourceChange::PatternChange(_) => Option::Some(Ordering::Less), - SourceChange::NoteChange(other_note) => { - note.partial_cmp(other_note) - } - } - } + SourceChange::PatternChange(pattern) => match other { + SourceChange::PatternChange(other_pattern) => pattern.partial_cmp(other_pattern), + SourceChange::NoteChange(_) => Option::Some(Ordering::Greater), + }, + SourceChange::NoteChange(note) => match other { + SourceChange::PatternChange(_) => Option::Some(Ordering::Less), + SourceChange::NoteChange(other_note) => note.partial_cmp(other_note), + }, } } } - impl PartialEq for SourceChange { fn eq(&self, other: &Self) -> bool { match self { - SourceChange::PatternChange(pattern) => { - match other { - SourceChange::PatternChange(other_pattern) => pattern.eq(other_pattern), - SourceChange::NoteChange(_) => false - } - } - SourceChange::NoteChange(note) => { - match other { - SourceChange::PatternChange(_) => false, - SourceChange::NoteChange(other_note) => note.eq(other_note) - } - } + SourceChange::PatternChange(pattern) => match other { + SourceChange::PatternChange(other_pattern) => pattern.eq(other_pattern), + SourceChange::NoteChange(_) => false, + }, + SourceChange::NoteChange(note) => match other { + SourceChange::PatternChange(_) => false, + SourceChange::NoteChange(other_note) => note.eq(other_note), + }, } } } - impl Ord for SourceChange { fn cmp(&self, other: &Self) -> Ordering { self.partial_cmp(other).unwrap() } } -impl Eq for SourceChange { - -} +impl Eq for SourceChange {} impl TimedEvent for SourceChange { fn timestamp(&self) -> usize { match self { SourceChange::NoteChange(note) => note.timestamp(), - SourceChange::PatternChange(pattern) => pattern.timestamp() + SourceChange::PatternChange(pattern) => pattern.timestamp(), } } fn id(&self) -> usize { match self { SourceChange::NoteChange(note) => note.id(), - SourceChange::PatternChange(pattern) => pattern.id() + SourceChange::PatternChange(pattern) => pattern.id(), } } } diff --git a/arpegiator/src/midi_messages/device.rs b/arpegiator/src/midi_messages/device.rs index 19cfd49..ebe4db4 100644 --- a/arpegiator/src/midi_messages/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -7,11 +7,10 @@ use util::messages::CC; use util::midi_message_type::MidiMessageType; use util::midi_message_with_delta::MidiMessageWithDelta; -use crate::midi_messages::note::{NoteIndex, Note, CCIndex}; +use crate::midi_messages::note::{CCIndex, Note, NoteIndex}; use crate::midi_messages::timed_event::TimedEvent; use itertools::Itertools; - pub struct Device { pub _name: String, pub notes: HashMap, @@ -20,9 +19,8 @@ pub struct Device { pub note_index: usize, } - impl Device { - pub fn new (name: String) -> Self { + pub fn new(name: String) -> Self { Device { _name: name, notes: Default::default(), @@ -42,7 +40,6 @@ impl Device { } } - #[derive(Copy, Clone, Debug)] pub struct Channel { pub pressure: u8, @@ -55,20 +52,38 @@ pub enum Expression { Timbre, Pressure, PitchBend, - AfterTouch + AfterTouch, } pub enum DeviceChange { - AddNote { time: usize, note: Note }, - RemoveNote { time: usize, note: Note }, - NoteExpressionChange { time: usize, expression: Expression, note: Note }, + AddNote { + time: usize, + note: Note, + }, + RemoveNote { + time: usize, + note: Note, + }, + NoteExpressionChange { + time: usize, + expression: Expression, + note: Note, + }, // replacing happens when a note on is triggered for a note and channel that is already on - ReplaceNote { time: usize, old_note: Note, new_note: Note }, - CCChange { time: usize, cc: CC }, - Ignored { time: usize }, + ReplaceNote { + time: usize, + old_note: Note, + new_note: Note, + }, + CCChange { + time: usize, + cc: CC, + }, + Ignored { + time: usize, + }, } - impl TimedEvent for DeviceChange { fn timestamp(&self) -> usize { match self { @@ -77,7 +92,7 @@ impl TimedEvent for DeviceChange { DeviceChange::NoteExpressionChange { time, .. } => *time, DeviceChange::ReplaceNote { time, .. } => *time, DeviceChange::CCChange { time, .. } => *time, - DeviceChange::Ignored { time, .. } => *time + DeviceChange::Ignored { time, .. } => *time, } } @@ -90,7 +105,7 @@ impl TimedEvent for DeviceChange { DeviceChange::NoteExpressionChange { note, .. } => note.id, DeviceChange::ReplaceNote { new_note: note, .. } => note.id, DeviceChange::CCChange { .. } => 0, - DeviceChange::Ignored { .. } => 0 + DeviceChange::Ignored { .. } => 0, } } } @@ -112,12 +127,18 @@ impl PartialEq for DeviceChange { } } - impl Device { - pub fn update(&mut self, - midi_message: MidiMessageWithDelta, current_time: usize, id: Option) -> DeviceChange { - #[cfg(feature="device_debug")] - info!("[{}] Got event: {:?} {:?} {:02X?}", self._name, id, current_time, midi_message); + pub fn update( + &mut self, + midi_message: MidiMessageWithDelta, + current_time: usize, + id: Option, + ) -> DeviceChange { + #[cfg(feature = "device_debug")] + info!( + "[{}] Got event: {:?} {:?} {:02X?}", + self._name, id, current_time, midi_message + ); let time = current_time + midi_message.delta_frames as usize; @@ -129,9 +150,12 @@ impl Device { self.note_index += 1; note_id } - Some(id) => id + Some(id) => id, + }; + let index = NoteIndex { + channel: note.channel, + pitch: note.pitch, }; - let index = NoteIndex { channel: note.channel, pitch: note.pitch }; let new_note = Note { id: note_id, pressed_at: time, @@ -146,16 +170,19 @@ impl Device { }; match self.notes.insert(index, new_note) { - None => { - DeviceChange::AddNote { time, note: new_note } - } - Some(old_note) => { - DeviceChange::ReplaceNote { time, old_note, new_note } - } + None => DeviceChange::AddNote { time, note: new_note }, + Some(old_note) => DeviceChange::ReplaceNote { + time, + old_note, + new_note, + }, } } MidiMessageType::NoteOffMessage(note) => { - let index = NoteIndex { channel: note.channel, pitch: note.pitch }; + let index = NoteIndex { + channel: note.channel, + pitch: note.pitch, + }; match self.notes.remove(&index) { None => { @@ -171,7 +198,13 @@ impl Device { } } MidiMessageType::CCMessage(cc) => { - self.cc.insert(CCIndex { channel: cc.channel, index: cc.cc }, cc.value); + self.cc.insert( + CCIndex { + channel: cc.channel, + index: cc.cc, + }, + cc.value, + ); if cc.cc == TIMBRECC { self.channels[cc.channel as usize].timbre = cc.value; for (_, note) in self.notes.iter_mut() { @@ -204,7 +237,7 @@ impl Device { } } DeviceChange::Ignored { time } - }, + } MidiMessageType::AfterTouchMessage(message) => { // redundant with pressure, but that's the message that bitwig will properly handle for by-note // expressions @@ -221,7 +254,7 @@ impl Device { } } DeviceChange::Ignored { time } - }, + } MidiMessageType::PitchBendMessage(message) => { self.channels[message.channel as usize].pitchbend = message.millisemitones; for (_, note) in self.notes.iter_mut() { @@ -240,7 +273,6 @@ impl Device { } MidiMessageType::UnsupportedChannelMessage(_) => DeviceChange::Ignored { time }, MidiMessageType::Unsupported => DeviceChange::Ignored { time }, - } } } diff --git a/arpegiator/src/midi_messages/device_out.rs b/arpegiator/src/midi_messages/device_out.rs index 91ce26c..71acab8 100644 --- a/arpegiator/src/midi_messages/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -1,8 +1,8 @@ #[allow(unused_imports)] use { - log::{error, info}, async_channel::Sender, - std::mem::take + log::{error, info}, + std::mem::take, }; use util::messages::{NoteOff, PitchBend}; @@ -11,22 +11,21 @@ use util::raw_message::RawMessage; use crate::midi_messages::device::Device; use crate::midi_messages::expressive_note::ExpressiveNote; -use crate::midi_messages::pattern::Pattern; use crate::midi_messages::note::Note; -#[cfg(not(feature="midi_hack_transmission"))] use crate::workers::main_worker::WorkerCommand; - +use crate::midi_messages::pattern::Pattern; +#[cfg(not(feature = "midi_hack_transmission"))] +use crate::workers::main_worker::WorkerCommand; pub(crate) struct DeviceOut { device: Device, pub output_queue: Vec, } - impl DeviceOut { pub fn new(name: String) -> Self { Self { device: Device::new(name), - output_queue: vec![] + output_queue: vec![], } } @@ -34,43 +33,50 @@ impl DeviceOut { self.output_queue.push(midi_message); self.device.update(midi_message, current_time, id); if !self.device.notes.is_empty() { - #[cfg(feature="device_debug")] + #[cfg(feature = "device_debug")] info!("Device out state after update: {:2X?}", self.device.notes) } } - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] pub fn flush_to(&mut self, reception_time: u64, midi_output_sender: &Sender) { if self.output_queue.is_empty() { - return + return; } { - midi_output_sender.try_send( - WorkerCommand::SendToMidiOutput { - reception_time, messages: take(&mut self.output_queue) - }).unwrap_or_else( - |err| error!("Could not send to the controller worker {}", err) - ); + midi_output_sender + .try_send(WorkerCommand::SendToMidiOutput { + reception_time, + messages: take(&mut self.output_queue), + }) + .unwrap_or_else(|err| error!("Could not send to the controller worker {}", err)); } } pub fn update_pitch(&mut self, note_id: usize, target_pitch: u8, delta_frames: u16, current_time: usize) { match self.device.notes.values().find(|note| note.id == note_id) { None => { - info!("Cannot find note to pitchbend. Required note_id: {}. Current notes: {:02X?}", - note_id, self.device.notes.values() + info!( + "Cannot find note to pitchbend. Required note_id: {}. Current notes: {:02X?}", + note_id, + self.device.notes.values() ) } Some(note) => { let raw_message: RawMessage = PitchBend { channel: note.channel, - millisemitones: (target_pitch as i32 - note.pitch as i32) * 1000 - }.into(); - self.update(MidiMessageWithDelta { - delta_frames, - data: raw_message - }, current_time, None); + millisemitones: (target_pitch as i32 - note.pitch as i32) * 1000, + } + .into(); + self.update( + MidiMessageWithDelta { + delta_frames, + data: raw_message, + }, + current_time, + None, + ); } }; } @@ -78,29 +84,36 @@ impl DeviceOut { pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { let note = match self.device.notes.values().find(|note| note.id == note_id) { None => { - #[cfg(feature="device_debug")] + #[cfg(feature = "device_debug")] info!("Cannot find note to stop: {:02x?}", note_id); return; } - Some(note) => note + Some(note) => note, }; let raw_message: RawMessage = NoteOff { channel: note.channel, pitch: note.pitch, velocity: velocity_off, - }.into(); + } + .into(); - self.update(MidiMessageWithDelta { - delta_frames, - data: raw_message, - }, current_time, None); + self.update( + MidiMessageWithDelta { + delta_frames, + data: raw_message, + }, + current_time, + None, + ); } pub fn push_note_on(&mut self, pattern: &Pattern, note: &Note, current_time: usize) { let pitch = match pattern.transpose(note.pitch) { - None => { return; } - Some(pitch) => pitch + None => { + return; + } + Some(pitch) => pitch, }; for raw_message in (ExpressiveNote { @@ -110,7 +123,9 @@ impl DeviceOut { pressure: pattern.pressure, timbre: pattern.timbre, pitchbend: pattern.pitchbend, - }).into_rawmessages() { + }) + .into_rawmessages() + { self.update( MidiMessageWithDelta { delta_frames: (pattern.pressed_at - current_time) as u16, diff --git a/arpegiator/src/midi_messages/expressive_note.rs b/arpegiator/src/midi_messages/expressive_note.rs index 3926086..c9f6a32 100644 --- a/arpegiator/src/midi_messages/expressive_note.rs +++ b/arpegiator/src/midi_messages/expressive_note.rs @@ -1,16 +1,16 @@ #[allow(unused_imports)] use log::info; +use util::messages::{NoteOn, PitchBend, Timbre}; use util::raw_message::RawMessage; -use util::messages::{NoteOn, Timbre, PitchBend}; -#[cfg(feature= "pressure_as_channel_pressure")] +#[cfg(feature = "pressure_as_channel_pressure")] use util::messages::Pressure; -#[cfg(feature= "pressure_as_aftertouch")] +#[cfg(feature = "pressure_as_aftertouch")] use util::messages::AfterTouch; -#[cfg(feature= "pressure_as_cc7")] +#[cfg(feature = "pressure_as_cc7")] use util::messages::CC; pub struct ExpressiveNote { @@ -22,35 +22,37 @@ pub struct ExpressiveNote { pub pitchbend: i32, } - impl ExpressiveNote { - #[cfg(feature= "pressure_as_aftertouch")] + #[cfg(feature = "pressure_as_aftertouch")] #[inline] fn get_pressure_note(&self) -> RawMessage { AfterTouch { channel: self.channel, pitch: self.pitch, value: self.pressure, - }.into() + } + .into() } - #[cfg(feature= "pressure_as_cc7")] + #[cfg(feature = "pressure_as_cc7")] #[inline] fn get_pressure_note(&self) -> RawMessage { CC { channel: self.channel, value: self.pressure, - cc: 7 - }.into() + cc: 7, + } + .into() } - #[cfg(feature= "pressure_as_channel_pressure")] + #[cfg(feature = "pressure_as_channel_pressure")] #[inline] fn get_pressure_note(&self) -> RawMessage { Pressure { channel: self.channel, value: self.pressure, - }.into() + } + .into() } pub fn into_rawmessages(self) -> Vec { @@ -58,17 +60,20 @@ impl ExpressiveNote { PitchBend { channel: self.channel, millisemitones: self.pitchbend, - }.into(), + } + .into(), Timbre { channel: self.channel, value: self.timbre, - }.into(), + } + .into(), self.get_pressure_note(), NoteOn { channel: self.channel, pitch: self.pitch, velocity: self.velocity, // todo mixing between pattern and note - }.into(), + } + .into(), ] } } diff --git a/arpegiator/src/midi_messages/mod.rs b/arpegiator/src/midi_messages/mod.rs index 298a5aa..8aa1005 100644 --- a/arpegiator/src/midi_messages/mod.rs +++ b/arpegiator/src/midi_messages/mod.rs @@ -2,7 +2,7 @@ pub(crate) mod change; pub(crate) mod device; pub(crate) mod device_out; pub(crate) mod expressive_note; -pub(crate) mod pattern; pub(crate) mod note; +pub(crate) mod pattern; pub(crate) mod pattern_device; pub(crate) mod timed_event; diff --git a/arpegiator/src/midi_messages/note.rs b/arpegiator/src/midi_messages/note.rs index 5bc8047..cec455d 100644 --- a/arpegiator/src/midi_messages/note.rs +++ b/arpegiator/src/midi_messages/note.rs @@ -14,24 +14,21 @@ pub struct Note { pub pressure: u8, pub timbre: u8, - pub pitchbend: i32, // in millisemitones + pub pitchbend: i32, // in millisemitones } - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct NoteIndex { pub channel: u8, - pub pitch: u8 + pub pitch: u8, } - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct CCIndex { pub channel: u8, - pub index: u8 + pub index: u8, } - impl PartialOrd for Note { fn partial_cmp(&self, other: &Self) -> Option { let cmp = self.pitch.cmp(&other.pitch); @@ -45,7 +42,6 @@ impl PartialOrd for Note { } } - impl PartialOrd for NoteIndex { fn partial_cmp(&self, other: &Self) -> Option { let cmp = self.pitch.cmp(&other.pitch); @@ -58,7 +54,6 @@ impl PartialOrd for NoteIndex { } } - impl PartialEq for Note { fn eq(&self, other: &Self) -> bool { self.id == other.id diff --git a/arpegiator/src/midi_messages/pattern.rs b/arpegiator/src/midi_messages/pattern.rs index 2b87584..b37ab6c 100644 --- a/arpegiator/src/midi_messages/pattern.rs +++ b/arpegiator/src/midi_messages/pattern.rs @@ -1,7 +1,7 @@ use crate::midi_messages::note::Note; use crate::midi_messages::timed_event::TimedEvent; -pub const C3: u8 = 60 ; +pub const C3: u8 = 60; #[derive(Copy, Clone, Debug)] pub struct Pattern { @@ -19,7 +19,7 @@ pub struct Pattern { pub pressure: u8, pub timbre: u8, - pub pitchbend: i32, // in millisemitones + pub pitchbend: i32, // in millisemitones } impl Pattern { @@ -34,7 +34,6 @@ impl Pattern { } } - impl TimedEvent for Pattern { fn timestamp(&self) -> usize { if self.released_at > 0 { @@ -49,7 +48,6 @@ impl TimedEvent for Pattern { } } - impl From for Pattern { fn from(note: Note) -> Self { let index = note.pitch % 12; @@ -66,7 +64,7 @@ impl From for Pattern { pressed_at: note.pressed_at, released_at: 0, velocity: note.velocity, - pitchbend: 0 + pitchbend: 0, } } } diff --git a/arpegiator/src/midi_messages/pattern_device.rs b/arpegiator/src/midi_messages/pattern_device.rs index 1ee4033..78fc63d 100644 --- a/arpegiator/src/midi_messages/pattern_device.rs +++ b/arpegiator/src/midi_messages/pattern_device.rs @@ -2,32 +2,47 @@ use log::error; use core::iter::Filter; use std::cmp::Ordering; -use std::collections::HashMap; use std::collections::hash_map::Values; +use std::collections::HashMap; -use util::messages::CC; use crate::midi_messages::device::{DeviceChange, Expression}; use crate::midi_messages::pattern::Pattern; use crate::midi_messages::timed_event::TimedEvent; - - +use util::messages::CC; #[derive(Default)] pub struct PatternDevice { - patterns: HashMap + patterns: HashMap, } - pub enum PatternDeviceChange { - AddPattern { time: usize, pattern: Pattern }, - PatternExpressionChange { time: usize, expression: Expression, pattern: Pattern }, - RemovePattern { time: usize, pattern: Pattern }, - ReplacePattern { time: usize, old_pattern: Pattern, new_pattern: Pattern }, - CC { cc: CC, time: usize }, - None { time: usize }, + AddPattern { + time: usize, + pattern: Pattern, + }, + PatternExpressionChange { + time: usize, + expression: Expression, + pattern: Pattern, + }, + RemovePattern { + time: usize, + pattern: Pattern, + }, + ReplacePattern { + time: usize, + old_pattern: Pattern, + new_pattern: Pattern, + }, + CC { + cc: CC, + time: usize, + }, + None { + time: usize, + }, } - impl PartialOrd for PatternDeviceChange { fn partial_cmp(&self, other: &Self) -> Option { let timestamp_cmp = self.timestamp().cmp(&other.timestamp()); @@ -53,7 +68,7 @@ impl TimedEvent for PatternDeviceChange { PatternDeviceChange::RemovePattern { time, .. } => *time, PatternDeviceChange::ReplacePattern { time, .. } => *time, PatternDeviceChange::None { time, .. } => *time, - PatternDeviceChange::CC { time, .. } => *time + PatternDeviceChange::CC { time, .. } => *time, } } @@ -62,14 +77,15 @@ impl TimedEvent for PatternDeviceChange { PatternDeviceChange::AddPattern { pattern, .. } => pattern.id, PatternDeviceChange::PatternExpressionChange { pattern, .. } => pattern.id, PatternDeviceChange::RemovePattern { pattern, .. } => pattern.id, - PatternDeviceChange::ReplacePattern { new_pattern: pattern, .. } => pattern.id, + PatternDeviceChange::ReplacePattern { + new_pattern: pattern, .. + } => pattern.id, PatternDeviceChange::CC { .. } => 0, PatternDeviceChange::None { .. } => 0, } } } - impl PatternDevice { pub fn update(&mut self, change: DeviceChange) -> PatternDeviceChange { match change { @@ -78,52 +94,65 @@ impl PatternDevice { let pattern = self.patterns.insert(note.id, new_pattern); match pattern { - None => PatternDeviceChange::AddPattern { time, pattern: new_pattern }, - Some(old_pattern) => { - PatternDeviceChange::ReplacePattern { time, old_pattern, new_pattern } - } + None => PatternDeviceChange::AddPattern { + time, + pattern: new_pattern, + }, + Some(old_pattern) => PatternDeviceChange::ReplacePattern { + time, + old_pattern, + new_pattern, + }, } } - DeviceChange::RemoveNote { time, note } => { - match self.patterns.remove(¬e.id) { - None => PatternDeviceChange::None { time }, - Some(mut pattern) => { - pattern.velocity_off = note.velocity_off; - pattern.released_at = note.released_at; - PatternDeviceChange::RemovePattern { time, pattern } - } + DeviceChange::RemoveNote { time, note } => match self.patterns.remove(¬e.id) { + None => PatternDeviceChange::None { time }, + Some(mut pattern) => { + pattern.velocity_off = note.velocity_off; + pattern.released_at = note.released_at; + PatternDeviceChange::RemovePattern { time, pattern } } - } - DeviceChange::NoteExpressionChange { time, expression, note } => { - match self.patterns.get_mut(¬e.id) { - None => PatternDeviceChange::None { time }, - Some(pattern) => { - pattern.pressure = note.pressure; - pattern.timbre = note.timbre; - pattern.pitchbend = note.pitchbend; - PatternDeviceChange::PatternExpressionChange { time, expression, pattern: *pattern } + }, + DeviceChange::NoteExpressionChange { time, expression, note } => match self.patterns.get_mut(¬e.id) { + None => PatternDeviceChange::None { time }, + Some(pattern) => { + pattern.pressure = note.pressure; + pattern.timbre = note.timbre; + pattern.pitchbend = note.pitchbend; + PatternDeviceChange::PatternExpressionChange { + time, + expression, + pattern: *pattern, } } - } - DeviceChange::ReplaceNote { time, old_note, new_note } => { + }, + DeviceChange::ReplaceNote { + time, + old_note, + new_note, + } => { let new_pattern = Pattern::from(new_note); match self.patterns.insert(new_pattern.id, new_pattern) { None => { error!("Expected to replace a pattern matching, found nothing {:?}", old_note); PatternDeviceChange::None { time } } - Some(old_pattern) => { - PatternDeviceChange::ReplacePattern { time, old_pattern, new_pattern } - } + Some(old_pattern) => PatternDeviceChange::ReplacePattern { + time, + old_pattern, + new_pattern, + }, } } DeviceChange::CCChange { time, cc } => PatternDeviceChange::CC { cc, time }, - DeviceChange::Ignored { time } => PatternDeviceChange::None { time } + DeviceChange::Ignored { time } => PatternDeviceChange::None { time }, } } pub fn at(&self, index: u8) -> Filter, PatternIteratorClosure> { - self.patterns.values().filter(Box::new(move |pattern| pattern.index == index)) + self.patterns + .values() + .filter(Box::new(move |pattern| pattern.index == index)) } } diff --git a/arpegiator/src/midi_messages/timed_event.rs b/arpegiator/src/midi_messages/timed_event.rs index 5d1fab9..1b5d922 100644 --- a/arpegiator/src/midi_messages/timed_event.rs +++ b/arpegiator/src/midi_messages/timed_event.rs @@ -1,4 +1,4 @@ pub trait TimedEvent { - fn timestamp(&self) -> usize ; - fn id(&self) -> usize ; + fn timestamp(&self) -> usize; + fn id(&self) -> usize; } diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index d7a84e2..cf6a41e 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -1,27 +1,27 @@ #[allow(unused_imports)] use { + async_channel::Sender, log::{error, info}, - util::parameter_value_conversion::{f32_to_byte, f32_to_bool}, std::sync::Mutex, - async_channel::Sender + util::parameter_value_conversion::{f32_to_bool, f32_to_byte}, }; use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; -use util::parameters::ParameterConversion; -#[cfg(not(feature="midi_hack_transmission"))] use crate::workers::main_worker::WorkerCommand; +#[cfg(not(feature = "midi_hack_transmission"))] +use crate::workers::main_worker::WorkerCommand; use util::duration_display; +use util::parameters::ParameterConversion; use util::system::Uuid; - -#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(not(feature = "midi_hack_transmission"))] pub const PARAMETER_COUNT: usize = 4; -#[cfg(feature="midi_hack_transmission")] +#[cfg(feature = "midi_hack_transmission")] pub const PARAMETER_COUNT: usize = 3; -#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(not(feature = "midi_hack_transmission"))] const BASE_PORT: u16 = 6000; // 1 = Immediate - TODO : must be default @@ -48,41 +48,49 @@ think about those cases: // ideally while a note eposes its pitch and a pitchbend value, a method should directly tell the pitch in // millisemitones relative to 0 ( C-2 ) pub(crate) enum PitchBendValues { - Off, // no pitchbend, means same pitch until pattern ends + Off, // no pitchbend, means same pitch until pattern ends DurationToReachTarget(f32), - Immediate + Immediate, } pub(crate) struct ArpegiatorParameters { pub transfer: ParameterTransfer, - #[cfg(not(feature="midi_hack_transmission"))] pub worker_commands: Mutex>>, + #[cfg(not(feature = "midi_hack_transmission"))] + pub worker_commands: Mutex>>, } impl ArpegiatorParameters { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] pub fn get_port(&self) -> u16 { BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 } - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] pub fn update_port(&self, event_id: Uuid) { let port = self.get_byte_parameter(Parameter::PortIndex) as u16 + BASE_PORT; info!("Applying parameter change: port={}", port); - if let Err(error) = self.worker_commands.lock().unwrap().as_ref().unwrap().try_send( - WorkerCommand::SetPort(port, event_id) - ) { - info!("[{}] main worker is shutdown - ignoring port change ({})", event_id, error); + if let Err(error) = self + .worker_commands + .lock() + .unwrap() + .as_ref() + .unwrap() + .try_send(WorkerCommand::SetPort(port, event_id)) + { + info!( + "[{}] main worker is shutdown - ignoring port change ({})", + event_id, error + ); } } } - #[repr(i32)] pub enum Parameter { - HoldNotes = 0, // a started pattern will find a note to play, even if no note is playing for that index - PatternLegato, // pattern is not restarted if start/end match, and note is thus held + HoldNotes = 0, // a started pattern will find a note to play, even if no note is playing for that index + PatternLegato, // pattern is not restarted if start/end match, and note is thus held Pitchbend, - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] PortIndex, } @@ -92,14 +100,13 @@ impl From for Parameter { 0 => Parameter::HoldNotes, 1 => Parameter::PatternLegato, 2 => Parameter::Pitchbend, - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] 3 => Parameter::PortIndex, _ => panic!("no such parameter {}", i), } } } - impl Into for Parameter { fn into(self) -> i32 { self as i32 @@ -116,12 +123,11 @@ impl ParameterConversion for ArpegiatorParameters { } } - impl ArpegiatorParameters { pub fn new() -> Self { let parameters = ArpegiatorParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] worker_commands: Mutex::new(None), }; parameters.set_parameter(Parameter::PatternLegato.into(), 1.); @@ -140,39 +146,34 @@ impl ArpegiatorParameters { } } - impl PluginParameters for ArpegiatorParameters { fn get_parameter_text(&self, index: i32) -> String { let parameter = index.into(); match parameter { - #[cfg(not(feature="midi_hack_transmission"))] - Parameter::PortIndex => { - self.get_port().to_string() - } - Parameter::HoldNotes | Parameter::PatternLegato => { - match self.get_bool_parameter(parameter) { - true => "On", - false => "Off" - }.to_string() - } - Parameter::Pitchbend => { - match self.get_pitchbend() { - PitchBendValues::Off => "Off".into(), - PitchBendValues::DurationToReachTarget(value) => duration_display(value), - PitchBendValues::Immediate => "Immediate".into() - } + #[cfg(not(feature = "midi_hack_transmission"))] + Parameter::PortIndex => self.get_port().to_string(), + Parameter::HoldNotes | Parameter::PatternLegato => match self.get_bool_parameter(parameter) { + true => "On", + false => "Off", } + .to_string(), + Parameter::Pitchbend => match self.get_pitchbend() { + PitchBendValues::Off => "Off".into(), + PitchBendValues::DurationToReachTarget(value) => duration_display(value), + PitchBendValues::Immediate => "Immediate".into(), + }, } } fn get_parameter_name(&self, index: i32) -> String { match index.into() { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] Parameter::PortIndex => "Port", Parameter::HoldNotes => "Hold notes", Parameter::PatternLegato => "Pattern Legato", - Parameter::Pitchbend => "Use pitchbend" - }.to_string() + Parameter::Pitchbend => "Use pitchbend", + } + .to_string() } fn get_parameter(&self, index: i32) -> f32 { @@ -182,7 +183,7 @@ impl PluginParameters for ArpegiatorParameters { fn set_parameter(&self, index: i32, value: f32) { let parameter = index.into(); match parameter { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] Parameter::PortIndex => { let new_value = f32_to_byte(value); let old_value = self.get_byte_parameter(Parameter::PortIndex); @@ -200,7 +201,7 @@ impl PluginParameters for ArpegiatorParameters { self.transfer.set_parameter(index as usize, value); } } - Parameter::Pitchbend => self.transfer.set_parameter(index as usize, value) + Parameter::Pitchbend => self.transfer.set_parameter(index as usize, value), } } @@ -216,7 +217,7 @@ impl PluginParameters for ArpegiatorParameters { let event_id = Uuid::new_v4(); info!("[{}] Load present data", event_id); self.deserialize_state(data); - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] { self.update_port(event_id) } @@ -226,7 +227,7 @@ impl PluginParameters for ArpegiatorParameters { let event_id = Uuid::new_v4(); info!("[{}] Load bank data", event_id); self.deserialize_state(data); - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] { self.update_port(event_id) } diff --git a/arpegiator/src/system.rs b/arpegiator/src/system.rs index a510229..c098d11 100644 --- a/arpegiator/src/system.rs +++ b/arpegiator/src/system.rs @@ -1,8 +1,10 @@ #[cfg(target_os = "macos")] #[inline] pub fn second_to_mach_timebase() -> f64 { - let mut timebase_info = mach::mach_time::mach_timebase_info { numer: 0, denom: 0 } ; - unsafe { mach::mach_time::mach_timebase_info(&mut timebase_info) ;} + let mut timebase_info = mach::mach_time::mach_timebase_info { numer: 0, denom: 0 }; + unsafe { + mach::mach_time::mach_timebase_info(&mut timebase_info); + } 10e9 * timebase_info.denom as f64 / timebase_info.numer as f64 } diff --git a/arpegiator/src/workers/ipc_worker.rs b/arpegiator/src/workers/ipc_worker.rs index cda8f39..3605254 100644 --- a/arpegiator/src/workers/ipc_worker.rs +++ b/arpegiator/src/workers/ipc_worker.rs @@ -4,16 +4,15 @@ use log::{error, info}; use async_channel::Sender; use async_std::net::UdpSocket; use async_std::task; -use ipc_channel::ipc::{IpcSender, IpcReceiver, IpcReceiverSet, IpcSelectionResult}; +use ipc_channel::ipc::{IpcReceiver, IpcReceiverSet, IpcSelectionResult, IpcSender}; -use util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}; +use util::ipc_payload::{BootstrapPayload, IPCCommand, PatternPayload}; use crate::workers::main_worker::WorkerCommand; -use std::{thread, error}; use std::mem::take; +use std::{error, thread}; use util::system::Uuid; - pub(crate) enum IPCWorkerCommand { SocketReceive(IpcReceiver, Uuid), Stop(Sender<()>, Uuid), @@ -28,7 +27,6 @@ fn bootstrap_ipc(ipc_server_name: String) -> Result, Box Ok(ipc_receiver) } - async fn udp_receive_worker(socket: UdpSocket, sender: Sender) { let mut buf = vec![0u8; 1024]; let mut exit_event_id = Uuid::default(); @@ -39,97 +37,117 @@ async fn udp_receive_worker(socket: UdpSocket, sender: Sender) info!("[{}] Received {} bytes", exit_event_id, len); let ipc_worker_command = match bincode::deserialize::(&buf[..len]) { - Ok(ipc_server_name) => { - match bootstrap_ipc(ipc_server_name) { - Ok(ipc_receiver) => { - info!("[{}] Received IPC Receiver via UDP", exit_event_id); - IPCWorkerCommand::SocketReceive(ipc_receiver, exit_event_id) - } - Err(err) => { - info!("[{}] failed to get an IPC Receiver via UDP: {}", exit_event_id, err); - continue; - } + Ok(ipc_server_name) => match bootstrap_ipc(ipc_server_name) { + Ok(ipc_receiver) => { + info!("[{}] Received IPC Receiver via UDP", exit_event_id); + IPCWorkerCommand::SocketReceive(ipc_receiver, exit_event_id) } - } + Err(err) => { + info!("[{}] failed to get an IPC Receiver via UDP: {}", exit_event_id, err); + continue; + } + }, Err(err) => { - error!("[{}] Ignoring invalid UDP payload ({}) - expected a name string", exit_event_id, err); + error!( + "[{}] Ignoring invalid UDP payload ({}) - expected a name string", + exit_event_id, err + ); continue; } }; if let Err(err) = sender.send(ipc_worker_command).await { - error!("[{}] UDP Worker: error while trying to send worker command {}, quitting UDP Worker", - exit_event_id, err); + error!( + "[{}] UDP Worker: error while trying to send worker command {}, quitting UDP Worker", + exit_event_id, err + ); break; } - }; + } #[cfg(feature = "worker_debug")] info!("[{}] Leaving udp receiver worker", exit_event_id) } - -fn spawn_ipc_receiver_thread(ipc_receiver: IpcReceiver, - ipc_worker_sender: Sender) -> Result, Box> { +fn spawn_ipc_receiver_thread( + ipc_receiver: IpcReceiver, + ipc_worker_sender: Sender, +) -> Result, Box> { let mut set = IpcReceiverSet::new()?; set.add(ipc_receiver)?; let (sender, receiver) = ipc_channel::ipc::channel::()?; set.add(receiver)?; - thread::spawn(|| task::block_on(async move { - let mut exit_event_id = Uuid::default(); - - #[cfg(feature = "worker_debug")] info!("started ipc worker"); - 'mainloop: while let Ok(results) = set.select() { - for result in results { - let opaque_message = match result { - IpcSelectionResult::MessageReceived(_, opaque_message) => opaque_message, - IpcSelectionResult::ChannelClosed(_) => { - exit_event_id = Uuid::new_v4(); - error!("[{}] ipc channel closed", exit_event_id); - break 'mainloop; - } - }; - match opaque_message.to::().unwrap() { - IPCCommand::PatternPayload(payload) => { - let event_id = Uuid::new_v4(); - if let Err(err) = ipc_worker_sender.send(IPCWorkerCommand::PayloadReceived(payload, event_id)).await { - exit_event_id = event_id; - error!("[{}] could not send payload to ipc worker {}", exit_event_id, err); + thread::spawn(|| { + task::block_on(async move { + let mut exit_event_id = Uuid::default(); + + #[cfg(feature = "worker_debug")] + info!("started ipc worker"); + 'mainloop: while let Ok(results) = set.select() { + for result in results { + let opaque_message = match result { + IpcSelectionResult::MessageReceived(_, opaque_message) => opaque_message, + IpcSelectionResult::ChannelClosed(_) => { + exit_event_id = Uuid::new_v4(); + error!("[{}] ipc channel closed", exit_event_id); break 'mainloop; } - } - IPCCommand::Ping(sender) => { - info!("Received ping from peer"); - match sender.send(()) { - Ok(_) => { info!("pong sent") } - Err(err) => { info!("error while sending pong: {}", err) } + }; + match opaque_message.to::().unwrap() { + IPCCommand::PatternPayload(payload) => { + let event_id = Uuid::new_v4(); + if let Err(err) = ipc_worker_sender + .send(IPCWorkerCommand::PayloadReceived(payload, event_id)) + .await + { + exit_event_id = event_id; + error!("[{}] could not send payload to ipc worker {}", exit_event_id, err); + break 'mainloop; + } + } + IPCCommand::Ping(sender) => { + info!("Received ping from peer"); + match sender.send(()) { + Ok(_) => { + info!("pong sent") + } + Err(err) => { + info!("error while sending pong: {}", err) + } + } + } + IPCCommand::Stop(ack_channel_sender, event_id) => { + exit_event_id = event_id; + #[cfg(feature = "worker_debug")] + info!("[{}] Stopping IPC worker thread", exit_event_id); + ack_channel_sender.send(()).unwrap_or_else(|err| { + error!("[{}] Could not signal ipc thread stop {}", err, exit_event_id); + }); + break 'mainloop; } - } - IPCCommand::Stop(ack_channel_sender, event_id) => { - exit_event_id = event_id; - #[cfg(feature = "worker_debug")] info!("[{}] Stopping IPC worker thread", exit_event_id); - ack_channel_sender.send(()).unwrap_or_else(|err| { - error!("[{}] Could not signal ipc thread stop {}", err, exit_event_id); - }); - break 'mainloop; } } } - }; - ipc_worker_sender.try_send(IPCWorkerCommand::IPCDisconnect(exit_event_id)).unwrap_or_else(|err| { - error!("[{}] Error {} while signaling IPC receiver quitting", exit_event_id, err); - }); - })); + ipc_worker_sender + .try_send(IPCWorkerCommand::IPCDisconnect(exit_event_id)) + .unwrap_or_else(|err| { + error!( + "[{}] Error {} while signaling IPC receiver quitting", + exit_event_id, err + ); + }); + }) + }); Ok(sender) } - -pub(crate) fn spawn_ipc_worker(port: u16, - pattern_sender: Sender, - worker_command_sender: Sender, +pub(crate) fn spawn_ipc_worker( + port: u16, + pattern_sender: Sender, + worker_command_sender: Sender, ) -> Result, Box> { let (ipc_worker_sender, ipc_worker_receiver) = async_channel::unbounded::(); @@ -137,95 +155,119 @@ pub(crate) fn spawn_ipc_worker(port: u16, let socket = task::block_on(UdpSocket::bind(format!("127.0.0.1:{}", port)))?; - thread::spawn(move || task::block_on(async move { - let mut ipc_receiver_sender: Option> = None; - let udp_worker_handle = task::spawn( - udp_receive_worker(socket, ipc_worker_sender.clone()) - ); - - let mut exit_event_id : Uuid = Uuid::default(); - - while let Ok(command) = ipc_worker_receiver.recv().await { - match command { - IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket, event_id) => { - if ipc_receiver_sender.is_some() { - if let Err(err) = close_ipc_receiver_thread( - take(&mut ipc_receiver_sender).unwrap(), event_id) { - error!("[{}] Error while shutting down ipc receiver worker {}", event_id, err) + thread::spawn(move || { + task::block_on(async move { + let mut ipc_receiver_sender: Option> = None; + let udp_worker_handle = task::spawn(udp_receive_worker(socket, ipc_worker_sender.clone())); + + let mut exit_event_id: Uuid = Uuid::default(); + + while let Ok(command) = ipc_worker_receiver.recv().await { + match command { + IPCWorkerCommand::SocketReceive(ipc_receiver_from_socket, event_id) => { + if ipc_receiver_sender.is_some() { + if let Err(err) = + close_ipc_receiver_thread(take(&mut ipc_receiver_sender).unwrap(), event_id) + { + error!("[{}] Error while shutting down ipc receiver worker {}", event_id, err) + } } - } - let ipc_worker_sender = ipc_worker_sender.clone(); + let ipc_worker_sender = ipc_worker_sender.clone(); - if let Ok(sender) = spawn_ipc_receiver_thread(ipc_receiver_from_socket, ipc_worker_sender) { - ipc_receiver_sender = Some(sender); - }; - } - IPCWorkerCommand::Stop(sender, event_id) => { - exit_event_id = event_id; - match sender.send(()).await { - Ok(_) => { info!("[{}] Quitting socket port {}", exit_event_id, port); } - Err(err) => { info!("[{}] Error while quitting socket port {}: {}", exit_event_id, port, err); } + if let Ok(sender) = spawn_ipc_receiver_thread(ipc_receiver_from_socket, ipc_worker_sender) { + ipc_receiver_sender = Some(sender); + }; } - break; - } - IPCWorkerCommand::IPCDisconnect(event_id) => { - ipc_receiver_sender = None; - error!("[{}] IPC Receiver disconnected", event_id) - } - IPCWorkerCommand::PayloadReceived(payload, event_id) => { - let _payload_time = payload.time; - if let Err(err) = pattern_sender.send(payload).await { + IPCWorkerCommand::Stop(sender, event_id) => { exit_event_id = event_id; - error!("[{}] IPC worker: notes sender channel error, quitting ({})", exit_event_id, err); + match sender.send(()).await { + Ok(_) => { + info!("[{}] Quitting socket port {}", exit_event_id, port); + } + Err(err) => { + info!("[{}] Error while quitting socket port {}: {}", exit_event_id, port, err); + } + } break; } + IPCWorkerCommand::IPCDisconnect(event_id) => { + ipc_receiver_sender = None; + error!("[{}] IPC Receiver disconnected", event_id) + } + IPCWorkerCommand::PayloadReceived(payload, event_id) => { + let _payload_time = payload.time; + if let Err(err) = pattern_sender.send(payload).await { + exit_event_id = event_id; + error!( + "[{}] IPC worker: notes sender channel error, quitting ({})", + exit_event_id, err + ); + break; + } - #[cfg(feature = "device_debug")] - { - let local_time = unsafe { mach::mach_time::mach_absolute_time() }; - let diff_nanoseconds = local_time - _payload_time; - info!("Received time: {:?} current time: {:?} = {} nanoseconds", - payload_time, - local_time, - diff_nanoseconds); + #[cfg(feature = "device_debug")] + { + let local_time = unsafe { mach::mach_time::mach_absolute_time() }; + let diff_nanoseconds = local_time - _payload_time; + info!( + "Received time: {:?} current time: {:?} = {} nanoseconds", + payload_time, local_time, diff_nanoseconds + ); + } } } } - } - #[cfg(feature = "worker_debug")] info!("[{}] stopping udp worker on port {}", exit_event_id, port); - udp_worker_handle.cancel().await; - #[cfg(feature = "worker_debug")] info!("[{}] udp worker on port {} stopped", exit_event_id, port); - - if let Some(sender) = take(&mut ipc_receiver_sender) { - close_ipc_receiver_thread(sender, exit_event_id).unwrap_or_else(|err| { - info!("[{}] ipc receiver thread did not quit gracefully: {}", exit_event_id, err); - }) - } + #[cfg(feature = "worker_debug")] + info!("[{}] stopping udp worker on port {}", exit_event_id, port); + udp_worker_handle.cancel().await; + #[cfg(feature = "worker_debug")] + info!("[{}] udp worker on port {} stopped", exit_event_id, port); + + if let Some(sender) = take(&mut ipc_receiver_sender) { + close_ipc_receiver_thread(sender, exit_event_id).unwrap_or_else(|err| { + info!( + "[{}] ipc receiver thread did not quit gracefully: {}", + exit_event_id, err + ); + }) + } - if let Err(err) = worker_command_sender.send(WorkerCommand::IPCWorkerStopped(exit_event_id, port)).await { - info!("[{}] Could not signal main worker {}", err, exit_event_id); - } + if let Err(err) = worker_command_sender + .send(WorkerCommand::IPCWorkerStopped(exit_event_id, port)) + .await + { + info!("[{}] Could not signal main worker {}", err, exit_event_id); + } - #[cfg(feature = "worker_debug")] info!("[{}] exiting ipc worker ( port {} )", exit_event_id, port); - })); + #[cfg(feature = "worker_debug")] + info!("[{}] exiting ipc worker ( port {} )", exit_event_id, port); + }) + }); Ok(returned_ipc_worker_sender) } - -fn close_ipc_receiver_thread(ipc_receiver_sender: IpcSender, event_id: Uuid) -> Result<(), Box> { - #[cfg(feature = "worker_debug")] info!("[{}] close_ipc_receiver_thread: enter", event_id); +fn close_ipc_receiver_thread( + ipc_receiver_sender: IpcSender, + event_id: Uuid, +) -> Result<(), Box> { + #[cfg(feature = "worker_debug")] + info!("[{}] close_ipc_receiver_thread: enter", event_id); let (ack_sender, ack_receiver) = ipc_channel::ipc::channel::<()>()?; - #[cfg(feature = "worker_debug")] info!("[{}] stopping ipc receiver", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] stopping ipc receiver", event_id); ipc_receiver_sender.send(IPCCommand::Stop(ack_sender, event_id))?; - #[cfg(feature = "worker_debug")] info!("[{}] waiting for ack from ipc receiver", event_id); - ack_receiver.try_recv().map_err(|x| format!("Error while receiving ack from ipc receiver {:?}", x))?; + #[cfg(feature = "worker_debug")] + info!("[{}] waiting for ack from ipc receiver", event_id); + ack_receiver + .try_recv() + .map_err(|x| format!("Error while receiving ack from ipc receiver {:?}", x))?; - #[cfg(feature = "worker_debug")] info!("[{}] stopped ipc receiver", event_id); + #[cfg(feature = "worker_debug")] + info!("[{}] stopped ipc receiver", event_id); Ok(()) } diff --git a/arpegiator/src/workers/main_worker.rs b/arpegiator/src/workers/main_worker.rs index b3482df..fc74eb5 100644 --- a/arpegiator/src/workers/main_worker.rs +++ b/arpegiator/src/workers/main_worker.rs @@ -1,26 +1,28 @@ use std::thread; use std::thread::JoinHandle; -use async_channel::{Receiver, Sender, unbounded}; +use async_channel::{unbounded, Receiver, Sender}; use async_std::task; use log::{error, info}; -use util::midi_message_with_delta::MidiMessageWithDelta; use util::ipc_payload::PatternPayload; +use util::midi_message_with_delta::MidiMessageWithDelta; use crate::workers::ipc_worker::{spawn_ipc_worker, IPCWorkerCommand}; -use crate::workers::midi_output_worker::{MidiOutputWorkerCommand, spawn_midi_output_worker}; +use crate::workers::midi_output_worker::{spawn_midi_output_worker, MidiOutputWorkerCommand}; use std::mem::take; use util::system::Uuid; - #[derive(Debug)] pub(crate) enum WorkerCommand { Stop(Uuid), SetPort(u16, Uuid), SetSampleRate(f32), SetBlockSize(i64), - SendToMidiOutput { reception_time: u64, messages: Vec }, + SendToMidiOutput { + reception_time: u64, + messages: Vec, + }, IPCWorkerStopped(Uuid, u16), } @@ -30,7 +32,6 @@ pub(crate) struct WorkerChannels { pub worker: JoinHandle<()>, } - pub(crate) fn create_worker_thread() -> WorkerChannels { let (command_sender, command_receiver) = unbounded::(); let (pattern_sender, pattern_receiver) = unbounded::(); @@ -38,12 +39,13 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { let returned_command_sender = command_sender.clone(); let main = { - #[cfg(feature = "worker_debug")] info!("starting workers"); + #[cfg(feature = "worker_debug")] + info!("starting workers"); async move { - let mut current_port : Option = None; - let mut midi_out_worker_sender : Option> = None; - let mut ipc_worker_sender : Option> = None; + let mut current_port: Option = None; + let mut midi_out_worker_sender: Option> = None; + let mut ipc_worker_sender: Option> = None; let mut exit_event_id = Uuid::default(); let mut exit_reason = ""; @@ -93,21 +95,34 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { } } - WorkerCommand::SendToMidiOutput { reception_time, messages } => { + WorkerCommand::SendToMidiOutput { + reception_time, + messages, + } => { if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { - midi_out_worker_sender.send( - MidiOutputWorkerCommand::SendToController { reception_time, messages } - ).await.unwrap(); + midi_out_worker_sender + .send(MidiOutputWorkerCommand::SendToController { + reception_time, + messages, + }) + .await + .unwrap(); } } WorkerCommand::SetSampleRate(rate) => { if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { - midi_out_worker_sender.send(MidiOutputWorkerCommand::SetSampleRate(rate)).await.unwrap(); + midi_out_worker_sender + .send(MidiOutputWorkerCommand::SetSampleRate(rate)) + .await + .unwrap(); } } WorkerCommand::SetBlockSize(size) => { if let Some(midi_out_worker_sender) = midi_out_worker_sender.as_ref() { - midi_out_worker_sender.send(MidiOutputWorkerCommand::SetBlockSize(size)).await.unwrap(); + midi_out_worker_sender + .send(MidiOutputWorkerCommand::SetBlockSize(size)) + .await + .unwrap(); } } WorkerCommand::IPCWorkerStopped(event_id, ipc_worker_port) => { @@ -138,34 +153,51 @@ pub(crate) fn create_worker_thread() -> WorkerChannels { WorkerChannels { command_sender: returned_command_sender, pattern_receiver, - worker + worker, } } async fn close_workers( midi_out_worker_sender: &mut Option>, ipc_worker_sender: &mut Option>, - event_id: Uuid + event_id: Uuid, ) { if let Some(ipc_worker_sender) = take(ipc_worker_sender) { let (ack_sender, ack_receiver) = async_channel::bounded(1); - ipc_worker_sender.send(IPCWorkerCommand::Stop(ack_sender, event_id)).await.unwrap_or_else(|err| { - error!("[{}] Could not contact worker sender for shutdown : {}", event_id, err); - }); + ipc_worker_sender + .send(IPCWorkerCommand::Stop(ack_sender, event_id)) + .await + .unwrap_or_else(|err| { + error!("[{}] Could not contact worker sender for shutdown : {}", event_id, err); + }); match ack_receiver.recv().await { - Ok(_) => { info!("[{}] ipc worker exit ack", event_id) } - Err(err) => { error!("[{}] ipc worker did not ack exit: {}", event_id, err) } + Ok(_) => { + info!("[{}] ipc worker exit ack", event_id) + } + Err(err) => { + error!("[{}] ipc worker did not ack exit: {}", event_id, err) + } } }; if let Some(midi_out_worker_sender) = take(midi_out_worker_sender) { let (ack_sender, ack_receiver) = async_channel::bounded(1); - midi_out_worker_sender.send(MidiOutputWorkerCommand::Stop(ack_sender, event_id)).await.unwrap_or_else(|err| { - error!("[{}] Could not contact midi output worker for shutdown : {}", event_id, err); - }); + midi_out_worker_sender + .send(MidiOutputWorkerCommand::Stop(ack_sender, event_id)) + .await + .unwrap_or_else(|err| { + error!( + "[{}] Could not contact midi output worker for shutdown : {}", + event_id, err + ); + }); match ack_receiver.recv().await { - Ok(_) => { info!("[{}] midi out worker exit ack", event_id) } - Err(err) => { error!("[{}] midi out did not ack exit: {}", event_id, err) } + Ok(_) => { + info!("[{}] midi out worker exit ack", event_id) + } + Err(err) => { + error!("[{}] midi out did not ack exit: {}", event_id, err) + } } }; } diff --git a/arpegiator/src/workers/midi_output_worker.rs b/arpegiator/src/workers/midi_output_worker.rs index acca3c9..d2a280a 100644 --- a/arpegiator/src/workers/midi_output_worker.rs +++ b/arpegiator/src/workers/midi_output_worker.rs @@ -1,65 +1,57 @@ #[allow(unused_imports)] use log::{error, info}; -use async_channel::{Sender, unbounded}; +use async_channel::{unbounded, Sender}; use async_std::task; #[cfg(target_os = "macos")] -use { - coremidi::PacketBuffer -}; +use coremidi::PacketBuffer; - -#[cfg(target_os = "linux")] -use { - midir::MidiOutput, - midir::os::unix::VirtualOutput -}; -use midir::MidiInput; use midir::os::unix::VirtualInput; +use midir::MidiInput; +#[cfg(target_os = "linux")] +use {midir::os::unix::VirtualOutput, midir::MidiOutput}; use util::midi_message_with_delta::MidiMessageWithDelta; - #[cfg(target_os = "macos")] use crate::system::second_to_mach_timebase; use std::error; use util::system::Uuid; - #[derive(Debug)] pub(crate) enum MidiOutputWorkerCommand { - SendToController { reception_time: u64, messages: Vec }, + SendToController { + reception_time: u64, + messages: Vec, + }, Stop(Sender<()>, Uuid), SetSampleRate(f32), - SetBlockSize(i64) + SetBlockSize(i64), } - -pub(crate) fn spawn_midi_output_worker(name: String) -> - Result, Box> { - +pub(crate) fn spawn_midi_output_worker( + name: String, +) -> Result, Box> { #[cfg(target_os = "macos")] - let (second_to_mach, mut sample_to_mach, mut _block_size, mut block_duration) : (f64, u64, u64, u64) = { - (second_to_mach_timebase(), 0, 0, 0) - }; + let (second_to_mach, mut sample_to_mach, mut _block_size, mut block_duration): (f64, u64, u64, u64) = + { (second_to_mach_timebase(), 0, 0, 0) }; #[cfg(target_os = "linux")] let mut midi_out_connection = { info!("Creating midi device {}", name); let midi_out = MidiOutput::new(&*name)?; - midi_out.create_virtual(&*name).map_err(|e| format!("Cannot create midi out: {}", e))? + midi_out + .create_virtual(&*name) + .map_err(|e| format!("Cannot create midi out: {}", e))? }; #[cfg(target_os = "macos")] let (client, source) = { - let client = coremidi::Client::new(&*name) - .map_err( - |x| format!("os error: {:?}", x) - )?; - let source = client.virtual_source(&*name).map_err( - |x| format!("os error: {:?}", x) - )?; + let client = coremidi::Client::new(&*name).map_err(|x| format!("os error: {:?}", x))?; + let source = client + .virtual_source(&*name) + .map_err(|x| format!("os error: {:?}", x))?; info!("Created virtual port {}", name); (client, source) }; @@ -67,17 +59,22 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> let midi_in = MidiInput::new(&*name)?; // create input device just to ease setup. returned connection must not be dropped in order to keep the device alive - let midi_in_connection = midi_in.create_virtual(&*name, |_time, _data, _| { - // noop for now - }, ()).map_err( - |x| format!("os error: {:?}", x) - )?; + let midi_in_connection = midi_in + .create_virtual( + &*name, + |_time, _data, _| { + // noop for now + }, + (), + ) + .map_err(|x| format!("os error: {:?}", x))?; let (sender, receiver) = unbounded::(); task::spawn(async move { // move the client inside the task, otherwise it will be dropped and the device won't appear - #[cfg(target_os = "macos")] let _client = client ; + #[cfg(target_os = "macos")] + let _client = client; // move it here to keep the device visible. no use for now, just helps to have a consistent in/out setup let _midi_in_connection = midi_in_connection; @@ -86,7 +83,10 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> match command { #[allow(unused_mut)] #[allow(unused_variables)] - MidiOutputWorkerCommand::SendToController { reception_time, mut messages } => { + MidiOutputWorkerCommand::SendToController { + reception_time, + mut messages, + } => { #[cfg(target_os = "linux")] for message in messages { // TODO timing is lost, we should actually wait until buffer_start_time and wait for the @@ -102,7 +102,7 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> // we cannot accurately tell when those notes should be played, we just know when it was // received and the earliest time being reception + time that represents a buffer //let play_time = reception_time + block_duration; - let play_time = reception_time ; + let play_time = reception_time; let message = messages.remove(0); let mut buffer = PacketBuffer::new( message.delta_frames as u64 * sample_to_mach + play_time, @@ -110,8 +110,10 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> ); for message in messages { - buffer.push_data(message.delta_frames as u64 * sample_to_mach + play_time, - &message.data.get_bytes()); + buffer.push_data( + message.delta_frames as u64 * sample_to_mach + play_time, + &message.data.get_bytes(), + ); } source.received(&buffer).unwrap(); @@ -119,25 +121,27 @@ pub(crate) fn spawn_midi_output_worker(name: String) -> } MidiOutputWorkerCommand::Stop(sender, event_id) => { match sender.send(()).await { - Ok(_) => { info!("[{}] Stopping controller {}", event_id, name); } - Err(err) => { info!("[{}] Error while quitting midi out {}: {}", event_id, name, err); } + Ok(_) => { + info!("[{}] Stopping controller {}", event_id, name); + } + Err(err) => { + info!("[{}] Error while quitting midi out {}: {}", event_id, name, err); + } } return; } - MidiOutputWorkerCommand::SetSampleRate(_rate) => { - #[cfg(target_os = "macos")] - { - let sample_to_second = 1.0 / _rate as f64; - sample_to_mach = (sample_to_second * second_to_mach) as u64; - block_duration = sample_to_mach * _block_size; - } + MidiOutputWorkerCommand::SetSampleRate(_rate) => + #[cfg(target_os = "macos")] + { + let sample_to_second = 1.0 / _rate as f64; + sample_to_mach = (sample_to_second * second_to_mach) as u64; + block_duration = sample_to_mach * _block_size; } - MidiOutputWorkerCommand::SetBlockSize(_size) => { - #[cfg(target_os = "macos")] - { - _block_size = _size as u64; - block_duration = sample_to_mach * _block_size; - } + MidiOutputWorkerCommand::SetBlockSize(_size) => + #[cfg(target_os = "macos")] + { + _block_size = _size as u64; + block_duration = sample_to_mach * _block_size; } } } diff --git a/arpegiator/src/workers/mod.rs b/arpegiator/src/workers/mod.rs index 0f751d0..8392c7f 100644 --- a/arpegiator/src/workers/mod.rs +++ b/arpegiator/src/workers/mod.rs @@ -1,3 +1,6 @@ -#[cfg(not(feature="midi_hack_transmission"))] pub mod ipc_worker; -#[cfg(not(feature="midi_hack_transmission"))] pub mod main_worker; -#[cfg(not(feature="midi_hack_transmission"))] mod midi_output_worker; +#[cfg(not(feature = "midi_hack_transmission"))] +pub mod ipc_worker; +#[cfg(not(feature = "midi_hack_transmission"))] +pub mod main_worker; +#[cfg(not(feature = "midi_hack_transmission"))] +mod midi_output_worker; diff --git a/arpegiator_pattern_receiver/src/ipc_worker.rs b/arpegiator_pattern_receiver/src/ipc_worker.rs index 16a987a..f8de8ed 100644 --- a/arpegiator_pattern_receiver/src/ipc_worker.rs +++ b/arpegiator_pattern_receiver/src/ipc_worker.rs @@ -1,26 +1,28 @@ use { - log::{error, info}, - std::{thread, error}, - async_channel::{Sender, Receiver}, + async_channel::{Receiver, Sender}, async_std::net::UdpSocket, + async_std::task, ipc_channel::ipc::IpcSender, - util::ipc_payload::{PatternPayload, IPCCommand, BootstrapPayload}, + log::{error, info}, std::net::ToSocketAddrs, - async_std::task, - std::time::Duration + std::time::Duration, + std::{error, thread}, + util::ipc_payload::{BootstrapPayload, IPCCommand, PatternPayload}, }; pub(crate) enum IPCWorkerCommand { Stop, SetPort(u16), Send(PatternPayload), - TryConnect + TryConnect, } - async fn try_udp_send_receiver(port: u16) -> Result, Box> { let socket = UdpSocket::bind("127.0.0.1:0").await?; - let to = format!("127.0.0.1:{}", port).to_socket_addrs()?.next().ok_or("empty list")?; + let to = format!("127.0.0.1:{}", port) + .to_socket_addrs()? + .next() + .ok_or("empty list")?; let (one_shot, name) = ipc_channel::ipc::IpcOneShotServer::new()?; let serialized_name = bincode::serialize(&name)?; @@ -41,18 +43,20 @@ async fn try_udp_send_receiver(port: u16) -> Result, Box::connect(name).or(Err("Cannot connect to signal timeout on ipc bootstrap"))? - .send(BootstrapPayload::Timeout).or(Err("Cannot send timeout to ipc bootstrapper"))?; + IpcSender::::connect(name) + .or(Err("Cannot connect to signal timeout on ipc bootstrap"))? + .send(BootstrapPayload::Timeout) + .or(Err("Cannot send timeout to ipc bootstrapper"))?; Err(format!("Connection timeout {}", e)) })?; let (ping_sender, ping_receiver) = ipc_channel::ipc::channel::<()>()?; info!("sending ping"); ipc_sender.send(IPCCommand::Ping(ping_sender))?; - task::sleep(Duration::new(1,0)).await; + task::sleep(Duration::new(1, 0)).await; ping_receiver.try_recv().or(Err("Pong not received"))?; info!("Ping received"); @@ -78,14 +82,18 @@ async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_rece ipc_sender = match try_udp_send_receiver(port.unwrap()).await { Ok(ipc_sender) => Some(ipc_sender), Err(err) => { - error!("Error while connecting to arpegiator on port {} : {}", port.unwrap(), err); - task::sleep(Duration::new(1,0)).await; + error!( + "Error while connecting to arpegiator on port {} : {}", + port.unwrap(), + err + ); + task::sleep(Duration::new(1, 0)).await; retry_scheduled = true; ipc_worker_sender.send(IPCWorkerCommand::TryConnect).await.unwrap(); None } }; - }, + } IPCWorkerCommand::SetPort(new_port) => { ipc_sender = None; port = Some(new_port); @@ -93,7 +101,6 @@ async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_rece ipc_worker_sender.send(IPCWorkerCommand::TryConnect).await.unwrap(); retry_scheduled = true } - } IPCWorkerCommand::Send(payload) => { let ipc_sender_ref = match ipc_sender.as_ref() { @@ -101,7 +108,7 @@ async fn ipc_worker(ipc_worker_sender: Sender, ipc_worker_rece error!("IPC not ready, ignoring {:?}", payload); continue; } - Some(ipc_sender) => ipc_sender + Some(ipc_sender) => ipc_sender, }; if let Err(err) = ipc_sender_ref.send(IPCCommand::PatternPayload(payload)) { @@ -121,9 +128,7 @@ pub(crate) fn spawn_ipc_worker() -> Sender { let (worker_sender, worker_receiver) = async_channel::unbounded(); { let worker_sender = worker_sender.clone(); - thread::spawn(move || task::block_on( - ipc_worker(worker_sender, worker_receiver) - )) + thread::spawn(move || task::block_on(ipc_worker(worker_sender, worker_receiver))) }; worker_sender } diff --git a/arpegiator_pattern_receiver/src/lib.rs b/arpegiator_pattern_receiver/src/lib.rs index 7ed9ff7..f29bb28 100644 --- a/arpegiator_pattern_receiver/src/lib.rs +++ b/arpegiator_pattern_receiver/src/lib.rs @@ -1,84 +1,82 @@ +use std::sync::Arc; #[allow(unused_imports)] use { - std::mem::take, - log::{info, error}, async_channel::Sender, - vst::event::Event, + log::{error, info}, + std::mem::take, + vst::api::MidiEvent, vst::buffer::SendEventBuffer, - vst::api::MidiEvent + vst::event::Event, }; -use std::sync::Arc; use vst::api; -use vst::buffer::{AudioBuffer}; +use vst::buffer::AudioBuffer; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use util::logging::logging_setup; -#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(not(feature = "midi_hack_transmission"))] use { + crate::ipc_worker::{spawn_ipc_worker, IPCWorkerCommand}, util::ipc_payload::PatternPayload, - crate::ipc_worker::{IPCWorkerCommand, spawn_ipc_worker}, - util::midi_message_with_delta::MidiMessageWithDelta + util::midi_message_with_delta::MidiMessageWithDelta, }; use crate::parameters::{ArpegiatorPatternReceiverParameters, PARAMETER_COUNT}; +#[cfg(not(feature = "midi_hack_transmission"))] +mod ipc_worker; mod parameters; -#[cfg(not(feature="midi_hack_transmission"))] mod ipc_worker; #[macro_use] extern crate vst; plugin_main!(ArpegiatorPatternReceiver); - struct ArpegiatorPatternReceiver { #[allow(dead_code)] host: HostCallback, #[cfg(feature = "midi_hack_transmission")] send_buffer: SendEventBuffer, - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] ipc_worker_sender: Option>, - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] messages: Vec, - #[cfg(feature="midi_hack_transmission")] + #[cfg(feature = "midi_hack_transmission")] messages: Vec, current_time: usize, resumed: bool, - parameters: Arc + parameters: Arc, } - impl Default for ArpegiatorPatternReceiver { fn default() -> Self { ArpegiatorPatternReceiver { host: Default::default(), - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] ipc_worker_sender: None, messages: vec![], current_time: 0, resumed: false, parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()), - #[cfg(feature="midi_hack_transmission")] - send_buffer: Default::default() + #[cfg(feature = "midi_hack_transmission")] + send_buffer: Default::default(), } } } impl ArpegiatorPatternReceiver { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] fn stop_worker(&mut self) { if let Some(sender) = take(&mut self.ipc_worker_sender) { - sender.try_send(IPCWorkerCommand::Stop).unwrap_or_else(|err| { - error!("Error while closing sender channel : {}", err) - }); + sender + .try_send(IPCWorkerCommand::Stop) + .unwrap_or_else(|err| error!("Error while closing sender channel : {}", err)); sender.close(); } } } - impl Plugin for ArpegiatorPatternReceiver { fn get_info(&self) -> Info { Info { @@ -102,22 +100,24 @@ impl Plugin for ArpegiatorPatternReceiver { fn new(host: HostCallback) -> Self { logging_setup(); - info!("{} midi_hack_transmission={}", - build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, + info!( + "{} midi_hack_transmission={}", + build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp), - cfg!(feature = "midi_hack_transmission")); + cfg!(feature = "midi_hack_transmission") + ); ArpegiatorPatternReceiver { host, - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] ipc_worker_sender: None, messages: vec![], current_time: 0, resumed: false, parameters: Arc::new(ArpegiatorPatternReceiverParameters::new()), - #[cfg(feature="midi_hack_transmission")] - send_buffer: Default::default() + #[cfg(feature = "midi_hack_transmission")] + send_buffer: Default::default(), } } @@ -127,16 +127,18 @@ impl Plugin for ArpegiatorPatternReceiver { } self.resumed = true; - self.current_time = 0 ; + self.current_time = 0; - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] { self.stop_worker(); - let sender= spawn_ipc_worker(); + let sender = spawn_ipc_worker(); self.ipc_worker_sender = Some(sender.clone()); - sender.try_send(IPCWorkerCommand::SetPort(self.parameters.get_port())).unwrap(); + sender + .try_send(IPCWorkerCommand::SetPort(self.parameters.get_port())) + .unwrap(); if let Ok(mut socket_command) = self.parameters.ipc_worker_sender.lock() { *socket_command = Some(sender); @@ -150,7 +152,7 @@ impl Plugin for ArpegiatorPatternReceiver { } self.resumed = false; - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] { self.stop_worker() } @@ -166,7 +168,6 @@ impl Plugin for ArpegiatorPatternReceiver { if s == "MPE" { Yes } else { - Maybe } } @@ -176,21 +177,25 @@ impl Plugin for ArpegiatorPatternReceiver { fn process(&mut self, buffer: &mut AudioBuffer) { if !self.messages.is_empty() { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] if let Some(ipc_worker_sender) = &self.ipc_worker_sender { let payload = PatternPayload { time: { - #[cfg(target_os = "macos")] unsafe { mach::mach_time::mach_absolute_time() } - #[cfg(target_os = "linux")] 0 + #[cfg(target_os = "macos")] + unsafe { + mach::mach_time::mach_absolute_time() + } + #[cfg(target_os = "linux")] + 0 }, - messages: take(&mut self.messages) - } ; + messages: take(&mut self.messages), + }; ipc_worker_sender.try_send(IPCWorkerCommand::Send(payload)).unwrap() } else { self.messages.clear(); } - #[cfg(feature="midi_hack_transmission")] + #[cfg(feature = "midi_hack_transmission")] { self.send_buffer.send_events(&self.messages, &mut self.host); self.messages.clear() @@ -201,28 +206,36 @@ impl Plugin for ArpegiatorPatternReceiver { } fn process_events(&mut self, events: &api::Events) { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] if self.ipc_worker_sender.is_none() { return; } - self.messages.extend(events.events().map(|event| match event { - #[allow(unused_mut)] - Event::Midi(mut event) => { - #[cfg(not(feature = "midi_hack_transmission"))] { - Ok(MidiMessageWithDelta { - delta_frames: event.delta_frames as u16, - data: event.data.into(), - }) - } - #[cfg(feature = "midi_hack_transmission")] { - event.data[0] -= 0x80; - Ok(event) - } - }, - Event::SysEx(_) => Err(()), - Event::Deprecated(_) => Err(()) - }).filter(|item| item.is_ok()).map(|item| item.unwrap())); + self.messages.extend( + events + .events() + .map(|event| match event { + #[allow(unused_mut)] + Event::Midi(mut event) => { + #[cfg(not(feature = "midi_hack_transmission"))] + { + Ok(MidiMessageWithDelta { + delta_frames: event.delta_frames as u16, + data: event.data.into(), + }) + } + #[cfg(feature = "midi_hack_transmission")] + { + event.data[0] -= 0x80; + Ok(event) + } + } + Event::SysEx(_) => Err(()), + Event::Deprecated(_) => Err(()), + }) + .filter(|item| item.is_ok()) + .map(|item| item.unwrap()), + ); } fn get_parameter_object(&mut self) -> Arc { @@ -232,7 +245,7 @@ impl Plugin for ArpegiatorPatternReceiver { impl Drop for ArpegiatorPatternReceiver { fn drop(&mut self) { - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] { self.stop_worker(); } diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs index 6d196d9..17f8284 100644 --- a/arpegiator_pattern_receiver/src/parameters.rs +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -1,33 +1,28 @@ #[allow(unused_imports)] use { - log::{info, error}, + log::{error, info}, std::error, - util::parameter_value_conversion::f32_to_byte + util::parameter_value_conversion::f32_to_byte, }; use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; use util::parameters::ParameterConversion; -#[cfg(not(feature="midi_hack_transmission"))] -use { - crate::ipc_worker::IPCWorkerCommand, - async_channel::Sender, - std::sync::Mutex -}; - +#[cfg(not(feature = "midi_hack_transmission"))] +use {crate::ipc_worker::IPCWorkerCommand, async_channel::Sender, std::sync::Mutex}; - -#[cfg(feature="midi_hack_transmission")] +#[cfg(feature = "midi_hack_transmission")] pub const PARAMETER_COUNT: usize = 0; -#[cfg(not(feature="midi_hack_transmission"))] +#[cfg(not(feature = "midi_hack_transmission"))] pub const PARAMETER_COUNT: usize = 1; const BASE_PORT: u16 = 6000; pub(crate) struct ArpegiatorPatternReceiverParameters { pub transfer: ParameterTransfer, - #[cfg(not(feature="midi_hack_transmission"))] pub ipc_worker_sender: Mutex>> + #[cfg(not(feature = "midi_hack_transmission"))] + pub ipc_worker_sender: Mutex>>, } impl ArpegiatorPatternReceiverParameters { @@ -35,17 +30,18 @@ impl ArpegiatorPatternReceiverParameters { BASE_PORT + self.get_byte_parameter(Parameter::PortIndex) as u16 } - #[cfg(not(feature="midi_hack_transmission"))] + #[cfg(not(feature = "midi_hack_transmission"))] fn update_port(&self) -> Result<(), Box> { let port = self.get_byte_parameter(Parameter::PortIndex); - self.ipc_worker_sender.lock()?.as_ref(). - ok_or("cannot unlock ipc worker sender mutex")?. - try_send(IPCWorkerCommand::SetPort(BASE_PORT + port as u16))?; + self.ipc_worker_sender + .lock()? + .as_ref() + .ok_or("cannot unlock ipc worker sender mutex")? + .try_send(IPCWorkerCommand::SetPort(BASE_PORT + port as u16))?; Ok(()) } } - #[repr(i32)] pub enum Parameter { PortIndex = 0, @@ -60,7 +56,6 @@ impl From for Parameter { } } - impl Into for Parameter { fn into(self) -> i32 { self as i32 @@ -77,30 +72,28 @@ impl ParameterConversion for ArpegiatorPatternReceiverParameters { } } - impl ArpegiatorPatternReceiverParameters { pub fn new() -> Self { ArpegiatorPatternReceiverParameters { transfer: ParameterTransfer::new(PARAMETER_COUNT), - #[cfg(not(feature="midi_hack_transmission"))] ipc_worker_sender: Mutex::new(None) + #[cfg(not(feature = "midi_hack_transmission"))] + ipc_worker_sender: Mutex::new(None), } } } - impl PluginParameters for ArpegiatorPatternReceiverParameters { fn get_parameter_text(&self, index: i32) -> String { match index.into() { - Parameter::PortIndex => { - self.get_port().to_string() - } + Parameter::PortIndex => self.get_port().to_string(), } } fn get_parameter_name(&self, index: i32) -> String { match index.into() { Parameter::PortIndex => "Port", - }.to_string() + } + .to_string() } fn get_parameter(&self, index: i32) -> f32 { @@ -110,7 +103,7 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { fn set_parameter(&self, index: i32, value: f32) { match index.into() { Parameter::PortIndex => { - #[cfg(not(feature="midi_hack_transmission"))] { + #[cfg(not(feature = "midi_hack_transmission"))] { let new_value = f32_to_byte(value); let old_value = self.get_byte_parameter(Parameter::PortIndex); if old_value != new_value { @@ -134,7 +127,8 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { fn load_preset_data(&self, data: &[u8]) { self.deserialize_state(data); - #[cfg(not(feature = "midi_hack_transmission"))] { + #[cfg(not(feature = "midi_hack_transmission"))] + { self.update_port().unwrap_or_else(|err| { error!("Could not update port: {}", err); }); @@ -143,7 +137,8 @@ impl PluginParameters for ArpegiatorPatternReceiverParameters { fn load_bank_data(&self, data: &[u8]) { self.deserialize_state(data); - #[cfg(not(feature = "midi_hack_transmission"))] { + #[cfg(not(feature = "midi_hack_transmission"))] + { self.update_port().unwrap_or_else(|err| { error!("Could not update port: {}", err); }); diff --git a/audio_data/src/lib.rs b/audio_data/src/lib.rs index ff35350..affabfc 100644 --- a/audio_data/src/lib.rs +++ b/audio_data/src/lib.rs @@ -3,15 +3,13 @@ extern crate vst; use log::info; +use std::time::SystemTime; +use util::logging::logging_setup; +use util::transmute_buffer::{transmute_raw_buffer, transmute_raw_buffer_mut}; use vst::api; use vst::buffer::{AudioBuffer, SendEventBuffer}; use vst::event::{Event, MidiEvent}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; -use std::time::SystemTime; -use util::logging::logging_setup; -use util::transmute_buffer::{transmute_raw_buffer, transmute_raw_buffer_mut}; - - plugin_main!(AudioData); @@ -20,7 +18,7 @@ pub struct AudioData { send_buffer: SendEventBuffer, host: HostCallback, last_was: u128, - last_now: u128 + last_now: u128, } impl Default for AudioData { @@ -30,7 +28,7 @@ impl Default for AudioData { send_buffer: Default::default(), host: Default::default(), last_was: 0, - last_now: 0 + last_now: 0, } } } @@ -42,7 +40,6 @@ impl AudioData { } } - impl Plugin for AudioData { fn get_info(&self) -> Info { Info { @@ -64,7 +61,6 @@ impl Plugin for AudioData { } } - fn new(host: HostCallback) -> Self { logging_setup(); AudioData { @@ -72,7 +68,7 @@ impl Plugin for AudioData { send_buffer: Default::default(), host, last_was: 0, - last_now: 0 + last_now: 0, } } @@ -89,15 +85,23 @@ impl Plugin for AudioData { fn process(&mut self, buffer: &mut AudioBuffer) { self.send_midi(); let (inputs, mut outputs) = buffer.split(); - let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_micros(); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_micros(); let input_buffer = transmute_raw_buffer(inputs.get(0)); let was = input_buffer[0]; if self.last_was != was { self.last_was = was; - info!("difference = {} microseconds. Since last check: diff={} now={} was={}", now - was, now - - self.last_now, now, was); + info!( + "difference = {} microseconds. Since last check: diff={} now={} was={}", + now - was, + now - self.last_now, + now, + was + ); } self.last_now = now; diff --git a/max_note_duration/src/lib.rs b/max_note_duration/src/lib.rs index a0d9436..ce12862 100644 --- a/max_note_duration/src/lib.rs +++ b/max_note_duration/src/lib.rs @@ -5,17 +5,17 @@ extern crate vst; use std::sync::Arc; +use vst::api::Events; use vst::buffer::{AudioBuffer, SendEventBuffer}; -use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use vst::event::{Event, MidiEvent}; -use vst::api::Events; +use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use parameters::MaxNoteDurationPluginParameters; use parameters::Parameter; -use util::midi_message_type::MidiMessageType; -use util::parameters::ParameterConversion; use std::collections::HashSet; use std::hash::{Hash, Hasher}; +use util::midi_message_type::MidiMessageType; +use util::parameters::ParameterConversion; plugin_main!(MaxNoteDurationPlugin); @@ -32,7 +32,6 @@ impl PartialEq for PlayingNote { } } - impl Hash for PlayingNote { fn hash(&self, state: &mut H) { self.channel.hash(state); @@ -40,7 +39,6 @@ impl Hash for PlayingNote { } } - pub struct MaxNoteDurationPlugin { current_time_in_samples: usize, parameters: Arc, @@ -67,7 +65,6 @@ impl Default for MaxNoteDurationPlugin { } } - impl MaxNoteDurationPlugin { #[allow(dead_code)] fn seconds_per_sample(&self) -> f32 { @@ -84,7 +81,6 @@ impl MaxNoteDurationPlugin { } } - impl Plugin for MaxNoteDurationPlugin { fn get_info(&self) -> Info { Info { @@ -134,13 +130,18 @@ impl Plugin for MaxNoteDurationPlugin { use vst::plugin::CanDo::*; match can_do { - SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline | ReceiveTimeInfo | MidiKeyBasedInstrumentControl | Bypass => Yes, + SendEvents + | SendMidiEvent + | ReceiveEvents + | ReceiveMidiEvent + | Offline + | ReceiveTimeInfo + | MidiKeyBasedInstrumentControl + | Bypass => Yes, MidiProgramNames => No, ReceiveSysExEvent => Yes, MidiSingleNoteTuningChange => No, - Other(_) => { - Maybe - } + Other(_) => Maybe, } } @@ -173,8 +174,11 @@ impl Plugin for MaxNoteDurationPlugin { } fn process_events(&mut self, events: &Events) { - let maximum_duration = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter(Parameter::MaxDuration, - 10., 20.)); + let maximum_duration = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter( + Parameter::MaxDuration, + 10., + 20., + )); for event in events.events() { let midi_event = if let Event::Midi(midi_event) = event { @@ -188,7 +192,7 @@ impl Plugin for MaxNoteDurationPlugin { self.current_playing_notes.remove(&PlayingNote { channel: note.channel, pitch: note.pitch, - deadline: 0, // ignored for comparisons + deadline: 0, // ignored for comparisons }); } MidiMessageType::NoteOnMessage(note) => { diff --git a/max_note_duration/src/parameters.rs b/max_note_duration/src/parameters.rs index 0fd0fb5..b749579 100644 --- a/max_note_duration/src/parameters.rs +++ b/max_note_duration/src/parameters.rs @@ -1,10 +1,10 @@ use std::sync::Mutex; -use vst::plugin::HostCallback ; +use vst::plugin::HostCallback; use vst::util::ParameterTransfer; -use util::{HostCallbackLock, duration_display}; use util::parameters::ParameterConversion; +use util::{duration_display, HostCallbackLock}; const PARAMETER_COUNT: usize = 1; @@ -27,14 +27,12 @@ impl From for Parameter { } } - impl Into for Parameter { fn into(self) -> i32 { self as i32 } } - impl ParameterConversion for MaxNoteDurationPluginParameters { fn get_parameter_transfer(&self) -> &ParameterTransfer { &self.transfer @@ -45,7 +43,6 @@ impl ParameterConversion for MaxNoteDurationPluginParameters { } } - impl MaxNoteDurationPluginParameters { pub fn new(host: HostCallback) -> Self { MaxNoteDurationPluginParameters { @@ -55,7 +52,6 @@ impl MaxNoteDurationPluginParameters { } } - impl Default for MaxNoteDurationPluginParameters { fn default() -> Self { MaxNoteDurationPluginParameters { @@ -65,7 +61,6 @@ impl Default for MaxNoteDurationPluginParameters { } } - impl vst::plugin::PluginParameters for MaxNoteDurationPluginParameters { fn get_parameter_text(&self, index: i32) -> String { match index.into() { diff --git a/midi_delay/src/lib.rs b/midi_delay/src/lib.rs index 78300ad..7e37472 100644 --- a/midi_delay/src/lib.rs +++ b/midi_delay/src/lib.rs @@ -3,8 +3,8 @@ mod parameters; #[macro_use] extern crate vst; -use std::sync::Arc; use std::cell::RefCell; +use std::sync::Arc; use vst::api::Events; use vst::buffer::{AudioBuffer, SendEventBuffer}; use vst::event::Event; @@ -16,11 +16,8 @@ use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; - - plugin_main!(MidiDelay); - pub struct MidiDelay { current_time_in_samples: usize, message_queue: AbsoluteTimeMidiMessageVector, @@ -29,7 +26,6 @@ pub struct MidiDelay { send_buffer: RefCell, } - impl Default for MidiDelay { fn default() -> Self { MidiDelay { @@ -42,7 +38,6 @@ impl Default for MidiDelay { } } - impl MidiDelay { fn increase_time_in_samples(&mut self, samples: usize) { let new_time_in_samples = self.current_time_in_samples + samples; @@ -60,23 +55,23 @@ impl MidiDelay { fn send_events(&mut self, samples: usize) { if let Ok(mut host_callback_lock) = self.parameters.host.lock() { - let (next_message_queue, events) - = process_scheduled_events( + let (next_message_queue, events) = process_scheduled_events( samples, self.current_time_in_samples, &self.message_queue, 0, false, - self.parameters.get_parameter(Parameter::Delay.into()) > 0.0 - ); + self.parameters.get_parameter(Parameter::Delay.into()) > 0.0, + ); self.message_queue = next_message_queue; - self.send_buffer.borrow_mut().send_events(events, &mut host_callback_lock.host); + self.send_buffer + .borrow_mut() + .send_events(events, &mut host_callback_lock.host); } } } - impl Plugin for MidiDelay { fn get_info(&self) -> Info { Info { @@ -118,7 +113,7 @@ impl Plugin for MidiDelay { SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline | Bypass => Yes, MidiProgramNames | ReceiveSysExEvent | MidiSingleNoteTuningChange => No, Other(_) => Maybe, - _ => Maybe + _ => Maybe, } } @@ -136,15 +131,17 @@ impl Plugin for MidiDelay { } fn process_events(&mut self, events: &Events) { - let midi_delay = self.seconds_to_samples( - self.parameters.get_exponential_scale_parameter(Parameter::Delay, 1., 80.) - ); + let midi_delay = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter( + Parameter::Delay, + 1., + 80., + )); for event in events.events() { let midi_event = if let Event::Midi(midi_event) = event { midi_event } else { - continue + continue; }; if let MidiMessageType::NoteOffMessage(_) = MidiMessageType::from(&midi_event.data) { @@ -168,11 +165,10 @@ impl Plugin for MidiDelay { } else { self.message_queue.insert_message( midi_event.data, - midi_delay + midi_event.delta_frames as usize + self.current_time_in_samples, MessageReason::Live + midi_delay + midi_event.delta_frames as usize + self.current_time_in_samples, + MessageReason::Live, ); } - - } } } diff --git a/midi_delay/src/parameters.rs b/midi_delay/src/parameters.rs index f7fbb74..5e10bdd 100644 --- a/midi_delay/src/parameters.rs +++ b/midi_delay/src/parameters.rs @@ -1,8 +1,8 @@ use std::sync::Mutex; -use util::{HostCallbackLock, duration_display}; +use util::parameters::ParameterConversion; +use util::{duration_display, HostCallbackLock}; use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; -use util::parameters::ParameterConversion; pub struct MidiDelayParameters { pub host: Mutex, @@ -14,7 +14,6 @@ pub enum Parameter { Delay = 0, } - impl From for Parameter { fn from(i: i32) -> Self { match i { @@ -30,7 +29,6 @@ impl Into for Parameter { } } - impl ParameterConversion for MidiDelayParameters { fn get_parameter_transfer(&self) -> &ParameterTransfer { &self.transfer @@ -50,7 +48,6 @@ impl MidiDelayParameters { } } - impl PluginParameters for MidiDelayParameters { fn get_parameter_text(&self, index: i32) -> String { match index.into() { @@ -67,8 +64,9 @@ impl PluginParameters for MidiDelayParameters { fn get_parameter_name(&self, index: i32) -> String { match Parameter::from(index as i32) { - Parameter::Delay => "Delay" - }.to_string() + Parameter::Delay => "Delay", + } + .to_string() } fn get_parameter(&self, index: i32) -> f32 { diff --git a/note_fan_out/src/lib.rs b/note_fan_out/src/lib.rs index 9f363cb..9f0e595 100644 --- a/note_fan_out/src/lib.rs +++ b/note_fan_out/src/lib.rs @@ -4,7 +4,7 @@ mod parameters; extern crate vst; use std::collections::HashSet; -use std::hash::{Hasher, Hash}; +use std::hash::{Hash, Hasher}; use std::sync::Arc; use vst::api; use vst::buffer::{AudioBuffer, SendEventBuffer}; @@ -20,7 +20,6 @@ use util::raw_message::RawMessage; plugin_main!(NoteFanOut); - #[derive(Default)] pub struct NoteFanOut { events: Vec, @@ -28,25 +27,23 @@ pub struct NoteFanOut { send_buffer: SendEventBuffer, parameters: Arc, current_step: u8, - notes_counter: usize + notes_counter: usize, } - impl NoteFanOut { fn send_midi(&mut self) { if let Ok(mut host_callback_lock) = self.parameters.host.lock() { - self.send_buffer - .send_events(&self.events, &mut host_callback_lock.host); + self.send_buffer.send_events(&self.events, &mut host_callback_lock.host); } self.events.clear(); } } -#[derive(Eq,Clone)] +#[derive(Eq, Clone)] struct PlayingNote { channel: u8, pitch: u8, - mapped_channel: u8 + mapped_channel: u8, } impl PartialEq for PlayingNote { @@ -98,7 +95,7 @@ impl Plugin for NoteFanOut { SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent | Offline | Bypass => Yes, MidiProgramNames | ReceiveSysExEvent | MidiSingleNoteTuningChange => No, Other(_) => Maybe, - _ => Maybe + _ => Maybe, } } @@ -116,23 +113,23 @@ impl Plugin for NoteFanOut { match midi_message { MidiMessageType::NoteOnMessage(midi_message) => { - let target_channel = match self.parameters.get_channel_distribution(Parameter::ChannelDistribute) { - ChannelDistribution::Channels(distribution) => { - let target_channel = (self.notes_counter % (distribution as usize)) as u8 + 1; - self.notes_counter += 1; - target_channel - } - ChannelDistribution::Off => { - GenericChannelMessage::from(&e.data).get_channel() - } - }; + let target_channel = + match self.parameters.get_channel_distribution(Parameter::ChannelDistribute) { + ChannelDistribution::Channels(distribution) => { + let target_channel = (self.notes_counter % (distribution as usize)) as u8 + 1; + self.notes_counter += 1; + target_channel + } + ChannelDistribution::Off => GenericChannelMessage::from(&e.data).get_channel(), + }; if steps == 0 || selection == self.current_step { - let raw_message : RawMessage = NoteOn { + let raw_message: RawMessage = NoteOn { channel: target_channel, pitch: midi_message.pitch, - velocity: midi_message.velocity - }.into(); + velocity: midi_message.velocity, + } + .into(); self.events.push(MidiEvent { data: raw_message.into(), @@ -141,34 +138,35 @@ impl Plugin for NoteFanOut { note_length: e.note_length, note_offset: e.note_offset, detune: e.detune, - note_off_velocity: e.note_off_velocity + note_off_velocity: e.note_off_velocity, }); self.current_playing_notes.insert(PlayingNote { channel: midi_message.get_channel(), pitch: midi_message.get_pitch(), - mapped_channel: target_channel + mapped_channel: target_channel, }); } if steps > 0 { - self.current_step = (self.current_step + 1) % steps ; + self.current_step = (self.current_step + 1) % steps; } } MidiMessageType::NoteOffMessage(midi_message) => { let lookup = PlayingNote { channel: midi_message.get_channel(), pitch: midi_message.get_pitch(), - mapped_channel: midi_message.get_channel() + mapped_channel: midi_message.get_channel(), }; match self.current_playing_notes.take(&lookup) { Some(note) => { - let raw_message : RawMessage = NoteOff { + let raw_message: RawMessage = NoteOff { channel: note.mapped_channel, pitch: midi_message.pitch, - velocity: midi_message.velocity - }.into(); + velocity: midi_message.velocity, + } + .into(); self.events.push(MidiEvent { data: raw_message.into(), @@ -177,7 +175,7 @@ impl Plugin for NoteFanOut { note_length: e.note_length, note_offset: e.note_offset, detune: e.detune, - note_off_velocity: e.note_off_velocity + note_off_velocity: e.note_off_velocity, }); } None => { diff --git a/note_fan_out/src/parameters.rs b/note_fan_out/src/parameters.rs index c246a6a..8a28322 100644 --- a/note_fan_out/src/parameters.rs +++ b/note_fan_out/src/parameters.rs @@ -1,9 +1,9 @@ use std::sync::Mutex; use util::parameter_value_conversion::f32_to_byte; +use util::parameters::ParameterConversion; use util::HostCallbackLock; use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; -use util::parameters::ParameterConversion; pub struct NoteFanoutParameters { pub host: Mutex, @@ -18,7 +18,6 @@ pub enum Parameter { ChannelDistribute, } - impl Clone for Parameter { fn clone(&self) -> Self { let value = *self as i32; @@ -75,7 +74,7 @@ impl NoteFanoutParameters { pub enum ChannelDistribution { Channels(u8), - Off + Off, } impl From for ChannelDistribution { @@ -85,27 +84,26 @@ impl From for ChannelDistribution { if channels_value < 2 { ChannelDistribution::Off } else { - ChannelDistribution::Channels(channels_value) // channel 0 ( displayed as 1 ) is reserved for MPE + ChannelDistribution::Channels(channels_value) // channel 0 ( displayed as 1 ) is reserved for MPE } } } - impl Into for ChannelDistribution { fn into(self) -> f32 { match self { ChannelDistribution::Channels(value) => { // normalize over 15 values, first range is off - ((value as f32 - 2.) / 13.) * 14. / 15. + 1./15. + ((value as f32 - 2.) / 13.) * 14. / 15. + 1. / 15. } - ChannelDistribution::Off => 0.0 + ChannelDistribution::Off => 0.0, } } } impl PluginParameters for NoteFanoutParameters { fn get_parameter_text(&self, index: i32) -> String { - match Parameter::from(index ) { + match Parameter::from(index) { Parameter::Steps => { let value = self.get_byte_parameter(Parameter::Steps) / 8; if value == 0 { @@ -117,12 +115,10 @@ impl PluginParameters for NoteFanoutParameters { Parameter::Selection => { format!("{}", self.get_byte_parameter(Parameter::Selection) / 8) } - Parameter::ChannelDistribute => { - match self.get_channel_distribution(Parameter::ChannelDistribute) { - ChannelDistribution::Channels(c) => format!("{}", c), - ChannelDistribution::Off => "Off".to_string() - } - } + Parameter::ChannelDistribute => match self.get_channel_distribution(Parameter::ChannelDistribute) { + ChannelDistribution::Channels(c) => format!("{}", c), + ChannelDistribution::Off => "Off".to_string(), + }, } } @@ -130,7 +126,7 @@ impl PluginParameters for NoteFanoutParameters { match Parameter::from(index as i32) { Parameter::Steps => "Steps", Parameter::Selection => "Selection", - Parameter::ChannelDistribute => "Channel distribution" + Parameter::ChannelDistribute => "Channel distribution", } .to_string() } diff --git a/note_generator/src/bin/host.rs b/note_generator/src/bin/host.rs index 8e428cc..b59cd33 100644 --- a/note_generator/src/bin/host.rs +++ b/note_generator/src/bin/host.rs @@ -49,23 +49,14 @@ fn main() { VST ID: {}\n\t\ Version: {}\n\t\ Initial Delay: {} samples", - info.name, - info.vendor, - info.presets, - info.parameters, - info.unique_id, - info.version, - info.initial_delay + info.name, info.vendor, info.presets, info.parameters, info.unique_id, info.version, info.initial_delay ); // Initialize the instance instance.init(); println!("{}", instance.can_do(CanDo::Offline) == Supported::No); - println!( - "{}", - instance.can_do(CanDo::ReceiveEvents) == Supported::Yes - ); + println!("{}", instance.can_do(CanDo::ReceiveEvents) == Supported::Yes); let mut host_buffer: HostBuffer = HostBuffer::new(2, 2); let inputs = vec![vec![0.0; 1000]; 2]; let mut outputs = vec![vec![0.0; 1000]; 2]; diff --git a/note_generator/src/lib.rs b/note_generator/src/lib.rs index 3e32aaf..19e837f 100644 --- a/note_generator/src/lib.rs +++ b/note_generator/src/lib.rs @@ -69,14 +69,7 @@ impl NoteGeneratorPlugin { let pitchbend_value = self.parameters.get_u14_parameter(Parameter::PitchBend); let msb = pitchbend_value >> 7; let lsb = pitchbend_value & 0x7F; - make_midi_message( - [ - channel + PITCHBEND, - lsb as u8, - msb as u8, - ], - delta, - ) + make_midi_message([channel + PITCHBEND, lsb as u8, msb as u8], delta) } fn get_current_pressure(&self, delta: i32) -> MidiEvent { @@ -134,8 +127,7 @@ impl NoteGeneratorPlugin { } if let Ok(mut host_callback_lock) = self.parameters.host.lock() { - self.send_buffer - .send_events(&self.events, &mut host_callback_lock.host); + self.send_buffer.send_events(&self.events, &mut host_callback_lock.host); } self.events.clear(); } diff --git a/note_generator/src/parameters.rs b/note_generator/src/parameters.rs index 93e3f2f..f22abea 100644 --- a/note_generator/src/parameters.rs +++ b/note_generator/src/parameters.rs @@ -1,10 +1,10 @@ use std::sync::Mutex; use util::constants::{C0, NOTE_NAMES}; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte, f32_to_u14}; +use util::parameters::ParameterConversion; use util::HostCallbackLock; use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; -use util::parameters::ParameterConversion; pub struct NoteGeneratorPluginParameters { pub host: Mutex, @@ -90,7 +90,10 @@ impl NoteGeneratorPluginParameters { #[inline] fn get_pitchbend_label(&self) -> String { let semitones = self.get_u14_parameter(Parameter::PitchBend); - format!("{:.2} semitones", ((semitones as i32 * 96000 / 16384) - 48000) as f32 / 1000.) + format!( + "{:.2} semitones", + ((semitones as i32 * 96000 / 16384) - 48000) as f32 / 1000. + ) } #[inline] @@ -184,39 +187,35 @@ impl PluginParameters for NoteGeneratorPluginParameters { } Err(_) => false, }, - Parameter::Velocity | Parameter::NoteOffVelocity | Parameter::Pressure => { - match text.parse::() { - Ok(n) => { - if n < 128 { - self.set_byte_parameter(Parameter::Velocity, n); - true - } else { - false - } + Parameter::Velocity | Parameter::NoteOffVelocity | Parameter::Pressure => match text.parse::() { + Ok(n) => { + if n < 128 { + self.set_byte_parameter(Parameter::Velocity, n); + true + } else { + false } - Err(_) => false, } - } + Err(_) => false, + }, Parameter::Pitch => match NOTE_NAMES.iter().position(|&s| text.starts_with(s)) { None => false, - Some(position) => { - match text[NOTE_NAMES[position].len()..text.len()].parse::() { - Ok(octave) => { - if octave >= -2 && octave <= 8 { - let pitch = octave as i16 * 12 + C0 as i16 + position as i16; - if pitch < 128 { - self.set_byte_parameter(Parameter::Pitch, pitch as u8); - true - } else { - false - } + Some(position) => match text[NOTE_NAMES[position].len()..text.len()].parse::() { + Ok(octave) => { + if octave >= -2 && octave <= 8 { + let pitch = octave as i16 * 12 + C0 as i16 + position as i16; + if pitch < 128 { + self.set_byte_parameter(Parameter::Pitch, pitch as u8); + true } else { false } + } else { + false } - Err(_) => false, } - } + Err(_) => false, + }, }, Parameter::Trigger => match text.to_ascii_lowercase().as_ref() { "0" | "off" | "" => { diff --git a/note_off_delay/src/bin/note_off_host.rs b/note_off_delay/src/bin/note_off_host.rs index 1f31701..c96bb5f 100644 --- a/note_off_delay/src/bin/note_off_host.rs +++ b/note_off_delay/src/bin/note_off_host.rs @@ -4,7 +4,7 @@ use std::env; use std::path::Path; use std::sync::{Arc, Mutex}; -use vst::api::Supported ; +use vst::api::Supported; use vst::host::{Host, HostBuffer, PluginLoader}; use vst::plugin::CanDo; use vst::plugin::Plugin; @@ -49,23 +49,14 @@ fn main() { VST ID: {}\n\t\ Version: {}\n\t\ Initial Delay: {} samples", - info.name, - info.vendor, - info.presets, - info.parameters, - info.unique_id, - info.version, - info.initial_delay + info.name, info.vendor, info.presets, info.parameters, info.unique_id, info.version, info.initial_delay ); // Initialize the instance instance.init(); println!("{}", instance.can_do(CanDo::Offline) == Supported::No); - println!( - "{}", - instance.can_do(CanDo::ReceiveEvents) == Supported::Yes - ); + println!("{}", instance.can_do(CanDo::ReceiveEvents) == Supported::Yes); let mut host_buffer: HostBuffer = HostBuffer::new(2, 2); let inputs = vec![vec![0.0; 1000]; 2]; let mut outputs = vec![vec![0.0; 1000]; 2]; @@ -126,9 +117,7 @@ fn main() { let v = parameters.get_preset_data(); parameters.load_preset_data(&v); - instance.process(&mut audio_buffer); - println!("Closing instance..."); } diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index d6fe992..d390abe 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -6,10 +6,10 @@ extern crate vst; use std::cell::RefCell; use std::sync::Arc; +use vst::api::Events; use vst::buffer::{AudioBuffer, SendEventBuffer}; -use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin, PluginParameters}; use vst::event::Event; -use vst::api::Events; +use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin, PluginParameters}; use parameters::NoteOffDelayPluginParameters; use parameters::Parameter; @@ -37,7 +37,7 @@ impl Default for NoteOffDelayPlugin { parameters: Arc::new(Default::default()), sample_rate: 44100.0, current_time_in_samples: 0, - message_queue: Default::default() + message_queue: Default::default(), } } } @@ -50,12 +50,15 @@ impl NoteOffDelayPlugin { self.current_time_in_samples, &self.message_queue, self.parameters.get_max_notes(), - self.parameters.get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), - self.parameters.get_parameter(Parameter::Delay.into()) > 0.0 + self.parameters + .get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), + self.parameters.get_parameter(Parameter::Delay.into()) > 0.0, ); self.message_queue = next_message_queue; - self.send_buffer.borrow_mut().send_events(events, &mut host_callback_lock.host); + self.send_buffer + .borrow_mut() + .send_events(events, &mut host_callback_lock.host); } } @@ -70,10 +73,7 @@ impl NoteOffDelayPlugin { fn debug_events_in(&mut self, events: &Events) { for e in events.events() { - DebugSocket::send( - &*(format_event(&e) - + &*format!(" current time={}", self.current_time_in_samples)), - ); + DebugSocket::send(&*(format_event(&e) + &*format!(" current time={}", self.current_time_in_samples))); } } @@ -161,14 +161,17 @@ impl Plugin for NoteOffDelayPlugin { fn process_events(&mut self, events: &Events) { self.debug_events_in(events); - let note_off_delay = self.seconds_to_samples(self.parameters - .get_exponential_scale_parameter(Parameter::Delay, 10., 20.)); + let note_off_delay = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter( + Parameter::Delay, + 10., + 20., + )); for event in events.events() { let midi_event = if let Event::Midi(midi_event) = event { midi_event } else { - continue + continue; }; // TODO: minimum time, maximum time ( with delay ) @@ -177,7 +180,8 @@ impl Plugin for NoteOffDelayPlugin { MidiMessageType::NoteOffMessage(_) => { self.message_queue.insert_message( midi_event.data, - midi_event.delta_frames as usize + self.current_time_in_samples, MessageReason::Live, + midi_event.delta_frames as usize + self.current_time_in_samples, + MessageReason::Live, ); if note_off_delay > 0 { @@ -188,7 +192,6 @@ impl Plugin for NoteOffDelayPlugin { MessageReason::Delayed, ); } - } MidiMessageType::Unsupported => { continue; @@ -196,7 +199,8 @@ impl Plugin for NoteOffDelayPlugin { _ => { self.message_queue.insert_message( midi_event.data, - midi_event.delta_frames as usize + self.current_time_in_samples, MessageReason::Live, + midi_event.delta_frames as usize + self.current_time_in_samples, + MessageReason::Live, ); } }; diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index f77bb64..7a8138a 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -1,12 +1,12 @@ use std::sync::Mutex; -use vst::plugin::HostCallback ; +use vst::plugin::HostCallback; use vst::util::ParameterTransfer; use util::debug::DebugSocket; -use util::parameter_value_conversion::{f32_to_byte, f32_to_bool}; -use util::{HostCallbackLock, duration_display}; +use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; use util::parameters::ParameterConversion; +use util::{duration_display, HostCallbackLock}; const PARAMETER_COUNT: usize = 3; @@ -19,7 +19,7 @@ pub struct NoteOffDelayPluginParameters { pub enum Parameter { Delay = 0, MaxNotes, - MaxNotesAppliesToDelayedNotesOnly + MaxNotesAppliesToDelayedNotesOnly, } impl From for Parameter { @@ -33,14 +33,12 @@ impl From for Parameter { } } - impl Into for Parameter { fn into(self) -> i32 { self as i32 } } - impl ParameterConversion for NoteOffDelayPluginParameters { fn get_parameter_transfer(&self) -> &ParameterTransfer { &self.transfer @@ -51,7 +49,6 @@ impl ParameterConversion for NoteOffDelayPluginParameters { } } - impl NoteOffDelayPluginParameters { pub fn new(host: HostCallback) -> Self { NoteOffDelayPluginParameters { @@ -69,7 +66,6 @@ impl NoteOffDelayPluginParameters { } } - impl Default for NoteOffDelayPluginParameters { fn default() -> Self { NoteOffDelayPluginParameters { @@ -79,7 +75,6 @@ impl Default for NoteOffDelayPluginParameters { } } - impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn get_parameter_text(&self, index: i32) -> String { match index.into() { @@ -105,7 +100,8 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { "On" } else { "Off" - }.to_string() + } + .to_string() } } } @@ -140,8 +136,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { } } Parameter::MaxNotesAppliesToDelayedNotesOnly => { - self.set_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly, - f32_to_bool(value)) + self.set_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly, f32_to_bool(value)) } } } diff --git a/util/src/absolute_time_midi_message.rs b/util/src/absolute_time_midi_message.rs index 36795ff..1a6fe38 100644 --- a/util/src/absolute_time_midi_message.rs +++ b/util/src/absolute_time_midi_message.rs @@ -1,13 +1,13 @@ use core::clone::Clone; -use core::fmt::Display; use core::fmt; +use core::fmt::Display; //use core::option::Option::{Some, None}; //use vst::event::{Event, MidiEvent}; use super::raw_message::RawMessage; -use vst::event::MidiEvent; -use std::cmp::max; use crate::delayed_message_consumer::MessageReason; +use std::cmp::max; +use vst::event::MidiEvent; #[derive(Copy)] pub struct AbsoluteTimeMidiMessage { @@ -27,7 +27,7 @@ impl AbsoluteTimeMidiMessage { note_length: None, note_offset: None, detune: 0, - note_off_velocity: 0 + note_off_velocity: 0, } } @@ -42,14 +42,13 @@ impl AbsoluteTimeMidiMessage { } } - impl Clone for AbsoluteTimeMidiMessage { fn clone(&self) -> Self { AbsoluteTimeMidiMessage { id: self.id, data: self.data, play_time_in_samples: self.play_time_in_samples, - reason: self.reason + reason: self.reason, } } @@ -64,8 +63,8 @@ impl Clone for AbsoluteTimeMidiMessage { impl Display for AbsoluteTimeMidiMessage { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&*format!( - "{} {} [{:#04X} {:#04X} {:#04X}]", self.play_time_in_samples, - self.id, self.data[0], self.data[1], self.data[2] + "{} {} [{:#04X} {:#04X} {:#04X}]", + self.play_time_in_samples, self.id, self.data[0], self.data[1], self.data[2] )) } } diff --git a/util/src/absolute_time_midi_message_vector.rs b/util/src/absolute_time_midi_message_vector.rs index f3998b3..44aefb7 100644 --- a/util/src/absolute_time_midi_message_vector.rs +++ b/util/src/absolute_time_midi_message_vector.rs @@ -30,7 +30,6 @@ impl DerefMut for AbsoluteTimeMidiMessageVector { } } - // plot twist: this vector is for all events, not just note on/note off. // would still be OK to add that metadata to any event after all ? no // as a weak reference should be ok @@ -42,15 +41,11 @@ impl AbsoluteTimeMidiMessageVector { pub fn insert_message(&mut self, data: [u8; 3], play_time_in_samples: usize, reason: MessageReason) { // we generate unique identifier per event. this is in order to match note on/note off pairs let channel_pitch_lookup = match MidiMessageType::from(RawMessage::from(data)) { - MidiMessageType::NoteOffMessage(midi_message) => { - Some((midi_message.channel, midi_message.pitch)) - } - _ => { - None - } + MidiMessageType::NoteOffMessage(midi_message) => Some((midi_message.channel, midi_message.pitch)), + _ => None, }; - let mut last_note_on_match = None ; + let mut last_note_on_match = None; let insert_point = self.iter().position(|message_at_position| { if let Some((channel, pitch)) = channel_pitch_lookup { @@ -77,7 +72,7 @@ impl AbsoluteTimeMidiMessageVector { data: data.into(), id, play_time_in_samples, - reason + reason, }; DebugSocket::send(&*format!("Inserting {}", message)); @@ -89,17 +84,13 @@ impl AbsoluteTimeMidiMessageVector { } pub fn ordered_insert(&mut self, message: AbsoluteTimeMidiMessage) { - let position = self.iter().position(|message_at_position| { - message.play_time_in_samples < message_at_position.play_time_in_samples - }); + let position = self + .iter() + .position(|message_at_position| message.play_time_in_samples < message_at_position.play_time_in_samples); match position { - Some(position) => { - self.insert(position, message) - } - None => { - self.push(message) - } + Some(position) => self.insert(position, message), + None => self.push(message), } } } diff --git a/util/src/constants.rs b/util/src/constants.rs index 11f72b5..6a2e63e 100644 --- a/util/src/constants.rs +++ b/util/src/constants.rs @@ -8,6 +8,4 @@ pub const PRESSURE: u8 = 0xD0; pub const PITCHBEND: u8 = 0xE0; pub const C0: i8 = 0x18; -pub static NOTE_NAMES: &[&str; 12] = &[ - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", -]; +pub static NOTE_NAMES: &[&str; 12] = &["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; diff --git a/util/src/debug.rs b/util/src/debug.rs index 2411873..0b2aa31 100644 --- a/util/src/debug.rs +++ b/util/src/debug.rs @@ -25,7 +25,7 @@ impl DebugSocket { pub fn send(debug_str: &str) { if debug_str.is_empty() { - return + return; } let debug_string = debug_str.to_owned() + "\n"; unsafe { diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index 9f30d02..8175ca1 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -1,14 +1,13 @@ use core::iter::Iterator; -use std::collections::HashMap; use std::collections::hash_map::RandomState; -use std::ops::{DerefMut, Deref}; +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; use vst::event::MidiEvent; use super::absolute_time_midi_message::AbsoluteTimeMidiMessage; use super::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; -use super::midi_message_type::MidiMessageType; use super::messages::NoteOff; - +use super::midi_message_type::MidiMessageType; #[derive(Hash, Clone, Copy, PartialEq, Eq)] struct PlayingNoteIndex { @@ -19,9 +18,9 @@ struct PlayingNoteIndex { #[derive(Clone, Copy, PartialEq)] pub enum MessageReason { Live, - Delayed, // the same event will exist live and delayed + Delayed, // the same event will exist live and delayed MaxNotes, - Retrigger + Retrigger, } #[derive(Default)] @@ -43,27 +42,29 @@ impl DerefMut for PlayingNotes { impl PlayingNotes { fn oldest_playing_note(&self, delayed_only: bool) -> Option<&AbsoluteTimeMidiMessage> { - self.iter().fold( - None, |prev, (_, message)| { - if prev.is_none() - || (!(delayed_only && message.reason == MessageReason::Live) && prev.unwrap().id > message.id) { - Some(message) - } else { - prev - } + self.iter().fold(None, |prev, (_, message)| { + if prev.is_none() + || (!(delayed_only && message.reason == MessageReason::Live) && prev.unwrap().id > message.id) + { + Some(message) + } else { + prev } - ) + }) } } - -pub fn process_scheduled_events(samples: usize, current_time_in_samples: usize, - messages: &AbsoluteTimeMidiMessageVector, max_notes: u8, - apply_max_notes_to_delayed_notes_only: bool, delay_is_active: bool +pub fn process_scheduled_events( + samples: usize, + current_time_in_samples: usize, + messages: &AbsoluteTimeMidiMessageVector, + max_notes: u8, + apply_max_notes_to_delayed_notes_only: bool, + delay_is_active: bool, ) -> (AbsoluteTimeMidiMessageVector, Vec) { let mut playing_notes: PlayingNotes = PlayingNotes::default(); let mut queued_messages = AbsoluteTimeMidiMessageVector::default(); - let mut notes_on_to_requeue : HashMap = HashMap::new(); + let mut notes_on_to_requeue: HashMap = HashMap::new(); let mut events: Vec = vec![]; let mut add_event = |event: AbsoluteTimeMidiMessage, playing_notes: &mut PlayingNotes| { @@ -75,34 +76,45 @@ pub fn process_scheduled_events(samples: usize, current_time_in_samples: usize, // back in the scheduled queue if event.play_time_in_samples >= current_time_in_samples { if let MidiMessageType::NoteOffMessage(_) = MidiMessageType::from(event) { - let note_on = notes_on_to_requeue.get_mut( & event.id); - if note_on.is_none() { return } // no such note running, skip + let note_on = notes_on_to_requeue.get_mut(&event.id); + if note_on.is_none() { + return; + } // no such note running, skip let note_on = note_on.unwrap(); if event.reason == MessageReason::Live && delay_is_active { // mark the note on as delayed from now on, but don't sent the note off note_on.reason = MessageReason::Delayed; - return + return; } - if apply_max_notes_to_delayed_notes_only && - event.reason == MessageReason::MaxNotes && - note_on.reason == MessageReason::Live { + if apply_max_notes_to_delayed_notes_only + && event.reason == MessageReason::MaxNotes + && note_on.reason == MessageReason::Live + { // should be redundant, as MaxNotes messages are not generated for Live notes. // keeping the logic to facilitate potential refactoring - return + return; } // stop this note, don't requeue - playing_notes.remove(&PlayingNoteIndex { pitch: event.get_pitch(), channel: event.get_channel() }); + playing_notes.remove(&PlayingNoteIndex { + pitch: event.get_pitch(), + channel: event.get_channel(), + }); notes_on_to_requeue.remove(&event.id); } events.push(event.new_midi_event(current_time_in_samples)); } if let MidiMessageType::NoteOnMessage(_) = MidiMessageType::from(event) { - playing_notes.insert(PlayingNoteIndex { pitch: event.get_pitch(), channel: event.get_channel() }, - event); + playing_notes.insert( + PlayingNoteIndex { + pitch: event.get_pitch(), + channel: event.get_channel(), + }, + event, + ); notes_on_to_requeue.insert(event.id, event); } } else { @@ -114,7 +126,9 @@ pub fn process_scheduled_events(samples: usize, current_time_in_samples: usize, if message.play_time_in_samples < current_time_in_samples { match MidiMessageType::from(message) { MidiMessageType::NoteOnMessage(_) => {} - _ => { panic!("Only pending note on are expected to be found in the past") } + _ => { + panic!("Only pending note on are expected to be found in the past") + } } }; @@ -122,39 +136,50 @@ pub fn process_scheduled_events(samples: usize, current_time_in_samples: usize, MidiMessageType::NoteOnMessage(note_on) => { // if still playing : generate note off at current sample, put note on with // delta + 1 in the queue - let index = PlayingNoteIndex { channel: note_on.channel, pitch: note_on.pitch }; + let index = PlayingNoteIndex { + channel: note_on.channel, + pitch: note_on.pitch, + }; if let Some(already_playing_note) = playing_notes.get(&index) { // we were still playing that note. generate a note off first. - add_event(AbsoluteTimeMidiMessage { - data: NoteOff { - channel: note_on.channel, - pitch: note_on.pitch, - velocity: 0, - }.into(), - id: already_playing_note.id, - reason: MessageReason::Retrigger, - play_time_in_samples: message.play_time_in_samples, - }, &mut playing_notes); + add_event( + AbsoluteTimeMidiMessage { + data: NoteOff { + channel: note_on.channel, + pitch: note_on.pitch, + velocity: 0, + } + .into(), + id: already_playing_note.id, + reason: MessageReason::Retrigger, + play_time_in_samples: message.play_time_in_samples, + }, + &mut playing_notes, + ); // move the note on to the next sample or the daw might be confused message.play_time_in_samples += 1; } else if max_notes > 0 && playing_notes.len() >= max_notes as usize { - if let Some(oldest_playing_note) - = playing_notes.oldest_playing_note(apply_max_notes_to_delayed_notes_only) { - + if let Some(oldest_playing_note) = + playing_notes.oldest_playing_note(apply_max_notes_to_delayed_notes_only) + { let oldest_playing_note = *oldest_playing_note; // drop the borrow - add_event(AbsoluteTimeMidiMessage { - data: NoteOff { - channel: oldest_playing_note.get_channel(), - pitch: oldest_playing_note.get_pitch(), - velocity: 0, - }.into(), - id: oldest_playing_note.id, - play_time_in_samples: message.play_time_in_samples, - reason: MessageReason::MaxNotes, - }, &mut playing_notes); + add_event( + AbsoluteTimeMidiMessage { + data: NoteOff { + channel: oldest_playing_note.get_channel(), + pitch: oldest_playing_note.get_pitch(), + velocity: 0, + } + .into(), + id: oldest_playing_note.id, + play_time_in_samples: message.play_time_in_samples, + reason: MessageReason::MaxNotes, + }, + &mut playing_notes, + ); }; } @@ -162,7 +187,10 @@ pub fn process_scheduled_events(samples: usize, current_time_in_samples: usize, } MidiMessageType::NoteOffMessage(note_off) => { - let playing_note = PlayingNoteIndex { channel: note_off.channel, pitch: note_off.pitch }; + let playing_note = PlayingNoteIndex { + channel: note_off.channel, + pitch: note_off.pitch, + }; match playing_notes.get(&playing_note) { Some(currently_playing_note) => { if currently_playing_note.id == message.id { diff --git a/util/src/ipc_payload.rs b/util/src/ipc_payload.rs index 904e719..aabfc52 100644 --- a/util/src/ipc_payload.rs +++ b/util/src/ipc_payload.rs @@ -1,9 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::midi_message_with_delta::MidiMessageWithDelta; -use ipc_channel::ipc::IpcSender; use crate::system::Uuid; - +use ipc_channel::ipc::IpcSender; #[derive(Debug, Serialize, Deserialize)] pub struct PatternPayload { @@ -11,19 +10,17 @@ pub struct PatternPayload { pub messages: Vec, } - #[derive(Debug, Serialize, Deserialize)] pub enum IPCCommand { PatternPayload(PatternPayload), // stop is only used locally, but send over an IPC channel so the worker can listen both on the remote IPC // for new patterns, and on local IPC for stopping the worker Stop(IpcSender<()>, Uuid), - Ping(IpcSender<()>) + Ping(IpcSender<()>), } - #[derive(Serialize, Deserialize)] pub enum BootstrapPayload { Channel(IpcSender), - Timeout + Timeout, } diff --git a/util/src/lib.rs b/util/src/lib.rs index a5814c8..efb430d 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -3,21 +3,21 @@ extern crate global_counter; use vst::event::MidiEvent; use vst::plugin::HostCallback; -pub mod constants; -pub mod debug; -pub mod parameter_value_conversion; -pub mod parameters; -pub mod messages; pub mod absolute_time_midi_message; -pub mod midi_message_type; -pub mod raw_message; pub mod absolute_time_midi_message_vector; +pub mod constants; +pub mod debug; pub mod delayed_message_consumer; -pub mod transmute_buffer; +pub mod ipc_payload; pub mod logging; +pub mod messages; +pub mod midi_message_type; pub mod midi_message_with_delta; -pub mod ipc_payload; +pub mod parameter_value_conversion; +pub mod parameters; +pub mod raw_message; pub mod system; +pub mod transmute_buffer; #[derive(Default)] pub struct HostCallbackLock { diff --git a/util/src/logging.rs b/util/src/logging.rs index dbf9da7..f6121db 100644 --- a/util/src/logging.rs +++ b/util/src/logging.rs @@ -1,23 +1,27 @@ -#[cfg(not(feature="enable_logging"))] +#[cfg(not(feature = "enable_logging"))] pub fn logging_setup() {} - -#[cfg(feature="enable_logging")] +#[cfg(feature = "enable_logging")] pub fn logging_setup() { use log::info; use simplelog::*; use std::fs::OpenOptions; - unsafe { if LOGGING_SETUP { return } } + unsafe { + if LOGGING_SETUP { + return; + } + } if let Ok(file) = OpenOptions::new().append(true).create(true).open("/tmp/plugin.log") { let mut config = ConfigBuilder::new(); config.set_time_format("%+".to_string()); - WriteLogger::init( - LevelFilter::Info, config.build(), file, - ).unwrap(); - info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $ - .compiler, $.timestamp)) + WriteLogger::init(LevelFilter::Info, config.build(), file).unwrap(); + info!( + "{}", + build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $ + .compiler, $.timestamp) + ) } log_panics::init(); diff --git a/util/src/messages.rs b/util/src/messages.rs index 440666f..96806af 100644 --- a/util/src/messages.rs +++ b/util/src/messages.rs @@ -1,12 +1,12 @@ #[allow(unused_imports)] -use log::{info,error}; +use log::{error, info}; use vst::event::Event::Midi; use vst::event::{Event, MidiEvent}; -use super::constants::{NOTE_ON, NOTE_OFF, PRESSURE, PITCHBEND}; +use super::constants::{NOTE_OFF, NOTE_ON, PITCHBEND, PRESSURE}; use super::raw_message::RawMessage; -use crate::constants::{TIMBRECC, AFTERTOUCH}; +use crate::constants::{AFTERTOUCH, TIMBRECC}; pub fn format_midi_event(e: &MidiEvent) -> String { format!( @@ -46,13 +46,13 @@ pub fn format_event(e: &Event) -> String { */ pub trait ChannelMessage { - fn get_channel(&self) -> u8 ; + fn get_channel(&self) -> u8; } pub struct NoteOn { pub channel: u8, pub pitch: u8, - pub velocity: u8 + pub velocity: u8, } impl Into for NoteOn { @@ -71,24 +71,22 @@ impl NoteMessage for NoteOn { } } - impl From for NoteOn { fn from(data: RawMessage) -> Self { NoteOn { channel: data[0] & 0x0F, pitch: data[1], - velocity: data[2] + velocity: data[2], } } } - impl From for NoteOff { fn from(data: RawMessage) -> Self { NoteOff { channel: data[0] & 0x0F, pitch: data[1], - velocity: data[2] + velocity: data[2], } } } @@ -99,23 +97,26 @@ impl ChannelMessage for NoteOn { } } -pub trait NoteMessage where Self: ChannelMessage { +pub trait NoteMessage +where + Self: ChannelMessage, +{ fn get_pitch(&self) -> u8; - fn get_velocity(&self) -> u8 ; + fn get_velocity(&self) -> u8; } pub struct NoteOff { pub channel: u8, pub pitch: u8, - pub velocity: u8 + pub velocity: u8, } impl From for NoteOff { fn from(m: NoteOn) -> Self { - NoteOff{ + NoteOff { channel: m.channel, pitch: m.pitch, - velocity: 0 + velocity: 0, } } } @@ -144,7 +145,7 @@ impl NoteMessage for NoteOff { pub struct Pressure { pub channel: u8, - pub value: u8 + pub value: u8, } impl Into for Pressure { @@ -157,7 +158,7 @@ impl From for Pressure { fn from(data: RawMessage) -> Self { Pressure { channel: data[0] & 0x0F, - value: data[1] + value: data[1], } } } @@ -170,7 +171,7 @@ impl ChannelMessage for Pressure { pub struct PitchBend { pub channel: u8, - pub millisemitones: i32 + pub millisemitones: i32, } impl ChannelMessage for PitchBend { @@ -192,24 +193,23 @@ impl Into for PitchBend { impl From for PitchBend { fn from(data: RawMessage) -> Self { - let lsb : i32 = data[1] as i32; - let msb : i32 = data[2] as i32; + let lsb: i32 = data[1] as i32; + let msb: i32 = data[2] as i32; let value = lsb + (msb << 7); let millisemitones = (value * 96000 / 16384) - 48000; PitchBend { channel: data[0] & 0x0F, - millisemitones + millisemitones, } } } - #[derive(Debug)] pub struct AfterTouch { pub channel: u8, pub pitch: u8, - pub value: u8 + pub value: u8, } impl ChannelMessage for AfterTouch { @@ -229,7 +229,7 @@ impl From for AfterTouch { AfterTouch { channel: data[0] & 0x0F, pitch: data[1], - value: data[2] + value: data[2], } } } @@ -237,7 +237,7 @@ impl From for AfterTouch { pub struct CC { pub channel: u8, pub cc: u8, - pub value: u8 + pub value: u8, } impl Into for CC { @@ -251,7 +251,7 @@ impl From for CC { CC { channel: data[0] & 0x0F, cc: data[1], - value: data[2] + value: data[2], } } } @@ -276,7 +276,6 @@ impl From for GenericChannelMessage { } } - impl From<&[u8; 3]> for GenericChannelMessage { fn from(data: &[u8; 3]) -> Self { GenericChannelMessage(RawMessage::from(*data)) @@ -285,7 +284,7 @@ impl From<&[u8; 3]> for GenericChannelMessage { pub struct Timbre { pub channel: u8, - pub value: u8 + pub value: u8, } impl Into for Timbre { @@ -293,7 +292,8 @@ impl Into for Timbre { CC { channel: self.channel, cc: TIMBRECC, - value: self.value - }.into() + value: self.value, + } + .into() } } diff --git a/util/src/midi_message_type.rs b/util/src/midi_message_type.rs index 129b8bb..b2a3ddf 100644 --- a/util/src/midi_message_type.rs +++ b/util/src/midi_message_type.rs @@ -1,9 +1,8 @@ -use core::convert::From; -use super::raw_message::RawMessage; use super::absolute_time_midi_message::AbsoluteTimeMidiMessage; -use super::messages::{NoteOn, NoteOff, CC, Pressure, PitchBend, GenericChannelMessage}; +use super::messages::{GenericChannelMessage, NoteOff, NoteOn, PitchBend, Pressure, CC}; +use super::raw_message::RawMessage; use crate::messages::AfterTouch; - +use core::convert::From; pub enum MidiMessageType { NoteOnMessage(NoteOn), @@ -13,7 +12,7 @@ pub enum MidiMessageType { PitchBendMessage(PitchBend), AfterTouchMessage(AfterTouch), UnsupportedChannelMessage(GenericChannelMessage), - Unsupported + Unsupported, } impl MidiMessageType { @@ -21,20 +20,19 @@ impl MidiMessageType { let (channel, pitch) = match self { MidiMessageType::NoteOnMessage(m) => (m.channel, m.pitch), MidiMessageType::NoteOffMessage(m) => (m.channel, m.pitch), - _ => return false + _ => return false, }; let (channel2, pitch2) = match other { MidiMessageType::NoteOnMessage(m) => (m.channel, m.pitch), MidiMessageType::NoteOffMessage(m) => (m.channel, m.pitch), - _ => return false + _ => return false, }; channel == channel2 && pitch == pitch2 } } - impl From for MidiMessageType { fn from(data: RawMessage) -> Self { match data[0] & 0xF0 { @@ -44,12 +42,11 @@ impl From for MidiMessageType { 0xD0 => MidiMessageType::PressureMessage(Pressure::from(data)), 0xE0 => MidiMessageType::PitchBendMessage(PitchBend::from(data)), 0xA0 | 0xC0 | 0xF0 => MidiMessageType::UnsupportedChannelMessage(GenericChannelMessage::from(data)), - _ => MidiMessageType::Unsupported + _ => MidiMessageType::Unsupported, } } } - impl From<&[u8; 3]> for MidiMessageType { fn from(data: &[u8; 3]) -> Self { Self::from(RawMessage::from(*data)) diff --git a/util/src/midi_message_with_delta.rs b/util/src/midi_message_with_delta.rs index 7f22d6e..0ad24ce 100644 --- a/util/src/midi_message_with_delta.rs +++ b/util/src/midi_message_with_delta.rs @@ -1,8 +1,7 @@ -use serde::{Serialize, Deserialize}; -use vst::event::MidiEvent; use crate::raw_message::RawMessage; +use serde::{Deserialize, Serialize}; use vst::buffer::PlaceholderEvent; - +use vst::event::MidiEvent; #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct MidiMessageWithDelta { @@ -10,7 +9,6 @@ pub struct MidiMessageWithDelta { pub data: RawMessage, } - impl vst::buffer::WriteIntoPlaceholder for MidiMessageWithDelta { fn write_into(&self, out: &mut PlaceholderEvent) { MidiEvent { @@ -20,8 +18,9 @@ impl vst::buffer::WriteIntoPlaceholder for MidiMessageWithDelta { note_length: None, note_offset: None, detune: 0, - note_off_velocity: 0 - }.write_into(out) + note_off_velocity: 0, + } + .write_into(out) } } diff --git a/util/src/parameter_value_conversion.rs b/util/src/parameter_value_conversion.rs index cb0c7f0..dc4f13f 100644 --- a/util/src/parameter_value_conversion.rs +++ b/util/src/parameter_value_conversion.rs @@ -33,7 +33,6 @@ pub fn f32_to_u14(value: f32) -> u16 { (value * (0x3FFF_usize) as f32) as u16 } - // TODO better try to find a type that fits in 32 bits and store it as binary into the f32, // disregarding what f32 is suppose to contain diff --git a/util/src/parameters.rs b/util/src/parameters.rs index 84a0709..f49519f 100644 --- a/util/src/parameters.rs +++ b/util/src/parameters.rs @@ -1,12 +1,13 @@ use vst::plugin::PluginParameters; use vst::util::ParameterTransfer; -use super::parameter_value_conversion::{f32_to_byte, byte_to_f32, f32_to_bool, bool_to_f32, u14_to_f32, f32_to_u14}; +use super::parameter_value_conversion::{bool_to_f32, byte_to_f32, f32_to_bool, f32_to_byte, f32_to_u14, u14_to_f32}; // TODO can Parameter implement just from/into i32, and provide a default implementation for usize ? pub trait ParameterConversion - where ParameterType: Into + From, - Self: PluginParameters +where + ParameterType: Into + From, + Self: PluginParameters, { #[inline] fn get_byte_parameter(&self, index: ParameterType) -> u8 { @@ -58,19 +59,22 @@ pub trait ParameterConversion // self.get_parameter_transfer().get_parameter(index as usize) // } - fn get_parameter_transfer(&self) -> &ParameterTransfer ; + fn get_parameter_transfer(&self) -> &ParameterTransfer; - - fn get_parameter_count() -> usize ; + fn get_parameter_count() -> usize; fn serialize_state(&self) -> Vec { (0..Self::get_parameter_count()) - .map(|i | self.get_byte_parameter((i as i32).into())) + .map(|i| self.get_byte_parameter((i as i32).into())) .collect() } fn deserialize_state(&self, data: &[u8]) { - for (i, item) in data.iter().enumerate().take_while(|(i,_)| *i < Self::get_parameter_count()) { + for (i, item) in data + .iter() + .enumerate() + .take_while(|(i, _)| *i < Self::get_parameter_count()) + { self.set_byte_parameter((i as i32).into(), *item); } } diff --git a/util/src/raw_message.rs b/util/src/raw_message.rs index 15011cd..a9c84f2 100644 --- a/util/src/raw_message.rs +++ b/util/src/raw_message.rs @@ -1,14 +1,13 @@ +use super::messages::ChannelMessage; +use crate::constants::PRESSURE; use core::clone::Clone; use core::convert::{From, Into}; use core::ops::Index; -use serde::{Serialize, Deserialize}; -use super::messages::ChannelMessage; -use crate::constants::PRESSURE; +use serde::{Deserialize, Serialize}; #[derive(Copy, Debug, Serialize, Deserialize)] pub struct RawMessage([u8; 3]); - impl RawMessage { pub fn get_bytes(&self) -> &[u8] { // RawMessage is 3 bytes long to keep moving around a known-size message, but some actually are 2 bytes long @@ -17,7 +16,6 @@ impl RawMessage { } else { &self.0 } - } } @@ -33,19 +31,18 @@ impl Clone for RawMessage { } } -impl From<[u8;3]> for RawMessage { +impl From<[u8; 3]> for RawMessage { fn from(e: [u8; 3]) -> Self { RawMessage(e) } } -impl Into<[u8;3]> for RawMessage { +impl Into<[u8; 3]> for RawMessage { fn into(self) -> [u8; 3] { self.0 } } - impl Index for RawMessage { type Output = u8; diff --git a/util/src/system.rs b/util/src/system.rs index 128b304..0b41642 100644 --- a/util/src/system.rs +++ b/util/src/system.rs @@ -1,6 +1,6 @@ -use std::fmt::{Debug, Display}; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::fmt; +use std::fmt::{Debug, Display}; // generate unique IDs on top of Uuid, which does not implement Serialize/Deserialize @@ -10,7 +10,7 @@ pub struct Uuid(u128); impl Default for Uuid { fn default() -> Self { Self { - 0: uuid::Uuid::default().to_u128_le() + 0: uuid::Uuid::default().to_u128_le(), } } } @@ -18,7 +18,7 @@ impl Default for Uuid { impl Uuid { pub fn new_v4() -> Self { Self { - 0: uuid::Uuid::new_v4().to_u128_le() + 0: uuid::Uuid::new_v4().to_u128_le(), } } } diff --git a/util/src/transmute_buffer.rs b/util/src/transmute_buffer.rs index 3e4552b..62ba223 100644 --- a/util/src/transmute_buffer.rs +++ b/util/src/transmute_buffer.rs @@ -1,22 +1,22 @@ pub fn transmute_raw_buffer_mut(buffer: &mut [F]) -> &mut [T] { - use std::slice; use std::mem::size_of; + use std::slice; unsafe { slice::from_raw_parts_mut( buffer.as_mut_ptr() as *mut T, - buffer.len() * size_of::() / size_of::() + buffer.len() * size_of::() / size_of::(), ) } } -pub fn transmute_raw_buffer(buffer: & [F]) -> &[T] { - use std::slice; +pub fn transmute_raw_buffer(buffer: &[F]) -> &[T] { use std::mem::size_of; + use std::slice; unsafe { slice::from_raw_parts_mut( buffer.as_ptr() as *mut T, - buffer.len() * size_of::() / size_of::() + buffer.len() * size_of::() / size_of::(), ) } } From ba10d329c6532d55042f52c891a3958282032cbb Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 14 Jan 2021 20:53:57 +0100 Subject: [PATCH 27/47] cleanup comment --- arpegiator/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 8979e96..299c353 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -457,9 +457,6 @@ impl Plugin for ArpegiatorPlugin { .into(), ), Expression::PitchBend => { - // TODO should change the pitch as is, meaning it's the pitchbend is just added to - // the result, independently from the notes we're supposed to match - // the result should be: target note + pattern pitchbend Some( PitchBend { channel: pattern.channel, From 44edea129ffbb03699dab2dd9938b4e683051b45 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 14 Jan 2021 23:26:47 +0100 Subject: [PATCH 28/47] implement legato --- arpegiator/src/lib.rs | 43 ++++++++++++++++++++++++++++++++---- arpegiator/src/parameters.rs | 2 +- util/src/raw_message.rs | 31 ++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 299c353..c887861 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -4,6 +4,8 @@ use { std::mem::take, }; +use std::cmp::Ordering; +use std::collections::HashSet; use std::os::raw::c_void; use std::sync::Arc; @@ -35,9 +37,9 @@ use mach::mach_time::mach_absolute_time; use crate::midi_messages::change::SourceChange; use crate::midi_messages::pattern_device::{PatternDevice, PatternDeviceChange}; use crate::midi_messages::timed_event::TimedEvent; -use crate::parameters::{ArpegiatorParameters, PitchBendValues, PARAMETER_COUNT}; -use std::cmp::Ordering; +use crate::parameters::{ArpegiatorParameters, PitchBendValues, PARAMETER_COUNT, Parameter}; use util::system::Uuid; +use util::parameters::ParameterConversion; #[cfg(not(feature = "midi_hack_transmission"))] mod system; @@ -337,7 +339,7 @@ impl Plugin for ArpegiatorPlugin { let notes_device_in = &mut self.notes_device_in; #[cfg(feature = "midi_hack_transmission")] - let (pattern_messages, notes): (Vec, Vec) = { + let (mut pattern_messages, notes): (Vec, Vec) = { let (mut patterns, mut notes): (Vec, Vec) = self.events.drain(..).partition(|item| item.data[0] < 0x80); notes.sort_by_key(|x| x.delta_frames); @@ -352,6 +354,37 @@ impl Plugin for ArpegiatorPlugin { (patterns, notes) }; + if self.parameters.get_bool_parameter(Parameter::PatternLegato) { + let mut groups = vec![]; + for (_, pattern_group) in &pattern_messages.iter().group_by(|x| x.delta_frames) { + let mut legato_patterns = HashSet::new(); + let pattern_group = pattern_group.collect_vec(); + let notes_off = pattern_group.iter().filter( + |x| x.data.get_bytes()[0] & 0xF0 == 0x80 + ).map(|x| [x.data.get_bytes()[0] & 0x0F, x.data.get_bytes()[1]]).collect_vec(); + + for pattern in pattern_group.iter().filter(|pattern| pattern.data.get_bytes()[0] & 0xF0 == 0x90) { + let channel_pitch = [pattern.data.get_bytes()[0] & 0x0F, pattern.data.get_bytes()[1]]; + if notes_off.contains(&channel_pitch) { + legato_patterns.insert(channel_pitch); + } + } + let pattern_group = pattern_group.into_iter().filter(|pattern| { + let status = pattern.data.get_bytes()[0] & 0xF0; + if status == 0x80 || status == 0x90 { + let channel = pattern.data.get_bytes()[0] & 0x0F ; + let pitch = pattern.data.get_bytes()[1]; + !legato_patterns.contains(&[channel, pitch]) + } else { + true + } + }).collect_vec(); + groups.push(pattern_group) + } + + pattern_messages = groups.into_iter().flatten().copied().collect_vec(); + } + let pattern_changes = pattern_messages.into_iter().map(|message| { let change = pattern_device_in.update(message, current_time_in_samples, None); let change = pattern_device.update(change); @@ -379,7 +412,9 @@ impl Plugin for ArpegiatorPlugin { DeviceChange::AddNote { .. } => { match self.parameters.get_pitchbend() { PitchBendValues::Off => {} - PitchBendValues::DurationToReachTarget(_) => {} + PitchBendValues::DurationToReachTarget(_) => { + // sample & hold in bitwig probably does the job already + } PitchBendValues::Immediate => { // incoming note can move before other notes, so we have to recalculate pitches of // all playing notes diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index cf6a41e..9ccc059 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -139,7 +139,7 @@ impl ArpegiatorParameters { x if x <= 0. => PitchBendValues::Immediate, x if x >= 1. => PitchBendValues::Off, _ => { - let value = self.get_exponential_scale_parameter(Parameter::Pitchbend, 1., 80.); + let value = self.get_exponential_scale_parameter(Parameter::Pitchbend, 5., 80.); PitchBendValues::DurationToReachTarget(value) } } diff --git a/util/src/raw_message.rs b/util/src/raw_message.rs index a9c84f2..1eedf5a 100644 --- a/util/src/raw_message.rs +++ b/util/src/raw_message.rs @@ -4,11 +4,14 @@ use core::clone::Clone; use core::convert::{From, Into}; use core::ops::Index; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; #[derive(Copy, Debug, Serialize, Deserialize)] pub struct RawMessage([u8; 3]); + impl RawMessage { + #[inline] pub fn get_bytes(&self) -> &[u8] { // RawMessage is 3 bytes long to keep moving around a known-size message, but some actually are 2 bytes long if self.0[0] & 0xF0 == PRESSURE { @@ -50,3 +53,31 @@ impl Index for RawMessage { &self.0[index] } } + + +impl PartialOrd for RawMessage { + fn partial_cmp(&self, other: &Self) -> Option { + let cmp_0 = self.0[0].cmp(&other.0[0]); + match cmp_0 { + Ordering::Equal => { + Some(self.0[1].cmp(&other.0[1])) + } + _ => Some(cmp_0) + } + } +} + +impl PartialEq for RawMessage { + fn eq(&self, other: &Self) -> bool { + self.0[0] == other.0[0] && self.0[1] == other.0[1] + } +} + +impl Ord for RawMessage { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap() + } +} + + +impl Eq for RawMessage {} From 456fa1f7aa7f0f0acf7a0f03948ef214760eff1f Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 14 Jan 2021 23:44:25 +0100 Subject: [PATCH 29/47] also drop pitchbend when we have a legato pattern --- arpegiator/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index c887861..a2a2fff 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -40,6 +40,7 @@ use crate::midi_messages::timed_event::TimedEvent; use crate::parameters::{ArpegiatorParameters, PitchBendValues, PARAMETER_COUNT, Parameter}; use util::system::Uuid; use util::parameters::ParameterConversion; +use util::constants::PITCHBEND; #[cfg(not(feature = "midi_hack_transmission"))] mod system; @@ -371,10 +372,13 @@ impl Plugin for ArpegiatorPlugin { } let pattern_group = pattern_group.into_iter().filter(|pattern| { let status = pattern.data.get_bytes()[0] & 0xF0; + let channel = pattern.data.get_bytes()[0] & 0x0F ; + if status == 0x80 || status == 0x90 { - let channel = pattern.data.get_bytes()[0] & 0x0F ; let pitch = pattern.data.get_bytes()[1]; !legato_patterns.contains(&[channel, pitch]) + } else if status == PITCHBEND { + legato_patterns.iter().find(|channel_pitch| channel_pitch[0] == channel).is_none() } else { true } From 1b634e8e90c48d270b87c883fc9c44c8a614b018 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 17 Jan 2021 21:58:54 +0100 Subject: [PATCH 30/47] implement configurable velocity source --- arpegiator/Cargo.toml | 2 +- arpegiator/src/lib.rs | 169 ++++++++++++------ arpegiator/src/midi_messages/device.rs | 71 +++++++- arpegiator/src/midi_messages/device_out.rs | 39 ++-- arpegiator/src/midi_messages/note.rs | 6 + .../src/midi_messages/pattern_device.rs | 21 +++ arpegiator/src/parameters.rs | 110 +++++++----- util/src/parameters.rs | 9 +- util/src/raw_message.rs | 21 ++- 9 files changed, 323 insertions(+), 125 deletions(-) diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 1eb821f..0fbd98f 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -27,7 +27,7 @@ name = "arpegiator" crate-type = ["cdylib", "lib"] [features] -default = ["pressure_as_cc7", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] +default = ["pressure_as_channel_pressure", "forward_pattern_cc", "worker_debug", "midi_hack_transmission"] pressure_as_channel_pressure = [] pressure_as_aftertouch = [] diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index a2a2fff..3f800b9 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -5,7 +5,6 @@ use { }; use std::cmp::Ordering; -use std::collections::HashSet; use std::os::raw::c_void; use std::sync::Arc; @@ -40,7 +39,6 @@ use crate::midi_messages::timed_event::TimedEvent; use crate::parameters::{ArpegiatorParameters, PitchBendValues, PARAMETER_COUNT, Parameter}; use util::system::Uuid; use util::parameters::ParameterConversion; -use util::constants::PITCHBEND; #[cfg(not(feature = "midi_hack_transmission"))] mod system; @@ -55,6 +53,14 @@ extern crate vst; plugin_main!(ArpegiatorPlugin); + +struct PitchbendInProgress { + channel: u8, + increment_per_block: i32, + target: i32 +} + + pub struct ArpegiatorPlugin { events: Vec, _host: HostCallback, @@ -71,6 +77,7 @@ pub struct ArpegiatorPlugin { #[cfg(not(feature = "midi_hack_transmission"))] worker_channels: Option, resumed: bool, + pitchbend_in_progress: Vec } impl ArpegiatorPlugin { @@ -110,6 +117,7 @@ impl Default for ArpegiatorPlugin { #[cfg(not(feature = "midi_hack_transmission"))] worker_channels: None, resumed: false, + pitchbend_in_progress: vec![] } } } @@ -172,6 +180,7 @@ impl Plugin for ArpegiatorPlugin { #[cfg(not(feature = "midi_hack_transmission"))] worker_channels: None, resumed: false, + pitchbend_in_progress: vec![] } } @@ -340,11 +349,11 @@ impl Plugin for ArpegiatorPlugin { let notes_device_in = &mut self.notes_device_in; #[cfg(feature = "midi_hack_transmission")] - let (mut pattern_messages, notes): (Vec, Vec) = { + let (pattern_messages, notes): (Vec, Vec) = { let (mut patterns, mut notes): (Vec, Vec) = self.events.drain(..).partition(|item| item.data[0] < 0x80); - notes.sort_by_key(|x| x.delta_frames); - patterns.sort_by_key(|x| x.delta_frames); + notes.sort_by_key(|x| [x.delta_frames, x.data[0] as i32, x.data[1] as i32]); + patterns.sort_by_key(|x| [x.delta_frames, x.data[0] as i32, x.data[1] as i32]); let patterns = patterns .iter() .map(|event| MidiMessageWithDelta { @@ -355,53 +364,23 @@ impl Plugin for ArpegiatorPlugin { (patterns, notes) }; - if self.parameters.get_bool_parameter(Parameter::PatternLegato) { - let mut groups = vec![]; - for (_, pattern_group) in &pattern_messages.iter().group_by(|x| x.delta_frames) { - let mut legato_patterns = HashSet::new(); - let pattern_group = pattern_group.collect_vec(); - let notes_off = pattern_group.iter().filter( - |x| x.data.get_bytes()[0] & 0xF0 == 0x80 - ).map(|x| [x.data.get_bytes()[0] & 0x0F, x.data.get_bytes()[1]]).collect_vec(); - - for pattern in pattern_group.iter().filter(|pattern| pattern.data.get_bytes()[0] & 0xF0 == 0x90) { - let channel_pitch = [pattern.data.get_bytes()[0] & 0x0F, pattern.data.get_bytes()[1]]; - if notes_off.contains(&channel_pitch) { - legato_patterns.insert(channel_pitch); - } - } - let pattern_group = pattern_group.into_iter().filter(|pattern| { - let status = pattern.data.get_bytes()[0] & 0xF0; - let channel = pattern.data.get_bytes()[0] & 0x0F ; - - if status == 0x80 || status == 0x90 { - let pitch = pattern.data.get_bytes()[1]; - !legato_patterns.contains(&[channel, pitch]) - } else if status == PITCHBEND { - legato_patterns.iter().find(|channel_pitch| channel_pitch[0] == channel).is_none() - } else { - true - } - }).collect_vec(); - groups.push(pattern_group) - } + pattern_device_in.legato = self.parameters.get_bool_parameter(Parameter::PatternLegato); + let pattern_changes = pattern_device_in.process_buffer(pattern_messages, current_time_in_samples); - pattern_messages = groups.into_iter().flatten().copied().collect_vec(); - } - - let pattern_changes = pattern_messages.into_iter().map(|message| { - let change = pattern_device_in.update(message, current_time_in_samples, None); - let change = pattern_device.update(change); - SourceChange::PatternChange(change) + let pattern_changes = pattern_changes.into_iter().map(|change| { + SourceChange::PatternChange(pattern_device.update(change)) }); - let note_changes = notes.iter().map(|event| { - let midi_message_with_delta = MidiMessageWithDelta { + let notes_in_as_messages = notes.iter().map(|event| + MidiMessageWithDelta { delta_frames: event.delta_frames as u16, data: event.data.into(), - }; + } + ).collect_vec(); + + let note_changes = notes_device_in.process_buffer(notes_in_as_messages, current_time_in_samples); - let change = notes_device_in.update(midi_message_with_delta, current_time_in_samples, None); + let note_changes = note_changes.into_iter().map(|change| { SourceChange::NoteChange(change) }); @@ -414,12 +393,11 @@ impl Plugin for ArpegiatorPlugin { SourceChange::NoteChange(change) => { match change { DeviceChange::AddNote { .. } => { - match self.parameters.get_pitchbend() { + let pitch_bend_parameter_value = self.parameters.get_pitchbend(); + + match pitch_bend_parameter_value { PitchBendValues::Off => {} - PitchBendValues::DurationToReachTarget(_) => { - // sample & hold in bitwig probably does the job already - } - PitchBendValues::Immediate => { + _ => { // incoming note can move before other notes, so we have to recalculate pitches of // all playing notes for (position, note) in self @@ -444,12 +422,62 @@ impl Plugin for ArpegiatorPlugin { "Applying pitchbend to pattern {}, at position {}", pattern.id, target_pitch ); - self.device_out.update_pitch( - pattern.id, - target_pitch, - delta_frames, - current_time_in_samples, - ); + // TODO this difference actually needs to apply relative to the + // pitchbend already in place. When the output pitchbend is met, + // we stop tweaking it + let note_out = self.device_out.find_by_note_id(pattern.id); + if note_out.is_none() { continue } + let note_out = note_out.unwrap(); + let difference = note_out.difference_in_millisemitones(target_pitch); + + let increment = match pitch_bend_parameter_value { + PitchBendValues::DurationToReachTarget(duration_in_seconds) => { + let blocks_per_second = self.sample_rate as f32 / self.block_size as f32; + let blocks_to_target = (blocks_per_second * duration_in_seconds) as i32; + let increment = difference / blocks_to_target; + self.pitchbend_in_progress.push(PitchbendInProgress { + channel: note_out.channel, + increment_per_block: increment, + target: difference + }); + self.device_out.update_pitch( + pattern.id, + increment, // "increment" is innacurate we need to + // start from the pitchbend applied now + // patterns can have octave difference, but for now + // the pitchbend we'd like is the same. it always + // make sense this way as long as pattern follow notes, + // but we may want a pitchbend modulation different for + // each octave. that's for another implementation, + // let's just make sure at that point that the 'target' + // pitchbend is reachable while modulation is applied + // *after*. Means, the output device might not be the + // right place to find that reference. + + /* + so : + - notes in can have its own pitch bend, so no + - patterns in can have their own pitch bend so no + - device out can already be consolidation of pitchbends + so no + - so it seems some intermediary after device note in + would be appropriate. it is only related to device + note in anyway. + */ + delta_frames, + current_time_in_samples, + ); + } + PitchBendValues::Immediate => { + self.device_out.update_pitch( + pattern.id, + difference, + delta_frames, + current_time_in_samples, + ); + } + _ => { panic!("'off' is not possible here") } + }; } } } @@ -472,6 +500,9 @@ impl Plugin for ArpegiatorPlugin { } } DeviceChange::Ignored { .. } => {} + DeviceChange::NoteLegato { .. } => { + panic!("Legato not supported for notes in") + } } } SourceChange::PatternChange(change) => { @@ -480,7 +511,11 @@ impl Plugin for ArpegiatorPlugin { // TODO "hold notes" logic match self.notes_device_in.nth(pattern.index as usize) { None => {} - Some(note) => self.device_out.push_note_on(&pattern, ¬e, current_time_in_samples), + Some(note) => self.device_out.push_note_on( + &pattern, + ¬e, + current_time_in_samples, + self.parameters.get_velocitysource()), } } @@ -505,6 +540,14 @@ impl Plugin for ArpegiatorPlugin { ) } Expression::Pressure | Expression::AfterTouch => { + // TODO output pressure could be a combination of: + /* + - pattern pressure + - note pressure + - pattern velocity + - note velocity ( generalization of "pitchbend" note changes affect an ongoing + pattern ) + */ #[cfg(feature = "pressure_as_channel_pressure")] { Some( @@ -598,7 +641,15 @@ impl Plugin for ArpegiatorPlugin { }; self.device_out - .push_note_on(&new_pattern, note, current_time_in_samples); + .push_note_on( + &new_pattern, + note, + current_time_in_samples, + self.parameters.get_velocitysource() + ); + }, + PatternDeviceChange::Legato { old_pattern, new_pattern , .. } => { + self.device_out.legato(old_pattern.id, new_pattern.id); } PatternDeviceChange::CC { cc: _cc, time: _time } => { #[cfg(feature = "forward_pattern_cc")] { diff --git a/arpegiator/src/midi_messages/device.rs b/arpegiator/src/midi_messages/device.rs index ebe4db4..e7ba403 100644 --- a/arpegiator/src/midi_messages/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -17,6 +17,7 @@ pub struct Device { pub cc: HashMap, pub channels: [Channel; 16], pub note_index: usize, + pub legato: bool, } impl Device { @@ -31,6 +32,7 @@ impl Device { timbre: 0, }; 16], note_index: 0, + legato: false, } } @@ -79,6 +81,11 @@ pub enum DeviceChange { time: usize, cc: CC, }, + NoteLegato { + time: usize, + old_note: Note, + new_note: Note + }, Ignored { time: usize, }, @@ -93,6 +100,7 @@ impl TimedEvent for DeviceChange { DeviceChange::ReplaceNote { time, .. } => *time, DeviceChange::CCChange { time, .. } => *time, DeviceChange::Ignored { time, .. } => *time, + DeviceChange::NoteLegato { time, .. } => *time } } @@ -104,6 +112,7 @@ impl TimedEvent for DeviceChange { DeviceChange::RemoveNote { note, .. } => note.id, DeviceChange::NoteExpressionChange { note, .. } => note.id, DeviceChange::ReplaceNote { new_note: note, .. } => note.id, + DeviceChange::NoteLegato { new_note: note, .. } => note.id, DeviceChange::CCChange { .. } => 0, DeviceChange::Ignored { .. } => 0, } @@ -127,13 +136,9 @@ impl PartialEq for DeviceChange { } } + impl Device { - pub fn update( - &mut self, - midi_message: MidiMessageWithDelta, - current_time: usize, - id: Option, - ) -> DeviceChange { + pub fn push(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) -> DeviceChange { #[cfg(feature = "device_debug")] info!( "[{}] Got event: {:?} {:?} {:02X?}", @@ -275,4 +280,58 @@ impl Device { MidiMessageType::Unsupported => DeviceChange::Ignored { time }, } } + + pub fn process_buffer(&mut self, messages: Vec, current_time: usize) -> Vec { + let mut output = vec![]; + + for message in messages { + output.push(self.push(message, current_time, None)) + } + + if self.legato { + // TODO pressure modulation at velocity change is a possibility + + let mut legato_output = vec![]; + while !output.is_empty() { + let change_1 = output.remove(0); + + // remove note is sorted as being before add note + if let DeviceChange::RemoveNote { time: time_1, note: note_1 } = change_1 { + if let Some(position) = output.iter().position(|change| { + matches!(change, DeviceChange::AddNote { time: time_2, note: note_2 } if time_1 == *time_2 && note_1.pitch == note_2.pitch && note_1.channel == note_2.channel) + }) { + let add_note = output.remove(position); + + if let Some(position) = output.iter().position(|change| { + match change { + DeviceChange::NoteExpressionChange { time: time_2, expression, note: note_2 } + if time_1 == *time_2 && note_1.channel == note_2.channel => { + matches!(expression, Expression::PitchBend) + } + _ => false + } + }) { + output.remove(position); + } + + if let DeviceChange::AddNote { note: new_note, .. } = add_note { + output.push(DeviceChange::NoteLegato { time: time_1, old_note: note_1, new_note }); + } + + continue; + } + } + + if let DeviceChange::ReplaceNote { .. } = change_1 { + // ignore restarts in legato + continue; + } + + legato_output.push(change_1) + } + legato_output + } else { + output + } + } } diff --git a/arpegiator/src/midi_messages/device_out.rs b/arpegiator/src/midi_messages/device_out.rs index 71acab8..771f9fc 100644 --- a/arpegiator/src/midi_messages/device_out.rs +++ b/arpegiator/src/midi_messages/device_out.rs @@ -15,6 +15,7 @@ use crate::midi_messages::note::Note; use crate::midi_messages::pattern::Pattern; #[cfg(not(feature = "midi_hack_transmission"))] use crate::workers::main_worker::WorkerCommand; +use crate::parameters::VelocitySource; pub(crate) struct DeviceOut { device: Device, @@ -22,6 +23,12 @@ pub(crate) struct DeviceOut { } impl DeviceOut { + pub(crate) fn legato(&mut self, old_id: usize, new_id: usize) { + if let Some(note) = self.device.notes.values_mut().find(|note| note.id == old_id) { + note.id = new_id; + } + } + pub fn new(name: String) -> Self { Self { device: Device::new(name), @@ -31,7 +38,7 @@ impl DeviceOut { pub fn update(&mut self, midi_message: MidiMessageWithDelta, current_time: usize, id: Option) { self.output_queue.push(midi_message); - self.device.update(midi_message, current_time, id); + self.device.push(midi_message, current_time, id); if !self.device.notes.is_empty() { #[cfg(feature = "device_debug")] info!("Device out state after update: {:2X?}", self.device.notes) @@ -54,8 +61,12 @@ impl DeviceOut { } } - pub fn update_pitch(&mut self, note_id: usize, target_pitch: u8, delta_frames: u16, current_time: usize) { - match self.device.notes.values().find(|note| note.id == note_id) { + pub fn find_by_note_id(&self, note_id: usize) -> Option<&Note> { + self.device.notes.values().find(|note| note.id == note_id) + } + + pub fn update_pitch(&mut self, note_id: usize, increment: i32, delta_frames: u16, current_time: usize) { + match self.find_by_note_id(note_id) { None => { info!( "Cannot find note to pitchbend. Required note_id: {}. Current notes: {:02X?}", @@ -66,9 +77,9 @@ impl DeviceOut { Some(note) => { let raw_message: RawMessage = PitchBend { channel: note.channel, - millisemitones: (target_pitch as i32 - note.pitch as i32) * 1000, - } - .into(); + millisemitones: increment + }.into(); + self.update( MidiMessageWithDelta { delta_frames, @@ -78,7 +89,7 @@ impl DeviceOut { None, ); } - }; + } } pub fn push_note_off(&mut self, note_id: usize, velocity_off: u8, delta_frames: u16, current_time: usize) { @@ -108,7 +119,7 @@ impl DeviceOut { ); } - pub fn push_note_on(&mut self, pattern: &Pattern, note: &Note, current_time: usize) { + pub fn push_note_on(&mut self, pattern: &Pattern, note: &Note, current_time: usize, velocity_source: VelocitySource) { let pitch = match pattern.transpose(note.pitch) { None => { return; @@ -116,16 +127,20 @@ impl DeviceOut { Some(pitch) => pitch, }; + let velocity = match velocity_source { + VelocitySource::Pattern => pattern.velocity, + VelocitySource::Mixed(x) => (pattern.velocity as f32 * (1. - x) + note.velocity as f32 * x) as u8, + VelocitySource::Notes => note.velocity + }; + for raw_message in (ExpressiveNote { channel: pattern.channel, pitch, - velocity: pattern.velocity, + velocity, pressure: pattern.pressure, timbre: pattern.timbre, pitchbend: pattern.pitchbend, - }) - .into_rawmessages() - { + }).into_rawmessages() { self.update( MidiMessageWithDelta { delta_frames: (pattern.pressed_at - current_time) as u16, diff --git a/arpegiator/src/midi_messages/note.rs b/arpegiator/src/midi_messages/note.rs index cec455d..df5b8ca 100644 --- a/arpegiator/src/midi_messages/note.rs +++ b/arpegiator/src/midi_messages/note.rs @@ -17,6 +17,12 @@ pub struct Note { pub pitchbend: i32, // in millisemitones } +impl Note { + pub fn difference_in_millisemitones(&self, target_pitch: u8) -> i32 { + (target_pitch as i32 - self.pitch as i32) * 1000 + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct NoteIndex { pub channel: u8, diff --git a/arpegiator/src/midi_messages/pattern_device.rs b/arpegiator/src/midi_messages/pattern_device.rs index 78fc63d..2e143ad 100644 --- a/arpegiator/src/midi_messages/pattern_device.rs +++ b/arpegiator/src/midi_messages/pattern_device.rs @@ -34,6 +34,11 @@ pub enum PatternDeviceChange { old_pattern: Pattern, new_pattern: Pattern, }, + Legato { + time: usize, + old_pattern: Pattern, + new_pattern: Pattern, + }, CC { cc: CC, time: usize, @@ -69,6 +74,7 @@ impl TimedEvent for PatternDeviceChange { PatternDeviceChange::ReplacePattern { time, .. } => *time, PatternDeviceChange::None { time, .. } => *time, PatternDeviceChange::CC { time, .. } => *time, + PatternDeviceChange::Legato { time, .. } => *time } } @@ -82,6 +88,7 @@ impl TimedEvent for PatternDeviceChange { } => pattern.id, PatternDeviceChange::CC { .. } => 0, PatternDeviceChange::None { .. } => 0, + PatternDeviceChange::Legato { new_pattern, .. } => new_pattern.id } } } @@ -144,6 +151,20 @@ impl PatternDevice { }, } } + DeviceChange::NoteLegato { time, old_note, new_note } => { + let new_pattern = Pattern::from(new_note); + match self.patterns.insert(new_pattern.id, new_pattern) { + None => { + error!("Expected to replace a pattern matching, found nothing {:?}", old_note); + PatternDeviceChange::None { time } + } + Some(old_pattern) => PatternDeviceChange::Legato { + time, + old_pattern, + new_pattern, + }, + } + }, DeviceChange::CCChange { time, cc } => PatternDeviceChange::CC { cc, time }, DeviceChange::Ignored { time } => PatternDeviceChange::None { time }, } diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 9ccc059..3a13bac 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -12,47 +12,79 @@ use vst::util::ParameterTransfer; #[cfg(not(feature = "midi_hack_transmission"))] use crate::workers::main_worker::WorkerCommand; use util::duration_display; -use util::parameters::ParameterConversion; +use util::parameters::{ParameterConversion, get_exponential_scale_value}; use util::system::Uuid; #[cfg(not(feature = "midi_hack_transmission"))] -pub const PARAMETER_COUNT: usize = 4; +pub const PARAMETER_COUNT: usize = 5; #[cfg(feature = "midi_hack_transmission")] -pub const PARAMETER_COUNT: usize = 3; +pub const PARAMETER_COUNT: usize = 4; #[cfg(not(feature = "midi_hack_transmission"))] const BASE_PORT: u16 = 6000; -// 1 = Immediate - TODO : must be default -// 0 = Off -// highest = faster -// lowest = slower -// choose or configurable: -// fixed time between start/end pitch -// fixed time per semitone -/* -think about those cases: - large difference / small interval - small difference / large interval => weirdest - - thus fixed time sounds weird, but the player can keep changing the target note, thus has the possibility to - influence the speed. - but then we need to reset the time at each change - - also : velocity would influence pressure - - */ - -// note: if pitchbend is not off, it makes sense to consume note pitchbend as is -// ideally while a note eposes its pitch and a pitchbend value, a method should directly tell the pitch in -// millisemitones relative to 0 ( C-2 ) pub(crate) enum PitchBendValues { Off, // no pitchbend, means same pitch until pattern ends DurationToReachTarget(f32), Immediate, } +impl From for PitchBendValues { + fn from(x: f32) -> Self { + match x { + x if x <= 0. => PitchBendValues::Immediate, + x if x >= 1. => PitchBendValues::Off, + _ => { + let value = get_exponential_scale_value(x, 5., 80.); + PitchBendValues::DurationToReachTarget(value) + } + } + } +} + +// TODO trait to string, change parameter to string in parameters trait + +impl ToString for PitchBendValues { + fn to_string(&self) -> String { + match self { + PitchBendValues::Off => "Off".to_string(), + PitchBendValues::DurationToReachTarget(x) => duration_display(*x), + PitchBendValues::Immediate => "Immediate".to_string() + } + } +} + +pub(crate) enum VelocitySource { + Pattern, + Mixed(f32), + Notes +} + + +impl ToString for VelocitySource { + fn to_string(&self) -> String { + match self { + VelocitySource::Pattern => "Pattern".to_string(), + VelocitySource::Mixed(x) => format!("{}%", (x * 100.) as u8), + VelocitySource::Notes => "Notes".to_string() + } + } +} + +impl From for VelocitySource { + fn from(x: f32) -> Self { + match x { + x if x <= 0. => VelocitySource::Pattern, + x if x >= 1. => VelocitySource::Notes, + x => { + VelocitySource::Mixed(x) + } + } + } +} + + pub(crate) struct ArpegiatorParameters { pub transfer: ParameterTransfer, #[cfg(not(feature = "midi_hack_transmission"))] @@ -90,6 +122,7 @@ pub enum Parameter { HoldNotes = 0, // a started pattern will find a note to play, even if no note is playing for that index PatternLegato, // pattern is not restarted if start/end match, and note is thus held Pitchbend, + VelocitySource, #[cfg(not(feature = "midi_hack_transmission"))] PortIndex, } @@ -100,8 +133,9 @@ impl From for Parameter { 0 => Parameter::HoldNotes, 1 => Parameter::PatternLegato, 2 => Parameter::Pitchbend, + 3 => Parameter::VelocitySource, #[cfg(not(feature = "midi_hack_transmission"))] - 3 => Parameter::PortIndex, + 4 => Parameter::PortIndex, _ => panic!("no such parameter {}", i), } } @@ -135,14 +169,11 @@ impl ArpegiatorParameters { } pub fn get_pitchbend(&self) -> PitchBendValues { - match self.get_parameter(Parameter::Pitchbend.into()) { - x if x <= 0. => PitchBendValues::Immediate, - x if x >= 1. => PitchBendValues::Off, - _ => { - let value = self.get_exponential_scale_parameter(Parameter::Pitchbend, 5., 80.); - PitchBendValues::DurationToReachTarget(value) - } - } + PitchBendValues::from(self.get_parameter(Parameter::Pitchbend.into())) + } + + pub fn get_velocitysource(&self) -> VelocitySource { + VelocitySource::from(self.get_parameter(Parameter::VelocitySource.into())) } } @@ -157,11 +188,8 @@ impl PluginParameters for ArpegiatorParameters { false => "Off", } .to_string(), - Parameter::Pitchbend => match self.get_pitchbend() { - PitchBendValues::Off => "Off".into(), - PitchBendValues::DurationToReachTarget(value) => duration_display(value), - PitchBendValues::Immediate => "Immediate".into(), - }, + Parameter::Pitchbend => self.get_pitchbend().to_string(), + Parameter::VelocitySource => self.get_velocitysource().to_string() } } @@ -172,6 +200,7 @@ impl PluginParameters for ArpegiatorParameters { Parameter::HoldNotes => "Hold notes", Parameter::PatternLegato => "Pattern Legato", Parameter::Pitchbend => "Use pitchbend", + Parameter::VelocitySource => "Velocity", } .to_string() } @@ -202,6 +231,7 @@ impl PluginParameters for ArpegiatorParameters { } } Parameter::Pitchbend => self.transfer.set_parameter(index as usize, value), + Parameter::VelocitySource => self.transfer.set_parameter(index as usize, value), } } diff --git a/util/src/parameters.rs b/util/src/parameters.rs index f49519f..18525c2 100644 --- a/util/src/parameters.rs +++ b/util/src/parameters.rs @@ -3,6 +3,13 @@ use vst::util::ParameterTransfer; use super::parameter_value_conversion::{bool_to_f32, byte_to_f32, f32_to_bool, f32_to_byte, f32_to_u14, u14_to_f32}; + +#[inline] +pub fn get_exponential_scale_value(value: f32, max: f32, factor: f32) -> f32 { + (factor.powf(value) - 1.) * max / (factor - 1.0) +} + + // TODO can Parameter implement just from/into i32, and provide a default implementation for usize ? pub trait ParameterConversion where @@ -34,7 +41,7 @@ where #[inline] fn get_exponential_scale_parameter(&self, index: ParameterType, max: f32, factor: f32) -> f32 { let x = self.get_parameter_transfer().get_parameter(index.into() as usize); - (factor.powf(x) - 1.) * max / (factor - 1.0) + get_exponential_scale_value(x, max, factor) } #[inline] diff --git a/util/src/raw_message.rs b/util/src/raw_message.rs index 1eedf5a..9be7935 100644 --- a/util/src/raw_message.rs +++ b/util/src/raw_message.rs @@ -14,11 +14,14 @@ impl RawMessage { #[inline] pub fn get_bytes(&self) -> &[u8] { // RawMessage is 3 bytes long to keep moving around a known-size message, but some actually are 2 bytes long - if self.0[0] & 0xF0 == PRESSURE { - &self.0[..2] - } else { - &self.0 - } + &self.0 + + // bitwig crashes when receiving pressure messages from a VST ; commented out as it might help, still unlikely + // if self.0[0] & 0xF0 == PRESSURE { + // &self.0[..2] + // } else { + // &self.0 + // } } } @@ -42,7 +45,13 @@ impl From<[u8; 3]> for RawMessage { impl Into<[u8; 3]> for RawMessage { fn into(self) -> [u8; 3] { - self.0 + // attempt of stopping crash at pressure + // TODO log what is output + if self.0[0] & 0xF0 == PRESSURE { + [self.0[0],self.0[1],0] + } else { + self.0 + } } } From 26552b8dee989b6bdaec219fb7c2c6ebc8798917 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 27 Mar 2021 15:47:17 +0100 Subject: [PATCH 31/47] refactor --- midi_delay/src/lib.rs | 4 ++-- note_off_delay/src/parameters.rs | 21 ++++++++++------- util/src/delayed_message_consumer.rs | 34 ++++++++++++++++++++++++++-- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/midi_delay/src/lib.rs b/midi_delay/src/lib.rs index 7e37472..d4c4d92 100644 --- a/midi_delay/src/lib.rs +++ b/midi_delay/src/lib.rs @@ -12,7 +12,7 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin, PluginParameters} use parameters::{MidiDelayParameters, Parameter}; use util::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; -use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; +use util::delayed_message_consumer::{process_scheduled_events, MessageReason, MaxNotesParameter}; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; @@ -59,7 +59,7 @@ impl MidiDelay { samples, self.current_time_in_samples, &self.message_queue, - 0, + MaxNotesParameter::Infinite, false, self.parameters.get_parameter(Parameter::Delay.into()) > 0.0, ); diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index 7a8138a..e77ec94 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -7,6 +7,7 @@ use util::debug::DebugSocket; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; use util::parameters::ParameterConversion; use util::{duration_display, HostCallbackLock}; +use util::delayed_message_consumer::MaxNotesParameter; const PARAMETER_COUNT: usize = 3; @@ -49,6 +50,7 @@ impl ParameterConversion for NoteOffDelayPluginParameters { } } + impl NoteOffDelayPluginParameters { pub fn new(host: HostCallback) -> Self { NoteOffDelayPluginParameters { @@ -57,12 +59,11 @@ impl NoteOffDelayPluginParameters { } } - pub fn get_max_notes(&self) -> u8 { - self.get_byte_parameter(Parameter::MaxNotes) / 4 - } - - pub fn set_max_notes(&self, value: u8) { - self.set_byte_parameter(Parameter::MaxNotes, value * 4) + pub fn get_max_notes(&self) -> MaxNotesParameter { + match self.get_byte_parameter(Parameter::MaxNotes) / 4 { + 0 => MaxNotesParameter::Infinite, + i => MaxNotesParameter::Limited(i) + } } } @@ -130,9 +131,13 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { } Parameter::MaxNotes => { let old_value = self.get_max_notes(); - let max_notes = f32_to_byte(value) / 4; + let byte_value = f32_to_byte(value) ; + let max_notes = match byte_value / 4 { + 0 => MaxNotesParameter::Infinite, + i => MaxNotesParameter::Limited(i) + }; if max_notes != old_value { - self.set_max_notes(max_notes) + self.set_byte_parameter(Parameter::MaxNotes, byte_value); } } Parameter::MaxNotesAppliesToDelayedNotesOnly => { diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index 8175ca1..970410e 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -8,6 +8,8 @@ use super::absolute_time_midi_message::AbsoluteTimeMidiMessage; use super::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; use super::messages::NoteOff; use super::midi_message_type::MidiMessageType; +use std::fmt::{Display, Formatter}; +use std::fmt; #[derive(Hash, Clone, Copy, PartialEq, Eq)] struct PlayingNoteIndex { @@ -15,6 +17,34 @@ struct PlayingNoteIndex { pitch: u8, } +#[derive(Eq, PartialEq)] +pub enum MaxNotesParameter { + Infinite, + Limited(u8) +} + +impl Display for MaxNotesParameter { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + MaxNotesParameter::Infinite => "Infinite".to_string(), + MaxNotesParameter::Limited(x) => format!("Limited({})", x) + }.fmt(f) + } +} + + +impl MaxNotesParameter { + pub fn should_limit(&self, currently_playing: usize) -> bool { + match self { + MaxNotesParameter::Infinite => false, + MaxNotesParameter::Limited(limit) => { + currently_playing >= *limit as usize + } + } + } +} + + #[derive(Clone, Copy, PartialEq)] pub enum MessageReason { Live, @@ -58,7 +88,7 @@ pub fn process_scheduled_events( samples: usize, current_time_in_samples: usize, messages: &AbsoluteTimeMidiMessageVector, - max_notes: u8, + max_notes: MaxNotesParameter, apply_max_notes_to_delayed_notes_only: bool, delay_is_active: bool, ) -> (AbsoluteTimeMidiMessageVector, Vec) { @@ -160,7 +190,7 @@ pub fn process_scheduled_events( // move the note on to the next sample or the daw might be confused message.play_time_in_samples += 1; - } else if max_notes > 0 && playing_notes.len() >= max_notes as usize { + } else if max_notes.should_limit(playing_notes.len()) { if let Some(oldest_playing_note) = playing_notes.oldest_playing_note(apply_max_notes_to_delayed_notes_only) { From e25185bbf3de5cb6a043f7bd28ba8e94add5785c Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 27 Mar 2021 16:25:54 +0100 Subject: [PATCH 32/47] don't delay note off if we reached the limit --- util/src/delayed_message_consumer.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index 970410e..b53c4e3 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -106,13 +106,15 @@ pub fn process_scheduled_events( // back in the scheduled queue if event.play_time_in_samples >= current_time_in_samples { if let MidiMessageType::NoteOffMessage(_) = MidiMessageType::from(event) { - let note_on = notes_on_to_requeue.get_mut(&event.id); - if note_on.is_none() { - return; - } // no such note running, skip - let note_on = note_on.unwrap(); + let note_on = match notes_on_to_requeue.get_mut(&event.id) { + None => { + // no such note running, skip + return ; + } + Some(note_on) => note_on + }; - if event.reason == MessageReason::Live && delay_is_active { + if event.reason == MessageReason::Live && delay_is_active && !max_notes.should_limit(playing_notes.len()) { // mark the note on as delayed from now on, but don't sent the note off note_on.reason = MessageReason::Delayed; return; From bf10999d13d4b5374f816077f9440129b178efb7 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 27 Mar 2021 16:57:20 +0100 Subject: [PATCH 33/47] apply limit to released notes instead of delaying their note off --- util/src/delayed_message_consumer.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index b53c4e3..7d31068 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -106,7 +106,9 @@ pub fn process_scheduled_events( // back in the scheduled queue if event.play_time_in_samples >= current_time_in_samples { if let MidiMessageType::NoteOffMessage(_) = MidiMessageType::from(event) { - let note_on = match notes_on_to_requeue.get_mut(&event.id) { + let note_off_event = event ; + + let note_on = match notes_on_to_requeue.get_mut(¬e_off_event.id) { None => { // no such note running, skip return ; @@ -114,14 +116,14 @@ pub fn process_scheduled_events( Some(note_on) => note_on }; - if event.reason == MessageReason::Live && delay_is_active && !max_notes.should_limit(playing_notes.len()) { + if note_off_event.reason == MessageReason::Live && delay_is_active && !max_notes.should_limit(playing_notes.len() - 1) { // mark the note on as delayed from now on, but don't sent the note off note_on.reason = MessageReason::Delayed; return; } if apply_max_notes_to_delayed_notes_only - && event.reason == MessageReason::MaxNotes + && note_off_event.reason == MessageReason::MaxNotes && note_on.reason == MessageReason::Live { // should be redundant, as MaxNotes messages are not generated for Live notes. @@ -131,10 +133,10 @@ pub fn process_scheduled_events( // stop this note, don't requeue playing_notes.remove(&PlayingNoteIndex { - pitch: event.get_pitch(), - channel: event.get_channel(), + pitch: note_off_event.get_pitch(), + channel: note_off_event.get_channel(), }); - notes_on_to_requeue.remove(&event.id); + notes_on_to_requeue.remove(¬e_off_event.id); } events.push(event.new_midi_event(current_time_in_samples)); } From 4e70b8366b757675730d56b44746b9537a24a7be Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Wed, 31 Mar 2021 23:06:02 +0200 Subject: [PATCH 34/47] refactor delay parameter --- arpegiator/src/lib.rs | 2 +- note_off_delay/src/lib.rs | 16 +++----- note_off_delay/src/parameters.rs | 59 ++++++++++++++++++++++------ util/src/delayed_message_consumer.rs | 2 +- util/src/lib.rs | 10 ++--- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/arpegiator/src/lib.rs b/arpegiator/src/lib.rs index 3f800b9..784fd95 100644 --- a/arpegiator/src/lib.rs +++ b/arpegiator/src/lib.rs @@ -570,7 +570,7 @@ impl Plugin for ArpegiatorPlugin { Some( AfterTouch { channel: pattern.channel, - _pitch, + pitch: _pitch, value: pattern.pressure, } .into(), diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index d390abe..bec2aaf 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -19,6 +19,7 @@ use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::messages::format_event; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; +use crate::parameters::Delay; plugin_main!(NoteOffDelayPlugin); @@ -52,7 +53,7 @@ impl NoteOffDelayPlugin { self.parameters.get_max_notes(), self.parameters .get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), - self.parameters.get_parameter(Parameter::Delay.into()) > 0.0, + self.parameters.get_delay().is_active(), ); self.message_queue = next_message_queue; @@ -161,12 +162,6 @@ impl Plugin for NoteOffDelayPlugin { fn process_events(&mut self, events: &Events) { self.debug_events_in(events); - let note_off_delay = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter( - Parameter::Delay, - 10., - 20., - )); - for event in events.events() { let midi_event = if let Event::Midi(midi_event) = event { midi_event @@ -184,14 +179,15 @@ impl Plugin for NoteOffDelayPlugin { MessageReason::Live, ); - if note_off_delay > 0 { + if let Delay::Duration(seconds) = self.parameters.get_delay() { + let delay_in_samples = self.seconds_to_samples(seconds); // send two times the note off, the live one will be only used to mark the note on as delayed self.message_queue.insert_message( midi_event.data, - note_off_delay + midi_event.delta_frames as usize + self.current_time_in_samples, + delay_in_samples + midi_event.delta_frames as usize + self.current_time_in_samples, MessageReason::Delayed, ); - } + } ; } MidiMessageType::Unsupported => { continue; diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index e77ec94..fe476e9 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -1,13 +1,15 @@ use std::sync::Mutex; -use vst::plugin::HostCallback; +use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; use util::debug::DebugSocket; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; -use util::parameters::ParameterConversion; +use util::parameters::{ParameterConversion, get_exponential_scale_value}; use util::{duration_display, HostCallbackLock}; use util::delayed_message_consumer::MaxNotesParameter; +use std::fmt::{Display, Formatter}; +use std::fmt; const PARAMETER_COUNT: usize = 3; @@ -65,6 +67,10 @@ impl NoteOffDelayPluginParameters { i => MaxNotesParameter::Limited(i) } } + + pub fn get_delay(&self) -> Delay { + Delay::from(self.get_parameter(Parameter::Delay.into())) + } } impl Default for NoteOffDelayPluginParameters { @@ -76,18 +82,47 @@ impl Default for NoteOffDelayPluginParameters { } } +pub enum Delay { + Off, + Duration(f32), +} + +impl Delay { + pub fn is_active(&self) -> bool { + match self { + Delay::Off => false, + Delay::Duration(_) => true + } + } +} + +impl Display for Delay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Delay::Off => "off".to_string(), + Delay::Duration(seconds) => { + duration_display(*seconds) + } + }.fmt(f) + } +} + +impl From for Delay { + fn from(parameter_value: f32) -> Self { + match get_exponential_scale_value(parameter_value, 10., 20.) { + x if x == 0.0 => Delay::Off, + value => Delay::Duration(value) + } + } +} + impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn get_parameter_text(&self, index: i32) -> String { match index.into() { Parameter::Delay => { - let value = self.get_exponential_scale_parameter(Parameter::Delay, 10., 20.); - - if value > 0.0 { - duration_display(value) - } else { - "Off".to_string() - } + self.get_delay().to_string() } + Parameter::MaxNotes => { if self.get_parameter(Parameter::MaxNotes as i32) == 0.0 { "Off".to_string() @@ -102,7 +137,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { } else { "Off" } - .to_string() + .to_string() } } } @@ -113,7 +148,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { Parameter::MaxNotes => "Max Notes", Parameter::MaxNotesAppliesToDelayedNotesOnly => "Apply max notes to delayed notes only", } - .to_string() + .to_string() } fn get_parameter(&self, index: i32) -> f32 { @@ -131,7 +166,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { } Parameter::MaxNotes => { let old_value = self.get_max_notes(); - let byte_value = f32_to_byte(value) ; + let byte_value = f32_to_byte(value); let max_notes = match byte_value / 4 { 0 => MaxNotesParameter::Infinite, i => MaxNotesParameter::Limited(i) diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index 7d31068..a84e7ac 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -27,7 +27,7 @@ impl Display for MaxNotesParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { MaxNotesParameter::Infinite => "Infinite".to_string(), - MaxNotesParameter::Limited(x) => format!("Limited({})", x) + MaxNotesParameter::Limited(x) => x.to_string() }.fmt(f) } } diff --git a/util/src/lib.rs b/util/src/lib.rs index efb430d..2efff31 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -36,15 +36,15 @@ pub fn make_midi_message(bytes: [u8; 3], delta_frames: i32) -> MidiEvent { } } -pub fn duration_display(value: f32) -> String { +pub fn duration_display(seconds: f32) -> String { let mut out = String::new(); - let mut _value = value; + let mut _value = seconds; if _value >= 1.0 { - out += &*format!("{:.0}s ", value); - _value -= value.trunc(); + out += &*format!("{:.0}s ", seconds); + _value -= seconds.trunc(); } if _value > 0.0 { - out += &*format!("{:3.0}ms", value * 1000.0); + out += &*format!("{:3.0}ms", seconds * 1000.0); } out } From b8b6603ff6356a7007e2c1135341f99d28ab5fa0 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 1 Apr 2021 08:54:47 +0200 Subject: [PATCH 35/47] simplify note insertion --- note_off_delay/src/lib.rs | 2 +- util/src/absolute_time_midi_message_vector.rs | 53 +++++++------------ 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index bec2aaf..a8797c2 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use vst::api::Events; use vst::buffer::{AudioBuffer, SendEventBuffer}; use vst::event::Event; -use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin, PluginParameters}; +use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use parameters::NoteOffDelayPluginParameters; use parameters::Parameter; diff --git a/util/src/absolute_time_midi_message_vector.rs b/util/src/absolute_time_midi_message_vector.rs index 44aefb7..9b2a57c 100644 --- a/util/src/absolute_time_midi_message_vector.rs +++ b/util/src/absolute_time_midi_message_vector.rs @@ -3,7 +3,6 @@ use std::ops::{Deref, DerefMut}; use global_counter::primitive::exact::CounterUsize; use super::absolute_time_midi_message::AbsoluteTimeMidiMessage; -use super::debug::DebugSocket; use super::delayed_message_consumer::MessageReason; use super::midi_message_type::MidiMessageType; use super::raw_message::RawMessage; @@ -38,49 +37,37 @@ impl DerefMut for AbsoluteTimeMidiMessageVector { static NOTE_SEQUENCE_ID: CounterUsize = CounterUsize::new(0); impl AbsoluteTimeMidiMessageVector { - pub fn insert_message(&mut self, data: [u8; 3], play_time_in_samples: usize, reason: MessageReason) { - // we generate unique identifier per event. this is in order to match note on/note off pairs - let channel_pitch_lookup = match MidiMessageType::from(RawMessage::from(data)) { - MidiMessageType::NoteOffMessage(midi_message) => Some((midi_message.channel, midi_message.pitch)), - _ => None, - }; - - let mut last_note_on_match = None; - - let insert_point = self.iter().position(|message_at_position| { - if let Some((channel, pitch)) = channel_pitch_lookup { - if let MidiMessageType::NoteOnMessage(midi_message) = MidiMessageType::from(*message_at_position) { - if channel == midi_message.channel && pitch == midi_message.pitch { - last_note_on_match = Some(message_at_position); - } - } + pub fn get_matching_note_on(&self, channel: u8, pitch: u8, play_time_in_samples: usize) -> Option<&AbsoluteTimeMidiMessage> { + self.iter().filter(|message| + play_time_in_samples < message.play_time_in_samples && + match MidiMessageType::from(**message) { + MidiMessageType::NoteOnMessage(midi_message) => channel == midi_message.channel && pitch == midi_message.pitch, + _ => false } + ).last() + } - // we use '<' and not '<=', because this method is called in the same order as events - // come, and find their position starting from the beginning of the vector. By moving - // past equally timed elements, we keep the original order. - play_time_in_samples < message_at_position.play_time_in_samples - }); + pub fn insert_message(&mut self, data: [u8; 3], play_time_in_samples: usize, reason: MessageReason) { + let raw_message = RawMessage::from(data) ; - let id = if let Some(note_message) = last_note_on_match { - note_message.id - } else { - NOTE_SEQUENCE_ID.inc() + let id = match MidiMessageType::from(raw_message) { + MidiMessageType::NoteOffMessage(midi_message) => { + match self.get_matching_note_on(midi_message.channel, midi_message.pitch, play_time_in_samples) { + Some(note_on_message) => note_on_message.id, + None => NOTE_SEQUENCE_ID.inc() + } + }, + _ => NOTE_SEQUENCE_ID.inc(), }; let message = AbsoluteTimeMidiMessage { - data: data.into(), + data: raw_message, id, play_time_in_samples, reason, }; - DebugSocket::send(&*format!("Inserting {}", message)); - if let Some(insert_point) = insert_point { - self.insert(insert_point, message); - } else { - self.push(message); - } + self.ordered_insert(message); } pub fn ordered_insert(&mut self, message: AbsoluteTimeMidiMessage) { From d97b83321c6b784251c1e67532f3955973f42896 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 1 Apr 2021 09:18:37 +0200 Subject: [PATCH 36/47] replace debugsocket --- note_off_delay/src/lib.rs | 12 ++++----- note_off_delay/src/parameters.rs | 4 +-- util/src/debug.rs | 46 -------------------------------- util/src/lib.rs | 1 - 4 files changed, 8 insertions(+), 55 deletions(-) delete mode 100644 util/src/debug.rs diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index a8797c2..5a84d95 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -3,6 +3,7 @@ mod parameters; #[macro_use] extern crate vst; +use log::info; use std::cell::RefCell; use std::sync::Arc; @@ -14,7 +15,7 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use parameters::NoteOffDelayPluginParameters; use parameters::Parameter; use util::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; -use util::debug::DebugSocket; +use util::logging::logging_setup; use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::messages::format_event; use util::midi_message_type::MidiMessageType; @@ -74,7 +75,7 @@ impl NoteOffDelayPlugin { fn debug_events_in(&mut self, events: &Events) { for e in events.events() { - DebugSocket::send(&*(format_event(&e) + &*format!(" current time={}", self.current_time_in_samples))); + info!("{} current time={}", format_event(&e), self.current_time_in_samples); } } @@ -105,10 +106,9 @@ impl Plugin for NoteOffDelayPlugin { } fn new(host: HostCallback) -> Self { + logging_setup(); + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp)); let parameters = NoteOffDelayPluginParameters::new(host); - DebugSocket::send( - build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp), - ); NoteOffDelayPlugin { current_time_in_samples: 0, message_queue: Default::default(), @@ -146,7 +146,7 @@ impl Plugin for NoteOffDelayPlugin { // if s == "MPE" { // Yes // } else { - // DebugSocket::send(&*s); + // info!("{}", s) ; // No // } Maybe diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index fe476e9..0f68ef7 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -1,9 +1,9 @@ +use log::info; use std::sync::Mutex; use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; -use util::debug::DebugSocket; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; use util::parameters::{ParameterConversion, get_exponential_scale_value}; use util::{duration_display, HostCallbackLock}; @@ -158,7 +158,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn set_parameter(&self, index: i32, value: f32) { match index.into() { Parameter::Delay => { - DebugSocket::send(&*format!("Parameter {} set to {}", index, value)); + info!("Parameter {} set to {}", index, value); let old_value = self.get_parameter(index); if (value - old_value).abs() > 0.0001 { self.transfer.set_parameter(index as usize, value) diff --git a/util/src/debug.rs b/util/src/debug.rs deleted file mode 100644 index 0b2aa31..0000000 --- a/util/src/debug.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::net::{SocketAddr, UdpSocket}; - -pub struct DebugSocket { - socket: UdpSocket, - to: SocketAddr, -} - -impl DebugSocket { - fn create() { - let socket_result = (10000..20000) - .map(|port| UdpSocket::bind(format!("127.0.0.1:{}", port))) - .filter_map(Result::ok) - .next(); - - let socket = socket_result.unwrap(); - socket.set_nonblocking(true).unwrap(); - - unsafe { - DEBUG_SOCKET = Some(DebugSocket { - socket, - to: "127.0.0.1:5555".parse().unwrap(), - }) - } - } - - pub fn send(debug_str: &str) { - if debug_str.is_empty() { - return; - } - let debug_string = debug_str.to_owned() + "\n"; - unsafe { - if DEBUG_SOCKET.is_none() { - Self::create() - } - - if let Some(debug_socket) = &DEBUG_SOCKET { - debug_socket - .socket - .send_to(debug_string.as_bytes(), debug_socket.to) - .unwrap(); - } - } - } -} - -static mut DEBUG_SOCKET: Option = None; diff --git a/util/src/lib.rs b/util/src/lib.rs index 2efff31..8cf57ec 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -6,7 +6,6 @@ use vst::plugin::HostCallback; pub mod absolute_time_midi_message; pub mod absolute_time_midi_message_vector; pub mod constants; -pub mod debug; pub mod delayed_message_consumer; pub mod ipc_payload; pub mod logging; From 3aff8d2e5698dfe4c75f4fc72dda0207f373c8c2 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 1 Apr 2021 13:52:34 +0200 Subject: [PATCH 37/47] implement delay multiplier --- note_off_delay/src/lib.rs | 36 +++++++---- note_off_delay/src/parameters.rs | 100 ++++++++++++++++++++++++------- 2 files changed, 101 insertions(+), 35 deletions(-) diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 5a84d95..3b9402d 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -20,7 +20,6 @@ use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::messages::format_event; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; -use crate::parameters::Delay; plugin_main!(NoteOffDelayPlugin); @@ -90,7 +89,7 @@ impl Plugin for NoteOffDelayPlugin { name: "Note Off Delay".to_string(), vendor: "DJ Crontab".to_string(), unique_id: 234213173, - parameters: 3, + parameters: 4, category: Category::Effect, initial_delay: 0, version: 1, @@ -172,22 +171,35 @@ impl Plugin for NoteOffDelayPlugin { // TODO: minimum time, maximum time ( with delay ) match MidiMessageType::from(&midi_event.data) { - MidiMessageType::NoteOffMessage(_) => { + MidiMessageType::NoteOffMessage(note_off) => { self.message_queue.insert_message( midi_event.data, midi_event.delta_frames as usize + self.current_time_in_samples, MessageReason::Live, ); - if let Delay::Duration(seconds) = self.parameters.get_delay() { - let delay_in_samples = self.seconds_to_samples(seconds); - // send two times the note off, the live one will be only used to mark the note on as delayed - self.message_queue.insert_message( - midi_event.data, - delay_in_samples + midi_event.delta_frames as usize + self.current_time_in_samples, - MessageReason::Delayed, - ); - } ; + let delay = self.parameters.get_delay() ; + + if delay.is_active() { + let note_off_play_time = midi_event.delta_frames as usize + self.current_time_in_samples; + match self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch, note_off_play_time) { + None => {} + Some(note_on) => { + let duration = note_off_play_time - note_on.play_time_in_samples; + match self.parameters.get_delay().apply(duration, self.sample_rate) { + None => panic!("delay is supposed to be active"), + Some(new_time) => { + // send two times the note off, the live one will be only used to mark the note on as delayed + self.message_queue.insert_message( + midi_event.data, + new_time, + MessageReason::Delayed, + ); + } + } + } + } + } } MidiMessageType::Unsupported => { continue; diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index 0f68ef7..b371351 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -1,4 +1,3 @@ -use log::info; use std::sync::Mutex; use vst::plugin::{HostCallback, PluginParameters}; @@ -11,7 +10,7 @@ use util::delayed_message_consumer::MaxNotesParameter; use std::fmt::{Display, Formatter}; use std::fmt; -const PARAMETER_COUNT: usize = 3; +const PARAMETER_COUNT: usize = 4; pub struct NoteOffDelayPluginParameters { pub host_mutex: Mutex, @@ -20,17 +19,19 @@ pub struct NoteOffDelayPluginParameters { #[repr(i32)] pub enum Parameter { - Delay = 0, + DelayOffset = 0, MaxNotes, MaxNotesAppliesToDelayedNotesOnly, + MultiplyLength, } impl From for Parameter { fn from(i: i32) -> Self { match i { - 0 => Parameter::Delay, + 0 => Parameter::DelayOffset, 1 => Parameter::MaxNotes, 2 => Parameter::MaxNotesAppliesToDelayedNotesOnly, + 3 => Parameter::MultiplyLength, _ => panic!("no such parameter {}", i), } } @@ -69,7 +70,10 @@ impl NoteOffDelayPluginParameters { } pub fn get_delay(&self) -> Delay { - Delay::from(self.get_parameter(Parameter::Delay.into())) + Delay { + offset: DelayOffset::from(self.get_parameter(Parameter::DelayOffset.into())), + multiplier: DelayMultiplier::from(self.get_parameter(Parameter::MultiplyLength.into())) + } } } @@ -82,36 +86,84 @@ impl Default for NoteOffDelayPluginParameters { } } -pub enum Delay { + +pub struct Delay { + pub offset: DelayOffset, + pub multiplier: DelayMultiplier, +} + + +pub enum DelayOffset { Off, Duration(f32), } +pub enum DelayMultiplier { + Off, + Multiplier(f32), +} + impl Delay { pub fn is_active(&self) -> bool { - match self { - Delay::Off => false, - Delay::Duration(_) => true + match (&self.offset, &self.multiplier) { + (DelayOffset::Off, DelayMultiplier::Off) => false, + _ => true + } + } + + pub fn apply(&self, duration_in_samples: usize, sample_rate: f32) -> Option { + if self.is_active() { + let duration_in_samples = match self.multiplier { + DelayMultiplier::Off => duration_in_samples as f32, + DelayMultiplier::Multiplier(x) => x * duration_in_samples as f32 + }; + + Some(match self.offset { + DelayOffset::Off => duration_in_samples as usize, + DelayOffset::Duration(x) => (duration_in_samples + x * sample_rate) as usize + }) + } else { + None } } } -impl Display for Delay { +impl Display for DelayOffset { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - Delay::Off => "off".to_string(), - Delay::Duration(seconds) => { + DelayOffset::Off => "off".to_string(), + DelayOffset::Duration(seconds) => { duration_display(*seconds) } }.fmt(f) } } -impl From for Delay { +impl Display for DelayMultiplier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + DelayMultiplier::Off => "off".to_string(), + DelayMultiplier::Multiplier(multiplier) => { + format!("{}x", multiplier) + } + }.fmt(f) + } +} + +impl From for DelayOffset { fn from(parameter_value: f32) -> Self { match get_exponential_scale_value(parameter_value, 10., 20.) { - x if x == 0.0 => Delay::Off, - value => Delay::Duration(value) + x if x == 0.0 => DelayOffset::Off, + value => DelayOffset::Duration(value) + } + } +} + +impl From for DelayMultiplier { + fn from(parameter_value: f32) -> Self { + match get_exponential_scale_value(parameter_value, 9., 20.) { + x if x == 0.0 => DelayMultiplier::Off, + value => DelayMultiplier::Multiplier(1.0 + value) } } } @@ -119,8 +171,8 @@ impl From for Delay { impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn get_parameter_text(&self, index: i32) -> String { match index.into() { - Parameter::Delay => { - self.get_delay().to_string() + Parameter::DelayOffset => { + DelayOffset::from(self.get_parameter(Parameter::DelayOffset as i32)).to_string() } Parameter::MaxNotes => { @@ -136,17 +188,20 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { "On" } else { "Off" - } - .to_string() + }.to_string() + } + Parameter::MultiplyLength => { + DelayOffset::from(self.get_parameter(Parameter::MultiplyLength as i32)).to_string() } } } fn get_parameter_name(&self, index: i32) -> String { match index.into() { - Parameter::Delay => "Delay", + Parameter::DelayOffset => "Delay", Parameter::MaxNotes => "Max Notes", Parameter::MaxNotesAppliesToDelayedNotesOnly => "Apply max notes to delayed notes only", + Parameter::MultiplyLength => "Length multiplier" } .to_string() } @@ -157,10 +212,9 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn set_parameter(&self, index: i32, value: f32) { match index.into() { - Parameter::Delay => { - info!("Parameter {} set to {}", index, value); + Parameter::DelayOffset | Parameter::MultiplyLength => { let old_value = self.get_parameter(index); - if (value - old_value).abs() > 0.0001 { + if (value - old_value).abs() > 0.0001 || (value == 0.0 && old_value != 0.0) { self.transfer.set_parameter(index as usize, value) } } From 95131feeb0b54d5dda3bfbd01515bdec50e7c1fa Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Thu, 1 Apr 2021 14:59:11 +0200 Subject: [PATCH 38/47] fix note on match --- note_off_delay/src/lib.rs | 11 ++++++----- note_off_delay/src/parameters.rs | 11 ++++------- util/src/absolute_time_midi_message_vector.rs | 5 ++--- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 3b9402d..8703daf 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -172,27 +172,28 @@ impl Plugin for NoteOffDelayPlugin { match MidiMessageType::from(&midi_event.data) { MidiMessageType::NoteOffMessage(note_off) => { + let note_off_play_time = midi_event.delta_frames as usize + self.current_time_in_samples; + self.message_queue.insert_message( midi_event.data, - midi_event.delta_frames as usize + self.current_time_in_samples, + note_off_play_time, MessageReason::Live, ); let delay = self.parameters.get_delay() ; if delay.is_active() { - let note_off_play_time = midi_event.delta_frames as usize + self.current_time_in_samples; - match self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch, note_off_play_time) { + match self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch) { None => {} Some(note_on) => { let duration = note_off_play_time - note_on.play_time_in_samples; match self.parameters.get_delay().apply(duration, self.sample_rate) { None => panic!("delay is supposed to be active"), - Some(new_time) => { + Some(new_duration) => { // send two times the note off, the live one will be only used to mark the note on as delayed self.message_queue.insert_message( midi_event.data, - new_time, + note_on.play_time_in_samples + new_duration, MessageReason::Delayed, ); } diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index b371351..5e660ba 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -105,10 +105,7 @@ pub enum DelayMultiplier { impl Delay { pub fn is_active(&self) -> bool { - match (&self.offset, &self.multiplier) { - (DelayOffset::Off, DelayMultiplier::Off) => false, - _ => true - } + !matches!((&self.offset, &self.multiplier), (DelayOffset::Off, DelayMultiplier::Off)) } pub fn apply(&self, duration_in_samples: usize, sample_rate: f32) -> Option { @@ -144,7 +141,7 @@ impl Display for DelayMultiplier { match self { DelayMultiplier::Off => "off".to_string(), DelayMultiplier::Multiplier(multiplier) => { - format!("{}x", multiplier) + format!("{:.3}x", multiplier) } }.fmt(f) } @@ -161,7 +158,7 @@ impl From for DelayOffset { impl From for DelayMultiplier { fn from(parameter_value: f32) -> Self { - match get_exponential_scale_value(parameter_value, 9., 20.) { + match get_exponential_scale_value(parameter_value, 19., 20.) { x if x == 0.0 => DelayMultiplier::Off, value => DelayMultiplier::Multiplier(1.0 + value) } @@ -191,7 +188,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { }.to_string() } Parameter::MultiplyLength => { - DelayOffset::from(self.get_parameter(Parameter::MultiplyLength as i32)).to_string() + DelayMultiplier::from(self.get_parameter(Parameter::MultiplyLength as i32)).to_string() } } } diff --git a/util/src/absolute_time_midi_message_vector.rs b/util/src/absolute_time_midi_message_vector.rs index 9b2a57c..00a3fdc 100644 --- a/util/src/absolute_time_midi_message_vector.rs +++ b/util/src/absolute_time_midi_message_vector.rs @@ -37,9 +37,8 @@ impl DerefMut for AbsoluteTimeMidiMessageVector { static NOTE_SEQUENCE_ID: CounterUsize = CounterUsize::new(0); impl AbsoluteTimeMidiMessageVector { - pub fn get_matching_note_on(&self, channel: u8, pitch: u8, play_time_in_samples: usize) -> Option<&AbsoluteTimeMidiMessage> { + pub fn get_matching_note_on(&self, channel: u8, pitch: u8) -> Option<&AbsoluteTimeMidiMessage> { self.iter().filter(|message| - play_time_in_samples < message.play_time_in_samples && match MidiMessageType::from(**message) { MidiMessageType::NoteOnMessage(midi_message) => channel == midi_message.channel && pitch == midi_message.pitch, _ => false @@ -52,7 +51,7 @@ impl AbsoluteTimeMidiMessageVector { let id = match MidiMessageType::from(raw_message) { MidiMessageType::NoteOffMessage(midi_message) => { - match self.get_matching_note_on(midi_message.channel, midi_message.pitch, play_time_in_samples) { + match self.get_matching_note_on(midi_message.channel, midi_message.pitch) { Some(note_on_message) => note_on_message.id, None => NOTE_SEQUENCE_ID.inc() } From f3b7250b8b6d6a21d37df18b23db780bb7141ff6 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Mon, 5 Apr 2021 11:42:22 +0200 Subject: [PATCH 39/47] update vst-rs --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 2557acf..70f2303 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1547,7 +1547,7 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "vst" version = "0.2.1" -source = "git+https://github.com/rustaudio/vst-rs#9eb1bef1826db1581b4162081de05c1090935afb" +source = "git+https://github.com/rustaudio/vst-rs#a398697b0214129720af2480e98f1abf7beecfce" dependencies = [ "bitflags", "libc", From 05ca554da155c3ebbd4498f964cb8d29297938e4 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Mon, 5 Apr 2021 16:04:06 +0200 Subject: [PATCH 40/47] synchronized delay --- Cargo.lock | 3 + midi_delay/Cargo.toml | 5 ++ midi_delay/build.rs | 5 ++ midi_delay/src/lib.rs | 75 +++++++++-------- midi_delay/src/parameters.rs | 20 +++-- note_off_delay/src/lib.rs | 2 +- note_off_delay/src/parameters.rs | 26 +----- util/src/absolute_time_midi_message_vector.rs | 12 +++ util/src/delayed_message_consumer.rs | 23 ++++++ util/src/lib.rs | 81 +++++++++++++++++++ 10 files changed, 188 insertions(+), 64 deletions(-) create mode 100644 midi_delay/build.rs diff --git a/Cargo.lock b/Cargo.lock index 70f2303..f1061af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -918,6 +918,9 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" name = "midi_delay" version = "0.1.0" dependencies = [ + "build-info", + "build-info-build", + "log", "util", "vst", ] diff --git a/midi_delay/Cargo.toml b/midi_delay/Cargo.toml index 28e2e3f..7b94bdd 100644 --- a/midi_delay/Cargo.toml +++ b/midi_delay/Cargo.toml @@ -7,7 +7,12 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } +log = "0.4.11" +build-info = "0.0.20" [lib] name = "midi_delay" crate-type = ["cdylib", "lib"] + +[build-dependencies] +build-info-build = "0.0.20" diff --git a/midi_delay/build.rs b/midi_delay/build.rs new file mode 100644 index 0000000..a49ab18 --- /dev/null +++ b/midi_delay/build.rs @@ -0,0 +1,5 @@ +fn main() { + // Calling `build_info_build::build_script` collects all data and makes it available to `build_info::build_info!` + // and `build_info::format!` in the main program. + build_info_build::build_script(); +} diff --git a/midi_delay/src/lib.rs b/midi_delay/src/lib.rs index d4c4d92..adba9dd 100644 --- a/midi_delay/src/lib.rs +++ b/midi_delay/src/lib.rs @@ -1,5 +1,8 @@ mod parameters; +#[allow(unused_imports)] +use log::{error, info}; + #[macro_use] extern crate vst; @@ -12,9 +15,12 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin, PluginParameters} use parameters::{MidiDelayParameters, Parameter}; use util::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; -use util::delayed_message_consumer::{process_scheduled_events, MessageReason, MaxNotesParameter}; +use util::delayed_message_consumer::{process_scheduled_events, MessageReason, MaxNotesParameter, raw_process_scheduled_events}; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; +use vst::host::Host; +use util::logging::logging_setup; +use util::SyncDuration; plugin_main!(MidiDelay); @@ -23,6 +29,7 @@ pub struct MidiDelay { message_queue: AbsoluteTimeMidiMessageVector, parameters: Arc, sample_rate: f32, + bpm: f64, send_buffer: RefCell, } @@ -33,6 +40,7 @@ impl Default for MidiDelay { message_queue: Default::default(), parameters: Arc::new(Default::default()), sample_rate: 44100.0, + bpm: 0.0, send_buffer: Default::default(), } } @@ -55,13 +63,10 @@ impl MidiDelay { fn send_events(&mut self, samples: usize) { if let Ok(mut host_callback_lock) = self.parameters.host.lock() { - let (next_message_queue, events) = process_scheduled_events( + let (next_message_queue, events) = raw_process_scheduled_events( samples, self.current_time_in_samples, &self.message_queue, - MaxNotesParameter::Infinite, - false, - self.parameters.get_parameter(Parameter::Delay.into()) > 0.0, ); self.message_queue = next_message_queue; @@ -70,6 +75,21 @@ impl MidiDelay { .send_events(events, &mut host_callback_lock.host); } } + + fn update_bpm(&mut self) { + use vst::api::TimeInfoFlags; + if let Ok(host_callback_lock) = self.parameters.host.lock() { + match host_callback_lock.host.get_time_info(TimeInfoFlags::TEMPO_VALID.bits()) { + None => (), + Some(ti) => { + if ti.flags & TimeInfoFlags::TEMPO_VALID.bits() != 0 { + self.bpm = ti.tempo; + info!("{}", self.bpm); + } + } + } + } + } } impl Plugin for MidiDelay { @@ -78,7 +98,7 @@ impl Plugin for MidiDelay { name: "Midi Delay".to_string(), vendor: "DJ Crontab".to_string(), unique_id: 133498, - parameters: 1, + parameters: 2, category: Category::Effect, initial_delay: 0, version: 2, @@ -94,6 +114,8 @@ impl Plugin for MidiDelay { } fn new(host: HostCallback) -> Self { + logging_setup(); + info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp)); let parameters = MidiDelayParameters::new(host); MidiDelay { @@ -101,6 +123,7 @@ impl Plugin for MidiDelay { message_queue: Default::default(), parameters: Arc::new(parameters), sample_rate: 44100.0, + bpm: 0.0, send_buffer: Default::default(), } } @@ -131,12 +154,19 @@ impl Plugin for MidiDelay { } fn process_events(&mut self, events: &Events) { - let midi_delay = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter( + self.update_bpm(); + let mut midi_delay = self.seconds_to_samples(self.parameters.get_exponential_scale_parameter( Parameter::Delay, 1., 80., )); - + let sync_delay = SyncDuration::from(self.parameters.get_parameter(Parameter::SyncDelay.into())); + match sync_delay.delay_to_samples(self.bpm, self.sample_rate) { + None => {} + Some(delay) => { + midi_delay += delay + } + } for event in events.events() { let midi_event = if let Event::Midi(midi_event) = event { midi_event @@ -144,31 +174,10 @@ impl Plugin for MidiDelay { continue; }; - if let MidiMessageType::NoteOffMessage(_) = MidiMessageType::from(&midi_event.data) { - // TODO because of changes in process_scheduled_events specific to delay note off plugin, - // note offs need a special handling ; not all usages of process_scheduled_events should have to care - // about that - - self.message_queue.insert_message( - midi_event.data, - midi_event.delta_frames as usize + self.current_time_in_samples, - MessageReason::Live, - ); - - if midi_delay > 0 { - self.message_queue.insert_message( - midi_event.data, - midi_delay + midi_event.delta_frames as usize + self.current_time_in_samples, - MessageReason::Delayed, - ); - } - } else { - self.message_queue.insert_message( - midi_event.data, - midi_delay + midi_event.delta_frames as usize + self.current_time_in_samples, - MessageReason::Live, - ); - } + self.message_queue.raw_insert( + midi_event.data, + midi_event.delta_frames as usize + self.current_time_in_samples + midi_delay, + ); } } } diff --git a/midi_delay/src/parameters.rs b/midi_delay/src/parameters.rs index 5e10bdd..ecb864a 100644 --- a/midi_delay/src/parameters.rs +++ b/midi_delay/src/parameters.rs @@ -1,6 +1,6 @@ use std::sync::Mutex; use util::parameters::ParameterConversion; -use util::{duration_display, HostCallbackLock}; +use util::{duration_display, HostCallbackLock, SyncDuration}; use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; @@ -12,13 +12,15 @@ pub struct MidiDelayParameters { #[repr(i32)] pub enum Parameter { Delay = 0, + SyncDelay = 1, } impl From for Parameter { fn from(i: i32) -> Self { match i { 0 => Parameter::Delay, - _ => panic!(format!("No such Parameter {}", i)), + 1 => Parameter::SyncDelay, + _ => panic!("No such Parameter {}", i), } } } @@ -35,7 +37,7 @@ impl ParameterConversion for MidiDelayParameters { } fn get_parameter_count() -> usize { - 1 + 2 } } @@ -43,7 +45,7 @@ impl MidiDelayParameters { pub fn new(host: HostCallback) -> Self { MidiDelayParameters { host: Mutex::new(HostCallbackLock { host }), - transfer: ParameterTransfer::new(1), + transfer: ParameterTransfer::new(2), } } } @@ -59,12 +61,18 @@ impl PluginParameters for MidiDelayParameters { "Off".to_string() } } + Parameter::SyncDelay => { + let value = self.get_parameter(Parameter::SyncDelay.into()); + let tempo_delay = SyncDuration::from(value); + tempo_delay.to_string() + } } } fn get_parameter_name(&self, index: i32) -> String { match Parameter::from(index as i32) { Parameter::Delay => "Delay", + Parameter::SyncDelay => "Sync Delay" } .to_string() } @@ -75,9 +83,9 @@ impl PluginParameters for MidiDelayParameters { fn set_parameter(&self, index: i32, value: f32) { match index.into() { - Parameter::Delay => { + Parameter::Delay | Parameter::SyncDelay => { let old_value = self.get_parameter(index); - if (value - old_value).abs() > 0.00001 { + if (value - old_value).abs() > 0.0001 || (value == 0.0 && old_value != 0.0) { self.transfer.set_parameter(index as usize, value) } } diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 8703daf..27ebb54 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -183,7 +183,7 @@ impl Plugin for NoteOffDelayPlugin { let delay = self.parameters.get_delay() ; if delay.is_active() { - match self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch) { + match self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch).cloned() { None => {} Some(note_on) => { let duration = note_off_play_time - note_on.play_time_in_samples; diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index 5e660ba..0103b6d 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -5,7 +5,7 @@ use vst::util::ParameterTransfer; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; use util::parameters::{ParameterConversion, get_exponential_scale_value}; -use util::{duration_display, HostCallbackLock}; +use util::{duration_display, HostCallbackLock, DelayOffset}; use util::delayed_message_consumer::MaxNotesParameter; use std::fmt::{Display, Formatter}; use std::fmt; @@ -93,11 +93,6 @@ pub struct Delay { } -pub enum DelayOffset { - Off, - Duration(f32), -} - pub enum DelayMultiplier { Off, Multiplier(f32), @@ -125,16 +120,6 @@ impl Delay { } } -impl Display for DelayOffset { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - DelayOffset::Off => "off".to_string(), - DelayOffset::Duration(seconds) => { - duration_display(*seconds) - } - }.fmt(f) - } -} impl Display for DelayMultiplier { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -147,14 +132,7 @@ impl Display for DelayMultiplier { } } -impl From for DelayOffset { - fn from(parameter_value: f32) -> Self { - match get_exponential_scale_value(parameter_value, 10., 20.) { - x if x == 0.0 => DelayOffset::Off, - value => DelayOffset::Duration(value) - } - } -} + impl From for DelayMultiplier { fn from(parameter_value: f32) -> Self { diff --git a/util/src/absolute_time_midi_message_vector.rs b/util/src/absolute_time_midi_message_vector.rs index 00a3fdc..fc347b3 100644 --- a/util/src/absolute_time_midi_message_vector.rs +++ b/util/src/absolute_time_midi_message_vector.rs @@ -46,6 +46,18 @@ impl AbsoluteTimeMidiMessageVector { ).last() } + pub fn raw_insert(&mut self, data: [u8; 3], play_time_in_samples: usize) { + // insert in vector, disregarding matching note on/off + let message = AbsoluteTimeMidiMessage { + data: data.into(), + id: NOTE_SEQUENCE_ID.inc(), + play_time_in_samples, + reason: MessageReason::PlayUnprocessed + }; + + self.ordered_insert(message); + } + pub fn insert_message(&mut self, data: [u8; 3], play_time_in_samples: usize, reason: MessageReason) { let raw_message = RawMessage::from(data) ; diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index a84e7ac..bc095e4 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -51,6 +51,7 @@ pub enum MessageReason { Delayed, // the same event will exist live and delayed MaxNotes, Retrigger, + PlayUnprocessed } #[derive(Default)] @@ -255,3 +256,25 @@ pub fn process_scheduled_events( (queued_messages, events) } + + +pub fn raw_process_scheduled_events( + samples: usize, + current_time_in_samples: usize, + messages: &AbsoluteTimeMidiMessageVector, +) -> (AbsoluteTimeMidiMessageVector, Vec) { + let mut queued_messages = AbsoluteTimeMidiMessageVector::default(); + let mut events: Vec = vec![]; + + for mut message in messages.iter().copied() { + if message.play_time_in_samples < current_time_in_samples + samples { + if message.play_time_in_samples >= current_time_in_samples { + events.push(message.new_midi_event(current_time_in_samples)); + } + } else { + queued_messages.push(message); + } + } + + (queued_messages, events) +} diff --git a/util/src/lib.rs b/util/src/lib.rs index 8cf57ec..6292a81 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -2,6 +2,9 @@ extern crate global_counter; use vst::event::MidiEvent; use vst::plugin::HostCallback; +use std::fmt::{Display, Formatter}; +use std::fmt; +use crate::parameters::get_exponential_scale_value; pub mod absolute_time_midi_message; pub mod absolute_time_midi_message_vector; @@ -47,3 +50,81 @@ pub fn duration_display(seconds: f32) -> String { } out } + +impl Display for DelayOffset { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + DelayOffset::Off => "off".to_string(), + DelayOffset::Duration(seconds) => { + duration_display(*seconds) + } + }.fmt(f) + } +} + +pub enum DelayOffset { + Off, + Duration(f32), +} + +impl From for DelayOffset { + fn from(parameter_value: f32) -> Self { + match get_exponential_scale_value(parameter_value, 10., 20.) { + x if x == 0.0 => DelayOffset::Off, + value => DelayOffset::Duration(value) + } + } +} + +pub enum SyncDuration { + Off, + Subdivision(u8, u8), +} + + +impl From for SyncDuration { + fn from(value: f32) -> Self { + let value = (value * 10.0) as u8; + + match value { + 0 => SyncDuration::Off, + 1 => SyncDuration::Subdivision(1, 128), + 2 => SyncDuration::Subdivision(1, 64), + 3 => SyncDuration::Subdivision(1, 32), + 4 => SyncDuration::Subdivision(1, 16), + 5 => SyncDuration::Subdivision(1, 8), + 6 => SyncDuration::Subdivision(3, 16), + 7 => SyncDuration::Subdivision(1, 4), + 8 => SyncDuration::Subdivision(1, 2), + 9 => SyncDuration::Subdivision(3, 4), + _ => SyncDuration::Subdivision(1, 1), + } + } +} + +impl Display for SyncDuration { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + SyncDuration::Off => "off".to_string(), + SyncDuration::Subdivision(numerator, denominator) => { + match (numerator, denominator) { + (1, 1) => "1".to_string(), + (n, d) => format!("{}/{}", n, d) + } + } + }.fmt(f) + } +} + + +impl SyncDuration { + // this considers a 4/4 signature ( 4 beats per bar ) + + pub fn delay_to_samples(&self, bpm: f64, sample_rate: f32) -> Option { + match self { + SyncDuration::Off => None, + SyncDuration::Subdivision(numerator, denominator) => + Some((((*numerator as f64 / *denominator as f64 * 4.0) / bpm * 60.) as f32 * sample_rate) as usize), + } + } +} \ No newline at end of file From 452713b40d0773dbdd3b5d581ac651dcdde6c660 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 5 Jun 2021 13:57:53 +0200 Subject: [PATCH 41/47] update deps / fix clippy messages --- Cargo.lock | 262 +++++++++++------- arpegiator/Cargo.toml | 18 +- arpegiator/src/midi_messages/device.rs | 8 +- arpegiator/src/parameters.rs | 6 +- arpegiator_pattern_receiver/Cargo.toml | 20 +- arpegiator_pattern_receiver/src/parameters.rs | 6 +- audio_data/Cargo.toml | 6 +- max_note_duration/Cargo.toml | 4 +- max_note_duration/src/parameters.rs | 6 +- midi_delay/Cargo.toml | 4 +- midi_delay/src/lib.rs | 3 +- midi_delay/src/parameters.rs | 6 +- note_fan_out/src/parameters.rs | 14 +- note_generator/src/bin/host.rs | 2 +- note_generator/src/parameters.rs | 8 +- note_off_delay/Cargo.toml | 6 +- note_off_delay/src/bin/note_off_host.rs | 2 +- note_off_delay/src/lib.rs | 1 + note_off_delay/src/parameters.rs | 8 +- util/Cargo.toml | 18 +- util/src/delayed_message_consumer.rs | 2 +- util/src/messages.rs | 51 ++-- util/src/raw_message.rs | 17 +- 23 files changed, 264 insertions(+), 214 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1061af..36c984f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "ansi_term" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ "winapi 0.3.9", ] @@ -95,9 +95,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59740d83946db6a5af71ae25ddf9562c2b176b2ca42cf99a455f09f4a220d6b9" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" dependencies = [ "concurrent-queue", "event-listener", @@ -120,12 +120,15 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "1.4.3" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73079b49cd26b8fd5a15f68fc7707fc78698dc2a3d61430f2a7a9430230dfa04" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" dependencies = [ + "async-channel", "async-executor", "async-io", + "async-mutex", + "blocking", "futures-lite", "num_cpus", "once_cell", @@ -151,6 +154,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + [[package]] name = "async-mutex" version = "1.4.0" @@ -162,15 +174,14 @@ dependencies = [ [[package]] name = "async-std" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f84f1280a2b436a2c77c2582602732b6c2f4321d5494d6e799e6c367859a8" +checksum = "d9f06685bad74e0570f5213741bea82158279a4103d988e57bfada11ad230341" dependencies = [ "async-channel", "async-global-executor", "async-io", - "async-mutex", - "blocking", + "async-lock", "crossbeam-utils 0.8.1", "futures-channel", "futures-core", @@ -233,17 +244,16 @@ dependencies = [ [[package]] name = "base64" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bincode" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder", "serde", ] @@ -269,9 +279,9 @@ dependencies = [ [[package]] name = "build-info" -version = "0.0.20" +version = "0.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69844574061700533300a9b5ddaaa97f474408df3711bb4d469082c0086b0fe2" +checksum = "569d94157949b69d52fd80b77a33cffa083ebcd7abf6d60999b41cc977664a32" dependencies = [ "build-info-common", "build-info-proc", @@ -281,9 +291,9 @@ dependencies = [ [[package]] name = "build-info-build" -version = "0.0.20" +version = "0.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28813baefff56676114a7aa46eb32dd2c1eb077c08c517920a08b75b6c59632a" +checksum = "7975fa865b3562339bdec59ee877d2a8618c4f95dc0e4729f3d501818cae11b1" dependencies = [ "anyhow", "base64", @@ -302,21 +312,21 @@ dependencies = [ [[package]] name = "build-info-common" -version = "0.0.20" +version = "0.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57af7dfb8cd188925b45f04f44cd3545f82cb296f229cf23db3ae8a74bc2031a" +checksum = "9c3210a1a81517312746d7ecad5cde4ad9d3ec20640cb6e0c9900c9c6aa6dea1" dependencies = [ "chrono", "derive_more", - "semver 0.10.0", + "semver", "serde", ] [[package]] name = "build-info-proc" -version = "0.0.20" +version = "0.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25de6ce9c02af741ab76c4c02a7a3f117ebc7d6d96939eb250bbd816163bc936" +checksum = "22b0c07441c4c64c569cbf637e1e181b1c9c934764c7dcaac0ab7d8ffc8e1eba" dependencies = [ "anyhow", "base64", @@ -341,25 +351,40 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" -[[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" - [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +[[package]] +name = "camino" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" +dependencies = [ + "serde", +] + [[package]] name = "cargo_metadata" -version = "0.11.4" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a567c24b86754d629addc2db89e340ac9398d07b5875efcff837e3878e17ec" +checksum = "081e3f0755c1f380c2d010481b6fa2e02973586d5f2b24eebb7a2a1d98b143d8" dependencies = [ - "semver 0.10.0", + "camino", + "cargo-platform", + "semver", + "semver-parser", "serde", "serde_json", ] @@ -399,15 +424,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "concurrent-queue" version = "1.2.2" @@ -511,10 +527,10 @@ dependencies = [ ] [[package]] -name = "difference" -version = "2.0.0" +name = "diff" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "either" @@ -606,9 +622,9 @@ checksum = "611834ce18aaa1bd13c4b374f5d653e1027cf99b6b502584ff8c9a64413b30bb" [[package]] name = "futures-lite" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", @@ -630,6 +646,17 @@ dependencies = [ "wasi 0.9.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + [[package]] name = "gimli" version = "0.23.0" @@ -657,9 +684,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "global_counter" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037cd38cf90be505d4f74eef395d027308d74b5dda77e328ad53244b30566a36" +checksum = "2a77c0a6d353621e0ba32c0eb64a5ed3832d900135712779e38bd2ea31d6509f" dependencies = [ "once_cell", "parking_lot", @@ -718,9 +745,9 @@ dependencies = [ [[package]] name = "ipc-channel" -version = "0.14.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3698b8affd5656032a074a7d40b3c2a29b71971f3e1ff6042b9d40724e20d97c" +checksum = "ad9b32d360ae2d4662212f1d29bc8a277256f49029cea20fd6c182b89819aea7" dependencies = [ "bincode", "crossbeam-channel", @@ -732,6 +759,7 @@ dependencies = [ "serde", "tempfile", "uuid", + "winapi 0.3.9", ] [[package]] @@ -834,20 +862,21 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.3.4" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" dependencies = [ "scopeguard", ] [[package]] name = "log" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -1047,9 +1076,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e9a41747ae4633fce5adffb4d2e81ffc5e89593cb19917f8fb2cc5ff76507bf" +checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512" dependencies = [ "autocfg", "num-integer", @@ -1114,24 +1143,25 @@ checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "parking_lot" -version = "0.10.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" +checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ + "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.7.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" +checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" dependencies = [ - "cfg-if 0.1.10", - "cloudabi", + "cfg-if 1.0.0", + "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.8", "smallvec", "winapi 0.3.9", ] @@ -1142,6 +1172,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "pin-project-lite" version = "0.2.0" @@ -1181,13 +1220,13 @@ checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "pretty_assertions" -version = "0.6.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +checksum = "1cab0e7c02cf376875e9335e0ba1da535775beb5450d21e1dffca068818ed98b" dependencies = [ "ansi_term", "ctor", - "difference", + "diff", "output_vt100", ] @@ -1223,9 +1262,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.24" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -1245,7 +1284,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.16", "libc", "rand_chacha", "rand_core", @@ -1268,7 +1307,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.16", ] [[package]] @@ -1286,6 +1325,15 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "742739e41cd49414de871ea5e549afb7e2a3ac77b589bcbebe8c82fab37147fc" +dependencies = [ + "bitflags", +] + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1303,11 +1351,11 @@ checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" [[package]] name = "rustc_version" -version = "0.2.3" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ - "semver 0.9.0", + "semver", ] [[package]] @@ -1324,18 +1372,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "394cec28fa623e00903caf7ba4fa6fb9a0e260280bb8cdbbba029611108a0190" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ "semver-parser", "serde", @@ -1343,24 +1382,27 @@ dependencies = [ [[package]] name = "semver-parser" -version = "0.7.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] [[package]] name = "serde" -version = "1.0.118" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.118" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", @@ -1380,9 +1422,9 @@ dependencies = [ [[package]] name = "simplelog" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720" +checksum = "59d0fe306a0ced1c88a58042dc22fc2ddd000982c26d75f6aa09a394547c41e0" dependencies = [ "chrono", "log", @@ -1397,15 +1439,15 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" +checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" [[package]] name = "syn" -version = "1.0.51" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b4f34193997d92804d359ed09953e25d5138df6bcc055a71bf68ee89fdf9223" +checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", @@ -1421,7 +1463,7 @@ dependencies = [ "cfg-if 0.1.10", "libc", "rand", - "redox_syscall", + "redox_syscall 0.1.57", "remove_dir_all", "winapi 0.3.9", ] @@ -1461,6 +1503,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -1516,11 +1564,21 @@ dependencies = [ [[package]] name = "uuid" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "rand", + "getrandom 0.2.3", +] + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd320e1520f94261153e96f7534476ad869c14022aee1e59af7c778075d840ae" +dependencies = [ + "ctor", + "version_check", ] [[package]] diff --git a/arpegiator/Cargo.toml b/arpegiator/Cargo.toml index 0fbd98f..f24d375 100644 --- a/arpegiator/Cargo.toml +++ b/arpegiator/Cargo.toml @@ -7,20 +7,20 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } -build-info = "0.0.20" -log = "0.4.11" +build-info = "0.0.23" +log = "0.4.14" num-traits = "0.2.14" itertools = "0.10.0" -bincode = "1.3.1" +bincode = "1.3.3" midir = "0.7.0" -async-std = "1.8.0" -async-channel = "1.5.1" -futures-lite = "1.11.3" -ipc-channel = "0.14.1" -uuid = "0.8.1" +async-std = "1.9.0" +async-channel = "1.6.1" +futures-lite = "1.12.0" +ipc-channel = "0.15.0" +uuid = "0.8.2" [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" [lib] name = "arpegiator" diff --git a/arpegiator/src/midi_messages/device.rs b/arpegiator/src/midi_messages/device.rs index e7ba403..ecb5f9c 100644 --- a/arpegiator/src/midi_messages/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -303,13 +303,7 @@ impl Device { let add_note = output.remove(position); if let Some(position) = output.iter().position(|change| { - match change { - DeviceChange::NoteExpressionChange { time: time_2, expression, note: note_2 } - if time_1 == *time_2 && note_1.channel == note_2.channel => { - matches!(expression, Expression::PitchBend) - } - _ => false - } + matches!(change, DeviceChange::NoteExpressionChange { time: time_2, expression: Expression::PitchBend, note: note_2 } if time_1 == *time_2 && note_1.channel == note_2.channel) }) { output.remove(position); } diff --git a/arpegiator/src/parameters.rs b/arpegiator/src/parameters.rs index 3a13bac..0363dfc 100644 --- a/arpegiator/src/parameters.rs +++ b/arpegiator/src/parameters.rs @@ -141,9 +141,9 @@ impl From for Parameter { } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(p: Parameter) -> Self { + p as i32 } } diff --git a/arpegiator_pattern_receiver/Cargo.toml b/arpegiator_pattern_receiver/Cargo.toml index 247b3d6..8cacf4e 100644 --- a/arpegiator_pattern_receiver/Cargo.toml +++ b/arpegiator_pattern_receiver/Cargo.toml @@ -7,25 +7,25 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } -build-info = "0.0.20" -log = "0.4.11" -serde = "1.0.118" -bincode = "1.3.1" -async-std = "1.8.0" -async-channel = "1.5.1" -futures-lite = "1.11.3" -ipc-channel = "0.14.1" +build-info = "0.0.23" +log = "0.4.14" +serde = "1.0.126" +bincode = "1.3.3" +async-std = "1.9.0" +async-channel = "1.6.1" +futures-lite = "1.12.0" +ipc-channel = "0.15.0" itertools = "0.10.0" [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" [lib] name = "arpegiator_pattern_receiver" crate-type = ["cdylib", "lib"] [target.'cfg(target_os = "macos")'.dependencies.mach] -version = "0.3" +version = "0.3.2" [features] # this transmits midi via the vst output instead of using an IPC, setting the highest bit to 0 so we transmit diff --git a/arpegiator_pattern_receiver/src/parameters.rs b/arpegiator_pattern_receiver/src/parameters.rs index 17f8284..b2905db 100644 --- a/arpegiator_pattern_receiver/src/parameters.rs +++ b/arpegiator_pattern_receiver/src/parameters.rs @@ -56,9 +56,9 @@ impl From for Parameter { } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(p: Parameter) -> Self { + p as i32 } } diff --git a/audio_data/Cargo.toml b/audio_data/Cargo.toml index 41eeaf9..1405439 100644 --- a/audio_data/Cargo.toml +++ b/audio_data/Cargo.toml @@ -7,11 +7,11 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } -build-info = "0.0.20" -log = "0.4.11" +build-info = "0.0.23" +log = "0.4.14" [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" [lib] name = "audio_data" diff --git a/max_note_duration/Cargo.toml b/max_note_duration/Cargo.toml index c558771..9a9efc0 100644 --- a/max_note_duration/Cargo.toml +++ b/max_note_duration/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } -build-info = "0.0.20" +build-info = "0.0.23" [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" [lib] name = "max_note_duration" diff --git a/max_note_duration/src/parameters.rs b/max_note_duration/src/parameters.rs index b749579..cc99df9 100644 --- a/max_note_duration/src/parameters.rs +++ b/max_note_duration/src/parameters.rs @@ -27,9 +27,9 @@ impl From for Parameter { } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(p: Parameter) -> Self { + p as i32 } } diff --git a/midi_delay/Cargo.toml b/midi_delay/Cargo.toml index 7b94bdd..9add6d6 100644 --- a/midi_delay/Cargo.toml +++ b/midi_delay/Cargo.toml @@ -8,11 +8,11 @@ edition = "2018" vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } log = "0.4.11" -build-info = "0.0.20" +build-info = "0.0.23" [lib] name = "midi_delay" crate-type = ["cdylib", "lib"] [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" diff --git a/midi_delay/src/lib.rs b/midi_delay/src/lib.rs index adba9dd..a697047 100644 --- a/midi_delay/src/lib.rs +++ b/midi_delay/src/lib.rs @@ -15,8 +15,7 @@ use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin, PluginParameters} use parameters::{MidiDelayParameters, Parameter}; use util::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; -use util::delayed_message_consumer::{process_scheduled_events, MessageReason, MaxNotesParameter, raw_process_scheduled_events}; -use util::midi_message_type::MidiMessageType; +use util::delayed_message_consumer::raw_process_scheduled_events; use util::parameters::ParameterConversion; use vst::host::Host; use util::logging::logging_setup; diff --git a/midi_delay/src/parameters.rs b/midi_delay/src/parameters.rs index ecb864a..4e1a20e 100644 --- a/midi_delay/src/parameters.rs +++ b/midi_delay/src/parameters.rs @@ -25,9 +25,9 @@ impl From for Parameter { } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(p: Parameter) -> Self { + p as i32 } } diff --git a/note_fan_out/src/parameters.rs b/note_fan_out/src/parameters.rs index 8a28322..f08a83d 100644 --- a/note_fan_out/src/parameters.rs +++ b/note_fan_out/src/parameters.rs @@ -36,14 +36,14 @@ impl From for Parameter { 0 => Parameter::Steps, 1 => Parameter::Selection, 2 => Parameter::ChannelDistribute, - _ => panic!(format!("No such Parameter {}", i)), + _ => panic!("No such Parameter {}", i), } } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(p: Parameter) -> Self { + p as i32 } } @@ -89,9 +89,9 @@ impl From for ChannelDistribution { } } -impl Into for ChannelDistribution { - fn into(self) -> f32 { - match self { +impl From for f32 { + fn from(cd: ChannelDistribution) -> f32 { + match cd { ChannelDistribution::Channels(value) => { // normalize over 15 values, first range is off ((value as f32 - 2.) / 13.) * 14. / 15. + 1. / 15. diff --git a/note_generator/src/bin/host.rs b/note_generator/src/bin/host.rs index b59cd33..b455c90 100644 --- a/note_generator/src/bin/host.rs +++ b/note_generator/src/bin/host.rs @@ -33,7 +33,7 @@ fn main() { // Load the plugin let mut loader = PluginLoader::load(path, Arc::clone(&host)) - .unwrap_or_else(|e| panic!(format!("Failed to load plugin: {} {}", e, arg))); + .unwrap_or_else(|e| panic!("Failed to load plugin: {} {}", e, arg)); // Create an instance of the plugin let mut instance = loader.instance().unwrap(); diff --git a/note_generator/src/parameters.rs b/note_generator/src/parameters.rs index f22abea..11691e4 100644 --- a/note_generator/src/parameters.rs +++ b/note_generator/src/parameters.rs @@ -36,14 +36,14 @@ impl From for Parameter { 6 => Parameter::Trigger, 7 => Parameter::TriggeredPitch, 8 => Parameter::TriggeredChannel, - _ => panic!(format!("No such Parameter {}", i)), + _ => panic!("No such Parameter {}", i), } } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(parameter: Parameter) -> Self { + parameter as i32 } } diff --git a/note_off_delay/Cargo.toml b/note_off_delay/Cargo.toml index 3fd68ba..6ff9fe4 100644 --- a/note_off_delay/Cargo.toml +++ b/note_off_delay/Cargo.toml @@ -7,11 +7,11 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } util = { path = "../util" } -build-info = "0.0.20" -log = "0.4.11" +build-info = "0.0.23" +log = "0.4.14" [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" [lib] name = "note_off_delay" diff --git a/note_off_delay/src/bin/note_off_host.rs b/note_off_delay/src/bin/note_off_host.rs index c96bb5f..9b43311 100644 --- a/note_off_delay/src/bin/note_off_host.rs +++ b/note_off_delay/src/bin/note_off_host.rs @@ -33,7 +33,7 @@ fn main() { // Load the plugin let mut loader = PluginLoader::load(path, Arc::clone(&host)) - .unwrap_or_else(|e| panic!(format!("Failed to load plugin: {} {}", e, arg))); + .unwrap_or_else(|e| panic!("Failed to load plugin: {} {}", e, arg)); // Create an instance of the plugin let mut instance = loader.instance().unwrap(); diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 27ebb54..48cd2b5 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -68,6 +68,7 @@ impl NoteOffDelayPlugin { 1.0 / self.sample_rate } + #[allow(dead_code)] fn seconds_to_samples(&self, seconds: f32) -> usize { (seconds * self.sample_rate) as usize } diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index 0103b6d..5a148d8 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -5,7 +5,7 @@ use vst::util::ParameterTransfer; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; use util::parameters::{ParameterConversion, get_exponential_scale_value}; -use util::{duration_display, HostCallbackLock, DelayOffset}; +use util::{HostCallbackLock, DelayOffset}; use util::delayed_message_consumer::MaxNotesParameter; use std::fmt::{Display, Formatter}; use std::fmt; @@ -37,9 +37,9 @@ impl From for Parameter { } } -impl Into for Parameter { - fn into(self) -> i32 { - self as i32 +impl From for i32 { + fn from(p: Parameter) -> Self { + p as i32 } } diff --git a/util/Cargo.toml b/util/Cargo.toml index f0dcb6d..c2ef3d9 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -6,21 +6,21 @@ edition = "2018" [dependencies] vst = { git = "https://github.com/rustaudio/vst-rs" } -global_counter = "0.2.1" -log = "0.4.11" -simplelog = "0.9.0" -build-info = "0.0.20" -serde = "1.0.118" -ipc-channel = "0.14.1" -async-channel = "1.5.1" -uuid = "0.8.1" +global_counter = "0.2.2" +log = "0.4.14" +simplelog = "0.10.0" +build-info = "0.0.23" +serde = "1.0.126" +ipc-channel = "0.15.0" +async-channel = "1.6.1" +uuid = "0.8.2" [dependencies.log-panics] version = "2.0.0" features = ["with-backtrace"] [build-dependencies] -build-info-build = "0.0.20" +build-info-build = "0.0.23" [features] default = ["enable_logging"] diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index bc095e4..0cd683c 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -266,7 +266,7 @@ pub fn raw_process_scheduled_events( let mut queued_messages = AbsoluteTimeMidiMessageVector::default(); let mut events: Vec = vec![]; - for mut message in messages.iter().copied() { + for message in messages.iter().copied() { if message.play_time_in_samples < current_time_in_samples + samples { if message.play_time_in_samples >= current_time_in_samples { events.push(message.new_midi_event(current_time_in_samples)); diff --git a/util/src/messages.rs b/util/src/messages.rs index 96806af..dbc5bbb 100644 --- a/util/src/messages.rs +++ b/util/src/messages.rs @@ -55,9 +55,9 @@ pub struct NoteOn { pub velocity: u8, } -impl Into for NoteOn { - fn into(self) -> RawMessage { - [NOTE_ON + self.channel, self.pitch, self.velocity].into() +impl From for RawMessage { + fn from(note_on: NoteOn) -> Self { + RawMessage([NOTE_ON + note_on.channel, note_on.pitch, note_on.velocity]) } } @@ -121,9 +121,9 @@ impl From for NoteOff { } } -impl Into for NoteOff { - fn into(self) -> RawMessage { - [NOTE_OFF + self.channel, self.pitch, self.velocity].into() +impl From for RawMessage { + fn from(note_off: NoteOff) -> Self { + RawMessage([NOTE_OFF + note_off.channel, note_off.pitch, note_off.velocity]) } } @@ -148,9 +148,9 @@ pub struct Pressure { pub value: u8, } -impl Into for Pressure { - fn into(self) -> RawMessage { - [PRESSURE + self.channel, self.value, 0].into() +impl From for RawMessage { + fn from(pressure: Pressure) -> Self { + RawMessage([PRESSURE + pressure.channel, pressure.value, 0]) } } @@ -180,14 +180,14 @@ impl ChannelMessage for PitchBend { } } -impl Into for PitchBend { - fn into(self) -> RawMessage { +impl From for RawMessage { + fn from(pitch_bend: PitchBend) -> Self { // 96000 millisemitones are expressed over the possible values of 14 bits ( 16384 ) // which never gets us an exact integer amount of semitones - let value = ((self.millisemitones + 48000) * 16384) / 96000; + let value = ((pitch_bend.millisemitones + 48000) * 16384) / 96000; let msb = value >> 7; let lsb = value & 0x7F; - [self.channel + PITCHBEND, lsb as u8, msb as u8].into() + RawMessage([pitch_bend.channel + PITCHBEND, lsb as u8, msb as u8]) } } @@ -218,9 +218,9 @@ impl ChannelMessage for AfterTouch { } } -impl Into for AfterTouch { - fn into(self) -> RawMessage { - [self.channel + AFTERTOUCH, self.pitch, self.value].into() +impl From for RawMessage { + fn from(aftertouch: AfterTouch) -> Self { + RawMessage([aftertouch.channel + AFTERTOUCH, aftertouch.pitch, aftertouch.value]) } } @@ -240,9 +240,9 @@ pub struct CC { pub value: u8, } -impl Into for CC { - fn into(self) -> RawMessage { - [0xB0 + self.channel, self.cc, self.value].into() +impl From for RawMessage { + fn from(cc: CC) -> Self { + RawMessage([0xB0 + cc.channel, cc.cc, cc.value]) } } @@ -287,13 +287,12 @@ pub struct Timbre { pub value: u8, } -impl Into for Timbre { - fn into(self) -> RawMessage { - CC { - channel: self.channel, +impl From for RawMessage { + fn from(timbre: Timbre) -> Self { + RawMessage::from(CC { + channel: timbre.channel, cc: TIMBRECC, - value: self.value, - } - .into() + value: timbre.value, + }) } } diff --git a/util/src/raw_message.rs b/util/src/raw_message.rs index 9be7935..8e5a7eb 100644 --- a/util/src/raw_message.rs +++ b/util/src/raw_message.rs @@ -1,13 +1,13 @@ use super::messages::ChannelMessage; use crate::constants::PRESSURE; use core::clone::Clone; -use core::convert::{From, Into}; +use core::convert::From; use core::ops::Index; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; #[derive(Copy, Debug, Serialize, Deserialize)] -pub struct RawMessage([u8; 3]); +pub struct RawMessage(pub [u8; 3]); impl RawMessage { @@ -43,14 +43,13 @@ impl From<[u8; 3]> for RawMessage { } } -impl Into<[u8; 3]> for RawMessage { - fn into(self) -> [u8; 3] { - // attempt of stopping crash at pressure - // TODO log what is output - if self.0[0] & 0xF0 == PRESSURE { - [self.0[0],self.0[1],0] + +impl From for [u8; 3] { + fn from(raw_message: RawMessage) -> Self { + if raw_message.0[0] & 0xF0 == PRESSURE { + [raw_message.0[0],raw_message.0[1],0] } else { - self.0 + raw_message.0 } } } From 5ed5ad34c3d70291b9e675206251df1e48c3b019 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 19 Jun 2021 09:10:16 +0200 Subject: [PATCH 42/47] apply delay only for notes above duration threshold --- note_off_delay/src/lib.rs | 5 ++-- note_off_delay/src/parameters.rs | 41 +++++++++++++++++++++----------- util/src/lib.rs | 15 ++++++------ 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 48cd2b5..7f2adbb 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -20,6 +20,7 @@ use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::messages::format_event; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; +use crate::parameters::PARAMETER_COUNT; plugin_main!(NoteOffDelayPlugin); @@ -90,7 +91,7 @@ impl Plugin for NoteOffDelayPlugin { name: "Note Off Delay".to_string(), vendor: "DJ Crontab".to_string(), unique_id: 234213173, - parameters: 4, + parameters: PARAMETER_COUNT as i32, category: Category::Effect, initial_delay: 0, version: 1, @@ -188,7 +189,7 @@ impl Plugin for NoteOffDelayPlugin { None => {} Some(note_on) => { let duration = note_off_play_time - note_on.play_time_in_samples; - match self.parameters.get_delay().apply(duration, self.sample_rate) { + match delay.apply(duration, self.sample_rate) { None => panic!("delay is supposed to be active"), Some(new_duration) => { // send two times the note off, the live one will be only used to mark the note on as delayed diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index 5a148d8..fac2cb3 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -5,12 +5,12 @@ use vst::util::ParameterTransfer; use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; use util::parameters::{ParameterConversion, get_exponential_scale_value}; -use util::{HostCallbackLock, DelayOffset}; +use util::{HostCallbackLock, Duration}; use util::delayed_message_consumer::MaxNotesParameter; use std::fmt::{Display, Formatter}; use std::fmt; -const PARAMETER_COUNT: usize = 4; +pub const PARAMETER_COUNT: usize = 5; pub struct NoteOffDelayPluginParameters { pub host_mutex: Mutex, @@ -23,6 +23,7 @@ pub enum Parameter { MaxNotes, MaxNotesAppliesToDelayedNotesOnly, MultiplyLength, + Threshold } impl From for Parameter { @@ -32,6 +33,7 @@ impl From for Parameter { 1 => Parameter::MaxNotes, 2 => Parameter::MaxNotesAppliesToDelayedNotesOnly, 3 => Parameter::MultiplyLength, + 4 => Parameter::Threshold, _ => panic!("no such parameter {}", i), } } @@ -71,8 +73,9 @@ impl NoteOffDelayPluginParameters { pub fn get_delay(&self) -> Delay { Delay { - offset: DelayOffset::from(self.get_parameter(Parameter::DelayOffset.into())), - multiplier: DelayMultiplier::from(self.get_parameter(Parameter::MultiplyLength.into())) + offset: Duration::from(self.get_parameter(Parameter::DelayOffset.into())), + multiplier: DelayMultiplier::from(self.get_parameter(Parameter::MultiplyLength.into())), + threshold: Duration::from(self.get_parameter(Parameter::Threshold.into())), } } } @@ -88,8 +91,9 @@ impl Default for NoteOffDelayPluginParameters { pub struct Delay { - pub offset: DelayOffset, + pub offset: Duration, pub multiplier: DelayMultiplier, + pub threshold: Duration } @@ -100,19 +104,26 @@ pub enum DelayMultiplier { impl Delay { pub fn is_active(&self) -> bool { - !matches!((&self.offset, &self.multiplier), (DelayOffset::Off, DelayMultiplier::Off)) + !matches!((&self.offset, &self.multiplier), (Duration::Off, DelayMultiplier::Off)) } pub fn apply(&self, duration_in_samples: usize, sample_rate: f32) -> Option { if self.is_active() { + if let Duration::Duration(threshold) = self.threshold { + let threshold_in_samples = (threshold * sample_rate) as usize; + if duration_in_samples < threshold_in_samples { + return Some(duration_in_samples) + } + } + let duration_in_samples = match self.multiplier { DelayMultiplier::Off => duration_in_samples as f32, DelayMultiplier::Multiplier(x) => x * duration_in_samples as f32 }; Some(match self.offset { - DelayOffset::Off => duration_in_samples as usize, - DelayOffset::Duration(x) => (duration_in_samples + x * sample_rate) as usize + Duration::Off => duration_in_samples as usize, + Duration::Duration(x) => (duration_in_samples + x * sample_rate) as usize }) } else { None @@ -145,13 +156,14 @@ impl From for DelayMultiplier { impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn get_parameter_text(&self, index: i32) -> String { + let value = self.get_parameter(index); match index.into() { - Parameter::DelayOffset => { - DelayOffset::from(self.get_parameter(Parameter::DelayOffset as i32)).to_string() + Parameter::DelayOffset | Parameter::Threshold => { + Duration::from(value).to_string() } Parameter::MaxNotes => { - if self.get_parameter(Parameter::MaxNotes as i32) == 0.0 { + if value == 0.0 { "Off".to_string() } else { format!("{}", self.get_max_notes()) @@ -166,7 +178,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { }.to_string() } Parameter::MultiplyLength => { - DelayMultiplier::from(self.get_parameter(Parameter::MultiplyLength as i32)).to_string() + DelayMultiplier::from(value).to_string() } } } @@ -176,7 +188,8 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { Parameter::DelayOffset => "Delay", Parameter::MaxNotes => "Max Notes", Parameter::MaxNotesAppliesToDelayedNotesOnly => "Apply max notes to delayed notes only", - Parameter::MultiplyLength => "Length multiplier" + Parameter::MultiplyLength => "Length multiplier", + Parameter::Threshold => "Threshold" } .to_string() } @@ -187,7 +200,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn set_parameter(&self, index: i32, value: f32) { match index.into() { - Parameter::DelayOffset | Parameter::MultiplyLength => { + Parameter::DelayOffset | Parameter::MultiplyLength | Parameter::Threshold => { let old_value = self.get_parameter(index); if (value - old_value).abs() > 0.0001 || (value == 0.0 && old_value != 0.0) { self.transfer.set_parameter(index as usize, value) diff --git a/util/src/lib.rs b/util/src/lib.rs index 6292a81..41b4dec 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -51,27 +51,28 @@ pub fn duration_display(seconds: f32) -> String { out } -impl Display for DelayOffset { +impl Display for Duration { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - DelayOffset::Off => "off".to_string(), - DelayOffset::Duration(seconds) => { + Duration::Off => "off".to_string(), + Duration::Duration(seconds) => { duration_display(*seconds) } }.fmt(f) } } -pub enum DelayOffset { +pub enum Duration { Off, Duration(f32), } -impl From for DelayOffset { + +impl From for Duration { fn from(parameter_value: f32) -> Self { match get_exponential_scale_value(parameter_value, 10., 20.) { - x if x == 0.0 => DelayOffset::Off, - value => DelayOffset::Duration(value) + x if x == 0.0 => Duration::Off, + value => Duration::Duration(value) } } } From ea675cc85e9f13bb73da0226c5ce7a3f6803ae75 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 20 Jun 2021 00:08:37 +0200 Subject: [PATCH 43/47] add tests --- note_off_delay/src/lib.rs | 30 ++++++---- note_off_delay/src/parameters.rs | 50 +++++++---------- note_off_delay/src/tests.rs | 94 ++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 41 deletions(-) create mode 100644 note_off_delay/src/tests.rs diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 7f2adbb..8884987 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -1,4 +1,5 @@ mod parameters; +mod tests; #[macro_use] extern crate vst; @@ -9,7 +10,7 @@ use std::sync::Arc; use vst::api::Events; use vst::buffer::{AudioBuffer, SendEventBuffer}; -use vst::event::Event; +use vst::event::{Event, MidiEvent}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; use parameters::NoteOffDelayPluginParameters; @@ -45,19 +46,24 @@ impl Default for NoteOffDelayPlugin { } impl NoteOffDelayPlugin { + fn process_scheduled_events(&self, samples: usize) -> (AbsoluteTimeMidiMessageVector, Vec) { + process_scheduled_events( + samples, + self.current_time_in_samples, + &self.message_queue, + self.parameters.get_max_notes(), + self.parameters + .get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), + self.parameters.get_delay().is_active(), + ) + } + fn send_events(&mut self, samples: usize) { + let (next_message_queue, events) = self.process_scheduled_events(samples); + + self.message_queue = next_message_queue; + if let Ok(mut host_callback_lock) = self.parameters.host_mutex.lock() { - let (next_message_queue, events) = process_scheduled_events( - samples, - self.current_time_in_samples, - &self.message_queue, - self.parameters.get_max_notes(), - self.parameters - .get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), - self.parameters.get_delay().is_active(), - ); - - self.message_queue = next_message_queue; self.send_buffer .borrow_mut() .send_events(events, &mut host_callback_lock.host); diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index fac2cb3..bd98cc3 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -3,12 +3,12 @@ use std::sync::Mutex; use vst::plugin::{HostCallback, PluginParameters}; use vst::util::ParameterTransfer; -use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; -use util::parameters::{ParameterConversion, get_exponential_scale_value}; -use util::{HostCallbackLock, Duration}; -use util::delayed_message_consumer::MaxNotesParameter; -use std::fmt::{Display, Formatter}; use std::fmt; +use std::fmt::{Display, Formatter}; +use util::delayed_message_consumer::MaxNotesParameter; +use util::parameter_value_conversion::{f32_to_bool, f32_to_byte}; +use util::parameters::{get_exponential_scale_value, ParameterConversion}; +use util::{Duration, HostCallbackLock}; pub const PARAMETER_COUNT: usize = 5; @@ -23,7 +23,7 @@ pub enum Parameter { MaxNotes, MaxNotesAppliesToDelayedNotesOnly, MultiplyLength, - Threshold + Threshold, } impl From for Parameter { @@ -55,7 +55,6 @@ impl ParameterConversion for NoteOffDelayPluginParameters { } } - impl NoteOffDelayPluginParameters { pub fn new(host: HostCallback) -> Self { NoteOffDelayPluginParameters { @@ -67,7 +66,7 @@ impl NoteOffDelayPluginParameters { pub fn get_max_notes(&self) -> MaxNotesParameter { match self.get_byte_parameter(Parameter::MaxNotes) / 4 { 0 => MaxNotesParameter::Infinite, - i => MaxNotesParameter::Limited(i) + i => MaxNotesParameter::Limited(i), } } @@ -89,14 +88,12 @@ impl Default for NoteOffDelayPluginParameters { } } - pub struct Delay { pub offset: Duration, pub multiplier: DelayMultiplier, - pub threshold: Duration + pub threshold: Duration, } - pub enum DelayMultiplier { Off, Multiplier(f32), @@ -112,18 +109,18 @@ impl Delay { if let Duration::Duration(threshold) = self.threshold { let threshold_in_samples = (threshold * sample_rate) as usize; if duration_in_samples < threshold_in_samples { - return Some(duration_in_samples) + return Some(duration_in_samples); } } let duration_in_samples = match self.multiplier { DelayMultiplier::Off => duration_in_samples as f32, - DelayMultiplier::Multiplier(x) => x * duration_in_samples as f32 + DelayMultiplier::Multiplier(x) => x * duration_in_samples as f32, }; Some(match self.offset { Duration::Off => duration_in_samples as usize, - Duration::Duration(x) => (duration_in_samples + x * sample_rate) as usize + Duration::Duration(x) => (duration_in_samples + x * sample_rate) as usize, }) } else { None @@ -131,7 +128,6 @@ impl Delay { } } - impl Display for DelayMultiplier { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -139,17 +135,16 @@ impl Display for DelayMultiplier { DelayMultiplier::Multiplier(multiplier) => { format!("{:.3}x", multiplier) } - }.fmt(f) + } + .fmt(f) } } - - impl From for DelayMultiplier { fn from(parameter_value: f32) -> Self { match get_exponential_scale_value(parameter_value, 19., 20.) { x if x == 0.0 => DelayMultiplier::Off, - value => DelayMultiplier::Multiplier(1.0 + value) + value => DelayMultiplier::Multiplier(1.0 + value), } } } @@ -158,9 +153,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { fn get_parameter_text(&self, index: i32) -> String { let value = self.get_parameter(index); match index.into() { - Parameter::DelayOffset | Parameter::Threshold => { - Duration::from(value).to_string() - } + Parameter::DelayOffset | Parameter::Threshold => Duration::from(value).to_string(), Parameter::MaxNotes => { if value == 0.0 { @@ -175,11 +168,10 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { "On" } else { "Off" - }.to_string() - } - Parameter::MultiplyLength => { - DelayMultiplier::from(value).to_string() + } + .to_string() } + Parameter::MultiplyLength => DelayMultiplier::from(value).to_string(), } } @@ -189,9 +181,9 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { Parameter::MaxNotes => "Max Notes", Parameter::MaxNotesAppliesToDelayedNotesOnly => "Apply max notes to delayed notes only", Parameter::MultiplyLength => "Length multiplier", - Parameter::Threshold => "Threshold" + Parameter::Threshold => "Threshold", } - .to_string() + .to_string() } fn get_parameter(&self, index: i32) -> f32 { @@ -211,7 +203,7 @@ impl vst::plugin::PluginParameters for NoteOffDelayPluginParameters { let byte_value = f32_to_byte(value); let max_notes = match byte_value / 4 { 0 => MaxNotesParameter::Infinite, - i => MaxNotesParameter::Limited(i) + i => MaxNotesParameter::Limited(i), }; if max_notes != old_value { self.set_byte_parameter(Parameter::MaxNotes, byte_value); diff --git a/note_off_delay/src/tests.rs b/note_off_delay/src/tests.rs new file mode 100644 index 0000000..0487d64 --- /dev/null +++ b/note_off_delay/src/tests.rs @@ -0,0 +1,94 @@ +#[cfg(test)] +mod test { + use crate::parameters::Parameter; + use crate::NoteOffDelayPlugin; + use core::mem; + use std::convert::TryFrom; + use vst::api::{Event, EventType, Events, MidiEvent}; + use vst::plugin::{Plugin, PluginParameters}; + + fn make_event(message: [u8; 3], delta_frames: i32) -> Event { + let midi_event: MidiEvent = MidiEvent { + event_type: EventType::Midi, + byte_size: mem::size_of::() as i32, + delta_frames, + flags: 0, + note_length: 0, + note_offset: 0, + midi_data: [message[0], message[1], message[2]], + _midi_reserved: 0, + detune: 0, + note_off_velocity: 0, + _reserved1: 0, + _reserved2: 0, + }; + let mut event: Event = unsafe { std::mem::transmute(midi_event) }; + event.event_type = EventType::Midi; + + event + } + + #[test] + fn test_process_scheduled_events() { + let original = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; + let expected = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 725)]; + + assert_process_scheduled_events(original, expected, 0.01) + } + + #[test] + fn test_process_scheduled_events_feature_off() { + let original = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; + let expected = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; + + assert_process_scheduled_events(original, expected, 0.0) + } + + #[test] + fn test_process_scheduled_events_next() { + let original = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; + let expected = vec![([0x90, 0x60, 0x60], 10)]; + + assert_process_scheduled_events(original, expected, 0.8) + } + + fn compare(events: Vec, expected: Vec<([u8; 3], i32)>) { + assert_eq!(events.len(), expected.len()); + for ((test_data, test_delta_frame), (expected_data, expected_delta_frame)) in events + .iter() + .map(|event| (event.data, event.delta_frames)) + .zip(expected.iter()) + { + assert_eq!(test_data, *expected_data); + assert_eq!(test_delta_frame, *expected_delta_frame); + } + } + + fn assert_process_scheduled_events( + original: Vec<([u8; 3], i32)>, + expected: Vec<([u8; 3], i32)>, + delay_parameter_value: f32, + ) { + let mut plugin = NoteOffDelayPlugin::default(); + plugin + .parameters + .set_parameter(i32::from(Parameter::DelayOffset), delay_parameter_value); + + let mut original_as_events: Vec = original + .iter() + .map(|(raw_message, delta_frame)| make_event(*raw_message, *delta_frame)) + .collect(); + + let events: Vec<*mut Event> = original_as_events.iter_mut().map(|x| x as *mut Event).collect(); + let events = Events { + num_events: events.len() as i32, + _reserved: 0, + events: <[*mut Event; 2]>::try_from(events).unwrap(), + }; + + plugin.process_events(&events); + let (_, events) = plugin.process_scheduled_events(1024); + + compare(events, expected); + } +} From 1308a2d8e496b78a400ef52c7ef348b4476d3083 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 20 Jun 2021 02:25:48 +0200 Subject: [PATCH 44/47] refactor scheduling as a stateful struct --- note_off_delay/src/lib.rs | 35 ++-- util/src/delayed_message_consumer.rs | 233 ++++++++++++++------------- 2 files changed, 142 insertions(+), 126 deletions(-) diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 8884987..8a1c22b 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -13,15 +13,15 @@ use vst::buffer::{AudioBuffer, SendEventBuffer}; use vst::event::{Event, MidiEvent}; use vst::plugin::{CanDo, Category, HostCallback, Info, Plugin}; +use crate::parameters::PARAMETER_COUNT; use parameters::NoteOffDelayPluginParameters; use parameters::Parameter; use util::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; +use util::delayed_message_consumer::{MessageReason, ScheduledEventsHelper}; use util::logging::logging_setup; -use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::messages::format_event; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; -use crate::parameters::PARAMETER_COUNT; plugin_main!(NoteOffDelayPlugin); @@ -47,20 +47,19 @@ impl Default for NoteOffDelayPlugin { impl NoteOffDelayPlugin { fn process_scheduled_events(&self, samples: usize) -> (AbsoluteTimeMidiMessageVector, Vec) { - process_scheduled_events( + let helper = ScheduledEventsHelper::new( samples, - self.current_time_in_samples, - &self.message_queue, + self.parameters.get_delay().is_active(), self.parameters.get_max_notes(), self.parameters .get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), - self.parameters.get_delay().is_active(), - ) + self.current_time_in_samples, + ); + helper.process_scheduled_events(&self.message_queue) } fn send_events(&mut self, samples: usize) { let (next_message_queue, events) = self.process_scheduled_events(samples); - self.message_queue = next_message_queue; if let Ok(mut host_callback_lock) = self.parameters.host_mutex.lock() { @@ -114,7 +113,10 @@ impl Plugin for NoteOffDelayPlugin { fn new(host: HostCallback) -> Self { logging_setup(); - info!("{}", build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp)); + info!( + "{}", + build_info::format!("{{{} v{} built with {} at {}}}", $.crate_info.name, $.crate_info.version, $.compiler, $.timestamp) + ); let parameters = NoteOffDelayPluginParameters::new(host); NoteOffDelayPlugin { current_time_in_samples: 0, @@ -182,16 +184,17 @@ impl Plugin for NoteOffDelayPlugin { MidiMessageType::NoteOffMessage(note_off) => { let note_off_play_time = midi_event.delta_frames as usize + self.current_time_in_samples; - self.message_queue.insert_message( - midi_event.data, - note_off_play_time, - MessageReason::Live, - ); + self.message_queue + .insert_message(midi_event.data, note_off_play_time, MessageReason::Live); - let delay = self.parameters.get_delay() ; + let delay = self.parameters.get_delay(); if delay.is_active() { - match self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch).cloned() { + match self + .message_queue + .get_matching_note_on(note_off.channel, note_off.pitch) + .cloned() + { None => {} Some(note_on) => { let duration = note_off_play_time - note_on.play_time_in_samples; diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index 0cd683c..b0fed2b 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -8,8 +8,8 @@ use super::absolute_time_midi_message::AbsoluteTimeMidiMessage; use super::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; use super::messages::NoteOff; use super::midi_message_type::MidiMessageType; -use std::fmt::{Display, Formatter}; use std::fmt; +use std::fmt::{Display, Formatter}; #[derive(Hash, Clone, Copy, PartialEq, Eq)] struct PlayingNoteIndex { @@ -17,41 +17,38 @@ struct PlayingNoteIndex { pitch: u8, } -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Clone)] pub enum MaxNotesParameter { Infinite, - Limited(u8) + Limited(u8), } impl Display for MaxNotesParameter { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { MaxNotesParameter::Infinite => "Infinite".to_string(), - MaxNotesParameter::Limited(x) => x.to_string() - }.fmt(f) + MaxNotesParameter::Limited(x) => x.to_string(), + } + .fmt(f) } } - impl MaxNotesParameter { pub fn should_limit(&self, currently_playing: usize) -> bool { match self { MaxNotesParameter::Infinite => false, - MaxNotesParameter::Limited(limit) => { - currently_playing >= *limit as usize - } + MaxNotesParameter::Limited(limit) => currently_playing >= *limit as usize, } } } - #[derive(Clone, Copy, PartialEq)] pub enum MessageReason { Live, Delayed, // the same event will exist live and delayed MaxNotes, Retrigger, - PlayUnprocessed + PlayUnprocessed, } #[derive(Default)] @@ -85,45 +82,65 @@ impl PlayingNotes { } } -pub fn process_scheduled_events( - samples: usize, - current_time_in_samples: usize, - messages: &AbsoluteTimeMidiMessageVector, +pub struct ScheduledEventsHelper { + playing_notes: PlayingNotes, + pub queued_messages: AbsoluteTimeMidiMessageVector, + notes_on_to_requeue: HashMap, + pub events: Vec, + buffer_duration_in_samples: usize, + delay_is_active: bool, max_notes: MaxNotesParameter, apply_max_notes_to_delayed_notes_only: bool, - delay_is_active: bool, -) -> (AbsoluteTimeMidiMessageVector, Vec) { - let mut playing_notes: PlayingNotes = PlayingNotes::default(); - let mut queued_messages = AbsoluteTimeMidiMessageVector::default(); - let mut notes_on_to_requeue: HashMap = HashMap::new(); - let mut events: Vec = vec![]; + current_time_in_samples: usize, +} - let mut add_event = |event: AbsoluteTimeMidiMessage, playing_notes: &mut PlayingNotes| { - // note: playing_notes cannot be captured by closure because the method also uses it, causing the borrow checker - // to complain +impl ScheduledEventsHelper { + pub fn new( + buffer_duration_in_samples: usize, + delay_is_active: bool, + max_notes: MaxNotesParameter, + apply_max_notes_to_delayed_notes_only: bool, + current_time_in_samples: usize, + ) -> Self { + Self { + playing_notes: Default::default(), + queued_messages: Default::default(), + notes_on_to_requeue: Default::default(), + events: vec![], + buffer_duration_in_samples, + delay_is_active, + max_notes, + apply_max_notes_to_delayed_notes_only, + current_time_in_samples, + } + } - if event.play_time_in_samples < current_time_in_samples + samples { + fn add_event(&mut self, event: AbsoluteTimeMidiMessage) { + if event.play_time_in_samples < self.current_time_in_samples + self.buffer_duration_in_samples { // test if it belongs to that time window, as we don't want to replay notes on we put // back in the scheduled queue - if event.play_time_in_samples >= current_time_in_samples { + if event.play_time_in_samples >= self.current_time_in_samples { if let MidiMessageType::NoteOffMessage(_) = MidiMessageType::from(event) { - let note_off_event = event ; + let note_off_event = event; - let note_on = match notes_on_to_requeue.get_mut(¬e_off_event.id) { + let note_on = match self.notes_on_to_requeue.get_mut(¬e_off_event.id) { None => { // no such note running, skip - return ; + return; } - Some(note_on) => note_on + Some(note_on) => note_on, }; - if note_off_event.reason == MessageReason::Live && delay_is_active && !max_notes.should_limit(playing_notes.len() - 1) { + if note_off_event.reason == MessageReason::Live + && self.delay_is_active + && !self.max_notes.should_limit(self.playing_notes.len() - 1) + { // mark the note on as delayed from now on, but don't sent the note off note_on.reason = MessageReason::Delayed; return; } - if apply_max_notes_to_delayed_notes_only + if self.apply_max_notes_to_delayed_notes_only && note_off_event.reason == MessageReason::MaxNotes && note_on.reason == MessageReason::Live { @@ -133,76 +150,74 @@ pub fn process_scheduled_events( } // stop this note, don't requeue - playing_notes.remove(&PlayingNoteIndex { + self.playing_notes.remove(&PlayingNoteIndex { pitch: note_off_event.get_pitch(), channel: note_off_event.get_channel(), }); - notes_on_to_requeue.remove(¬e_off_event.id); + self.notes_on_to_requeue.remove(¬e_off_event.id); } - events.push(event.new_midi_event(current_time_in_samples)); + self.events.push(event.new_midi_event(self.current_time_in_samples)); } if let MidiMessageType::NoteOnMessage(_) = MidiMessageType::from(event) { - playing_notes.insert( + self.playing_notes.insert( PlayingNoteIndex { pitch: event.get_pitch(), channel: event.get_channel(), }, event, ); - notes_on_to_requeue.insert(event.id, event); + self.notes_on_to_requeue.insert(event.id, event); } } else { - queued_messages.push(event); + self.queued_messages.push(event); } - }; + } - for mut message in messages.iter().copied() { - if message.play_time_in_samples < current_time_in_samples { - match MidiMessageType::from(message) { - MidiMessageType::NoteOnMessage(_) => {} - _ => { - panic!("Only pending note on are expected to be found in the past") + pub fn process_scheduled_events(mut self, messages: &AbsoluteTimeMidiMessageVector) -> (AbsoluteTimeMidiMessageVector, Vec) { + for mut message in messages.iter().copied() { + if message.play_time_in_samples < self.current_time_in_samples { + match MidiMessageType::from(message) { + MidiMessageType::NoteOnMessage(_) => {} + _ => { + panic!("Only pending note on are expected to be found in the past") + } } - } - }; + }; - match MidiMessageType::from(message) { - MidiMessageType::NoteOnMessage(note_on) => { - // if still playing : generate note off at current sample, put note on with - // delta + 1 in the queue - let index = PlayingNoteIndex { - channel: note_on.channel, - pitch: note_on.pitch, - }; + match MidiMessageType::from(message) { + MidiMessageType::NoteOnMessage(note_on) => { + // if still playing : generate note off at current sample, put note on with + // delta + 1 in the queue + let index = PlayingNoteIndex { + channel: note_on.channel, + pitch: note_on.pitch, + }; - if let Some(already_playing_note) = playing_notes.get(&index) { - // we were still playing that note. generate a note off first. - add_event( - AbsoluteTimeMidiMessage { + if let Some(&AbsoluteTimeMidiMessage { id, .. }) = self.playing_notes.get(&index) { + // we were still playing that note. generate a note off first. + self.add_event(AbsoluteTimeMidiMessage { data: NoteOff { channel: note_on.channel, pitch: note_on.pitch, velocity: 0, } .into(), - id: already_playing_note.id, + id, reason: MessageReason::Retrigger, play_time_in_samples: message.play_time_in_samples, - }, - &mut playing_notes, - ); - - // move the note on to the next sample or the daw might be confused - message.play_time_in_samples += 1; - } else if max_notes.should_limit(playing_notes.len()) { - if let Some(oldest_playing_note) = - playing_notes.oldest_playing_note(apply_max_notes_to_delayed_notes_only) - { - let oldest_playing_note = *oldest_playing_note; // drop the borrow - - add_event( - AbsoluteTimeMidiMessage { + }); + + // move the note on to the next sample or the daw might be confused + message.play_time_in_samples += 1; + } else if self.max_notes.should_limit(self.playing_notes.len()) { + if let Some(oldest_playing_note) = self + .playing_notes + .oldest_playing_note(self.apply_max_notes_to_delayed_notes_only) + { + let oldest_playing_note = *oldest_playing_note; // drop the borrow + + self.add_event(AbsoluteTimeMidiMessage { data: NoteOff { channel: oldest_playing_note.get_channel(), pitch: oldest_playing_note.get_pitch(), @@ -212,52 +227,50 @@ pub fn process_scheduled_events( id: oldest_playing_note.id, play_time_in_samples: message.play_time_in_samples, reason: MessageReason::MaxNotes, - }, - &mut playing_notes, - ); - }; - } + }); + }; + } - add_event(message, &mut playing_notes); - } + self.add_event(message); + } - MidiMessageType::NoteOffMessage(note_off) => { - let playing_note = PlayingNoteIndex { - channel: note_off.channel, - pitch: note_off.pitch, - }; - match playing_notes.get(&playing_note) { - Some(currently_playing_note) => { - if currently_playing_note.id == message.id { - add_event(message, &mut playing_notes); - continue; - } else { - // this note was interrupted earlier already, don't send that - // note off or we may interrupt a new note with that delayed note - // off + MidiMessageType::NoteOffMessage(note_off) => { + let playing_note = PlayingNoteIndex { + channel: note_off.channel, + pitch: note_off.pitch, + }; + match self.playing_notes.get(&playing_note) { + Some(currently_playing_note) => { + if currently_playing_note.id == message.id { + self.add_event(message); + continue; + } else { + // this note was interrupted earlier already, don't send that + // note off or we may interrupt a new note with that delayed note + // off + continue; + } + } + None => { + // was not playing at all, skip continue; } - } - None => { - // was not playing at all, skip - continue; - } - }; - } - _ => { - add_event(message, &mut playing_notes); + }; + } + _ => { + self.add_event(message); + } } } - } - for (_, event) in notes_on_to_requeue { - queued_messages.ordered_insert(event) - } + for (_, event) in self.notes_on_to_requeue.drain() { + self.queued_messages.ordered_insert(event) + }; - (queued_messages, events) + (self.queued_messages, self.events) + } } - pub fn raw_process_scheduled_events( samples: usize, current_time_in_samples: usize, From 6ddaff3fae2250e64101da714012f037457d34fb Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sat, 19 Jun 2021 14:40:38 +0200 Subject: [PATCH 45/47] simplify further schedule helper --- note_off_delay/src/lib.rs | 12 +++++----- util/src/delayed_message_consumer.rs | 33 +++++++++++++++++++++++----- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index 8a1c22b..e220261 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -17,7 +17,7 @@ use crate::parameters::PARAMETER_COUNT; use parameters::NoteOffDelayPluginParameters; use parameters::Parameter; use util::absolute_time_midi_message_vector::AbsoluteTimeMidiMessageVector; -use util::delayed_message_consumer::{MessageReason, ScheduledEventsHelper}; +use util::delayed_message_consumer::{process_scheduled_events, MessageReason}; use util::logging::logging_setup; use util::messages::format_event; use util::midi_message_type::MidiMessageType; @@ -47,20 +47,20 @@ impl Default for NoteOffDelayPlugin { impl NoteOffDelayPlugin { fn process_scheduled_events(&self, samples: usize) -> (AbsoluteTimeMidiMessageVector, Vec) { - let helper = ScheduledEventsHelper::new( + process_scheduled_events( samples, self.parameters.get_delay().is_active(), self.parameters.get_max_notes(), self.parameters .get_bool_parameter(Parameter::MaxNotesAppliesToDelayedNotesOnly), self.current_time_in_samples, - ); - helper.process_scheduled_events(&self.message_queue) + &self.message_queue, + ) } fn send_events(&mut self, samples: usize) { - let (next_message_queue, events) = self.process_scheduled_events(samples); - self.message_queue = next_message_queue; + let (queued_messages, events) = self.process_scheduled_events(samples); + self.message_queue = queued_messages; if let Ok(mut host_callback_lock) = self.parameters.host_mutex.lock() { self.send_buffer diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index b0fed2b..7a5b6cf 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -82,11 +82,11 @@ impl PlayingNotes { } } -pub struct ScheduledEventsHelper { +struct ScheduledEventsHelper { playing_notes: PlayingNotes, - pub queued_messages: AbsoluteTimeMidiMessageVector, + queued_messages: AbsoluteTimeMidiMessageVector, notes_on_to_requeue: HashMap, - pub events: Vec, + events: Vec, buffer_duration_in_samples: usize, delay_is_active: bool, max_notes: MaxNotesParameter, @@ -94,8 +94,26 @@ pub struct ScheduledEventsHelper { current_time_in_samples: usize, } +pub fn process_scheduled_events( + buffer_duration_in_samples: usize, + delay_is_active: bool, + max_notes: MaxNotesParameter, + apply_max_notes_to_delayed_notes_only: bool, + current_time_in_samples: usize, + messages: &AbsoluteTimeMidiMessageVector, +) -> (AbsoluteTimeMidiMessageVector, Vec) { + let helper = ScheduledEventsHelper::new( + buffer_duration_in_samples, + delay_is_active, + max_notes, + apply_max_notes_to_delayed_notes_only, + current_time_in_samples, + ); + helper.process_scheduled_events(messages) +} + impl ScheduledEventsHelper { - pub fn new( + fn new( buffer_duration_in_samples: usize, delay_is_active: bool, max_notes: MaxNotesParameter, @@ -174,7 +192,10 @@ impl ScheduledEventsHelper { } } - pub fn process_scheduled_events(mut self, messages: &AbsoluteTimeMidiMessageVector) -> (AbsoluteTimeMidiMessageVector, Vec) { + fn process_scheduled_events( + mut self, + messages: &AbsoluteTimeMidiMessageVector, + ) -> (AbsoluteTimeMidiMessageVector, Vec) { for mut message in messages.iter().copied() { if message.play_time_in_samples < self.current_time_in_samples { match MidiMessageType::from(message) { @@ -265,7 +286,7 @@ impl ScheduledEventsHelper { for (_, event) in self.notes_on_to_requeue.drain() { self.queued_messages.ordered_insert(event) - }; + } (self.queued_messages, self.events) } From c7dddd7d288cdc0550c4f39fb072f905ddebc070 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 20 Jun 2021 10:11:48 +0200 Subject: [PATCH 46/47] simplify --- arpegiator/src/midi_messages/device.rs | 20 ++++----- .../src/midi_messages/pattern_device.rs | 16 +++---- note_off_delay/src/lib.rs | 44 ++++++++----------- util/src/delayed_message_consumer.rs | 10 ++--- 4 files changed, 42 insertions(+), 48 deletions(-) diff --git a/arpegiator/src/midi_messages/device.rs b/arpegiator/src/midi_messages/device.rs index ecb5f9c..594cf3a 100644 --- a/arpegiator/src/midi_messages/device.rs +++ b/arpegiator/src/midi_messages/device.rs @@ -93,14 +93,14 @@ pub enum DeviceChange { impl TimedEvent for DeviceChange { fn timestamp(&self) -> usize { - match self { - DeviceChange::AddNote { time, .. } => *time, - DeviceChange::RemoveNote { time, .. } => *time, - DeviceChange::NoteExpressionChange { time, .. } => *time, - DeviceChange::ReplaceNote { time, .. } => *time, - DeviceChange::CCChange { time, .. } => *time, - DeviceChange::Ignored { time, .. } => *time, - DeviceChange::NoteLegato { time, .. } => *time + *match self { + DeviceChange::AddNote { time, .. } => time, + DeviceChange::RemoveNote { time, .. } => time, + DeviceChange::NoteExpressionChange { time, .. } => time, + DeviceChange::ReplaceNote { time, .. } => time, + DeviceChange::CCChange { time, .. } => time, + DeviceChange::Ignored { time, .. } => time, + DeviceChange::NoteLegato { time, .. } => time } } @@ -298,12 +298,12 @@ impl Device { // remove note is sorted as being before add note if let DeviceChange::RemoveNote { time: time_1, note: note_1 } = change_1 { if let Some(position) = output.iter().position(|change| { - matches!(change, DeviceChange::AddNote { time: time_2, note: note_2 } if time_1 == *time_2 && note_1.pitch == note_2.pitch && note_1.channel == note_2.channel) + matches!(change, &DeviceChange::AddNote { time: time_2, note: note_2 } if time_1 == time_2 && note_1.pitch == note_2.pitch && note_1.channel == note_2.channel) }) { let add_note = output.remove(position); if let Some(position) = output.iter().position(|change| { - matches!(change, DeviceChange::NoteExpressionChange { time: time_2, expression: Expression::PitchBend, note: note_2 } if time_1 == *time_2 && note_1.channel == note_2.channel) + matches!(change, &DeviceChange::NoteExpressionChange { time: time_2, expression: Expression::PitchBend, note: note_2 } if time_1 == time_2 && note_1.channel == note_2.channel) }) { output.remove(position); } diff --git a/arpegiator/src/midi_messages/pattern_device.rs b/arpegiator/src/midi_messages/pattern_device.rs index 2e143ad..bf8b9da 100644 --- a/arpegiator/src/midi_messages/pattern_device.rs +++ b/arpegiator/src/midi_messages/pattern_device.rs @@ -67,14 +67,14 @@ impl PartialEq for PatternDeviceChange { impl TimedEvent for PatternDeviceChange { fn timestamp(&self) -> usize { - match self { - PatternDeviceChange::AddPattern { time, .. } => *time, - PatternDeviceChange::PatternExpressionChange { time, .. } => *time, - PatternDeviceChange::RemovePattern { time, .. } => *time, - PatternDeviceChange::ReplacePattern { time, .. } => *time, - PatternDeviceChange::None { time, .. } => *time, - PatternDeviceChange::CC { time, .. } => *time, - PatternDeviceChange::Legato { time, .. } => *time + *match self { + PatternDeviceChange::AddPattern { time, .. } => time, + PatternDeviceChange::PatternExpressionChange { time, .. } => time, + PatternDeviceChange::RemovePattern { time, .. } => time, + PatternDeviceChange::ReplacePattern { time, .. } => time, + PatternDeviceChange::None { time, .. } => time, + PatternDeviceChange::CC { time, .. } => time, + PatternDeviceChange::Legato { time, .. } => time } } diff --git a/note_off_delay/src/lib.rs b/note_off_delay/src/lib.rs index e220261..fb906ca 100644 --- a/note_off_delay/src/lib.rs +++ b/note_off_delay/src/lib.rs @@ -22,6 +22,7 @@ use util::logging::logging_setup; use util::messages::format_event; use util::midi_message_type::MidiMessageType; use util::parameters::ParameterConversion; +use util::absolute_time_midi_message::AbsoluteTimeMidiMessage; plugin_main!(NoteOffDelayPlugin); @@ -171,13 +172,15 @@ impl Plugin for NoteOffDelayPlugin { fn process_events(&mut self, events: &Events) { self.debug_events_in(events); - for event in events.events() { - let midi_event = if let Event::Midi(midi_event) = event { - midi_event + let midi_events = events.events().filter_map( + |event| if let Event::Midi(midi_event) = event { + Some(midi_event) } else { - continue; - }; + None + } + ); + for midi_event in midi_events { // TODO: minimum time, maximum time ( with delay ) match MidiMessageType::from(&midi_event.data) { @@ -190,26 +193,17 @@ impl Plugin for NoteOffDelayPlugin { let delay = self.parameters.get_delay(); if delay.is_active() { - match self - .message_queue - .get_matching_note_on(note_off.channel, note_off.pitch) - .cloned() - { - None => {} - Some(note_on) => { - let duration = note_off_play_time - note_on.play_time_in_samples; - match delay.apply(duration, self.sample_rate) { - None => panic!("delay is supposed to be active"), - Some(new_duration) => { - // send two times the note off, the live one will be only used to mark the note on as delayed - self.message_queue.insert_message( - midi_event.data, - note_on.play_time_in_samples + new_duration, - MessageReason::Delayed, - ); - } - } - } + let matching_note_on = self.message_queue.get_matching_note_on(note_off.channel, note_off.pitch); + if let Some(&AbsoluteTimeMidiMessage { play_time_in_samples,.. }) = matching_note_on { + let duration = note_off_play_time - play_time_in_samples; + let new_duration = delay.apply(duration, self.sample_rate).expect("delay is supposed to be active"); + + // send two times the note off, the live one will be only used to mark the note on as delayed + self.message_queue.insert_message( + midi_event.data, + play_time_in_samples + new_duration, + MessageReason::Delayed, + ); } } } diff --git a/util/src/delayed_message_consumer.rs b/util/src/delayed_message_consumer.rs index 7a5b6cf..11d34af 100644 --- a/util/src/delayed_message_consumer.rs +++ b/util/src/delayed_message_consumer.rs @@ -197,8 +197,10 @@ impl ScheduledEventsHelper { messages: &AbsoluteTimeMidiMessageVector, ) -> (AbsoluteTimeMidiMessageVector, Vec) { for mut message in messages.iter().copied() { + let midi_message_type = MidiMessageType::from(message); + if message.play_time_in_samples < self.current_time_in_samples { - match MidiMessageType::from(message) { + match midi_message_type { MidiMessageType::NoteOnMessage(_) => {} _ => { panic!("Only pending note on are expected to be found in the past") @@ -206,7 +208,7 @@ impl ScheduledEventsHelper { } }; - match MidiMessageType::from(message) { + match midi_message_type { MidiMessageType::NoteOnMessage(note_on) => { // if still playing : generate note off at current sample, put note on with // delta + 1 in the queue @@ -232,12 +234,10 @@ impl ScheduledEventsHelper { // move the note on to the next sample or the daw might be confused message.play_time_in_samples += 1; } else if self.max_notes.should_limit(self.playing_notes.len()) { - if let Some(oldest_playing_note) = self + if let Some(&oldest_playing_note) = self .playing_notes .oldest_playing_note(self.apply_max_notes_to_delayed_notes_only) { - let oldest_playing_note = *oldest_playing_note; // drop the borrow - self.add_event(AbsoluteTimeMidiMessage { data: NoteOff { channel: oldest_playing_note.get_channel(), From bdff9edd56eb8375bb85d69dc43c97fc40db8c86 Mon Sep 17 00:00:00 2001 From: Vincent Alsteen Date: Sun, 20 Jun 2021 22:49:18 +0200 Subject: [PATCH 47/47] find reverse parameter value, test against samples --- note_off_delay/src/parameters.rs | 2 +- note_off_delay/src/tests.rs | 15 ++++++++++----- util/src/parameters.rs | 5 +++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/note_off_delay/src/parameters.rs b/note_off_delay/src/parameters.rs index bd98cc3..5a27e04 100644 --- a/note_off_delay/src/parameters.rs +++ b/note_off_delay/src/parameters.rs @@ -120,7 +120,7 @@ impl Delay { Some(match self.offset { Duration::Off => duration_in_samples as usize, - Duration::Duration(x) => (duration_in_samples + x * sample_rate) as usize, + Duration::Duration(x) => (duration_in_samples + x * sample_rate).round() as usize, }) } else { None diff --git a/note_off_delay/src/tests.rs b/note_off_delay/src/tests.rs index 0487d64..7cf92b6 100644 --- a/note_off_delay/src/tests.rs +++ b/note_off_delay/src/tests.rs @@ -4,6 +4,7 @@ mod test { use crate::NoteOffDelayPlugin; use core::mem; use std::convert::TryFrom; + use util::parameters::get_reverse_exponential_scale_value; use vst::api::{Event, EventType, Events, MidiEvent}; use vst::plugin::{Plugin, PluginParameters}; @@ -31,9 +32,9 @@ mod test { #[test] fn test_process_scheduled_events() { let original = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; - let expected = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 725)]; + let expected = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 120)]; - assert_process_scheduled_events(original, expected, 0.01) + assert_process_scheduled_events(original, expected, 100) } #[test] @@ -41,7 +42,7 @@ mod test { let original = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; let expected = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; - assert_process_scheduled_events(original, expected, 0.0) + assert_process_scheduled_events(original, expected, 0) } #[test] @@ -49,7 +50,7 @@ mod test { let original = vec![([0x90, 0x60, 0x60], 10), ([0x80, 0x60, 0x60], 20)]; let expected = vec![([0x90, 0x60, 0x60], 10)]; - assert_process_scheduled_events(original, expected, 0.8) + assert_process_scheduled_events(original, expected, 1004) } fn compare(events: Vec, expected: Vec<([u8; 3], i32)>) { @@ -67,9 +68,13 @@ mod test { fn assert_process_scheduled_events( original: Vec<([u8; 3], i32)>, expected: Vec<([u8; 3], i32)>, - delay_parameter_value: f32, + delay_parameter_value_samples: usize, ) { let mut plugin = NoteOffDelayPlugin::default(); + + let delay_parameter_value_seconds = delay_parameter_value_samples as f32 / 44100.; + let delay_parameter_value = get_reverse_exponential_scale_value(delay_parameter_value_seconds, 10., 20.); + plugin .parameters .set_parameter(i32::from(Parameter::DelayOffset), delay_parameter_value); diff --git a/util/src/parameters.rs b/util/src/parameters.rs index 18525c2..6ba4f93 100644 --- a/util/src/parameters.rs +++ b/util/src/parameters.rs @@ -9,6 +9,11 @@ pub fn get_exponential_scale_value(value: f32, max: f32, factor: f32) -> f32 { (factor.powf(value) - 1.) * max / (factor - 1.0) } +#[inline] +pub fn get_reverse_exponential_scale_value(value: f32, max: f32, factor: f32) -> f32 { + ((value * (factor - 1.0) / max) + 1.).log(factor) +} + // TODO can Parameter implement just from/into i32, and provide a default implementation for usize ? pub trait ParameterConversion