├── .clang-format ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── NI.package.cmake ├── README.md ├── cmake ├── code-coverage.cmake ├── nimidi2_add_test.cmake └── nimidi2_treat_warnings_as_errors.cmake ├── docs ├── channel_voice_message.examples.cpp ├── channel_voice_message.md ├── data_message.examples.cpp ├── data_message.md ├── docs.cpp ├── extended_data_message.examples.cpp ├── extended_data_message.md ├── flex_data_message.examples.cpp ├── flex_data_message.md ├── midi1_byte_stream.examples.cpp ├── midi1_byte_stream.md ├── midi1_channel_voice_message.examples.cpp ├── midi1_channel_voice_message.md ├── midi2_channel_voice_message.examples.cpp ├── midi2_channel_voice_message.md ├── stream_message.examples.cpp ├── stream_message.md ├── sysex.examples.cpp ├── sysex.md ├── sysex_collector.examples.cpp ├── sysex_collector.md ├── system_message.examples.cpp ├── system_message.md ├── types.examples.cpp ├── types.md ├── universal_packet.md ├── universal_sysex.examples.cpp ├── universal_sysex.md ├── utility_message.examples.cpp └── utility_message.md ├── inc └── midi │ ├── capability_inquiry.h │ ├── channel_voice_message.h │ ├── data_message.h │ ├── extended_data_message.h │ ├── flex_data_message.h │ ├── jitter_reduction_timestamps.h │ ├── manufacturer.h │ ├── midi1_byte_stream.h │ ├── midi1_channel_voice_message.h │ ├── midi2_channel_voice_message.h │ ├── stream_message.h │ ├── sysex.h │ ├── sysex_collector.h │ ├── system_message.h │ ├── types.h │ ├── universal_packet.h │ ├── universal_sysex.h │ └── utility_message.h ├── src ├── capability_inquiry.cpp ├── jitter_reduction_timestamps.cpp ├── midi1_byte_stream.cpp ├── sysex.cpp ├── sysex_collector.cpp ├── universal_packet.cpp └── universal_sysex.cpp ├── tests ├── capability_inquiry_tests.cpp ├── channel_voice_message_tests.cpp ├── ci_process_inquiry_tests.cpp ├── ci_profile_configuration_tests.cpp ├── ci_property_exchange_tests.cpp ├── data_message_tests.cpp ├── extended_data_message_tests.cpp ├── flex_data_message_tests.cpp ├── jitter_reduction_timestamps_tests.cpp ├── midi1_byte_stream_tests.cpp ├── midi1_channel_voice_message_tests.cpp ├── midi2_channel_voice_message_tests.cpp ├── stream_message_tests.cpp ├── sysex7_collector_tests.cpp ├── sysex7_test_data.cpp ├── sysex7_test_data.h ├── sysex8_collector_tests.cpp ├── sysex8_test_data.cpp ├── sysex8_test_data.h ├── sysex_tests.cpp ├── sysex_tests.h ├── system_message_tests.cpp ├── tests.cpp ├── type_tests.cpp ├── universal_packet_tests.cpp ├── universal_sysex_tests.cpp ├── utility_message_tests.cpp └── value_translation_tests.cpp └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Mozilla 3 | 4 | Language: Cpp 5 | Standard: c++17 6 | 7 | ColumnLimit: 120 8 | IndentWidth: 4 9 | TabWidth: 4 10 | UseTab: Never 11 | 12 | MaxEmptyLinesToKeep: 1 13 | KeepEmptyLinesAtTheStartOfBlocks: false 14 | 15 | AlignConsecutiveAssignments: Consecutive 16 | AlignConsecutiveBitFields: Consecutive 17 | AlignConsecutiveDeclarations: Consecutive 18 | 19 | AllowAllArgumentsOnNextLine: true 20 | AllowAllParametersOfDeclarationOnNextLine: true 21 | 22 | AllowShortBlocksOnASingleLine: false 23 | AllowShortIfStatementsOnASingleLine: false 24 | AllowShortLambdasOnASingleLine: true 25 | AllowShortLoopsOnASingleLine: false 26 | AllowShortFunctionsOnASingleLine: Inline 27 | 28 | AlwaysBreakAfterDefinitionReturnType: None 29 | AlwaysBreakAfterReturnType: None 30 | 31 | BinPackArguments: false 32 | BinPackParameters: false 33 | 34 | BreakBeforeBraces: Custom 35 | BraceWrapping: 36 | AfterCaseLabel: true 37 | AfterClass: true 38 | AfterControlStatement: Always 39 | AfterEnum: false 40 | AfterFunction: true 41 | AfterNamespace: false 42 | AfterObjCDeclaration: false 43 | AfterStruct: true 44 | AfterUnion: true 45 | AfterExternBlock: true 46 | BeforeCatch: false 47 | BeforeElse: true 48 | BeforeLambdaBody: false 49 | BeforeWhile: true 50 | IndentBraces: false 51 | SplitEmptyFunction: true 52 | SplitEmptyRecord: false 53 | SplitEmptyNamespace: true 54 | 55 | CompactNamespaces: true 56 | Cpp11BracedListStyle: false 57 | 58 | FixNamespaceComments: true 59 | 60 | IndentCaseLabels: false 61 | 62 | NamespaceIndentation: Inner 63 | 64 | PointerAlignment: Left 65 | 66 | ReflowComments: true 67 | 68 | SpaceInEmptyBlock: true 69 | 70 | DisableFormat: false 71 | ... 72 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ni-midi2 CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'bugfix/**' 8 | - 'feature/**' 9 | - 'release/**' 10 | 11 | pull_request: 12 | branches: [ main ] 13 | 14 | jobs: 15 | build: 16 | name: ${{ matrix.config.name }} 17 | runs-on: ${{ matrix.config.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | config: 22 | - name: "Windows 2022" 23 | os: windows-2022 24 | build_type: Release 25 | 26 | - name: "MacOS 13 (xc14)" 27 | os: macos-13 28 | build_type: Release 29 | set_xcode_version_cmd: xcodes select 14.3.1 30 | 31 | - name: "Ubuntu 22.04 (GCC-11)" 32 | os: ubuntu-22.04 33 | build_type: Release 34 | cc: "gcc-11" 35 | cxx: "g++-11" 36 | 37 | - name: "Ubuntu 22.04 (GCC-10)" 38 | os: ubuntu-22.04 39 | build_type: Release 40 | cc: "gcc-10" 41 | cxx: "g++-10" 42 | 43 | - name: "Ubuntu 22.04 (GCC-9)" 44 | os: ubuntu-22.04 45 | build_type: Release 46 | cc: "gcc-9" 47 | cxx: "g++-9" 48 | 49 | - name: "sysex pmr (Ubuntu 22.04, GCC-11)" 50 | os: ubuntu-22.04 51 | cmake_options: "-DNIMIDI2_PMR_SYSEX_DATA=ON" 52 | build_type: Release 53 | cc: "gcc-11" 54 | cxx: "g++-11" 55 | 56 | - name: "non-unity builds (Ubuntu 22.04, GCC-11)" 57 | os: ubuntu-22.04 58 | cmake_options: "-DNIMIDI2_UNITY_BUILDS=OFF" 59 | build_type: Release 60 | cc: "gcc-11" 61 | cxx: "g++-11" 62 | 63 | - name: "Code-Coverage (Ubuntu 22.04, GCC-10)" 64 | os: ubuntu-22.04 65 | build_type: Coverage 66 | code_coverage: true 67 | cc: "gcc-10" 68 | cxx: "g++-10" 69 | gcov: "gcov-10" 70 | 71 | 72 | steps: 73 | - name: Set xcode version 74 | if: matrix.config.set_xcode_version_cmd != null 75 | run: | 76 | ${{ matrix.config.set_xcode_version_cmd }} 77 | 78 | - name: Set compiler version 79 | if: matrix.config.cc != null && matrix.config.cxx != null 80 | run: | 81 | sudo apt install ${{ matrix.config.cc }} ${{ matrix.config.cxx }} 82 | echo "CC=${{ matrix.config.cc }}" >> $GITHUB_ENV 83 | echo "CXX=${{ matrix.config.cxx }}" >> $GITHUB_ENV 84 | 85 | - name: Set GCOV version 86 | if: matrix.config.gcov != null 87 | run: | 88 | echo "GCOV=${{ matrix.config.gcov }}" >> $GITHUB_ENV 89 | 90 | - name: Checkout source 91 | uses: actions/checkout@v3 92 | 93 | - name: Install vcpkg 94 | run: | 95 | git clone https://github.com/Microsoft/vcpkg.git 96 | ./vcpkg/bootstrap-vcpkg.sh 97 | 98 | - name: Configure CMake + Install Dependencies 99 | run: | 100 | cmake -B ${{ github.workspace }}/build . -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} ${{ matrix.config.cmake_options }} -DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake 101 | 102 | - name: Build 103 | run: | 104 | cmake --build ${{ github.workspace }}/build --config ${{ matrix.config.build_type }} 105 | 106 | - name: Test 107 | working-directory: ${{ github.workspace }}/build 108 | run: | 109 | ctest --verbose -C ${{ matrix.config.build_type }} --timeout 900 110 | 111 | - name: Generate Code-Coverage results 112 | if: matrix.config.code_coverage == true 113 | working-directory: ${{ github.workspace }}/build 114 | run: | 115 | sudo apt-get update 116 | sudo apt-get install -y lcov 117 | lcov --version 118 | echo $(which ${GCOV}) 119 | lcov --gcov-tool $(which ${GCOV}) -d . -c -o coverage.info 120 | 121 | - name: Upload code coverage results 122 | if: matrix.config.code_coverage == true 123 | uses: codecov/codecov-action@v3.1.0 124 | with: 125 | files: ${{ github.workspace }}/build/coverage.info 126 | verbose: true 127 | gcov: true 128 | gcov_ignore: tests 129 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | libs 2 | test-reports 3 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.5.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | - id: mixed-line-ending 9 | args: [--fix=auto] 10 | 11 | 12 | - repo: https://github.com/pre-commit/mirrors-clang-format 13 | rev: v17.0.4 14 | hooks: 15 | - id: clang-format 16 | types_or: [c++, c] 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v1.10.1 2 | 3 | * fix AKAIs manufacturer ID 4 | 5 | # v1.10.0 6 | 7 | * improve documentation and add example code (part 1, more to follow) 8 | * add convenience functions to add `uint32` (5 bytes) to `sysex7` 9 | 10 | # v1.9.0 11 | 12 | * remove redundant _midi2_ from APIs and deprecate old versions 13 | * add `is_registered_per_note_controller_pitch_message()` 14 | * add convinience conversions between `controller_value` and `pitch_7_25` 15 | 16 | # v1.8.5 17 | 18 | * support 14 bit in `controller_value`, useful for MIDI 1 (N)RPNs 19 | 20 | # v1.8.4 21 | 22 | * fix Property Exchange chunk validation 23 | 24 | # v1.8.3 25 | 26 | * more VS 2022 warning fixes 27 | 28 | # v1.8.2 29 | 30 | * fix VS 2022 warnings in as_double() tests 31 | 32 | # v1.8.1 33 | 34 | * fix signed/unsigned mismatch warning with older MSVC compilers 35 | 36 | # v1.8.0 37 | 38 | * fix MIDI CI profile messages, some implementations were not yet compatible with MIDI CI 1.2 39 | 40 | # v1.7.0 41 | 42 | * add note attribute helpers 43 | * add pitch bend sensitivity helpers 44 | 45 | # v1.6.0 46 | 47 | * add support for double precision numbers 48 | * fix under/overflows in +operators 49 | 50 | # v1.5.1 51 | 52 | * fix channel parameters to `channel_t` type alias 53 | 54 | # v1.5.0 55 | 56 | * sysex: support for polymorphic memory resources 57 | * minor unit test tweaks 58 | * CI: verify non-unity builds and pmr sysex option 59 | 60 | # v1.4.0 61 | 62 | * fix: add missing `target_muid` to `make_invalidate_muid_message` and `invalidate_muid_view` 63 | 64 | # v1.3.0 65 | 66 | * add 'is_midi2__message' for per note and registered / assignable controller messages 67 | * add CI badges to README 68 | * `pre-commit` updates 69 | * sysex collectors: `clear()` sysex in `reset()` 70 | 71 | # v1.2.0 72 | 73 | * add support for System Exclusive 8 messages 74 | * introduce `sysex7_packet`, move payload members from `data_message` into `sysex7_packet` 75 | 76 | ## Migration guide 77 | 78 | `make_sysex7__packet` factory functions now return `sysex7_packet` instead of `data_packet`. 79 | This might require minor changes to variable types and function signatures. 80 | 81 | # v1.1.1 82 | 83 | * fix `device_identity` handling in MIDI CI and stream messages 84 | * fix faulty assertions in `sysex7::add_uint14` / `sysex7::add_uint28` 85 | 86 | # v1.1.0 87 | 88 | * cherry-pick UMP specification `scaleUp` as `upsample_x_to_ybit` 89 | * add `as_midi1_channel_voice_message` and `as_midi1_channel_voice_message` 90 | * add `send_sysex7` and `send_xxx` stream messages 91 | * introduce `sysex::data_type` and optional custom sysex data allocator 92 | * improve MIDI-CI message creation to avoid redundant resizes/allocs of sysex data vector 93 | * introduce sysex7_collector::set_max_sysex_data_size 94 | * some more minor tweaks and improvements 95 | 96 | # v1.0.0 97 | 98 | * initial public release 99 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | include_guard(GLOBAL) 4 | 5 | project( ni-midi2 LANGUAGES CXX ) 6 | 7 | if( CMAKE_PROJECT_NAME STREQUAL "ni-midi2" ) 8 | set( IS_NIMIDI2 TRUE ) 9 | else() 10 | set( IS_NIMIDI2 FALSE ) 11 | endif() 12 | 13 | option( NIMIDI2_TREAT_WARNINGS_AS_ERRORS "Treat compile warnings as errors" OFF ) 14 | option( NIMIDI2_UNITY_BUILDS "Build ni-midi2 with unity builds" ON ) 15 | option( NIMIDI2_TESTS "Build ni-midi2 tests" ${IS_NIMIDI2} ) 16 | option( NIMIDI2_EXAMPLES "Build ni-midi2 examples" ${IS_NIMIDI2} ) 17 | 18 | option( NIMIDI2_CUSTOM_SYSEX_DATA_ALLOCATOR "Build with custom sysex data allocator" OFF ) 19 | option( NIMIDI2_PMR_SYSEX_DATA "Build with sysex data use pmr" OFF ) 20 | 21 | if (NIMIDI2_PMR_SYSEX_DATA AND NIMIDI2_CUSTOM_SYSEX_DATA_ALLOCATOR) 22 | message(FATAL_ERROR "NIMIDI2_PMR_SYSEX_DATA and NIMIDI2_CUSTOM_SYSEX_DATA_ALLOCATOR are mutually exclusibe") 23 | endif() 24 | 25 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 26 | 27 | include(code-coverage) 28 | include(nimidi2_add_test) 29 | include(nimidi2_treat_warnings_as_errors) 30 | 31 | if(NOT CMAKE_CXX_STANDARD) 32 | if (NOT NIMIDI2_PMR_SYSEX_DATA) 33 | set(CMAKE_CXX_STANDARD 17) 34 | else() 35 | set(CMAKE_CXX_STANDARD 20) # pmr requires at least c++20 36 | endif() 37 | endif() 38 | 39 | set(LibSources 40 | inc/midi/types.h 41 | inc/midi/manufacturer.h 42 | inc/midi/universal_packet.h src/universal_packet.cpp 43 | inc/midi/utility_message.h 44 | inc/midi/system_message.h 45 | inc/midi/channel_voice_message.h 46 | inc/midi/midi1_byte_stream.h src/midi1_byte_stream.cpp 47 | inc/midi/midi1_channel_voice_message.h 48 | inc/midi/midi2_channel_voice_message.h 49 | inc/midi/data_message.h 50 | inc/midi/extended_data_message.h 51 | inc/midi/flex_data_message.h 52 | inc/midi/stream_message.h 53 | inc/midi/sysex.h src/sysex.cpp 54 | inc/midi/sysex_collector.h src/sysex_collector.cpp 55 | inc/midi/universal_sysex.h src/universal_sysex.cpp 56 | inc/midi/capability_inquiry.h src/capability_inquiry.cpp 57 | inc/midi/jitter_reduction_timestamps.h src/jitter_reduction_timestamps.cpp 58 | ) 59 | 60 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${LibSources}) 61 | 62 | add_library(ni-midi2 EXCLUDE_FROM_ALL ${LibSources}) 63 | target_include_directories(ni-midi2 PUBLIC inc) 64 | add_library(ni::midi2 ALIAS ni-midi2) 65 | 66 | if (NIMIDI2_TREAT_WARNINGS_AS_ERRORS) 67 | nimidi2_treat_warnings_as_errors(ni-midi2) 68 | endif() 69 | if( NIMIDI2_UNITY_BUILDS ) 70 | set_target_properties(ni-midi2 PROPERTIES 71 | UNITY_BUILD ON 72 | UNITY_BUILD_BATCH_SIZE 32) 73 | endif() 74 | 75 | if ( NIMIDI2_CUSTOM_SYSEX_DATA_ALLOCATOR ) 76 | target_compile_definitions(ni-midi2 PUBLIC NIMIDI2_CUSTOM_SYSEX_DATA_ALLOCATOR=1) 77 | endif() 78 | 79 | if ( NIMIDI2_PMR_SYSEX_DATA ) 80 | target_compile_definitions(ni-midi2 PUBLIC NIMIDI2_PMR_SYSEX_DATA=1) 81 | endif() 82 | 83 | if( NIMIDI2_TESTS ) 84 | enable_testing() 85 | 86 | set(TestSources 87 | tests/tests.cpp 88 | tests/type_tests.cpp 89 | tests/value_translation_tests.cpp 90 | tests/universal_packet_tests.cpp 91 | tests/data_message_tests.cpp 92 | tests/extended_data_message_tests.cpp 93 | tests/flex_data_message_tests.cpp 94 | tests/stream_message_tests.cpp 95 | tests/system_message_tests.cpp 96 | tests/utility_message_tests.cpp 97 | tests/midi1_channel_voice_message_tests.cpp 98 | tests/midi2_channel_voice_message_tests.cpp 99 | tests/channel_voice_message_tests.cpp 100 | tests/sysex_tests.cpp tests/sysex_tests.h 101 | tests/sysex7_collector_tests.cpp 102 | tests/sysex7_test_data.cpp tests/sysex7_test_data.h 103 | tests/sysex8_collector_tests.cpp 104 | tests/sysex8_test_data.cpp tests/sysex8_test_data.h 105 | tests/universal_sysex_tests.cpp 106 | tests/capability_inquiry_tests.cpp 107 | tests/ci_profile_configuration_tests.cpp 108 | tests/ci_property_exchange_tests.cpp 109 | tests/ci_process_inquiry_tests.cpp 110 | tests/jitter_reduction_timestamps_tests.cpp 111 | tests/midi1_byte_stream_tests.cpp 112 | ) 113 | 114 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${TestSources}) 115 | 116 | add_executable(ni-midi2-tests ${TestSources}) 117 | target_link_libraries(ni-midi2-tests PRIVATE ni::midi2) 118 | 119 | if (NIMIDI2_TREAT_WARNINGS_AS_ERRORS) 120 | nimidi2_treat_warnings_as_errors(ni-midi2-tests) 121 | endif() 122 | if(NIMIDI2_UNITY_BUILDS) 123 | set_target_properties(ni-midi2-tests PROPERTIES UNITY_BUILD ON) 124 | endif() 125 | 126 | find_package(GTest REQUIRED) 127 | target_link_libraries(ni-midi2-tests PRIVATE GTest::GTest GTest::gmock_main) 128 | 129 | nimidi2_add_test(ni-midi2-tests GTEST) 130 | endif(NIMIDI2_TESTS) 131 | 132 | if( NIMIDI2_EXAMPLES ) 133 | set(ExampleSources 134 | docs/docs.cpp 135 | docs/channel_voice_message.examples.cpp 136 | docs/data_message.examples.cpp 137 | docs/extended_data_message.examples.cpp 138 | docs/flex_data_message.examples.cpp 139 | docs/midi1_byte_stream.examples.cpp 140 | docs/midi1_channel_voice_message.examples.cpp 141 | docs/midi2_channel_voice_message.examples.cpp 142 | docs/stream_message.examples.cpp 143 | docs/sysex_collector.examples.cpp 144 | docs/sysex.examples.cpp 145 | docs/system_message.examples.cpp 146 | docs/types.examples.cpp 147 | docs/universal_sysex.examples.cpp 148 | docs/utility_message.examples.cpp 149 | ) 150 | 151 | source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${ExampleSources}) 152 | 153 | add_executable(ni-midi2-examples ${ExampleSources}) 154 | target_link_libraries(ni-midi2-examples PRIVATE ni::midi2) 155 | endif(NIMIDI2_EXAMPLES) 156 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Code Style 2 | 3 | This project is using `snake_case` for identifiers and a C++ `std` library style API design. 4 | 5 | Non-public member variables are prefixed with `m_`, setters names are prefixed with `set` and getters do not have any prefix. 6 | 7 | group_t m_group; 8 | 9 | group_t group() const; 10 | void set_group(group_t); 11 | 12 | There is a `clang-format` provided, use this before committing / creating PRs. 13 | 14 | # pre-commit Hooks 15 | 16 | This project provides a [pre-commit](https://pre-commit.com/index.html) configuration file that fixes common file issues and runs [clang-format](https://github.com/ssciwr/clang-format-wheel) on changed files. 17 | 18 | Install `pre-commit` using Python `pip` or the packet manager of your choice: 19 | 20 | # using pip 21 | pip install pre-commit 22 | 23 | # homebrew on macOS 24 | brew install pre-commit 25 | 26 | # apt on Ubuntu 27 | apt install pre-commit 28 | 29 | Install the pre-commit hooks in the local checkout: 30 | 31 | cd path/to/ni-midi2 32 | pre-commit install 33 | 34 | # Unit Tests 35 | 36 | The unit test framework used is [GoogleTest](https://github.com/google/googletest). 37 | 38 | When making changes ensure that all existing tests keep working. 39 | 40 | Provide new tests for new functionality and for validation of bug fixes. 41 | 42 | # Test Coverage 43 | 44 | You can enable code coverage by configuring with `CODE_COVERAGE=ON` or `CMAKE_BUILD_TYPE=Coverage`. Supported code coverage toolchains are `lcov/gcovr` and `llvm`. 45 | 46 | Run the `ni-midi2-tests` executable and generate coverage reports: 47 | 48 | # llvm toolchain 49 | cd 50 | export LLVM_PROFILE_FILE=ni-midi2.profraw 51 | ./ni-midi2-tests 52 | llvm-profdata merge -sparse ni-midi2.profraw -o ni-midi2.profdata 53 | llvm-cov show -format=html -instr-profile=ni-midi2.profdata -object ni-midi2-tests -output-dir=ni-midi2-coverage-html -ignore-filename-regex='tests' 54 | 55 | # lcov/gcovr toolchain 56 | #!/bin/sh 57 | cd 58 | ./ni-midi2-tests 59 | lcov --capture --directory . --output-file coverage.info 60 | genhtml coverage.info --output-directory ni-midi2-coverage-html 61 | 62 | When you add unit tests for new functionality ensure that your tests cover all new source code. 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Native Instruments 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 | -------------------------------------------------------------------------------- /NI.package.cmake: -------------------------------------------------------------------------------- 1 | ### 2 | ### this file is private to the Native Instruments build system, please ignore 3 | ### 4 | 5 | if (TARGET ni-midi2) 6 | return() 7 | endif() 8 | 9 | ### set package path to current path if not set. 10 | ### 11 | ### we have to define and use a local function here so that CMAKE_CURRENT_FUNCTION_LIST_DIR is set. 12 | function( pr_init_nimidi2_package_path ) 13 | if( NOT DEFINED NI_NIMIDI2_PACKAGE_PATH ) 14 | set( NI_NIMIDI2_PACKAGE_PATH ${CMAKE_CURRENT_FUNCTION_LIST_DIR} PARENT_SCOPE ) 15 | endif() 16 | endfunction() 17 | pr_init_nimidi2_package_path() 18 | 19 | set( ni_midi2_sources 20 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/types.h" 21 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/types.h" 22 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/manufacturer.h" 23 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/universal_packet.h" 24 | "${NI_NIMIDI2_PACKAGE_PATH}/src/universal_packet.cpp" 25 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/utility_message.h" 26 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/system_message.h" 27 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/channel_voice_message.h" 28 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/midi1_byte_stream.h" 29 | "${NI_NIMIDI2_PACKAGE_PATH}/src/midi1_byte_stream.cpp" 30 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/midi1_channel_voice_message.h" 31 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/midi2_channel_voice_message.h" 32 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/data_message.h" 33 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/extended_data_message.h" 34 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/flex_data_message.h" 35 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/stream_message.h" 36 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/sysex.h" 37 | "${NI_NIMIDI2_PACKAGE_PATH}/src/sysex.cpp" 38 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/sysex_collector.h" 39 | "${NI_NIMIDI2_PACKAGE_PATH}/src/sysex_collector.cpp" 40 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/universal_sysex.h" 41 | "${NI_NIMIDI2_PACKAGE_PATH}/src/universal_sysex.cpp" 42 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/capability_inquiry.h" 43 | "${NI_NIMIDI2_PACKAGE_PATH}/src/capability_inquiry.cpp" 44 | "${NI_NIMIDI2_PACKAGE_PATH}/inc/midi/jitter_reduction_timestamps.h" 45 | "${NI_NIMIDI2_PACKAGE_PATH}/src/jitter_reduction_timestamps.cpp" 46 | ) 47 | 48 | add_library( ni-midi2 EXCLUDE_FROM_ALL ${ni_midi2_sources} ) 49 | set_target_properties( ni-midi2 PROPERTIES 50 | FOLDER 3rdparty 51 | UNITY_BUILD ON 52 | UNITY_BUILD_BATCH_SIZE 32 53 | ) 54 | target_include_directories( ni-midi2 55 | SYSTEM PUBLIC "${NI_NIMIDI2_PACKAGE_PATH}/inc" 56 | ) 57 | add_library(ni::midi2 ALIAS ni-midi2) 58 | 59 | option( NIMIDI2_PMR_SYSEX_DATA "Build with sysex data use pmr" OFF ) 60 | 61 | if (NIMIDI2_PMR_SYSEX_DATA) 62 | target_compile_definitions(ni-midi2 PUBLIC NIMIDI2_PMR_SYSEX_DATA) 63 | endif() 64 | 65 | ###### Tests ###### 66 | 67 | option( NI_MIDI2_BUILD_TESTS "Build ni-midi tests" ${NI_3RDPARTY_BUILD_TESTS} ) 68 | 69 | if ( NI_MIDI2_BUILD_TESTS ) 70 | enable_testing() 71 | 72 | ni_build_headers_in_isolation( ni-midi2 ) 73 | 74 | set( ni_midi2_test_sources 75 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/tests.cpp" 76 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/type_tests.cpp" 77 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/value_translation_tests.cpp" 78 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/universal_packet_tests.cpp" 79 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/data_message_tests.cpp" 80 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/extended_data_message_tests.cpp" 81 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/flex_data_message_tests.cpp" 82 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/stream_message_tests.cpp" 83 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/system_message_tests.cpp" 84 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/utility_message_tests.cpp" 85 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/midi1_channel_voice_message_tests.cpp" 86 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/midi2_channel_voice_message_tests.cpp" 87 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/channel_voice_message_tests.cpp" 88 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/sysex_tests.cpp" 89 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/sysex7_collector_tests.cpp" 90 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/sysex7_test_data.cpp" 91 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/sysex8_collector_tests.cpp" 92 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/sysex8_test_data.cpp" 93 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/universal_sysex_tests.cpp" 94 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/capability_inquiry_tests.cpp" 95 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/ci_profile_configuration_tests.cpp" 96 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/ci_property_exchange_tests.cpp" 97 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/ci_process_inquiry_tests.cpp" 98 | "${NI_NIMIDI2_PACKAGE_PATH}/tests/midi1_byte_stream_tests.cpp" 99 | ) 100 | 101 | add_executable( ni-midi2-tests ${ni_midi2_test_sources}) 102 | target_link_libraries(ni-midi2-tests PRIVATE ni::midi2 gtest) 103 | 104 | set_target_properties(ni-midi2-tests PROPERTIES UNITY_BUILD ON) 105 | 106 | ni_add_test(ni-midi2-tests GTEST) 107 | endif( NI_MIDI2_BUILD_TESTS ) 108 | -------------------------------------------------------------------------------- /cmake/code-coverage.cmake: -------------------------------------------------------------------------------- 1 | if( CODE_COVERAGE OR (CMAKE_BUILD_TYPE MATCHES Coverage) ) 2 | if (NOT CODE_COVERAGE) 3 | set( CODE_COVERAGE ON FORCE) 4 | endif() 5 | if (CMAKE_BUILD_TYPE MATCHES Coverage) 6 | set( CMAKE_BUILD_TYPE Debug FORCE ) 7 | endif() 8 | 9 | if( CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang") 10 | message(STATUS "Building with llvm Code Coverage Tools") 11 | 12 | add_compile_options(-fprofile-instr-generate -fcoverage-mapping) 13 | add_link_options(-fprofile-instr-generate -fcoverage-mapping) 14 | 15 | elseif(CMAKE_COMPILER_IS_GNUCXX) 16 | message(STATUS "Building with lcov Code Coverage Tools") 17 | 18 | add_compile_options( --coverage -fprofile-arcs -ftest-coverage ) 19 | add_link_options( --coverage -fprofile-arcs -ftest-coverage) 20 | else() 21 | message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.") 22 | endif() 23 | endif() 24 | -------------------------------------------------------------------------------- /cmake/nimidi2_add_test.cmake: -------------------------------------------------------------------------------- 1 | 2 | function(nimidi2_add_test Target) 3 | 4 | cmake_parse_arguments( PARSE_ARGV 1 NI_ADD_TEST "GTEST" "" "" ) 5 | 6 | if( NOT NI_ADD_TEST_GTEST ) 7 | message( SEND_ERROR "ni_add_test: GTEST must be specified" ) 8 | endif() 9 | 10 | if( UNIX ) 11 | set( TestOutput ${Target}.xml) 12 | elseif(WIN64) 13 | set( TestOutput ${Target}64.xml) 14 | elseif(WIN32) 15 | set( TestOutput ${Target}32.xml) 16 | else() 17 | message(WARNING "Unsupported Platform") 18 | set( TestOutput ${Target}.xml) 19 | endif() 20 | 21 | add_test(NAME ${Target}_run 22 | COMMAND $ --gtest_output=xml:test-reports/${TestOutput} 23 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 24 | 25 | endfunction() 26 | -------------------------------------------------------------------------------- /cmake/nimidi2_treat_warnings_as_errors.cmake: -------------------------------------------------------------------------------- 1 | function( nimidi2_treat_warnings_as_errors Target ) 2 | 3 | if( CMAKE_GENERATOR MATCHES "Xcode" ) 4 | set_target_properties( ${Target} PROPERTIES XCODE_ATTRIBUTE_GCC_TREAT_WARNINGS_AS_ERRORS "YES" ) 5 | target_compile_options( ${Target} PRIVATE -Wno-deprecated-declarations) 6 | elseif( CMAKE_CXX_COMPILER_ID MATCHES "Clang|AppleClang|GNU" ) 7 | target_compile_options( ${Target} PRIVATE -Werror -Wno-deprecated-declarations ) 8 | elseif( MSVC ) 9 | target_compile_options( ${Target} PRIVATE /WX /wd4996 ) 10 | endif() 11 | 12 | endfunction() 13 | -------------------------------------------------------------------------------- /docs/channel_voice_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void note_on_examples() 6 | { 7 | using namespace midi; 8 | 9 | const note_nr_t note_nr{ 99 }; 10 | const velocity vel{ 0.9876 }; 11 | const pitch_7_9 default_pitch{ note_nr }; 12 | const pitch_7_9 custom_pitch{ 99.02 }; 13 | 14 | auto m1 = make_midi1_note_on_message(0, 0, note_nr, vel); 15 | auto m2 = make_midi2_note_on_message(0, 0, note_nr, vel); 16 | auto m3 = make_midi2_note_on_message(0, 0, note_nr, vel, custom_pitch); 17 | auto m4 = make_midi2_note_on_message(0, 0, note_nr, vel, uint8_t{ 42 }, uint16_t{ 1234 }); 18 | 19 | assert(is_note_on_message(m1)); 20 | assert(is_note_on_message(m2)); 21 | assert(is_note_on_message(m3)); 22 | assert(is_note_on_message(m4)); 23 | 24 | assert(get_note_nr(m1) == note_nr); 25 | assert(get_note_nr(m2) == note_nr); 26 | assert(get_note_nr(m3) == note_nr); 27 | assert(get_note_nr(m4) == note_nr); 28 | assert(get_note_velocity(m1) == velocity{ vel.as_uint7() }); 29 | assert(get_note_velocity(m2) == vel); 30 | assert(get_note_velocity(m3) == vel); 31 | assert(get_note_velocity(m4) == vel); 32 | assert(get_note_pitch(m1) == default_pitch); 33 | assert(get_note_pitch(m2) == default_pitch); 34 | assert(get_note_pitch(m3) == custom_pitch); 35 | assert(get_note_pitch(m4) == default_pitch); 36 | } 37 | 38 | void note_off_examples() 39 | { 40 | using namespace midi; 41 | 42 | const note_nr_t note_nr{ 71 }; 43 | const velocity vel{ 0.1 }; 44 | 45 | auto m1 = make_midi1_note_off_message(0, 0, note_nr, vel); 46 | auto m2 = make_midi2_note_off_message(0, 0, note_nr, vel); 47 | auto m3 = make_midi1_note_on_message(0, 0, note_nr, velocity{ uint7_t{ 0 } }); 48 | 49 | assert(is_note_off_message(m1)); 50 | assert(is_note_off_message(m2)); 51 | assert(is_note_off_message(m3)); 52 | assert(get_note_nr(m1) == note_nr); 53 | assert(get_note_nr(m2) == note_nr); 54 | assert(get_note_nr(m3) == note_nr); 55 | assert(get_note_velocity(m1) == velocity{ vel.as_uint7() }); 56 | assert(get_note_velocity(m2) == vel); 57 | assert(get_note_velocity(m3) == velocity{ uint7_t{ 64 } }); 58 | } 59 | 60 | void poly_pressure_examples() 61 | { 62 | using namespace midi; 63 | 64 | note_nr_t note_nr{ 34 }; 65 | controller_value pressure{ 0.75 }; 66 | 67 | auto m1 = make_midi1_poly_pressure_message(0, 0, note_nr, pressure); 68 | auto m2 = make_midi2_poly_pressure_message(0, 0, note_nr, pressure); 69 | 70 | assert(is_poly_pressure_message(m1)); 71 | assert(is_poly_pressure_message(m2)); 72 | 73 | assert(get_note_nr(m1) == note_nr); 74 | assert(get_note_nr(m2) == note_nr); 75 | 76 | assert(get_poly_pressure_value(m2) == pressure); 77 | assert(get_poly_pressure_value(m1) == controller_value{ pressure.as_uint7() }); 78 | } 79 | 80 | void control_change_examples() 81 | { 82 | using namespace midi; 83 | 84 | controller_t ctrl{ control_change::brightness }; 85 | controller_value val{ uint32_t{ 0x41221892 } }; 86 | 87 | auto m1 = make_midi1_control_change_message(0, 0, ctrl, val); 88 | auto m2 = make_midi2_control_change_message(0, 0, ctrl, val); 89 | 90 | assert(is_control_change_message(m1)); 91 | assert(is_control_change_message(m2)); 92 | 93 | assert(get_controller_nr(m1) == ctrl); 94 | assert(get_controller_nr(m2) == ctrl); 95 | 96 | assert(get_controller_value(m2) == val); 97 | assert(get_controller_value(m1) == controller_value{ val.as_uint7() }); 98 | } 99 | 100 | void program_change_examples() 101 | { 102 | using namespace midi; 103 | 104 | program_t program{ 7 }; 105 | 106 | // TODO: demonstrate MIDI2 bank 107 | auto m1 = make_midi1_program_change_message(0, 0, program); 108 | auto m2 = make_midi2_program_change_message(0, 0, program); 109 | 110 | assert(is_program_change_message(m1)); 111 | assert(is_program_change_message(m2)); 112 | 113 | assert(get_program_value(m1) == program); 114 | assert(get_program_value(m2) == program); 115 | } 116 | 117 | void channel_pressure_examples() 118 | { 119 | using namespace midi; 120 | 121 | controller_value pressure{ 0.75 }; 122 | 123 | auto m1 = make_midi1_channel_pressure_message(0, 0, pressure); 124 | auto m2 = make_midi2_channel_pressure_message(0, 0, pressure); 125 | 126 | assert(is_channel_pressure_message(m1)); 127 | assert(is_channel_pressure_message(m2)); 128 | 129 | assert(get_channel_pressure_value(m2) == pressure); 130 | assert(get_channel_pressure_value(m1) == controller_value{ pressure.as_uint7() }); 131 | } 132 | 133 | void channel_pitch_bend_examples() 134 | { 135 | using namespace midi; 136 | 137 | pitch_bend pb{ -0.04 }; 138 | 139 | auto m1 = make_midi1_pitch_bend_message(0, 0, pb); 140 | auto m2 = make_midi2_pitch_bend_message(0, 0, pb); 141 | 142 | assert(is_channel_pitch_bend_message(m1)); 143 | assert(is_channel_pitch_bend_message(m2)); 144 | 145 | assert(get_channel_pitch_bend_value(m2) == pb); 146 | assert(get_channel_pitch_bend_value(m1) == pitch_bend{ pb.as_uint14() }); 147 | } 148 | 149 | void channel_voice_message_processor(const midi::universal_packet& p) 150 | { 151 | using namespace midi; 152 | 153 | auto allocate_voice = [](note_nr_t, velocity, pitch_7_9) {}; 154 | auto release_voice = [](note_nr_t, velocity) {}; 155 | auto voice_pressure = [](note_nr_t, controller_value) {}; 156 | auto voice_controller = [](note_nr_t, uint8_t, controller_value) {}; 157 | auto voice_pitch_bend = [](note_nr_t, pitch_bend) {}; 158 | auto channel_controller = [](controller_t, controller_value) {}; 159 | auto channel_pitch_bend = [](pitch_bend) {}; 160 | 161 | if (is_note_on_message(p)) 162 | { 163 | allocate_voice(get_note_nr(p), get_note_velocity(p), get_note_pitch(p)); 164 | } 165 | else if (is_note_off_message(p)) 166 | { 167 | release_voice(get_note_nr(p), get_note_velocity(p)); 168 | } 169 | else if (is_poly_pressure_message(p)) 170 | { 171 | voice_pressure(get_note_nr(p), get_poly_pressure_value(p)); 172 | } 173 | else if (is_registered_per_note_controller_message(p)) 174 | { 175 | voice_controller(get_note_nr(p), get_per_note_controller_index(p), get_controller_value(p)); 176 | } 177 | else if (is_per_note_pitch_bend_message(p)) 178 | { 179 | voice_pitch_bend(get_note_nr(p), get_per_note_pitch_bend_value(p)); 180 | } 181 | else if (is_control_change_message(p)) 182 | { 183 | channel_controller(get_controller_nr(p), get_controller_value(p)); 184 | } 185 | else if (is_channel_pitch_bend_message(p)) 186 | { 187 | channel_pitch_bend(get_channel_pitch_bend_value(p)); 188 | } 189 | } 190 | 191 | void run_channel_voice_message_examples() 192 | { 193 | note_on_examples(); 194 | note_off_examples(); 195 | poly_pressure_examples(); 196 | control_change_examples(); 197 | program_change_examples(); 198 | channel_pressure_examples(); 199 | channel_pitch_bend_examples(); 200 | channel_voice_message_processor(midi::universal_packet{ 0x20901234 }); 201 | } 202 | -------------------------------------------------------------------------------- /docs/channel_voice_message.md: -------------------------------------------------------------------------------- 1 | # Protocol Agnostic Channel Voice Message Helpers 2 | 3 | Protocol agnostic Channel Voice Message helpers allow you to handle MIDI 1 (low resolution) 4 | and MIDI 2 (high resolution) Channel Voice Messages in a single branch of code. By using these helpers 5 | it does not matter of what packet type incoming Channel Voice Message are. 6 | 7 | In a first step one filters incoming packets by Channel Voice Message type, and then extract 8 | the appropriate properties from the packet. 9 | 10 | Code examples can be found in [`channel_voice_message.examples.cpp`](channel_voice_message.examples.cpp). 11 | 12 | ## Channel Voice Message Filters (`is_XXX_message`) 13 | 14 | Available filter functions are: 15 | 16 | bool is_channel_voice_message_with_status(const universal_packet&, status_t); 17 | 18 | bool is_note_on_message(const universal_packet&); 19 | bool is_note_off_message(const universal_packet&); 20 | bool is_poly_pressure_message(const universal_packet&); 21 | bool is_control_change_message(const universal_packet&); 22 | bool is_program_change_message(const universal_packet&); 23 | bool is_channel_pressure_message(const universal_packet&); 24 | bool is_channel_pitch_bend_message(const universal_packet&); 25 | 26 | Additionally, one may use 27 | 28 | bool is_note_on_with_attribute(const universal_packet&, uint8_t); 29 | bool is_note_off_with_attribute(const universal_packet&, uint8_t); 30 | bool is_note_on_with_pitch_7_9(const universal_packet&); 31 | 32 | to check for MIDI 2 note messages with attributes. 33 | 34 | ## Note Message Properties 35 | 36 | The following functions are available to extract properties from Note messages: 37 | 38 | note_nr_t get_note_nr(const universal_packet&); 39 | pitch_7_9 get_note_pitch(const universal_packet&); 40 | velocity get_note_velocity(const universal_packet&); 41 | 42 | `get_note_nr` is applicable to _Poly Pressure_ and _Per-Note Controller_ messages, too. 43 | 44 | Additionally, one may use 45 | 46 | uint8_t get_midi2_note_attribute(const universal_packet&); 47 | uint16_t get_midi2_note_attribute_data(const universal_packet&); 48 | 49 | to retrieve Per Note Attribute type and data. 50 | 51 | ## Controller Message Properties 52 | 53 | Use 54 | 55 | controller_t get_controller_nr(const universal_packet&); 56 | controller_value get_controller_value(const universal_packet&); 57 | 58 | to extract _Control Change_ message properties. 59 | 60 | `get_controller_value` is applicable to _Poly Pressure_, _Registered/Assignable Controller_ and _Per-Note Controller_ messages, too. 61 | 62 | Use these functions to retrieve properties of specific messages: 63 | 64 | controller_value get_poly_pressure_value(const universal_packet&); 65 | uint7_t get_program_value(const universal_packet&); 66 | controller_value get_channel_pressure_value(const universal_packet&); 67 | pitch_bend get_channel_pitch_bend_value(const universal_packet&); 68 | 69 | ## Conversion between Protocols 70 | 71 | One may use 72 | 73 | std::optional 74 | as_midi1_channel_voice_message(const midi2_channel_voice_message_view&); 75 | 76 | to convert a MIDI 2 Channel Voice Message to its MIDI 1 counterpart and 77 | 78 | std::optional 79 | as_midi2_channel_voice_message(const midi1_channel_voice_message_view&); 80 | 81 | to convert a MIDI 1 Channel Voice Message to its MIDI 2 counterpart. 82 | 83 | **Note:** MIDI 1 <-> MIDI 2 translation rules do require some sequences of MIDI 1 messages to be translated to single MIDI 2 messages and vice versa. The above mentioned functions do not follow these rules. Actually they filter some _Control Change_ messages (mostly related to (N)RPNs) and cannot handle MIDI 2 _Program Change_ messages with `bank` data. -------------------------------------------------------------------------------- /docs/data_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | void sysex7_message_examples() 6 | { 7 | using namespace midi; 8 | 9 | auto p1 = make_sysex7_complete_packet(group_t{ 4 }); 10 | p1.add_payload_byte(0x7D); 11 | p1.add_payload_byte(0x01); 12 | p1.add_payload_byte(0x02); 13 | 14 | assert(is_data_message(p1)); 15 | 16 | if (auto m1 = as_sysex7_packet_view(p1)) 17 | { 18 | assert(m1->payload_size() == 3); 19 | assert(m1->payload_byte(0) == 0x7D); 20 | assert(m1->payload_byte(1) == 1); 21 | assert(m1->payload_byte(2) == 2); 22 | } 23 | 24 | sysex7_packet p2[] = { make_sysex7_start_packet(group_t{ 7 }), 25 | make_sysex7_continue_packet(group_t{ 7 }), 26 | make_sysex7_end_packet(group_t{ 7 }) }; 27 | 28 | const char payload[] = { 0x7D, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; 29 | sysex7_packet* dest = p2; 30 | for (auto b : payload) 31 | { 32 | dest->add_payload_byte(b); 33 | if (dest->payload_size() == 6) 34 | ++dest; 35 | } 36 | 37 | assert(is_sysex7_packet(p2[0])); 38 | auto m21 = sysex7_packet_view{ p2[0] }; 39 | assert(m21.payload_size() == 6); 40 | assert(m21.payload_byte(5) == 5); 41 | 42 | assert(is_sysex7_packet(p2[1])); 43 | auto m22 = sysex7_packet_view{ p2[1] }; 44 | assert(m22.payload_size() == 6); 45 | assert(m22.payload_byte(3) == 9); 46 | 47 | assert(is_sysex7_packet(p2[1])); 48 | auto m23 = sysex7_packet_view{ p2[2] }; 49 | assert(m23.payload_size() == 4); 50 | assert(m23.payload_byte(3) == 15); 51 | } 52 | 53 | void run_data_message_examples() 54 | { 55 | sysex7_message_examples(); 56 | } 57 | -------------------------------------------------------------------------------- /docs/data_message.md: -------------------------------------------------------------------------------- 1 | # Data Messages 2 | 3 | Code examples can be found in [`data_message.examples.cpp`](data_message.examples.cpp). 4 | 5 | ## Message Creation and Filtering 6 | 7 | Data Messages are represented by a 8 | 9 | struct data_message : universal_packet 10 | { 11 | explicit data_message(status_t); 12 | }; 13 | 14 | Usually one will not use this class directly, but instead 15 | 16 | struct sysex7_packet : data_message 17 | { 18 | sysex7_packet(status_t, group_t); 19 | 20 | packet_format format() const; 21 | 22 | uint8_t payload_byte(size_t b) const; 23 | void set_payload_byte(size_t, uint8_t); 24 | 25 | size_t payload_size() const; 26 | void set_payload_size(size_t); 27 | 28 | void add_payload_byte(uint8_t); 29 | }; 30 | 31 | A `sysex7_packet` can hold a payload of up to six bytes and provides APIs to add or read the payload bytes. Be aware that the payload shall only be 7 bit data. 32 | 33 | Instead of using sysex7_packet constructors one can create messages using factory functions: 34 | 35 | sysex7_packet make_sysex7_complete_packet(group_t); 36 | sysex7_packet make_sysex7_start_packet(group_t); 37 | sysex7_packet make_sysex7_continue_packet(group_t); 38 | sysex7_packet make_sysex7_end_packet(group_t); 39 | 40 | Filtering of Data Messages can be done checking `universal_packet::type()` against 41 | `packet_type::data` or use 42 | 43 | bool is_data_message(const universal_packet&); 44 | bool is_sysex7_packet(const universal_packet&); 45 | 46 | ### SysEx7 Packet View 47 | 48 | Once validated being a `sysex7` packet one can create a `sysex7_packet_view` 49 | for a `universal_packet`: 50 | 51 | struct sysex7_packet_view 52 | { 53 | explicit sysex7_packet_view(const universal_packet&); 54 | 55 | group_t group() const; 56 | status_t status() const; 57 | packet_format format() const; 58 | size_t payload_size() const; 59 | uint8_t payload_byte(size_t b) const; 60 | }; 61 | 62 | View members provide accessors to the properties of the message. 63 | 64 | The additional 65 | 66 | std::optional as_sysex7_packet_view(const universal_packet&); 67 | 68 | helper allows to combine packet type check and view creation in a single statement. 69 | -------------------------------------------------------------------------------- /docs/docs.cpp: -------------------------------------------------------------------------------- 1 | extern void run_channel_voice_message_examples(); 2 | extern void run_data_message_examples(); 3 | extern void run_extended_data_message_examples(); 4 | extern void run_flex_data_message_examples(); 5 | extern void run_midi1_byte_stream_examples(); 6 | extern void run_midi1_channel_voice_message_examples(); 7 | extern void run_midi2_channel_voice_message_examples(); 8 | extern void run_stream_message_examples(); 9 | extern void run_sysex_collector_examples(); 10 | extern void run_sysex_examples(); 11 | extern void run_system_message_examples(); 12 | extern void run_types_examples(); 13 | 14 | int main() 15 | { 16 | run_types_examples(); 17 | 18 | run_system_message_examples(); 19 | 20 | run_midi1_channel_voice_message_examples(); 21 | run_midi2_channel_voice_message_examples(); 22 | run_channel_voice_message_examples(); 23 | run_midi1_byte_stream_examples(); 24 | 25 | run_data_message_examples(); 26 | run_sysex_examples(); 27 | run_extended_data_message_examples(); 28 | run_sysex_collector_examples(); 29 | 30 | run_stream_message_examples(); 31 | run_flex_data_message_examples(); 32 | 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /docs/extended_data_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void sysex8_message_examples() 8 | { 9 | using namespace midi; 10 | 11 | auto p1 = make_sysex8_complete_packet(uint8_t { 1 }, group_t { 4 }); 12 | p1.add_payload_byte(0xFF); 13 | p1.add_payload_byte(0xF1); 14 | p1.add_payload_byte(0xF2); 15 | 16 | assert(is_extended_data_message(p1)); 17 | 18 | if (auto m1 = as_sysex8_packet_view(p1)) 19 | { 20 | assert(m1->payload_size() == 3); 21 | assert(m1->payload_byte(0) == 0xFF); 22 | assert(m1->payload_byte(1) == 0xF1); 23 | assert(m1->payload_byte(2) == 0xF2); 24 | } 25 | 26 | sysex8_packet p2[] = { 27 | make_sysex8_start_packet(uint8_t { 2 }, group_t { 7 }), 28 | make_sysex8_continue_packet(uint8_t { 2 }, group_t { 7 }), 29 | make_sysex8_end_packet(uint8_t { 2 }, group_t { 7 }) 30 | }; 31 | 32 | std::vector payload(30); 33 | std::iota(payload.begin(), payload.end(), 200); 34 | 35 | sysex8_packet *dest = p2; 36 | for (auto b : payload) 37 | { 38 | dest->add_payload_byte(b); 39 | if (dest->payload_size() == 13) ++dest; 40 | } 41 | 42 | assert(is_sysex8_packet(p2[0])); 43 | auto m21 = sysex8_packet_view{ p2[0] }; 44 | assert(m21.payload_size() == 13); 45 | assert(m21.payload_byte(12) == 212); 46 | 47 | assert(is_sysex8_packet(p2[1])); 48 | auto m22 = sysex8_packet_view{ p2[1] }; 49 | assert(m22.payload_size() == 13); 50 | assert(m22.payload_byte(7) == 220); 51 | 52 | assert(is_sysex8_packet(p2[1])); 53 | auto m23 = sysex8_packet_view{ p2[2] }; 54 | assert(m23.payload_size() == 4); 55 | assert(m23.payload_byte(3) == 229); 56 | } 57 | 58 | void run_extended_data_message_examples() 59 | { 60 | sysex8_message_examples(); 61 | } 62 | -------------------------------------------------------------------------------- /docs/extended_data_message.md: -------------------------------------------------------------------------------- 1 | # Extended Data Messages 2 | 3 | Code examples can be found in [`extended_data_message.examples.cpp`](extended_data_message.examples.cpp). 4 | 5 | ## Message Creation and Filtering 6 | 7 | Extended Data Messages are represented by a 8 | 9 | struct extended_data_message : universal_packet 10 | { 11 | explicit extended_data_message(status_t); 12 | }; 13 | 14 | Usually one will not use this class directly, but instead 15 | 16 | struct sysex8_packet : extended_data_message 17 | { 18 | sysex8_packet(status_t, uint8_t stream_id, group_t); 19 | 20 | packet_format format() const; 21 | 22 | uint8_t stream_id() const; 23 | void set_stream_id(uint8_t); 24 | 25 | uint8_t payload_byte(size_t b) const; 26 | void set_payload_byte(size_t, uint8_t); 27 | 28 | size_t payload_size() const; 29 | void set_payload_size(size_t); 30 | 31 | void add_payload_byte(uint8_t); 32 | }; 33 | 34 | A `sysex8_packet` can hold a payload of up to 13 bytes and provides APIs to add or read the payload bytes. 35 | System Exclusive 8 packets payload is allowed to be 8 bit, different to traditional MIDI System Exclusive (7 bit). 36 | 37 | In MIDI 2 is allowed to have multiple parallel System Exclusive 8 streams running in parallel. Therefore every `sysex8_packet` needs to have a `stream_id`. A UMP Endpoint communicates the maximum number of parallel UMP streams allowed using [Stream Messages](stream_message.md). 38 | 39 | Instead of using sysex8_packet constructors one can create messages using factory functions: 40 | 41 | sysex8_packet make_sysex8_complete_packet(uint8_t stream_id, group_t); 42 | sysex8_packet make_sysex8_start_packet(uint8_t stream_id, group_t); 43 | sysex8_packet make_sysex8_continue_packet(uint8_t stream_id, group_t); 44 | sysex8_packet make_sysex8_end_packet(uint8_t stream_id, group_t); 45 | 46 | Filtering of Extended Data Messages can be done checking `universal_packet::type()` against 47 | `packet_type::extended_data` or use 48 | 49 | bool is_extended_data_message(const universal_packet&); 50 | bool is_sysex8_packet(const universal_packet&); 51 | 52 | ### SysEx8 Packet View 53 | 54 | Once validated being a `sysex8` packet one can create a `sysex8_packet_view` 55 | for a `universal_packet`: 56 | 57 | struct sysex8_packet_view 58 | { 59 | explicit sysex8_packet_view(const universal_packet&); 60 | 61 | group_t group() const; 62 | packet_format format() const; 63 | uint8_t stream_id() const; 64 | size_t payload_size() const; 65 | uint8_t payload_byte(size_t b) const; 66 | }; 67 | 68 | View members provide accessors to the properties of the message. 69 | 70 | The additional 71 | 72 | std::optional as_sysex8_packet_view(const universal_packet&); 73 | 74 | helper allows to combine packet type check and view creation in a single statement. 75 | -------------------------------------------------------------------------------- /docs/flex_data_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void run_flex_data_message_examples() { /* TODO */ } 4 | -------------------------------------------------------------------------------- /docs/flex_data_message.md: -------------------------------------------------------------------------------- 1 | # Flex Data Messages 2 | 3 | _WORK IN PROGRESS_ 4 | 5 | ## Base Type 6 | 7 | struct flex_data_message : universal_packet 8 | { 9 | flex_data_message( 10 | group_t, packet_format, packet_address, uint4_t, status_t, status_t, uint32_t = 0, uint32_t = 0, uint32_t = 0); 11 | 12 | packet_format format() const; 13 | packet_address address() const; 14 | status_t status_bank() const; 15 | status_t status() const; 16 | 17 | std::string payload_as_string() const; 18 | static std::string payload_as_string(const universal_packet&); 19 | }; 20 | 21 | bool is_flex_data_message(const universal_packet&); 22 | 23 | 24 | ### Factory Functions 25 | 26 | flex_data_message make_flex_data_message(group_t, 27 | packet_format, 28 | packet_address, 29 | uint4_t channel, 30 | status_t status_bank, 31 | status_t status, 32 | uint32_t data1 = 0, 33 | uint32_t data2 = 0, 34 | uint32_t data3 = 0); 35 | flex_data_message make_flex_data_text_message(group_t, 36 | packet_format, 37 | packet_address, 38 | uint4_t channel, 39 | status_t status_bank, 40 | status_t status, 41 | const std::string_view& text); 42 | 43 | flex_data_message make_set_tempo_message(group_t, uint32_t ten_ns_per_quarter_note); 44 | flex_data_message make_set_time_signature_message(group_t, 45 | uint8_t numerator, 46 | uint8_t denominator, 47 | uint8_t nr_32rd_notes); 48 | flex_data_message make_set_metronome_message(group_t, 49 | uint8_t num_clocks_per_primary_click, 50 | uint8_t bar_accent_part1, 51 | uint8_t bar_accent_part2, 52 | uint8_t bar_accent_part3, 53 | uint8_t num_subdivision_clicks1, 54 | uint8_t num_subdivision_clicks2); 55 | flex_data_message make_set_key_signature_message( 56 | group_t, packet_address, uint4_t channel, uint4_t sharps_or_flats, uint4_t tonic_note); 57 | flex_data_message make_set_chord_message( 58 | group_t, packet_address, uint4_t channel, uint32_t data1, uint32_t data2, uint32_t data3); 59 | 60 | 61 | ### Flex Data Message Data View: 62 | 63 | struct flex_data_message_view 64 | { 65 | explicit flex_data_message_view(const universal_packet&); 66 | 67 | group_t group() const; 68 | packet_format format() const; 69 | packet_address address() const; 70 | channel_t channel() const; 71 | status_t status_bank() const; 72 | status_t status() const; 73 | uint32_t data1() const; 74 | uint32_t data2() const; 75 | uint32_t data3() const; 76 | 77 | const std::string payload_as_string() const; 78 | }; 79 | 80 | std::optional as_flex_data_message_view(const universal_packet&); 81 | -------------------------------------------------------------------------------- /docs/midi1_byte_stream.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void midi1_byte_stream_parser_examples() 4 | { 5 | // from channel_voice_message.examples.cpp 6 | extern void channel_voice_message_processor(const midi::universal_packet& p); 7 | 8 | using namespace midi; 9 | 10 | auto process_packet = [](midi::universal_packet p) { channel_voice_message_processor(p); }; 11 | auto process_sysex7 = [](const midi::sysex7&) {}; 12 | 13 | midi1_byte_stream_parser p(process_packet, process_sysex7); 14 | 15 | const std::vector midi1{ 0x90, 0x12, 0x34, 0xB0, 0x44, 0x75, 0xE0, 0x33, 16 | 0x44, 0xA0, 0x12, 0x60, 0x80, 0x12, 0x40 }; 17 | p.feed(midi1.data(), midi1.size()); 18 | } 19 | 20 | void from_to_midi1_byte_stream_examples() 21 | { 22 | using namespace midi; 23 | 24 | auto m1 = from_midi1_byte_stream(0x90, 0x12, 0x34); 25 | auto m2 = from_midi1_byte_stream(0xB0, 0x44, 0x75); 26 | 27 | auto send_bytes = [](uint8_t*, size_t) {}; 28 | 29 | uint8_t bytes[8]; 30 | auto cnt = to_midi1_byte_stream(universal_packet{ 0x20E03344 }, bytes); 31 | send_bytes(bytes, cnt); 32 | 33 | cnt = to_midi1_byte_stream(universal_packet{ 0x20801240 }, bytes); 34 | send_bytes(bytes, cnt); 35 | } 36 | 37 | void run_midi1_byte_stream_examples() 38 | { 39 | midi1_byte_stream_parser_examples(); 40 | from_to_midi1_byte_stream_examples(); 41 | } 42 | -------------------------------------------------------------------------------- /docs/midi1_byte_stream.md: -------------------------------------------------------------------------------- 1 | # MIDI 1 Byte Stream Helpers 2 | 3 | _WORK IN PROGRESS_ 4 | 5 | Code examples can be found in [`midi1_byte_stream.examples.cpp`](midi1_byte_stream.examples.cpp). 6 | 7 | ## MIDI 1 Byte Stream Parser 8 | 9 | class midi1_byte_stream_parser 10 | { 11 | public: 12 | using packet_callback = std::function; 13 | using sysex_callback = std::function; 14 | 15 | explicit midi1_byte_stream_parser(packet_callback, sysex_callback = {}, bool enable_callbacks = true); 16 | midi1_byte_stream_parser(group_t, packet_callback, sysex_callback = {}, bool enable_callbacks = true); 17 | 18 | bool callbacks_enabled() const; 19 | void enable_callbacks(bool); 20 | 21 | group_t group() const; 22 | void set_group(group_t); 23 | 24 | void feed(uint8_t); 25 | void feed(const uint8_t* data, size_t num_bytes); 26 | void feed(const uint8_t* begin, const uint8_t* end); 27 | 28 | void reset(); 29 | }; 30 | 31 | ## MIDI 1 Byte Stream Conversion 32 | 33 | universal_packet from_midi1_byte_stream(uint8_t status, uint7_t d1, uint7_t d2); 34 | 35 | size_t midi1_byte_stream_size(const universal_packet&); 36 | 37 | size_t to_midi1_byte_stream(const universal_packet&, uint8_t bytes[8]); 38 | -------------------------------------------------------------------------------- /docs/midi1_channel_voice_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | 7 | void midi1_note_message_examples() 8 | { 9 | using namespace midi; 10 | 11 | const group_t group = 3; 12 | const channel_t channel = 8; 13 | const note_nr_t note_nr{ 23 }; 14 | const velocity vel{ uint7_t{ 100 } }; 15 | 16 | midi1_channel_voice_message note_on = make_midi1_note_on_message(group, channel, note_nr, vel); 17 | midi1_channel_voice_message note_off = make_midi1_note_off_message(group, channel, note_nr); 18 | 19 | if (is_midi1_channel_voice_message(note_on)) 20 | { 21 | midi1_channel_voice_message_view v{ note_on }; 22 | 23 | assert(v.group() == group); 24 | assert(v.channel() == channel); 25 | assert(v.data_byte_1() == note_nr); 26 | assert(v.data_byte_2() == vel.as_uint7()); 27 | } 28 | 29 | if (auto v = as_midi1_channel_voice_message_view(note_off)) 30 | { 31 | assert(v->group() == group); 32 | assert(v->channel() == channel); 33 | assert(v->data_byte_1() == note_nr); 34 | assert(v->data_byte_2() == 64); 35 | } 36 | 37 | assert(is_note_on_message(note_on)); 38 | assert(is_note_off_message(note_off)); 39 | assert(get_note_nr(note_on) == note_nr); 40 | assert(get_note_nr(note_off) == note_nr); 41 | assert(get_note_velocity(note_on) == vel); 42 | assert(get_note_velocity(note_off) == velocity{}); 43 | assert(get_note_pitch(note_on) == pitch_7_9{ note_nr }); 44 | } 45 | 46 | void midi1_poly_pressure_message_examples() 47 | { 48 | using namespace midi; 49 | 50 | const group_t group = 9; 51 | const channel_t channel = 12; 52 | const note_nr_t note_nr{ 71 }; 53 | const controller_value value{ uint7_t{ 83 } }; 54 | 55 | midi1_channel_voice_message poly_pressure = make_midi1_poly_pressure_message(group, channel, note_nr, value); 56 | 57 | if (is_midi1_channel_voice_message(poly_pressure)) 58 | { 59 | midi1_channel_voice_message_view v{ poly_pressure }; 60 | 61 | assert(v.group() == group); 62 | assert(v.channel() == channel); 63 | assert(v.data_byte_1() == note_nr); 64 | assert(v.data_byte_2() == value.as_uint7()); 65 | } 66 | 67 | assert(is_poly_pressure_message(poly_pressure)); 68 | assert(get_note_nr(poly_pressure) == note_nr); 69 | assert(get_poly_pressure_value(poly_pressure) == value); 70 | } 71 | 72 | void midi1_control_change_message_examples() 73 | { 74 | using namespace midi; 75 | 76 | const group_t group = 2; 77 | const channel_t channel = 4; 78 | const controller_t ctrl_nr{ 24 }; 79 | const controller_value value{ uint7_t{ 87 } }; 80 | 81 | midi1_channel_voice_message control_change = make_midi1_control_change_message(group, channel, ctrl_nr, value); 82 | 83 | if (auto v = as_midi1_channel_voice_message_view(control_change)) 84 | { 85 | assert(v->group() == group); 86 | assert(v->channel() == channel); 87 | assert(v->data_byte_1() == ctrl_nr); 88 | assert(v->data_byte_2() == value.as_uint7()); 89 | } 90 | 91 | assert(is_control_change_message(control_change)); 92 | assert(get_controller_nr(control_change) == ctrl_nr); 93 | assert(get_controller_value(control_change) == value); 94 | } 95 | 96 | void midi1_program_change_message_examples() 97 | { 98 | using namespace midi; 99 | 100 | const group_t group = 0; 101 | const channel_t channel = 0; 102 | const controller_t program{ 99 }; 103 | 104 | midi1_channel_voice_message program_change = make_midi1_program_change_message(group, channel, program); 105 | 106 | if (auto v = as_midi1_channel_voice_message_view(program_change)) 107 | { 108 | assert(v->group() == group); 109 | assert(v->channel() == channel); 110 | assert(v->data_byte_1() == program); 111 | } 112 | 113 | assert(is_program_change_message(program_change)); 114 | assert(get_program_value(program_change) == program); 115 | } 116 | 117 | void midi1_channel_pressure_message_examples() 118 | { 119 | using namespace midi; 120 | 121 | const group_t group = 15; 122 | const channel_t channel = 7; 123 | const controller_value pressure{ uint7_t{ 121 } }; 124 | 125 | midi1_channel_voice_message channel_pressure = make_midi1_channel_pressure_message(group, channel, pressure); 126 | 127 | if (is_midi1_channel_voice_message(channel_pressure)) 128 | { 129 | midi1_channel_voice_message_view v{ channel_pressure }; 130 | 131 | assert(v.group() == group); 132 | assert(v.channel() == channel); 133 | assert(v.data_byte_1() == pressure.as_uint7()); 134 | } 135 | 136 | assert(is_channel_pressure_message(channel_pressure)); 137 | assert(get_channel_pressure_value(channel_pressure) == pressure); 138 | } 139 | 140 | void midi1_pitch_bend_message_examples() 141 | { 142 | using namespace midi; 143 | 144 | const group_t group = 3; 145 | const channel_t channel = 4; 146 | const pitch_bend value{ uint14_t{ 0x2412 } }; 147 | 148 | midi1_channel_voice_message pb = make_midi1_pitch_bend_message(group, channel, value); 149 | 150 | if (is_midi1_channel_voice_message(pb)) 151 | { 152 | midi1_channel_voice_message_view v{ pb }; 153 | 154 | assert(v.group() == group); 155 | assert(v.channel() == channel); 156 | assert(v.data_byte_1() == (value.as_uint14() & 0x7F)); 157 | assert(v.data_byte_2() == (value.as_uint14() >> 7)); 158 | } 159 | 160 | assert(is_channel_pitch_bend_message(pb)); 161 | assert(get_channel_pitch_bend_value(pb) == value); 162 | } 163 | 164 | void run_midi1_channel_voice_message_examples() 165 | { 166 | midi1_note_message_examples(); 167 | midi1_poly_pressure_message_examples(); 168 | midi1_control_change_message_examples(); 169 | midi1_program_change_message_examples(); 170 | midi1_channel_pressure_message_examples(); 171 | midi1_pitch_bend_message_examples(); 172 | } 173 | -------------------------------------------------------------------------------- /docs/midi1_channel_voice_message.md: -------------------------------------------------------------------------------- 1 | # MIDI 1 Channel Voice Messages 2 | 3 | Code examples can be found in [`midi1_channel_voice_message.examples.cpp`](midi1_channel_voice_message.examples.cpp). 4 | 5 | ## Message Creation and Filtering 6 | 7 | MIDI 1 Channel Voice Messages are represented by a 8 | 9 | struct midi1_channel_voice_message : universal_packet 10 | { 11 | midi1_channel_voice_message(); 12 | midi1_channel_voice_message(group_t, status_t, uint7_t data1 = 0, uint7_t data2 = 0); 13 | midi1_channel_voice_message(const universal_packet&); 14 | }; 15 | 16 | Instead of using `midi1_channel_voice_message` constructors one can create messages using factory functions: 17 | 18 | midi1_channel_voice_message 19 | make_midi1_channel_voice_message(group_t, status_t, channel_t, uint7_t data1, uint7_t data2 = 0); 20 | 21 | midi1_channel_voice_message 22 | make_midi1_note_off_message(group_t, channel_t, note_nr_t, velocity); 23 | midi1_channel_voice_message 24 | make_midi1_note_on_message(group_t, channel_t, note_nr_t, velocity); 25 | midi1_channel_voice_message 26 | make_midi1_poly_pressure_message(group_t, channel_t, note_nr_t, controller_value); 27 | midi1_channel_voice_message 28 | make_midi1_control_change_message(group_t, channel_t, controller_t, controller_value); 29 | midi1_channel_voice_message 30 | make_midi1_program_change_message(group_t, channel_t, program_t); 31 | midi1_channel_voice_message 32 | make_midi1_channel_pressure_message(group_t, channel_t, controller_value); 33 | midi1_channel_voice_message 34 | make_midi1_pitch_bend_message(group_t, channel_t, pitch_bend); 35 | 36 | Filtering of MIDI 1 Channel Voice Messages can be done checking `universal_packet::type()` against 37 | `packet_type::midi1_channel_voice` or use 38 | 39 | bool is_midi1_channel_voice_message(const universal_packet&); 40 | 41 | ## Message View 42 | 43 | Once validated against `packet_type::midi1_channel_voice` one can create a `midi1_channel_voice_message_view` 44 | for a `universal_packet`: 45 | 46 | struct midi1_channel_voice_message_view 47 | { 48 | midi1_channel_voice_message_view(const universal_packet&); 49 | 50 | group_t group() const; 51 | status_t status() const; 52 | channel_t channel() const; 53 | uint7_t data_byte_1() const; 54 | uint7_t data_byte_2() const; 55 | 56 | uint14_t get_14bit_value() const; 57 | }; 58 | 59 | View members provide accessors to the properties of the message. 60 | 61 | The additional 62 | 63 | std::optional as_midi1_channel_voice_message_view(const universal_packet&); 64 | 65 | helper allows to combine packet type check and view creation in a single statement. -------------------------------------------------------------------------------- /docs/midi2_channel_voice_message.md: -------------------------------------------------------------------------------- 1 | # High Resolution (MIDI 2) Channel Voice Messages 2 | 3 | Code examples can be found in [`midi2_channel_voice_message.examples.cpp`](midi2_channel_voice_message.examples.cpp). 4 | 5 | ## Message Creation and Filtering 6 | 7 | MIDI 2 Channel Voice Messages are represented by a 8 | 9 | struct midi2_channel_voice_message : universal_packet 10 | { 11 | midi2_channel_voice_message(); 12 | midi2_channel_voice_message(group_t, status_t, channel_t, uint8_t byte3, uint8_t byte4, uint32_t data); 13 | midi2_channel_voice_message(const universal_packet&); 14 | }; 15 | 16 | Instead of using `midi2_channel_voice_message` constructors one can create messages using factory functions: 17 | 18 | midi2_channel_voice_message 19 | make_midi2_channel_voice_message(group_t, status_t, channel_t, uint7_t index1, uint7_t index2, uint32_t data); 20 | 21 | midi2_channel_voice_message 22 | make_midi2_note_off_message(group_t, channel_t, note_nr_t, velocity, uint8_t attribute = 0, uint16_t attribute_data = 0); 23 | midi2_channel_voice_message 24 | make_midi2_note_on_message(group_t, channel_t, note_nr_t, velocity); 25 | midi2_channel_voice_message 26 | make_midi2_note_on_message(group_t, channel_t, note_nr_t, velocity, pitch_7_9); 27 | midi2_channel_voice_message 28 | make_midi2_note_on_message(group_t, channel_t, note_nr_t, velocity, uint8_t attribute, uint16_t attribute_data); 29 | 30 | midi2_channel_voice_message 31 | make_midi2_poly_pressure_message(group_t, channel_t, note_nr_t, controller_value); 32 | midi2_channel_voice_message 33 | make_registered_per_note_controller_message(group_t, channel_t, note_nr_t, uint8_t controller, controller_value); 34 | midi2_channel_voice_message 35 | make_assignable_per_note_controller_message(group_t, channel_t, note_nr_t, uint8_t controller, controller_value); 36 | midi2_channel_voice_message 37 | make_per_note_management_message(group_t, channel_t, note_nr_t, note_management_flags); 38 | 39 | midi2_channel_voice_message 40 | make_midi2_control_change_message(group_t, channel_t, uint7_t controller, controller_value); 41 | midi2_channel_voice_message 42 | make_registered_controller_message(group_t, channel_t, uint7_t bank, uint7_t index, controller_value); 43 | midi2_channel_voice_message 44 | make_assignable_controller_message(group_t, channel_t, uint7_t bank, uint7_t index, controller_value); 45 | midi2_channel_voice_message 46 | make_relative_registered_controller_message(group_t, channel_t, uint7_t bank, uint7_t index, controller_increment); 47 | midi2_channel_voice_message 48 | make_relative_assignable_controller_message(group_t, channel_t, uint7_t bank, uint7_t index, controller_increment); 49 | 50 | midi2_channel_voice_message 51 | make_midi2_program_change_message(group_t, channel_t, program_t); 52 | midi2_channel_voice_message 53 | make_midi2_program_change_message(group_t, channel_t, program_t, uint14_t bank); 54 | 55 | midi2_channel_voice_message 56 | make_midi2_channel_pressure_message(group_t, channel_t, controller_value); 57 | 58 | midi2_channel_voice_message 59 | make_midi2_pitch_bend_message(group_t, channel_t, pitch_bend); 60 | midi2_channel_voice_message 61 | make_per_note_pitch_bend_message(group_t, channel_t, note_nr_t, pitch_bend); 62 | 63 | Filtering of MIDI 2 Channel Voice Messages can be done checking `universal_packet::type()` against 64 | `packet_type::midi2_channel_voice` or use 65 | 66 | bool is_midi2_channel_voice_message(const universal_packet&); 67 | bool is_registered_controller_message(const universal_packet&); 68 | bool is_assignable_controller_message(const universal_packet&); 69 | bool is_registered_per_note_controller_message(const universal_packet&); 70 | bool is_registered_per_note_controller_pitch_message(const universal_packet&); 71 | bool is_assignable_per_note_controller_message(const universal_packet&); 72 | bool is_per_note_pitch_bend_message(const universal_packet&); 73 | 74 | bool is_note_on_with_attribute(const universal_packet&, uint8_t); 75 | bool is_note_off_with_attribute(const universal_packet&, uint8_t); 76 | bool is_note_on_with_pitch_7_9(const universal_packet&); 77 | 78 | bool is_pitch_bend_sensitivity_message(const universal_packet&); 79 | bool is_per_note_pitch_bend_sensitivity_message(const universal_packet&); 80 | 81 | ## Message View and Properties 82 | 83 | Once validated against `packet_type::midi2_channel_voice` one can create a `midi2_channel_voice_message_view` 84 | for a `universal_packet`: 85 | 86 | struct midi2_channel_voice_message_view 87 | { 88 | midi2_channel_voice_message_view(const universal_packet&); 89 | 90 | group_t group() const; 91 | status_t status() const; 92 | channel_t channel() const; 93 | uint7_t byte3() const; 94 | uint7_t byte4() const; 95 | uint32_t data() const; 96 | }; 97 | 98 | View members provide accessors to the properties of the message. 99 | 100 | The additional 101 | 102 | std::optional as_midi2_channel_voice_message_view(const universal_packet&); 103 | 104 | helper allows to combine packet type check and view creation in a single statement. 105 | 106 | There are free functions available to retrieve properties of the specific messages: 107 | 108 | uint8_t get_midi2_note_attribute(const universal_packet&); 109 | uint16_t get_midi2_note_attribute_data(const universal_packet&); 110 | 111 | uint8_t get_per_note_controller_index(const universal_packet&); 112 | 113 | pitch_bend_sensitivity get_pitch_bend_sensitivity_value(const universal_packet&); 114 | pitch_bend_sensitivity get_per_note_pitch_bend_sensitivity_value(const universal_packet&); 115 | 116 | pitch_bend get_per_note_pitch_bend_value(const universal_packet&); 117 | -------------------------------------------------------------------------------- /docs/stream_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void run_stream_message_examples() { /* TODO */ } 4 | -------------------------------------------------------------------------------- /docs/stream_message.md: -------------------------------------------------------------------------------- 1 | # Stream Messages 2 | 3 | _WORK IN PROGRESS_ 4 | 5 | Code examples can be found in [`stream_message.examples.cpp`](stream_message.examples.cpp). 6 | 7 | ## Base Type 8 | 9 | struct stream_message : universal_packet 10 | { 11 | stream_message(status_t, packet_format = packet_format::complete); 12 | 13 | packet_format format(); 14 | void set_format(packet_format); 15 | }; 16 | 17 | bool is_stream_message(const universal_packet&); 18 | 19 | 20 | ### Factory Functions 21 | 22 | stream_message make_endpoint_discovery_message(uint8_t filter, 23 | uint8_t ump_version_major = 1, 24 | uint8_t ump_version_minor = 1); 25 | stream_message make_endpoint_info_message(uint8_t num_function_blocks, 26 | bool static_function_blocks, 27 | uint8_t protocols, 28 | uint8_t extensions, 29 | uint8_t ump_version_major = 1, 30 | uint8_t ump_version_minor = 1); 31 | stream_message make_device_identity_message(const device_identity&); 32 | stream_message make_endpoint_name_message(packet_format, const std::string_view&); 33 | stream_message make_product_instance_id_message(packet_format, const std::string_view&); 34 | stream_message make_stream_configuration_request(protocol_t, extensions_t = 0); 35 | stream_message make_stream_configuration_notification(protocol_t, extensions_t = 0); 36 | 37 | stream_message make_function_block_discovery_message(uint8_t function_block, uint8_t filter); 38 | stream_message make_function_block_info_message(uint7_t function_block, 39 | uint2_t direction, 40 | group_t first_group, 41 | uint4_t num_groups_spanned = 1); 42 | stream_message make_function_block_info_message(uint7_t function_block, 43 | const function_block_options&, 44 | group_t first_group, 45 | uint4_t num_groups_spanned = 1); 46 | stream_message make_function_block_name_message(packet_format, 47 | uint7_t function_block, 48 | const std::string_view& n); 49 | 50 | 51 | struct function_block_options 52 | { 53 | // active 54 | bool active = true; 55 | 56 | // directions 57 | static constexpr uint2_t direction_input = 0b01; //!< Input, Function Block receives MIDI Messages only 58 | static constexpr uint2_t direction_output = 0b10; //!< Output, Function Block transmits MIDI Messages only 59 | static constexpr uint2_t bidirectional = 60 | 0b11; //!< Bidirectional Connections. Every Input Group member has a matching Output Group. 61 | 62 | uint2_t direction = bidirectional; 63 | 64 | // MIDI 1 65 | static constexpr uint2_t not_midi1 = 0b00; //!< Not MIDI 1.0 66 | static constexpr uint2_t midi1_unrestricted = 0b01; //!< MIDI 1.0 - don't restrict Bandwidth 67 | static constexpr uint2_t midi1_31250 = 0b10; //!< Restrict Bandwidth to 31.25Kbps 68 | 69 | uint2_t midi1 = not_midi1; 70 | 71 | // ui hints 72 | static constexpr uint2_t ui_hint_as_direction = 0b00; 73 | static constexpr uint2_t ui_hint_receiver = 0b01; 74 | static constexpr uint2_t ui_hint_sender = 0b10; 75 | 76 | uint2_t ui_hint = ui_hint_as_direction; 77 | 78 | uint8_t ci_message_version = 0x00; 79 | uint8_t max_num_sysex8_streams = 0; 80 | }; 81 | 82 | ## Multi Part Message helpers 83 | 84 | template 85 | void send_endpoint_name(std::string_view, Sender&&); 86 | 87 | template 88 | void send_product_instance_id(std::string_view, Sender&&); 89 | 90 | template 91 | void send_function_block_name(uint7_t function_block, std::string_view, Sender&&); 92 | 93 | ## Data Views 94 | 95 | ### Endpoint Discovery View 96 | 97 | namespace discovery_filter { 98 | constexpr uint8_t endpoint_info = 0b00001; 99 | constexpr uint8_t device_identity = 0b00010; 100 | constexpr uint8_t endpoint_name = 0b00100; 101 | constexpr uint8_t product_instance_id = 0b01000; 102 | constexpr uint8_t stream_configuration = 0b10000; 103 | constexpr uint8_t endpoint_all = 0b11111; 104 | 105 | constexpr uint8_t function_block_info = 0b01; 106 | constexpr uint8_t function_block_name = 0b10; 107 | constexpr uint8_t function_block_all = 0b11; 108 | } // namespace discovery_filter 109 | 110 | struct endpoint_discovery_view 111 | { 112 | endpoint_discovery_view(const universal_packet&); 113 | 114 | uint8_t ump_version_major() const; 115 | uint8_t ump_version_minor() const; 116 | uint16_t ump_version() const; 117 | 118 | uint8_t filter() const; 119 | 120 | bool requests_info() const; 121 | bool requests_device_identity() const; 122 | bool requests_name() const; 123 | bool requests_product_instance_id() const; 124 | bool requests_stream_configuration() const; 125 | }; 126 | 127 | 128 | std::optional as_endpoint_discovery_view(const universal_packet&); 129 | 130 | ### Endpoint Info View 131 | 132 | struct endpoint_info_view 133 | { 134 | endpoint_info_view(const universal_packet&); 135 | 136 | uint8_t ump_version_major() const; 137 | uint8_t ump_version_minor() const; 138 | uint16_t ump_version() const; 139 | 140 | uint8_t num_function_blocks() const; 141 | bool static_function_blocks() const; 142 | uint8_t protocols() const; 143 | uint8_t extensions() const; 144 | }; 145 | 146 | std::optional as_endpoint_info_view(const universal_packet&); 147 | 148 | ### Device Identity View 149 | 150 | struct device_identity_view 151 | { 152 | device_identity_view(const universal_packet&); 153 | 154 | device_identity identity() const; 155 | }; 156 | 157 | std::optional as_device_identity_view(const universal_packet&); 158 | 159 | ### Endpoint Name View 160 | 161 | struct endpoint_name_view 162 | { 163 | endpoint_name_view(const universal_packet&); 164 | 165 | packet_format format() const; 166 | std::string payload() const; 167 | }; 168 | 169 | std::optional as_endpoint_name_view(const universal_packet&); 170 | 171 | ### Product Instance ID View 172 | 173 | struct product_instance_id_view 174 | { 175 | product_instance_id_view(const universal_packet&); 176 | 177 | packet_format format() const; 178 | std::string payload() const; 179 | }; 180 | 181 | std::optional as_product_instance_id_view(const universal_packet&); 182 | 183 | ### Stream Configuration View 184 | 185 | struct stream_configuration_view 186 | { 187 | stream_configuration_view(const universal_packet&); 188 | 189 | protocol_t protocol() const; 190 | extensions_t extensions() const; 191 | }; 192 | 193 | std::optional as_stream_configuration_view(const universal_packet&); 194 | 195 | ### Function Block Discovery View 196 | 197 | struct function_block_discovery_view 198 | { 199 | function_block_discovery_view(const universal_packet&); 200 | 201 | uint8_t function_block() const; 202 | uint8_t filter() const; 203 | 204 | bool requests_function_block(uint8_t block) const; 205 | bool requests_info() const; 206 | bool requests_name() const; 207 | }; 208 | 209 | std::optional as_function_block_discovery_view(const universal_packet&); 210 | 211 | ### Function Block Info View 212 | 213 | struct function_block_info_view 214 | { 215 | function_block_info_view(const universal_packet&); 216 | 217 | bool active() const; 218 | uint8_t function_block() const; 219 | 220 | uint8_t direction() const; 221 | uint8_t midi1() const; 222 | uint8_t ui_hint() const; 223 | 224 | uint8_t first_group() const; 225 | uint8_t num_groups_spanned() const; 226 | 227 | uint7_t ci_message_version() const; 228 | uint8_t max_num_sysex8_streams() const; 229 | }; 230 | 231 | std::optional as_function_block_info_view(const universal_packet&); 232 | 233 | 234 | ### Function Block Name View 235 | 236 | struct function_block_name_view 237 | { 238 | function_block_name_view(const universal_packet&); 239 | 240 | packet_format format() const; 241 | uint8_t function_block() const; 242 | std::string payload() const; 243 | }; 244 | 245 | std::optional as_function_block_name_view(const universal_packet&); 246 | -------------------------------------------------------------------------------- /docs/sysex.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void sysex_examples() 4 | { 5 | using namespace midi; 6 | 7 | { 8 | sysex7 sx{ manufacturer::native_instruments, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; 9 | 10 | assert(sx.is_7bit()); 11 | assert(sx.is_valid()); 12 | assert(sx.data.size() == 9); 13 | } 14 | 15 | { 16 | sysex8 sx{ manufacturer::native_instruments, { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0x88 } }; 17 | 18 | assert(!sx.is_7bit()); 19 | assert(sx.is_8bit()); 20 | assert(sx.data.size() == 10); 21 | } 22 | 23 | { 24 | sysex sx{ manufacturer::native_instruments, { 1, 2 } }; 25 | 26 | assert(sx.is_7bit()); 27 | assert(!sx.is_8bit()); 28 | assert(sx.data.size() == 2); 29 | } 30 | 31 | { 32 | sysex7 sx; 33 | sx.manufacturerID = manufacturer::educational; 34 | 35 | sx.add_uint7(42); 36 | sx.add_uint28(10); 37 | const uint8_t some_data[] = { 1, 2, 3, 4 }; 38 | sx.add_data(some_data, sizeof(some_data)); 39 | 40 | assert(sx.is_7bit()); 41 | assert(sx.data.size() == 9); 42 | 43 | const auto identity = device_identity{ midi::manufacturer::native_instruments, 0x1730, 49, 0x00010005 }; 44 | sx.add_device_identity(identity); 45 | 46 | assert(sx.is_7bit()); 47 | assert(sx.data.size() == 20); 48 | 49 | // read data 50 | auto v = sx.make_uint28(1); 51 | assert(v == 10); 52 | 53 | auto i = sx.make_device_identity(9); 54 | assert(i.manufacturer == identity.manufacturer); 55 | assert(i.family == identity.family); 56 | assert(i.model == identity.model); 57 | assert(i.revision == identity.revision); 58 | } 59 | } 60 | 61 | void send_sysex_examples() 62 | { 63 | using namespace midi; 64 | 65 | auto send_packet = [](const universal_packet&) {}; 66 | 67 | sysex7 sx7{ manufacturer::native_instruments, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; 68 | group_t group = 4; 69 | send_sysex7(sx7, group, send_packet); 70 | 71 | sysex8 sx8{ manufacturer::native_instruments, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; 72 | uint8_t stream_id = 0; 73 | send_sysex8(sx8, stream_id, group, send_packet); 74 | } 75 | 76 | void sysex_as_packets_examples() 77 | { 78 | using namespace midi; 79 | 80 | sysex7 sx7{ manufacturer::native_instruments, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; 81 | auto packets1 = as_sysex7_packets(sx7, 9); 82 | 83 | sysex8 sx8{ manufacturer::native_instruments, { 1, 2, 3, 4, 5, 6, 7, 8, 9 } }; 84 | auto packets2 = as_sysex8_packets(sx8, 0); 85 | } 86 | 87 | void run_sysex_examples() 88 | { 89 | sysex_examples(); 90 | send_sysex_examples(); 91 | sysex_as_packets_examples(); 92 | } 93 | -------------------------------------------------------------------------------- /docs/sysex.md: -------------------------------------------------------------------------------- 1 | # System Exclusive Messages 2 | 3 | _WORK IN PROGRESS_ 4 | 5 | Code examples can be found in [`sysex.examples.cpp`](sysex.examples.cpp). 6 | -------------------------------------------------------------------------------- /docs/sysex_collector.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void sysex_collector_readme_code() 4 | { 5 | using namespace midi; 6 | 7 | universal_packet p; 8 | 9 | { 10 | sysex7_collector c{ [](const sysex7& s) { 11 | // do something with message 12 | // ... 13 | } }; 14 | 15 | if (is_sysex7_packet(p)) 16 | c.feed(p); 17 | } 18 | 19 | { 20 | sysex8_collector c{ [](const sysex8& s, uint8_t stream_id) { 21 | // do something with message 22 | // ... 23 | } }; 24 | 25 | if (is_sysex8_packet(p)) 26 | c.feed(p); 27 | } 28 | } 29 | 30 | void run_sysex_collector_examples() 31 | { 32 | sysex_collector_readme_code(); 33 | } 34 | -------------------------------------------------------------------------------- /docs/sysex_collector.md: -------------------------------------------------------------------------------- 1 | # Sysex Collectors 2 | 3 | _WORK IN PROGRESS_ 4 | 5 | Code examples can be found in [`sysex_collector.examples.cpp`](sysex_collector.examples.cpp). 6 | -------------------------------------------------------------------------------- /docs/system_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void system_message_examples() 4 | { 5 | using namespace midi; 6 | 7 | auto a = make_system_message(0, system_status::clock); 8 | if (is_system_message(a)) 9 | { 10 | auto m = system_message_view{ a }; 11 | 12 | group_t g = m.group(); 13 | status_t s = m.status(); 14 | } 15 | 16 | auto b = make_system_message(0, system_status::song_select, 44); 17 | if (auto m = as_system_message_view(b)) 18 | { 19 | group_t g = m->group(); 20 | status_t s = m->status(); 21 | uint7_t d1 = m->data_byte_1(); 22 | } 23 | 24 | auto c = make_song_position_message(0, 0x1234); 25 | if (auto m = as_system_message_view(c)) 26 | { 27 | group_t g = m->group(); 28 | status_t s = m->status(); 29 | uint7_t d1 = m->data_byte_1(); 30 | uint7_t d2 = m->data_byte_2(); 31 | uint14_t pos = m->get_song_position(); 32 | } 33 | } 34 | 35 | void system_message_readme_code() 36 | { 37 | using namespace midi; 38 | 39 | auto packet = make_system_message(0, system_status::song_select, 4); 40 | 41 | if (packet.type() == packet_type::system) 42 | { 43 | auto m = system_message_view{ packet }; 44 | 45 | // access message data 46 | if (m.status() == system_status::song_select) 47 | { 48 | auto song = m.data_byte_1(); 49 | } 50 | } 51 | 52 | if (auto m = as_system_message_view(packet)) 53 | { 54 | // access message data 55 | if (m->status() == system_status::song_position) 56 | { 57 | auto pos = m->get_song_position(); 58 | } 59 | } 60 | } 61 | 62 | void run_system_message_examples() 63 | { 64 | system_message_examples(); 65 | system_message_readme_code(); 66 | } 67 | -------------------------------------------------------------------------------- /docs/system_message.md: -------------------------------------------------------------------------------- 1 | # System Messages 2 | 3 | Create system messages by using the 4 | 5 | system_message make_system_message(group_t, status_t, uint7_t data1 = 0, uint7_t data2 = 0); 6 | system_message make_song_position_message(group_t, uint14_t position); 7 | 8 | factory functions. Use 9 | 10 | bool is_system_message(const universal_packet&); 11 | 12 | to filter for system messages, you may use the 13 | 14 | system_message_view(const universal_packet& ump) 15 | 16 | for further inspection of the message: 17 | 18 | if (is_system_message(packet)) 19 | { 20 | auto m = system_message_view{ packet }; 21 | 22 | group_t g = m.group(); 23 | status_t s = m.status(); 24 | uint7_t d1 = m.data_byte_1(); 25 | uint7_t d2 = m.data_byte_2(); 26 | } 27 | 28 | Alternatively use 29 | 30 | std::optional as_system_message_view(const universal_packet&); 31 | 32 | to filter and view creation is a single call: 33 | 34 | auto c = make_song_position_message(0, 0x1234); 35 | if (auto m = as_system_message_view(c)) 36 | { 37 | group_t g = m->group(); 38 | status_t s = m->status(); 39 | uint7_t d1 = m->data_byte_1(); 40 | uint7_t d2 = m->data_byte_2(); 41 | uint14_t pos = m->get_song_position(); 42 | } 43 | 44 | More code examples can be found in [`system_message.examples.cpp`](system_message.examples.cpp). 45 | -------------------------------------------------------------------------------- /docs/types.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | void velocity_examples() 8 | { 9 | using namespace midi; 10 | 11 | auto from7bit = velocity{ uint7_t{ 44 } }; 12 | auto from14bit = velocity{ uint14_t{ 0x0312 } }; 13 | auto fromFloat = velocity{ 0.7f }; 14 | auto fromDouble = velocity{ 0.98 }; 15 | 16 | auto as7bit = fromDouble.as_uint7(); 17 | auto as14bit = fromFloat.value; 18 | auto asFloat = from14bit.as_float(); 19 | auto asDouble = from7bit.as_double(); 20 | 21 | auto m = make_midi1_note_on_message(0, 0, note_nr_t{ 81 }, velocity{ 0.1 }); 22 | auto velA = get_note_velocity(m); 23 | } 24 | 25 | void pitch_bend_examples() 26 | { 27 | using namespace midi; 28 | 29 | auto from14bit = pitch_bend{ uint14_t{ 0x1234 } }; 30 | auto from32bit = pitch_bend{ uint32_t{ 0xA0000000 } }; 31 | auto fromFloat = pitch_bend{ -0.385f }; 32 | auto fromDouble = pitch_bend{ 0.98 }; 33 | 34 | auto as14bit = fromFloat.as_uint14(); 35 | auto as32bit = fromDouble.value; 36 | auto asFloat = from32bit.as_float(); 37 | auto asDouble = from14bit.as_double(); 38 | 39 | auto increment = from14bit * pitch_bend_sensitivity{ 4. }; 40 | } 41 | 42 | void pitch_increment_examples() 43 | { 44 | using namespace midi; 45 | 46 | auto fromInt32 = pitch_increment{ -44444 }; // small fraction of a cent 47 | auto fromFloat = pitch_increment{ -25.7f }; // 25700 cent 48 | auto fromDouble = pitch_increment{ 0.1 }; // 10 cent 49 | 50 | pitch_increment increment = fromFloat + fromDouble; 51 | increment += fromInt32; 52 | 53 | auto pitch = pitch_7_25{ note_nr_t{ 68 } } + increment; 54 | } 55 | 56 | void pitch_7_9_examples() 57 | { 58 | using namespace midi; 59 | 60 | auto fromNoteNr = pitch_7_9{ note_nr_t{ 74 } }; 61 | auto from16bit = pitch_7_9{ uint16_t{ 0x88A0 } }; 62 | auto fromFloat = pitch_7_9{ 69.4f }; 63 | auto fromDouble = pitch_7_9{ 127.0 }; 64 | 65 | auto asNoteNr = fromFloat.note_nr(); 66 | auto as16bit = from16bit.value; 67 | auto asFloat = fromDouble.as_float(); 68 | auto asDouble = fromNoteNr.as_double(); 69 | 70 | auto m = make_midi2_note_on_message(0, 0, note_nr_t{ 0 }, velocity{ 1. }, fromDouble); 71 | } 72 | 73 | void pitch_7_25_examples() 74 | { 75 | using namespace midi; 76 | 77 | auto fromNoteNr = pitch_7_25{ note_nr_t{ 12 } }; 78 | auto from32bit = pitch_7_25{ uint32_t{ 0xA10BFFFF } }; 79 | auto fromFloat = pitch_7_25{ 69.4f }; 80 | auto fromDouble = pitch_7_25{ 127.0 }; 81 | auto from7_9 = pitch_7_25{ pitch_7_9{ 33.7 } }; 82 | 83 | auto asNoteNr = fromFloat.note_nr(); 84 | auto as32bit = fromDouble.value; 85 | auto asFloat = from32bit.as_float(); 86 | auto asDouble = fromNoteNr.as_double(); 87 | 88 | pitch_7_25 pitch; 89 | pitch = pitch_7_9{ 33.4 }; 90 | pitch += pitch_increment{ -177993 }; 91 | 92 | pitch = pitch + 0.45f; 93 | pitch = pitch + 4.999; 94 | } 95 | 96 | void pitch_bend_sensitivity_examples() 97 | { 98 | using namespace midi; 99 | 100 | auto fromNoteNr = pitch_bend_sensitivity{ note_nr_t{ 2 } }; 101 | auto fromFloat = pitch_bend_sensitivity{ 3.5 }; 102 | auto fromDouble = pitch_bend_sensitivity{ 0.98 }; 103 | 104 | auto asNoteNr = fromFloat.note_nr(); 105 | auto asFloat = fromDouble.as_float(); 106 | auto asDouble = fromNoteNr.as_double(); 107 | 108 | pitch_increment i = pitch_bend{ -0.5 } * fromNoteNr; 109 | } 110 | 111 | void controller_increment_examples() 112 | { 113 | using namespace midi; 114 | 115 | auto inc = controller_increment{ 0x02000000 }; 116 | 117 | auto v = controller_value{ uint7_t{ 99 } } + inc; 118 | } 119 | 120 | void controller_value_examples() 121 | { 122 | using namespace midi; 123 | 124 | auto from7bit = controller_value{ uint7_t{ 44 } }; 125 | auto from32bit = controller_value{ uint32_t{ 0x17224028 } }; 126 | auto fromFloat = controller_value{ 0.34f }; 127 | auto fromDouble = controller_value{ 0.7754 }; 128 | 129 | auto as7bit = fromDouble.as_uint7(); 130 | auto as32bit = fromFloat.value; 131 | auto asFloat = from32bit.as_float(); 132 | auto asDouble = from7bit.as_double(); 133 | 134 | controller_value v{ 0.923157 }; 135 | v += controller_increment{ 0x200 }; 136 | v = v + controller_increment{ -0x400 }; 137 | 138 | auto m = make_midi2_control_change_message(0, 0, controller_t{ 21 }, v); 139 | auto v2 = get_controller_value(m); 140 | } 141 | 142 | void device_identity_examples() 143 | { 144 | using namespace midi; 145 | 146 | constexpr device_identity identity{ manufacturer::native_instruments, 0x2200, 49, 0x1234 }; 147 | 148 | auto m = universal_sysex::make_identity_reply(identity); 149 | } 150 | 151 | void downsample_examples() 152 | { 153 | using namespace midi; 154 | 155 | auto as7bit1 = downsample_16_to_7bit(0x1234); 156 | auto as7bit2 = downsample_32_to_7bit(0xFEDCBA98); 157 | auto as14bit = downsample_32_to_7bit(0xFEDCBA98); 158 | } 159 | 160 | void upsample_examples() 161 | { 162 | using namespace midi; 163 | 164 | auto as16bit = upsample_7_to_16bit(uint7_t{ 42 }); 165 | auto as32bit1 = upsample_7_to_32bit(uint7_t{ 42 }); 166 | auto as32bit2 = upsample_14_to_32bit(uint14_t{ 0x0567 }); 167 | 168 | auto as14bit = upsample_x_to_ybit(0x34, 6, 14); 169 | } 170 | 171 | void types_readme_code() 172 | { 173 | using namespace midi; 174 | 175 | const pitch_bend_sensitivity cur_pitch_bend_sensitivity{ note_nr_t{ 2 } }; 176 | const pitch_7_25 a_pitch_7_25{ note_nr_t{ 68 } }; 177 | const pitch_bend a_pitch_bend_value{ -0.44f }; 178 | 179 | auto pitch_with_pb = a_pitch_7_25 + a_pitch_bend_value * cur_pitch_bend_sensitivity; 180 | 181 | controller_value a_controller_value{ uint32_t{ 0x1234567 } }; 182 | controller_increment a_controller_increment{ 0x20000 }; 183 | 184 | a_controller_value += a_controller_increment; 185 | 186 | auto from7bit = controller_value{ uint7_t{ 44 } }; 187 | auto from32bit = controller_value{ uint32_t{ 0x45883312 } }; 188 | auto fromFloat = pitch_7_25{ 66.1f }; 189 | 190 | { 191 | auto v = velocity{ uint7_t{ 44 } }; 192 | auto pb1 = pitch_bend{ uint32_t{ 0x62311258 } }; 193 | auto pb2 = pitch_bend{ -0.128 }; 194 | auto p1 = pitch_7_25{ 81.3f }; 195 | auto p2 = pitch_7_25{ note_nr_t{ 68 } }; 196 | auto c1 = controller_value{ uint7_t{ 126 } }; 197 | auto c2 = controller_value{ 0.93 }; 198 | } 199 | 200 | { 201 | auto pb1 = pitch_bend{ uint32_t{ 0x62311258 } }.as_double(); // [-1.0 .. 1.0] 202 | auto pb2 = pitch_bend{ -0.128 }.as_uint14(); // 14 bit MIDI 1 203 | auto p1 = pitch_7_25{ 81.3f }.as_float(); // [0.0 .. 128.0) 204 | auto c1 = controller_value{ uint7_t{ 126 } }.as_double(); // [0.0 .. 1.0] 205 | } 206 | { 207 | pitch_7_25 p1{ note_nr_t{ 68 } }; 208 | p1 += pitch_increment{ -199 }; 209 | pitch_increment pbi = pitch_bend{ -0.128 } * pitch_bend_sensitivity{ note_nr_t{ 2 } }; 210 | pitch_7_25 p3 = p1 + pbi; 211 | pitch_7_25 p4 = p3 + 0.011f; 212 | 213 | controller_value v1 = controller_value{ 0.44 } + controller_increment{ -1234 }; 214 | v1 += controller_increment{ 0x4000 }; 215 | } 216 | } 217 | 218 | void run_types_examples() 219 | { 220 | velocity_examples(); 221 | pitch_bend_examples(); 222 | pitch_increment_examples(); 223 | pitch_7_9_examples(); 224 | pitch_7_25_examples(); 225 | pitch_bend_sensitivity_examples(); 226 | controller_increment_examples(); 227 | controller_value_examples(); 228 | device_identity_examples(); 229 | downsample_examples(); 230 | upsample_examples(); 231 | types_readme_code(); 232 | } 233 | -------------------------------------------------------------------------------- /docs/types.md: -------------------------------------------------------------------------------- 1 | # Common Types and Scaling Helpers 2 | 3 | Code examples can be found in [`types.examples.cpp`](types.examples.cpp). 4 | 5 | ## Common Types 6 | 7 | To improve readability, the library defines type aliases for several MIDI entities: 8 | 9 | using uint2_t = std::uint8_t; 10 | using uint4_t = std::uint8_t; 11 | using uint7_t = std::uint8_t; 12 | using uint8_t = std::uint8_t; 13 | using uint14_t = std::uint16_t; 14 | using uint16_t = std::uint16_t; 15 | using uint28_t = std::uint32_t; 16 | using uint32_t = std::uint32_t; 17 | using int32_t = std::int32_t; 18 | using size_t = std::size_t; 19 | 20 | using group_t = uint4_t; 21 | using status_t = uint8_t; 22 | using channel_t = uint4_t; 23 | using note_nr_t = uint7_t; 24 | using controller_t = uint7_t; 25 | using program_t = uint7_t; 26 | using muid_t = uint28_t; 27 | using manufacturer_t = uint32_t; 28 | using protocol_t = uint8_t; 29 | using extensions_t = uint8_t; 30 | 31 | Intentionally no strong types are used, this may change in a future version, maybe configurable by a `cmake` option. 32 | 33 | Additionally strong types are available for core MIDI concepts: 34 | 35 | struct velocity; 36 | struct pitch_bend; 37 | struct pitch_increment; 38 | struct pitch_7_9; 39 | struct pitch_7_25; 40 | struct pitch_bend_sensitivity; 41 | struct controller_increment; 42 | struct controller_value; 43 | struct device_identity; 44 | 45 | Instances of these types can be initialized with native MIDI 1 bit depths (7 or 14) and with high resolution values (16 or 32 bits). 46 | 47 | auto v = velocity{ uint7_t{ 44 } }; 48 | auto pb1 = pitch_bend{ uint32_t { 0x62311258 } }; 49 | auto pb2 = pitch_bend{ -0.128 }; 50 | auto p1 = pitch_7_25{ 81.3f }; 51 | auto p2 = pitch_7_25{ note_nr_t { 68 } }; 52 | auto c1 = controller_value{ uint7_t{ 126 } }; 53 | auto c2 = controller_value{ 0.93 }; 54 | 55 | One can query the underlying value for different bit depths / types: 56 | 57 | auto pb1 = pitch_bend{ uint32_t { 0x62311258 } }.as_double(); // [-1.0 .. 1.0] 58 | auto pb2 = pitch_bend{ -0.128 }.as_uint14(); // 14 bit MIDI 1 59 | auto p1 = pitch_7_25{ 81.3f }.as_float(); // [0.0 .. 128.0) 60 | auto c1 = controller_value{ uint7_t{ 126 } }.as_double(); // [0.0 .. 1.0] 61 | 62 | There are numerical operators to do math with related types: 63 | 64 | pitch_7_25 p1 += pitch_increment{ -199 }; 65 | pitch_increment pbi = pitch_bend{ -0.128 } * pitch_bend_sensitivity{ note_nr_t{ 2 } }; 66 | pitch_7_25 p3 = p1 + pbi; 67 | pitch_7_25 p4 = p3 + 0.011f; 68 | 69 | controller_value v1 = controller_value{ 0.44 } + controller_increment{ -1234 }; 70 | v1 += controller_increment{ 0x4000 }; 71 | 72 | ## Scaling Helpers 73 | 74 | The MIDI 2 specifications stricly defines how values should be down- and upscaled between different bit depths. 75 | 76 | There are predefined helper functions for the typical use cases: 77 | 78 | auto as7bit1 = downsample_16_to_7bit(0x1234); 79 | auto as7bit2 = downsample_32_to_7bit(0xFEDCBA98); 80 | auto as14bit = downsample_32_to_7bit(0xFEDCBA98); 81 | 82 | auto as16bit = upsample_7_to_16bit(uint7_t{ 42 }); 83 | auto as32bit1 = upsample_7_to_32bit(uint7_t{ 42 }); 84 | auto as32bit2 = upsample_14_to_32bit(uint14_t{ 0x0567 }); 85 | 86 | Additionally there is a function that upsamples values between arbitrary bit depths: 87 | 88 | auto as14bit = upsample_x_to_ybit(0x34, 6, 14); 89 | 90 | Different to upsampling downsampling can be implemented using standard bit shifting: 91 | 92 | auto as14bit = 20bitValue >> 6; 93 | -------------------------------------------------------------------------------- /docs/universal_sysex.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* TODO */ 4 | -------------------------------------------------------------------------------- /docs/universal_sysex.md: -------------------------------------------------------------------------------- 1 | # Universal System Exclusive Messages 2 | 3 | **TODO** 4 | -------------------------------------------------------------------------------- /docs/utility_message.examples.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* TODO */ 4 | -------------------------------------------------------------------------------- /docs/utility_message.md: -------------------------------------------------------------------------------- 1 | # Utility Messages 2 | 3 | ## Message Creation and Filtering 4 | 5 | Utility Messages are represented by a 6 | 7 | struct utility_message : universal_packet 8 | { 9 | utility_message(status_t, uint16_t payload = 0u); 10 | 11 | // utility messages are groupless since UMP 1.1 12 | constexpr group_t group() const = delete; 13 | constexpr void set_group(group_t) = delete; 14 | }; 15 | 16 | As Utility messages are groupless the `group` accessors are deleted. 17 | 18 | Instead of using `utility_message` constructors one can create messages using factory functions: 19 | 20 | constexpr utility_message make_utility_message(status_t, uint16_t data); 21 | 22 | Filtering of Utility Messages can be done checking `universal_packet::type()` against 23 | `packet_type::utility` or use 24 | 25 | bool universal_packet::is_utility_message(); 26 | 27 | Currently the only use case for Utility messages are [Jitter Reduction Timestamps](../inc/midi/jitter_reduction_timestamps.h). 28 | 29 | ## Message View 30 | 31 | Once validated against `packet_type::utility` one can create a `utility_message_view` 32 | for a `universal_packet`: 33 | 34 | struct utility_message_view 35 | { 36 | utility_message_view(const universal_packet&); 37 | 38 | constexpr status_t status() const; 39 | constexpr uint16_t payload() const; 40 | }; 41 | -------------------------------------------------------------------------------- /inc/midi/data_message.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | //-------------------------------------------------------------------------- 34 | 35 | namespace midi { 36 | 37 | //-------------------------------------------------------------------------- 38 | 39 | struct data_message : universal_packet 40 | { 41 | constexpr data_message(); 42 | constexpr explicit data_message(status_t); 43 | ~data_message() = default; 44 | }; 45 | 46 | //-------------------------------------------------------------------------- 47 | 48 | struct sysex7_packet : data_message 49 | { 50 | constexpr sysex7_packet() = default; 51 | constexpr sysex7_packet(status_t, group_t); 52 | ~sysex7_packet() = default; 53 | 54 | constexpr packet_format format() const { return packet_format((status() >> 4) & 0b11); } 55 | 56 | constexpr uint8_t payload_byte(size_t b) const { return get_byte(2 + b); } 57 | constexpr void set_payload_byte(size_t, uint8_t); 58 | 59 | constexpr size_t payload_size() const { return status() & 0x0F; } 60 | constexpr void set_payload_size(size_t); 61 | 62 | constexpr void add_payload_byte(uint8_t); 63 | }; 64 | 65 | //-------------------------------------------------------------------------- 66 | 67 | constexpr bool is_data_message(const universal_packet&); 68 | constexpr bool is_sysex7_packet(const universal_packet&); 69 | 70 | //-------------------------------------------------------------------------- 71 | 72 | struct sysex7_packet_view 73 | { 74 | constexpr explicit sysex7_packet_view(const universal_packet& ump) 75 | : p(ump) 76 | { 77 | assert(is_sysex7_packet(ump)); 78 | } 79 | 80 | constexpr group_t group() const { return p.group(); } 81 | constexpr status_t status() const { return p.status() & 0xF0; } 82 | constexpr packet_format format() const { return packet_format((p.status() >> 4) & 0b11); } 83 | constexpr size_t payload_size() const { return p.status() & 0x0F; } 84 | constexpr uint8_t payload_byte(size_t b) const { return p.get_byte(2 + b); } 85 | 86 | private: 87 | const universal_packet& p; 88 | }; 89 | 90 | //-------------------------------------------------------------------------- 91 | 92 | constexpr std::optional as_sysex7_packet_view(const universal_packet&); 93 | 94 | //-------------------------------------------------------------------------- 95 | 96 | constexpr sysex7_packet make_sysex7_complete_packet(group_t = 0); 97 | constexpr sysex7_packet make_sysex7_start_packet(group_t = 0); 98 | constexpr sysex7_packet make_sysex7_continue_packet(group_t = 0); 99 | constexpr sysex7_packet make_sysex7_end_packet(group_t = 0); 100 | 101 | //-------------------------------------------------------------------------- 102 | 103 | constexpr data_message::data_message() 104 | : universal_packet(0x30000000) 105 | { 106 | } 107 | constexpr data_message::data_message(status_t status) 108 | : universal_packet(0x30000000u | (status << 16)) 109 | { 110 | } 111 | 112 | //-------------------------------------------------------------------------- 113 | 114 | constexpr sysex7_packet::sysex7_packet(status_t status, group_t group) 115 | : data_message(status) 116 | { 117 | set_group(group); 118 | } 119 | 120 | constexpr void sysex7_packet::set_payload_byte(size_t b, uint8_t data) 121 | { 122 | set_byte_7bit(2 + b, data); 123 | } 124 | 125 | constexpr void sysex7_packet::set_payload_size(size_t size) 126 | { 127 | assert(size <= 6); 128 | set_byte(1, (status() & 0xF0) + (size & 0x0F)); 129 | } 130 | 131 | constexpr void sysex7_packet::add_payload_byte(uint8_t byte) 132 | { 133 | const auto size = payload_size(); 134 | assert(size < 6); 135 | set_byte_7bit(2 + size, byte); 136 | set_payload_size(size + 1); 137 | } 138 | 139 | //-------------------------------------------------------------------------- 140 | 141 | constexpr sysex7_packet make_sysex7_complete_packet(group_t group) 142 | { 143 | return sysex7_packet{ data_status::sysex7_complete, group }; 144 | } 145 | constexpr sysex7_packet make_sysex7_start_packet(group_t group) 146 | { 147 | return sysex7_packet{ data_status::sysex7_start, group }; 148 | } 149 | constexpr sysex7_packet make_sysex7_continue_packet(group_t group) 150 | { 151 | return sysex7_packet{ data_status::sysex7_continue, group }; 152 | } 153 | constexpr sysex7_packet make_sysex7_end_packet(group_t group) 154 | { 155 | return sysex7_packet{ data_status::sysex7_end, group }; 156 | } 157 | 158 | //-------------------------------------------------------------------------- 159 | 160 | constexpr bool is_data_message(const universal_packet& p) 161 | { 162 | return p.type() == packet_type::data; 163 | } 164 | constexpr bool is_sysex7_packet(const universal_packet& p) 165 | { 166 | return is_data_message(p) && ((p.status() & 0xF0) <= data_status::sysex7_end) && ((p.status() & 0x0F) <= 6); 167 | } 168 | constexpr std::optional as_sysex7_packet_view(const universal_packet& p) 169 | { 170 | if (is_sysex7_packet(p)) 171 | return sysex7_packet_view{ p }; 172 | else 173 | return std::nullopt; 174 | } 175 | 176 | //-------------------------------------------------------------------------- 177 | 178 | } // namespace midi 179 | 180 | //-------------------------------------------------------------------------- 181 | -------------------------------------------------------------------------------- /inc/midi/extended_data_message.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | 30 | //-------------------------------------------------------------------------- 31 | 32 | #include 33 | #include 34 | 35 | //-------------------------------------------------------------------------- 36 | 37 | namespace midi { 38 | 39 | //-------------------------------------------------------------------------- 40 | 41 | struct extended_data_message : universal_packet 42 | { 43 | constexpr extended_data_message(); 44 | constexpr explicit extended_data_message(status_t); 45 | ~extended_data_message() = default; 46 | }; 47 | 48 | //-------------------------------------------------------------------------- 49 | 50 | struct sysex8_packet : extended_data_message 51 | { 52 | constexpr sysex8_packet(); 53 | constexpr sysex8_packet(status_t, uint8_t stream_id, group_t); 54 | ~sysex8_packet() = default; 55 | 56 | constexpr packet_format format() const { return packet_format((status() >> 4) & 0b11); } 57 | 58 | constexpr uint8_t stream_id() const { return get_byte(2); } 59 | constexpr void set_stream_id(uint8_t); 60 | 61 | constexpr uint8_t payload_byte(size_t b) const { return get_byte(3 + b); } 62 | constexpr void set_payload_byte(size_t, uint8_t); 63 | 64 | constexpr size_t payload_size() const; 65 | constexpr void set_payload_size(size_t); 66 | 67 | constexpr void add_payload_byte(uint8_t); 68 | }; 69 | 70 | //-------------------------------------------------------------------------- 71 | 72 | constexpr bool is_extended_data_message(const universal_packet&); 73 | constexpr bool is_sysex8_packet(const universal_packet&); 74 | 75 | //-------------------------------------------------------------------------- 76 | 77 | struct sysex8_packet_view 78 | { 79 | constexpr explicit sysex8_packet_view(const universal_packet& ump) 80 | : p(ump) 81 | { 82 | assert(is_sysex8_packet(ump)); 83 | } 84 | 85 | constexpr group_t group() const { return p.group(); } 86 | constexpr packet_format format() const { return packet_format((p.status() >> 4) & 0b11); } 87 | constexpr uint8_t stream_id() const { return p.get_byte(2); } 88 | constexpr size_t payload_size() const; 89 | constexpr uint8_t payload_byte(size_t b) const { return p.get_byte(3 + b); } 90 | 91 | private: 92 | const universal_packet& p; 93 | }; 94 | 95 | //-------------------------------------------------------------------------- 96 | 97 | constexpr std::optional as_sysex8_packet_view(const universal_packet&); 98 | 99 | //-------------------------------------------------------------------------- 100 | 101 | constexpr sysex8_packet make_sysex8_complete_packet(uint8_t stream_id, group_t = 0); 102 | constexpr sysex8_packet make_sysex8_start_packet(uint8_t stream_id, group_t = 0); 103 | constexpr sysex8_packet make_sysex8_continue_packet(uint8_t stream_id, group_t = 0); 104 | constexpr sysex8_packet make_sysex8_end_packet(uint8_t stream_id, group_t = 0); 105 | 106 | //-------------------------------------------------------------------------- 107 | // constexpr implementations 108 | //-------------------------------------------------------------------------- 109 | 110 | constexpr extended_data_message::extended_data_message() 111 | : universal_packet(0x50000000) 112 | { 113 | } 114 | constexpr extended_data_message::extended_data_message(status_t status) 115 | : universal_packet(0x50000000u | (status << 16)) 116 | { 117 | } 118 | 119 | //-------------------------------------------------------------------------- 120 | 121 | constexpr sysex8_packet::sysex8_packet() 122 | { 123 | data[0] |= 0x00010000u; 124 | } 125 | constexpr sysex8_packet::sysex8_packet(status_t status, uint8_t stream_id, group_t group) 126 | { 127 | data[0] = 0x50010000u | (group << 24) | (status << 16) | (stream_id << 8); 128 | } 129 | 130 | constexpr void sysex8_packet::set_stream_id(uint8_t i) 131 | { 132 | set_byte(2, i); 133 | } 134 | 135 | constexpr size_t sysex8_packet::payload_size() const 136 | { 137 | const auto s = static_cast(status() & 0x0F); 138 | return (s > 0 ? s - 1 : 0); 139 | } 140 | 141 | constexpr void sysex8_packet::set_payload_size(size_t size) 142 | { 143 | assert(size <= 13); 144 | set_byte(1, (status() & 0xF0) + ((size + 1) & 0x0F)); 145 | } 146 | 147 | constexpr void sysex8_packet::set_payload_byte(size_t b, uint8_t data) 148 | { 149 | set_byte(3 + b, data); 150 | } 151 | 152 | constexpr void sysex8_packet::add_payload_byte(uint8_t byte) 153 | { 154 | const auto size = payload_size(); 155 | assert(size < 13); 156 | set_byte(3 + size, byte); 157 | set_payload_size(size + 1); 158 | } 159 | 160 | //-------------------------------------------------------------------------- 161 | 162 | constexpr bool is_extended_data_message(const universal_packet& p) 163 | { 164 | return p.type() == packet_type::extended_data; 165 | } 166 | 167 | constexpr bool is_sysex8_packet(const universal_packet& p) 168 | { 169 | return is_extended_data_message(p) && ((p.status() & 0xF0) <= extended_data_status::sysex8_end) && 170 | ((p.status() & 0x0F) > 0) && ((p.status() & 0x0F) <= 14); 171 | } 172 | 173 | //-------------------------------------------------------------------------- 174 | 175 | constexpr sysex8_packet make_sysex8_complete_packet(uint8_t stream_id, group_t group) 176 | { 177 | return sysex8_packet{ extended_data_status::sysex8_complete, stream_id, group }; 178 | } 179 | constexpr sysex8_packet make_sysex8_start_packet(uint8_t stream_id, group_t group) 180 | { 181 | return sysex8_packet{ extended_data_status::sysex8_start, stream_id, group }; 182 | } 183 | constexpr sysex8_packet make_sysex8_continue_packet(uint8_t stream_id, group_t group) 184 | { 185 | return sysex8_packet{ extended_data_status::sysex8_continue, stream_id, group }; 186 | } 187 | constexpr sysex8_packet make_sysex8_end_packet(uint8_t stream_id, group_t group) 188 | { 189 | return sysex8_packet{ extended_data_status::sysex8_end, stream_id, group }; 190 | } 191 | 192 | //-------------------------------------------------------------------------- 193 | 194 | constexpr size_t sysex8_packet_view::payload_size() const 195 | { 196 | const auto s = static_cast(p.status() & 0x0F); 197 | return (s > 0 ? s - 1 : 0); 198 | } 199 | 200 | constexpr std::optional as_sysex8_packet_view(const universal_packet& p) 201 | { 202 | if (is_sysex8_packet(p)) 203 | return sysex8_packet_view{ p }; 204 | else 205 | return std::nullopt; 206 | } 207 | 208 | //-------------------------------------------------------------------------- 209 | 210 | } // namespace midi 211 | 212 | //-------------------------------------------------------------------------- 213 | -------------------------------------------------------------------------------- /inc/midi/jitter_reduction_timestamps.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | 30 | //-------------------------------------------------------------------------- 31 | 32 | #include 33 | 34 | //-------------------------------------------------------------------------- 35 | 36 | namespace midi { 37 | 38 | //-------------------------------------------------------------------------- 39 | 40 | constexpr uint16_t jr_clock_frequency = 31250; 41 | 42 | //-------------------------------------------------------------------------- 43 | 44 | using jr_ticks = std::chrono::duration>; 45 | 46 | //-------------------------------------------------------------------------- 47 | 48 | struct jr_timestamp_t 49 | { 50 | uint16_t value; 51 | 52 | jr_timestamp_t() 53 | : value(0) 54 | { 55 | } 56 | explicit jr_timestamp_t(uint16_t v) 57 | : value(v) 58 | { 59 | } 60 | 61 | bool operator==(jr_timestamp_t other) const { return (value == other.value); } 62 | bool operator!=(jr_timestamp_t other) const { return (value != other.value); } 63 | 64 | jr_ticks operator-(jr_timestamp_t other) const 65 | { 66 | int diff = static_cast(value) - static_cast(other.value); 67 | if (diff < 0) 68 | diff += 0x10000; 69 | return jr_ticks{ static_cast(diff) }; 70 | } 71 | }; 72 | 73 | //-------------------------------------------------------------------------- 74 | 75 | class jr_clock 76 | { 77 | public: 78 | typedef jr_ticks duration; 79 | typedef duration::rep rep; 80 | typedef duration::period period; 81 | typedef uint16_t time_point; 82 | 83 | static jr_timestamp_t now(); 84 | }; 85 | 86 | //-------------------------------------------------------------------------- 87 | //! base class for Jitter Reduction messages 88 | struct jr_message : universal_packet 89 | { 90 | inline jr_timestamp_t timestamp() const 91 | { 92 | return jr_timestamp_t(static_cast(get_byte(2) << 8) | get_byte(3)); 93 | } 94 | inline void set_timestamp(jr_timestamp_t ts) 95 | { 96 | set_byte(2, static_cast(ts.value >> 8)); 97 | set_byte(3, static_cast(ts.value & 0xFF)); 98 | }; 99 | 100 | protected: 101 | inline explicit jr_message(uint8_t status) 102 | { 103 | set_type(packet_type::utility); 104 | set_byte(1, status); 105 | } 106 | inline jr_message(uint8_t status, jr_timestamp_t ts) 107 | : jr_message(status) 108 | { 109 | set_timestamp(ts); 110 | } 111 | }; 112 | 113 | //-------------------------------------------------------------------------- 114 | //! Jitter Reduction Clock message 115 | struct jr_clock_message : jr_message 116 | { 117 | inline jr_clock_message() 118 | : jr_message(utility_status::jr_clock, {}) 119 | { 120 | } 121 | inline explicit jr_clock_message(jr_timestamp_t ts) 122 | : jr_message(utility_status::jr_clock, ts) 123 | { 124 | } 125 | }; 126 | 127 | //-------------------------------------------------------------------------- 128 | //! Jitter Reduction Timestamp message 129 | struct jr_timestamp_message : jr_message 130 | { 131 | inline jr_timestamp_message() 132 | : jr_message(utility_status::jr_timestamp, {}) 133 | { 134 | } 135 | inline explicit jr_timestamp_message(jr_timestamp_t ts) 136 | : jr_message(utility_status::jr_timestamp, ts) 137 | { 138 | } 139 | }; 140 | 141 | //-------------------------------------------------------------------------- 142 | 143 | class jr_clock_follower 144 | { 145 | public: 146 | jr_clock_follower() = default; 147 | 148 | void reset(); 149 | 150 | using system_clock = std::chrono::high_resolution_clock; 151 | using time_point = system_clock::time_point; 152 | using duration = system_clock::duration; 153 | 154 | void process_clock(time_point, jr_timestamp_t); 155 | time_point schedule_message(time_point, jr_timestamp_t); 156 | 157 | inline duration security_offset() const { return m_security_offset; } 158 | inline duration jitter() const { return m_jitter; } 159 | 160 | void set_security_offset(duration); 161 | 162 | protected: 163 | void update_stats(duration jitter); 164 | void recalc_security_offset(); 165 | 166 | private: 167 | const system_clock::time_point zero = system_clock::now(); 168 | 169 | jr_timestamp_t m_clock_ts{ 0 }; //!< last jr clock timestamp 170 | time_point m_clock_time{ zero }; //!< jitter-reduced send time of last jr clock 171 | time_point m_message_time{ zero }; //!< schedule time of last message 172 | 173 | duration m_jitter{ 0 }; //!< current jitter 174 | duration m_security_offset{ std::chrono::milliseconds(2) }; //!< security offset, default two ms 175 | }; 176 | 177 | //-------------------------------------------------------------------------- 178 | 179 | } // namespace midi 180 | 181 | //-------------------------------------------------------------------------- 182 | -------------------------------------------------------------------------------- /inc/midi/manufacturer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | 29 | //-------------------------------------------------------------------------- 30 | 31 | namespace midi { 32 | 33 | //-------------------------------------------------------------------------- 34 | 35 | using manufacturer_t = uint32_t; 36 | 37 | //-------------------------------------------------------------------------- 38 | 39 | namespace manufacturer { 40 | constexpr manufacturer_t sequential_circuits = 0x010000; 41 | constexpr manufacturer_t moog = 0x040000; 42 | constexpr manufacturer_t lexicon = 0x060000; 43 | constexpr manufacturer_t kurzweil = 0x070000; 44 | constexpr manufacturer_t fender = 0x080000; 45 | constexpr manufacturer_t oberheim = 0x100000; 46 | constexpr manufacturer_t midi9 = 0x090000; 47 | constexpr manufacturer_t simmons = 0x120000; 48 | constexpr manufacturer_t digidesign = 0x130000; 49 | constexpr manufacturer_t fairlight = 0x140000; 50 | constexpr manufacturer_t emu = 0x180000; 51 | constexpr manufacturer_t hohner = 0x240000; 52 | constexpr manufacturer_t ppg = 0x290000; 53 | constexpr manufacturer_t wersi = 0x3B0000; 54 | constexpr manufacturer_t waldorf = 0x3E0000; 55 | constexpr manufacturer_t kawai = 0x400000; 56 | constexpr manufacturer_t roland = 0x410000; 57 | constexpr manufacturer_t korg = 0x420000; 58 | constexpr manufacturer_t yamaha = 0x430000; 59 | constexpr manufacturer_t casio = 0x440000; 60 | constexpr manufacturer_t akai = 0x470000; 61 | constexpr manufacturer_t fujitsu = 0x4B0000; 62 | constexpr manufacturer_t sony = 0x4C0000; 63 | constexpr manufacturer_t teac = 0x4E0000; 64 | constexpr manufacturer_t fostex = 0x510000; 65 | constexpr manufacturer_t zoom = 0x520000; 66 | constexpr manufacturer_t suzuki = 0x550000; 67 | 68 | constexpr manufacturer_t educational = 0x7D0000; 69 | constexpr manufacturer_t universal_non_realtime = 0x7E0000; 70 | constexpr manufacturer_t universal_realtime = 0x7F0000; 71 | 72 | constexpr manufacturer_t rane = 0x000017; 73 | constexpr manufacturer_t allen_heath = 0x00001A; 74 | constexpr manufacturer_t motu = 0x00003B; 75 | constexpr manufacturer_t atari = 0x000058; 76 | constexpr manufacturer_t mackie = 0x000066; 77 | constexpr manufacturer_t amd = 0x00006F; 78 | 79 | constexpr manufacturer_t m_audio = 0x000105; 80 | constexpr manufacturer_t microsoft = 0x00010A; 81 | constexpr manufacturer_t line6 = 0x00010C; 82 | constexpr manufacturer_t cakewalk = 0x000121; 83 | constexpr manufacturer_t numark = 0x00013F; 84 | 85 | constexpr manufacturer_t denon = 0x00020B; 86 | constexpr manufacturer_t google = 0x00020D; 87 | constexpr manufacturer_t imitone = 0x000213; 88 | constexpr manufacturer_t universal_audio = 0x000218; 89 | constexpr manufacturer_t sensel = 0x00021D; 90 | constexpr manufacturer_t mk2_image = 0x000222; 91 | 92 | constexpr manufacturer_t bon_tempi = 0x00200B; 93 | constexpr manufacturer_t fatar = 0x00201A; 94 | constexpr manufacturer_t pinnacle = 0x00201E; 95 | constexpr manufacturer_t tc_electronics = 0x00201F; 96 | constexpr manufacturer_t doepfer = 0x002020; 97 | constexpr manufacturer_t focusrite = 0x002029; 98 | constexpr manufacturer_t novation = 0x002029; 99 | constexpr manufacturer_t emagic = 0x002031; 100 | constexpr manufacturer_t behringer = 0x002032; 101 | constexpr manufacturer_t access = 0x002033; 102 | constexpr manufacturer_t terratec = 0x002036; 103 | constexpr manufacturer_t propellerhead = 0x00203A; 104 | constexpr manufacturer_t klavis = 0x002047; 105 | constexpr manufacturer_t vermona = 0x00204D; 106 | constexpr manufacturer_t nokia = 0x00204E; 107 | constexpr manufacturer_t hartmann = 0x002050; 108 | constexpr manufacturer_t waves = 0x002066; 109 | constexpr manufacturer_t arturia = 0x00206B; 110 | constexpr manufacturer_t hanpin = 0x002079; 111 | constexpr manufacturer_t serato = 0x00207F; 112 | constexpr manufacturer_t presonus = 0x002103; 113 | constexpr manufacturer_t native_instruments = 0x002109; 114 | constexpr manufacturer_t ploytec = 0x00210D; 115 | constexpr manufacturer_t roli = 0x002110; 116 | constexpr manufacturer_t ik_multimedia = 0x00211A; 117 | constexpr manufacturer_t ableton = 0x00211D; 118 | constexpr manufacturer_t bome = 0x002132; 119 | constexpr manufacturer_t touchkeys = 0x002136; 120 | } // namespace manufacturer 121 | 122 | //-------------------------------------------------------------------------- 123 | 124 | } // namespace midi 125 | 126 | //-------------------------------------------------------------------------- 127 | -------------------------------------------------------------------------------- /inc/midi/midi1_byte_stream.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | //-------------------------------------------------------------------------- 36 | 37 | namespace midi { 38 | 39 | //-------------------------------------------------------------------------- 40 | 41 | class midi1_byte_stream_parser 42 | { 43 | public: 44 | using packet_callback = std::function; 45 | using sysex_callback = std::function; 46 | 47 | explicit midi1_byte_stream_parser(packet_callback, sysex_callback = {}, bool enable_callbacks = true); 48 | midi1_byte_stream_parser(group_t, packet_callback, sysex_callback = {}, bool enable_callbacks = true); 49 | 50 | bool callbacks_enabled() const { return m_invoke_callbacks; } 51 | void enable_callbacks(bool enable) { m_invoke_callbacks = enable; } 52 | 53 | group_t group() const; 54 | void set_group(group_t); 55 | 56 | void feed(uint8_t); 57 | void feed(const uint8_t* data, size_t num_bytes); 58 | void feed(const uint8_t* begin, const uint8_t* end); 59 | 60 | void reset(); 61 | 62 | protected: 63 | void system_realtime(uint8_t); 64 | void system_common(uint8_t); 65 | void channel_voice(uint8_t); 66 | 67 | bool has_sysex_callback() const { return static_cast(m_sysex_callback); } 68 | 69 | void sysex_start(); 70 | void sysex_continue_callback(uint8_t); 71 | void sysex_end_callback(); 72 | void sysex_continue_packet(uint8_t); 73 | void sysex_end_packet(); 74 | 75 | private: 76 | uint8_t m_group{ 0 }; 77 | packet_callback m_packet_callback; 78 | sysex_callback m_sysex_callback; 79 | bool m_invoke_callbacks{ true }; 80 | 81 | universal_packet m_packet; 82 | sysex7 m_sysex; 83 | 84 | uint8_t m_packet_byte{ 0 }; 85 | uint8_t m_num_missing_bytes{ 0 }; 86 | }; 87 | 88 | //-------------------------------------------------------------------------- 89 | 90 | constexpr universal_packet from_midi1_byte_stream(uint8_t status, uint7_t d1, uint7_t d2); 91 | 92 | //-------------------------------------------------------------------------- 93 | 94 | constexpr size_t midi1_byte_stream_size(const universal_packet&); 95 | 96 | //-------------------------------------------------------------------------- 97 | 98 | size_t to_midi1_byte_stream(const universal_packet&, uint8_t bytes[8]); 99 | 100 | //-------------------------------------------------------------------------- 101 | 102 | inline midi1_byte_stream_parser::midi1_byte_stream_parser(packet_callback pcb, 103 | sysex_callback sxcb, 104 | bool enable_callbacks) 105 | : m_packet_callback(std::move(pcb)) 106 | , m_sysex_callback(std::move(sxcb)) 107 | , m_invoke_callbacks(enable_callbacks) 108 | { 109 | } 110 | 111 | inline midi1_byte_stream_parser::midi1_byte_stream_parser(group_t group, 112 | packet_callback pcb, 113 | sysex_callback sxcb, 114 | bool enable_callbacks) 115 | : m_group(group) 116 | , m_packet_callback(std::move(pcb)) 117 | , m_sysex_callback(std::move(sxcb)) 118 | , m_invoke_callbacks(enable_callbacks) 119 | { 120 | } 121 | 122 | //-------------------------------------------------------------------------- 123 | 124 | inline group_t midi1_byte_stream_parser::group() const 125 | { 126 | return m_group; 127 | } 128 | 129 | //-------------------------------------------------------------------------- 130 | 131 | inline void midi1_byte_stream_parser::set_group(group_t group) 132 | { 133 | m_group = group; 134 | } 135 | 136 | //-------------------------------------------------------------------------- 137 | 138 | inline void midi1_byte_stream_parser::feed(const uint8_t* data, size_t num_bytes) 139 | { 140 | feed(data, data + num_bytes); 141 | } 142 | 143 | //-------------------------------------------------------------------------- 144 | 145 | constexpr size_t midi1_byte_stream_size(const universal_packet& p) 146 | { 147 | switch (p.type()) 148 | { 149 | case packet_type::system: 150 | switch (p.status()) 151 | { 152 | case system_status::song_position: 153 | return 3u; 154 | case system_status::mtc_quarter_frame: 155 | case system_status::song_select: 156 | return 2u; 157 | case system_status::tune_request: 158 | case system_status::clock: 159 | case system_status::start: 160 | case system_status::cont: 161 | case system_status::stop: 162 | case system_status::active_sense: 163 | case system_status::reset: 164 | return 1u; 165 | } 166 | break; 167 | case packet_type::midi1_channel_voice: 168 | switch (p.status() & 0xF0) 169 | { 170 | case midi1_channel_voice_status::note_off: 171 | case midi1_channel_voice_status::note_on: 172 | case midi1_channel_voice_status::poly_pressure: 173 | case midi1_channel_voice_status::control_change: 174 | case midi1_channel_voice_status::pitch_bend: 175 | case system_status::song_position: 176 | return 3u; 177 | case midi1_channel_voice_status::program_change: 178 | case midi1_channel_voice_status::channel_pressure: 179 | return 2u; 180 | } 181 | break; 182 | case packet_type::data: 183 | switch (p.status() & 0xF0) 184 | { 185 | case data_status::sysex7_complete: 186 | case data_status::sysex7_start: 187 | case data_status::sysex7_end: 188 | case data_status::sysex7_continue: 189 | if ((p.status() & 0x0F) <= 6) 190 | return p.status() & 0x0F; 191 | break; 192 | } 193 | break; 194 | default: 195 | break; 196 | } 197 | 198 | return 0u; 199 | } 200 | 201 | //-------------------------------------------------------------------------- 202 | 203 | constexpr universal_packet from_midi1_byte_stream(uint8_t status, uint7_t d1, uint7_t d2) 204 | { 205 | uint8_t type = static_cast(packet_type::midi1_channel_voice); 206 | 207 | if ((status & 0xF0) == 0xF0) // System message? 208 | { 209 | type = static_cast(packet_type::system); 210 | 211 | switch (status) 212 | { 213 | case 0xF0: 214 | case 0xF7: 215 | // assert((status!=0xF0) && (status!=0xF7)); // use sysex() instead! 216 | // fall through 217 | case 0xF4: 218 | case 0xF5: 219 | case 0xF9: 220 | case 0xFD: 221 | // reserved 222 | return {}; 223 | default: 224 | break; 225 | } 226 | } 227 | else if (status < 0x80) 228 | { 229 | return {}; 230 | } 231 | 232 | return universal_packet{ static_cast((type << 28) | (status << 16u) | (d1 << 8u) | d2) }; 233 | } 234 | 235 | //-------------------------------------------------------------------------- 236 | 237 | } // namespace midi 238 | 239 | //-------------------------------------------------------------------------- 240 | -------------------------------------------------------------------------------- /inc/midi/sysex_collector.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | //-------------------------------------------------------------------------- 34 | 35 | namespace midi { 36 | 37 | //-------------------------------------------------------------------------- 38 | 39 | class sysex7_collector 40 | { 41 | public: 42 | using callback = std::function; 43 | 44 | explicit sysex7_collector(callback); 45 | 46 | void set_callback(callback); 47 | void set_max_sysex_data_size(size_t); //!< limit maximum size of accepted sysex data 48 | 49 | void feed(const universal_packet&); 50 | void reset(); 51 | 52 | private: 53 | sysex7 m_sysex7; 54 | size_t m_max_sysex_data_size{ 0 }; 55 | status_t m_state{ data_status::sysex7_start }; 56 | uint8_t m_manufacturerIDBytesRead{ 0 }; 57 | callback m_cb; 58 | }; 59 | 60 | //-------------------------------------------------------------------------- 61 | 62 | class sysex8_collector 63 | { 64 | public: 65 | using callback = std::function; 66 | 67 | explicit sysex8_collector(callback); 68 | 69 | void set_callback(callback); 70 | void set_max_sysex_data_size(size_t); //!< limit maximum size of accepted sysex data 71 | 72 | void feed(const universal_packet&); 73 | void reset(); 74 | 75 | uint8_t stream_id() const { return m_stream_id; } 76 | 77 | private: 78 | uint8_t m_stream_id{ 0 }; 79 | sysex8 m_sysex8; 80 | size_t m_max_sysex_data_size{ 0 }; 81 | packet_format m_state{ packet_format::start }; 82 | enum { detect, one_byte, three_bytes, invalid, done } m_manufacturer_id_state = detect; 83 | callback m_cb; 84 | }; 85 | 86 | //-------------------------------------------------------------------------- 87 | 88 | inline sysex7_collector::sysex7_collector(callback cb) 89 | : m_cb(std::move(cb)) 90 | { 91 | } 92 | inline void sysex7_collector::set_callback(callback cb) 93 | { 94 | m_cb = std::move(cb); 95 | } 96 | 97 | //-------------------------------------------------------------------------- 98 | 99 | inline sysex8_collector::sysex8_collector(callback cb) 100 | : m_cb(std::move(cb)) 101 | { 102 | } 103 | inline void sysex8_collector::set_callback(callback cb) 104 | { 105 | m_cb = std::move(cb); 106 | } 107 | 108 | //-------------------------------------------------------------------------- 109 | 110 | } // namespace midi 111 | 112 | //-------------------------------------------------------------------------- 113 | -------------------------------------------------------------------------------- /inc/midi/system_message.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | 30 | //-------------------------------------------------------------------------- 31 | 32 | #include 33 | #include 34 | 35 | //-------------------------------------------------------------------------- 36 | 37 | namespace midi { 38 | 39 | //-------------------------------------------------------------------------- 40 | 41 | struct system_message : universal_packet 42 | { 43 | constexpr system_message(); 44 | constexpr system_message(group_t, status_t, uint7_t data1 = 0, uint7_t data2 = 0); 45 | ~system_message() = default; 46 | }; 47 | 48 | //-------------------------------------------------------------------------- 49 | 50 | constexpr bool is_system_message(const universal_packet&); 51 | 52 | //-------------------------------------------------------------------------- 53 | 54 | struct system_message_view 55 | { 56 | constexpr explicit system_message_view(const universal_packet& ump) 57 | : p(ump) 58 | { 59 | assert(p.type() == packet_type::system); 60 | } 61 | 62 | constexpr group_t group() const { return p.group(); } 63 | constexpr status_t status() const { return p.status(); } 64 | constexpr uint7_t data_byte_1() const { return p.byte3() & 0x7Fu; } 65 | constexpr uint7_t data_byte_2() const { return p.byte4() & 0x7Fu; } 66 | 67 | constexpr uint14_t get_song_position() const; 68 | 69 | private: 70 | const universal_packet& p; 71 | }; 72 | 73 | //-------------------------------------------------------------------------- 74 | 75 | constexpr std::optional as_system_message_view(const universal_packet&); 76 | 77 | //-------------------------------------------------------------------------- 78 | 79 | constexpr system_message make_system_message(group_t, status_t, uint7_t data1 = 0, uint7_t data2 = 0); 80 | constexpr system_message make_song_position_message(group_t, uint14_t position); 81 | 82 | //-------------------------------------------------------------------------- 83 | // constexpr implementations 84 | //-------------------------------------------------------------------------- 85 | 86 | constexpr system_message::system_message() 87 | : universal_packet(0x10000000) 88 | { 89 | } 90 | constexpr system_message::system_message(group_t group, status_t status, uint7_t data1, uint7_t data2) 91 | : universal_packet(0x10000000u | ((group & 0x0F) << 24) | (status << 16) | (data1 << 8) | data2) 92 | { 93 | } 94 | 95 | //-------------------------------------------------------------------------- 96 | 97 | constexpr bool is_system_message(const universal_packet& p) 98 | { 99 | return (p.type() == packet_type::system); 100 | } 101 | 102 | //-------------------------------------------------------------------------- 103 | 104 | constexpr uint14_t system_message_view::get_song_position() const 105 | { 106 | if (p.status() == system_status::song_position) 107 | { 108 | return data_byte_1() | (data_byte_2() << 7); 109 | } 110 | 111 | return 0; 112 | } 113 | 114 | //-------------------------------------------------------------------------- 115 | 116 | constexpr std::optional as_system_message_view(const universal_packet& p) 117 | { 118 | if (is_system_message(p)) 119 | return system_message_view{ p }; 120 | else 121 | return std::nullopt; 122 | } 123 | 124 | //-------------------------------------------------------------------------- 125 | 126 | constexpr system_message make_system_message(group_t group, status_t status, uint7_t data1, uint7_t data2) 127 | { 128 | return system_message{ group, status, data1, data2 }; 129 | } 130 | 131 | constexpr system_message make_song_position_message(group_t group, uint14_t position) 132 | { 133 | return make_system_message(group, 134 | system_status::song_position, 135 | static_cast(position & 0x7F), 136 | static_cast((position >> 7) & 0x7F)); 137 | } 138 | 139 | //-------------------------------------------------------------------------- 140 | 141 | } // namespace midi 142 | 143 | //-------------------------------------------------------------------------- 144 | -------------------------------------------------------------------------------- /inc/midi/utility_message.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | 30 | //-------------------------------------------------------------------------- 31 | 32 | #include 33 | 34 | //-------------------------------------------------------------------------- 35 | 36 | namespace midi { 37 | 38 | //-------------------------------------------------------------------------- 39 | 40 | struct utility_message : universal_packet 41 | { 42 | constexpr utility_message() = default; 43 | constexpr explicit utility_message(status_t, uint16_t payload = 0u); 44 | ~utility_message() = default; 45 | 46 | // utility messages are groupless since UMP 1.1 47 | constexpr group_t group() const = delete; 48 | constexpr void set_group(group_t) = delete; 49 | }; 50 | 51 | //-------------------------------------------------------------------------- 52 | 53 | struct utility_message_view 54 | { 55 | constexpr explicit utility_message_view(const universal_packet& ump) 56 | : p(ump) 57 | { 58 | assert(p.type() == packet_type::utility); 59 | } 60 | 61 | constexpr status_t status() const { return p.status(); } 62 | constexpr uint16_t payload() const { return static_cast(p.byte3() << 8) | p.byte4(); } 63 | 64 | private: 65 | const universal_packet& p; 66 | }; 67 | 68 | //-------------------------------------------------------------------------- 69 | 70 | constexpr utility_message make_utility_message(status_t, uint16_t data); 71 | 72 | //-------------------------------------------------------------------------- 73 | // constexpr implementations 74 | //-------------------------------------------------------------------------- 75 | 76 | constexpr utility_message::utility_message(status_t status, uint16_t payload) 77 | : universal_packet(static_cast(status << 16) | payload) 78 | { 79 | } 80 | 81 | //-------------------------------------------------------------------------- 82 | 83 | constexpr utility_message make_utility_message(status_t status, uint16_t data) 84 | { 85 | return utility_message{ status, data }; 86 | } 87 | 88 | //-------------------------------------------------------------------------- 89 | 90 | } // namespace midi 91 | 92 | //-------------------------------------------------------------------------- 93 | -------------------------------------------------------------------------------- /src/jitter_reduction_timestamps.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | #include 26 | 27 | //-------------------------------------------------------------------------- 28 | 29 | namespace midi { 30 | 31 | //-------------------------------------------------------------------------- 32 | 33 | jr_timestamp_t jr_clock::now() 34 | { 35 | using system_clock = std::chrono::high_resolution_clock; 36 | static const auto zero = system_clock::now(); 37 | 38 | const auto jr_timestamp_now = std::chrono::duration_cast(system_clock::now() - zero); 39 | 40 | jr_timestamp_t result(jr_timestamp_now.count() & 0xFFFF); // 16 bit 41 | 42 | return result; 43 | } 44 | 45 | //-------------------------------------------------------------------------- 46 | 47 | void jr_clock_follower::reset() 48 | { 49 | m_clock_ts = {}; 50 | m_clock_time = zero; 51 | m_message_time = zero; 52 | m_jitter = std::chrono::milliseconds(0); 53 | m_security_offset = std::chrono::milliseconds(2); 54 | } 55 | 56 | //-------------------------------------------------------------------------- 57 | 58 | void jr_clock_follower::set_security_offset(duration offset) 59 | { 60 | assert(offset >= duration{ 0 }); 61 | m_security_offset = offset; 62 | } 63 | 64 | //-------------------------------------------------------------------------- 65 | //! update clock statistics with received jr clock timestamp 66 | void jr_clock_follower::process_clock(time_point received, jr_timestamp_t jr_timestamp) 67 | { 68 | if (m_clock_time == zero) 69 | { 70 | m_clock_time = received; 71 | m_clock_ts = jr_timestamp; 72 | m_message_time = received; 73 | return; 74 | } 75 | 76 | const auto diff_ts = jr_timestamp - m_clock_ts; 77 | 78 | m_clock_ts = jr_timestamp; 79 | 80 | const auto diff_ts_duration = std::chrono::duration_cast(diff_ts); 81 | const auto expected_receive_time = m_clock_time + diff_ts_duration; 82 | const auto jitter = received - expected_receive_time; 83 | 84 | if (received < expected_receive_time) 85 | // we are either in the startup phase or sender clock is slower than we 86 | { 87 | // use received as new reference time 88 | m_clock_time = received; 89 | 90 | if (received > m_message_time) // ensure that message order is preserved 91 | { 92 | m_message_time = received; 93 | } 94 | } 95 | else 96 | { 97 | m_clock_time = expected_receive_time; 98 | m_message_time = received; 99 | } 100 | 101 | update_stats(jitter); 102 | } 103 | 104 | //-------------------------------------------------------------------------- 105 | //! calculate the scheduled process time for a jr timestamp 106 | jr_clock_follower::time_point jr_clock_follower::schedule_message(time_point received, jr_timestamp_t jr_timestamp) 107 | { 108 | if (m_clock_time != zero) 109 | { 110 | const auto diff_ts = jr_timestamp - m_clock_ts; 111 | const auto system_clock_duration = std::chrono::duration_cast(diff_ts); 112 | const auto message_time = m_clock_time + system_clock_duration; 113 | 114 | // ensure that messages stay in correct order 115 | if (message_time > m_message_time) 116 | { 117 | m_message_time = message_time; 118 | } 119 | } 120 | else 121 | { 122 | m_message_time = received; 123 | } 124 | 125 | const auto result = m_message_time + security_offset(); 126 | return result; 127 | } 128 | 129 | //-------------------------------------------------------------------------- 130 | 131 | void jr_clock_follower::update_stats(duration jitter) 132 | { 133 | if (jitter < duration{ 0 }) 134 | { 135 | m_jitter -= jitter; 136 | recalc_security_offset(); 137 | } 138 | else if (jitter > m_jitter) 139 | { 140 | m_jitter = jitter; 141 | recalc_security_offset(); 142 | } 143 | } 144 | 145 | //-------------------------------------------------------------------------- 146 | 147 | void jr_clock_follower::recalc_security_offset() 148 | { 149 | const auto value = m_jitter * 12 / 10; 150 | if (value > m_security_offset) 151 | m_security_offset = value; 152 | } 153 | 154 | //-------------------------------------------------------------------------- 155 | 156 | } // namespace midi 157 | 158 | //-------------------------------------------------------------------------- 159 | -------------------------------------------------------------------------------- /src/sysex.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | namespace midi { 28 | 29 | //-------------------------------------------------------------------------- 30 | 31 | size_t sysex::total_data_size() const 32 | { 33 | if (manufacturerID) 34 | { 35 | return data.size() + ((manufacturerID & 0xFF0000) ? 1u : 3u); 36 | } 37 | 38 | return data.size(); 39 | } 40 | 41 | //-------------------------------------------------------------------------- 42 | 43 | bool sysex::empty() const 44 | { 45 | return (manufacturerID == 0) && data.empty(); 46 | } 47 | 48 | //-------------------------------------------------------------------------- 49 | 50 | bool sysex::is_7bit() const 51 | { 52 | for (const auto b : data) 53 | if (b & 0x80) 54 | return false; 55 | 56 | return true; 57 | } 58 | 59 | //-------------------------------------------------------------------------- 60 | 61 | bool sysex::is_8bit() const 62 | { 63 | for (const auto b : data) 64 | if (b & 0x80) 65 | return true; 66 | 67 | return false; 68 | } 69 | 70 | //-------------------------------------------------------------------------- 71 | 72 | void sysex::clear() 73 | { 74 | manufacturerID = 0; 75 | data.clear(); 76 | 77 | if (data.capacity() > 16384) 78 | data.shrink_to_fit(); 79 | } 80 | 81 | //----------------------------------------------- 82 | 83 | void sysex7::add_device_identity(const device_identity& identity) 84 | { 85 | assert((identity.manufacturer & 0xFF808080) == 0); 86 | assert((identity.family & 0xC000) == 0); 87 | assert((identity.model & 0xC000) == 0); 88 | assert((identity.revision & 0xF0000000) == 0); 89 | 90 | data.push_back((identity.manufacturer >> 16) & 0x7F); 91 | data.push_back((identity.manufacturer >> 8) & 0x7F); 92 | data.push_back(identity.manufacturer & 0x7F); 93 | 94 | add_uint14(identity.family); 95 | add_uint14(identity.model); 96 | add_uint28(identity.revision); 97 | } 98 | 99 | //----------------------------------------------- 100 | 101 | device_identity sysex7::make_device_identity(size_t data_pos) const 102 | { 103 | assert(data_pos + 10 < data.size()); 104 | 105 | const auto d = data.data() + data_pos; 106 | 107 | device_identity result; 108 | result.manufacturer = (uint14_t(d[0]) << 16) | (uint14_t(d[1]) << 8) | d[2]; 109 | result.family = make_uint14(data_pos + 3); 110 | result.model = make_uint14(data_pos + 5); 111 | result.revision = make_uint28(data_pos + 7); 112 | return result; 113 | } 114 | 115 | //-------------------------------------------------------------------------- 116 | 117 | } // namespace midi 118 | 119 | //-------------------------------------------------------------------------- 120 | -------------------------------------------------------------------------------- /src/sysex_collector.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | //-------------------------------------------------------------------------- 30 | 31 | namespace midi { 32 | 33 | //-------------------------------------------------------------------------- 34 | 35 | void sysex7_collector::set_max_sysex_data_size(size_t s) 36 | { 37 | if ((m_max_sysex_data_size = s)) 38 | { 39 | m_sysex7.data.reserve(m_max_sysex_data_size); 40 | } 41 | } 42 | 43 | //-------------------------------------------------------------------------- 44 | 45 | void sysex7_collector::feed(const universal_packet& p) 46 | { 47 | if (!is_sysex7_packet(p)) 48 | return; 49 | 50 | auto m = sysex7_packet_view{ p }; 51 | 52 | switch (m.status()) 53 | { 54 | case data_status::sysex7_complete: 55 | case data_status::sysex7_start: 56 | if (m_state != data_status::sysex7_start) 57 | { 58 | // invalid message sequence, reset 59 | reset(); 60 | } 61 | break; 62 | default: 63 | if (m_state != data_status::sysex7_continue) 64 | { 65 | // invalid message sequence, reset 66 | reset(); 67 | return; 68 | } 69 | } 70 | 71 | const auto numBytes = m.payload_size(); 72 | if (m_sysex7.data.size() + numBytes > m_sysex7.data.capacity()) 73 | { 74 | const bool limited_sysex_data_size = (m_max_sysex_data_size > 0); 75 | size_t new_capacity = std::max(size_t{ 128 }, 2 * m_sysex7.data.capacity()); 76 | if (limited_sysex_data_size) 77 | { 78 | new_capacity = std::min(new_capacity, m_max_sysex_data_size); 79 | } 80 | m_sysex7.data.reserve(new_capacity); 81 | 82 | if (limited_sysex_data_size && (m_sysex7.data.size() + numBytes > m_sysex7.data.capacity())) 83 | { 84 | // panic, data size exceeds m_max_sysex_data_size, wait for start of new sysex message 85 | m_state = data_status::sysex7_start; 86 | return; 87 | } 88 | } 89 | 90 | for (unsigned b = 0; b < numBytes; ++b) 91 | { 92 | const auto byte = m.payload_byte(b); 93 | switch (m_manufacturerIDBytesRead) 94 | { 95 | case 0: 96 | if (byte) 97 | { 98 | m_sysex7.manufacturerID = (byte << 16); 99 | m_manufacturerIDBytesRead = 3; 100 | } 101 | else 102 | { 103 | m_manufacturerIDBytesRead = 1; 104 | } 105 | break; 106 | case 1: 107 | m_sysex7.manufacturerID = (byte << 8); 108 | m_manufacturerIDBytesRead = 2; 109 | break; 110 | case 2: 111 | m_sysex7.manufacturerID |= byte; 112 | m_manufacturerIDBytesRead = 3; 113 | break; 114 | default: 115 | // collect data 116 | m_sysex7.data.push_back(byte); 117 | break; 118 | } 119 | } 120 | 121 | switch (m.status()) 122 | { 123 | case data_status::sysex7_complete: 124 | case data_status::sysex7_end: 125 | if (m_cb) 126 | { 127 | m_cb(m_sysex7); 128 | } 129 | reset(); 130 | break; 131 | default: 132 | m_state = data_status::sysex7_continue; 133 | break; 134 | } 135 | } 136 | 137 | //-------------------------------------------------------------------------- 138 | 139 | void sysex7_collector::reset() 140 | { 141 | m_sysex7.clear(); 142 | m_state = data_status::sysex7_start; 143 | m_manufacturerIDBytesRead = 0; 144 | } 145 | 146 | //-------------------------------------------------------------------------- 147 | 148 | void sysex8_collector::set_max_sysex_data_size(size_t s) 149 | { 150 | if ((m_max_sysex_data_size = s)) 151 | { 152 | m_sysex8.data.reserve(m_max_sysex_data_size); 153 | } 154 | } 155 | 156 | //-------------------------------------------------------------------------- 157 | 158 | void sysex8_collector::feed(const universal_packet& p) 159 | { 160 | if (!is_sysex8_packet(p)) 161 | return; 162 | 163 | auto m = sysex8_packet_view{ p }; 164 | 165 | switch (m.format()) 166 | { 167 | case packet_format::complete: 168 | case packet_format::start: 169 | if (m_state != packet_format::start) 170 | { 171 | // invalid message sequence, reset 172 | reset(); 173 | } 174 | m_stream_id = m.stream_id(); 175 | break; 176 | default: 177 | if (m_state != packet_format::cont) 178 | { 179 | // invalid message sequence, reset 180 | reset(); 181 | return; 182 | } 183 | if (m.stream_id() != m_stream_id) 184 | { 185 | // invalid stream id, ignore packet 186 | return; 187 | } 188 | } 189 | 190 | const auto numBytes = m.payload_size(); 191 | if (m_sysex8.data.size() + numBytes > m_sysex8.data.capacity()) 192 | { 193 | const bool limited_sysex_data_size = (m_max_sysex_data_size > 0); 194 | size_t new_capacity = std::max(size_t{ 128 }, 2 * m_sysex8.data.capacity()); 195 | if (limited_sysex_data_size) 196 | { 197 | new_capacity = std::min(new_capacity, m_max_sysex_data_size); 198 | } 199 | m_sysex8.data.reserve(new_capacity); 200 | 201 | if (limited_sysex_data_size && (m_sysex8.data.size() + numBytes > m_sysex8.data.capacity())) 202 | { 203 | // panic, data size exceeds m_max_sysex_data_size, wait for start of new sysex message 204 | m_state = packet_format::start; 205 | return; 206 | } 207 | } 208 | 209 | for (unsigned b = 0; b < numBytes; ++b) 210 | { 211 | const auto byte = m.payload_byte(b); 212 | switch (m_manufacturer_id_state) 213 | { 214 | case detect: 215 | if (byte & 0x80) 216 | { 217 | m_sysex8.manufacturerID = ((byte & 0x7F) << 8); 218 | m_manufacturer_id_state = three_bytes; 219 | } 220 | else 221 | { 222 | m_sysex8.manufacturerID = 0; 223 | m_manufacturer_id_state = (byte == 0) ? one_byte : invalid; 224 | } 225 | break; 226 | case one_byte: 227 | m_sysex8.manufacturerID = ((byte & 0x7F) << 16); 228 | m_manufacturer_id_state = done; 229 | break; 230 | case three_bytes: 231 | m_sysex8.manufacturerID |= (byte & 0x7F); 232 | m_manufacturer_id_state = done; 233 | break; 234 | case invalid: 235 | // ignore byte 236 | m_manufacturer_id_state = done; 237 | break; 238 | default: 239 | // collect data 240 | m_sysex8.data.push_back(byte); 241 | break; 242 | } 243 | } 244 | 245 | switch (m.format()) 246 | { 247 | case packet_format::complete: 248 | case packet_format::end: 249 | if (m_cb) 250 | { 251 | m_cb(m_sysex8, m_stream_id); 252 | } 253 | reset(); 254 | break; 255 | default: 256 | m_state = packet_format::cont; 257 | break; 258 | } 259 | } 260 | 261 | //-------------------------------------------------------------------------- 262 | 263 | void sysex8_collector::reset() 264 | { 265 | m_sysex8.clear(); 266 | m_stream_id = 0; 267 | m_state = packet_format::start; 268 | m_manufacturer_id_state = detect; 269 | } 270 | 271 | //-------------------------------------------------------------------------- 272 | 273 | } // namespace midi 274 | 275 | //-------------------------------------------------------------------------- 276 | -------------------------------------------------------------------------------- /src/universal_packet.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | //-------------------------------------------------------------------------- 32 | 33 | namespace midi { 34 | 35 | //-------------------------------------------------------------------------- 36 | 37 | namespace { 38 | 39 | struct iobase_restore_flags 40 | { 41 | explicit iobase_restore_flags(std::ios_base& strm) 42 | : strm(strm) 43 | { 44 | } 45 | 46 | ~iobase_restore_flags() { strm.flags(flags); } 47 | 48 | std::ios_base& strm; 49 | std::ios_base::fmtflags flags = strm.flags(); 50 | }; 51 | 52 | } // namespace 53 | 54 | //-------------------------------------------------------------------------- 55 | 56 | std::ostream& operator<<(std::ostream& out, const universal_packet& p) 57 | { 58 | iobase_restore_flags flag_restorer(out); 59 | 60 | for (auto w = 0u; w < p.size(); ++w) 61 | { 62 | if (w) 63 | out << ' '; 64 | out << std::hex << std::setfill('0') << std::setw(8) << p.data[w]; 65 | } 66 | return out; 67 | } 68 | 69 | //-------------------------------------------------------------------------- 70 | 71 | std::istream& operator>>(std::istream& in, universal_packet& p) 72 | { 73 | iobase_restore_flags flag_restorer(in); 74 | in >> std::hex >> p.data[0]; 75 | if (in.good()) 76 | { 77 | const auto num_words = p.size(); 78 | for (auto w = 1u; w < num_words; ++w) 79 | { 80 | in >> std::hex >> p.data[w]; 81 | if (!in.good()) 82 | break; 83 | } 84 | } 85 | 86 | return in; 87 | } 88 | 89 | //-------------------------------------------------------------------------- 90 | 91 | } // namespace midi 92 | 93 | //-------------------------------------------------------------------------- 94 | -------------------------------------------------------------------------------- /src/universal_sysex.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | //-------------------------------------------------------------------------- 26 | 27 | namespace midi::universal_sysex { 28 | 29 | //----------------------------------------------- 30 | 31 | identity_reply::identity_reply( 32 | manufacturer_t sysex_id, uint14_t family, uint14_t family_member, uint28_t revision, uint7_t device_id) 33 | : message(manufacturer::universal_non_realtime) 34 | { 35 | const bool threeByteManufacturerID = (sysex_id < 0x10000); 36 | data.reserve(threeByteManufacturerID ? 14 : 12); 37 | 38 | data.push_back(device_id); 39 | data.push_back(0x06); 40 | data.push_back(subtype::identity_reply); 41 | 42 | if (threeByteManufacturerID) 43 | { 44 | data.push_back(0x00); 45 | data.push_back((sysex_id >> 8) & 0x7F); 46 | data.push_back(sysex_id & 0x7F); 47 | } 48 | else 49 | { 50 | data.push_back((sysex_id >> 16) & 0x7F); 51 | } 52 | 53 | add_uint14(family); 54 | add_uint14(family_member); 55 | add_uint28(revision); 56 | } 57 | 58 | //----------------------------------------------- 59 | 60 | identity_reply::identity_reply(const device_identity& identity) 61 | : identity_reply(identity.manufacturer, identity.family, identity.model, identity.revision) 62 | { 63 | } 64 | 65 | //-------------------------------------------------------------------------- 66 | 67 | } // namespace midi::universal_sysex 68 | 69 | //-------------------------------------------------------------------------- 70 | -------------------------------------------------------------------------------- /tests/ci_process_inquiry_tests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | #include 26 | 27 | #include "sysex_tests.h" 28 | 29 | //----------------------------------------------- 30 | 31 | class ci_process_inquiry : public ::testing::Test 32 | { }; 33 | 34 | //----------------------------------------------- 35 | 36 | TEST_F(ci_process_inquiry, process_inquiry_capabilities_inquiry) 37 | { 38 | using VUT = midi::capability_inquiry_view; 39 | 40 | { 41 | SYSEX_ALLOCATOR_CAPTURE_COUNT(c); 42 | 43 | auto sx = midi::ci::make_process_inquiry_capabilities_inquiry(0x7665544, 0x24D2B78, 0x08); 44 | EXPECT_EQ(12u, sx.data.size()); 45 | 46 | EXPECT_TRUE(VUT::validate(sx)); 47 | EXPECT_TRUE(midi::ci::as(sx)); 48 | 49 | auto m = VUT{ sx }; 50 | 51 | EXPECT_EQ(0x08, m.device_id()); 52 | 53 | EXPECT_EQ(midi::ci::version, m.message_version()); 54 | EXPECT_EQ(0x7665544u, m.source_muid()); 55 | EXPECT_EQ(0x24D2B78u, m.destination_muid()); 56 | 57 | SYSEX_ALLOCATOR_VERIFY_DIFF(c, 1); 58 | } 59 | } 60 | 61 | //----------------------------------------------- 62 | 63 | TEST_F(ci_process_inquiry, process_inquiry_capabilities_reply) 64 | { 65 | using VUT = midi::ci::process_inquiry_capabilities_reply_view; 66 | 67 | { 68 | SYSEX_ALLOCATOR_CAPTURE_COUNT(c); 69 | 70 | auto sx = midi::ci::make_process_inquiry_capabilities_reply(0x7665544, 0x24D2B78, 19, 0x08); 71 | EXPECT_EQ(13u, sx.data.size()); 72 | 73 | EXPECT_TRUE(midi::is_capability_inquiry_message(sx)); 74 | EXPECT_TRUE(VUT::validate(sx)); 75 | 76 | auto m = VUT{ sx }; 77 | 78 | EXPECT_EQ(midi::ci::version, m.message_version()); 79 | EXPECT_EQ(0x7665544u, m.source_muid()); 80 | EXPECT_EQ(0x24D2B78u, m.destination_muid()); 81 | 82 | EXPECT_EQ(0x08, m.device_id()); 83 | EXPECT_EQ(19u, m.supported_features()); 84 | 85 | SYSEX_ALLOCATOR_VERIFY_DIFF(c, 1); 86 | } 87 | } 88 | 89 | //----------------------------------------------- 90 | 91 | TEST_F(ci_process_inquiry, midi_message_report_inquiry) 92 | { 93 | using VUT = midi::ci::midi_message_report_inquiry_view; 94 | 95 | { 96 | SYSEX_ALLOCATOR_CAPTURE_COUNT(c); 97 | 98 | auto sx = midi::ci::make_midi_message_report_inquiry(0x7665544, 0x24D2B78, 0x12, 0x34, 0x56, 0x78, 0x04); 99 | EXPECT_EQ(17u, sx.data.size()); 100 | 101 | EXPECT_TRUE(midi::is_capability_inquiry_message(sx)); 102 | EXPECT_TRUE(VUT::validate(sx)); 103 | 104 | auto m = VUT{ sx }; 105 | 106 | EXPECT_EQ(0x12, m.message_data_control()); 107 | EXPECT_EQ(0x34, m.system_message_types()); 108 | EXPECT_EQ(0x56, m.channel_controller_message_types()); 109 | EXPECT_EQ(0x78, m.note_data_message_types()); 110 | 111 | EXPECT_EQ(0x04, m.device_id()); 112 | 113 | EXPECT_EQ(midi::ci::version, m.message_version()); 114 | EXPECT_EQ(0x7665544u, m.source_muid()); 115 | EXPECT_EQ(0x24D2B78u, m.destination_muid()); 116 | 117 | SYSEX_ALLOCATOR_VERIFY_DIFF(c, 1); 118 | } 119 | } 120 | 121 | //----------------------------------------------- 122 | 123 | TEST_F(ci_process_inquiry, midi_message_report_reply) 124 | { 125 | using VUT = midi::ci::midi_message_report_reply_view; 126 | 127 | { 128 | SYSEX_ALLOCATOR_CAPTURE_COUNT(c); 129 | 130 | auto sx = midi::ci::make_midi_message_report_reply(0x7665544, 0x76, 0x54, 0x32, 0x0A); 131 | EXPECT_EQ(16u, sx.data.size()); 132 | 133 | EXPECT_TRUE(midi::is_capability_inquiry_message(sx)); 134 | EXPECT_TRUE(VUT::validate(sx)); 135 | 136 | auto m = VUT{ sx }; 137 | 138 | EXPECT_EQ(0x76, m.system_message_types()); 139 | EXPECT_EQ(0x54, m.channel_controller_message_types()); 140 | EXPECT_EQ(0x32, m.note_data_message_types()); 141 | 142 | EXPECT_EQ(0x0A, m.device_id()); 143 | 144 | EXPECT_EQ(midi::ci::version, m.message_version()); 145 | EXPECT_EQ(0x7665544u, m.source_muid()); 146 | EXPECT_EQ(0xFFFFFFFu, m.destination_muid()); 147 | 148 | SYSEX_ALLOCATOR_VERIFY_DIFF(c, 1); 149 | } 150 | } 151 | 152 | //----------------------------------------------- 153 | 154 | TEST_F(ci_process_inquiry, midi_message_report_end) 155 | { 156 | using VUT = midi::capability_inquiry_view; 157 | 158 | { 159 | SYSEX_ALLOCATOR_CAPTURE_COUNT(c); 160 | 161 | auto sx = midi::ci::make_midi_message_report_end(0x7665544, 0x03); 162 | EXPECT_EQ(12u, sx.data.size()); 163 | 164 | EXPECT_TRUE(midi::is_capability_inquiry_message(sx)); 165 | EXPECT_TRUE(VUT::validate(sx)); 166 | 167 | auto m = VUT{ sx }; 168 | 169 | EXPECT_EQ(0x03, m.device_id()); 170 | 171 | EXPECT_EQ(midi::ci::version, m.message_version()); 172 | EXPECT_EQ(0x7665544u, m.source_muid()); 173 | EXPECT_EQ(0xFFFFFFFu, m.destination_muid()); 174 | 175 | SYSEX_ALLOCATOR_VERIFY_DIFF(c, 1); 176 | } 177 | } 178 | 179 | //----------------------------------------------- 180 | -------------------------------------------------------------------------------- /tests/sysex7_collector_tests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | #include 26 | 27 | #include "sysex7_test_data.h" 28 | 29 | //----------------------------------------------- 30 | 31 | class sysex7_collector : public ::testing::Test 32 | { 33 | public: 34 | }; 35 | 36 | //----------------------------------------------- 37 | 38 | TEST_F(sysex7_collector, constructor) 39 | { 40 | using namespace midi; 41 | 42 | auto c = midi::sysex7_collector({}); 43 | } 44 | 45 | //----------------------------------------------- 46 | 47 | TEST_F(sysex7_collector, set_callback) 48 | { 49 | using namespace midi; 50 | 51 | auto c = midi::sysex7_collector({}); 52 | 53 | bool called = false; 54 | auto f = [&called](const sysex7&) { called = true; }; 55 | c.set_callback(f); 56 | 57 | c.feed(make_sysex7_complete_packet(0)); 58 | EXPECT_TRUE(called); 59 | } 60 | 61 | //----------------------------------------------- 62 | 63 | TEST_F(sysex7_collector, regular_collect) 64 | { 65 | using namespace midi; 66 | 67 | { 68 | for (const auto& entry : sysex7_test_cases) 69 | { 70 | bool output_generated = false; 71 | auto cb = [&](midi::sysex sx) { 72 | output_generated = true; 73 | EXPECT_EQ(sx, entry.sysex) << entry.description; 74 | }; 75 | 76 | auto c = midi::sysex7_collector{ cb }; 77 | for (const auto& p : entry.packets) 78 | { 79 | c.feed(p); 80 | } 81 | 82 | EXPECT_TRUE(output_generated); 83 | } 84 | } 85 | 86 | { 87 | bool output_generated = false; 88 | unsigned curEntry = 0; 89 | auto cb = [&](midi::sysex sx) { 90 | output_generated = true; 91 | EXPECT_EQ(sx, sysex7_test_cases[curEntry].sysex) << sysex7_test_cases[curEntry].description; 92 | }; 93 | 94 | auto c = midi::sysex7_collector{ cb }; 95 | 96 | for (const auto& entry : sysex7_test_cases) 97 | { 98 | for (const auto& p : entry.packets) 99 | { 100 | c.feed(p); 101 | } 102 | 103 | EXPECT_TRUE(output_generated); 104 | output_generated = false; 105 | ++curEntry; 106 | } 107 | } 108 | } 109 | 110 | //----------------------------------------------- 111 | 112 | TEST_F(sysex7_collector, exceptional_collect) 113 | { 114 | using namespace midi; 115 | 116 | // single end packet will be ignored 117 | { 118 | auto cb = [&](midi::sysex) { GTEST_FAIL(); }; 119 | 120 | auto c = midi::sysex7_collector{ cb }; 121 | 122 | auto p = make_sysex7_end_packet(); 123 | p.add_payload_byte(11); 124 | c.feed(p); 125 | } 126 | 127 | // continue + end packet will be ignored 128 | { 129 | auto cb = [&](midi::sysex) { GTEST_FAIL(); }; 130 | 131 | auto c = midi::sysex7_collector{ cb }; 132 | 133 | auto p1 = make_sysex7_continue_packet(); 134 | p1.add_payload_byte(10); 135 | c.feed(p1); 136 | 137 | auto p2 = make_sysex7_end_packet(); 138 | p1.add_payload_byte(11); 139 | c.feed(p2); 140 | } 141 | 142 | // sysex will be collected after invalid packets 143 | { 144 | std::vector packets{ { 0x31441234, 0 }, { 0x31060F1E, 0x2D3C4B5A } }; 145 | 146 | midi::sysex result{ 0x0F0000, { 0x1E, 0x2D, 0x3C, 0x4B, 0x5A } }; 147 | 148 | bool output_generated = false; 149 | auto cb = [&](midi::sysex sx) { 150 | output_generated = true; 151 | EXPECT_EQ(sx, result); 152 | }; 153 | 154 | auto c = midi::sysex7_collector{ cb }; 155 | 156 | for (const auto& p : packets) 157 | { 158 | c.feed(p); 159 | } 160 | 161 | EXPECT_TRUE(output_generated); 162 | } 163 | 164 | // incomplete sysex will be discarded 165 | { 166 | std::vector packets{ { 0x35160F1E, 0x2D3C4B5A }, 167 | { 0x35226978, 0x00000000 }, 168 | { 0x35017D00, 0 } }; 169 | 170 | midi::sysex result{ 0x7D0000 }; 171 | 172 | bool output_generated = false; 173 | auto cb = [&](midi::sysex sx) { 174 | output_generated = true; 175 | EXPECT_EQ(sx, result); 176 | }; 177 | 178 | auto c = midi::sysex7_collector{ cb }; 179 | 180 | for (const auto& p : packets) 181 | { 182 | c.feed(p); 183 | } 184 | 185 | EXPECT_TRUE(output_generated); 186 | } 187 | 188 | // empty sysex end packet triggers callback 189 | { 190 | std::vector packets{ { 0x3E167E12, 0x34567809 }, 191 | { 0x3E261A2B, 0x3C4D5E6F }, 192 | { 0x3E300000, 0 } }; 193 | 194 | midi::sysex result{ 0x7E0000, { 0x12, 0x34, 0x56, 0x78, 0x09, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E, 0x6F } }; 195 | 196 | bool output_generated = false; 197 | auto cb = [&](midi::sysex sx) { 198 | output_generated = true; 199 | EXPECT_EQ(sx, result); 200 | }; 201 | 202 | auto c = midi::sysex7_collector{ cb }; 203 | 204 | for (const auto& p : packets) 205 | { 206 | c.feed(p); 207 | } 208 | 209 | EXPECT_TRUE(output_generated); 210 | } 211 | } 212 | 213 | //----------------------------------------------- 214 | 215 | TEST_F(sysex7_collector, limited_data_size_collect) 216 | { 217 | using namespace midi; 218 | 219 | { 220 | bool output_generated = false; 221 | auto cb = [&](midi::sysex sx) { 222 | output_generated = true; 223 | EXPECT_EQ(sx.data.size(), 15); 224 | }; 225 | 226 | auto c = midi::sysex7_collector{ cb }; 227 | c.set_max_sysex_data_size(256); 228 | 229 | std::vector packets{ { 0x30167E12, 0x34567809 }, 230 | { 0x30261A2B, 0x3C4D5E6F }, 231 | { 0x30340000, 0 } }; 232 | 233 | for (const auto& p : packets) 234 | { 235 | c.feed(p); 236 | } 237 | 238 | EXPECT_TRUE(output_generated); 239 | } 240 | 241 | { 242 | bool output_generated = false; 243 | auto cb = [&](midi::sysex) { output_generated = true; }; 244 | 245 | auto c = midi::sysex7_collector{ cb }; 246 | c.set_max_sysex_data_size(12); 247 | 248 | std::vector packets{ { 0x30167E12, 0x34567809 }, 249 | { 0x30261A2B, 0x3C4D5E6F }, 250 | { 0x30320000, 0 } }; 251 | 252 | for (const auto& p : packets) 253 | { 254 | c.feed(p); 255 | } 256 | 257 | EXPECT_FALSE(output_generated); 258 | } 259 | 260 | { 261 | bool output_generated = false; 262 | auto cb = [&](midi::sysex sx) { 263 | output_generated = true; 264 | EXPECT_EQ(sx.data.size(), 12); 265 | }; 266 | 267 | auto c = midi::sysex7_collector{ cb }; 268 | c.set_max_sysex_data_size(12); 269 | 270 | std::vector packets{ { 0x30167E12, 0x34567809 }, 271 | { 0x30261A2B, 0x3C4D5E6F }, 272 | { 0x30310000, 0 } }; 273 | 274 | for (const auto& p : packets) 275 | { 276 | c.feed(p); 277 | } 278 | 279 | EXPECT_TRUE(output_generated); 280 | } 281 | } 282 | 283 | //----------------------------------------------- 284 | -------------------------------------------------------------------------------- /tests/sysex7_test_data.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include "sysex7_test_data.h" 24 | 25 | //----------------------------------------------- 26 | 27 | std::vector midi::sysex7_test_cases{ 28 | { "empty sysex", { { 0x30010000, 0x00000000 } }, {} }, 29 | { "one byte manufacturer only", { { 0x31010400, 0x00000000 } }, midi::sysex7{ midi::manufacturer::moog } }, 30 | { "three byte manufacturer only", 31 | { { 0x35030021, 0x09000000 } }, 32 | midi::sysex7{ midi::manufacturer::native_instruments } }, 33 | { "three byte manufacturer, complete message", 34 | { { 0x31050002, 0x0D152600 } }, 35 | midi::sysex7{ midi::manufacturer::google, { 0x15, 0x26 } } }, 36 | { "one byte manufacturer, complete message", 37 | { { 0x3F060905, 0x04030201 } }, 38 | midi::sysex7{ midi::manufacturer::midi9, { 5, 4, 3, 2, 1 } } }, 39 | { 40 | "two messages", 41 | { { 0x3A164E09, 0x08070605 }, { 0x3A340403, 0x02010000 } }, 42 | midi::sysex7{ midi::manufacturer::teac, { 9, 8, 7, 6, 5, 4, 3, 2, 1 } }, 43 | }, 44 | { "three messages", 45 | { { 0x36160021, 0x1D112233 }, { 0x36264455, 0x66771829 }, { 0x36353A4B, 0x5C6D7C00 } }, 46 | midi::sysex7{ midi::manufacturer::ableton, 47 | { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 0x7C } } }, 48 | { "four messages", 49 | { 50 | { 0x31160020, 0x6B112233 }, 51 | { 0x31264455, 0x66771829 }, 52 | { 0x31263A4B, 0x5C6D7C0F }, 53 | { 0x31311000, 0x00000000 }, 54 | }, 55 | midi::sysex7{ 56 | midi::manufacturer::arturia, 57 | { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 0x7C, 0x0F, 0x10 } } } 58 | }; 59 | -------------------------------------------------------------------------------- /tests/sysex7_test_data.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | namespace midi { 32 | 33 | struct sysex7_test_case 34 | { 35 | std::string description; 36 | std::vector packets; 37 | sysex7 sysex; 38 | }; 39 | 40 | extern std::vector sysex7_test_cases; 41 | 42 | } // namespace midi 43 | -------------------------------------------------------------------------------- /tests/sysex8_test_data.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include "sysex8_test_data.h" 24 | 25 | //----------------------------------------------- 26 | 27 | std::vector midi::sysex8_test_cases{ 28 | { "empty sysex", { { 0x50034400, 0x00000000 } }, 0x44, {} }, 29 | { "one byte manufacturer only", { { 0x51033400, 0x04000000 } }, 0x34, midi::sysex8{ midi::manufacturer::moog } }, 30 | { "three byte manufacturer only", 31 | { { 0x550300A1, 0x09000000 } }, 32 | 0x00, 33 | midi::sysex8{ midi::manufacturer::native_instruments } }, 34 | { "three byte manufacturer, complete packet", 35 | { { 0x5106AA82, 0x0D85A600 } }, 36 | 0xAA, 37 | midi::sysex8{ midi::manufacturer::google, { 0x85, 0xA6, 0x00 } } }, 38 | { "one byte manufacturer, complete packet", 39 | { { 0x5F089300, 0x09F5E4D3, 0xC2B10000 } }, 40 | 0x93, 41 | midi::sysex8{ midi::manufacturer::midi9, { 0xF5, 0xE4, 0xD3, 0xC2, 0xB1 } } }, 42 | { 43 | "two packets", 44 | { { 0x5A1E3900, 0x4E090807, 0x06050403, 0x020100FF }, { 0x5A3639EE, 0xDDCCBBAA } }, 45 | 0x39, 46 | midi::sysex8{ midi::manufacturer::teac, { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA } }, 47 | }, 48 | { "three packets", 49 | { { 0x561EC3A1, 0x1D112233, 0x44556677, 0x18293A4B }, 50 | { 0x562EC35C, 0x6D7C8B9A, 0xA9B8C7D6, 0xE5F40312 }, 51 | { 0x5632C331, 0 } }, 52 | 0xC3, 53 | midi::sysex8{ midi::manufacturer::ableton, 54 | { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 55 | 0x7C, 0x8B, 0x9A, 0xA9, 0xB8, 0xC7, 0xD6, 0xE5, 0xF4, 0x03, 0x12, 0x31 } } }, 56 | { "lots of data", 57 | { { 0x511E83A1, 0x09112233, 0x44556677, 0x8899AABB }, 58 | { 0x512E83CC, 0xDDEEFF18, 0x293A4B5C, 0x6D7E8F90 }, 59 | { 0x512E83A1, 0xB2C3D4E5, 0xF6E7D8F0, 0xE1D2C3B4 }, 60 | { 0x512E83A5, 0x96877869, 0x5A4B3C2D, 0x1E0F420F }, 61 | { 0x51328310, 0x00000000 } }, 62 | 0x83, 63 | midi::sysex8{ midi::manufacturer::native_instruments, 64 | { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 65 | 0xEE, 0xFF, 0x18, 0x29, 0x3A, 0x4B, 0x5C, 0x6D, 0x7E, 0x8F, 0x90, 0xA1, 0xB2, 66 | 0xC3, 0xD4, 0xE5, 0xF6, 0xE7, 0xD8, 0xF0, 0xE1, 0xD2, 0xC3, 0xB4, 0xA5, 0x96, 67 | 0x87, 0x78, 0x69, 0x5A, 0x4B, 0x3C, 0x2D, 0x1E, 0x0F, 0x42, 0x0F, 0x10 } } } 68 | }; 69 | -------------------------------------------------------------------------------- /tests/sysex8_test_data.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | namespace midi { 32 | 33 | struct sysex8_test_case 34 | { 35 | std::string description; 36 | std::vector packets; 37 | uint8_t stream_id; 38 | sysex8 sysex; 39 | }; 40 | 41 | extern std::vector sysex8_test_cases; 42 | 43 | } // namespace midi 44 | -------------------------------------------------------------------------------- /tests/sysex_tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern unsigned num_sysex_data_allocations(); 4 | 5 | #if NIMIDI2_CUSTOM_SYSEX_DATA_ALLOCATOR 6 | #define SYSEX_ALLOCATOR_CAPTURE_COUNT(var) const auto var = num_sysex_data_allocations() 7 | #define SYSEX_ALLOCATOR_VERIFY_DIFF(var, diff) EXPECT_EQ(num_sysex_data_allocations(), (var + diff)) 8 | #else 9 | #define SYSEX_ALLOCATOR_CAPTURE_COUNT(var) (void)0 10 | #define SYSEX_ALLOCATOR_VERIFY_DIFF(var, diff) (void)0 11 | #endif 12 | -------------------------------------------------------------------------------- /tests/system_message_tests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | #include 26 | 27 | //----------------------------------------------- 28 | 29 | class system_message : public ::testing::Test 30 | { 31 | public: 32 | }; 33 | 34 | //----------------------------------------------- 35 | 36 | TEST_F(system_message, constructors) 37 | { 38 | using namespace midi; 39 | 40 | { 41 | midi::system_message m; 42 | 43 | EXPECT_TRUE(is_system_message(m)); 44 | 45 | EXPECT_EQ(0x10000000u, m.data[0]); 46 | EXPECT_EQ(0u, m.data[1]); 47 | EXPECT_EQ(0u, m.data[2]); 48 | EXPECT_EQ(0u, m.data[3]); 49 | 50 | EXPECT_EQ(1u, m.size()); 51 | EXPECT_EQ(packet_type::system, m.type()); 52 | EXPECT_EQ(0u, m.status()); 53 | EXPECT_EQ(0u, m.group()); 54 | } 55 | 56 | { 57 | midi::system_message m{ 4, system_status::clock }; 58 | 59 | EXPECT_TRUE(is_system_message(m)); 60 | 61 | EXPECT_EQ(0x14F80000u, m.data[0]); 62 | EXPECT_EQ(0u, m.data[1]); 63 | EXPECT_EQ(0u, m.data[2]); 64 | EXPECT_EQ(0u, m.data[3]); 65 | 66 | EXPECT_EQ(1u, m.size()); 67 | EXPECT_EQ(packet_type::system, m.type()); 68 | EXPECT_EQ(system_status::clock, m.status()); 69 | EXPECT_EQ(4u, m.group()); 70 | } 71 | 72 | { 73 | midi::system_message m{ 9, system_status::mtc_quarter_frame, 0x46u }; 74 | 75 | EXPECT_TRUE(is_system_message(m)); 76 | 77 | EXPECT_EQ(0x19F14600u, m.data[0]); 78 | EXPECT_EQ(0u, m.data[1]); 79 | EXPECT_EQ(0u, m.data[2]); 80 | EXPECT_EQ(0u, m.data[3]); 81 | 82 | EXPECT_EQ(1u, m.size()); 83 | EXPECT_EQ(packet_type::system, m.type()); 84 | EXPECT_EQ(system_status::mtc_quarter_frame, m.status()); 85 | EXPECT_EQ(9u, m.group()); 86 | } 87 | 88 | { 89 | midi::system_message m{ 12, system_status::song_select, 66 }; 90 | 91 | EXPECT_TRUE(is_system_message(m)); 92 | 93 | EXPECT_EQ(0x1CF34200u, m.data[0]); 94 | EXPECT_EQ(0u, m.data[1]); 95 | EXPECT_EQ(0u, m.data[2]); 96 | EXPECT_EQ(0u, m.data[3]); 97 | 98 | EXPECT_EQ(1u, m.size()); 99 | EXPECT_EQ(packet_type::system, m.type()); 100 | EXPECT_EQ(system_status::song_select, m.status()); 101 | EXPECT_EQ(12u, m.group()); 102 | } 103 | } 104 | 105 | //----------------------------------------------- 106 | 107 | TEST_F(system_message, system_message_view) 108 | { 109 | using namespace midi; 110 | 111 | { 112 | auto m = midi::system_message{}; 113 | EXPECT_TRUE(is_system_message(m)); 114 | 115 | EXPECT_TRUE(as_system_message_view(m)); 116 | 117 | auto v = system_message_view{ m }; 118 | 119 | EXPECT_EQ(0u, v.group()); 120 | EXPECT_EQ(0u, v.status()); 121 | EXPECT_EQ(0u, v.data_byte_1()); 122 | EXPECT_EQ(0u, v.data_byte_2()); 123 | EXPECT_EQ(0u, v.get_song_position()); 124 | } 125 | 126 | { 127 | static constexpr auto m = midi::system_message{ 5, system_status::clock }; 128 | EXPECT_TRUE(is_system_message(m)); 129 | 130 | EXPECT_TRUE(as_system_message_view(m)); 131 | 132 | constexpr auto v = system_message_view{ m }; 133 | 134 | EXPECT_EQ(5u, v.group()); 135 | EXPECT_EQ(system_status::clock, v.status()); 136 | EXPECT_EQ(0u, v.data_byte_1()); 137 | EXPECT_EQ(0u, v.data_byte_2()); 138 | EXPECT_EQ(0u, v.get_song_position()); 139 | } 140 | 141 | { 142 | auto m = midi::system_message{ 11, system_status::mtc_quarter_frame, 0x46 }; 143 | EXPECT_TRUE(is_system_message(m)); 144 | 145 | EXPECT_TRUE(as_system_message_view(m)); 146 | 147 | auto v = system_message_view{ m }; 148 | 149 | EXPECT_EQ(11u, v.group()); 150 | EXPECT_EQ(system_status::mtc_quarter_frame, v.status()); 151 | EXPECT_EQ(0x46, v.data_byte_1()); 152 | EXPECT_EQ(0u, v.data_byte_2()); 153 | EXPECT_EQ(0u, v.get_song_position()); 154 | } 155 | 156 | { 157 | midi::system_message m{ 12, system_status::song_select, 66 }; 158 | EXPECT_TRUE(is_system_message(m)); 159 | 160 | EXPECT_TRUE(as_system_message_view(m)); 161 | 162 | auto v = system_message_view{ m }; 163 | 164 | EXPECT_EQ(12u, v.group()); 165 | EXPECT_EQ(system_status::song_select, v.status()); 166 | EXPECT_EQ(66, v.data_byte_1()); 167 | EXPECT_EQ(0u, v.data_byte_2()); 168 | EXPECT_EQ(0u, v.get_song_position()); 169 | } 170 | 171 | { 172 | midi::system_message m{ 3, system_status::song_position, 0x34u, 0x24u }; 173 | EXPECT_TRUE(is_system_message(m)); 174 | 175 | EXPECT_TRUE(as_system_message_view(m)); 176 | 177 | auto v = system_message_view{ m }; 178 | 179 | EXPECT_EQ(3u, v.group()); 180 | EXPECT_EQ(system_status::song_position, v.status()); 181 | EXPECT_EQ(0x34u, v.data_byte_1()); 182 | EXPECT_EQ(0x24u, v.data_byte_2()); 183 | EXPECT_EQ(0x1234u, v.get_song_position()); 184 | } 185 | 186 | { 187 | EXPECT_FALSE(as_system_message_view(universal_packet{ 0x21112233 })); 188 | } 189 | } 190 | 191 | //----------------------------------------------- 192 | 193 | TEST_F(system_message, make_system_message) 194 | { 195 | using namespace midi; 196 | 197 | { 198 | static constexpr auto m = make_system_message(9, system_status::song_position, 0x74, 0x69); 199 | EXPECT_EQ(universal_packet{ 0x19F27469 }, m); 200 | EXPECT_TRUE(is_system_message(m)); 201 | 202 | EXPECT_TRUE(as_system_message_view(m)); 203 | 204 | constexpr auto v = system_message_view{ m }; 205 | 206 | EXPECT_EQ(9u, v.group()); 207 | EXPECT_EQ(system_status::song_position, v.status()); 208 | EXPECT_EQ(0x74u, v.data_byte_1()); 209 | EXPECT_EQ(0x69u, v.data_byte_2()); 210 | EXPECT_EQ(0x34F4u, v.get_song_position()); 211 | } 212 | 213 | { 214 | const auto m = make_system_message(4, system_status::mtc_quarter_frame, 0x43); 215 | EXPECT_EQ(universal_packet{ 0x14F14300 }, m); 216 | EXPECT_TRUE(is_system_message(m)); 217 | 218 | EXPECT_TRUE(as_system_message_view(m)); 219 | 220 | auto v = system_message_view{ m }; 221 | 222 | EXPECT_EQ(4u, v.group()); 223 | EXPECT_EQ(system_status::mtc_quarter_frame, v.status()); 224 | EXPECT_EQ(0x43u, v.data_byte_1()); 225 | EXPECT_EQ(0u, v.data_byte_2()); 226 | EXPECT_EQ(0u, v.get_song_position()); 227 | } 228 | 229 | { 230 | constexpr auto m = make_system_message(12, system_status::clock); 231 | EXPECT_EQ(universal_packet{ 0x1CF80000 }, m); 232 | EXPECT_TRUE(is_system_message(m)); 233 | 234 | EXPECT_TRUE(as_system_message_view(m)); 235 | 236 | auto v = system_message_view{ m }; 237 | 238 | EXPECT_EQ(12u, v.group()); 239 | EXPECT_EQ(system_status::clock, v.status()); 240 | EXPECT_EQ(0u, v.data_byte_1()); 241 | EXPECT_EQ(0u, v.data_byte_2()); 242 | EXPECT_EQ(0u, v.get_song_position()); 243 | } 244 | } 245 | 246 | //----------------------------------------------- 247 | 248 | TEST_F(system_message, make_song_position_message) 249 | { 250 | using namespace midi; 251 | 252 | { 253 | static constexpr auto m = make_song_position_message(3, 0x1234); 254 | EXPECT_EQ(universal_packet{ 0x13F23424 }, m); 255 | EXPECT_TRUE(is_system_message(m)); 256 | 257 | EXPECT_TRUE(as_system_message_view(m)); 258 | 259 | constexpr auto v = system_message_view{ m }; 260 | 261 | EXPECT_EQ(3u, v.group()); 262 | EXPECT_EQ(system_status::song_position, v.status()); 263 | EXPECT_EQ(0x34u, v.data_byte_1()); 264 | EXPECT_EQ(0x24u, v.data_byte_2()); 265 | EXPECT_EQ(0x1234u, v.get_song_position()); 266 | } 267 | 268 | { 269 | const auto m = make_song_position_message(13, 0xFAFA); 270 | EXPECT_EQ(universal_packet{ 0x1DF27A75 }, m); 271 | EXPECT_TRUE(is_system_message(m)); 272 | 273 | EXPECT_TRUE(as_system_message_view(m)); 274 | 275 | auto v = system_message_view{ m }; 276 | 277 | EXPECT_EQ(13u, v.group()); 278 | EXPECT_EQ(system_status::song_position, v.status()); 279 | EXPECT_EQ(0x7Au, v.data_byte_1()); 280 | EXPECT_EQ(0x75u, v.data_byte_2()); 281 | EXPECT_EQ(0x3AFAu, v.get_song_position()); 282 | } 283 | } 284 | 285 | //----------------------------------------------- 286 | -------------------------------------------------------------------------------- /tests/tests.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2023 Native Instruments 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | // 22 | 23 | #include 24 | 25 | int main(int argc, char* argv[]) 26 | { 27 | ::testing::InitGoogleTest(&argc, argv); 28 | 29 | return RUN_ALL_TESTS(); 30 | } 31 | -------------------------------------------------------------------------------- /tests/utility_message_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | //----------------------------------------------- 6 | 7 | class utility_message : public ::testing::Test 8 | { 9 | public: 10 | }; 11 | 12 | //----------------------------------------------- 13 | 14 | TEST_F(utility_message, constructors) 15 | { 16 | using namespace midi; 17 | 18 | { 19 | midi::utility_message m; 20 | 21 | EXPECT_EQ(0u, m.data[0]); 22 | EXPECT_EQ(0u, m.data[1]); 23 | EXPECT_EQ(0u, m.data[2]); 24 | EXPECT_EQ(0u, m.data[3]); 25 | 26 | EXPECT_EQ(1u, m.size()); 27 | EXPECT_EQ(packet_type::utility, m.type()); 28 | EXPECT_EQ(utility_status::noop, m.status()); 29 | } 30 | 31 | { 32 | midi::utility_message m{ utility_status::jr_clock }; 33 | 34 | EXPECT_EQ(0x00100000u, m.data[0]); 35 | EXPECT_EQ(0u, m.data[1]); 36 | EXPECT_EQ(0u, m.data[2]); 37 | EXPECT_EQ(0u, m.data[3]); 38 | 39 | EXPECT_EQ(1u, m.size()); 40 | EXPECT_EQ(packet_type::utility, m.type()); 41 | EXPECT_EQ(utility_status::jr_clock, m.status()); 42 | } 43 | 44 | { 45 | midi::utility_message m{ utility_status::jr_timestamp, 0xABCD }; 46 | 47 | EXPECT_EQ(0x0020ABCDu, m.data[0]); 48 | EXPECT_EQ(0u, m.data[1]); 49 | EXPECT_EQ(0u, m.data[2]); 50 | EXPECT_EQ(0u, m.data[3]); 51 | 52 | EXPECT_EQ(1u, m.size()); 53 | EXPECT_EQ(packet_type::utility, m.type()); 54 | EXPECT_EQ(utility_status::jr_timestamp, m.status()); 55 | } 56 | } 57 | 58 | //----------------------------------------------- 59 | 60 | TEST_F(utility_message, view) 61 | { 62 | using namespace midi; 63 | 64 | { 65 | auto m = midi::utility_message{}; 66 | auto v = utility_message_view{ m }; 67 | 68 | EXPECT_EQ(utility_status::noop, v.status()); 69 | EXPECT_EQ(0u, v.payload()); 70 | } 71 | 72 | { 73 | auto m = midi::utility_message{ utility_status::jr_clock }; 74 | auto v = utility_message_view{ m }; 75 | 76 | EXPECT_EQ(utility_status::jr_clock, v.status()); 77 | EXPECT_EQ(0u, v.payload()); 78 | } 79 | 80 | { 81 | auto m = midi::utility_message{ utility_status::jr_timestamp, 0xABCD }; 82 | auto v = utility_message_view{ m }; 83 | 84 | EXPECT_EQ(utility_status::jr_timestamp, v.status()); 85 | EXPECT_EQ(0xABCDu, v.payload()); 86 | } 87 | } 88 | 89 | //----------------------------------------------- 90 | 91 | TEST_F(utility_message, make_utility_message) 92 | { 93 | using namespace midi; 94 | 95 | { 96 | constexpr auto m = make_utility_message(utility_status::jr_clock, 0xF499); 97 | EXPECT_EQ(universal_packet{ 0x0010F499 }, m); 98 | 99 | auto v = utility_message_view{ m }; 100 | 101 | EXPECT_EQ(utility_status::jr_clock, v.status()); 102 | EXPECT_EQ(0xF499u, v.payload()); 103 | } 104 | 105 | { 106 | constexpr auto m = make_utility_message(utility_status::jr_timestamp, 0x17CC); 107 | EXPECT_EQ(universal_packet{ 0x002017CC }, m); 108 | 109 | auto v = utility_message_view{ m }; 110 | 111 | EXPECT_EQ(utility_status::jr_timestamp, v.status()); 112 | EXPECT_EQ(0x17CCu, v.payload()); 113 | } 114 | } 115 | 116 | //----------------------------------------------- 117 | 118 | TEST_F(utility_message, utility_message_view) 119 | { 120 | using namespace midi; 121 | 122 | { 123 | static constexpr auto m = make_utility_message(utility_status::jr_clock, 0xF499); 124 | EXPECT_EQ(universal_packet{ 0x0010F499 }, m); 125 | 126 | constexpr auto v = utility_message_view{ m }; 127 | 128 | EXPECT_EQ(utility_status::jr_clock, v.status()); 129 | EXPECT_EQ(0xF499u, v.payload()); 130 | } 131 | } 132 | 133 | //----------------------------------------------- 134 | -------------------------------------------------------------------------------- /tests/value_translation_tests.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | //----------------------------------------------- 7 | /*! Unit test class for MIDI 1 <-> MIDI 2 value translation implementations 8 | 9 | This class checks the following MIDI 2 translation requirements: 10 | 11 | * the minumum value of the source range translates to the minumum value of the destination range 12 | * the maximum value of the source range translates to the maximum value of the destination range 13 | * the center value of the source range translates to the center value of the destination range 14 | * any translation MIDI 1 -> MIDI 2 -> MIDI 1 preserves the source value 15 | 16 | */ 17 | class value_translation : public ::testing::Test 18 | { 19 | public: 20 | }; 21 | 22 | //----------------------------------------------- 23 | 24 | TEST_F(value_translation, downscale_min) 25 | { 26 | EXPECT_EQ(0u, midi::downsample_16_to_7bit(0)) << "min 16 -> 7 is 0"; 27 | EXPECT_EQ(0u, midi::downsample_32_to_7bit(0)) << "min 32 -> 7 is 0"; 28 | EXPECT_EQ(0u, midi::downsample_32_to_14bit(0)) << "min 32 -> 14 is 0"; 29 | } 30 | 31 | //----------------------------------------------- 32 | 33 | TEST_F(value_translation, downscale_center) 34 | { 35 | EXPECT_EQ(0x40, midi::downsample_16_to_7bit(0x8000)) << "center 16 -> 7 is 0x40"; 36 | EXPECT_EQ(0x40, midi::downsample_32_to_7bit(0x80000000)) << "center 32 -> 7 is 0x40"; 37 | EXPECT_EQ(0x2000, midi::downsample_32_to_14bit(0x80000000)) << "center 32 -> 14 is 0x2000"; 38 | } 39 | 40 | //----------------------------------------------- 41 | 42 | TEST_F(value_translation, downscale_max) 43 | { 44 | EXPECT_EQ(0x7F, midi::downsample_16_to_7bit(0xFFFF)) << "max 16 -> 7 is 0x7F"; 45 | EXPECT_EQ(0x7F, midi::downsample_32_to_7bit(0xFFFFFFFF)) << "max 32 -> 7 is 0x7F"; 46 | EXPECT_EQ(0x3FFF, midi::downsample_32_to_14bit(0xFFFFFFFF)) << "max 32 -> 14 is 0x3FFF"; 47 | } 48 | 49 | //----------------------------------------------- 50 | 51 | TEST_F(value_translation, upscale_min) 52 | { 53 | EXPECT_EQ(0u, midi::upsample_7_to_16bit(0)) << "min 7 -> 16 is 0"; 54 | EXPECT_EQ(0u, midi::upsample_7_to_32bit(0)) << "min 7 -> 32 is 0"; 55 | EXPECT_EQ(0u, midi::upsample_14_to_32bit(0)) << "min 14 -> 32 is 0"; 56 | } 57 | 58 | //----------------------------------------------- 59 | 60 | TEST_F(value_translation, upscale_center) 61 | { 62 | EXPECT_EQ(0x8000, midi::upsample_7_to_16bit(0x40)) << "center 7 -> 16 is 0x8000"; 63 | EXPECT_EQ(0x80000000, midi::upsample_7_to_32bit(0x40)) << "center 7 -> 32 is 0x80000000"; 64 | EXPECT_EQ(0x80000000, midi::upsample_14_to_32bit(0x2000)) << "center 14 -> 32 is 0x80000000"; 65 | } 66 | 67 | //----------------------------------------------- 68 | 69 | TEST_F(value_translation, upscale_max) 70 | { 71 | EXPECT_EQ(0xFFFF, midi::upsample_7_to_16bit(0x7F)) << "max 7 -> 16 is 0xFFFF"; 72 | EXPECT_EQ(0xFFFFFFFF, midi::upsample_7_to_32bit(0x7F)) << "max 7 -> 32 is 0xFFFFFFFF"; 73 | EXPECT_EQ(0xFFFFFFFF, midi::upsample_14_to_32bit(0x3FFF)) << "max 14 -> 32 is 0xFFFFFFFF"; 74 | } 75 | 76 | //----------------------------------------------- 77 | 78 | TEST_F(value_translation, preserve_7_16_7) 79 | { 80 | for (midi::uint7_t v = 0u; v < 0x80; ++v) 81 | { 82 | EXPECT_EQ(v, midi::downsample_16_to_7bit(midi::upsample_7_to_16bit(v))) << "preserve_7_16_7(" << v << ")"; 83 | } 84 | } 85 | 86 | //----------------------------------------------- 87 | 88 | TEST_F(value_translation, preserve_7_32_7) 89 | { 90 | for (midi::uint7_t v = 0u; v < 0x80; ++v) 91 | { 92 | EXPECT_EQ(v, midi::downsample_32_to_7bit(midi::upsample_7_to_32bit(v))) << "preserve_7_32_7(" << v << ")"; 93 | } 94 | } 95 | 96 | //----------------------------------------------- 97 | 98 | TEST_F(value_translation, preserve_14_32_14) 99 | { 100 | for (midi::uint14_t v = 0u; v < 0x4000; ++v) 101 | { 102 | EXPECT_EQ(v, midi::downsample_32_to_14bit(midi::upsample_14_to_32bit(v))) << "preserve_14_32_14(" << v << ")"; 103 | } 104 | } 105 | 106 | //----------------------------------------------- 107 | 108 | TEST_F(value_translation, upsample_x_to_ybit) 109 | { 110 | for (midi::uint7_t v = 0u; v < 0x80; ++v) 111 | { 112 | EXPECT_EQ(midi::upsample_7_to_16bit(v), midi::upsample_x_to_ybit(v, 7, 16)); 113 | } 114 | for (midi::uint7_t v = 0u; v < 0x80; ++v) 115 | { 116 | EXPECT_EQ(midi::upsample_7_to_32bit(v), midi::upsample_x_to_ybit(v, 7, 32)); 117 | } 118 | for (midi::uint14_t v = 0u; v < 0x4000; ++v) 119 | { 120 | EXPECT_EQ(midi::upsample_14_to_32bit(v), midi::upsample_x_to_ybit(v, 14, 32)); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ni-midi2", 3 | "version-string": "1.0.0", 4 | "dependencies": [ 5 | "gtest" 6 | ] 7 | } 8 | --------------------------------------------------------------------------------