├── .cargo └── config ├── assets ├── DTM-Mono.otf ├── ui_2x_v2.png ├── POLYCAT ON.png ├── cat-imgs │ ├── baksik │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ ├── chrysi │ │ ├── 0.png │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png │ └── severian │ │ ├── out8.png │ │ ├── out10.png │ │ ├── out13.png │ │ ├── out16.png │ │ ├── out19.png │ │ ├── out22.png │ │ ├── out24.png │ │ ├── out26.png │ │ ├── out28.png │ │ └── out30.png └── Spine.json ├── affinity_photo ├── ui_2x.png ├── ui_mac.png ├── DTM-Sans.otf ├── ui_4x_v2.png ├── POLYCAT OFF.png ├── metal_knob.afphoto └── brushed_metal.afphoto ├── xtask ├── src │ └── main.rs └── Cargo.toml ├── src ├── bin │ ├── standalone.rs │ └── perf.rs ├── keys.rs ├── chorus.rs ├── ui_knob.rs ├── common.rs ├── lib.rs ├── params.rs ├── ui.rs └── sound_gen.rs ├── .gitignore ├── vplayer.sh ├── run.sh ├── CREDITS.txt ├── notes ├── filter_tests.txt ├── interpolation.txt └── findings.txt ├── Justfile ├── osx_bundler.sh ├── README.md ├── Cargo.toml └── Cargo.lock /.cargo/config: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --release --" -------------------------------------------------------------------------------- /assets/DTM-Mono.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/DTM-Mono.otf -------------------------------------------------------------------------------- /assets/ui_2x_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/ui_2x_v2.png -------------------------------------------------------------------------------- /assets/POLYCAT ON.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/POLYCAT ON.png -------------------------------------------------------------------------------- /affinity_photo/ui_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/ui_2x.png -------------------------------------------------------------------------------- /affinity_photo/ui_mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/ui_mac.png -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() -> nih_plug_xtask::Result<()> { 2 | nih_plug_xtask::main() 3 | } 4 | -------------------------------------------------------------------------------- /affinity_photo/DTM-Sans.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/DTM-Sans.otf -------------------------------------------------------------------------------- /affinity_photo/ui_4x_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/ui_4x_v2.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/0.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/1.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/2.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/3.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/4.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/5.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/6.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/7.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/8.png -------------------------------------------------------------------------------- /assets/cat-imgs/baksik/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/baksik/9.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/0.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/1.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/2.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/3.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/4.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/5.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/6.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/7.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/8.png -------------------------------------------------------------------------------- /assets/cat-imgs/chrysi/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/chrysi/9.png -------------------------------------------------------------------------------- /affinity_photo/POLYCAT OFF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/POLYCAT OFF.png -------------------------------------------------------------------------------- /affinity_photo/metal_knob.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/metal_knob.afphoto -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out8.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out10.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out13.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out16.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out19.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out22.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out24.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out26.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out28.png -------------------------------------------------------------------------------- /assets/cat-imgs/severian/out30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/assets/cat-imgs/severian/out30.png -------------------------------------------------------------------------------- /affinity_photo/brushed_metal.afphoto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a2aaron/nyasynth/HEAD/affinity_photo/brushed_metal.afphoto -------------------------------------------------------------------------------- /src/bin/standalone.rs: -------------------------------------------------------------------------------- 1 | use nyasynth::Nyasynth; 2 | 3 | fn main() { 4 | nih_plug::wrapper::standalone::nih_export_standalone::(); 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /iced_audio 3 | /iced_baseview 4 | /biquad-rs 5 | /traces 6 | /recordings 7 | *.log 8 | *.vst 9 | *.wav 10 | *.mp3 11 | *.mid 12 | *.trace 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /vplayer.sh: -------------------------------------------------------------------------------- 1 | trash nyasynth_stdout.log 2 | trash nyasynth_stderr.log 3 | pkill "vPlayer 3" 4 | cargo xtask bundle nyasynth --release 5 | open /Applications/vPlayer\ 3.app/ --stdout ./nyasynth_stdout.log --stderr ./nyasynth_stderr.log -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | trash $1.trace 2 | trash $2.wav 3 | cargo build --release 4 | cargo xtask bundle nyasynth --release 5 | xctrace record --template 'Time Profiler' --output $1.trace --launch -- target/release/perf --in megalovania.mid --out $2.wav --polycat -------------------------------------------------------------------------------- /CREDITS.txt: -------------------------------------------------------------------------------- 1 | "Determination" font created by Haley Wakamatsu 2 | - https://www.behance.net/gallery/31268855/Determination-Better-Undertale-Font 3 | 4 | Cat gif originally from Meowsynth, featuring the cat Baksik 5 | - http://web.archive.org/web/20120930004514/http://www.myspace.com/baksik -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | nih_plug_xtask = { git = "https://github.com/robbert-vdh/nih-plug.git" } -------------------------------------------------------------------------------- /notes/filter_tests.txt: -------------------------------------------------------------------------------- 1 | velocity sweep is from 0 to 200 (LMMS units), stepping by 20 each note 2 | 3 | cutoff: 0.43 (default) 4 | 5 | env mod: 0.0 6 | MEOW: 0.0 sustain 7 | Decay: 1.0 (default) 8 | Attack: 0.48 (default) 9 | 10 | peak is around 280hz - 300hz across all velocities 11 | 12 | 13 | at max velocity, total freq sweep is ~350hz - 8khz over the MEOW env. at zero velocity, total freq sweep is ~350hz constant 14 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | hello: 2 | clang -nostdlib hello.s -l system -o target/hello -g 3 | ./target/hello 4 | 5 | perf name: 6 | -trash {{name}}.trace 7 | -trash {{name}}.wav 8 | cargo build --release 9 | cargo xtask bundle nyasynth --release 10 | xctrace record --template 'Time Profiler' --output {{name}}.trace --launch -- target/release/perf --in megalovania.mid --out {{name}}.wav --polycat 11 | 12 | vplayer: 13 | -trash nyasynth_stdout.log 14 | -trash nyasynth_stderr.log 15 | -pkill "vPlayer 3" 16 | cargo xtask bundle nyasynth --release 17 | open /Applications/vPlayer\ 3.app/ --stdout ./nyasynth_stdout.log --stderr ./nyasynth_stderr.log 18 | 19 | run: 20 | cargo run --release --bin standalone -- --midi-input "USB Axiom 49 Port 1" --backend auto --sample-rate 44100 21 | 22 | release-all: 23 | cargo build --release 24 | cargo xwin build --release --target x86_64-pc-windows-msvc 25 | cargo xtask bundle-universal nyasynth --release 26 | strip -Sx target/bundled/nyasynth.vst3/Contents/MacOS/nyasynth 27 | codesign -f -s - target/bundled/nyasynth.vst3 28 | 29 | release-small: 30 | cargo build --release 31 | ls -lh target/release/libnyasynth.dylib 32 | strip target/release/libnyasynth.dylib -Sx 33 | ls -lh target/release/libnyasynth.dylib -------------------------------------------------------------------------------- /osx_bundler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Make sure we have the arguments we need 4 | if [[ -z $1 || -z $2 ]]; then 5 | echo "Generates a macOS bundle from a compiled dylib file" 6 | echo "Example:" 7 | echo -e "\t$0 Plugin target/release/plugin.dylib" 8 | echo -e "\tCreates a Plugin.vst bundle" 9 | else 10 | # Make the bundle folder 11 | mkdir -p "$1.vst/Contents/MacOS" 12 | 13 | # Create the PkgInfo 14 | echo "BNDL????" > "$1.vst/Contents/PkgInfo" 15 | 16 | #build the Info.Plist 17 | echo " 18 | 19 | 20 | 21 | CFBundleDevelopmentRegion 22 | English 23 | CFBundleExecutable 24 | $1 25 | CFBundleGetInfoString 26 | vst 27 | CFBundleIconFile 28 | 29 | CFBundleIdentifier 30 | com.rust-vst.$1 31 | CFBundleInfoDictionaryVersion 32 | 6.0 33 | CFBundleName 34 | $1 35 | CFBundlePackageType 36 | BNDL 37 | CFBundleVersion 38 | 1.0 39 | CFBundleSignature 40 | $((RANDOM % 9999)) 41 | CSResourcesFileMapped 42 | 43 | 44 | " > "$1.vst/Contents/Info.plist" 45 | 46 | # move the provided library to the correct location 47 | cp "$2" "$1.vst/Contents/MacOS/$1" 48 | 49 | echo "Created bundle $1.vst" 50 | fi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nyasynth - The World's Second Meowizer 2 | 3 | Do you remember [Meowsynth](https://www.youtube.com/watch?v=_VYtQ9jP73s)? So do I. Unfortunately, it 4 | seems that Meowsynth is only 32-bit and hence isn't compatible with many 64-bit DAWs (in particular, 5 | it's not compatible with Ableton, which doesn't support 32-bit VSTs any more). Additionally, there 6 | isn't a Mac or Linux version. This repo aims to fix that by being a recreation of the synthesizer! 7 | 8 | [Listen to Nyasynth in action!](https://youtu.be/A2qSIzGI7p8) 9 | 10 | ![A screenshot of the Nyasynth UI as it apperas on Mac.](affinity_photo/ui_mac.png) 11 | 12 | # Download 13 | Download Nyasynth from the [releases page](https://github.com/a2aaron/nyasynth/releases/tag/1.0). 14 | Or click on one of the links below: 15 | 16 | [Download for Windows x86-64](https://github.com/a2aaron/nyasynth/releases/download/1.0/Nyasynth.1.0.Windows.x86-64.zip) 17 | 18 | [Download for Apple (ARM Binary)](https://github.com/a2aaron/nyasynth/releases/download/1.0/Nyasynth.1.0.Mac.OSX.AArch64.zip) 19 | 20 | [Download for Apple (Universal Binary)](https://github.com/a2aaron/nyasynth/releases/download/1.0/Nyasynth.1.0.Mac.OSX.Universal.zip) 21 | 22 | (If your platform isn't listed above, you can try compiling from source by following the instructions below.) 23 | 24 | # Build Instructions 25 | First, make sure you have [Rust](https://www.rust-lang.org/) installed. 26 | 27 | To build the plugin as a vst3 bundle, run the following command: 28 | 29 | ``` 30 | cargo xtask bundle nyasynth --release 31 | ``` 32 | 33 | This will create a `nyasynth.vst3` bundle in `/target/bundled/`. Install this into any DAW of your choice. 34 | 35 | 36 | You can also create a standalone binary by running the following command: 37 | 38 | ``` 39 | cargo build --release --bin standalone 40 | ``` 41 | 42 | This will create a `standalone` binary in `/target/release/`. You can see the arguments it uses with `standalone -h`. See [nih-plug](https://github.com/robbert-vdh/nih-plug) for more information. 43 | -------------------------------------------------------------------------------- /notes/interpolation.txt: -------------------------------------------------------------------------------- 1 | x0 = 1 c0 + -1 c1 + c2 2 | x1 = 0 c0 + 0 c1 + c2 3 | x2 = 1 c0 + 1 c1 + c2 4 | 5 | c2 = x1 6 | 7 | x0 = 1 c0 + -1 c1 + x1 8 | x2 = 1 c0 + 1 c1 + x1 9 | 10 | x0 + x2 = 2c0 + 2x1 11 | 12 | 1/2x0 + 1/2x2 - x1 = c0 13 | 14 | x2 - c0 - x1 = c1 15 | x2 - (1/2x0 + 1/2x2 - x1) - x1 = c1 16 | 17 | x2 + -1/2x0 - 1/2x2 + x1 - x1 = c1 18 | x2 + -1/2x0 - 1/2x2 = c1 19 | -1/2x0 + 1/2x2 = c1 20 | 21 | ---- 22 | CUBIC INTERPOLATION 23 | 24 | f(t) = c0 t^3 + c1 t^2 + c2 t + c3 25 | 26 | 27 | x0 = -8c0 + 4c1 + -2c2 + c3 28 | x1 = -1c0 + 1c1 + -1c2 + c3 29 | x2 = 0c0 + 0c1 + 0c2 + c3 30 | x3 = 1c0 + 1c1 + 1c2 + c3 31 | 32 | # Derivation of c3 33 | x2 = c3 34 | c3 = x2 35 | 36 | # Substitute x2 with c3 37 | x0 = -8c0 + 4c1 + -2c2 + x2 38 | x1 = -1c0 + 1c1 + -1c2 + x2 39 | x3 = 1c0 + 1c1 + 1c2 + x2 40 | 41 | # Derivation of c1 42 | x1 + x3 = 1c0 + 1c1 + 1c2 + x2 + -1c0 + 1c1 + -1c2 + x2 43 | = + 1c1 + 1c2 + x2 + + 1c1 + -1c2 + x2 44 | = + 1c1 + + x2 + + 1c1 + + x2 45 | = 2c1 + 2x2 46 | = 2c1 + 2x2 47 | 2c1 + 2x2 = x1 + x3 48 | 2c1 = x1 - 2x2 + x3 49 | c1 = 1/2 x1 - x2 + 1/2 x3 50 | 51 | 52 | # Derivation of c2 53 | x3 = c0 + c1 + c2 + x2 54 | 8x3 = 8c0 + 8c1 + 8c2 + 8x2 55 | x0 = -8c0 + 4c1 + -2c2 + x2 56 | 8x3 + x0 = 12c1 + 6c2 + 9x2 57 | 58 | 8x3 - 9x2 + x0 - 12c1 = 6c2 59 | 8x3 - 9x2 + x0 - 12(1/2 x1 - x2 + 1/2 x3) = 6c2 60 | 8x3 - 9x2 + x0 - 6 x1 + 12 x2 - 6 x3 = 6c2 61 | 2x3 - 9x2 + x0 - 6 x1 + 12 x2 = 6c2 62 | 2x3 + 3x2 + x0 - 6 x1 = 6c2 63 | 64 | 2 x3 + 3 x2 - 6 x1 + x0 = 6 c2 65 | 1/3 x3 + 1/2 x2 - x1 + 1/6 x0 = c2 66 | 67 | # Derivation of c0 68 | 69 | 70 | # Results 71 | c0 = 72 | c1 = 1/2 x3 - x2 + 1/2 x1 73 | c2 = 1/3 x3 + 1/2 x2 - x1 + 1/6 x0 74 | c3 = x2 75 | 76 | https://www.desmos.com/calculator/bxcarpst5l 77 | 78 | c0 = -1/6 x0 + 1/2 x1 + -1/2 x2 + 1/6 x3 79 | c1 = 1/2 x1 + - x2 + 1/2 x3 80 | c2 = 1/6 x0 + - x1 + 1/2 x2 + 1/3 x3 81 | c3 = x2 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nyasynth" 3 | version = "0.1.0" 4 | authors = ["Aaron Kofsky "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | # Repo pinned to https://github.com/a2aaron/nih-plug/commit/2ecc194e461de1bdbc117d86449b2fb30fafb356 11 | # This forked version is needed to expose some of nih-plug's internals, specifically so that perf.rs 12 | # can implement a minimal host. Additionally, this adds a method to ProcessContext to allow peeking 13 | # of events. 14 | nih_plug = { git = "https://github.com/a2aaron/nih-plug.git", rev = "2ecc194e461de1bdbc117d86449b2fb30fafb356", features = ["vst3", "standalone"]} 15 | nih_plug_egui = { git = "https://github.com/a2aaron/nih-plug.git", rev = "2ecc194e461de1bdbc117d86449b2fb30fafb356" } 16 | # nih_plug = { path = "../nih-plug", features = ["vst3", "standalone"]} 17 | derive_more = "0.99" 18 | biquad = "0.4.0" 19 | ordered-float = "3.4.0" 20 | getrandom = "0.2.8" 21 | atomic_float = "0.1.0" 22 | once_cell = "1.17.1" 23 | image = { version = "0.24.5", default-features = false, features = ["png"] } 24 | 25 | # perf.rs dependencies 26 | midly = "0.5.3" 27 | wav = "1.0.0" 28 | clap = { version = "4.1.8", features = ["derive"] } 29 | serde_json = "1.0.94" 30 | serde = "1.0.156" 31 | 32 | [profile.release] 33 | lto = true 34 | 35 | [lib] 36 | crate-type = ["cdylib", "lib"] 37 | 38 | [workspace] 39 | members = ["xtask"] 40 | 41 | # Cross compilation for Linux - use main-centos to get the oldest managable glibc 42 | [package.metadata.cross.target.x86_64-unknown-linux-gnu] 43 | xargo = false 44 | image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main-centos" 45 | pre-build = [ 46 | "yum install -y mesa-libGL-devel libXcursor-devel alsa-lib-devel python3 xcb-util-wm-devel", 47 | # This probably only works on an x86 host. 48 | "cd /tmp && curl -L 'https://github.com/jackaudio/jack2/archive/refs/tags/v1.9.22.tar.gz' -o jack2-v1.9.22.tar.gz && tar xf jack2-v1.9.22.tar.gz && cd jack2-1.9.22 && ./waf configure && ./waf build && ./waf install", 49 | ] 50 | 51 | [package.metadata.cross.build.env] 52 | passthrough = [ 53 | "PKG_CONFIG_PATH=/usr/local/lib/pkgconfig", 54 | ] -------------------------------------------------------------------------------- /src/keys.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{Note, Vel}; 2 | 3 | pub struct KeyTracker { 4 | /// A list of the currently held keys. 5 | pub held_keys: Vec<(Note, Vel)>, 6 | /// The note from which the next held note will be pitchbent from. If this is None, then 7 | /// the next held note will not have any pitchbend. 8 | pub portamento_key: Option, 9 | } 10 | 11 | impl KeyTracker { 12 | pub fn new() -> KeyTracker { 13 | KeyTracker { 14 | held_keys: Vec::with_capacity(16), 15 | portamento_key: None, 16 | } 17 | } 18 | 19 | /// Handle a NoteOn event. This function returns Some if the note passed into the function should 20 | /// have portamento, and None if not. 21 | pub fn note_on(&mut self, note: Note, vel: Vel, polycat: bool) -> Option { 22 | self.held_keys.push((note, vel)); 23 | if polycat { 24 | let portamento = self.portamento_key; 25 | self.portamento_key = Some(note); 26 | portamento 27 | } else { 28 | match self.held_keys.last().copied() { 29 | Some((top_note, _)) => Some(top_note), 30 | None => todo!(), 31 | } 32 | } 33 | } 34 | 35 | /// Handle a NoteOff event. This function returns Some if the note removed would cause the top 36 | /// of the stack to change. The returned value is the new top of stack. This is used in monocat 37 | /// mode, where removing the top-most note (aka: the only currently playing note) causes an 38 | /// internal note on event to occur. 39 | pub fn note_off(&mut self, note: Note) -> Option<(Note, Vel)> { 40 | if self.portamento_key == Some(note) { 41 | self.portamento_key = None; 42 | } 43 | 44 | // If the released key is actually in the key stack, then remove it. Otherwise, do nothing. 45 | if let Some(index) = self.held_keys.iter().position(|x| x.0 == note) { 46 | self.held_keys.remove(index); 47 | 48 | // If the top-of-stack key was released, then we need to return the second to last note 49 | // if one exists 50 | let note_on_event = if index == self.held_keys.len() { 51 | self.held_keys.last().copied() 52 | } else { 53 | None 54 | }; 55 | 56 | note_on_event 57 | } else { 58 | None 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /notes/findings.txt: -------------------------------------------------------------------------------- 1 | Attack - Filter... Attack? (But also not, it seems to be related to sustain somehow) 2 | Chorus - Chorus mix (public) 3 | Cutoff - Filter frequency 4 | Decay - Filter decay? 5 | Depth - Chorus depth? 6 | Dry/Wet - Dry/Wet for filter 7 | E - Meow Sustain (public) 8 | EnvMod - Controls "Attack" and "Decay" filter. 9 | Filter Type - Filter type, LP/HP/BP1/BP2/Notch 10 | Gain - Master volume 11 | M - Meow Attack (public) 12 | Min - Chorus distance? (appears to control delaytime for the chorus effect) 13 | Noise - Noise mix (public) 14 | O - Meow Decay (public) (yes it's in the wrong order) 15 | Patch Mem - Text (#1) - ??? 16 | Patch Mem - Text (#2) - ??? 17 | Phase - Phase 18 | Portamento time - Portamento Time (public) 19 | Q - Q value for filter 20 | Rate - Chorus rate? 21 | Vibrato Amount - Vibrato Amount (public) 22 | Vibrato Attack - Vibrato Attack (public) 23 | W - Meow Release (public) 24 | Waveform (#1 - beat) - Vibrato time, in beats and synced to BPM (public) 25 | Waveform (#2 - float) - ??? 26 | Waveform (#3 - int) - pitchbend amount (public) 27 | Waveform (#4 - On/Off) - Polycat toggle (public) 28 | 29 | 30 | MEOW envelope appears to control both volume and filter ADSRs 31 | EnvMod filter ADSR seems to also affect filter, maybe they are just added 32 | 33 | ---- 34 | Other 35 | Gain - Master volume 36 | Noise - Noise mix (public) 37 | Phase - Phase 38 | Portamento time - Portamento Time (public) 39 | Waveform (#3 - int) - pitchbend amount (public) 40 | Waveform (#4 - On/Off) - Polycat toggle (public) 41 | 42 | MEOW - Volume + Filter Freq ADSR 43 | M - Meow Attack (public) 44 | E - Meow Sustain (public) 45 | O - Meow Decay (public) (yes it's in the wrong order) 46 | W - Meow Release (public) 47 | 48 | Filter 49 | Cutoff - Filter frequency 50 | Q - Q value for filter 51 | Filter Type - Filter type, LP/HP/BP1/BP2/Notch 52 | Dry/Wet - Dry/Wet for filter 53 | 54 | Filter Envelope 55 | Attack - Filter... Attack? (But also not, it seems to be related to sustain somehow) 56 | Decay - Filter decay? 57 | EnvMod - Controls "Attack" and "Decay" filter. 58 | 59 | Chorus 60 | Chorus - Chorus mix (public) 61 | Depth - Chorus depth? 62 | Min - Chorus distance? (appears to control delaytime for the chorus effect) 63 | Rate - Chorus rate? 64 | 65 | Vibrato 66 | Vibrato Amount - Vibrato Amount (public) 67 | Vibrato Attack - Vibrato Attack (public) 68 | Waveform (#1 - beat) - Vibrato time, in beats and synced to BPM (public) 69 | 70 | Unknown 71 | Patch Mem - Text (#1) - ??? 72 | Patch Mem - Text (#2) - ??? 73 | Waveform (#2 - float) - ??? 74 | 75 | 76 | MEOW envelope appears to control both volume and filter ADSRs 77 | EnvMod filter ADSR seems to also affect filter, maybe they are just added -------------------------------------------------------------------------------- /assets/Spine.json: -------------------------------------------------------------------------------- 1 | {"skeleton":{"images":""}, 2 | "bones":[{"name":"root"}], 3 | "slots":[ 4 | {"name":"Image Emboss","bone":"root","attachment":"Image Emboss"}, 5 | {"name":"Logo Bg","bone":"root","attachment":"Logo Bg"}, 6 | {"name":"Polycat BG","bone":"root","attachment":"Polycat BG"}, 7 | {"name":"POLYCAT OFF","bone":"root","attachment":"POLYCAT OFF"}, 8 | {"name":"POLYCAT ON","bone":"root","attachment":"POLYCAT ON"}, 9 | {"name":"Pitchbend Emboss","bone":"root","attachment":"Pitchbend Emboss"}, 10 | {"name":"Chorus Knob","bone":"root","attachment":"Chorus Knob"}, 11 | {"name":"Noise Knob","bone":"root","attachment":"Noise Knob"}, 12 | {"name":"Portamento Knob","bone":"root","attachment":"Portamento Knob"}, 13 | {"name":"Vibrato Speed Emboss","bone":"root","attachment":"Vibrato Speed Emboss"}, 14 | {"name":"Vibrato Attack Knob","bone":"root","attachment":"Vibrato Attack Knob"}, 15 | {"name":"Vibrato Amount Knob","bone":"root","attachment":"Vibrato Amount Knob"}, 16 | {"name":"Meow Release Knob","bone":"root","attachment":"Meow Release Knob"}, 17 | {"name":"Meow Sustain Knob","bone":"root","attachment":"Meow Sustain Knob"}, 18 | {"name":"Meow Decay Knob","bone":"root","attachment":"Meow Decay Knob"}, 19 | {"name":"Meow Attack Knob","bone":"root","attachment":"Meow Attack Knob"}, 20 | {"name":"background","bone":"root","attachment":"background"} 21 | ], 22 | "skins":{ 23 | "default":{ 24 | "Image Emboss":{"Image Emboss":{"x":445,"y":827.5,"width":760,"height":651}}, 25 | "Logo Bg":{"Logo Bg":{"x":454.5,"y":238.5,"width":777,"height":377}}, 26 | "Polycat BG":{"Polycat BG":{"x":1315,"y":145,"width":838,"height":158}}, 27 | "POLYCAT OFF":{"POLYCAT OFF":{"x":1315,"y":147,"width":384,"height":66}}, 28 | "POLYCAT ON":{"POLYCAT ON":{"x":1315,"y":147,"width":440,"height":120}}, 29 | "Pitchbend Emboss":{"Pitchbend Emboss":{"x":1605,"y":350,"width":118,"height":118}}, 30 | "Chorus Knob":{"Chorus Knob":{"x":1414,"y":350,"width":140,"height":140}}, 31 | "Noise Knob":{"Noise Knob":{"x":1221,"y":350,"width":140,"height":140}}, 32 | "Portamento Knob":{"Portamento Knob":{"x":1029,"y":350,"width":140,"height":140}}, 33 | "Vibrato Speed Emboss":{"Vibrato Speed Emboss":{"x":1523,"y":645,"width":256,"height":106}}, 34 | "Vibrato Attack Knob":{"Vibrato Attack Knob":{"x":1221,"y":645,"width":140,"height":140}}, 35 | "Vibrato Amount Knob":{"Vibrato Amount Knob":{"x":1029,"y":645,"width":140,"height":140}}, 36 | "Meow Release Knob":{"Meow Release Knob":{"x":1605,"y":990,"width":140,"height":140}}, 37 | "Meow Sustain Knob":{"Meow Sustain Knob":{"x":1413,"y":990,"width":140,"height":140}}, 38 | "Meow Decay Knob":{"Meow Decay Knob":{"x":1221,"y":990,"width":140,"height":140}}, 39 | "Meow Attack Knob":{"Meow Attack Knob":{"x":1029,"y":990,"width":140,"height":140}}, 40 | "background":{"background":{"x":900,"y":600,"width":1800,"height":1200}} 41 | } 42 | }, 43 | "animations":{"animation":{}} 44 | } -------------------------------------------------------------------------------- /src/chorus.rs: -------------------------------------------------------------------------------- 1 | use biquad::{Biquad, ToHertz}; 2 | 3 | use crate::{ 4 | common::SampleRate, 5 | params::{ChorusParams, MAX_CHORUS_DEPTH, MAX_CHORUS_DISTANCE}, 6 | sound_gen::{NoteShape, Oscillator}, 7 | }; 8 | 9 | const CHORUS_SIZE: usize = (100.0 + 2.0 * MAX_CHORUS_DEPTH + MAX_CHORUS_DISTANCE) as usize; 10 | 11 | pub struct Chorus { 12 | delay_line: Vec, 13 | write_head: usize, 14 | read_head_oscillator: Oscillator, 15 | // To remove crackling 16 | filter: biquad::DirectForm1, 17 | } 18 | 19 | impl Chorus { 20 | pub fn new(sample_rate: SampleRate) -> Chorus { 21 | let coefficients = get_coefficients(sample_rate); 22 | Chorus { 23 | delay_line: vec![0.0; CHORUS_SIZE], 24 | write_head: 0, 25 | read_head_oscillator: Oscillator::new(), 26 | filter: biquad::DirectForm1::::new(coefficients), 27 | } 28 | } 29 | 30 | pub fn set_sample_rate(&mut self, sample_rate: SampleRate) { 31 | let new_coefficients = get_coefficients(sample_rate); 32 | self.filter.update_coefficients(new_coefficients); 33 | } 34 | 35 | pub fn next_sample( 36 | &mut self, 37 | in_sample: f32, 38 | sample_rate: SampleRate, 39 | params: &ChorusParams, 40 | shape: NoteShape, 41 | ) -> f32 { 42 | self.write_head = (self.write_head + 1).rem_euclid(self.delay_line.len()); 43 | self.delay_line[self.write_head] = in_sample; 44 | 45 | let read_head_mod = self 46 | .read_head_oscillator 47 | .next_sample(sample_rate, shape, params.rate); 48 | 49 | let offset = params.min_distance + ((read_head_mod + 1.0) * params.depth); 50 | 51 | let value = self.fractional_lookup(offset); 52 | self.filter.run(value) 53 | } 54 | 55 | // Do fractional delay interpolation. The offset value is in samples and will be how many samples 56 | // behind the write head to look at. This function does linear interpolation. 57 | fn fractional_lookup(&self, offset: f32) -> f32 { 58 | let index = self.write_head as f32 - offset; 59 | 60 | let index_upper = index.ceil() as isize; 61 | let index_lower = index_upper - 1; 62 | let index_lower2 = index_upper - 2; 63 | let index_lower3 = index_upper - 3; 64 | 65 | let index_upper = index_upper.rem_euclid(self.delay_line.len() as isize) as usize; 66 | let index_lower = index_lower.rem_euclid(self.delay_line.len() as isize) as usize; 67 | let index_lower2 = index_lower2.rem_euclid(self.delay_line.len() as isize) as usize; 68 | let index_lower3 = index_lower3.rem_euclid(self.delay_line.len() as isize) as usize; 69 | 70 | let index_fractional = index.fract(); 71 | 72 | let sample_upper = self.delay_line[index_upper]; 73 | let sample_lower = self.delay_line[index_lower]; 74 | let sample_lower2 = self.delay_line[index_lower2]; 75 | let sample_lower3 = self.delay_line[index_lower3]; 76 | 77 | cubic_interpolate( 78 | sample_lower3, 79 | sample_lower2, 80 | sample_lower, 81 | sample_upper, 82 | index_fractional, 83 | ) 84 | } 85 | } 86 | 87 | /// Interpolates between x2 and x3, where x0, x1, x2, and x3 are all evenly spaced points. 88 | /// See https://www.desmos.com/calculator/hsiestmj4o for quadratic interpolation. 89 | /// See https://www.desmos.com/calculator/bxcarpst5l for cubic interpolation. 90 | fn cubic_interpolate(x0: f32, x1: f32, x2: f32, x3: f32, t: f32) -> f32 { 91 | let c0 = (x0 / -6.0) + (x1 / 2.0) + (x2 / -2.0) + (x3 / 6.0); 92 | let c1 = (x1 / 2.0) - x2 + (x3 / 2.0); 93 | let c2 = (x0 / 6.0) - x1 + (x2 / 2.0) + (x3 / 3.0); 94 | let c3 = x2; 95 | 96 | c0 * t * t * t + c1 * t * t + c2 * t + c3 97 | } 98 | fn get_coefficients(sample_rate: SampleRate) -> biquad::Coefficients { 99 | biquad::Coefficients::::from_params( 100 | biquad::Type::LowPass, 101 | sample_rate.hz(), 102 | (sample_rate.get() / 4.0).hz(), 103 | 0.1, 104 | ) 105 | .unwrap() 106 | } 107 | -------------------------------------------------------------------------------- /src/ui_knob.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::TAU; 2 | 3 | use nih_plug::prelude::{Param, ParamSetter}; 4 | use nih_plug_egui::egui::{ 5 | epaint::PathShape, pos2, vec2, Align2, Color32, FontId, Id, Pos2, Rect, Response, Rgba, Sense, 6 | Shape, Stroke, Ui, Widget, 7 | }; 8 | use once_cell::sync::Lazy; 9 | 10 | use crate::common::lerp; 11 | 12 | static DRAG_AMOUNT_MEMORY_ID: Lazy = Lazy::new(|| Id::new("drag_amount_memory_id")); 13 | 14 | struct SliderRegion<'a, P: Param> { 15 | param: &'a P, 16 | param_setter: &'a ParamSetter<'a>, 17 | } 18 | 19 | impl<'a, P: Param> SliderRegion<'a, P> { 20 | fn new(param: &'a P, param_setter: &'a ParamSetter) -> Self { 21 | SliderRegion { 22 | param, 23 | param_setter, 24 | } 25 | } 26 | 27 | // Handle the input for a given response. Returns an f32 containing the normalized value of 28 | // the parameter. 29 | fn handle_response(&self, ui: &Ui, response: &Response) -> f32 { 30 | let value = self.param.unmodulated_normalized_value(); 31 | if response.drag_started() { 32 | self.param_setter.begin_set_parameter(self.param); 33 | ui.memory().data.insert_temp(*DRAG_AMOUNT_MEMORY_ID, value) 34 | } 35 | 36 | if response.dragged() { 37 | // Invert the y axis, since we want dragging up to increase the value and down to 38 | // decrease it, but drag_delta() has the y-axis increasing downwards. 39 | let delta = -response.drag_delta().y; 40 | let mut memory = ui.memory(); 41 | let value = memory.data.get_temp_mut_or(*DRAG_AMOUNT_MEMORY_ID, value); 42 | *value = (*value + delta / 100.0).clamp(0.0, 1.0); 43 | self.param_setter 44 | .set_parameter_normalized(self.param, *value); 45 | } 46 | 47 | if response.drag_released() { 48 | self.param_setter.end_set_parameter(self.param); 49 | } 50 | value 51 | } 52 | 53 | fn get_string(&self) -> String { 54 | self.param.to_string() 55 | } 56 | } 57 | 58 | pub struct ArcKnob<'a, P: Param> { 59 | slider_region: SliderRegion<'a, P>, 60 | radius: f32, 61 | center: Pos2, 62 | } 63 | 64 | impl<'a, P: Param> ArcKnob<'a, P> { 65 | pub fn for_param(param: &'a P, param_setter: &'a ParamSetter, radius: f32, pos: Pos2) -> Self { 66 | ArcKnob { 67 | slider_region: SliderRegion::new(param, param_setter), 68 | radius, 69 | center: pos, 70 | } 71 | } 72 | } 73 | 74 | impl<'a, P: Param> Widget for ArcKnob<'a, P> { 75 | fn ui(self, ui: &mut Ui) -> Response { 76 | let size = vec2(self.radius * 2.0, self.radius * 2.0); 77 | let rect = Rect::from_center_size(self.center, size); 78 | let response = ui.allocate_rect(rect, Sense::click_and_drag()); 79 | let value = self.slider_region.handle_response(&ui, &response); 80 | 81 | let painter = ui.painter_at(response.rect); 82 | let center = response.rect.center(); 83 | 84 | // Draw the arc 85 | let stroke_width = 5.0; 86 | let radius = self.radius - stroke_width - 2.0; 87 | let stroke = Stroke::new(stroke_width, Rgba::from_rgb(1.0, 1.0, 0.0)); 88 | let shape = Shape::Path(PathShape { 89 | points: get_arc_points(center, radius, value, 0.03), 90 | closed: false, 91 | fill: Color32::TRANSPARENT, 92 | stroke, 93 | }); 94 | painter.add(shape); 95 | response 96 | } 97 | } 98 | 99 | fn get_arc_points(center: Pos2, radius: f32, value: f32, max_arc_distance: f32) -> Vec { 100 | let start_turns: f32 = 0.625; 101 | let arc_length = lerp(0.0, -0.75, value); 102 | let end_turns = start_turns + arc_length; 103 | 104 | let points = (arc_length.abs() / max_arc_distance).ceil() as usize; 105 | let points = points.max(1); 106 | (0..=points) 107 | .map(|i| { 108 | let t = i as f32 / (points - 1) as f32; 109 | let angle = lerp(start_turns * TAU, end_turns * TAU, t); 110 | let x = radius * angle.cos(); 111 | let y = -radius * angle.sin(); 112 | pos2(x, y) + center.to_vec2() 113 | }) 114 | .collect() 115 | } 116 | 117 | pub struct TextSlider<'a, P: Param> { 118 | slider_region: SliderRegion<'a, P>, 119 | location: Rect, 120 | } 121 | 122 | impl<'a, P: Param> TextSlider<'a, P> { 123 | pub fn for_param(param: &'a P, param_setter: &'a ParamSetter, location: Rect) -> Self { 124 | TextSlider { 125 | slider_region: SliderRegion::new(param, param_setter), 126 | location, 127 | } 128 | } 129 | } 130 | 131 | impl<'a, P: Param> Widget for TextSlider<'a, P> { 132 | fn ui(self, ui: &mut Ui) -> Response { 133 | let response = ui.allocate_rect(self.location, Sense::click_and_drag()); 134 | self.slider_region.handle_response(&ui, &response); 135 | 136 | let painter = ui.painter_at(self.location); 137 | let center = self.location.center(); 138 | 139 | // Draw the text 140 | let text = self.slider_region.get_string(); 141 | let anchor = Align2::CENTER_CENTER; 142 | let color = Color32::from(Rgba::WHITE); 143 | let font = FontId::monospace(16.0); 144 | painter.text(center, anchor, text, font, color); 145 | response 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Sub}; 2 | 3 | use atomic_float::AtomicF32; 4 | use biquad::ToHertz; 5 | use derive_more::{Add, From, Into, Sub}; 6 | use nih_plug::{ 7 | nih_debug_assert, 8 | prelude::{Enum, FloatRange, Smoothable}, 9 | util::midi_note_to_freq, 10 | }; 11 | use ordered_float::OrderedFloat; 12 | 13 | use crate::sound_gen::EnvelopeType; 14 | 15 | pub type SampleTime = usize; 16 | 17 | /// A sample rate in Hz/seconds. Must be a positive value. 18 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 19 | pub struct SampleRate(pub f32); 20 | 21 | impl SampleRate { 22 | pub fn new(rate: f32) -> Option { 23 | if rate <= 0.0 { 24 | None 25 | } else { 26 | Some(SampleRate(rate)) 27 | } 28 | } 29 | 30 | pub fn get(&self) -> f32 { 31 | self.0 32 | } 33 | 34 | pub fn to_seconds(&self, samples: SampleTime) -> Seconds { 35 | let seconds = samples as f32 / self.get(); 36 | Seconds::new(seconds) 37 | } 38 | 39 | pub fn hz(&self) -> biquad::Hertz { 40 | self.get().hz() 41 | } 42 | } 43 | 44 | impl From for SampleRate { 45 | fn from(value: f32) -> Self { 46 | SampleRate::new(value).unwrap() 47 | } 48 | } 49 | 50 | /// A normalized 0.0-1.0 float representation of a velocity value. 51 | #[derive(Debug, Clone, Copy, From)] 52 | pub struct Vel { 53 | pub raw: f32, 54 | pub eased: f32, 55 | } 56 | 57 | impl Vel { 58 | pub fn new(raw: f32) -> Vel { 59 | fn ease_in_expo(x: f32) -> f32 { 60 | if x <= 0.0 { 61 | 0.0 62 | } else { 63 | (2.0f32.powf(10.0 * x) - 1.0) / (2.0f32.powf(10.0) - 1.0) 64 | } 65 | } 66 | 67 | // Easing sort of experimentally determined and is meant for use with the filter cutoff value. 68 | // See the following: 69 | // https://www.desmos.com/calculator/grjkm7iknd 70 | // https://docs.google.com/spreadsheets/d/174y4e5t8698O4-Wh9idkMVeZFb-L-7l8zLmDMGz-D-A/edit?usp=sharing 71 | Vel { 72 | raw, 73 | eased: ease_in_expo(raw), 74 | } 75 | } 76 | } 77 | 78 | /// A wrapper struct representing a duration of seconds. This struct implements [std::ops::Div], so 79 | /// it's possible to divide a [Seconds] by another [Seconds] and get an [f32]. 80 | #[derive(Debug, Clone, Copy, Add, Sub, From, PartialEq, Eq, PartialOrd, Ord)] 81 | pub struct Seconds(pub OrderedFloat); 82 | 83 | impl Seconds { 84 | pub const ZERO: Seconds = Seconds(OrderedFloat(0.0)); 85 | 86 | /// Construct a new [Seconds]. 87 | pub const fn new(seconds: f32) -> Seconds { 88 | let value = OrderedFloat(seconds); 89 | Seconds(value) 90 | } 91 | 92 | /// Get the number of seconds as an [f32]. 93 | pub const fn get(&self) -> f32 { 94 | self.0 .0 95 | } 96 | 97 | /// Interprets the seconds value as a period and converts it to Hz. 98 | pub fn as_hz(&self) -> biquad::Hertz { 99 | let hz = 1.0 / self.get(); 100 | hz.hz() 101 | } 102 | } 103 | 104 | impl std::ops::Mul for Seconds { 105 | type Output = Seconds; 106 | 107 | fn mul(self, rhs: f32) -> Self::Output { 108 | Seconds::new(self.get() * rhs) 109 | } 110 | } 111 | 112 | impl std::ops::Div for Seconds { 113 | type Output = f32; 114 | 115 | fn div(self, rhs: Self) -> Self::Output { 116 | self.get() / rhs.get() 117 | } 118 | } 119 | 120 | impl From for f32 { 121 | fn from(value: Seconds) -> Self { 122 | value.get() 123 | } 124 | } 125 | 126 | impl From for Seconds { 127 | fn from(value: f32) -> Self { 128 | Seconds(value.into()) 129 | } 130 | } 131 | 132 | /// A MIDI note 133 | #[derive(Debug, Clone, Copy, PartialEq, From, Into)] 134 | pub struct Note(pub u8); 135 | 136 | /// A struct representing linear pitch space. This exists so that portamento and filter cutoff 137 | /// sweep do not need to recompute their start and end frequencies every sample. 138 | #[derive(Debug, Clone, Copy, PartialEq, Add, Sub, From, Into)] 139 | pub struct Pitch(pub f32); 140 | 141 | impl Pitch { 142 | pub fn from_note(note: Note) -> Self { 143 | Pitch(midi_note_to_freq(note.0).log2()) 144 | } 145 | 146 | pub fn from_hertz(hertz: Hertz) -> Self { 147 | Pitch(hertz.get().log2()) 148 | } 149 | 150 | pub fn into_hertz(&self) -> Hertz { 151 | Hertz(self.0.exp2()) 152 | } 153 | } 154 | 155 | impl std::ops::Mul for Pitch { 156 | type Output = Pitch; 157 | 158 | fn mul(self, rhs: f32) -> Self::Output { 159 | Pitch(rhs * self.0) 160 | } 161 | } 162 | 163 | /// A struct representing Hertz. 164 | #[derive(Debug, Clone, Copy, PartialEq, Add, Sub, From, Into)] 165 | pub struct Hertz(pub f32); 166 | 167 | impl Hertz { 168 | pub fn new(hz: f32) -> Hertz { 169 | Hertz(hz) 170 | } 171 | 172 | pub fn get(&self) -> f32 { 173 | self.0 174 | } 175 | 176 | // Lerp linearly in octave-space 177 | pub fn lerp_octave(start: Hertz, end: Hertz, t: f32) -> Hertz { 178 | let start = start.get().log2(); 179 | let end = end.get().log2(); 180 | let interpolated = lerp(start, end, t); 181 | let hz = interpolated.exp2(); 182 | Hertz::new(hz) 183 | } 184 | 185 | pub fn ease_exp(start: f32, end: f32) -> FloatRange { 186 | FloatRange::Skewed { 187 | min: start, 188 | max: end, 189 | factor: 6.0, 190 | } 191 | } 192 | 193 | pub fn clamp(&self, min: f32, max: f32) -> Hertz { 194 | Hertz(self.get().clamp(min, max)) 195 | } 196 | } 197 | 198 | impl From> for Hertz { 199 | fn from(value: biquad::Hertz) -> Self { 200 | Hertz(value.hz()) 201 | } 202 | } 203 | 204 | impl From for biquad::Hertz { 205 | fn from(value: Hertz) -> Self { 206 | // biquad::Hertz does not accept negative Hertz. 207 | let hz = value.get().max(0.0); 208 | biquad::Hertz::::from_hz(hz).unwrap() 209 | } 210 | } 211 | 212 | impl std::ops::Mul for Hertz { 213 | type Output = Hertz; 214 | 215 | fn mul(self, rhs: f32) -> Self::Output { 216 | Hertz(self.0 * rhs) 217 | } 218 | } 219 | 220 | impl std::ops::Div for Hertz { 221 | type Output = f32; 222 | fn div(self, rhs: Hertz) -> Self::Output { 223 | self.0 / rhs.0 224 | } 225 | } 226 | 227 | /// A pitchbend value in [-1.0, +1.0] range, where +1.0 means "max upward bend" 228 | /// and -1.0 means "max downward bend" 229 | #[derive(Debug, Clone, Copy, From, Into)] 230 | pub struct Pitchbend(f32); 231 | 232 | impl Pitchbend { 233 | pub fn new(value: f32) -> Pitchbend { 234 | Pitchbend(value) 235 | } 236 | 237 | pub fn get(&self) -> f32 { 238 | self.0 239 | } 240 | 241 | /// Convert a pitchbend value that is in the range [0.0, 1.0] to a NormalizedPitchbend. 242 | /// The pitchbend value is assumed such that 0.5 is considered to be "no bend", 0.0 is "max 243 | /// downward bend", and 1.0 is "max upward bend". 244 | pub fn from_zero_one_range(value: f32) -> Pitchbend { 245 | nih_debug_assert!(0.0 <= value && value <= 1.0); 246 | Pitchbend((value * 2.0) - 1.0) 247 | } 248 | } 249 | 250 | impl Default for Pitchbend { 251 | fn default() -> Self { 252 | Self(0.0) 253 | } 254 | } 255 | 256 | impl Smoothable for Pitchbend { 257 | type Atomic = AtomicPitchbend; 258 | 259 | fn to_f32(self) -> f32 { 260 | self.get() 261 | } 262 | 263 | fn from_f32(value: f32) -> Self { 264 | Pitchbend(value) 265 | } 266 | 267 | fn atomic_new(value: Self) -> Self::Atomic { 268 | AtomicPitchbend(AtomicF32::from(value.get())) 269 | } 270 | 271 | fn atomic_load(this: &Self::Atomic) -> Self { 272 | Pitchbend(this.0.load(std::sync::atomic::Ordering::Relaxed)) 273 | } 274 | 275 | fn atomic_store(this: &Self::Atomic, value: Self) { 276 | this.0 277 | .store(value.get(), std::sync::atomic::Ordering::Relaxed) 278 | } 279 | } 280 | 281 | pub struct AtomicPitchbend(AtomicF32); 282 | 283 | impl Default for AtomicPitchbend { 284 | fn default() -> Self { 285 | Self(AtomicF32::from(0.0)) 286 | } 287 | } 288 | 289 | #[derive(Debug, Clone, Copy, PartialEq, Add, Sub)] 290 | /// A struct representing Decibels. This struct can be used with [Easing] and [EnvelopeType]. Note 291 | /// that the specific implementation for EnvelopeType has decibels lerped in amplitude space for the 292 | /// attack of a note, and then lerped in dB space for the rest of the note. This is to ensure that 293 | /// sharp attacks remain sharp. Additionally, note that decibel values below `NEG_INF_DB_THRESHOLD` 294 | /// are treated as zero. This is done so that it is possible to lerp from negative infinity decibels 295 | /// (that is, an amplitude of zero) to positive amounts in a reasonable fashion (in particular, this 296 | /// means extremely quiet sounds will instead become silence). 297 | pub struct Decibel(pub f32); 298 | 299 | impl Decibel { 300 | /// The threshold for which Decibel values below it will be treated as negative 301 | /// infinity dB. 302 | pub const NEG_INF_DB_THRESHOLD: f32 = -70.0; 303 | 304 | pub const fn from_db(db: f32) -> Decibel { 305 | Decibel(db) 306 | } 307 | 308 | pub const fn neg_inf_db() -> Decibel { 309 | Decibel::from_db(Decibel::NEG_INF_DB_THRESHOLD) 310 | } 311 | 312 | pub const fn zero_db() -> Decibel { 313 | Decibel::from_db(0.0) 314 | } 315 | 316 | pub fn from_amp(amp: f32) -> Decibel { 317 | Decibel::from_db(f32::log10(amp) * 10.0) 318 | } 319 | 320 | // Linearly interpolate in amplitude space. 321 | pub fn lerp_amp(start: Decibel, end: Decibel, t: f32) -> Decibel { 322 | let amp = lerp(start.get_amp(), end.get_amp(), t); 323 | Decibel::from_amp(amp) 324 | } 325 | 326 | // Linearly interpolate in Decibel space. 327 | pub fn lerp_db(start: f32, end: f32, t: f32) -> Decibel { 328 | let db = lerp(start, end, t); 329 | Decibel::from_db(db) 330 | } 331 | 332 | /// Get the peak amplitude a signal played at the given Decibel amount would produce. 333 | pub fn get_amp(&self) -> f32 { 334 | if self.get_db() <= Decibel::NEG_INF_DB_THRESHOLD { 335 | 0.0 336 | } else { 337 | 10.0f32.powf(self.get_db() / 10.0) 338 | } 339 | } 340 | 341 | // Get the decibel value as an f32. 342 | pub fn get_db(&self) -> f32 { 343 | self.0 344 | } 345 | } 346 | 347 | impl EnvelopeType for Decibel { 348 | fn lerp_attack(start: Self, end: Self, t: f32) -> Self { 349 | // Lerp in amplitude space during the attack phase. This is useful 350 | // long attacks usually need linear amplitude ramp ups. 351 | Decibel::lerp_amp(start, end, t) 352 | } 353 | fn lerp_decay(start: Self, end: Self, t: f32) -> Self { 354 | Decibel::lerp_db(start.get_db(), end.get_db(), t) 355 | } 356 | fn lerp_release(start: Self, end: Self, t: f32) -> Self { 357 | Decibel::lerp_db(start.get_db(), end.get_db(), t) 358 | } 359 | fn lerp_retrigger(start: Self, end: Self, t: f32) -> Self { 360 | Decibel::lerp_amp(start, end, t) 361 | } 362 | fn one() -> Self { 363 | Decibel::zero_db() 364 | } 365 | fn zero() -> Self { 366 | Decibel::neg_inf_db() 367 | } 368 | } 369 | 370 | impl std::ops::Mul for Decibel { 371 | type Output = Decibel; 372 | 373 | fn mul(self, rhs: f32) -> Self::Output { 374 | Decibel::from_db(self.0 * rhs) 375 | } 376 | } 377 | 378 | impl std::ops::Div for Decibel { 379 | type Output = f32; 380 | fn div(self, rhs: Decibel) -> Self::Output { 381 | self.get_db() / rhs.get_db() 382 | } 383 | } 384 | 385 | #[derive(Clone, Copy, PartialEq, Eq, Enum)] 386 | pub enum FilterType { 387 | #[name = "Low Pass (Single Pole)"] 388 | SinglePoleLowPass, 389 | #[name = "Low Pass"] 390 | LowPass, 391 | #[name = "High Pass"] 392 | HighPass, 393 | #[name = "Band Pass"] 394 | BandPass, 395 | #[name = "Notch"] 396 | Notch, 397 | } 398 | 399 | impl From> for FilterType { 400 | fn from(value: biquad::Type) -> Self { 401 | match value { 402 | biquad::Type::SinglePoleLowPass => FilterType::SinglePoleLowPass, 403 | biquad::Type::LowPass => FilterType::LowPass, 404 | biquad::Type::HighPass => FilterType::HighPass, 405 | biquad::Type::BandPass => FilterType::BandPass, 406 | biquad::Type::Notch => FilterType::Notch, 407 | biquad::Type::SinglePoleLowPassApprox => todo!(), 408 | biquad::Type::AllPass => todo!(), 409 | biquad::Type::LowShelf(_) => todo!(), 410 | biquad::Type::HighShelf(_) => todo!(), 411 | biquad::Type::PeakingEQ(_) => todo!(), 412 | } 413 | } 414 | } 415 | 416 | impl From for biquad::Type { 417 | fn from(value: FilterType) -> Self { 418 | match value { 419 | FilterType::SinglePoleLowPass => biquad::Type::SinglePoleLowPass, 420 | FilterType::LowPass => biquad::Type::LowPass, 421 | FilterType::HighPass => biquad::Type::HighPass, 422 | FilterType::BandPass => biquad::Type::BandPass, 423 | FilterType::Notch => biquad::Type::Notch, 424 | } 425 | } 426 | } 427 | 428 | /// Lerp between two values. This function is clamped. 429 | // TODO: you could reduce the Copy bound to just Clone although it'd be more annoying to work with. 430 | pub fn lerp(start: T, end: T, t: f32) -> T 431 | where 432 | T: Add + Sub + Mul + Copy, 433 | { 434 | (end - start) * t.clamp(0.0, 1.0) + start 435 | } 436 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod chorus; 2 | pub mod common; 3 | mod keys; 4 | mod params; 5 | mod sound_gen; 6 | mod ui; 7 | mod ui_knob; 8 | 9 | use std::sync::{atomic::Ordering, Arc}; 10 | 11 | use atomic_float::AtomicF32; 12 | use nih_plug::{nih_export_vst3, prelude::*}; 13 | 14 | use chorus::Chorus; 15 | use common::{lerp, Note, Pitch, Pitchbend, SampleRate, Vel}; 16 | use keys::KeyTracker; 17 | use params::{MeowParameters, Parameters}; 18 | use sound_gen::{NoiseGenerator, Oscillator, Voice, RETRIGGER_TIME}; 19 | 20 | /// The main plugin struct. 21 | pub struct Nyasynth { 22 | /// All the notes to be played. 23 | notes: Vec, 24 | /// The parameters which are shared with the VST host 25 | params: Arc, 26 | pitch_bend_smoother: Smoother, 27 | key_tracker: KeyTracker, 28 | // The vibrato LFO is global--the vibrato amount is shared across all generators, although each 29 | // generator gets it's own vibrato envelope. 30 | vibrato_lfo: Oscillator, 31 | // The chorus effect is also global. 32 | chorus: Chorus, 33 | /// The global noise generator 34 | noise_generator: NoiseGenerator, 35 | sample_rate: SampleRate, 36 | envelope_amount: Arc, 37 | } 38 | 39 | impl Plugin for Nyasynth { 40 | type SysExMessage = (); 41 | type BackgroundTask = (); 42 | 43 | const NAME: &'static str = "Nyasynth"; 44 | const VENDOR: &'static str = "a2aaron"; 45 | const URL: &'static str = "https://a2aaron.github.io/"; 46 | const EMAIL: &'static str = "aaronko@umich.edu"; 47 | const VERSION: &'static str = "1.0"; 48 | 49 | const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[AudioIOLayout { 50 | main_input_channels: None, 51 | main_output_channels: NonZeroU32::new(2), 52 | aux_input_ports: &[], 53 | aux_output_ports: &[], 54 | names: PortNames::const_default(), 55 | }]; 56 | 57 | const MIDI_INPUT: MidiConfig = MidiConfig::MidiCCs; 58 | const MIDI_OUTPUT: MidiConfig = MidiConfig::None; 59 | 60 | const SAMPLE_ACCURATE_AUTOMATION: bool = false; 61 | const HARD_REALTIME_ONLY: bool = false; 62 | 63 | fn initialize( 64 | &mut self, 65 | _audio_io_layout: &AudioIOLayout, 66 | buffer_config: &BufferConfig, 67 | context: &mut impl InitContext, 68 | ) -> bool { 69 | nih_plug::wrapper::setup_logger(); 70 | nih_log!("Initalizing VST..."); 71 | // On a retrigger, the next note is delayed by RETRIGGER_TIME. Hence, there is a latency 72 | // of RETRIGGER_TIME. Note that this latency doesn't exist for non-retriggered notes. 73 | context.set_latency_samples(RETRIGGER_TIME as u32); 74 | self.set_sample_rate(SampleRate(buffer_config.sample_rate)); 75 | true 76 | } 77 | 78 | fn process( 79 | &mut self, 80 | buffer: &mut Buffer, 81 | _aux: &mut AuxiliaryBuffers, 82 | context: &mut impl ProcessContext, 83 | ) -> ProcessStatus { 84 | let sample_rate = SampleRate(context.transport().sample_rate); 85 | self.set_sample_rate(sample_rate); 86 | 87 | let num_samples = buffer.samples(); 88 | let tempo = context.transport().tempo.unwrap_or(120.0) as f32; 89 | 90 | let params = MeowParameters::new(&self.params, tempo); 91 | 92 | // remove "dead" notes 93 | // we do this _before_ processing any events 94 | // because this is the start of a new frame, and we want to make sure 95 | // that midi messages do not apply to dead notes 96 | // ex: if we do this after processing midi messages, a bug occurs where 97 | // - frame 0 - note is in release state and is dead by end of frame 98 | // - frame 1 - process events send midi messages to dead note 99 | // - frame 1 - process removes dead note 100 | // - frame 1 - user is confused to why note does not play despite holding 101 | // down key (the KeyOn event was "eaten" by the dead note!) 102 | { 103 | // this weird block is required because the closure in `retain` captures all of `self` 104 | // if you pass it `self.sample_rate` or `self.params`. Doing it like this allows it to 105 | // only capture the `params` field, which avoids the issue of cannot borrow while 106 | // mutably borrowed 107 | self.notes.retain(|gen| gen.is_alive(sample_rate, ¶ms)); 108 | } 109 | 110 | let (left_out, right_out) = { 111 | let outputs = buffer.as_slice(); 112 | let (left_out, rest) = outputs.split_first_mut().unwrap(); 113 | let right_out = &mut rest[0]; 114 | (left_out, right_out) 115 | }; 116 | 117 | let mut block_start = 0; 118 | let mut max_envelope = 0.0f32; 119 | while block_start < num_samples { 120 | // Initially set the block size to 64 (or, if the number of samples in the buffer 121 | // is smaller than 64, to just that value) 122 | let mut block_len = (num_samples - block_start).min(64); 123 | // Consume all events from the context which happen before or at the start 124 | // of the block. This also shrinks the current block if there would be an event within 125 | // the block. 126 | while let Some(next_event) = context.peek_event() { 127 | let timing = next_event.timing() as usize; 128 | // If the event occurs before or at the start of this block, then process the event 129 | if timing <= block_start { 130 | self.process_event(¶ms, sample_rate, context.next_event().unwrap()) 131 | } else if timing < block_start + block_len { 132 | // If the event would occur in the middle of the block, then do not process the 133 | // event and cut this block short such that the event occurs on the first 134 | // sample of the next block. 135 | block_len = timing - block_start; 136 | } else { 137 | break; 138 | } 139 | } 140 | 141 | let block_end = block_start + block_len; 142 | 143 | // Fill each block with zeros 144 | left_out[block_start..block_end].fill(0.0); 145 | right_out[block_start..block_end].fill(0.0); 146 | 147 | let vibrato_params = ¶ms.vibrato_lfo; 148 | 149 | for i in 0..block_len { 150 | // Get the vibrato modifier, which is global across all of the voices. (Note that each 151 | // generator gets it's own vibrato envelope). 152 | let vibrato_mod = self.vibrato_lfo.next_sample( 153 | sample_rate, 154 | params.vibrato_note_shape, 155 | vibrato_params.speed, 156 | ) * vibrato_params.amount; 157 | 158 | let pitch_bend = self.pitch_bend_smoother.next(); 159 | 160 | for voice in &mut self.notes { 161 | let (left, right, total_volume) = voice.next_sample( 162 | ¶ms, 163 | &mut self.noise_generator, 164 | sample_rate, 165 | pitch_bend, 166 | vibrato_mod, 167 | ); 168 | max_envelope = max_envelope.max(total_volume); 169 | 170 | left_out[block_start + i] += left; 171 | right_out[block_start + i] += right; 172 | } 173 | } 174 | 175 | block_start = block_end; 176 | } 177 | 178 | self.envelope_amount.store(max_envelope, Ordering::Relaxed); 179 | 180 | let chorus_params = ¶ms.chorus; 181 | // Chorus and other post processing effects 182 | for i in 0..num_samples { 183 | let left = left_out[i]; 184 | let right = right_out[i]; 185 | 186 | // Get the chorus effect 187 | let chorus = self.chorus.next_sample( 188 | left, 189 | sample_rate, 190 | &chorus_params, 191 | params.chorus_note_shape, 192 | ); 193 | 194 | let left = lerp(left, chorus, chorus_params.mix); 195 | let right = lerp(right, chorus, chorus_params.mix); 196 | 197 | left_out[i] = left * params.master_vol.get_amp(); 198 | right_out[i] = right * params.master_vol.get_amp(); 199 | } 200 | ProcessStatus::Normal 201 | } 202 | 203 | fn filter_state(_state: &mut PluginState) {} 204 | 205 | fn reset(&mut self) {} 206 | 207 | fn deactivate(&mut self) { 208 | // Turn all notes off (this is done so that notes do not "dangle", since 209 | // its possible that noteoff won't ever be recieved). 210 | for note in &mut self.notes { 211 | note.note_off(); 212 | } 213 | } 214 | 215 | fn params(&self) -> Arc { 216 | Arc::clone(&self.params) as Arc 217 | } 218 | 219 | fn task_executor(&self) -> TaskExecutor { 220 | Box::new(|_| ()) 221 | } 222 | 223 | fn editor(&self, _async_executor: AsyncExecutor) -> Option> { 224 | ui::get_editor(self.params.clone(), self.envelope_amount.clone()) 225 | } 226 | } 227 | impl Default for Nyasynth { 228 | fn default() -> Self { 229 | let sample_rate = SampleRate::from(44100.0); 230 | Nyasynth { 231 | params: Arc::new(Parameters::new()), 232 | notes: Vec::with_capacity(16), 233 | key_tracker: KeyTracker::new(), 234 | vibrato_lfo: Oscillator::new(), 235 | chorus: Chorus::new(sample_rate), 236 | noise_generator: NoiseGenerator::new(), 237 | sample_rate: SampleRate(44100.0), 238 | pitch_bend_smoother: Smoother::new(SmoothingStyle::Linear(0.1)), 239 | envelope_amount: Arc::new(0.0.into()), 240 | } 241 | } 242 | } 243 | 244 | impl Vst3Plugin for Nyasynth { 245 | const VST3_CLASS_ID: [u8; 16] = *b"nyasynth.a2aaron"; 246 | 247 | const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] = 248 | &[Vst3SubCategory::Synth, Vst3SubCategory::Instrument]; 249 | } 250 | 251 | impl ClapPlugin for Nyasynth { 252 | const CLAP_ID: &'static str = "nyasynth"; 253 | 254 | const CLAP_DESCRIPTION: Option<&'static str> = Some("The World's Second Meowizer"); 255 | 256 | const CLAP_MANUAL_URL: Option<&'static str> = None; 257 | 258 | const CLAP_SUPPORT_URL: Option<&'static str> = Some("https://github.com/a2aaron/nyasynth"); 259 | 260 | const CLAP_FEATURES: &'static [ClapFeature] = 261 | &[ClapFeature::Synthesizer, ClapFeature::Instrument]; 262 | } 263 | 264 | impl Nyasynth { 265 | fn set_sample_rate(&mut self, sample_rate: SampleRate) { 266 | if sample_rate != self.sample_rate { 267 | self.sample_rate = sample_rate; 268 | self.chorus.set_sample_rate(sample_rate); 269 | } 270 | } 271 | 272 | fn process_event( 273 | &mut self, 274 | params: &MeowParameters, 275 | sample_rate: SampleRate, 276 | event: NoteEvent<()>, 277 | ) { 278 | match event { 279 | NoteEvent::NoteOn { note, velocity, .. } => { 280 | let vel = Vel::new(velocity); 281 | let note = Note(note); 282 | let polycat = params.polycat; 283 | let bend_note = self.key_tracker.note_on(note, vel, polycat); 284 | if polycat { 285 | // In polycat mode, we simply add the new note. 286 | let start_pitch = bend_note.map(Pitch::from_note); 287 | let gen = Voice::new(¶ms, start_pitch, note, vel, sample_rate); 288 | self.notes.push(gen); 289 | } else { 290 | // Monocat mode. 291 | 292 | // If there are no generators playing, start a new note 293 | if self.notes.len() == 0 { 294 | let gen = Voice::new(¶ms, None, note, vel, sample_rate); 295 | self.notes.push(gen); 296 | } else { 297 | // If there is a generator playing, retrigger it. If the generator is release state 298 | // then also do portamento. 299 | let last_note = self.notes.last_mut().unwrap(); 300 | let bend_from_current = !last_note.is_released(); 301 | let new_gen = last_note.start_crossfade( 302 | params, 303 | sample_rate, 304 | params.portamento_time, 305 | bend_from_current, 306 | note, 307 | vel, 308 | ); 309 | self.notes.push(new_gen); 310 | } 311 | }; 312 | } 313 | NoteEvent::NoteOff { note, .. } => { 314 | let polycat = params.polycat; 315 | let note = Note(note); 316 | let top_of_stack = self.key_tracker.note_off(note); 317 | 318 | if polycat { 319 | // On note off, send note off to all sound generators matching the note 320 | // This is done only to notes which are not yet released 321 | for gen in self 322 | .notes 323 | .iter_mut() 324 | .filter(|gen| !gen.is_released() && gen.note == note) 325 | { 326 | gen.note_off(); 327 | } 328 | } else { 329 | // Monocat mode. 330 | 331 | if self.key_tracker.held_keys.len() == 0 { 332 | // If there aren't any notes currently being held anymore, just send note off 333 | self.notes.iter_mut().for_each(|x| x.note_off()); 334 | } else { 335 | // If there is a sound playing and the key tracker has a new top-of-stack note, 336 | // then ask the generator retrigger. 337 | match (self.notes.last_mut(), top_of_stack) { 338 | (None, None) => (), 339 | (None, Some(_)) => (), 340 | (Some(_), None) => (), 341 | (Some(gen), Some((new_note, new_vel))) => { 342 | let new_gen = gen.start_crossfade( 343 | params, 344 | sample_rate, 345 | params.portamento_time, 346 | true, 347 | new_note, 348 | new_vel, 349 | ); 350 | self.notes.push(new_gen) 351 | } 352 | } 353 | } 354 | } 355 | } 356 | NoteEvent::MidiPitchBend { value, .. } => { 357 | let pitch_bend = Pitchbend::from_zero_one_range(value); 358 | self.pitch_bend_smoother 359 | .set_target(sample_rate.get(), pitch_bend); 360 | } 361 | _ => (), 362 | } 363 | } 364 | } 365 | 366 | impl Nyasynth { 367 | pub fn debug_params(&mut self) -> &mut Arc { 368 | &mut self.params 369 | } 370 | } 371 | 372 | // Export symbols for main 373 | nih_export_vst3!(Nyasynth); 374 | nih_export_clap!(Nyasynth); 375 | -------------------------------------------------------------------------------- /src/bin/perf.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, error::Error, path::PathBuf}; 2 | 3 | use clap::Parser; 4 | use derive_more::{Add, AddAssign, From, Into, Sub, SubAssign}; 5 | use nih_plug::context::process::Transport; 6 | use nih_plug::context::PluginApi; 7 | use nih_plug::prelude::*; 8 | use nyasynth::Nyasynth; 9 | use nyasynth::{ 10 | self, 11 | common::{SampleRate, SampleTime}, 12 | }; 13 | 14 | type VstEvent = NoteEvent<::SysExMessage>; 15 | 16 | struct MidiBlocks { 17 | event_blocks: BTreeMap>, 18 | } 19 | 20 | impl MidiBlocks { 21 | fn new( 22 | smf: midly::Smf<'_>, 23 | sample_rate: SampleRate, 24 | block_size: usize, 25 | tempo_info: TempoInfo, 26 | ) -> MidiBlocks { 27 | let events = to_vst_events(&smf, sample_rate, tempo_info); 28 | let event_blocks = split_blocks(events, block_size); 29 | MidiBlocks { event_blocks } 30 | } 31 | 32 | fn get(&self, i: usize) -> Vec { 33 | self.event_blocks.get(&i).unwrap_or(&vec![]).clone() 34 | } 35 | 36 | fn max_block(&self) -> usize { 37 | *self.event_blocks.keys().max().unwrap_or(&0) 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone, Copy)] 42 | struct TempoInfo { 43 | header_timing: midly::Timing, 44 | microseconds_per_beat: u32, 45 | } 46 | 47 | impl TempoInfo { 48 | fn new(smf: &midly::Smf) -> TempoInfo { 49 | let mut first_tempo_event = None; 50 | for track in &smf.tracks { 51 | for event in track { 52 | if let midly::TrackEventKind::Meta(meta) = event.kind { 53 | if let midly::MetaMessage::Tempo(microseconds_per_beat) = meta { 54 | first_tempo_event = Some(microseconds_per_beat.as_int()) 55 | } 56 | } 57 | } 58 | } 59 | TempoInfo { 60 | header_timing: smf.header.timing, 61 | microseconds_per_beat: first_tempo_event.unwrap_or(500000), // 120.0 bpm 62 | } 63 | } 64 | 65 | fn beats_per_minute(&self) -> f64 { 66 | let seconds_per_beat = self.microseconds_per_beat as f64 / 1_000_000.0; 67 | let minutes_per_beat = seconds_per_beat / 60.0; 68 | let beats_per_minute = 1.0 / minutes_per_beat; 69 | beats_per_minute 70 | } 71 | 72 | fn ticks_per_second(&self) -> f64 { 73 | match self.header_timing { 74 | midly::Timing::Metrical(ticks_per_beat) => { 75 | let seconds_per_beat = self.microseconds_per_beat as f64 / 1_000_000.0; 76 | let beats_per_second = 1.0 / seconds_per_beat; 77 | ticks_per_beat.as_int() as f64 * beats_per_second 78 | } 79 | midly::Timing::Timecode(frames_per_second, ticks_per_frame) => { 80 | ticks_per_frame as f64 * frames_per_second.as_f32() as f64 81 | } 82 | } 83 | } 84 | 85 | fn ticks_to_samples(&self, ticks: MIDITick, sample_rate: SampleRate) -> usize { 86 | let seconds = ticks.as_f64() / self.ticks_per_second(); 87 | let samples = sample_rate.0 as f64 * seconds; 88 | samples as usize 89 | } 90 | } 91 | 92 | // Split events into blocks such that no block exactly `block_size` samples large. Events must be sorted by the sample time. 93 | // This also sets the `delta_frames` for the events to the appropriate value. 94 | fn split_blocks( 95 | events: Vec<(VstEvent, SampleTime)>, 96 | block_size: usize, 97 | ) -> BTreeMap> { 98 | let mut blocks = BTreeMap::new(); 99 | 100 | for (mut event, samples) in events { 101 | let block_number = samples / block_size; 102 | let interblock_sample_number = samples % block_size; 103 | let block = blocks.entry(block_number).or_insert(vec![]); 104 | 105 | let timing = match &mut event { 106 | NoteEvent::NoteOn { timing, .. } => timing, 107 | NoteEvent::NoteOff { timing, .. } => timing, 108 | NoteEvent::Choke { timing, .. } => timing, 109 | NoteEvent::VoiceTerminated { timing, .. } => timing, 110 | NoteEvent::PolyModulation { timing, .. } => timing, 111 | NoteEvent::MonoAutomation { timing, .. } => timing, 112 | NoteEvent::PolyPressure { timing, .. } => timing, 113 | NoteEvent::PolyVolume { timing, .. } => timing, 114 | NoteEvent::PolyPan { timing, .. } => timing, 115 | NoteEvent::PolyTuning { timing, .. } => timing, 116 | NoteEvent::PolyVibrato { timing, .. } => timing, 117 | NoteEvent::PolyExpression { timing, .. } => timing, 118 | NoteEvent::PolyBrightness { timing, .. } => timing, 119 | NoteEvent::MidiChannelPressure { timing, .. } => timing, 120 | NoteEvent::MidiPitchBend { timing, .. } => timing, 121 | NoteEvent::MidiCC { timing, .. } => timing, 122 | NoteEvent::MidiProgramChange { timing, .. } => timing, 123 | NoteEvent::MidiSysEx { timing, .. } => timing, 124 | _ => unreachable!(), 125 | }; 126 | *timing = interblock_sample_number as u32; 127 | block.push(event) 128 | } 129 | blocks 130 | } 131 | 132 | /// Convert the tracks in a [midly::Smf] object into [VstEvent] events. Additionally, the 133 | /// time for when this event occurs, given in [SampleTime] is also provided. Note that the `delta_frames` 134 | /// value on the [VstEvent]s is always zero, and [VstMidiEvent] values are not set other than `data`. 135 | fn to_vst_events( 136 | smf: &midly::Smf, 137 | sample_rate: SampleRate, 138 | tempo_info: TempoInfo, 139 | ) -> Vec<(VstEvent, SampleTime)> { 140 | let mut vst_events = vec![]; 141 | for track in &smf.tracks { 142 | let mut delta_ticks = MIDITick(0); 143 | for track_event in track { 144 | delta_ticks += track_event.delta.into(); 145 | let vst_event = match track_event.kind { 146 | midly::TrackEventKind::Midi { channel, message } => { 147 | Some(to_vst_event(channel, message)) 148 | } 149 | midly::TrackEventKind::SysEx(_) => None, 150 | midly::TrackEventKind::Escape(_) => None, 151 | midly::TrackEventKind::Meta(_) => None, 152 | }; 153 | if let Some(vst_event) = vst_event { 154 | let samples = tempo_info.ticks_to_samples(delta_ticks, sample_rate); 155 | vst_events.push((vst_event, samples)) 156 | } 157 | } 158 | } 159 | // Sort by sample times. 160 | vst_events.sort_by(|(_, a), (_, b)| a.cmp(b)); 161 | vst_events 162 | } 163 | 164 | fn to_vst_event(channel: midly::num::u4, midi_event: midly::MidiMessage) -> VstEvent { 165 | fn normalize_u7(u7: midly::num::u7) -> f32 { 166 | u7.as_int() as f32 / 127.0 167 | } 168 | 169 | let channel = channel.as_int(); 170 | match midi_event { 171 | midly::MidiMessage::NoteOff { key, vel } => NoteEvent::NoteOff { 172 | timing: 0, 173 | voice_id: None, 174 | channel, 175 | note: key.as_int(), 176 | velocity: normalize_u7(vel), 177 | }, 178 | midly::MidiMessage::NoteOn { key, vel } => NoteEvent::NoteOn { 179 | timing: 0, 180 | voice_id: None, 181 | channel, 182 | note: key.as_int(), 183 | velocity: normalize_u7(vel), 184 | }, 185 | midly::MidiMessage::Aftertouch { key, vel } => NoteEvent::PolyPressure { 186 | timing: 0, 187 | voice_id: None, 188 | channel, 189 | note: key.as_int(), 190 | pressure: normalize_u7(vel), 191 | }, 192 | midly::MidiMessage::Controller { controller, value } => NoteEvent::MidiCC { 193 | timing: 0, 194 | channel, 195 | cc: controller.as_int(), 196 | value: normalize_u7(value), 197 | }, 198 | midly::MidiMessage::ProgramChange { program } => NoteEvent::MidiProgramChange { 199 | timing: 0, 200 | channel, 201 | program: program.as_int(), 202 | }, 203 | midly::MidiMessage::ChannelAftertouch { vel } => NoteEvent::MidiChannelPressure { 204 | timing: 0, 205 | channel, 206 | pressure: normalize_u7(vel), 207 | }, 208 | midly::MidiMessage::PitchBend { bend } => NoteEvent::MidiPitchBend { 209 | timing: 0, 210 | channel, 211 | // Note: midly pitchbend values are in the range [-1.0, 1.0] 212 | // but nih-plug's pitchbend values are in the range [0.0, 1.0] 213 | value: (bend.as_f32() + 1.0) / 2.0, 214 | }, 215 | } 216 | } 217 | 218 | #[derive(Debug, Clone, Copy, Add, AddAssign, Sub, SubAssign, From, Into)] 219 | /// A MIDI tick. The number of beats or seconds a tick is equal to depends on the particular MIDI file being played. 220 | struct MIDITick(u32); 221 | impl MIDITick { 222 | fn as_f64(&self) -> f64 { 223 | self.0 as f64 224 | } 225 | } 226 | impl From for MIDITick { 227 | fn from(value: midly::num::u28) -> Self { 228 | MIDITick(value.as_int()) 229 | } 230 | } 231 | 232 | impl From for f64 { 233 | fn from(value: MIDITick) -> Self { 234 | value.as_f64() 235 | } 236 | } 237 | 238 | struct DebugContext; 239 | 240 | impl InitContext for DebugContext { 241 | fn plugin_api(&self) -> PluginApi { 242 | PluginApi::Standalone 243 | } 244 | 245 | fn execute(&self, _task: ()) {} 246 | 247 | fn set_latency_samples(&self, _samples: u32) {} 248 | 249 | fn set_current_voice_capacity(&self, _capacity: u32) {} 250 | } 251 | 252 | impl GuiContext for DebugContext { 253 | fn plugin_api(&self) -> PluginApi { 254 | PluginApi::Standalone 255 | } 256 | 257 | fn request_resize(&self) -> bool { 258 | false 259 | } 260 | 261 | unsafe fn raw_begin_set_parameter(&self, _param: ParamPtr) {} 262 | 263 | unsafe fn raw_set_parameter_normalized(&self, param: ParamPtr, normalized: f32) { 264 | param.set_normalized_value(normalized); 265 | } 266 | 267 | unsafe fn raw_end_set_parameter(&self, _param: ParamPtr) {} 268 | 269 | fn get_state(&self) -> PluginState { 270 | todo!() 271 | } 272 | 273 | fn set_state(&self, _state: PluginState) { 274 | todo!() 275 | } 276 | } 277 | 278 | struct DebugProcessContext { 279 | events: Vec, 280 | event_index: usize, 281 | transport: Transport, 282 | } 283 | 284 | impl DebugProcessContext { 285 | fn new( 286 | events: Vec, 287 | tempo_info: &TempoInfo, 288 | sample_rate: SampleRate, 289 | ) -> DebugProcessContext { 290 | let tempo = tempo_info.beats_per_minute(); 291 | let sample_rate = sample_rate.get(); 292 | let mut transport: Transport = Transport::new(sample_rate); 293 | transport.tempo = Some(tempo); 294 | DebugProcessContext { 295 | events, 296 | event_index: 0, 297 | transport, 298 | } 299 | } 300 | } 301 | 302 | impl ProcessContext for DebugProcessContext { 303 | fn plugin_api(&self) -> PluginApi { 304 | PluginApi::Standalone 305 | } 306 | 307 | fn execute_background(&self, _task: ()) {} 308 | 309 | fn execute_gui(&self, _task: ()) {} 310 | 311 | fn transport(&self) -> &Transport { 312 | &self.transport 313 | } 314 | 315 | fn next_event(&mut self) -> Option> { 316 | let event = self.events.get(self.event_index); 317 | self.event_index += 1; 318 | event.copied() 319 | } 320 | 321 | fn peek_event(&self) -> Option<&PluginNoteEvent> { 322 | self.events.get(self.event_index) 323 | } 324 | 325 | fn send_event(&mut self, _event: PluginNoteEvent) {} 326 | 327 | fn set_latency_samples(&self, _samples: u32) {} 328 | 329 | fn set_current_voice_capacity(&self, _capacity: u32) {} 330 | } 331 | 332 | #[derive(Debug, Parser)] 333 | struct Args { 334 | #[arg(short, long = "in")] 335 | in_file: PathBuf, 336 | #[arg(short, long = "out")] 337 | out_file: PathBuf, 338 | #[arg(short, long)] 339 | polycat: bool, 340 | } 341 | 342 | fn main() -> Result<(), Box> { 343 | let args = Args::parse(); 344 | let block_size = 1024; 345 | let sample_rate = SampleRate(44100.0); 346 | 347 | let raw = std::fs::read(args.in_file)?; 348 | let smf = midly::Smf::parse(&raw)?; 349 | 350 | let tempo_info = TempoInfo::new(&smf); 351 | let blocks = MidiBlocks::new(smf, sample_rate, block_size, tempo_info); 352 | 353 | let mut nyasynth = Nyasynth::default(); 354 | let mut context = DebugContext; 355 | 356 | let audio_io_layout = Nyasynth::AUDIO_IO_LAYOUTS[0]; 357 | let buffer_config = BufferConfig { 358 | sample_rate: sample_rate.get(), 359 | min_buffer_size: None, 360 | max_buffer_size: block_size as u32, 361 | process_mode: ProcessMode::Offline, 362 | }; 363 | nyasynth.initialize(&audio_io_layout, &buffer_config, &mut context); 364 | { 365 | let params = nyasynth.debug_params(); 366 | let param_setter = ParamSetter::new(&context); 367 | { 368 | param_setter.set_parameter(¶ms.polycat, args.polycat); 369 | // set to 0.5s 370 | param_setter.set_parameter(¶ms.meow_decay, 0.5); 371 | // set to 40ms 372 | param_setter.set_parameter(¶ms.meow_release, 40.0 / 1000.0); 373 | // set vibrato amount 374 | param_setter.set_parameter(¶ms.vibrato_amount, 0.5); 375 | // set chorus on 376 | param_setter.set_parameter(¶ms.chorus_mix, 0.5); 377 | // set noise on 378 | param_setter.set_parameter(¶ms.noise_mix, 0.5); 379 | } 380 | } 381 | 382 | // // Set noise on. 383 | // // params.set_parameter(8, 1.0); 384 | 385 | // // Set vibrato amount 386 | // // params.set_parameter(4, 1.0); 387 | 388 | // // Set vibrato rate 389 | // // params.set_parameter(6, 0.5); 390 | 391 | // // Set chorus amount 392 | // // params.set_parameter(9, 0.5); 393 | 394 | nyasynth.reset(); 395 | 396 | let mut outputs: Vec = Vec::with_capacity(8_000_000); 397 | 398 | fn new_buffer<'a>(backing_buffer: &'a mut [Vec]) -> Buffer<'a> { 399 | let num_samples = backing_buffer[0].len(); 400 | let mut buffer = Buffer::default(); 401 | unsafe { 402 | buffer.set_slices(num_samples, move |output_slices| { 403 | let (first_channel, other_channels) = backing_buffer.split_at_mut(1); 404 | *output_slices = vec![&mut first_channel[0], &mut other_channels[0]]; 405 | }); 406 | } 407 | buffer 408 | } 409 | 410 | let mut backing_buffer = vec![vec![0.0; block_size]; 2]; 411 | for i in 0..(blocks.max_block() + 100) { 412 | let block = blocks.get(i); 413 | let mut context = DebugProcessContext::new(block, &tempo_info, sample_rate); 414 | let mut buffer = new_buffer(&mut backing_buffer); 415 | let mut aux = AuxiliaryBuffers { 416 | inputs: &mut [], 417 | outputs: &mut [], 418 | }; 419 | // nyasynth.process_events(events_buffer.events()); 420 | nyasynth.process(&mut buffer, &mut aux, &mut context); 421 | 422 | let output_left = &buffer.as_slice()[0]; 423 | outputs.extend_from_slice(output_left); 424 | } 425 | 426 | let mut out_file = std::fs::File::create(args.out_file)?; 427 | let header = wav::Header::new(wav::WAV_FORMAT_IEEE_FLOAT, 1, 44100, 32); 428 | wav::write( 429 | header, 430 | &wav::BitDepth::ThirtyTwoFloat(outputs), 431 | &mut out_file, 432 | )?; 433 | Ok(()) 434 | } 435 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use nih_plug::prelude::{ 4 | BoolParam, Enum, EnumParam, FloatParam, FloatRange, IntParam, IntRange, Param, Params, 5 | }; 6 | 7 | use crate::common::{Decibel, FilterType, Hertz, Seconds}; 8 | use crate::sound_gen::NoteShape; 9 | 10 | // Default values for master volume 11 | const DEFAULT_MASTER_VOL: Decibel = Decibel::from_db(-6.0); 12 | 13 | // Default values for volume envelope 14 | const DEFAULT_MEOW_ATTACK: Seconds = Seconds::new(30.0 / 1000.0); 15 | const DEFAULT_MEOW_DECAY: Seconds = Seconds::new(1.25); 16 | const DEFAULT_MEOW_SUSTAIN: Decibel = Decibel::from_db(-15.0); 17 | const DEFAULT_MEOW_RELEASE: Seconds = Seconds::new(490.0 / 1000.0); 18 | 19 | const DEFAULT_VIBRATO_AMOUNT: f32 = 0.0; 20 | const DEFAULT_VIBRATO_ATTACK: Seconds = Seconds::new(0.0); 21 | const DEFAULT_VIBRATO_RATE: VibratoRate = VibratoRate::Eighth; 22 | 23 | const DEFAULT_FILTER_ENVLOPE_MOD: Hertz = Hertz(7000.0); 24 | const DEFAULT_FILTER_DRY_WET: f32 = 1.0; // 100% filter 25 | const DEFAULT_FILTER_Q: f32 = 2.5; 26 | const DEFAULT_FILTER_TYPE: FilterType = FilterType::LowPass; // Low Pass 27 | const DEFAULT_FILTER_CUTOFF_FREQ: Hertz = Hertz(350.0); // this which will be around 7350 at max meow sustain on max velocity. 28 | 29 | const DEFAULT_CHORUS_MIX: f32 = 0.0; 30 | const DEFAULT_CHORUS_DEPTH: f32 = 44.0; 31 | const DEFAULT_CHORUS_DISTANCE: f32 = 450.0; 32 | const DEFAULT_CHORUS_RATE: Hertz = Hertz(0.33); 33 | 34 | const DEFAULT_NOISE_MIX: f32 = 0.0; 35 | 36 | const DEFAULT_PITCHBEND: u8 = 12; // +12 semis 37 | const DEFAULT_PORTAMENTO: Seconds = Seconds::new(120.0 / 1000.0); 38 | const DEFAULT_POLYCAT: bool = false; // Off 39 | 40 | pub const MAX_CHORUS_DEPTH: f32 = 100.0; 41 | pub const MAX_CHORUS_DISTANCE: f32 = 1000.0; 42 | 43 | /// The public facing parameters struct containing the computed values for each parameter value. 44 | /// Avoid constructing too many of these--it is expensive to do so. 45 | pub struct MeowParameters { 46 | pub master_vol: Decibel, 47 | pub noise_mix: f32, 48 | pub portamento_time: Seconds, 49 | pub pitchbend_max: u8, 50 | pub polycat: bool, 51 | pub vol_envelope: VolumeEnvelopeParams, 52 | pub filter: FilterParams, 53 | pub filter_envelope: FilterEnvelopeParams, 54 | pub chorus: ChorusParams, 55 | pub vibrato_attack: VibratoEnvelopeParams, 56 | pub vibrato_lfo: VibratoLFOParams, 57 | pub vibrato_note_shape: NoteShape, 58 | pub chorus_note_shape: NoteShape, 59 | } 60 | 61 | impl MeowParameters { 62 | /// Construct a MeowParameters from a normal Parameters. Doing this calls a lot of easing functions 63 | /// so avoid calling it too often (once per block, or ideally only once every time a parameter 64 | /// updates). 65 | pub fn new(parameters: &Parameters, tempo: f32) -> MeowParameters { 66 | fn seconds(param: &FloatParam) -> Seconds { 67 | Seconds::from(param.value()) 68 | } 69 | 70 | fn hertz(param: &FloatParam) -> Hertz { 71 | Hertz::from(param.value()) 72 | } 73 | 74 | fn decibel(param: &FloatParam) -> Decibel { 75 | Decibel::from_db(param.value()) 76 | } 77 | 78 | // This exhaustive destructuring helps ensure that if you add a field to Parameters, that you 79 | // also need to add a field to MeowParameters. 80 | let Parameters { 81 | meow_attack, 82 | meow_decay, 83 | meow_sustain, 84 | meow_release, 85 | vibrato_amount, 86 | vibrato_attack, 87 | vibrato_rate, 88 | portamento_time, 89 | noise_mix, 90 | chorus_mix, 91 | pitch_bend, 92 | polycat, 93 | gain, 94 | filter_envlope_mod, 95 | filter_dry_wet, 96 | filter_q, 97 | filter_type, 98 | filter_cutoff_freq, 99 | chorus_depth, 100 | chorus_distance, 101 | chorus_rate, 102 | vibrato_note_shape, 103 | chorus_note_shape, 104 | } = parameters; 105 | MeowParameters { 106 | master_vol: decibel(gain), 107 | noise_mix: noise_mix.value(), 108 | portamento_time: seconds(portamento_time), 109 | pitchbend_max: pitch_bend.value() as u8, 110 | polycat: polycat.value(), 111 | vol_envelope: VolumeEnvelopeParams { 112 | attack: seconds(meow_attack), 113 | decay: seconds(meow_decay), 114 | sustain: meow_sustain.modulated_normalized_value(), 115 | release: seconds(meow_release), 116 | }, 117 | filter: FilterParams { 118 | cutoff_freq: hertz(filter_cutoff_freq), 119 | q_value: filter_q.value(), 120 | filter_type: filter_type.value().into(), 121 | dry_wet: filter_dry_wet.value(), 122 | }, 123 | filter_envelope: FilterEnvelopeParams { 124 | attack: seconds(meow_attack), 125 | decay: seconds(meow_decay), 126 | sustain: meow_sustain.modulated_normalized_value(), 127 | release: seconds(meow_release), 128 | env_mod: hertz(filter_envlope_mod), 129 | }, 130 | chorus: ChorusParams { 131 | rate: Hertz(chorus_rate.value()), 132 | depth: chorus_depth.value(), 133 | min_distance: chorus_distance.value(), 134 | mix: chorus_mix.value(), 135 | }, 136 | vibrato_attack: VibratoEnvelopeParams { 137 | attack: Seconds::from(vibrato_attack.value()), 138 | }, 139 | vibrato_lfo: VibratoLFOParams { 140 | speed: vibrato_rate.value().as_hz(tempo), 141 | amount: vibrato_amount.value(), 142 | }, 143 | vibrato_note_shape: vibrato_note_shape.value(), 144 | chorus_note_shape: chorus_note_shape.value(), 145 | } 146 | } 147 | } 148 | 149 | // This deny is triggered if you have a field that isn't read from. The places that you probably need 150 | // to add code are in Parameters::get() and also a corresponding field in MeowParameters. 151 | #[deny(dead_code)] 152 | #[derive(Params)] 153 | pub struct Parameters { 154 | // Public parameters (exposed in UI) 155 | #[id = "meow_attack"] 156 | pub meow_attack: FloatParam, 157 | #[id = "meow_decay"] 158 | pub meow_decay: FloatParam, 159 | #[id = "meow_sustain"] 160 | pub meow_sustain: FloatParam, 161 | #[id = "meow_release"] 162 | pub meow_release: FloatParam, 163 | #[id = "vibrato_amount"] 164 | pub vibrato_amount: FloatParam, 165 | #[id = "vibrato_attack"] 166 | pub vibrato_attack: FloatParam, 167 | #[id = "vibrato_rate"] 168 | pub vibrato_rate: EnumParam, 169 | #[id = "portamento_time"] 170 | pub portamento_time: FloatParam, 171 | #[id = "noise_mix"] 172 | pub noise_mix: FloatParam, 173 | #[id = "chorus_mix"] 174 | pub chorus_mix: FloatParam, 175 | #[id = "pitch_bend"] 176 | pub pitch_bend: IntParam, 177 | #[id = "polycat"] 178 | pub polycat: BoolParam, 179 | // Internal parameter (not exposed by the original Meowsynth) 180 | #[id = "gain"] 181 | gain: FloatParam, 182 | #[id = "filter_envlope_mod"] 183 | filter_envlope_mod: FloatParam, 184 | #[id = "filter_dry_wet"] 185 | filter_dry_wet: FloatParam, 186 | #[id = "filter_q"] 187 | filter_q: FloatParam, 188 | #[id = "filter_type"] 189 | filter_type: EnumParam, 190 | #[id = "filter_cutoff_freq"] 191 | filter_cutoff_freq: FloatParam, 192 | #[id = "chorus_depth"] 193 | chorus_depth: FloatParam, 194 | #[id = "chorus_distance"] 195 | chorus_distance: FloatParam, 196 | #[id = "chorus_rate"] 197 | chorus_rate: FloatParam, 198 | // "Debug" parameters (these might become not "debug" pretty soon) 199 | #[id = "vibrato_note_shape"] 200 | vibrato_note_shape: EnumParam, 201 | #[id = "chorus_note_shape"] 202 | chorus_note_shape: EnumParam, 203 | } 204 | 205 | impl Default for Parameters { 206 | fn default() -> Self { 207 | Parameters::new() 208 | } 209 | } 210 | 211 | impl Parameters { 212 | pub fn new() -> Parameters { 213 | fn polycat_formatter(value: bool) -> String { 214 | if value { 215 | "On".to_string() 216 | } else { 217 | "Off".to_string() 218 | } 219 | } 220 | 221 | fn time(name: &'static str, default: Seconds, min: f32, max: f32) -> FloatParam { 222 | fn formatter(value: f32) -> String { 223 | if value < 1.0 { 224 | format!("{:.1} ms", value * 1000.0) 225 | } else { 226 | format!("{:.2} sec", value) 227 | } 228 | } 229 | 230 | let range = FloatRange::Skewed { 231 | min, 232 | max, 233 | factor: FloatRange::skew_factor(-2.0), 234 | }; 235 | FloatParam::new(name, default.get(), range).with_value_to_string(Arc::new(formatter)) 236 | } 237 | 238 | fn decibel(name: &'static str, default: Decibel, min: f32, max: f32) -> FloatParam { 239 | fn formatter(decibel: f32) -> String { 240 | if decibel <= Decibel::NEG_INF_DB_THRESHOLD { 241 | "-inf".to_string() 242 | } else if decibel < 0.0 { 243 | format!("{:.2}", decibel) 244 | } else { 245 | format!("+{:.2}", decibel) 246 | } 247 | } 248 | 249 | let range = FloatRange::Skewed { 250 | min, 251 | max, 252 | factor: FloatRange::gain_skew_factor(min, max), 253 | }; 254 | FloatParam::new(name, default.get_db(), range) 255 | .with_unit(" db") 256 | .with_value_to_string(Arc::new(formatter)) 257 | } 258 | 259 | fn percent(name: &'static str, default: f32) -> FloatParam { 260 | fn formatter(percent: f32) -> String { 261 | format!("{:.1}", percent * 100.0) 262 | } 263 | let range = FloatRange::Linear { min: 0.0, max: 1.0 }; 264 | FloatParam::new(name, default, range) 265 | .with_unit(" %") 266 | .with_value_to_string(Arc::new(formatter)) 267 | } 268 | 269 | pub fn freq(name: &'static str, default: Hertz, range: FloatRange) -> FloatParam { 270 | fn formatter(hz: f32) -> String { 271 | if hz < 1000.0 { 272 | format!("{:.2} Hz", hz) 273 | } else { 274 | format!("{:.2} kHz", hz / 1000.0) 275 | } 276 | } 277 | FloatParam::new(name, default.get(), range).with_value_to_string(Arc::new(formatter)) 278 | } 279 | 280 | const fn ease_linear(min: f32, max: f32) -> FloatRange { 281 | FloatRange::Linear { min, max } 282 | } 283 | 284 | let filter_envelope_mod = Hertz::ease_exp(0.0, 22100.0); 285 | let filter_cutoff_freq = Hertz::ease_exp(20.0, 22100.0); 286 | let filter_q = ease_linear(0.01, 10.0); 287 | 288 | let chorus_rate = Hertz::ease_exp(0.1, 10.0); 289 | let chorus_depth = ease_linear(0.0, MAX_CHORUS_DEPTH); 290 | let chorus_distance = ease_linear(0.0, MAX_CHORUS_DISTANCE); 291 | 292 | Parameters { 293 | meow_attack: time("Meow Attack", DEFAULT_MEOW_ATTACK, 0.001, 10.0), 294 | meow_decay: time("Meow Decay", DEFAULT_MEOW_DECAY, 0.001, 5.0), 295 | meow_sustain: decibel("Meow Sustain", DEFAULT_MEOW_SUSTAIN, -24.0, 0.0), 296 | meow_release: time("Meow Release", DEFAULT_MEOW_RELEASE, 0.001, 4.0), 297 | vibrato_amount: percent("Vibrato Amount", DEFAULT_VIBRATO_AMOUNT), 298 | vibrato_attack: time("Vibrato Attack", DEFAULT_VIBRATO_ATTACK, 0.001, 5.0), 299 | vibrato_rate: EnumParam::new("Vibrato Rate", DEFAULT_VIBRATO_RATE), 300 | portamento_time: time("Portamento", DEFAULT_PORTAMENTO, 0.0001, 5.0), 301 | noise_mix: percent("Noise", DEFAULT_NOISE_MIX), 302 | chorus_mix: percent("Chorus", DEFAULT_CHORUS_MIX), 303 | pitch_bend: IntParam::new( 304 | "Pitchbend", 305 | DEFAULT_PITCHBEND as i32, 306 | IntRange::Linear { min: 1, max: 12 }, 307 | ), 308 | polycat: BoolParam::new("Polycat", DEFAULT_POLYCAT) 309 | .with_value_to_string(Arc::new(polycat_formatter)), 310 | // Internal parameters (might not be exposed) 311 | gain: decibel("Master Volume", DEFAULT_MASTER_VOL, -36.0, 12.0), 312 | filter_envlope_mod: freq( 313 | "Filter EnvMod", 314 | DEFAULT_FILTER_ENVLOPE_MOD, 315 | filter_envelope_mod, 316 | ), 317 | filter_dry_wet: percent("Filter DryWet", DEFAULT_FILTER_DRY_WET), 318 | filter_q: FloatParam::new("Filter Q", DEFAULT_FILTER_Q, filter_q), 319 | filter_type: EnumParam::new("Filter Type", DEFAULT_FILTER_TYPE), 320 | filter_cutoff_freq: freq( 321 | "Filter Cutoff", 322 | DEFAULT_FILTER_CUTOFF_FREQ, 323 | filter_cutoff_freq, 324 | ), 325 | chorus_depth: FloatParam::new("Chorus Depth", DEFAULT_CHORUS_DEPTH, chorus_depth), 326 | chorus_distance: FloatParam::new( 327 | "Chorus Distance", 328 | DEFAULT_CHORUS_DISTANCE, 329 | chorus_distance, 330 | ), 331 | chorus_rate: freq("Chorus Rate", DEFAULT_CHORUS_RATE, chorus_rate), 332 | vibrato_note_shape: EnumParam::new("Vibrato Note Shape", NoteShape::Triangle), 333 | chorus_note_shape: EnumParam::new("Chorus Note Shape", NoteShape::Sine), 334 | } 335 | } 336 | } 337 | 338 | pub struct ChorusParams { 339 | pub rate: Hertz, 340 | pub depth: f32, 341 | pub min_distance: f32, 342 | pub mix: f32, 343 | } 344 | 345 | // A set of immutable envelope parameters. The envelope is defined as follows: 346 | // - In the attack phase, the envelope value goes from the `zero` value to the 347 | // `max` value. 348 | // - In the decay phase, the envelope value goes from the `max` value to the 349 | // `sustain` value. 350 | // - In the sustain phase, the envelope value is constant at the `sustain` value. 351 | // - In the release phase, the envelope value goes from the `sustain` value to 352 | // `zero` value. 353 | // The envelope value is then scaled by the `multiply` value 354 | pub trait EnvelopeParams { 355 | // In seconds, how long attack phase is 356 | fn attack(&self) -> Seconds; 357 | // In seconds, how long hold phase is 358 | fn hold(&self) -> Seconds; 359 | // In seconds, how long decay phase is 360 | fn decay(&self) -> Seconds; 361 | // The value to go to during sustain phase 362 | fn sustain(&self) -> T; 363 | // In seconds, how long release phase is 364 | fn release(&self) -> Seconds; 365 | // In -1.0 to 1.0 range usually. Multiplied by the value given by the ADSR 366 | fn multiply(&self) -> f32 { 367 | 1.0 368 | } 369 | } 370 | 371 | pub struct VolumeEnvelopeParams { 372 | attack: Seconds, 373 | decay: Seconds, 374 | sustain: f32, 375 | release: Seconds, 376 | } 377 | 378 | impl EnvelopeParams for VolumeEnvelopeParams { 379 | fn attack(&self) -> Seconds { 380 | self.attack 381 | } 382 | 383 | fn hold(&self) -> Seconds { 384 | Seconds::ZERO 385 | } 386 | 387 | fn decay(&self) -> Seconds { 388 | self.decay 389 | } 390 | 391 | fn sustain(&self) -> f32 { 392 | self.sustain 393 | } 394 | 395 | fn release(&self) -> Seconds { 396 | self.release 397 | } 398 | } 399 | 400 | pub struct FilterEnvelopeParams { 401 | attack: Seconds, 402 | sustain: f32, 403 | decay: Seconds, 404 | release: Seconds, 405 | pub env_mod: Hertz, 406 | } 407 | 408 | impl EnvelopeParams for FilterEnvelopeParams { 409 | fn attack(&self) -> Seconds { 410 | self.attack 411 | } 412 | 413 | fn hold(&self) -> Seconds { 414 | Seconds::ZERO 415 | } 416 | 417 | fn decay(&self) -> Seconds { 418 | self.decay 419 | } 420 | 421 | fn sustain(&self) -> f32 { 422 | self.sustain 423 | } 424 | 425 | fn release(&self) -> Seconds { 426 | self.release 427 | } 428 | } 429 | 430 | pub struct FilterParams { 431 | pub cutoff_freq: Hertz, 432 | pub q_value: f32, 433 | pub filter_type: biquad::Type, 434 | pub dry_wet: f32, 435 | } 436 | 437 | #[derive(Debug)] 438 | pub struct VibratoLFOParams { 439 | pub speed: Hertz, 440 | pub amount: f32, 441 | } 442 | 443 | pub struct VibratoEnvelopeParams { 444 | attack: Seconds, 445 | } 446 | 447 | impl EnvelopeParams for VibratoEnvelopeParams { 448 | fn attack(&self) -> Seconds { 449 | self.attack 450 | } 451 | 452 | fn hold(&self) -> Seconds { 453 | Seconds::ZERO 454 | } 455 | 456 | fn decay(&self) -> Seconds { 457 | Seconds::new(0.001) 458 | } 459 | 460 | fn sustain(&self) -> f32 { 461 | 1.0 462 | } 463 | 464 | fn release(&self) -> Seconds { 465 | Seconds::new(5.0) 466 | } 467 | } 468 | 469 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)] 470 | pub enum VibratoRate { 471 | #[name = "4 bar"] 472 | FourBar, 473 | #[name = "2 bar"] 474 | TwoBar, 475 | #[name = "1 bar"] 476 | OneBar, 477 | #[name = "1/2"] 478 | Half, 479 | #[name = "1/4"] 480 | Quarter, 481 | #[name = "1/8"] 482 | Eighth, 483 | #[name = "1/12"] 484 | Twelfth, 485 | #[name = "1/16"] 486 | Sixteenth, 487 | } 488 | 489 | impl VibratoRate { 490 | /// Converts the vibrato rate to herts, given a tempo in beats per minute 491 | pub fn as_hz(&self, tempo: f32) -> Hertz { 492 | let beats_per_seconds = tempo / 60.0; 493 | let multiplier = match self { 494 | VibratoRate::FourBar => 1.0 / 16.0, 495 | VibratoRate::TwoBar => 1.0 / 8.0, 496 | VibratoRate::OneBar => 1.0 / 4.0, 497 | VibratoRate::Half => 1.0 / 2.0, 498 | VibratoRate::Quarter => 1.0, 499 | VibratoRate::Eighth => 2.0, 500 | VibratoRate::Twelfth => 3.0, 501 | VibratoRate::Sixteenth => 4.0, 502 | }; 503 | let hertz = beats_per_seconds * multiplier; 504 | Hertz::new(hertz) 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/ui.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | sync::{atomic::Ordering, Arc}, 4 | }; 5 | 6 | use atomic_float::AtomicF32; 7 | use nih_plug::{ 8 | nih_error, 9 | prelude::{Editor, Param, ParamSetter}, 10 | }; 11 | use nih_plug_egui::{ 12 | create_egui_editor, 13 | egui::{ 14 | self, pos2, vec2, Color32, ColorImage, FontDefinitions, FontId, Frame, Pos2, Rect, Rgba, 15 | Rounding, Sense, Shape, TextureHandle, Ui, Vec2, 16 | }, 17 | EguiState, 18 | }; 19 | 20 | use crate::{ 21 | params::Parameters, 22 | ui_knob::{ArcKnob, TextSlider}, 23 | }; 24 | 25 | const SCREEN_WIDTH: u32 = 450; 26 | const SCREEN_HEIGHT: u32 = 300; 27 | 28 | fn make_arc_knob(ui: &mut Ui, setter: &ParamSetter, param: &impl Param, center: Pos2) { 29 | // Knobs are 140.0x140.0 px, but need to scaled down by a factor of 4. 30 | let radius = 140.0 / 2.0 / 4.0; 31 | ui.add(ArcKnob::for_param(param, setter, radius, center)); 32 | } 33 | 34 | fn make_text_slider(ui: &mut Ui, setter: &ParamSetter, param: &impl Param, location: Rect) { 35 | ui.add(TextSlider::for_param(param, setter, location)); 36 | } 37 | 38 | struct WidgetLocations { 39 | meow_attack: Pos2, 40 | meow_decay: Pos2, 41 | meow_sustain: Pos2, 42 | meow_release: Pos2, 43 | vibrato_attack: Pos2, 44 | vibrato_amount: Pos2, 45 | vibrato_speed: Rect, 46 | portamento_time: Pos2, 47 | noise_mix: Pos2, 48 | chorus_mix: Pos2, 49 | pitch_bend: Rect, 50 | polycat_button: Rect, 51 | polycat_on: Rect, 52 | cat_image: Rect, 53 | nyasynth_logo: Rect, 54 | } 55 | impl WidgetLocations { 56 | fn from_spine_json(value: serde_json::Value) -> WidgetLocations { 57 | use serde_json::Value; 58 | fn unwrap_object(value: &Value) -> &serde_json::Map { 59 | match value { 60 | Value::Object(map) => map, 61 | _ => panic!("Expected an object, got {}", value.to_string()), 62 | } 63 | } 64 | 65 | fn unwrap_f64(value: &Value) -> f64 { 66 | match value { 67 | Value::Number(number) => number.as_f64().unwrap(), 68 | _ => panic!("Expected a number, got {}", value.to_string()), 69 | } 70 | } 71 | 72 | /// The position data from Affinty Photo's Spine JSON export 73 | /// See http://en.esotericsoftware.com/spine-json-format for details 74 | /// It seems that the origin is located at the bottom left corner of the entire image 75 | /// and the y-axis increases going up. 76 | struct SpineData { 77 | /// The x-coordinate of the bottom left corner of the AABB 78 | x: f64, 79 | /// The y-coordinate of the bottom left corner of the AABB 80 | y: f64, 81 | /// The AABB width 82 | width: f64, 83 | /// The AABB height 84 | height: f64, 85 | } 86 | impl SpineData { 87 | fn center(&self, original_size: (f64, f64)) -> Pos2 { 88 | let x_scale_factor = SCREEN_WIDTH as f32 / original_size.0 as f32; 89 | let y_scale_factor = SCREEN_HEIGHT as f32 / original_size.1 as f32; 90 | pos2( 91 | self.x as f32 * x_scale_factor, 92 | (original_size.1 - self.y) as f32 * y_scale_factor, 93 | ) 94 | } 95 | fn size(&self, original_size: (f64, f64)) -> Vec2 { 96 | let x_scale_factor = SCREEN_WIDTH as f32 / original_size.0 as f32; 97 | let y_scale_factor = SCREEN_HEIGHT as f32 / original_size.1 as f32; 98 | vec2( 99 | self.width as f32 * x_scale_factor, 100 | self.height as f32 * y_scale_factor, 101 | ) 102 | } 103 | fn as_rect(&self, original_size: (f64, f64)) -> Rect { 104 | Rect::from_center_size(self.center(original_size), self.size(original_size)) 105 | } 106 | } 107 | fn unwrap_data(data: &Value) -> SpineData { 108 | let obj = unwrap_object(data); 109 | let x = unwrap_f64(obj.get("x").unwrap()); 110 | let y = unwrap_f64(obj.get("y").unwrap()); 111 | let width = unwrap_f64(obj.get("width").unwrap()); 112 | let height = unwrap_f64(obj.get("height").unwrap()); 113 | SpineData { 114 | x, 115 | y, 116 | width, 117 | height, 118 | } 119 | } 120 | 121 | fn get_data(default_map: &serde_json::Map, key: &str) -> SpineData { 122 | let object = unwrap_object(default_map.get(key).unwrap()); 123 | unwrap_data(object.get(key).unwrap()) 124 | } 125 | 126 | let main_obj = unwrap_object(&value); 127 | let skins = unwrap_object(main_obj.get("skins").unwrap()); 128 | let default = unwrap_object(skins.get("default").unwrap()); 129 | let bg = get_data(default, "background"); 130 | let original_size = (bg.width, bg.height); 131 | 132 | let cat_image = get_data(default, "Image Emboss") 133 | .as_rect(original_size) 134 | .shrink(10.0); 135 | 136 | WidgetLocations { 137 | meow_attack: get_data(default, "Meow Attack Knob").center(original_size), 138 | meow_decay: get_data(default, "Meow Decay Knob").center(original_size), 139 | meow_sustain: get_data(default, "Meow Sustain Knob").center(original_size), 140 | meow_release: get_data(default, "Meow Release Knob").center(original_size), 141 | vibrato_attack: get_data(default, "Vibrato Attack Knob").center(original_size), 142 | vibrato_amount: get_data(default, "Vibrato Amount Knob").center(original_size), 143 | vibrato_speed: get_data(default, "Vibrato Speed Emboss").as_rect(original_size), 144 | portamento_time: get_data(default, "Portamento Knob").center(original_size), 145 | noise_mix: get_data(default, "Noise Knob").center(original_size), 146 | chorus_mix: get_data(default, "Chorus Knob").center(original_size), 147 | pitch_bend: get_data(default, "Pitchbend Emboss").as_rect(original_size), 148 | polycat_button: get_data(default, "Polycat BG").as_rect(original_size), 149 | polycat_on: get_data(default, "POLYCAT ON").as_rect(original_size), 150 | nyasynth_logo: get_data(default, "Logo Bg").as_rect(original_size), 151 | cat_image, 152 | } 153 | } 154 | } 155 | 156 | struct EditorState { 157 | cat_images: Vec>, 158 | which_cat: usize, 159 | brushed_metal: Option, 160 | polycat_on: Option, 161 | polycat_state: bool, 162 | logo_open: bool, 163 | widget_location: WidgetLocations, 164 | envelope_amount: Arc, 165 | } 166 | 167 | impl EditorState { 168 | fn new(polycat_state: bool, envelope_amount: Arc) -> EditorState { 169 | EditorState { 170 | widget_location: WidgetLocations::from_spine_json( 171 | serde_json::from_str(include_str!("../assets/Spine.json")).unwrap(), 172 | ), 173 | cat_images: vec![], 174 | which_cat: 0, 175 | brushed_metal: None, 176 | polycat_on: None, 177 | polycat_state, 178 | logo_open: false, 179 | envelope_amount, 180 | } 181 | } 182 | 183 | fn cat_image(&self) -> TextureHandle { 184 | let cat_images = &self.cat_images[self.which_cat]; 185 | let amount = self.envelope_amount.load(Ordering::Relaxed); 186 | let i = (amount * (cat_images.len() - 1) as f32).floor() as usize; 187 | let i = i.clamp(0, cat_images.len() - 1); 188 | cat_images[i].clone() 189 | } 190 | 191 | fn brushed_metal(&self) -> TextureHandle { 192 | self.brushed_metal.clone().unwrap() 193 | } 194 | 195 | fn polycat_on(&self) -> TextureHandle { 196 | self.polycat_on.clone().unwrap() 197 | } 198 | } 199 | 200 | fn load_image_from_memory(image_data: &[u8]) -> Result> { 201 | // Always use PNG here--this is the only format that we use anyways. 202 | let format = image::ImageFormat::Png; 203 | let image = image::load_from_memory_with_format(image_data, format)?; 204 | let size = [image.width() as _, image.height() as _]; 205 | let image_buffer = image.to_rgba8(); 206 | let pixels = image_buffer.as_flat_samples(); 207 | Ok(ColorImage::from_rgba_unmultiplied(size, pixels.as_slice())) 208 | } 209 | 210 | pub fn get_editor( 211 | params: Arc, 212 | envelope_amount: Arc, 213 | ) -> Option> { 214 | let egui_state = EguiState::from_size(SCREEN_WIDTH, SCREEN_HEIGHT); 215 | let editor_state = EditorState::new(params.polycat.value(), envelope_amount); 216 | 217 | create_egui_editor( 218 | egui_state, 219 | editor_state, 220 | |cx, editor_state| { 221 | let load_image = |name: &str, image: &[u8]| -> TextureHandle { 222 | let image = load_image_from_memory(image); 223 | let image = match image { 224 | Ok(image) => image, 225 | Err(err) => { 226 | nih_error!( 227 | "Couldn't load image {}. Reason: {:?}. Falling back to example image", 228 | name, 229 | err 230 | ); 231 | ColorImage::example() 232 | } 233 | }; 234 | cx.load_texture(name, image, egui::TextureFilter::Linear) 235 | }; 236 | 237 | let load_cat_image = |name: &str, image: &[u8]| -> TextureHandle { 238 | let image = load_image_from_memory(image); 239 | let image = match image { 240 | Ok(image) => image, 241 | Err(err) => { 242 | nih_error!( 243 | "Couldn't load image {}, Reason: {:?}. Falling back to example image", 244 | name, 245 | err 246 | ); 247 | ColorImage::example() 248 | } 249 | }; 250 | // use nearest neighbor scaling for added Comedy 251 | cx.load_texture(name, image, egui::TextureFilter::Nearest) 252 | }; 253 | 254 | let cat_images = &mut editor_state.cat_images; 255 | let baksik = [ 256 | include_bytes!("../assets/cat-imgs/baksik/0.png").as_slice(), 257 | include_bytes!("../assets/cat-imgs/baksik/1.png").as_slice(), 258 | include_bytes!("../assets/cat-imgs/baksik/2.png").as_slice(), 259 | include_bytes!("../assets/cat-imgs/baksik/3.png").as_slice(), 260 | include_bytes!("../assets/cat-imgs/baksik/4.png").as_slice(), 261 | include_bytes!("../assets/cat-imgs/baksik/5.png").as_slice(), 262 | include_bytes!("../assets/cat-imgs/baksik/6.png").as_slice(), 263 | include_bytes!("../assets/cat-imgs/baksik/7.png").as_slice(), 264 | include_bytes!("../assets/cat-imgs/baksik/8.png").as_slice(), 265 | include_bytes!("../assets/cat-imgs/baksik/9.png").as_slice(), 266 | ] 267 | .iter() 268 | .enumerate() 269 | .map(|(i, img)| load_cat_image(&format!("baksik-{}", i), img)) 270 | .collect(); 271 | cat_images.push(baksik); 272 | 273 | let severian = [ 274 | include_bytes!("../assets/cat-imgs/severian/out8.png").as_slice(), 275 | include_bytes!("../assets/cat-imgs/severian/out10.png").as_slice(), 276 | include_bytes!("../assets/cat-imgs/severian/out13.png").as_slice(), 277 | include_bytes!("../assets/cat-imgs/severian/out16.png").as_slice(), 278 | include_bytes!("../assets/cat-imgs/severian/out19.png").as_slice(), 279 | include_bytes!("../assets/cat-imgs/severian/out22.png").as_slice(), 280 | include_bytes!("../assets/cat-imgs/severian/out24.png").as_slice(), 281 | include_bytes!("../assets/cat-imgs/severian/out26.png").as_slice(), 282 | include_bytes!("../assets/cat-imgs/severian/out28.png").as_slice(), 283 | include_bytes!("../assets/cat-imgs/severian/out30.png").as_slice(), 284 | ] 285 | .iter() 286 | .enumerate() 287 | .map(|(i, img)| load_cat_image(&format!("severian-{}", i), img)) 288 | .collect(); 289 | cat_images.push(severian); 290 | 291 | let chrysi = [ 292 | include_bytes!("../assets/cat-imgs/chrysi/0.png").as_slice(), 293 | include_bytes!("../assets/cat-imgs/chrysi/1.png").as_slice(), 294 | include_bytes!("../assets/cat-imgs/chrysi/2.png").as_slice(), 295 | include_bytes!("../assets/cat-imgs/chrysi/3.png").as_slice(), 296 | include_bytes!("../assets/cat-imgs/chrysi/4.png").as_slice(), 297 | include_bytes!("../assets/cat-imgs/chrysi/5.png").as_slice(), 298 | include_bytes!("../assets/cat-imgs/chrysi/6.png").as_slice(), 299 | include_bytes!("../assets/cat-imgs/chrysi/7.png").as_slice(), 300 | include_bytes!("../assets/cat-imgs/chrysi/8.png").as_slice(), 301 | include_bytes!("../assets/cat-imgs/chrysi/9.png").as_slice(), 302 | ] 303 | .iter() 304 | .enumerate() 305 | .map(|(i, img)| load_cat_image(&format!("chrysi-{}", i), img)) 306 | .collect(); 307 | cat_images.push(chrysi); 308 | 309 | editor_state.brushed_metal = Some(load_image( 310 | "metal-knob", 311 | include_bytes!("../assets/ui_2x_v2.png"), 312 | )); 313 | 314 | editor_state.polycat_on = Some(load_image( 315 | "polycat-on", 316 | include_bytes!("../assets/POLYCAT ON.png"), 317 | )); 318 | 319 | let determination = 320 | egui::FontData::from_static(include_bytes!("../assets/DTM-Mono.otf")); 321 | 322 | let mut fonts = FontDefinitions::default(); 323 | fonts 324 | .font_data 325 | .insert("Determination".to_string(), determination); 326 | 327 | // Put my font as last fallback for monospace: 328 | fonts 329 | .families 330 | .entry(egui::FontFamily::Monospace) 331 | .or_default() 332 | .insert(0, "Determination".to_owned()); 333 | cx.set_fonts(fonts); 334 | }, 335 | move |cx, setter, editor_state| { 336 | egui::CentralPanel::default() 337 | .frame( 338 | Frame::none() 339 | .fill(Rgba::from_srgba_premultiplied(0x89, 0xA9, 0xBD, 0xFF).into()), 340 | ) 341 | .show(cx, |ui| { 342 | let locs = &editor_state.widget_location; 343 | 344 | // UI Background 345 | let background = image_shape(editor_state.brushed_metal(), ui.max_rect()); 346 | ui.painter().add(background); 347 | 348 | // Cat Image 349 | let image = image_shape(editor_state.cat_image(), locs.cat_image); 350 | ui.painter().add(image); 351 | let cat_image = ui.allocate_rect(locs.cat_image, Sense::click()); 352 | if cat_image.clicked() { 353 | editor_state.which_cat = 354 | (editor_state.which_cat + 1) % editor_state.cat_images.len() 355 | } 356 | 357 | // Knobs 358 | make_arc_knob(ui, &setter, ¶ms.meow_attack, locs.meow_attack); 359 | make_arc_knob(ui, &setter, ¶ms.meow_decay, locs.meow_decay); 360 | make_arc_knob(ui, &setter, ¶ms.meow_sustain, locs.meow_sustain); 361 | make_arc_knob(ui, &setter, ¶ms.meow_release, locs.meow_release); 362 | make_arc_knob(ui, &setter, ¶ms.vibrato_amount, locs.vibrato_amount); 363 | make_arc_knob(ui, &setter, ¶ms.vibrato_attack, locs.vibrato_attack); 364 | make_text_slider(ui, setter, ¶ms.vibrato_rate, locs.vibrato_speed); 365 | make_arc_knob(ui, &setter, ¶ms.portamento_time, locs.portamento_time); 366 | make_arc_knob(ui, &setter, ¶ms.noise_mix, locs.noise_mix); 367 | make_arc_knob(ui, &setter, ¶ms.chorus_mix, locs.chorus_mix); 368 | make_text_slider(ui, setter, ¶ms.pitch_bend, locs.pitch_bend); 369 | 370 | // Polycat Button 371 | let button = ui.allocate_rect(locs.polycat_button, Sense::click()); 372 | if button.clicked() { 373 | editor_state.polycat_state = !editor_state.polycat_state; 374 | setter.begin_set_parameter(¶ms.polycat); 375 | setter.set_parameter(¶ms.polycat, editor_state.polycat_state); 376 | setter.end_set_parameter(¶ms.polycat); 377 | } 378 | if editor_state.polycat_state { 379 | let shape = image_shape(editor_state.polycat_on(), locs.polycat_on); 380 | ui.painter().add(shape); 381 | }; 382 | 383 | // Nyasynth Logo 384 | 385 | // Nyasynth Text 386 | let nyasynth_logo = ui.allocate_rect(locs.nyasynth_logo, Sense::click()); 387 | if nyasynth_logo.clicked() { 388 | editor_state.logo_open = !editor_state.logo_open; 389 | } 390 | if editor_state.logo_open { 391 | let painter = ui.painter(); 392 | painter.rect_filled( 393 | locs.cat_image, 394 | Rounding::same(1.0), 395 | Color32::from_black_alpha(200), 396 | ); 397 | let font_id = FontId::monospace(11.0); 398 | let color = Color32::WHITE; 399 | let galley = painter.layout( 400 | CREDITS_TEXT.to_string(), 401 | font_id, 402 | color, 403 | locs.cat_image.width(), 404 | ); 405 | painter.galley(locs.cat_image.left_top() + vec2(2.0, 2.0), galley); 406 | } 407 | }); 408 | }, 409 | ) 410 | } 411 | 412 | const CREDITS_TEXT: &str = r#"Determination font by Haley Wakamatsu 413 | behance.net/JapanYoshi 414 | 415 | Cat gif of Baksik originally from Meowsynth 416 | web.archive.org/web/20120930004514/myspace.com/baksik 417 | 418 | Nyasynth programmed by a2aaron 419 | github.com/a2aaron/nyasynth 420 | "#; 421 | 422 | fn image_shape(texture_handle: TextureHandle, rect: Rect) -> Shape { 423 | Shape::image( 424 | texture_handle.id(), 425 | rect, 426 | Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), 427 | Color32::WHITE, 428 | ) 429 | } 430 | -------------------------------------------------------------------------------- /src/sound_gen.rs: -------------------------------------------------------------------------------- 1 | use biquad::{Biquad, DirectForm1, ToHertz, Q_BUTTERWORTH_F32}; 2 | use nih_plug::prelude::Enum; 3 | 4 | use crate::{ 5 | common::{lerp, Hertz, Note, Pitch, Pitchbend, SampleRate, SampleTime, Seconds, Vel}, 6 | params::{EnvelopeParams, MeowParameters}, 7 | }; 8 | 9 | const TAU: f32 = std::f32::consts::TAU; 10 | 11 | // The time, in samples, for how long retrigger phase is. 12 | pub const RETRIGGER_TIME: SampleTime = 88; // 88 samples is about 2 miliseconds. 13 | 14 | /// A value in range [0.0, 1.0] which denotes the position wihtin a wave cycle. 15 | type Angle = f32; 16 | 17 | /// A small noise generator using xorshift. 18 | pub struct NoiseGenerator { 19 | state: u32, 20 | } 21 | 22 | impl NoiseGenerator { 23 | pub fn new() -> NoiseGenerator { 24 | let mut bytes = [0, 0, 0, 0]; 25 | // If this fails, then we just default to the random seed of 413. Any non-zero seed is acceptable 26 | // for our white noise generating purposes. Also, this almost certainly won't fail. 27 | let _ = getrandom::getrandom(&mut bytes); 28 | let mut seed = u32::from_be_bytes(bytes); 29 | if seed == 0 { 30 | seed = 413 31 | } 32 | NoiseGenerator { state: seed } 33 | } 34 | 35 | fn next(&mut self) -> f32 { 36 | // RNG algorithm used here is Xorshift, specifically the one listed at Wikipedia 37 | // https://en.wikipedia.org/wiki/Xorshift 38 | let x = self.state; 39 | let x = x ^ (x << 13); 40 | let x = x ^ (x >> 17); 41 | let x = x ^ (x << 5); 42 | self.state = x; 43 | 44 | // Mantissa trick: Every float in [2.0 - 4.0] is evenly spaced 45 | // so if you want evenly distributed floats, just jam random bits in the mantissa 46 | // and then convert to the appropriate range by subtraciting. 47 | 48 | // set exponent + sign bit to zero 49 | let x = x & 0b0_00000000_11111111111111111111111; 50 | // set exponent to 1000000 51 | let x = x | 0b0_10000000_00000000000000000000000; 52 | // This ensures x has the following value: 53 | // 0 10000000 XXXXXXXXXXXXXXXXXXXXXXX 54 | // ^ ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ 55 | // | | mantissa 56 | // | exponent 57 | // sign 58 | // Where X is a random bit, and 0 or 1 are constant. This ensures that x, interpreted as a 59 | // float, is a randomly chosen float in range [2.0 - 4.0] 60 | // Finally, to get the [-1.0, 1.0] range, we just subtract by 3.0. 61 | f32::from_bits(x) - 3.0 62 | } 63 | } 64 | // A type that an Envelope and EnvelopeParameter can work with. This type must 65 | // support addition and subtraction and scalar multiplication with f32. It can also 66 | // specify the easing used for the attack, decay, release, and retrigger phases 67 | // of the envelope (by default, it will use `lerp` and uses whatever the defined 68 | // Add/Sub/Mul operations do) 69 | pub trait EnvelopeType: std::ops::Mul + Copy + Clone + std::fmt::Debug 70 | where 71 | Self: std::marker::Sized, 72 | { 73 | fn lerp_attack(start: Self, end: Self, t: f32) -> Self; 74 | fn lerp_decay(start: Self, end: Self, t: f32) -> Self; 75 | fn lerp_release(start: Self, end: Self, t: f32) -> Self; 76 | fn lerp_retrigger(start: Self, end: Self, t: f32) -> Self; 77 | // The value to ease from during attack phase and to ease to during release phase 78 | fn zero() -> Self; 79 | // The value to ease to during decay phase and to ease from during decay phase 80 | fn one() -> Self; 81 | } 82 | 83 | impl EnvelopeType for f32 { 84 | fn lerp_attack(start: Self, end: Self, t: f32) -> Self { 85 | lerp::(start, end, t) 86 | } 87 | fn lerp_decay(start: Self, end: Self, t: f32) -> Self { 88 | lerp::(start, end, t) 89 | } 90 | fn lerp_release(start: Self, end: Self, t: f32) -> Self { 91 | lerp::(start, end, t) 92 | } 93 | fn lerp_retrigger(start: Self, end: Self, t: f32) -> Self { 94 | lerp::(start, end, t) 95 | } 96 | 97 | fn zero() -> Self { 98 | 0.0 99 | } 100 | fn one() -> Self { 101 | 1.0 102 | } 103 | } 104 | 105 | #[derive(Debug)] 106 | pub struct Voice { 107 | pub note: Note, 108 | // The ending pitch from which portamento ends up at. This and `start_pitch` are unaffected by 109 | // by pitch bend and pitch modifiers. 110 | end_pitch: Pitch, 111 | // The starting pitch from which portamento bends from. 112 | start_pitch: Pitch, 113 | // The velocity of the note that this SoundGenerator is playing, ignoring all 114 | // amplitude modulation effects. This is a 0.0 - 1.0 normalized value. 115 | vel: Vel, 116 | // The time, in samples, that this SoundGenerator has run since the last note on 117 | // event. This is NOT an interframe sample number! 118 | samples_since_note_on: SampleTime, 119 | // The current state of the SoundGenerator (held, released, etc) 120 | note_state: NoteState, 121 | // The computed filter sweep values. This is updated on NoteOn (or any note velocity change) as 122 | // well as once per block. 123 | filter_sweep: FilterSweeper, 124 | // The crossfader envelope, used when crossfading between notes in monocat mode. 125 | crossfader: Option, 126 | // The signal generating oscillator 127 | osc: BandlimitedOscillator, 128 | // The ADSR volume envelope 129 | vol_env: Envelope, 130 | // The vibrato attack envelope 131 | vibrato_env: Envelope, 132 | // The state for the EQ/filters, applied after the signal is generated 133 | filter: DirectForm1, 134 | // The ADSR filter envelope 135 | filter_env: Envelope, 136 | } 137 | 138 | impl Voice { 139 | pub fn new( 140 | params: &MeowParameters, 141 | start_pitch: Option, 142 | note: Note, 143 | vel: Vel, 144 | sample_rate: SampleRate, 145 | ) -> Voice { 146 | let end_pitch = Pitch::from_note(note); 147 | let start_pitch = start_pitch.unwrap_or(end_pitch); 148 | Voice { 149 | note, 150 | start_pitch, 151 | end_pitch, 152 | vel, 153 | samples_since_note_on: 0, 154 | note_state: NoteState::Held, 155 | filter_sweep: FilterSweeper::new(params, vel), 156 | crossfader: None, 157 | osc: BandlimitedOscillator::new(), 158 | vol_env: Envelope::::new(), 159 | vibrato_env: Envelope::::new(), 160 | filter_env: Envelope::::new(), 161 | filter: DirectForm1::::new( 162 | biquad::Coefficients::::from_params( 163 | biquad::Type::LowPass, 164 | sample_rate.hz(), 165 | (10000).hz(), 166 | Q_BUTTERWORTH_F32, 167 | ) 168 | .unwrap(), 169 | ), 170 | } 171 | } 172 | 173 | /// Returns true if the note is "alive" (playing audio). A note is dead if 174 | /// it is in the release state and it is after the total release time. 175 | pub fn is_alive(&self, sample_rate: SampleRate, params: &MeowParameters) -> bool { 176 | match self.note_state { 177 | NoteState::Held => true, 178 | NoteState::Released(release_time) => { 179 | // The number of seconds it has been since release 180 | let time = sample_rate.to_seconds(self.samples_since_note_on - release_time); 181 | time < params.vol_envelope.release() 182 | } 183 | } 184 | } 185 | 186 | pub fn next_sample( 187 | &mut self, 188 | params: &MeowParameters, 189 | noise_generator: &mut NoiseGenerator, 190 | sample_rate: SampleRate, 191 | pitch_bend: Pitchbend, 192 | vibrato_mod: f32, 193 | ) -> (f32, f32, f32) { 194 | self.samples_since_note_on += 1; 195 | let context = self.get_note_context(sample_rate); 196 | 197 | // Compute volume from parameters 198 | let vol_env = { 199 | // Easing computed somewhat empirically. 200 | // See https://www.desmos.com/calculator/r7k5ee8k5j for details. 201 | let x = self.vol_env.get(¶ms.vol_envelope, context); 202 | (x * x * x + x) / 2.0 203 | }; 204 | let total_volume = self.vel.raw * vol_env.max(0.0); 205 | 206 | // Compute pitch modifiers 207 | let pitch_mod = { 208 | let pitch_bend_mod = pitch_bend.get() * (params.pitchbend_max as f32); 209 | 210 | // Both vibrato_mod and vibrato_env are in the 0.0-1.0 range. We multiply by two here to 211 | // allow the vibrato to modulate the pitch by up to two semitones. 212 | let vibrato_env = self.vibrato_env.get(¶ms.vibrato_attack, context); 213 | let vibrato_mod = vibrato_mod * vibrato_env * 2.0; 214 | 215 | // Given any note, the note a single semitone away is 2^1/12 times the original note 216 | // So (2^1/12)^n = 2^(n/12) is n semitones away. 217 | Pitch((vibrato_mod + pitch_bend_mod) / 12.0) 218 | }; 219 | let base_note = self.get_current_pitch(sample_rate, params.portamento_time); 220 | 221 | // Note that we can just add these values together. This is because base_note and pitch_mod 222 | // are in the same linear space (specifically: +1.0 maps to one octave, which happens because 223 | // converting to and from Hertz uses exp2 and log2). 224 | let pitch = (base_note + pitch_mod).into_hertz(); 225 | 226 | // Get next sample 227 | let value = self.osc.next_sample(sample_rate, pitch); 228 | 229 | // Apply noise, if the noise is turned on. 230 | let value = if params.noise_mix > 0.01 { 231 | let noise = noise_generator.next(); 232 | value + noise * params.noise_mix 233 | } else { 234 | value 235 | }; 236 | 237 | // Apply filter 238 | let value = { 239 | // Only update the filter once every 16 samples (reduces expensive 240 | // biquad::Coefficients::from_params calls without reducing sound quality much.) 241 | if self.samples_since_note_on % 16 == 0 { 242 | let filter = ¶ms.filter; 243 | // TODO: investigate if this is correct 244 | let filter_env = self.filter_env.get(¶ms.filter_envelope, context); 245 | 246 | let cutoff_freq = self.filter_sweep.lerp(filter_env); 247 | 248 | // avoid numerical instability encountered at very low 249 | // or high frequencies. Clamping at around 20 Hz also 250 | // avoids blowing out the speakers. 251 | let cutoff_freq = cutoff_freq.clamp(20.0, sample_rate.0 * 0.99 / 2.0); 252 | 253 | let coefficents = biquad::Coefficients::::from_params( 254 | filter.filter_type, 255 | sample_rate.hz(), 256 | cutoff_freq.into(), 257 | filter.q_value.max(0.0), 258 | ) 259 | .unwrap(); 260 | self.filter.update_coefficients(coefficents); 261 | } 262 | 263 | let output = self.filter.run(value); 264 | if output.is_finite() { 265 | lerp(value, output, params.filter.dry_wet) 266 | } else { 267 | // If the output happens to be NaN or Infinity, output the 268 | // original signal instead. Hopefully, this will "reset" 269 | // the filter on the next sample, instead of being filled 270 | // with garbage values. 271 | value 272 | } 273 | }; 274 | let value = value * total_volume; 275 | 276 | let value = if let Some(crossfader) = &mut self.crossfader { 277 | value * crossfader.next() 278 | } else { 279 | value 280 | }; 281 | 282 | (value, value, total_volume) 283 | } 284 | 285 | pub fn note_off(&mut self) { 286 | self.vol_env.remember(); 287 | self.filter_env.remember(); 288 | self.vibrato_env.remember(); 289 | self.note_state = NoteState::Released(self.samples_since_note_on); 290 | } 291 | 292 | pub fn is_released(&self) -> bool { 293 | match self.note_state { 294 | NoteState::Released(_) => true, 295 | NoteState::Held => false, 296 | } 297 | } 298 | 299 | pub fn start_crossfade( 300 | &mut self, 301 | params: &MeowParameters, 302 | sample_rate: SampleRate, 303 | portamento_time: Seconds, 304 | bend_from_current: bool, 305 | new_note: Note, 306 | new_vel: Vel, 307 | ) -> Voice { 308 | self.note_off(); 309 | let start_pitch = if bend_from_current { 310 | Some(self.get_current_pitch(sample_rate, portamento_time)) 311 | } else { 312 | None 313 | }; 314 | let mut new_gen = Voice::new(params, start_pitch, new_note, new_vel, sample_rate); 315 | self.crossfader = Some(Crossfader::fade_out()); 316 | new_gen.crossfader = Some(Crossfader::fade_in()); 317 | new_gen 318 | } 319 | 320 | fn get_note_context(&self, sample_rate: SampleRate) -> NoteContext { 321 | NoteContext { 322 | note_state: self.note_state, 323 | sample_rate, 324 | samples_since_note_on: self.samples_since_note_on, 325 | } 326 | } 327 | 328 | fn get_current_pitch(&self, sample_rate: SampleRate, portamento_time: Seconds) -> Pitch { 329 | let time = sample_rate.to_seconds(self.samples_since_note_on); 330 | let t = (time / portamento_time).clamp(0.0, 1.0); 331 | lerp(self.start_pitch, self.end_pitch, t) 332 | } 333 | } 334 | 335 | #[derive(Debug, Clone, Copy)] 336 | struct Crossfader { 337 | state: CrossfadeState, 338 | samples: usize, 339 | } 340 | 341 | impl Crossfader { 342 | fn fade_in() -> Crossfader { 343 | Crossfader { 344 | state: CrossfadeState::FadeIn, 345 | samples: 0, 346 | } 347 | } 348 | 349 | fn fade_out() -> Crossfader { 350 | Crossfader { 351 | state: CrossfadeState::FadeOut, 352 | samples: 0, 353 | } 354 | } 355 | 356 | fn next(&mut self) -> f32 { 357 | const FADE_LENGTH: usize = 44; 358 | if self.samples >= FADE_LENGTH { 359 | match self.state { 360 | CrossfadeState::FadeIn => 1.0, 361 | CrossfadeState::FadeOut => 0.0, 362 | } 363 | } else { 364 | let t = self.samples as f32 / FADE_LENGTH as f32; 365 | self.samples += 1; 366 | 367 | match self.state { 368 | CrossfadeState::FadeIn => lerp(0.0, 1.0, t), 369 | CrossfadeState::FadeOut => lerp(1.0, 0.0, t), 370 | } 371 | } 372 | } 373 | } 374 | 375 | #[derive(Debug, Clone, Copy)] 376 | enum CrossfadeState { 377 | FadeIn, 378 | FadeOut, 379 | } 380 | 381 | #[derive(Debug, Clone, Copy)] 382 | struct FilterSweeper { 383 | start_pitch: Pitch, 384 | end_pitch: Pitch, 385 | } 386 | 387 | impl FilterSweeper { 388 | fn new(params: &MeowParameters, base_vel: Vel) -> FilterSweeper { 389 | let start_freq = params.filter.cutoff_freq; 390 | let end_freq = params.filter.cutoff_freq + params.filter_envelope.env_mod * base_vel.eased; 391 | FilterSweeper { 392 | start_pitch: Pitch::from_hertz(start_freq), 393 | end_pitch: Pitch::from_hertz(end_freq), 394 | } 395 | } 396 | 397 | fn lerp(&self, t: f32) -> Hertz { 398 | let pitch = lerp(self.start_pitch, self.end_pitch, t); 399 | pitch.into_hertz() 400 | } 401 | } 402 | 403 | #[derive(Debug)] 404 | pub struct Oscillator { 405 | angle: Angle, 406 | } 407 | 408 | impl Oscillator { 409 | pub fn new() -> Oscillator { 410 | Oscillator { angle: 0.0 } 411 | } 412 | 413 | /// Return the next sample from the oscillator 414 | /// sample_rate - the sample rate of the note. This is used to ensure that 415 | /// the pitch of a note stays the same across sample rates 416 | /// shape - what noteshape to use for the signal 417 | /// pitch - the pitch multiplier to be applied to the base frequency of the 418 | /// oscillator. 419 | pub fn next_sample(&mut self, sample_rate: SampleRate, shape: NoteShape, pitch: Hertz) -> f32 { 420 | let value = shape.get(self.angle); 421 | 422 | // Update the angle. Each sample is 1.0 / sample_rate apart for a complete waveform. 423 | let angle_delta = pitch.get() / sample_rate.get(); 424 | 425 | // Compute (self.angle + angle_delta) % 1.0. 426 | // Note that we use `fract` instead of just doing `% 1.0` since fmod is slow. 427 | self.angle = (self.angle + angle_delta).fract(); 428 | 429 | value 430 | } 431 | } 432 | 433 | /// Convience struct for holding the external state a particular note (when it was 434 | /// triggered, what state it is in, etc) 435 | /// This is mostly needed for doing envelope calculations. 436 | #[derive(Debug, Clone, Copy)] 437 | struct NoteContext { 438 | /// The number of samples since the most recent note on event. 439 | /// This value is expected to reset on Retrigger-Held state transitions. 440 | samples_since_note_on: SampleTime, 441 | /// The current state of the note being played. 442 | note_state: NoteState, 443 | /// The sample rate, in Hz/sec. 444 | sample_rate: SampleRate, 445 | } 446 | 447 | #[derive(Debug)] 448 | pub struct Envelope { 449 | // The value to lerp from when in Retrigger or Release state 450 | ease_from: T, 451 | // The previous computed envelope value, updated every time get() is called 452 | last_env_value: T, 453 | } 454 | 455 | impl Envelope { 456 | pub fn new() -> Envelope { 457 | Envelope { 458 | ease_from: T::zero(), 459 | last_env_value: T::zero(), 460 | } 461 | } 462 | 463 | /// Get the current envelope value. 464 | fn get(&mut self, params: &impl EnvelopeParams, context: NoteContext) -> T { 465 | let time = context.samples_since_note_on; 466 | let note_state = context.note_state; 467 | let sample_rate = context.sample_rate; 468 | 469 | let value = match note_state { 470 | NoteState::Held => { 471 | let time = sample_rate.to_seconds(time); 472 | let attack = params.attack(); 473 | let hold = params.hold(); 474 | let decay = params.decay(); 475 | let sustain = params.sustain(); 476 | // We check if the attack time is zero. If so, we skip the attack phase. 477 | if time < attack && attack.get() != 0.0 { 478 | // Attack 479 | T::lerp_attack(T::zero(), T::one(), time / attack) 480 | } else if time < attack + hold { 481 | // Hold 482 | T::one() 483 | } else if time < attack + hold + decay && decay.get() != 0.0 { 484 | // Similarly, we check if decay is zero. If so, skikp right to sustain. 485 | // Decay 486 | let time = time - attack - hold; 487 | T::lerp_decay(T::one(), sustain, time / decay) 488 | } else { 489 | // Sustain 490 | sustain 491 | } 492 | } 493 | NoteState::Released(rel_time) => { 494 | let time = sample_rate.to_seconds(time - rel_time); 495 | // If release is zero, then skip release and drop instantly to zero. 496 | if params.release().get() != 0.0 { 497 | T::lerp_release(self.ease_from, T::zero(), time / params.release()) 498 | } else { 499 | T::zero() 500 | } 501 | } 502 | }; 503 | // Store the premultiplied value. This is because using the post-multiplied 504 | // value will cause us to apply the multiply value again in release phase 505 | // which will cause unwanted clicks. 506 | self.last_env_value = value; 507 | value * params.multiply() 508 | } 509 | 510 | /// Set self.ease_from to the value computed by the most recent `get` call. 511 | /// This needs to be called JUST BEFORE transitioning from a Held to Released 512 | /// state or from Released to Retrigger state. 513 | /// In particular, if going from Held to Released, then `note_state` should 514 | /// be Held and `time` should be the last sample of the Hold state. 515 | /// And if going from Released to Retrigger, then `note_state` should be 516 | /// Released and `time` should be the last sample of the Released state. 517 | fn remember(&mut self) -> T { 518 | self.ease_from = self.last_env_value; 519 | self.ease_from 520 | } 521 | } 522 | 523 | /// The state of a note, along with the time and velocity that note has, if 524 | /// relevant. The typical life cycle of a note is as follows: 525 | /// None -> Held -> Released -> [removed] or Retrigger -> Held 526 | /// Notes start at None, then become Held. When they are released, they become 527 | /// Released, and are either removed if the release time on the note expires or 528 | /// become Retrigger if the note is retriggered during the release time. 529 | /// Retriggered notes become Held after a few samples automatically. 530 | /// TODO: Note states should really not track the volume of the note. That should 531 | /// be tracked on a per envelope basis, I think. 532 | #[derive(Debug, Clone, Copy)] 533 | enum NoteState { 534 | /// The note is being held down 535 | Held, 536 | /// The note has just been released. The field is in samples and denotes how many 537 | /// samples since the oscillator has started. 538 | Released(SampleTime), 539 | } 540 | 541 | #[derive(Debug, Clone, Copy, PartialEq, Enum)] 542 | pub enum NoteShape { 543 | /// A sine wave 544 | Sine, 545 | /// A sawtooth wave 546 | Sawtooth, 547 | /// A triangle wave, with a warp parameter. 548 | Triangle, 549 | } 550 | 551 | impl NoteShape { 552 | /// Return the raw waveform using the given angle 553 | fn get(&self, angle: Angle) -> f32 { 554 | // See https://www.desmos.com/calculator/dqg8kdvung for visuals 555 | // and https://www.desmos.com/calculator/hs8zd0sfkh for more visuals 556 | match self { 557 | NoteShape::Sine => (angle * TAU).sin(), 558 | NoteShape::Sawtooth => 2.0 * angle - 1.0, 559 | NoteShape::Triangle => { 560 | // Otherwise, compute a triangle/skewed triangle shape. 561 | if angle < 0.5 { 562 | 4.0 * angle - 1.0 563 | } else { 564 | -4.0 * angle + 3.0 565 | } 566 | } 567 | } 568 | } 569 | } 570 | 571 | #[derive(Debug)] 572 | struct BandlimitedOscillator { 573 | angle: f32, 574 | } 575 | 576 | impl BandlimitedOscillator { 577 | fn new() -> BandlimitedOscillator { 578 | BandlimitedOscillator { angle: 0.0 } 579 | } 580 | 581 | fn next_sample(&mut self, sample_rate: SampleRate, pitch: Hertz) -> f32 { 582 | fn sawtooth(angle: f32) -> f32 { 583 | 2.0 * angle - 1.0 584 | } 585 | let angle_delta = pitch.get() / sample_rate.get(); 586 | let x = sawtooth(self.angle); 587 | let x = x - polyblep(self.angle, angle_delta); 588 | 589 | self.angle = (self.angle + angle_delta).fract(); 590 | x 591 | } 592 | } 593 | 594 | // Polyblep code adapted from https://www.martin-finke.de/articles/audio-plugins-018-polyblep-oscillator/ 595 | // See https://www.desmos.com/calculator/wfxgajtbd0 for visuals. 596 | fn polyblep(mut angle: f32, angle_delta: f32) -> f32 { 597 | // If the angle is just after a discontinuity 598 | if angle < angle_delta { 599 | angle /= angle_delta; 600 | // -x^2 + 2x - 1.0 601 | angle + angle - angle * angle - 1.0 602 | } 603 | // Else if the angle just before the discontiunity 604 | else if angle > 1.0 - angle_delta { 605 | angle = (angle - 1.0) / angle_delta; 606 | // x^2 + 2x + 1 607 | angle * angle + angle + angle + 1.0 608 | } 609 | // 0 otherwise 610 | else { 611 | 0.0 612 | } 613 | } 614 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ab_glyph" 7 | version = "0.2.29" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" 10 | dependencies = [ 11 | "ab_glyph_rasterizer", 12 | "owned_ttf_parser", 13 | ] 14 | 15 | [[package]] 16 | name = "ab_glyph_rasterizer" 17 | version = "0.1.8" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" 20 | 21 | [[package]] 22 | name = "addr2line" 23 | version = "0.24.2" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 26 | dependencies = [ 27 | "gimli", 28 | ] 29 | 30 | [[package]] 31 | name = "adler2" 32 | version = "2.0.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 35 | 36 | [[package]] 37 | name = "ahash" 38 | version = "0.8.11" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 41 | dependencies = [ 42 | "cfg-if", 43 | "getrandom 0.2.16", 44 | "once_cell", 45 | "version_check", 46 | "zerocopy", 47 | ] 48 | 49 | [[package]] 50 | name = "aho-corasick" 51 | version = "1.1.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 54 | dependencies = [ 55 | "memchr", 56 | ] 57 | 58 | [[package]] 59 | name = "alsa" 60 | version = "0.7.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "e2562ad8dcf0f789f65c6fdaad8a8a9708ed6b488e649da28c01656ad66b8b47" 63 | dependencies = [ 64 | "alsa-sys", 65 | "bitflags 1.3.2", 66 | "libc", 67 | "nix 0.24.3", 68 | ] 69 | 70 | [[package]] 71 | name = "alsa" 72 | version = "0.9.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" 75 | dependencies = [ 76 | "alsa-sys", 77 | "bitflags 2.9.0", 78 | "cfg-if", 79 | "libc", 80 | ] 81 | 82 | [[package]] 83 | name = "alsa-sys" 84 | version = "0.3.1" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" 87 | dependencies = [ 88 | "libc", 89 | "pkg-config", 90 | ] 91 | 92 | [[package]] 93 | name = "anstream" 94 | version = "0.6.18" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 97 | dependencies = [ 98 | "anstyle", 99 | "anstyle-parse", 100 | "anstyle-query", 101 | "anstyle-wincon", 102 | "colorchoice", 103 | "is_terminal_polyfill", 104 | "utf8parse", 105 | ] 106 | 107 | [[package]] 108 | name = "anstyle" 109 | version = "1.0.10" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 112 | 113 | [[package]] 114 | name = "anstyle-parse" 115 | version = "0.2.6" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 118 | dependencies = [ 119 | "utf8parse", 120 | ] 121 | 122 | [[package]] 123 | name = "anstyle-query" 124 | version = "1.1.2" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 127 | dependencies = [ 128 | "windows-sys 0.59.0", 129 | ] 130 | 131 | [[package]] 132 | name = "anstyle-wincon" 133 | version = "3.0.7" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 136 | dependencies = [ 137 | "anstyle", 138 | "once_cell", 139 | "windows-sys 0.59.0", 140 | ] 141 | 142 | [[package]] 143 | name = "anyhow" 144 | version = "1.0.98" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 147 | 148 | [[package]] 149 | name = "anymap" 150 | version = "0.12.1" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" 153 | 154 | [[package]] 155 | name = "atomic_float" 156 | version = "0.1.0" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" 159 | 160 | [[package]] 161 | name = "atomic_refcell" 162 | version = "0.1.13" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" 165 | 166 | [[package]] 167 | name = "atty" 168 | version = "0.2.14" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 171 | dependencies = [ 172 | "hermit-abi", 173 | "libc", 174 | "winapi", 175 | ] 176 | 177 | [[package]] 178 | name = "autocfg" 179 | version = "1.4.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 182 | 183 | [[package]] 184 | name = "backtrace" 185 | version = "0.3.74" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 188 | dependencies = [ 189 | "addr2line", 190 | "cfg-if", 191 | "libc", 192 | "miniz_oxide", 193 | "object", 194 | "rustc-demangle", 195 | "windows-targets 0.52.6", 196 | ] 197 | 198 | [[package]] 199 | name = "baseview" 200 | version = "0.1.0" 201 | source = "git+https://github.com/RustAudio/baseview.git?rev=7001c2521fa1a439a01967cb881b411cd75d9ee0#7001c2521fa1a439a01967cb881b411cd75d9ee0" 202 | dependencies = [ 203 | "cocoa", 204 | "core-foundation", 205 | "keyboard-types", 206 | "nix 0.22.3", 207 | "objc", 208 | "raw-window-handle", 209 | "uuid", 210 | "winapi", 211 | "x11", 212 | "xcb", 213 | "xcb-util", 214 | ] 215 | 216 | [[package]] 217 | name = "baseview" 218 | version = "0.1.0" 219 | source = "git+https://github.com/RustAudio/baseview.git?rev=eae4033e7d2cc9c31ccaa2794d5d08eedf2f510c#eae4033e7d2cc9c31ccaa2794d5d08eedf2f510c" 220 | dependencies = [ 221 | "cocoa", 222 | "core-foundation", 223 | "keyboard-types", 224 | "nix 0.22.3", 225 | "objc", 226 | "raw-window-handle", 227 | "uuid", 228 | "winapi", 229 | "x11", 230 | "xcb", 231 | "xcb-util", 232 | ] 233 | 234 | [[package]] 235 | name = "bindgen" 236 | version = "0.70.1" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" 239 | dependencies = [ 240 | "bitflags 2.9.0", 241 | "cexpr", 242 | "clang-sys", 243 | "itertools", 244 | "proc-macro2", 245 | "quote", 246 | "regex", 247 | "rustc-hash", 248 | "shlex", 249 | "syn 2.0.100", 250 | ] 251 | 252 | [[package]] 253 | name = "biquad" 254 | version = "0.4.2" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "820524f5e3e3add696ddf69f79575772e152c0e78e9f0370b56990a7e808ec3e" 257 | dependencies = [ 258 | "libm", 259 | ] 260 | 261 | [[package]] 262 | name = "bitflags" 263 | version = "1.3.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 266 | 267 | [[package]] 268 | name = "bitflags" 269 | version = "2.9.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 272 | 273 | [[package]] 274 | name = "block" 275 | version = "0.1.6" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" 278 | 279 | [[package]] 280 | name = "bumpalo" 281 | version = "3.17.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 284 | 285 | [[package]] 286 | name = "bytemuck" 287 | version = "1.22.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" 290 | dependencies = [ 291 | "bytemuck_derive", 292 | ] 293 | 294 | [[package]] 295 | name = "bytemuck_derive" 296 | version = "1.9.3" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" 299 | dependencies = [ 300 | "proc-macro2", 301 | "quote", 302 | "syn 2.0.100", 303 | ] 304 | 305 | [[package]] 306 | name = "byteorder" 307 | version = "1.5.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 310 | 311 | [[package]] 312 | name = "bytes" 313 | version = "1.10.1" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 316 | 317 | [[package]] 318 | name = "cache-padded" 319 | version = "1.3.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" 322 | 323 | [[package]] 324 | name = "camino" 325 | version = "1.1.9" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 328 | dependencies = [ 329 | "serde", 330 | ] 331 | 332 | [[package]] 333 | name = "cargo-platform" 334 | version = "0.1.9" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 337 | dependencies = [ 338 | "serde", 339 | ] 340 | 341 | [[package]] 342 | name = "cargo_metadata" 343 | version = "0.18.1" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" 346 | dependencies = [ 347 | "camino", 348 | "cargo-platform", 349 | "semver", 350 | "serde", 351 | "serde_json", 352 | "thiserror", 353 | ] 354 | 355 | [[package]] 356 | name = "cc" 357 | version = "1.2.19" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" 360 | dependencies = [ 361 | "jobserver", 362 | "libc", 363 | "shlex", 364 | ] 365 | 366 | [[package]] 367 | name = "cesu8" 368 | version = "1.1.0" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 371 | 372 | [[package]] 373 | name = "cexpr" 374 | version = "0.6.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 377 | dependencies = [ 378 | "nom", 379 | ] 380 | 381 | [[package]] 382 | name = "cfg-if" 383 | version = "1.0.0" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 386 | 387 | [[package]] 388 | name = "clang-sys" 389 | version = "1.8.1" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 392 | dependencies = [ 393 | "glob", 394 | "libc", 395 | "libloading 0.8.6", 396 | ] 397 | 398 | [[package]] 399 | name = "clap" 400 | version = "4.5.37" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 403 | dependencies = [ 404 | "clap_builder", 405 | "clap_derive", 406 | ] 407 | 408 | [[package]] 409 | name = "clap-sys" 410 | version = "0.3.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "27f4e06657d33d5d194db1155cef359c359483a4170362bf4e105146dd0109e3" 413 | 414 | [[package]] 415 | name = "clap_builder" 416 | version = "4.5.37" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 419 | dependencies = [ 420 | "anstream", 421 | "anstyle", 422 | "clap_lex", 423 | "strsim", 424 | "terminal_size", 425 | ] 426 | 427 | [[package]] 428 | name = "clap_derive" 429 | version = "4.5.32" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 432 | dependencies = [ 433 | "heck", 434 | "proc-macro2", 435 | "quote", 436 | "syn 2.0.100", 437 | ] 438 | 439 | [[package]] 440 | name = "clap_lex" 441 | version = "0.7.4" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 444 | 445 | [[package]] 446 | name = "clipboard-win" 447 | version = "3.1.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" 450 | dependencies = [ 451 | "lazy-bytes-cast", 452 | "winapi", 453 | ] 454 | 455 | [[package]] 456 | name = "cocoa" 457 | version = "0.24.1" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" 460 | dependencies = [ 461 | "bitflags 1.3.2", 462 | "block", 463 | "cocoa-foundation", 464 | "core-foundation", 465 | "core-graphics", 466 | "foreign-types", 467 | "libc", 468 | "objc", 469 | ] 470 | 471 | [[package]] 472 | name = "cocoa-foundation" 473 | version = "0.1.2" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" 476 | dependencies = [ 477 | "bitflags 1.3.2", 478 | "block", 479 | "core-foundation", 480 | "core-graphics-types", 481 | "libc", 482 | "objc", 483 | ] 484 | 485 | [[package]] 486 | name = "color_quant" 487 | version = "1.1.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" 490 | 491 | [[package]] 492 | name = "colorchoice" 493 | version = "1.0.3" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 496 | 497 | [[package]] 498 | name = "combine" 499 | version = "4.6.7" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 502 | dependencies = [ 503 | "bytes", 504 | "memchr", 505 | ] 506 | 507 | [[package]] 508 | name = "convert_case" 509 | version = "0.4.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 512 | 513 | [[package]] 514 | name = "copypasta" 515 | version = "0.8.2" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "133fc8675ee3a4ec9aa513584deda9aa0faeda3586b87f7f0f2ba082c66fb172" 518 | dependencies = [ 519 | "clipboard-win", 520 | "objc", 521 | "objc-foundation", 522 | "objc_id", 523 | "smithay-clipboard", 524 | "x11-clipboard", 525 | ] 526 | 527 | [[package]] 528 | name = "core-foundation" 529 | version = "0.9.4" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 532 | dependencies = [ 533 | "core-foundation-sys", 534 | "libc", 535 | ] 536 | 537 | [[package]] 538 | name = "core-foundation-sys" 539 | version = "0.8.7" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 542 | 543 | [[package]] 544 | name = "core-graphics" 545 | version = "0.22.3" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 548 | dependencies = [ 549 | "bitflags 1.3.2", 550 | "core-foundation", 551 | "core-graphics-types", 552 | "foreign-types", 553 | "libc", 554 | ] 555 | 556 | [[package]] 557 | name = "core-graphics-types" 558 | version = "0.1.3" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 561 | dependencies = [ 562 | "bitflags 1.3.2", 563 | "core-foundation", 564 | "libc", 565 | ] 566 | 567 | [[package]] 568 | name = "coreaudio-rs" 569 | version = "0.11.3" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace" 572 | dependencies = [ 573 | "bitflags 1.3.2", 574 | "core-foundation-sys", 575 | "coreaudio-sys", 576 | ] 577 | 578 | [[package]] 579 | name = "coreaudio-sys" 580 | version = "0.2.16" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" 583 | dependencies = [ 584 | "bindgen", 585 | ] 586 | 587 | [[package]] 588 | name = "coremidi" 589 | version = "0.6.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5" 592 | dependencies = [ 593 | "core-foundation", 594 | "core-foundation-sys", 595 | "coremidi-sys", 596 | ] 597 | 598 | [[package]] 599 | name = "coremidi-sys" 600 | version = "3.1.1" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "709d142e542467e028d5dc5f0374392339ab7dead0c48c129504de2ccd667e1b" 603 | dependencies = [ 604 | "core-foundation-sys", 605 | ] 606 | 607 | [[package]] 608 | name = "cpal" 609 | version = "0.15.3" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779" 612 | dependencies = [ 613 | "alsa 0.9.1", 614 | "core-foundation-sys", 615 | "coreaudio-rs", 616 | "dasp_sample", 617 | "jni", 618 | "js-sys", 619 | "libc", 620 | "mach2", 621 | "ndk", 622 | "ndk-context", 623 | "oboe", 624 | "wasm-bindgen", 625 | "wasm-bindgen-futures", 626 | "web-sys", 627 | "windows 0.54.0", 628 | ] 629 | 630 | [[package]] 631 | name = "crc32fast" 632 | version = "1.4.2" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 635 | dependencies = [ 636 | "cfg-if", 637 | ] 638 | 639 | [[package]] 640 | name = "crossbeam" 641 | version = "0.8.4" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" 644 | dependencies = [ 645 | "crossbeam-channel", 646 | "crossbeam-deque", 647 | "crossbeam-epoch", 648 | "crossbeam-queue", 649 | "crossbeam-utils", 650 | ] 651 | 652 | [[package]] 653 | name = "crossbeam-channel" 654 | version = "0.5.15" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 657 | dependencies = [ 658 | "crossbeam-utils", 659 | ] 660 | 661 | [[package]] 662 | name = "crossbeam-deque" 663 | version = "0.8.6" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 666 | dependencies = [ 667 | "crossbeam-epoch", 668 | "crossbeam-utils", 669 | ] 670 | 671 | [[package]] 672 | name = "crossbeam-epoch" 673 | version = "0.9.18" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 676 | dependencies = [ 677 | "crossbeam-utils", 678 | ] 679 | 680 | [[package]] 681 | name = "crossbeam-queue" 682 | version = "0.3.12" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 685 | dependencies = [ 686 | "crossbeam-utils", 687 | ] 688 | 689 | [[package]] 690 | name = "crossbeam-utils" 691 | version = "0.8.21" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 694 | 695 | [[package]] 696 | name = "cty" 697 | version = "0.2.2" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" 700 | 701 | [[package]] 702 | name = "dasp_sample" 703 | version = "0.11.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" 706 | 707 | [[package]] 708 | name = "deranged" 709 | version = "0.4.0" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 712 | dependencies = [ 713 | "powerfmt", 714 | ] 715 | 716 | [[package]] 717 | name = "derive_more" 718 | version = "0.99.19" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" 721 | dependencies = [ 722 | "convert_case", 723 | "proc-macro2", 724 | "quote", 725 | "rustc_version", 726 | "syn 2.0.100", 727 | ] 728 | 729 | [[package]] 730 | name = "dlib" 731 | version = "0.5.2" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 734 | dependencies = [ 735 | "libloading 0.8.6", 736 | ] 737 | 738 | [[package]] 739 | name = "downcast-rs" 740 | version = "1.2.1" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 743 | 744 | [[package]] 745 | name = "egui" 746 | version = "0.19.0" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "fc9fcd393c3daaaf5909008a1d948319d538b79c51871e4df0993260260a94e4" 749 | dependencies = [ 750 | "ahash", 751 | "epaint", 752 | "nohash-hasher", 753 | ] 754 | 755 | [[package]] 756 | name = "egui-baseview" 757 | version = "0.1.0" 758 | source = "git+https://github.com/BillyDM/egui-baseview.git?rev=46e21cc11c57c705fb83611389399ec3d2670a44#46e21cc11c57c705fb83611389399ec3d2670a44" 759 | dependencies = [ 760 | "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=eae4033e7d2cc9c31ccaa2794d5d08eedf2f510c)", 761 | "copypasta", 762 | "egui", 763 | "egui_glow", 764 | "keyboard-types", 765 | "raw-window-handle", 766 | ] 767 | 768 | [[package]] 769 | name = "egui_glow" 770 | version = "0.19.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "ad77d4a00402bae9658ee64be148f4b2a0b38e4fc7874970575ca01ed1c5b75d" 773 | dependencies = [ 774 | "bytemuck", 775 | "egui", 776 | "glow", 777 | "memoffset", 778 | "tracing", 779 | "wasm-bindgen", 780 | "web-sys", 781 | ] 782 | 783 | [[package]] 784 | name = "either" 785 | version = "1.15.0" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 788 | 789 | [[package]] 790 | name = "emath" 791 | version = "0.19.0" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "9542a40106fdba943a055f418d1746a050e1a903a049b030c2b097d4686a33cf" 794 | dependencies = [ 795 | "bytemuck", 796 | ] 797 | 798 | [[package]] 799 | name = "epaint" 800 | version = "0.19.0" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "5ba04741be7f6602b1a1b28f1082cce45948a7032961c52814f8946b28493300" 803 | dependencies = [ 804 | "ab_glyph", 805 | "ahash", 806 | "atomic_refcell", 807 | "bytemuck", 808 | "emath", 809 | "nohash-hasher", 810 | "parking_lot", 811 | ] 812 | 813 | [[package]] 814 | name = "equivalent" 815 | version = "1.0.2" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 818 | 819 | [[package]] 820 | name = "errno" 821 | version = "0.3.11" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 824 | dependencies = [ 825 | "libc", 826 | "windows-sys 0.59.0", 827 | ] 828 | 829 | [[package]] 830 | name = "fdeflate" 831 | version = "0.3.7" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 834 | dependencies = [ 835 | "simd-adler32", 836 | ] 837 | 838 | [[package]] 839 | name = "flate2" 840 | version = "1.1.1" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" 843 | dependencies = [ 844 | "crc32fast", 845 | "miniz_oxide", 846 | ] 847 | 848 | [[package]] 849 | name = "foreign-types" 850 | version = "0.3.2" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 853 | dependencies = [ 854 | "foreign-types-shared", 855 | ] 856 | 857 | [[package]] 858 | name = "foreign-types-shared" 859 | version = "0.1.1" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 862 | 863 | [[package]] 864 | name = "gethostname" 865 | version = "0.2.3" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" 868 | dependencies = [ 869 | "libc", 870 | "winapi", 871 | ] 872 | 873 | [[package]] 874 | name = "getrandom" 875 | version = "0.2.16" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 878 | dependencies = [ 879 | "cfg-if", 880 | "libc", 881 | "wasi 0.11.0+wasi-snapshot-preview1", 882 | ] 883 | 884 | [[package]] 885 | name = "getrandom" 886 | version = "0.3.2" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 889 | dependencies = [ 890 | "cfg-if", 891 | "libc", 892 | "r-efi", 893 | "wasi 0.14.2+wasi-0.2.4", 894 | ] 895 | 896 | [[package]] 897 | name = "gimli" 898 | version = "0.31.1" 899 | source = "registry+https://github.com/rust-lang/crates.io-index" 900 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 901 | 902 | [[package]] 903 | name = "glob" 904 | version = "0.3.2" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 907 | 908 | [[package]] 909 | name = "glow" 910 | version = "0.11.2" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "d8bd5877156a19b8ac83a29b2306fe20537429d318f3ff0a1a2119f8d9c61919" 913 | dependencies = [ 914 | "js-sys", 915 | "slotmap", 916 | "wasm-bindgen", 917 | "web-sys", 918 | ] 919 | 920 | [[package]] 921 | name = "goblin" 922 | version = "0.6.1" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "0d6b4de4a8eb6c46a8c77e1d3be942cb9a8bf073c22374578e5ba4b08ed0ff68" 925 | dependencies = [ 926 | "log", 927 | "plain", 928 | "scroll", 929 | ] 930 | 931 | [[package]] 932 | name = "hashbrown" 933 | version = "0.15.2" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 936 | 937 | [[package]] 938 | name = "heck" 939 | version = "0.5.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 942 | 943 | [[package]] 944 | name = "hermit-abi" 945 | version = "0.1.19" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 948 | dependencies = [ 949 | "libc", 950 | ] 951 | 952 | [[package]] 953 | name = "image" 954 | version = "0.24.9" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" 957 | dependencies = [ 958 | "bytemuck", 959 | "byteorder", 960 | "color_quant", 961 | "num-traits", 962 | "png", 963 | ] 964 | 965 | [[package]] 966 | name = "indexmap" 967 | version = "2.9.0" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 970 | dependencies = [ 971 | "equivalent", 972 | "hashbrown", 973 | ] 974 | 975 | [[package]] 976 | name = "is_terminal_polyfill" 977 | version = "1.70.1" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 980 | 981 | [[package]] 982 | name = "itertools" 983 | version = "0.13.0" 984 | source = "registry+https://github.com/rust-lang/crates.io-index" 985 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 986 | dependencies = [ 987 | "either", 988 | ] 989 | 990 | [[package]] 991 | name = "itoa" 992 | version = "1.0.15" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 995 | 996 | [[package]] 997 | name = "jack" 998 | version = "0.11.4" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "0e5a18a3c2aefb354fb77111ade228b20267bdc779de84e7a4ccf7ea96b9a6cd" 1001 | dependencies = [ 1002 | "bitflags 1.3.2", 1003 | "jack-sys", 1004 | "lazy_static", 1005 | "libc", 1006 | "log", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "jack-sys" 1011 | version = "0.5.1" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab" 1014 | dependencies = [ 1015 | "bitflags 1.3.2", 1016 | "lazy_static", 1017 | "libc", 1018 | "libloading 0.7.4", 1019 | "log", 1020 | "pkg-config", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "jni" 1025 | version = "0.21.1" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 1028 | dependencies = [ 1029 | "cesu8", 1030 | "cfg-if", 1031 | "combine", 1032 | "jni-sys", 1033 | "log", 1034 | "thiserror", 1035 | "walkdir", 1036 | "windows-sys 0.45.0", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "jni-sys" 1041 | version = "0.3.0" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 1044 | 1045 | [[package]] 1046 | name = "jobserver" 1047 | version = "0.1.33" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1050 | dependencies = [ 1051 | "getrandom 0.3.2", 1052 | "libc", 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "js-sys" 1057 | version = "0.3.77" 1058 | source = "registry+https://github.com/rust-lang/crates.io-index" 1059 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1060 | dependencies = [ 1061 | "once_cell", 1062 | "wasm-bindgen", 1063 | ] 1064 | 1065 | [[package]] 1066 | name = "keyboard-types" 1067 | version = "0.6.2" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" 1070 | dependencies = [ 1071 | "bitflags 1.3.2", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "lazy-bytes-cast" 1076 | version = "5.0.1" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" 1079 | 1080 | [[package]] 1081 | name = "lazy_static" 1082 | version = "1.5.0" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1085 | 1086 | [[package]] 1087 | name = "libc" 1088 | version = "0.2.172" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 1091 | 1092 | [[package]] 1093 | name = "libloading" 1094 | version = "0.7.4" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" 1097 | dependencies = [ 1098 | "cfg-if", 1099 | "winapi", 1100 | ] 1101 | 1102 | [[package]] 1103 | name = "libloading" 1104 | version = "0.8.6" 1105 | source = "registry+https://github.com/rust-lang/crates.io-index" 1106 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 1107 | dependencies = [ 1108 | "cfg-if", 1109 | "windows-targets 0.52.6", 1110 | ] 1111 | 1112 | [[package]] 1113 | name = "libm" 1114 | version = "0.1.4" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" 1117 | 1118 | [[package]] 1119 | name = "linux-raw-sys" 1120 | version = "0.9.4" 1121 | source = "registry+https://github.com/rust-lang/crates.io-index" 1122 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 1123 | 1124 | [[package]] 1125 | name = "lock_api" 1126 | version = "0.4.12" 1127 | source = "registry+https://github.com/rust-lang/crates.io-index" 1128 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 1129 | dependencies = [ 1130 | "autocfg", 1131 | "scopeguard", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "log" 1136 | version = "0.4.27" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1139 | 1140 | [[package]] 1141 | name = "mach2" 1142 | version = "0.4.2" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" 1145 | dependencies = [ 1146 | "libc", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "malloc_buf" 1151 | version = "0.0.6" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" 1154 | dependencies = [ 1155 | "libc", 1156 | ] 1157 | 1158 | [[package]] 1159 | name = "memchr" 1160 | version = "2.7.4" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1163 | 1164 | [[package]] 1165 | name = "memmap2" 1166 | version = "0.5.10" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" 1169 | dependencies = [ 1170 | "libc", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "memoffset" 1175 | version = "0.6.5" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 1178 | dependencies = [ 1179 | "autocfg", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "midi-consts" 1184 | version = "0.1.0" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "6f2dd5c7f8aaf48a76e389068ab25ed80bdbc226b887f9013844c415698c9952" 1187 | 1188 | [[package]] 1189 | name = "midir" 1190 | version = "0.9.1" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "a456444d83e7ead06ae6a5c0a215ed70282947ff3897fb45fcb052b757284731" 1193 | dependencies = [ 1194 | "alsa 0.7.1", 1195 | "bitflags 1.3.2", 1196 | "coremidi", 1197 | "js-sys", 1198 | "libc", 1199 | "wasm-bindgen", 1200 | "web-sys", 1201 | "windows 0.43.0", 1202 | ] 1203 | 1204 | [[package]] 1205 | name = "midly" 1206 | version = "0.5.3" 1207 | source = "registry+https://github.com/rust-lang/crates.io-index" 1208 | checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a" 1209 | dependencies = [ 1210 | "rayon", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "minimal-lexical" 1215 | version = "0.2.1" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1218 | 1219 | [[package]] 1220 | name = "miniz_oxide" 1221 | version = "0.8.8" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 1224 | dependencies = [ 1225 | "adler2", 1226 | "simd-adler32", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "ndk" 1231 | version = "0.8.0" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" 1234 | dependencies = [ 1235 | "bitflags 2.9.0", 1236 | "jni-sys", 1237 | "log", 1238 | "ndk-sys", 1239 | "num_enum", 1240 | "thiserror", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "ndk-context" 1245 | version = "0.1.1" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 1248 | 1249 | [[package]] 1250 | name = "ndk-sys" 1251 | version = "0.5.0+25.2.9519653" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691" 1254 | dependencies = [ 1255 | "jni-sys", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "nih_log" 1260 | version = "0.2.0" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "016c1e345f1130730057d9b5381f423a36393b21c4f38f3ed05638448b6a5c48" 1263 | dependencies = [ 1264 | "atty", 1265 | "log", 1266 | "once_cell", 1267 | "termcolor", 1268 | "time", 1269 | "windows 0.44.0", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "nih_plug" 1274 | version = "0.0.0" 1275 | source = "git+https://github.com/a2aaron/nih-plug.git?rev=2ecc194e461de1bdbc117d86449b2fb30fafb356#2ecc194e461de1bdbc117d86449b2fb30fafb356" 1276 | dependencies = [ 1277 | "anyhow", 1278 | "anymap", 1279 | "atomic_float", 1280 | "atomic_refcell", 1281 | "backtrace", 1282 | "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=7001c2521fa1a439a01967cb881b411cd75d9ee0)", 1283 | "bitflags 1.3.2", 1284 | "cfg-if", 1285 | "clap", 1286 | "clap-sys", 1287 | "core-foundation", 1288 | "cpal", 1289 | "crossbeam", 1290 | "jack", 1291 | "lazy_static", 1292 | "libc", 1293 | "log", 1294 | "midi-consts", 1295 | "midir", 1296 | "nih_log", 1297 | "nih_plug_derive", 1298 | "objc", 1299 | "parking_lot", 1300 | "raw-window-handle", 1301 | "rtrb", 1302 | "serde", 1303 | "serde_json", 1304 | "vst3-sys", 1305 | "widestring", 1306 | "windows 0.44.0", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "nih_plug_derive" 1311 | version = "0.1.0" 1312 | source = "git+https://github.com/a2aaron/nih-plug.git?rev=2ecc194e461de1bdbc117d86449b2fb30fafb356#2ecc194e461de1bdbc117d86449b2fb30fafb356" 1313 | dependencies = [ 1314 | "proc-macro2", 1315 | "quote", 1316 | "syn 1.0.109", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "nih_plug_egui" 1321 | version = "0.0.0" 1322 | source = "git+https://github.com/a2aaron/nih-plug.git?rev=2ecc194e461de1bdbc117d86449b2fb30fafb356#2ecc194e461de1bdbc117d86449b2fb30fafb356" 1323 | dependencies = [ 1324 | "baseview 0.1.0 (git+https://github.com/RustAudio/baseview.git?rev=eae4033e7d2cc9c31ccaa2794d5d08eedf2f510c)", 1325 | "crossbeam", 1326 | "egui", 1327 | "egui-baseview", 1328 | "lazy_static", 1329 | "nih_plug", 1330 | "parking_lot", 1331 | "serde", 1332 | ] 1333 | 1334 | [[package]] 1335 | name = "nih_plug_xtask" 1336 | version = "0.1.0" 1337 | source = "git+https://github.com/robbert-vdh/nih-plug.git#d64b2ab9cfb94773c5ee4d0e72aef5921ee95d2d" 1338 | dependencies = [ 1339 | "anyhow", 1340 | "cargo_metadata", 1341 | "goblin", 1342 | "reflink", 1343 | "serde", 1344 | "toml", 1345 | ] 1346 | 1347 | [[package]] 1348 | name = "nix" 1349 | version = "0.22.3" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" 1352 | dependencies = [ 1353 | "bitflags 1.3.2", 1354 | "cc", 1355 | "cfg-if", 1356 | "libc", 1357 | "memoffset", 1358 | ] 1359 | 1360 | [[package]] 1361 | name = "nix" 1362 | version = "0.24.3" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" 1365 | dependencies = [ 1366 | "bitflags 1.3.2", 1367 | "cfg-if", 1368 | "libc", 1369 | "memoffset", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "nohash-hasher" 1374 | version = "0.2.0" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" 1377 | 1378 | [[package]] 1379 | name = "nom" 1380 | version = "7.1.3" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1383 | dependencies = [ 1384 | "memchr", 1385 | "minimal-lexical", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "num-conv" 1390 | version = "0.1.0" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1393 | 1394 | [[package]] 1395 | name = "num-derive" 1396 | version = "0.4.2" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 1399 | dependencies = [ 1400 | "proc-macro2", 1401 | "quote", 1402 | "syn 2.0.100", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "num-traits" 1407 | version = "0.2.19" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1410 | dependencies = [ 1411 | "autocfg", 1412 | ] 1413 | 1414 | [[package]] 1415 | name = "num_enum" 1416 | version = "0.7.3" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 1419 | dependencies = [ 1420 | "num_enum_derive", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "num_enum_derive" 1425 | version = "0.7.3" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 1428 | dependencies = [ 1429 | "proc-macro-crate", 1430 | "proc-macro2", 1431 | "quote", 1432 | "syn 2.0.100", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "num_threads" 1437 | version = "0.1.7" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" 1440 | dependencies = [ 1441 | "libc", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "nyasynth" 1446 | version = "0.1.0" 1447 | dependencies = [ 1448 | "atomic_float", 1449 | "biquad", 1450 | "clap", 1451 | "derive_more", 1452 | "getrandom 0.2.16", 1453 | "image", 1454 | "midly", 1455 | "nih_plug", 1456 | "nih_plug_egui", 1457 | "once_cell", 1458 | "ordered-float", 1459 | "serde", 1460 | "serde_json", 1461 | "wav", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "objc" 1466 | version = "0.2.7" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" 1469 | dependencies = [ 1470 | "malloc_buf", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "objc-foundation" 1475 | version = "0.1.1" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" 1478 | dependencies = [ 1479 | "block", 1480 | "objc", 1481 | "objc_id", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "objc_id" 1486 | version = "0.1.1" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" 1489 | dependencies = [ 1490 | "objc", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "object" 1495 | version = "0.36.7" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1498 | dependencies = [ 1499 | "memchr", 1500 | ] 1501 | 1502 | [[package]] 1503 | name = "oboe" 1504 | version = "0.6.1" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb" 1507 | dependencies = [ 1508 | "jni", 1509 | "ndk", 1510 | "ndk-context", 1511 | "num-derive", 1512 | "num-traits", 1513 | "oboe-sys", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "oboe-sys" 1518 | version = "0.6.1" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d" 1521 | dependencies = [ 1522 | "cc", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "once_cell" 1527 | version = "1.21.3" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1530 | 1531 | [[package]] 1532 | name = "ordered-float" 1533 | version = "3.9.2" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" 1536 | dependencies = [ 1537 | "num-traits", 1538 | ] 1539 | 1540 | [[package]] 1541 | name = "owned_ttf_parser" 1542 | version = "0.25.0" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" 1545 | dependencies = [ 1546 | "ttf-parser", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "parking_lot" 1551 | version = "0.12.3" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1554 | dependencies = [ 1555 | "lock_api", 1556 | "parking_lot_core", 1557 | ] 1558 | 1559 | [[package]] 1560 | name = "parking_lot_core" 1561 | version = "0.9.10" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1564 | dependencies = [ 1565 | "cfg-if", 1566 | "libc", 1567 | "redox_syscall", 1568 | "smallvec", 1569 | "windows-targets 0.52.6", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "pin-project-lite" 1574 | version = "0.2.16" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1577 | 1578 | [[package]] 1579 | name = "pkg-config" 1580 | version = "0.3.32" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1583 | 1584 | [[package]] 1585 | name = "plain" 1586 | version = "0.2.3" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 1589 | 1590 | [[package]] 1591 | name = "png" 1592 | version = "0.17.16" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" 1595 | dependencies = [ 1596 | "bitflags 1.3.2", 1597 | "crc32fast", 1598 | "fdeflate", 1599 | "flate2", 1600 | "miniz_oxide", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "powerfmt" 1605 | version = "0.2.0" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1608 | 1609 | [[package]] 1610 | name = "proc-macro-crate" 1611 | version = "3.3.0" 1612 | source = "registry+https://github.com/rust-lang/crates.io-index" 1613 | checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" 1614 | dependencies = [ 1615 | "toml_edit 0.22.24", 1616 | ] 1617 | 1618 | [[package]] 1619 | name = "proc-macro2" 1620 | version = "1.0.95" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1623 | dependencies = [ 1624 | "unicode-ident", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "quote" 1629 | version = "1.0.40" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1632 | dependencies = [ 1633 | "proc-macro2", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "r-efi" 1638 | version = "5.2.0" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1641 | 1642 | [[package]] 1643 | name = "raw-window-handle" 1644 | version = "0.4.3" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" 1647 | dependencies = [ 1648 | "cty", 1649 | ] 1650 | 1651 | [[package]] 1652 | name = "rayon" 1653 | version = "1.10.0" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1656 | dependencies = [ 1657 | "either", 1658 | "rayon-core", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "rayon-core" 1663 | version = "1.12.1" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1666 | dependencies = [ 1667 | "crossbeam-deque", 1668 | "crossbeam-utils", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "redox_syscall" 1673 | version = "0.5.11" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 1676 | dependencies = [ 1677 | "bitflags 2.9.0", 1678 | ] 1679 | 1680 | [[package]] 1681 | name = "reflink" 1682 | version = "0.1.3" 1683 | source = "git+https://github.com/nicokoch/reflink.git?rev=e8d93b465f5d9ad340cd052b64bbc77b8ee107e2#e8d93b465f5d9ad340cd052b64bbc77b8ee107e2" 1684 | dependencies = [ 1685 | "libc", 1686 | "winapi", 1687 | ] 1688 | 1689 | [[package]] 1690 | name = "regex" 1691 | version = "1.11.1" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1694 | dependencies = [ 1695 | "aho-corasick", 1696 | "memchr", 1697 | "regex-automata", 1698 | "regex-syntax", 1699 | ] 1700 | 1701 | [[package]] 1702 | name = "regex-automata" 1703 | version = "0.4.9" 1704 | source = "registry+https://github.com/rust-lang/crates.io-index" 1705 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1706 | dependencies = [ 1707 | "aho-corasick", 1708 | "memchr", 1709 | "regex-syntax", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "regex-syntax" 1714 | version = "0.8.5" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1717 | 1718 | [[package]] 1719 | name = "riff" 1720 | version = "1.0.1" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "b9b1a3d5f46d53f4a3478e2be4a5a5ce5108ea58b100dcd139830eae7f79a3a1" 1723 | 1724 | [[package]] 1725 | name = "rtrb" 1726 | version = "0.2.3" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "99e704dd104faf2326a320140f70f0b736d607c1caa1b1748a6c568a79819109" 1729 | dependencies = [ 1730 | "cache-padded", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "rustc-demangle" 1735 | version = "0.1.24" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1738 | 1739 | [[package]] 1740 | name = "rustc-hash" 1741 | version = "1.1.0" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1744 | 1745 | [[package]] 1746 | name = "rustc_version" 1747 | version = "0.4.1" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 1750 | dependencies = [ 1751 | "semver", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "rustix" 1756 | version = "1.0.5" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" 1759 | dependencies = [ 1760 | "bitflags 2.9.0", 1761 | "errno", 1762 | "libc", 1763 | "linux-raw-sys", 1764 | "windows-sys 0.59.0", 1765 | ] 1766 | 1767 | [[package]] 1768 | name = "rustversion" 1769 | version = "1.0.20" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1772 | 1773 | [[package]] 1774 | name = "ryu" 1775 | version = "1.0.20" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1778 | 1779 | [[package]] 1780 | name = "same-file" 1781 | version = "1.0.6" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1784 | dependencies = [ 1785 | "winapi-util", 1786 | ] 1787 | 1788 | [[package]] 1789 | name = "scoped-tls" 1790 | version = "1.0.1" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1793 | 1794 | [[package]] 1795 | name = "scopeguard" 1796 | version = "1.2.0" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1799 | 1800 | [[package]] 1801 | name = "scroll" 1802 | version = "0.11.0" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" 1805 | dependencies = [ 1806 | "scroll_derive", 1807 | ] 1808 | 1809 | [[package]] 1810 | name = "scroll_derive" 1811 | version = "0.11.1" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" 1814 | dependencies = [ 1815 | "proc-macro2", 1816 | "quote", 1817 | "syn 2.0.100", 1818 | ] 1819 | 1820 | [[package]] 1821 | name = "semver" 1822 | version = "1.0.26" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 1825 | dependencies = [ 1826 | "serde", 1827 | ] 1828 | 1829 | [[package]] 1830 | name = "serde" 1831 | version = "1.0.219" 1832 | source = "registry+https://github.com/rust-lang/crates.io-index" 1833 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1834 | dependencies = [ 1835 | "serde_derive", 1836 | ] 1837 | 1838 | [[package]] 1839 | name = "serde_derive" 1840 | version = "1.0.219" 1841 | source = "registry+https://github.com/rust-lang/crates.io-index" 1842 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1843 | dependencies = [ 1844 | "proc-macro2", 1845 | "quote", 1846 | "syn 2.0.100", 1847 | ] 1848 | 1849 | [[package]] 1850 | name = "serde_json" 1851 | version = "1.0.140" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1854 | dependencies = [ 1855 | "itoa", 1856 | "memchr", 1857 | "ryu", 1858 | "serde", 1859 | ] 1860 | 1861 | [[package]] 1862 | name = "serde_spanned" 1863 | version = "0.6.8" 1864 | source = "registry+https://github.com/rust-lang/crates.io-index" 1865 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1866 | dependencies = [ 1867 | "serde", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "shlex" 1872 | version = "1.3.0" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1875 | 1876 | [[package]] 1877 | name = "simd-adler32" 1878 | version = "0.3.7" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1881 | 1882 | [[package]] 1883 | name = "slotmap" 1884 | version = "1.0.7" 1885 | source = "registry+https://github.com/rust-lang/crates.io-index" 1886 | checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" 1887 | dependencies = [ 1888 | "version_check", 1889 | ] 1890 | 1891 | [[package]] 1892 | name = "smallvec" 1893 | version = "1.15.0" 1894 | source = "registry+https://github.com/rust-lang/crates.io-index" 1895 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1896 | 1897 | [[package]] 1898 | name = "smithay-client-toolkit" 1899 | version = "0.16.1" 1900 | source = "registry+https://github.com/rust-lang/crates.io-index" 1901 | checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" 1902 | dependencies = [ 1903 | "bitflags 1.3.2", 1904 | "dlib", 1905 | "lazy_static", 1906 | "log", 1907 | "memmap2", 1908 | "nix 0.24.3", 1909 | "pkg-config", 1910 | "wayland-client", 1911 | "wayland-cursor", 1912 | "wayland-protocols", 1913 | ] 1914 | 1915 | [[package]] 1916 | name = "smithay-clipboard" 1917 | version = "0.6.6" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "0a345c870a1fae0b1b779085e81b51e614767c239e93503588e54c5b17f4b0e8" 1920 | dependencies = [ 1921 | "smithay-client-toolkit", 1922 | "wayland-client", 1923 | ] 1924 | 1925 | [[package]] 1926 | name = "strsim" 1927 | version = "0.11.1" 1928 | source = "registry+https://github.com/rust-lang/crates.io-index" 1929 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1930 | 1931 | [[package]] 1932 | name = "syn" 1933 | version = "1.0.109" 1934 | source = "registry+https://github.com/rust-lang/crates.io-index" 1935 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1936 | dependencies = [ 1937 | "proc-macro2", 1938 | "quote", 1939 | "unicode-ident", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "syn" 1944 | version = "2.0.100" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 1947 | dependencies = [ 1948 | "proc-macro2", 1949 | "quote", 1950 | "unicode-ident", 1951 | ] 1952 | 1953 | [[package]] 1954 | name = "termcolor" 1955 | version = "1.4.1" 1956 | source = "registry+https://github.com/rust-lang/crates.io-index" 1957 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1958 | dependencies = [ 1959 | "winapi-util", 1960 | ] 1961 | 1962 | [[package]] 1963 | name = "terminal_size" 1964 | version = "0.4.2" 1965 | source = "registry+https://github.com/rust-lang/crates.io-index" 1966 | checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" 1967 | dependencies = [ 1968 | "rustix", 1969 | "windows-sys 0.59.0", 1970 | ] 1971 | 1972 | [[package]] 1973 | name = "thiserror" 1974 | version = "1.0.69" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1977 | dependencies = [ 1978 | "thiserror-impl", 1979 | ] 1980 | 1981 | [[package]] 1982 | name = "thiserror-impl" 1983 | version = "1.0.69" 1984 | source = "registry+https://github.com/rust-lang/crates.io-index" 1985 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1986 | dependencies = [ 1987 | "proc-macro2", 1988 | "quote", 1989 | "syn 2.0.100", 1990 | ] 1991 | 1992 | [[package]] 1993 | name = "time" 1994 | version = "0.3.41" 1995 | source = "registry+https://github.com/rust-lang/crates.io-index" 1996 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 1997 | dependencies = [ 1998 | "deranged", 1999 | "itoa", 2000 | "libc", 2001 | "num-conv", 2002 | "num_threads", 2003 | "powerfmt", 2004 | "serde", 2005 | "time-core", 2006 | "time-macros", 2007 | ] 2008 | 2009 | [[package]] 2010 | name = "time-core" 2011 | version = "0.1.4" 2012 | source = "registry+https://github.com/rust-lang/crates.io-index" 2013 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 2014 | 2015 | [[package]] 2016 | name = "time-macros" 2017 | version = "0.2.22" 2018 | source = "registry+https://github.com/rust-lang/crates.io-index" 2019 | checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 2020 | dependencies = [ 2021 | "num-conv", 2022 | "time-core", 2023 | ] 2024 | 2025 | [[package]] 2026 | name = "toml" 2027 | version = "0.7.8" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 2030 | dependencies = [ 2031 | "serde", 2032 | "serde_spanned", 2033 | "toml_datetime", 2034 | "toml_edit 0.19.15", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "toml_datetime" 2039 | version = "0.6.8" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 2042 | dependencies = [ 2043 | "serde", 2044 | ] 2045 | 2046 | [[package]] 2047 | name = "toml_edit" 2048 | version = "0.19.15" 2049 | source = "registry+https://github.com/rust-lang/crates.io-index" 2050 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 2051 | dependencies = [ 2052 | "indexmap", 2053 | "serde", 2054 | "serde_spanned", 2055 | "toml_datetime", 2056 | "winnow 0.5.40", 2057 | ] 2058 | 2059 | [[package]] 2060 | name = "toml_edit" 2061 | version = "0.22.24" 2062 | source = "registry+https://github.com/rust-lang/crates.io-index" 2063 | checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" 2064 | dependencies = [ 2065 | "indexmap", 2066 | "toml_datetime", 2067 | "winnow 0.7.6", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "tracing" 2072 | version = "0.1.41" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2075 | dependencies = [ 2076 | "pin-project-lite", 2077 | "tracing-core", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "tracing-core" 2082 | version = "0.1.33" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2085 | dependencies = [ 2086 | "once_cell", 2087 | ] 2088 | 2089 | [[package]] 2090 | name = "ttf-parser" 2091 | version = "0.25.1" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" 2094 | 2095 | [[package]] 2096 | name = "unicode-ident" 2097 | version = "1.0.18" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2100 | 2101 | [[package]] 2102 | name = "utf8parse" 2103 | version = "0.2.2" 2104 | source = "registry+https://github.com/rust-lang/crates.io-index" 2105 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2106 | 2107 | [[package]] 2108 | name = "uuid" 2109 | version = "0.8.2" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 2112 | dependencies = [ 2113 | "getrandom 0.2.16", 2114 | ] 2115 | 2116 | [[package]] 2117 | name = "version_check" 2118 | version = "0.9.5" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2121 | 2122 | [[package]] 2123 | name = "vst3-com" 2124 | version = "0.1.0" 2125 | source = "git+https://github.com/robbert-vdh/vst3-sys.git?branch=fix%2Fdrop-box-from-raw#b3ff4d775940f5b476b9d1cca02a90e07e1922a2" 2126 | dependencies = [ 2127 | "vst3-com-macros", 2128 | ] 2129 | 2130 | [[package]] 2131 | name = "vst3-com-macros" 2132 | version = "0.2.0" 2133 | source = "git+https://github.com/robbert-vdh/vst3-sys.git?branch=fix%2Fdrop-box-from-raw#b3ff4d775940f5b476b9d1cca02a90e07e1922a2" 2134 | dependencies = [ 2135 | "proc-macro2", 2136 | "quote", 2137 | "syn 1.0.109", 2138 | "vst3-com-macros-support", 2139 | ] 2140 | 2141 | [[package]] 2142 | name = "vst3-com-macros-support" 2143 | version = "0.2.0" 2144 | source = "git+https://github.com/robbert-vdh/vst3-sys.git?branch=fix%2Fdrop-box-from-raw#b3ff4d775940f5b476b9d1cca02a90e07e1922a2" 2145 | dependencies = [ 2146 | "proc-macro2", 2147 | "quote", 2148 | "syn 1.0.109", 2149 | ] 2150 | 2151 | [[package]] 2152 | name = "vst3-sys" 2153 | version = "0.1.0" 2154 | source = "git+https://github.com/robbert-vdh/vst3-sys.git?branch=fix%2Fdrop-box-from-raw#b3ff4d775940f5b476b9d1cca02a90e07e1922a2" 2155 | dependencies = [ 2156 | "vst3-com", 2157 | ] 2158 | 2159 | [[package]] 2160 | name = "walkdir" 2161 | version = "2.5.0" 2162 | source = "registry+https://github.com/rust-lang/crates.io-index" 2163 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2164 | dependencies = [ 2165 | "same-file", 2166 | "winapi-util", 2167 | ] 2168 | 2169 | [[package]] 2170 | name = "wasi" 2171 | version = "0.11.0+wasi-snapshot-preview1" 2172 | source = "registry+https://github.com/rust-lang/crates.io-index" 2173 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2174 | 2175 | [[package]] 2176 | name = "wasi" 2177 | version = "0.14.2+wasi-0.2.4" 2178 | source = "registry+https://github.com/rust-lang/crates.io-index" 2179 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2180 | dependencies = [ 2181 | "wit-bindgen-rt", 2182 | ] 2183 | 2184 | [[package]] 2185 | name = "wasm-bindgen" 2186 | version = "0.2.100" 2187 | source = "registry+https://github.com/rust-lang/crates.io-index" 2188 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2189 | dependencies = [ 2190 | "cfg-if", 2191 | "once_cell", 2192 | "rustversion", 2193 | "wasm-bindgen-macro", 2194 | ] 2195 | 2196 | [[package]] 2197 | name = "wasm-bindgen-backend" 2198 | version = "0.2.100" 2199 | source = "registry+https://github.com/rust-lang/crates.io-index" 2200 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2201 | dependencies = [ 2202 | "bumpalo", 2203 | "log", 2204 | "proc-macro2", 2205 | "quote", 2206 | "syn 2.0.100", 2207 | "wasm-bindgen-shared", 2208 | ] 2209 | 2210 | [[package]] 2211 | name = "wasm-bindgen-futures" 2212 | version = "0.4.50" 2213 | source = "registry+https://github.com/rust-lang/crates.io-index" 2214 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2215 | dependencies = [ 2216 | "cfg-if", 2217 | "js-sys", 2218 | "once_cell", 2219 | "wasm-bindgen", 2220 | "web-sys", 2221 | ] 2222 | 2223 | [[package]] 2224 | name = "wasm-bindgen-macro" 2225 | version = "0.2.100" 2226 | source = "registry+https://github.com/rust-lang/crates.io-index" 2227 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2228 | dependencies = [ 2229 | "quote", 2230 | "wasm-bindgen-macro-support", 2231 | ] 2232 | 2233 | [[package]] 2234 | name = "wasm-bindgen-macro-support" 2235 | version = "0.2.100" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2238 | dependencies = [ 2239 | "proc-macro2", 2240 | "quote", 2241 | "syn 2.0.100", 2242 | "wasm-bindgen-backend", 2243 | "wasm-bindgen-shared", 2244 | ] 2245 | 2246 | [[package]] 2247 | name = "wasm-bindgen-shared" 2248 | version = "0.2.100" 2249 | source = "registry+https://github.com/rust-lang/crates.io-index" 2250 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2251 | dependencies = [ 2252 | "unicode-ident", 2253 | ] 2254 | 2255 | [[package]] 2256 | name = "wav" 2257 | version = "1.0.1" 2258 | source = "registry+https://github.com/rust-lang/crates.io-index" 2259 | checksum = "99d97402f69875b579ec37f2aa52d1f455a1d6224251edba32e8c18a5da2698d" 2260 | dependencies = [ 2261 | "riff", 2262 | ] 2263 | 2264 | [[package]] 2265 | name = "wayland-client" 2266 | version = "0.29.5" 2267 | source = "registry+https://github.com/rust-lang/crates.io-index" 2268 | checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" 2269 | dependencies = [ 2270 | "bitflags 1.3.2", 2271 | "downcast-rs", 2272 | "libc", 2273 | "nix 0.24.3", 2274 | "scoped-tls", 2275 | "wayland-commons", 2276 | "wayland-scanner", 2277 | "wayland-sys", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "wayland-commons" 2282 | version = "0.29.5" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" 2285 | dependencies = [ 2286 | "nix 0.24.3", 2287 | "once_cell", 2288 | "smallvec", 2289 | "wayland-sys", 2290 | ] 2291 | 2292 | [[package]] 2293 | name = "wayland-cursor" 2294 | version = "0.29.5" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" 2297 | dependencies = [ 2298 | "nix 0.24.3", 2299 | "wayland-client", 2300 | "xcursor", 2301 | ] 2302 | 2303 | [[package]] 2304 | name = "wayland-protocols" 2305 | version = "0.29.5" 2306 | source = "registry+https://github.com/rust-lang/crates.io-index" 2307 | checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" 2308 | dependencies = [ 2309 | "bitflags 1.3.2", 2310 | "wayland-client", 2311 | "wayland-commons", 2312 | "wayland-scanner", 2313 | ] 2314 | 2315 | [[package]] 2316 | name = "wayland-scanner" 2317 | version = "0.29.5" 2318 | source = "registry+https://github.com/rust-lang/crates.io-index" 2319 | checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" 2320 | dependencies = [ 2321 | "proc-macro2", 2322 | "quote", 2323 | "xml-rs", 2324 | ] 2325 | 2326 | [[package]] 2327 | name = "wayland-sys" 2328 | version = "0.29.5" 2329 | source = "registry+https://github.com/rust-lang/crates.io-index" 2330 | checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" 2331 | dependencies = [ 2332 | "dlib", 2333 | "lazy_static", 2334 | "pkg-config", 2335 | ] 2336 | 2337 | [[package]] 2338 | name = "web-sys" 2339 | version = "0.3.77" 2340 | source = "registry+https://github.com/rust-lang/crates.io-index" 2341 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2342 | dependencies = [ 2343 | "js-sys", 2344 | "wasm-bindgen", 2345 | ] 2346 | 2347 | [[package]] 2348 | name = "widestring" 2349 | version = "1.2.0" 2350 | source = "registry+https://github.com/rust-lang/crates.io-index" 2351 | checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" 2352 | 2353 | [[package]] 2354 | name = "winapi" 2355 | version = "0.3.9" 2356 | source = "registry+https://github.com/rust-lang/crates.io-index" 2357 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2358 | dependencies = [ 2359 | "winapi-i686-pc-windows-gnu", 2360 | "winapi-x86_64-pc-windows-gnu", 2361 | ] 2362 | 2363 | [[package]] 2364 | name = "winapi-i686-pc-windows-gnu" 2365 | version = "0.4.0" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2368 | 2369 | [[package]] 2370 | name = "winapi-util" 2371 | version = "0.1.9" 2372 | source = "registry+https://github.com/rust-lang/crates.io-index" 2373 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2374 | dependencies = [ 2375 | "windows-sys 0.59.0", 2376 | ] 2377 | 2378 | [[package]] 2379 | name = "winapi-wsapoll" 2380 | version = "0.1.2" 2381 | source = "registry+https://github.com/rust-lang/crates.io-index" 2382 | checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" 2383 | dependencies = [ 2384 | "winapi", 2385 | ] 2386 | 2387 | [[package]] 2388 | name = "winapi-x86_64-pc-windows-gnu" 2389 | version = "0.4.0" 2390 | source = "registry+https://github.com/rust-lang/crates.io-index" 2391 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2392 | 2393 | [[package]] 2394 | name = "windows" 2395 | version = "0.43.0" 2396 | source = "registry+https://github.com/rust-lang/crates.io-index" 2397 | checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" 2398 | dependencies = [ 2399 | "windows_aarch64_gnullvm 0.42.2", 2400 | "windows_aarch64_msvc 0.42.2", 2401 | "windows_i686_gnu 0.42.2", 2402 | "windows_i686_msvc 0.42.2", 2403 | "windows_x86_64_gnu 0.42.2", 2404 | "windows_x86_64_gnullvm 0.42.2", 2405 | "windows_x86_64_msvc 0.42.2", 2406 | ] 2407 | 2408 | [[package]] 2409 | name = "windows" 2410 | version = "0.44.0" 2411 | source = "registry+https://github.com/rust-lang/crates.io-index" 2412 | checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" 2413 | dependencies = [ 2414 | "windows-targets 0.42.2", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "windows" 2419 | version = "0.54.0" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" 2422 | dependencies = [ 2423 | "windows-core", 2424 | "windows-targets 0.52.6", 2425 | ] 2426 | 2427 | [[package]] 2428 | name = "windows-core" 2429 | version = "0.54.0" 2430 | source = "registry+https://github.com/rust-lang/crates.io-index" 2431 | checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" 2432 | dependencies = [ 2433 | "windows-result", 2434 | "windows-targets 0.52.6", 2435 | ] 2436 | 2437 | [[package]] 2438 | name = "windows-result" 2439 | version = "0.1.2" 2440 | source = "registry+https://github.com/rust-lang/crates.io-index" 2441 | checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" 2442 | dependencies = [ 2443 | "windows-targets 0.52.6", 2444 | ] 2445 | 2446 | [[package]] 2447 | name = "windows-sys" 2448 | version = "0.45.0" 2449 | source = "registry+https://github.com/rust-lang/crates.io-index" 2450 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 2451 | dependencies = [ 2452 | "windows-targets 0.42.2", 2453 | ] 2454 | 2455 | [[package]] 2456 | name = "windows-sys" 2457 | version = "0.59.0" 2458 | source = "registry+https://github.com/rust-lang/crates.io-index" 2459 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2460 | dependencies = [ 2461 | "windows-targets 0.52.6", 2462 | ] 2463 | 2464 | [[package]] 2465 | name = "windows-targets" 2466 | version = "0.42.2" 2467 | source = "registry+https://github.com/rust-lang/crates.io-index" 2468 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 2469 | dependencies = [ 2470 | "windows_aarch64_gnullvm 0.42.2", 2471 | "windows_aarch64_msvc 0.42.2", 2472 | "windows_i686_gnu 0.42.2", 2473 | "windows_i686_msvc 0.42.2", 2474 | "windows_x86_64_gnu 0.42.2", 2475 | "windows_x86_64_gnullvm 0.42.2", 2476 | "windows_x86_64_msvc 0.42.2", 2477 | ] 2478 | 2479 | [[package]] 2480 | name = "windows-targets" 2481 | version = "0.52.6" 2482 | source = "registry+https://github.com/rust-lang/crates.io-index" 2483 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2484 | dependencies = [ 2485 | "windows_aarch64_gnullvm 0.52.6", 2486 | "windows_aarch64_msvc 0.52.6", 2487 | "windows_i686_gnu 0.52.6", 2488 | "windows_i686_gnullvm", 2489 | "windows_i686_msvc 0.52.6", 2490 | "windows_x86_64_gnu 0.52.6", 2491 | "windows_x86_64_gnullvm 0.52.6", 2492 | "windows_x86_64_msvc 0.52.6", 2493 | ] 2494 | 2495 | [[package]] 2496 | name = "windows_aarch64_gnullvm" 2497 | version = "0.42.2" 2498 | source = "registry+https://github.com/rust-lang/crates.io-index" 2499 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 2500 | 2501 | [[package]] 2502 | name = "windows_aarch64_gnullvm" 2503 | version = "0.52.6" 2504 | source = "registry+https://github.com/rust-lang/crates.io-index" 2505 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2506 | 2507 | [[package]] 2508 | name = "windows_aarch64_msvc" 2509 | version = "0.42.2" 2510 | source = "registry+https://github.com/rust-lang/crates.io-index" 2511 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 2512 | 2513 | [[package]] 2514 | name = "windows_aarch64_msvc" 2515 | version = "0.52.6" 2516 | source = "registry+https://github.com/rust-lang/crates.io-index" 2517 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2518 | 2519 | [[package]] 2520 | name = "windows_i686_gnu" 2521 | version = "0.42.2" 2522 | source = "registry+https://github.com/rust-lang/crates.io-index" 2523 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 2524 | 2525 | [[package]] 2526 | name = "windows_i686_gnu" 2527 | version = "0.52.6" 2528 | source = "registry+https://github.com/rust-lang/crates.io-index" 2529 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2530 | 2531 | [[package]] 2532 | name = "windows_i686_gnullvm" 2533 | version = "0.52.6" 2534 | source = "registry+https://github.com/rust-lang/crates.io-index" 2535 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2536 | 2537 | [[package]] 2538 | name = "windows_i686_msvc" 2539 | version = "0.42.2" 2540 | source = "registry+https://github.com/rust-lang/crates.io-index" 2541 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 2542 | 2543 | [[package]] 2544 | name = "windows_i686_msvc" 2545 | version = "0.52.6" 2546 | source = "registry+https://github.com/rust-lang/crates.io-index" 2547 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2548 | 2549 | [[package]] 2550 | name = "windows_x86_64_gnu" 2551 | version = "0.42.2" 2552 | source = "registry+https://github.com/rust-lang/crates.io-index" 2553 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 2554 | 2555 | [[package]] 2556 | name = "windows_x86_64_gnu" 2557 | version = "0.52.6" 2558 | source = "registry+https://github.com/rust-lang/crates.io-index" 2559 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2560 | 2561 | [[package]] 2562 | name = "windows_x86_64_gnullvm" 2563 | version = "0.42.2" 2564 | source = "registry+https://github.com/rust-lang/crates.io-index" 2565 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 2566 | 2567 | [[package]] 2568 | name = "windows_x86_64_gnullvm" 2569 | version = "0.52.6" 2570 | source = "registry+https://github.com/rust-lang/crates.io-index" 2571 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2572 | 2573 | [[package]] 2574 | name = "windows_x86_64_msvc" 2575 | version = "0.42.2" 2576 | source = "registry+https://github.com/rust-lang/crates.io-index" 2577 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 2578 | 2579 | [[package]] 2580 | name = "windows_x86_64_msvc" 2581 | version = "0.52.6" 2582 | source = "registry+https://github.com/rust-lang/crates.io-index" 2583 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2584 | 2585 | [[package]] 2586 | name = "winnow" 2587 | version = "0.5.40" 2588 | source = "registry+https://github.com/rust-lang/crates.io-index" 2589 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 2590 | dependencies = [ 2591 | "memchr", 2592 | ] 2593 | 2594 | [[package]] 2595 | name = "winnow" 2596 | version = "0.7.6" 2597 | source = "registry+https://github.com/rust-lang/crates.io-index" 2598 | checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" 2599 | dependencies = [ 2600 | "memchr", 2601 | ] 2602 | 2603 | [[package]] 2604 | name = "wit-bindgen-rt" 2605 | version = "0.39.0" 2606 | source = "registry+https://github.com/rust-lang/crates.io-index" 2607 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2608 | dependencies = [ 2609 | "bitflags 2.9.0", 2610 | ] 2611 | 2612 | [[package]] 2613 | name = "x11" 2614 | version = "2.21.0" 2615 | source = "registry+https://github.com/rust-lang/crates.io-index" 2616 | checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" 2617 | dependencies = [ 2618 | "libc", 2619 | "pkg-config", 2620 | ] 2621 | 2622 | [[package]] 2623 | name = "x11-clipboard" 2624 | version = "0.7.1" 2625 | source = "registry+https://github.com/rust-lang/crates.io-index" 2626 | checksum = "980b9aa9226c3b7de8e2adb11bf20124327c054e0e5812d2aac0b5b5a87e7464" 2627 | dependencies = [ 2628 | "x11rb", 2629 | ] 2630 | 2631 | [[package]] 2632 | name = "x11rb" 2633 | version = "0.10.1" 2634 | source = "registry+https://github.com/rust-lang/crates.io-index" 2635 | checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" 2636 | dependencies = [ 2637 | "gethostname", 2638 | "nix 0.24.3", 2639 | "winapi", 2640 | "winapi-wsapoll", 2641 | "x11rb-protocol", 2642 | ] 2643 | 2644 | [[package]] 2645 | name = "x11rb-protocol" 2646 | version = "0.10.0" 2647 | source = "registry+https://github.com/rust-lang/crates.io-index" 2648 | checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67" 2649 | dependencies = [ 2650 | "nix 0.24.3", 2651 | ] 2652 | 2653 | [[package]] 2654 | name = "xcb" 2655 | version = "0.9.0" 2656 | source = "registry+https://github.com/rust-lang/crates.io-index" 2657 | checksum = "62056f63138b39116f82a540c983cc11f1c90cd70b3d492a70c25eaa50bd22a6" 2658 | dependencies = [ 2659 | "libc", 2660 | "log", 2661 | "x11", 2662 | ] 2663 | 2664 | [[package]] 2665 | name = "xcb-util" 2666 | version = "0.3.0" 2667 | source = "registry+https://github.com/rust-lang/crates.io-index" 2668 | checksum = "43893e47f27bf7d81d489feef3a0e34a457e90bc314b7e74ad9bb3980e4c1c48" 2669 | dependencies = [ 2670 | "libc", 2671 | "xcb", 2672 | ] 2673 | 2674 | [[package]] 2675 | name = "xcursor" 2676 | version = "0.3.8" 2677 | source = "registry+https://github.com/rust-lang/crates.io-index" 2678 | checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" 2679 | 2680 | [[package]] 2681 | name = "xml-rs" 2682 | version = "0.8.26" 2683 | source = "registry+https://github.com/rust-lang/crates.io-index" 2684 | checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" 2685 | 2686 | [[package]] 2687 | name = "xtask" 2688 | version = "0.1.0" 2689 | dependencies = [ 2690 | "nih_plug_xtask", 2691 | ] 2692 | 2693 | [[package]] 2694 | name = "zerocopy" 2695 | version = "0.7.35" 2696 | source = "registry+https://github.com/rust-lang/crates.io-index" 2697 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2698 | dependencies = [ 2699 | "zerocopy-derive", 2700 | ] 2701 | 2702 | [[package]] 2703 | name = "zerocopy-derive" 2704 | version = "0.7.35" 2705 | source = "registry+https://github.com/rust-lang/crates.io-index" 2706 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2707 | dependencies = [ 2708 | "proc-macro2", 2709 | "quote", 2710 | "syn 2.0.100", 2711 | ] 2712 | --------------------------------------------------------------------------------