From f36ff0780cf1aad62b635dc706adc412389a60b7 Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Fri, 6 Nov 2026 03:22:00 +0100 Subject: [PATCH] feat: add Anvil sub anchor and Crucible resampler Two new CLAP effects aimed at riddim and dubstep production, plus shared crates so all four plugins keep one look and reuse DSP. Anvil 0.1.0 (io.storyboard.sub-anchor): - Replaces the band below Split with a clean mono sine. Every note gets the same peak level and restarts at the same phase, so switch-ups keep their weight and kick hits line up. - Pitch and note starts come from the audio (YIN, 80 ms lookahead reported to the host) or from MIDI notes sent to the effect. - Dynamics, Drive, Octave, Glide, Phase, Gate and Keep; Solo sub monitor, note readout, level meters and a low-band oscilloscope. Crucible 0.1.0 (io.storyboard.resampler): - Records its input continuously in a lock-free ring with host transport markers. Capture takes the newest 1-16 complete bars on the bar grid, following tempo changes, or the newest audio when stopped. - Grid (straight and triplet) or transient slicing, manual cuts, slice audition without audio-thread allocation. - Export writes the loop, trimmed, declicked and normalised slices named and tagged with their root note, and an optional 2048-sample wavetable for Serum and Vital. Shared: - common-ui holds the palette, fonts, knobs and panels taken from Patina; Patina renders identically. Rotary knobs at their minimum no longer tessellate a zero-length arc into NaN vertices. - common-dsp holds biquads, a Linkwitz-Riley crossover, YIN, note helpers and a float WAV writer with sampler root and Serum frame chunks. - Catalog, CI matrix, release tags, editor previews and docs cover both plugins; a script test keeps the catalog and workflows in step. --- .gitea/workflows/check.yml | 6 +- .gitea/workflows/release.yml | 2 +- Cargo.lock | 51 + Cargo.toml | 4 +- README.md | 36 +- docs/anvil/CHANGELOG.md | 13 + docs/anvil/README.md | 62 + docs/anvil/assets/editor.png | Bin 0 -> 248776 bytes docs/crucible/CHANGELOG.md | 13 + docs/crucible/README.md | 60 + docs/crucible/assets/editor.png | Bin 0 -> 225756 bytes docs/development.md | 14 +- plugins/anvil/core/Cargo.toml | 12 + plugins/anvil/core/src/lib.rs | 596 +++++ plugins/anvil/core/src/settings.rs | 95 + plugins/anvil/core/tests/dsp.rs | 213 ++ plugins/anvil/core/tests/realtime.rs | 69 + plugins/anvil/plugin/Cargo.lock | 2382 +++++++++++++++++ plugins/anvil/plugin/Cargo.toml | 25 + plugins/anvil/plugin/src/lib.rs | 199 ++ plugins/anvil/plugin/src/params.rs | 192 ++ plugins/anvil/plugin/tests/host.rs | 212 ++ plugins/anvil/ui/Cargo.toml | 11 + plugins/anvil/ui/src/lib.rs | 350 +++ plugins/catalog.json | 37 +- plugins/common/dsp/Cargo.toml | 7 + plugins/common/dsp/src/lib.rs | 263 ++ plugins/common/dsp/src/wav.rs | 142 + plugins/common/dsp/tests/dsp.rs | 82 + plugins/common/ui/Cargo.toml | 8 + plugins/{patina => common}/ui/assets/OFL.txt | 0 .../ui/assets/RobotoCondensed-Medium.ttf | Bin .../ui/assets/RobotoCondensed-Regular.ttf | Bin plugins/common/ui/src/lib.rs | 320 +++ plugins/crucible/core/Cargo.toml | 13 + plugins/crucible/core/src/export.rs | 93 + plugins/crucible/core/src/lib.rs | 18 + plugins/crucible/core/src/player.rs | 73 + plugins/crucible/core/src/ring.rs | 206 ++ plugins/crucible/core/src/settings.rs | 120 + plugins/crucible/core/src/take.rs | 478 ++++ plugins/crucible/core/tests/core.rs | 258 ++ plugins/crucible/core/tests/realtime.rs | 68 + plugins/crucible/plugin/Cargo.lock | 2382 +++++++++++++++++ plugins/crucible/plugin/Cargo.toml | 24 + plugins/crucible/plugin/src/lib.rs | 200 ++ plugins/crucible/plugin/tests/host.rs | 205 ++ plugins/crucible/ui/Cargo.toml | 11 + plugins/crucible/ui/src/lib.rs | 567 ++++ plugins/patina/plugin/Cargo.lock | 8 + plugins/patina/ui/Cargo.toml | 1 + plugins/patina/ui/src/lib.rs | 264 +- scripts/release.py | 2 +- scripts/tests/test_release.py | 19 + tools/editor-check/Cargo.lock | 54 + tools/editor-check/Cargo.toml | 5 + tools/editor-check/src/main.rs | 113 +- 57 files changed, 10373 insertions(+), 285 deletions(-) create mode 100644 docs/anvil/CHANGELOG.md create mode 100644 docs/anvil/README.md create mode 100644 docs/anvil/assets/editor.png create mode 100644 docs/crucible/CHANGELOG.md create mode 100644 docs/crucible/README.md create mode 100644 docs/crucible/assets/editor.png create mode 100644 plugins/anvil/core/Cargo.toml create mode 100644 plugins/anvil/core/src/lib.rs create mode 100644 plugins/anvil/core/src/settings.rs create mode 100644 plugins/anvil/core/tests/dsp.rs create mode 100644 plugins/anvil/core/tests/realtime.rs create mode 100644 plugins/anvil/plugin/Cargo.lock create mode 100644 plugins/anvil/plugin/Cargo.toml create mode 100644 plugins/anvil/plugin/src/lib.rs create mode 100644 plugins/anvil/plugin/src/params.rs create mode 100644 plugins/anvil/plugin/tests/host.rs create mode 100644 plugins/anvil/ui/Cargo.toml create mode 100644 plugins/anvil/ui/src/lib.rs create mode 100644 plugins/common/dsp/Cargo.toml create mode 100644 plugins/common/dsp/src/lib.rs create mode 100644 plugins/common/dsp/src/wav.rs create mode 100644 plugins/common/dsp/tests/dsp.rs create mode 100644 plugins/common/ui/Cargo.toml rename plugins/{patina => common}/ui/assets/OFL.txt (100%) rename plugins/{patina => common}/ui/assets/RobotoCondensed-Medium.ttf (100%) rename plugins/{patina => common}/ui/assets/RobotoCondensed-Regular.ttf (100%) create mode 100644 plugins/common/ui/src/lib.rs create mode 100644 plugins/crucible/core/Cargo.toml create mode 100644 plugins/crucible/core/src/export.rs create mode 100644 plugins/crucible/core/src/lib.rs create mode 100644 plugins/crucible/core/src/player.rs create mode 100644 plugins/crucible/core/src/ring.rs create mode 100644 plugins/crucible/core/src/settings.rs create mode 100644 plugins/crucible/core/src/take.rs create mode 100644 plugins/crucible/core/tests/core.rs create mode 100644 plugins/crucible/core/tests/realtime.rs create mode 100644 plugins/crucible/plugin/Cargo.lock create mode 100644 plugins/crucible/plugin/Cargo.toml create mode 100644 plugins/crucible/plugin/src/lib.rs create mode 100644 plugins/crucible/plugin/tests/host.rs create mode 100644 plugins/crucible/ui/Cargo.toml create mode 100644 plugins/crucible/ui/src/lib.rs diff --git a/.gitea/workflows/check.yml b/.gitea/workflows/check.yml index afa031a..6cd5edd 100644 --- a/.gitea/workflows/check.yml +++ b/.gitea/workflows/check.yml @@ -22,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - plugin: [rivet, patina] + plugin: [rivet, patina, anvil, crucible] steps: - uses: https://github.com/actions/checkout@v4 with: @@ -56,6 +56,8 @@ jobs: cargo run --manifest-path tools/editor-check/Cargo.toml --locked -- target/editor/rivet.png --plugin rivet cargo run --manifest-path tools/editor-check/Cargo.toml --locked -- target/editor/patina.png --plugin patina cargo run --manifest-path tools/editor-check/Cargo.toml --locked -- target/editor/patina-compact.png --plugin patina --compact + cargo run --manifest-path tools/editor-check/Cargo.toml --locked -- target/editor/anvil.png --plugin anvil + cargo run --manifest-path tools/editor-check/Cargo.toml --locked -- target/editor/crucible.png --plugin crucible - uses: https://github.com/actions/upload-artifact@v3-node20 with: name: editor-previews @@ -81,6 +83,8 @@ jobs: - run: | python3 scripts/build-plugin.py --plugin rivet --target x86_64-pc-windows-gnu python3 scripts/build-plugin.py --plugin patina --target x86_64-pc-windows-gnu + python3 scripts/build-plugin.py --plugin anvil --target x86_64-pc-windows-gnu + python3 scripts/build-plugin.py --plugin crucible --target x86_64-pc-windows-gnu python3 scripts/package-windows.py - uses: https://github.com/actions/upload-artifact@v3-node20 with: diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 57d5903..a753917 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,7 +1,7 @@ name: Independent plugin release on: push: - tags: ["rivet-v*", "patina-v*"] + tags: ["rivet-v*", "patina-v*", "anvil-v*", "crucible-v*"] permissions: contents: read diff --git a/Cargo.lock b/Cargo.lock index 26c8b4c..d191581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,25 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "anvil-core" +version = "0.1.0" +dependencies = [ + "common-dsp", + "serde", + "serde_json", +] + +[[package]] +name = "anvil-ui" +version = "0.1.0" +dependencies = [ + "anvil-core", + "common-dsp", + "common-ui", + "egui", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -48,6 +67,17 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "common-dsp" +version = "0.1.0" + +[[package]] +name = "common-ui" +version = "0.1.0" +dependencies = [ + "egui", +] + [[package]] name = "crossbeam-queue" version = "0.3.14" @@ -63,6 +93,26 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[package]] +name = "crucible-core" +version = "0.1.0" +dependencies = [ + "common-dsp", + "crossbeam-queue", + "serde", + "serde_json", +] + +[[package]] +name = "crucible-ui" +version = "0.1.0" +dependencies = [ + "common-dsp", + "common-ui", + "crucible-core", + "egui", +] + [[package]] name = "ecolor" version = "0.31.1" @@ -233,6 +283,7 @@ dependencies = [ name = "patina-ui" version = "0.2.0" dependencies = [ + "common-ui", "egui", "patina-core", ] diff --git a/Cargo.toml b/Cargo.toml index 6be5600..9e255a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = ["plugins/rivet/core", "plugins/rivet/midi", "plugins/rivet/preview", "plugins/rivet/app", "plugins/patina/core", "plugins/patina/render", "plugins/patina/ui"] -exclude = ["plugins/rivet/plugin", "tools/editor-check", "plugins/patina/plugin"] +members = ["plugins/common/ui", "plugins/common/dsp", "plugins/anvil/core", "plugins/anvil/ui", "plugins/crucible/core", "plugins/crucible/ui", "plugins/rivet/core", "plugins/rivet/midi", "plugins/rivet/preview", "plugins/rivet/app", "plugins/patina/core", "plugins/patina/render", "plugins/patina/ui"] +exclude = ["plugins/rivet/plugin", "tools/editor-check", "plugins/patina/plugin", "plugins/anvil/plugin", "plugins/crucible/plugin"] [workspace.package] edition = "2021" diff --git a/README.md b/README.md index fbb7df8..cd35551 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ # Rivet & Patina -Two Rust CLAP plugins for writing and refining bass music. +Four Rust CLAP plugins for writing, designing and mixing bass music. | Plugin | Purpose | Guide | | --- | --- | --- | | **Rivet** | Tell a MIDI motif as a story on a storyboard of roles, with joined seams | [Rivet](docs/rivet/README.md) | | **Patina** | Smooth harsh resonances while retaining tone and transient character | [Patina](docs/patina/README.md) | +| **Anvil** | Give every bass note the same clean sub: one level, one start phase | [Anvil](docs/anvil/README.md) | +| **Crucible** | Capture the last bars, slice them and export clean, key-labelled samples | [Crucible](docs/crucible/README.md) | The names come from metalwork: a **rivet** joins pieces; **patina** changes a -surface while leaving its character. They share a Rust foundation and keep +surface while leaving its character; an **anvil** is the solid base everything is +struck on; a **crucible** melts material down to be cast again. They share a Rust foundation and keep their own musical purpose, code, documentation, and version. ## Install Download the desired plugin's Windows ZIP from its Gitea release. Extract it -and copy `Rivet.clap` or `Patina.clap` into: +and copy its `.clap` file (`Rivet.clap`, `Patina.clap`, `Anvil.clap` or +`Crucible.clap`) into: ```text C:\Program Files\Common Files\CLAP\ @@ -22,11 +26,11 @@ C:\Program Files\Common Files\CLAP\ In FL Studio 2024.1 or newer, open **Options → Manage plugins**, enable **Verify plugins**, and **Find installed plugins**. Load Rivet in the Channel Rack; -load Patina in a mixer effect slot. +load Patina, Anvil and Crucible in mixer effect slots. Normal branch/PR pipelines also provide a combined Windows preview ZIP in the -**windows** job. Releases are separate: `rivet-v0.3.0` and `patina-v0.2.0` -publish independently. See [release workflow](docs/releases.md). +**windows** job. Releases are separate: tags such as `rivet-v0.4.0`, +`patina-v0.2.0`, `anvil-v0.1.0` and `crucible-v0.1.0` publish independently. See [release workflow](docs/releases.md). These names replace Motif Storyboard and Resonance Compressor. Their CLAP IDs and saved-state identifiers are preserved; replace old scanned copies and rescan. @@ -44,13 +48,23 @@ plugins/ plugin/ CLAP wrapper, editor, optional standalone patina/ core/ Spectral detector and attenuation engine - ui/ Shared native editor and embedded fonts + ui/ Patina's native editor render/ Offline WAV evaluation plugin/ CLAP audio-effect wrapper -tools/editor-check/ Headless visual QA for both editors + anvil/ + core/ Pitch tracking, crossover and sub oscillator + ui/ Anvil's native editor + plugin/ CLAP audio-effect wrapper with MIDI input + crucible/ + core/ Input history, capture, slicing, cleanup and export + ui/ Crucible's native editor + plugin/ CLAP audio-effect wrapper + common/ + ui/ Shared look: palette, embedded fonts, knobs and panels + dsp/ Shared filters, YIN pitch detection and WAV files +tools/editor-check/ Headless visual QA for all editors scripts/ Shared build, packaging, release, and analysis tools -docs/rivet/ Rivet guide, theory, research, and editor previews -docs/patina/ Patina guide, research, and editor preview +docs// Guide, changelog and editor preview per plugin references/ Local reference audio; excluded from distribution ``` @@ -62,6 +76,8 @@ separate. [Development and testing](docs/development.md) covers the commands. cargo test --workspace --locked python scripts/build-plugin.py --plugin rivet --target x86_64-pc-windows-gnu python scripts/build-plugin.py --plugin patina --target x86_64-pc-windows-gnu +python scripts/build-plugin.py --plugin anvil --target x86_64-pc-windows-gnu +python scripts/build-plugin.py --plugin crucible --target x86_64-pc-windows-gnu python scripts/package-windows.py ``` diff --git a/docs/anvil/CHANGELOG.md b/docs/anvil/CHANGELOG.md new file mode 100644 index 0000000..5c441c4 --- /dev/null +++ b/docs/anvil/CHANGELOG.md @@ -0,0 +1,13 @@ +# Anvil changelog + +## 0.1.0 + +- Introduce Anvil, a sub anchor for bass music. +- Replace the band below Split with a clean mono sine. Every note gets the same + peak level and starts at the same phase, so switch-ups and kick hits line up. +- Detect pitch and note starts from the audio with 80 ms lookahead, or follow + MIDI notes sent to the effect. +- Controls: Sub level, Split, Dynamics, Drive, Octave, Glide, Phase, Gate, Keep, + Output, Retrigger, Snap to semitone, Bypass. Solo sub monitors only the new sub. +- Note readout, level meters and an oscilloscope of the original low band against + the new sub. diff --git a/docs/anvil/README.md b/docs/anvil/README.md new file mode 100644 index 0000000..fea0760 --- /dev/null +++ b/docs/anvil/README.md @@ -0,0 +1,62 @@ +# Anvil + +Anvil gives every bass note the same solid sub. Current version **0.1.0**. + +In riddim and dubstep, each switch-up patch brings its own sub: a different level, +a different phase, sometimes two layers that cancel. The drop's weight jumps from +note to note and the kick lands differently on each one. Anvil removes the +original band below **Split** and puts a clean mono sine in its place. The sine +follows the bass pitch, has the same peak level on every note, and starts at the +same phase on every note. + +![Anvil](assets/editor.png) + +## Install on Windows + +Copy `Anvil.clap` into `C:\Program Files\Common Files\CLAP\`. In FL Studio 2024.1 +or newer, open Options → Manage plugins, enable Verify plugins, and Find installed +plugins. Load Anvil in the mixer slot of the bass bus. Keep the font license with +redistributed packages. + +## Work with it + +1. Put Anvil on the bass bus, after the sound design and before the bus + compression or clipping. +2. Set **Split** just above the sub. 80–100 Hz suits most riddim basses. +3. Set **Sub level** once. Every patch on the bus now gets this level. +4. Use **Phase** to line the start of each note up with your kick. With + **Retrigger** on, every note starts at this phase. +5. Turn on **Solo sub** to hear only the new sub; the scope shows the original + low band (grey) against the new sub (accent). + +Other controls: + +- **Dynamics** at 0 % holds the level for the whole note. Raise it to let the sub + follow the original's volume shape, for example a wobble. +- **Drive** adds upper harmonics so the sub reads on phones and laptops. +- **Octave** moves the sub against the detected note. Use −1 for a bass played + one octave up. +- **Glide** slides between overlapping notes; 0 jumps. +- **Gate** is the input level that counts as a note. Raise it if tails or reverb + start new notes. +- **Keep** blends some of the original low band back under the new sub. +- **Snap to semitone** rounds detected pitch. Turn it off for pitch bends and slides. + +### Pitch from MIDI + +Detection follows the audio by default. For heavy formant or FM basses where the +fundamental is unclear, select **MIDI** and route the bass's MIDI notes to Anvil. +In FL Studio, set the bass channel's MIDI output port and Anvil's input port to the +same number, or duplicate the notes into a pattern on a channel that feeds Anvil. + +## Timing and limits + +Detection uses 80 ms of lookahead, reported to the host for delay compensation, +so each note's pitch is known before its attack leaves the plugin. Pitch range is +25–250 Hz, one voice: chords and layered notes in the sub band are not separated. +Notes shorter than about 15 ms may not be recognised. A pitch move of more than +0.7 semitone that holds for two analysis hops (10 ms) starts a new note. + +The crossover is a 4th-order Linkwitz–Riley pair, so with Keep at 100 % and no +note playing, the signal passes with flat magnitude. Stereo above the split is +kept; the new sub is mono. diff --git a/docs/anvil/assets/editor.png b/docs/anvil/assets/editor.png new file mode 100644 index 0000000000000000000000000000000000000000..6839fd73f3267654ebb2cb63b6f5183b7355cd63 GIT binary patch literal 248776 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ⅈ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%0Ab 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�ob7 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 +(z1RX7`E*CG3U!EZ6VJr#&)3EE}~cOIIgk6 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 z zj!7G+$Av1wbRSLhhU=N-k;dvDaDSnwGh z;I&?&A5shSmT~&h;^b~@YvHf)imG@p`0Nkn#LNbp$WdCaEDwWi#8#6L>L&cjh{ zv>_g-&|gDR7VJuRJ*7-=`(R_iShj{ZOtlTVIgq17rSs6UMIQ~B9e$MoWSe$DJg^`T z0(*pJdNXvnsg=#DM}{cV;TTAX0ra_4+CeZTy?Mg7mP@xZeCR9rt0N;iSA0P4Cq9y| z9cO7Q0y8U*50~r+lwb_j#I%wq5adGwSO3zzwkn2e&8=BuIX?LJyj&i7rbB3_)PM$z zgW(wh8R9V~5^{5N0qN6F{d25&9*f&Zj5J(6HLvDV*NUT&Vk`qgh7(2ZfRWilLz*^M zQGc9NZ+85(#jvX7sLFCv4+&gP4q_+e;Nb-Q(AZ{XOjIbmKb`qZ>Fjlqa*3^hkLW^e z)7U5sb0*L$5N7|5{r_nIyOqxMmMwz!X@}!$0BxCdU?a%BJbQNQW>a{#_H?J_)|Ss7 zSuS-B>vp z)*MO?sNoEEAGh{!C+b9HGwqhcTX#hPXr+FE?Cxuxm}=#$pYaPEA|xIwJTW{s!0R7Z zL>?-P!q8pqH+|$%skO>lq{t1}->Lgc{UE6rnTc(Sld0h4{F&%_;n;%GOBX#%-oI2l zs#6?`fW92{eXX;DoacBMNZD}H;7hX0pUl;~+1pvSSyMBbyiQ!(MPFdcBO#%?NeKg% zUch~HeAoREq0OqIM2w+fgkHgq2fmfCm_tVIpS&Awsw>dSBE?(akAfHM)St)4o;zGY zPIY~s=q&-vl8;y{Xj^Wv-GqWJTJc*4NLbih+HVG0K^vXH=aU2QSwU4s$c`;YE+hbA=YCt{vXdO`kkkBc0Bcu62G+ zGd#$W+icDtG~9zdb%h(?19pwLPL#4_IcQ$&OZDCT}X*g-yCrL z^vmrB2X$RgWQ;spYD5cOpal5GC5V4DSl&?WvsJ~`4~uRb0E%!xr7ZmgF>!-G4=+N$jCAY^_;~#cyVBh=>r)N#$>q-dg>OG zg#2lkGaGN?-QQC_+iqoCaZCpN zw+&v;S4JsQA|T90Wh?YB%Acp9Kaf3mZ!(EKr-Rb%gzpXF^dgx9Jh_Y7 z8)O-QueRF>l~Xf?FmohFwq+BX)aVt&x1gm1ugx4B8X%WL;gmB4Kz8ZSfbzQFtR$=c z`W8!B+ndrHJ35bqxg%OQTjK1$7$pv<74>})YmIL2G3-ctIc%6t zRVZg6sQM!&;kq{XPd~tmTAI#lAiQ$yaz?RV3Y}V%C%{oi^nJP-;K2cDWh(iE2IxL% z-y(%}@%?~-`_+B>mPPJ?)7f|k%n$i2Tef#4>7R?LKm5 zgn+%ml>abz0FMO36{UBa__41q4GlIdgBg#;BG}=x@ z{||R>0@dWby$dIRfFn3mpeTc))Z-8a3#egms#BF}&#A29Tbrwpg@?>0;a81K#(w}iVPt_7(((r`;G0X=bZa}>)!9)|GMkHR+m+VofU;WaxQVD8BjI$z7jOhZ(E8|&oAI%GZ!Glduz=eGmUKI|#pcNz{Rv#%) zB#)MI$DNp~1(64QT79*rcOEG7*pM`9NW5P@0fviCNQ#LxPkcUiL->nQ!`s4K3%<>S z>O)@vr64mzK+`2yi@@PP_`;|qMiJDHpdPq8Bp2fRO@0eZFTdX}C|TW$LATK7J$F2B zR|$_1wVa{e6XlQ%ch!~>I~Af$~izQ(B&WQP|BS zprv8u32Lp~ImpGJ6SBkD3-AX1e#`0sEb@$5Y}_Is09cVw-aA2>xHxe`&uig{3 z-tTyfdy;aTf0n`&dQ_7j^8@h$C}b4c>FtDO`bu0+4VM!I5gdF%h~T8G_cR`N7h{KY zi4u_=&0UX!Ga#BoG%O6sOj=#2k4SZwnm^5Byo_b_m1AMl4*6cpBQt$kz7ZCpbNf*M zFO^I`c;Mzf)9!kexwg{}j)&9Ub=O8MML8zf%lWlK*)HE{*P(f-j(J*Tp?l>%ioJQ` z8B=w&s4vz&LFKr+GMhI?=Tfqh$K0`c_<%Xe0JF2qwkKCq?!tshdlHEeXpGf2bI}b< z$^nYM%*~r^rWK)YhBeHe09}S!jB5lQy*&y)*ZdjAhADpN0;(q0bsuZAWKUVZK0o*&@hnT+>|rqhs!R)$q8r4L2OmcW)8SP#r=5dUI0KG6nl~QA><$ z@bNm09x12+{(aw2d+0dtEDla;?@>pexZY4iav|$s{PuFli$?pz!G0T2W!fsNytqyck5 zHDWZbDI9|*MEz4%^Qgg`eFg#I5fhf_uO?;odK^8}!uSb%1bo6KE$}sP=Az8Y=Y`I3 z3JSHh3jM1*Av7TG0@3&(!+`byx8~%wicQDLSP=zk=jpa zqT*i=GGo4`T_TvlXxT7n)wVvrNt`2;gOh}h6{hd4F&~HM7RjH`ew2ExLq%_Q)fQM?Cb;Xu=I?94Z*?${70aL{q6L zV-bUfjRP)%59w4$Sa^s4U?b)5NYOUP07fRa_xE`*`Nk=CC2>rn-)gN$(nKD;j>HB;Cmzo1}UO z*y)51G+c0L82U1U&Kwk2fbLntor*atWHTTdh{Ki?Un$%Jd4|*hy$z0cjI*nIdk_bJ zSlF9MHl{hL2}q2gfeLI6OJ-ngQ>MJ_z)}7N7?5UQ5vmZp90kt`3;n`upFhWO)@)1;k%V{8HnJrXQM%7;%W zQzVfh33MFIS;nw}BYmeg*Ji}>!#s7;TmoNYHTgG=qJ+5YlL%H6$3_ro)tUApr;D$1E_v&GsZR1PN~h z9jy|snB+5WYEv1eGqcq1bJ<;Ii*|Fm) z{lk6x?&bP~==#1JP&Z=YyeUBQ2*Y%wGuBfko%Q|xr0YJk&df`xKljCvrvQK5(V z2k22jYCvvFozu~nof?l`4B`tS)EnUx;FQ?ge)u0g3Ke;>OHj<3=@NnGMuDRWr!^!L zS@aNbowOogg7RjQU&*NY-7^~7+J=R@g#@PF4}D$6P?-T);k3RE%)}HZL$IhYWK{+c z^auGyCJ7d;XH<{bR9RTFaAU~{UfpokdmS7YZU0ITBR)OM%g!2XYswt_O?tLoBak+|#2F43##GIUUEQW@B4 zN85ps&?$n{dnr!90z3e$d9wv@V?=G`s>_09GwTSHuDCiN@3>naWrjs4YZ7vx-&LHl zKU7B=su3(Hw+CebwSpa$3RV&78-=eW3iMeb96DFvkt#IQFOQSNBaqB1@hRm zBBOT2{`x}Ss_-c17)HNcL~TH= z5#zOCJVD!BEt7l-IzKUdh8kD{8BY>^!@Nq)(-rl_-*J3e}5eeQka%QkQL`#ORW?@Uhnkcxft&@u&|!1Zutl`@xSDL_mYnihh%9$MH2b{#OuDNSOuPJaEH5F!W=R zOvqy0g&CXL42fAo2j20NrFr3?Vp-rWqtE)OjAl_SU5Mr_ODlpNmYX@kF!CwP&<_026~GrM<8qGA3P`M$m1D-s zw%nkF_X%wzLVqC2gTH}$AU(_7(|0{LI7GKBbceh;x8qe@O*JE%gMbJez-~3Fl_+FF z#G*xT!zcm^K8}=E0?DZXg@e>65zI9RENo9I(ZuFc3nfEzS(J*y9{RG3$_WA^^wMG{ zV%`Lg1Lp)f^@bc*bj>5?EK zW$;Ad3rfO!*C5BlM@&SY8L_wqKg1$T6EB0MhhRi8704Ur=}9gOpGIbb)(e!VQKexu z=s-XvXITX7G0bkeAWL7v@{Hn5T@wMB4Ko*XXGgKx*+(pt@e?uT0h4k@%|rqTGu^xF z-^~_zc2sO{&@Lb?1DM1Z6Ry`jYBC(u!6gSP1&IXF3?avd%O(kN#MPDCDMAnej5G%m z&Mfk-rE?C(9l)cJfk4<=99Lb(|7MUQyV*8}725my)0BJRW1W0?)+XnYu`Mj1{!OYi zenG;?U1UA4=zAGE*!<#}L+L4x*!%p!+O*ySZ4BpW2ktKDlcW8xExGm>!v?F62}i6C zpeaO{3L#5C!;#=?L;nv?f(J^MCG>Xz$1!bH%0%6^6a7mAVG4S`xXaX)EW}-dJ|&!H zki-Edg)}Zh9Kl{!ZCA)PQDfWr}p57CpuLKp#-NZ}Fp8-zRrfkk~V(D^&goUIyA7O{+uyp22do3yeastxwO5kJT|Vm`;&ko zi7V%!-E?Qu;NCGA9!*<2K4wr!h6wJT!ZBt^FZq%79kVuQ!l?ie6_wFak|sB%c#WM6 zY$};?4yJ$9yMiAPZ=ny)9HzBAK^iS#1V+km$2jL190;E#!w?2qr&uWa01J$fk%r)S zJAfUiK0S@~%}5EDq+=*NTYvxy4N!uO(WIHHMNu0INo4^eZHqT*@Ow~jGIazfLf~!5 z)T-92cOIb$dUZM@Bk_x599MKI&QfQ)gWr1FSQiRl*{ zxp?$6;H-fOWM&eZY5Rc($U;Lg;tZ`x^-v6hw&HAi<=cF9SlDd&Y(ve1fX4V| zUV~rve;Pist4&gInUui-GI8x-qVJ2xT*JZnX{psxyUy81^m`2M6(k&pllW9@Ll*_N z7L(8D`gt(dqUIzLdl!WqORdiU+8{WXSQqMaESU!SF^fYNh3A5H#2Lgcd~qDqo&d-~ z-vGY~I2dIf&KjVJ7)d7Bnx1VWX@y}}gDI+6lSHMU2ogUKoMTGV!0tp)iO>0v=*Pq$ zrnCg;7!F&gw3{)Qv1@j1#;o89dh|Rcy+k)kK>A z48=0y3@|~uV%8c@CO<@`29hKuRO#q~7cS}yO|!rxW?~7U%lr(@<}u^T)PuscS!}(w6)CiH?M{go;3Byk3ke?nB}RgOHv&~q~)AK7P=aBKZ3(38#A1bK>X=Z zo#C<3n}<|-F-UmM=g^+QBu#duHYV;cng-4vlrM>)VZ4SK3A`pW=NS<84hB!vB7n>b zLW71B-$&dRRdip-$UU^AYWRoay%RdU-8pG(ea|vkuXaO@C1>3_?Q^E?^`b;e%H)2B8qg4E^941bJ{Jh|W&()7~x-6V(lbLa+#IFrff&M@^`J zzA#NJ>PIk*I;LU26r)4vXtAP)qY>~%M~LUidkxAh-Cw%(ghNv;y0RF zR;t7YjxS*4&yjB7>oLsJ1c1n4(lF@Aw#?l$$^%rI>1hfyDtvj{D`JZZE60Lx zgs2;s#FoNFaOpVR0_qnRLFz<`l3W8E0c|X=YG&bO==x#YT9Cwu*ARJt8H3{58(YAN zojO${1&cnxz#ix6T9(g)q73RAM~yPHTKBH&$*@PeV-XCP&OQ-+ktDFCX+ZI~XNa+Z z=K~IgvP6c);qUo2^HrLbqTxyOlJJJ;`HuEOgpQp>!+V|xGRTi@$d}pzupje-=IEz> zhAH3Q2{o$s==sx0sM<(P58MgmdU{Ks$Ac3Y`cldV`58{J-L0RqF7==4aFiV4ym#V) zvF|L`Q;v^(8{yQ5ac0n%*DHKcN|00omO(wDSu_Yw08&Mh~_~2)e|axbT_o4 zLCIwZ6i28xttRs{p_p}!iTn#P4Rwr_=&L|h!5WS!;tHt1H`M1GfgX?75#9ui%9s^6 zxp`mVb|IFiu~ODFF0=X*e|(f%XJyn=$G=fypp^R*|9k zg1fvhLtco8;6k?@cO!)y+7OjPo?29%*K+8sbU@TGtw*8X3AO1Xz^G? z3vA)R79gXUfD>U5BtN$C)S#CqExsVMpVl%KMh?)B!`d_ zg=^%Ifw-Nwp#Kda%4DiV;{m!0AAmwt3)BIH5+jyui98P0Ccx>L!)8zWAPM-FafXHN_ zYa(zOW4_4HVmmGBU1-=KPXK!zX*zL0t=c_yQrf7-Z_W^h0ALQpy684jGX`i*6~w7g zW?+Q%h()FB9MTlLGyo;-$_(H)L_)xcH4}p%W_acRZckAP34vmS)it_+X{^hDIugE{JoA1nqeVo&4kSP`OBLN7RsYabL9&{g** zjqnZl9IAJLUjjWri3n&uri`l-<&P15dLyh^QH3YaSE}1Az0f>)XA+tp>C}|R2UD(x zECiBM%9@LeAhUeJ-~s@`nWM=N67!)Y0`@-v_Yqe!sdF-z+`B%=eL&CwoMH77eQY>? z!QZ!IIfI}SM~cpqK)#8<)d3186b)OEY=&;(TNRa>C(yX8)kcjw8}*|OTU&Y^U$aKrz%Y_*V2DY&trz3E!Wkg{ahc(4t(9Q->f?a8ap-?kq%+wdJW7_aUSAg*_ z09c>NoHP8X1m~euAf2biv_LQxSssQ!?wLwgFa?ulU|j6q;e6C6R? zK4=mE5lthyc68230f1N{C=ood{fCo=>8RF477p(-uq)J@06tJe$s*b8Wr`+h>p3J9D)rVqFRC@c(|DuWPn)Q5qOzRJX|#xrXCpa0-m zCS$OxEZ3@t>4@lAVB33Rw{MuLhhz#UM#@7L^jk3gG#ccjpd@`TS_})?b3oOJu|_gf zMlm57G%)P>L5M&i71jXX#$BJP9dELTLKzQ$1XTp0C+x7;?ML+i=MpdtV1^D(&n`>6 zDXPk1{E92?6<87|Lx?Lu{&IL3V@&uV^CD`KO~PmbF@HaRgN}k@HV(Z5h!UeXHpg#? z>@Z#u4z>s;5#UU_i10-0OQzj2zTJk_Qc4w8SR~RG1lO6=po2dqMNESKQ7w@@7Y(R0 z&SWW3IKJHR_gGRDH)&r>7$)F9e13p*U6!|QS7jtJ^Ba`bz4<;^ zAy-f?;#v%`TG8axlXlMpqk?|U5a0(|N-G&$lYYmOfXkHR@pjM@f#VV3IG7yLWEE+@ zZFUWTeBF{DV#M5ofU;6(D?*Nh4P(eQqsUl<8*H-LlUNJrU)UJyqu;mF-3+T(1~XsG zO`>;%^@@XM)ucp;IW%*j(QdU@a*Wn6)oIYE*wB8I zumBxY$w#mR@UZzmWBrkfFy8HiWJZcJ4sb+;Syb$}TcI5)WdX+%(Kj?eU;w5;Q16&X z3w<-pfP|kzo@vV}ia>LKNA~Z`$RCh5bq+@bC5A^Z?~rQYf(?M}OT{c%RK$$Ox%sBQ zj-8{10kyE(K*l2aDr=1)i3pNpG=OZZtOO$$GVO&LBf*Iz%RvVS{wbQxup{xPaK16X z43iRUG(txa4Ot6woD_80E>bg?5V3$U0v1{!@>2>3q+dvcHX9|6LJju2xC z#5x6$8HS?`kU)UU%1XrtFN-=O%mWPgSKVUnI02(5rNy0ocY`03yB!d+iOurNDDJ@Jd zGaD281p zW8&LIRqeV9H_HEDi-vwRFW57;EY>(O*{|8HB`KHjOMY5IeCsSjgE{f?psFg@oC72qSw@ zD_F~O!DM_~gkJ*iZ{4Qgh|Wo1k~Jkxpi{JehRz|54>XDBX5Zy6^+V8LFkUJ-ZnMEh z29`u=98hQVkJ3?y1_zE*JPCy%b{^AIBB&M`FGoP$!qx$!iXJXCl@K~Gb!SOiRG;+$ zHAZndV?qVFWNGf-X+<;E8}oqaYO*w382>+SV=L@QoOxY7*ma;}FntctPeCDGiVI44 z2ufv2DGCjy(};9Jo1k&5CfHnHZ&GbhUAN9tAdrkNbDOQNfmW3^Jp;x4M9D4_ z@Tm~Z;8mZc#AA$RMTKvc2y#khy)yl35(%VCPP816t7Ry@x5ZdM%Q%nARu{K=RxxO%(r-+$-#ujy~pcsoGg*YEdl8~eG;xbiP~;6MMVzmc{5?Hti_`3m-t-?PS^eAAwR9?P~C zoPu^#of#)|NX!W1$mnf?#}aEnqLyG=v1kJqBelYg#U8-u0S!}OslhOFbRD{sM3vE1 zuT|`9cQC-!^&*ZNL}=0S2oz%DVS|J4%R&W3f$RakA%|B4#cTj%{**d-F};3doKPx3 z#zui3Z$}&fVAV`0W=G&k9=gqjpC+iO)P^OJ9n)tuXHgnP*)P%bnal=?f{5J=(}*!y z8Vjl}gRNmG0Xudz8qxk3uhf9zG^2LZJ_aYz8b)>;eQ>aOV{j|rMuJ2{-C{<~kA!R9 zB(z;|OIXBOqR4MKn&ggX>QUoN_A<)m(aJMe0aG1tN9d*@-+>?cb{vAlGeSCxoS+n0 zC!*~V^Ol->E22y)i%kdLgKp2D44xh~@dm8}(UWsq*X7M)k#yI?uF@FRH0Qv>38CL4 zjA{%}wR39ocxQDE(ZK<+lmgR{!tTJT_p5tr5_of-EhK{#33MX@g$cZe3GJ1c)x86P z0n!HAz}j%xscKzrYlT&2v~aLO=qV8BMfl#zxmccGvhL^W(L!Z~vk!eu$8o@%`)Xob zYko-AR~?I$@0+}CJl=DA75Ch5SI+TU9ogb<9^c?cCxjYZx0Bk#M3ZzX)~5Bo#C4uq z<=kHCAn2MFl5lX*fuV&Q={ZjEud^W*t2#U~;>fynU#U%VGB)dH;)V!{8?J40fAKf> z%Pr8wfUSlhra-S|mgQvlu*tAETY1db=&A1`w~+a9s^dc3-!84jrF!AI2z)bhx*I}j zYib(jxQ0)nsl9OfdfsHRoU1)YH~ec$r<5gogt1TGl{`ur)p)_Z?a89>W(!hQ2~^fE z>R-^g?|Gkn4++_P{_fy?;RuA3I`{qiZ9_`F(mM)H^qLxH3b=aa%2~}QB^MbXeM5K2 zNS}kv56z+(X8~`%5#u?+`)b_OH;V9dE&%|V0q@JSnMia9*(}oqxXjQ&G$}mH2DJ%0 z>diO8!nK@#p?g7V!!VNb1>Sbu?)3HeeI$D-ALPfoEnyq1BG@QEbNPxO5p@4_^ zfc9{vkO5#jf|?c*wGp7;YSVTl*=`1?r(zc{(U`!a*uzwFF_J=fX>Bbi^ii@zVrjbD znVBR?&8st80ZPOB#f(jF%=nlb4O_04!=xS zOubJq9!W$Rtf?K$M?(>YR2DLM%+KJIrM4@skgX5RHGFcs+?!@4{k|urvjDYp`JB?H zGK*x4fDYBW>H^wfBcss1&4!Sh-cA?hv_@VH!6e)-On>Q2qFEY>2*8Z#;~^oTQ<%aP zv8Z*?%%{{6wULjtXa`en6RM-eo^f%gR#)nJxf`+xn*v7=2RB0YBpc!v4Fv0R&Z|L7 z)x<}s&SMLrOcWpO$$jB{v0^3!h2WpU6^;~sQ(pqLSYq4U!Uek1OnIqbgaJ;cSh_7odObPkMf+CXaB%46sm8|j0U;8@G}WzZIK2LZZ4=lcW(z#_b~6_R-70KIzFfk9~NMVBbn(lAJyp&T$JC z3}JY6!8`-{l3qy7Oy>J6%*T!@h@NZ-?NS8MhW_adN9lEnP5O1|MW>P%M9 z1Kd{TQhNPa4jAiZS#G%Wb;sfjmls!6&VVdWJ{4-i?QQN&@38;$ zMwp>4+$wy&WMAijYUvqhL_%3Ht0}y0$&$C;@bGs^xwR?-R`bBbqATXHWb+to0UTp} z7$M*|19L_gi{dUB6fqn{#OevM5v^lk7KBd2 zap3fTpan3Bp;dWx`JigUxmy`D8J$}#U4R9KDEDYw9mg~Xp_I8W#QQ&z2Z%TUO2V`! zbO}KtLDj}?kJO4F6-4X`BqwxgV+aLg5Z(rCT~J++;nE>*N3|UBSb(C=U_+e@$M9_= zKa^SAok%%7RStw|KE&3;UNc6_F(>SgYd@2Pa-V=#R2L|$0a@YnuZ#htfFc3W4oM23 z1pt+r2hV9W*D`}K2wGlh5)kuosDz|4QS&aFq?+n5K;v^ZyNEt1$fU`DZRsK*fCCr; z$oXelZ&c%!oe<0gMIZ_YGCm*VGeCG^o#Kb1Z$>cyi9CuE+$yft-KEI**QmRjR%Q!q z+aob4K&1dYedxyk^|t`x46m@>{9sS9bI)_dP?~Z^m>BAb(h9``y->ZR_Oj%F_GcZpbQ(7c zUPwr3{lMvq$JM`?m~bd5VN|r~kg~g3N7%h&Y4bH$9q_+N;D2^z;42szg!c^RPqv`7 zKGljvd%v=mET$%x^APQOd&ZV0-=cn|3ZQ1MPtQaB92=f~oQnf3+JYJQOWALi*#*l$@ z)4_01QQ1MrZAyj(J;!a6`*7Lq>74#%48uY`q{mTF6Bp_%50NH>&e`Hi!i1*I!;hUG zWmaz!Nn6SXucfL!f7^nD(sw}?VSWn+xWM4iUjfL5jsp2=nbu*sSG}_=!e8GEeGcN*F{lhWXzo)e zHi2`)!nB}zcsG(Lc|`n1@&Fj}FvTQf8u%J4aj-WsNLdMoHs#oql@t&VKR6jWreIII zv=5@ZIfKHVCfLAl2bKkv5M}FV;oyJ=Y=O=Qjc71KpX7l4A#nZKcs2BZb$|#kgW!l~ zfadXLNs?VmuHi*gD?&)k=;|1M4G0kBF!sL|ks!*f!C)xrFH{})`?5*8$VMPZ4j2s$ z9z;+2rHP^$xMZXxm^T6N2m@aP38Ei@>8(rY7`~-Tu?#N)0usJK_%pfCt7b?w@S@~- z!n`)~Gs^&uP|C9geJ1ufD}<{xX4H=|cLBXl-L&S$Q#buHEe*V?xrI4O8#ocShXZ^< zlWVgfOso*t45>Gddn}!H3av)OUAztp(Oc_81$_vHk{QR;T`aJ4!Hs22C2|mYHh7VQ z(G=*>07INSf}#pzL`cvk-L*!nE$zJjY@7n9b>#s&^9>bZYDs=gBpGx*nb1K^ldT3A}F-ni!qA z-pQ#2rb=Z1OnhORHj?Mql_kt7PxmTk*NE#{!iPgb=_m6jWPQRNKJdo)zIEvMk zJz{6l`_rnPhHiPGpMSvQjl3@dr`6WgT|K`{EUKwpREE5P53L$~be13roznlSrR>ac zr^E7ap`N#INb(7d0vzQPSC_M34=~73AKw39tH{+NgzFSoiW`1`z$}T#ze)C)L8@u$Z;goqmk)in}B>`$uzL_ zIj9t*G~my#F(WrGw$zOXaV~p@UT2yq$1_H_X8O$U7XsEC1@cQuGL#yKyFzp|&?5A9 z;T@q$$M9U-r$BwJg7OZ=n<|yYcA)4$Oalw$Ri+L=u|dpF!PhVYY5P`H3xAM{-zj~T zz=rD9P^wSnmOH|%bt{^jM_-W4h*rG^L7wHbnrc%O6Isl{S#GLaoiZ>WXk3|&r7$+b z%|sd{mS1KvDUN(hT4i*+<)GAbke!nH^xcVtBNjhz(KMTZ-MGcK9<(+7B+hVeS_w&Q z^_+lieM0Dqgr)>RlTY*}-c@`4SZ0A?mZLPr075_m+q!~bV#|N+<#Fj>f0DJu?hJ1` z#P|nEfG%Zgg*Kh^1|0XsI8|eCj<|NXs|`({=F+hWsi|UtdcLGl-Euk08nizd>tkl z8>Ccx=AB_AZwS$ILp=j}F zzTP1#)w`&+CC7~S?2yz1?&1Fwl_MUM`6N|&<8mCiO;S}sS^HFYYOr?B&OFGv2cfsgbR?Rz0GUC+KmOAv?K#vjUe z^!TWe%7er7{IXm6eIU9Z+sr}{q9>Uy?sL4|bh<9JP&`uQ0jOR+X6NjRwsWKg(?m`z z1ATC@wvd{eO4FyAO!G{pGUl-2iHE{SFJ#~=oWa4 zl%`5=A9p?(92O^^Wm_x0&+Lrb?rDHxoZ#gmPK0$??`DD2hn6mB;e|c?uwTA_U@3d3 zP%GxZi{>gXPOfxenW(abEsdV05i&_vOCM0@oc%9ma843D)M6bM7D&#!5l{B5@$OZ? zQlU=x_`bEryli>OeJ1FyavVzM{zO?DWWvha?(IKIYL9JAaiLRaQR^18n975p&KUGW zHU7=`12Y5how8%Jl;eVwCDEYyqK@+=dUtV4>%)&vJuXo;%u?FA1yvpRzIyJ0ZO@>Z z4a18wO?XvL1g~$af$FLWtvz@ zN!Q`BiT#)P*RK2BCS9A?HVql?X}z_i+su{ zHSV~M4%6uo!@u_tUNuL|XjN@VlWB*xRELkEqMJ7F;GAoFu~hd}Uk*N##JaRN>{y^- zP2K&Hzo~-pkMY3`;d50}?Hd%Hk5cc)sc*`gxKX|Mz|gyU?)S~f)d#JgI%=kEMMq@{ zpL^Z;A-s*-viGJ)B=|z^KVVp}K>7ZHOU~u8Ab9;q6%-#7Tc6`kt}~%zIhjwfl_h>`-1aeS}e;--PE}mehrI zpL%%Zj|&y*GSVWrWanoHHFAN(9zQLao+TUWEQ{x6$s~^mlso;$FQ|WC)%3>k1k8o2 zj%^QJTLI|J;4muCqU^rU-^}*@zNJU>R{cQ|e(M4bef7ud{3+xz{dDI$Rb#!9PeRuX zW|jZ)dfN^>QLTf^#*u%3nfveG`j|V?|pIirF`I z#e2%$dTGJ?QqGlkQN6+wo_R^Z5_6^FhRgZKhK-1RO^iy^$b<)gPe~;xc!>l=fzl3+ zJdS&)wa;TDo`_iq`Xljfa2^xq3OzUsaxm6ZDN!V@1l_(-85pQDnpq;eXuVAF9VCzH z$;ALkf*OT{S#>Nxy=D(@B14d?D>MO#F%8rnV+`bsp!SN0CoE5~3O~){0jT^f)G}I> z45)98Ds0?e2}v{4QZu=sW2(H_l{@muTTjI~D&ohWH@~8qGU?Cvw$FLu1qpQvm|Hdi z-rk>Xe4n#^;ad-Zc_w{nEB`+hMfi;k+hCLkX1AIVU9}4o{qYaL2|zl7&#GrU_6T~$ zpdE7nGGM460gVE7q&1Nikp^MnjR#V`U_LUYw^kk2mn8Dw>C0A}6>~29%gTnPm+E?E zw87y&O*_1G6TX$i{BaZB`cQq#u(GJOdF!j{kBUY2W|;Nu<(TL4&xs#e^?$sR!O8#K z@Bg0{{oh2U%yTpU#cyV{;@;Kf8?6|+dcj67QRkKNYG0Aw|n5|L7xTW(Dhr{OitLNLNZkyEPH~k=(KS=op@r5WxaS=3WNRoNFFrYDKF=i03M`w_Kgbk`C0^?aFF=d{S zsE#q$Tq1+GB9mV;9w^vFUd%)s@!p9R&rqu|v>>i*w}F`wLzD;j-JAq`Tbi~5c1t73 z<-x7eY*x$0F_C1FeE7al`Ehv9alzq#-q<&P8xnSH6AyOsq7%YbDo6cYQZrh%tiHGL zco6Tl=-=+&h#);Dld+roUs{?I-wI{wS66LOpG*J4r}ZD!1hX!&5C7A{cf!N}UDe^w z3xG4_Pod#YVF6n~WI(0HzulR8FOR;dVBdt=w{Az@rGIcuA)WT0uG^9{fklP?Y0XYx zJ}oF+e-2G=wqp?762L0~bo!sZ`6XxoIKY89R=ss0>b=c6b`n?!WECUyH;WjGFV2F6%~m{L^=n9^#eyoR_coXzkK5Q#1-b zfAw|d2j2T|+eSqC6VGotqj8kA_uVC*-#nXyC$~!-jr0?uzfzE5jIHSOAz_L%XgM>1+J<)?1+owc^<=)`j_}cD($GAdY^tz2Jz+xjOM!56#pB$cUoK4BfBWIg35!#X0|Q|c>d~PS zy_l0cQ;;3>AWig0IvmI(AYz~VPj6>tR_*YLuk||(9C#u&vv?=!R_@}~M$gnI`@ETdJ*PxP|l)mU*u5Zlj+9X?BPm-v3Kv@{mjqLCY1yB zF=pU!x{|f&I$&`?A>hvP+5805{Nnbfc3IDdrnu`epT&F+zo&Q+w?^`<>at1K{D#1= znE?m4AMe;a3bSut`uKgw^|#);*?wQ}^lZbKA=cLCEN<_;{pH^4eQu8z@A*uWz3aL3 zO!1GH@9+$ge9$|$Ts}}Zc$vFihxRGwi)RObtsK7D=|2=+Hgc+P*2JeEkf77nDzN%~QH@yg%e9yfTOQE*y7J9705+C2&l;|G z`%oW}#XRpXKA(mp`swUTYo4)=9GUUnwN1X)4o=UgJ(ZuzW?^jW*!HSIrBlL!Z9Csx zMbFt_edl7-qJ>ZU?ZTW}5IE_bK1-{{yDVJ)gI&{yzi69TOj$bT!j`$29q=G}Ju(in zVqNy*e9Wt4vEiqf3

wG9nKPQhi}fsA-zp^5(& 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%0A3+iNXbv``51=l`WaNJ&nQqY>8H^H6)QV8z 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

FF1?XbKbm4oL&$`q_DuH!fTXSMhHxq z0^2_trULd`t=-M4CJ55L;#`}ps1tN3h+LL)=Vlqa$D=6LL}7eFu47B6xi_A7J0ly6 zEWH67i_htxxDg8JnT|021H~L>z2Zf7a_o<1hvw?bEVWU&ouxd;y;D$oObaP8Q#Vrfu4G#zi z&_Br5MfNza;u5Qa_q(wNjl_LyJr8VW9gF**|MxbtJu=0QhC81E_Fo=HP^WV|e(cahHd~=D@=tlE_aQ!u0;*TAY7QB)C z;akM|akDdsRF1I+p~(whI5QY(kGW+tAYj7i{TWv$GRNu$iE0KV9@tdu<0xqVD%F0A zpyGn@XE+RPLT0>x-Sd6dNFtsFA>tCLr8>0Nh*@7W+L2d3<>rblNm%zG*YiHb()P)A zXXJ!x+JcQq)K3@N+?w_C3jo0)$~)oIi@x*tu^PA6xuJ(W1;m-jpPZ_14ys5u^+Gs{ zh)TTy;XAIp%_0i6+zAG68tpife$B{-Tv}^+Y6d3PA-3Td8{9NL)acT#qSCBwniThi z8{6*X1Rs-`53&(By~HR%DX9vFCRiRw?}_$Tyr8QtpZ#J>eH@fqBr!f20Pk}tWCH+D zxr)8cm3={vMnfQLO;3E;!9WuESfNViAK;4~U1YtWnE-^|Ogb9!lBMnuCy2v4QYa{c`nwL=LeNDKNEqT<;5PyqWeZgh?*Yvvu;Y zJFlzW$sL#=hSG_%HsNLG1VkwyO7j|trQpz-LN?t>leAa$?u2wmqq3%1-3_^##B~+1 z|NP?iCQvCr!_rbA_M<~DEZ~r1BziZztaxr%kMjNPk@tb9rzWL(%Ch@&4)dbL+ zH7aMy`p?i}GEk>K&E$O^$kXJt5}80s+|f|%l%Ax@l=t=uGrU6)(3%72fM0McytsRw z3JP4);dO|p8%N#ADT`t~oJw)8<|WPp;}JVEcH;D})#M`dDuYC3O+7O`VCBPZW$c}q zj&cmuZbK4u*Q|rp*0LcbM7gh7w^H``nKNxG?vzKdA5P_EnI? zurZbqfw%(MB98(4TB1l-*jkZQKx~T7(SN7Jca2|Tv1@6+wHlJ4k`ZKEK{{u_wQ@n$ zvyS_97;pRD$d%Lg)NwD=wY|YI^zpuQPDss^BX)uGVU)W;F0Pp;o}o=>3rv_nhlKmD zzrKxB{YQxtakE zG{MN7SEUt|FOPf9aI^_888F|Qe=x|uEb=HNVoLnLMy@|uU70_SlgFxmLc>uD(NJpc zbZb5)IQf5XL5<2uj!APkx@nx!=ZsG5`SQ_id5JwzLUISppI^V6C#hVS$J|fE<9f8~ z0Q556bWC)nK)Nvn@&C0(CD?2_iBZ_?yqw9MP6AXv2WIDGUntg=6{^qfe91Uv4t>eY zd&%*>{|e+^1wa)+QR=HOv>8RvggvBGvtIt-jmmD>gC^b$n9Z6^OXb&y!8AeH=jHD^ z%As?1LxeT-F8e>A&42WsPfo$^l14PE)-|UYpaJQqxW-b90OQz%$-|`5tfGzv)u)Iq z4zw+ueyon$T?g)Dgl#K&iP|c8!$ew(b(E;U9cZIUUUs~UOe0p;_GY1f^u^^EJl=-4rlvx9CA>3Qs0^5Ptb;+-cm~!?YqVj;3FE-PAnL-MlAk z=dm!fT6QdQe06BDi9`B78dVO>W=4jK+CL^M9!?kuMTM-GJ|i(G;Xr; zVDQ^)g?yqMyA-RI>No1tj)=|^L7p^2rkcsFggPc(d}*T+WUHMBwXK46MY2+@K$>@% zH4_S29>YpNWm=8xkPRQSI4Meth!a(`M*XE-@E3wxFa6ce+ri#f{Ca5O_2AwlS>MdW z%DlvxMAj>vU!7*%n-Scf!faIuq!3=UD^@gscv^H*qp8H!3?hilpo+5odG! zTj8NkChEx>DDG+~PWBFJjcrUsEPzeDF4NeaZt@J}8f914_~?_B`W@tLt2wb)%rqfj z{PGXwshxC$$`NdK#RvO7y#@WV_xS2c{$X#XS^3e-Ok;^Qab9TBZ6_X|_<4k3f->+j z<*D6d>g^LJJ|ydPwdw3Dv_j?GL}`4Y6MYP1QTIb8D;3JXghN9?q6ww(UT#~0M3bd2 zghr&h{#+&Gm4i-WpCPF@N#qNMw2)0HWa7vM^`AT3h#7<5r`Iu27N0bnypgITZF3pf zj5331txzX-i}v5gm-AZ^iqGT(b)+I}dVx)bbiYZGT&FNN9=c-Xz_{YJ@Z#f!o}ztZ zN5WeGO4yBLgV>Axr(2>$o9GJJCaih?UK8wKp{H>u)2SIOt}I2jABV#Qr4_I&AU!p0fF&wnBNW+~BP8Slj_xkihy(VMsz z&KarH!&0jB%l74B$1i{?NN@AWNDIq(ZD}U~u;!fI>1SZyGs;>t3eZ|cClWFWI(ALe#SQ0dFnIhgF zLhPzMYz-0EgM902p4`cttTbHYw9YH;*i@iwP9Xcf)6qvL-QHaNU-|tx{4a>T9xgBQ zfTr}t!q&G(I*#NTgN<&|(C#F83LCe?d{;^z=OEbf}WEq+gq)qFmQFeBQ ziSzhY!USbO7Wf>uVRK-On7=MgIY0jDnwk2#??Jf~8IY~{Pfk#(VSbN?`FQ5elbMQd zvOI9^{#U4;BE^>3PQsLwq(ea7O70bD8&__$+Pd|U^m3awRP~PU^UJC!fCMp-bEbC0 z`$GE9y?XeV(Tz!UXBw46n{g`Z*9`?E&^26~M<);ulikGaZ|?xBomkw$-?zjLRDv_b zkE)8T$gaPFY|LXNWnWo?!*?8=x})Wun2T5%NjrG|3-1g3m+dMAk=h&$K#2dl~swAq({cDlj@ zo!C3PM^Wt4n=Td|!f>TohG8)YL~0^m*7_p@tc$}5uNGkA9oZ@*y?Dk=iB*A#Q!h`pqpCaOMEyT`$4WrsBvbztsbjL& z#5$-S0UoO=yh?`>awvwk1}SS2ETGJg=3EX$fRve`l^~*u@3!zMoio zM>GIkn|ydf*er`aDM^r0L?8EmPfG$hU_k zEl3Zct^kg)JR)08&li#TKRM{%Z@h1CsXU@k91nND9uokSN)0g%s=ld+Rz0PN3aO&@ zOb2!rwk^ar;PEzITZ2$<{!x^$ptA{@K0C&x!h|D4P`)SZ$tgz*y@ORMlBA@+iO6i5 zbbh@0dSh(m=0@dr(abX>4^C35RD5JjlekSbiZZGISPJ}fb6 zf?L^YSBW|eX_MmV92E*#Gm37X*F+<=Pp^r;zaPsQA~?(auakQv3GC{2HU-kVZt{Y{ z1P>GxjKJXL=4N&2+O--%rc8H?-QL&SyONid$G4+}wV^B<-0o{JhH_UGwJ(=Uq!Zlt z`bQ$5uemVOr<>KD422**ILxT5tZ;<6RKK=AUk0Z#q67lVRD5%+YkK0NsQW)Rle!Qn z*v*q(FmRSNj|Z0lk@!z2sPVG1!jk&xI{C==>@rs58YkyMxa4-Y7_~ll)n9QQ<0OGz zA-JCQ>8ZDS^47XDE)^8qklekIraKLs)CHUkJs?JN!CR0`WDK#XmsEoat7Au={rvo z+(Q0{!6o)UKZF7FDlarJ3cK_XCFhF=Hh~$`{BtAc)HK#B&@8Zu5OC6IbEjgI54TE4 zK_%oU1q)(CTwAkELLzLcrGu?{|GBHA{aT*7ntG`02DVgL;417s<3@p-1xrKA5ag?0 zr*a2=&8;C9jFUipTL!h=iYd@D|L(i^Iop4A5cKB=>`Pv7^H0u^j9}nh;*T2>t5WVa zH@EtlAExsnD`MOEOP1`TVm5EK?nPO08=dV8Z@( zQ|?z?2^En@T31B2457oYOkP1)`&ewL;V=-$%6d6aFJSy)7_V)pueXVCv4tG=`iZvY z`ScG__Pq&|;E_R6=9U+n0$H5J?{BgznRAdXL(DpV_j3=^KXMghz=&os@5@SvJELB0 z5rrjPtCM%IT8}}lD`C?+(`n-)g>97%iw7-ZTvc>^hK7`_ArnUmoGe-z`i0+KvD&&N zoo|VUFOP=8kN6x>mskC;FL)QUjwR%UwI4z>OpBra=%M|#QT^qVNV#?xF24CVjA%V;#Wyd`o!H&Z={=lc)V~y2|88mcWnoC z;LgoC2dGD;1BT5pF;;#Kr2L5O>}+4{=i=+o0>9RUjgrBYTC<5V{;0H!jBhkM05c~z z_{qfa`;rwo*KA8XOlIZQ%5G^3SjQIp@trX9NPV`teaN@htRqgpZJzn!H^IL%XZA>I z-+#Sy$p7C4jZ{F>m)WdRhf5E=O17dB|-v{5F=tgJ3d}QZ4SEci2(Gh9PL2`e@YKK6CmM%NI;1##| zS-tm8haMH@SeK{R8}NuynnVBz8wq~tVC;kk1`)DAZOBeLAM?6cXnvc71$0WIQ8|{~ zaTn@oNi%R!Rx|}K108$B@rGff#*@%f>_oOw!-e~4Fbe2kf^TDW9fHat0li2ktBkaV__M(opK}B> z`^Z(G?!X;B@qRBk%CzW4zDk zQUau5rj@6C3Ne%O;-5FoIyePy)tjm<574^)!3B>Fej+}JQtBHD%TYO+0s7RrL}s1r z1E0Am=NnkD_HM?=zS$Fx3Yk@lu^`U4y!K3yvk)3h+a!UBZFvY)IW2+wK4)lS(2nUN zFV^2k2=X1-4d%;g@|gBQWE@pzN%%?qc2JM@1qNj*C4NwavpzQk}dDWOVthyDyNQa8J=5 z;Et<`p*gdooidln_sjwS-+>34t!c0$NZ}lVD8}?%gEjj!&-}y z(bpJ}1X7zG%hun$4?GrOBbT!QJjnr(+d#2`e&+7+p&{A8_0rr(aOS1XT;Rl z`WWhEvNi%VJLx-)WFK9%{?dShQ^La=P|YcFHy9Gcc)za!x&}oHkxj|YB6%lj7WCeq zqz^UvUHh#{NS+X)2-hnrY!OrDyvtrxw|%0U+crd$WDibMbHD@q;7%Q+T}a@O_{$Q$ zRp?H6n+_G}A@0ABgfTeABcq~HI@J5`&-)lMiz9{Hq1?s6^ZMSzg5&WRhrH2 zzQ9pcLCB7zYANvKeW^^K{cM9s^%>}K_Yn&7e|iUz47K`7Ztv{_cgn%Q%lqjo_JQs* zQ6zmwg=9@5*;T|nxi?=wgygXJ^~@c<>Ys9gPCDNJtsYpDNz#B|WD2&nP7GKIlHOcw zP=GkfY@w4B4?4psmwgN&NoqnrV9&vWBS6mlZg%nBi(PZxeAA`{w0p%$8nGl&9mt7O z8Ntt7d{ujem75Rf1r6Py;bV!#o@cj8KA8x{@Xj|_U44(=#;+Uh7ybvZAGOz&H?Ysy zK_b^1c68%GSj<*WO1scY~$=dTxAA5Og?5823?U+K}Mh`Lc?*LO|#8AAA!I07^swv;EP7 zO%J|5^wn33$nF98H1@7c?zfpBi{~&RCffD{{(%|o<0Jn|UK5}u?3MyIH}@aF_Ds?y zo2A@0$y&AN&t5(^lCZOpJU&UexVpN6w)u2W(uj3+v7dc*bI`LS^aPFUAM`A?CBGj9 z_g|KUCQc+tw+-ypW8-sDf!mAmF+V-4FhS6lce2CvH`iJ3-TWj85~eGY%T zuPh~rg*4L{0wIOBpJASwH^1${=^`Zy)oJ!A=;8q}uUlT^plwca9%LU6?;GaKsUI}? z5!4TllNH6sDctfj_09>?;mzFv+CnLiznx%S$@0!!)wo^J{o57d6g^P_K{|n?Z7BY* z*IK4Q&Y%Z~P!a;sXaYmipByjt#Ksg4lSLOx;eJ^s0#*``0;{uwbp({-s!Z-DWY-F8 z)$GwA?)$wDx^6l&((xCAD3gCbtc7GOh(D-pj`u~i+Lypv!S!wYnza{KNprs8TJ#`% z7G!AeXAi8pI1qdrnEzSQW#V6|ZntJ;CPbmu zHKLHVv@{I@A4#*RQ2u}0W=X5WTgNQz?E*xL$kJs25v&=hREyr4PJ?6G{CY!3lk|8$ zY3p;B=uI4oJL}Don3p?ry}{`}&Z(RyX)yGX-Oao$g~tr{(XUv&fv!uDJ#w>pbMp~> zOf8IobM(TI3o2*9KqYFL+r@W*pg{f+a*Wa@Y%Up>*7^W*WLmsq#~}z$sQBU^0d<5E zag-RsB~Y$zuM_k+-@iY%wy?O;pxVn(j{|^sJ^=*1iCRU@yV;SmWGw|3K3L8-g$a%T zC?W1j5PQ(Idc4vyQo2cL4MZ;3<=!S5ixAJx)3*M@QD76_xC3woIZ`Nd4guI9oiBxa<U33(awv=gn;O zKwc>?F)N-VZg(q|ubou#^6yYk#k~|+v{v0E8AwB>Sg}slW`fW!Z+K6U)}60K`9er` zA?r^~Ywqlp?Qs^QX0{@KNtH0GR%ffo-9ed(QR&2fTPH00e&TD^WDhxVrfNTqay94LX9gon~k;0wK@0%Xl zPNx~G@_0)+ug)cr*|P5{Ofi1Kg6qP9hfmGc2nfv}vvnkLW#fh3^HnOUer^8?UmKa0 zib`fK+t88S2Bcwq-+%2B^Y|;yz4HLqevm zgKZp-gk>H72&O`f?ALCtB0j;xeV}*A*2>L!&|-1Ek!!gvjf33)FRC{j zRiAnS=3EE;_ayIrtq*dAx~t+>3CKZ%4l5gbP$Q8)ecxnQ*?@+f`t1jZ zzwwV}{*<$q2+1dD@8XAiv3JOz9~Vb?nupj{Z}{IS9X-v9$-kstEH5k2tcO4&*^_*U zd`KiY@%QBK;typhCZ`Hg(QyI07iiJ5UD^EDO#e)yqz@Mn--Jq3O+Aw!}7JTl1fs`9q@Buh~XNgr=8~hP7m!lo(C#i8Ey#dS=v_|Kv zzRBWM_={@iI6%SSC^zzdkEed+UfD1-8h8r^6q+x*gOfchN0bUaDZyc9&i(n0XyJDi zaaU_nJk);opuyRg*j0tex*1&#d&!ohDX?M9h5 zA|Q)2XYKWh!L2Wq?OD_7nSWuvrIgOq=RD0T*Dy<4WT2RgW2c?r>c zb2NV!Y&v-17-_do<+L`zG=gA$Ba2v*a2@oLkuEdFR+Mca*Rid^UsQp04|L+YVIRgP z1#wafBLssK%3Uj88<&Mg%z?}M{m>A#44_qWXq*~Ay&w6cbH9YOk~N;_hE57^)1RzS z(8!nW@p<4nM~2FI&*m-S2OB|p{>0Rfzw%-ocHJTdEF}Zs*y|Osc*(F+(n+IG0M~>R z4PEr%KOU@tChnb?>jOy6TGT*_)*oNKwOPuYfM|%8U21>kD+|aK+XtH&PLrzGF=z;k z%jvLyl&|!k`%x$Qib!;Cotn9TB1#3`pKa905@*~RKGzup^#DmzK3^n&0s~2(d>eMK=0x$2%vx?{>d-F?E7vf z$dfyV5DQ>{FoNmbAbt;I8+w0X&2Wqpdg8;tqYqsl`i}9p-LtGhn1^}OvLK=^h42Wp zO~@$`6O)sF0Sy%26Wjnz;+JQlysL+=r=G~Wr$MY>Au~8Fz)E_8uL9o$=rOG2L#~V%lih!k zeUl`rj6bDYMp?#SE)~#CtHk;fbxQE}(c30MdUY7m0s3r}ns-JqkOmhyOhs&dg#{J*baaBzd8 z$#f9RAbwul|;hNv9HPobYe4Gn*qz}H0c3fQ^>0vfspwHom zL6YHSby^~G^yI_GC%zqk;*DT9-L*W9bZHqk(Q8WXhAb3QCC4|TGHO{vej)aR)kPCg zakS*JH=?H({G8k$Nxvls^GHvrb2W&kt*ywSqszNg16dK+pYI8B3bbRJ7rn>xAuZ8k z@jZ0wgW;Mqe=gpTHG|MiLsjCauI|!^F>>BAIS>3$(nU}rBX)0H4`vZe7C%p7;$%pf zc9b}ssk3*zrA0kyT-P8D~kxxKx+w<8Rd|7+F*5p@08AGt;5r$F^c{O8^pqeW2qJhP zH`$9p=mx}ooqIBDC@AV!CO?+%(FKmSXMi?eDuCU&(SU$%0FSNc86pFYI zP7@prkv^@9D#Q>CI8jF{D#RBB4%AH&;ImH`!lzRmJ18NtNn{YnGXk&&~^8ErF zOE_loXt2HP;r@8CYPT-TACfJ;vk4^xCJ6C4`|v-}D>-U|+z*`2Vw~}0!3ukdY<2CH ziEk3J8O)URg!)4>m0W!>C1-mMs7VOJ!zTG9Gc7>Z4agvAzHB~;{qC@i$jhb zf`^B?i3sN+o_XQP8t3d9(soOsw|WW%l^r%b0~q{>CtZ#X1!(Bty={QZhKlJmiS;^G zCVYa9-I51M~jl1Eb`4O;*c1iAK4(Gzk9+ zA#5?AO#%b?82K>OuKS}VZ7J+~aC%;X2WTZHK@xTCq{D>5XuQKw3PL3iI?J})_ki+r zp8}IH8BPSsDqc`>O2z$E5%CSs3UH?-Bf_&CN=Th-kQlUM93?i2-9E;%07Q-BM_JAS z1qHwh1C|F?eQPUltb=;T*p=#h5}b$Name&ydB*K25$dEM0-GhBW`5r*S?7G;lV~%P z5BDUK>=+hq!Cn%9#+{HV>N^1g4J6G;B^vD2G1PRH2ajTzj0h7F*t>8jChP(sOm{Td z&!Z=JKSEDWFvh=y7!(#c*k7l}-V!#wKlp-E7nGQ5IW+|48L61Mdeh>#om=oZgNqru% z2=s5C39#Wd5sJ{wo4$}RbR4R>P8JFLm%W@T{n zdK`~IBL)|v7d8@?KtCRiU^tN#!(sr{Arol0@y#KG$KY}JqX%&S{Ws|GdSQVE_e3xq zz!72vJPaLB(u*KT(>+anc$rGB_FbA@pf$h*%DZRGzM5WRro@lBWA?@VQ8& zB$eiA?t{6xTFV8FFv%w$ZN^9Qc@&02pMj z`yonjmE`Zl*MljVj5Bcu3&?$u=KH%p^7RWsUHDdCD3GY8n;E4PN zVdcf<3A7wE8we3R#ja|0Dr`eq7IA>6-nrit(pSWqhli3v{vcyF191%20SE+%w-ON) zvaFH|KK@kT_|)U;>adFLBOYG~y)3d$ES{Q5-Wpi~Xig~=hyH-Qe2m5Y@tJTiXCq|Y z2#15oHPynR*X8hfveyeCOE0lGvhvPdiq+Z{S;kbs%m$*McMT#$utl;W;x-8z1!2b# zjKD7$%Z%p{w2BFc%F~Jm8|m9Ev!KO^{rZ+E`AB=H z&xaRU)M(OpUYn%slU>o*j`!Z0{qLh(!G*WW1^xlc#|@uWew_SW=H_Gf%g2{)jzXNX zl#yOLk%!ye?3xzZH6{L#=M%fa5xaaiG~%e2ckVe`-aI!?S8~yD9~@$5Yu8kYNo5K>DCG`PB|BeTXyrJmTppJsL{w{cWBD zv-b`DB*76FYPFP>XI%4~IM}|l`(GG-=q9SO6*{MBcp8gQSukF zcgHJdPeXg;cT`FEZ83~jeosJb*Z;GIKMi5`+YV=6nWgZJ*$JE~WVfLvS%F7@B zZK1y%^-G_!1Ipk0|1zw!kyjNvOVwFQ%#!FgPN-h&8veo#W|#E;{O*|#pL0X>A7^mR zvZs*N&a%=A_hx5jEyBw;zI@tQ0${8}EqnQzKLo+M{!((x4w*oM;b+Co~&G=2HX|Hx{y+nR@$|0a|#y`MKrBSG`^ z4Q8tXot@>SNAcSdbOG1@O^;7bT0V+>DQmtkXv&hDdse;qa#lqCJmuLgT3+6BUHgCW zPIcLh|7{QI(Em8W$YayJhks=8Kdk13kzckE1T#KX+WmI-dsuQVn7J23Vs;S-wxMr< z{ebm1ZqmIGxR-|6#ODtSTI7>fQ)GR3{G-)-X|M49!(9v9wVKo4&9iY6`iAWvC-@6Y z@Sj#=odBEtZ8^ykKxIcry(zye$Ep*{b;q36TH=wEN0YZ85d zO9zuI>#OC5FoRL^D%dNo)9eFQ%UVOa1&+jVpLFYEea;RrJOWP{!5o9Z#ykaV>d#;)2s~+L z3lrrufHm9#4=G#WLtKl4Iu*1HY(*cwj%0cN&vQ`VO-vQ`NdwQr_}H6=!99RQ!mu#l zLc7!Jw(@Y`xa`1&wm4^D(__=Y7IIy0ojZ*!P`w(ifUUPKtC=T;C->UH9rbE#Rqz4a zAnaf8g`R(p@^TD?33LJ59jpUC-88~!2M|mM9#Aq*lV+k*+M#v1K;HjE432J zx>9UJc{X0FCW|Idy;?X?C^Zga0m1%oWw&r zN0Q%FD~ab&I!_-ce%nk1aTsbgV+=R^h1@cC}6Yr&4TWtKDu-o`o#g=A$& zn~XVf!@B36<%R%~l}}(#G3!1c35a+&H3Tm|6M#M7YyG@&gb${F#bWTCGp z=0+nDrsi?yG5YPULysa*Js^dg>#w8S&) zI0bt#MS4^~!$(?#X?32z~rTwPhbajI#0?)x8y!^wafMSm`Y!ZxfUF^A& zT2d{n7P9wl&+^zoweJ8qqUZv20pXt7)ixA#QA?jRA_``1DKajmkV~tDM;H`*B0lOq=SYg3*k5`Sk(eck{?aW7NVUKAvm(D-_Fg+NtW#UlZoDmpGS0Sk=RvsqoaG(TAeZ=tWYPayB7m3*_ z&P-=JV0)Mju`P};=?cg9{gDUC?NqXMDYmpw1#?Rc#f}133XuXf!R9oJA!i;& z)Tj6=6Va75mCBR{YLainqIabH+nJx2dC3&>ejTOB}>5ZA3dpHQlOMl8eCn?ZrSXh9Dm zYO=d@PnL3}fbS~rvCYDGS+J*%4wMrVvgX^s8nJ1juDMEuti4-3kpvpNndly#<=5o2 zTu|r=T!OPvBQ{8e4=ha1T$LT;?&8yQM2ur)sLPjQ;=S7j!MjP93*K^Y zvV}8(dBPni(p6$=VS%k;qvLg)t4+G30s?s34|sXF66N6n?3|Xz3U-nT*o&MF1KlEV zfr@l6q3XU^ZD~^lyuMuQ21XJr#scXogrZwfC1kIP@{%M{c&z!0fC%+XPDc=o%GX(Y z-N5yd!Z2YV}tS@#fx4I7Pm8Y!sWtzh~%ItTI>i($mo=f%A0 z22+ktdMLFJhK&VF_Yj^0cJ&YUbOk*LYSOMGW`Ujc60j+swMbnW3-(TnDA+~ZVDJYh zz@6GC%M_4GdI(N5mH;{lOq>?jnWSARfn3hQ25JG$InX82@|KMRU!**mP|Rio!h1QO zr2>nU-UU^=5u1cF^W<}*Vg+wuYJLI8LLshz#jv5+#k$&o#m7eB+L2g5f#X2P^O8h& z@^UqzFq~(TvbpD2af-J1;)-+lGO3O@D@K?=>uEH*-Is>SG$oc$1#E31=<2678A}uj717CM$r~DXg#cXSiX<8g^~u7_%0q9i#YJ0n&x zC~#Of9+MTd5^L6i~`O zVEifJML>LTup^?LtMZj%IQYYgxg{~~K)@2rKK_)Yj7ltKhuC6<1(dvEBT#6e4X~Df z;+aku2TWU)M9~A5$sM)?EDVy9-4QDQow?jq`j)8!*7G4e4(o)+K_dVu6?4|X)Nl%{ zz;s&$N6vPjpvz$&4OF6yt>O$=vWzCtMIXe667E?b2EB`kJf@nOdEh8X%rHtQX>lSg z9iv)1Y>9-I&&35wxA55BnDE1HIJc^W3y?rwwUDBiyF%=VXjd;n{i8~hQELOX@<6Pr z8~W%GM-e;^apDaW`&{od`v{r?w^M_(H9qQwY$e@vtk@5x;sVD2m+Jx|o49kdD&K5w zZ~_hF6eO8>^cpxz*C?nO+aHJ(z8Kx1Fa$(`Z&SpDfk3*#i|i>tlvHrkK(wShLnVkg zCWZLGB9Gm2<{mf9`vGrAs{%J41|t{YKs?c#5|IQ1;jI|Um?USfzl#qzGe|5xlF)|m zaG{EHY({(=vP0ot+;JFrPG8(%M2wK|poU}D2Dp&cy4!&ZfDxXJsCU~Ti!o)gN(!f%J+fDHqYTe$0;+GsHk*9(D_^Q{HpJ3x%Dv*u&homP#< z2s#77Po|JEaNuNZ>;Vz!rRL~89I-1=;8^>E_9}@v$KES9+?dBZF7A_phT=J6F@{|) z)dh>dkX!7y^9aUR^*p4vEwaS8(*{^9E2aWo8281*uNgABhq36#XKg_E2P7>uR=ZkW z4UV9+Ju)=HTbyP5aI3;U;S9JMZvovt^hs~luDEtRbP-}xP*4xX5^M+Zna4TjhtvWV zM^f<+oZ|*?0Cz@esS>A^iNzG;A|rC`l~X z;z{8b$AI34q-$U#I1e4voLGwV@Y(?Km&YlQcwj;?HHWBE%xje#vBO-_NI@p0 z0oj(C&OhMrqzsC6P}r9zwjo69DW=*N+f#n1$5b~qB74;e z>kEh{z9_~YLYqi@i**3&%&;C@0S@Ph07Jm*@K=m2^0&WMqVRCwa>K@B{5@TH?D=** zpfO>qrZ&68GxGwA{9?hhkyk*RB!%}9i*YdBTrVURxsF3t2-@vz14$In6*OF=ZKNwW zZ557`K)$1xgm~dRZFypdoLvz&pvMm07FZqP2^x0+_7Mj{kG;79$v`L$6uu+g4#7K6 z5P#`mh&;e0IfsK+Asn4ut~Wf3=V4p`DyO0ivGTa}kiqRp$Ju(qY5HStBVq@{+nEQ0 zThI2eE3gSX>`W3P5VdUQQEVVjP=K?AV9AH#0Q2Lm(?HbW1|v8?kOBliz&BisZD>0X z6GB^0aBqMuG7#XA$h85yDj}R54n9ejW8OUM2KbM^1qv){-ASQEN|I{>b_x#8HlPb) z3T@%2!QOyMDUN}>1*oIzp+Fb731KIDucBCT1VT(8LMRCjbXo~<6bz5ptj0r0WsXg5y&b4H1?Mk<_CPRn4^9P#A3Syc{XhPL9v|9p zg60L{zCCMT4n^_LE7xv6{lh;qXai#7S2qm^Hzk$d}ElwT1ZAmA`;m*mR_UYM=1pcHa*-bG^Y)lD;{@hEv5i z*z}lEYMx%Hd%b+-pXGPeL#-RB)Zr&|?k1YromRi$EVa^utM*VfGoCfG;zp+~_Qs1Y ziS#+50Rua)n>8=4YS0cDuhH)1pgOKlIXz!BG5<|lL}cz2b=KE>VP2_il#n?Gv3hHFwlK>(Ho)sJdo&SDlYF3lyTypcTa%Z^aMU za|Y~LUw5Z=b`Lj(JZl6O1hO@4rNJp@WiwZ0@*E(=hvnUUs!KA%CH7c1=QzSKcXKbv zP_s-~o%Xbh^Lm;Le`54%h8u;9e-Henq;L_PNJ_(4?!`Z$0-AD2;&Ttq6;Tspb zmeGZS%Vg?h)gu=Uutd5Pnq`edn>_g~v6Ig=@Ds;wc57DKcvDJ450gf-PSf?af((5(xBwARtG%6&Kd)5^Su&8xD&ki$ zZsBb<>fJW!)mu%YX??9}OR>cw_V)HxCgTEIN;3$Pr2fZw{aImD`GQ~e8^kWAP|mYf zPOfCQ`ciPp1tyT(CNd@_)5_|DE-4$3a?{;%NeXO6`$%Sf1il1oL6K6OK-48fq>SA} zmM&d7;SLgFGRx3lm*gKaSllfn>ECxxSG&eZsZoC$HKCB8{J@p&Xql*AfS@>DZjLjGDF=bkzId!L*hg)S%ZP zMr-4-Ao9?D!|$VWSYk*x3e$*);A$8E2# z$AMIFC5=SO38brZW25K-gnHIfxqS^kU{z?kL|Q98c+inPpw8z~VT5v<&-uyU^W|aO z$uRc2j_STHAx(NuXgO)sYH>B{?Uu5QX^JHb?M)_;m$0;Yw5!*ULo-J26;^*9lsZ%y zd2|0KqL{v!H!O0grO2K(991Lfs9pZ%S|7bx2w!u@h7J1QrMl{oHXR;_G;t!P`O1; z2dz9XS$vF@*PR^9dD_a&vfl&?>z~MzeRlujcC#R1^d76_Dt%}@(>R6mY&~~pyav>C z1?^AvOqp{x0P1`Yp1Yy@UD$2gs$^Fy`|oPC|0)a4%cKr_Bfsotzk6wO(tLaah)U1` z>7cg@+d+3{>cIgvM=<@bGIc>F)t0y4PrB1|K%z_(^Qu&JM!oRpjT`mf610a*V=~jq zv~KN^a`Vj6&79B5s0K&sV{ey{cvL~Imc6m3lzQr7W%8XQ`L)e)%7I8>cj~1eU=*Rb zooF+0h^+oOB5~{#==$2#+-YB~gSbVps%)=ta3$mE5q*hhCUIORCmZ}rH>G_(mD~w8 z^HV3I4R=IlzvZ8KaKGmd*1NDhK`Lb}*u7*)G&|0#q~s4PSREM1`X!o?D>9de{;o=9 z4Bcc3qg&|{C8FtFFW6mkO7QUCT$FqIO;-&q<>pTaQ$-#_d9r6$^~FN{g1EGqeK`F; z^3gZy45M|Q+0!PYYT8Rg^Sb_iWT-Dqr_Q123Tqzjs(ZsFMZdyeQCR|!etl>E7kPPm zj#g~!j!0PsOGB0}BWRj@O?Z*XiPp$yAa8goIs5GYj471IHGw7j-KO|_qQup+V z22`I7(rbDmw7iD8_b5C#3SWd}4oVIn%v=wf_|lh#+iW-^G&m9-FJwGGDMizkwM&g zR1^Dr_^R<4FLt@kNWz|Bc>XrN!amru*VG_2*&^lW_V|`<#AyePwwsgvf?LEb&x#Bb z;@^2YDmuC_oO82#_(2(MAGs#+JDFdx^s!N(KPC5 z^<%&mn5mV<@jLnX=7;$svc|x7=aSpbXqAtRi_0h8F29ShxTce-TiB;+YTt)(Fmt9_ zD4!>rT=UxHpMSL#zmd1}nFXi(s}q&_>3H;0L5VBs2Hv+ruPGBtY;|Bd zxXv;v5-4|>MzcB?iX)8A0o7AAbau18vY=^H^izrYdM53BbBi51k;KLGUP&Ep<;+Ds z)tq8Zt)x!9MwgRO6CH@1h6j;m2t>wSk(_Rs&>`UYr7m7O#u!%T8#iNTTEtI%!XLr( zA1>?Fz64_ct8E*hi-^{%-urlDx>r~qO)$#)=yQQ(nHg|CgI~V)u=h^WaVcw4B~|ZT zLwaxG9MSk1^02)-?LHqNWp{JG4H}Aa;ris{&9e-uET6x?bg1(!cApGgQ$G26d3~X9 z(pDh6bSb)zb#KkHgSVTw_TGX$5YN5~fiy%HRQu@Dde!8`a>GSxrB*4`PBGD`zU0xX zrUOMNBXCE=^y4rZcDH;Gb0XSzTUI<9AJ_Cv>vV?7O>kPidj-QkU*2W ze3T>|3*t<97< zhHvJ6u0sd%hvyg?W$1CtpaJ&G&-Me-Kd6OOTNkxBL^<q3WUsf5Mp*&hbG^DE(3-VLnh&NBGB%LnFluFvrUtb8z zECJ4q7p*+LkYRYA{dHp_zHZvDF3bMzZl46&&JBg!a@oujQ!%GnDYygKY&(b?c>^4| zKNi;>l8Bne$Je$Xv2k&lc<+H*y>VdTwd>heyHodA^!cb~LNg}08k46s{>GgB<8S3}hTaIcsmRl~CHv$dp!8@Jb zm^`qO`SeKj$&dKumLH_blDcs}{XfZO>R+!cBwO6b^AHDKZ~UFFJh<(A#o6At5m5ss zC0+eV1_sv+xJCOoe*%#%R)wWT6Mzr zbhaBk?QS9K={`ZzKB2}(F!QBvf?7UDum_1851WHzoLMJ5lt}ZotmzU2=lF=9J$p7` zN`)|RUHpVyJR&BV>WLujhI7=Q-clNuz!U1Tg#%wPo_tj>U=0(e!nvocQL6FCs`H5Z z0xIo%m2AV=KGn0HiXAqRuCDn~DHZ@|?O;FzMl<1HG-7*&rz`q#rHr##cL$43gySl7 zx*QH54KJ8%YW1E&)U^x21DibE3SO!?IwI-Oyc%N)7rDA}Xv0K@Xd<9SbEU>k*LTWS z=k<9y7|@b1>eF?YHbd|eE?iv@#GRtm`9`B?gt={Q$ao>G&}e=_L~+upsgHKlx29=g zJ`wo_52{U5O!H!wNgdbNUGreH4(Ch1qyWwSqQ?@`7K=T(v0R%R-{hqI#g}%jSD4_Z zQil+C9LxK*ahqj^jOL=d8zR$LoPQuBNI64Z9pb%9cgAl!L4Yh@kUwCkR=0Q+OO?E8 zmU=mQ-WpD3ggnk*=^KfAUpte;Q6?#L5L{KufIU469)j;t9jrN*|95cMAvkoTvut#C zMz7rw^O_(mau5i;;%m+sQs)=tc#t(qn?&t5yBQtkmW1KzV(v06adbMS@2&^8zbsKi zL+{b#QgkvO(HlkPV*;Vjbc1*}M(NqRd2=7Tbqt}7feEQLgFC%hXRM$ZwjpsTE%yxP zrReT)-DT!Pai1!OrV3zjF!PuClajLp&NXekOby(kOxvV0YH8*b784!KdQtg1U<`<{ zdn!WE>9`$37E64!3^O;HR_??dgbGK0q19}bX-uZ3d<)XsyIG=0#2gI!nRJ!mQ_GN} zt!Du)9m$1n#hvO097YwK7{++mp0N4fxBt0HMGevd>22n3Yn=>VibiG z15W%oSZJow0AUZIAt>!$klF;Ig07co*2o(0!NcAzgP1mDaWIqU%hmY)Ir9JzDLV{a z?(BfPF7>gMZ^ZE3`bxtOWTQV}=EqD;TUf|>LnyY+%wvYU_9DUJba&Xl8@WX4)q2fo0dz!RMK=b_j+oqK1)i1b^;RmNe0T~f(yJI<4C|xH#YN;KsRuV!cx6!`D5+riS z#@WL#*5u!c5cvvyt;`rKC}Gue9%pj0qCCZl$?xygbf?sM=!^oDfe-@1SyDzFdF@{h zSYPkruT}4YBuMIy_GzvjYa<4#A0CvyTmpP5vAQ}kGj=A5zRkQgK##k0L2&Z5u8I)qW_0svq=XY*@|5xs)28B zS-rB!P^zMk5C3k0BBi;>S_+@QGlVnk~_;brPl+gpwRoGEz` z^Z%m&+=5mSC{kdZFmrwY7-DPz20^r3jm49)(PdD?nXBF{mo)@BK)e$T^2W!?z9y8=xMNSn%e^TM!eP z%S;058~7|E@?J_|lrokTU=|A|Tqq znv9@UUh=ZRDbNT6NQDF%SdE0@m#t6c_P;Y^N9@a(LbQEicObvm&l&X1%Lsb>Jb)QX z5PYb0+Z|0W?m#LA%KESCv-2UCB%E za^@ijHKjtFx)f$qwnlgl%tY0fmDVoi3Hvs6ytprPSC%q!fcBG-c{`mpz!*Q;?sN$7 z2Uai&St~ln<^a5*g<8NSc{5JR=W0!h5%1HGIKeP^P!aUSZ|7CX<1UzRIt{kj7dZ~S z3<`_@CBs8&D>_B@qg&@$X9(pXIN*abP|k;{wlfB!I#!JCwXt>ZChXuDbPJ%ExrfSy z`N-}VI)jI3Lz60oF;c!zy)=`f7_P5N50}{c1HMliA0wc7GhBhm@HR*;(AWV-a<i`bu~3er@3(d;kZv zSAGTq76FtM15f$D7Jac^Jis(wgZTeAzzd;fi{K&rt;ps=hg^-dr|xJd0*FmujL&|# zHne0B)pRy{D9l|h19W_q5X9}_BUgI$(fYJyyNCW*G?}si966xYZMKaR!>Ugs*A28b zo?g0=@rJcq53p4jnJ~B-ECG-JKo!8DAz*KVKz@@6X*A_v((cj?{trLcYBD-Rg(6UK z+V19iR??^{d06{V*M{7PDN%ms@0beRKL_#ciijGI!GJ4Jz>Za%0iXeu z3k6EamI_ce4zB25 zZ9;*GFf=N5usFLW(OH1=0+??Xk7WVCPmeQ5`c7U|0}qD)=pGnQ+5reUHYnfZcZ=<> z6@|y(csTGo%zX-=h|g9 zc1@?sE$3q+lIHRZPlhD)F-in=;Bmhb>4Qt6rdqDah5ERYvCx@r%$CVHz67J|Ug~sx zt}(37;>WcSXY|YOzgBU!BX-=V^*u3>n}XmF17#To!eJeh!bDZP;WdlCP_%$THnywUGy0$1XKfibd@C9X zmkr)yeM>ZMy>9r9wGauV$`AGz@Qsr?g?uP}B$H@di_~-_rM{jM;9JGq>uz89s8yKO zRjK{E%DgfZ?r=6~3pbP0s{_MyjNz1P+yH1|1;jCTHE=%gI|^Nv>jx309lO1S1dulck#`>;HwZp?PXw%ygTKxM$ZHq4om0@3ULU46pLfrBSV2I zc=^f@%`#RoJ=Nt`$|s{zH01dD6GNjlOM`01JG8GeErX^9DO>^xai%_7#^4)+Cyz5T z4{&Fa;LY@L!;mOu(}qKzKJ_eP$fIh0X%+c3TD-Z2IkL`b5y$_?1Kf#wqoU=yJ44;* zD?F$CJ72||L)5F~b8aj1QNfdetg*~K@r}$!O~*KmtreS6&h>Jhs$tj98FLt6<7cPSkq~JxwSA{x-s!0;1Oh(Z?>Rs=BGBTs4}13{J~m2 z^~d6Xm7DK`()a0lKDCo^KHjiC;6L+B5cg;8MafZBr|X$DYMcJS{hDqU!(VGK8hm45K1bBEnG3lbs%o zuR2FF57CBq(pgKVml ztOI>TsNTqwK-CYSL0!I62BH0ga4;Z5w@VMh2XMyAxTUkcpqVi}!pt7MCQ7$YZn-CH z!CshX3k&`{lcxFU@G4k3w{uw~ErXlKPcG&UU1m-%)C*MlqlCv7k6jx&!n7ZFrfBJxB=db4N;*OQXdKh;EDG%nF(54{2cL6-AI|G%3n@GT*fN<$~|X!V74Dn5?&NM&#)ObO!eZj&i)8al{& zf@Sia=!~(pK0lSW5T+->G&Av&-yL3c?~PB4$E5?^+z}5P>s}@6 zSln&HmVbRDZ>J~QU`g+K?swg3fK0)%8LS?D+T#8vm-^3q`|V1!Fm)OkG#6}Ehc*qS4sl0DwT?ycm;2u#DipFs5=pg!gwQagfD z*V9u?L#%geT8`DO_2?1~0Zys8<+C6FDGVvO3SX#ELaB-|wVqJ|9xxzGuHUTf^r~w= zFmX&|(n4v-Oo4KCj&?2-r%E>@d?A|2q1g|>be$m5uR!HtF;%V39}?ZYQrB`!{aw5| z(0Oz{O_L$(&<2!rvb0bqwP?r|=Mv?_Kg-qoYn~janb(-ynQt1OygwS+MH14JOr7WcQ>eb^6 z6ogkbGaavBhP?^RG~4FJGQQR&i(KSMP~NScdO{x&!LKExgK%s;0o^%zvadz^j5@jf zI=ipZ;;c)Vp`-IV67NiIE&&T#Z@$KEZvDjcZsmz5-zS|=J68Un{n4vheTsP&+z9EI zXr>H_r41)rj({&++?nVqZKhT}`LE8)zK`t0&xYp%{;+B*0CZ)0!NFo){c`Kkq29VD z#|)R5`o#pz^-MtOgj_T}O4Vju@9eCkI`H0`TijlmcCTAAD$?Xov6}x%hUPN+2A}m> zT+NfCoM)M$eCEs<;ouGOv|C?lM}|Q<`H^t?wZnl>MMm${j2x(&L)Q9}8#B}NVVY~I zX+q!0dwXgOC8CY!Laf6fv+CJT@tP&2$&Wj+mPVT<{6i9smxri@@i#u0*dDL9OML`2 z1=Uo)s^iJNGWJjqXNASr@GrU%s@)Ymx%M-YbmLzL=Eq^$ox;i2QYTwem!jb{lO1)` zc(hB^lBRpq;RHAWsz7R#&NOKQ^{3j{Rc>yE&Zm z{vt0g>W?(jBN>1cf^igbTLw|{4#T{jnaNN6o`3nv2J_ivwEB5L{Y{B?o+u7LAp$q> z@WxQzN$ncbgiERmcczsqPP_wHgr5G5E$u*978mxiAPgG$dCJn`l>7XREh)7rDj4?uD|=HtrZra9`c2>4{PiJ0fXtF_KW<>y+r|Q!Xxy?vILR2OxBqsWJh0f)ulAusq=n!uF~>!=f9@J z*;1t4a{?lB0aon;Kd$0X(g8qfb&W)k7lUlLJ-V8N zT<@tGer=*v$imsJKh zU1h!%GZ9)Tn|8)3{KMbh1Kgi)K1#VcwR2T4PF$M>mrjTt5AAc?|Zxh+}B9?ekX z?3SHafpK12N}beR1JJ7CUV-`~F!eTl?4OQv8RP_Kbsn!YZ zuqQHzce$7IEqeMO_Rfi+J)|3qtNLs4=ql@uKnF=P(|&2yKJ?)3obNLK;jh2S8_sA` zm6aSkJ>}^>mVO1H*`D(_3oNX%cnL0S?U~T z%jwN7snVp0P@>_6uXpS^CpW+)H}hb~-IQk4wEG+|Yyy*)Ur$F7a+k(sVy#c_69Bcba_ zKsQ!Yt)Tu0aZ7w!h#~XR3PNsPdx)$)mkXnc>fFRq;r=W=&3?VACaXvIj5|B?mhW@R-Yv4TE z3|y00cC|FF%ulThQrr$t*(ng-BO70R+EUu-wO0%NbRx1gWc*1@q|IrB0$CX7w!~~g zDPd;1ys?~E#)p0EJIRo1U`^L*)Y_3O-;LF$k3T(O5nACGPn(6HdzBhLz~ zK;*PuaEOUh2|{OaaFVPUE6*bU%Olq}J1Mc6UDvK!Ne2KA0FFDan~@X@^>{g^@2bs< zD>V;2QXKoI9?3F&Lk5X@05I-rNf@BrgVKE`R_a6F#8Bbw7>d{C#86()C)&;+^*);U zo(X==0NQ~=Bc|U|%^Y2NN4?*o2;&x)FrTbk06qLjn;rD&2%s~bcK@DW`lJ;`$Wftn zR4_@3;cR!Pwp$jUg@BoR1kjZ!>W@$y{Vxp0Ia=F4s^O?~bx=p558k5 z64w}nL1O0hk6-Hl%3~NHA(vT_XhvyIYL`0^-B9;rwC?=3-)@VmGi-U&)`MKC9AT=z z`u^ZZBv%N~B=Q#}@f_C$7*W`U@YEw8hi`l+ivcSjYKdk%TdAMOx7Y%kRw;fR)!%2% zum!@xvc1}WgwZ~K^e6|g>Sn=&o(s5wr$?#=TT1j@@y1mQ!+iz>4DZdwWTEMXyRZHk z-E3=F&yZhc0M4|BKQszjj&(VleTq?l0g*-?0)*@?fGb=k-N;k^pvdc%GSU0Y7QfoN zSG4i(zCE~tHeI;kQ@O3s{Jl^tdVry?&y)45*Oty1FD5+IqT6ZvrI8H zLx4_$LhmDR56|b%fFj82pIl@3uH5*x6`*PyPYYvA-Dehx&|Z<Hz;+ zz+>urgmMV0%JdCn;}S-JS6odt+3=b4n=c!}QYF3`z;oWljtd#E#)(POA(*R>zpsq0 zRSQjvL7Q7sxTo$f^z!oRV-0lzI?l8lxw?K__XR*(F@?8u=`(F8hI&LqmMfyRc|mDbl-8^FBjHzF`St1K!5gF17%$b-70huM4wCmM0($;?t`XZ=;l|@_nVwZf{}&&x-osG(6Fbp9ex9W zhr^;l_#rV%1>g@+N0nt<)p87?iHS@e2oQZ#QdO)E{(Ll(0HZ#GFSwp=uc+n9V)eu|0av5iTbq<+P+{$iqD5Fe9x%$*nY zL4tWI2*W#ncy;Z2$Lx!^?TQDhkjCrsnbG)xXKA`0_8@@ye~N%65GfZjpWMeXxpVP~bdD;ClhK|0CcNSGz{3O&{m5Sv&hjK<{H{Z8&=i0ZKWMpPpS7 z&E*m)h|y?#wzPM!uczX_|GKLs0U2=2M^hN;ub6q6!KMX(ff4BSTtp%Q z0d}g$nM2miEti1z#R7Z{rqS*C^?PLJJFgx`2!?;OMwZBomt?~jGKtl#OQm5pwH*Xq zs6~4)-iO#v`*Z5gzTCO!arhaRiMvG2-dI6Mmpk`KQnh-7SsE8IG{fh-0V3?~8ky^g zUSeogGCl*zsvIh$jqHOInB`cV@lIW#i)Fw?4rrSay)gDh5q<)W`SO4N)eis*=*3K$ zX@QA5;Q=h%rwZbl`QQ<<*Aphc&O|_rjL1XaD`<9j-QM0PIhy#_D=W!gROlYrixrAR zAg=p1Zg?0ISW>ZnjiH-mqM4S_)Zfw^#Jx3;&#;T1JZc(`M>pvnrMMv0RD}j2vhndb zm=pKON4n)MC&9Vbg5eGCOHu`!4p4_4lxerg(1Vx*5B(Z!ZKXo8zI!5^t1heQIZ(GI zrYmCCmklMgBfT0v*W%lk)_n5Rs@wEV#UmtU=`g1IsF=DkWiHiN- zywd+{jqC!XJ!pgDcgBa`Qs;v6?UIHjC8!(bSk_>W8JVS@pf!{#k4m&)fHWUsRsw^D z9lE(6|IWuF9MP1QsNq;$*C=$jXzgO-j<36!Et|<>4l<)}?oSX&4hN3nHn5+*aZr|c zUDRs6(6RD)@gcYub7AA|jQy1WxM8ntrdlXh#y#A>mhL9>VN z-qPVRjwzGlZDEeRSKUlJ$-t2R34c4DwT=qe9nfiHg}f?zGBW4}rT zrC2Qgh2~*Zp`E5(U1;Y%PZ$2l174-<>ac`qvcN&y?!kRE9#zWTI*^}=Zj)V@G6ct^ zj`2r0F6=Aah+`}g$I9a?s?CqOO|Q~tEC4SBFsZf=JkhQ<7nG*n$oyoGD6k7XC<$F& z=IH7&IVm(P6XmyJq)b`+0QKDzty zO#np1$uThVhLq_L$a43mXTVv#g4xz9_rHJ$I~&8P%AfTZSsj&Z`}xy4edTS4sh-RT z0K%XX0^`~a zVEJmm+!CZ%=7aF+@`tkCq4ybM>v2WoMJEqJZ^q2AgkEsUvCt{!X(h)TuCM$R0yEC$ z&8;6?yFmT%jl2zeWK!1GVKhavHTt(EsUY;UM^fcwY4bq@jF)Ov41wnFz8Zf}VYAve zl6|r%v0_iSAG!o`jTp;oe)(v;5>kq@6-x}%jyXq|$2rV_K9rhiz?9OHe;zVqii}zPvysxBR~PcI<`4j3!IuMfs(JBb`MJ;NIHTqwsL;#o!Cs& zHzQDzVdndfR!{!2`9q;Bdg$|erODriArmn|^Vrfyi3DCx_Hl+GC*;dQvgMt#w5N+c z6|%gf%NZx#q{pjHif81I{%U3Yc%Wv9hh^|1q?C0d6S-cavXO3!nEr)moVQo>%ZjRz zJZ5pHm-GZn`62vD7S8T8jsmqfC}b|QgCqjtik06n;}ffZ3>E}c6W&C}EKsM7GV>Ty zdjXod`Vw^q`&8Nas68gg>ENJ@CrKYACBR=bM7LDglV?2R5M&vqeA4 zKJ%E&ybPe;HRGdoYq`*J+PS8(uR(=cR9f024CI)odz7SIt1_)kH3y~Q$-#vVcYW|3hNvmX6-DHPmvuNrVbfIXm{66(DBptByEz-7I zF#V;k8cStc+ZBTPP6oInPi!-c$@B{ekD;9fxRDcRj%{g8YqI}DBpHUhj~m(WGr6N2 zYb|Kp6QqT-;FkcP2Mzq~gR>=Z99K;5cLP2ZGT}U5+7q>ju5*G`BWMn?_~=r=$T9)F z&Adc6Hs#AXWV1gRIyp`6)Qx!6T~tp0$0tQUuOLo&5+(;SPXK(}8v2ew+QxqWT0ZOx zx@)HVp?k@)U=(`s0E{0C-DchR=Nb&lv6#+4qoL6BlS)fanQ^av2C)}WY3QkCrP~J& zM`0-fEFDwxkGQm7_pz$OSw}3B`KYz2?{6+?w*0kY&%>aF(4kTDN{qC>6+_!|$WuR) zTOq^9`sGkYSn$&u2vrTGu}aQRc$ExuIS6XVL0~)r-FKK_M)%V>@*#E;twY8gqEGCX&9qZz()x4vkqsw4xwygXpKmj8mWc)+gegY+ z*2?OEf$0>3R%)0JsScLJC|cP{_|!c=RT?x%r;Q!Z=bu!@o9DQs7`sPCLdI&V4S9^> zxWukveu{I?61w?@3>rgv%MI^>LFrG=J4RQ(2L)r(U~AeMUCLKvbO+<j zC?NhH*4{m?$t!Ccz5~cXq#%rfMI|U|TMr;iQEMcqwL;Z09cPBB$U#&(6-7!dK_Cf& zRf-B~6)6^G>O12z(@|7LfrA5Ea^52_a$#Z^VG*A?q0wf0O>mLF7)n7nFxWD_A3 zQP^0ey5Jj4o^`JWo;!P{h>%&tIqIQ$=md8iZ0TRyJx6dx^>wT48 zK+G5jzM*ok{nq+ZA6-?MVMZwSZH;(*RQ&i_rP)rluy~{{YQ>9tLHJKJ7=YggDlcHk zMWtK0_E7uzl;_0ySaH*ctbd=jBFgalkA4f>oX)i=EJiym6%~DJBh3dpKN{R!S5X>= zD&%45p+m<-dCiWSwW4vYh8F+)Hc{g?g~^j#s`@BnU9rX&?-}x6N$#6Wr3~4>qFLf7 z)~#(7?W3jc{=v?BbBF$3G7?B78SrEcDIJdw#P##w&K-o2gO51)g4;f z3Y6|hVRNYHlI5343X`KJ+fr4~p5ERT*RLYZPv!8k=bn3xMLgL@PoHCyvtgM!T&n4n?2w@{pwY`@QV(2>sFWP` z8tPQvgX4K2vZU?uKO3!$-$WTE$p+nJw$+`TQ=^Rcqim{=7WGT0M8g_3VTH6SRdF<+ zB?4Z=R`tK;aqO1vYEcnjcF+mAZ}E-nqqD{nI?5SFcUEsbXgmoQ55)!nLt)doFK?cA z>!~1?FHqg7H7L_FZ0L}VZ0lbmwNf^M*F(f(nX~m(IUobWiR7LwK|kDXD}Vee?pmjJ z*vQ{hJ!LAjqm0h3l|i}t6n7tJ4@3=5mul9CkK3(}NZmtRkKW-H$&16E ztcg#0PnlBGKSiu8me!Z;ROqVI?(0r2s!6EQ^=fL>`W0Mc?_%-6l?r8HY4U^HhU(>= z$90QzjZI-w?k;NmhwqgBPiA$xKubl)fo;OB%g$d}Tn?U{A#MNE=H>L2$TIejhcWVC&tT+f&E+TPm`uY55b96Bkvna6=4E*MO--s?4 z!lL4as@7HKWapG*=RZ3usXaCIT7$Ivw8n^q+EAl52AbW{e+W;vrh2b>^4oUa-n9V3 zQ`Z7+y7cuIDRuy;cSYCHY^3D&h4U3RRL15gjRA3kW=zz;H-VHs(Z8gp_dA8x7SbDm;CUvDw~{J4duEp zuLi`!Ky-wWzguvnW;Q&UX}#v27opCjQEw>{!*!7rIe&GkLf@mh|M$;|+BI&cCAS+& z1b)KEg#f6>K&LxEIJMd~Tb0&y;C4=L#gDT5yR+UnpQ;gs8&#<64mwG=pBUW!9vRYs ze^0pQYwiwX1QAm}rKYfI23yky0o(Pa_spUY#x(uPcgP@8j)#??fBm-r)IKO+&fUaK zC8mFk))nH8Ai%E8b~}x|8`pgM*#z^{hOm>}$g3ca1W=6o9m1aT&9ZscR-iMjh%H)E z`F5@K?U8-52H9jVlYx|3cOQxx34rWc?lhhXCN#~k1*g>*cf`q+>T+)gaSxJ!HRp3+ zR#`C$w(QVp@xvFHmm|BdSY4TU+tGbUIe=30-~RL4Au8MfpxPk&=e5lAn-NKKsa6}P zN_W20bXrf?FQ29uR^0V9{S-FjOw{eGzk$Rcq@k>uWL6py;TtNx0WKU!lOz-*1PvB! z%2%uzD+xr93b>U-56ggBDJQJ8h6ek_!D*o6S$8kGF6W?RKW7;yUn)9^)i0OXy4`Q4 zK|RCUTu{}$Dk#^y8j-&2W|aAU@hAt_JduQM?O!raN3qRJpmHSLs)dBy7R)x%MT-P! zyK)={wHSyv0h$6^3`;BctDrqnBLh;H$+_y`&E-LOb}R&Nj+QssCho0RX65O_otwYR zDn1Wf@%c}anN8YO$bbqtP@T6IW$K-`1RFN&H%o}Z1X0r(8xJ5pGXV2|6T?BMXdDDz zT{uz*A+8>{okUoRW?WR%qL6M`EZ8r^9ORH7#~u09Pv{vMv2`A947G2Vjb02^L<@uT zZgd7DmxO1Bqe4KDd_vGRi;fVNOo|giL5R>s-YlE1`4qF`*Y)-gD@AO9gOk7}+#iUt zG8C#Uix|R*dvRa-W;**MSkA>{5my-u-MB032R_>NlKTRv^!Dt_&Zud7xai^P-Avb9vpnV#po(z69+(Dnx+7a0=Q#oFq~kV*%fg548kf zAwk*t9W)z2n-8Ond_0M;LfP3KrPzqnFv1d-IGX;v_#P;Qc`i#wJ;L!tKv9!|$#?8&{J9VOJ%J8k!WVE2HrmDyv}P(p^@rGPK)+7ELOTco&Rm zP~_=()DF&(ynOclhO$1M_N{7sx?aTOxZgbse0o`VaciZ!+*wqfT{g80JXpy4?l-@M zd_z4nYuT?MbD!7e)2L5G4=Y01ir|c=UE2Dr4P>v8?P0an?GZDdbqKW$_ly#u zLG#Dkr(O@_PN1$zzP+gKSp_-HAy8vIS!*Oy8JV~Cbjh8)P$l#+U1W>Kgl>QDNDogJ z<>p`*Xhz64hrpgIj%{~|o_CbPgWODIHnlSMSU(h0u!0(;k7>a<+D9{Ao&(ZsHdR$% z5#jlArdj8jw^)sX(mns1mHIqY`1A8UQX#CO@-po*U{Fsn*hhz#tbO4j(sh?`FPW{L z`MjnBru>hJ2N>qD)s;dUXn#L7^eoj`<~R`lN8YR`etK|^p3-iq{WI?WXGLB~;ypi` z`zNnnf?k~ubcrF#%T`Mg_xkz!Cb65ZeU0&%r7jWUAFT?%01Bxn>$Yj?W(MFD8j!^4(wY(+7o9YkzMpYeVm!{V5^rCEL+)hto+Vjp9sF0 z^ILnQXn}Bn2Fy0p;LJxbG8gR(R9^%_$%{m2;ZdDOgM=-R7RP8aTn20A6}+IrMZWI> zioe{ykW2oIk`LKofyN7RdyHp*>Ka?0$X~He1-=IaljcmXO+rzKDbJC?7%sl~%gq|7KyPW&mg6+;OmU@8 z*T2x%Q52>uMb+ZDW{7X&PxE_EyDKzC#gNPmn?ca6h*TE&^e!F^ktAgM!Nl(WsomX!5kEJ=rM~w(U(2{!ONbWg-tz<{B;bQ zF%gE)7b4UUhcQZH+z^Lh#a=wXXm-U&hL2#5WP?yyW1DS$yWSq1Ip(8z7e;zEYtw=G zLk+-uY8CLX(=f^4l|y9=66c0S3~K6}fu=`fK-V1@-<<-N9ZI&S^Fdn;dyYDT(a`6& zvPJ&}1Qp<&ZOW`Q@-qT`HVkdAL3|z5vss>w5TIv<8C7MQ54h+ylpKdJ-@kMLD5HeD zFG>=Ald^Q2Jri~`-^!MK22wl9%&(gshV@poVVdipLd&jDl!qo^;&v_sO2#OkG*T5N zorN=6pFI89;aY3b)cH$a^xP?;OzUr?ArnKVQSbq53 zd4|IDbEvO^K??p~-_v9vn(Y}I@J#)P7qJ>Mg zdp(Z%CMN!q%VY}-{|*@ps_4QIeHU526oscxn+)Z$VKBNcoHD!r<`+g^j9uB79388P zW&7s$M4dgDVQeKCWd64Bm(Cinp?L~jIy!_<9~F9uREgyqlX3QShL!*JA>9{hrGi*v zbkBxWmZ8B}j!tHiwOgSMZ$BSE5bepa%fJ~y!xiie-v zoxJ${J%9XoC>i68N(+z+H2)?+AE>VW;a1#R@ersLz0&>1uylV?FSD(t43dWOK55rl z(iU+Pyr$d_Pj@1W%R02Oxm^*E|Gw|L(8;OyRJ3ilfG%J*BfV@ArvpY(=>V6s5Kv`!Au=>5=mMSA22jEt*t4f|P+n$~p~ zCZq4MikuA4Myl z1fVbGhIh8zk=mrAFpgJg&gw#1E%OAl zh8{d8DgI@j?LgZA#OoX$8KxHM6fqKxBFv#9luq(SPZI0^d$zN-qR%0D&5;G57q|oH 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 literal 0 HcmV?d00001 diff --git a/docs/development.md b/docs/development.md index ac07e12..dc58494 100644 --- a/docs/development.md +++ b/docs/development.md @@ -12,7 +12,9 @@ licenses. Shared build/check/release tools consume it. Each wrapper's Cargo package version is the authoritative release version. The root workspace contains Rivet's core, MIDI, application, and offline preview -crates, plus Patina's core, editor, and renderer. Wrappers under +crates; Patina's core, editor, and renderer; Anvil's and Crucible's core and +editor crates; and the shared `common-ui` (palette, fonts, knobs, panels) and +`common-dsp` (filters, YIN pitch detection, WAV files) crates. Wrappers under `plugins//plugin/` remain independent workspaces because they depend on a pinned NIH-plug Git revision. Their host identities and persistence stay separate. @@ -23,6 +25,8 @@ cargo test --workspace --locked cargo clippy --workspace --all-targets --locked -- -D warnings python scripts/check-plugin.py --plugin rivet python scripts/check-plugin.py --plugin patina +python scripts/check-plugin.py --plugin anvil +python scripts/check-plugin.py --plugin crucible python -m unittest discover -s scripts/tests -v ``` @@ -43,12 +47,14 @@ install the Rust `x86_64-pc-windows-gnu` target and MinGW's ```sh python scripts/build-plugin.py --plugin rivet --target x86_64-pc-windows-gnu python scripts/build-plugin.py --plugin patina --target x86_64-pc-windows-gnu +python scripts/build-plugin.py --plugin anvil --target x86_64-pc-windows-gnu +python scripts/build-plugin.py --plugin crucible --target x86_64-pc-windows-gnu python scripts/package-windows.py python scripts/package-windows.py --plugin rivet python scripts/package-windows.py --plugin patina ``` -Build outputs use `dist//Rivet.clap` and `Patina.clap`, with docs and +Build outputs use `dist//.clap`, with docs and licenses retaining their directory structure. `CARGO_TARGET_DIR` is supported for sharing compilation between wrappers. MSVC packaging accepts `--target x86_64-pc-windows-msvc`. @@ -69,6 +75,8 @@ cargo run --manifest-path tools/editor-check/Cargo.toml -- /tmp/rivet.png --plug cargo run --manifest-path tools/editor-check/Cargo.toml -- /tmp/rivet-creative.png --plugin rivet --creative cargo run --manifest-path tools/editor-check/Cargo.toml -- /tmp/patina.png --plugin patina cargo run --manifest-path tools/editor-check/Cargo.toml -- /tmp/patina-compact.png --plugin patina --compact +cargo run --manifest-path tools/editor-check/Cargo.toml -- /tmp/anvil.png --plugin anvil +cargo run --manifest-path tools/editor-check/Cargo.toml -- /tmp/crucible.png --plugin crucible ``` Patina also accepts `--silent`, `--delta`, and `--bypass` as render fixtures. @@ -82,6 +90,8 @@ artistic quality. Use the product guides for FL Studio acceptance checks. | --- | --- | --- | | Rivet | Motif Storyboard | `io.storyboard.motif` | | Patina | Resonance Compressor | `io.storyboard.resonance-compressor` | +| Anvil | (new) | `io.storyboard.sub-anchor` | +| Crucible | (new) | `io.storyboard.resampler` | Rivet retains `story-session-v1` and its session migration support; Patina retains all parameter IDs. No JSON/state migration is introduced by the rename. These diff --git a/plugins/anvil/core/Cargo.toml b/plugins/anvil/core/Cargo.toml new file mode 100644 index 0000000..19aa150 --- /dev/null +++ b/plugins/anvil/core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "anvil-core" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +common-dsp = { path = "../../common/dsp" } +serde = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/plugins/anvil/core/src/lib.rs b/plugins/anvil/core/src/lib.rs new file mode 100644 index 0000000..cf9b204 --- /dev/null +++ b/plugins/anvil/core/src/lib.rs @@ -0,0 +1,596 @@ +//! Sub anchor: replaces the band below the split with a clean mono sine that follows the +//! bass pitch, holds one level for every note and restarts at the same phase on each note. +//! +//! The audio path is delayed by a fixed lookahead. Detection runs on the undelayed input, so +//! a note's pitch is known before its delayed attack leaves the plugin, and the sine starts +//! exactly on that attack. +mod settings; +use common_dsp::{ + coefficient, db_to_gain, gain_to_db, hz_to_midi, midi_to_hz, Biquad, Crossover, Yin, +}; +pub use settings::{Settings, Source}; +use std::f32::consts::TAU; + +/// Lowest detectable fundamental. Sets the lookahead (two periods). +pub const MIN_HZ: f32 = 25.; +pub const MAX_HZ: f32 = 250.; +pub const SCOPE: usize = 256; +const EVENTS: usize = 64; +const DETECT_RATE: f32 = 3000.; +const HOP_MS: f32 = 5.; +const ATTACK_MS: f32 = 2.; +const RELEASE_MS: f32 = 15.; +const CROSSFADE_MS: f32 = 3.; +const GATE_HOLD_MS: f32 = 20.; +const GATE_HYSTERESIS_DB: f32 = 6.; +/// A pitch move this large (semitones) counts as a new note. +const JUMP: f32 = 0.7; +const CLARITY: f32 = 0.5; + +#[derive(Clone, Copy, Debug, Default)] +pub struct Monitor { + /// Hear only the generated sub. + pub solo: bool, +} + +#[derive(Clone, Copy, Debug)] +pub struct AnalyzerFrame { + /// Current sub frequency in Hz, 0 when silent. + pub hz: f32, + /// MIDI note of the current sub, rounded. + pub note: Option, + pub low_db: f32, + pub sub_db: f32, + /// Original low band and new sub, triggered on a rising zero crossing of the sub. + pub scope_low: [f32; SCOPE], + pub scope_sub: [f32; SCOPE], + /// Duration of the scope window in milliseconds. + pub scope_ms: f32, + pub latency: u32, + pub sample_rate: f32, +} +impl Default for AnalyzerFrame { + fn default() -> Self { + Self { + hz: 0., + note: None, + low_db: -120., + sub_db: -120., + scope_low: [0.; SCOPE], + scope_sub: [0.; SCOPE], + scope_ms: 60., + latency: 0, + sample_rate: 48000., + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum Kind { + Onset, + Release, + NoteOn(u8), + NoteOff(u8), +} +#[derive(Clone, Copy, Debug)] +struct Event { + time: u64, + kind: Kind, +} + +#[derive(Clone, Copy, Debug, Default)] +struct Voice { + phase: f32, + hz: f32, + target: f32, + amp: f32, + releasing: bool, +} + +pub struct Engine { + rate: f32, + channels: usize, + settings: Settings, + latency: usize, + delay: Vec<[f32; 2]>, + delay_at: usize, + crossover: [Crossover; 2], + detect: [Biquad; 2], + decimate: usize, + decimate_count: usize, + detect_rate: f32, + ring: Vec, + ring_at: usize, + scratch: Vec, + yin: Yin, + hop: usize, + hop_count: usize, + gate_env: f32, + gate_release: f32, + gate_open: bool, + gate_hold: usize, + clock: u64, + events: [Event; EVENTS], + event_head: usize, + event_len: usize, + /// Output-time note state. + sounding: bool, + pending: bool, + tracked: f32, + candidate: Option<(f32, u8)>, + voice: Voice, + old: Voice, + old_gain: f32, + glide: f32, + held: [u8; 16], + held_len: usize, + low_env: f32, + note_peak: f32, + env_coef: f32, + smooth: f32, + level: f32, + keep: f32, + output: f32, + bypass: f32, + meter_coef: f32, + low_power: f32, + sub_power: f32, + scope_low: [f32; 2 * SCOPE], + scope_sub: [f32; 2 * SCOPE], + scope_at: usize, + scope_stride: usize, + scope_count: usize, +} + +impl Engine { + pub fn new(rate: f32, channels: usize) -> Result { + if !(8000.0..=768000.).contains(&rate) { + return Err("unsupported sample rate"); + } + if !(1..=2).contains(&channels) { + return Err("unsupported channel count"); + } + let decimate = (rate / DETECT_RATE).round().max(1.) as usize; + let detect_rate = rate / decimate as f32; + let max_lag = (detect_rate / MIN_HZ).ceil() as usize; + // YIN needs one comparison window of at least the longest period after the lags. + let analysis = 2 * max_lag + 3; + let latency = analysis * decimate; + let settings = Settings::default(); + let mut e = Self { + rate, + channels, + settings, + latency, + delay: vec![[0.; 2]; latency + 1], + delay_at: 0, + crossover: [Crossover::new(rate, settings.split); 2], + detect: [Biquad::lowpass(rate, 400., std::f32::consts::FRAC_1_SQRT_2); 2], + decimate, + decimate_count: 0, + detect_rate, + ring: vec![0.; analysis], + ring_at: 0, + scratch: vec![0.; analysis], + yin: Yin::new(max_lag + 2), + hop: ((detect_rate * HOP_MS * 0.001) as usize).max(1), + hop_count: 0, + gate_env: 0., + gate_release: 1. - coefficient(rate, 30.), + gate_open: false, + gate_hold: 0, + clock: 0, + events: [Event { + time: 0, + kind: Kind::Release, + }; EVENTS], + event_head: 0, + event_len: 0, + sounding: false, + pending: false, + tracked: 0., + candidate: None, + voice: Voice::default(), + old: Voice::default(), + old_gain: 0., + glide: 1., + held: [0; 16], + held_len: 0, + low_env: 0., + note_peak: 0., + env_coef: coefficient(rate, 10.), + smooth: coefficient(rate, 10.), + level: db_to_gain(settings.level), + keep: 0., + output: 1., + bypass: 0., + meter_coef: coefficient(rate, 150.), + low_power: 0., + sub_power: 0., + scope_low: [0.; 2 * SCOPE], + scope_sub: [0.; 2 * SCOPE], + scope_at: 0, + scope_stride: ((rate * 0.06) / SCOPE as f32).round().max(1.) as usize, + scope_count: 0, + }; + e.set_settings(settings)?; + Ok(e) + } + pub fn latency_samples(&self) -> u32 { + self.latency as u32 + } + pub fn channels(&self) -> usize { + self.channels + } + pub fn settings(&self) -> Settings { + self.settings + } + pub fn set_settings(&mut self, s: Settings) -> Result<(), &'static str> { + s.validate()?; + let old = self.settings; + if (s.split - old.split).abs() > 1e-3 { + for x in &mut self.crossover { + x.retune(self.rate, s.split); + } + } + if s.source != old.source { + self.release_all(); + } + self.glide = if s.glide <= 0. { + 1. + } else { + coefficient(self.rate, s.glide / 3.) + }; + self.settings = s; + Ok(()) + } + fn release_all(&mut self) { + self.voice.releasing = true; + self.sounding = false; + self.pending = false; + self.held_len = 0; + self.event_len = 0; + self.candidate = None; + } + pub fn reset(&mut self) { + self.delay.fill([0.; 2]); + for x in &mut self.crossover { + x.reset(); + } + for f in &mut self.detect { + f.reset(); + } + self.ring.fill(0.); + self.decimate_count = 0; + self.hop_count = 0; + self.gate_env = 0.; + self.gate_open = false; + self.gate_hold = 0; + self.release_all(); + self.voice = Voice::default(); + self.old = Voice::default(); + self.old_gain = 0.; + self.low_env = 0.; + self.note_peak = 0.; + self.low_power = 0.; + self.sub_power = 0.; + self.scope_low = [0.; 2 * SCOPE]; + self.scope_sub = [0.; 2 * SCOPE]; + } + /// MIDI note start at the current input sample. Used when the source is MIDI. + pub fn note_on(&mut self, note: u8) { + self.push(Kind::NoteOn(note.min(127))); + } + pub fn note_off(&mut self, note: u8) { + self.push(Kind::NoteOff(note.min(127))); + } + fn push(&mut self, kind: Kind) { + if self.event_len == EVENTS { + // Dropping the oldest keeps the newest note state; overflow only happens on floods. + self.event_head = (self.event_head + 1) % EVENTS; + self.event_len -= 1; + } + let at = (self.event_head + self.event_len) % EVENTS; + self.events[at] = Event { + time: self.clock, + kind, + }; + self.event_len += 1; + } + /// Detected pitch of the most recent analysis window, in fractional MIDI notes. + fn estimate(&mut self) -> Option { + let n = self.ring.len(); + for i in 0..n { + self.scratch[i] = self.ring[(self.ring_at + i) % n]; + } + let (hz, clarity) = self + .yin + .estimate(&self.scratch, self.detect_rate, MIN_HZ, MAX_HZ)?; + (clarity >= CLARITY).then(|| hz_to_midi(hz)) + } + fn start(&mut self, pitch: f32, glide: bool) { + let s = self.settings; + let note = if s.snap { pitch.round() } else { pitch }; + let hz = midi_to_hz(note + 12. * s.octave as f32); + self.tracked = pitch; + self.candidate = None; + self.pending = false; + self.sounding = true; + let audible = self.voice.amp > 1e-4 && !self.voice.releasing; + if glide && audible { + self.voice.target = hz; + return; + } + if s.retrigger || !audible { + if self.voice.amp > 1e-4 { + // Restarting the phase mid-cycle would click; the old cycle fades out instead. + self.old = self.voice; + self.old_gain = 1.; + } + self.voice = Voice { + phase: s.phase / 360., + hz, + target: hz, + amp: 0., + releasing: false, + }; + } else { + self.voice.target = hz; + self.voice.hz = hz; + } + self.note_peak = 0.; + } + fn stop(&mut self) { + self.voice.releasing = true; + self.sounding = false; + self.pending = false; + self.candidate = None; + } + fn handle(&mut self, kind: Kind) { + let midi = self.settings.source == Source::Midi; + match kind { + Kind::Onset if !midi => match self.estimate() { + Some(p) => self.start(p, false), + None => { + self.pending = true; + self.sounding = true; + } + }, + Kind::Release if !midi => self.stop(), + Kind::NoteOn(n) if midi => { + let len = self.held_len; + if let Some(i) = self.held[..len].iter().position(|h| *h == n) { + self.held.copy_within(i + 1..len, i); + self.held_len -= 1; + } + if self.held_len == self.held.len() { + self.held.copy_within(1.., 0); + self.held_len -= 1; + } + let legato = self.held_len > 0; + self.held[self.held_len] = n; + self.held_len += 1; + self.start(n as f32, legato && self.settings.glide > 0.); + } + Kind::NoteOff(n) if midi => { + let len = self.held_len; + let Some(i) = self.held[..len].iter().position(|h| *h == n) else { + return; + }; + let top = i + 1 == len; + self.held.copy_within(i + 1..len, i); + self.held_len -= 1; + if self.held_len == 0 { + self.stop(); + } else if top { + let next = self.held[self.held_len - 1]; + self.start(next as f32, self.settings.glide > 0.); + } + } + _ => {} + } + } + /// Regular analysis; its window starts at the current output time. + fn hop_analysis(&mut self) { + if self.settings.source != Source::Audio || !self.sounding { + return; + } + let Some(p) = self.estimate() else { + return; + }; + if self.pending { + self.start(p, false); + return; + } + if (p - self.tracked).abs() >= JUMP { + match self.candidate { + Some((c, count)) if (c - p).abs() < 0.5 => { + if count + 1 >= 2 { + self.start(p, self.settings.glide > 0.); + } else { + self.candidate = Some((p, count + 1)); + } + } + _ => self.candidate = Some((p, 1)), + } + } else { + self.candidate = None; + self.tracked += 0.3 * (p - self.tracked); + if !self.settings.snap { + self.voice.target = midi_to_hz(self.tracked + 12. * self.settings.octave as f32); + } + } + } + fn detect(&mut self, x: [f32; 2]) { + let mono = if self.channels == 2 { + 0.5 * (x[0] + x[1]) + } else { + x[0] + }; + let d = self.detect[0].process(mono); + let d = self.detect[1].process(d); + if self.settings.source == Source::Audio { + let a = d.abs(); + self.gate_env = if a > self.gate_env { + a + } else { + self.gate_env * self.gate_release + }; + let level = gain_to_db(self.gate_env); + if level > self.settings.gate { + self.gate_hold = (self.rate * GATE_HOLD_MS * 0.001) as usize; + if !self.gate_open { + self.gate_open = true; + self.push(Kind::Onset); + } + } else if self.gate_open && level < self.settings.gate - GATE_HYSTERESIS_DB { + if self.gate_hold == 0 { + self.gate_open = false; + self.push(Kind::Release); + } else { + self.gate_hold -= 1; + } + } + } + self.decimate_count += 1; + if self.decimate_count >= self.decimate { + self.decimate_count = 0; + self.ring[self.ring_at] = d; + self.ring_at = (self.ring_at + 1) % self.ring.len(); + self.hop_count += 1; + if self.hop_count >= self.hop { + self.hop_count = 0; + self.hop_analysis(); + } + } + } + #[inline] + fn shape(&self, x: f32) -> f32 { + let drive = self.settings.drive * 0.01; + if drive <= 0. { + return x; + } + let k = 1. + 6. * drive; + (k * x).tanh() / k.tanh() + } + fn oscillator(&mut self) -> f32 { + let attack = 1. / (self.rate * ATTACK_MS * 0.001); + let release = 1. / (self.rate * RELEASE_MS * 0.001); + let v = &mut self.voice; + v.amp = if v.releasing { + (v.amp - release).max(0.) + } else { + (v.amp + attack).min(1.) + }; + v.hz += (v.target - v.hz) * self.glide; + let mut out = 0.; + if v.amp > 0. { + out += (TAU * v.phase).sin() * v.amp; + v.phase = (v.phase + v.hz / self.rate).fract(); + } + if self.old_gain > 0. { + let o = &mut self.old; + out += (TAU * o.phase).sin() * o.amp * self.old_gain; + o.phase = (o.phase + o.hz / self.rate).fract(); + self.old_gain = (self.old_gain - 1. / (self.rate * CROSSFADE_MS * 0.001)).max(0.); + } + self.shape(out) + } + pub fn process_sample(&mut self, x: [f32; 2], monitor: Monitor) -> [f32; 2] { + self.detect(x); + let len = self.delay.len(); + self.delay[self.delay_at] = x; + self.delay_at = (self.delay_at + 1) % len; + // The slot after the newest write is the oldest: exactly `latency` samples ago. + let y = self.delay[self.delay_at]; + while self.event_len > 0 { + let e = self.events[self.event_head]; + if e.time + self.latency as u64 > self.clock { + break; + } + self.event_head = (self.event_head + 1) % EVENTS; + self.event_len -= 1; + self.handle(e.kind); + } + self.clock += 1; + let s = self.settings; + let (l0, h0) = self.crossover[0].process(y[0]); + let (l1, h1) = if self.channels == 2 { + self.crossover[1].process(y[1]) + } else { + (l0, h0) + }; + let low = 0.5 * (l0 + l1); + self.low_env += (low * low - self.low_env) * self.env_coef; + let env = self.low_env.sqrt(); + if !self.voice.releasing { + self.note_peak = self.note_peak.max(env); + } + let follow = if self.note_peak > 1e-6 { + (env / self.note_peak).min(1.) + } else { + 1. + }; + let dynamics = s.dynamics * 0.01; + let shape = 1. - dynamics + dynamics * follow; + self.level += (db_to_gain(s.level) - self.level) * self.smooth; + self.keep += (s.keep * 0.01 - self.keep) * self.smooth; + self.output += (db_to_gain(s.output) - self.output) * self.smooth; + self.bypass += (f32::from(s.bypass) - self.bypass) * self.smooth; + let sub = self.oscillator() * self.level * shape; + self.low_power += (low * low - self.low_power) * self.meter_coef; + self.sub_power += (sub * sub - self.sub_power) * self.meter_coef; + self.scope_count += 1; + if self.scope_count >= self.scope_stride { + self.scope_count = 0; + self.scope_low[self.scope_at] = low; + self.scope_sub[self.scope_at] = sub; + self.scope_at = (self.scope_at + 1) % (2 * SCOPE); + } + let wet = |l: f32, h: f32| { + if monitor.solo { + sub + } else { + h + self.keep * l + sub + } + }; + let w = [wet(l0, h0), wet(l1, h1)]; + let mut out = [0.; 2]; + for c in 0..self.channels { + out[c] = (w[c] * self.output) * (1. - self.bypass) + y[c] * self.bypass; + } + out + } + pub fn take_analyzer(&self) -> AnalyzerFrame { + let mut f = AnalyzerFrame { + latency: self.latency as u32, + sample_rate: self.rate, + low_db: 10. * self.low_power.max(1e-12).log10() + 3.01, + sub_db: 10. * self.sub_power.max(1e-12).log10() + 3.01, + scope_ms: (self.scope_stride * SCOPE) as f32 / self.rate * 1000., + ..AnalyzerFrame::default() + }; + if self.voice.amp > 0. && !self.voice.releasing { + f.hz = self.voice.hz; + f.note = Some(hz_to_midi(self.voice.hz).round().clamp(0., 127.) as u8); + } + // Trigger on the latest rising zero crossing that still leaves a full window behind it. + let n = 2 * SCOPE; + let oldest = self.scope_at; + let mut start = oldest; + for back in 0..SCOPE { + let i = (oldest + SCOPE - back) % n; + let prev = (i + n - 1) % n; + if self.scope_sub[prev] < 0. && self.scope_sub[i] >= 0. { + start = i; + break; + } + } + for k in 0..SCOPE { + let i = (start + k) % n; + f.scope_low[k] = self.scope_low[i]; + f.scope_sub[k] = self.scope_sub[i]; + } + f + } +} diff --git a/plugins/anvil/core/src/settings.rs b/plugins/anvil/core/src/settings.rs new file mode 100644 index 0000000..081a14a --- /dev/null +++ b/plugins/anvil/core/src/settings.rs @@ -0,0 +1,95 @@ +//! Host-independent controls. Levels in dB, times in milliseconds, frequencies in Hz. +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] +pub enum Source { + /// Pitch and note starts are detected from the incoming bass. + #[default] + Audio, + /// Notes come from MIDI note input; the audio only supplies the band above the split. + Midi, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[serde(deny_unknown_fields)] +pub struct Settings { + pub source: Source, + /// Peak level of the new sub in dBFS, the same for every note. + pub level: f32, + /// Crossover frequency: below it the original sub is replaced. + pub split: f32, + /// 0 % holds a constant level per note; 100 % follows the original low band's shape. + pub dynamics: f32, + /// Soft saturation of the sine, adding upper harmonics for small speakers. + pub drive: f32, + pub octave: i32, + /// Pitch slide time between overlapping notes. + pub glide: f32, + /// Start phase in degrees on every note start. + pub phase: f32, + /// Detection threshold in dBFS for note starts (Audio source). + pub gate: f32, + /// Amount of the original low band that is kept under the new sub. + pub keep: f32, + pub output: f32, + /// Restart the sine at `phase` on every note, so each note and kick hit lines up the same. + pub retrigger: bool, + /// Round detected pitch to the nearest semitone. + pub snap: bool, + pub bypass: bool, +} +impl Default for Settings { + fn default() -> Self { + Self { + source: Source::Audio, + level: -9., + split: 90., + dynamics: 0., + drive: 0., + octave: 0, + glide: 0., + phase: 0., + gate: -42., + keep: 0., + output: 0., + retrigger: true, + snap: true, + bypass: false, + } + } +} +impl Settings { + pub fn validate(&self) -> Result<(), &'static str> { + let finite = [ + self.level, + self.split, + self.dynamics, + self.drive, + self.glide, + self.phase, + self.gate, + self.keep, + self.output, + ] + .iter() + .all(|v| v.is_finite()); + if !finite { + return Err("non-finite setting"); + } + let ok = (-30.0..=0.).contains(&self.level) + && (40.0..=200.).contains(&self.split) + && (0.0..=100.).contains(&self.dynamics) + && (0.0..=100.).contains(&self.drive) + && (-2..=1).contains(&self.octave) + && (0.0..=200.).contains(&self.glide) + && (0.0..=360.).contains(&self.phase) + && (-60.0..=-20.).contains(&self.gate) + && (0.0..=100.).contains(&self.keep) + && (-12.0..=12.).contains(&self.output); + if ok { + Ok(()) + } else { + Err("setting out of range") + } + } +} diff --git a/plugins/anvil/core/tests/dsp.rs b/plugins/anvil/core/tests/dsp.rs new file mode 100644 index 0000000..205dc51 --- /dev/null +++ b/plugins/anvil/core/tests/dsp.rs @@ -0,0 +1,213 @@ +use anvil_core::{Engine, Monitor, Settings, Source}; +use common_dsp::Saw; + +const RATE: f32 = 48000.; + +/// Saw notes (start s, end s, Hz, peak) with 1 ms fades. +fn notes(list: &[(f32, f32, f32, f32)], total: f32) -> Vec { + let mut x = vec![0.; (total * RATE) as usize]; + for &(a, b, hz, amp) in list { + let (a, b) = ((a * RATE) as usize, (b * RATE) as usize); + let mut saw = Saw::new(RATE, hz); + let fade = (0.001 * RATE) as usize; + for i in a..b.min(x.len()) { + let edge = ((i - a).min(b - 1 - i) as f32 / fade as f32).min(1.); + x[i] += saw.sample() * amp * edge; + } + } + x +} +fn render(e: &mut Engine, x: &[f32], solo: bool) -> Vec { + x.iter() + .map(|&s| e.process_sample([s, s], Monitor { solo })[0]) + .collect() +} +fn rms(x: &[f32]) -> f32 { + (x.iter().map(|v| v * v).sum::() / x.len() as f32).sqrt() +} +fn db(x: f32) -> f32 { + 20. * x.max(1e-9).log10() +} +/// Frequency from interpolated rising zero crossings. +fn frequency(x: &[f32]) -> f32 { + let mut first = None; + let mut last = 0.; + let mut count = 0; + for i in 1..x.len() { + if x[i - 1] < 0. && x[i] >= 0. { + let t = (i - 1) as f32 + x[i - 1] / (x[i - 1] - x[i]); + if first.is_none() { + first = Some(t); + } else { + count += 1; + } + last = t; + } + } + count as f32 * RATE / (last - first.unwrap()) +} +fn seconds(t: f32) -> usize { + (t * RATE) as usize +} +fn engine(s: Settings) -> (Engine, usize) { + let mut e = Engine::new(RATE, 2).unwrap(); + e.set_settings(s).unwrap(); + let l = e.latency_samples() as usize; + (e, l) +} + +#[test] +fn every_note_gets_the_same_sub_level() { + let (mut e, l) = engine(Settings::default()); + let x = notes(&[(0.1, 0.6, 55., 0.5), (0.8, 1.3, 55., 0.1)], 1.6); + let y = render(&mut e, &x, true); + let loud = rms(&y[seconds(0.3) + l..seconds(0.5) + l]); + let quiet = rms(&y[seconds(1.0) + l..seconds(1.2) + l]); + assert!( + (db(loud) - db(quiet)).abs() < 0.5, + "{} vs {}", + db(loud), + db(quiet) + ); + // −9 dBFS peak sine. + assert!((db(loud * 2f32.sqrt()) + 9.).abs() < 0.5, "{}", db(loud)); +} + +#[test] +fn sub_follows_the_detected_pitch_and_octave() { + for (octave, expect) in [(0, 43.654), (-1, 21.827)] { + let (mut e, l) = engine(Settings { + octave, + ..Settings::default() + }); + let x = notes(&[(0.1, 1.1, 43.65, 0.5)], 1.3); + let y = render(&mut e, &x, true); + let f = frequency(&y[seconds(0.3) + l..seconds(1.0) + l]); + assert!((f / expect - 1.).abs() < 0.005, "octave {octave}: {f}"); + } +} + +#[test] +fn a_pitch_change_without_a_gap_is_a_new_note() { + let (mut e, l) = engine(Settings::default()); + let x = notes(&[(0.1, 0.6, 55., 0.5), (0.6, 1.1, 82.41, 0.5)], 1.3); + let y = render(&mut e, &x, true); + let f = frequency(&y[seconds(0.75) + l..seconds(1.05) + l]); + assert!((f / 82.41 - 1.).abs() < 0.005, "{f}"); +} + +#[test] +fn every_note_restarts_at_the_start_phase() { + let (mut e, l) = engine(Settings { + phase: 90., + ..Settings::default() + }); + let x = notes(&[(0.1, 0.4, 55., 0.5), (0.6, 0.9, 55., 0.3)], 1.1); + let y = render(&mut e, &x, true); + let period = (RATE / 55.) as usize; + let a = &y[seconds(0.1) + l..seconds(0.1) + l + 2 * period]; + let b = &y[seconds(0.6) + l..seconds(0.6) + l + 2 * period]; + let peak = y.iter().fold(0f32, |m, v| m.max(v.abs())); + let worst = a.iter().zip(b).fold(0f32, |m, (p, q)| m.max((p - q).abs())); + assert!(worst < 0.1 * peak, "{worst} of {peak}"); + // Fit the phase over the second period, after the 2 ms attack. + let w = std::f32::consts::TAU * 55. / RATE; + let (mut s, mut c) = (0., 0.); + for (k, v) in a.iter().enumerate().skip(period) { + s += v * (w * k as f32).sin(); + c += v * (w * k as f32).cos(); + } + let phase = c.atan2(s).to_degrees(); + assert!((phase - 90.).abs() < 20., "{phase}"); +} + +#[test] +fn midi_notes_set_pitch_and_are_delayed_by_the_latency() { + let (mut e, l) = engine(Settings { + source: Source::Midi, + ..Settings::default() + }); + let mut y = Vec::new(); + for i in 0..seconds(0.8) { + if i == 1000 { + e.note_on(33); + } + if i == seconds(0.6) { + e.note_off(33); + } + y.push(e.process_sample([0.; 2], Monitor { solo: true })[0]); + } + assert!(y[..1000 + l].iter().all(|v| *v == 0.)); + assert!(y[1000 + l + 50].abs() > 0.); + let f = frequency(&y[seconds(0.2) + l..seconds(0.5)]); + assert!((f / 55. - 1.).abs() < 0.001, "{f}"); + assert!(y[seconds(0.6) + l + seconds(0.05)..] + .iter() + .all(|v| v.abs() < 1e-6)); +} + +#[test] +fn note_changes_do_not_click() { + let (mut e, _) = engine(Settings::default()); + let x = notes( + &[ + (0.1, 0.4, 55., 0.5), + (0.4, 0.7, 73.42, 0.5), + (0.7, 1.0, 41.2, 0.5), + ], + 1.3, + ); + let y = render(&mut e, &x, true); + let step = y.windows(2).fold(0f32, |m, w| m.max((w[1] - w[0]).abs())); + // A −9 dBFS sine at 73 Hz moves at most 0.0034 per sample. + assert!(step < 0.012, "{step}"); +} + +#[test] +fn keep_without_notes_is_an_allpass() { + let (mut e, _) = engine(Settings { + source: Source::Midi, + keep: 100., + ..Settings::default() + }); + for hz in [40., 90., 300., 3000.] { + e.reset(); + let x: Vec = (0..seconds(1.)) + .map(|i| 0.3 * (std::f32::consts::TAU * hz * i as f32 / RATE).sin()) + .collect(); + let y = render(&mut e, &x, false); + let d = db(rms(&y[seconds(0.5)..])) - db(rms(&x[seconds(0.5)..])); + assert!(d.abs() < 0.1, "{hz}: {d}"); + } +} + +#[test] +fn split_removes_original_sub_and_keeps_mids() { + let (mut e, l) = engine(Settings { + source: Source::Midi, + ..Settings::default() + }); + let x: Vec = (0..seconds(1.)) + .map(|i| 0.3 * (std::f32::consts::TAU * 40. * i as f32 / RATE).sin()) + .collect(); + let y = render(&mut e, &x, false); + assert!(db(rms(&y[seconds(0.5) + l..])) < db(0.3) - 18.); +} + +#[test] +fn settings_are_validated_and_serialise() { + let mut e = Engine::new(RATE, 1).unwrap(); + assert!(e + .set_settings(Settings { + level: 3., + ..Settings::default() + }) + .is_err()); + let json = serde_json::to_string(&Settings::default()).unwrap(); + assert_eq!( + serde_json::from_str::(&json).unwrap(), + Settings::default() + ); + assert!(Engine::new(1000., 2).is_err()); + assert!(Engine::new(48000., 3).is_err()); +} diff --git a/plugins/anvil/core/tests/realtime.rs b/plugins/anvil/core/tests/realtime.rs new file mode 100644 index 0000000..bdc20ae --- /dev/null +++ b/plugins/anvil/core/tests/realtime.rs @@ -0,0 +1,69 @@ +use anvil_core::{Engine, Monitor, Settings, Source}; +use std::{ + alloc::{GlobalAlloc, Layout, System}, + cell::Cell, +}; + +struct CheckedAllocator; +thread_local! { + static CHECK:Cell=const {Cell::new(false)}; + static OPERATIONS:Cell=const {Cell::new(0)}; +} +fn record() { + if CHECK.try_with(Cell::get).unwrap_or(false) { + let _ = OPERATIONS.try_with(|n| n.set(n.get() + 1)); + } +} +unsafe impl GlobalAlloc for CheckedAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + record(); + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) { + record(); + unsafe { System.dealloc(pointer, layout) } + } + unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, size: usize) -> *mut u8 { + record(); + unsafe { System.realloc(pointer, layout, size) } + } +} +#[global_allocator] +static ALLOCATOR: CheckedAllocator = CheckedAllocator; + +#[test] +fn processing_notes_settings_and_analysis_do_not_allocate_or_free() { + let mut engine = Engine::new(48000., 2).unwrap(); + let mut settings = Settings::default(); + engine.set_settings(settings).unwrap(); + CHECK.with(|v| v.set(true)); + for i in 0..96000 { + if i % 4800 == 0 { + settings.split = 60. + (i % 100) as f32; + settings.source = if i > 48000 { + Source::Midi + } else { + Source::Audio + }; + settings.octave = -((i / 4800) % 2); + engine.set_settings(settings).unwrap(); + engine.note_on(30 + (i / 4800 % 12) as u8); + } + if i % 4800 == 2400 { + engine.note_off(30 + (i / 4800 % 12) as u8); + } + let t = i as f32 / 48000.; + let x = if (i / 2400) % 2 == 0 { + (std::f32::consts::TAU * 55. * t).sin() * 0.5 + } else { + 0. + }; + std::hint::black_box(engine.process_sample([x, x], Monitor { solo: i % 3 == 0 })); + if i % 1600 == 0 { + std::hint::black_box(engine.take_analyzer()); + } + } + engine.reset(); + CHECK.with(|v| v.set(false)); + assert_eq!(OPERATIONS.with(Cell::get), 0); +} diff --git a/plugins/anvil/plugin/Cargo.lock b/plugins/anvil/plugin/Cargo.lock new file mode 100644 index 0000000..f218c81 --- /dev/null +++ b/plugins/anvil/plugin/Cargo.lock @@ -0,0 +1,2382 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "addr2line" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e567177890eb1617b1f774005b66b26b2377afd138a2ca37aae7d8f0c81429d4" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.2", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.21", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "anvil" +version = "0.1.0" +dependencies = [ + "anvil-core", + "anvil-ui", + "clap-sys", + "crossbeam-queue", + "nih_plug", + "nih_plug_egui", + "serde_json", +] + +[[package]] +name = "anvil-core" +version = "0.1.0" +dependencies = [ + "common-dsp", + "serde", +] + +[[package]] +name = "anvil-ui" +version = "0.1.0" +dependencies = [ + "anvil-core", + "common-dsp", + "common-ui", + "egui", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "anymap3" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5dfbc6d8d2675589ccbe4d0fd61df2419075625f8c1a62325e718e2b0049f9" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_float" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" + +[[package]] +name = "atomic_refcell" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e4227379beff4205943696e6c3e0cd809bacdf3f0edd6e3dd153e2269571a4" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "backtrace" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f4220217f3288537b83684130f42fad43320b1c3b805f1910d683d52ded24f" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "baseview" +version = "0.1.0" +source = "git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0#9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" +dependencies = [ + "cocoa", + "core-foundation", + "keyboard-types", + "nix", + "objc", + "raw-window-handle 0.5.2", + "uuid", + "winapi", + "x11", + "x11rb", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.2", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clap-sys" +version = "0.5.0" +source = "git+https://github.com/micahrj/clap-sys.git?rev=25d7f53fdb6363ad63fbd80049cb7a42a97ac156#25d7f53fdb6363ad63fbd80049cb7a42a97ac156" + +[[package]] +name = "clipboard-win" +version = "5.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a" +dependencies = [ + "error-code", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "common-dsp" +version = "0.1.0" + +[[package]] +name = "common-ui" +version = "0.1.0" +dependencies = [ + "egui", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "copypasta" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6811e17f81fe246ef2bc553f76b6ee6ab41a694845df1d37e52a92b7bbd38a" +dependencies = [ + "clipboard-win", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crossbeam" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71406cd8807725f7ac2f999a4cdd32e98f829fdf65f528343cebf945e41df1e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "ahash", + "bitflags 2.13.2", + "emath", + "epaint", + "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-baseview" +version = "0.5.0" +source = "git+https://github.com/BillyDM/egui-baseview.git?rev=ec70c3fe6b2f070dcacbc22924431edbe24bd1c0#ec70c3fe6b2f070dcacbc22924431edbe24bd1c0" +dependencies = [ + "baseview", + "copypasta", + "egui", + "egui_glow", + "keyboard-types", + "log", + "open", + "raw-window-handle 0.5.2", + "thiserror 2.0.21", +] + +[[package]] +name = "egui_glow" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset 0.9.1", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "emath" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "epaint" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.6", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "gimli" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1033caf0b349c518623b5396bfb2cf0bddf44f0306d543a250e5743297aafd10" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "bitflags 2.13.2", + "libc", + "plain", + "redox_syscall 0.9.4", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "midi-consts" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2dd5c7f8aaf48a76e389068ab25ed80bdbc226b887f9013844c415698c9952" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.2", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nih_log" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0cdb52ef79af48ae110401c883bdb9c15e0306a99ab6ecf18bc52068b668e54" +dependencies = [ + "atty", + "log", + "once_cell", + "termcolor", + "time", + "windows", +] + +[[package]] +name = "nih_plug" +version = "0.0.0" +source = "git+https://github.com/robbert-vdh/nih-plug.git?rev=de421011f41a6d10fc8c7a6084e4f4dee0143683#de421011f41a6d10fc8c7a6084e4f4dee0143683" +dependencies = [ + "anyhow", + "anymap3", + "atomic_float", + "atomic_refcell", + "backtrace", + "bitflags 1.3.2", + "cfg-if", + "clap-sys", + "core-foundation", + "crossbeam", + "libc", + "log", + "midi-consts", + "nih_log", + "nih_plug_derive", + "objc", + "parking_lot", + "raw-window-handle 0.5.2", + "serde", + "serde_json", + "widestring", + "windows", +] + +[[package]] +name = "nih_plug_derive" +version = "0.1.0" +source = "git+https://github.com/robbert-vdh/nih-plug.git?rev=de421011f41a6d10fc8c7a6084e4f4dee0143683#de421011f41a6d10fc8c7a6084e4f4dee0143683" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nih_plug_egui" +version = "0.0.0" +source = "git+https://github.com/robbert-vdh/nih-plug.git?rev=de421011f41a6d10fc8c7a6084e4f4dee0143683#de421011f41a6d10fc8c7a6084e4f4dee0143683" +dependencies = [ + "baseview", + "crossbeam", + "egui-baseview", + "nih_plug", + "parking_lot", + "raw-window-handle 0.5.2", + "serde", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num_threads" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabcc654086861f9e1bc5115258de62ef92c79b20f4615a1b0cc57c4e82027d4" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.2", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "object" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "open" +version = "5.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.3", + "pin-project-lite", + "rustix 1.1.6", + "windows-sys 0.61.2", +] + +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.13.2", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.6", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.2", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/plugins/anvil/plugin/Cargo.toml b/plugins/anvil/plugin/Cargo.toml new file mode 100644 index 0000000..e2d8462 --- /dev/null +++ b/plugins/anvil/plugin/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "anvil" +version = "0.1.0" +edition = "2021" +license = "ISC" + +[workspace] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", rev = "de421011f41a6d10fc8c7a6084e4f4dee0143683", default-features = false } +nih_plug_egui = { git = "https://github.com/robbert-vdh/nih-plug.git", rev = "de421011f41a6d10fc8c7a6084e4f4dee0143683" } +anvil-core = { path = "../core" } +anvil-ui = { path = "../ui" } +crossbeam-queue = "0.3" + +[dev-dependencies] +clap-sys = { git = "https://github.com/micahrj/clap-sys.git", rev = "25d7f53fdb6363ad63fbd80049cb7a42a97ac156" } +serde_json = "1" + +[profile.release] +lto = "thin" +strip = "symbols" diff --git a/plugins/anvil/plugin/src/lib.rs b/plugins/anvil/plugin/src/lib.rs new file mode 100644 index 0000000..4e5ddc2 --- /dev/null +++ b/plugins/anvil/plugin/src/lib.rs @@ -0,0 +1,199 @@ +//! CLAP audio effect. All DSP state is owned by the processing thread. +mod params; +use anvil_core::{AnalyzerFrame, Engine, Monitor}; +use crossbeam_queue::ArrayQueue; +use nih_plug::prelude::*; +use nih_plug_egui::{create_egui_editor, egui}; +use params::AnvilParams; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; + +struct Shared { + analyzer: ArrayQueue, + solo: AtomicBool, +} +struct EditorData { + ui: anvil_ui::State, + gestures: [bool; 9], +} +pub struct Anvil { + params: Arc, + shared: Arc, + engine: Option, + publish_count: usize, + publish_interval: usize, +} +impl Default for Anvil { + fn default() -> Self { + Self { + params: Arc::new(AnvilParams::default()), + shared: Arc::new(Shared { + analyzer: ArrayQueue::new(2), + solo: AtomicBool::new(false), + }), + engine: None, + publish_count: 0, + publish_interval: 1600, + } + } +} +impl Plugin for Anvil { + const NAME: &'static str = "Anvil"; + const VENDOR: &'static str = "Rivet & Patina"; + const URL: &'static str = ""; + const EMAIL: &'static str = ""; + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + ..AudioIOLayout::const_default() + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + const MIDI_INPUT: MidiConfig = MidiConfig::Basic; + const SAMPLE_ACCURATE_AUTOMATION: bool = true; + type SysExMessage = (); + type BackgroundTask = (); + fn params(&self) -> Arc { + self.params.clone() + } + fn editor(&mut self, _: AsyncExecutor) -> Option> { + let params = self.params.clone(); + let shared = self.shared.clone(); + create_egui_editor( + params.editor.clone(), + EditorData { + ui: anvil_ui::State::default(), + gestures: [false; 9], + }, + |ctx, data| { + data.ui.solo = false; + anvil_ui::style(ctx); + }, + move |ctx, setter, data| { + while let Some(frame) = shared.analyzer.pop() { + data.ui.accept_frame(frame); + } + let before = params.settings(); + let mut edited = before; + nih_plug_egui::resizable_window::ResizableWindow::new("anvil-editor") + .min_size(egui::vec2( + anvil_ui::WIDTH as f32 - 80., + anvil_ui::HEIGHT as f32 - 40., + )) + .show(ctx, ¶ms.editor, |ui| { + anvil_ui::show(ui, &mut edited, &mut data.ui) + }); + params.apply( + setter, + &before, + &edited, + data.ui.gesture_active, + &mut data.gestures, + ); + // Solo is a monitor only: never saved and never left on after the editor closes. + shared.solo.store(data.ui.solo, Ordering::Release); + if data.ui.analyzer.note.is_some() { + ctx.request_repaint(); + } + }, + ) + } + fn initialize( + &mut self, + layout: &AudioIOLayout, + config: &BufferConfig, + context: &mut impl InitContext, + ) -> bool { + let Ok(mut engine) = Engine::new( + config.sample_rate, + layout.main_input_channels.map_or(0, |v| v.get() as usize), + ) else { + return false; + }; + if engine.set_settings(self.params.settings()).is_err() { + return false; + } + context.set_latency_samples(engine.latency_samples()); + self.publish_interval = (config.sample_rate / 30.) as usize; + self.engine = Some(engine); + self.shared.solo.store(false, Ordering::Release); + true + } + fn reset(&mut self) { + if let Some(e) = self.engine.as_mut() { + e.reset(); + } + self.publish_count = 0; + } + fn process( + &mut self, + buffer: &mut Buffer, + _: &mut AuxiliaryBuffers, + context: &mut impl ProcessContext, + ) -> ProcessStatus { + let Some(engine) = self.engine.as_mut() else { + return ProcessStatus::Normal; + }; + let _ = engine.set_settings(self.params.settings()); + let solo = self.shared.solo.load(Ordering::Acquire) && self.params.editor.is_open(); + let mut next = context.next_event(); + for (index, mut sample) in buffer.iter_samples().enumerate() { + while let Some(event) = next { + if event.timing() as usize > index { + break; + } + match event { + NoteEvent::NoteOn { note, velocity, .. } if velocity > 0. => { + engine.note_on(note) + } + NoteEvent::NoteOn { note, .. } | NoteEvent::NoteOff { note, .. } => { + engine.note_off(note) + } + NoteEvent::Choke { note, .. } => engine.note_off(note), + _ => {} + } + next = context.next_event(); + } + let x = [ + *sample.get_mut(0).unwrap(), + if engine.channels() == 2 { + *sample.get_mut(1).unwrap() + } else { + 0. + }, + ]; + let out = engine.process_sample(x, Monitor { solo }); + for (c, value) in sample.into_iter().enumerate() { + *value = out[c]; + } + self.publish_count += 1; + if self.publish_count >= self.publish_interval { + self.shared.analyzer.force_push(engine.take_analyzer()); + self.publish_count = 0; + } + } + // The lookahead delay and the sub release must drain after input stops. + ProcessStatus::KeepAlive + } +} +impl ClapPlugin for Anvil { + const CLAP_ID: &'static str = "io.storyboard.sub-anchor"; + const CLAP_DESCRIPTION: Option<&'static str> = + Some("Replace the sub with a clean sine at one level and phase for every note"); + const CLAP_MANUAL_URL: Option<&'static str> = None; + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Stereo, + ClapFeature::Mono, + ]; +} +nih_export_clap!(Anvil); diff --git a/plugins/anvil/plugin/src/params.rs b/plugins/anvil/plugin/src/params.rs new file mode 100644 index 0000000..d05c70e --- /dev/null +++ b/plugins/anvil/plugin/src/params.rs @@ -0,0 +1,192 @@ +use anvil_core::{Settings, Source}; +use nih_plug::prelude::*; +use nih_plug_egui::EguiState; +use std::sync::Arc; + +#[derive(Enum, Debug, PartialEq, Eq, Clone, Copy)] +pub enum SourceParam { + #[id = "audio"] + Audio, + #[id = "midi"] + #[name = "MIDI"] + Midi, +} +fn linear(name: &str, v: f32, min: f32, max: f32, unit: &'static str) -> FloatParam { + FloatParam::new(name, v, FloatRange::Linear { min, max }).with_unit(unit) +} +#[derive(Params)] +pub struct AnvilParams { + #[persist = "editor"] + pub editor: Arc, + #[id = "source"] + pub source: EnumParam, + #[id = "level"] + pub level: FloatParam, + #[id = "split"] + pub split: FloatParam, + #[id = "dynamics"] + pub dynamics: FloatParam, + #[id = "drive"] + pub drive: FloatParam, + #[id = "octave"] + pub octave: IntParam, + #[id = "glide"] + pub glide: FloatParam, + #[id = "phase"] + pub phase: FloatParam, + #[id = "gate"] + pub gate: FloatParam, + #[id = "keep"] + pub keep: FloatParam, + #[id = "output"] + pub output: FloatParam, + #[id = "retrigger"] + pub retrigger: BoolParam, + #[id = "snap"] + pub snap: BoolParam, + #[id = "bypass"] + pub bypass: BoolParam, +} +impl Default for AnvilParams { + fn default() -> Self { + let d = Settings::default(); + Self { + editor: EguiState::from_size(anvil_ui::WIDTH, anvil_ui::HEIGHT), + source: EnumParam::new("Pitch source", SourceParam::Audio), + level: linear("Sub level", d.level, -30., 0., " dBFS"), + split: FloatParam::new( + "Split", + d.split, + FloatRange::Skewed { + min: 40., + max: 200., + factor: FloatRange::skew_factor(-1.), + }, + ) + .with_unit(" Hz"), + dynamics: linear("Dynamics", d.dynamics, 0., 100., " %"), + drive: linear("Drive", d.drive, 0., 100., " %"), + octave: IntParam::new("Octave", d.octave, IntRange::Linear { min: -2, max: 1 }), + glide: linear("Glide", d.glide, 0., 200., " ms"), + phase: linear("Phase", d.phase, 0., 360., "°"), + gate: linear("Gate", d.gate, -60., -20., " dB"), + keep: linear("Keep", d.keep, 0., 100., " %"), + output: linear("Output", d.output, -12., 12., " dB"), + retrigger: BoolParam::new("Retrigger", d.retrigger), + snap: BoolParam::new("Snap to semitone", d.snap), + bypass: BoolParam::new("Bypass", false).make_bypass(), + } + } +} +impl AnvilParams { + pub fn settings(&self) -> Settings { + Settings { + source: match self.source.value() { + SourceParam::Audio => Source::Audio, + SourceParam::Midi => Source::Midi, + }, + level: self.level.value(), + split: self.split.value(), + dynamics: self.dynamics.value(), + drive: self.drive.value(), + octave: self.octave.value(), + glide: self.glide.value(), + phase: self.phase.value(), + gate: self.gate.value(), + keep: self.keep.value(), + output: self.output.value(), + retrigger: self.retrigger.value(), + snap: self.snap.value(), + bypass: self.bypass.value(), + } + } + /// Pushes every changed editor value to the host. Continuous controls hold one + /// gesture while the pointer is down; switches are set in a single gesture. + pub fn apply( + &self, + setter: &ParamSetter, + old: &Settings, + new: &Settings, + dragging: bool, + open: &mut [bool; 9], + ) { + let floats: [(&FloatParam, f32, f32); 8] = [ + (&self.level, old.level, new.level), + (&self.split, old.split, new.split), + (&self.dynamics, old.dynamics, new.dynamics), + (&self.drive, old.drive, new.drive), + (&self.glide, old.glide, new.glide), + (&self.phase, old.phase, new.phase), + (&self.gate, old.gate, new.gate), + (&self.keep, old.keep, new.keep), + ]; + let mut continuous = |i: usize, changed: bool, set: &dyn Fn()| { + if changed { + if !open[i] { + open[i] = true; + set_begin(setter, self, i); + } + set(); + } + if !dragging && open[i] { + set_end(setter, self, i); + open[i] = false; + } + }; + for (i, (p, a, b)) in floats.into_iter().enumerate() { + continuous(i, a != b, &|| setter.set_parameter(p, b)); + } + continuous(8, old.output != new.output, &|| { + setter.set_parameter(&self.output, new.output) + }); + let once = |changed: bool, f: &dyn Fn()| { + if changed { + f(); + } + }; + once(new.octave != old.octave, &|| { + setter.begin_set_parameter(&self.octave); + setter.set_parameter(&self.octave, new.octave); + setter.end_set_parameter(&self.octave); + }); + for (p, a, b) in [ + (&self.retrigger, old.retrigger, new.retrigger), + (&self.snap, old.snap, new.snap), + (&self.bypass, old.bypass, new.bypass), + ] { + once(a != b, &|| { + setter.begin_set_parameter(p); + setter.set_parameter(p, b); + setter.end_set_parameter(p); + }); + } + once(new.source != old.source, &|| { + let v = match new.source { + Source::Audio => SourceParam::Audio, + Source::Midi => SourceParam::Midi, + }; + setter.begin_set_parameter(&self.source); + setter.set_parameter(&self.source, v); + setter.end_set_parameter(&self.source); + }); + } + fn continuous(&self, i: usize) -> &FloatParam { + [ + &self.level, + &self.split, + &self.dynamics, + &self.drive, + &self.glide, + &self.phase, + &self.gate, + &self.keep, + &self.output, + ][i] + } +} +fn set_begin(setter: &ParamSetter, p: &AnvilParams, i: usize) { + setter.begin_set_parameter(p.continuous(i)); +} +fn set_end(setter: &ParamSetter, p: &AnvilParams, i: usize) { + setter.end_set_parameter(p.continuous(i)); +} diff --git a/plugins/anvil/plugin/tests/host.rs b/plugins/anvil/plugin/tests/host.rs new file mode 100644 index 0000000..93fdb56 --- /dev/null +++ b/plugins/anvil/plugin/tests/host.rs @@ -0,0 +1,212 @@ +//! Exercise the actual exported CLAP ABI without requiring a GUI or installed DAW. +use clap_sys::{ + audio_buffer::*, + events::*, + ext::{audio_ports::*, latency::*, state::*}, + factory::plugin_factory::*, + host::*, + process::*, + stream::*, + version::*, +}; +use std::{ + ffi::{c_char, c_void, CStr}, + io::{Cursor, Read}, + ptr, +}; + +unsafe extern "C" fn extension(_: *const clap_host, _: *const c_char) -> *const c_void { + ptr::null() +} +unsafe extern "C" fn request(_: *const clap_host) {} +unsafe extern "C" fn count(_: *const clap_input_events) -> u32 { + 0 +} +unsafe extern "C" fn event(_: *const clap_input_events, _: u32) -> *const clap_event_header { + ptr::null() +} +unsafe extern "C" fn push(_: *const clap_output_events, _: *const clap_event_header) -> bool { + true +} +unsafe extern "C" fn write(stream: *const clap_ostream, buffer: *const c_void, size: u64) -> i64 { + // SAFETY: The test owns the Vec for the entire synchronous save call. + let bytes = unsafe { &mut *((*stream).ctx as *mut Vec) }; + bytes.extend_from_slice(unsafe { + std::slice::from_raw_parts(buffer as *const u8, size as usize) + }); + size as i64 +} +unsafe extern "C" fn read(stream: *const clap_istream, buffer: *mut c_void, size: u64) -> i64 { + // SAFETY: The test owns the Cursor; the wrapper supplies writable stream storage. + let cursor = unsafe { &mut *((*stream).ctx as *mut Cursor>) }; + cursor + .read(unsafe { std::slice::from_raw_parts_mut(buffer as *mut u8, size as usize) }) + .unwrap() as i64 +} + +#[test] +fn effect_ports_state_latency_and_audio_lifecycle() { + // SAFETY: All callbacks, strings, streams, buffers, and the host outlive their + // synchronous ABI calls. Activation/processing/destruction follow CLAP order. + unsafe { + let entry = &anvil::clap_entry; + assert!(entry.init.unwrap()(c"anvil.clap".as_ptr())); + let factory = entry.get_factory.unwrap()(CLAP_PLUGIN_FACTORY_ID.as_ptr()) + as *const clap_plugin_factory; + assert!(!factory.is_null()); + let factory = &*factory; + assert_eq!(factory.get_plugin_count.unwrap()(factory), 1); + let descriptor = factory.get_plugin_descriptor.unwrap()(factory, 0); + assert!(!descriptor.is_null()); + assert_eq!(CStr::from_ptr((*descriptor).name), c"Anvil"); + assert_eq!( + CStr::from_ptr((*descriptor).id), + c"io.storyboard.sub-anchor" + ); + assert!(factory.get_plugin_descriptor.unwrap()(factory, 1).is_null()); + let host = clap_host { + clap_version: CLAP_VERSION, + host_data: ptr::null_mut(), + name: c"Storyboard test host".as_ptr(), + vendor: c"Tests".as_ptr(), + url: c"".as_ptr(), + version: c"1.0".as_ptr(), + get_extension: Some(extension), + request_restart: Some(request), + request_process: Some(request), + request_callback: Some(request), + }; + let plugin = factory.create_plugin.unwrap()(factory, &host, (*descriptor).id); + assert!(!plugin.is_null()); + let p = &*plugin; + assert!(p.init.unwrap()(plugin)); + let ports = p.get_extension.unwrap()(plugin, CLAP_EXT_AUDIO_PORTS.as_ptr()) + as *const clap_plugin_audio_ports; + assert!(!ports.is_null()); + assert_eq!((*ports).count.unwrap()(plugin, false), 1); + assert_eq!((*ports).count.unwrap()(plugin, true), 1); + let latency = p.get_extension.unwrap()(plugin, CLAP_EXT_LATENCY.as_ptr()) + as *const clap_plugin_latency; + assert!(!latency.is_null()); + let state = + p.get_extension.unwrap()(plugin, CLAP_EXT_STATE.as_ptr()) as *const clap_plugin_state; + assert!(!state.is_null()); + let state = &*state; + let save = || { + let mut bytes = Vec::new(); + let stream = clap_ostream { + ctx: &mut bytes as *mut Vec as *mut c_void, + write: Some(write), + }; + assert!(state.save.unwrap()(plugin, &stream)); + bytes + }; + let mut envelope: nih_plug::prelude::PluginState = + serde_json::from_slice(&save()[8..]).unwrap(); + use nih_plug::wrapper::state::ParamValue; + envelope + .params + .insert("level".into(), ParamValue::F32(-14.)); + envelope.params.insert("octave".into(), ParamValue::I32(-1)); + envelope + .params + .insert("snap".into(), ParamValue::Bool(false)); + envelope + .params + .insert("source".into(), ParamValue::String("midi".into())); + let encoded = serde_json::to_vec(&envelope).unwrap(); + let mut framed = (encoded.len() as u64).to_le_bytes().to_vec(); + framed.extend_from_slice(&encoded); + let mut bytes = Cursor::new(framed); + let stream = clap_istream { + ctx: &mut bytes as *mut Cursor> as *mut c_void, + read: Some(read), + }; + assert!(state.load.unwrap()(plugin, &stream)); + let restored: nih_plug::prelude::PluginState = + serde_json::from_slice(&save()[8..]).unwrap(); + assert_eq!( + serde_json::to_value(&restored.params).unwrap(), + serde_json::to_value(&envelope.params).unwrap() + ); + assert!(!restored.fields.contains_key("solo")); + for rate in [44100., 48000., 96000.] { + assert!(p.activate.unwrap()(plugin, rate, 1, 512)); + let delay = (*latency).get.unwrap()(plugin) as usize; + // About 80 ms of lookahead: two periods of the lowest detected note. + assert!((delay as f64 / rate - 0.08).abs() < 0.01, "{delay}"); + assert!(p.start_processing.unwrap()(plugin)); + let total = delay + 9600; + let mut position = 0; + let (mut energy_in, mut energy_out) = (0f64, 0f64); + while position < total { + let frames = [1, 127, 512, 31][position % 4].min(total - position); + // A 1 kHz tone sits above the split and passes through unchanged. + let tone = |n: usize| { + 0.25 * (std::f64::consts::TAU * 1000. * n as f64 / rate).sin() as f32 + }; + let mut input_left = [0f32; 512]; + let mut input_right = [0f32; 512]; + for i in 0..frames { + input_left[i] = tone(position + i); + input_right[i] = -tone(position + i); + } + let mut left = [0f32; 512]; + let mut right = [0f32; 512]; + let mut in_channels = [input_left.as_mut_ptr(), input_right.as_mut_ptr()]; + let mut out_channels = [left.as_mut_ptr(), right.as_mut_ptr()]; + let incoming = clap_audio_buffer { + data32: in_channels.as_mut_ptr(), + data64: ptr::null_mut(), + channel_count: 2, + latency: 0, + constant_mask: 0, + }; + let mut outgoing_audio = clap_audio_buffer { + data32: out_channels.as_mut_ptr(), + data64: ptr::null_mut(), + channel_count: 2, + latency: 0, + constant_mask: 0, + }; + let input = clap_input_events { + ctx: ptr::null_mut(), + size: Some(count), + get: Some(event), + }; + let outgoing = clap_output_events { + ctx: ptr::null_mut(), + try_push: Some(push), + }; + let process = clap_process { + steady_time: position as i64, + frames_count: frames as u32, + transport: ptr::null(), + audio_inputs: &incoming, + audio_outputs: &mut outgoing_audio, + audio_inputs_count: 1, + audio_outputs_count: 1, + in_events: &input, + out_events: &outgoing, + }; + assert_ne!(p.process.unwrap()(plugin, &process), CLAP_PROCESS_ERROR); + for i in 0..frames { + let n = position + i; + if n < delay { + assert!(left[i].abs() < 1e-6, "output before the lookahead"); + } else if n > delay + 4800 { + energy_in += (tone(n - delay) as f64).powi(2); + energy_out += (left[i] as f64).powi(2) + (right[i] as f64).powi(2); + } + } + position += frames; + } + let ratio_db = 10. * (energy_out / (2. * energy_in)).log10(); + assert!(ratio_db.abs() < 0.2, "{ratio_db}"); + p.stop_processing.unwrap()(plugin); + p.deactivate.unwrap()(plugin); + } + p.destroy.unwrap()(plugin); + entry.deinit.unwrap()(); + } +} diff --git a/plugins/anvil/ui/Cargo.toml b/plugins/anvil/ui/Cargo.toml new file mode 100644 index 0000000..e3272e9 --- /dev/null +++ b/plugins/anvil/ui/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "anvil-ui" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +egui = "0.31.1" +anvil-core = { path = "../core" } +common-ui = { path = "../../common/ui" } +common-dsp = { path = "../../common/dsp" } diff --git a/plugins/anvil/ui/src/lib.rs b/plugins/anvil/ui/src/lib.rs new file mode 100644 index 0000000..cb12748 --- /dev/null +++ b/plugins/anvil/ui/src/lib.rs @@ -0,0 +1,350 @@ +//! Anvil editor, shared by the CLAP wrapper and the headless renderer. +use anvil_core::{AnalyzerFrame, Settings, Source, SCOPE}; +use common_ui::*; +use egui::{self, Color32, FontId, Pos2, RichText, Sense, Stroke, Vec2}; + +pub use common_ui::style; +pub const WIDTH: u32 = 980; +pub const HEIGHT: u32 = 704; + +pub struct State { + pub solo: bool, + pub analyzer: AnalyzerFrame, + /// True while a pointer button is held, so knob drags become one host gesture. + pub gesture_active: bool, + pub help: String, +} +impl Default for State { + fn default() -> Self { + Self { + solo: false, + analyzer: AnalyzerFrame::default(), + gesture_active: false, + help: DEFAULT_HELP.into(), + } + } +} +const DEFAULT_HELP: &str = + "Put Anvil on the bass, set Split just above the sub, then set Sub level once for every patch."; +impl State { + pub fn accept_frame(&mut self, frame: AnalyzerFrame) { + let old = self.analyzer; + self.analyzer = frame; + // Meters fall smoothly; the readout and scope stay exact. + self.analyzer.low_db = frame.low_db.max(old.low_db - 1.5); + self.analyzer.sub_db = frame.sub_db.max(old.sub_db - 1.5); + } +} +fn hint(r: &egui::Response, state: &mut State, text: &str) { + if r.hovered() || r.has_focus() { + state.help = text.into(); + } +} +#[allow(clippy::too_many_arguments)] +fn knob( + ui: &mut egui::Ui, + state: &mut State, + name: &str, + value: &mut f32, + range: std::ops::RangeInclusive, + default: f32, + unit: &str, + size: f32, + help: &str, +) { + ui.vertical_centered(|ui| { + let r = rotary(ui, name, value, range, default, unit, size, ACCENT); + hint(&r, state, help); + }); +} +fn toggle(ui: &mut egui::Ui, state: &mut State, on: &mut bool, label: &str, help: &str) { + let r = ui.selectable_label(*on, RichText::new(label).size(13.)); + if r.clicked() { + *on = !*on; + } + hint(&r, state, help); +} +fn scope(ui: &mut egui::Ui, frame: &AnalyzerFrame, height: f32) { + let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), height), Sense::hover()); + let p = ui.painter_at(rect); + p.rect_filled(rect, 2., SCREEN); + let mid = rect.center().y; + for k in 1..6 { + let x = rect.left() + rect.width() * k as f32 / 6.; + p.line_segment( + [Pos2::new(x, rect.top()), Pos2::new(x, rect.bottom())], + Stroke::new(1.0_f32, Color32::from_rgb(24, 26, 30)), + ); + } + p.line_segment( + [Pos2::new(rect.left(), mid), Pos2::new(rect.right(), mid)], + Stroke::new(1.0_f32, EDGE), + ); + let peak = frame + .scope_low + .iter() + .chain(frame.scope_sub.iter()) + .fold(0.05f32, |m, v| m.max(v.abs())); + let scale = rect.height() * 0.42 / peak; + for (data, color, width) in [ + (&frame.scope_low, STEEL, 1.5_f32), + (&frame.scope_sub, ACCENT, 2.0_f32), + ] { + let points: Vec = data + .iter() + .enumerate() + .map(|(i, v)| { + Pos2::new( + rect.left() + rect.width() * i as f32 / (SCOPE - 1) as f32, + mid - v * scale, + ) + }) + .collect(); + p.add(egui::Shape::line(points, Stroke::new(width, color))); + } + p.text( + rect.left_top() + Vec2::new(10., 8.), + egui::Align2::LEFT_TOP, + "ORIGINAL LOW BAND", + FontId::proportional(11.), + STEEL, + ); + p.text( + rect.left_top() + Vec2::new(130., 8.), + egui::Align2::LEFT_TOP, + "NEW SUB", + FontId::proportional(11.), + ACCENT, + ); + p.text( + rect.right_top() + Vec2::new(-10., 8.), + egui::Align2::RIGHT_TOP, + format!("{:.0} ms", frame.scope_ms), + FontId::proportional(11.), + MUTED, + ); + p.rect_stroke( + rect, + 2., + Stroke::new(1.0_f32, EDGE), + egui::StrokeKind::Inside, + ); +} + +pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) { + backdrop(ui); + state.help = DEFAULT_HELP.into(); + state.gesture_active = ui.input(|i| i.pointer.any_down()); + ui.add_space(2.); + header(ui, "ANVIL", "SUB ANCHOR", |ui| { + let r = ui + .selectable_label(settings.bypass, RichText::new("BYPASS").size(13.)) + .on_hover_text("Crossfade to the delayed dry signal."); + if r.clicked() { + settings.bypass = !settings.bypass; + } + let r = ui + .selectable_label(state.solo, RichText::new("SOLO SUB").size(13.)) + .on_hover_text("Hear only the generated sub."); + if r.clicked() { + state.solo = !state.solo; + } + ui.separator(); + for (source, label) in [(Source::Midi, "MIDI"), (Source::Audio, "AUDIO")] { + let r = ui.selectable_label(settings.source == source, RichText::new(label).size(13.)); + if r.clicked() { + settings.source = source; + } + } + caption(ui, "PITCH FROM"); + }); + ui.add_space(6.); + let frame = state.analyzer; + ui.horizontal_top(|ui| { + panel(ui, |ui| { + ui.set_width(260.); + ui.set_min_height(TOP); + ui.vertical(|ui| note_panel(ui, settings, &frame)); + }); + ui.add_space(4.); + panel(ui, |ui| { + ui.set_width(ui.available_width()); + scope(ui, &frame, TOP); + }); + }); + ui.add_space(6.); + panel(ui, |ui| { + ui.set_width(ui.available_width()); + ui.vertical(|ui| controls(ui, settings, state)); + }); + ui.separator(); + ui.label(RichText::new(&state.help).color(MUTED)); +} +const TOP: f32 = 196.; +fn note_panel(ui: &mut egui::Ui, settings: &Settings, frame: &AnalyzerFrame) { + caption(ui, "NOTE"); + let (name, hz) = match frame.note { + Some(n) if frame.hz > 0. => (common_dsp::note_name(n), format!("{:.1} Hz", frame.hz)), + _ => ("—".into(), "no note".into()), + }; + ui.label( + RichText::new(name) + .font(FontId::new(54., egui::FontFamily::Name("medium".into()))) + .color(if frame.note.is_some() { TEXT } else { DIM }), + ); + ui.label(RichText::new(hz).monospace().size(14.).color(MUTED)); + ui.add_space(10.); + meter(ui, "ORIGINAL", frame.low_db, STEEL, 100.); + meter(ui, "NEW SUB", frame.sub_db, ACCENT, 100.); + ui.add_space(10.); + ui.label( + RichText::new(format!( + "{:.0} ms lookahead · {}", + frame.latency as f32 / frame.sample_rate * 1000., + match settings.source { + Source::Audio => "detecting pitch", + Source::Midi => "following MIDI notes", + } + )) + .size(12.) + .color(MUTED), + ); +} +fn controls(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) { + let defaults = Settings::default(); + ui.columns(4, |c| { + knob( + &mut c[0], + state, + "Sub level", + &mut settings.level, + -30. ..=0., + defaults.level, + " dBFS", + 88., + "Peak level of the new sub. Every note gets exactly this level.", + ); + knob( + &mut c[1], + state, + "Split", + &mut settings.split, + 40. ..=200., + defaults.split, + " Hz", + 88., + "Below this frequency the original sub is replaced.", + ); + knob( + &mut c[2], + state, + "Dynamics", + &mut settings.dynamics, + 0. ..=100., + defaults.dynamics, + " %", + 88., + "0 %: constant level per note. Higher: follow the original's wobble shape.", + ); + knob( + &mut c[3], + state, + "Drive", + &mut settings.drive, + 0. ..=100., + defaults.drive, + " %", + 88., + "Adds upper harmonics so the sub reads on small speakers.", + ); + }); + ui.add_space(6.); + ui.separator(); + ui.columns(6, |c| { + let mut octave = settings.octave as f32; + knob( + &mut c[0], + state, + "Octave", + &mut octave, + -2. ..=1., + 0., + "", + 54., + "Shift the sub against the detected note. −1 for basses played high.", + ); + settings.octave = octave.round() as i32; + knob( + &mut c[1], + state, + "Glide", + &mut settings.glide, + 0. ..=200., + defaults.glide, + " ms", + 54., + "Slide time when notes overlap. 0 jumps.", + ); + knob( + &mut c[2], + state, + "Phase", + &mut settings.phase, + 0. ..=360., + defaults.phase, + "°", + 54., + "Start phase of every note. Line it up with your kick.", + ); + knob( + &mut c[3], + state, + "Gate", + &mut settings.gate, + -60. ..=-20., + defaults.gate, + " dB", + 54., + "Input level that counts as a note. Raise it if tails retrigger.", + ); + knob( + &mut c[4], + state, + "Keep", + &mut settings.keep, + 0. ..=100., + defaults.keep, + " %", + 54., + "Blend some original low band back under the new sub.", + ); + knob( + &mut c[5], + state, + "Output", + &mut settings.output, + -12. ..=12., + defaults.output, + " dB", + 54., + "Output trim.", + ); + }); + ui.add_space(4.); + ui.horizontal(|ui| { + toggle( + ui, + state, + &mut settings.retrigger, + "RETRIGGER", + "Restart the sine at Phase on every note, so each note and kick hit lines up.", + ); + toggle( + ui, + state, + &mut settings.snap, + "SNAP TO SEMITONE", + "Round the detected pitch. Turn off for slides and pitch bends.", + ); + }); +} diff --git a/plugins/catalog.json b/plugins/catalog.json index 751b27e..f4d99c3 100644 --- a/plugins/catalog.json +++ b/plugins/catalog.json @@ -22,12 +22,47 @@ "documentation": "docs/patina", "clap_id": "io.storyboard.resonance-compressor", "licenses": { - "plugins/patina/ui/assets/OFL.txt": "licenses/Roboto-OFL.txt" + "plugins/common/ui/assets/OFL.txt": "licenses/Roboto-OFL.txt" }, "packages": [ + "common-ui", "patina-core", "patina-ui", "patina-render" ] + }, + "anvil": { + "name": "Anvil", + "description": "Replace the sub with a clean sine at one level and phase for every note", + "manifest": "plugins/anvil/plugin/Cargo.toml", + "library": "anvil", + "documentation": "docs/anvil", + "clap_id": "io.storyboard.sub-anchor", + "licenses": { + "plugins/common/ui/assets/OFL.txt": "licenses/Roboto-OFL.txt" + }, + "packages": [ + "common-ui", + "common-dsp", + "anvil-core", + "anvil-ui" + ] + }, + "crucible": { + "name": "Crucible", + "description": "Capture the last bars, slice them and export clean, key-labelled samples", + "manifest": "plugins/crucible/plugin/Cargo.toml", + "library": "crucible", + "documentation": "docs/crucible", + "clap_id": "io.storyboard.resampler", + "licenses": { + "plugins/common/ui/assets/OFL.txt": "licenses/Roboto-OFL.txt" + }, + "packages": [ + "common-ui", + "common-dsp", + "crucible-core", + "crucible-ui" + ] } } diff --git a/plugins/common/dsp/Cargo.toml b/plugins/common/dsp/Cargo.toml new file mode 100644 index 0000000..80c1434 --- /dev/null +++ b/plugins/common/dsp/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "common-dsp" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] diff --git a/plugins/common/dsp/src/lib.rs b/plugins/common/dsp/src/lib.rs new file mode 100644 index 0000000..9a2b878 --- /dev/null +++ b/plugins/common/dsp/src/lib.rs @@ -0,0 +1,263 @@ +//! Shared allocation-free DSP building blocks: filters, pitch detection, notes, WAV files. +pub mod wav; + +use std::f32::consts::TAU; + +/// Transposed direct form II biquad (RBJ cookbook coefficients). +#[derive(Clone, Copy, Debug, Default)] +pub struct Biquad { + b0: f32, + b1: f32, + b2: f32, + a1: f32, + a2: f32, + z1: f32, + z2: f32, +} +impl Biquad { + fn from(b: [f32; 3], a: [f32; 3]) -> Self { + Self { + b0: b[0] / a[0], + b1: b[1] / a[0], + b2: b[2] / a[0], + a1: a[1] / a[0], + a2: a[2] / a[0], + z1: 0., + z2: 0., + } + } + pub fn lowpass(rate: f32, hz: f32, q: f32) -> Self { + let w = TAU * (hz / rate).clamp(1e-5, 0.49); + let (s, c) = w.sin_cos(); + let alpha = s / (2. * q); + Self::from( + [(1. - c) / 2., 1. - c, (1. - c) / 2.], + [1. + alpha, -2. * c, 1. - alpha], + ) + } + pub fn highpass(rate: f32, hz: f32, q: f32) -> Self { + let w = TAU * (hz / rate).clamp(1e-5, 0.49); + let (s, c) = w.sin_cos(); + let alpha = s / (2. * q); + Self::from( + [(1. + c) / 2., -(1. + c), (1. + c) / 2.], + [1. + alpha, -2. * c, 1. - alpha], + ) + } + /// Peaking EQ; used by tests to model resonant material. + pub fn peak(rate: f32, hz: f32, q: f32, gain_db: f32) -> Self { + let w = TAU * (hz / rate).clamp(1e-5, 0.49); + let (s, c) = w.sin_cos(); + let a = 10f32.powf(gain_db / 40.); + let alpha = s / (2. * q); + Self::from( + [1. + alpha * a, -2. * c, 1. - alpha * a], + [1. + alpha / a, -2. * c, 1. - alpha / a], + ) + } + /// Replace the coefficients and keep the filter state, for smooth parameter changes. + pub fn retune(&mut self, other: Self) { + let (z1, z2) = (self.z1, self.z2); + *self = other; + self.z1 = z1; + self.z2 = z2; + } + #[inline] + pub fn process(&mut self, x: f32) -> f32 { + let y = self.b0 * x + self.z1; + self.z1 = self.b1 * x - self.a1 * y + self.z2; + self.z2 = self.b2 * x - self.a2 * y; + y + } + pub fn reset(&mut self) { + self.z1 = 0.; + self.z2 = 0.; + } +} + +/// Fourth-order Linkwitz–Riley crossover. Low plus high is an allpass: flat magnitude. +#[derive(Clone, Copy, Debug, Default)] +pub struct Crossover { + low: [Biquad; 2], + high: [Biquad; 2], +} +impl Crossover { + pub fn new(rate: f32, hz: f32) -> Self { + let q = std::f32::consts::FRAC_1_SQRT_2; + Self { + low: [Biquad::lowpass(rate, hz, q); 2], + high: [Biquad::highpass(rate, hz, q); 2], + } + } + pub fn retune(&mut self, rate: f32, hz: f32) { + let fresh = Self::new(rate, hz); + for i in 0..2 { + self.low[i].retune(fresh.low[i]); + self.high[i].retune(fresh.high[i]); + } + } + /// Returns (low, high). + #[inline] + pub fn process(&mut self, x: f32) -> (f32, f32) { + let low = self.low[0].process(x); + let low = self.low[1].process(low); + let high = self.high[0].process(x); + let high = self.high[1].process(high); + // LR4 bands are in phase at the crossover point, so they sum to an allpass. + (low, high) + } + pub fn reset(&mut self) { + for f in self.low.iter_mut().chain(self.high.iter_mut()) { + f.reset(); + } + } +} + +/// YIN fundamental-frequency estimator with preallocated scratch. +pub struct Yin { + diff: Vec, + pub threshold: f32, +} +impl Yin { + /// `max_lag` is the longest period in samples that will ever be requested. + pub fn new(max_lag: usize) -> Self { + Self { + diff: vec![0.; max_lag + 2], + threshold: 0.15, + } + } + pub fn max_lag(&self) -> usize { + self.diff.len() - 2 + } + /// Estimates the fundamental of `x` in Hz with its clarity (1 = perfectly periodic). + /// `x` must hold at least twice the longest period. Never allocates. + pub fn estimate( + &mut self, + x: &[f32], + rate: f32, + min_hz: f32, + max_hz: f32, + ) -> Option<(f32, f32)> { + let hi = ((rate / min_hz).ceil() as usize).min(self.max_lag()); + let lo = ((rate / max_hz).floor() as usize).max(2); + if hi <= lo + 2 || x.len() < hi + 2 { + return None; + } + let window = x.len() - hi - 1; + if window < hi { + return None; + } + let energy: f32 = x[..window].iter().map(|v| v * v).sum(); + if energy < 1e-9 * window as f32 { + return None; + } + let d = &mut self.diff; + d[0] = 1.; + let mut running = 0.; + for tau in 1..=hi + 1 { + let mut s = 0.; + for j in 0..window { + let e = x[j] - x[j + tau]; + s += e * e; + } + running += s; + // Cumulative-mean normalisation: suppresses the zero-lag dip. + d[tau] = if running > 0. { + s * tau as f32 / running + } else { + 1. + }; + } + let mut best = None; + let mut tau = lo; + while tau < hi { + if d[tau] < self.threshold { + while tau + 1 < hi && d[tau + 1] < d[tau] { + tau += 1; + } + best = Some(tau); + break; + } + tau += 1; + } + let tau = match best { + Some(t) => t, + None => { + let (t, v) = (lo..hi) + .map(|t| (t, d[t])) + .min_by(|a, b| a.1.total_cmp(&b.1))?; + if v > 0.35 { + return None; + } + t + } + }; + let (a, b, c) = (d[tau - 1], d[tau], d[tau + 1]); + let denom = a - 2. * b + c; + let shift = if denom.abs() > 1e-12 { + (0.5 * (a - c) / denom).clamp(-1., 1.) + } else { + 0. + }; + Some((rate / (tau as f32 + shift), (1. - b).clamp(0., 1.))) + } +} + +pub fn midi_to_hz(note: f32) -> f32 { + 440. * 2f32.powf((note - 69.) / 12.) +} +pub fn hz_to_midi(hz: f32) -> f32 { + 69. + 12. * (hz.max(1e-3) / 440.).log2() +} +/// Scientific note name: MIDI 60 = C4, 43.65 Hz = F1. FL Studio labels MIDI 60 as C5. +pub fn note_name(note: u8) -> String { + const NAMES: [&str; 12] = [ + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", + ]; + format!("{}{}", NAMES[note as usize % 12], note as i32 / 12 - 1) +} +pub fn db_to_gain(db: f32) -> f32 { + 10f32.powf(db / 20.) +} +pub fn gain_to_db(g: f32) -> f32 { + 20. * g.abs().max(1e-9).log10() +} +/// One-pole smoothing coefficient for a time constant in milliseconds. +pub fn coefficient(rate: f32, ms: f32) -> f32 { + if ms <= 0. { + 1. + } else { + 1. - (-1. / (rate * ms * 0.001)).exp() + } +} +/// PolyBLEP sawtooth oscillator: cheap and close to band-limited, for tests and previews. +pub struct Saw { + phase: f32, + inc: f32, +} +impl Saw { + pub fn new(rate: f32, hz: f32) -> Self { + Self { + phase: 0., + inc: hz / rate, + } + } + pub fn sample(&mut self) -> f32 { + let t = self.phase; + let dt = self.inc; + let blep = if t < dt { + let x = t / dt; + x + x - x * x - 1. + } else if t > 1. - dt { + let x = (t - 1.) / dt; + x * x + x + x + 1. + } else { + 0. + }; + self.phase += dt; + if self.phase >= 1. { + self.phase -= 1.; + } + 2. * t - 1. - blep + } +} diff --git a/plugins/common/dsp/src/wav.rs b/plugins/common/dsp/src/wav.rs new file mode 100644 index 0000000..fd24caa --- /dev/null +++ b/plugins/common/dsp/src/wav.rs @@ -0,0 +1,142 @@ +//! Minimal RIFF/WAVE writer and reader: 32-bit float data, sampler root key, Serum wavetable tag. +use std::{io, path::Path}; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct Wave { + pub channels: u16, + pub rate: u32, + /// Interleaved samples. + pub samples: Vec, + /// MIDI root key, written as a `smpl` chunk that samplers (FL included) read. + pub root: Option, + /// Wavetable frame size, written as Serum's `clm ` chunk. + pub frame: Option, +} + +fn chunk(out: &mut Vec, id: &[u8; 4], body: &[u8]) { + out.extend_from_slice(id); + out.extend_from_slice(&(body.len() as u32).to_le_bytes()); + out.extend_from_slice(body); + if body.len() % 2 == 1 { + out.push(0); + } +} + +pub fn encode(w: &Wave) -> Vec { + let channels = w.channels.max(1); + let frames = w.samples.len() / channels as usize; + let mut body = Vec::with_capacity(w.samples.len() * 4 + 256); + body.extend_from_slice(b"WAVE"); + let mut fmt = Vec::new(); + fmt.extend_from_slice(&3u16.to_le_bytes()); // IEEE float + fmt.extend_from_slice(&channels.to_le_bytes()); + fmt.extend_from_slice(&w.rate.to_le_bytes()); + fmt.extend_from_slice(&(w.rate * 4 * channels as u32).to_le_bytes()); + fmt.extend_from_slice(&(4 * channels).to_le_bytes()); + fmt.extend_from_slice(&32u16.to_le_bytes()); + fmt.extend_from_slice(&0u16.to_le_bytes()); + chunk(&mut body, b"fmt ", &fmt); + chunk(&mut body, b"fact", &(frames as u32).to_le_bytes()); + if let Some(frame) = w.frame { + // Serum reads the frame size from the "NNNN" prefix. + chunk( + &mut body, + b"clm ", + format!("{frame} 00000000 wavetable (Crucible)").as_bytes(), + ); + } + if let Some(root) = w.root { + let mut smpl = Vec::new(); + let period = (1e9 / w.rate.max(1) as f64).round() as u32; + for v in [0, 0, period, root as u32, 0, 0, 0, 0, 0] { + smpl.extend_from_slice(&v.to_le_bytes()); + } + chunk(&mut body, b"smpl", &smpl); + } + let mut data = Vec::with_capacity(w.samples.len() * 4); + for s in &w.samples[..frames * channels as usize] { + data.extend_from_slice(&s.to_le_bytes()); + } + chunk(&mut body, b"data", &data); + let mut out = Vec::with_capacity(body.len() + 8); + out.extend_from_slice(b"RIFF"); + out.extend_from_slice(&(body.len() as u32).to_le_bytes()); + out.extend_from_slice(&body); + out +} + +pub fn write(path: &Path, w: &Wave) -> io::Result<()> { + std::fs::write(path, encode(w)) +} + +/// Reads float32 or PCM16/24 files written by this module or common tools. +pub fn decode(b: &[u8]) -> Result { + if b.len() < 12 || &b[..4] != b"RIFF" || &b[8..12] != b"WAVE" { + return Err("expected RIFF/WAVE"); + } + let mut w = Wave::default(); + let (mut format, mut bits) = (0u16, 0u16); + let mut at = 12; + let mut data = false; + let u16_at = |o: usize| u16::from_le_bytes([b[o], b[o + 1]]); + let u32_at = |o: usize| u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]); + while at + 8 <= b.len() { + let id = &b[at..at + 4]; + let len = u32_at(at + 4) as usize; + let start = at + 8; + let end = start + .checked_add(len) + .filter(|e| *e <= b.len()) + .ok_or("truncated chunk")?; + match id { + b"fmt " if len >= 16 => { + format = u16_at(start); + w.channels = u16_at(start + 2); + w.rate = u32_at(start + 4); + bits = u16_at(start + 14); + } + b"smpl" if len >= 16 => w.root = Some(u32_at(start + 12) as u8), + b"clm " => { + let text = std::str::from_utf8(&b[start..end]).map_err(|_| "bad clm chunk")?; + w.frame = text + .strip_prefix("") + .and_then(|t| t.split_whitespace().next()) + .and_then(|n| n.parse().ok()); + } + b"data" => { + data = true; + let d = &b[start..end]; + w.samples = match (format, bits) { + (3, 32) => d + .as_chunks::<4>() + .0 + .iter() + .map(|c| f32::from_le_bytes(*c)) + .collect(), + (1, 16) => d + .as_chunks::<2>() + .0 + .iter() + .map(|c| i16::from_le_bytes(*c) as f32 / 32768.) + .collect(), + (1, 24) => d + .as_chunks::<3>() + .0 + .iter() + .map(|c| (i32::from_le_bytes([0, c[0], c[1], c[2]]) >> 8) as f32 / 8388608.) + .collect(), + _ => return Err("unsupported sample format"), + }; + } + _ => {} + } + at = end + (len % 2); + } + if w.channels == 0 || w.rate == 0 { + return Err("missing fmt chunk"); + } + if !data { + return Err("missing data chunk"); + } + Ok(w) +} diff --git a/plugins/common/dsp/tests/dsp.rs b/plugins/common/dsp/tests/dsp.rs new file mode 100644 index 0000000..94fa182 --- /dev/null +++ b/plugins/common/dsp/tests/dsp.rs @@ -0,0 +1,82 @@ +use common_dsp::{hz_to_midi, midi_to_hz, note_name, wav, Crossover, Saw, Yin}; + +fn cents(a: f32, b: f32) -> f32 { + 1200. * (a / b).log2() +} + +#[test] +fn yin_tracks_bass_fundamentals_within_two_cents() { + let rate = 48000.; + let mut yin = Yin::new(2400); + for hz in [43.65, 55., 110., 220.] { + let mut saw = Saw::new(rate, hz); + let x: Vec = (0..6000).map(|_| saw.sample()).collect(); + let (got, clarity) = yin.estimate(&x, rate, 25., 1000.).unwrap(); + assert!(cents(got, hz).abs() < 2., "{hz}: {got}"); + assert!(clarity > 0.8); + } +} + +#[test] +fn yin_rejects_silence_and_noise() { + let mut yin = Yin::new(2400); + assert!(yin.estimate(&[0.; 6000], 48000., 25., 1000.).is_none()); + let mut seed = 1u32; + let noise: Vec = (0..6000) + .map(|_| { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + (seed >> 8) as f32 / (1u32 << 24) as f32 - 0.5 + }) + .collect(); + assert!(yin.estimate(&noise, 48000., 25., 1000.).is_none()); +} + +#[test] +fn crossover_bands_sum_to_flat_magnitude() { + let rate = 48000.; + for hz in [20., 45., 90., 180., 1000., 8000., 18000.] { + let mut x = Crossover::new(rate, 90.); + let (mut a, mut b) = (0f64, 0f64); + let n = 96000; + for i in 0..n { + let s = (std::f32::consts::TAU * hz * i as f32 / rate).sin(); + let (l, h) = x.process(s); + if i > n / 2 { + a += (s as f64).powi(2); + b += ((l + h) as f64).powi(2); + } + } + let db = 10. * (b / a).log10(); + assert!(db.abs() < 0.1, "{hz} Hz: {db} dB"); + } +} + +#[test] +fn notes_round_trip() { + assert!((midi_to_hz(29.) - 43.65).abs() < 0.01); + assert!((hz_to_midi(440.) - 69.).abs() < 1e-4); + assert_eq!(note_name(29), "F1"); + assert_eq!(note_name(61), "C#4"); +} + +#[test] +fn wave_round_trip_keeps_root_and_wavetable_chunks() { + let w = wav::Wave { + channels: 2, + rate: 44100, + samples: (0..999) + .map(|i| (i as f32 * 0.01).sin()) + .collect::>()[..998] + .to_vec(), + root: Some(29), + frame: Some(2048), + }; + let bytes = wav::encode(&w); + assert_eq!(&bytes[..4], b"RIFF"); + assert_eq!( + u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize, + bytes.len() - 8 + ); + assert_eq!(wav::decode(&bytes).unwrap(), w); + assert!(wav::decode(&bytes[..40]).is_err()); +} diff --git a/plugins/common/ui/Cargo.toml b/plugins/common/ui/Cargo.toml new file mode 100644 index 0000000..28da044 --- /dev/null +++ b/plugins/common/ui/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "common-ui" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +egui = "0.31.1" diff --git a/plugins/patina/ui/assets/OFL.txt b/plugins/common/ui/assets/OFL.txt similarity index 100% rename from plugins/patina/ui/assets/OFL.txt rename to plugins/common/ui/assets/OFL.txt diff --git a/plugins/patina/ui/assets/RobotoCondensed-Medium.ttf b/plugins/common/ui/assets/RobotoCondensed-Medium.ttf similarity index 100% rename from plugins/patina/ui/assets/RobotoCondensed-Medium.ttf rename to plugins/common/ui/assets/RobotoCondensed-Medium.ttf diff --git a/plugins/patina/ui/assets/RobotoCondensed-Regular.ttf b/plugins/common/ui/assets/RobotoCondensed-Regular.ttf similarity index 100% rename from plugins/patina/ui/assets/RobotoCondensed-Regular.ttf rename to plugins/common/ui/assets/RobotoCondensed-Regular.ttf diff --git a/plugins/common/ui/src/lib.rs b/plugins/common/ui/src/lib.rs new file mode 100644 index 0000000..84bf837 --- /dev/null +++ b/plugins/common/ui/src/lib.rs @@ -0,0 +1,320 @@ +//! Shared hardware look for every plugin editor: palette, fonts, knobs, panels. +use egui::{self, Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, Vec2}; + +// Dark anodized hardware: graphite surfaces, one signal colour, steel data traces. +pub const BG: Color32 = Color32::from_rgb(14, 15, 17); +pub const PANEL: Color32 = Color32::from_rgb(25, 27, 31); +pub const PANEL_TOP: Color32 = Color32::from_rgb(34, 36, 41); +pub const EDGE: Color32 = Color32::from_rgb(44, 47, 53); +pub const SCREEN: Color32 = Color32::from_rgb(9, 10, 11); +pub const TEXT: Color32 = Color32::from_rgb(232, 233, 236); +pub const MUTED: Color32 = Color32::from_rgb(140, 146, 156); +pub const DIM: Color32 = Color32::from_rgb(70, 74, 82); +/// Signal colour: cuts, active states, value arcs. +pub const ACCENT: Color32 = Color32::from_rgb(255, 1, 96); +/// Measured input and neutral data. +pub const STEEL: Color32 = Color32::from_rgb(184, 192, 204); +/// Focus / protect curve, distinct from cuts without competing with them. +pub const FOCUS: Color32 = Color32::from_rgb(126, 167, 216); +pub fn style(ctx: &egui::Context) { + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "regular".into(), + egui::FontData::from_static(include_bytes!("../assets/RobotoCondensed-Regular.ttf")).into(), + ); + fonts.font_data.insert( + "medium".into(), + egui::FontData::from_static(include_bytes!("../assets/RobotoCondensed-Medium.ttf")).into(), + ); + fonts + .families + .get_mut(&egui::FontFamily::Proportional) + .unwrap() + .insert(0, "regular".into()); + fonts.families.insert( + egui::FontFamily::Name("medium".into()), + vec![ + "medium".into(), + "Ubuntu-Light".into(), + "NotoEmoji-Regular".into(), + "emoji-icon-font".into(), + ], + ); + ctx.set_fonts(fonts); + let mut s = (*ctx.style()).clone(); + let v = &mut s.visuals; + *v = egui::Visuals::dark(); + v.panel_fill = BG; + v.window_fill = PANEL; + v.extreme_bg_color = SCREEN; + v.faint_bg_color = PANEL; + v.override_text_color = Some(TEXT); + v.selection.bg_fill = Color32::from_rgb(110, 6, 46); + v.selection.stroke = Stroke::new(1.0_f32, ACCENT); + v.hyperlink_color = ACCENT; + v.window_stroke = Stroke::new(1.0_f32, EDGE); + for (w, fill, stroke) in [ + (&mut v.widgets.noninteractive, PANEL, EDGE), + (&mut v.widgets.inactive, Color32::from_rgb(35, 38, 43), EDGE), + ( + &mut v.widgets.hovered, + Color32::from_rgb(45, 48, 54), + Color32::from_rgb(78, 82, 90), + ), + (&mut v.widgets.active, Color32::from_rgb(58, 18, 34), ACCENT), + (&mut v.widgets.open, Color32::from_rgb(35, 38, 43), EDGE), + ] { + w.bg_fill = fill; + w.weak_bg_fill = fill; + w.bg_stroke = Stroke::new(1.0_f32, stroke); + w.corner_radius = 3.into(); + } + v.widgets.noninteractive.fg_stroke = Stroke::new(1.0_f32, TEXT); + v.widgets.inactive.fg_stroke = Stroke::new(1.0_f32, TEXT); + v.slider_trailing_fill = true; + s.spacing.item_spacing = Vec2::new(10., 8.); + s.spacing.button_padding = Vec2::new(12., 6.); + s.text_styles + .insert(egui::TextStyle::Body, FontId::proportional(16.)); + s.text_styles + .insert(egui::TextStyle::Button, FontId::proportional(16.)); + ctx.set_style(s); +} +/// Disc with a vertical light-to-dark gradient, like brushed metal lit from above. +pub fn metal_disc(p: &egui::Painter, center: Pos2, radius: f32, top: Color32, bottom: Color32) { + let mut mesh = egui::Mesh::default(); + let mix = |t: f32| { + let l = |a: u8, b: u8| (a as f32 + (b as f32 - a as f32) * t).round() as u8; + Color32::from_rgb( + l(top.r(), bottom.r()), + l(top.g(), bottom.g()), + l(top.b(), bottom.b()), + ) + }; + mesh.colored_vertex(center, mix(0.5)); + let n = 48; + for i in 0..=n { + let a = std::f32::consts::TAU * i as f32 / n as f32; + let point = center + Vec2::angled(a) * radius; + mesh.colored_vertex(point, mix((point.y - (center.y - radius)) / (2. * radius))); + if i > 0 { + mesh.add_triangle(0, i, i + 1); + } + } + p.add(egui::Shape::mesh(mesh)); +} +#[allow(clippy::too_many_arguments)] +pub fn rotary( + ui: &mut egui::Ui, + name: &str, + value: &mut f32, + range: std::ops::RangeInclusive, + default: f32, + unit: &str, + size: f32, + color: Color32, +) -> egui::Response { + let low = *range.start(); + let high = *range.end(); + let (rect, response) = ui.allocate_exact_size(Vec2::splat(size), Sense::click_and_drag()); + if response.drag_started() { + response.request_focus(); + } + if response.dragged() { + let delta = ui.input(|i| i.pointer.delta().y); + let fine = if ui.input(|i| i.modifiers.shift) { + 0.1 + } else { + 1. + }; + *value = (*value - delta * (high - low) / 180. * fine).clamp(low, high); + } + if response.double_clicked() { + *value = default; + } + if response.has_focus() { + let step = if ui.input(|i| i.modifiers.shift) { + 0.1 + } else { + 1. + }; + if ui.input(|i| i.key_pressed(egui::Key::ArrowUp) || i.key_pressed(egui::Key::ArrowRight)) { + *value = (*value + step).min(high); + } + if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) || i.key_pressed(egui::Key::ArrowLeft)) + { + *value = (*value - step).max(low); + } + } + let center = rect.center(); + let radius = size * 0.46; + let p = ui.painter(); + let start = std::f32::consts::PI * 0.75; + let span = std::f32::consts::PI * 1.5; + let normalized = ((*value - low) / (high - low)).clamp(0., 1.); + // Scale track and value arc around the knob. + for (fraction, stroke) in [ + (1., Stroke::new(2.5_f32, DIM)), + (normalized, Stroke::new(2.5_f32, color)), + ] { + // A zero-length polyline tessellates to NaN vertices. + if fraction < 1e-3 { + continue; + } + let points: Vec<_> = (0..=64) + .map(|i| { + let a = start + span * fraction * i as f32 / 64.; + center + Vec2::angled(a) * (radius - 1.5) + }) + .collect(); + p.add(egui::Shape::line(points, stroke)); + } + // Machined body: drop shadow, brushed gradient skirt, flat cap. + let body = radius - 7.; + p.circle_filled( + center + Vec2::new(0., 2.), + body + 1., + Color32::from_black_alpha(140), + ); + metal_disc( + p, + center, + body, + Color32::from_rgb(78, 82, 90), + Color32::from_rgb(28, 30, 34), + ); + p.circle_stroke( + center, + body, + Stroke::new(1.0_f32, Color32::from_rgb(10, 11, 12)), + ); + let cap = body * 0.74; + metal_disc( + p, + center, + cap, + Color32::from_rgb(44, 47, 52), + Color32::from_rgb(33, 35, 40), + ); + p.circle_stroke( + center, + cap, + Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, 22)), + ); + let angle = start + span * normalized; + p.line_segment( + [ + center + Vec2::angled(angle) * (cap * 0.35), + center + Vec2::angled(angle) * (cap - 2.), + ], + Stroke::new(2.0_f32, TEXT), + ); + if response.has_focus() { + p.circle_stroke(center, radius + 2., Stroke::new(1.0_f32, color)); + } + ui.label( + RichText::new(name.to_uppercase()) + .size(if size > 100. { 14. } else { 12. }) + .color(MUTED), + ); + ui.add( + egui::DragValue::new(value) + .range(range) + .speed(0.1) + .suffix(unit), + ); + response +} +/// Vertical gradient rectangle, used for panel faces. +pub fn gradient(rect: Rect, top: Color32, bottom: Color32) -> egui::Shape { + let mut mesh = egui::Mesh::default(); + for (pos, color) in [ + (rect.left_top(), top), + (rect.right_top(), top), + (rect.right_bottom(), bottom), + (rect.left_bottom(), bottom), + ] { + mesh.colored_vertex(pos, color); + } + mesh.add_triangle(0, 1, 2); + mesh.add_triangle(0, 2, 3); + egui::Shape::mesh(mesh) +} +/// Raised hardware panel: graphite face, hairline edge, faint top highlight. +pub fn panel(ui: &mut egui::Ui, add: impl FnOnce(&mut egui::Ui) -> R) -> R { + let face = ui.painter().add(egui::Shape::Noop); + let inner = egui::Frame::new() + .inner_margin(14.) + .stroke(Stroke::new(1.0_f32, EDGE)) + .show(ui, add); + let r = inner.response.rect; + ui.painter().set(face, gradient(r, PANEL_TOP, PANEL)); + ui.painter().line_segment( + [ + r.left_top() + Vec2::new(1., 1.), + r.right_top() + Vec2::new(-1., 1.), + ], + Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, 16)), + ); + inner.inner +} +pub fn caption(ui: &mut egui::Ui, text: &str) { + ui.label(RichText::new(text).size(11.).color(MUTED)); +} +/// Small labelled hardware-style readout. +pub fn readout(ui: &mut egui::Ui, label: &str, value: String, color: Color32) { + ui.label(RichText::new(label).size(11.).color(MUTED)); + ui.label(RichText::new(value).monospace().size(13.).color(color)); + ui.add_space(10.); +} +/// Product header: accent mark, product name, subtitle, then right-aligned controls. +pub fn header(ui: &mut egui::Ui, name: &str, subtitle: &str, right: impl FnOnce(&mut egui::Ui)) { + ui.horizontal(|ui| { + let (mark, _) = ui.allocate_exact_size(Vec2::new(4., 26.), Sense::hover()); + ui.painter().rect_filled(mark, 1., ACCENT); + ui.label( + RichText::new(name) + .font(FontId::new(26., egui::FontFamily::Name("medium".into()))) + .color(TEXT), + ); + ui.label(RichText::new(subtitle).size(12.).color(MUTED)); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), right); + }); +} +/// Full-window backdrop gradient used behind every editor. +pub fn backdrop(ui: &egui::Ui) { + ui.painter() + .add(gradient(ui.max_rect(), Color32::from_rgb(20, 21, 24), BG)); +} +/// Horizontal level bar in dBFS (−60..0) with a hairline frame. +pub fn meter(ui: &mut egui::Ui, label: &str, db: f32, color: Color32, width: f32) { + ui.horizontal(|ui| { + ui.add_sized( + Vec2::new(70., 16.), + egui::Label::new(RichText::new(label).size(11.).color(MUTED)), + ); + let (rect, _) = ui.allocate_exact_size(Vec2::new(width, 10.), Sense::hover()); + let p = ui.painter(); + p.rect_filled(rect, 1., SCREEN); + let t = ((db + 60.) / 60.).clamp(0., 1.); + let mut fill = rect; + fill.set_width(rect.width() * t); + p.rect_filled(fill, 1., color); + p.rect_stroke( + rect, + 1., + Stroke::new(1.0_f32, EDGE), + egui::StrokeKind::Inside, + ); + ui.label( + RichText::new(if db <= -60. { + "−∞".to_string() + } else { + format!("{db:.1} dB") + }) + .monospace() + .size(12.) + .color(TEXT), + ); + }); +} diff --git a/plugins/crucible/core/Cargo.toml b/plugins/crucible/core/Cargo.toml new file mode 100644 index 0000000..ff1d237 --- /dev/null +++ b/plugins/crucible/core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "crucible-core" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +common-dsp = { path = "../../common/dsp" } +crossbeam-queue = "0.3" +serde = { workspace = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/plugins/crucible/core/src/export.rs b/plugins/crucible/core/src/export.rs new file mode 100644 index 0000000..5cc83b7 --- /dev/null +++ b/plugins/crucible/core/src/export.rs @@ -0,0 +1,93 @@ +//! Writes a take as ready-to-use files: the full loop, one file per slice and a wavetable. +use crate::settings::Settings; +use crate::take::{render, wavetable, Take, TABLE}; +use common_dsp::{note_name, wav}; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone, PartialEq)] +pub struct Exported { + pub folder: PathBuf, + pub files: usize, + /// Why no wavetable was written although one was requested. + pub wavetable_skipped: Option<&'static str>, +} + +/// UTC date and time for folder names, from the Unix time in seconds. +pub fn stamp(unix: u64) -> String { + let days = (unix / 86400) as i64; + let secs = unix % 86400; + // Civil-from-days (H. Hinnant), proleptic Gregorian calendar. + let z = days + 719468; + let era = z.div_euclid(146097); + let doe = z - era * 146097; + let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let day = doy - (153 * mp + 2) / 5 + 1; + let month = if mp < 10 { mp + 3 } else { mp - 9 }; + let year = yoe + era * 400 + i64::from(month <= 2); + format!( + "{year:04}-{month:02}-{day:02}_{:02}{:02}{:02}", + secs / 3600, + secs / 60 % 60, + secs % 60 + ) +} + +pub fn export( + take: &Take, + settings: &Settings, + selected: Option, + root: &Path, + unix: u64, +) -> std::io::Result { + let folder = root.join(format!("{}_{:.0}bpm", stamp(unix), take.tempo())); + std::fs::create_dir_all(&folder)?; + let rate = take.rate.round() as u32; + let stereo = |samples: Vec, root: Option| wav::Wave { + channels: 2, + rate, + samples, + root, + frame: None, + }; + wav::write(&folder.join("loop.wav"), &stereo(take.audio.clone(), None))?; + let mut files = 1; + for (i, s) in take.slices.iter().enumerate().filter(|(_, s)| s.enabled) { + let name = match s.root { + Some(n) => format!("s{:02}_{}.wav", i + 1, note_name(n)), + None => format!("s{:02}.wav", i + 1), + }; + wav::write( + &folder.join(name), + &stereo(render(take, s, settings), s.root), + )?; + files += 1; + } + let mut wavetable_skipped = None; + if settings.wavetable { + let source = selected + .and_then(|i| take.slices.get(i)) + .or_else(|| take.slices.iter().find(|s| s.enabled && s.root.is_some())); + match source.map(|s| wavetable(&take.mono(s.start, s.end), take.rate)) { + Some(Some(table)) => { + let w = wav::Wave { + channels: 1, + rate, + samples: table, + root: None, + frame: Some(TABLE as u32), + }; + wav::write(&folder.join("wavetable.wav"), &w)?; + files += 1; + } + Some(None) => wavetable_skipped = Some("the slice has no clear pitch"), + None => wavetable_skipped = Some("no slice selected"), + } + } + Ok(Exported { + folder, + files, + wavetable_skipped, + }) +} diff --git a/plugins/crucible/core/src/lib.rs b/plugins/crucible/core/src/lib.rs new file mode 100644 index 0000000..028ef81 --- /dev/null +++ b/plugins/crucible/core/src/lib.rs @@ -0,0 +1,18 @@ +//! Resampler: always records the input, captures the newest bars on request, slices them, +//! cleans each slice and writes ready-to-use files. +mod export; +mod player; +mod ring; +mod settings; +mod take; +pub use export::{export, stamp, Exported}; +pub use player::{Clip, Player, Queues}; +pub use ring::{Marker, Recorder, Ring, Transport, MARKERS}; +pub use settings::{default_folder, Division, Mode, Settings}; +pub use take::{ + capture, render, root_key, transients, wavetable, Segment, Slice, Take, FALLBACK_TEMPO, + OVERVIEW, TABLE, TABLE_FRAMES, +}; + +/// Seconds of history: 16 bars at 60 BPM in 4/4, plus slack for the capture copy. +pub const HISTORY: f32 = 72.; diff --git a/plugins/crucible/core/src/player.rs b/plugins/crucible/core/src/player.rs new file mode 100644 index 0000000..04fe846 --- /dev/null +++ b/plugins/crucible/core/src/player.rs @@ -0,0 +1,73 @@ +//! Audition of slices on the audio thread without allocating or freeing there. +use crossbeam_queue::ArrayQueue; +use std::sync::Arc; + +pub type Clip = Arc<[f32]>; + +/// Shared queues. The GUI sends clips and drains finished ones, so all frees happen there. +pub struct Queues { + incoming: ArrayQueue>, + finished: ArrayQueue, +} +impl Default for Queues { + fn default() -> Self { + Self { + incoming: ArrayQueue::new(4), + finished: ArrayQueue::new(16), + } + } +} +impl Queues { + /// Plays an interleaved stereo clip, replacing the current one. + pub fn play(&self, clip: Clip) { + self.drain(); + let _ = self.incoming.force_push(Some(clip)); + } + pub fn stop(&self) { + let _ = self.incoming.force_push(None); + } + /// Frees finished clips. Call regularly from the GUI thread. + pub fn drain(&self) { + while self.finished.pop().is_some() {} + } +} + +#[derive(Default)] +pub struct Player { + clip: Option, + at: usize, +} +impl Player { + fn retire(&mut self, queues: &Queues) { + if let Some(old) = self.clip.take() { + // The GUI drains this queue every frame; a full queue only happens with no editor. + if let Err(old) = queues.finished.push(old) { + std::mem::forget(old); + } + } + } + /// Call once per block. + pub fn block(&mut self, queues: &Queues) { + while let Some(next) = queues.incoming.pop() { + self.retire(queues); + self.clip = next; + self.at = 0; + } + } + #[inline] + pub fn next(&mut self, queues: &Queues) -> [f32; 2] { + let Some(clip) = &self.clip else { + return [0.; 2]; + }; + if self.at + 1 >= clip.len() { + self.retire(queues); + return [0.; 2]; + } + let out = [clip[self.at], clip[self.at + 1]]; + self.at += 2; + out + } + pub fn playing(&self) -> bool { + self.clip.is_some() + } +} diff --git a/plugins/crucible/core/src/ring.rs b/plugins/crucible/core/src/ring.rs new file mode 100644 index 0000000..f55944b --- /dev/null +++ b/plugins/crucible/core/src/ring.rs @@ -0,0 +1,206 @@ +//! Lock-free history of the input plus the host transport, shared by the audio and GUI threads. +//! +//! The audio thread writes samples with relaxed stores and publishes the frame count with a +//! release store. The GUI thread reads the count with acquire, copies, then checks the count +//! again: anything the writer may have overwritten during the copy is reported, never returned. +use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; + +/// Transport markers kept for alignment; one is written at least every half second. +pub const MARKERS: usize = 512; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Transport { + pub playing: bool, + pub tempo: f64, + /// Song position in quarter notes at the first sample of the block. + pub beats: f64, + pub numerator: u32, + pub denominator: u32, +} +impl Transport { + /// Bar length in quarter notes. + pub fn bar(&self) -> f64 { + self.numerator.max(1) as f64 * 4. / self.denominator.max(1) as f64 + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Marker { + /// Absolute recorded frame index. + pub frame: u64, + pub transport: Transport, +} +impl Marker { + /// Song position at `frame`, extrapolated at this marker's tempo. + pub fn beats_at(&self, frame: f64, rate: f64) -> f64 { + self.transport.beats + (frame - self.frame as f64) / rate * self.transport.tempo / 60. + } + pub fn frame_at(&self, beats: f64, rate: f64) -> f64 { + self.frame as f64 + (beats - self.transport.beats) * 60. / self.transport.tempo * rate + } +} + +pub struct Ring { + rate: f32, + frames: usize, + data: Box<[AtomicU32]>, + written: AtomicU64, + markers: Box<[[AtomicU64; 4]]>, + marker_count: AtomicU64, + peak: AtomicU32, +} +impl Ring { + /// Stereo history of `seconds` at `rate`. + pub fn new(rate: f32, seconds: f32) -> Self { + let frames = (rate * seconds).ceil().max(1.) as usize; + Self { + rate, + frames, + data: (0..frames * 2).map(|_| AtomicU32::new(0)).collect(), + written: AtomicU64::new(0), + markers: (0..MARKERS) + .map(|_| std::array::from_fn(|_| AtomicU64::new(0))) + .collect(), + marker_count: AtomicU64::new(0), + peak: AtomicU32::new(0), + } + } + pub fn rate(&self) -> f32 { + self.rate + } + pub fn capacity(&self) -> usize { + self.frames + } + pub fn written(&self) -> u64 { + self.written.load(Ordering::Acquire) + } + /// Input peak since the last call, for the editor meter. + pub fn take_peak(&self) -> f32 { + f32::from_bits(self.peak.swap(0, Ordering::AcqRel)) + } + /// Markers that are certainly intact, oldest first. + pub fn markers(&self) -> Vec { + let count = self.marker_count.load(Ordering::Acquire); + // A margin keeps slots the writer may be refilling during this read out of the result. + let first = count.saturating_sub(MARKERS as u64 - 8); + let mut out: Vec = (first..count) + .map(|i| { + let s = &self.markers[(i % MARKERS as u64) as usize]; + let meta = s[3].load(Ordering::Relaxed); + Marker { + frame: s[0].load(Ordering::Relaxed), + transport: Transport { + beats: f64::from_bits(s[1].load(Ordering::Relaxed)), + tempo: f64::from_bits(s[2].load(Ordering::Relaxed)), + numerator: (meta & 0xffff) as u32, + denominator: ((meta >> 16) & 0xffff) as u32, + playing: meta >> 32 & 1 == 1, + }, + } + }) + .collect(); + let after = self.marker_count.load(Ordering::Acquire); + let safe = after.saturating_sub(MARKERS as u64 - 8); + out.drain(..(safe.saturating_sub(first) as usize).min(out.len())); + out + } + /// Copies frames `[start, end)` as interleaved stereo. Fails if any were overwritten. + pub fn copy(&self, start: u64, end: u64) -> Result, &'static str> { + let written = self.written(); + if end > written || start > end { + return Err("Capture range is not recorded yet."); + } + if written - start > self.frames as u64 { + return Err("Capture is longer than the recording buffer."); + } + let mut out = Vec::with_capacity(((end - start) * 2) as usize); + for f in start..end { + let at = (f % self.frames as u64) as usize * 2; + out.push(f32::from_bits(self.data[at].load(Ordering::Relaxed))); + out.push(f32::from_bits(self.data[at + 1].load(Ordering::Relaxed))); + } + if self.written() - start > self.frames as u64 { + return Err("Capture was overwritten while copying. Try again."); + } + Ok(out) + } +} + +/// Audio-thread writer. Never allocates. +pub struct Recorder { + last: Option, + frame: u64, + since_marker: u64, + peak: f32, +} +impl Default for Recorder { + fn default() -> Self { + Self::new() + } +} +impl Recorder { + pub fn new() -> Self { + Self { + last: None, + frame: 0, + since_marker: 0, + peak: 0., + } + } + /// Call at the start of every block with the host transport. + pub fn block(&mut self, ring: &Ring, transport: Option) { + let Some(t) = + transport.filter(|t| t.tempo.is_finite() && t.tempo > 1. && t.beats.is_finite()) + else { + return; + }; + let rate = ring.rate as f64; + let changed = match self.last { + None => true, + Some(m) => { + let expected = m.beats_at(self.frame as f64, rate); + m.transport.playing != t.playing + || (m.transport.tempo - t.tempo).abs() > 1e-6 + || m.transport.numerator != t.numerator + || m.transport.denominator != t.denominator + || (t.playing && (expected - t.beats).abs() > 0.01) + } + }; + if changed || self.since_marker as f64 >= rate * 0.5 { + let m = Marker { + frame: self.frame, + transport: t, + }; + let count = ring.marker_count.load(Ordering::Relaxed); + let s = &ring.markers[(count % MARKERS as u64) as usize]; + s[0].store(m.frame, Ordering::Relaxed); + s[1].store(t.beats.to_bits(), Ordering::Relaxed); + s[2].store(t.tempo.to_bits(), Ordering::Relaxed); + let meta = (t.numerator as u64 & 0xffff) + | (t.denominator as u64 & 0xffff) << 16 + | (t.playing as u64) << 32; + s[3].store(meta, Ordering::Relaxed); + ring.marker_count.store(count + 1, Ordering::Release); + self.last = Some(m); + self.since_marker = 0; + } + } + #[inline] + pub fn push(&mut self, ring: &Ring, frame: [f32; 2]) { + let at = (self.frame % ring.frames as u64) as usize * 2; + ring.data[at].store(frame[0].to_bits(), Ordering::Relaxed); + ring.data[at + 1].store(frame[1].to_bits(), Ordering::Relaxed); + self.frame += 1; + self.since_marker += 1; + self.peak = self.peak.max(frame[0].abs()).max(frame[1].abs()); + } + /// Makes the frames pushed so far visible to the GUI thread. Call at the end of a block. + pub fn publish(&mut self, ring: &Ring) { + ring.written.store(self.frame, Ordering::Release); + let old = f32::from_bits(ring.peak.load(Ordering::Relaxed)); + if self.peak > old { + ring.peak.store(self.peak.to_bits(), Ordering::Relaxed); + } + self.peak = 0.; + } +} diff --git a/plugins/crucible/core/src/settings.rs b/plugins/crucible/core/src/settings.rs new file mode 100644 index 0000000..b10448c --- /dev/null +++ b/plugins/crucible/core/src/settings.rs @@ -0,0 +1,120 @@ +//! Persisted editor choices. Takes are not part of plugin state; exported files are. +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] +pub enum Mode { + /// Cut on the bar/beat grid. + #[default] + Grid, + /// Cut where a new hit starts. + Transient, +} +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] +pub enum Division { + Quarter, + Eighth, + #[default] + Sixteenth, + EighthTriplet, + SixteenthTriplet, +} +impl Division { + pub const ALL: [Division; 5] = [ + Division::Quarter, + Division::Eighth, + Division::Sixteenth, + Division::EighthTriplet, + Division::SixteenthTriplet, + ]; + /// Slices per quarter note. + pub fn per_quarter(self) -> u32 { + match self { + Division::Quarter => 1, + Division::Eighth => 2, + Division::Sixteenth => 4, + Division::EighthTriplet => 3, + Division::SixteenthTriplet => 6, + } + } + pub fn label(self) -> &'static str { + match self { + Division::Quarter => "1/4", + Division::Eighth => "1/8", + Division::Sixteenth => "1/16", + Division::EighthTriplet => "1/8T", + Division::SixteenthTriplet => "1/16T", + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(default)] +pub struct Settings { + pub bars: u32, + pub mode: Mode, + pub division: Division, + /// Transient sensitivity, 0–100 %. + pub sensitivity: f32, + /// Fade length at both slice edges in milliseconds. + pub declick: f32, + /// Peak-normalise each slice to −0.3 dBFS. + pub normalize: bool, + /// Remove leading silence below −60 dBFS. + pub trim: bool, + /// Also write a wavetable from the selected slice. + pub wavetable: bool, + /// Export folder; empty selects the default. + pub folder: String, +} +impl Default for Settings { + fn default() -> Self { + Self { + bars: 4, + mode: Mode::Grid, + division: Division::Sixteenth, + sensitivity: 50., + declick: 2., + normalize: true, + trim: true, + wavetable: false, + folder: String::new(), + } + } +} +impl Settings { + pub const BARS: [u32; 5] = [1, 2, 4, 8, 16]; + /// Clamps values from old or hand-edited state into their ranges. + pub fn sanitize(&mut self) { + if !Self::BARS.contains(&self.bars) { + self.bars = 4; + } + self.sensitivity = if self.sensitivity.is_finite() { + self.sensitivity.clamp(0., 100.) + } else { + 50. + }; + self.declick = if self.declick.is_finite() { + self.declick.clamp(0., 20.) + } else { + 2. + }; + } + pub fn folder(&self) -> PathBuf { + if !self.folder.trim().is_empty() { + return PathBuf::from(self.folder.trim()); + } + default_folder() + } +} +pub fn default_folder() -> PathBuf { + if cfg!(windows) { + if let Some(home) = std::env::var_os("USERPROFILE") { + return PathBuf::from(home).join("Documents").join("Crucible"); + } + } + match std::env::var_os("HOME") { + Some(home) => PathBuf::from(home).join("Crucible"), + None => PathBuf::from("Crucible"), + } +} diff --git a/plugins/crucible/core/src/take.rs b/plugins/crucible/core/src/take.rs new file mode 100644 index 0000000..052a56d --- /dev/null +++ b/plugins/crucible/core/src/take.rs @@ -0,0 +1,478 @@ +//! Captured audio with its tempo map, slice boundaries and per-slice cleanup. +use crate::ring::{Marker, Ring, Transport}; +use crate::settings::{Division, Mode, Settings}; +use common_dsp::{hz_to_midi, Yin}; + +/// Used when the host reports no tempo. +pub const FALLBACK_TEMPO: f64 = 140.; +/// Waveform overview resolution. +pub const OVERVIEW: usize = 2048; + +/// One constant-tempo stretch of the take, relative to the take start. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Segment { + pub frame: f64, + pub quarters: f64, + pub tempo: f64, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Slice { + pub start: usize, + pub end: usize, + pub enabled: bool, + pub root: Option, +} + +#[derive(Clone, Debug)] +pub struct Take { + pub rate: f32, + /// Interleaved stereo. + pub audio: Vec, + pub bars: u32, + pub numerator: u32, + pub denominator: u32, + /// True when aligned to the host's bar lines; false when the transport was stopped. + pub synced: bool, + pub segments: Vec, + /// Slice boundaries in frames, sorted, starting at 0 and ending at the take length. + pub cuts: Vec, + pub slices: Vec, + /// Min/max per overview bucket of the mono sum. + pub overview: Vec<[f32; 2]>, +} + +impl Take { + pub fn frames(&self) -> usize { + self.audio.len() / 2 + } + pub fn tempo(&self) -> f64 { + self.segments.first().map_or(FALLBACK_TEMPO, |s| s.tempo) + } + pub fn bar_quarters(&self) -> f64 { + self.numerator.max(1) as f64 * 4. / self.denominator.max(1) as f64 + } + pub fn quarters(&self) -> f64 { + self.bars as f64 * self.bar_quarters() + } + /// Frame at a position in quarter notes from the take start. + pub fn frame_at(&self, quarters: f64) -> f64 { + let s = self + .segments + .iter() + .rev() + .find(|s| s.quarters <= quarters + 1e-9) + .or(self.segments.first()); + match s { + Some(s) => s.frame + (quarters - s.quarters) * 60. / s.tempo * self.rate as f64, + None => quarters * 60. / FALLBACK_TEMPO * self.rate as f64, + } + } + pub fn mono(&self, start: usize, end: usize) -> Vec { + self.audio[start * 2..end * 2] + .as_chunks::<2>() + .0 + .iter() + .map(|f| 0.5 * (f[0] + f[1])) + .collect() + } + fn build_overview(&mut self) { + let n = self.frames(); + self.overview = (0..OVERVIEW) + .map(|b| { + let (a, z) = ( + b * n / OVERVIEW, + ((b + 1) * n / OVERVIEW).max(b * n / OVERVIEW + 1), + ); + let mut mm = [0f32, 0f32]; + for f in a..z.min(n) { + let v = 0.5 * (self.audio[2 * f] + self.audio[2 * f + 1]); + mm[0] = mm[0].min(v); + mm[1] = mm[1].max(v); + } + mm + }) + .collect(); + } + pub fn from_audio( + rate: f32, + audio: Vec, + bars: u32, + transport: Transport, + segments: Vec, + synced: bool, + ) -> Self { + let mut t = Take { + rate, + audio, + bars, + numerator: transport.numerator.max(1), + denominator: transport.denominator.max(1), + synced, + segments, + cuts: Vec::new(), + slices: Vec::new(), + overview: Vec::new(), + }; + t.build_overview(); + let n = t.frames(); + t.cuts = vec![0, n]; + t.rebuild_slices(); + t + } + /// Re-cuts the take from the settings, replacing manual edits. + pub fn slice(&mut self, settings: &Settings) { + let n = self.frames(); + let mut cuts = match settings.mode { + Mode::Grid => grid(self, settings.division), + Mode::Transient => transients(&self.mono(0, n), self.rate, settings.sensitivity), + }; + cuts.push(0); + cuts.push(n); + cuts.sort_unstable(); + cuts.dedup(); + cuts.retain(|c| *c <= n); + self.cuts = cuts; + self.rebuild_slices(); + } + /// Adds a cut; returns false if it is too close to an existing one. + pub fn add_cut(&mut self, frame: usize) -> bool { + let min = (self.rate * 0.005) as usize; + if frame == 0 || frame >= self.frames() || self.cuts.iter().any(|c| c.abs_diff(frame) < min) + { + return false; + } + let at = self.cuts.partition_point(|c| *c < frame); + self.cuts.insert(at, frame); + self.rebuild_slices(); + true + } + /// Removes the inner cut nearest to `frame`. + pub fn remove_cut(&mut self, frame: usize) -> bool { + let inner = 1..self.cuts.len().saturating_sub(1); + let Some(i) = inner.min_by_key(|i| self.cuts[*i].abs_diff(frame)) else { + return false; + }; + self.cuts.remove(i); + self.rebuild_slices(); + true + } + fn rebuild_slices(&mut self) { + let old = std::mem::take(&mut self.slices); + let mut yin = Yin::new((self.rate / 35.) as usize + 4); + self.slices = self + .cuts + .windows(2) + .map(|w| { + // Manual include/exclude choices survive a re-cut when the slice keeps its start. + let enabled = old + .iter() + .find(|s| s.start == w[0]) + .is_none_or(|s| s.enabled); + let mono = self.mono(w[0], w[1]); + Slice { + start: w[0], + end: w[1], + enabled, + root: root_key(&mut yin, &mono, self.rate), + } + }) + .collect(); + } + pub fn slice_at(&self, frame: usize) -> Option { + self.slices + .iter() + .position(|s| frame >= s.start && frame < s.end) + } +} + +fn grid(take: &Take, division: Division) -> Vec { + let per = division.per_quarter() as f64; + let steps = (take.quarters() * per).round() as usize; + (1..steps) + .map(|k| take.frame_at(k as f64 / per).round() as usize) + .collect() +} + +/// Onsets where a window's high-frequency weighted energy jumps above the window before it. +/// Windows span 40 ms, longer than one cycle of the lowest bass note, so the edges inside a +/// distorted bass cycle never count as hits. Each onset is refined to its first loud sample. +pub fn transients(x: &[f32], rate: f32, sensitivity: f32) -> Vec { + let hop = ((rate / 375.) as usize).max(32); + let window = ((rate * 0.04) as usize / hop).max(1) * hop; + if x.len() < 2 * window + hop { + return Vec::new(); + } + // Prefix sums of weighted energy, in f64 so long takes keep their precision. + let mut sums = Vec::with_capacity(x.len() + 1); + sums.push(0f64); + let mut prev = 0f32; + for v in x { + let d = v - prev; + prev = *v; + let e = (v * v + 4. * d * d) as f64; + sums.push(sums.last().unwrap() + e); + } + let level = |end: usize| { + let e = (sums[end] - sums[end - window]) / window as f64; + 10. * (e + 1e-10).log10() as f32 + }; + let rise_db = 14. - 0.11 * sensitivity.clamp(0., 100.); + let floor = -55.; + let gap = (0.04 * rate) as usize; + let mut cuts: Vec = Vec::new(); + let mut end = 2 * window; + while end <= x.len() { + let now = level(end); + if now > floor && now - level(end - window) >= rise_db { + let from = end - window; + let to = (end + hop).min(x.len()); + let peak = x[from..to].iter().fold(0f32, |m, v| m.max(v.abs())); + let first = x[from..to] + .iter() + .position(|v| v.abs() >= 0.3 * peak) + .unwrap_or(0); + // Start slightly before the first loud sample so the attack stays whole. + let onset = (from + first).saturating_sub((rate * 0.0005) as usize); + if cuts.last().is_none_or(|c| onset >= c + gap) { + cuts.push(onset); + // Skip past this attack so it is not found again as the window slides on. + end = onset + window + gap; + } + } + end += hop; + } + cuts +} + +/// Root note of a slice, if it is clearly pitched. +pub fn root_key(yin: &mut Yin, mono: &[f32], rate: f32) -> Option { + let window = (2 * yin.max_lag() + 2).min(mono.len()); + if window < (rate / 1000.) as usize * 4 || mono.len() < window { + return None; + } + // Skip the attack: the body of a hit carries its pitch. + let mut notes: Vec = Vec::new(); + let start = (mono.len() / 5).min(mono.len() - window); + let step = (window / 2).max(1); + let mut at = start; + while at + window <= mono.len() && notes.len() < 6 { + if let Some((hz, clarity)) = yin.estimate(&mono[at..at + window], rate, 35., 1000.) { + if clarity > 0.7 { + notes.push(hz_to_midi(hz)); + } + } + at += step; + } + if notes.is_empty() { + return None; + } + notes.sort_by(f32::total_cmp); + let n = notes[notes.len() / 2].round(); + (0. ..=127.).contains(&n).then_some(n as u8) +} + +/// Builds a take from the newest `bars` complete bars, or from the newest audio when stopped. +pub fn capture(ring: &Ring, bars: u32) -> Result { + let rate = ring.rate() as f64; + let written = ring.written(); + let markers = ring.markers(); + let last = markers.iter().rev().find(|m| m.frame <= written).copied(); + let fallback = Transport { + playing: false, + tempo: FALLBACK_TEMPO, + beats: 0., + numerator: 4, + denominator: 4, + }; + let t = last.map_or(fallback, |m| m.transport); + let bar = t.bar(); + let quarters = bars as f64 * bar; + let (start, end, segments, synced) = match last { + Some(m) if m.transport.playing => { + let now = m.beats_at(written as f64, rate); + let end_beats = (now / bar + 1e-9).floor() * bar; + let start_beats = end_beats - quarters; + let end = frame_for(&markers, end_beats, rate); + let start = frame_for(&markers, start_beats, rate); + let segments = segments(&markers, start_beats, end_beats, start, rate); + (start, end, segments, true) + } + _ => { + let length = quarters * 60. / t.tempo * rate; + let segments = vec![Segment { + frame: 0., + quarters: 0., + tempo: t.tempo, + }]; + (written as f64 - length, written as f64, segments, false) + } + }; + if start < 0. { + return Err("Not enough audio recorded yet. Play the section first."); + } + let (start, end) = (start.round() as u64, end.round() as u64); + if end - start > ring.capacity() as u64 { + return Err("Too long for the recording buffer. Capture fewer bars."); + } + let audio = ring.copy(start, end)?; + Ok(Take::from_audio( + ring.rate(), + audio, + bars, + t, + segments, + synced, + )) +} + +/// Frame of a song position, from the marker whose stretch contains it. +fn frame_for(markers: &[Marker], beats: f64, rate: f64) -> f64 { + let m = markers + .iter() + .rev() + .find(|m| m.transport.playing && m.transport.beats <= beats + 1e-9) + .or_else(|| markers.iter().find(|m| m.transport.playing)); + m.map_or(0., |m| m.frame_at(beats, rate)) +} + +fn segments(markers: &[Marker], from: f64, to: f64, origin: f64, rate: f64) -> Vec { + let mut out: Vec = Vec::new(); + for m in markers.iter().filter(|m| m.transport.playing) { + let q = m.transport.beats - from; + if m.transport.beats >= to { + break; + } + let tempo = m.transport.tempo; + if q <= 0. { + out.clear(); + out.push(Segment { + frame: m.frame_at(from, rate) - origin, + quarters: 0., + tempo, + }); + } else if out.last().is_none_or(|s| (s.tempo - tempo).abs() > 1e-6) { + out.push(Segment { + frame: m.frame as f64 - origin, + quarters: q, + tempo, + }); + } + } + if out.is_empty() { + out.push(Segment { + frame: 0., + quarters: 0., + tempo: FALLBACK_TEMPO, + }); + } + out +} + +/// A cleaned-up slice ready to save: trimmed, faded and optionally normalised. +pub fn render(take: &Take, slice: &Slice, settings: &Settings) -> Vec { + let mut start = slice.start; + let end = slice.end; + if settings.trim { + let threshold = 10f32.powf(-60. / 20.); + if let Some(i) = take.audio[start * 2..end * 2] + .as_chunks::<2>() + .0 + .iter() + .position(|f| f[0].abs().max(f[1].abs()) > threshold) + { + // Keep a short pre-roll so the attack is not cut into. + start += i.saturating_sub((take.rate * 0.001) as usize); + } + } + let mut out = take.audio[start * 2..end * 2].to_vec(); + let frames = out.len() / 2; + let fade = ((settings.declick * 0.001 * take.rate) as usize).min(frames / 2); + for i in 0..fade { + let g = i as f32 / fade as f32; + for c in 0..2 { + out[2 * i + c] *= g; + out[2 * (frames - 1 - i) + c] *= g; + } + } + if settings.normalize { + let peak = out.iter().fold(0f32, |m, v| m.max(v.abs())); + if peak > 1e-6 { + let g = 10f32.powf(-0.3 / 20.) / peak; + out.iter_mut().for_each(|v| *v *= g); + } + } + out +} + +pub const TABLE: usize = 2048; +pub const TABLE_FRAMES: usize = 256; + +/// Single-cycle frames from a pitched slice. Each frame starts on a rising zero crossing, +/// so neighbouring frames stay in phase and morph smoothly in a wavetable synth. +pub fn wavetable(mono: &[f32], rate: f32) -> Option> { + let mut yin = Yin::new((rate / 30.) as usize + 4); + let window = 2 * yin.max_lag() + 2; + let probe = &mono[(mono.len() / 5).min(mono.len().saturating_sub(window))..]; + let (hz, _) = yin.estimate(&probe[..window.min(probe.len())], rate, 30., 2000.)?; + let period = rate as f64 / hz as f64; + let rising = |i: usize| mono[i - 1] < 0. && mono[i] >= 0.; + let crossing = |i: usize| (i - 1) as f64 + mono[i - 1] as f64 / (mono[i - 1] - mono[i]) as f64; + let mut starts = Vec::new(); + let mut at = (1..mono.len()).find(|i| rising(*i))?; + starts.push(crossing(at)); + loop { + let expect = starts.last().unwrap() + period; + let tolerance = period * 0.2; + let lo = (expect - tolerance).max(1.) as usize; + let hi = ((expect + tolerance) as usize).min(mono.len() - 1); + if lo >= hi { + break; + } + // The rising crossing nearest one period on; slight drift and detune are followed. + let Some(next) = (lo..=hi).filter(|i| rising(*i)).min_by(|a, b| { + (crossing(*a) - expect) + .abs() + .total_cmp(&(crossing(*b) - expect).abs()) + }) else { + // No crossing: skip one period and look again. + at = (expect as usize).min(mono.len() - 1); + match (at..mono.len()).find(|i| rising(*i)) { + Some(i) => { + starts.push(crossing(i)); + continue; + } + None => break, + } + }; + starts.push(crossing(next)); + } + let cycles: Vec<(f64, f64)> = starts + .windows(2) + .map(|w| (w[0], w[1])) + .filter(|(a, b)| (b - a - period).abs() < period * 0.25) + .collect(); + if cycles.is_empty() { + return None; + } + let count = cycles.len().min(TABLE_FRAMES); + let mut table = Vec::with_capacity(count * TABLE); + for k in 0..count { + let (a, b) = cycles[k * cycles.len() / count]; + for j in 0..TABLE { + let pos = a + (b - a) * j as f64 / TABLE as f64; + let i = pos.floor() as usize; + let frac = (pos - i as f64) as f32; + let x0 = mono[i.min(mono.len() - 1)]; + let x1 = mono[(i + 1).min(mono.len() - 1)]; + table.push(x0 + (x1 - x0) * frac); + } + } + let peak = table.iter().fold(0f32, |m, v| m.max(v.abs())); + if peak < 1e-6 { + return None; + } + let g = 10f32.powf(-0.3 / 20.) / peak; + table.iter_mut().for_each(|v| *v *= g); + Some(table) +} diff --git a/plugins/crucible/core/tests/core.rs b/plugins/crucible/core/tests/core.rs new file mode 100644 index 0000000..6beb03d --- /dev/null +++ b/plugins/crucible/core/tests/core.rs @@ -0,0 +1,258 @@ +use common_dsp::{wav, Saw}; +use crucible_core::*; + +const RATE: f32 = 48000.; + +fn playing(tempo: f64, beats: f64) -> Transport { + Transport { + playing: true, + tempo, + beats, + numerator: 4, + denominator: 4, + } +} +/// Records `frames` of a ramp (sample value = frame index) in blocks, with transport. +fn record(ring: &Ring, rec: &mut Recorder, from: u64, frames: u64, tempo: f64, beats0: f64) { + let block = 480u64; + let mut f = 0; + while f < frames { + let beats = beats0 + f as f64 / RATE as f64 * tempo / 60.; + rec.block(ring, Some(playing(tempo, beats))); + for i in 0..block.min(frames - f) { + let v = (from + f + i) as f32; + rec.push(ring, [v, -v]); + } + rec.publish(ring); + f += block; + } +} + +#[test] +fn ring_copy_is_exact_across_the_wrap_and_detects_overwrites() { + let ring = Ring::new(RATE, 1.); + let mut rec = Recorder::new(); + record(&ring, &mut rec, 0, 70000, 120., 0.); + let got = ring.copy(60000, 70000).unwrap(); + for (k, f) in got.chunks(2).enumerate() { + assert_eq!(f[0], (60000 + k) as f32); + assert_eq!(f[1], -((60000 + k) as f32)); + } + assert!(ring.copy(10000, 20000).is_err()); + assert!(ring.copy(69000, 71000).is_err()); +} + +#[test] +fn capture_ends_on_the_last_bar_line() { + let ring = Ring::new(RATE, HISTORY); + let mut rec = Recorder::new(); + // 120 BPM: one beat = 24000 frames, one 4/4 bar = 96000 frames. Stop mid bar 4. + record(&ring, &mut rec, 0, 96000 * 3 + 30000, 120., 0.); + let take = capture(&ring, 2).unwrap(); + assert!(take.synced); + assert_eq!(take.frames(), 2 * 96000); + assert_eq!(take.audio[0], 96000.); + assert_eq!(take.audio[take.audio.len() - 2], (3 * 96000 - 1) as f32); + assert!(capture(&ring, 4).is_err(), "only three full bars exist"); +} + +#[test] +fn capture_follows_tempo_changes() { + let ring = Ring::new(RATE, HISTORY); + let mut rec = Recorder::new(); + // 4 beats at 120 BPM (96000 frames), then 60 BPM (48000 frames per beat). + record(&ring, &mut rec, 0, 96000, 120., 0.); + record(&ring, &mut rec, 96000, 5 * 48000 + 1000, 60., 4.); + let take = capture(&ring, 2).unwrap(); + // Bars 1–2 end at beat 8 = 96000 + 4 × 48000. + assert_eq!(take.frames(), 96000 + 4 * 48000); + assert_eq!(take.audio[0], 0.); + assert_eq!(take.segments.len(), 2); + assert!((take.frame_at(4.) - 96000.).abs() < 1.); + assert!((take.frame_at(6.) - (96000. + 2. * 48000.)).abs() < 1.); +} + +#[test] +fn stopped_transport_captures_the_newest_audio() { + let ring = Ring::new(RATE, HISTORY); + let mut rec = Recorder::new(); + for _ in 0..500 { + rec.block( + &ring, + Some(Transport { + playing: false, + ..playing(150., 0.) + }), + ); + for _ in 0..480 { + rec.push(&ring, [0.1, 0.1]); + } + rec.publish(&ring); + } + let take = capture(&ring, 1).unwrap(); + assert!(!take.synced); + // One bar at 150 BPM = 1.6 s. + assert_eq!(take.frames(), 76800); +} + +fn take_of(mono: Vec, tempo: f64, bars: u32) -> Take { + let audio = mono.iter().flat_map(|v| [*v, *v]).collect(); + Take::from_audio( + RATE, + audio, + bars, + playing(tempo, 0.), + vec![Segment { + frame: 0., + quarters: 0., + tempo, + }], + true, + ) +} + +#[test] +fn grid_slices_straight_and_triplet() { + let mut take = take_of(vec![0.; 96000], 120., 1); + let mut s = Settings { + division: Division::Sixteenth, + ..Settings::default() + }; + take.slice(&s); + assert_eq!(take.slices.len(), 16); + assert_eq!(take.cuts[1], 6000); + s.division = Division::EighthTriplet; + take.slice(&s); + assert_eq!(take.slices.len(), 12); + assert_eq!(take.cuts[1], 8000); +} + +#[test] +fn transients_find_hits_within_two_milliseconds() { + let mut x = vec![0.; 96000]; + let hits = [4800usize, 20000, 31111, 60000, 80000]; + for &h in &hits { + for i in 0..2000 { + x[h + i] = (i as f32 * 0.3).sin() * (-(i as f32) / 400.).exp() * 0.8; + } + } + let found = transients(&x, RATE, 50.); + assert_eq!(found.len(), hits.len(), "{found:?}"); + for (f, h) in found.iter().zip(hits) { + assert!(f.abs_diff(h) < 96, "{f} vs {h}"); + } + let mut take = take_of(x, 120., 1); + take.slice(&Settings { + mode: Mode::Transient, + ..Settings::default() + }); + assert_eq!(take.slices.len(), hits.len() + 1); +} + +#[test] +fn manual_cuts_add_and_remove() { + let mut take = take_of(vec![0.; 48000], 120., 1); + assert!(take.add_cut(12000)); + assert!(!take.add_cut(12010), "too close"); + assert_eq!(take.slices.len(), 2); + take.slices[1].enabled = false; + assert!(take.add_cut(30000)); + assert!(!take.slices[1].enabled, "choice survives a re-cut"); + assert!(take.remove_cut(29000)); + assert_eq!(take.cuts, vec![0, 12000, 48000]); +} + +#[test] +fn render_trims_fades_and_normalises() { + let mut x = vec![0.; 4800]; + x[1000..] + .iter_mut() + .enumerate() + .for_each(|(i, v)| *v = 0.25 * (i as f32 * 0.05).sin().signum()); + let take = take_of(x, 120., 1); + let out = render(&take, &take.slices[0], &Settings::default()); + let frames = out.len() / 2; + // Leading silence gone except a 1 ms pre-roll. + assert_eq!(frames, 3800 + 48); + assert_eq!(out[0], 0.); + assert_eq!(out[out.len() - 1], 0.); + let peak = out.iter().fold(0f32, |m, v| m.max(v.abs())); + assert!((20. * peak.log10() + 0.3).abs() < 0.01); +} + +#[test] +fn root_key_is_detected_per_slice() { + let mut saw = Saw::new(RATE, 43.65); + let take = take_of((0..48000).map(|_| saw.sample() * 0.5).collect(), 120., 1); + assert_eq!(take.slices[0].root, Some(29)); + let silent = take_of(vec![0.; 48000], 120., 1); + assert_eq!(silent.slices[0].root, None); +} + +#[test] +fn wavetable_frames_start_on_rising_zero_crossings_and_match() { + let mut saw = Saw::new(RATE, 110.); + let x: Vec = (0..192000).map(|_| saw.sample() * 0.5).collect(); + let table = wavetable(&x, RATE).unwrap(); + let frames = table.len() / TABLE; + assert_eq!(frames, TABLE_FRAMES); + for f in table.chunks(TABLE) { + assert!(f[0].abs() < 0.02 && f[1] > f[0], "{} {}", f[0], f[1]); + let a = &table[..TABLE]; + let dot: f32 = a.iter().zip(f).map(|(p, q)| p * q).sum(); + let na: f32 = a.iter().map(|v| v * v).sum(); + let nb: f32 = f.iter().map(|v| v * v).sum(); + assert!(dot / (na * nb).sqrt() > 0.99); + } + assert!(wavetable(&vec![0.; 48000], RATE).is_none()); +} + +#[test] +fn export_writes_loop_slices_and_wavetable() { + let dir = std::env::temp_dir().join(format!("crucible-test-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + let mut saw = Saw::new(RATE, 55.); + let mut take = take_of((0..96000).map(|_| saw.sample() * 0.5).collect(), 120., 1); + let settings = Settings { + division: Division::Quarter, + wavetable: true, + ..Settings::default() + }; + take.slice(&settings); + take.slices[3].enabled = false; + let done = export(&take, &settings, Some(0), &dir, 1_700_000_000).unwrap(); + assert_eq!(done.files, 1 + 3 + 1); + assert!(done.folder.ends_with("2023-11-14_221320_120bpm")); + let read = |name: &str| wav::decode(&std::fs::read(done.folder.join(name)).unwrap()).unwrap(); + assert_eq!(read("loop.wav").samples.len(), 192000); + assert_eq!(read("s01_A1.wav").root, Some(33)); + assert_eq!(read("wavetable.wav").frame, Some(2048)); + assert!(!done.folder.join("s04_A1.wav").exists()); + std::fs::remove_dir_all(&dir).unwrap(); +} + +#[test] +fn audition_plays_and_returns_clips_for_freeing() { + let q = Queues::default(); + let mut p = Player::default(); + q.play(std::sync::Arc::from(vec![0.5f32, -0.5, 0.25, -0.25])); + p.block(&q); + assert_eq!(p.next(&q), [0.5, -0.5]); + assert_eq!(p.next(&q), [0.25, -0.25]); + assert_eq!(p.next(&q), [0., 0.]); + assert!(!p.playing()); + q.drain(); +} + +#[test] +fn settings_survive_json_and_are_sanitised() { + let mut s: Settings = + serde_json::from_str(r#"{"bars":3,"declick":99,"sensitivity":-5}"#).unwrap(); + s.sanitize(); + assert_eq!((s.bars, s.declick, s.sensitivity), (4, 20., 0.)); + let json = serde_json::to_string(&Settings::default()).unwrap(); + assert_eq!( + serde_json::from_str::(&json).unwrap(), + Settings::default() + ); +} diff --git a/plugins/crucible/core/tests/realtime.rs b/plugins/crucible/core/tests/realtime.rs new file mode 100644 index 0000000..af4f445 --- /dev/null +++ b/plugins/crucible/core/tests/realtime.rs @@ -0,0 +1,68 @@ +use crucible_core::{Player, Queues, Recorder, Ring, Transport}; +use std::sync::Arc; +use std::{ + alloc::{GlobalAlloc, Layout, System}, + cell::Cell, +}; + +struct CheckedAllocator; +thread_local! { + static CHECK:Cell=const {Cell::new(false)}; + static OPERATIONS:Cell=const {Cell::new(0)}; +} +fn record() { + if CHECK.try_with(Cell::get).unwrap_or(false) { + let _ = OPERATIONS.try_with(|n| n.set(n.get() + 1)); + } +} +unsafe impl GlobalAlloc for CheckedAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + record(); + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) { + record(); + unsafe { System.dealloc(pointer, layout) } + } + unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, size: usize) -> *mut u8 { + record(); + unsafe { System.realloc(pointer, layout, size) } + } +} +#[global_allocator] +static ALLOCATOR: CheckedAllocator = CheckedAllocator; + +#[test] +fn recording_transport_and_audition_do_not_allocate_or_free() { + let ring = Ring::new(48000., 4.); + let queues = Queues::default(); + let mut recorder = Recorder::new(); + let mut player = Player::default(); + let clips: Vec> = (0..4).map(|i| Arc::from(vec![0.1; 2000 + i])).collect(); + CHECK.with(|v| v.set(true)); + for block in 0..2000u32 { + if block % 300 == 0 { + // Sending only moves the Arc; freeing happens on drain, outside the check. + queues.play(clips[(block / 300) as usize % 4].clone()); + } + recorder.block( + &ring, + Some(Transport { + playing: block % 700 < 600, + tempo: if block < 1000 { 140. } else { 150. }, + beats: block as f64 * 0.1, + numerator: 4, + denominator: 4, + }), + ); + player.block(&queues); + for i in 0..256 { + let a = player.next(&queues); + recorder.push(&ring, [i as f32 * 1e-3 + a[0], a[1]]); + } + recorder.publish(&ring); + } + CHECK.with(|v| v.set(false)); + assert_eq!(OPERATIONS.with(Cell::get), 0); + queues.drain(); +} diff --git a/plugins/crucible/plugin/Cargo.lock b/plugins/crucible/plugin/Cargo.lock new file mode 100644 index 0000000..21605c3 --- /dev/null +++ b/plugins/crucible/plugin/Cargo.lock @@ -0,0 +1,2382 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "addr2line" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e567177890eb1617b1f774005b66b26b2377afd138a2ca37aae7d8f0c81429d4" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.2", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.21", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "anymap3" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5dfbc6d8d2675589ccbe4d0fd61df2419075625f8c1a62325e718e2b0049f9" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_float" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62af46d040ba9df09edc6528dae9d8e49f5f3e82f55b7d2ec31a733c38dbc49d" + +[[package]] +name = "atomic_refcell" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e4227379beff4205943696e6c3e0cd809bacdf3f0edd6e3dd153e2269571a4" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "backtrace" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f4220217f3288537b83684130f42fad43320b1c3b805f1910d683d52ded24f" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "baseview" +version = "0.1.0" +source = "git+https://github.com/RustAudio/baseview.git?rev=9a0b42c09d712777b2edb4c5e0cb6baf21e988f0#9a0b42c09d712777b2edb4c5e0cb6baf21e988f0" +dependencies = [ + "cocoa", + "core-foundation", + "keyboard-types", + "nix", + "objc", + "raw-window-handle 0.5.2", + "uuid", + "winapi", + "x11", + "x11rb", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.2", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clap-sys" +version = "0.5.0" +source = "git+https://github.com/micahrj/clap-sys.git?rev=25d7f53fdb6363ad63fbd80049cb7a42a97ac156#25d7f53fdb6363ad63fbd80049cb7a42a97ac156" + +[[package]] +name = "clipboard-win" +version = "5.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a" +dependencies = [ + "error-code", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics 0.22.3", + "foreign-types 0.3.2", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "common-dsp" +version = "0.1.0" + +[[package]] +name = "common-ui" +version = "0.1.0" +dependencies = [ + "egui", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "copypasta" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6811e17f81fe246ef2bc553f76b6ee6ab41a694845df1d37e52a92b7bbd38a" +dependencies = [ + "clipboard-win", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "x11-clipboard", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types 0.5.0", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "crossbeam" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71406cd8807725f7ac2f999a4cdd32e98f829fdf65f528343cebf945e41df1e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crucible" +version = "0.1.0" +dependencies = [ + "clap-sys", + "crucible-core", + "crucible-ui", + "nih_plug", + "nih_plug_egui", + "serde_json", +] + +[[package]] +name = "crucible-core" +version = "0.1.0" +dependencies = [ + "common-dsp", + "crossbeam-queue", + "serde", +] + +[[package]] +name = "crucible-ui" +version = "0.1.0" +dependencies = [ + "common-dsp", + "common-ui", + "crucible-core", + "egui", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "deranged" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "ecolor" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" +dependencies = [ + "bytemuck", + "emath", +] + +[[package]] +name = "egui" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" +dependencies = [ + "ahash", + "bitflags 2.13.2", + "emath", + "epaint", + "nohash-hasher", + "profiling", +] + +[[package]] +name = "egui-baseview" +version = "0.5.0" +source = "git+https://github.com/BillyDM/egui-baseview.git?rev=ec70c3fe6b2f070dcacbc22924431edbe24bd1c0#ec70c3fe6b2f070dcacbc22924431edbe24bd1c0" +dependencies = [ + "baseview", + "copypasta", + "egui", + "egui_glow", + "keyboard-types", + "log", + "open", + "raw-window-handle 0.5.2", + "thiserror 2.0.21", +] + +[[package]] +name = "egui_glow" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" +dependencies = [ + "ahash", + "bytemuck", + "egui", + "glow", + "log", + "memoffset 0.9.1", + "profiling", + "wasm-bindgen", + "web-sys", + "winit", +] + +[[package]] +name = "emath" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "epaint" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" +dependencies = [ + "ab_glyph", + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "nohash-hasher", + "parking_lot", + "profiling", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.6", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "gimli" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1033caf0b349c518623b5396bfb2cf0bddf44f0306d543a250e5743297aafd10" + +[[package]] +name = "glow" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "bitflags 2.13.2", + "libc", + "plain", + "redox_syscall 0.9.4", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d15f86d12da44cd8d4ec3c73984537eb7f4cadcb9776a575a0a8a05ca11005c" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "midi-consts" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f2dd5c7f8aaf48a76e389068ab25ed80bdbc226b887f9013844c415698c9952" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.2", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle 0.6.2", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "nih_log" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0cdb52ef79af48ae110401c883bdb9c15e0306a99ab6ecf18bc52068b668e54" +dependencies = [ + "atty", + "log", + "once_cell", + "termcolor", + "time", + "windows", +] + +[[package]] +name = "nih_plug" +version = "0.0.0" +source = "git+https://github.com/robbert-vdh/nih-plug.git?rev=de421011f41a6d10fc8c7a6084e4f4dee0143683#de421011f41a6d10fc8c7a6084e4f4dee0143683" +dependencies = [ + "anyhow", + "anymap3", + "atomic_float", + "atomic_refcell", + "backtrace", + "bitflags 1.3.2", + "cfg-if", + "clap-sys", + "core-foundation", + "crossbeam", + "libc", + "log", + "midi-consts", + "nih_log", + "nih_plug_derive", + "objc", + "parking_lot", + "raw-window-handle 0.5.2", + "serde", + "serde_json", + "widestring", + "windows", +] + +[[package]] +name = "nih_plug_derive" +version = "0.1.0" +source = "git+https://github.com/robbert-vdh/nih-plug.git?rev=de421011f41a6d10fc8c7a6084e4f4dee0143683#de421011f41a6d10fc8c7a6084e4f4dee0143683" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "nih_plug_egui" +version = "0.0.0" +source = "git+https://github.com/robbert-vdh/nih-plug.git?rev=de421011f41a6d10fc8c7a6084e4f4dee0143683#de421011f41a6d10fc8c7a6084e4f4dee0143683" +dependencies = [ + "baseview", + "crossbeam", + "egui-baseview", + "nih_plug", + "parking_lot", + "raw-window-handle 0.5.2", + "serde", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num_threads" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabcc654086861f9e1bc5115258de62ef92c79b20f4615a1b0cc57c4e82027d4" +dependencies = [ + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.2", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "object" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "open" +version = "5.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" +dependencies = [ + "is-wsl", + "libc", +] + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.3", + "pin-project-lite", + "rustix 1.1.6", + "windows-sys 0.61.2", +] + +[[package]] +name = "powerfmt" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a6394b9e965e73d0a289ee54f589087e2c676aedf60885baf52c76b771e4958" + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl 2.0.21", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tokio" +version = "1.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" +dependencies = [ + "pin-project-lite", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "android-activity", + "atomic-waker", + "bitflags 2.13.2", + "block2", + "bytemuck", + "calloop", + "cfg_aliases", + "concurrent-queue", + "core-foundation", + "core-graphics 0.23.2", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle 0.6.2", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-clipboard" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662d74b3d77e396b8e5beb00b9cad6a9eccf40b2ef68cc858784b14c41d535a3" +dependencies = [ + "libc", + "x11rb", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.6", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.2", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/plugins/crucible/plugin/Cargo.toml b/plugins/crucible/plugin/Cargo.toml new file mode 100644 index 0000000..c31d8fb --- /dev/null +++ b/plugins/crucible/plugin/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "crucible" +version = "0.1.0" +edition = "2021" +license = "ISC" + +[workspace] + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", rev = "de421011f41a6d10fc8c7a6084e4f4dee0143683", default-features = false } +nih_plug_egui = { git = "https://github.com/robbert-vdh/nih-plug.git", rev = "de421011f41a6d10fc8c7a6084e4f4dee0143683" } +crucible-core = { path = "../core" } +crucible-ui = { path = "../ui" } + +[dev-dependencies] +clap-sys = { git = "https://github.com/micahrj/clap-sys.git", rev = "25d7f53fdb6363ad63fbd80049cb7a42a97ac156" } +serde_json = "1" + +[profile.release] +lto = "thin" +strip = "symbols" diff --git a/plugins/crucible/plugin/src/lib.rs b/plugins/crucible/plugin/src/lib.rs new file mode 100644 index 0000000..09ff383 --- /dev/null +++ b/plugins/crucible/plugin/src/lib.rs @@ -0,0 +1,200 @@ +//! CLAP audio effect. Audio passes through unchanged; the input is recorded continuously. +use crucible_core::{Player, Queues, Recorder, Ring, Settings, Transport, HISTORY}; +use nih_plug::prelude::*; +use nih_plug_egui::{create_egui_editor, egui, EguiState}; +use std::sync::{Arc, Mutex, RwLock}; + +#[derive(Params)] +pub struct CrucibleParams { + #[persist = "editor"] + pub editor: Arc, + #[persist = "crucible-settings"] + pub settings: RwLock, + #[id = "bypass"] + pub bypass: BoolParam, +} +impl Default for CrucibleParams { + fn default() -> Self { + Self { + editor: EguiState::from_size(crucible_ui::WIDTH, crucible_ui::HEIGHT), + settings: RwLock::new(Settings::default()), + bypass: BoolParam::new("Bypass", false).make_bypass(), + } + } +} + +struct Shared { + /// Replaced on activation; the editor clones the Arc briefly, never the audio thread. + ring: Mutex>>, + queues: Arc, +} +pub struct Crucible { + params: Arc, + shared: Arc, + ring: Option>, + recorder: Recorder, + player: Player, +} +impl Default for Crucible { + fn default() -> Self { + Self { + params: Arc::new(CrucibleParams::default()), + shared: Arc::new(Shared { + ring: Mutex::new(None), + queues: Arc::new(Queues::default()), + }), + ring: None, + recorder: Recorder::new(), + player: Player::default(), + } + } +} +impl Plugin for Crucible { + const NAME: &'static str = "Crucible"; + const VENDOR: &'static str = "Rivet & Patina"; + const URL: &'static str = ""; + const EMAIL: &'static str = ""; + const VERSION: &'static str = env!("CARGO_PKG_VERSION"); + const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[ + AudioIOLayout { + main_input_channels: NonZeroU32::new(2), + main_output_channels: NonZeroU32::new(2), + ..AudioIOLayout::const_default() + }, + AudioIOLayout { + main_input_channels: NonZeroU32::new(1), + main_output_channels: NonZeroU32::new(1), + ..AudioIOLayout::const_default() + }, + ]; + const SAMPLE_ACCURATE_AUTOMATION: bool = false; + type SysExMessage = (); + type BackgroundTask = (); + fn params(&self) -> Arc { + self.params.clone() + } + fn editor(&mut self, _: AsyncExecutor) -> Option> { + let params = self.params.clone(); + let shared = self.shared.clone(); + create_egui_editor( + params.editor.clone(), + crucible_ui::State::default(), + |ctx, _| crucible_ui::style(ctx), + move |ctx, _setter, state| { + let link = crucible_ui::Link { + ring: shared.ring.lock().ok().and_then(|r| r.clone()), + queues: shared.queues.clone(), + }; + let mut settings = params + .settings + .read() + .map(|s| s.clone()) + .unwrap_or_default(); + let before = settings.clone(); + nih_plug_egui::resizable_window::ResizableWindow::new("crucible-editor") + .min_size(egui::vec2( + crucible_ui::WIDTH as f32 - 80., + crucible_ui::HEIGHT as f32 - 40., + )) + .show(ctx, ¶ms.editor, |ui| { + crucible_ui::show(ui, &mut settings, state, &link) + }); + if settings != before { + if let Ok(mut s) = params.settings.write() { + *s = settings; + } + } + // Meters and the transport readout follow the audio. + ctx.request_repaint_after(std::time::Duration::from_millis(50)); + }, + ) + } + fn initialize( + &mut self, + _: &AudioIOLayout, + config: &BufferConfig, + _: &mut impl InitContext, + ) -> bool { + if let Ok(mut s) = self.params.settings.write() { + s.sanitize(); + } + let ring = Arc::new(Ring::new(config.sample_rate, HISTORY)); + self.ring = Some(ring.clone()); + self.recorder = Recorder::new(); + self.player = Player::default(); + if let Ok(mut slot) = self.shared.ring.lock() { + *slot = Some(ring); + } + true + } + fn reset(&mut self) { + self.player = Player::default(); + } + fn process( + &mut self, + buffer: &mut Buffer, + _: &mut AuxiliaryBuffers, + context: &mut impl ProcessContext, + ) -> ProcessStatus { + let Some(ring) = self.ring.as_deref() else { + return ProcessStatus::Normal; + }; + let bypass = self.params.bypass.value(); + let t = context.transport(); + let transport = match (t.tempo, t.pos_beats()) { + (Some(tempo), Some(beats)) => Some(Transport { + playing: t.playing, + tempo, + beats, + numerator: t.time_sig_numerator.unwrap_or(4).clamp(1, 64) as u32, + denominator: t.time_sig_denominator.unwrap_or(4).clamp(1, 64) as u32, + }), + _ => None, + }; + if !bypass { + self.recorder.block(ring, transport); + } + self.player.block(&self.shared.queues); + let channels = buffer.channels(); + for mut sample in buffer.iter_samples() { + let left = *sample.get_mut(0).unwrap(); + let right = if channels > 1 { + *sample.get_mut(1).unwrap() + } else { + left + }; + if bypass { + continue; + } + self.recorder.push(ring, [left, right]); + // Auditioned slices are added to the output, never recorded. + let a = self.player.next(&self.shared.queues); + if self.player.playing() || a != [0.; 2] { + *sample.get_mut(0).unwrap() += if channels > 1 { + a[0] + } else { + 0.5 * (a[0] + a[1]) + }; + if channels > 1 { + *sample.get_mut(1).unwrap() += a[1]; + } + } + } + self.recorder.publish(ring); + ProcessStatus::Normal + } +} +impl ClapPlugin for Crucible { + const CLAP_ID: &'static str = "io.storyboard.resampler"; + const CLAP_DESCRIPTION: Option<&'static str> = + Some("Capture the last bars, slice them and export clean, key-labelled samples"); + const CLAP_MANUAL_URL: Option<&'static str> = None; + const CLAP_SUPPORT_URL: Option<&'static str> = None; + const CLAP_FEATURES: &'static [ClapFeature] = &[ + ClapFeature::AudioEffect, + ClapFeature::Utility, + ClapFeature::Stereo, + ClapFeature::Mono, + ]; +} +nih_export_clap!(Crucible); diff --git a/plugins/crucible/plugin/tests/host.rs b/plugins/crucible/plugin/tests/host.rs new file mode 100644 index 0000000..2ca3e3e --- /dev/null +++ b/plugins/crucible/plugin/tests/host.rs @@ -0,0 +1,205 @@ +//! Exercise the actual exported CLAP ABI without requiring a GUI or installed DAW. +use clap_sys::{ + audio_buffer::*, + events::*, + ext::{audio_ports::*, latency::*, state::*}, + factory::plugin_factory::*, + host::*, + process::*, + stream::*, + version::*, +}; +use std::{ + ffi::{c_char, c_void, CStr}, + io::{Cursor, Read}, + ptr, +}; + +unsafe extern "C" fn extension(_: *const clap_host, _: *const c_char) -> *const c_void { + ptr::null() +} +unsafe extern "C" fn request(_: *const clap_host) {} +unsafe extern "C" fn count(_: *const clap_input_events) -> u32 { + 0 +} +unsafe extern "C" fn event(_: *const clap_input_events, _: u32) -> *const clap_event_header { + ptr::null() +} +unsafe extern "C" fn push(_: *const clap_output_events, _: *const clap_event_header) -> bool { + true +} +unsafe extern "C" fn write(stream: *const clap_ostream, buffer: *const c_void, size: u64) -> i64 { + // SAFETY: The test owns the Vec for the entire synchronous save call. + let bytes = unsafe { &mut *((*stream).ctx as *mut Vec) }; + bytes.extend_from_slice(unsafe { + std::slice::from_raw_parts(buffer as *const u8, size as usize) + }); + size as i64 +} +unsafe extern "C" fn read(stream: *const clap_istream, buffer: *mut c_void, size: u64) -> i64 { + // SAFETY: The test owns the Cursor; the wrapper supplies writable stream storage. + let cursor = unsafe { &mut *((*stream).ctx as *mut Cursor>) }; + cursor + .read(unsafe { std::slice::from_raw_parts_mut(buffer as *mut u8, size as usize) }) + .unwrap() as i64 +} + +#[test] +fn effect_ports_state_latency_and_audio_lifecycle() { + // SAFETY: All callbacks, strings, streams, buffers, and the host outlive their + // synchronous ABI calls. Activation/processing/destruction follow CLAP order. + unsafe { + let entry = &crucible::clap_entry; + assert!(entry.init.unwrap()(c"crucible.clap".as_ptr())); + let factory = entry.get_factory.unwrap()(CLAP_PLUGIN_FACTORY_ID.as_ptr()) + as *const clap_plugin_factory; + assert!(!factory.is_null()); + let factory = &*factory; + assert_eq!(factory.get_plugin_count.unwrap()(factory), 1); + let descriptor = factory.get_plugin_descriptor.unwrap()(factory, 0); + assert!(!descriptor.is_null()); + assert_eq!(CStr::from_ptr((*descriptor).name), c"Crucible"); + assert_eq!(CStr::from_ptr((*descriptor).id), c"io.storyboard.resampler"); + assert!(factory.get_plugin_descriptor.unwrap()(factory, 1).is_null()); + let host = clap_host { + clap_version: CLAP_VERSION, + host_data: ptr::null_mut(), + name: c"Storyboard test host".as_ptr(), + vendor: c"Tests".as_ptr(), + url: c"".as_ptr(), + version: c"1.0".as_ptr(), + get_extension: Some(extension), + request_restart: Some(request), + request_process: Some(request), + request_callback: Some(request), + }; + let plugin = factory.create_plugin.unwrap()(factory, &host, (*descriptor).id); + assert!(!plugin.is_null()); + let p = &*plugin; + assert!(p.init.unwrap()(plugin)); + let ports = p.get_extension.unwrap()(plugin, CLAP_EXT_AUDIO_PORTS.as_ptr()) + as *const clap_plugin_audio_ports; + assert!(!ports.is_null()); + assert_eq!((*ports).count.unwrap()(plugin, false), 1); + assert_eq!((*ports).count.unwrap()(plugin, true), 1); + let latency = p.get_extension.unwrap()(plugin, CLAP_EXT_LATENCY.as_ptr()) + as *const clap_plugin_latency; + assert!(!latency.is_null()); + let state = + p.get_extension.unwrap()(plugin, CLAP_EXT_STATE.as_ptr()) as *const clap_plugin_state; + assert!(!state.is_null()); + let state = &*state; + let save = || { + let mut bytes = Vec::new(); + let stream = clap_ostream { + ctx: &mut bytes as *mut Vec as *mut c_void, + write: Some(write), + }; + assert!(state.save.unwrap()(plugin, &stream)); + bytes + }; + let mut envelope: nih_plug::prelude::PluginState = + serde_json::from_slice(&save()[8..]).unwrap(); + use nih_plug::wrapper::state::ParamValue; + assert!(envelope.fields.contains_key("crucible-settings")); + envelope + .params + .insert("bypass".into(), ParamValue::Bool(false)); + let settings = crucible_core::Settings { + bars: 8, + declick: 5., + wavetable: true, + folder: "D:/Samples/Crucible".into(), + ..Default::default() + }; + envelope.fields.insert( + "crucible-settings".into(), + serde_json::to_string(&settings).unwrap(), + ); + let encoded = serde_json::to_vec(&envelope).unwrap(); + let mut framed = (encoded.len() as u64).to_le_bytes().to_vec(); + framed.extend_from_slice(&encoded); + let mut bytes = Cursor::new(framed); + let stream = clap_istream { + ctx: &mut bytes as *mut Cursor> as *mut c_void, + read: Some(read), + }; + assert!(state.load.unwrap()(plugin, &stream)); + let restored: nih_plug::prelude::PluginState = + serde_json::from_slice(&save()[8..]).unwrap(); + assert_eq!( + serde_json::to_value(&restored.params).unwrap(), + serde_json::to_value(&envelope.params).unwrap() + ); + let restored_settings: crucible_core::Settings = + serde_json::from_str(&restored.fields["crucible-settings"]).unwrap(); + assert_eq!(restored_settings, settings); + for rate in [44100., 48000., 96000.] { + assert!(p.activate.unwrap()(plugin, rate, 1, 512)); + let delay = (*latency).get.unwrap()(plugin) as usize; + assert_eq!(delay, 0); + assert!(p.start_processing.unwrap()(plugin)); + let mut position = 0; + while position < delay + 1024 { + let frames = [1, 127, 512, 31][position % 4].min(delay + 1024 - position); + let mut input_left = [0f32; 512]; + let mut input_right = [0f32; 512]; + for i in 0..frames { + let x = if position + i == 0 { 0.5 } else { 0. }; + input_left[i] = x; + input_right[i] = -x; + } + let mut left = [0f32; 512]; + let mut right = [0f32; 512]; + let mut in_channels = [input_left.as_mut_ptr(), input_right.as_mut_ptr()]; + let mut out_channels = [left.as_mut_ptr(), right.as_mut_ptr()]; + let incoming = clap_audio_buffer { + data32: in_channels.as_mut_ptr(), + data64: ptr::null_mut(), + channel_count: 2, + latency: 0, + constant_mask: 0, + }; + let mut outgoing_audio = clap_audio_buffer { + data32: out_channels.as_mut_ptr(), + data64: ptr::null_mut(), + channel_count: 2, + latency: 0, + constant_mask: 0, + }; + let input = clap_input_events { + ctx: ptr::null_mut(), + size: Some(count), + get: Some(event), + }; + let outgoing = clap_output_events { + ctx: ptr::null_mut(), + try_push: Some(push), + }; + let process = clap_process { + steady_time: position as i64, + frames_count: frames as u32, + transport: ptr::null(), + audio_inputs: &incoming, + audio_outputs: &mut outgoing_audio, + audio_inputs_count: 1, + audio_outputs_count: 1, + in_events: &input, + out_events: &outgoing, + }; + assert_ne!(p.process.unwrap()(plugin, &process), CLAP_PROCESS_ERROR); + for i in 0..frames { + // Audio passes through untouched. + let expected = if position + i == 0 { 0.5 } else { 0. }; + assert!((left[i] - expected).abs() < 1e-6); + assert!((right[i] + expected).abs() < 1e-6); + } + position += frames; + } + p.stop_processing.unwrap()(plugin); + p.deactivate.unwrap()(plugin); + } + p.destroy.unwrap()(plugin); + entry.deinit.unwrap()(); + } +} diff --git a/plugins/crucible/ui/Cargo.toml b/plugins/crucible/ui/Cargo.toml new file mode 100644 index 0000000..64de4bd --- /dev/null +++ b/plugins/crucible/ui/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "crucible-ui" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +egui = "0.31.1" +crucible-core = { path = "../core" } +common-ui = { path = "../../common/ui" } +common-dsp = { path = "../../common/dsp" } diff --git a/plugins/crucible/ui/src/lib.rs b/plugins/crucible/ui/src/lib.rs new file mode 100644 index 0000000..04e8379 --- /dev/null +++ b/plugins/crucible/ui/src/lib.rs @@ -0,0 +1,567 @@ +//! Crucible editor, shared by the CLAP wrapper and the headless renderer. +use common_ui::*; +use crucible_core::{ + capture, export, render, Division, Exported, Mode, Queues, Ring, Settings, Take, Transport, + OVERVIEW, +}; +use egui::{self, Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, Vec2}; +use std::sync::{Arc, Mutex}; + +pub use common_ui::style; +pub const WIDTH: u32 = 1040; +pub const HEIGHT: u32 = 680; +const MAX_TAKES: usize = 8; +const DEFAULT_HELP: &str = + "Play your bass, press Capture, then drag the exported files from FL's browser."; + +/// Connection to the audio thread. +pub struct Link { + pub ring: Option>, + pub queues: Arc, +} + +pub struct State { + pub takes: Vec, + pub take: Option, + pub slice: Option, + pub status: String, + pub help: String, + pub peak: f32, + pub transport: Option, + pub folder: String, + counter: usize, + names: Vec, + /// Filled by the export thread when it finishes. + job: Option>>>>, + last_export: Option, +} +impl Default for State { + fn default() -> Self { + Self { + takes: Vec::new(), + take: None, + slice: None, + status: String::new(), + help: DEFAULT_HELP.into(), + peak: 0., + transport: None, + folder: String::new(), + counter: 0, + names: Vec::new(), + job: None, + last_export: None, + } + } +} +impl State { + /// Adds a take (newest first) and slices it with the current settings. + pub fn add(&mut self, mut take: Take, settings: &Settings) { + take.slice(settings); + self.counter += 1; + self.names.insert( + 0, + format!( + "Take {} · {} bar{} · {:.0} BPM", + self.counter, + take.bars, + if take.bars == 1 { "" } else { "s" }, + take.tempo() + ), + ); + self.takes.insert(0, take); + self.takes.truncate(MAX_TAKES); + self.names.truncate(MAX_TAKES); + self.take = Some(0); + self.slice = None; + } + fn current(&mut self) -> Option<&mut Take> { + self.take.and_then(|i| self.takes.get_mut(i)) + } +} + +fn hint(r: &egui::Response, state: &mut State, text: &str) { + if r.hovered() { + state.help = text.into(); + } +} +fn chip(ui: &mut egui::Ui, on: bool, label: &str) -> egui::Response { + ui.selectable_label(on, RichText::new(label).size(13.)) +} +fn unix_now() -> u64 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_or(0, |d| d.as_secs()) +} +fn open_folder(path: &std::path::Path) { + let program = if cfg!(windows) { + "explorer" + } else { + "xdg-open" + }; + let _ = std::process::Command::new(program).arg(path).spawn(); +} + +fn audition(link: &Link, take: &Take, index: usize, settings: &Settings) { + if let Some(slice) = take.slices.get(index) { + link.queues.play(Arc::from(render(take, slice, settings))); + } +} + +pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State, link: &Link) { + backdrop(ui); + link.queues.drain(); + state.help = DEFAULT_HELP.into(); + if let Some(ring) = &link.ring { + let p = ring.take_peak(); + state.peak = p.max(state.peak * 0.9); + if let Some(m) = ring.markers().last() { + state.transport = Some(m.transport); + } + } + let finished = state + .job + .as_ref() + .and_then(|job| job.lock().ok().and_then(|mut slot| slot.take())); + if let Some(result) = finished { + state.job = None; + state.status = match result { + Ok(done) => { + let mut s = format!("Exported {} files to {}", done.files, done.folder.display()); + if let Some(why) = done.wavetable_skipped { + s += &format!(" (no wavetable: {why})"); + } + state.last_export = Some(done.folder); + s + } + Err(e) => format!("Export failed: {e}"), + }; + } + ui.add_space(2.); + header(ui, "CRUCIBLE", "RESAMPLER", |ui| { + let db = 20. * state.peak.max(1e-6).log10(); + ui.label( + RichText::new(if db < -90. { + "—".into() + } else { + format!("{db:.1} dB") + }) + .monospace() + .size(12.) + .color(if db > -0.1 { ACCENT } else { MUTED }), + ); + caption(ui, "INPUT"); + }); + ui.add_space(6.); + panel(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| capture_row(ui, settings, state, link)); + }); + ui.add_space(6.); + let body = 300.; + ui.horizontal_top(|ui| { + panel(ui, |ui| { + ui.set_width(190.); + ui.set_min_height(body); + ui.vertical(|ui| takes(ui, state)); + }); + ui.add_space(4.); + panel(ui, |ui| { + ui.set_width(ui.available_width()); + waveform(ui, settings, state, link, body); + }); + }); + ui.add_space(6.); + panel(ui, |ui| { + ui.set_width(ui.available_width()); + ui.vertical(|ui| controls(ui, settings, state, link)); + }); + ui.separator(); + let line = if state.status.is_empty() || state.help != DEFAULT_HELP { + state.help.clone() + } else { + state.status.clone() + }; + ui.label(RichText::new(line).color(MUTED)); +} + +fn capture_row(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State, link: &Link) { + let button = egui::Button::new( + RichText::new("CAPTURE") + .font(FontId::new(17., egui::FontFamily::Name("medium".into()))) + .color(Color32::WHITE), + ) + .fill(Color32::from_rgb(150, 0, 56)) + .stroke(Stroke::new(1.0_f32, ACCENT)) + .min_size(Vec2::new(130., 34.)); + let r = ui.add_enabled(link.ring.is_some(), button); + hint( + &r, + state, + "Grab the newest complete bars. While stopped, grabs the newest audio instead.", + ); + if r.clicked() { + if let Some(ring) = &link.ring { + match capture(ring, settings.bars) { + Ok(take) => { + let synced = take.synced; + state.add(take, settings); + state.status = if synced { + format!("Captured {} bars on the bar grid.", settings.bars) + } else { + "Transport stopped: captured the newest audio at the last tempo.".into() + }; + } + Err(e) => state.status = e.into(), + } + } + } + ui.add_space(10.); + caption(ui, "BARS"); + for bars in Settings::BARS { + if chip(ui, settings.bars == bars, &bars.to_string()).clicked() { + settings.bars = bars; + } + } + ui.add_space(10.); + ui.separator(); + let text = match state.transport { + Some(t) => format!( + "{:.1} BPM · {}/{} · {}", + t.tempo, + t.numerator, + t.denominator, + if t.playing { "playing" } else { "stopped" } + ), + None if link.ring.is_some() => "No host tempo yet · 140 BPM assumed".into(), + None => "Audio engine inactive".into(), + }; + ui.label(RichText::new(text).monospace().size(13.).color(STEEL)); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if let Some(ring) = &link.ring { + ui.label( + RichText::new(format!( + "{:.0} s history", + ring.capacity() as f32 / ring.rate() + )) + .size(12.) + .color(MUTED), + ); + } + }); +} + +fn takes(ui: &mut egui::Ui, state: &mut State) { + caption(ui, "TAKES"); + if state.takes.is_empty() { + ui.label(RichText::new("Nothing captured yet.").color(DIM)); + } + let mut pick = None; + for (i, name) in state.names.iter().enumerate() { + let r = ui.add_sized( + Vec2::new(ui.available_width(), 26.), + egui::SelectableLabel::new(state.take == Some(i), RichText::new(name).size(13.)), + ); + if r.clicked() { + pick = Some(i); + } + } + if let Some(i) = pick { + state.take = Some(i); + state.slice = None; + } + if let Some(i) = state.take { + ui.add_space(8.); + if ui.button(RichText::new("REMOVE TAKE").size(12.)).clicked() { + state.takes.remove(i); + state.names.remove(i); + state.take = (!state.takes.is_empty()).then_some(0); + state.slice = None; + } + } +} + +fn waveform(ui: &mut egui::Ui, settings: &Settings, state: &mut State, link: &Link, height: f32) { + let (rect, response) = + ui.allocate_exact_size(Vec2::new(ui.available_width(), height), Sense::click()); + let p = ui.painter_at(rect); + p.rect_filled(rect, 2., SCREEN); + let Some(ti) = state.take.filter(|i| *i < state.takes.len()) else { + p.text( + rect.center(), + egui::Align2::CENTER_CENTER, + "Play the part you want to resample, then press CAPTURE", + FontId::proportional(15.), + DIM, + ); + return; + }; + let take = &state.takes[ti]; + let frames = take.frames().max(1) as f32; + let label_band = 18.; + let wave = Rect::from_min_max(rect.min + Vec2::new(0., label_band), rect.max); + let x_of = |frame: usize| wave.left() + wave.width() * frame as f32 / frames; + // Slices: tint, disabled dimming, selection. + for (i, s) in take.slices.iter().enumerate() { + let r = Rect::from_min_max( + Pos2::new(x_of(s.start), wave.top()), + Pos2::new(x_of(s.end), wave.bottom()), + ); + let fill = if state.slice == Some(i) { + Color32::from_rgba_unmultiplied(255, 1, 96, 28) + } else if !s.enabled { + Color32::from_rgba_unmultiplied(0, 0, 0, 120) + } else { + Color32::TRANSPARENT + }; + p.rect_filled(r, 0., fill); + if r.width() > 26. { + let text = s.root.map_or("·".into(), common_dsp::note_name); + p.text( + Pos2::new(r.center().x, rect.top() + 3.), + egui::Align2::CENTER_TOP, + text, + FontId::proportional(11.), + if s.enabled { STEEL } else { DIM }, + ); + } + } + // Bar and beat grid. + let quarters = take.quarters(); + let bar = take.bar_quarters(); + let mut q = 0.; + while q <= quarters + 1e-6 { + let x = x_of(take.frame_at(q).round() as usize); + let is_bar = ((q / bar).round() * bar - q).abs() < 1e-6; + p.line_segment( + [Pos2::new(x, wave.top()), Pos2::new(x, wave.bottom())], + Stroke::new( + 1.0_f32, + if is_bar { + Color32::from_rgb(60, 64, 72) + } else { + Color32::from_rgb(28, 30, 34) + }, + ), + ); + q += 1.; + } + // Overview waveform. + let mid = wave.center().y; + let half = wave.height() * 0.46; + let peak = take + .overview + .iter() + .fold(0.01f32, |m, v| m.max(v[0].abs()).max(v[1].abs())); + let columns = wave.width().max(1.) as usize; + for c in 0..columns { + let a = c * OVERVIEW / columns; + let z = ((c + 1) * OVERVIEW / columns).max(a + 1).min(OVERVIEW); + let (mut lo, mut hi) = (0f32, 0f32); + for v in &take.overview[a..z] { + lo = lo.min(v[0]); + hi = hi.max(v[1]); + } + let x = wave.left() + c as f32 + 0.5; + p.line_segment( + [ + Pos2::new(x, mid - hi / peak * half), + Pos2::new(x, mid - lo / peak * half + 0.5), + ], + Stroke::new(1.0_f32, STEEL), + ); + } + // Cuts. + for c in &take.cuts[1..take.cuts.len().saturating_sub(1)] { + let x = x_of(*c); + p.line_segment( + [Pos2::new(x, rect.top()), Pos2::new(x, rect.bottom())], + Stroke::new(1.0_f32, ACCENT), + ); + } + p.rect_stroke( + rect, + 2., + Stroke::new(1.0_f32, EDGE), + egui::StrokeKind::Inside, + ); + let frame_at = + |pos: Pos2| (((pos.x - wave.left()) / wave.width()).clamp(0., 1.) * frames) as usize; + if response.hovered() { + state.help = + "Click a slice to hear it. Double-click to add a cut, right-click to remove the nearest." + .into(); + } + let pointer = response.interact_pointer_pos(); + if response.double_clicked() { + if let Some(pos) = pointer { + let f = frame_at(pos); + if let Some(t) = state.current() { + if t.add_cut(f) { + state.slice = state.takes[ti].slice_at(f); + } + } + } + } else if response.clicked() { + if let Some(pos) = pointer { + let f = frame_at(pos); + state.slice = state.takes[ti].slice_at(f); + if let Some(i) = state.slice { + audition(link, &state.takes[ti], i, settings); + } + } + } else if response.secondary_clicked() { + if let Some(pos) = pointer { + let f = frame_at(pos); + if let Some(t) = state.current() { + t.remove_cut(f); + } + state.slice = None; + } + } +} + +fn controls(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State, link: &Link) { + let before = settings.clone(); + ui.horizontal(|ui| { + caption(ui, "SLICE BY"); + for (mode, label) in [(Mode::Grid, "GRID"), (Mode::Transient, "TRANSIENTS")] { + if chip(ui, settings.mode == mode, label).clicked() { + settings.mode = mode; + } + } + ui.add_space(8.); + match settings.mode { + Mode::Grid => { + for d in Division::ALL { + if chip(ui, settings.division == d, d.label()).clicked() { + settings.division = d; + } + } + } + Mode::Transient => { + caption(ui, "SENSITIVITY"); + let r = ui.add( + egui::Slider::new(&mut settings.sensitivity, 0. ..=100.) + .suffix(" %") + .clamping(egui::SliderClamping::Always), + ); + hint(&r, state, "Higher finds quieter hits."); + } + } + ui.separator(); + caption(ui, "DECLICK"); + let r = ui.add( + egui::Slider::new(&mut settings.declick, 0. ..=20.) + .suffix(" ms") + .clamping(egui::SliderClamping::Always), + ); + hint( + &r, + state, + "Fade at both ends of every slice, so cuts never click.", + ); + }); + ui.horizontal(|ui| { + let r = chip(ui, settings.trim, "TRIM SILENCE"); + if r.clicked() { + settings.trim = !settings.trim; + } + hint(&r, state, "Remove silence before the first sound of a slice."); + let r = chip(ui, settings.normalize, "NORMALIZE"); + if r.clicked() { + settings.normalize = !settings.normalize; + } + hint(&r, state, "Peak every slice at −0.3 dBFS."); + let r = chip(ui, settings.wavetable, "WAVETABLE"); + if r.clicked() { + settings.wavetable = !settings.wavetable; + } + hint( + &r, + state, + "Also write wavetable.wav from the selected slice: 2048-sample frames for Serum or Vital.", + ); + ui.separator(); + let take = state.take; + let slice = state.slice; + match (take.and_then(|t| state.takes.get_mut(t)), slice) { + (Some(t), Some(i)) if i < t.slices.len() => { + let s = &mut t.slices[i]; + let seconds = (s.end - s.start) as f32 / t.rate; + ui.label( + RichText::new(format!( + "SLICE {} · {} · {:.2} s", + i + 1, + s.root.map_or("no pitch".into(), common_dsp::note_name), + seconds + )) + .size(13.) + .color(TEXT), + ); + ui.checkbox(&mut s.enabled, "Export"); + if ui.small_button("▶").clicked() { + audition(link, t, i, settings); + } + } + _ => { + ui.label(RichText::new("No slice selected").size(13.).color(DIM)); + } + } + }); + let recut = settings.mode != before.mode + || settings.division != before.division + || (settings.mode == Mode::Transient && settings.sensitivity != before.sensitivity); + if recut { + if let Some(t) = state.current() { + t.slice(settings); + } + state.slice = None; + } + ui.add_space(4.); + ui.separator(); + ui.horizontal(|ui| { + caption(ui, "FOLDER"); + if state.folder.is_empty() { + state.folder = settings.folder().display().to_string(); + } + let r = ui.add(egui::TextEdit::singleline(&mut state.folder).desired_width(420.)); + if r.changed() { + settings.folder = state.folder.clone(); + } + hint( + &r, + state, + "Add this folder to FL's browser once; exports then appear there for dragging.", + ); + let can = state.take.is_some() && state.job.is_none(); + let export_button = + egui::Button::new(RichText::new("EXPORT").size(14.).color(Color32::WHITE)) + .fill(Color32::from_rgb(150, 0, 56)) + .stroke(Stroke::new(1.0_f32, ACCENT)); + if ui.add_enabled(can, export_button).clicked() { + if let Some(take) = state.take.and_then(|i| state.takes.get(i)).cloned() { + let slot = Arc::new(Mutex::new(None)); + let done = slot.clone(); + let settings = settings.clone(); + let selected = state.slice; + std::thread::spawn(move || { + let folder = settings.folder(); + let result = export(&take, &settings, selected, &folder, unix_now()); + if let Ok(mut slot) = done.lock() { + *slot = Some(result); + } + }); + state.job = Some(slot); + state.status = "Exporting…".into(); + } + } + if ui.button(RichText::new("OPEN FOLDER").size(13.)).clicked() { + let path = state + .last_export + .clone() + .unwrap_or_else(|| settings.folder()); + let _ = std::fs::create_dir_all(&path); + open_folder(&path); + } + }); +} diff --git a/plugins/patina/plugin/Cargo.lock b/plugins/patina/plugin/Cargo.lock index 8b8ca14..429d283 100644 --- a/plugins/patina/plugin/Cargo.lock +++ b/plugins/patina/plugin/Cargo.lock @@ -306,6 +306,13 @@ dependencies = [ "memchr", ] +[[package]] +name = "common-ui" +version = "0.1.0" +dependencies = [ + "egui", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1435,6 +1442,7 @@ dependencies = [ name = "patina-ui" version = "0.2.0" dependencies = [ + "common-ui", "egui", "patina-core", ] diff --git a/plugins/patina/ui/Cargo.toml b/plugins/patina/ui/Cargo.toml index cdbc016..7df722b 100644 --- a/plugins/patina/ui/Cargo.toml +++ b/plugins/patina/ui/Cargo.toml @@ -7,3 +7,4 @@ license = "ISC" [dependencies] egui = "0.31.1" patina-core = { path = "../core" } +common-ui = { path = "../../common/ui" } diff --git a/plugins/patina/ui/src/lib.rs b/plugins/patina/ui/src/lib.rs index 6dc3ff2..554ac50 100644 --- a/plugins/patina/ui/src/lib.rs +++ b/plugins/patina/ui/src/lib.rs @@ -1,87 +1,10 @@ //! Shared native editor, also exercised by the headless renderer. +pub use common_ui::style; +use common_ui::*; use egui::{self, Color32, FontId, Pos2, Rect, RichText, Sense, Stroke, Vec2}; use patina_core::{focus_offset, frequency, AnalyzerFrame, Focus, Settings, GRID}; use std::time::{Duration, Instant}; -// Dark anodized hardware: graphite surfaces, one signal colour, steel data traces. -const BG: Color32 = Color32::from_rgb(14, 15, 17); -const PANEL: Color32 = Color32::from_rgb(25, 27, 31); -const PANEL_TOP: Color32 = Color32::from_rgb(34, 36, 41); -const EDGE: Color32 = Color32::from_rgb(44, 47, 53); -const SCREEN: Color32 = Color32::from_rgb(9, 10, 11); -const TEXT: Color32 = Color32::from_rgb(232, 233, 236); -const MUTED: Color32 = Color32::from_rgb(140, 146, 156); -const DIM: Color32 = Color32::from_rgb(70, 74, 82); -/// Signal colour: cuts, active states, value arcs. -const ACCENT: Color32 = Color32::from_rgb(255, 1, 96); -/// Measured input and neutral data. -const STEEL: Color32 = Color32::from_rgb(184, 192, 204); -/// Focus / protect curve, distinct from cuts without competing with them. -const FOCUS: Color32 = Color32::from_rgb(126, 167, 216); -pub fn style(ctx: &egui::Context) { - let mut fonts = egui::FontDefinitions::default(); - fonts.font_data.insert( - "regular".into(), - egui::FontData::from_static(include_bytes!("../assets/RobotoCondensed-Regular.ttf")).into(), - ); - fonts.font_data.insert( - "medium".into(), - egui::FontData::from_static(include_bytes!("../assets/RobotoCondensed-Medium.ttf")).into(), - ); - fonts - .families - .get_mut(&egui::FontFamily::Proportional) - .unwrap() - .insert(0, "regular".into()); - fonts.families.insert( - egui::FontFamily::Name("medium".into()), - vec![ - "medium".into(), - "Ubuntu-Light".into(), - "NotoEmoji-Regular".into(), - "emoji-icon-font".into(), - ], - ); - ctx.set_fonts(fonts); - let mut s = (*ctx.style()).clone(); - let v = &mut s.visuals; - *v = egui::Visuals::dark(); - v.panel_fill = BG; - v.window_fill = PANEL; - v.extreme_bg_color = SCREEN; - v.faint_bg_color = PANEL; - v.override_text_color = Some(TEXT); - v.selection.bg_fill = Color32::from_rgb(110, 6, 46); - v.selection.stroke = Stroke::new(1.0_f32, ACCENT); - v.hyperlink_color = ACCENT; - v.window_stroke = Stroke::new(1.0_f32, EDGE); - for (w, fill, stroke) in [ - (&mut v.widgets.noninteractive, PANEL, EDGE), - (&mut v.widgets.inactive, Color32::from_rgb(35, 38, 43), EDGE), - ( - &mut v.widgets.hovered, - Color32::from_rgb(45, 48, 54), - Color32::from_rgb(78, 82, 90), - ), - (&mut v.widgets.active, Color32::from_rgb(58, 18, 34), ACCENT), - (&mut v.widgets.open, Color32::from_rgb(35, 38, 43), EDGE), - ] { - w.bg_fill = fill; - w.weak_bg_fill = fill; - w.bg_stroke = Stroke::new(1.0_f32, stroke); - w.corner_radius = 3.into(); - } - v.widgets.noninteractive.fg_stroke = Stroke::new(1.0_f32, TEXT); - v.widgets.inactive.fg_stroke = Stroke::new(1.0_f32, TEXT); - v.slider_trailing_fill = true; - s.spacing.item_spacing = Vec2::new(10., 8.); - s.spacing.button_padding = Vec2::new(12., 6.); - s.text_styles - .insert(egui::TextStyle::Body, FontId::proportional(16.)); - s.text_styles - .insert(egui::TextStyle::Button, FontId::proportional(16.)); - ctx.set_style(s); -} pub struct State { pub delta: bool, pub selected: Option, @@ -139,29 +62,6 @@ impl State { fn db(x: f32) -> f32 { 20. * x.max(1e-6).log10() } -/// Disc with a vertical light-to-dark gradient, like brushed metal lit from above. -fn metal_disc(p: &egui::Painter, center: Pos2, radius: f32, top: Color32, bottom: Color32) { - let mut mesh = egui::Mesh::default(); - let mix = |t: f32| { - let l = |a: u8, b: u8| (a as f32 + (b as f32 - a as f32) * t).round() as u8; - Color32::from_rgb( - l(top.r(), bottom.r()), - l(top.g(), bottom.g()), - l(top.b(), bottom.b()), - ) - }; - mesh.colored_vertex(center, mix(0.5)); - let n = 48; - for i in 0..=n { - let a = std::f32::consts::TAU * i as f32 / n as f32; - let point = center + Vec2::angled(a) * radius; - mesh.colored_vertex(point, mix((point.y - (center.y - radius)) / (2. * radius))); - if i > 0 { - mesh.add_triangle(0, i, i + 1); - } - } - p.add(egui::Shape::mesh(mesh)); -} /// Shows a help line in the status bar while the knob is hovered or focused. fn hint(response: egui::Response, state: &mut State, text: &str) { if response.hovered() || response.has_focus() { @@ -212,166 +112,6 @@ fn control( } }); } -#[allow(clippy::too_many_arguments)] -fn rotary( - ui: &mut egui::Ui, - name: &str, - value: &mut f32, - range: std::ops::RangeInclusive, - default: f32, - unit: &str, - size: f32, - color: Color32, -) -> egui::Response { - let low = *range.start(); - let high = *range.end(); - let (rect, response) = ui.allocate_exact_size(Vec2::splat(size), Sense::click_and_drag()); - if response.drag_started() { - response.request_focus(); - } - if response.dragged() { - let delta = ui.input(|i| i.pointer.delta().y); - let fine = if ui.input(|i| i.modifiers.shift) { - 0.1 - } else { - 1. - }; - *value = (*value - delta * (high - low) / 180. * fine).clamp(low, high); - } - if response.double_clicked() { - *value = default; - } - if response.has_focus() { - let step = if ui.input(|i| i.modifiers.shift) { - 0.1 - } else { - 1. - }; - if ui.input(|i| i.key_pressed(egui::Key::ArrowUp) || i.key_pressed(egui::Key::ArrowRight)) { - *value = (*value + step).min(high); - } - if ui.input(|i| i.key_pressed(egui::Key::ArrowDown) || i.key_pressed(egui::Key::ArrowLeft)) - { - *value = (*value - step).max(low); - } - } - let center = rect.center(); - let radius = size * 0.46; - let p = ui.painter(); - let start = std::f32::consts::PI * 0.75; - let span = std::f32::consts::PI * 1.5; - let normalized = ((*value - low) / (high - low)).clamp(0., 1.); - // Scale track and value arc around the knob. - for (fraction, stroke) in [ - (1., Stroke::new(2.5_f32, DIM)), - (normalized, Stroke::new(2.5_f32, color)), - ] { - let points: Vec<_> = (0..=64) - .map(|i| { - let a = start + span * fraction * i as f32 / 64.; - center + Vec2::angled(a) * (radius - 1.5) - }) - .collect(); - p.add(egui::Shape::line(points, stroke)); - } - // Machined body: drop shadow, brushed gradient skirt, flat cap. - let body = radius - 7.; - p.circle_filled( - center + Vec2::new(0., 2.), - body + 1., - Color32::from_black_alpha(140), - ); - metal_disc( - p, - center, - body, - Color32::from_rgb(78, 82, 90), - Color32::from_rgb(28, 30, 34), - ); - p.circle_stroke( - center, - body, - Stroke::new(1.0_f32, Color32::from_rgb(10, 11, 12)), - ); - let cap = body * 0.74; - metal_disc( - p, - center, - cap, - Color32::from_rgb(44, 47, 52), - Color32::from_rgb(33, 35, 40), - ); - p.circle_stroke( - center, - cap, - Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, 22)), - ); - let angle = start + span * normalized; - p.line_segment( - [ - center + Vec2::angled(angle) * (cap * 0.35), - center + Vec2::angled(angle) * (cap - 2.), - ], - Stroke::new(2.0_f32, TEXT), - ); - if response.has_focus() { - p.circle_stroke(center, radius + 2., Stroke::new(1.0_f32, color)); - } - ui.label( - RichText::new(name.to_uppercase()) - .size(if size > 100. { 14. } else { 12. }) - .color(MUTED), - ); - ui.add( - egui::DragValue::new(value) - .range(range) - .speed(0.1) - .suffix(unit), - ); - response -} -/// Vertical gradient rectangle, used for panel faces. -fn gradient(rect: Rect, top: Color32, bottom: Color32) -> egui::Shape { - let mut mesh = egui::Mesh::default(); - for (pos, color) in [ - (rect.left_top(), top), - (rect.right_top(), top), - (rect.right_bottom(), bottom), - (rect.left_bottom(), bottom), - ] { - mesh.colored_vertex(pos, color); - } - mesh.add_triangle(0, 1, 2); - mesh.add_triangle(0, 2, 3); - egui::Shape::mesh(mesh) -} -/// Raised hardware panel: graphite face, hairline edge, faint top highlight. -fn panel(ui: &mut egui::Ui, add: impl FnOnce(&mut egui::Ui) -> R) -> R { - let face = ui.painter().add(egui::Shape::Noop); - let inner = egui::Frame::new() - .inner_margin(14.) - .stroke(Stroke::new(1.0_f32, EDGE)) - .show(ui, add); - let r = inner.response.rect; - ui.painter().set(face, gradient(r, PANEL_TOP, PANEL)); - ui.painter().line_segment( - [ - r.left_top() + Vec2::new(1., 1.), - r.right_top() + Vec2::new(-1., 1.), - ], - Stroke::new(1.0_f32, Color32::from_rgba_unmultiplied(255, 255, 255, 16)), - ); - inner.inner -} -fn caption(ui: &mut egui::Ui, text: &str) { - ui.label(RichText::new(text).size(11.).color(MUTED)); -} -/// Small labelled hardware-style readout. -fn readout(ui: &mut egui::Ui, label: &str, value: String, color: Color32) { - ui.label(RichText::new(label).size(11.).color(MUTED)); - ui.label(RichText::new(value).monospace().size(13.).color(color)); - ui.add_space(10.); -} pub fn show(ui: &mut egui::Ui, settings: &mut Settings, state: &mut State) { ui.spacing_mut().slider_width = ((ui.available_width() - 100.) / 4. - 85.).max(95.); let before = *settings; diff --git a/scripts/release.py b/scripts/release.py index 94ebf62..29c0ccc 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -73,7 +73,7 @@ def plan(tag, root=ROOT): tag = tag.removeprefix("refs/tags/") plugin = next((p for p in catalog(root) if tag.startswith(p + "-v")), None) if plugin is None: - raise ValueError("Use a rivet-v or patina-v tag") + raise ValueError("Use a -v tag, for example " + ", ".join(p + "-v1.0.0" for p in catalog(root))) release_version = tag_version(plugin, tag, root) release_notes(plugin, release_version, root) return {"plugin": plugin, "version": release_version, "tag": tag, diff --git a/scripts/tests/test_release.py b/scripts/tests/test_release.py index 542063b..55e3260 100644 --- a/scripts/tests/test_release.py +++ b/scripts/tests/test_release.py @@ -103,6 +103,25 @@ class ReleaseTests(unittest.TestCase): with self.assertRaises(ValueError): tag_version(plugin, tag, self.root) + def test_unknown_tag_names_every_catalogued_plugin(self): + with self.assertRaisesRegex(ValueError, "rivet-v1.0.0, patina-v1.0.0"): + release.plan("refs/tags/unknown-v1.0.0", self.root) + + def test_repository_catalog_is_complete(self): + root = Path(__file__).resolve().parents[2] + products = json.loads((root / "plugins/catalog.json").read_text()) + check = (root / ".gitea/workflows/check.yml").read_text() + triggers = (root / ".gitea/workflows/release.yml").read_text() + ids = [p["clap_id"] for p in products.values()] + self.assertEqual(len(ids), len(set(ids)), "CLAP IDs must be unique") + for plugin, product in products.items(): + self.assertTrue((root / product["manifest"]).is_file(), plugin) + self.assertTrue((root / product["documentation"] / "CHANGELOG.md").is_file(), plugin) + self.assertIn(f'"{plugin}-v*"', triggers) + self.assertIn(f"--plugin {plugin} --target", check) + for source in product.get("licenses", {}): + self.assertTrue((root / source).is_file(), source) + def test_individual_zip_excludes_other_plugin_and_is_reproducible(self): path = self.package("patina") first = path.read_bytes() diff --git a/tools/editor-check/Cargo.lock b/tools/editor-check/Cargo.lock index 0ed3793..059dd1b 100644 --- a/tools/editor-check/Cargo.lock +++ b/tools/editor-check/Cargo.lock @@ -36,6 +36,24 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "anvil-core" +version = "0.1.0" +dependencies = [ + "common-dsp", + "serde", +] + +[[package]] +name = "anvil-ui" +version = "0.1.0" +dependencies = [ + "anvil-core", + "common-dsp", + "common-ui", + "egui", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -66,6 +84,17 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" +[[package]] +name = "common-dsp" +version = "0.1.0" + +[[package]] +name = "common-ui" +version = "0.1.0" +dependencies = [ + "egui", +] + [[package]] name = "crc32fast" version = "1.5.3" @@ -90,6 +119,25 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[package]] +name = "crucible-core" +version = "0.1.0" +dependencies = [ + "common-dsp", + "crossbeam-queue", + "serde", +] + +[[package]] +name = "crucible-ui" +version = "0.1.0" +dependencies = [ + "common-dsp", + "common-ui", + "crucible-core", + "egui", +] + [[package]] name = "ecolor" version = "0.31.1" @@ -103,6 +151,11 @@ dependencies = [ name = "editor-check" version = "0.1.0" dependencies = [ + "anvil-core", + "anvil-ui", + "common-dsp", + "crucible-core", + "crucible-ui", "egui", "image", "patina-core", @@ -327,6 +380,7 @@ dependencies = [ name = "patina-ui" version = "0.2.0" dependencies = [ + "common-ui", "egui", "patina-core", ] diff --git a/tools/editor-check/Cargo.toml b/tools/editor-check/Cargo.toml index 3ec629e..ab0eeae 100644 --- a/tools/editor-check/Cargo.toml +++ b/tools/editor-check/Cargo.toml @@ -14,3 +14,8 @@ rivet-core = { path = "../../plugins/rivet/core" } rivet-midi = { path = "../../plugins/rivet/midi" } patina-core = { path = "../../plugins/patina/core" } patina-ui = { path = "../../plugins/patina/ui" } +anvil-core = { path = "../../plugins/anvil/core" } +anvil-ui = { path = "../../plugins/anvil/ui" } +common-dsp = { path = "../../plugins/common/dsp" } +crucible-core = { path = "../../plugins/crucible/core" } +crucible-ui = { path = "../../plugins/crucible/ui" } diff --git a/tools/editor-check/src/main.rs b/tools/editor-check/src/main.rs index 9890398..94065dc 100644 --- a/tools/editor-check/src/main.rs +++ b/tools/editor-check/src/main.rs @@ -10,13 +10,19 @@ fn main() { .find(|a| a[0] == "--plugin") .map(|a| a[1].as_str()) .unwrap_or("rivet"); - if !["rivet", "patina"].contains(&plugin) { - eprintln!("Use --plugin rivet or --plugin patina"); + if !["rivet", "patina", "anvil", "crucible"].contains(&plugin) { + eprintln!("Use --plugin rivet, patina, anvil or crucible"); std::process::exit(2); } let patina = plugin == "patina" || args.iter().any(|arg| arg == "--resonance"); let compact = std::env::args().any(|arg| arg == "--compact"); - let (width, height) = if patina { + let anvil = plugin == "anvil"; + let crucible = plugin == "crucible"; + let (width, height) = if crucible { + (crucible_ui::WIDTH, crucible_ui::HEIGHT) + } else if anvil { + (anvil_ui::WIDTH, anvil_ui::HEIGHT) + } else if patina { if compact { (1040, 700) } else { @@ -63,6 +69,90 @@ fn main() { } } } + let mut anvil_settings = anvil_core::Settings::default(); + let mut anvil_state = anvil_ui::State::default(); + if anvil { + let mut engine = anvil_core::Engine::new(48000., 2).unwrap(); + engine.set_settings(anvil_settings).unwrap(); + let mut saw = common_dsp::Saw::new(48000., 43.65); + for i in 0..36000 { + // A distorted F1 bass whose own sub drifts in phase and level. + let x = (saw.sample() * 1.6 + 0.3 * (i as f32 * 0.0009).sin()).tanh() * 0.4; + engine.process_sample([x, x], anvil_core::Monitor::default()); + if i % 1600 == 0 { + anvil_state.accept_frame(engine.take_analyzer()); + } + } + anvil_state.accept_frame(engine.take_analyzer()); + } + let mut crucible_settings = crucible_core::Settings { + folder: "C:\\Users\\you\\Documents\\Crucible".into(), + ..Default::default() + }; + let mut crucible_state = crucible_ui::State::default(); + let crucible_link = crucible_ui::Link { + ring: Some(Arc::new(crucible_core::Ring::new( + 48000., + crucible_core::HISTORY, + ))), + queues: Arc::new(crucible_core::Queues::default()), + }; + if crucible { + // Four bars of a 150 BPM riddim pattern: square-ish bass hits on varied notes. + let rate = 48000.; + let beat = 60. / 150. * rate; + let frames = (16. * beat) as usize; + let mut audio = vec![0f32; frames * 2]; + let pattern = [ + (0., 29), + (1.5, 29), + (2.75, 32), + (4., 27), + (5.5, 29), + (6.5, 34), + (8., 29), + (9.5, 29), + (10.75, 36), + (12., 27), + (13.5, 32), + (14.5, 29), + ]; + for (at, note) in pattern { + let start = (at * beat) as usize; + let hz = common_dsp::midi_to_hz(note as f32); + let mut saw = common_dsp::Saw::new(rate, hz); + for i in 0..(0.6 * beat) as usize { + let f = start + i; + if f >= frames { + break; + } + let env = (1. - i as f32 / (0.6 * beat)).powf(0.4); + let v = (saw.sample() * 2.5).tanh() * 0.6 * env; + audio[2 * f] += v; + audio[2 * f + 1] += v; + } + } + let transport = crucible_core::Transport { + playing: true, + tempo: 150., + beats: 0., + numerator: 4, + denominator: 4, + }; + let segments = vec![crucible_core::Segment { + frame: 0., + quarters: 0., + tempo: 150., + }]; + let take = crucible_core::Take::from_audio(rate, audio, 4, transport, segments, true); + crucible_settings.mode = crucible_core::Mode::Transient; + crucible_state.add(take.clone(), &crucible_settings); + crucible_state.add(take, &crucible_settings); + crucible_state.slice = Some(2); + crucible_state.takes[0].slices[5].enabled = false; + crucible_state.transport = Some(transport); + crucible_state.status = "Captured 4 bars on the bar grid.".into(); + } let shared = Arc::new(rivet_app::Shared::default()); let mut session = rivet_core::Session::demo(); session.motifs[0].phrase.notes[0].locked = true; @@ -83,7 +173,11 @@ fn main() { let session_motif = session.motifs[0].id; shared.replace(session).unwrap(); let ctx = egui::Context::default(); - if patina { + if crucible { + crucible_ui::style(&ctx); + } else if anvil { + anvil_ui::style(&ctx); + } else if patina { patina_ui::style(&ctx); } else { editor::style(&ctx); @@ -108,7 +202,16 @@ fn main() { }, |ui| { egui::CentralPanel::default().show(ui, |ui| { - if patina { + if crucible { + crucible_ui::show( + ui, + &mut crucible_settings, + &mut crucible_state, + &crucible_link, + ); + } else if anvil { + anvil_ui::show(ui, &mut anvil_settings, &mut anvil_state); + } else if patina { patina_ui::show(ui, &mut patina_settings, &mut patina_state); } else { editor::show(ui, &shared, &mut state);