├── .github ├── scripts │ ├── bump-version.sh │ ├── configure-release-pat.sh │ ├── files-with-current-version-string │ ├── last-released-version.sh │ └── setup-git-flow.sh └── workflows │ ├── ci.yml │ ├── finish-release.yml │ └── start-release.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── COMMIT_MESSAGE_TEMPLATE ├── CONTRIBUTOR.md ├── Cargo.toml ├── LICENCE_Unicode-3.0 ├── LICENSE_APACHE_2_0 ├── LICENSE_MIT ├── README.md ├── cliff.toml ├── codespell-ignore ├── configure-hooks.sh ├── deny.toml ├── examples ├── handling_messages │ ├── Cargo.toml │ └── src │ │ └── main.rs └── no_std_dynamic_message_generator │ ├── Cargo.toml │ └── src │ └── main.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ ├── generic_sysex_inserting_payloads.rs │ ├── sysex7_payload_roundtrip.rs │ └── sysex8_payload_roundtrip.rs ├── midi2 ├── Cargo.toml ├── README.md └── src │ ├── buffer.rs │ ├── channel_voice1.rs │ ├── channel_voice1 │ ├── README.md │ ├── channel_pressure.rs │ ├── control_change.rs │ ├── key_pressure.rs │ ├── note_off.rs │ ├── note_on.rs │ ├── packet.rs │ ├── pitch_bend.rs │ └── program_change.rs │ ├── channel_voice2.rs │ ├── channel_voice2 │ ├── README.md │ ├── assignable_controller.rs │ ├── assignable_per_note_controller.rs │ ├── attribute.rs │ ├── channel_pitch_bend.rs │ ├── channel_pressure.rs │ ├── control_change.rs │ ├── controller.rs │ ├── key_pressure.rs │ ├── note_off.rs │ ├── note_on.rs │ ├── packet.rs │ ├── per_note_management.rs │ ├── per_note_pitch_bend.rs │ ├── program_change.rs │ ├── registered_controller.rs │ ├── registered_per_note_controller.rs │ ├── relative_assignable_controller.rs │ └── relative_registered_controller.rs │ ├── ci.rs │ ├── ci │ ├── README.md │ ├── common_properties.rs │ ├── device_id.rs │ ├── discovery.rs │ ├── old_mod.rs │ ├── property.rs │ └── version.rs │ ├── detail.rs │ ├── detail │ ├── bit_ops.rs │ ├── common_err_strings.rs │ ├── common_properties.rs │ ├── encode_7bit.rs │ ├── helpers.rs │ ├── property.rs │ ├── schema.rs │ ├── test_support.rs │ └── test_support │ │ └── rubbish_payload_iterator.rs │ ├── error.rs │ ├── flex_data.rs │ ├── flex_data │ ├── README.md │ ├── packet.rs │ ├── set_chord_name.rs │ ├── set_key_signature.rs │ ├── set_metronome.rs │ ├── set_tempo.rs │ ├── set_time_signature.rs │ ├── text.rs │ ├── tonic.rs │ └── unknown_metadata_text.rs │ ├── lib.rs │ ├── message.rs │ ├── packet.rs │ ├── packets.rs │ ├── sysex7.rs │ ├── sysex7 │ ├── README.md │ └── packet.rs │ ├── sysex8.rs │ ├── sysex8 │ ├── README.md │ └── packet.rs │ ├── system_common.rs │ ├── system_common │ ├── README.md │ ├── packet.rs │ ├── song_position_pointer.rs │ ├── song_select.rs │ └── time_code.rs │ ├── traits.rs │ ├── ump_stream.rs │ ├── ump_stream │ ├── device_identity.rs │ ├── end_of_clip.rs │ ├── endpoint_discovery.rs │ ├── endpoint_info.rs │ ├── endpoint_name.rs │ ├── function_block_discovery.rs │ ├── function_block_info.rs │ ├── function_block_name.rs │ ├── packet.rs │ ├── product_instance_id.rs │ ├── start_of_clip.rs │ ├── stream_configuration_notification.rs │ ├── stream_configuration_request.rs │ └── ump_stream_group.rs │ ├── utility.rs │ └── utility │ └── packet.rs ├── midi2_proc ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── common.rs │ ├── derives.rs │ ├── generate_ci.rs │ ├── generate_message.rs │ └── lib.rs └── requirements.txt /.github/scripts/bump-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 2 ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | OLD_VERSION=$1 9 | NEW_VERSION=$2 10 | ERROR_CODE=0 11 | 12 | if [[ "$OSTYPE" == "darwin"* ]]; then 13 | SED_CMD=("sed" "-i" "") 14 | else 15 | SED_CMD=("sed" "-i") 16 | fi 17 | 18 | cat .github/scripts/files-with-current-version-string | xargs -I % "${SED_CMD[@]}" s/$OLD_VERSION/$NEW_VERSION/g % || ERROR_CODE=2 19 | 20 | if [ "$ERROR_CODE" -ne 0 ]; then 21 | echo "Error: An error while replacing version strings." 22 | exit $ERROR_CODE 23 | fi 24 | 25 | echo "Version bumped from $OLD_VERSION to $NEW_VERSION" 26 | -------------------------------------------------------------------------------- /.github/scripts/configure-release-pat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ]; then 4 | echo "Usage: $0 " 5 | exit 1 6 | fi 7 | 8 | PAT=$1 9 | 10 | git config --global user.email "ben_leadbetter@hotmail.com " 11 | git config --global user.name "GitHub Actions" 12 | git remote set-url origin https://x-access-token:${PAT}@github.com/midi2-dev/bl-midi2-rs 13 | -------------------------------------------------------------------------------- /.github/scripts/files-with-current-version-string: -------------------------------------------------------------------------------- 1 | README.md 2 | midi2/Cargo.toml 3 | midi2_proc/Cargo.toml 4 | -------------------------------------------------------------------------------- /.github/scripts/last-released-version.sh: -------------------------------------------------------------------------------- 1 | git tag --sort=committerdate | grep -E "^[0-9]+\.[0-9]+\.[0-9]$" | tail -n 1 2 | -------------------------------------------------------------------------------- /.github/scripts/setup-git-flow.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get update 4 | sudo apt-get install -y git-flow 5 | 6 | git fetch origin 7 | git switch --track origin/main 8 | git switch --track origin/develop 9 | 10 | git config --global user.email "ben_leadbetter@hotmail.com" 11 | git config --global user.name "CI" 12 | 13 | git config gitflow.branch.master main 14 | git config gitflow.branch.develop develop 15 | git config gitflow.prefix.feature feature/ 16 | git config gitflow.prefix.bugfix bugfix/ 17 | git config gitflow.prefix.release release/ 18 | git config gitflow.prefix.hotfix hotfix/ 19 | git config gitflow.prefix.support support/ 20 | git config gitflow.prefix.versiontag "" 21 | git config gitflow.path.hooks "" 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: [ "main", "develop" ] 6 | pull_request: 7 | branches: [ "main", "develop" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | test: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, macos-latest, windows-latest] 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Run tests 21 | run: cargo test --verbose --all-features 22 | 23 | deny: 24 | runs-on: ubuntu-24.04 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: EmbarkStudios/cargo-deny-action@v1 28 | with: 29 | command: check all 30 | manifest-path: midi2/Cargo.toml 31 | 32 | clippy: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - name: Install Clippy 37 | run: rustup component add clippy 38 | - name: Clippy version 39 | run: cargo clippy --version 40 | - name: Run Clippy 41 | run: cargo clippy --all-features -- -D warnings 42 | -------------------------------------------------------------------------------- /.github/workflows/finish-release.yml: -------------------------------------------------------------------------------- 1 | name: Finish release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: Semver version to release 8 | required: true 9 | type: 10 | description: What type of release 11 | required: true 12 | default: 'release' 13 | type: choice 14 | options: 15 | - release 16 | - hotfix 17 | 18 | permissions: 19 | contents: write 20 | pull-requests: write 21 | 22 | jobs: 23 | release-finish: 24 | name: Finish release 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | ref: ${{ github.event.inputs.type }}/${{ github.event.inputs.version }} 32 | - name: Check semver 33 | uses: obi1kenobi/cargo-semver-checks-action@v2 34 | with: 35 | verbose: true 36 | - name: Setup git-flow 37 | run: ./.github/scripts/setup-git-flow.sh 38 | - name: Configure PAT for release 39 | run: ./.github/scripts/configure-release-pat.sh ${{ secrets.RELEASE_PAT }} 40 | - name: Finish release 41 | run: git flow ${{ github.event.inputs.type }} finish ${{ github.event.inputs.version }} -m ${{ github.event.inputs.version }} --push 42 | - name: Publish midi2_proc 43 | uses: ryohidaka/action-cargo-publish@v0.1.0 44 | with: 45 | path: midi2_proc 46 | token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 47 | - name: Publish midi2 48 | uses: ryohidaka/action-cargo-publish@v0.1.0 49 | with: 50 | path: midi2 51 | token: ${{ secrets.CARGO_REGISTRY_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/start-release.yml: -------------------------------------------------------------------------------- 1 | name: Start release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: Semver version to release 8 | required: true 9 | type: 10 | description: What type of release 11 | required: true 12 | default: 'release' 13 | type: choice 14 | options: 15 | - release 16 | - hotfix 17 | 18 | permissions: 19 | contents: write 20 | pull-requests: write 21 | 22 | jobs: 23 | release-start: 24 | name: Release start 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | - name: Setup git-flow 32 | run: ./.github/scripts/setup-git-flow.sh 33 | - name: Start release 34 | run: git flow ${{ github.event.inputs.type }} start ${{ github.event.inputs.version }} 35 | - name: Determine last release 36 | run: echo "LAST_RELEASE=$(./.github/scripts/last-released-version.sh)" >> $GITHUB_ENV 37 | - name: Bump version 38 | run: ./.github/scripts/bump-version.sh $LAST_RELEASE ${{ github.event.inputs.version }} 39 | - name: Update changelog 40 | if: ${{ github.event.inputs.type == 'release' }} 41 | uses: orhun/git-cliff-action@v4 42 | with: 43 | args: --unreleased --prepend CHANGELOG.md --tag ${{ github.event.inputs.version }} --github-token ${{ secrets.GITHUB_TOKEN }} 44 | - name: Commit release prep changes 45 | run: | 46 | git add CHANGELOG.md 47 | cat .github/scripts/files-with-current-version-string | xargs -I % git add % 48 | git commit -m "chore(release): prepare ${{ github.event.inputs.version }}" 49 | - name: Push branch 50 | run: git push origin HEAD 51 | # waiting on org level permissions for this step 52 | # - name: Create Pull Request 53 | # run: gh pr create --base main --head release/${{ github.event.inputs.version }} --title "Release | ${{ github.event.inputs.version }}" --body "Prepare release of version ${{ github.event.inputs.version }}." 54 | # env: 55 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | midi2_proc/target 4 | midi2_proc/Cargo.lock 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/AndrejOrsula/pre-commit-cargo.git 3 | rev: 0.4.0 4 | hooks: 5 | - id: cargo-fmt 6 | stages: [pre-commit] 7 | - repo: https://github.com/compilerla/conventional-pre-commit 8 | rev: v4.2.0 9 | hooks: 10 | - id: conventional-pre-commit 11 | stages: [commit-msg] 12 | - repo: https://github.com/pre-commit/pre-commit-hooks 13 | rev: v5.0.0 14 | hooks: 15 | - id: check-added-large-files 16 | stages: [pre-commit] 17 | - id: check-case-conflict 18 | stages: [pre-commit] 19 | - id: check-merge-conflict 20 | stages: [pre-commit] 21 | - id: check-toml 22 | stages: [pre-commit] 23 | - id: check-yaml 24 | stages: [pre-commit] 25 | - id: detect-private-key 26 | stages: [pre-commit] 27 | - id: mixed-line-ending 28 | stages: [pre-commit] 29 | - repo: https://github.com/codespell-project/codespell 30 | rev: v2.4.1 31 | hooks: 32 | - id: codespell 33 | args: [-I, codespell-ignore, -w] 34 | ci: 35 | autofix_commit_msg: | 36 | ci: [pre-commit.ci] auto fixes from pre-commit.com hooks 37 | 38 | for more information, see https://pre-commit.ci 39 | autoupdate_commit_msg: 'ci: [pre-commit.ci] pre-commit autoupdate' 40 | -------------------------------------------------------------------------------- /COMMIT_MESSAGE_TEMPLATE: -------------------------------------------------------------------------------- 1 | 2 | # [optional scope]: 3 | # │ │ 4 | # │ └─⫸ e.g. channel-voice, sysex, ci 5 | # │ 6 | # └─⫸ {build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test} 7 | # 8 | # [optional body] 9 | # 10 | # [optional footer(s)] 11 | -------------------------------------------------------------------------------- /CONTRIBUTOR.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are more than welcome! 4 | The library is still in its early development phase. 5 | There is not even a definite road map at this point. 6 | Please utilise the Issues feature on GitHub 7 | or open a PR from your own fork to start a discussion ☺️.. 8 | 9 | ## 🪝 Hooks 🪝 10 | 11 | We recommend using the hooks while developing in this repository. 12 | The hooks manager is a python package which needs installing locally. 13 | The best way to do this is via a virtual environment. 14 | 15 | ```shell 16 | > python3 -m venv .venv 17 | > source .venv/bin/activate 18 | > pip install -r requirements.txt 19 | > ./configure-hooks.sh install 20 | ``` 21 | 22 | ## 🧱 Building 🧱 23 | 24 | To build, simply follow the usual cargo flow. 25 | ```shell 26 | > cargo build --all-features 27 | ``` 28 | 29 | ## Tests 30 | 31 | The project currently has a good unit tests coverage, 32 | but no integration test coverage yet. 33 | To run the tests, follow the usual cargo flow. 34 | 35 | ```shell 36 | > cargo test --all-features 37 | ``` 38 | 39 | ## 🌍 A Tour of midi2 🌍 40 | 41 | todo 42 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["midi2", "midi2_proc", "examples/*", "fuzz"] 3 | default-members = ["midi2"] 4 | resolver = "2" 5 | 6 | [workspace.metadata] 7 | licenses = ["Unicode-3.0"] 8 | -------------------------------------------------------------------------------- /LICENCE_Unicode-3.0: -------------------------------------------------------------------------------- 1 | UNICODE LICENSE V3 2 | 3 | COPYRIGHT AND PERMISSION NOTICE 4 | 5 | Copyright © 1991-2024 Unicode, Inc. 6 | 7 | NOTICE TO USER: Carefully read the following legal agreement. BY 8 | DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR 9 | SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE 10 | TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT 11 | DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a 14 | copy of data files and any associated documentation (the "Data Files") or 15 | software and any associated documentation (the "Software") to deal in the 16 | Data Files or Software without restriction, including without limitation 17 | the rights to use, copy, modify, merge, publish, distribute, and/or sell 18 | copies of the Data Files or Software, and to permit persons to whom the 19 | Data Files or Software are furnished to do so, provided that either (a) 20 | this copyright and permission notice appear with all copies of the Data 21 | Files or Software, or (b) this copyright and permission notice appear in 22 | associated Documentation. 23 | 24 | THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY 25 | KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF 27 | THIRD PARTY RIGHTS. 28 | 29 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 30 | BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, 31 | OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 32 | WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 33 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA 34 | FILES OR SOFTWARE. 35 | 36 | Except as contained in this notice, the name of a copyright holder shall 37 | not be used in advertising or otherwise to promote the sale, use or other 38 | dealings in these Data Files or Software without prior written 39 | authorization of the copyright holder. 40 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ben Leadbetter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | [remote.github] 2 | owner = "midi2-dev" 3 | repo = "bl-midi2-rs" 4 | 5 | [changelog] 6 | header = """ 7 | # Changelog 8 | 9 | """ 10 | body = """ 11 | {%- macro remote_url() -%} 12 | https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }} 13 | {%- endmacro -%} 14 | 15 | {% macro print_commit(commit) -%} 16 | - {% if commit.scope %}*({{ commit.scope }})* {% endif %}\ 17 | {% if commit.breaking %}[**breaking**] {% endif %}\ 18 | {{ commit.message | upper_first }} - \ 19 | ([{{ commit.id | truncate(length=7, end="") }}]({{ self::remote_url() }}/commit/{{ commit.id }}))\ 20 | {% endmacro -%} 21 | 22 | {% if version %}\ 23 | {% if previous.version %}\ 24 | ## [{{ version | trim_start_matches(pat="v") }}]\ 25 | ({{ self::remote_url() }}/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }} 26 | {% else %}\ 27 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 28 | {% endif %}\ 29 | {% else %}\ 30 | ## [unreleased] 31 | {% endif %}\ 32 | 33 | {% for group, commits in commits | group_by(attribute="group") %} 34 | ### {{ group | striptags | trim | upper_first }} 35 | {% for commit in commits 36 | | filter(attribute="scope") 37 | | sort(attribute="scope") %} 38 | {{ self::print_commit(commit=commit) }} 39 | {%- endfor %} 40 | {% for commit in commits %} 41 | {%- if not commit.scope -%} 42 | {{ self::print_commit(commit=commit) }} 43 | {% endif -%} 44 | {% endfor -%} 45 | {% endfor -%} 46 | {%- if github -%} 47 | {% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %} 48 | ## New Contributors ❤️ 49 | {% endif %}\ 50 | {% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %} 51 | * @{{ contributor.username }} made their first contribution 52 | {%- if contributor.pr_number %} in \ 53 | [#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \ 54 | {%- endif %} 55 | {%- endfor -%} 56 | {%- endif %} 57 | 58 | 59 | """ 60 | postprocessors = [ 61 | { pattern = '', replace = "https://github.com/midi2-dev/bl-midi2-rs" }, 62 | ] 63 | 64 | [git] 65 | commit_preprocessors = [ 66 | # Replace GitHub issue numbers 67 | { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))" }, 68 | ] 69 | commit_parsers = [ 70 | { message = "^feat", group = "✨ Features" }, 71 | { message = "^fix", group = "🐛 Fixes" }, 72 | { message = "^doc", group = "📚 Documentation" }, 73 | { message = "^perf", group = "⚡ Performance" }, 74 | { message = "^refactor\\(clippy\\)", skip = true }, 75 | { message = "^refactor", group = "🛠️ Refactor" }, 76 | { message = "^style", group = "💅 Styling" }, 77 | { message = "^test", group = "🧪 Testing" }, 78 | { message = "^chore|^ci", skip = true }, 79 | { body = ".*security", group = "🔒 Security" }, 80 | { message = "^revert", group = "◀️🔙 Revert" }, 81 | ] 82 | 83 | tag_pattern = "^[0-9]+\\.[0-9]+\\.[0-9]+$" 84 | sort_commits = "newest" 85 | -------------------------------------------------------------------------------- /codespell-ignore: -------------------------------------------------------------------------------- 1 | crate 2 | -------------------------------------------------------------------------------- /configure-hooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | install() { 4 | pre-commit install --install-hooks 5 | pre-commit install --install-hooks -t commit-msg 6 | git config commit.template COMMIT_MESSAGE_TEMPLATE 7 | } 8 | 9 | uninstall() { 10 | pre-commit uninstall 11 | pre-commit uninstall -t commit-msg 12 | git config --unset commit.template 13 | } 14 | 15 | case "$1" in 16 | install) 17 | install 18 | ;; 19 | uninstall) 20 | uninstall 21 | ;; 22 | *) 23 | echo "Usage: $0 {install|uninstall}" 24 | exit 1 25 | ;; 26 | esac 27 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [licenses] 2 | allow = ["MIT", "Apache-2.0", "Unicode-3.0"] 3 | 4 | [licenses.private] 5 | ignore = true 6 | 7 | [bans] 8 | multiple-versions = "deny" 9 | 10 | [sources] 11 | unknown-registry = "deny" 12 | unknown-git = "deny" 13 | -------------------------------------------------------------------------------- /examples/handling_messages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "handling_messages" 4 | publish = false 5 | version = "0.0.0" 6 | 7 | [dependencies] 8 | midi2 = { path = "../../midi2", default-features = false, features = [ 9 | "channel-voice2", 10 | "sysex7", 11 | ] } 12 | -------------------------------------------------------------------------------- /examples/handling_messages/src/main.rs: -------------------------------------------------------------------------------- 1 | use midi2::prelude::*; 2 | 3 | fn handle_message(buffer: &[u32]) { 4 | match UmpMessage::try_from(buffer) { 5 | Ok(UmpMessage::ChannelVoice2(m)) => { 6 | println!("Channel Voice2: channel: {}", m.channel()); 7 | match m { 8 | channel_voice2::ChannelVoice2::NoteOn(m) => { 9 | println!( 10 | "Note On! note: {}, velocity: {}", 11 | m.note_number(), 12 | m.velocity() 13 | ); 14 | } 15 | channel_voice2::ChannelVoice2::NoteOff(m) => { 16 | println!( 17 | "Note Off! note: {}, velocity: {}", 18 | m.note_number(), 19 | m.velocity() 20 | ); 21 | } 22 | _ => {} 23 | } 24 | } 25 | Ok(UmpMessage::Sysex7(m)) => { 26 | println!( 27 | "Sysex 7bit: payload: {:?}", 28 | m.payload().collect::>() 29 | ); 30 | } 31 | Err(e) => { 32 | println!("Error parsing ump buffer: {:?}", e); 33 | } 34 | _ => {} 35 | } 36 | } 37 | 38 | fn main() { 39 | handle_message(&[0x4898_5E03, 0x6A14_E98A]); // note on 40 | handle_message(&[0x4288_4E01, 0x9DE6_CC6E]); // note off 41 | handle_message(&[0xF000_0101, 0x0000_001F]); // err - ump-stream feature not enabled 42 | handle_message(&[ 43 | 0x3016_0001, 44 | 0x0203_0405, 45 | 0x3026_0607, 46 | 0x0809_0A0B, 47 | 0x3026_0C0D, 48 | 0x0E0F_1011, 49 | 0x3026_1213, 50 | 0x1415_1617, 51 | 0x3036_1819, 52 | 0x1A1B_1C1D, 53 | ]); // sysex7 54 | } 55 | -------------------------------------------------------------------------------- /examples/no_std_dynamic_message_generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "no_std_dynamic_message_generator" 4 | publish = false 5 | version = "0.0.0" 6 | 7 | [dependencies] 8 | midi2 = { path = "../../midi2", default-features = false, features = [ 9 | "channel-voice1", 10 | "sysex7", 11 | ] } 12 | -------------------------------------------------------------------------------- /examples/no_std_dynamic_message_generator/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | extern crate std; 4 | 5 | use midi2::{channel_voice1::*, prelude::*, sysex7::*}; 6 | 7 | #[derive(Clone, Copy, Debug)] 8 | struct Buffer(*const [u8]); 9 | 10 | impl midi2::buffer::Buffer for Buffer { 11 | type Unit = u8; 12 | fn buffer(&self) -> &[Self::Unit] { 13 | unsafe { &*self.0 } 14 | } 15 | } 16 | 17 | impl midi2::buffer::BufferTryResize for Buffer { 18 | fn try_resize(&mut self, new_len: usize) -> Result<(), midi2::error::BufferOverflow> { 19 | if new_len < self.0.len() { 20 | return Err(midi2::error::BufferOverflow); 21 | } 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl midi2::buffer::FromBuffer<&mut [u8]> for Buffer { 27 | fn from_buffer(buffer: &mut [u8]) -> Self { 28 | let ptr: *const u8 = buffer.as_ptr(); 29 | let len = buffer.len(); 30 | Buffer(core::ptr::slice_from_raw_parts(ptr, len)) 31 | } 32 | } 33 | 34 | type Message = BytesMessage; 35 | 36 | struct MessageIterator<'a> { 37 | messages: &'a [Option], 38 | index: usize, 39 | } 40 | 41 | impl Iterator for MessageIterator<'_> { 42 | type Item = Message; 43 | 44 | fn next(&mut self) -> Option { 45 | if self.index < self.messages.len() { 46 | let index = self.index; 47 | self.index += 1; 48 | Some(*self.messages[index].as_ref().unwrap()) 49 | } else { 50 | None 51 | } 52 | } 53 | } 54 | 55 | struct Generator { 56 | buffer: [u8; 128], 57 | messages: [Option; Self::MAX_MESSAGES], 58 | } 59 | 60 | impl Generator { 61 | const MAX_MESSAGES: usize = 16; 62 | 63 | fn new() -> Self { 64 | Self { 65 | buffer: [0x0; 128], 66 | messages: [None; Self::MAX_MESSAGES], 67 | } 68 | } 69 | 70 | fn generate(&mut self) -> Result { 71 | let mut number_of_messages = 0; 72 | let buffer = &mut self.buffer[..]; 73 | 74 | // create some channel voice messages 75 | 76 | let (message_buffer, buffer) = buffer.split_at_mut(2); 77 | let mut channel_pressure = ChannelPressure::try_new_with_buffer(message_buffer)?; 78 | channel_pressure.set_pressure(u7::new(0x50)); 79 | self.messages[number_of_messages] = 80 | Some(ChannelPressure::::rebuffer_from(channel_pressure).into()); 81 | number_of_messages += 1; 82 | 83 | let (message_buffer, buffer) = buffer.split_at_mut(3); 84 | let mut note_on = NoteOn::try_new_with_buffer(message_buffer)?; 85 | note_on.set_note_number(u7::new(0x38)); 86 | note_on.set_velocity(u7::new(0x20)); 87 | self.messages[number_of_messages] = Some(NoteOn::::rebuffer_from(note_on).into()); 88 | number_of_messages += 1; 89 | 90 | let (message_buffer, buffer) = buffer.split_at_mut(3); 91 | let mut control_change = ControlChange::try_new_with_buffer(message_buffer)?; 92 | control_change.set_control(u7::new(0x34)); 93 | control_change.set_control_data(u7::new(0x2A)); 94 | self.messages[number_of_messages] = 95 | Some(ControlChange::::rebuffer_from(control_change).into()); 96 | number_of_messages += 1; 97 | 98 | // and a sysex 99 | 100 | let (message_buffer, _) = buffer.split_at_mut(22); 101 | let mut sysex = Sysex7::try_new_with_buffer(message_buffer)?; 102 | sysex.try_set_payload((0..20).map(u7::new))?; 103 | self.messages[number_of_messages] = Some(Sysex7::::rebuffer_from(sysex).into()); 104 | number_of_messages += 1; 105 | 106 | Ok(MessageIterator { 107 | messages: &self.messages[0..number_of_messages], 108 | index: 0, 109 | }) 110 | } 111 | } 112 | 113 | fn main() { 114 | let mut generator = Generator::new(); 115 | for message in generator.generate().unwrap() { 116 | std::println!("{:?}", message); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "midi2-fuzz" 4 | publish = false 5 | version = "0.0.0" 6 | 7 | [package.metadata] 8 | cargo-fuzz = true 9 | 10 | [dependencies] 11 | libfuzzer-sys = "0.4" 12 | arbitrary = { version = "1.0", features = [ 13 | "derive", 14 | ] } 15 | rand = "0.9.1" 16 | 17 | [dependencies.midi2] 18 | path = "../midi2" 19 | default-features = false 20 | features = [ 21 | "std", 22 | "sysex8", 23 | "sysex7", 24 | ] 25 | 26 | [[bin]] 27 | name = "sysex8_payload_roundtrip" 28 | path = "./fuzz_targets/sysex8_payload_roundtrip.rs" 29 | test = false 30 | doc = false 31 | bench = false 32 | 33 | [[bin]] 34 | name = "sysex7_payload_roundtrip" 35 | path = "./fuzz_targets/sysex7_payload_roundtrip.rs" 36 | test = false 37 | doc = false 38 | bench = false 39 | 40 | [[bin]] 41 | name = "generic_sysex_inserting_payloads" 42 | path = "./fuzz_targets/generic_sysex_inserting_payloads.rs" 43 | test = false 44 | doc = false 45 | bench = false 46 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/generic_sysex_inserting_payloads.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use midi2::Sysex; 5 | use rand::{Rng, SeedableRng}; 6 | 7 | struct FixedSizeBuffer(Vec); 8 | 9 | impl midi2::buffer::Buffer for FixedSizeBuffer { 10 | type Unit = U; 11 | fn buffer(&self) -> &[Self::Unit] { 12 | &self.0 13 | } 14 | } 15 | 16 | impl midi2::buffer::BufferMut for FixedSizeBuffer { 17 | fn buffer_mut(&mut self) -> &mut [Self::Unit] { 18 | &mut self.0 19 | } 20 | } 21 | 22 | impl midi2::buffer::BufferTryResize for FixedSizeBuffer { 23 | fn try_resize(&mut self, new_size: usize) -> Result<(), midi2::error::BufferOverflow> { 24 | if new_size > self.0.len() { 25 | return Err(midi2::error::BufferOverflow); 26 | } 27 | Ok(()) 28 | } 29 | } 30 | 31 | impl FixedSizeBuffer { 32 | fn new(size: usize) -> Self { 33 | Self(std::iter::repeat_n(U::zero(), size).collect()) 34 | } 35 | } 36 | 37 | #[derive(arbitrary::Arbitrary, Debug)] 38 | struct InputData { 39 | seed: u64, 40 | initial_data: Vec, 41 | data_to_insert: Vec, 42 | } 43 | 44 | const MAX_BUFFER_SIZE: usize = 1024; 45 | 46 | trait IntoByte { 47 | fn byte(&self) -> B; 48 | } 49 | 50 | impl IntoByte for u8 { 51 | fn byte(&self) -> midi2::ux::u7 { 52 | midi2::num::u7::new(self & 0x7F) 53 | } 54 | } 55 | 56 | impl IntoByte for u8 { 57 | fn byte(&self) -> u8 { 58 | *self 59 | } 60 | } 61 | 62 | fn test_case(data: &InputData, mut message: M, index: usize) 63 | where 64 | B: midi2::buffer::Buffer + midi2::buffer::BufferTryResize + midi2::buffer::BufferMut, 65 | M: midi2::Sysex, 66 | >::Byte: Eq + core::fmt::Debug, 67 | u8: IntoByte<>::Byte>, 68 | { 69 | let Ok(()) = message.try_set_payload(data.initial_data.iter().map(u8::byte)) else { 70 | return; 71 | }; 72 | 73 | { 74 | let initial = message.payload().collect::>(); 75 | assert_eq!( 76 | initial, 77 | data.initial_data.iter().map(u8::byte).collect::>() 78 | ); 79 | } 80 | 81 | let Ok(()) = message.try_insert_payload(data.data_to_insert.iter().map(u8::byte), index) else { 82 | return; 83 | }; 84 | 85 | let actual = message.payload().collect::>(); 86 | let expected = { 87 | let mut ret = data.initial_data.clone(); 88 | ret.splice(index..index, data.data_to_insert.clone()); 89 | ret.iter().map(u8::byte).collect::>() 90 | }; 91 | assert_eq!(actual, expected); 92 | } 93 | 94 | fuzz_target!(|data: InputData| { 95 | let mut rng = rand::rngs::StdRng::seed_from_u64(data.seed); 96 | let fized_size_buffer_size = rng.random_range(4..MAX_BUFFER_SIZE); 97 | let index = if data.initial_data.is_empty() { 98 | 0 99 | } else { 100 | rng.random_range(0..data.initial_data.len()) 101 | }; 102 | test_case( 103 | &data, 104 | midi2::sysex8::Sysex8::>::try_new_with_buffer( 105 | FixedSizeBuffer::::new(fized_size_buffer_size), 106 | ) 107 | .unwrap(), 108 | index, 109 | ); 110 | test_case( 111 | &data, 112 | midi2::sysex7::Sysex7::>::try_new_with_buffer( 113 | FixedSizeBuffer::::new(fized_size_buffer_size), 114 | ) 115 | .unwrap(), 116 | index, 117 | ); 118 | test_case( 119 | &data, 120 | midi2::sysex7::Sysex7::>::try_new_with_buffer( 121 | FixedSizeBuffer::::new(fized_size_buffer_size), 122 | ) 123 | .unwrap(), 124 | index, 125 | ); 126 | }); 127 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/sysex7_payload_roundtrip.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use midi2::{prelude::*, sysex7::*}; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let to_u7 = |b: u8| u7::new(b & 0x7F); 8 | let mut message = Sysex7::>::new(); 9 | message.set_payload(data.iter().cloned().map(to_u7)); 10 | 11 | // payload is unchanged 12 | let payload = message.payload().collect::>(); 13 | assert_eq!( 14 | payload, 15 | data.iter().cloned().map(to_u7).collect::>() 16 | ); 17 | 18 | // message is in a valid state 19 | let mut buffer = Vec::new(); 20 | buffer.extend_from_slice(message.data()); 21 | let _ = Sysex7::try_from(&buffer[..]).expect("Valid data"); 22 | }); 23 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/sysex8_payload_roundtrip.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use libfuzzer_sys::fuzz_target; 4 | use midi2::{prelude::*, sysex8::*}; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let mut message = Sysex8::>::new(); 8 | message.set_payload(data.iter().cloned()); 9 | 10 | // payload is unchanged 11 | let payload = message.payload().collect::>(); 12 | assert_eq!(payload, data.to_vec()); 13 | 14 | // message is in a valid state 15 | let mut buffer = Vec::new(); 16 | buffer.extend_from_slice(message.data()); 17 | let _ = Sysex8::try_from(&buffer[..]).expect("Valid data"); 18 | }); 19 | -------------------------------------------------------------------------------- /midi2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "midi2" 3 | version = "0.9.0" 4 | description = "Ergonomic, versatile, strong types wrapping MIDI 2.0 message data." 5 | edition = "2021" 6 | readme = "README.md" 7 | license = "MIT OR Apache-2.0" 8 | authors = [ 9 | "Ben Leadbetter ", 10 | ] 11 | repository = "https://github.com/midi2-dev/bl-midi2-rs.git" 12 | 13 | [lints.clippy] 14 | # packet indexing causes false positives 15 | manual_div_ceil = "allow" 16 | 17 | [dependencies] 18 | derive_more = { version = "2.0.1", features = ["from"], default-features = false } 19 | fixed = "1.28.0" 20 | midi2_proc = { version = "0.9.0", path = "../midi2_proc" } 21 | ux = "0.1.6" 22 | 23 | [dev-dependencies] 24 | pretty_assertions = "1.4.0" 25 | static_assertions = "1.1.0" 26 | 27 | [package.metadata.docs.rs] 28 | all-features = true 29 | rustdoc-args = ["--cfg", "docsrs"] 30 | 31 | [features] 32 | default = ["std", "channel-voice2"] 33 | # wip 34 | ci = ["sysex7"] 35 | flex-data = [] 36 | channel-voice1 = [] 37 | channel-voice2 = [] 38 | std = [] 39 | sysex7 = [] 40 | sysex8 = [] 41 | system-common = [] 42 | ump-stream = [] 43 | utility = [] 44 | -------------------------------------------------------------------------------- /midi2/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /midi2/src/channel_voice1/README.md: -------------------------------------------------------------------------------- 1 | MIDI 1.0 Channel Voice Messages 2 | 3 | ## Basic Usage 4 | 5 | ```rust 6 | use midi2::{ 7 | prelude::*, 8 | channel_voice1::ControlChange, 9 | }; 10 | 11 | let mut message = ControlChange::<[u32; 4]>::new(); 12 | message.set_channel(u4::new(0xA)); 13 | message.set_group(u4::new(0xC)); 14 | message.set_control(u7::new(0x36)); 15 | message.set_control_data(u7::new(0x37)); 16 | 17 | assert_eq!(message.data(), &[0x2CBA_3637]); 18 | assert_eq!(message.channel(), u4::new(0xA)); 19 | assert_eq!(message.group(), u4::new(0xC)); 20 | assert_eq!(message.control(), u7::new(0x36)); 21 | assert_eq!(message.control_data(), u7::new(0x37)); 22 | ``` 23 | 24 | ## Channeled 25 | 26 | `channel_voice1` messages are [Channeled](crate::Channeled). 27 | 28 | ## Grouped 29 | 30 | `channel_voice1` messages are [Grouped](crate::Grouped) 31 | when backed with [Ump](crate::buffer::Ump) buffers. 32 | 33 | ## Aggregate Message 34 | 35 | There is a single aggregate [ChannelVoice1] enum type which 36 | can represent an arbitrary `channel_voice1` message. 37 | 38 | ```rust 39 | use midi2::{ 40 | prelude::*, 41 | channel_voice1::ChannelVoice1, 42 | }; 43 | 44 | let mut message = ChannelVoice1::try_from(&[0x2CBA_3637_u32][..]).expect("Valid data"); 45 | 46 | match message { 47 | ChannelVoice1::ChannelPressure(m) => println!("channel_pressure {:?}", m.data()), 48 | ChannelVoice1::ControlChange(m) => println!("control_change {:?}", m.data()), 49 | ChannelVoice1::KeyPressure(m) => println!("key_pressure {:?}", m.data()), 50 | ChannelVoice1::NoteOff(m) => println!("note_off {:?}", m.data()), 51 | ChannelVoice1::NoteOn(m) => println!("note_on {:?}", m.data()), 52 | ChannelVoice1::PitchBend(m) => println!("pitch_bend {:?}", m.data()), 53 | ChannelVoice1::ProgramChange(m) => println!("program_change {:?}", m.data()), 54 | } 55 | ``` 56 | 57 | ## Generic Over [Unit](crate::buffer::Unit) 58 | 59 | `channel_voice1` messages can also be represented with [Bytes](crate::buffer::Bytes) buffers 60 | as well as [Ump](crate::buffer::Ump) buffers. 61 | 62 | ```rust 63 | use midi2::{ 64 | prelude::*, 65 | channel_voice1::ControlChange, 66 | }; 67 | 68 | let mut message = ControlChange::<[u8; 3]>::new(); 69 | message.set_channel(u4::new(0xA)); 70 | message.set_control(u7::new(0x36)); 71 | message.set_control_data(u7::new(0x37)); 72 | 73 | assert_eq!(message.data(), &[0xBA, 0x36, 0x37]); 74 | ``` 75 | 76 | ## Fixed Size 77 | 78 | All `channel_voice1` messages are Fixed size. 79 | 80 | ```rust 81 | use midi2::channel_voice1::KeyPressure; 82 | 83 | 84 | // All channel_voice1 bytes-backed messages fit into a `[u8; 3]` 85 | let _ = KeyPressure::<[u8; 3]>::new(); 86 | 87 | // All channel_voice1 ump-backed messages fit into a `[u32; 1]` 88 | let _ = KeyPressure::<[u32; 1]>::new(); 89 | ``` 90 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/control_change.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice1::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | }; 5 | 6 | pub(crate) const STATUS: u8 = 0b1011; 7 | 8 | /// MIDI 1.0 Channel Voice Control Change Message 9 | /// 10 | /// See the [module docs](crate::channel_voice1) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(crate::channel_voice1::ChannelVoice1), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(3) 16 | )] 17 | struct ControlChange { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(common_properties::ChannelVoiceStatusProperty)] 21 | status: (), 22 | #[property(common_properties::ChannelProperty)] 23 | channel: crate::ux::u4, 24 | #[property(common_properties::GroupProperty)] 25 | group: crate::ux::u4, 26 | #[property(common_properties::HybridSchemaProperty< 27 | crate::ux::u7, 28 | schema::Bytes<0x00, 0x7F, 0x0>, 29 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 30 | >)] 31 | control: crate::ux::u7, 32 | #[property(common_properties::HybridSchemaProperty< 33 | crate::ux::u7, 34 | schema::Bytes<0x00, 0x0, 0x7F>, 35 | schema::Ump<0x0000_007F, 0x0, 0x0, 0x0>, 36 | >)] 37 | control_data: crate::ux::u7, 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use crate::{ux::*, Channeled, Grouped, Packets}; 44 | use pretty_assertions::assert_eq; 45 | 46 | #[test] 47 | fn setters() { 48 | let mut message = ControlChange::<[u32; 4]>::new(); 49 | message.set_group(u4::new(0xA)); 50 | message.set_channel(u4::new(0x7)); 51 | message.set_control(u7::new(0x36)); 52 | message.set_control_data(u7::new(0x37)); 53 | assert_eq!(message, ControlChange([0x2AB7_3637, 0x0, 0x0, 0x0])); 54 | } 55 | 56 | #[test] 57 | fn setters_bytes() { 58 | let mut message = ControlChange::<[u8; 3]>::new(); 59 | message.set_channel(u4::new(0x7)); 60 | message.set_control(u7::new(0x36)); 61 | message.set_control_data(u7::new(0x37)); 62 | assert_eq!(message, ControlChange([0xB7, 0x36, 0x37])); 63 | } 64 | 65 | #[test] 66 | fn group() { 67 | assert_eq!( 68 | ControlChange::try_from(&[0x2AB7_3637_u32][..]) 69 | .unwrap() 70 | .group(), 71 | u4::new(0xA), 72 | ); 73 | } 74 | 75 | #[test] 76 | fn channel() { 77 | assert_eq!( 78 | ControlChange::try_from(&[0x2AB7_3637_u32][..]) 79 | .unwrap() 80 | .channel(), 81 | u4::new(0x7), 82 | ); 83 | } 84 | 85 | #[test] 86 | fn channel_bytes() { 87 | assert_eq!( 88 | ControlChange::try_from(&[0xB7_u8, 0x36_u8, 0x37_u8][..]) 89 | .unwrap() 90 | .channel(), 91 | u4::new(0x7), 92 | ); 93 | } 94 | 95 | #[test] 96 | fn control() { 97 | assert_eq!( 98 | ControlChange::try_from(&[0x2AB7_3637_u32][..]) 99 | .unwrap() 100 | .control(), 101 | u7::new(0x36), 102 | ); 103 | } 104 | 105 | #[test] 106 | fn control_bytes() { 107 | assert_eq!( 108 | ControlChange::try_from(&[0xB7_u8, 0x36_u8, 0x37_u8][..]) 109 | .unwrap() 110 | .control(), 111 | u7::new(0x36), 112 | ); 113 | } 114 | 115 | #[test] 116 | fn control_data() { 117 | assert_eq!( 118 | ControlChange::try_from(&[0x2AB7_3637_u32][..]) 119 | .unwrap() 120 | .control_data(), 121 | u7::new(0x37), 122 | ); 123 | } 124 | 125 | #[test] 126 | fn control_data_bytes() { 127 | assert_eq!( 128 | ControlChange::try_from(&[0xB7_u8, 0x36_u8, 0x37_u8][..]) 129 | .unwrap() 130 | .control_data(), 131 | u7::new(0x37), 132 | ); 133 | } 134 | 135 | #[test] 136 | fn packets() { 137 | let buffer = [0x2AB7_3637_u32]; 138 | let message = ControlChange::try_from(&buffer[..]).unwrap(); 139 | let mut packets = message.packets(); 140 | assert_eq!(&*packets.next().unwrap(), &[0x2AB7_3637_u32][..]); 141 | assert_eq!(packets.next(), None); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/key_pressure.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice1::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | }; 5 | 6 | pub(crate) const STATUS: u8 = 0b1010; 7 | 8 | /// MIDI 1.0 Channel Voice Key Pressure Message 9 | /// 10 | /// See the [module docs](crate::channel_voice1) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(crate::channel_voice1::ChannelVoice1), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(3) 16 | )] 17 | struct KeyPressure { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(common_properties::ChannelVoiceStatusProperty)] 21 | status: (), 22 | #[property(common_properties::ChannelProperty)] 23 | channel: crate::ux::u4, 24 | #[property(common_properties::GroupProperty)] 25 | group: crate::ux::u4, 26 | #[property(common_properties::HybridSchemaProperty< 27 | crate::ux::u7, 28 | schema::Bytes<0x00, 0x7F, 0x0>, 29 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 30 | >)] 31 | note_number: crate::ux::u7, 32 | #[property(common_properties::HybridSchemaProperty< 33 | crate::ux::u7, 34 | schema::Bytes<0x00, 0x0, 0x7F>, 35 | schema::Ump<0x0000_007F, 0x0, 0x0, 0x0>, 36 | >)] 37 | pressure: crate::ux::u7, 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use crate::{ 44 | traits::{Channeled, Grouped}, 45 | ux::*, 46 | }; 47 | use pretty_assertions::assert_eq; 48 | 49 | #[test] 50 | fn setters() { 51 | let mut message = KeyPressure::<[u32; 4]>::new(); 52 | message.set_group(u4::new(0xA)); 53 | message.set_channel(u4::new(0x3)); 54 | message.set_note_number(u7::new(0x7F)); 55 | message.set_pressure(u7::new(0x5C)); 56 | assert_eq!(message, KeyPressure([0x2AA3_7F5C, 0x0, 0x0, 0x0])); 57 | } 58 | 59 | #[test] 60 | fn group() { 61 | assert_eq!( 62 | KeyPressure::try_from(&[0x2AA3_7F5C_u32][..]) 63 | .unwrap() 64 | .group(), 65 | u4::new(0xA), 66 | ); 67 | } 68 | 69 | #[test] 70 | fn channel() { 71 | assert_eq!( 72 | KeyPressure::try_from(&[0x2AA3_7F5C_u32][..]) 73 | .unwrap() 74 | .channel(), 75 | u4::new(0x3), 76 | ); 77 | } 78 | 79 | #[test] 80 | fn note_number() { 81 | assert_eq!( 82 | KeyPressure::try_from(&[0x2AA3_7F5C_u32][..]) 83 | .unwrap() 84 | .note_number(), 85 | u7::new(0x7F), 86 | ); 87 | } 88 | 89 | #[test] 90 | fn pressure() { 91 | assert_eq!( 92 | KeyPressure::try_from(&[0x2AA3_7F5C_u32][..]) 93 | .unwrap() 94 | .pressure(), 95 | u7::new(0x5C), 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/note_off.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice1::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | }; 5 | 6 | pub(crate) const STATUS: u8 = 0b1000; 7 | 8 | /// MIDI 1.0 Channel Voice Note Off Message 9 | /// 10 | /// See the [module docs](crate::channel_voice1) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(crate::channel_voice1::ChannelVoice1), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(3) 16 | )] 17 | struct NoteOff { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(common_properties::ChannelVoiceStatusProperty)] 21 | status: (), 22 | #[property(common_properties::ChannelProperty)] 23 | channel: crate::ux::u4, 24 | #[property(common_properties::GroupProperty)] 25 | group: crate::ux::u4, 26 | #[property(common_properties::HybridSchemaProperty< 27 | crate::ux::u7, 28 | schema::Bytes<0x00, 0x7F, 0x0>, 29 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 30 | >)] 31 | note_number: crate::ux::u7, 32 | #[property(common_properties::HybridSchemaProperty< 33 | crate::ux::u7, 34 | schema::Bytes<0x00, 0x0, 0x7F>, 35 | schema::Ump<0x0000_007F, 0x0, 0x0, 0x0>, 36 | >)] 37 | velocity: crate::ux::u7, 38 | } 39 | 40 | // #[midi2_proc::generate_message(Grouped, Channeled)] 41 | // struct NoteOff { 42 | // ump_type: Property< 43 | // NumericalConstant, 44 | // UmpSchema<0xF000_0000, 0x0, 0x0, 0x0>, 45 | // (), 46 | // >, 47 | // status: Property< 48 | // NumericalConstant, 49 | // UmpSchema<0x00F0_0000, 0x0, 0x0, 0x0>, 50 | // BytesSchema<0xF0, 0x0, 0x0>, 51 | // >, 52 | // channel: Property, BytesSchema<0x0F, 0x0, 0x0>>, 53 | // note_number: Property, BytesSchema<0x0, 0x7F, 0x0>>, 54 | // velocity: Property, BytesSchema<0x0, 0x0, 0x7F>>, 55 | // } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::*; 60 | use crate::{ 61 | traits::{Channeled, Grouped}, 62 | ux::*, 63 | }; 64 | use pretty_assertions::assert_eq; 65 | 66 | #[test] 67 | fn builder() { 68 | let mut message = NoteOff::<[u32; 4]>::new(); 69 | message.set_group(u4::new(0x1)); 70 | message.set_channel(u4::new(0xA)); 71 | message.set_note_number(u7::new(0x68)); 72 | message.set_velocity(u7::new(0x1B)); 73 | assert_eq!(message, NoteOff([0x218A_681B, 0x0, 0x0, 0x0])); 74 | } 75 | 76 | #[test] 77 | fn group() { 78 | assert_eq!( 79 | NoteOff::try_from(&[0x218A_681B_u32][..]).unwrap().group(), 80 | u4::new(0x1), 81 | ); 82 | } 83 | 84 | #[test] 85 | fn channel() { 86 | assert_eq!( 87 | NoteOff::try_from(&[0x218A_681B_u32][..]).unwrap().channel(), 88 | u4::new(0xA), 89 | ); 90 | } 91 | 92 | #[test] 93 | fn note_number() { 94 | assert_eq!( 95 | NoteOff::try_from(&[0x218A_681B_u32][..]) 96 | .unwrap() 97 | .note_number(), 98 | u7::new(0x68), 99 | ); 100 | } 101 | 102 | #[test] 103 | fn velocity() { 104 | assert_eq!( 105 | NoteOff::try_from(&[0x218A_681B_u32][..]) 106 | .unwrap() 107 | .velocity(), 108 | u7::new(0x1B), 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/note_on.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice1::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | }; 5 | 6 | pub(crate) const STATUS: u8 = 0b1001; 7 | 8 | /// MIDI 1.0 Channel Voice Note On Message 9 | /// 10 | /// See the [module docs](crate::channel_voice1) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(crate::channel_voice1::ChannelVoice1), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(3) 16 | )] 17 | struct NoteOn { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(common_properties::ChannelVoiceStatusProperty)] 21 | status: (), 22 | #[property(common_properties::ChannelProperty)] 23 | channel: crate::ux::u4, 24 | #[property(common_properties::GroupProperty)] 25 | group: crate::ux::u4, 26 | #[property(common_properties::HybridSchemaProperty< 27 | crate::ux::u7, 28 | schema::Bytes<0x00, 0x7F, 0x0>, 29 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 30 | >)] 31 | note_number: crate::ux::u7, 32 | #[property(common_properties::HybridSchemaProperty< 33 | crate::ux::u7, 34 | schema::Bytes<0x00, 0x0, 0x7F>, 35 | schema::Ump<0x0000_007F, 0x0, 0x0, 0x0>, 36 | >)] 37 | velocity: crate::ux::u7, 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use crate::{ 44 | traits::{Channeled, Grouped}, 45 | ux::*, 46 | }; 47 | use pretty_assertions::assert_eq; 48 | 49 | static_assertions::assert_impl_all!(NoteOn<&[u32]>: Clone); 50 | static_assertions::assert_impl_all!(NoteOn<&[u32]>: Copy); 51 | 52 | #[test] 53 | fn setters() { 54 | let mut message = NoteOn::<[u32; 4]>::new(); 55 | message.set_group(u4::new(0xD)); 56 | message.set_channel(u4::new(0xE)); 57 | message.set_note_number(u7::new(0x75)); 58 | message.set_velocity(u7::new(0x3D)); 59 | assert_eq!(message, NoteOn([0x2D9E_753D, 0x0, 0x0, 0x0])); 60 | } 61 | 62 | #[test] 63 | fn setters_bytes() { 64 | let mut message = NoteOn::<[u8; 3]>::new(); 65 | 66 | message.set_channel(u4::new(0xE)); 67 | message.set_note_number(u7::new(0x75)); 68 | message.set_velocity(u7::new(0x3D)); 69 | 70 | assert_eq!(message, NoteOn([0x9E, 0x75, 0x3D])); 71 | } 72 | 73 | #[test] 74 | fn group() { 75 | assert_eq!( 76 | NoteOn::try_from(&[0x2D9E_753D_u32][..]).unwrap().group(), 77 | u4::new(0xD), 78 | ); 79 | } 80 | 81 | #[test] 82 | fn channel() { 83 | assert_eq!( 84 | NoteOn::try_from(&[0x2D9E_753D_u32][..]).unwrap().channel(), 85 | u4::new(0xE), 86 | ); 87 | } 88 | 89 | #[test] 90 | fn note() { 91 | assert_eq!( 92 | NoteOn::try_from(&[0x2D9E_753D_u32][..]) 93 | .unwrap() 94 | .note_number(), 95 | u7::new(0x75), 96 | ); 97 | } 98 | 99 | #[test] 100 | fn velocity() { 101 | assert_eq!( 102 | NoteOn::try_from(&[0x2D9E_753D_u32][..]).unwrap().velocity(), 103 | u7::new(0x3D), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{channel_voice1, error}; 2 | 3 | #[derive(Eq, PartialEq, Clone, midi2_proc::Debug)] 4 | pub struct Packet(pub [u32; 1]); 5 | 6 | impl crate::traits::BufferAccess<[u32; 1]> for Packet { 7 | fn buffer_access(&self) -> &[u32; 1] { 8 | &self.0 9 | } 10 | fn buffer_access_mut(&mut self) -> &mut [u32; 1] 11 | where 12 | [u32; 1]: crate::buffer::BufferMut, 13 | { 14 | &mut self.0 15 | } 16 | } 17 | 18 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 19 | type Error = error::InvalidData; 20 | fn try_from(data: &'a [u32]) -> Result { 21 | if data.is_empty() { 22 | return Err(error::InvalidData( 23 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT, 24 | )); 25 | } 26 | 27 | use crate::detail::BitOps; 28 | if u8::from(data[0].nibble(0)) != channel_voice1::UMP_MESSAGE_TYPE { 29 | return Err(error::InvalidData( 30 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 31 | )); 32 | } 33 | 34 | Ok(Packet({ 35 | let mut buffer = [0x0; 1]; 36 | buffer[0] = data[0]; 37 | buffer 38 | })) 39 | } 40 | } 41 | 42 | impl core::ops::Deref for Packet { 43 | type Target = [u32]; 44 | fn deref(&self) -> &Self::Target { 45 | &self.0[..] 46 | } 47 | } 48 | 49 | impl crate::Grouped<[u32; 1]> for Packet { 50 | fn group(&self) -> crate::ux::u4 { 51 | use crate::detail::BitOps; 52 | self.0[0].nibble(1) 53 | } 54 | fn set_group(&mut self, group: crate::ux::u4) 55 | where 56 | [u32; 1]: crate::buffer::BufferMut, 57 | { 58 | use crate::detail::BitOps; 59 | self.0[0].set_nibble(1, group); 60 | } 61 | } 62 | 63 | impl crate::Channeled<[u32; 1]> for Packet { 64 | fn channel(&self) -> crate::ux::u4 { 65 | use crate::detail::BitOps; 66 | self.0[0].nibble(3) 67 | } 68 | fn set_channel(&mut self, channel: crate::ux::u4) 69 | where 70 | [u32; 1]: crate::buffer::BufferMut, 71 | { 72 | use crate::detail::BitOps; 73 | self.0[0].set_nibble(3, channel); 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use pretty_assertions::assert_eq; 81 | 82 | #[test] 83 | fn construction() { 84 | assert!(Packet::try_from(&[0x2000_0000][..]).is_ok()); 85 | } 86 | 87 | #[test] 88 | fn construction_long_slice() { 89 | assert!(Packet::try_from(&[0x2000_0000, 0x0, 0x0, 0x0][..]).is_ok()); 90 | } 91 | 92 | #[test] 93 | fn construction_very_long_slice() { 94 | assert!(Packet::try_from(&[0x2000_0000, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0][..]).is_ok()); 95 | } 96 | 97 | #[test] 98 | fn construction_incorrect_ump_message_type() { 99 | assert_eq!( 100 | Packet::try_from(&[0x0000_0000][..]), 101 | Err(error::InvalidData( 102 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 103 | )), 104 | ); 105 | } 106 | 107 | #[test] 108 | fn channel() { 109 | use crate::Channeled; 110 | assert_eq!( 111 | Packet::try_from(&[0x2008_0000][..]).unwrap().channel(), 112 | ux::u4::new(0x8) 113 | ); 114 | } 115 | 116 | #[test] 117 | fn set_channel() { 118 | use crate::Channeled; 119 | let mut packet = Packet::try_from(&[0x2000_0000][..]).unwrap(); 120 | packet.set_channel(ux::u4::new(0x8)); 121 | assert_eq!(&*packet, &[0x2008_0000][..]); 122 | } 123 | 124 | #[test] 125 | fn group() { 126 | use crate::Grouped; 127 | assert_eq!( 128 | Packet::try_from(&[0x2A00_0000][..]).unwrap().group(), 129 | ux::u4::new(0xA) 130 | ); 131 | } 132 | 133 | #[test] 134 | fn set_group() { 135 | use crate::Grouped; 136 | let mut packet = Packet::try_from(&[0x2000_0000][..]).unwrap(); 137 | packet.set_group(ux::u4::new(0xA)); 138 | assert_eq!(&*packet, &[0x2A00_0000][..]); 139 | } 140 | 141 | #[test] 142 | fn construction_short_slice() { 143 | assert_eq!( 144 | Packet::try_from(&[][..]), 145 | Err(error::InvalidData( 146 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT 147 | )), 148 | ); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/pitch_bend.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice1::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | }; 5 | 6 | pub(crate) const STATUS: u8 = 0b1110; 7 | 8 | /// MIDI 1.0 Channel Voice Pitch Bend Message 9 | /// 10 | /// See the [module docs](crate::channel_voice1) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(crate::channel_voice1::ChannelVoice1), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(3) 16 | )] 17 | struct PitchBend { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(common_properties::ChannelVoiceStatusProperty)] 21 | status: (), 22 | #[property(common_properties::ChannelProperty)] 23 | channel: crate::ux::u4, 24 | #[property(common_properties::GroupProperty)] 25 | group: crate::ux::u4, 26 | #[property(common_properties::HybridSchemaProperty< 27 | crate::ux::u14, 28 | schema::Bytes<0x00, 0x7F, 0x7F>, 29 | schema::Ump<0x0000_7F7F, 0x0, 0x0, 0x0>, 30 | >)] 31 | bend: crate::ux::u14, 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use crate::{ 38 | traits::{Channeled, Grouped}, 39 | ux::*, 40 | }; 41 | use pretty_assertions::assert_eq; 42 | 43 | #[test] 44 | fn builder() { 45 | let mut message = PitchBend::<[u32; 4]>::new(); 46 | message.set_group(u4::new(0x1)); 47 | message.set_channel(u4::new(0xE)); 48 | message.set_bend(u14::new(0x147)); 49 | assert_eq!(message, PitchBend([0x21EE_4702, 0x0, 0x0, 0x0])); 50 | } 51 | 52 | #[test] 53 | fn group() { 54 | assert_eq!( 55 | PitchBend::try_from(&[0x21EE_4702_u32][..]).unwrap().group(), 56 | u4::new(0x1), 57 | ); 58 | } 59 | 60 | #[test] 61 | fn channel() { 62 | assert_eq!( 63 | PitchBend::try_from(&[0x21EE_4702_u32][..]) 64 | .unwrap() 65 | .channel(), 66 | u4::new(0xE), 67 | ); 68 | } 69 | 70 | #[test] 71 | fn bend() { 72 | assert_eq!( 73 | PitchBend::try_from(&[0x21EE_4702_u32][..]).unwrap().bend(), 74 | u14::new(0x147) 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /midi2/src/channel_voice1/program_change.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice1::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | }; 5 | 6 | pub(crate) const STATUS: u8 = 0b1100; 7 | 8 | /// MIDI 1.0 Channel Voice Program Change Message 9 | /// 10 | /// See the [module docs](crate::channel_voice1) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(crate::channel_voice1::ChannelVoice1), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(2) 16 | )] 17 | struct ProgramChange { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(common_properties::ChannelVoiceStatusProperty)] 21 | status: (), 22 | #[property(common_properties::ChannelProperty)] 23 | channel: crate::ux::u4, 24 | #[property(common_properties::GroupProperty)] 25 | group: crate::ux::u4, 26 | #[property(common_properties::HybridSchemaProperty< 27 | crate::ux::u7, 28 | schema::Bytes<0x00, 0x7F, 0x0>, 29 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 30 | >)] 31 | program: crate::ux::u7, 32 | } 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use crate::{ 38 | traits::{Channeled, Grouped}, 39 | ux::*, 40 | }; 41 | use pretty_assertions::assert_eq; 42 | 43 | #[test] 44 | fn builder() { 45 | let mut message = ProgramChange::<[u32; 4]>::new(); 46 | message.set_group(u4::new(0x4)); 47 | message.set_channel(u4::new(0x7)); 48 | message.set_program(u7::new(0x63)); 49 | assert_eq!(message, ProgramChange([0x24C7_6300, 0x0, 0x0, 0x0])); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/README.md: -------------------------------------------------------------------------------- 1 | MIDI 2.0 Channel Voice Messages 2 | 3 | ## Basic Usage 4 | 5 | ```rust 6 | use midi2::{ 7 | prelude::*, 8 | num::Fixed7_9, 9 | channel_voice2::{NoteAttribute, NoteOn}, 10 | }; 11 | 12 | let mut message = NoteOn::<[u32; 4]>::new(); 13 | message.set_group(u4::new(0x8)); 14 | message.set_channel(u4::new(0x8)); 15 | message.set_note_number(u7::new(0x5E)); 16 | message.set_velocity(0x6A14); 17 | message.set_attribute(Some(NoteAttribute::Pitch7_9(Fixed7_9::from_num(70.52)))); 18 | 19 | assert_eq!(message.data(), &[0x4898_5E03, 0x6A14_8D0A]); 20 | assert_eq!(message.group(), u4::new(0x8)); 21 | assert_eq!(message.channel(), u4::new(0x8)); 22 | assert_eq!(message.note_number(), u7::new(0x5E)); 23 | assert_eq!(message.velocity(), 0x6A14); 24 | assert_eq!( 25 | message.attribute(), 26 | Some(NoteAttribute::Pitch7_9(Fixed7_9::from_num(70.52))) 27 | ); 28 | ``` 29 | 30 | ## Channeled 31 | 32 | `channel_voice2` messages are [Channeled](crate::Channeled). 33 | 34 | ## Grouped 35 | 36 | `channel_voice2` messages are [Grouped](crate::Grouped). 37 | 38 | ## Aggregate Message 39 | 40 | There is a single aggregate [ChannelVoice2] enum type which 41 | can represent an arbitrary `channel_voice2` message. 42 | 43 | ```rust 44 | use midi2::{ 45 | prelude::*, 46 | channel_voice2::ChannelVoice2, 47 | }; 48 | 49 | let mut message = ChannelVoice2::try_from(&[0x4898_5E03, 0x6A14_E98A][..]).expect("Valid data"); 50 | 51 | match message { 52 | ChannelVoice2::AssignableController(m) => println!("assignable_controller {:?}", m.data()), 53 | ChannelVoice2::AssignablePerNoteController(m) => println!("assignable_per_note_controller {:?}", m.data()), 54 | ChannelVoice2::ChannelPitchBend(m) => println!("channel_pitch_bend {:?}", m.data()), 55 | ChannelVoice2::ChannelPressure(m) => println!("channel_pressure {:?}", m.data()), 56 | ChannelVoice2::ControlChange(m) => println!("control_change {:?}", m.data()), 57 | ChannelVoice2::KeyPressure(m) => println!("key_pressure {:?}", m.data()), 58 | ChannelVoice2::NoteOff(m) => println!("note_off {:?}", m.data()), 59 | ChannelVoice2::NoteOn(m) => println!("note_on {:?}", m.data()), 60 | ChannelVoice2::PerNoteManagement(m) => println!("per_note_management {:?}", m.data()), 61 | ChannelVoice2::PerNotePitchBend(m) => println!("per_note_pitch_bend {:?}", m.data()), 62 | ChannelVoice2::ProgramChange(m) => println!("program_change {:?}", m.data()), 63 | ChannelVoice2::RegisteredController(m) => println!("registered_controller {:?}", m.data()), 64 | ChannelVoice2::RegisteredPerNoteController(m) => println!("registered_per_note_controller {:?}", m.data()), 65 | ChannelVoice2::RelativeAssignableController(m) => println!("relative_assignable_controller {:?}", m.data()), 66 | ChannelVoice2::RelativeRegisteredController(m) => println!("relative_registered_controller {:?}", m.data()), 67 | } 68 | ``` 69 | 70 | ## Fixed Size 71 | 72 | All `channel_voice1` messages are Fixed size and will fit 73 | into an array of [u32] size 2 or greater. 74 | 75 | ```rust 76 | use midi2::channel_voice2::NoteOn; 77 | 78 | let _ = NoteOn::<[u32; 2]>::new(); 79 | let _ = NoteOn::<[u32; 4]>::new(); 80 | ``` 81 | 82 | Arrays smaller than two are invalid backing buffers. 83 | 84 | ```rust,compile_fail,E0080 85 | use midi2::channel_voice2::NoteOn; 86 | 87 | let _ = NoteOn::<[u32; 1]>::new(); // compile err - buffer too short 88 | ``` 89 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/assignable_controller.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0011; 8 | 9 | /// MIDI 2.0 Channel Voice Assignable Controller Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct AssignableController { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | bank: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | index: u7, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | controller_data: u32, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn setters() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = AssignableController::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0xC)); 41 | message.set_channel(u4::new(0x8)); 42 | message.set_bank(u7::new(0x51)); 43 | message.set_index(u7::new(0x38)); 44 | message.set_controller_data(0x3F3ADD42); 45 | assert_eq!( 46 | message, 47 | AssignableController([0x4C38_5138, 0x3F3ADD42, 0x0, 0x0]), 48 | ); 49 | } 50 | 51 | #[test] 52 | fn group() { 53 | use crate::traits::Grouped; 54 | 55 | assert_eq!( 56 | AssignableController::try_from(&[0x4C38_5138, 0x3F3ADD42][..]) 57 | .unwrap() 58 | .group(), 59 | u4::new(0xC), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn channel() { 65 | use crate::traits::Channeled; 66 | 67 | assert_eq!( 68 | AssignableController::try_from(&[0x4C38_5138, 0x3F3ADD42][..]) 69 | .unwrap() 70 | .channel(), 71 | u4::new(0x8), 72 | ); 73 | } 74 | 75 | #[test] 76 | pub fn bank() { 77 | assert_eq!( 78 | AssignableController::try_from(&[0x4C38_5138, 0x3F3ADD42][..]) 79 | .unwrap() 80 | .bank(), 81 | u7::new(0x51), 82 | ); 83 | } 84 | 85 | #[test] 86 | pub fn index() { 87 | assert_eq!( 88 | AssignableController::try_from(&[0x4C38_5138, 0x3F3ADD42][..]) 89 | .unwrap() 90 | .index(), 91 | u7::new(0x38), 92 | ); 93 | } 94 | 95 | #[test] 96 | pub fn controller_data() { 97 | assert_eq!( 98 | AssignableController::try_from(&[0x4C38_5138, 0x3F3ADD42][..]) 99 | .unwrap() 100 | .controller_data(), 101 | 0x3F3ADD42, 102 | ); 103 | } 104 | 105 | #[test] 106 | fn packets() { 107 | use crate::Packets; 108 | 109 | let buffer = [0x4C38_5138, 0x3F3ADD42]; 110 | let message = AssignableController::try_from(&buffer[..]).unwrap(); 111 | let mut packets = message.packets(); 112 | assert_eq!(&*packets.next().unwrap(), &[0x4C38_5138, 0x3F3ADD42][..]); 113 | assert_eq!(packets.next(), None); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/assignable_per_note_controller.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0001; 8 | 9 | /// MIDI 2.0 Channel Voice Assignable Per Note Controller Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct AssignablePerNoteController { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | note_number: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | index: u8, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | controller_data: u32, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn builder() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = AssignablePerNoteController::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0x2)); 41 | message.set_channel(u4::new(0x4)); 42 | message.set_note_number(u7::new(0x6F)); 43 | message.set_index(0xB1); 44 | message.set_controller_data(0x46105EE5); 45 | 46 | assert_eq!( 47 | message, 48 | AssignablePerNoteController([0x4214_6FB1, 0x46105EE5, 0x0, 0x0,]), 49 | ); 50 | } 51 | 52 | #[test] 53 | fn group() { 54 | use crate::traits::Grouped; 55 | assert_eq!( 56 | AssignablePerNoteController::try_from(&[0x4214_6FB1, 0x46105EE5][..]) 57 | .unwrap() 58 | .group(), 59 | u4::new(0x2), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn channel() { 65 | use crate::traits::Channeled; 66 | assert_eq!( 67 | AssignablePerNoteController::try_from(&[0x4214_6FB1, 0x46105EE5][..]) 68 | .unwrap() 69 | .channel(), 70 | u4::new(0x4), 71 | ); 72 | } 73 | 74 | #[test] 75 | fn note_number() { 76 | assert_eq!( 77 | AssignablePerNoteController::try_from(&[0x4214_6FB1, 0x46105EE5][..]) 78 | .unwrap() 79 | .note_number(), 80 | u7::new(0x6F), 81 | ); 82 | } 83 | 84 | #[test] 85 | fn index() { 86 | assert_eq!( 87 | AssignablePerNoteController::try_from(&[0x4214_6FB1, 0x46105EE5][..]) 88 | .unwrap() 89 | .index(), 90 | 0xB1, 91 | ); 92 | } 93 | 94 | #[test] 95 | fn controller_data() { 96 | assert_eq!( 97 | AssignablePerNoteController::try_from(&[0x4214_6FB1, 0x46105EE5][..]) 98 | .unwrap() 99 | .controller_data(), 100 | 0x46105EE5, 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/attribute.rs: -------------------------------------------------------------------------------- 1 | use crate::detail::{property, BitOps}; 2 | 3 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 4 | #[non_exhaustive] 5 | pub enum Attribute { 6 | ManufacturerSpecific(u16), 7 | ProfileSpecific(u16), 8 | Pitch7_9(crate::num::Fixed7_9), 9 | } 10 | 11 | const ERR_INVALID_NOTE_ATTRIBUTE: &str = "Couldn't interpret note attribute"; 12 | 13 | pub fn validate_ump(bytes: &[u32]) -> Result<(), crate::error::InvalidData> { 14 | match bytes[0].octet(3) { 15 | 0x0 => Ok(()), 16 | 0x1 => Ok(()), 17 | 0x2 => Ok(()), 18 | 0x3 => Ok(()), 19 | _ => Err(crate::error::InvalidData(ERR_INVALID_NOTE_ATTRIBUTE)), 20 | } 21 | } 22 | 23 | pub fn from_ump(buffer: &[u32]) -> Option { 24 | match buffer[0].octet(3) { 25 | 0x0 => None, 26 | 0x1 => Some(Attribute::ManufacturerSpecific(buffer[1].word(1))), 27 | 0x2 => Some(Attribute::ProfileSpecific(buffer[1].word(1))), 28 | 0x3 => Some(Attribute::Pitch7_9(crate::num::Fixed7_9::from_bits( 29 | buffer[1].word(1), 30 | ))), 31 | _ => panic!("Invalid status"), 32 | } 33 | } 34 | 35 | pub fn write_attribute(bytes: &mut [u32], attr: Option) -> &mut [u32] { 36 | match attr { 37 | None => { 38 | bytes[0].set_octet(3, 0x0); 39 | } 40 | Some(a) => match a { 41 | Attribute::ManufacturerSpecific(d) => { 42 | bytes[0].set_octet(3, 0x1); 43 | bytes[1].set_word(1, d); 44 | } 45 | Attribute::ProfileSpecific(d) => { 46 | bytes[0].set_octet(3, 0x2); 47 | bytes[1].set_word(1, d); 48 | } 49 | Attribute::Pitch7_9(fixed) => { 50 | bytes[0].set_octet(3, 0x3); 51 | bytes[1].set_word(1, fixed.to_bits()); 52 | } 53 | }, 54 | } 55 | &mut bytes[..2] 56 | } 57 | 58 | pub struct AttributeProperty; 59 | 60 | impl property::Property for AttributeProperty { 61 | type Type = Option; 62 | } 63 | 64 | impl<'a, B: crate::buffer::Ump> property::ReadProperty<'a, B> for AttributeProperty { 65 | fn read(buffer: &'a B) -> Self::Type { 66 | from_ump(buffer.buffer()) 67 | } 68 | fn validate(buffer: &B) -> Result<(), crate::error::InvalidData> { 69 | validate_ump(buffer.buffer()) 70 | } 71 | } 72 | 73 | impl property::WriteProperty 74 | for AttributeProperty 75 | { 76 | fn validate(_v: &Self::Type) -> Result<(), crate::error::InvalidData> { 77 | Ok(()) 78 | } 79 | fn write(buffer: &mut B, v: Self::Type) { 80 | write_attribute(buffer.buffer_mut(), v); 81 | } 82 | fn default() -> Self::Type { 83 | Default::default() 84 | } 85 | } 86 | 87 | // impl Property, UmpSchema<0x0000_00FF, 0x0000_FFFF, 0x0, 0x0>, ()> for Ump { 88 | // fn get(data: &[::Data]) -> Option { 89 | // } 90 | // fn write(data: &mut [::Data], v: Option) { 91 | // } 92 | // fn validate(data: &[::Data]) -> Result<()> { 93 | // } 94 | // } 95 | 96 | #[cfg(test)] 97 | mod tests { 98 | use super::*; 99 | 100 | fn try_from_ump(bytes: &[u32]) -> Result, crate::error::InvalidData> { 101 | validate_ump(bytes)?; 102 | Ok(from_ump(bytes)) 103 | } 104 | 105 | #[test] 106 | fn from_packet_invalid() { 107 | assert_eq!( 108 | try_from_ump(&[0x0000_0004]), 109 | Err(crate::error::InvalidData(ERR_INVALID_NOTE_ATTRIBUTE)), 110 | ); 111 | } 112 | 113 | #[test] 114 | fn from_packet_none() { 115 | assert_eq!(try_from_ump(&[0x0, 0x0]), Ok(None),); 116 | } 117 | 118 | #[test] 119 | fn from_packet_generic() { 120 | assert_eq!( 121 | try_from_ump(&[0x0000_0001, 0x0000_1234]), 122 | Ok(Some(Attribute::ManufacturerSpecific(0x1234))), 123 | ); 124 | } 125 | 126 | #[test] 127 | fn from_packet_pitch7_9() { 128 | assert_eq!( 129 | try_from_ump(&[0x0000_0003, 0b0000_0000_0000_0000_0011_0011_0011_0011]), 130 | Ok(Some(Attribute::Pitch7_9(crate::num::Fixed7_9::from_bits( 131 | 0b0011_0011_0011_0011 132 | )))), 133 | ); 134 | } 135 | 136 | #[test] 137 | fn write_attribute_none() { 138 | assert_eq!(write_attribute(&mut [0x0, 0x0], None), &[0x0, 0x0],); 139 | } 140 | 141 | #[test] 142 | fn write_attribute_generic() { 143 | assert_eq!( 144 | write_attribute(&mut [0x0, 0x0], Some(Attribute::ProfileSpecific(0x0666))), 145 | &[0x0000_0002, 0x0000_0666], 146 | ); 147 | } 148 | 149 | #[test] 150 | fn write_attribute_pitch7_9() { 151 | let attribute = Attribute::Pitch7_9(crate::num::Fixed7_9::from_bits(0b1011_1001_0001_0111)); 152 | assert_eq!( 153 | write_attribute(&mut [0x0, 0x0], Some(attribute)), 154 | &[0x0000_0003, 0b0000_0000_0000_0000_1011_1001_0001_0111] 155 | ); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/channel_pitch_bend.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::u4, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b1110; 8 | 9 | /// MIDI 2.0 Channel Voice Channel Pitch Bend Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct ChannelPitchBend { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | pitch_bend_data: u32, 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | use pretty_assertions::assert_eq; 30 | 31 | #[test] 32 | fn builder() { 33 | use crate::traits::{Channeled, Grouped}; 34 | 35 | let mut message = ChannelPitchBend::<[u32; 4]>::new(); 36 | message.set_group(u4::new(0xB)); 37 | message.set_channel(u4::new(0x9)); 38 | message.set_pitch_bend_data(0x08306AF8); 39 | 40 | assert_eq!( 41 | message, 42 | ChannelPitchBend([0x4BE9_0000, 0x0830_6AF8, 0x0, 0x0]), 43 | ); 44 | } 45 | 46 | #[test] 47 | fn pitch_bend_data() { 48 | assert_eq!( 49 | ChannelPitchBend::try_from(&[0x4BE9_0000, 0x0830_6AF8][..]) 50 | .unwrap() 51 | .pitch_bend_data(), 52 | 0x0830_6AF8, 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/channel_pressure.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b1101; 8 | 9 | /// MIDI 2.0 Channel Voice Channel Pressure Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct ChannelPressure { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | bank: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | index: u7, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | channel_pressure_data: u32, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn setter() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = ChannelPressure::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0xE)); 41 | message.set_channel(u4::new(0xD)); 42 | message.set_channel_pressure_data(0xDE0DE0F2); 43 | 44 | assert_eq!( 45 | message, 46 | ChannelPressure([0x4EDD_0000, 0xDE0D_E0F2, 0x0, 0x0]), 47 | ); 48 | } 49 | 50 | #[test] 51 | fn new_arr_3() { 52 | assert_eq!( 53 | ChannelPressure::<[u32; 3]>::new(), 54 | ChannelPressure([0x40D0_0000, 0x0, 0x0]), 55 | ); 56 | } 57 | 58 | #[test] 59 | fn new_arr_2() { 60 | assert_eq!( 61 | ChannelPressure::<[u32; 2]>::new(), 62 | ChannelPressure([0x40D0_0000, 0x0]), 63 | ); 64 | } 65 | 66 | #[test] 67 | fn channel_pressure_data() { 68 | assert_eq!( 69 | ChannelPressure::try_from(&[0x4EDD_0000, 0xDE0D_E0F2][..]) 70 | .unwrap() 71 | .channel_pressure_data(), 72 | 0xDE0DE0F2, 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/control_change.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b1011; 8 | 9 | /// MIDI 2.0 Channel Voice Control Change Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct ControlChange { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | control: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | control_change_data: u32, 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use pretty_assertions::assert_eq; 32 | 33 | #[test] 34 | fn setters() { 35 | use crate::traits::{Channeled, Grouped}; 36 | let mut message = ControlChange::<[u32; 4]>::new(); 37 | message.set_group(u4::new(0x3)); 38 | message.set_channel(u4::new(0x9)); 39 | message.set_control(u7::new(0x30)); 40 | message.set_control_change_data(0x2468_1012); 41 | 42 | assert_eq!(message, ControlChange([0x43B9_3000, 0x2468_1012, 0x0, 0x0])); 43 | } 44 | 45 | #[test] 46 | fn control() { 47 | assert_eq!( 48 | ControlChange::try_from(&[0x43B9_3000, 0x2468_1012][..]) 49 | .unwrap() 50 | .control(), 51 | u7::new(0x30), 52 | ); 53 | } 54 | 55 | #[test] 56 | fn control_change_data() { 57 | assert_eq!( 58 | ControlChange::try_from(&[0x43B9_3000, 0x2468_1012][..]) 59 | .unwrap() 60 | .control_change_data(), 61 | 0x2468_1012, 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/key_pressure.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b1010; 8 | 9 | /// MIDI 2.0 Channel Voice Key Pressure Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct KeyPressure { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | note_number: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | key_pressure_data: u32, 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use pretty_assertions::assert_eq; 32 | 33 | #[test] 34 | fn builder() { 35 | use crate::traits::{Channeled, Grouped}; 36 | 37 | let mut message = KeyPressure::<[u32; 4]>::new(); 38 | message.set_group(u4::new(0xB)); 39 | message.set_channel(u4::new(0xC)); 40 | message.set_note_number(u7::new(0x59)); 41 | message.set_key_pressure_data(0xC0B83064); 42 | assert_eq!(message, KeyPressure([0x4BAC_5900, 0xC0B83064, 0x0, 0x0]),); 43 | } 44 | 45 | #[test] 46 | fn note_number() { 47 | assert_eq!( 48 | KeyPressure::try_from(&[0x4BAC_5900, 0xC0B83064][..]) 49 | .unwrap() 50 | .note_number(), 51 | u7::new(0x59), 52 | ); 53 | } 54 | 55 | #[test] 56 | fn key_pressure_data() { 57 | assert_eq!( 58 | KeyPressure::try_from(&[0x4BAC_5900, 0xC0B83064][..]) 59 | .unwrap() 60 | .key_pressure_data(), 61 | 0xC0B83064, 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/note_off.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::{ 3 | attribute::{Attribute, AttributeProperty}, 4 | UMP_MESSAGE_TYPE, 5 | }, 6 | detail::{common_properties, schema}, 7 | ux::{u4, u7}, 8 | }; 9 | 10 | pub(crate) const STATUS: u8 = 0b1000; 11 | 12 | /// MIDI 2.0 Channel Voice Note Off Message 13 | /// 14 | /// See the [module docs](crate::channel_voice2) for more info. 15 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 16 | struct NoteOff { 17 | #[property(common_properties::UmpMessageTypeProperty)] 18 | ump_type: (), 19 | #[property(common_properties::ChannelVoiceStatusProperty)] 20 | status: (), 21 | #[property(common_properties::UmpSchemaProperty>)] 22 | channel: u4, 23 | #[property(common_properties::GroupProperty)] 24 | group: u4, 25 | #[property(common_properties::UmpSchemaProperty>)] 26 | note_number: u7, 27 | #[property(common_properties::UmpSchemaProperty>)] 28 | velocity: u16, 29 | #[property(AttributeProperty)] 30 | attribute: Option, 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | use pretty_assertions::assert_eq; 37 | 38 | #[test] 39 | fn builder() { 40 | use crate::traits::{Channeled, Grouped}; 41 | 42 | let mut message = NoteOff::<[u32; 4]>::new(); 43 | message.set_group(u4::new(0x2)); 44 | message.set_channel(u4::new(0x4)); 45 | message.set_note_number(u7::new(0x4E)); 46 | message.set_velocity(0x9DE6); 47 | message.set_attribute(Some(Attribute::ManufacturerSpecific(0xCC6E))); 48 | 49 | assert_eq!(message, NoteOff([0x4284_4E01, 0x9DE6_CC6E, 0x0, 0x0]),); 50 | } 51 | 52 | #[test] 53 | fn builder_no_attribute() { 54 | use crate::traits::{Channeled, Grouped}; 55 | 56 | let mut message = NoteOff::<[u32; 4]>::new(); 57 | message.set_group(u4::new(0x2)); 58 | message.set_channel(u4::new(0x4)); 59 | message.set_note_number(u7::new(0x4E)); 60 | message.set_velocity(0x9DE6); 61 | 62 | assert_eq!(message, NoteOff([0x4284_4E00, 0x9DE6_0000, 0x0, 0x0]),); 63 | } 64 | 65 | #[test] 66 | fn note_number() { 67 | assert_eq!( 68 | NoteOff::try_from(&[0x4284_4E01, 0x9DE6_CC6E][..]) 69 | .unwrap() 70 | .note_number(), 71 | u7::new(0x4E), 72 | ); 73 | } 74 | 75 | #[test] 76 | fn volocity() { 77 | assert_eq!( 78 | NoteOff::try_from(&[0x4284_4E01, 0x9DE6_CC6E][..]) 79 | .unwrap() 80 | .velocity(), 81 | 0x9DE6, 82 | ); 83 | } 84 | 85 | #[test] 86 | fn attribute() { 87 | assert_eq!( 88 | NoteOff::try_from(&[0x4284_4E01, 0x9DE6_CC6E][..]) 89 | .unwrap() 90 | .attribute(), 91 | Some(Attribute::ManufacturerSpecific(0xCC6E)), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/note_on.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::{ 3 | attribute::{Attribute, AttributeProperty}, 4 | UMP_MESSAGE_TYPE, 5 | }, 6 | detail::{common_properties, schema}, 7 | ux::{u4, u7}, 8 | }; 9 | 10 | pub(crate) const STATUS: u8 = 0b1001; 11 | 12 | /// MIDI 2.0 Channel Voice Note On Message 13 | /// 14 | /// See the [module docs](crate::channel_voice2) for more info. 15 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 16 | struct NoteOn { 17 | #[property(common_properties::UmpMessageTypeProperty)] 18 | ump_type: (), 19 | #[property(common_properties::ChannelVoiceStatusProperty)] 20 | status: (), 21 | #[property(common_properties::UmpSchemaProperty>)] 22 | channel: u4, 23 | #[property(common_properties::GroupProperty)] 24 | group: u4, 25 | #[property(common_properties::UmpSchemaProperty>)] 26 | note_number: u7, 27 | #[property(common_properties::UmpSchemaProperty>)] 28 | velocity: u16, 29 | #[property(AttributeProperty)] 30 | attribute: Option, 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | use pretty_assertions::assert_eq; 37 | 38 | #[test] 39 | fn builder() { 40 | use crate::num::Fixed7_9; 41 | use crate::traits::{Channeled, Grouped}; 42 | 43 | let mut message = NoteOn::<[u32; 4]>::new(); 44 | message.set_group(u4::new(0x8)); 45 | message.set_channel(u4::new(0x8)); 46 | message.set_note_number(u7::new(0x5E)); 47 | message.set_velocity(0x6A14); 48 | message.set_attribute(Some(Attribute::Pitch7_9(Fixed7_9::from_bits( 49 | 0b1110100110001010, 50 | )))); 51 | 52 | assert_eq!(message, NoteOn([0x4898_5E03, 0x6A14_E98A, 0x0, 0x0]),); 53 | } 54 | 55 | #[test] 56 | fn builder_no_attribute() { 57 | use crate::traits::{Channeled, Grouped}; 58 | 59 | let mut message = NoteOn::<[u32; 4]>::new(); 60 | message.set_group(u4::new(0x8)); 61 | message.set_channel(u4::new(0x8)); 62 | message.set_note_number(u7::new(0x5E)); 63 | message.set_velocity(0x6A14); 64 | 65 | assert_eq!(message, NoteOn([0x4898_5E00, 0x6A14_0000, 0x0, 0x0]),); 66 | } 67 | 68 | #[test] 69 | fn note_number() { 70 | assert_eq!( 71 | NoteOn::try_from(&[0x4898_5E03, 0x6A14_E98A][..]) 72 | .unwrap() 73 | .note_number(), 74 | u7::new(0x5E), 75 | ); 76 | } 77 | 78 | #[test] 79 | fn volocity() { 80 | assert_eq!( 81 | NoteOn::try_from(&[0x4898_5E03, 0x6A14_E98A][..]) 82 | .unwrap() 83 | .velocity(), 84 | 0x6A14, 85 | ); 86 | } 87 | 88 | #[test] 89 | fn attribute() { 90 | use crate::num::Fixed7_9; 91 | 92 | assert_eq!( 93 | NoteOn::try_from(&[0x4898_5E03, 0x6A14_E98A][..]) 94 | .unwrap() 95 | .attribute(), 96 | Some(Attribute::Pitch7_9(Fixed7_9::from_bits(0b1110100110001010))), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{channel_voice2, error}; 2 | 3 | #[derive(Eq, PartialEq, Clone, midi2_proc::Debug)] 4 | pub struct Packet(pub(crate) [u32; 2]); 5 | 6 | impl crate::traits::BufferAccess<[u32; 2]> for Packet { 7 | fn buffer_access(&self) -> &[u32; 2] { 8 | &self.0 9 | } 10 | fn buffer_access_mut(&mut self) -> &mut [u32; 2] 11 | where 12 | [u32; 2]: crate::buffer::BufferMut, 13 | { 14 | &mut self.0 15 | } 16 | } 17 | 18 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 19 | type Error = error::InvalidData; 20 | fn try_from(data: &'a [u32]) -> Result { 21 | if data.is_empty() { 22 | return Err(error::InvalidData( 23 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT, 24 | )); 25 | } 26 | 27 | use crate::detail::BitOps; 28 | if u8::from(data[0].nibble(0)) != channel_voice2::UMP_MESSAGE_TYPE { 29 | return Err(error::InvalidData( 30 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 31 | )); 32 | } 33 | 34 | Ok(Packet({ 35 | let mut buffer = [0x0; 2]; 36 | let sz = 2.min(data.len()); 37 | buffer[..sz].copy_from_slice(&data[..sz]); 38 | buffer 39 | })) 40 | } 41 | } 42 | 43 | impl core::ops::Deref for Packet { 44 | type Target = [u32]; 45 | fn deref(&self) -> &Self::Target { 46 | &self.0[..] 47 | } 48 | } 49 | 50 | impl crate::Grouped<[u32; 2]> for Packet { 51 | fn group(&self) -> crate::ux::u4 { 52 | use crate::detail::BitOps; 53 | self.0[0].nibble(1) 54 | } 55 | fn set_group(&mut self, group: crate::ux::u4) 56 | where 57 | [u32; 2]: crate::buffer::BufferMut, 58 | { 59 | use crate::detail::BitOps; 60 | self.0[0].set_nibble(1, group); 61 | } 62 | } 63 | 64 | impl crate::Channeled<[u32; 2]> for Packet { 65 | fn channel(&self) -> crate::ux::u4 { 66 | use crate::detail::BitOps; 67 | self.0[0].nibble(3) 68 | } 69 | fn set_channel(&mut self, channel: crate::ux::u4) 70 | where 71 | [u32; 2]: crate::buffer::BufferMut, 72 | { 73 | use crate::detail::BitOps; 74 | self.0[0].set_nibble(3, channel); 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | 82 | #[test] 83 | fn construction() { 84 | assert!(Packet::try_from(&[0x4000_0000][..]).is_ok()); 85 | } 86 | 87 | #[test] 88 | fn construction_slice_size2() { 89 | assert!(Packet::try_from(&[0x4000_0000, 0x0][..]).is_ok()); 90 | } 91 | 92 | #[test] 93 | fn construction_slice_size4() { 94 | assert!(Packet::try_from(&[0x4000_0000, 0x0, 0x0, 0x0][..]).is_ok()); 95 | } 96 | 97 | #[test] 98 | fn construction_incorrect_ump_message_type() { 99 | assert_eq!( 100 | Packet::try_from(&[0x0000_0000][..]), 101 | Err(error::InvalidData( 102 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 103 | )), 104 | ); 105 | } 106 | 107 | #[test] 108 | fn construction_short_slice() { 109 | assert_eq!( 110 | Packet::try_from(&[][..]), 111 | Err(error::InvalidData( 112 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT 113 | )), 114 | ); 115 | } 116 | 117 | #[test] 118 | fn channel() { 119 | use crate::Channeled; 120 | assert_eq!( 121 | Packet::try_from(&[0x4008_0000][..]).unwrap().channel(), 122 | ux::u4::new(0x8) 123 | ); 124 | } 125 | 126 | #[test] 127 | fn set_channel() { 128 | use crate::Channeled; 129 | let mut packet = Packet::try_from(&[0x4000_0000][..]).unwrap(); 130 | packet.set_channel(ux::u4::new(0x8)); 131 | assert_eq!(&*packet, &[0x4008_0000, 0x0][..]); 132 | } 133 | 134 | #[test] 135 | fn group() { 136 | use crate::Grouped; 137 | assert_eq!( 138 | Packet::try_from(&[0x4A00_0000][..]).unwrap().group(), 139 | ux::u4::new(0xA) 140 | ); 141 | } 142 | 143 | #[test] 144 | fn set_group() { 145 | use crate::Grouped; 146 | let mut packet = Packet::try_from(&[0x4000_0000][..]).unwrap(); 147 | packet.set_group(ux::u4::new(0xA)); 148 | assert_eq!(&*packet, &[0x4A00_0000, 0x0][..]); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/per_note_management.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b1111; 8 | 9 | /// MIDI 2.0 Channel Voice Per Note Management Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(1))] 13 | struct PerNoteManagement { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | note_number: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | detach: bool, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | reset: bool, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn setters() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = PerNoteManagement::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0xB)); 41 | message.set_channel(u4::new(0x9)); 42 | message.set_note_number(u7::new(0x1C)); 43 | message.set_detach(true); 44 | message.set_reset(true); 45 | 46 | assert_eq!(message, PerNoteManagement([0x4BF9_1C03, 0x0, 0x0, 0x0]),); 47 | } 48 | 49 | #[test] 50 | fn note_number() { 51 | assert_eq!( 52 | PerNoteManagement::try_from(&[0x4BF9_1C03][..]) 53 | .unwrap() 54 | .note_number(), 55 | u7::new(0x1C), 56 | ); 57 | } 58 | 59 | #[test] 60 | fn detach() { 61 | assert!(PerNoteManagement::try_from(&[0x4BF9_1C03][..]) 62 | .unwrap() 63 | .detach(),); 64 | } 65 | 66 | #[test] 67 | fn reset() { 68 | assert!(PerNoteManagement::try_from(&[0x4BF9_1C03][..]) 69 | .unwrap() 70 | .reset(),); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/per_note_pitch_bend.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0110; 8 | 9 | /// MIDI 2.0 Channel Voice Per Note Pitch Bend Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct PerNotePitchBend { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | note_number: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | pitch_bend_data: u32, 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use pretty_assertions::assert_eq; 32 | 33 | #[test] 34 | fn builder() { 35 | use crate::traits::{Channeled, Grouped}; 36 | 37 | let mut message = PerNotePitchBend::<[u32; 4]>::new(); 38 | message.set_group(u4::new(0x9)); 39 | message.set_channel(u4::new(0x2)); 40 | message.set_note_number(u7::new(0x76)); 41 | message.set_pitch_bend_data(0x2AD74672); 42 | 43 | assert_eq!( 44 | message, 45 | PerNotePitchBend([0x4962_7600, 0x2AD74672, 0x0, 0x0]), 46 | ); 47 | } 48 | 49 | #[test] 50 | fn note_number() { 51 | assert_eq!( 52 | PerNotePitchBend::try_from(&[0x4962_7600, 0x2AD74672][..]) 53 | .unwrap() 54 | .note_number(), 55 | u7::new(0x76), 56 | ); 57 | } 58 | 59 | #[test] 60 | fn pitch_bend_data() { 61 | assert_eq!( 62 | PerNotePitchBend::try_from(&[0x4962_7600, 0x2AD74672][..]) 63 | .unwrap() 64 | .pitch_bend_data(), 65 | 0x2AD74672, 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/program_change.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, property, schema}, 4 | ux::{u14, u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b1100; 8 | 9 | /// MIDI 2.0 Channel Voice Program Change Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct ProgramChange { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | program: u7, 24 | #[property(BankProperty)] 25 | bank: Option, 26 | } 27 | 28 | struct BankProperty; 29 | 30 | impl property::Property for BankProperty { 31 | type Type = Option; 32 | } 33 | 34 | impl<'a, B: crate::buffer::Ump> property::ReadProperty<'a, B> for BankProperty { 35 | fn read(buffer: &'a B) -> Self::Type { 36 | use crate::detail::{BitOps, Encode7Bit}; 37 | 38 | let data = buffer.buffer(); 39 | if data[0].bit(31) { 40 | Some(u14::from_u7s(&[data[1].octet(2), data[1].octet(3)])) 41 | } else { 42 | None 43 | } 44 | } 45 | fn validate(_buffer: &B) -> Result<(), crate::error::InvalidData> { 46 | Ok(()) 47 | } 48 | } 49 | 50 | impl property::WriteProperty for BankProperty { 51 | fn validate(_v: &Self::Type) -> Result<(), crate::error::InvalidData> { 52 | Ok(()) 53 | } 54 | fn write(buffer: &mut B, v: Self::Type) { 55 | use crate::detail::{BitOps, Encode7Bit}; 56 | 57 | let data = buffer.buffer_mut(); 58 | match v { 59 | Some(v) => { 60 | let mut u7s = [u7::default(); 2]; 61 | v.to_u7s(&mut u7s); 62 | data[1].set_octet(2, u7s[0].into()); 63 | data[1].set_octet(3, u7s[1].into()); 64 | data[0].set_bit(31, true); 65 | } 66 | None => { 67 | data[0].set_bit(31, false); 68 | data[1].set_word(1, 0x0); 69 | } 70 | } 71 | } 72 | fn default() -> Self::Type { 73 | None 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | use pretty_assertions::assert_eq; 81 | 82 | #[test] 83 | fn builder() { 84 | use crate::traits::{Channeled, Grouped}; 85 | 86 | let mut message = ProgramChange::<[u32; 4]>::new(); 87 | message.set_group(u4::new(0xF)); 88 | message.set_channel(u4::new(0xE)); 89 | message.set_program(u7::new(0x75)); 90 | message.set_bank(Some(u14::new(0x1F5E))); 91 | 92 | assert_eq!(message, ProgramChange([0x4FCE_0001, 0x7500_5E3E, 0x0, 0x0]),); 93 | } 94 | 95 | #[test] 96 | fn builder_no_bank() { 97 | use crate::traits::{Channeled, Grouped}; 98 | 99 | let mut message = ProgramChange::<[u32; 4]>::new(); 100 | message.set_group(u4::new(0xF)); 101 | message.set_channel(u4::new(0xE)); 102 | message.set_program(u7::new(0x75)); 103 | 104 | assert_eq!(message, ProgramChange([0x4FCE_0000, 0x7500_0000, 0x0, 0x0]),); 105 | } 106 | 107 | #[test] 108 | fn program() { 109 | assert_eq!( 110 | ProgramChange::try_from(&[0x4FCE_0001, 0x7500_5E3E][..]) 111 | .unwrap() 112 | .program(), 113 | u7::new(0x75), 114 | ) 115 | } 116 | 117 | #[test] 118 | fn bank() { 119 | assert_eq!( 120 | ProgramChange::try_from(&[0x4FCE_0001, 0x7500_5E3E][..]) 121 | .unwrap() 122 | .bank(), 123 | Some(u14::new(0x1F5E)), 124 | ) 125 | } 126 | 127 | #[test] 128 | fn no_bank() { 129 | assert_eq!( 130 | ProgramChange::try_from(&[0x4FCE_0000, 0x7500_0000][..]) 131 | .unwrap() 132 | .bank(), 133 | None, 134 | ) 135 | } 136 | 137 | #[test] 138 | fn rebuffer_mut_slice_to_slice() { 139 | use crate::RebufferInto; 140 | let mut buffer = [0x0; 4]; 141 | let mut mut_slice_message = ProgramChange::try_new_with_buffer(&mut buffer[..]).unwrap(); 142 | mut_slice_message.set_program(u7::new(0x4F)); 143 | let slice_message: ProgramChange<&[u32]> = mut_slice_message.rebuffer_into(); 144 | assert_eq!(slice_message.data(), &[0x40C0_0000, 0x4F00_0000][..]); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/registered_controller.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0010; 8 | 9 | /// MIDI 2.0 Channel Voice Registered Controller Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct RegisteredController { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | bank: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | index: u7, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | controller_data: u32, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn builder() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = RegisteredController::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0xA)); 41 | message.set_channel(u4::new(0xB)); 42 | message.set_bank(u7::new(0x7D)); 43 | message.set_index(u7::new(0x64)); 44 | message.set_controller_data(0x46845E00); 45 | 46 | assert_eq!( 47 | message, 48 | RegisteredController([0x4A2B_7D64, 0x46845E00, 0x0, 0x0]), 49 | ); 50 | } 51 | 52 | #[test] 53 | pub fn bank() { 54 | assert_eq!( 55 | RegisteredController::try_from(&[0x4A2B_7D64, 0x46845E00][..]) 56 | .unwrap() 57 | .bank(), 58 | u7::new(0x7D), 59 | ); 60 | } 61 | 62 | #[test] 63 | pub fn index() { 64 | assert_eq!( 65 | RegisteredController::try_from(&[0x4A2B_7D64, 0x46845E00][..]) 66 | .unwrap() 67 | .index(), 68 | u7::new(0x64), 69 | ); 70 | } 71 | 72 | #[test] 73 | pub fn controller_data() { 74 | assert_eq!( 75 | RegisteredController::try_from(&[0x4A2B_7D64, 0x46845E00][..]) 76 | .unwrap() 77 | .controller_data(), 78 | 0x46845E00, 79 | ); 80 | } 81 | 82 | #[test] 83 | pub fn data() { 84 | assert_eq!( 85 | RegisteredController::try_from(&[0x4A2B_7D64, 0x46845E00][..]) 86 | .unwrap() 87 | .data(), 88 | &[0x4A2B_7D64, 0x46845E00], 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/registered_per_note_controller.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::{controller, UMP_MESSAGE_TYPE}, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0000; 8 | 9 | /// MIDI 2.0 Channel Voice Registered Per Note Controller Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct RegisteredPerNoteController { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | note_number: u7, 24 | #[property(controller::ControllerProperty)] 25 | controller: controller::Controller, 26 | } 27 | 28 | #[cfg(test)] 29 | mod tests { 30 | use super::*; 31 | use pretty_assertions::assert_eq; 32 | 33 | #[test] 34 | fn builder() { 35 | use crate::traits::{Channeled, Grouped}; 36 | 37 | let mut message = RegisteredPerNoteController::<[u32; 4]>::new(); 38 | message.set_group(u4::new(0x4)); 39 | message.set_channel(u4::new(0x5)); 40 | message.set_note_number(u7::new(0x6C)); 41 | message.set_controller(controller::Controller::Volume(0xE1E35E92)); 42 | 43 | assert_eq!( 44 | message, 45 | RegisteredPerNoteController([0x4405_6C07, 0xE1E35E92, 0x0, 0x0,]), 46 | ); 47 | } 48 | 49 | #[test] 50 | fn note_number() { 51 | assert_eq!( 52 | RegisteredPerNoteController::try_from(&[0x4405_6C07, 0xE1E35E92][..]) 53 | .unwrap() 54 | .note_number(), 55 | u7::new(0x6C), 56 | ); 57 | } 58 | 59 | #[test] 60 | fn controller() { 61 | assert_eq!( 62 | RegisteredPerNoteController::try_from(&[0x4405_6C07, 0xE1E35E92][..]) 63 | .unwrap() 64 | .controller(), 65 | controller::Controller::Volume(0xE1E35E92), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/relative_assignable_controller.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0101; 8 | 9 | /// MIDI 2.0 Channel Voice Relative Assignable Controller Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct RelativeAssignableController { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | bank: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | index: u7, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | controller_data: u32, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn builder() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = RelativeAssignableController::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0x3)); 41 | message.set_channel(u4::new(0x1)); 42 | message.set_bank(u7::new(0x24)); 43 | message.set_index(u7::new(0x52)); 44 | message.set_controller_data(0x898874E4); 45 | 46 | assert_eq!( 47 | message, 48 | RelativeAssignableController([0x4351_2452, 0x898874E4, 0x0, 0x0,]), 49 | ); 50 | } 51 | 52 | #[test] 53 | pub fn bank() { 54 | assert_eq!( 55 | RelativeAssignableController::try_from(&[0x4351_2452, 0x898874E4][..]) 56 | .unwrap() 57 | .bank(), 58 | u7::new(0x24), 59 | ); 60 | } 61 | 62 | #[test] 63 | pub fn index() { 64 | assert_eq!( 65 | RelativeAssignableController::try_from(&[0x4351_2452, 0x898874E4][..]) 66 | .unwrap() 67 | .index(), 68 | u7::new(0x52), 69 | ); 70 | } 71 | 72 | #[test] 73 | pub fn controller_data() { 74 | assert_eq!( 75 | RelativeAssignableController::try_from(&[0x4351_2452, 0x898874E4][..]) 76 | .unwrap() 77 | .controller_data(), 78 | 0x898874E4, 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /midi2/src/channel_voice2/relative_registered_controller.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | channel_voice2::UMP_MESSAGE_TYPE, 3 | detail::{common_properties, schema}, 4 | ux::{u4, u7}, 5 | }; 6 | 7 | pub(crate) const STATUS: u8 = 0b0100; 8 | 9 | /// MIDI 2.0 Channel Voice Relative Registered Controller Message 10 | /// 11 | /// See the [module docs](crate::channel_voice2) for more info. 12 | #[midi2_proc::generate_message(Via(crate::channel_voice2::ChannelVoice2), FixedSize, MinSizeUmp(2))] 13 | struct RelativeRegisteredController { 14 | #[property(common_properties::UmpMessageTypeProperty)] 15 | ump_type: (), 16 | #[property(common_properties::ChannelVoiceStatusProperty)] 17 | status: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | channel: u4, 20 | #[property(common_properties::GroupProperty)] 21 | group: u4, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | bank: u7, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | index: u7, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | controller_data: u32, 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | use super::*; 33 | use pretty_assertions::assert_eq; 34 | 35 | #[test] 36 | fn builder() { 37 | use crate::traits::{Channeled, Grouped}; 38 | 39 | let mut message = RelativeRegisteredController::<[u32; 4]>::new(); 40 | message.set_group(u4::new(0x1)); 41 | message.set_channel(u4::new(0xE)); 42 | message.set_bank(u7::new(0x45)); 43 | message.set_index(u7::new(0x02)); 44 | message.set_controller_data(0xAF525908); 45 | 46 | assert_eq!( 47 | message, 48 | RelativeRegisteredController([0x414E_4502, 0xAF525908, 0x0, 0x0,]), 49 | ); 50 | } 51 | 52 | #[test] 53 | pub fn bank() { 54 | assert_eq!( 55 | RelativeRegisteredController::try_from(&[0x414E_4502, 0xAF525908][..]) 56 | .unwrap() 57 | .bank(), 58 | u7::new(0x45), 59 | ); 60 | } 61 | 62 | #[test] 63 | pub fn index() { 64 | assert_eq!( 65 | RelativeRegisteredController::try_from(&[0x414E_4502, 0xAF525908][..]) 66 | .unwrap() 67 | .index(), 68 | u7::new(0x02), 69 | ); 70 | } 71 | 72 | #[test] 73 | pub fn controller_data() { 74 | assert_eq!( 75 | RelativeRegisteredController::try_from(&[0x414E_4502, 0xAF525908][..]) 76 | .unwrap() 77 | .controller_data(), 78 | 0xAF525908, 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /midi2/src/ci.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("ci/README.md")] 2 | 3 | mod common_properties; 4 | mod device_id; 5 | mod discovery; 6 | mod version; 7 | 8 | pub use device_id::*; 9 | pub use discovery::*; 10 | pub use version::*; 11 | 12 | pub trait Ci { 13 | fn device_id(&self) -> device_id::DeviceId 14 | where 15 | Self: version::CiVersion<0x1>; 16 | fn source(&self) -> ux::u28 17 | where 18 | Self: version::CiVersion<0x1>; 19 | fn destination(&self) -> ux::u28 20 | where 21 | Self: version::CiVersion<0x1>; 22 | } 23 | -------------------------------------------------------------------------------- /midi2/src/ci/README.md: -------------------------------------------------------------------------------- 1 | # MIDI 2.0 Compatibility Inquiry 2 | 3 | 🚧 WIP 🚧 4 | -------------------------------------------------------------------------------- /midi2/src/ci/device_id.rs: -------------------------------------------------------------------------------- 1 | use ux::u4; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 4 | pub enum DeviceId { 5 | Channel(u4), 6 | Group, 7 | FunctionBlock, 8 | } 9 | 10 | impl core::default::Default for DeviceId { 11 | fn default() -> Self { 12 | Self::Channel(Default::default()) 13 | } 14 | } 15 | 16 | impl DeviceId { 17 | pub(crate) fn from_u8(v: u8) -> Result { 18 | if v == 0x7F { 19 | Ok(DeviceId::FunctionBlock) 20 | } else if v == 0x7E { 21 | Ok(DeviceId::Group) 22 | } else if v < 0x0F { 23 | Ok(DeviceId::Channel(v.try_into().unwrap())) 24 | } else { 25 | Err(crate::error::InvalidData( 26 | "Couldn't interpreset Device ID field", 27 | )) 28 | } 29 | } 30 | pub(crate) fn to_u8(self) -> u8 { 31 | match self { 32 | DeviceId::Group => 0x7E, 33 | DeviceId::FunctionBlock => 0x7F, 34 | DeviceId::Channel(c) => c.into(), 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /midi2/src/ci/old_mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | util::{Encode7Bit, Truncate}, 3 | *, 4 | }; 5 | 6 | pub use discovery::query::DiscoveryQuery; 7 | pub use discovery::query::DiscoveryQueryBorrowed; 8 | pub use discovery::query::DiscoveryQueryBorrowedBuilder; 9 | pub use discovery::reply::DiscoveryReply; 10 | pub use discovery::reply::DiscoveryReplyBorrowed; 11 | pub use discovery::reply::DiscoveryReplyBorrowedBuilder; 12 | pub use invalidate_muid::InvalidateMuid; 13 | pub use invalidate_muid::InvalidateMuidBorrowed; 14 | pub use invalidate_muid::InvalidateMuidBorrowedBuilder; 15 | pub use nak::Nak; 16 | pub use nak::NakBorrowed; 17 | pub use nak::NakBorrowedBuilder; 18 | 19 | mod discovery; 20 | mod helpers; 21 | mod invalidate_muid; 22 | mod nak; 23 | 24 | // todo: bump 25 | const VERSION: u7 = u7::new(0x01); 26 | 27 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 28 | pub enum DeviceId { 29 | Channel(u4), 30 | Group, 31 | FunctionBlock, 32 | } 33 | 34 | impl core::default::Default for DeviceId { 35 | fn default() -> Self { 36 | Self::Channel(Default::default()) 37 | } 38 | } 39 | 40 | impl DeviceId { 41 | pub(crate) fn from_u8(v: u8) -> Result { 42 | if v == 0x7F { 43 | Ok(DeviceId::FunctionBlock) 44 | } else if v == 0x7E { 45 | Ok(DeviceId::Group) 46 | } else if v < 0x0F { 47 | Ok(DeviceId::Channel(v.try_into().unwrap())) 48 | } else { 49 | Err(Error::InvalidData) 50 | } 51 | } 52 | pub(crate) fn to_u8(self) -> u8 { 53 | match self { 54 | DeviceId::Group => 0x7E, 55 | DeviceId::FunctionBlock => 0x7F, 56 | DeviceId::Channel(c) => c.into(), 57 | } 58 | } 59 | } 60 | 61 | pub trait Ci: ByteData { 62 | fn device_id(&self) -> DeviceId { 63 | DeviceId::from_u8(self.byte_data()[2]).unwrap() 64 | } 65 | fn version(&self) -> u7 { 66 | self.byte_data()[5].truncate() 67 | } 68 | fn source(&self) -> u28 { 69 | u28::from_u7s(&self.byte_data()[6..10]) 70 | } 71 | fn destination(&self) -> u28 { 72 | u28::from_u7s(&self.byte_data()[10..14]) 73 | } 74 | } 75 | 76 | #[derive(Default)] 77 | pub struct CiStandardData { 78 | pub device_id: DeviceId, 79 | pub source: Option, 80 | pub destination: Option, 81 | pub sysex_sub_id2: Option, 82 | } 83 | 84 | pub struct CiStandardDataIterator<'a>(&'a CiStandardData, [u7; 13], usize); 85 | 86 | const UNIVERSAL_SYSEX: u7 = u7::new(0x7E); 87 | const UNIVERSAL_SYSEX_SUB_ID_MIDI_CI: u7 = u7::new(0x0D); 88 | 89 | impl CiStandardData { 90 | fn payload(&self) -> Result { 91 | let Some(source) = self.source else { 92 | return Err(Error::InvalidData); 93 | }; 94 | let Some(destination) = self.destination else { 95 | return Err(Error::InvalidData); 96 | }; 97 | let Some(sysex_sub_id2) = self.sysex_sub_id2 else { 98 | return Err(Error::InvalidData); 99 | }; 100 | 101 | let mut data = [u7::default(); 13]; 102 | data[0] = UNIVERSAL_SYSEX; 103 | data[1] = self.device_id.to_u8().truncate(); 104 | data[2] = UNIVERSAL_SYSEX_SUB_ID_MIDI_CI; 105 | data[3] = sysex_sub_id2; 106 | data[4] = VERSION; 107 | source.to_u7s(&mut data[5..9]); 108 | destination.to_u7s(&mut data[9..13]); 109 | 110 | Ok(CiStandardDataIterator(self, data, 0)) 111 | } 112 | } 113 | 114 | impl<'a> core::iter::Iterator for CiStandardDataIterator<'a> { 115 | type Item = u7; 116 | fn next(&mut self) -> Option { 117 | if self.2 == self.1.len() { 118 | None 119 | } else { 120 | let ret = Some(self.1[self.2]); 121 | self.2 += 1; 122 | ret 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /midi2/src/ci/property.rs: -------------------------------------------------------------------------------- 1 | pub trait ReadCiProperty<'a, const VERSION: u8, B: crate::buffer::Buffer>: 2 | crate::detail::property::Property 3 | { 4 | fn read(buffer: &'a B) -> Self::Type; 5 | } 6 | 7 | pub trait ValidateCiPropertyData: 8 | crate::detail::property::Property 9 | { 10 | fn validate(buffer: &B) -> Result<(), crate::error::InvalidData>; 11 | } 12 | 13 | pub trait WriteCiProperty: 14 | crate::detail::property::Property 15 | { 16 | fn write(buffer: &mut B, v: Self::Type); 17 | fn default() -> Self::Type; 18 | } 19 | 20 | pub trait ResizeCiProperty: 21 | crate::detail::property::Property 22 | { 23 | fn resize(buffer: &mut B, value: &Self::Type) 24 | where 25 | B: crate::buffer::BufferResize; 26 | fn try_resize(buffer: &mut B, value: &Self::Type) -> Result<(), crate::error::BufferOverflow> 27 | where 28 | B: crate::buffer::BufferTryResize; 29 | } 30 | -------------------------------------------------------------------------------- /midi2/src/ci/version.rs: -------------------------------------------------------------------------------- 1 | pub trait CiVersion {} 2 | -------------------------------------------------------------------------------- /midi2/src/detail.rs: -------------------------------------------------------------------------------- 1 | mod bit_ops; 2 | mod encode_7bit; 3 | 4 | pub mod common_err_strings; 5 | pub mod common_properties; 6 | pub mod helpers; 7 | pub mod property; 8 | pub mod schema; 9 | 10 | #[cfg(test)] 11 | pub mod test_support; 12 | 13 | pub use bit_ops::BitOps; 14 | pub use encode_7bit::Encode7Bit; 15 | -------------------------------------------------------------------------------- /midi2/src/detail/common_err_strings.rs: -------------------------------------------------------------------------------- 1 | pub const ERR_SLICE_TOO_SHORT: &str = "Slice is too short"; 2 | pub const ERR_INCORRECT_UMP_MESSAGE_TYPE: &str = "Incorrect UMP message type"; 3 | -------------------------------------------------------------------------------- /midi2/src/detail/encode_7bit.rs: -------------------------------------------------------------------------------- 1 | use ux::{u14, u21, u28, u7}; 2 | 3 | pub trait Byte: Copy { 4 | fn to_u8(self) -> u8; 5 | fn from_u8(v: u8) -> Self; 6 | } 7 | 8 | impl Byte for u7 { 9 | fn to_u8(self) -> u8 { 10 | self.into() 11 | } 12 | fn from_u8(v: u8) -> Self { 13 | u7::new(v & 0x7F) 14 | } 15 | } 16 | 17 | impl Byte for u8 { 18 | fn from_u8(v: u8) -> Self { 19 | v & 0x7F 20 | } 21 | fn to_u8(self) -> u8 { 22 | self & 0x7F 23 | } 24 | } 25 | 26 | pub trait Encode7Bit { 27 | fn from_u7s(u7s: &[T]) -> Self; 28 | fn to_u7s(&self, data: &mut [T]); 29 | } 30 | 31 | impl Encode7Bit for u28 { 32 | fn to_u7s(&self, data: &mut [T]) { 33 | debug_assert!(data.len() == 4); 34 | 35 | let v = u32::from(*self); 36 | 37 | data[0] = T::from_u8(v as u8); 38 | data[1] = T::from_u8((v >> 7) as u8); 39 | data[2] = T::from_u8((v >> (7 * 2)) as u8); 40 | data[3] = T::from_u8((v >> (7 * 3)) as u8); 41 | } 42 | fn from_u7s(u7s: &[T]) -> Self { 43 | debug_assert!(u7s.len() == 4); 44 | 45 | let mut ret: u28 = Default::default(); 46 | 47 | ret |= u28::from(u7s[0].to_u8() & 0x7F); 48 | ret |= u28::from(u7s[1].to_u8() & 0x7F) << 7; 49 | ret |= u28::from(u7s[2].to_u8() & 0x7F) << (7 * 2); 50 | ret |= u28::from(u7s[3].to_u8() & 0x7F) << (7 * 3); 51 | 52 | ret 53 | } 54 | } 55 | 56 | impl Encode7Bit for u21 { 57 | fn to_u7s(&self, data: &mut [T]) { 58 | debug_assert!(data.len() == 3); 59 | 60 | let v = u32::from(*self); 61 | 62 | data[0] = T::from_u8(v as u8); 63 | data[1] = T::from_u8((v >> 7) as u8); 64 | data[2] = T::from_u8((v >> (7 * 2)) as u8); 65 | } 66 | fn from_u7s(u7s: &[T]) -> Self { 67 | debug_assert!(u7s.len() == 3); 68 | 69 | let mut ret: u21 = Default::default(); 70 | 71 | ret |= u21::from(u7s[0].to_u8() & 0x7F); 72 | ret |= u21::from(u7s[1].to_u8() & 0x7F) << 7; 73 | ret |= u21::from(u7s[2].to_u8() & 0x7F) << (7 * 2); 74 | 75 | ret 76 | } 77 | } 78 | 79 | impl Encode7Bit for u14 { 80 | fn to_u7s(&self, data: &mut [T]) { 81 | debug_assert!(data.len() == 2); 82 | 83 | let v = u16::from(*self); 84 | 85 | data[0] = T::from_u8(v as u8); 86 | data[1] = T::from_u8((v >> 7) as u8); 87 | } 88 | fn from_u7s(u7s: &[T]) -> Self { 89 | debug_assert!(u7s.len() == 2); 90 | 91 | let mut ret: u14 = Default::default(); 92 | 93 | ret |= u14::from(u7s[0].to_u8() & 0x7F); 94 | ret |= u14::from(u7s[1].to_u8() & 0x7F) << 7; 95 | 96 | ret 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /midi2/src/detail/property.rs: -------------------------------------------------------------------------------- 1 | pub trait Property { 2 | type Type; 3 | } 4 | 5 | pub trait ReadProperty<'a, B: crate::buffer::Buffer>: Property { 6 | fn read(buffer: &'a B) -> Self::Type; 7 | // validate that the data in the buffer represents a valid instance of the property 8 | fn validate(buffer: &B) -> Result<(), crate::error::InvalidData>; 9 | } 10 | 11 | pub trait WriteProperty: Property { 12 | fn write(buffer: &mut B, v: Self::Type); 13 | // validate that the value represents a valid instance of the property. 14 | // ideally the type system should do this for us so this will 15 | // most often just trivially return Ok 16 | // 17 | // This function is currently unused, but we'll keep it hangin' around 18 | // in case we need it sometime down the line. 19 | #[allow(dead_code)] 20 | fn validate(v: &Self::Type) -> Result<(), crate::error::InvalidData>; 21 | fn default() -> Self::Type; 22 | } 23 | 24 | // properties which may require resizing the underlying buffer 25 | // before writing the value 26 | pub trait ResizeProperty: 27 | WriteProperty 28 | { 29 | fn resize(buffer: &mut B, value: &Self::Type) 30 | where 31 | B: crate::buffer::BufferResize; 32 | fn try_resize(buffer: &mut B, value: &Self::Type) -> Result<(), crate::error::BufferOverflow> 33 | where 34 | B: crate::buffer::BufferTryResize; 35 | } 36 | -------------------------------------------------------------------------------- /midi2/src/detail/test_support.rs: -------------------------------------------------------------------------------- 1 | pub mod rubbish_payload_iterator; 2 | -------------------------------------------------------------------------------- /midi2/src/detail/test_support/rubbish_payload_iterator.rs: -------------------------------------------------------------------------------- 1 | /// This is for testing payload insertion implementation on sysex messages. 2 | /// The iterator returns no size hints so the optimisation case for these 3 | /// payload insertion implementations will hit their worst case for mem-copying. 4 | pub struct RubbishPayloadIterator(u8); 5 | 6 | impl RubbishPayloadIterator { 7 | pub fn new() -> Self { 8 | RubbishPayloadIterator(0) 9 | } 10 | } 11 | 12 | impl core::iter::Iterator for RubbishPayloadIterator { 13 | type Item = u8; 14 | fn next(&mut self) -> Option { 15 | if self.0 == 50 { 16 | return None; 17 | } 18 | let ret = Some(self.0); 19 | self.0 += 1; 20 | ret 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /midi2/src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug, PartialEq, Eq)] 2 | pub enum Error { 3 | BufferOverflow, 4 | InvalidData(InvalidData), 5 | } 6 | 7 | impl core::convert::From for Error { 8 | fn from(_value: BufferOverflow) -> Self { 9 | Error::BufferOverflow 10 | } 11 | } 12 | 13 | impl core::convert::From for Error { 14 | fn from(value: InvalidData) -> Self { 15 | Error::InvalidData(value) 16 | } 17 | } 18 | 19 | #[derive(Clone, Debug, PartialEq, Eq, Default)] 20 | pub struct BufferOverflow; 21 | 22 | #[derive(Clone, Debug, PartialEq, Eq)] 23 | pub struct InvalidData(pub &'static str); 24 | 25 | #[cfg(feature = "std")] 26 | impl std::error::Error for BufferOverflow {} 27 | 28 | #[cfg(feature = "std")] 29 | impl std::fmt::Display for BufferOverflow { 30 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 31 | ::fmt(self, f) 32 | } 33 | } 34 | 35 | #[cfg(feature = "std")] 36 | impl std::error::Error for InvalidData {} 37 | 38 | #[cfg(feature = "std")] 39 | impl std::fmt::Display for InvalidData { 40 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 41 | ::fmt(self, f) 42 | } 43 | } 44 | 45 | impl core::convert::From for BufferOverflow { 46 | fn from(_value: crate::traits::SysexTryResizeError) -> Self { 47 | BufferOverflow 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /midi2/src/flex_data/README.md: -------------------------------------------------------------------------------- 1 | MIDI 2.0 Flex Data Messages 2 | 3 | ## Basic Usage 4 | 5 | ```rust 6 | use midi2::{ 7 | prelude::*, 8 | flex_data::{FlexData, FlexDataMessage}, 9 | }; 10 | 11 | let message = FlexData::try_from(&[ 12 | 0xD70B_0006, 13 | 0xF703_3519, 14 | 0x4B00_0000, 15 | 0x110A_0020 16 | ][..]).expect("Valid data"); 17 | 18 | // all flex_data messages are grouped 19 | println!("Group: {}", message.group()); 20 | // all flex_data messages have a bank property 21 | println!("Bank: {:?}", message.bank()); 22 | 23 | match message { 24 | FlexData::SetChordName(m) => { 25 | println!("Set Chord Name {:?}", m.data()); 26 | // some flex_data messages have an optional `channel` field 27 | println!("Channel: {:?}", m.optional_channel()); 28 | }, 29 | FlexData::ComposerName(m) => { 30 | println!("Composer Name {:?}", m.data()); 31 | // string properties of flex_data messages carrying string data 32 | // can be read as a std::string::String (std feature enabled) 33 | println!("Name {:?}", m.name()); 34 | // or as an iterator over the utf-8 bytes (no_std friendly) 35 | println!("Name bytes {:?}", m.name_bytes().collect::>()); 36 | } 37 | FlexData::SetKeySignature(m) => println!("Set Key Signature {:?}", m.data()), 38 | FlexData::SetMetronome(m) => println!("Set Metronome {:?}", m.data()), 39 | FlexData::SetTempo(m) => println!("Set Tempo {:?}", m.data()), 40 | FlexData::SetTimeSignature(m) => println!("Set Time Signature {:?}", m.data()), 41 | FlexData::UnknownMetadataText(m) => println!("Unknown Metadata Text {:?}", m.data()), 42 | FlexData::ProjectName(m) => println!("Project Name {:?}", m.data()), 43 | FlexData::CompositionName(m) => println!("Composition Name {:?}", m.data()), 44 | FlexData::MidiClipName(m) => println!("Midi Clip Name {:?}", m.data()), 45 | FlexData::CopyrightNotice(m) => println!("Copyright Notice {:?}", m.data()), 46 | FlexData::LyricistName(m) => println!("Lyricist Name {:?}", m.data()), 47 | FlexData::ArrangerName(m) => println!("Arranger Name {:?}", m.data()), 48 | FlexData::PublisherName(m) => println!("Publisher Name {:?}", m.data()), 49 | FlexData::PrimaryPerformerName(m) => println!("Primary Performer Name {:?}", m.data()), 50 | FlexData::AccompanyingPerformerName(m) => println!("Accompanying Performer Name {:?}", m.data()), 51 | FlexData::RecordingDate(m) => println!("Recording Date {:?}", m.data()), 52 | FlexData::RecordingLocation(m) => println!("Recording Location {:?}", m.data()), 53 | FlexData::UnknownPerformanceText(m) => println!("Unknown Performance Text {:?}", m.data()), 54 | FlexData::Lyrics(m) => println!("Lyrics {:?}", m.data()), 55 | FlexData::LyricsLanguage(m) => println!("Lyrics Language {:?}", m.data()), 56 | FlexData::Ruby(m) => println!("Ruby {:?}", m.data()), 57 | FlexData::RubyLanguage(m) => println!("Ruby Language {:?}", m.data()), 58 | _ => {}, 59 | } 60 | ``` 61 | 62 | ## Dynamically Sized 63 | 64 | Some flex_data messages are fixed size and some are dynamically sized. 65 | All default constructed flex_data messages will fit into a `[u32; 4]`. 66 | -------------------------------------------------------------------------------- /midi2/src/flex_data/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{error, flex_data}; 2 | 3 | #[derive(Eq, PartialEq, Clone, midi2_proc::Debug)] 4 | pub struct Packet(pub(crate) [u32; 4]); 5 | 6 | impl crate::traits::BufferAccess<[u32; 4]> for Packet { 7 | fn buffer_access(&self) -> &[u32; 4] { 8 | &self.0 9 | } 10 | fn buffer_access_mut(&mut self) -> &mut [u32; 4] 11 | where 12 | [u32; 4]: crate::buffer::BufferMut, 13 | { 14 | &mut self.0 15 | } 16 | } 17 | 18 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 19 | type Error = error::InvalidData; 20 | fn try_from(data: &'a [u32]) -> Result { 21 | if data.len() < 2 { 22 | return Err(error::InvalidData("Slice is too short")); 23 | } 24 | 25 | use crate::detail::BitOps; 26 | if u8::from(data[0].nibble(0)) != flex_data::UMP_MESSAGE_TYPE { 27 | return Err(error::InvalidData( 28 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 29 | )); 30 | } 31 | 32 | Ok(Packet({ 33 | let mut buffer = [0x0; 4]; 34 | let sz = 4.min(data.len()); 35 | buffer[..sz].copy_from_slice(&data[..sz]); 36 | buffer 37 | })) 38 | } 39 | } 40 | 41 | impl core::ops::Deref for Packet { 42 | type Target = [u32]; 43 | fn deref(&self) -> &Self::Target { 44 | &self.0[..] 45 | } 46 | } 47 | 48 | impl crate::Grouped<[u32; 4]> for Packet { 49 | fn group(&self) -> crate::ux::u4 { 50 | use crate::detail::BitOps; 51 | self.0[0].nibble(1) 52 | } 53 | fn set_group(&mut self, group: crate::ux::u4) 54 | where 55 | [u32; 4]: crate::buffer::BufferMut, 56 | { 57 | use crate::detail::BitOps; 58 | self.0[0].set_nibble(1, group); 59 | } 60 | } 61 | 62 | #[derive(Debug, Clone, Eq, PartialEq)] 63 | pub enum Format { 64 | Complete, 65 | Start, 66 | Continue, 67 | End, 68 | } 69 | 70 | impl Packet { 71 | pub fn format(&self) -> Format { 72 | format_from_data(&self.0[..]) 73 | } 74 | } 75 | 76 | fn format_from_data(data: &[u32]) -> Format { 77 | use crate::detail::BitOps; 78 | use Format::*; 79 | match u8::from(data[0].crumb(4)) { 80 | 0x0 => Complete, 81 | 0x1 => Start, 82 | 0x2 => Continue, 83 | 0x3 => End, 84 | _ => unreachable!(), 85 | } 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | use super::*; 91 | use pretty_assertions::assert_eq; 92 | 93 | #[test] 94 | fn construction() { 95 | assert!(Packet::try_from(&[0xD000_0000, 0x0000_0000][..]).is_ok()) 96 | } 97 | 98 | #[test] 99 | fn construction_long_slice() { 100 | assert!(Packet::try_from(&[0xD000_0000, 0x0000_0000, 0x0, 0x0][..]).is_ok()) 101 | } 102 | 103 | #[test] 104 | fn construction_very_long_slice() { 105 | assert!(Packet::try_from(&[0xD000_0000, 0x0000_0000, 0x0, 0x0, 0x0][..]).is_ok()) 106 | } 107 | 108 | #[test] 109 | fn construction_short_slice() { 110 | assert_eq!( 111 | Packet::try_from(&[0xD000_0000][..]), 112 | Err(error::InvalidData( 113 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT 114 | )), 115 | ) 116 | } 117 | 118 | #[test] 119 | fn construction_incorrect_ump_message_type() { 120 | assert_eq!( 121 | Packet::try_from(&[0x0000_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]), 122 | Err(error::InvalidData( 123 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 124 | )), 125 | ) 126 | } 127 | 128 | #[test] 129 | fn complete_format() { 130 | assert_eq!( 131 | Packet::try_from(&[0xD000_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 132 | .unwrap() 133 | .format(), 134 | Format::Complete, 135 | ) 136 | } 137 | 138 | #[test] 139 | fn start_format() { 140 | assert_eq!( 141 | Packet::try_from(&[0xD040_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 142 | .unwrap() 143 | .format(), 144 | Format::Start, 145 | ) 146 | } 147 | 148 | #[test] 149 | fn continue_format() { 150 | assert_eq!( 151 | Packet::try_from(&[0xD080_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 152 | .unwrap() 153 | .format(), 154 | Format::Continue, 155 | ) 156 | } 157 | 158 | #[test] 159 | fn end_format() { 160 | assert_eq!( 161 | Packet::try_from(&[0xD0C0_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 162 | .unwrap() 163 | .format(), 164 | Format::End, 165 | ) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /midi2/src/flex_data/set_metronome.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | flex_data::{self, UMP_MESSAGE_TYPE}, 4 | }; 5 | 6 | const STATUS: u8 = 0x2; 7 | 8 | /// MIDI 2.0 Flex Data Set Metronome Message 9 | /// 10 | /// See the [module docs](crate::flex_data) for more info. 11 | #[midi2_proc::generate_message(Via(crate::flex_data::FlexData), FixedSize, MinSizeUmp(3))] 12 | struct SetMetronome { 13 | #[property(common_properties::UmpMessageTypeProperty)] 14 | ump_type: (), 15 | #[property(common_properties::GroupProperty)] 16 | group: crate::ux::u4, 17 | #[property(flex_data::FormatProperty<{flex_data::COMPLETE_FORMAT}>)] 18 | format: (), 19 | #[property(flex_data::BankProperty<{flex_data::SETUP_AND_PERFORMANCE_BANK}>)] 20 | bank: (), 21 | #[property(flex_data::StatusProperty<{STATUS}>)] 22 | status: (), 23 | #[property(flex_data::NoChannelProperty)] 24 | no_channel: (), 25 | #[property(common_properties::UmpSchemaProperty< 26 | u8, 27 | schema::Ump<0x0, 0xFF00_0000, 0x0, 0x0>, 28 | >)] 29 | number_of_clocks_per_primary_click: u8, 30 | #[property(common_properties::UmpSchemaProperty< 31 | u8, 32 | schema::Ump<0x0, 0x00FF_0000, 0x0, 0x0>, 33 | >)] 34 | bar_accent1: u8, 35 | #[property(common_properties::UmpSchemaProperty< 36 | u8, 37 | schema::Ump<0x0, 0x0000_FF00, 0x0, 0x0>, 38 | >)] 39 | bar_accent2: u8, 40 | #[property(common_properties::UmpSchemaProperty< 41 | u8, 42 | schema::Ump<0x0, 0x0000_00FF, 0x0, 0x0>, 43 | >)] 44 | bar_accent3: u8, 45 | #[property(common_properties::UmpSchemaProperty< 46 | u8, 47 | schema::Ump<0x0, 0x0, 0xFF00_0000, 0x0>, 48 | >)] 49 | number_of_subdivision_clicks1: u8, 50 | #[property(common_properties::UmpSchemaProperty< 51 | u8, 52 | schema::Ump<0x0, 0x0, 0x00FF_0000, 0x0>, 53 | >)] 54 | number_of_subdivision_clicks2: u8, 55 | } 56 | 57 | impl flex_data::FlexDataMessage for SetMetronome {} 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | use crate::{traits::Grouped, ux::u4}; 63 | use pretty_assertions::assert_eq; 64 | 65 | #[test] 66 | fn setters() { 67 | let mut message = SetMetronome::<[u32; 4]>::new(); 68 | message.set_group(u4::new(0x1)); 69 | message.set_number_of_clocks_per_primary_click(0x9B); 70 | message.set_bar_accent1(0x4A); 71 | message.set_bar_accent2(0xFE); 72 | message.set_bar_accent3(0x56); 73 | message.set_number_of_subdivision_clicks1(0xB8); 74 | message.set_number_of_subdivision_clicks2(0x1B); 75 | assert_eq!( 76 | message, 77 | SetMetronome([0xD110_0002, 0x9B4A_FE56, 0xB81B_0000, 0x0,]), 78 | ); 79 | } 80 | 81 | #[test] 82 | fn number_of_clocks_per_primary_click() { 83 | assert_eq!( 84 | SetMetronome::try_from(&[0xD110_0002, 0x9B4A_FE56, 0xB81B_0000][..]) 85 | .unwrap() 86 | .number_of_clocks_per_primary_click(), 87 | 0x9B, 88 | ); 89 | } 90 | 91 | #[test] 92 | fn bar_accent1() { 93 | assert_eq!( 94 | SetMetronome::try_from(&[0xD110_0002, 0x9B4A_FE56, 0xB81B_0000][..]) 95 | .unwrap() 96 | .bar_accent1(), 97 | 0x4A, 98 | ); 99 | } 100 | 101 | #[test] 102 | fn bar_accent2() { 103 | assert_eq!( 104 | SetMetronome::try_from(&[0xD110_0002, 0x9B4A_FE56, 0xB81B_0000][..]) 105 | .unwrap() 106 | .bar_accent2(), 107 | 0xFE, 108 | ); 109 | } 110 | 111 | #[test] 112 | fn bar_accent3() { 113 | assert_eq!( 114 | SetMetronome::try_from(&[0xD110_0002, 0x9B4A_FE56, 0xB81B_0000][..]) 115 | .unwrap() 116 | .bar_accent3(), 117 | 0x56, 118 | ); 119 | } 120 | 121 | #[test] 122 | fn number_of_subdivision_clicks1() { 123 | assert_eq!( 124 | SetMetronome::try_from(&[0xD110_0002, 0x9B4A_FE56, 0xB81B_0000][..]) 125 | .unwrap() 126 | .number_of_subdivision_clicks1(), 127 | 0xB8, 128 | ); 129 | } 130 | 131 | #[test] 132 | fn number_of_subdivision_clicks2() { 133 | assert_eq!( 134 | SetMetronome::try_from(&[0xD110_0002, 0x9B4A_FE56, 0xB81B_0000][..]) 135 | .unwrap() 136 | .number_of_subdivision_clicks2(), 137 | 0x1B, 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /midi2/src/flex_data/set_tempo.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | flex_data::{self, UMP_MESSAGE_TYPE}, 4 | }; 5 | 6 | const STATUS: u8 = 0x0; 7 | 8 | /// MIDI 2.0 Flex Data Set Tempo Message 9 | /// 10 | /// See the [module docs](crate::flex_data) for more info. 11 | #[midi2_proc::generate_message(Via(crate::flex_data::FlexData), FixedSize, MinSizeUmp(2))] 12 | struct SetTempo { 13 | #[property(common_properties::UmpMessageTypeProperty)] 14 | ump_type: (), 15 | #[property(common_properties::GroupProperty)] 16 | group: crate::ux::u4, 17 | #[property(flex_data::FormatProperty<{flex_data::COMPLETE_FORMAT}>)] 18 | format: (), 19 | #[property(flex_data::BankProperty<{flex_data::SETUP_AND_PERFORMANCE_BANK}>)] 20 | bank: (), 21 | #[property(flex_data::StatusProperty<{STATUS}>)] 22 | status: (), 23 | #[property(flex_data::NoChannelProperty)] 24 | no_channel: (), 25 | #[property(common_properties::UmpSchemaProperty< 26 | u32, 27 | schema::Ump<0x0, 0xFFFF_FFFF, 0x0, 0x0>, 28 | >)] 29 | number_of_10_nanosecond_units_per_quarter_note: u32, 30 | } 31 | 32 | impl flex_data::FlexDataMessage for SetTempo {} 33 | 34 | #[cfg(test)] 35 | mod tests { 36 | use super::*; 37 | use crate::{traits::Grouped, ux::u4}; 38 | use pretty_assertions::assert_eq; 39 | 40 | #[test] 41 | fn builder() { 42 | let mut message = SetTempo::<[u32; 4]>::new(); 43 | message.set_group(u4::new(0x7)); 44 | message.set_number_of_10_nanosecond_units_per_quarter_note(0xF751FE05); 45 | assert_eq!(message, SetTempo([0xD710_0000, 0xF751_FE05, 0x0, 0x0,]),); 46 | } 47 | 48 | #[test] 49 | fn number_of_10_nanosecond_units_per_quarter_note() { 50 | assert_eq!( 51 | SetTempo::try_from(&[0xD710_0000, 0xF751_FE05,][..]) 52 | .unwrap() 53 | .number_of_10_nanosecond_units_per_quarter_note(), 54 | 0xF751FE05, 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /midi2/src/flex_data/set_time_signature.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | flex_data::{self, UMP_MESSAGE_TYPE}, 4 | }; 5 | 6 | const STATUS: u8 = 0x1; 7 | 8 | /// MIDI 2.0 Flex Data Set Time Signature Message 9 | /// 10 | /// See the [module docs](crate::flex_data) for more info. 11 | #[midi2_proc::generate_message(Via(crate::flex_data::FlexData), FixedSize, MinSizeUmp(2))] 12 | struct SetTimeSignature { 13 | #[property(common_properties::UmpMessageTypeProperty)] 14 | ump_type: (), 15 | #[property(common_properties::GroupProperty)] 16 | group: crate::ux::u4, 17 | #[property(flex_data::FormatProperty<{flex_data::COMPLETE_FORMAT}>)] 18 | format: (), 19 | #[property(flex_data::BankProperty<{flex_data::SETUP_AND_PERFORMANCE_BANK}>)] 20 | bank: (), 21 | #[property(flex_data::StatusProperty<{STATUS}>)] 22 | status: (), 23 | #[property(flex_data::NoChannelProperty)] 24 | no_channel: (), 25 | #[property(common_properties::UmpSchemaProperty< 26 | u8, 27 | schema::Ump<0x0, 0xFF00_0000, 0x0, 0x0>, 28 | >)] 29 | numerator: u8, 30 | #[property(common_properties::UmpSchemaProperty< 31 | u8, 32 | schema::Ump<0x0, 0x00FF_0000, 0x0, 0x0>, 33 | >)] 34 | denominator: u8, 35 | #[property(common_properties::UmpSchemaProperty< 36 | u8, 37 | schema::Ump<0x0, 0x0000_FF00, 0x0, 0x0>, 38 | >)] 39 | number_of_32nd_notes: u8, 40 | } 41 | 42 | impl flex_data::FlexDataMessage for SetTimeSignature {} 43 | 44 | #[cfg(test)] 45 | mod tests { 46 | use super::*; 47 | use crate::{flex_data::FlexDataMessage, traits::Grouped, ux::u4}; 48 | use pretty_assertions::assert_eq; 49 | 50 | #[test] 51 | fn builder() { 52 | let mut message = SetTimeSignature::<[u32; 4]>::new(); 53 | message.set_group(u4::new(0xA)); 54 | message.set_numerator(0xCD); 55 | message.set_denominator(0x90); 56 | message.set_number_of_32nd_notes(0x7E); 57 | assert_eq!( 58 | message, 59 | SetTimeSignature([0xDA10_0001, 0xCD90_7E00, 0x0, 0x0,]), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn numerator() { 65 | assert_eq!( 66 | SetTimeSignature::try_from(&[0xDA10_0001, 0xCD90_7E00,][..]) 67 | .unwrap() 68 | .numerator(), 69 | 0xCD, 70 | ); 71 | } 72 | 73 | #[test] 74 | fn denominator() { 75 | assert_eq!( 76 | SetTimeSignature::try_from(&[0xDA10_0001, 0xCD90_7E00,][..]) 77 | .unwrap() 78 | .denominator(), 79 | 0x90, 80 | ); 81 | } 82 | 83 | #[test] 84 | fn number_of_32nd_notes() { 85 | assert_eq!( 86 | SetTimeSignature::try_from(&[0xDA10_0001, 0xCD90_7E00,][..]) 87 | .unwrap() 88 | .number_of_32nd_notes(), 89 | 0x7E, 90 | ); 91 | } 92 | 93 | #[test] 94 | fn bank() { 95 | assert_eq!( 96 | SetTimeSignature::try_from(&[0xDA10_0001, 0xCD90_7E00,][..]) 97 | .unwrap() 98 | .bank(), 99 | flex_data::Bank::SetupAndPerformance, 100 | ); 101 | } 102 | 103 | #[test] 104 | fn status() { 105 | assert_eq!( 106 | SetTimeSignature::try_from(&[0xDA10_0001, 0xCD90_7E00,][..]) 107 | .unwrap() 108 | .status(), 109 | STATUS, 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /midi2/src/flex_data/tonic.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{schema, BitOps}, 3 | ux::*, 4 | }; 5 | 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | pub enum Tonic { 8 | A, 9 | B, 10 | C, 11 | D, 12 | E, 13 | F, 14 | G, 15 | NonStandard, 16 | } 17 | 18 | pub struct TonicProperty(S); 19 | 20 | impl crate::detail::property::Property 21 | for TonicProperty 22 | { 23 | type Type = Tonic; 24 | } 25 | 26 | impl<'a, B: crate::buffer::Ump> crate::detail::property::ReadProperty<'a, B> 27 | for TonicProperty> 28 | { 29 | fn validate(buffer: &B) -> Result<(), crate::error::InvalidData> { 30 | Tonic::from_nibble(buffer.buffer()[1].nibble(1))?; 31 | Ok(()) 32 | } 33 | fn read(buffer: &'a B) -> Self::Type { 34 | Tonic::from_nibble(buffer.buffer()[1].nibble(1)).unwrap() 35 | } 36 | } 37 | 38 | impl crate::detail::property::WriteProperty 39 | for TonicProperty> 40 | { 41 | fn validate(_: &Tonic) -> Result<(), crate::error::InvalidData> { 42 | Ok(()) 43 | } 44 | fn default() -> Self::Type { 45 | Default::default() 46 | } 47 | fn write(buffer: &mut B, v: Self::Type) { 48 | buffer.buffer_mut()[1].set_nibble(1, v.into_nibble()); 49 | } 50 | } 51 | 52 | impl<'a, B: crate::buffer::Ump> crate::detail::property::ReadProperty<'a, B> 53 | for TonicProperty> 54 | { 55 | fn validate(buffer: &B) -> Result<(), crate::error::InvalidData> { 56 | Tonic::from_nibble(buffer.buffer()[3].nibble(1))?; 57 | Ok(()) 58 | } 59 | fn read(buffer: &'a B) -> Self::Type { 60 | Tonic::from_nibble(buffer.buffer()[3].nibble(1)).unwrap() 61 | } 62 | } 63 | 64 | impl crate::detail::property::WriteProperty 65 | for TonicProperty> 66 | { 67 | fn validate(_: &Tonic) -> Result<(), crate::error::InvalidData> { 68 | Ok(()) 69 | } 70 | fn default() -> Self::Type { 71 | Default::default() 72 | } 73 | fn write(buffer: &mut B, v: Self::Type) { 74 | buffer.buffer_mut()[3].set_nibble(1, v.into_nibble()); 75 | } 76 | } 77 | 78 | impl core::default::Default for Tonic { 79 | /// Default value is [Tonic::C] 80 | fn default() -> Self { 81 | Tonic::C 82 | } 83 | } 84 | 85 | impl Tonic { 86 | fn from_nibble(nibble: u4) -> Result { 87 | use Tonic::*; 88 | match u8::from(nibble) { 89 | 0x0 => Ok(NonStandard), 90 | 0x1 => Ok(A), 91 | 0x2 => Ok(B), 92 | 0x3 => Ok(C), 93 | 0x4 => Ok(D), 94 | 0x5 => Ok(E), 95 | 0x6 => Ok(F), 96 | 0x7 => Ok(G), 97 | _ => Err(crate::error::InvalidData("Couldn't interpret Tonic field")), 98 | } 99 | } 100 | 101 | fn into_nibble(self) -> u4 { 102 | use Tonic::*; 103 | u4::new(match self { 104 | A => 0x1, 105 | B => 0x2, 106 | C => 0x3, 107 | D => 0x4, 108 | E => 0x5, 109 | F => 0x6, 110 | G => 0x7, 111 | NonStandard => 0x0, 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /midi2/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(docsrs, feature(doc_cfg))] 3 | #![doc = include_str!("../README.md")] 4 | 5 | #[cfg(any(feature = "std", test))] 6 | extern crate std; 7 | 8 | #[cfg(feature = "channel-voice1")] 9 | pub mod channel_voice1; 10 | #[cfg(feature = "channel-voice2")] 11 | pub mod channel_voice2; 12 | #[cfg(feature = "ci")] 13 | pub mod ci; 14 | #[cfg(feature = "flex-data")] 15 | pub mod flex_data; 16 | #[cfg(feature = "sysex7")] 17 | pub mod sysex7; 18 | #[cfg(feature = "sysex8")] 19 | pub mod sysex8; 20 | #[cfg(feature = "system-common")] 21 | pub mod system_common; 22 | #[cfg(feature = "ump-stream")] 23 | pub mod ump_stream; 24 | #[cfg(feature = "utility")] 25 | pub mod utility; 26 | 27 | pub mod buffer; 28 | pub mod error; 29 | 30 | mod detail; 31 | mod message; 32 | mod packet; 33 | mod packets; 34 | mod traits; 35 | 36 | pub use ux; 37 | 38 | pub use message::*; 39 | pub use packets::*; 40 | pub use traits::*; 41 | 42 | pub mod num { 43 | pub use ux::*; 44 | pub type Fixed7_9 = fixed::FixedU16; 45 | pub type Fixed7_25 = fixed::FixedU32; 46 | } 47 | 48 | pub mod prelude { 49 | pub use super::*; 50 | pub use crate::ux::*; 51 | } 52 | -------------------------------------------------------------------------------- /midi2/src/packet.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("sysex7/README.md")] 2 | 3 | use crate::{detail::common_err_strings, error::InvalidData}; 4 | 5 | #[derive(Eq, PartialEq, Clone, Debug, derive_more::From)] 6 | pub enum Packet { 7 | #[cfg(feature = "channel-voice1")] 8 | ChannelVoice1(crate::channel_voice1::Packet), 9 | #[cfg(feature = "channel-voice2")] 10 | ChannelVoice2(crate::channel_voice2::Packet), 11 | #[cfg(feature = "flex-data")] 12 | FlexData(crate::flex_data::Packet), 13 | #[cfg(feature = "sysex7")] 14 | Sysex7(crate::sysex7::Packet), 15 | #[cfg(feature = "sysex8")] 16 | Sysex8(crate::sysex8::Packet), 17 | #[cfg(feature = "system-common")] 18 | SystemCommon(crate::system_common::Packet), 19 | #[cfg(feature = "ump-stream")] 20 | UmpStream(crate::ump_stream::Packet), 21 | #[cfg(feature = "utility")] 22 | Utility(crate::utility::Packet), 23 | } 24 | 25 | impl core::ops::Deref for Packet { 26 | type Target = [u32]; 27 | fn deref(&self) -> &Self::Target { 28 | match self { 29 | #[cfg(feature = "channel-voice1")] 30 | Self::ChannelVoice1(p) => p.deref(), 31 | #[cfg(feature = "channel-voice2")] 32 | Self::ChannelVoice2(p) => p.deref(), 33 | #[cfg(feature = "flex-data")] 34 | Self::FlexData(p) => p.deref(), 35 | #[cfg(feature = "sysex7")] 36 | Self::Sysex7(p) => p.deref(), 37 | #[cfg(feature = "sysex8")] 38 | Self::Sysex8(p) => p.deref(), 39 | #[cfg(feature = "system-common")] 40 | Self::SystemCommon(p) => p.deref(), 41 | #[cfg(feature = "ump-stream")] 42 | Self::UmpStream(p) => p.deref(), 43 | #[cfg(feature = "utility")] 44 | Self::Utility(p) => p.deref(), 45 | } 46 | } 47 | } 48 | 49 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 50 | type Error = crate::error::InvalidData; 51 | fn try_from(data: &'a [u32]) -> Result { 52 | #[cfg(feature = "channel-voice1")] 53 | use crate::channel_voice1; 54 | #[cfg(feature = "channel-voice2")] 55 | use crate::channel_voice2; 56 | use crate::detail::BitOps; 57 | #[cfg(feature = "flex-data")] 58 | use crate::flex_data; 59 | #[cfg(feature = "sysex7")] 60 | use crate::sysex7; 61 | #[cfg(feature = "sysex8")] 62 | use crate::sysex8; 63 | #[cfg(feature = "system-common")] 64 | use crate::system_common; 65 | #[cfg(feature = "ump-stream")] 66 | use crate::ump_stream; 67 | #[cfg(feature = "utility")] 68 | use crate::utility; 69 | 70 | if data.is_empty() { 71 | return Err(InvalidData(common_err_strings::ERR_SLICE_TOO_SHORT)); 72 | } 73 | 74 | match u8::from(data[0].nibble(0)) { 75 | #[cfg(feature = "channel-voice1")] 76 | channel_voice1::UMP_MESSAGE_TYPE => Ok(channel_voice1::Packet::try_from(data)?.into()), 77 | #[cfg(feature = "channel-voice2")] 78 | channel_voice2::UMP_MESSAGE_TYPE => Ok(channel_voice2::Packet::try_from(data)?.into()), 79 | #[cfg(feature = "flex-data")] 80 | flex_data::UMP_MESSAGE_TYPE => Ok(flex_data::Packet::try_from(data)?.into()), 81 | #[cfg(feature = "sysex7")] 82 | sysex7::UMP_MESSAGE_TYPE => Ok(sysex7::Packet::try_from(data)?.into()), 83 | #[cfg(feature = "sysex8")] 84 | sysex8::UMP_MESSAGE_TYPE => Ok(sysex8::Packet::try_from(data)?.into()), 85 | #[cfg(feature = "system-common")] 86 | system_common::UMP_MESSAGE_TYPE => Ok(system_common::Packet::try_from(data)?.into()), 87 | #[cfg(feature = "ump-stream")] 88 | ump_stream::UMP_MESSAGE_TYPE => Ok(ump_stream::Packet::try_from(data)?.into()), 89 | #[cfg(feature = "utility")] 90 | utility::UMP_MESSAGE_TYPE => Ok(utility::Packet::try_from(data)?.into()), 91 | _ => Err(crate::error::InvalidData( 92 | common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 93 | )), 94 | } 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | use core::ops::Deref; 102 | 103 | #[test] 104 | #[cfg(feature = "utility")] 105 | fn construction() { 106 | let data = [0x0]; 107 | assert_eq!(Packet::try_from(&data[..]).unwrap().deref(), &[0x0]); 108 | } 109 | 110 | #[test] 111 | #[cfg(feature = "sysex8")] 112 | fn construction_from_long_slice() { 113 | let data = [0x5001_0000, 0x0, 0x0, 0x0, 0x0]; 114 | assert_eq!( 115 | &*Packet::try_from(&data[..]).unwrap(), 116 | &[0x5001_0000, 0x0, 0x0, 0x0] 117 | ); 118 | } 119 | 120 | #[test] 121 | fn construction_from_empty_data() { 122 | let data = []; 123 | assert_eq!( 124 | Packet::try_from(&data[..]), 125 | Err(InvalidData(common_err_strings::ERR_SLICE_TOO_SHORT)) 126 | ); 127 | } 128 | 129 | #[test] 130 | fn construction_from_reserved_ump_type_field() { 131 | let data = [0xE000_0000, 0x0, 0x0, 0x0]; 132 | assert_eq!( 133 | Packet::try_from(&data[..]), 134 | Err(InvalidData( 135 | common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 136 | )) 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /midi2/src/packets.rs: -------------------------------------------------------------------------------- 1 | use crate::packet::Packet; 2 | 3 | /// Iterator type for reading the individual packets of a 4 | /// [Ump](crate::buffer::Ump) backed message. 5 | /// 6 | /// Returned from [Packets::packets]. 7 | #[derive(Debug, Clone)] 8 | pub struct PacketsIterator<'a>(pub(crate) core::slice::ChunksExact<'a, u32>); 9 | 10 | impl core::iter::Iterator for PacketsIterator<'_> { 11 | type Item = crate::packet::Packet; 12 | fn next(&mut self) -> Option { 13 | self.0.next().map(|data| Packet::try_from(data).unwrap()) 14 | } 15 | fn nth(&mut self, n: usize) -> Option { 16 | self.0.nth(n).map(|data| Packet::try_from(data).unwrap()) 17 | } 18 | fn count(self) -> usize 19 | where 20 | Self: Sized, 21 | { 22 | self.0.count() 23 | } 24 | fn size_hint(&self) -> (usize, Option) { 25 | self.0.size_hint() 26 | } 27 | } 28 | 29 | impl core::iter::FusedIterator for PacketsIterator<'_> {} 30 | 31 | impl core::iter::ExactSizeIterator for PacketsIterator<'_> { 32 | fn len(&self) -> usize { 33 | self.0.len() 34 | } 35 | } 36 | 37 | /// Read the individual packets of a message represented with UMP packets. 38 | /// 39 | /// ## Basic Usage 40 | /// 41 | /// ```rust 42 | /// use midi2::prelude::*; 43 | /// 44 | /// let mut message = flex_data::ProjectName::>::new(); 45 | /// message.set_text("Shadows of the Forgotten Cathedral"); 46 | /// 47 | /// let mut packets = message.packets(); 48 | /// 49 | /// assert_eq!(&*packets.next().unwrap(), &[0xD0500101, 0x53686164, 0x6F777320, 0x6F662074][..]); 50 | /// assert_eq!(&*packets.next().unwrap(), &[0xD0900101, 0x68652046, 0x6F72676F, 0x7474656E][..]); 51 | /// assert_eq!(&*packets.next().unwrap(), &[0xD0D00101, 0x20436174, 0x68656472, 0x616C0000][..]); 52 | /// assert_eq!(packets.next(), None); 53 | /// ``` 54 | /// 55 | /// Packets may be shorter than 128 bytes for certain messages which are represented by shorter 56 | /// packets. 57 | /// 58 | /// ```rust 59 | /// use midi2::prelude::*; 60 | /// 61 | /// let mut message = sysex7::Sysex7::>::new(); 62 | /// message.set_payload((0..20).map(u7::new)); 63 | /// 64 | /// let mut packets = message.packets(); 65 | /// 66 | /// assert_eq!(&*packets.next().unwrap(), &[0x30160001, 0x2030405][..]); 67 | /// assert_eq!(&*packets.next().unwrap(), &[0x30260607, 0x8090A0B][..]); 68 | /// assert_eq!(&*packets.next().unwrap(), &[0x30260C0D, 0xE0F1011][..]); 69 | /// assert_eq!(&*packets.next().unwrap(), &[0x30321213, 0x0][..]); 70 | /// assert_eq!(packets.next(), None); 71 | /// ``` 72 | pub trait Packets { 73 | fn packets(&self) -> PacketsIterator; 74 | } 75 | -------------------------------------------------------------------------------- /midi2/src/sysex7/README.md: -------------------------------------------------------------------------------- 1 | # System Exclusive 7Bit 2 | 3 | A semantic wrapper type around MIDI system exclusive 7bit data. 4 | 5 | ## Abstract over [Buffer](crate::buffer::Buffer) 6 | 7 | Use it with a [Ump](crate::buffer::Ump) buffer. 8 | 9 | ```rust 10 | use midi2::prelude::*; 11 | 12 | let mut message = sysex7::Sysex7::>::new(); 13 | message.set_payload((0u8..30u8).map(u7::new)); 14 | message.set_group(u4::new(0xA)); 15 | 16 | assert_eq!( 17 | message.data(), 18 | &[ 19 | 0x3A16_0001, 20 | 0x0203_0405, 21 | 0x3A26_0607, 22 | 0x0809_0A0B, 23 | 0x3A26_0C0D, 24 | 0x0E0F_1011, 25 | 0x3A26_1213, 26 | 0x1415_1617, 27 | 0x3A36_1819, 28 | 0x1A1B_1C1D, 29 | ], 30 | ); 31 | ``` 32 | 33 | Or use it with a [Bytes](crate::buffer::Bytes) buffer. 34 | 35 | ```rust 36 | use midi2::prelude::*; 37 | 38 | let mut message = sysex7::Sysex7::>::new(); 39 | message.set_payload((0u8..30u8).map(u7::new)); 40 | 41 | assert_eq!( 42 | message.data(), 43 | &[ 44 | 0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 45 | 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 46 | 0x1B, 0x1C, 0x1D, 0xF7, 47 | ], 48 | ); 49 | ``` 50 | 51 | ## Borrowed workflow 52 | 53 | You can create a [Sysex7] from 54 | borrowed data and avoid copying or allocating if you have the data already existing 55 | in a buffer. 56 | 57 | ```rust 58 | use midi2::prelude::*; 59 | 60 | let buffer = [ 61 | 0x3416_0001_u32, 62 | 0x0203_0405_u32, 63 | 0x3426_0607_u32, 64 | 0x0809_0A0B_u32, 65 | 0x3433_0C0D_u32, 66 | 0x0E00_0000_u32, 67 | ]; 68 | 69 | let borrowed = sysex7::Sysex7::try_from(&buffer[..]) 70 | .expect("Data is valid"); 71 | 72 | assert_eq!( 73 | borrowed.data(), 74 | &[ 75 | 0x3416_0001_u32, 76 | 0x0203_0405_u32, 77 | 0x3426_0607_u32, 78 | 0x0809_0A0B_u32, 79 | 0x3433_0C0D_u32, 80 | 0x0E00_0000_u32, 81 | ], 82 | ); 83 | 84 | // Borrowed messages are immutable and their lifetimes are 85 | // tied to the original buffer. 86 | // 87 | // To create an owned version use the `Rebuffer` traits. 88 | 89 | let mut owned: sysex7::Sysex7::> = borrowed.rebuffer_into(); 90 | owned.set_group(u4::new(0x5)); 91 | 92 | assert_eq!( 93 | owned.data(), 94 | &[ 95 | 0x3516_0001_u32, 96 | 0x0203_0405_u32, 97 | 0x3526_0607_u32, 98 | 0x0809_0A0B_u32, 99 | 0x3533_0C0D_u32, 100 | 0x0E00_0000_u32, 101 | ], 102 | ); 103 | ``` 104 | 105 | ## Fixed size buffers 106 | 107 | Use with fixed size, or fallible buffers. 108 | 109 | ```rust 110 | use midi2::prelude::*; 111 | 112 | let mut message = sysex7::Sysex7::<[u8; 22]>::new(); 113 | 114 | // only fallible methods are available 115 | assert_eq!(message.try_set_payload((0u8..20u8).map(u7::new)), Ok(())); 116 | assert_eq!( 117 | message.data(), 118 | &[ 119 | 0xF0, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 120 | 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0xF7, 121 | ], 122 | ); 123 | 124 | // setting payloads larger than the available size will fail 125 | assert_eq!( 126 | message.try_set_payload((0u8..30u8).map(u7::new)), 127 | Err(midi2::error::BufferOverflow), 128 | ); 129 | assert_eq!(message.data(), &[0xF0, 0xF7]); 130 | ``` 131 | -------------------------------------------------------------------------------- /midi2/src/sysex8/README.md: -------------------------------------------------------------------------------- 1 | # System Exclusive 8Bit 2 | 3 | A semantic wrapper type around MIDI 2.0 system exclusive 8bit data. 4 | 5 | todo 6 | -------------------------------------------------------------------------------- /midi2/src/system_common/README.md: -------------------------------------------------------------------------------- 1 | MIDI System Common and System Real Time messages. 2 | 3 | ## Abstract over [Buffer](crate::buffer::Buffer) 4 | 5 | System Common and System Real Time messages can be represented 6 | with classical MIDI byte arrays or with the MIDI 2.0 Universal 7 | Message Packet formats. 8 | 9 | As such, types in this module are abstract over [Buffer](crate::buffer::Buffer). 10 | 11 | When backed by a buffer with `Unit = u8` the underlying data is in the 12 | byte stream format, and is backwards compatible with classical MIDI 13 | standards. 14 | 15 | ```rust 16 | use midi2::prelude::*; 17 | 18 | let mut message = system_common::SongSelect::<[u8; 3]>::new(); 19 | message.set_song(u7::new(0x42)); 20 | assert_eq!(message.data(), &[0xF3, 0x42]); 21 | ``` 22 | 23 | And when backed by a buffer with `Unit = u32` the underlying data is 24 | encoded into Universal Message Packets. 25 | 26 | ```rust 27 | use midi2::prelude::*; 28 | 29 | let mut message = system_common::SongSelect::<[u32; 4]>::new(); 30 | message.set_song(u7::new(0x42)); 31 | message.set_group(u4::new(0x3)); 32 | assert_eq!(message.data(), &[0x13F3_4200]); 33 | ``` 34 | 35 | ## Fixed Size 36 | 37 | `system_common` messages always fit within an 38 | array of 'u32' size 1 or greater, when represented 39 | with a [Ump](crate::buffer::Ump) buffer. 40 | 41 | ```rust 42 | use midi2::system_common::SongSelect; 43 | 44 | let _ = SongSelect::<[u32; 1]>::new(); 45 | let _ = SongSelect::<[u32; 4]>::new(); 46 | ``` 47 | 48 | `system_common` messages always fit within an 49 | array of 'u8' size 3 or greater, when represented 50 | with a [Bytes](crate::buffer::Bytes) buffer. 51 | 52 | ```rust 53 | use midi2::system_common::SongSelect; 54 | 55 | let _ = SongSelect::<[u8; 3]>::new(); 56 | ``` 57 | -------------------------------------------------------------------------------- /midi2/src/system_common/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{error, system_common}; 2 | 3 | #[derive(Eq, PartialEq, Clone, midi2_proc::Debug)] 4 | pub struct Packet(pub(crate) [u32; 1]); 5 | 6 | impl crate::traits::BufferAccess<[u32; 1]> for Packet { 7 | fn buffer_access(&self) -> &[u32; 1] { 8 | &self.0 9 | } 10 | fn buffer_access_mut(&mut self) -> &mut [u32; 1] 11 | where 12 | [u32; 1]: crate::buffer::BufferMut, 13 | { 14 | &mut self.0 15 | } 16 | } 17 | 18 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 19 | type Error = error::InvalidData; 20 | fn try_from(data: &'a [u32]) -> Result { 21 | if data.is_empty() { 22 | return Err(error::InvalidData( 23 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT, 24 | )); 25 | } 26 | 27 | use crate::detail::BitOps; 28 | if u8::from(data[0].nibble(0)) != system_common::UMP_MESSAGE_TYPE { 29 | return Err(error::InvalidData( 30 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 31 | )); 32 | } 33 | 34 | Ok(Packet({ 35 | let mut buffer = [0x0; 1]; 36 | buffer[0] = data[0]; 37 | buffer 38 | })) 39 | } 40 | } 41 | 42 | impl core::ops::Deref for Packet { 43 | type Target = [u32]; 44 | fn deref(&self) -> &Self::Target { 45 | &self.0[..] 46 | } 47 | } 48 | 49 | impl crate::Grouped<[u32; 1]> for Packet { 50 | fn group(&self) -> crate::ux::u4 { 51 | use crate::detail::BitOps; 52 | self.0[0].nibble(1) 53 | } 54 | fn set_group(&mut self, group: crate::ux::u4) 55 | where 56 | [u32; 1]: crate::buffer::BufferMut, 57 | { 58 | use crate::detail::BitOps; 59 | self.0[0].set_nibble(1, group); 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::*; 66 | use pretty_assertions::assert_eq; 67 | 68 | #[test] 69 | fn construction() { 70 | assert!(Packet::try_from(&[0x1000_0000][..]).is_ok()); 71 | } 72 | 73 | #[test] 74 | fn construction_long_slice() { 75 | assert!(Packet::try_from(&[0x1000_0000, 0x0][..]).is_ok()); 76 | } 77 | 78 | #[test] 79 | fn construction_incorrect_ump_message_type() { 80 | assert_eq!( 81 | Packet::try_from(&[0x0000_0000][..]), 82 | Err(error::InvalidData( 83 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 84 | )), 85 | ); 86 | } 87 | 88 | #[test] 89 | fn construction_short_slice() { 90 | assert_eq!( 91 | Packet::try_from(&[][..]), 92 | Err(error::InvalidData( 93 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT 94 | )), 95 | ); 96 | } 97 | 98 | #[test] 99 | fn group() { 100 | use crate::Grouped; 101 | assert_eq!( 102 | Packet::try_from(&[0x1A00_0000][..]).unwrap().group(), 103 | crate::num::u4::new(0xA), 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /midi2/src/system_common/song_position_pointer.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | system_common::{self, UMP_MESSAGE_TYPE}, 4 | }; 5 | 6 | pub const STATUS: u8 = 0xF2; 7 | 8 | /// MIDI 2.0 Channel Voice Song Position Pointer Message 9 | /// 10 | /// See the [module docs](crate::system_common) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(system_common::SystemCommon), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(2) 16 | )] 17 | struct SongPositionPointer { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(system_common::SystemCommonStatus<{STATUS}>)] 21 | status: (), 22 | #[property(common_properties::GroupProperty)] 23 | group: crate::ux::u4, 24 | #[property(common_properties::HybridSchemaProperty< 25 | crate::ux::u14, 26 | schema::Bytes<0x0, 0x7F, 0x7F>, 27 | schema::Ump<0x0000_7F7F, 0x0, 0x0, 0x0>, 28 | >)] 29 | position: crate::ux::u14, 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use crate::{traits::Grouped, ux::*}; 36 | use pretty_assertions::assert_eq; 37 | 38 | #[test] 39 | fn setters() { 40 | let mut message = SongPositionPointer::<[u32; 4]>::new(); 41 | message.set_group(u4::new(0xA)); 42 | message.set_position(u14::new(0x367D)); 43 | assert_eq!(message, SongPositionPointer([0x1AF2_7D6C, 0x0, 0x0, 0x0]),); 44 | } 45 | 46 | #[test] 47 | fn setters_bytes() { 48 | let mut message = SongPositionPointer::<[u8; 3]>::new(); 49 | message.set_position(u14::new(0x367D)); 50 | assert_eq!(message, SongPositionPointer([0xF2, 0x7D, 0x6C]),); 51 | } 52 | 53 | #[test] 54 | fn group() { 55 | assert_eq!( 56 | SongPositionPointer::try_from(&[0x1AF2_7D6C_u32][..]) 57 | .unwrap() 58 | .group(), 59 | u4::new(0xA), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn position() { 65 | assert_eq!( 66 | SongPositionPointer::try_from(&[0x1AF2_7D6C_u32][..]) 67 | .unwrap() 68 | .position(), 69 | u14::new(0x367D), 70 | ); 71 | } 72 | 73 | #[test] 74 | fn position_bytes() { 75 | assert_eq!( 76 | SongPositionPointer::try_from(&[0xF2_u8, 0x7D, 0x6C][..]) 77 | .unwrap() 78 | .position(), 79 | u14::new(0x367D), 80 | ); 81 | } 82 | 83 | #[test] 84 | fn packets() { 85 | use crate::Packets; 86 | 87 | let message = SongPositionPointer::try_from(&[0x1AF2_7D6C][..]).unwrap(); 88 | 89 | let mut packets = message.packets(); 90 | assert_eq!(&*packets.next().unwrap(), &[0x1AF2_7D6C][..]); 91 | assert_eq!(packets.next(), None); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /midi2/src/system_common/song_select.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | system_common::{self, UMP_MESSAGE_TYPE}, 4 | }; 5 | 6 | pub const STATUS: u8 = 0xF3; 7 | 8 | /// MIDI 2.0 Channel Voice Song Select Message 9 | /// 10 | /// See the [module docs](crate::system_common) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(system_common::SystemCommon), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(2) 16 | )] 17 | struct SongSelect { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(system_common::SystemCommonStatus<{STATUS}>)] 21 | status: (), 22 | #[property(common_properties::GroupProperty)] 23 | group: crate::ux::u4, 24 | #[property(common_properties::HybridSchemaProperty< 25 | crate::ux::u7, 26 | schema::Bytes<0x0, 0x7F, 0x0>, 27 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 28 | >)] 29 | song: crate::ux::u7, 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use crate::{traits::Grouped, ux::*}; 36 | use pretty_assertions::assert_eq; 37 | 38 | #[test] 39 | fn setter() { 40 | let mut message = SongSelect::<[u32; 4]>::new(); 41 | message.set_group(u4::new(0xA)); 42 | message.set_song(u7::new(0x4F)); 43 | assert_eq!(message, SongSelect([0x1AF3_4F00, 0x0, 0x0, 0x0]),); 44 | } 45 | 46 | #[test] 47 | fn setters_bytes() { 48 | let mut message = SongSelect::<[u8; 3]>::new(); 49 | message.set_song(u7::new(0x4F)); 50 | assert_eq!(message, SongSelect([0xF3, 0x4F, 0x0]),); 51 | } 52 | 53 | #[test] 54 | fn group() { 55 | assert_eq!( 56 | SongSelect::try_from(&[0x1AF3_4F00_u32][..]) 57 | .unwrap() 58 | .group(), 59 | u4::new(0xA), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn song() { 65 | assert_eq!( 66 | SongSelect::try_from(&[0x1AF3_4F00_u32][..]).unwrap().song(), 67 | u7::new(0x4F), 68 | ); 69 | } 70 | 71 | #[test] 72 | fn song_bytes() { 73 | assert_eq!( 74 | SongSelect::try_from(&[0xF3_u8, 0x4F][..]).unwrap().song(), 75 | u7::new(0x4F), 76 | ) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /midi2/src/system_common/time_code.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | system_common::{self, UMP_MESSAGE_TYPE}, 4 | }; 5 | 6 | pub const STATUS: u8 = 0xF1; 7 | 8 | /// MIDI 2.0 Channel Voice Time Code Message 9 | /// 10 | /// See the [module docs](crate::system_common) for more info. 11 | #[midi2_proc::generate_message( 12 | Via(system_common::SystemCommon), 13 | FixedSize, 14 | MinSizeUmp(1), 15 | MinSizeBytes(3) 16 | )] 17 | struct TimeCode { 18 | #[property(common_properties::UmpMessageTypeProperty)] 19 | ump_type: (), 20 | #[property(system_common::SystemCommonStatus<{STATUS}>)] 21 | status: (), 22 | #[property(common_properties::GroupProperty)] 23 | group: crate::ux::u4, 24 | #[property(common_properties::HybridSchemaProperty< 25 | crate::ux::u7, 26 | schema::Bytes<0x0, 0x7F, 0x0>, 27 | schema::Ump<0x0000_7F00, 0x0, 0x0, 0x0>, 28 | >)] 29 | time_code: crate::ux::u7, 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use crate::{traits::Grouped, ux::*}; 36 | use pretty_assertions::assert_eq; 37 | 38 | #[test] 39 | fn setter() { 40 | let mut message = TimeCode::<[u32; 4]>::new(); 41 | message.set_group(u4::new(0x5)); 42 | message.set_time_code(u7::new(0x5F)); 43 | assert_eq!(message, TimeCode([0x15F1_5F00, 0x0, 0x0, 0x0]),); 44 | } 45 | 46 | #[test] 47 | fn setters_bytes() { 48 | let mut message = TimeCode::<[u8; 3]>::new(); 49 | message.set_time_code(u7::new(0x5F)); 50 | assert_eq!(message, TimeCode([0xF1, 0x5F, 0x0,]),); 51 | } 52 | 53 | #[test] 54 | fn group() { 55 | assert_eq!( 56 | TimeCode::try_from(&[0x15F1_5F00_u32][..]).unwrap().group(), 57 | u4::new(0x5), 58 | ); 59 | } 60 | 61 | #[test] 62 | fn time_code() { 63 | assert_eq!( 64 | TimeCode::try_from(&[0x15F1_5F00_u32][..]) 65 | .unwrap() 66 | .time_code(), 67 | u7::new(0x5F), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/device_identity.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | ump_stream, 4 | ump_stream::UMP_MESSAGE_TYPE, 5 | ux::{u14, u7}, 6 | }; 7 | 8 | pub(crate) const STATUS: u16 = 0x2; 9 | 10 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(4))] 11 | struct DeviceIdentity { 12 | #[property(common_properties::UmpMessageTypeProperty)] 13 | ump_type: (), 14 | #[property(ump_stream::StatusProperty)] 15 | status: (), 16 | #[property(ump_stream::ConsistentFormatsProperty)] 17 | consistent_formats: (), 18 | #[property(common_properties::UmpSchemaProperty<[u7; 3], schema::Ump<0x0, 0x007F_7F7F, 0x0, 0x0>>)] 19 | device_manufacturer: [u7; 3], 20 | #[property(common_properties::UmpSchemaProperty>)] 21 | device_family: u14, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | device_family_model_number: u14, 24 | #[property(common_properties::UmpSchemaProperty<[u7; 4], schema::Ump<0x0, 0x0, 0x0, 0x7F7F_7F7F>>)] 25 | software_version: [u7; 4], 26 | } 27 | 28 | impl schema::UmpSchemaRepr> for [crate::ux::u7; 3] { 29 | fn write(buffer: &mut [u32], value: Self) { 30 | use crate::detail::BitOps; 31 | buffer[1].set_septet(1, value[0]); 32 | buffer[1].set_septet(2, value[1]); 33 | buffer[1].set_septet(3, value[2]); 34 | } 35 | fn read(buffer: &[u32]) -> Self { 36 | use crate::detail::BitOps; 37 | [ 38 | buffer[1].septet(1), 39 | buffer[1].septet(2), 40 | buffer[1].septet(3), 41 | ] 42 | } 43 | } 44 | 45 | impl schema::UmpSchemaRepr> for [crate::ux::u7; 4] { 46 | fn write(buffer: &mut [u32], value: Self) { 47 | use crate::detail::BitOps; 48 | buffer[3].set_septet(0, value[0]); 49 | buffer[3].set_septet(1, value[1]); 50 | buffer[3].set_septet(2, value[2]); 51 | buffer[3].set_septet(3, value[3]); 52 | } 53 | fn read(buffer: &[u32]) -> Self { 54 | use crate::detail::BitOps; 55 | [ 56 | buffer[3].septet(0), 57 | buffer[3].septet(1), 58 | buffer[3].septet(2), 59 | buffer[3].septet(3), 60 | ] 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::*; 67 | use pretty_assertions::assert_eq; 68 | 69 | #[test] 70 | fn builder() { 71 | let mut message = DeviceIdentity::<[u32; 4]>::new(); 72 | message.set_device_manufacturer([u7::new(0x0F), u7::new(0x33), u7::new(0x28)]); 73 | message.set_device_family(u14::new(0xF4A)); 74 | message.set_device_family_model_number(u14::new(0x3818)); 75 | message.set_software_version([u7::new(0x43), u7::new(0x54), u7::new(0x32), u7::new(0x1)]); 76 | assert_eq!( 77 | message, 78 | DeviceIdentity([0xF002_0000, 0x000f_3328, 0x4A1E_1870, 0x4354_3201,]), 79 | ); 80 | } 81 | 82 | #[test] 83 | fn device_manufacturer() { 84 | assert_eq!( 85 | DeviceIdentity::try_from(&[0xF002_0000, 0x000F_3328, 0x4A1E_1870, 0x4354_3201][..]) 86 | .unwrap() 87 | .device_manufacturer(), 88 | [u7::new(0x0F), u7::new(0x33), u7::new(0x28)], 89 | ); 90 | } 91 | 92 | #[test] 93 | fn device_family() { 94 | assert_eq!( 95 | DeviceIdentity::try_from(&[0xF002_0000, 0x000F_3328, 0x4A1E_1870, 0x4354_3201][..]) 96 | .unwrap() 97 | .device_family(), 98 | u14::new(0xF4A), 99 | ); 100 | } 101 | 102 | #[test] 103 | fn device_family_model_number() { 104 | assert_eq!( 105 | DeviceIdentity::try_from(&[0xF002_0000, 0x000F_3328, 0x4A1E_1870, 0x4354_3201][..]) 106 | .unwrap() 107 | .device_family_model_number(), 108 | u14::new(0x3818), 109 | ); 110 | } 111 | 112 | #[test] 113 | fn software_version() { 114 | assert_eq!( 115 | DeviceIdentity::try_from(&[0xF002_0000, 0x000F_3328, 0x4A1E_1870, 0x4354_3201][..]) 116 | .unwrap() 117 | .software_version(), 118 | [u7::new(0x43), u7::new(0x54), u7::new(0x32), u7::new(0x1)], 119 | ); 120 | } 121 | 122 | #[test] 123 | fn packets() { 124 | use crate::Packets; 125 | 126 | let message = 127 | DeviceIdentity::try_from(&[0xF002_0000, 0x000F_3328, 0x4A1E_1870, 0x4354_3201][..]) 128 | .unwrap(); 129 | let mut packets = message.packets(); 130 | 131 | assert_eq!( 132 | &*packets.next().unwrap(), 133 | &[0xF002_0000, 0x000F_3328, 0x4A1E_1870, 0x4354_3201][..], 134 | ); 135 | assert_eq!(packets.next(), None); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/end_of_clip.rs: -------------------------------------------------------------------------------- 1 | use crate::{detail::common_properties, ump_stream, ump_stream::UMP_MESSAGE_TYPE}; 2 | 3 | pub(crate) const STATUS: u16 = 0x21; 4 | 5 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(1))] 6 | struct EndOfClip { 7 | #[property(common_properties::UmpMessageTypeProperty)] 8 | ump_type: (), 9 | #[property(ump_stream::StatusProperty)] 10 | status: (), 11 | #[property(ump_stream::ConsistentFormatsProperty)] 12 | consistent_formats: (), 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | use pretty_assertions::assert_eq; 19 | 20 | #[test] 21 | fn setters() { 22 | assert_eq!( 23 | EndOfClip::<[u32; 4]>::new(), 24 | EndOfClip([0xF021_0000, 0x0, 0x0, 0x0]) 25 | ); 26 | } 27 | 28 | #[test] 29 | fn from_data() { 30 | assert_eq!( 31 | EndOfClip::try_from(&[0xF021_0000][..]), 32 | Ok(EndOfClip(&[0xF021_0000][..])) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/endpoint_discovery.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | ump_stream, 4 | ump_stream::UMP_MESSAGE_TYPE, 5 | }; 6 | 7 | pub(crate) const STATUS: u16 = 0x0; 8 | 9 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(2))] 10 | struct EndpointDiscovery { 11 | #[property(common_properties::UmpMessageTypeProperty)] 12 | ump_type: (), 13 | #[property(ump_stream::StatusProperty)] 14 | status: (), 15 | #[property(ump_stream::ConsistentFormatsProperty)] 16 | consistent_formats: (), 17 | #[property(common_properties::UmpSchemaProperty>)] 18 | ump_version_major: u8, 19 | #[property(common_properties::UmpSchemaProperty>)] 20 | ump_version_minor: u8, 21 | #[property(common_properties::UmpSchemaProperty>)] 22 | request_endpoint_info: bool, 23 | #[property(common_properties::UmpSchemaProperty>)] 24 | request_device_identity: bool, 25 | #[property(common_properties::UmpSchemaProperty>)] 26 | request_endpoint_name: bool, 27 | #[property(common_properties::UmpSchemaProperty>)] 28 | request_product_instance_id: bool, 29 | #[property(common_properties::UmpSchemaProperty>)] 30 | request_stream_configuration: bool, 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use super::*; 36 | use pretty_assertions::assert_eq; 37 | 38 | #[test] 39 | fn builder() { 40 | let mut message = EndpointDiscovery::<[u32; 4]>::new(); 41 | message.set_ump_version_major(0x1); 42 | message.set_ump_version_minor(0x1); 43 | message.set_request_endpoint_info(true); 44 | message.set_request_device_identity(true); 45 | message.set_request_endpoint_name(true); 46 | message.set_request_product_instance_id(true); 47 | message.set_request_stream_configuration(true); 48 | assert_eq!( 49 | message, 50 | EndpointDiscovery([0xF000_0101, 0x0000_001F, 0x0, 0x0]), 51 | ); 52 | } 53 | 54 | #[test] 55 | fn ump_version_major() { 56 | assert_eq!( 57 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 58 | .unwrap() 59 | .ump_version_major(), 60 | 0x1, 61 | ); 62 | } 63 | 64 | #[test] 65 | fn ump_version_minor() { 66 | assert_eq!( 67 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 68 | .unwrap() 69 | .ump_version_minor(), 70 | 0x1, 71 | ); 72 | } 73 | 74 | #[test] 75 | fn request_endpoint_info() { 76 | assert_eq!( 77 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 78 | .unwrap() 79 | .request_endpoint_info(), 80 | true, 81 | ); 82 | } 83 | 84 | #[test] 85 | fn request_device_identity() { 86 | assert_eq!( 87 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 88 | .unwrap() 89 | .request_device_identity(), 90 | true, 91 | ); 92 | } 93 | 94 | #[test] 95 | fn request_endpoint_name() { 96 | assert_eq!( 97 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 98 | .unwrap() 99 | .request_endpoint_name(), 100 | true, 101 | ); 102 | } 103 | 104 | #[test] 105 | fn request_product_instance_id() { 106 | assert_eq!( 107 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 108 | .unwrap() 109 | .request_product_instance_id(), 110 | true, 111 | ); 112 | } 113 | 114 | #[test] 115 | fn request_stream_configuration() { 116 | assert_eq!( 117 | EndpointDiscovery::try_from(&[0xF000_0101, 0x0000_001F][..]) 118 | .unwrap() 119 | .request_stream_configuration(), 120 | true, 121 | ); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/endpoint_info.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | ump_stream, 4 | ump_stream::UMP_MESSAGE_TYPE, 5 | ux::u7, 6 | }; 7 | 8 | pub(crate) const STATUS: u16 = 0x01; 9 | 10 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(2))] 11 | struct EndpointInfo { 12 | #[property(common_properties::UmpMessageTypeProperty)] 13 | ump_type: (), 14 | #[property(ump_stream::StatusProperty)] 15 | status: (), 16 | #[property(ump_stream::ConsistentFormatsProperty)] 17 | consistent_formats: (), 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | ump_version_major: u8, 20 | #[property(common_properties::UmpSchemaProperty>)] 21 | ump_version_minor: u8, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | static_function_blocks: bool, 24 | #[property(common_properties::UmpSchemaProperty>)] 25 | supports_midi2_protocol: bool, 26 | #[property(common_properties::UmpSchemaProperty>)] 27 | supports_midi1_protocol: bool, 28 | #[property(common_properties::UmpSchemaProperty>)] 29 | supports_receiving_jr_timestamps: bool, 30 | #[property(common_properties::UmpSchemaProperty>)] 31 | supports_sending_jr_timestamps: bool, 32 | #[property(common_properties::UmpSchemaProperty>)] 33 | number_of_function_blocks: u7, 34 | } 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | use pretty_assertions::assert_eq; 40 | 41 | #[test] 42 | fn builder() { 43 | let mut message = EndpointInfo::<[u32; 4]>::new(); 44 | message.set_ump_version_major(0x1); 45 | message.set_ump_version_minor(0x1); 46 | message.set_static_function_blocks(true); 47 | message.set_number_of_function_blocks(u7::new(0x20)); 48 | message.set_supports_midi2_protocol(true); 49 | message.set_supports_midi1_protocol(true); 50 | message.set_supports_sending_jr_timestamps(true); 51 | message.set_supports_receiving_jr_timestamps(true); 52 | 53 | assert_eq!( 54 | message, 55 | EndpointInfo([ 56 | 0xF001_0101, 57 | 0b1010_0000_0000_0000_0000_0011_0000_0011, 58 | 0x0, 59 | 0x0 60 | ]) 61 | ); 62 | } 63 | 64 | #[test] 65 | fn ump_version_major() { 66 | assert_eq!( 67 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 68 | .unwrap() 69 | .ump_version_major(), 70 | 0x1, 71 | ); 72 | } 73 | 74 | #[test] 75 | fn ump_version_minor() { 76 | assert_eq!( 77 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 78 | .unwrap() 79 | .ump_version_minor(), 80 | 0x1, 81 | ); 82 | } 83 | 84 | #[test] 85 | fn static_function_blocks() { 86 | assert_eq!( 87 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 88 | .unwrap() 89 | .static_function_blocks(), 90 | true, 91 | ); 92 | } 93 | 94 | #[test] 95 | fn number_of_function_blocks() { 96 | assert_eq!( 97 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 98 | .unwrap() 99 | .number_of_function_blocks(), 100 | u7::new(0x20), 101 | ); 102 | } 103 | 104 | #[test] 105 | fn supports_midi2_protocol() { 106 | assert_eq!( 107 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 108 | .unwrap() 109 | .supports_midi2_protocol(), 110 | true, 111 | ); 112 | } 113 | 114 | #[test] 115 | fn supports_midi1_protocol() { 116 | assert_eq!( 117 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 118 | .unwrap() 119 | .supports_midi1_protocol(), 120 | true, 121 | ); 122 | } 123 | 124 | #[test] 125 | fn supports_sending_jr_timestamps() { 126 | assert_eq!( 127 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 128 | .unwrap() 129 | .supports_sending_jr_timestamps(), 130 | true, 131 | ); 132 | } 133 | 134 | #[test] 135 | fn supports_receiving_jr_timestamps() { 136 | assert_eq!( 137 | EndpointInfo::try_from(&[0xF001_0101, 0b1010_0000_0000_0000_0000_0011_0000_0011,][..]) 138 | .unwrap() 139 | .supports_receiving_jr_timestamps(), 140 | true, 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/endpoint_name.rs: -------------------------------------------------------------------------------- 1 | use crate::{detail::common_properties, ump_stream, ump_stream::UMP_MESSAGE_TYPE}; 2 | 3 | pub(crate) const STATUS: u16 = 0x3; 4 | 5 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), MinSizeUmp(4))] 6 | struct EndpointName { 7 | #[property(common_properties::UmpMessageTypeProperty)] 8 | ump_type: (), 9 | #[property(ump_stream::StatusProperty)] 10 | status: (), 11 | #[property(ump_stream::ConsistentFormatsProperty)] 12 | consistent_formats: (), 13 | #[property(ump_stream::TextWriteStrProperty<0>)] 14 | #[writeonly] 15 | #[resize] 16 | name: &str, 17 | #[property(ump_stream::TextReadBytesProperty)] 18 | #[readonly] 19 | name_bytes: ump_stream::TextBytesIterator, 20 | #[property(ump_stream::TextReadStringProperty)] 21 | #[readonly] 22 | #[std] 23 | name: std::string::String, 24 | } 25 | 26 | impl crate::traits::Size for EndpointName { 27 | fn size(&self) -> usize { 28 | ump_stream::message_size(&self.0) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use pretty_assertions::assert_eq; 36 | 37 | #[test] 38 | fn data() { 39 | use crate::traits::Data; 40 | assert_eq!( 41 | EndpointName::try_from( 42 | &[ 43 | 0xF403_4769, 44 | 0x6D6D_6520, 45 | 0x736F_6D65, 46 | 0x2073_6967, 47 | 0xFC03_6E61, 48 | 0x6C20_F09F, 49 | 0x948A_20F0, 50 | 0x9F99_8C00, 51 | ][..] 52 | ) 53 | .unwrap() 54 | .data(), 55 | &[ 56 | 0xF403_4769, 57 | 0x6D6D_6520, 58 | 0x736F_6D65, 59 | 0x2073_6967, 60 | 0xFC03_6E61, 61 | 0x6C20_F09F, 62 | 0x948A_20F0, 63 | 0x9F99_8C00, 64 | ] 65 | ); 66 | } 67 | 68 | #[test] 69 | fn set_name_and_clear_name() { 70 | let mut message = EndpointName::>::new(); 71 | message.set_name("Gimme some signal 🔊 🙌"); 72 | message.set_name(""); 73 | assert_eq!( 74 | message, 75 | EndpointName(std::vec![ 76 | 0xF003_0000, 77 | 0x0000_0000, 78 | 0x0000_0000, 79 | 0x0000_0000, 80 | ]), 81 | ); 82 | } 83 | 84 | #[test] 85 | #[cfg(feature = "std")] 86 | fn get_name() { 87 | let buffer = [ 88 | 0xF403_4769, 89 | 0x6D6D_6520, 90 | 0x736F_6D65, 91 | 0x2073_6967, 92 | 0xFC03_6E61, 93 | 0x6C20_F09F, 94 | 0x948A_20F0, 95 | 0x9F99_8C00, 96 | ]; 97 | let message = EndpointName::try_from(&buffer[..]).unwrap(); 98 | assert_eq!(message.name(), "Gimme some signal 🔊 🙌"); 99 | } 100 | 101 | #[test] 102 | fn read_empty_bytes() { 103 | assert_eq!( 104 | EndpointName::>::new() 105 | .name_bytes() 106 | .collect::>(), 107 | std::vec::Vec::::new(), 108 | ); 109 | } 110 | 111 | #[test] 112 | fn packets() { 113 | use crate::Packets; 114 | let message = EndpointName::try_from( 115 | &[ 116 | 0xF403_4769, 117 | 0x6D6D_6520, 118 | 0x736F_6D65, 119 | 0x2073_6967, 120 | 0xFC03_6E61, 121 | 0x6C20_F09F, 122 | 0x948A_20F0, 123 | 0x9F99_8C00, 124 | ][..], 125 | ) 126 | .unwrap(); 127 | let mut packets = message.packets(); 128 | 129 | assert_eq!( 130 | &*packets.next().unwrap(), 131 | &[0xF403_4769, 0x6D6D_6520, 0x736F_6D65, 0x2073_6967,][..], 132 | ); 133 | assert_eq!( 134 | &*packets.next().unwrap(), 135 | &[0xFC03_6E61, 0x6C20_F09F, 0x948A_20F0, 0x9F99_8C00,][..], 136 | ); 137 | assert_eq!(packets.next(), None); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/function_block_discovery.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | ump_stream, 4 | ump_stream::UMP_MESSAGE_TYPE, 5 | }; 6 | 7 | pub(crate) const STATUS: u16 = 0x10; 8 | 9 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(1))] 10 | struct FunctionBlockDiscovery { 11 | #[property(common_properties::UmpMessageTypeProperty)] 12 | ump_type: (), 13 | #[property(ump_stream::StatusProperty)] 14 | status: (), 15 | #[property(ump_stream::ConsistentFormatsProperty)] 16 | consistent_formats: (), 17 | 18 | #[property(common_properties::UmpSchemaProperty>)] 19 | function_block_number: u8, 20 | #[property(common_properties::UmpSchemaProperty>)] 21 | requesting_function_block_info: bool, 22 | #[property(common_properties::UmpSchemaProperty>)] 23 | requesting_function_block_name: bool, 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | use pretty_assertions::assert_eq; 30 | 31 | #[test] 32 | fn setters() { 33 | let mut message = FunctionBlockDiscovery::<[u32; 4]>::new(); 34 | message.set_function_block_number(0x09); 35 | message.set_requesting_function_block_info(true); 36 | message.set_requesting_function_block_name(true); 37 | assert_eq!( 38 | message, 39 | FunctionBlockDiscovery([0xF010_0903, 0x0, 0x0, 0x0]) 40 | ); 41 | } 42 | 43 | #[test] 44 | fn function_block_number() { 45 | assert_eq!( 46 | FunctionBlockDiscovery::try_from(&[0xF010_0903][..]) 47 | .unwrap() 48 | .function_block_number(), 49 | 0x09, 50 | ); 51 | } 52 | 53 | #[test] 54 | fn requesting_function_block_info() { 55 | assert_eq!( 56 | FunctionBlockDiscovery::try_from(&[0xF010_0903][..]) 57 | .unwrap() 58 | .requesting_function_block_info(), 59 | true 60 | ); 61 | } 62 | 63 | #[test] 64 | fn requesting_function_block_name() { 65 | assert_eq!( 66 | FunctionBlockDiscovery::try_from(&[0xF010_0903][..]) 67 | .unwrap() 68 | .requesting_function_block_name(), 69 | true 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{error, ump_stream}; 2 | 3 | #[derive(Eq, PartialEq, Clone, midi2_proc::Debug)] 4 | pub struct Packet(pub(crate) [u32; 4]); 5 | 6 | impl crate::traits::BufferAccess<[u32; 4]> for Packet { 7 | fn buffer_access(&self) -> &[u32; 4] { 8 | &self.0 9 | } 10 | fn buffer_access_mut(&mut self) -> &mut [u32; 4] 11 | where 12 | [u32; 4]: crate::buffer::BufferMut, 13 | { 14 | &mut self.0 15 | } 16 | } 17 | 18 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 19 | type Error = error::InvalidData; 20 | fn try_from(data: &'a [u32]) -> Result { 21 | if data.is_empty() { 22 | return Err(error::InvalidData( 23 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT, 24 | )); 25 | } 26 | 27 | use crate::detail::BitOps; 28 | if u8::from(data[0].nibble(0)) != ump_stream::UMP_MESSAGE_TYPE { 29 | return Err(error::InvalidData( 30 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 31 | )); 32 | } 33 | 34 | Ok(Packet({ 35 | let mut buffer = [0x0; 4]; 36 | let sz = 4.min(data.len()); 37 | buffer[..sz].copy_from_slice(&data[..sz]); 38 | buffer 39 | })) 40 | } 41 | } 42 | 43 | impl core::ops::Deref for Packet { 44 | type Target = [u32]; 45 | fn deref(&self) -> &Self::Target { 46 | &self.0[..] 47 | } 48 | } 49 | 50 | #[derive(Debug, Clone, Eq, PartialEq)] 51 | pub enum Format { 52 | Complete, 53 | Start, 54 | Continue, 55 | End, 56 | } 57 | 58 | impl Packet { 59 | pub fn format(&self) -> Format { 60 | format_from_data(&self.0[..]) 61 | } 62 | } 63 | 64 | fn format_from_data(data: &[u32]) -> Format { 65 | use crate::detail::BitOps; 66 | use Format::*; 67 | match u8::from(data[0].crumb(2)) { 68 | 0x0 => Complete, 69 | 0x1 => Start, 70 | 0x2 => Continue, 71 | 0x3 => End, 72 | _ => unreachable!(), 73 | } 74 | } 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | use pretty_assertions::assert_eq; 80 | 81 | #[test] 82 | fn construction() { 83 | assert!(Packet::try_from(&[0xF000_0000][..]).is_ok()) 84 | } 85 | 86 | #[test] 87 | fn construction_long_slice() { 88 | assert!(Packet::try_from(&[0xF000_0000, 0x0, 0x0, 0x0][..]).is_ok()) 89 | } 90 | 91 | #[test] 92 | fn construction_very_long_slice() { 93 | assert!(Packet::try_from(&[0xF000_0000, 0x0, 0x0, 0x0, 0x0][..]).is_ok()) 94 | } 95 | 96 | #[test] 97 | fn construction_short_slice() { 98 | assert_eq!( 99 | Packet::try_from(&[][..]), 100 | Err(error::InvalidData( 101 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT 102 | )), 103 | ) 104 | } 105 | 106 | #[test] 107 | fn construction_incorrect_ump_message_type() { 108 | assert_eq!( 109 | Packet::try_from(&[0x0000_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]), 110 | Err(error::InvalidData( 111 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 112 | )), 113 | ) 114 | } 115 | 116 | #[test] 117 | fn complete_format() { 118 | assert_eq!( 119 | Packet::try_from(&[0xF000_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 120 | .unwrap() 121 | .format(), 122 | Format::Complete, 123 | ) 124 | } 125 | 126 | #[test] 127 | fn start_format() { 128 | assert_eq!( 129 | Packet::try_from(&[0xF400_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 130 | .unwrap() 131 | .format(), 132 | Format::Start, 133 | ) 134 | } 135 | 136 | #[test] 137 | fn continue_format() { 138 | assert_eq!( 139 | Packet::try_from(&[0xF800_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 140 | .unwrap() 141 | .format(), 142 | Format::Continue, 143 | ) 144 | } 145 | 146 | #[test] 147 | fn end_format() { 148 | assert_eq!( 149 | Packet::try_from(&[0xFC00_0000, 0x0000_0000, 0x0000_0000, 0x0000_0000][..]) 150 | .unwrap() 151 | .format(), 152 | Format::End, 153 | ) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/product_instance_id.rs: -------------------------------------------------------------------------------- 1 | use crate::{detail::common_properties, ump_stream, ump_stream::UMP_MESSAGE_TYPE}; 2 | 3 | pub(crate) const STATUS: u16 = 0x4; 4 | 5 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), MinSizeUmp(4))] 6 | struct ProductInstanceId { 7 | #[property(common_properties::UmpMessageTypeProperty)] 8 | ump_type: (), 9 | #[property(ump_stream::StatusProperty)] 10 | status: (), 11 | #[property(ump_stream::ConsistentFormatsProperty)] 12 | consistent_formats: (), 13 | #[property(ump_stream::TextWriteStrProperty<0>)] 14 | #[writeonly] 15 | #[resize] 16 | id: &str, 17 | #[property(ump_stream::TextReadBytesProperty)] 18 | #[readonly] 19 | id_bytes: ump_stream::TextBytesIterator, 20 | #[property(ump_stream::TextReadStringProperty)] 21 | #[readonly] 22 | #[std] 23 | id: std::string::String, 24 | } 25 | 26 | impl crate::traits::Size for ProductInstanceId { 27 | fn size(&self) -> usize { 28 | ump_stream::message_size(&self.0) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | use pretty_assertions::assert_eq; 36 | 37 | #[test] 38 | fn set_id() { 39 | let mut message = ProductInstanceId::>::new(); 40 | message.set_id("PianoPulse"); 41 | assert_eq!( 42 | message, 43 | ProductInstanceId(std::vec![ 44 | 0xF004_5069, 45 | 0x616E_6F50, 46 | 0x756C_7365, 47 | 0x0000_0000 48 | ]), 49 | ) 50 | } 51 | 52 | #[test] 53 | #[cfg(feature = "std")] 54 | fn id() { 55 | assert_eq!( 56 | ProductInstanceId::try_from(&[0xF004_5069, 0x616E_6F50, 0x756C_7365, 0x0000_0000,][..]) 57 | .unwrap() 58 | .id(), 59 | "PianoPulse", 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/start_of_clip.rs: -------------------------------------------------------------------------------- 1 | use crate::{detail::common_properties, ump_stream, ump_stream::UMP_MESSAGE_TYPE}; 2 | 3 | pub(crate) const STATUS: u16 = 0x20; 4 | 5 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(1))] 6 | struct StartOfClip { 7 | #[property(common_properties::UmpMessageTypeProperty)] 8 | ump_type: (), 9 | #[property(ump_stream::StatusProperty)] 10 | status: (), 11 | #[property(ump_stream::ConsistentFormatsProperty)] 12 | consistent_formats: (), 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::*; 18 | use pretty_assertions::assert_eq; 19 | 20 | #[test] 21 | fn builder() { 22 | assert_eq!( 23 | StartOfClip::<[u32; 4]>::new(), 24 | StartOfClip([0xF020_0000, 0x0, 0x0, 0x0]), 25 | ); 26 | } 27 | 28 | #[test] 29 | fn from_data() { 30 | assert_eq!( 31 | StartOfClip::try_from(&[0xF020_0000][..]), 32 | Ok(StartOfClip(&[0xF020_0000][..])), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/stream_configuration_notification.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | ump_stream, 4 | ump_stream::UMP_MESSAGE_TYPE, 5 | }; 6 | 7 | pub(crate) const STATUS: u16 = 0x6; 8 | 9 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(1))] 10 | struct StreamConfigurationNotification { 11 | #[property(common_properties::UmpMessageTypeProperty)] 12 | ump_type: (), 13 | #[property(ump_stream::StatusProperty)] 14 | status: (), 15 | #[property(ump_stream::ConsistentFormatsProperty)] 16 | consistent_formats: (), 17 | #[property(common_properties::UmpSchemaProperty>)] 18 | protocol: u8, 19 | #[property(common_properties::UmpSchemaProperty>)] 20 | receive_jr_timestamps: bool, 21 | #[property(common_properties::UmpSchemaProperty>)] 22 | send_jr_timestamps: bool, 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | use pretty_assertions::assert_eq; 29 | 30 | #[test] 31 | fn builder() { 32 | let mut message = StreamConfigurationNotification::<[u32; 4]>::new(); 33 | message.set_protocol(0x2); 34 | message.set_receive_jr_timestamps(true); 35 | message.set_send_jr_timestamps(true); 36 | assert_eq!( 37 | message, 38 | StreamConfigurationNotification([0xF006_0203, 0x0, 0x0, 0x0,]), 39 | ); 40 | } 41 | 42 | #[test] 43 | fn protocol() { 44 | assert_eq!( 45 | StreamConfigurationNotification::try_from(&[0xF006_0203][..]) 46 | .unwrap() 47 | .protocol(), 48 | 0x2 49 | ); 50 | } 51 | 52 | #[test] 53 | fn receive_jr_timestamps() { 54 | assert_eq!( 55 | StreamConfigurationNotification::try_from(&[0xF006_0203][..]) 56 | .unwrap() 57 | .receive_jr_timestamps(), 58 | true 59 | ); 60 | } 61 | 62 | #[test] 63 | fn send_jr_timestamps() { 64 | assert_eq!( 65 | StreamConfigurationNotification::try_from(&[0xF006_0203][..]) 66 | .unwrap() 67 | .send_jr_timestamps(), 68 | true 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /midi2/src/ump_stream/stream_configuration_request.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | detail::{common_properties, schema}, 3 | ump_stream, 4 | ump_stream::UMP_MESSAGE_TYPE, 5 | }; 6 | 7 | pub(crate) const STATUS: u16 = 0x5; 8 | 9 | #[midi2_proc::generate_message(Via(ump_stream::UmpStream), FixedSize, MinSizeUmp(1))] 10 | struct StreamConfigurationRequest { 11 | #[property(common_properties::UmpMessageTypeProperty)] 12 | ump_type: (), 13 | #[property(ump_stream::StatusProperty)] 14 | status: (), 15 | #[property(ump_stream::ConsistentFormatsProperty)] 16 | consistent_formats: (), 17 | #[property(common_properties::UmpSchemaProperty>)] 18 | protocol: u8, 19 | #[property(common_properties::UmpSchemaProperty>)] 20 | receive_jr_timestamps: bool, 21 | #[property(common_properties::UmpSchemaProperty>)] 22 | send_jr_timestamps: bool, 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::*; 28 | use pretty_assertions::assert_eq; 29 | 30 | #[test] 31 | fn builder() { 32 | let mut message = StreamConfigurationRequest::<[u32; 4]>::new(); 33 | message.set_protocol(0x2); 34 | message.set_receive_jr_timestamps(true); 35 | message.set_send_jr_timestamps(true); 36 | assert_eq!( 37 | message, 38 | StreamConfigurationRequest([0xF005_0203, 0x0, 0x0, 0x0,]), 39 | ); 40 | } 41 | 42 | #[test] 43 | fn protocol() { 44 | assert_eq!( 45 | StreamConfigurationRequest::try_from(&[0xF005_0203][..]) 46 | .unwrap() 47 | .protocol(), 48 | 0x2 49 | ); 50 | } 51 | 52 | #[test] 53 | fn receive_jr_timestamps() { 54 | assert_eq!( 55 | StreamConfigurationRequest::try_from(&[0xF005_0203][..]) 56 | .unwrap() 57 | .receive_jr_timestamps(), 58 | true 59 | ); 60 | } 61 | 62 | #[test] 63 | fn send_jr_timestamps() { 64 | assert_eq!( 65 | StreamConfigurationRequest::try_from(&[0xF005_0203][..]) 66 | .unwrap() 67 | .send_jr_timestamps(), 68 | true 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /midi2/src/utility/packet.rs: -------------------------------------------------------------------------------- 1 | use crate::{error, utility}; 2 | 3 | #[derive(Eq, PartialEq, Clone, midi2_proc::Debug)] 4 | pub struct Packet(pub(crate) [u32; 1]); 5 | 6 | impl crate::traits::BufferAccess<[u32; 1]> for Packet { 7 | fn buffer_access(&self) -> &[u32; 1] { 8 | &self.0 9 | } 10 | fn buffer_access_mut(&mut self) -> &mut [u32; 1] 11 | where 12 | [u32; 1]: crate::buffer::BufferMut, 13 | { 14 | &mut self.0 15 | } 16 | } 17 | 18 | impl<'a> core::convert::TryFrom<&'a [u32]> for Packet { 19 | type Error = error::InvalidData; 20 | fn try_from(data: &'a [u32]) -> Result { 21 | if data.is_empty() { 22 | return Err(error::InvalidData( 23 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT, 24 | )); 25 | } 26 | 27 | use crate::detail::BitOps; 28 | if u8::from(data[0].nibble(0)) != utility::UMP_MESSAGE_TYPE { 29 | return Err(error::InvalidData( 30 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE, 31 | )); 32 | } 33 | 34 | Ok(Packet({ 35 | let mut buffer = [0x0; 1]; 36 | buffer[0] = data[0]; 37 | buffer 38 | })) 39 | } 40 | } 41 | 42 | impl core::ops::Deref for Packet { 43 | type Target = [u32]; 44 | fn deref(&self) -> &Self::Target { 45 | &self.0[..] 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::*; 52 | use pretty_assertions::assert_eq; 53 | 54 | #[test] 55 | fn construction() { 56 | assert!(Packet::try_from(&[0x0000_0000][..]).is_ok()); 57 | } 58 | 59 | #[test] 60 | fn construction_long_slice() { 61 | assert!(Packet::try_from(&[0x0000_0000, 0x0][..]).is_ok()); 62 | } 63 | 64 | #[test] 65 | fn construction_incorrect_ump_message_type() { 66 | assert_eq!( 67 | Packet::try_from(&[0x1000_0000][..]), 68 | Err(error::InvalidData( 69 | crate::detail::common_err_strings::ERR_INCORRECT_UMP_MESSAGE_TYPE 70 | )), 71 | ); 72 | } 73 | 74 | #[test] 75 | fn construction_short_slice() { 76 | assert_eq!( 77 | Packet::try_from(&[][..]), 78 | Err(error::InvalidData( 79 | crate::detail::common_err_strings::ERR_SLICE_TOO_SHORT 80 | )), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /midi2_proc/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "midi2_proc" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "proc-macro2", 10 | "quote", 11 | "syn", 12 | ] 13 | 14 | [[package]] 15 | name = "proc-macro2" 16 | version = "1.0.66" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" 19 | dependencies = [ 20 | "unicode-ident", 21 | ] 22 | 23 | [[package]] 24 | name = "quote" 25 | version = "1.0.33" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 28 | dependencies = [ 29 | "proc-macro2", 30 | ] 31 | 32 | [[package]] 33 | name = "syn" 34 | version = "2.0.32" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" 37 | dependencies = [ 38 | "proc-macro2", 39 | "quote", 40 | "unicode-ident", 41 | ] 42 | 43 | [[package]] 44 | name = "unicode-ident" 45 | version = "1.0.11" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" 48 | -------------------------------------------------------------------------------- /midi2_proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "midi2_proc" 3 | description = "Internal procedural macro crate. Only intended for use with midi2" 4 | version = "0.9.0" 5 | edition = "2021" 6 | readme = "README.md" 7 | license = "MIT OR Apache-2.0" 8 | authors = [ 9 | "Ben Leadbetter ", 10 | ] 11 | repository = "https://github.com/BenLeadbetter/midi2.git" 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | proc-macro2 = "1.0.81" 18 | quote = "1.0.36" 19 | syn = { version = "2.0.60", features = ["full"] } 20 | -------------------------------------------------------------------------------- /midi2_proc/README.md: -------------------------------------------------------------------------------- 1 | # Procedural Macros For midi2 2 | 3 | This crate is an internal dependency of the midi2 crate. 4 | -------------------------------------------------------------------------------- /midi2_proc/src/common.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | #[derive(Clone, Copy)] 5 | pub enum Representation { 6 | Ump, 7 | Bytes, 8 | UmpOrBytes, 9 | } 10 | 11 | pub enum BufferGeneric { 12 | UmpOrBytes(syn::TypeParam), 13 | Ump(syn::TypeParam), 14 | Bytes(syn::TypeParam), 15 | } 16 | 17 | impl BufferGeneric { 18 | pub fn ident(&self) -> syn::Ident { 19 | match self { 20 | Self::UmpOrBytes(param) => param.ident.clone(), 21 | Self::Ump(param) => param.ident.clone(), 22 | Self::Bytes(param) => param.ident.clone(), 23 | } 24 | } 25 | pub fn type_param(&self) -> syn::TypeParam { 26 | match self { 27 | Self::UmpOrBytes(param) => param.clone(), 28 | Self::Ump(param) => param.clone(), 29 | Self::Bytes(param) => param.clone(), 30 | } 31 | } 32 | } 33 | 34 | pub fn has_attr(field: &syn::Field, id: &str) -> bool { 35 | field.attrs.iter().any(|attr| { 36 | let syn::Meta::Path(path) = &attr.meta else { 37 | return false; 38 | }; 39 | path.segments 40 | .last() 41 | .iter() 42 | .any(|&segment| segment.ident == id) 43 | }) 44 | } 45 | 46 | pub fn meta_type(field: &syn::Field) -> syn::Type { 47 | field 48 | .attrs 49 | .iter() 50 | .filter_map(|attr| { 51 | use syn::Meta::*; 52 | match &attr.meta { 53 | List(list) => Some(list), 54 | _ => None, 55 | } 56 | }) 57 | .find(|list| { 58 | list.path 59 | .segments 60 | .last() 61 | .iter() 62 | .any(|&segment| segment.ident == "property") 63 | }) 64 | .map(|list| { 65 | list.parse_args::() 66 | .expect("Arguments to property attribute should be a valid type") 67 | }) 68 | .expect("fields must be annotated with the property attribute") 69 | } 70 | 71 | pub fn is_unit_tuple(ty: &syn::Type) -> bool { 72 | match ty { 73 | syn::Type::Tuple(tup) => tup.elems.is_empty(), 74 | _ => false, 75 | } 76 | } 77 | 78 | pub fn buffer_generic(generics: &syn::Generics) -> Option { 79 | let type_param = |param: &syn::GenericParam| { 80 | if let syn::GenericParam::Type(type_param) = param { 81 | Some(type_param.clone()) 82 | } else { 83 | None 84 | } 85 | }; 86 | let trait_bound = |bound: &syn::TypeParamBound| { 87 | if let syn::TypeParamBound::Trait(trait_bound) = bound { 88 | Some(trait_bound.clone()) 89 | } else { 90 | None 91 | } 92 | }; 93 | let is_buffer_bound = |id: &'static str| { 94 | move |bound: syn::TraitBound| match bound.path.segments.last().as_ref() { 95 | Some(segment) => segment.ident == id, 96 | None => false, 97 | } 98 | }; 99 | for param in generics.params.iter().filter_map(type_param) { 100 | if param 101 | .bounds 102 | .iter() 103 | .filter_map(trait_bound) 104 | .any(is_buffer_bound("Ump")) 105 | { 106 | return Some(BufferGeneric::Ump(param.clone())); 107 | }; 108 | if param 109 | .bounds 110 | .iter() 111 | .filter_map(trait_bound) 112 | .any(is_buffer_bound("Bytes")) 113 | { 114 | return Some(BufferGeneric::Bytes(param.clone())); 115 | }; 116 | if param 117 | .bounds 118 | .iter() 119 | .filter_map(trait_bound) 120 | .any(is_buffer_bound("Buffer")) 121 | { 122 | return Some(BufferGeneric::UmpOrBytes(param.clone())); 123 | }; 124 | } 125 | None 126 | } 127 | 128 | pub fn std_only_attribute(is_std_only: bool) -> TokenStream { 129 | if is_std_only { 130 | quote! { 131 | #[cfg(feature = "std")] 132 | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] 133 | } 134 | } else { 135 | TokenStream::new() 136 | } 137 | } 138 | 139 | pub fn parse_via_args(input: syn::parse::ParseStream) -> syn::Type { 140 | let syn::ExprParen { expr, .. } = input 141 | .parse() 142 | .expect("Bracketed expression should follow size arg"); 143 | 144 | let syn::Expr::Path(path) = *expr else { 145 | panic!("Via argument should contain a path type"); 146 | }; 147 | 148 | syn::Type::Path(syn::TypePath { 149 | qself: path.qself, 150 | path: path.path, 151 | }) 152 | } 153 | -------------------------------------------------------------------------------- /midi2_proc/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream as TokenStream1; 2 | 3 | mod common; 4 | mod derives; 5 | mod generate_ci; 6 | mod generate_message; 7 | 8 | #[proc_macro_attribute] 9 | pub fn generate_message(attrs: TokenStream1, item: TokenStream1) -> TokenStream1 { 10 | generate_message::generate_message(attrs, item) 11 | } 12 | 13 | #[proc_macro_attribute] 14 | pub fn generate_ci(attrs: TokenStream1, item: TokenStream1) -> TokenStream1 { 15 | generate_ci::generate_ci(attrs, item) 16 | } 17 | 18 | #[proc_macro_derive(Data)] 19 | pub fn derive_data(item: TokenStream1) -> TokenStream1 { 20 | derives::data(item) 21 | } 22 | 23 | #[proc_macro_derive(Packets)] 24 | pub fn derive_packets(item: TokenStream1) -> TokenStream1 { 25 | derives::packets(item) 26 | } 27 | 28 | #[proc_macro_derive(Grouped)] 29 | pub fn derive_grouped(item: TokenStream1) -> TokenStream1 { 30 | derives::grouped(item) 31 | } 32 | 33 | #[proc_macro_derive(Channeled)] 34 | pub fn derive_channeled(item: TokenStream1) -> TokenStream1 { 35 | derives::channeled(item) 36 | } 37 | 38 | #[proc_macro_derive(Debug)] 39 | pub fn derive_ump_debug(item: TokenStream1) -> TokenStream1 { 40 | derives::debug(item) 41 | } 42 | 43 | #[proc_macro_derive(FromBytes)] 44 | pub fn derive_from_bytes(item: TokenStream1) -> TokenStream1 { 45 | derives::from_bytes(item) 46 | } 47 | 48 | #[proc_macro_derive(FromUmp)] 49 | pub fn derive_from_ump(item: TokenStream1) -> TokenStream1 { 50 | derives::from_ump(item) 51 | } 52 | 53 | #[proc_macro_derive(TryFromBytes)] 54 | pub fn derive_try_from_bytes(item: TokenStream1) -> TokenStream1 { 55 | derives::try_from_bytes(item) 56 | } 57 | 58 | #[proc_macro_derive(TryFromUmp)] 59 | pub fn derive_try_from_ump(item: TokenStream1) -> TokenStream1 { 60 | derives::try_from_ump(item) 61 | } 62 | 63 | #[proc_macro_derive(RebufferFrom)] 64 | pub fn derive_rebuffer_from(item: TokenStream1) -> TokenStream1 { 65 | derives::rebuffer_from(item) 66 | } 67 | 68 | #[proc_macro_derive(RebufferFromArray)] 69 | pub fn derive_rebuffer_from_array(item: TokenStream1) -> TokenStream1 { 70 | derives::rebuffer_from_array(item) 71 | } 72 | 73 | #[proc_macro_derive(TryRebufferFrom)] 74 | pub fn derive_try_rebuffer_from(item: TokenStream1) -> TokenStream1 { 75 | derives::try_rebuffer_from(item) 76 | } 77 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | wheel 2 | pre-commit~=3.3 3 | codespell~=2.2 4 | --------------------------------------------------------------------------------