diff --git a/.envrc b/.envrc index a73927ee..2506dccb 100644 --- a/.envrc +++ b/.envrc @@ -1,3 +1,3 @@ CARGO_TERM_COLOR=always RUST_BACKTRACE=full -RUST_LOG="debug,rstmt=info,debug" \ No newline at end of file +RUST_LOG="rsfi=info,debug,tower_http=info" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dc90b95c..ef25834d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,10 +8,12 @@ title: 'Bug report: ' type: bug --- -**Describe the bug** -A clear and concise description of what the bug is. +## Description + + + +### Reproduction -**To Reproduce** Steps to reproduce the behavior: 1. Go to '...' @@ -19,24 +21,29 @@ Steps to reproduce the behavior: 3. Scroll down to '....' 4. See error -**Expected behavior** -A clear and concise description of what you expected to happen. +#### *Expectations* + + + +#### *Actual* -**Screenshots** -If applicable, add screenshots to help explain your problem. + + +### Host Details + + **Desktop (please complete the following information):** +- Device Type: [e.g. iPhone6] - OS: [e.g. iOS] -- Browser [e.g. chrome, safari] -- Version [e.g. 22] +- Browser: [e.g. chrome, safari] +- Version: v0.0.x + +## Discussion -**Smartphone (please complete the following information):** + -- Device: [e.g. iPhone6] -- OS: [e.g. iOS8.1] -- Browser [e.g. stock browser, safari] -- Version [e.g. 22] +### Screenshots -**Additional context** -Add any other context about the problem here. + diff --git a/.github/ISSUE_TEMPLATE/tracking.md b/.github/ISSUE_TEMPLATE/tracking.md index 53b4bec2..452f3650 100644 --- a/.github/ISSUE_TEMPLATE/tracking.md +++ b/.github/ISSUE_TEMPLATE/tracking.md @@ -7,9 +7,9 @@ title: 'Tracking Issue for ' type: feature --- - + -_**Goals**_ +## Goals The goals of this proposal: diff --git a/.github/workflows/cargo-publish.yml b/.github/workflows/cargo-publish.yml index 38795e83..0c332422 100644 --- a/.github/workflows/cargo-publish.yml +++ b/.github/workflows/cargo-publish.yml @@ -9,6 +9,8 @@ env: RUST_BACKTRACE: full on: + release: + types: [published] repository_dispatch: types: [crates-io, cargo-publish] workflow_dispatch: @@ -34,11 +36,12 @@ jobs: - rstmt-macros - rstmt steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 with: fetch-depth: 0 - repository: ${{ github.repository }} ref: ${{ github.event.client_payload.ref || github.ref }} + repository: ${{ github.event.client_payload.repository || github.repository }} token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 3f7ba82d..7bd0856a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -22,7 +22,10 @@ jobs: continue-on-error: true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Nix uses: cachix/install-nix-action@v31 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4b5be747..1a9bc0a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,18 +26,19 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 with: fetch-depth: 0 - ref: ${{ github.ref }} + ref: ${{ github.event.client_payload.ref || github.ref }} repository: ${{ github.repository }} - token: ${{ github.token }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Dispatch & Publish to crates.io uses: peter-evans/repository-dispatch@v4 with: event-type: cargo-publish client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}"}' - token: ${{ github.token }} + token: ${{ secrets.GITHUB_TOKEN }} - name: Create GitHub Release uses: softprops/action-gh-release@v2 continue-on-error: true diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 4cd8370c..164ec765 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -39,7 +39,10 @@ jobs: matrix: target: [x86_64-unknown-linux-gnu] steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -59,7 +62,10 @@ jobs: features: [full, default] target: [x86_64-unknown-linux-gnu] steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -67,26 +73,27 @@ jobs: target: ${{ matrix.target }} toolchain: stable override: true - - name: Test (default) - if: matrix.features == 'default' - run: cargo test -r --locked --workspace --target ${{ matrix.target}} - name: Test (${{ matrix.features }}) - if: matrix.features != 'default' run: cargo test -r --locked --workspace --target ${{ matrix.target}} --features ${{ matrix.features }} test_nightly: - if: github.event.inputs.toolchain == 'nightly' || false - continue-on-error: true + if: github.event_name == 'workflow_dispatch' && github.event.inputs.toolchain == 'nightly' needs: build runs-on: ubuntu-latest + env: + RUSTFLAGS: "-C panic=abort -Z panic_abort_tests" strategy: fail-fast: false matrix: + package: [rstmt-traits] features: - all - no_std - "alloc,nightly" steps: - - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -99,12 +106,8 @@ jobs: - name: Test (no_std) continue-on-error: true if: matrix.features == 'no_std' - run: cargo test -r --locked --workspace --no-default-features --features nightly - env: - RUSTFLAGS: "-C panic=abort -Z panic_abort_tests" + run: cargo test -r --locked --workspace --no-default-features -p ${{ matrix.package }} - name: Test (${{ matrix.features }}) continue-on-error: true if: matrix.features != 'all' && matrix.features != 'no_std' - run: cargo test -r --locked --workspace --no-default-features --features ${{ matrix.features }} - env: - RUSTFLAGS: "-C panic=abort -Z panic_abort_tests" + run: cargo test -r --locked --workspace --no-default-features -p ${{ matrix.package }} -F ${{ matrix.features }} diff --git a/Cargo.lock b/Cargo.lock index b4c58b0d..901574ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,9 +79,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "shlex", @@ -95,9 +95,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "num-traits", @@ -153,42 +153,55 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "contained" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a1ab5394ad9555b4ede356ee384f97380d9af10c85292dffbfcc842fae8150" +checksum = "171e57f4f9cf61380ad201fa1016ea5842440adff054c4314973bff1ebe0ae07" dependencies = [ "contained-core", + "contained-derive", "contained-macros", ] [[package]] name = "contained-core" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cda86cb77598cde9b724e93c10434cf07c3a173703d825fdc3e64d6b46c34b" +checksum = "e390113196f3af3198f351253848f9e3f08ecdfb1401556ec9cbc21c0a2f25dd" dependencies = [ + "getrandom", "hashbrown 0.16.1", "num-complex", "num-traits", "rand 0.9.2", - "rand_core 0.9.3", "rand_distr", "serde", + "serde_derive", "serde_json", "thiserror", ] +[[package]] +name = "contained-derive" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022e04851be80b860c3a70ab3baa7934dcc8291391f97eb0a63e97734afcd28e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "contained-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b24b213c630b8ecea73dc39b48103e631a4953abe852b670faff1d9513a6a96e" +checksum = "0c94f9f4d49568014c259c7ea8d29eaa19c68afc09e2dd92afe06953a784259f" dependencies = [ "proc-macro2", "quote", @@ -325,9 +338,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fnv" @@ -478,9 +491,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -519,12 +532,38 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", + "serde", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -643,6 +682,21 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -699,7 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core 0.9.3", + "rand_core 0.9.5", "serde", ] @@ -710,7 +764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -724,9 +778,9 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom", "serde", @@ -744,6 +798,12 @@ dependencies = [ "serde_with", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.11.0" @@ -876,11 +936,12 @@ dependencies = [ [[package]] name = "rspace-traits" -version = "0.0.7" +version = "0.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9a0ef5a8f6f27c6004e5be25e2cb1a16767e02826f0754ba5f0b9915f2b6082" +checksum = "f14d444107a912e6c13038f81d3d44ba4147fcbf82872b0a0a572263f556b334" dependencies = [ "hashbrown 0.16.1", + "ndarray", "num-complex", "num-traits", "paste", @@ -889,7 +950,7 @@ dependencies = [ [[package]] name = "rstmt" -version = "0.0.12" +version = "0.0.13" dependencies = [ "anyhow", "criterion", @@ -903,7 +964,7 @@ dependencies = [ [[package]] name = "rstmt-core" -version = "0.0.12" +version = "0.0.13" dependencies = [ "anyhow", "contained", @@ -931,7 +992,7 @@ dependencies = [ [[package]] name = "rstmt-macros" -version = "0.0.12" +version = "0.0.13" dependencies = [ "proc-macro2", "quote", @@ -940,7 +1001,7 @@ dependencies = [ [[package]] name = "rstmt-nrt" -version = "0.0.12" +version = "0.0.13" dependencies = [ "anyhow", "getrandom", @@ -962,13 +1023,12 @@ dependencies = [ "strum", "thiserror", "tracing", - "variants", "wasm-bindgen", ] [[package]] name = "rstmt-traits" -version = "0.0.12" +version = "0.0.13" dependencies = [ "hashbrown 0.16.1", "num-complex", @@ -1145,18 +1205,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1174,30 +1234,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" dependencies = [ "num-conv", "time-core", @@ -1342,22 +1402,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", + "serde_json", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1365,9 +1427,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -1378,9 +1440,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -1398,9 +1460,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -1533,6 +1595,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" diff --git a/Cargo.toml b/Cargo.toml index 2d89c562..b501bd9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,19 +28,19 @@ keywords = [ license = "Apache-2.0" readme = "README.md" repository = "https://github.com/FL03/rstmt.git" -rust-version = "1.85.0" -version = "0.0.12" +rust-version = "1.86.0" +version = "0.0.13" [workspace.dependencies] -rstmt = { default-features = false, path = "rstmt", version = "0.0.12" } -rstmt-core = { default-features = false, path = "core", version = "0.0.12" } -rstmt-macros = { path = "macros", version = "0.0.12" } -rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.12" } -rstmt-traits = { default-features = false, path = "traits", version = "0.0.12" } +rstmt = { default-features = false, path = "rstmt", version = "0.0.13" } +rstmt-core = { default-features = false, path = "core", version = "0.0.13" } +rstmt-macros = { path = "macros", version = "0.0.13" } +rstmt-nrt = { default-features = false, path = "nrt", version = "0.0.13" } +rstmt-traits = { default-features = false, path = "traits", version = "0.0.13" } # custom -contained = { default-features = false, features = ["macros"], version = "0.2.3" } +contained = { default-features = false, features = ["derive", "macros"], version = "0.2.4" } rshyper = { default-features = false, features = ["macros"], version = "0.1.9" } -rspace-traits = { default-features = false, version = "0.0.7" } +rspace-traits = { default-features = false, version = "0.0.9" } variants = { default-features = false, features = ["derive"], version = "0.0.1" } # benchmarking & testing criterion = { default-features = false, version = "0.8" } @@ -86,7 +86,7 @@ opt-level = 2 overflow-checks = true panic = "abort" rpath = true -strip = "symbols" +strip = false [profile.release] codegen-units = 16 diff --git a/clippy.toml b/clippy.toml index 57a49828..55787ca3 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1 @@ -msrv = "1.85.0" \ No newline at end of file +msrv = "1.86.0" \ No newline at end of file diff --git a/core/Cargo.toml b/core/Cargo.toml index 419c1363..678fea4e 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,10 +26,6 @@ tag-name = "v{{version}}" [lib] bench = false -crate-type = ["lib"] -doc = true -doctest = true -test = true [dependencies] rstmt-traits = { workspace = true } @@ -103,6 +99,7 @@ std = [ "anyhow/std", "contained/std", "hashbrown?/default", + "getrandom?/std", "num-complex?/std", "num-integer/std", "num-traits/std", @@ -115,6 +112,7 @@ std = [ "strum/std", "tracing?/std", "variants/std", + "wasm-bindgen?/std", ] wasi = [ @@ -142,6 +140,7 @@ alloc = [ "serde?/alloc", "serde_json?/alloc", "variants/alloc", + "wasm-bindgen?/gg-alloc", ] complex = [ @@ -180,15 +179,20 @@ serde = [ "dep:serde", "dep:serde_derive", "serde?/derive", + "contained/serde", "hashbrown?/serde", "num-complex?/serde", "rand?/serde", "rand_distr?/serde", "rspace-traits/serde", "rstmt-traits/serde", + "wasm-bindgen?/serde", ] -serde_json = ["dep:serde_json"] +serde_json = [ + "dep:serde_json", + "wasm-bindgen?/serde_json", +] tracing = ["dep:tracing"] diff --git a/core/src/chords/chord_base.rs b/core/src/chords/chord_base.rs new file mode 100644 index 00000000..e74de2e0 --- /dev/null +++ b/core/src/chords/chord_base.rs @@ -0,0 +1,34 @@ +/* + Appellation: chord_base + Created At: 2026.01.20:08:24:06 + Contrib: @FL03 +*/ +use super::RawChord; + +pub type ChordSlice = ChordBase<[T], T>; + +pub type ChordArray = ChordBase<[T; N], T>; + +pub type ChordSliceRef<'a, T> = ChordBase<&'a [T], T>; + +pub type ChordSliceMut<'a, T> = ChordBase<&'a mut [T], T>; + +#[cfg(feature = "alloc")] +/// a type alias for a [`ChordBase`] that leverages a [`Vec`](alloc::vec::Vec) as its underlying +/// representation. +pub type Chord = ChordBase, T>; + +/// The [`ChordBase`] implementation is designed to be a generic container allowing +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(deny_unknown_fields, rename_all = "snake_case") +)] +#[repr(C)] +pub struct ChordBase::Elem> +where + R: ?Sized + RawChord, +{ + pub(crate) repr: R, +} diff --git a/core/src/chords/impls/impl_chord_base.rs b/core/src/chords/impls/impl_chord_base.rs new file mode 100644 index 00000000..a5723aad --- /dev/null +++ b/core/src/chords/impls/impl_chord_base.rs @@ -0,0 +1,34 @@ +/* + Appellation: impl_chord_base + Created At: 2026.01.20:08:26:02 + Contrib: @FL03 +*/ +use crate::chords::chord_base::ChordBase; + +use crate::chords::RawChord; + +impl ChordBase +where + S: RawChord, +{ + /// creates a new [`ChordBase`] instance from a given chord representation. + pub const fn new(repr: S) -> Self { + Self { repr } + } + /// returns a reference to the underlying chord representation. + pub const fn data(&self) -> &S { + &self.repr + } + /// returns a mutable reference to the underlying chord representation. + pub const fn data_mut(&mut self) -> &mut S { + &mut self.repr + } + /// returns the number of elements in the chord representation. + pub fn len(&self) -> usize { + self.repr.len() + } + /// returns true if the chord contains no elements + pub fn is_empty(&self) -> bool { + self.repr.is_empty() + } +} diff --git a/core/src/chord/mod.rs b/core/src/chords/mod.rs similarity index 77% rename from core/src/chord/mod.rs rename to core/src/chords/mod.rs index 70a80264..5a8a4d90 100644 --- a/core/src/chord/mod.rs +++ b/core/src/chords/mod.rs @@ -7,7 +7,13 @@ //! to generalize their behavior across various representations. //! #[doc(inline)] -pub use self::traits::*; +pub use self::{chord_base::*, traits::*}; + +mod chord_base; + +mod impls { + mod impl_chord_base; +} mod traits { #[doc(inline)] @@ -18,6 +24,6 @@ mod traits { // prelude (local) #[doc(hidden)] pub(crate) mod prelude { - #[doc(inline)] + pub use super::chord_base::*; pub use super::traits::*; } diff --git a/core/src/chord/traits/raw_chord.rs b/core/src/chords/traits/raw_chord.rs similarity index 79% rename from core/src/chord/traits/raw_chord.rs rename to core/src/chords/traits/raw_chord.rs index 7c590b3d..23f0b3f5 100644 --- a/core/src/chord/traits/raw_chord.rs +++ b/core/src/chords/traits/raw_chord.rs @@ -3,16 +3,18 @@ Created At: 2025.12.23:17:32:04 Contrib: @FL03 */ -use rspace_traits::RawSpace; /// The [`RawChord`] trait works to define a basic interface shared by all compatible /// reprsentations of a chord. Since a chord is essentially a sequence of pitches, the trait /// captures this behavior through association with an element type. -pub trait RawChord: RawSpace { +pub trait RawChord { + type Elem; + + private! {} /// returns a slice representation of the chord. fn as_slice(&self) -> &[Self::Elem]; /// returns the number of elements in the chord representation. fn len(&self) -> usize; - + /// returns true if the chord contains no elements fn is_empty(&self) -> bool { self.len() == 0 } @@ -20,15 +22,21 @@ pub trait RawChord: RawSpace { /// The [`RawChordMut`] trait extends the [`RawChord`] trait to provide mutable access to the /// underlying elements of the chord representation. -pub trait RawChordMut: RawSpace { +pub trait RawChordMut: RawChord { /// returns a mutable slice representation of the chord. fn as_mut_slice(&mut self) -> &mut [Self::Elem]; } - -pub trait ChordRepr: RawSpace {} +/// The [`ChordRepr`] trait extends the [`RawChord`] interface to include useful initialization +/// routines and other associated functions. +pub trait ChordRepr: RawChord + Sized { + /// creates a new chord representation from a slice of elements. + fn from_slice(slice: &[Self::Elem]) -> Self + where + Self::Elem: Clone; +} /// The [`RawChordIter`] trait extends the [`RawChord`] trait to provide an iterator over /// the elements of the chord representation. -pub trait RawChordIter: RawSpace { +pub trait RawChordIter: RawChord { type Iter<'b>: Iterator where Self::Elem: 'b, @@ -45,6 +53,10 @@ impl RawChord for &C where C: RawChord, { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { C::as_slice(*self) } @@ -58,6 +70,10 @@ impl RawChord for &mut C where C: RawChord, { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { C::as_slice(*self) } @@ -68,6 +84,10 @@ where } impl RawChord for (T, T, T) { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { unsafe { core::slice::from_raw_parts(self as *const (T, T, T) as *const T, 3) } } @@ -78,6 +98,10 @@ impl RawChord for (T, T, T) { } impl RawChord for [T] { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self } @@ -87,7 +111,17 @@ impl RawChord for [T] { } } +impl RawChordMut for [T] { + fn as_mut_slice(&mut self) -> &mut [T] { + self + } +} + impl RawChord for &[T] { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self } @@ -98,6 +132,9 @@ impl RawChord for &[T] { } impl RawChord for &mut [T] { + type Elem = T; + + seal! {} fn as_slice(&self) -> &[T] { self } @@ -107,12 +144,6 @@ impl RawChord for &mut [T] { } } -impl RawChordMut for [T] { - fn as_mut_slice(&mut self) -> &mut [T] { - self - } -} - impl RawChordMut for &mut [T] { fn as_mut_slice(&mut self) -> &mut [T] { self @@ -120,6 +151,10 @@ impl RawChordMut for &mut [T] { } impl RawChord for [T; N] { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self } @@ -141,6 +176,10 @@ mod impl_alloc { use alloc::vec::Vec; impl RawChord for Vec { + type Elem = T; + + seal! {} + fn as_slice(&self) -> &[T] { self.as_slice() } diff --git a/core/src/comp/impls/impl_scale.rs b/core/src/compose/impls/impl_scale.rs similarity index 65% rename from core/src/comp/impls/impl_scale.rs rename to core/src/compose/impls/impl_scale.rs index 897f0422..78c3429d 100644 --- a/core/src/comp/impls/impl_scale.rs +++ b/core/src/compose/impls/impl_scale.rs @@ -3,8 +3,10 @@ Created At: 2025.12.21:08:58:02 Contrib: @FL03 */ -use crate::comp::Scale; -use crate::freq::{Frequency, IntoFrequency, RawFrequency}; +use crate::compose::Scale; +use crate::freq::{ + Frequency, IntoFrequency, RawFrequency, classify_freq_with_scale, get_frequency_of_pitch, +}; use num_traits::{Float, FromPrimitive}; impl Scale { @@ -13,6 +15,15 @@ impl Scale { root: Frequency(root), } } + /// initialize a new scale using A4 as the root + pub fn a4() -> Self + where + T: FromPrimitive, + { + let root = ::from_usize(440).unwrap(); + Self::new(root) + } + /// creates a new [`Scale`] from the given root frequency pub const fn from_freq(root: Frequency) -> Self { Self { root } @@ -28,7 +39,7 @@ impl Scale { /// classifies the given frequency as a pitch class `n`, using the formula: /// /// ```math - /// n=\text{round}\Bigg(12\cdot\log_{2}\bigg(\frac{F}{\beta}\bigg)\Bigg) + /// n=\text{round}(12\cdot\log_{2}(\frac{F}{\beta})) /// ``` pub fn classify(&self, Frequency(freq): Frequency) -> Option where @@ -38,17 +49,17 @@ impl Scale { if freq <= T::zero() { return None; } - crate::classify_freq_with_scale(freq, self.root().value()) + classify_freq_with_scale(freq, self.root().value()) } - /// returns the frequency [Hz] of the given pitch class `n`, using the formula: + /// returns the frequency $F$, in hertz [Hz], of the given pitch class `n`, using: /// /// ```math - /// F= \beta \cdot 2^{\frac{n}{12}} + /// F=\beta\cdot{2^{\frac{n}{12}}} /// ``` pub fn get_freq_of_class(&self, n: isize) -> Frequency where T: RawFrequency + Float + FromPrimitive, { - crate::compute_freq_of_pitch(n, self.root().value()).into_frequency() + get_frequency_of_pitch(n, self.root().value()).into_frequency() } } diff --git a/core/src/compose/impls/impl_scale_repr.rs b/core/src/compose/impls/impl_scale_repr.rs new file mode 100644 index 00000000..49ac8594 --- /dev/null +++ b/core/src/compose/impls/impl_scale_repr.rs @@ -0,0 +1,25 @@ +/* + Appellation: impl_scale_repr + Created At: 2026.01.20:15:39:15 + Contrib: @FL03 +*/ +use crate::compose::Scale; +use crate::freq::Frequency; + +macro_rules! impl_scale_const { + (@impl $t:ty) => { + impl Scale<$t> { + pub const A4: Frequency<$t> = Frequency(440 as $t); + } + }; + + ($($t:ty),* $(,)?) => { + $(impl_scale_const!(@impl $t);)* + }; +} + +impl_scale_const! { + f32, f64, + u16, u32, u64, u128, usize, + i16, i32, i64, i128, isize, +} diff --git a/core/src/comp/mod.rs b/core/src/compose/mod.rs similarity index 93% rename from core/src/comp/mod.rs rename to core/src/compose/mod.rs index 2351acb2..2821956d 100644 --- a/core/src/comp/mod.rs +++ b/core/src/compose/mod.rs @@ -13,6 +13,7 @@ pub mod scale; mod impls { mod impl_scale; + mod impl_scale_repr; } #[doc(hidden)] diff --git a/core/src/comp/scale.rs b/core/src/compose/scale.rs similarity index 100% rename from core/src/comp/scale.rs rename to core/src/compose/scale.rs diff --git a/core/src/error.rs b/core/src/error.rs index 722dca40..be28a1b2 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -12,6 +12,7 @@ pub type Result = core::result::Result; /// The [`Error`] enum represents various errors that can occur in the application. #[derive(Debug, thiserror::Error)] +#[non_exhaustive] pub enum Error { #[error("Attempted to use an invalid accidental")] InvalidAccidental, @@ -23,8 +24,8 @@ pub enum Error { FromStrParseError, #[error("Unable to parse the string into the desired pitch class: {0}")] InvalidPitchClassParse(&'static str), - #[error("Mismatched symbols: expected {0}, found {1}")] - MismatchedSymbols(char, char), + #[error("Mismatched symbols")] + MismatchedSymbols, #[error("Invalid Chord")] InvalidChord, #[cfg(feature = "alloc")] @@ -32,30 +33,57 @@ pub enum Error { IncompatibleIntervals(String), #[error("Invalid Note")] InvalidNote, + // external errors #[error(transparent)] AnyError(#[from] anyhow::Error), + #[error(transparent)] + #[cfg(feature = "serde_json")] + JsonError(#[from] serde_json::Error), + // core errors + #[error(transparent)] + AddrParseError(#[from] core::net::AddrParseError), #[error("The impossible has occurred")] Infallible(#[from] core::convert::Infallible), #[error(transparent)] FmtError(#[from] core::fmt::Error), - #[cfg(feature = "alloc")] #[error(transparent)] - BoxError(#[from] Box), + Utf8Error(#[from] core::str::Utf8Error), + // std-based errors #[cfg(feature = "std")] #[error(transparent)] IOError(#[from] std::io::Error), + // alloc-based errors + #[cfg(feature = "alloc")] #[error(transparent)] - #[cfg(feature = "serde_json")] - JsonError(#[from] serde_json::Error), + BoxError(#[from] Box), #[cfg(feature = "alloc")] #[error("Unknown Error: {0}")] Unknown(String), } +impl Error { + /// creates a boxed error from the provided error + #[cfg(feature = "alloc")] + pub fn boxed(err: E) -> Self + where + E: core::error::Error + Send + Sync + 'static, + { + Error::BoxError(Box::new(err)) + } + /// creates a boxed error from the provided error + #[cfg(feature = "alloc")] + pub fn unknown(err: E) -> Self + where + E: alloc::string::ToString, + { + Error::Unknown(err.to_string()) + } +} + #[cfg(feature = "alloc")] impl From<&str> for Error { fn from(s: &str) -> Self { - Error::Unknown(String::from(s)) + Error::unknown(s) } } @@ -65,3 +93,158 @@ impl From for Error { Error::Unknown(s) } } + +#[doc(hidden)] +pub mod custom { + + #[cfg(feature = "alloc")] + use alloc::boxed::Box; + + #[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + strum::AsRefStr, + strum::Display, + strum::EnumCount, + strum::EnumString, + strum::VariantNames, + )] + #[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case") + )] + #[non_exhaustive] + pub enum ErrorKind { + InvalidAccidental, + ChordError, + NoteError, + PitchError, + InvalidPitchClass, + MismatchedPitchClasses, + FromStrParseError, + InvalidPitchClassParse, + MismatchedSymbols, + InvalidChord, + IncompatibleIntervals, + InvalidNote, + Utf8Error, + AnyError, + JsonError, + AddrParseError, + Infallible, + FmtError, + None, + IOError, + #[default] + Unknown, + } + + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] + pub struct ErrorBase> { + pub(crate) kind: ErrorKind, + pub(crate) source: Option, + } + + impl ErrorBase + where + E: core::error::Error, + { + pub const fn new(kind: ErrorKind, source: Option) -> Self { + Self { kind, source } + } + + pub const fn unknown(source: E) -> Self { + Self { + kind: ErrorKind::Unknown, + source: Some(source), + } + } + /// consumes the current error to create another of the given kind + pub fn with_kind(self, kind: ErrorKind) -> ErrorBase { + ErrorBase { + kind, + source: self.source, + } + } + /// consumes the current error to create another with the given message + pub fn with_source(self, source: E2) -> ErrorBase + where + E2: core::error::Error, + { + ErrorBase { + kind: self.kind, + source: Some(source), + } + } + } + + impl core::fmt::Display for ErrorBase + where + E: 'static + core::error::Error, + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if let Some(source) = &self.source { + write!(f, "{}: {}", self.kind, source) + } else { + write!(f, "{}", self.kind) + } + } + } + + impl core::error::Error for ErrorBase + where + E: 'static + core::error::Error, + { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.source + .as_ref() + .map(|e| e as &(dyn core::error::Error + 'static)) + } + } + + impl From> for ErrorBase + where + T: core::error::Error + 'static, + { + fn from(opt: Option) -> Self { + match opt { + Some(err) => Self { + kind: ErrorKind::Unknown, + source: Some(Box::new(err)), + }, + None => Self { + kind: ErrorKind::None, + source: None, + }, + } + } + } + + impl From for ErrorBase { + fn from(err: core::str::Utf8Error) -> Self { + Self { + kind: ErrorKind::Utf8Error, + #[cfg(feature = "alloc")] + source: Some(Box::new(err)), + } + } + } + + #[cfg(feature = "serde_json")] + impl From for ErrorBase { + fn from(err: serde_json::Error) -> Self { + Self { + kind: ErrorKind::JsonError, + source: Some(Box::new(err)), + } + } + } +} diff --git a/core/src/freq.rs b/core/src/freq.rs deleted file mode 100644 index ebde21fc..00000000 --- a/core/src/freq.rs +++ /dev/null @@ -1,90 +0,0 @@ -/* - Appellation: freq - Created At: 2025.12.29:17:13:00 - Contrib: @FL03 -*/ -mod impl_freq; -mod impl_freq_ext; -mod impl_freq_rand; -mod impl_freq_repr; - -/// [`RawFrequency`] is a marker trait denoting objects capable of representing a frequency -pub trait RawFrequency { - private! {} -} - -/// [`AsFrequency`] is a trait enabling the conversion of a reference to a type into a -/// [`Frequency`] instance. -pub trait AsFrequency { - /// Converts the current value into a [`Frequency`] instance. - fn as_frequency(&self) -> Frequency; -} -/// The [`IntoFrequency`] trait consumes the value and converts it into a [`Frequency`]. -pub trait IntoFrequency { - /// Converts the current value into a [`Frequency`] instance. - fn into_frequency(self) -> Frequency; -} - -/// The [`Frequency`] type is a generic wrapper around type `T` that implements the -/// [`RawFrequency`] trait. This implementation is designed to provide a consistent interface -/// for dealing with frequencies within the crate, enabling conversion, arithmetic operations, -/// and other utilities that are common to frequency values. -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(transparent) -)] -#[repr(transparent)] -pub struct Frequency(pub T); - -/* - ************* Implementations ************* -*/ -impl IntoFrequency for U -where - U: Into>, -{ - fn into_frequency(self) -> Frequency { - self.into() - } -} - -macro_rules! raw_frequency { - (@impl $T:ty) => { - impl RawFrequency for $T { - seal! {} - } - }; - {$($T:ty),* $(,)?} => { - $(raw_frequency!(@impl $T);)* - }; -} - -raw_frequency! { - f32, f64, - i8, i16, i32, i64, i128, isize, - u8, u16, u32, u64, u128, usize -} - -#[cfg(feature = "complex")] -impl RawFrequency for num_complex::Complex -where - T: RawFrequency, -{ - seal! {} -} - -#[cfg(test)] -mod tests { - use super::*; - - const A4: f64 = 440.0; - const C4: f64 = 261.6255653005986; - - #[test] - fn test_freq_classification() { - let f = Frequency::new(C4); - assert_eq! { f.classify_by(A4), -9 } - } -} diff --git a/core/src/freq/frequency.rs b/core/src/freq/frequency.rs new file mode 100644 index 00000000..f24f0b40 --- /dev/null +++ b/core/src/freq/frequency.rs @@ -0,0 +1,17 @@ +/* + Appellation: freq + Created At: 2025.12.29:17:13:00 + Contrib: @FL03 +*/ +/// The [`Frequency`] type is a generic wrapper around type `T` that implements the +/// [`RawFrequency`] trait. This implementation is designed to provide a consistent interface +/// for dealing with frequencies within the crate, enabling conversion, arithmetic operations, +/// and other utilities that are common to frequency values. +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(transparent) +)] +#[repr(transparent)] +pub struct Frequency(pub T); diff --git a/core/src/freq/impl_freq.rs b/core/src/freq/impls/impl_freq.rs similarity index 94% rename from core/src/freq/impl_freq.rs rename to core/src/freq/impls/impl_freq.rs index 75af5b95..9e85f08e 100644 --- a/core/src/freq/impl_freq.rs +++ b/core/src/freq/impls/impl_freq.rs @@ -2,10 +2,10 @@ appellation: impl_freq authors: @FL03 */ -use super::{Frequency, RawFrequency}; use crate::consts::A4_FREQUENCY; -use crate::pitch::{PitchClass, RawAccidental, RawPitchClass}; -use crate::utils::{classify_freq_with_scale, compute_freq_of_pitch}; +use crate::freq::frequency::Frequency; +use crate::freq::{RawFrequency, classify_freq_with_scale, get_frequency_of_pitch}; +use crate::pitch::{Accidental, PitchClass, RawPitchClass}; use num_traits::{Float, FromPrimitive, ToPrimitive}; use rstmt_traits::ClassifyBy; @@ -28,7 +28,7 @@ where T: Float + FromPrimitive, { let class = note.to_isize().unwrap(); - Self(compute_freq_of_pitch(class, root)) + Self(get_frequency_of_pitch(class, root)) } /// a shorthand method for creating a new frequency from the given pitch class using A4 as /// the base frequency @@ -44,7 +44,7 @@ where pub fn from_pitch_class(class: PitchClass, root: T) -> Self where P: RawPitchClass, - K: RawAccidental, + K: Accidental, T: RawFrequency + Float + FromPrimitive, { let semitones = class.get().index(); @@ -103,9 +103,9 @@ where #[inline] /// apply a function to a reference of the current frequency, capturing the result in a /// new instance - pub fn apply(&self, mut f: F) -> Frequency + pub fn apply(&self, f: F) -> Frequency where - F: FnMut(&T) -> U, + F: FnOnce(&T) -> U, { Frequency(f(self.get())) } diff --git a/core/src/freq/impl_freq_ext.rs b/core/src/freq/impls/impl_freq_ext.rs similarity index 97% rename from core/src/freq/impl_freq_ext.rs rename to core/src/freq/impls/impl_freq_ext.rs index 75c55028..f2a275b6 100644 --- a/core/src/freq/impl_freq_ext.rs +++ b/core/src/freq/impls/impl_freq_ext.rs @@ -3,8 +3,8 @@ Created At: 2025.12.29:17:36:58 Contrib: @FL03 */ -use super::Frequency; -use crate::RawFrequency; +use crate::freq::RawFrequency; +use crate::freq::frequency::Frequency; use num_traits::{Num, One, Zero}; contained::fmt_wrapper! { diff --git a/core/src/freq/impl_freq_rand.rs b/core/src/freq/impls/impl_freq_rand.rs similarity index 100% rename from core/src/freq/impl_freq_rand.rs rename to core/src/freq/impls/impl_freq_rand.rs diff --git a/core/src/freq/impl_freq_repr.rs b/core/src/freq/impls/impl_freq_repr.rs similarity index 100% rename from core/src/freq/impl_freq_repr.rs rename to core/src/freq/impls/impl_freq_repr.rs diff --git a/core/src/freq/mod.rs b/core/src/freq/mod.rs new file mode 100644 index 00000000..48a2cf7b --- /dev/null +++ b/core/src/freq/mod.rs @@ -0,0 +1,51 @@ +/* + Appellation: freq + Created At: 2026.01.20:08:34:12 + Contrib: @FL03 +*/ +#[doc(inline)] +pub use self::{frequency::*, traits::*, utils::*}; + +mod frequency; + +mod impls { + mod impl_freq; + mod impl_freq_ext; + mod impl_freq_rand; + mod impl_freq_repr; +} + +mod traits { + #[doc(inline)] + pub use self::{convert::*, raw_frequency::*}; + + mod convert; + mod raw_frequency; +} + +mod utils { + #[doc(inline)] + pub use self::frequency::*; + + mod frequency; +} +// prelude (local) +pub(crate) mod prelude { + pub use super::frequency::*; + pub use super::traits::*; + pub use super::utils::*; +} + +#[cfg(test)] +mod tests { + use super::*; + + const A4: f64 = 440.0; + const C4: f64 = 261.6255653005986; + + #[test] + fn test_freq_classification() { + let f = Frequency::new(C4); + assert_eq! { f.classify_by(A4), -9 } + } +} diff --git a/core/src/freq/traits/convert.rs b/core/src/freq/traits/convert.rs new file mode 100644 index 00000000..85396c09 --- /dev/null +++ b/core/src/freq/traits/convert.rs @@ -0,0 +1,28 @@ +/* + Appellation: convert + Created At: 2026.01.20:08:33:21 + Contrib: @FL03 +*/ +use crate::freq::Frequency; +/// [`AsFrequency`] is a trait enabling the conversion of a reference to a type into a +/// [`Frequency`] instance. +pub trait AsFrequency { + /// Converts the current value into a [`Frequency`] instance. + fn as_frequency(&self) -> Frequency; +} +/// The [`IntoFrequency`] trait consumes the value and converts it into a [`Frequency`]. +pub trait IntoFrequency { + /// Converts the current value into a [`Frequency`] instance. + fn into_frequency(self) -> Frequency; +} +/* + ************* Implementations ************* +*/ +impl IntoFrequency for U +where + U: Into>, +{ + fn into_frequency(self) -> Frequency { + self.into() + } +} diff --git a/core/src/freq/traits/raw_frequency.rs b/core/src/freq/traits/raw_frequency.rs new file mode 100644 index 00000000..9807136c --- /dev/null +++ b/core/src/freq/traits/raw_frequency.rs @@ -0,0 +1,38 @@ +/* + Appellation: raw_frequency + Created At: 2026.01.20:08:32:55 + Contrib: @FL03 +*/ + +/// [`RawFrequency`] is a marker trait denoting objects capable of representing a frequency +pub trait RawFrequency { + private! {} +} + +/* + ************* Implementations ************* +*/ +macro_rules! raw_frequency { + (@impl $T:ty) => { + impl RawFrequency for $T { + seal! {} + } + }; + {$($T:ty),* $(,)?} => { + $(raw_frequency!(@impl $T);)* + }; +} + +raw_frequency! { + f32, f64, + i8, i16, i32, i64, i128, isize, + u8, u16, u32, u64, u128, usize +} + +#[cfg(feature = "complex")] +impl RawFrequency for num_complex::Complex +where + T: RawFrequency, +{ + seal! {} +} diff --git a/core/src/utils/frequency.rs b/core/src/freq/utils/frequency.rs similarity index 94% rename from core/src/utils/frequency.rs rename to core/src/freq/utils/frequency.rs index 79e0aab2..0b52a536 100644 --- a/core/src/utils/frequency.rs +++ b/core/src/freq/utils/frequency.rs @@ -11,7 +11,7 @@ use num_traits::{Float, FromPrimitive}; /// ```math /// F=\beta\cdot{2^{\frac{n}{12}}} /// ``` -pub fn compute_freq_of_pitch(n: isize, root: T) -> T +pub fn get_frequency_of_pitch(n: isize, root: T) -> T where T: Float + FromPrimitive, { diff --git a/core/src/lib.rs b/core/src/lib.rs index bfe4ef8e..497d1310 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,7 +1,19 @@ -#![crate_name = "rstmt_core"] //! This crate provides the core functionality for the `rstmt` library, including [`Aspn`], //! [`NoteBase`], [`Pitch`], and [`Octave`]. Additionally, the crate provides a host of //! other primitives and utilities designed to manifest and manipulate musical concepts. +//! +//! ## Overview +//! +//! The core modules focus on establishing the basic primitives and interfaces needed to +//! represent musical notes, pitches, octaves, and related concepts. These modules +//! provide the foundational building blocks for more complex musical structures and +//! operations. +//! +//! These modules are designed to be efficient, flexible, and correct, ensuring conversions +//! between different representations are handled seamlessly. For example, _any_ [`PitchClass`] +//! is able to be converted directly into a [`Frequency`]. +#![crate_name = "rstmt_core"] +#![crate_type = "lib"] #![allow( clippy::derivable_impls, clippy::len_without_is_empty, @@ -15,7 +27,7 @@ clippy::upper_case_acronyms )] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(allocator_api))] +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // compiler check #[cfg(not(any(feature = "std", feature = "alloc")))] compile_error! { "either the \"std\" or \"alloc\" feature must be enabled" } @@ -31,48 +43,37 @@ pub(crate) mod macros { #[cfg(feature = "alloc")] extern crate alloc; -pub mod chord; -pub mod comp; +pub mod chords; +pub mod compose; pub mod consts; pub mod error; pub mod freq; pub mod intervals; -pub mod note; +pub mod notes; pub mod octave; pub mod pitch; pub mod types { //! this module imimplements various types and other primitives used throughout the library #[doc(inline)] - pub use self::{accents::*, harmonic_funcs::*, notes::*}; + pub use self::harmonic_funcs::*; - mod accents; mod harmonic_funcs; - mod notes; } -pub mod utils { - //! useful utilities for musical primitives for converting between different - //! representations, classification routines, and more. - #[doc(inline)] - pub use self::frequency::*; - - mod frequency; -} // re-exports #[doc(inline)] pub use self::{ - chord::{RawChord, RawChordMut}, - comp::Scale, + chords::{RawChord, RawChordMut}, + compose::Scale, consts::*, error::*, - freq::*, + freq::{Frequency, RawFrequency}, intervals::*, - note::*, + notes::*, octave::*, pitch::*, types::*, - utils::*, }; #[doc(inline)] pub use rstmt_traits as traits; @@ -83,14 +84,13 @@ pub use rstmt_traits::prelude::*; pub mod prelude { pub use rstmt_traits::prelude::*; - pub use crate::chord::prelude::*; - pub use crate::comp::prelude::*; + pub use crate::chords::prelude::*; + pub use crate::compose::prelude::*; pub use crate::consts::*; - pub use crate::freq::*; + pub use crate::freq::prelude::*; pub use crate::intervals::prelude::*; - pub use crate::note::*; + pub use crate::notes::prelude::*; pub use crate::octave::*; pub use crate::pitch::prelude::*; pub use crate::types::*; - pub use crate::utils::*; } diff --git a/core/src/note/impl_aspn.rs b/core/src/note/impl_aspn.rs deleted file mode 100644 index 38dad3d5..00000000 --- a/core/src/note/impl_aspn.rs +++ /dev/null @@ -1,55 +0,0 @@ -/* - Appellation: impl_aspn - Created At: 2025.12.20:08:16:03 - Contrib: @FL03 -*/ -use super::Aspn; -use crate::octave::Octave; -use rstmt_traits::PitchMod; - -impl Aspn { - pub fn new(class: usize, Octave(octave): Octave) -> Self { - Self { - class, - octave: Octave(octave), - } - } - /// returns a new note from a pitch value - pub fn from_pitch(pitch: usize) -> Self { - Self::new(pitch.pmod(), Octave(4)) - } - /// returns a copy to the index of the note's class - pub const fn class(&self) -> usize { - self.class - } - /// returns a mutable reference to the index of the note's class - pub fn class_mut(&mut self) -> &mut usize { - &mut self.class - } - /// returns a copy to the octave of the note - pub const fn octave(&self) -> Octave { - self.octave - } - /// returns a mutable reference to the current octave - pub const fn octave_mut(&mut self) -> &mut Octave { - &mut self.octave - } - /// set the pitch class of the note - pub fn set_class(&mut self, class: usize) -> &mut Self { - self.class = class.pmod(); - self - } - /// set the octave of the note - pub fn set_octave(&mut self, octave: Octave) -> &mut Self { - self.octave = octave; - self - } - /// consumes the current instance to create another with the given pitch class - pub fn with_class(self, class: usize) -> Self { - Self { class, ..self } - } - /// consumes the current instance to create another with the given octave - pub fn with_octave(self, octave: Octave) -> Self { - Self { octave, ..self } - } -} diff --git a/core/src/note.rs b/core/src/notes/aspn.rs similarity index 56% rename from core/src/note.rs rename to core/src/notes/aspn.rs index 3aca9c3c..497dd899 100644 --- a/core/src/note.rs +++ b/core/src/notes/aspn.rs @@ -2,23 +2,7 @@ Appellation: aspn Contrib: @FL03 */ -mod impl_aspn; -mod impl_aspn_ext; -mod impl_note_base; -mod impl_note_ext; -mod impl_note_repr; - use crate::octave::Octave; -use crate::pitch::{self, PitchClass, RawAccidental, RawPitchClass}; - -/// The [`AsAspn`] trait is used to convert a reference into a [`Aspn`] -pub trait AsAspn { - fn as_aspn(&self) -> Aspn; -} -/// A trait for converting a type into a [`Aspn`] -pub trait IntoAspn { - fn into_aspn(self) -> Aspn; -} /// An american scientific pitch notation ([`Aspn`]) representation of a musical note; this /// standard is used to represent notes in a way that is consistent with the @@ -38,23 +22,14 @@ pub struct Aspn { pub(crate) octave: Octave, } -/// The [`NoteBase`] is a generic representation of a musical note -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde(rename_all = "snake_case") -)] -#[repr(C)] -pub struct NoteBase

::Tag> -where - P: RawPitchClass, - K: RawAccidental, -{ - pub(crate) class: PitchClass, - pub(crate) octave: Octave, +/// The [`AsAspn`] trait is used to convert a reference into a [`Aspn`] +pub trait AsAspn { + fn as_aspn(&self) -> Aspn; +} +/// A trait for converting a type into a [`Aspn`] +pub trait IntoAspn { + fn into_aspn(self) -> Aspn; } - /* ************* Implementations ************* */ @@ -75,21 +50,3 @@ where self.into() } } - -#[cfg(test)] -mod tests { - use super::NoteBase; - use crate::octave::Octave; - use crate::pitch::{C, CNote}; - - #[test] - fn test_note_from_octave() { - assert_eq! { NoteBase::::from_octave(Octave(4)), "C.4" } - } - #[test] - // #[ignore = "need to fix"] - fn test_note_parse() { - let exp = NoteBase::new(C::default(), Octave(4)); - assert_eq! { "C.4".parse::>().unwrap(), exp } - } -} diff --git a/core/src/note/impl_aspn_ext.rs b/core/src/notes/impls/impl_aspn.rs similarity index 60% rename from core/src/note/impl_aspn_ext.rs rename to core/src/notes/impls/impl_aspn.rs index 6a43915c..b02ceabf 100644 --- a/core/src/note/impl_aspn_ext.rs +++ b/core/src/notes/impls/impl_aspn.rs @@ -1,11 +1,59 @@ /* - Appellation: impl_aspn_ext - Created At: 2025.12.20:08:15:34 + Appellation: impl_aspn + Created At: 2025.12.20:08:16:03 Contrib: @FL03 */ -use super::Aspn; +use crate::notes::aspn::Aspn; +use crate::octave::Octave; use rstmt_traits::PitchMod; +impl Aspn { + pub fn new(class: usize, Octave(octave): Octave) -> Self { + Self { + class, + octave: Octave(octave), + } + } + /// returns a new note from a pitch value + pub fn from_pitch(pitch: usize) -> Self { + Self::new(pitch.pmod(), Octave(4)) + } + /// returns a copy to the index of the note's class + pub const fn class(&self) -> usize { + self.class + } + /// returns a mutable reference to the index of the note's class + pub fn class_mut(&mut self) -> &mut usize { + &mut self.class + } + /// returns a copy to the octave of the note + pub const fn octave(&self) -> Octave { + self.octave + } + /// returns a mutable reference to the current octave + pub const fn octave_mut(&mut self) -> &mut Octave { + &mut self.octave + } + /// set the pitch class of the note + pub fn set_class(&mut self, class: usize) -> &mut Self { + self.class = class.pmod(); + self + } + /// set the octave of the note + pub fn set_octave(&mut self, octave: Octave) -> &mut Self { + self.octave = octave; + self + } + /// consumes the current instance to create another with the given pitch class + pub fn with_class(self, class: usize) -> Self { + Self { class, ..self } + } + /// consumes the current instance to create another with the given octave + pub fn with_octave(self, octave: Octave) -> Self { + Self { octave, ..self } + } +} + impl core::fmt::Display for Aspn { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}.{}", self.class, self.octave) diff --git a/core/src/note/impl_note_base.rs b/core/src/notes/impls/impl_note_base.rs similarity index 89% rename from core/src/note/impl_note_base.rs rename to core/src/notes/impls/impl_note_base.rs index 9c3fc0c0..487a5e85 100644 --- a/core/src/note/impl_note_base.rs +++ b/core/src/notes/impls/impl_note_base.rs @@ -3,14 +3,14 @@ Created At: 2025.12.20:09:35:09 Contrib: @FL03 */ -use super::NoteBase; +use crate::notes::note_base::NoteBase; use crate::octave::Octave; -use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawPitchClass}; impl NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { /// constructs a new [`NoteBase`] instance pub const fn new(class: PitchClass, octave: Octave) -> Self { @@ -20,7 +20,6 @@ where pub fn from_octave(octave: Octave) -> Self where P: PitchClassRepr, - K: Accidental, { Self { class: PitchClass::new(), diff --git a/core/src/note/impl_note_ext.rs b/core/src/notes/impls/impl_note_ext.rs similarity index 76% rename from core/src/note/impl_note_ext.rs rename to core/src/notes/impls/impl_note_ext.rs index fa132c73..830889b9 100644 --- a/core/src/note/impl_note_ext.rs +++ b/core/src/notes/impls/impl_note_ext.rs @@ -3,14 +3,16 @@ Created At: 2025.12.31:18:23:09 Contrib: @FL03 */ -use crate::note::NoteBase; +use crate::notes::note_base::NoteBase; use crate::octave::Octave; -use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClass, PitchClassRepr, RawPitchClass}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; impl core::fmt::Debug for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(self.aspn().as_str()) @@ -20,7 +22,7 @@ where impl core::fmt::Display for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.write_str(self.aspn().as_str()) @@ -30,7 +32,7 @@ where impl PartialEq for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &str) -> bool { self.aspn() == other @@ -40,25 +42,25 @@ where impl PartialEq<&str> for NoteBase where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &&str) -> bool { self.aspn() == *other } } + #[cfg(feature = "alloc")] impl core::str::FromStr for NoteBase where P: PitchClassRepr, - K: Accidental, -

::Err: core::fmt::Debug, + K: Accidental + core::str::FromStr, { - type Err = crate::error::Error; + type Err = crate::Error; fn from_str(s: &str) -> Result { - let parts: alloc::vec::Vec<&str> = s.split('.').collect(); + let parts: Vec<&str> = s.split('.').collect(); if parts.len() != 2 { - return Err(crate::error::Error::FromStrParseError); + return Err(crate::Error::FromStrParseError); } let lex_class = parts[0]; let lex_octave = parts[1]; diff --git a/core/src/note/impl_note_repr.rs b/core/src/notes/impls/impl_note_repr.rs similarity index 86% rename from core/src/note/impl_note_repr.rs rename to core/src/notes/impls/impl_note_repr.rs index 277dc39c..1164cee9 100644 --- a/core/src/note/impl_note_repr.rs +++ b/core/src/notes/impls/impl_note_repr.rs @@ -1,4 +1,9 @@ -use crate::note::NoteBase; +/* + Appellation: impl_note_repr + Created At: 2026.01.20:08:59:53 + Contrib: @FL03 +*/ +use crate::notes::note_base::NoteBase; use crate::octave::Octave; use crate::pitch::{Flat, Natural, PitchClass, RawPitchClass, Sharp}; diff --git a/core/src/notes/mod.rs b/core/src/notes/mod.rs new file mode 100644 index 00000000..9dc19b9e --- /dev/null +++ b/core/src/notes/mod.rs @@ -0,0 +1,57 @@ +/* + Appellation: notes + Created At: 2026.01.20:08:56:38 + Contrib: @FL03 +*/ +//! this module contains various implementations and traits for defining and manipulating +//! musical notes. The [`NoteBase`] struct provides a generic representation of a musical note +//! that is parameterized by a pitch class and an octave. +#[doc(inline)] +pub use self::{aspn::*, note_base::*, types::*}; + +mod aspn; +mod note_base; + +mod impls { + mod impl_aspn; + + mod impl_note_base; + mod impl_note_ext; + mod impl_note_repr; +} + +mod traits { + // #[doc(inline)] + // pub use self::*; +} + +mod types { + #[doc(inline)] + pub use self::{accents::*, notes::*}; + + mod accents; + mod notes; +} + +pub(crate) mod prelude { + pub use super::note_base::*; + pub use super::types::*; +} + +#[cfg(test)] +mod tests { + use super::NoteBase; + use crate::octave::Octave; + use crate::pitch::{C, CNote}; + + #[test] + fn test_note_init() { + assert_eq! { NoteBase::::from_octave(Octave(4)), "C.4" } + } + + #[test] + fn test_note_parse_from_str() { + let exp = NoteBase::new(C::default(), Octave(4)); + assert_eq! { "C.4".parse::>().unwrap(), exp } + } +} diff --git a/core/src/notes/note_base.rs b/core/src/notes/note_base.rs new file mode 100644 index 00000000..93c78bad --- /dev/null +++ b/core/src/notes/note_base.rs @@ -0,0 +1,24 @@ +/* + Appellation: aspn + Contrib: @FL03 +*/ + +use crate::octave::Octave; +use crate::pitch::{self, Accidental, PitchClass, RawPitchClass}; + +/// The [`NoteBase`] is a generic representation of a musical note +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr( + feature = "serde", + derive(serde::Deserialize, serde::Serialize), + serde(rename_all = "snake_case") +)] +#[repr(C)] +pub struct NoteBase

::Tag> +where + P: RawPitchClass, + K: Accidental, +{ + pub(crate) class: PitchClass, + pub(crate) octave: Octave, +} diff --git a/core/src/notes/traits/convert.rs b/core/src/notes/traits/convert.rs new file mode 100644 index 00000000..e69de29b diff --git a/core/src/types/accents.rs b/core/src/notes/types/accents.rs similarity index 100% rename from core/src/types/accents.rs rename to core/src/notes/types/accents.rs diff --git a/core/src/types/notes.rs b/core/src/notes/types/notes.rs similarity index 100% rename from core/src/types/notes.rs rename to core/src/notes/types/notes.rs diff --git a/core/src/octave/impl_octave.rs b/core/src/octave/impl_octave.rs index 42fb03dd..4a57d267 100644 --- a/core/src/octave/impl_octave.rs +++ b/core/src/octave/impl_octave.rs @@ -2,12 +2,9 @@ appellation: impl_octave authors: @FL03 */ -use crate::octave::{Octave, RawOctave}; +use crate::octave::Octave; -impl Octave -where - T: RawOctave, -{ +impl Octave { /// a functional constructor for [`Octave`], essentially wrapping the given value pub const fn new(octave: T) -> Self { Octave(octave) diff --git a/core/src/octave/impl_octave_repr.rs b/core/src/octave/impl_octave_repr.rs index b35da839..c52c15e1 100644 --- a/core/src/octave/impl_octave_repr.rs +++ b/core/src/octave/impl_octave_repr.rs @@ -2,12 +2,9 @@ appellation: impl_octave_repr authors: @FL03 */ -use crate::octave::{Octave, RawOctave}; +use crate::octave::Octave; -impl Octave<&T> -where - T: RawOctave, -{ +impl Octave<&T> { /// returns a new instance of the [`Octave`] with a cloned instance of the current value.alloc pub fn cloned(&self) -> Octave where @@ -24,10 +21,7 @@ where } } -impl Octave<&mut T> -where - T: RawOctave, -{ +impl Octave<&mut T> { /// returns a new instance of the [`Octave`] with a cloned instance of the current value.alloc pub fn cloned(&self) -> Octave where @@ -44,10 +38,7 @@ where } } -impl Octave<*const T> -where - T: RawOctave, -{ +impl Octave<*const T> { /// returns a new instance of the [`Octave`] with a copied instance of the current value pub const fn copied(&self) -> Octave where diff --git a/core/src/pitch/impls/impl_pclass.rs b/core/src/pitch/impls/impl_pclass.rs index 935e4e1b..03ce318b 100644 --- a/core/src/pitch/impls/impl_pclass.rs +++ b/core/src/pitch/impls/impl_pclass.rs @@ -4,32 +4,32 @@ Contrib: @FL03 */ use crate::freq::{Frequency, RawFrequency}; -use crate::pitch::{Flat, Natural, PitchClass, RawAccidental, RawPitchClass, Sharp}; +use crate::pitch::{Accidental, Flat, Natural, PitchClass, RawPitchClass, Sharp}; use num_traits::{Float, FromPrimitive}; impl PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { - pub fn new() -> Self - where - P: Default, - K: Default, - { + pub fn new() -> Self { Self { - class: P::default(), - kind: K::default(), + class: P::new(), + kind: K::new(), } } - pub fn from_class(class: P) -> Self - where - K: Default, - { + pub fn from_class(class: P) -> Self { Self { class, - kind: K::default(), + kind: K::new(), + } + } + + pub fn from_kind(kind: K) -> Self { + Self { + class: P::new(), + kind, } } /// returns a pointer to the class @@ -44,7 +44,7 @@ where pub fn as_frequency(&self) -> Frequency where P: RawPitchClass, - K: RawAccidental, + K: Accidental, T: RawFrequency + Float + FromPrimitive, { Frequency::from_class_on_a4(self.get().index()) diff --git a/core/src/pitch/impls/impl_pclass_ext.rs b/core/src/pitch/impls/impl_pclass_ext.rs index b9acd7ce..626b4538 100644 --- a/core/src/pitch/impls/impl_pclass_ext.rs +++ b/core/src/pitch/impls/impl_pclass_ext.rs @@ -5,12 +5,12 @@ */ use crate::error::Error; use crate::pitch::pitch_class::PitchClass; -use crate::pitch::traits::{Accidental, PitchClassRepr, RawAccidental, RawPitchClass}; +use crate::pitch::traits::{Accidental, PitchClassRepr, RawPitchClass}; impl core::fmt::Debug for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if self.is_natural() { @@ -24,7 +24,7 @@ where impl core::fmt::Display for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { if self.is_natural() { @@ -38,7 +38,7 @@ where impl core::str::FromStr for PitchClass where P: PitchClassRepr, - K: Accidental, + K: Accidental + core::str::FromStr, { type Err = Error; @@ -65,7 +65,7 @@ where if P::is(value) { return Ok(Self { class: P::new(), - kind: K::default(), + kind: K::new(), }); } Err(Error::MismatchedPitchClasses(value,

::IDX)) @@ -75,7 +75,7 @@ where impl AsRef for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn as_ref(&self) -> &isize { self.get().as_ref() @@ -85,7 +85,7 @@ where impl AsRef for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn as_ref(&self) -> &str { self.get().name() @@ -95,7 +95,7 @@ where impl core::borrow::Borrow for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn borrow(&self) -> &isize { self.get().borrow() @@ -105,7 +105,7 @@ where impl core::ops::Deref for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { type Target = P; @@ -117,21 +117,21 @@ where unsafe impl Send for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { } unsafe impl Sync for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { } impl PartialEq for PitchClass where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &isize) -> bool { self.get().index() == *other @@ -141,7 +141,7 @@ where impl PartialEq> for isize where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { fn eq(&self, other: &PitchClass) -> bool { *self == other.get().index() diff --git a/core/src/pitch/impls/impl_pclass_ops.rs b/core/src/pitch/impls/impl_pclass_ops.rs index 021e5893..929354f5 100644 --- a/core/src/pitch/impls/impl_pclass_ops.rs +++ b/core/src/pitch/impls/impl_pclass_ops.rs @@ -4,7 +4,7 @@ Contrib: @FL03 */ use crate::pitch::pitch_class::PitchClass; -use crate::pitch::traits::{RawAccidental, RawPitchClass}; +use crate::pitch::traits::{Accidental, RawPitchClass}; use rstmt_traits::PitchMod; /// Add two pitch-classes producing a wrapped semitone count in the tonal space. @@ -17,9 +17,9 @@ use rstmt_traits::PitchMod; impl core::ops::Add> for PitchClass where T1: RawPitchClass, - A1: RawAccidental, + A1: Accidental, T2: RawPitchClass, - A2: RawAccidental, + A2: Accidental, { type Output = isize; @@ -31,9 +31,9 @@ where impl core::ops::Sub> for PitchClass where T1: RawPitchClass, - A1: RawAccidental, + A1: Accidental, T2: RawPitchClass, - A2: RawAccidental, + A2: Accidental, { /// Subtract two pitch-classes producing a wrapped signed semitone interval. type Output = isize; diff --git a/core/src/pitch/pitch_class.rs b/core/src/pitch/pitch_class.rs index 46cdfaf8..3f1f9c59 100644 --- a/core/src/pitch/pitch_class.rs +++ b/core/src/pitch/pitch_class.rs @@ -3,7 +3,18 @@ Created At: 2025.12.20:09:31:05 Contrib: @FL03 */ -use crate::pitch::{RawAccidental, RawPitchClass}; +use crate::pitch::{Accidental, PitchClassRepr, RawPitchClass}; +use rstmt_traits::PitchMod; + +pub trait IntoPitchClass +where + P: RawPitchClass, + K: Accidental, +{ + fn into_pitch_class(self) -> PitchClass; + + private! {} +} /// The [`PitchClass`] implementations works to generically define the structure for a pitch /// class. This is accomplished through the use of two type parameters: `N`, which defines the @@ -22,7 +33,7 @@ use crate::pitch::{RawAccidental, RawPitchClass}; pub struct PitchClass

::Tag> where P: RawPitchClass, - K: RawAccidental, + K: Accidental, { pub(crate) class: P, pub(crate) kind: K, @@ -59,3 +70,18 @@ classes! { A::, B::, } + +impl IntoPitchClass for isize +where + P: PitchClassRepr, + K: Accidental, +{ + seal! {} + + fn into_pitch_class(self) -> PitchClass { + match self.pmod() { + x if x == P::IDX => PitchClass::new(), + _ => panic!("cannot convert {self} into pitch class"), + } + } +} diff --git a/core/src/pitch/traits/accidental.rs b/core/src/pitch/traits/accidental.rs index 7f537dcb..66b2cdfb 100644 --- a/core/src/pitch/traits/accidental.rs +++ b/core/src/pitch/traits/accidental.rs @@ -6,21 +6,29 @@ /// [`Accidental`] is a sealed marker trait used to designate various _kinds_ of musical notes, /// i.e., sharp, flat, natural, etc. -pub trait RawAccidental: - 'static + AsRef + Send + Sync + core::fmt::Debug + core::fmt::Display +pub trait Accidental +where + Self: 'static + AsRef + Send + Sync + core::fmt::Debug + core::fmt::Display, { private! {} + fn new() -> Self + where + Self: Sized; + + #[allow(clippy::should_implement_trait)] + fn from_str(s: &str) -> Result + where + Self: Sized + core::str::FromStr, + { + s.parse::() + } + fn name(&self) -> &str; fn symbol(&self) -> char; } -pub trait Accidental: RawAccidental -where - Self: Default + core::str::FromStr, -{ -} /* ************* Implementations ************* */ @@ -51,9 +59,13 @@ macro_rules! accidental { } } - impl $crate::pitch::RawAccidental for $name { + impl $crate::pitch::Accidental for $name { seal! {} + fn new() -> Self { + Self + } + fn name(&self) -> &str { self.name() } @@ -63,11 +75,6 @@ macro_rules! accidental { } } - impl $crate::pitch::Accidental for $name { - - - } - impl AsRef for $name { fn as_ref(&self) -> &str { stringify!($name) @@ -126,9 +133,13 @@ impl Natural { } } -impl crate::pitch::RawAccidental for Natural { +impl Accidental for Natural { seal! {} + fn new() -> Self { + Self::new() + } + fn name(&self) -> &str { self.name() } @@ -138,8 +149,6 @@ impl crate::pitch::RawAccidental for Natural { } } -impl crate::pitch::Accidental for Natural {} - impl AsRef for Natural { fn as_ref(&self) -> &str { "Natural" diff --git a/core/src/pitch/traits/classifiers.rs b/core/src/pitch/traits/classifiers.rs index 2481a9c6..19e17cbf 100644 --- a/core/src/pitch/traits/classifiers.rs +++ b/core/src/pitch/traits/classifiers.rs @@ -3,7 +3,7 @@ Created At: 2025.12.21:09:08:08 Contrib: @FL03 */ -use crate::pitch::RawAccidental; +use crate::pitch::Accidental; use rstmt_traits::PitchMod; /// The [`RawPitchClass`] is a sealed trait used to define raw pitch class types. @@ -19,7 +19,7 @@ where + core::fmt::Debug + core::fmt::Display, { - type Tag: RawAccidental; + type Tag: Accidental; private! {} diff --git a/default.nix b/default.nix index 254c70ac..55ddf119 100644 --- a/default.nix +++ b/default.nix @@ -26,7 +26,7 @@ let }; common = { - version = "0.0.12"; + version = "0.0.13"; src = self; # ./.; cargoLock = { diff --git a/flake.nix b/flake.nix index beca4571..31c6adee 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ { packages.default = rustPlatform.buildRustPackage { pname = "rstmt"; - version = "0.0.12"; + version = "0.0.13"; src = self; # "./."; # If Cargo.lock doesn't exist yet, remove or comment out this block: cargoLock = { diff --git a/nrt/Cargo.toml b/nrt/Cargo.toml index 54bec53d..0a48f0b9 100644 --- a/nrt/Cargo.toml +++ b/nrt/Cargo.toml @@ -36,7 +36,6 @@ rstmt-core = { workspace = true } # custom rshyper = { features = ["hyper_map"], workspace = true } rspace-traits = { workspace = true } -variants = { workspace = true } # data structures hashbrown = { optional = true, workspace = true } # concurrency & parallelism @@ -106,6 +105,7 @@ nightly = [ std = [ "alloc", "anyhow/std", + "getrandom?/std", "hashbrown?/default", "itertools/use_std", "num-complex?/std", @@ -119,7 +119,7 @@ std = [ "serde?/std", "serde_json?/std", "strum/std", - "variants/std", + "wasm-bindgen?/std", ] wasi = [ @@ -146,7 +146,7 @@ alloc = [ "rstmt-core/alloc", "serde?/alloc", "serde_json?/alloc", - "variants/alloc", + "wasm-bindgen?/gg-alloc", ] complex = [ @@ -204,12 +204,14 @@ serde = [ "rspace-traits/serde", "rshyper/serde", "rstmt-core/serde", + "wasm-bindgen?/serde", ] serde_json = [ "dep:serde_json", "rshyper/serde_json", "rstmt-core/serde_json", + "wasm-bindgen?/serde_json", ] tracing = [ diff --git a/nrt/src/error.rs b/nrt/src/error.rs index ada71bef..5e2445ef 100644 --- a/nrt/src/error.rs +++ b/nrt/src/error.rs @@ -4,9 +4,6 @@ */ //! custom error types for the `nrt` crate -#[cfg(feature = "alloc")] -use alloc::boxed::Box; - /// a type alias for a [`Result`](core::result::Result) with [`TriadError`] as its error type. pub(crate) type Result = core::result::Result; @@ -35,7 +32,7 @@ impl From for rstmt::Error { match err { TriadError::CoreError(e) => e, #[cfg(feature = "alloc")] - _ => rstmt::Error::BoxError(Box::new(err)), + _ => rstmt_core::Error::boxed(err), } } } diff --git a/nrt/src/impls/impl_triad_base.rs b/nrt/src/impls/impl_triad_base.rs index b9fe5067..de5a8b22 100644 --- a/nrt/src/impls/impl_triad_base.rs +++ b/nrt/src/impls/impl_triad_base.rs @@ -7,7 +7,7 @@ use crate::triad::TriadBase; use crate::traits::{TriadRepr, TriadReprMut, TriadType}; use crate::types::LPR; -use num_traits::{Float, FromPrimitive, ToPrimitive}; +use num_traits::{Float, FromPrimitive, ToPrimitive, Zero}; use rstmt_core::{Octave, PitchMod, Transform}; impl TriadBase @@ -16,17 +16,20 @@ where S: TriadRepr, { /// Returns a new instance of the [`TriadBase`] with the given chord and kind. - pub const fn new(chord: S, class: K) -> Self { + pub fn new(chord: S, class: K) -> Self + where + T: Zero, + { Self { chord, class, - octave: Octave(0), + octave: Octave::zero(), } } /// Create a new triad from a root pitch and class pub fn from_root_with_class(root: T, class: K) -> Self where - T: Copy + FromPrimitive + PitchMod + core::ops::Add, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { // generate the chord factors from the root and class let chord = [ @@ -37,7 +40,7 @@ where Self { class, chord: S::from_arr(chord), - octave: rstmt_core::Octave(0), + octave: Octave::zero(), } } #[inline] @@ -57,18 +60,19 @@ where pub const fn chord_mut(&mut self) -> &mut S { &mut self.chord } - /// returns a copy of the class of the triad. - pub const fn class(&self) -> K { - self.class + /// returns a reference of the class of the triad. + pub const fn class(&self) -> &K { + &self.class } /// returns a mutable reference to the class of the triad. pub const fn class_mut(&mut self) -> &mut K { &mut self.class } - pub const fn octave(&self) -> Octave { - self.octave + /// returns a reference to the octave of the triad. + pub const fn octave(&self) -> &Octave { + &self.octave } - pub const fn octave_mut(&mut self) -> &mut Octave { + pub const fn octave_mut(&mut self) -> &mut Octave { &mut self.octave } /// returns a reference to the root note of the triad. @@ -149,7 +153,7 @@ where } } #[inline] - pub fn with_octave(self, octave: Octave) -> Self { + pub fn with_octave(self, octave: Octave) -> Self { Self { octave, ..self } } /// consumes the triad and returns the chord and class @@ -175,10 +179,11 @@ where /// computes the centroid of the triad pub fn centroid(&self) -> Option<[U; 2]> where + T: Copy + ToPrimitive, U: Float + FromPrimitive + ToPrimitive + core::iter::Sum, S: Clone + IntoIterator, { - let y = U::from_isize(*self.octave)?; + let y = U::from(*self.octave().get())?; let x = self.chord().clone().into_iter().sum::() / U::from_u8(3)?; Some([x, y]) } @@ -207,28 +212,28 @@ where /// apply the given [`LPR`] transformation onto the triad, returning a new triad classified /// under `Q` where `Q` and `K` are related via the `Rel` associated type. For example, if /// transforming a major triad, then the resulting triad will be minor (and vice versa). - pub fn transform(&self, step: X) -> Y + pub fn transform(self, step: X) -> Y where Self: Transform, { >::transform(self, step) } /// apply the [`Leading`](LPR::Leading) transformation to the triad - pub fn leading(&self) -> Y + pub fn leading(self) -> Y where Self: Transform, { self.transform(LPR::Leading) } /// apply the [`Parallel`](LPR::Parallel) transformation to the triad - pub fn parallel(&self) -> Y + pub fn parallel(self) -> Y where Self: Transform, { self.transform(LPR::Parallel) } /// apply the [`Relative`](LPR::Relative) transformation to the triad - pub fn relative(&self) -> Y + pub fn relative(self) -> Y where Self: Transform, { diff --git a/nrt/src/impls/impl_triad_ext.rs b/nrt/src/impls/impl_triad_ext.rs index f220cd8c..d0708979 100644 --- a/nrt/src/impls/impl_triad_ext.rs +++ b/nrt/src/impls/impl_triad_ext.rs @@ -5,15 +5,15 @@ */ use crate::triad::TriadBase; -use crate::traits::{Relative, TriadRepr, TriadReprMut, TriadType}; +use crate::traits::{TriadRepr, TriadReprMut, TriadType}; use crate::types::{Factors, LPR}; -use num_traits::{FromPrimitive, One}; +use num_traits::{FromPrimitive, One, Zero}; use rstmt_core::{PitchMod, Transform}; -impl Transform for TriadBase +impl Transform for TriadBase where - K: TriadType + Relative, - R: TriadType + Relative, + K: TriadType, + K::Rel: TriadType, S: TriadRepr, T: Copy + FromPrimitive @@ -22,10 +22,10 @@ where + core::ops::Add + core::ops::Sub, { - type Output = TriadBase; + type Output = TriadBase; - fn transform(&self, transformation: LPR) -> Self::Output { - LPR::transform(&transformation, self).expect("transformation failed") + fn transform(self, transformation: LPR) -> Self::Output { + LPR::apply(transformation, self) } } @@ -44,6 +44,7 @@ impl core::fmt::Display for TriadBase where S: TriadRepr + core::fmt::Debug, K: TriadType + core::fmt::Display, + T: core::fmt::Display, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!( @@ -215,6 +216,7 @@ where impl From<(S, K)> for TriadBase where S: TriadRepr, + T: Zero, K: TriadType, { fn from((chord, class): (S, K)) -> Self { diff --git a/nrt/src/impls/impl_triad_repr.rs b/nrt/src/impls/impl_triad_repr.rs index 3e52f2c3..7a20b963 100644 --- a/nrt/src/impls/impl_triad_repr.rs +++ b/nrt/src/impls/impl_triad_repr.rs @@ -7,7 +7,7 @@ use crate::triad::TriadBase; use crate::traits::TriadRepr; use crate::types::{LPR, Triads}; -use num_traits::{Float, FromPrimitive, Num, ToPrimitive}; +use num_traits::{Float, FromPrimitive, Num, ToPrimitive, Zero}; use rstmt_core::traits::{PitchMod, Transform}; use rstmt_core::{Augmented, Diminished, Major, Minor}; @@ -19,7 +19,7 @@ where /// augmented triad. pub fn augmented(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Augmented) } @@ -33,7 +33,7 @@ where /// diminished triad. pub fn diminished(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Diminished) } @@ -47,7 +47,7 @@ where /// triad. pub fn major(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Major) } @@ -61,7 +61,7 @@ where /// triad. pub fn minor(root: T) -> Self where - T: Copy + FromPrimitive + core::ops::Add + PitchMod, + T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, { TriadBase::from_root_with_class(root, Minor) } @@ -78,21 +78,21 @@ where // /// creates a new diminished triad from the given root // pub fn diminished(root: T) -> Self // where - // T: Copy + FromPrimitive + core::ops::Add + PitchMod, + // T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, // { // Self::from_root_with_class(root, TriadClass::Diminished) // } // /// Create a new major triad from the given root // pub fn major(root: T) -> Self // where - // T: Copy + FromPrimitive + core::ops::Add + PitchMod, + // T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, // { // Self::from_root_with_class(root, TriadClass::Major) // } // /// creates a new minor triad from the given root // pub fn minor(root: T) -> Self // where - // T: Copy + FromPrimitive + core::ops::Add + PitchMod, + // T: Copy + FromPrimitive + Zero + PitchMod + core::ops::Add, // { // Self::from_root_with_class(root, TriadClass::Minor) // } @@ -106,7 +106,7 @@ where let note = p.into_aspn(); let px = U::from_usize(note.class().pmod()).unwrap(); let py = U::from_isize(*note.octave()).unwrap(); - let y = U::from_isize(*self.octave).unwrap(); + let y = U::from(*self.octave).unwrap(); let [v0, v1, v2] = self.chord().map(|n| U::from(n).unwrap()); let d00 = v0 * v0 + y * y; @@ -153,9 +153,8 @@ where where I: IntoIterator, { - path.into_iter().fold(*self, |triad, dirac| { - dirac.transform(triad).expect("transformation failed") - }) + path.into_iter() + .fold(*self, |triad, dirac| dirac.apply(triad)) } /// apply a chain of transformations to a triad in-place pub fn walk_inplace(&mut self, path: I) diff --git a/nrt/src/lib.rs b/nrt/src/lib.rs index 715e2b72..30268b0d 100644 --- a/nrt/src/lib.rs +++ b/nrt/src/lib.rs @@ -8,11 +8,6 @@ //! //! ## Background //! -//! Before diving into the implementation details, it is important to understand the concepts -//! at hand and the theory behind them. -//! -//! ### Neo-Riemannian Theory -//! //! The neo-riemannian theory is a loose collection of musical theories focused on the triad. //! Research in the field has been ongoing for over a century, culminating in the successful //! generalization of the tonnetz, a geometric representation of the triad and its @@ -35,10 +30,6 @@ //! // chain together two parallel transformations to confirm inversion //! assert_eq! { triad.parallel().parallel(), triad } //! ``` -//! -//! ## Resources -//! -//! - [The Generalized Tonnetz](https://dmitri.mycpanel.princeton.edu/tonnetzes.pdf) #![allow( clippy::derivable_impls, clippy::len_without_is_empty, @@ -52,6 +43,7 @@ clippy::upper_case_acronyms )] #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // compiler check #[cfg(not(any(feature = "std", feature = "alloc")))] compile_error! { "either the \"std\" or \"alloc\" feature must be enabled" } diff --git a/nrt/src/motion/impls/impl_motion_planner.rs b/nrt/src/motion/impls/impl_motion_planner.rs index af3e0f2e..53627b6d 100644 --- a/nrt/src/motion/impls/impl_motion_planner.rs +++ b/nrt/src/motion/impls/impl_motion_planner.rs @@ -640,7 +640,7 @@ where .par_bridge() .filter_map(|transform| { // Try applying the transformation - match transform.transform(&start_triad) { + match transform.apply(&start_triad) { Ok(next_triad) => { // Find edge ID if it exists let next_edge_id = self.tonnetz.triads.iter().find_map(|(&id, facet)| { diff --git a/nrt/src/tonnetz.rs b/nrt/src/tonnetz.rs index 9fd0e088..b096188e 100644 --- a/nrt/src/tonnetz.rs +++ b/nrt/src/tonnetz.rs @@ -4,16 +4,17 @@ Contrib: @FL03 */ //! The tonnetz is a conceptual lattice representation of tonal space first proposed in 1739 by -//! Leonhard Euler as a means of visualizing relationships between triads +//! Leonhard Euler as a means of visualizing relationships between triads. Over the centuries, +//! researchers have expanded upon Euler's initial concept, eventually culminating in the +//! development of the long-awaited _**generalized tonnetz**_. //! -//! ## Resources +//! # Resources //! -//! Listed below are some useful resources for understanding the tonnetz and its potential -//! applications in both music theroy as well as computer science: +//! Listed below are some useful resources for understanding the tonnetz, its history, and its +//! applications in music theory and beyond. //! //! - [The Generalized Tonnetz](https://dmitri.mycpanel.princeton.edu/tonnetzes.pdf) //! - [Wikipedia: Tonnetz](https://en.wikipedia.org/wiki/Tonnetz) -//! mod impl_hyper_tonnetz; use crate::triad::TriadBase; @@ -23,7 +24,7 @@ use rshyper::{EdgeId, RawIndex, UnHyperMap}; use rspace_traits::RawSpace; /// a type alias for a [`HashMap`] that maps an [`EdgeId`] to a [`TriadBase`] -pub(crate) type TriadMap::Elem, Ix = usize> = +pub(crate) type EdgeMap::Elem, Ix = usize> = HashMap, TriadBase>; /// a type alias for a [`HashMap`] that maps an [`EdgeId`] to a [`HashMap`] of [`LPR`] /// transformations. @@ -49,7 +50,7 @@ where /// a hypergraph representing the tonal space pub(crate) graph: NoteGraph, /// Maps EdgeIds to Triad for efficient access - pub(crate) triads: TriadMap, + pub(crate) triads: EdgeMap, /// Tracks adjacency between triads via transformations pub(crate) transformations: LprMap, } diff --git a/nrt/src/tonnetz/impl_hyper_tonnetz.rs b/nrt/src/tonnetz/impl_hyper_tonnetz.rs index 293a0f8e..320acc7b 100644 --- a/nrt/src/tonnetz/impl_hyper_tonnetz.rs +++ b/nrt/src/tonnetz/impl_hyper_tonnetz.rs @@ -3,7 +3,7 @@ Created At: 2025.12.28:10:45:40 Contrib: @FL03 */ -use crate::tonnetz::{HyperTonnetz, LprMap, NoteGraph, TriadMap}; +use crate::tonnetz::{EdgeMap, HyperTonnetz, LprMap, NoteGraph}; use crate::traits::{TriadRepr, TriadType}; use crate::triad::TriadBase; use core::hash::Hash; @@ -25,7 +25,7 @@ where { HyperTonnetz { graph: NoteGraph::new(), - triads: TriadMap::new(), + triads: EdgeMap::new(), transformations: LprMap::new(), } } @@ -50,11 +50,11 @@ where &mut self.graph } /// returns a reference to the triads map - pub const fn triads(&self) -> &TriadMap { + pub const fn triads(&self) -> &EdgeMap { &self.triads } /// returns a mutable reference to the triads map - pub const fn triads_mut(&mut self) -> &mut TriadMap { + pub const fn triads_mut(&mut self) -> &mut EdgeMap { &mut self.triads } /// returns a reference to the transformations map @@ -81,7 +81,7 @@ where } #[inline] /// update the triad map - pub fn set_triads(&mut self, triads: TriadMap) { + pub fn set_triads(&mut self, triads: EdgeMap) { self.triads = triads } #[inline] diff --git a/nrt/src/traits/triad_type.rs b/nrt/src/traits/triad_type.rs index 2b8390a1..5c28132a 100644 --- a/nrt/src/traits/triad_type.rs +++ b/nrt/src/traits/triad_type.rs @@ -16,18 +16,15 @@ pub trait Relative { /// The [`TriadType`] trait is used to represent the various classifications of a triad /// considered by the Neo-Riemannian theory. -pub trait TriadType: Relative +pub trait TriadType where - Self: 'static + Copy + Default + Relative + Send + Sync + core::fmt::Debug + core::fmt::Display, + Self: 'static + Copy + Relative + Send + Sync + core::fmt::Debug + core::fmt::Display, { private! {} fn new() -> Self where - Self: Sized, - { - Self::default() - } + Self: Sized; fn root(&self) -> usize; @@ -35,8 +32,18 @@ where fn third(&self) -> usize; /// consumes the instance, returning a variant of the [`TriadClass`] enum - fn dynamic(self) -> crate::Triads { - crate::Triads::from_class(self) + fn dynamic(&self) -> crate::Triads { + if self.is_major() { + Triads::Major + } else if self.is_minor() { + Triads::Minor + } else if self.is_augmented() { + Triads::Augmented + } else if self.is_diminished() { + Triads::Diminished + } else { + unreachable!("invalid triad type") + } } fn is_major(&self) -> bool { @@ -56,10 +63,31 @@ where } } +pub trait RelTriad: TriadType + Relative +where + Self::Rel: TriadType, +{ + private! {} + + fn relative(&self) -> Self::Rel; +} + /* ************* Implementations ************* */ +impl RelTriad for A +where + A: TriadType + Relative, + B: TriadType, +{ + seal! {} + + fn relative(&self) -> B { + ::new() + } +} + impl Relative for Triads { type Rel = Triads; @@ -116,6 +144,10 @@ macro_rules! triad_kind { seal! {} + fn new() -> Self { + Self::default() + } + fn root(&self) -> usize { $r } diff --git a/nrt/src/triad.rs b/nrt/src/triad.rs index 12768f30..b05f4e34 100644 --- a/nrt/src/triad.rs +++ b/nrt/src/triad.rs @@ -8,22 +8,60 @@ //! //! # Overview //! +//! A triad is defined to be a chord, composed of three notes, each of which maintain certain +//! intervallic relationships with one another. More specifically, the distance between the +//! first and second as well as the second and third notes is defined to be a major or minor +//! third, whilst the distance between the first and third notes is some variant of a _fifth_. +//! +//! These compositional contraints lead to four possible triadic chord types, namely: major, +//! minor, augmented, and diminished. Each of these chord types can be represented as a 3-tuple +//! containing the pitch classes of each note within the chord. For example, a C-major triad +//! can be represented as the tuple (0, 4, 7), where `0` represents the root note C, `4` the +//! major third E, and `7` as the perfect fifth G. +//! +//! Additionally, triads may be transformed using any one of three transformations defined as: +//! leading, parallel, and relative. The behavior of these transformations is determined by the +//! interval between the first two notes of the triad, i.e. a major or minor third. Each of +//! these transformations is capable of being chained together into discrete and continuous +//! sequences or spaces and is its own inverse. This means that any consecutive applications of +//! any one particular transformation simply reverts the object back into its original state. +//! +//! That being said, augmented and diminished traids _can_ be transformed, however, the exact +//! nature of their responses is not as predictable as with major / minor triads. +//! +//! ## Examples +//! +//! ### _Basic Usage_ +//! +//! Initialize a C-major triad and verify its composition +//! //! ```rust //! use rstmt_nrt::Triad; -//! -//! // initialize a c-major triad: (0, 4, 7) +//! // create new triad //! let c_major = Triad::major(0); //! // verify the composition //! assert_eq! { c_major, [0, 4, 7] } -//! assert! { c_major.is_major() && !c_major.is_minor() } //! ``` //! -//! # Background +//! ### _Transformations_ //! -//! A triad is defined to be a chord, composed of three notes, each of which maintain certain -//! intervallic relationships with one another. More specifically, the distance between the -//! first and second as well as the second and third notes is defined to be a major or minor -//! third, whilst the distance between the first and third notes is some variant of a _fifth_. +//! Initialize a C-major triad before applying each of the three transformations: +//! +//! ```rust +//! use rstmt_nrt::Triad; +//! // create new triad +//! let c_major = Triad::major(0); +//! // apply leading transformation +//! assert_eq! { c_major.leading(), Triad::minor(4) } +//! // apply parallel transformation +//! assert_eq! { c_major.parallel(), Triad::minor(0) } +//! // apply relative transformation +//! assert_eq! { c_major.relative(), Triad::minor(9) } +//! // confirm inverses +//! assert_eq! { c_major.leading().leading(), c_major } +//! assert_eq! { c_major.parallel().parallel(), c_major } +//! assert_eq! { c_major.relative().relative(), c_major } +//! ``` //! //! # References //! @@ -58,7 +96,7 @@ where { pub(crate) chord: S, pub(crate) class: K, - pub(crate) octave: Octave, + pub(crate) octave: Octave, } #[cfg(test)] @@ -93,7 +131,7 @@ mod tests { fn test_triad_properties() { let fsharp_minor = Triad::minor(6); assert! { fsharp_minor.is_minor() && !fsharp_minor.is_major() } - assert_eq! { fsharp_minor.class(), rstmt_core::Minor } + assert_eq! { fsharp_minor.class(), &rstmt_core::Minor } } #[test] diff --git a/nrt/src/types/factors.rs b/nrt/src/types/factors.rs index 4558a8c0..3003099d 100644 --- a/nrt/src/types/factors.rs +++ b/nrt/src/types/factors.rs @@ -37,7 +37,6 @@ use strum::IntoEnumIterator; Hash, Ord, PartialOrd, - variants::VariantConstructors, strum::AsRefStr, strum::Display, strum::EnumCount, @@ -45,9 +44,11 @@ use strum::IntoEnumIterator; strum::EnumString, strum::VariantArray, strum::VariantNames - ) + ), + strum(serialize_all = "lowercase") )] #[repr(usize)] +#[strum(serialize_all = "lowercase")] pub enum ChordFactor { #[strum(serialize = "r", serialize = "root")] Root(T) = 0, @@ -57,6 +58,31 @@ pub enum ChordFactor { Fifth(T) = 2, } +impl Factors { + /// a functional constructor for the [`Root`](Self::Root) variant + pub const fn root() -> Self { + Self::Root + } + /// a functional constructor for the [`Third`](Self::Third) variant + pub const fn third() -> Self { + Self::Third + } + /// a functional constructor for the [`Fifth`](Self::Fifth) variant + pub const fn fifth() -> Self { + Self::Fifth + } + /// returns an array of the possible [`Factors`] variants + pub fn factors_as_slice() -> [Self; 3] { + use Factors::*; + [Root, Third, Fifth] + } + #[cfg(feature = "alloc")] + /// returns a collection of all the other variants except the one that is called on + pub fn others(&self) -> alloc::vec::Vec { + Self::iter().filter(|x| x != self).collect() + } +} + impl ChordFactor { pub const fn new(data: T, factor: Factors) -> Self { match factor { @@ -112,46 +138,40 @@ impl ChordFactor { } } -mod impl_factors { - use super::*; - - impl Factors { - /// returns an array of the possible [`Factors`] variants - pub fn factors_as_slice() -> [Self; 3] { - use Factors::*; - [Root, Third, Fifth] - } - #[cfg(feature = "alloc")] - /// returns a collection of all the other variants except the one that is called on - pub fn others(&self) -> alloc::vec::Vec { - Self::iter().filter(|x| x != self).collect() - } - } - - impl Default for Factors { - fn default() -> Self { - Factors::Root - } +impl Default for Factors { + fn default() -> Self { + Factors::Root } +} - impl From for Factors { - fn from(x: usize) -> Self { - use strum::EnumCount; - match x % Self::COUNT { - 0 => Factors::Root, - 1 => Factors::Third, - _ => Factors::Fifth, +macro_rules! impl_from_factor { + (@impl $T:ty) => { + impl From<$T> for Factors { + fn from(x: $T) -> Self { + use strum::EnumCount; + match x % Self::COUNT as $T { + 0 => Factors::Root, + 1 => Factors::Third, + 2 => Factors::Fifth, + _ => unreachable!("Modular arithmetic error"), + } } } - } - impl From for usize { - fn from(x: Factors) -> Self { - x as usize + impl From for $T { + fn from(x: Factors) -> Self { + x as $T + } } + }; + ($($T:ty),* $(,)?) => { + $(impl_from_factor! { @impl $T })* } } +impl_from_factor! { u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize } + + #[cfg(test)] mod tests { use super::*; @@ -167,9 +187,6 @@ mod tests { use Factors::*; let factors = Factors::factors_as_slice(); - assert_eq!(factors.len(), 3); - assert_eq!(factors[0], Root); - assert_eq!(factors[1], Third); - assert_eq!(factors[2], Fifth); + assert_eq! { factors, [Root, Third, Fifth] } } } diff --git a/nrt/src/types/lpr.rs b/nrt/src/types/lpr.rs index 491c42b9..9cd61d8c 100644 --- a/nrt/src/types/lpr.rs +++ b/nrt/src/types/lpr.rs @@ -7,7 +7,7 @@ use crate::error::TriadError; use crate::traits::{Relative, TriadRepr, TriadType}; use crate::triad::TriadBase; use num_traits::{FromPrimitive, One}; -use rstmt_core::{PitchMod, TryTransform}; +use rstmt_core::{Dirac, PitchMod}; /// The [`LPR`] implementation enumerates the primary transformations considered within the /// Neo-Riemannian theory. Each transformation is its own inverse (meaning consecutive @@ -20,14 +20,14 @@ use rstmt_core::{PitchMod, TryTransform}; /// With The transformations are: /// /// - Leading (L): -/// - [Major] subtract a semitone from the root and move it to the fifth -/// - [Minor] add a semitone to the fifth and move it to the root +/// - [Major] decrement the root by a semitone; move to the fifth +/// - [Minor] increment the fifth by a semitone; move to the root /// - Parallel (P): -/// - [Major] subtract a semitone from the third -/// - [Minor] add a semitone to the third +/// - [Major] decrement the third by a semitone +/// - [Minor] increment the third by a semitone /// - Relative (R): -/// - [Major] add a tone to the fifth and move it to the root -/// - [Minor] subtract a tone from the root and move it to the fifth +/// - [Major] add a whole tone to the fifth; move to the root +/// - [Minor] subtract a whole tone from the root; move to the fifth /// /// Using category theory we could define these transformations to be contravariant functors /// mapping between _categories_ of triads. @@ -86,19 +86,59 @@ impl LPR { use strum::IntoEnumIterator; ::iter() } - /// Apply a transformation to a triad - pub fn transform(&self, triad: X) -> Result + /// a convenience method for applying a transformation onto a triad, panicking on failure. + pub fn apply(self, triad: X) -> Y where - Self: TryTransform, + Self: Dirac, { - >::try_transform(self, triad) + >::apply(self, triad) + } + + fn dirac(self, rhs: &TriadBase) -> TriadBase + where + K: TriadType, + R: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, + { + let major = rhs.class().root() == 4; + let two = T::from_u8(2).unwrap(); + + let &x = rhs.chord().root(); + let &y = rhs.chord().third(); + let &z = rhs.chord().fifth(); + + let notes: [T; 3] = if major { + match self { + LPR::Leading => [y, z, (x - T::one()).pmod()], + LPR::Parallel => [x, (y - T::one()).pmod(), z], + LPR::Relative => [(z + two).pmod(), x, y], + } + } else { + match self { + LPR::Leading => [(z + T::one()).pmod(), x, y], + LPR::Parallel => [x, (y + T::one()).pmod(), z], + LPR::Relative => [y, z, (x - two).pmod()], + } + }; + + TriadBase { + chord: S::from_arr(notes), + class: ::rel(&rhs.class()), + octave: *rhs.octave(), + } } } -impl TryTransform> for LPR +impl Dirac> for LPR where K: TriadType, - K::Rel: TriadType, + K::Rel: TriadType, S: TriadRepr, T: Copy + FromPrimitive @@ -108,17 +148,16 @@ where + core::ops::Sub, { type Output = TriadBase; - type Error = TriadError; - fn try_transform(&self, rhs: TriadBase) -> Result { - self.try_transform(&rhs) + fn apply(self, rhs: TriadBase) -> Self::Output { + self.dirac(&rhs) } } -impl TryTransform<&TriadBase> for LPR +impl Dirac> for &LPR where - K::Rel: TriadType, K: TriadType, + K::Rel: TriadType, S: TriadRepr, T: Copy + FromPrimitive @@ -128,37 +167,85 @@ where + core::ops::Sub, { type Output = TriadBase; - type Error = TriadError; - fn try_transform(&self, rhs: &TriadBase) -> Result { - if rhs.is_augmented() || rhs.is_diminished() { - return Err(TriadError::InvalidTriadClass); - } - let two = T::from_u8(2).unwrap(); + fn apply(self, rhs: TriadBase) -> Self::Output { + self.dirac(&rhs) + } +} - let &x = rhs.chord().root(); - let &y = rhs.chord().third(); - let &z = rhs.chord().fifth(); +impl Dirac> for &mut LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; - let notes: [T; 3] = if rhs.is_major() { - match self { - LPR::Leading => [y, z, (x - T::one()).pmod()], - LPR::Parallel => [x, (y - T::one()).pmod(), z], - LPR::Relative => [(z + two).pmod(), x, y], - } - } else { - match self { - LPR::Leading => [(z + T::one()).pmod(), x, y], - LPR::Parallel => [x, (y + T::one()).pmod(), z], - LPR::Relative => [y, z, (x - two).pmod()], - } - }; + fn apply(self, rhs: TriadBase) -> Self::Output { + self.dirac(&rhs) + } +} - Ok(TriadBase { - chord: S::from_arr(notes), - class: ::rel(&rhs.class()), - octave: rhs.octave(), - }) +impl Dirac<&TriadBase> for LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; + + fn apply(self, rhs: &TriadBase) -> Self::Output { + self.dirac(rhs) + } +} + +impl Dirac<&TriadBase> for &LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; + + fn apply(self, rhs: &TriadBase) -> Self::Output { + self.dirac(rhs) + } +} + +impl Dirac<&mut TriadBase> for LPR +where + K: TriadType, + K::Rel: TriadType, + S: TriadRepr, + T: Copy + + FromPrimitive + + One + + PitchMod + + core::ops::Add + + core::ops::Sub, +{ + type Output = TriadBase; + + fn apply(self, rhs: &mut TriadBase) -> Self::Output { + self.dirac(rhs) } } diff --git a/nrt/tests/transform.rs b/nrt/tests/transform.rs index 0a0ea80e..4715789b 100644 --- a/nrt/tests/transform.rs +++ b/nrt/tests/transform.rs @@ -3,29 +3,26 @@ Contrib: @FL03 */ use LPR::*; -use rstmt_nrt::{LPR, Triad, TriadError}; +use rstmt_nrt::{LPR, Triad}; #[test] -fn test_leading() -> Result<(), TriadError> { +fn test_leading() { let c_major = Triad::major(0); - assert_eq! { Leading.transform(&c_major)?, [4, 7, 11] } - assert_eq! { Leading.transform(Leading.transform(&c_major)?)?, c_major } - Ok(()) + assert_eq! { Leading.apply(&c_major), [4, 7, 11] } + assert_eq! { Leading.apply(Leading.apply(&c_major)), c_major } } #[test] -fn test_parallel() -> Result<(), TriadError> { +fn test_parallel() { let c_major = Triad::major(0); - assert_eq! { Parallel.transform(&c_major)?, [0, 3, 7] } - assert_eq! { Parallel.transform(Parallel.transform(&c_major)?)?, c_major } - Ok(()) + assert_eq! { Parallel.apply(&c_major), [0, 3, 7] } + assert_eq! { Parallel.apply(Parallel.apply(&c_major)), c_major } } #[test] -fn test_relative() -> Result<(), TriadError> { +fn test_relative() { // c-major let c_major = Triad::major(0); - assert_eq! { Relative.transform(&c_major)?, [9, 0, 4] } - assert_eq! { Relative.transform(Relative.transform(&c_major)?)?, c_major } - Ok(()) + assert_eq! { Relative.apply(&c_major), [9, 0, 4] } + assert_eq! { Relative.apply(Relative.apply(&c_major)), c_major } } diff --git a/rstmt/lib.rs b/rstmt/lib.rs index 6505a50e..76b7b81f 100644 --- a/rstmt/lib.rs +++ b/rstmt/lib.rs @@ -54,7 +54,7 @@ clippy::should_implement_trait )] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(allocator_api))] +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // compile time checks #[cfg(not(any(feature = "std", feature = "alloc")))] compile_error! { "Either the 'std' or 'alloc' feature must be enabled for this crate." } diff --git a/traits/Cargo.toml b/traits/Cargo.toml index e639ce39..5e02d3c1 100644 --- a/traits/Cargo.toml +++ b/traits/Cargo.toml @@ -70,6 +70,7 @@ std = [ "num-complex?/std", "num-traits/std", "serde?/std", + "wasm-bindgen?/std", ] wasi = [ @@ -85,6 +86,7 @@ alloc = [ "hashbrown?/alloc", "rspace-traits/alloc", "serde?/alloc", + "wasm-bindgen?/gg-alloc", ] complex = [ @@ -112,6 +114,7 @@ serde = [ "hashbrown?/serde", "rspace-traits/serde", "num-complex/serde", + "wasm-bindgen?/serde", ] wasm_bindgen = ["dep:wasm-bindgen"] diff --git a/traits/src/lib.rs b/traits/src/lib.rs index 40dcff31..1db00ec2 100644 --- a/traits/src/lib.rs +++ b/traits/src/lib.rs @@ -1,7 +1,7 @@ -#![crate_name = "rstmt_traits"] //! A collection of useful traits focused on musical abstractions, composition, and operations. //! //! +#![crate_type = "lib"] #![allow( clippy::derivable_impls, clippy::len_without_is_empty, @@ -15,10 +15,7 @@ clippy::upper_case_acronyms )] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(allocator_api))] -// compiler check -#[cfg(not(any(feature = "std", feature = "alloc")))] -compile_error! { "either the \"std\" or \"alloc\" feature must be enabled" } +#![cfg_attr(all(feature = "alloc", feature = "nightly"), feature(allocator_api))] // macros #[macro_use] pub(crate) mod macros { diff --git a/traits/src/ops/transform.rs b/traits/src/ops/transform.rs index 883854dd..4b550b2d 100644 --- a/traits/src/ops/transform.rs +++ b/traits/src/ops/transform.rs @@ -4,33 +4,17 @@ Contrib: @FL03 */ -/// The [`Transform`] trait establishes a common interface for objects that can be transformed -/// with respect to a given transformation, input, etc. to produce a new output. -pub trait Transform { +/// [`Dirac`] is used to define a specific transformation operation +pub trait Dirac { type Output; - fn transform(&self, rhs: Rhs) -> Self::Output; + fn apply(self, rhs: Rhs) -> Self::Output; } -/// [`TryTransform`] defines a fallible transformation operation that can fail, producing an -pub trait TryTransform { +/// The [`Transform`] trait establishes a binary operation that for objects capable of being +/// transformed by another object of type `Rhs`, producing some output. +/// +pub trait Transform { type Output; - type Error; - - fn try_transform(&self, rhs: Rhs) -> Result; -} - -/* - ************* Implementations ************* -*/ - -impl TryTransform for A -where - A: Transform, -{ - type Output = Y; - type Error = core::convert::Infallible; - fn try_transform(&self, rhs: X) -> Result { - Ok(>::transform(self, rhs)) - } + fn transform(self, rhs: Rhs) -> Self::Output; }