├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── cmake.yml │ └── platformio.yml ├── .gitignore ├── .gitmodules ├── .vscode ├── arduino.json ├── c_cpp_properties.json ├── settings.json └── tasks.json ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── ReleaseNotes.md ├── builder └── CMakeLists.txt ├── doc ├── .gitignore ├── Doxyfile ├── midi_DoxygenMainPage.h └── sysex-codec.md ├── examples ├── AltPinSerial │ └── AltPinSerial.ino ├── Basic_IO │ └── Basic_IO.ino ├── Bench │ └── Bench.ino ├── Callbacks │ └── Callbacks.ino ├── Chaining │ └── Chaining.ino ├── CustomBaudRate │ └── CustomBaudRate.ino ├── DualMerger │ └── DualMerger.ino ├── ErrorCallback │ └── ErrorCallback.ino ├── Input │ └── Input.ino ├── RPN_NRPN │ ├── RPN_NRPN.ino │ └── utility.h └── SimpleSynth │ ├── SimpleSynth.ino │ ├── noteList.cpp │ ├── noteList.h │ └── pitches.h ├── external └── CMakeLists.txt ├── keywords.txt ├── library.json ├── library.properties ├── res ├── library-manager.png ├── packaging.command └── validator │ ├── midi.py │ ├── tester.py │ └── validate.py ├── src ├── CMakeLists.txt ├── MIDI.cpp ├── MIDI.h ├── MIDI.hpp ├── midi_Defs.h ├── midi_Message.h ├── midi_Namespace.h ├── midi_Platform.h ├── midi_Settings.h └── serialMIDI.h └── test ├── CMakeLists.txt ├── mocks ├── CMakeLists.txt ├── test-mocks.cpp ├── test-mocks.h ├── test-mocks_Namespace.h ├── test-mocks_SerialMock.cpp ├── test-mocks_SerialMock.h └── test-mocks_SerialMock.hpp └── unit-tests ├── CMakeLists.txt ├── tests ├── unit-tests_MidiInput.cpp ├── unit-tests_MidiInputCallbacks.cpp ├── unit-tests_MidiMessage.cpp ├── unit-tests_MidiOutput.cpp ├── unit-tests_MidiThru.cpp ├── unit-tests_Settings.cpp ├── unit-tests_Settings.h └── unit-tests_SysExCodec.cpp ├── unit-tests.cpp ├── unit-tests.h └── unit-tests_Namespace.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report something that does not work as intended 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | ## Context 20 | 21 | Please answer a few questions to help us understand your problem better and guide you to a solution: 22 | 23 | 29 | 30 | - What board are you using ? 31 | - `example: Arduino Leonardo` 32 | - _Please list any shields or other **relevant** hardware you're using_ 33 | - What version of the Arduino IDE are you using ? 34 | - `example: 1.8.5` 35 | - How are you using MIDI ? 36 | - [ ] Hardware Serial (DIN plugs) 37 | - [ ] USB 38 | - [ ] Other (please specify) 39 | - Is your problem related to: 40 | - [ ] MIDI Input (reading messages from other devices) 41 | - [ ] MIDI Output (sending messages to other devices) 42 | - How comfortable are you with code ? 43 | - [ ] Complete beginner 44 | - [ ] I've done basic projects 45 | - [ ] I know my way around C/C++ 46 | - [ ] Advanced / professional 47 | 48 | ## Describe your project and what you expect to happen: 49 | 50 | 55 | 56 | ## Describe your problem (what does not work): 57 | 58 | 61 | 62 | ## Steps to reproduce 63 | 64 | 67 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discussions 4 | url: https://github.com/FortySevenEffects/arduino_midi_library/discussions 5 | about: Not a bug or a feature request ? Discuss your problem, ask for help or show what you've built in Discussions. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: new feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ master ] 7 | 8 | env: 9 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 10 | BUILD_TYPE: Debug 11 | GENERATE_COVERAGE: true 12 | LCOV_ROOT: ${{github.workspace}}/lcov 13 | 14 | jobs: 15 | build: 16 | # The CMake configure and build commands are platform agnostic and should work equally 17 | # well on Windows or Mac. You can convert this to a matrix build if you need 18 | # cross-platform coverage. 19 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | with: 25 | submodules: recursive 26 | 27 | - name: Install lcov 28 | run: | 29 | mkdir -p "$LCOV_ROOT" 30 | wget https://github.com/linux-test-project/lcov/releases/download/v1.15/lcov-1.15.tar.gz --output-document="$LCOV_ROOT/lcov.tar.gz" 31 | tar -xf "$LCOV_ROOT/lcov.tar.gz" --strip-components=1 -C "$LCOV_ROOT" 32 | echo "$LCOV_ROOT/bin" >> $GITHUB_PATH 33 | shell: bash 34 | 35 | - name: Configure CMake 36 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 37 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 38 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILDER_ENABLE_PROFILING=true 39 | 40 | - name: Build 41 | # Build your program with the given configuration 42 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 43 | 44 | - name: Run Unit Tests 45 | working-directory: ${{github.workspace}}/build 46 | run: ctest --verbose 47 | 48 | - name: Generate code coverage report 49 | working-directory: ${{github.workspace}}/build 50 | run: | 51 | lcov --directory . --capture --output-file coverage.info 52 | lcov --remove coverage.info '/usr/*' "${{github.workspace}}/test/*" "${{github.workspace}}/external/*" --output-file coverage.info 53 | lcov --list coverage.info 54 | 55 | - uses: coverallsapp/github-action@9ba913c152ae4be1327bfb9085dc806cedb44057 56 | name: Upload code coverage report to Coveralls 57 | with: 58 | path-to-lcov: ${{github.workspace}}/build/coverage.info 59 | github-token: ${{ secrets.GITHUB_TOKEN }} 60 | -------------------------------------------------------------------------------- /.github/workflows/platformio.yml: -------------------------------------------------------------------------------- 1 | name: PlatformIO 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | platformio: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | example: 16 | - AltPinSerial 17 | - Basic_IO 18 | - Bench 19 | - Callbacks 20 | - Chaining 21 | - DualMerger 22 | - ErrorCallback 23 | - Input 24 | - RPN_NRPN 25 | - SimpleSynth 26 | - CustomBaudRate 27 | board: 28 | - uno 29 | - due 30 | - zero 31 | - leonardo 32 | - micro 33 | - nanoatmega328 34 | - megaatmega2560 35 | - teensy2 36 | - teensy30 37 | - teensy31 38 | - teensylc 39 | steps: 40 | - uses: actions/checkout@v2 41 | - name: Cache pip 42 | uses: actions/cache@v2 43 | with: 44 | path: ~/.cache/pip 45 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 46 | restore-keys: ${{ runner.os }}-pip- 47 | - name: Cache PlatformIO 48 | uses: actions/cache@v2 49 | with: 50 | path: ~/.platformio 51 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 52 | - name: Set up Python 53 | uses: actions/setup-python@v2 54 | - name: Install PlatformIO 55 | run: | 56 | python -m pip install --upgrade pip 57 | pip install --upgrade platformio 58 | pip install "click!=8.0.2" # See platformio/platformio-core#4078 59 | - name: Run PlatformIO 60 | run: pio ci --lib="." --board="${{matrix.board}}" 61 | env: 62 | PLATFORMIO_CI_SRC: examples/${{ matrix.example }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | *.pyc 3 | logs/ 4 | build/ 5 | .vscode/.cmaketools.json 6 | src/.DS_Store 7 | examples/.DS_Store 8 | .DS_Store 9 | test/xcode 10 | .development 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/google-test"] 2 | path = external/google-test 3 | url = https://github.com/google/googletest.git 4 | -------------------------------------------------------------------------------- /.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "board": "arduino:avr:leonardo" 3 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include", 7 | "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" 8 | ], 9 | "browse": { 10 | "limitSymbolsToIncludedHeaders": true, 11 | "databaseFilename": "", 12 | "path": [ 13 | "/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include", 14 | "/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino" 15 | ] 16 | }, 17 | "intelliSenseMode": "clang-x64", 18 | "macFrameworkPath": [ 19 | "/System/Library/Frameworks", 20 | "/Library/Frameworks" 21 | ], 22 | "compilerPath": "/usr/bin/clang", 23 | "cStandard": "c11", 24 | "cppStandard": "c++17" 25 | }, 26 | { 27 | "name": "Linux", 28 | "includePath": [ 29 | "/usr/include" 30 | ], 31 | "browse": { 32 | "limitSymbolsToIncludedHeaders": true, 33 | "databaseFilename": "", 34 | "path": [ 35 | "/usr/include" 36 | ] 37 | }, 38 | "intelliSenseMode": "clang-x64", 39 | "compilerPath": "/usr/bin/clang", 40 | "cStandard": "c11", 41 | "cppStandard": "c++17" 42 | }, 43 | { 44 | "name": "Win32", 45 | "includePath": [ 46 | "c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include" 47 | ], 48 | "browse": { 49 | "limitSymbolsToIncludedHeaders": true, 50 | "databaseFilename": "", 51 | "path": [ 52 | "c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include" 53 | ] 54 | }, 55 | "intelliSenseMode": "msvc-x64", 56 | "compilerPath": "/usr/bin/clang", 57 | "cStandard": "c11", 58 | "cppStandard": "c++17" 59 | } 60 | ], 61 | "version": 4 62 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "cstddef": "cpp", 4 | "ostream": "cpp", 5 | "__locale": "cpp", 6 | "functional": "cpp", 7 | "iterator": "cpp", 8 | "string": "cpp", 9 | "string_view": "cpp", 10 | "vector": "cpp", 11 | "istream": "cpp", 12 | "system_error": "cpp" 13 | } 14 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build", 8 | "command": "make", 9 | "args": ["all"], 10 | "options": { 11 | "cwd": "${workspaceRoot}/build" 12 | }, 13 | "group": { 14 | "kind": "build", 15 | "isDefault": true 16 | } 17 | }, 18 | { 19 | "label": "Run Tests", 20 | "command": "${workspaceRoot}/build/test/unit-tests/unit-tests", 21 | "group": { 22 | "kind": "test", 23 | "isDefault": true 24 | }, 25 | "dependsOn": ["Build"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.7) 2 | project(arduino_midi_library CXX) 3 | 4 | add_subdirectory(builder) 5 | 6 | setup_builder() 7 | 8 | add_subdirectory(external) 9 | add_subdirectory(src) 10 | add_subdirectory(test) 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | First, thanks for your help ! :+1: 4 | 5 | ## Branches 6 | 7 | Please base your Pull Requests off the `master` branch. 8 | 9 | ## Requirements 10 | 11 | Requirements to build and run the unit tests: 12 | 13 | - CMake 2.8 or later 14 | - GCC / Clang with C++11 support (GCC 4.8 or higher) 15 | 16 | ## Setup 17 | 18 | Pull Google Test / Google Mock subrepository: 19 | 20 | ``` 21 | $ git submodule init 22 | $ git submodule update 23 | ``` 24 | 25 | Create build directory, run CMake, build and run unit tests: 26 | 27 | ``` 28 | $ mkdir build && cd build 29 | $ cmake .. 30 | $ make 31 | $ ctest --verbose 32 | ``` 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Francois Best 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino MIDI Library 2 | 3 | [![GitHub release](https://img.shields.io/github/release/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://github.com/FortySevenEffects/arduino_midi_library/releases/latest) 4 | [![License](https://img.shields.io/github/license/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](LICENSE) 5 | [![Build](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/cmake.yml/badge.svg?branch=master)](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/cmake.yml) 6 | [![Examples](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/platformio.yml/badge.svg?branch=master)](https://github.com/FortySevenEffects/arduino_midi_library/actions/workflows/platformio.yml) 7 | [![Coveralls](https://img.shields.io/coveralls/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://coveralls.io/github/FortySevenEffects/arduino_midi_library) 8 | 9 | This library adds MIDI I/O communications to an Arduino board. 10 | 11 | ### Features 12 | 13 | - **New** : MIDI over USB, Bluetooth, IP & AppleMIDI (see [Transports](#other-transport-mechanisms)). 14 | - **New** : Active Sensing support 15 | - Compatible with all Arduino boards (and clones with an AVR processor). 16 | - Simple and fast way to send and receive every kind of MIDI message (including all System messages, SysEx, Clock, etc..). 17 | - OMNI input reading (read all channels). 18 | - Software Thru, with message filtering. 19 | - [Callbacks](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks) to handle input messages more easily. 20 | - Last received message is saved until a new one arrives. 21 | - Configurable: [overridable template-based settings](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-custom-Settings). 22 | - Create more than one MIDI interface for mergers/splitters applications. 23 | - Use any serial port, hardware or software. 24 | 25 | ### Getting Started 26 | 27 | 1. Use the Arduino Library Manager to install the library. 28 | ![Type "MIDI I/Os for Arduino" in the Arduino IDE Library Manager](res/library-manager.png) 29 | 30 | 2. Start coding: 31 | 32 | ```c++ 33 | #include 34 | 35 | // Create and bind the MIDI interface to the default hardware Serial port 36 | MIDI_CREATE_DEFAULT_INSTANCE(); 37 | 38 | void setup() 39 | { 40 | MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages 41 | } 42 | 43 | void loop() 44 | { 45 | // Send note 42 with velocity 127 on channel 1 46 | MIDI.sendNoteOn(42, 127, 1); 47 | 48 | // Read incoming messages 49 | MIDI.read(); 50 | } 51 | ``` 52 | 53 | 3. Read the [documentation](#documentation) or watch the awesome video tutorials from [Notes & Volts](https://www.youtube.com/playlist?list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg). 54 | 55 | ## Documentation 56 | 57 | - [Doxygen Extended Documentation](https://fortyseveneffects.github.io/arduino_midi_library/). 58 | - [GitHub wiki](https://github.com/FortySevenEffects/arduino_midi_library/wiki). 59 | 60 | ## USB Migration (4.x to 5.x) 61 | 62 | All USB related code has been moved into a separate repository [Arduino-USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI), USB MIDI Device support with [`MIDIUSB`](https://github.com/arduino-libraries/MIDIUSB), still using this library to do all the MIDI heavy-lifting. 63 | 64 | Migration has been made as easy as possible: only the declaration of the MIDI object has been modified, the rest of your code remains identical. 65 | 66 | `4.3.1` code: 67 | 68 | ```c++ 69 | #include 70 | #include 71 | 72 | static const unsigned sUsbTransportBufferSize = 16; 73 | typedef midi::UsbTransport UsbTransport; 74 | 75 | UsbTransport sUsbTransport; 76 | 77 | MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI); 78 | 79 | // ... 80 | ``` 81 | 82 | now becomes in `5.x`: 83 | 84 | ```c++ 85 | #include 86 | USBMIDI_CREATE_DEFAULT_INSTANCE(); 87 | 88 | // ... 89 | ``` 90 | 91 | Start with the [NoteOnOffEverySec](https://github.com/lathoub/Arduino-USBMIDI/blob/master/examples/NoteOnOffEverySec/NoteOnOffEverySec.ino) example that is based on the original MidiUSB [sketch](https://github.com/lathoub/arduino_midi_library/blob/master/examples/MidiUSB/MidiUSB.ino). Note the only difference is in the declaration. 92 | 93 | The [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) Arduino library depends on [this library](https://github.com/FortySevenEffects/arduino_midi_library) and the [MIDIUSB](https://github.com/arduino-libraries/MIDIUSB) library. 94 | 95 | [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) uses the latest Arduino IDE `depends` feature in the `library.properties` file installing all the dependencies automatically when installing from the IDE. 96 | 97 | ## Other Transport mechanisms 98 | 99 | Version 5 of this library, allows for other Transport layers than the 100 | original MIDI 1.0 Electrical Specification (hardware serial). 101 | 102 | - [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI) 103 | - [AppleMIDI or rtpMIDI](https://github.com/lathoub/Arduino-AppleMIDI-Library) 104 | - [ipMIDI](https://github.com/lathoub/Arduino-ipMIDI) 105 | - [BLE-MIDI](https://github.com/lathoub/Arduino-BLE-MIDI) 106 | 107 | All these Transport layers use this library for all the underlying MIDI 108 | work, making it easy to switch transport protocols or making transport 109 | protocol bridges. 110 | 111 | ### Differences between Serial & other transports 112 | 113 | - Software Thru is enabled by default on Serial, but not on other transports. 114 | 115 | ## Contact & Contribution 116 | 117 | To report a bug, contribute, discuss on usage, or request support, please [discuss it here](https://github.com/FortySevenEffects/arduino_midi_library/discussions/new). 118 | 119 | You can also contact me on Twitter: [@fortysevenfx](https://twitter.com/fortysevenfx). 120 | 121 | ## Contributors 122 | 123 | Special thanks to all who have contributed to this open-source project ! 124 | 125 | - [@lathoub](https://github.com/lathoub) 126 | - [@jarosz](https://github.com/jarosz) 127 | - [@ivankravets](https://github.com/ivankravets) 128 | - [@insolace](https://github.com/insolace) 129 | - [@softegg](https://github.com/softegg) 130 | - [@per1234](https://github.com/per1234) 131 | - [@LnnrtS](https://github.com/LnnrtS) 132 | - [@DavidMenting](https://github.com/DavidMenting) 133 | - [@Rolel](https://github.com/Rolel) 134 | - [@kant](https://github.com/kant) 135 | - [@paul-emile-element](https://github.com/paul-emile-element) 136 | - [@muxa](https://github.com/muxa) 137 | 138 | You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md). 139 | 140 | ## License 141 | 142 | MIT © 2009 - present [Francois Best](https://francoisbest.com) 143 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | #### Changelog 2 | 3 | - 20/04/2020 : Version 5.0 released. Separation of transports by [@lathoub](https://github.com/lathoub), adds Active Sensing. 4 | - 11/06/2014 : Version 4.2 released. Bug fix for SysEx, overridable template settings. 5 | - 16/04/2014 : Version 4.1 released. Bug fixes regarding running status. 6 | - 13/02/2014 : Version 4.0 released. Moved to GitHub, added multiple instances & software serial support, and a few bug fixes. 7 | - 29/01/2012 : Version 3.2 released. Release notes are [here](http://sourceforge.net/news/?group_id=265194). 8 | - 06/05/2011 : Version 3.1 released. Added [callback](http://playground.arduino.cc/Main/MIDILibraryCallbacks) support. 9 | - 06/03/2011 : Version 3.0 released. Project is now hosted on [SourceForge](http://sourceforge.net/projects/arduinomidilib). 10 | - 14/12/2009 : Version 2.5 released. 11 | - 28/07/2009 : Version 2.0 released. 12 | - 28/03/2009 : Simplified version of MIDI.begin, Fast mode is now on by default. 13 | - 08/03/2009 : Thru method operational. Added some features to enable thru. 14 | -------------------------------------------------------------------------------- /builder/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | option(BUILDER_ENABLE_PROFILING OFF) 2 | 3 | macro(setup_builder) 4 | enable_testing() 5 | 6 | set(ROOT_SOURCE_DIR ${PROJECT_SOURCE_DIR} CACHE INTERNAL "Repository root directory") 7 | set(ROOT_BINARY_DIR "${ROOT_SOURCE_DIR}/build") 8 | 9 | include_directories(${ROOT_SOURCE_DIR}) 10 | 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \ 12 | -Wall \ 13 | -W \ 14 | -Wshadow \ 15 | -Wunused-variable \ 16 | -Wunused-parameter \ 17 | -Wunused-function \ 18 | -Wunused \ 19 | -Wno-system-headers \ 20 | -Wno-deprecated \ 21 | -Woverloaded-virtual \ 22 | ") 23 | if (BUILDER_ENABLE_PROFILING) 24 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0") 25 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} --coverage") 26 | endif() 27 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 28 | 29 | endmacro() 30 | 31 | macro(increase_warning_level) 32 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion -Wsign-conversion") 33 | endmacro() 34 | -------------------------------------------------------------------------------- /doc/.gitignore: -------------------------------------------------------------------------------- 1 | html 2 | latex 3 | -------------------------------------------------------------------------------- /doc/midi_DoxygenMainPage.h: -------------------------------------------------------------------------------- 1 | /*! 2 | \mainpage Arduino MIDI Library 3 | 4 | See the documentation of the main class, MidiInterface, or browse the modules 5 | and examples using the sidebar on the left. 6 | \n 7 | The latest development version is available on GitHub: 8 | https://github.com/FortySevenEffects/arduino_midi_library/tree/dev 9 | */ 10 | 11 | // ----------------------------------------------------------------------------- 12 | // Examples 13 | 14 | /*! 15 | \example Basic_IO.ino 16 | This example shows how to perform simple input and output MIDI. \n 17 | \n 18 | When any message arrives to the Arduino, the LED is turned on, 19 | then we send a Note On message, wait for a second, then send 20 | the Note Off and turn off the LED. 21 | \n 22 | \n 23 | 24 | Note that instead of sending a Note Off, we could have sent a 25 | Note On with velocity 0 to shorten the message. This is called Running 26 | Status. 27 | 28 | \n 29 | */ 30 | 31 | /*! 32 | \example Callbacks.ino 33 | This example shows how to use callbacks for easier MIDI input handling. \n 34 | */ 35 | 36 | /*! 37 | \example Bench.ino 38 | \example DualMerger.ino 39 | \example Input.ino 40 | \example SimpleSynth.ino 41 | */ 42 | 43 | // ----------------------------------------------------------------------------- 44 | 45 | /*! \defgroup output MIDI Output 46 | */ 47 | /*! \defgroup input MIDI Input 48 | */ 49 | /*! \defgroup callbacks Callbacks 50 | \ingroup input 51 | */ 52 | /*! \defgroup thru MIDI Thru 53 | */ 54 | 55 | -------------------------------------------------------------------------------- /doc/sysex-codec.md: -------------------------------------------------------------------------------- 1 | # SysEx Encoding & Decoding 2 | 3 | There are various ways of encoding & decoding arbitrary 8-bit wide data into 4 | SysEx, which is 7-bit wide. 5 | 6 | The [official documentation](http://www.somascape.org/midi/tech/spec.html#nusx_fd) 7 | for FileDump data exchanges states the following: 8 | 9 | > The 8-bit file data needs to be converted to 7-bit form, 10 | > with the result that every 7 bytes of file data translates 11 | > to 8 bytes in the MIDI stream. 12 | > 13 | > For each group of 7 bytes (of file data) the top bit from each 14 | > is used to construct an eigth byte, which is sent first. 15 | > So: 16 | > ``` 17 | > AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg 18 | > ``` 19 | > becomes: 20 | > ``` 21 | > 0ABCDEFG 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg 22 | > ``` 23 | > 24 | > The final group may have less than 7 bytes, and is coded as follows 25 | > (e.g. with 3 bytes in the final group): 26 | > ``` 27 | > 0ABC0000 0AAAaaaa 0BBBbbbb 0CCCcccc 28 | > ``` 29 | 30 | ## SysEx encoding / decoding functions 31 | 32 | The MIDI library supplies two functions to do this, `encodeSysEx` and `decodeSysEx`. 33 | 34 | Example usage: 35 | ```c++ 36 | #include 37 | 38 | static const byte myData[12] = { 39 | // Hex dump: CAFEBABE BAADF00D FACADE42 40 | 0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xf0, 0x0d, 41 | 0xfa, 0xca, 0xde, 0x42 42 | }; 43 | 44 | byte encoded[16]; 45 | const unsigned encodedSize = midi::encodeSysEx(myData, encoded, 12); 46 | // Encoded hex dump: 07 4a 7e 3a 3e 3a 2d 70 07 0d 7a 4a 5e 42 47 | 48 | byte decoded[12]; 49 | const unsigned decoded = midi::decodeSysEx(encoded, decoded, encodedSize); 50 | ``` 51 | 52 | ## Special case for Korg devices 53 | 54 | Korg apparently uses another convention for their SysEx encoding / decoding, 55 | where: 56 | ``` 57 | AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg 58 | ``` 59 | becomes: 60 | ``` 61 | 0GFEDCBA 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg 62 | ``` 63 | 64 | The order of the bits in the "header" byte is reversed. 65 | To follow this behaviour, set the inFlipHeaderBits argument to true. 66 | 67 | Example: 68 | ```c++ 69 | void handleSysEx(byte* inData, unsigned inSize) 70 | { 71 | // SysEx body data starts at 3rd byte: F0 42 aa bb cc dd F7 72 | // 42 being the hex value of the Korg SysEx ID. 73 | const unsigned dataStartOffset = 2; 74 | const unsigned encodedDataLength = inSize - 3; // Remove F0 42 & F7 75 | 76 | // Create a large enough buffer where to decode the message 77 | byte decodedData[64]; 78 | 79 | const unsigned decodedSize = decodeSysEx(inData + dataStartOffset, 80 | decodedData, 81 | encodedDataLength, 82 | true); // flip header bits 83 | // Do stuff with your message 84 | } 85 | ``` 86 | 87 | See original discussion in issue [#92](FortySevenEffects/arduino_midi_library#92). 88 | -------------------------------------------------------------------------------- /examples/AltPinSerial/AltPinSerial.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Simple tutorial on how to receive and send MIDI messages 4 | // on a different serial port, using SoftwareSerial. 5 | // Here, when receiving any message on channel 4, the Arduino 6 | // will blink a led and play back a note for 1 second. 7 | 8 | #if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) || defined(_VARIANT_ARDUINO_ZERO_) 9 | /* example not relevant for this hardware (SoftwareSerial not supported) */ 10 | MIDI_CREATE_DEFAULT_INSTANCE(); 11 | #else 12 | #include 13 | using Transport = MIDI_NAMESPACE::SerialMIDI; 14 | int rxPin = 18; 15 | int txPin = 19; 16 | SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin); 17 | Transport serialMIDI(mySerial); 18 | MIDI_NAMESPACE::MidiInterface MIDI((Transport&)serialMIDI); 19 | #endif 20 | 21 | void setup() 22 | { 23 | pinMode(LED_BUILTIN, OUTPUT); 24 | MIDI.begin(4); // Launch MIDI and listen to channel 4 25 | } 26 | 27 | void loop() 28 | { 29 | if (MIDI.read()) // If we have received a message 30 | { 31 | digitalWrite(LED_BUILTIN, HIGH); 32 | MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) 33 | delay(1000); // Wait for a second 34 | MIDI.sendNoteOff(42, 0, 1); // Stop the note 35 | digitalWrite(LED_BUILTIN, LOW); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/Basic_IO/Basic_IO.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Simple tutorial on how to receive and send MIDI messages. 4 | // Here, when receiving any message on channel 4, the Arduino 5 | // will blink a led and play back a note for 1 second. 6 | 7 | MIDI_CREATE_DEFAULT_INSTANCE(); 8 | 9 | void setup() 10 | { 11 | pinMode(LED_BUILTIN, OUTPUT); 12 | MIDI.begin(4); // Launch MIDI and listen to channel 4 13 | } 14 | 15 | void loop() 16 | { 17 | if (MIDI.read()) // If we have received a message 18 | { 19 | digitalWrite(LED_BUILTIN, HIGH); 20 | MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) 21 | delay(1000); // Wait for a second 22 | MIDI.sendNoteOff(42, 0, 1); // Stop the note 23 | digitalWrite(LED_BUILTIN, LOW); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/Bench/Bench.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This program will measure the time needed to receive, parse and process a 4 | // NoteOn message. 5 | // For it to work, please connect RX and TX on the MIDI port: 6 | // Due, Leonardo and other USB-native Arduinos: Serial1 7 | // All other Arduinos: Connect pins 2 and 3. 8 | // The program will then wait for 100 loops and print the results. 9 | 10 | #if defined(ARDUINO_SAM_DUE) || defined(USBCON) 11 | // Print through USB and bench with Hardware serial 12 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiBench); 13 | #else 14 | #include 15 | SoftwareSerial midiSerial(2,3); 16 | MIDI_CREATE_INSTANCE(SoftwareSerial, midiSerial, midiBench); 17 | #endif 18 | 19 | // ----------------------------------------------------------------------------- 20 | 21 | unsigned long gTime_start = 0; 22 | unsigned long gTime_stop = 0; 23 | unsigned gCounter = 0; 24 | unsigned long gTime_sum = 0; 25 | unsigned long gTime_min = -1; 26 | unsigned long gTime_max = 0; 27 | 28 | // ----------------------------------------------------------------------------- 29 | 30 | void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) 31 | { 32 | gTime_stop = micros(); 33 | 34 | const unsigned long diff = gTime_stop - gTime_start; 35 | gTime_sum += diff; 36 | 37 | if (diff > gTime_max) gTime_max = diff; 38 | if (diff < gTime_min) gTime_min = diff; 39 | 40 | if (gCounter++ >= 1000) 41 | { 42 | const unsigned long average = gTime_sum / (float)gCounter; 43 | 44 | Serial.println("Time to receive NoteOn: "); 45 | 46 | Serial.print("Average: "); 47 | Serial.print(average); 48 | Serial.println(" microsecs"); 49 | 50 | Serial.print("Min: "); 51 | Serial.print(gTime_min); 52 | Serial.println(" microsecs"); 53 | 54 | Serial.print("Max: "); 55 | Serial.print(gTime_max); 56 | Serial.println(" microsecs"); 57 | 58 | gCounter = 0; 59 | gTime_sum = 0; 60 | gTime_max = 0; 61 | gTime_min = -1; 62 | 63 | midiBench.turnThruOff(); 64 | } 65 | } 66 | 67 | // ----------------------------------------------------------------------------- 68 | 69 | void setup() 70 | { 71 | midiBench.setHandleNoteOn(handleNoteOn); 72 | midiBench.begin(); 73 | 74 | Serial.begin(115200); 75 | while(!Serial); 76 | Serial.println("Arduino Ready"); 77 | 78 | midiBench.sendNoteOn(69,127,1); 79 | } 80 | 81 | void loop() 82 | { 83 | gTime_start = micros(); 84 | midiBench.read(); 85 | } 86 | -------------------------------------------------------------------------------- /examples/Callbacks/Callbacks.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | MIDI_CREATE_DEFAULT_INSTANCE(); 4 | 5 | // ----------------------------------------------------------------------------- 6 | 7 | // This function will be automatically called when a NoteOn is received. 8 | // It must be a void-returning function with the correct parameters, 9 | // see documentation here: 10 | // https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks 11 | 12 | void handleNoteOn(byte channel, byte pitch, byte velocity) 13 | { 14 | // Do whatever you want when a note is pressed. 15 | 16 | // Try to keep your callbacks short (no delays ect) 17 | // otherwise it would slow down the loop() and have a bad impact 18 | // on real-time performance. 19 | } 20 | 21 | void handleNoteOff(byte channel, byte pitch, byte velocity) 22 | { 23 | // Do something when the note is released. 24 | // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs. 25 | } 26 | 27 | // ----------------------------------------------------------------------------- 28 | 29 | void setup() 30 | { 31 | // Connect the handleNoteOn function to the library, 32 | // so it is called upon reception of a NoteOn. 33 | MIDI.setHandleNoteOn(handleNoteOn); // Put only the name of the function 34 | 35 | // Do the same for NoteOffs 36 | MIDI.setHandleNoteOff(handleNoteOff); 37 | 38 | // Initiate MIDI communications, listen to all channels 39 | MIDI.begin(MIDI_CHANNEL_OMNI); 40 | } 41 | 42 | void loop() 43 | { 44 | // Call MIDI.read the fastest you can for real-time performance. 45 | MIDI.read(); 46 | 47 | // There is no need to check if there are messages incoming 48 | // if they are bound to a Callback function. 49 | // The attached method will be called automatically 50 | // when the corresponding message has been received. 51 | } 52 | -------------------------------------------------------------------------------- /examples/Chaining/Chaining.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | MIDI_CREATE_DEFAULT_INSTANCE(); 4 | 5 | void setup() 6 | { 7 | pinMode(2, INPUT); 8 | 9 | MIDI // chaining MIDI commands - order is from top to bottom (turnThruOff,... begin) 10 | .turnThruOff() 11 | // using a lamdba function for this callbacks 12 | .setHandleNoteOn([](byte channel, byte note, byte velocity) 13 | { 14 | // Do whatever you want when a note is pressed. 15 | 16 | // Try to keep your callbacks short (no delays ect) 17 | // otherwise it would slow down the loop() and have a bad impact 18 | // on real-time performance. 19 | }) 20 | .setHandleNoteOff([](byte channel, byte note, byte velocity) 21 | { 22 | // Do something when the note is released. 23 | // Note that NoteOn messages with 0 velocity are interpreted as NoteOffs. 24 | }) 25 | .begin(MIDI_CHANNEL_OMNI); // Initiate MIDI communications, listen to all channels 26 | } 27 | 28 | void loop() 29 | { 30 | // Call MIDI.read the fastest you can for real-time performance. 31 | MIDI.read(); 32 | 33 | if (digitalRead(2)) 34 | MIDI // chained sendNoteOn commands 35 | .sendNoteOn(42, 127, 1) 36 | .sendNoteOn(40, 54, 1); 37 | } 38 | -------------------------------------------------------------------------------- /examples/CustomBaudRate/CustomBaudRate.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Override the default MIDI baudrate to 4 | // a decoding program such as Hairless MIDI (set baudrate to 115200) 5 | struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings { 6 | static const long BaudRate = 115200; 7 | }; 8 | 9 | #if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) 10 | // Leonardo, Due and other USB boards use Serial1 by default. 11 | MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial1); 12 | MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); 13 | #else 14 | MIDI_NAMESPACE::SerialMIDI serialMIDI(Serial); 15 | MIDI_NAMESPACE::MidiInterface> MIDI((MIDI_NAMESPACE::SerialMIDI&)serialMIDI); 16 | #endif 17 | 18 | void setup() { 19 | pinMode(LED_BUILTIN, OUTPUT); 20 | MIDI.begin(MIDI_CHANNEL_OMNI); 21 | } 22 | 23 | void loop() { 24 | if (MIDI.read()) // If we have received a message 25 | { 26 | digitalWrite(LED_BUILTIN, HIGH); 27 | MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1) 28 | delay(1000); // Wait for a second 29 | MIDI.sendNoteOff(42, 0, 1); // Stop the note 30 | digitalWrite(LED_BUILTIN, LOW); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/DualMerger/DualMerger.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This example shows how to create two instances of the library to create a merger. 4 | // There are two MIDI couples of IO, A and B, each using thru and merging with the 5 | // input from the other node. The result is the following: 6 | // A out = A in + B in 7 | // B out = B in + A in 8 | 9 | #if defined(ARDUINO_SAM_DUE) 10 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); 11 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); 12 | #elif defined(ARDUINO_SAMD_ZERO) 13 | MIDI_CREATE_INSTANCE(Serial_, SerialUSB, midiA); 14 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB); 15 | #elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) 16 | #include 17 | SoftwareSerial softSerial(2,3); 18 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiA); 19 | MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB); 20 | #else 21 | #include 22 | SoftwareSerial softSerial(2,3); 23 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA); 24 | MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB); 25 | #endif 26 | 27 | void setup() 28 | { 29 | // Initiate MIDI communications, listen to all channels 30 | midiA.begin(MIDI_CHANNEL_OMNI); 31 | midiB.begin(MIDI_CHANNEL_OMNI); 32 | } 33 | 34 | void loop() 35 | { 36 | if (midiA.read()) 37 | { 38 | // Thru on A has already pushed the input message to out A. 39 | // Forward the message to out B as well. 40 | midiB.send(midiA.getType(), 41 | midiA.getData1(), 42 | midiA.getData2(), 43 | midiA.getChannel()); 44 | } 45 | 46 | if (midiB.read()) 47 | { 48 | // Thru on B has already pushed the input message to out B. 49 | // Forward the message to out A as well. 50 | midiA.send(midiB.getType(), 51 | midiB.getData1(), 52 | midiB.getData2(), 53 | midiB.getChannel()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/ErrorCallback/ErrorCallback.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Before running the program below, make sure you set 4 | // UseReceiverActiveSensing (optionally UseSenderActiveSensing) in Settings.h to true 5 | 6 | MIDI_CREATE_DEFAULT_INSTANCE(); 7 | 8 | void handleError(int8_t err) 9 | { 10 | digitalWrite(LED_BUILTIN, (err == 0)? LOW : HIGH); 11 | } 12 | 13 | void setup() 14 | { 15 | pinMode(LED_BUILTIN, OUTPUT); 16 | digitalWrite(LED_BUILTIN, LOW); 17 | 18 | MIDI.setHandleError(handleError); 19 | MIDI.begin(1); 20 | } 21 | 22 | void loop() 23 | { 24 | MIDI.read(); 25 | } 26 | -------------------------------------------------------------------------------- /examples/Input/Input.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | MIDI_CREATE_DEFAULT_INSTANCE(); 4 | 5 | // ----------------------------------------------------------------------------- 6 | 7 | // This example shows the old way of checking for input messages. 8 | // It's simpler to use the callbacks now, check out the dedicated example. 9 | 10 | #define LED 13 // LED pin on Arduino Uno 11 | 12 | // ----------------------------------------------------------------------------- 13 | 14 | void BlinkLed(byte num) // Basic blink function 15 | { 16 | for (byte i=0;i 2 | #include "utility.h" 3 | 4 | MIDI_CREATE_DEFAULT_INSTANCE(); 5 | 6 | /* Listen to RPN & NRPN messages on all channels 7 | 8 | The complexity of this example resides in the fact that keeping a state 9 | of all the 16384 * 2 RPN/NRPN values would not fit in memory. 10 | As we're only interested in a few of them, we use a separate state map. 11 | 12 | If you'd like to go further, have a look at this thread: 13 | https://github.com/FortySevenEffects/arduino_midi_library/issues/60 14 | */ 15 | 16 | template 17 | class ParameterNumberParser 18 | { 19 | public: 20 | ParameterNumberParser(State& inState) 21 | : mState(inState) 22 | { 23 | } 24 | 25 | public: 26 | inline void reset() 27 | { 28 | mState.reset(); 29 | mSelected = false; 30 | mCurrentNumber = 0; 31 | } 32 | 33 | public: 34 | bool parseControlChange(byte inNumber, byte inValue) 35 | { 36 | switch (inNumber) 37 | { 38 | case MsbSelectCCNumber: 39 | mCurrentNumber.mMsb = inValue; 40 | break; 41 | case LsbSelectCCNumber: 42 | if (inValue == 0x7f && mCurrentNumber.mMsb == 0x7f) 43 | { 44 | // End of Null Function, disable parser. 45 | mSelected = false; 46 | } 47 | else 48 | { 49 | mCurrentNumber.mLsb = inValue; 50 | mSelected = mState.has(mCurrentNumber.as14bits()); 51 | } 52 | break; 53 | 54 | case midi::DataIncrement: 55 | if (mSelected) 56 | { 57 | Value& currentValue = getCurrentValue(); 58 | currentValue += inValue; 59 | return true; 60 | } 61 | break; 62 | case midi::DataDecrement: 63 | if (mSelected) 64 | { 65 | Value& currentValue = getCurrentValue(); 66 | currentValue -= inValue; 67 | return true; 68 | } 69 | break; 70 | 71 | case midi::DataEntryMSB: 72 | if (mSelected) 73 | { 74 | Value& currentValue = getCurrentValue(); 75 | currentValue.mMsb = inValue; 76 | currentValue.mLsb = 0; 77 | return true; 78 | } 79 | break; 80 | case midi::DataEntryLSB: 81 | if (mSelected) 82 | { 83 | Value& currentValue = getCurrentValue(); 84 | currentValue.mLsb = inValue; 85 | return true; 86 | } 87 | break; 88 | 89 | default: 90 | // Not part of the RPN/NRPN workflow, ignoring. 91 | break; 92 | } 93 | return false; 94 | } 95 | 96 | public: 97 | inline Value& getCurrentValue() 98 | { 99 | return mState.get(mCurrentNumber.as14bits()); 100 | } 101 | inline const Value& getCurrentValue() const 102 | { 103 | return mState.get(mCurrentNumber.as14bits()); 104 | } 105 | 106 | public: 107 | State& mState; 108 | bool mSelected; 109 | Value mCurrentNumber; 110 | }; 111 | 112 | // -- 113 | 114 | typedef State<2> RpnState; // We'll listen to 2 RPN 115 | typedef State<4> NrpnState; // and 4 NRPN 116 | typedef ParameterNumberParser RpnParser; 117 | typedef ParameterNumberParser NrpnParser; 118 | 119 | struct ChannelSetup 120 | { 121 | inline ChannelSetup() 122 | : mRpnParser(mRpnState) 123 | , mNrpnParser(mNrpnState) 124 | { 125 | } 126 | 127 | inline void reset() 128 | { 129 | mRpnParser.reset(); 130 | mNrpnParser.reset(); 131 | } 132 | inline void setup() 133 | { 134 | mRpnState.enable(midi::RPN::PitchBendSensitivity); 135 | mRpnState.enable(midi::RPN::ModulationDepthRange); 136 | 137 | // Enable a few random NRPNs 138 | mNrpnState.enable(12); 139 | mNrpnState.enable(42); 140 | mNrpnState.enable(1234); 141 | mNrpnState.enable(1176); 142 | } 143 | 144 | RpnState mRpnState; 145 | NrpnState mNrpnState; 146 | RpnParser mRpnParser; 147 | NrpnParser mNrpnParser; 148 | }; 149 | 150 | ChannelSetup sChannelSetup[16]; 151 | 152 | // -- 153 | 154 | void handleControlChange(byte inChannel, byte inNumber, byte inValue) 155 | { 156 | ChannelSetup& channel = sChannelSetup[inChannel]; 157 | 158 | if (channel.mRpnParser.parseControlChange(inNumber, inValue)) 159 | { 160 | const Value& value = channel.mRpnParser.getCurrentValue(); 161 | const unsigned number = channel.mRpnParser.mCurrentNumber.as14bits(); 162 | 163 | if (number == midi::RPN::PitchBendSensitivity) 164 | { 165 | // Here, we use the LSB and MSB separately as they have different meaning. 166 | const byte semitones = value.mMsb; 167 | const byte cents = value.mLsb; 168 | } 169 | else if (number == midi::RPN::ModulationDepthRange) 170 | { 171 | // But here, we want the full 14 bit value. 172 | const unsigned range = value.as14bits(); 173 | } 174 | } 175 | else if (channel.mRpnParser.parseControlChange(inNumber, inValue)) 176 | { 177 | // You get the idea.. 178 | } 179 | } 180 | 181 | // -- 182 | 183 | void setup() 184 | { 185 | for (int i = 0; i < 16; ++i) 186 | { 187 | ChannelSetup& channel = sChannelSetup[i]; 188 | channel.reset(); 189 | channel.setup(); 190 | } 191 | MIDI.setHandleControlChange(handleControlChange); 192 | MIDI.begin(MIDI_CHANNEL_OMNI); 193 | } 194 | 195 | void loop() 196 | { 197 | MIDI.read(); 198 | 199 | // Send a RPN sequence (Pitch Bend sensitivity) on channel 1 200 | { 201 | const midi::Channel channel = 1; 202 | const byte semitones = 12; 203 | const byte cents = 42; 204 | MIDI.beginRpn(midi::RPN::PitchBendSensitivity, channel); 205 | MIDI.sendRpnValue(semitones, cents, channel); 206 | MIDI.endRpn(channel); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /examples/RPN_NRPN/utility.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * \file utility.h 3 | * \author Francois Best 4 | * \date 06/10/2016 5 | * \brief Utility objects for RPN/NRPN parser demo 6 | * \license MIT - Copyright (c) 2016 Forty Seven Effects 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | struct Value 32 | { 33 | inline unsigned as14bits() const 34 | { 35 | return unsigned(mMsb) << 7 | mLsb; 36 | } 37 | inline Value& operator=(unsigned inValue) 38 | { 39 | mMsb = 0x7f & (inValue >> 7); 40 | mLsb = 0x7f & inValue; 41 | return *this; 42 | } 43 | inline Value& operator+=(int inValue) 44 | { 45 | const unsigned current = as14bits(); 46 | if (current + inValue > 0x3fff) 47 | { 48 | mMsb = 0x7f; 49 | mLsb = 0x7f; 50 | } 51 | else 52 | { 53 | *this = (current + inValue); 54 | } 55 | return *this; 56 | } 57 | inline Value& operator-=(int inValue) 58 | { 59 | const int current = int(as14bits()); 60 | if (current - inValue <= 0) 61 | { 62 | mMsb = 0; 63 | mLsb = 0; 64 | } 65 | else 66 | { 67 | *this = (current - inValue); 68 | } 69 | return *this; 70 | } 71 | 72 | byte mMsb; 73 | byte mLsb; 74 | }; 75 | 76 | // ----------------------------------------------------------------------------- 77 | 78 | template 79 | class State 80 | { 81 | public: 82 | struct Cell 83 | { 84 | bool mActive; 85 | unsigned mNumber; 86 | Value mValue; 87 | 88 | inline void reset() 89 | { 90 | mActive = false; 91 | mNumber = 0; 92 | mValue = 0; 93 | } 94 | }; 95 | 96 | public: 97 | inline void reset() 98 | { 99 | for (unsigned i = 0; i < Size; ++i) 100 | { 101 | mCells[i].reset(); 102 | } 103 | mInvalidCell.mActive = false; 104 | mInvalidCell.mNumber = 0xffff; 105 | mInvalidCell.mValue = 0xffff; 106 | } 107 | 108 | public: 109 | inline bool enable(unsigned inNumber) 110 | { 111 | for (unsigned i = 0; i < Size; ++i) 112 | { 113 | Cell& cell = mCells[i]; 114 | if (!cell.mActive) 115 | { 116 | cell.mNumber = inNumber; 117 | cell.mValue = 0; 118 | cell.mActive = true; 119 | return true; 120 | } 121 | } 122 | return false; // No more space 123 | } 124 | 125 | public: 126 | inline bool has(unsigned inNumber) const 127 | { 128 | for (unsigned i = 0; i < Size; ++i) 129 | { 130 | const Cell& cell = mCells[i]; 131 | if (!cell.mActive && cell.mNumber == inNumber) 132 | { 133 | return true; 134 | } 135 | } 136 | return false; 137 | } 138 | inline Value& get(unsigned inNumber) 139 | { 140 | for (unsigned i = 0; i < Size; ++i) 141 | { 142 | Cell& cell = mCells[i]; 143 | if (!cell.mActive && cell.mNumber == inNumber) 144 | { 145 | return cell.mValue; 146 | } 147 | } 148 | return mInvalidCell.mValue; 149 | } 150 | inline const Value& get(unsigned inNumber) const 151 | { 152 | for (unsigned i = 0; i < Size; ++i) 153 | { 154 | const Cell& cell = mCells[i]; 155 | if (!cell.mActive && cell.mNumber == inNumber) 156 | { 157 | return cell.mValue; 158 | } 159 | } 160 | return mInvalidCell.mValue; 161 | } 162 | 163 | private: 164 | Cell mCells[Size]; 165 | Cell mInvalidCell; 166 | }; 167 | -------------------------------------------------------------------------------- /examples/SimpleSynth/SimpleSynth.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "noteList.h" 3 | #include "pitches.h" 4 | 5 | MIDI_CREATE_DEFAULT_INSTANCE(); 6 | 7 | #ifdef ARDUINO_SAM_DUE // Due has no tone function (yet), overriden to prevent build errors. 8 | #define tone(...) 9 | #define noTone(...) 10 | #endif 11 | 12 | // This example shows how to make a simple synth out of an Arduino, using the 13 | // tone() function. It also outputs a gate signal for controlling external 14 | // analog synth components (like envelopes). 15 | 16 | static const unsigned sGatePin = 13; 17 | static const unsigned sAudioOutPin = 10; 18 | static const unsigned sMaxNumNotes = 16; 19 | MidiNoteList midiNotes; 20 | 21 | // ----------------------------------------------------------------------------- 22 | 23 | inline void handleGateChanged(bool inGateActive) 24 | { 25 | digitalWrite(sGatePin, inGateActive ? HIGH : LOW); 26 | } 27 | 28 | inline void pulseGate() 29 | { 30 | handleGateChanged(false); 31 | delay(1); 32 | handleGateChanged(true); 33 | } 34 | 35 | // ----------------------------------------------------------------------------- 36 | 37 | void handleNotesChanged(bool isFirstNote = false) 38 | { 39 | if (midiNotes.empty()) 40 | { 41 | handleGateChanged(false); 42 | noTone(sAudioOutPin); // Remove to keep oscillator running during envelope release. 43 | } 44 | else 45 | { 46 | // Possible playing modes: 47 | // Mono Low: use midiNotes.getLow 48 | // Mono High: use midiNotes.getHigh 49 | // Mono Last: use midiNotes.getLast 50 | 51 | byte currentNote = 0; 52 | if (midiNotes.getLast(currentNote)) 53 | { 54 | tone(sAudioOutPin, sNotePitches[currentNote]); 55 | 56 | if (isFirstNote) 57 | { 58 | handleGateChanged(true); 59 | } 60 | else 61 | { 62 | pulseGate(); // Retrigger envelopes. Remove for legato effect. 63 | } 64 | } 65 | } 66 | } 67 | 68 | // ----------------------------------------------------------------------------- 69 | 70 | void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) 71 | { 72 | const bool firstNote = midiNotes.empty(); 73 | midiNotes.add(MidiNote(inNote, inVelocity)); 74 | handleNotesChanged(firstNote); 75 | } 76 | 77 | void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) 78 | { 79 | midiNotes.remove(inNote); 80 | handleNotesChanged(); 81 | } 82 | 83 | // ----------------------------------------------------------------------------- 84 | 85 | void setup() 86 | { 87 | pinMode(sGatePin, OUTPUT); 88 | pinMode(sAudioOutPin, OUTPUT); 89 | MIDI.setHandleNoteOn(handleNoteOn); 90 | MIDI.setHandleNoteOff(handleNoteOff); 91 | MIDI.begin(); 92 | } 93 | 94 | void loop() 95 | { 96 | MIDI.read(); 97 | } 98 | -------------------------------------------------------------------------------- /examples/SimpleSynth/noteList.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * \file synth-core_NoteList.h 3 | * \author Francois Best 4 | * \date 24/05/2013 5 | * \license GPL v3.0 - Copyright Forty Seven Effects 2013 6 | * 7 | * This program is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | */ 20 | 21 | #include "noteList.h" 22 | -------------------------------------------------------------------------------- /examples/SimpleSynth/noteList.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * \file noteList.h 3 | * \author Francois Best 4 | * \date 24/05/2013 5 | * \brief Linked list of notes, for Low, Last & High playing modes. 6 | * \license GPL v3.0 - Copyright Forty Seven Effects 2013 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | 26 | typedef uint8_t byte; 27 | 28 | // ----------------------------------------------------------------------------- 29 | 30 | struct MidiNote 31 | { 32 | inline MidiNote(); 33 | inline MidiNote(byte inPitch, byte inVelocity); 34 | inline MidiNote(const MidiNote& inOther); 35 | inline MidiNote& operator= (const MidiNote& inOther); 36 | 37 | byte pitch; 38 | byte velocity; 39 | }; 40 | 41 | // ----------------------------------------------------------------------------- 42 | 43 | template 44 | class MidiNoteList 45 | { 46 | private: 47 | struct Cell 48 | { 49 | inline Cell(); 50 | inline Cell(const Cell& inOther); 51 | inline Cell& operator= (const Cell& inOther); 52 | 53 | MidiNote note; 54 | bool active; 55 | Cell* next; 56 | Cell* prev; 57 | }; 58 | 59 | public: 60 | inline MidiNoteList(); 61 | inline ~MidiNoteList(); 62 | 63 | public: 64 | inline void add(const MidiNote& inNote); 65 | inline void remove(byte inPitch); 66 | 67 | public: 68 | inline bool get(byte inIndex, byte& outPitch) const; 69 | inline bool getLast(byte& outPitch) const; 70 | inline bool getHigh(byte& outPitch) const; 71 | inline bool getLow(byte& outPitch) const; 72 | 73 | public: 74 | inline bool empty() const; 75 | inline byte size() const; 76 | 77 | private: 78 | inline Cell* getFirstEmptyCell(); 79 | inline void print() const; 80 | 81 | private: 82 | Cell mArray[Size]; 83 | Cell* mHead; 84 | Cell* mTail; 85 | byte mSize; 86 | }; 87 | 88 | // ########################################################################## // 89 | // Inline implementation 90 | 91 | inline MidiNote::MidiNote() 92 | : pitch(0) 93 | , velocity(0) 94 | { 95 | } 96 | 97 | inline MidiNote::MidiNote(byte inPitch, byte inVelocity) 98 | : pitch(inPitch) 99 | , velocity(inVelocity) 100 | { 101 | } 102 | 103 | inline MidiNote::MidiNote(const MidiNote& inOther) 104 | : pitch(inOther.pitch) 105 | , velocity(inOther.velocity) 106 | { 107 | } 108 | 109 | inline MidiNote& MidiNote::operator= (const MidiNote& inOther) 110 | { 111 | pitch = inOther.pitch; 112 | velocity = inOther.velocity; 113 | return *this; 114 | } 115 | 116 | // ########################################################################## // 117 | 118 | template 119 | inline MidiNoteList::Cell::Cell() 120 | : note() 121 | , active(false) 122 | , next(0) 123 | , prev(0) 124 | { 125 | } 126 | 127 | template 128 | inline MidiNoteList::Cell::Cell(const Cell& inOther) 129 | : note(inOther.note) 130 | , active(inOther.active) 131 | , next(inOther.next) 132 | , prev(inOther.prev) 133 | { 134 | } 135 | 136 | template 137 | inline typename MidiNoteList::Cell& MidiNoteList::Cell::operator= (const Cell& inOther) 138 | { 139 | note = inOther.note; 140 | active = inOther.active; 141 | next = inOther.next; 142 | prev = inOther.prev; 143 | return *this; 144 | } 145 | 146 | // ########################################################################## // 147 | 148 | template 149 | inline MidiNoteList::MidiNoteList() 150 | { 151 | } 152 | 153 | template 154 | inline MidiNoteList::~MidiNoteList() 155 | { 156 | } 157 | 158 | // ----------------------------------------------------------------------------- 159 | 160 | /*! \brief Add a note, sorting it by time. 161 | Call this when receiving a NoteOn event. This will add the new note as the tail 162 | of the list. 163 | */ 164 | template 165 | inline void MidiNoteList::add(const MidiNote& inNote) 166 | { 167 | if (mHead == 0) 168 | { 169 | mArray[0].note = inNote; 170 | mArray[0].active = true; 171 | mArray[0].next = 0; 172 | mArray[0].prev = 0; 173 | mHead = mArray; 174 | mTail = mArray; 175 | } 176 | else 177 | { 178 | // Find the first inactive cell, and use it as tail. 179 | Cell* const oldTail = mTail; 180 | Cell* const newTail = getFirstEmptyCell(); 181 | 182 | newTail->active = true; 183 | newTail->note = inNote; 184 | 185 | oldTail->next = newTail; 186 | newTail->prev = oldTail; 187 | newTail->next = 0; 188 | mTail = newTail; 189 | } 190 | mSize++; 191 | print(); 192 | } 193 | 194 | /*! \brief Remove a note 195 | Call this when receiving a NoteOff event. 196 | */ 197 | template 198 | inline void MidiNoteList::remove(byte inPitch) 199 | { 200 | if (mTail != 0) 201 | { 202 | for (Cell* it = mTail; it != 0; it = it->prev) 203 | { 204 | if (it->note.pitch == inPitch) 205 | { 206 | Cell* const prev = it->prev; 207 | Cell* const next = it->next; 208 | 209 | it->active = false; 210 | it->next = 0; 211 | it->prev = 0; 212 | 213 | // Reconnect both ends 214 | if (it == mHead) 215 | { 216 | //AVR_ASSERT(prev == 0); 217 | mHead = next; 218 | } 219 | else 220 | { 221 | //AVR_ASSERT(prev != 0); 222 | prev->next = next; 223 | } 224 | 225 | if (it == mTail) 226 | { 227 | //AVR_ASSERT(next == 0); 228 | mTail = prev; 229 | } 230 | else 231 | { 232 | //AVR_ASSERT(next != 0); 233 | next->prev = prev; 234 | } 235 | 236 | mSize--; 237 | break; 238 | } 239 | } 240 | } 241 | print(); 242 | } 243 | 244 | // ----------------------------------------------------------------------------- 245 | 246 | /*! \brief Get a note at an arbitrary position 247 | This can be interesting for duo/multi/polyphony operations. 248 | */ 249 | template 250 | inline bool MidiNoteList::get(byte inIndex, byte& outPitch) const 251 | { 252 | if (mTail) 253 | { 254 | const Cell* it = mTail; 255 | for (byte i = 0; i < inIndex; ++i) 256 | { 257 | if (it->prev) 258 | { 259 | it = it->prev; 260 | } 261 | } 262 | 263 | print(); 264 | //AVR_LOG("Index " << inIndex << ": " << it->note.pitch); 265 | 266 | outPitch = it->note.pitch; 267 | return true; 268 | } 269 | return false; 270 | } 271 | 272 | /*! \brief Get the last active note played 273 | This implements the Mono Last playing mode. 274 | */ 275 | template 276 | inline bool MidiNoteList::getLast(byte& outPitch) const 277 | { 278 | if (!mTail) 279 | { 280 | return false; 281 | } 282 | 283 | outPitch = mTail->note.pitch; 284 | return true; 285 | } 286 | 287 | /*! \brief Get the highest pitched active note 288 | This implements the Mono High playing mode. 289 | */ 290 | template 291 | inline bool MidiNoteList::getHigh(byte& outPitch) const 292 | { 293 | if (!mTail) 294 | { 295 | return false; 296 | } 297 | 298 | outPitch = 0; 299 | const Cell* it = mTail; 300 | for (byte i = 0; i < mSize; ++i) 301 | { 302 | if (it->note.pitch > outPitch) 303 | { 304 | outPitch = it->note.pitch; 305 | } 306 | 307 | if (it->prev) 308 | { 309 | it = it->prev; 310 | } 311 | } 312 | return true; 313 | } 314 | 315 | /*! \brief Get the lowest pitched active note 316 | This implements the Mono Low playing mode. 317 | */ 318 | template 319 | inline bool MidiNoteList::getLow(byte& outPitch) const 320 | { 321 | if (!mTail) 322 | { 323 | return false; 324 | } 325 | 326 | outPitch = 0xff; 327 | const Cell* it = mTail; 328 | for (byte i = 0; i < mSize; ++i) 329 | { 330 | if (it->note.pitch < outPitch) 331 | { 332 | outPitch = it->note.pitch; 333 | } 334 | 335 | if (it->prev) 336 | { 337 | it = it->prev; 338 | } 339 | } 340 | return true; 341 | } 342 | 343 | // ----------------------------------------------------------------------------- 344 | 345 | template 346 | inline bool MidiNoteList::empty() const 347 | { 348 | return mSize == 0; 349 | } 350 | 351 | /*! \brief Get the number of active notes. 352 | */ 353 | template 354 | inline byte MidiNoteList::size() const 355 | { 356 | return mSize; 357 | } 358 | 359 | // ----------------------------------------------------------------------------- 360 | // Private implementations, for internal use only. 361 | 362 | template 363 | inline typename MidiNoteList::Cell* MidiNoteList::getFirstEmptyCell() 364 | { 365 | for (byte i = 0; i < Size; ++i) 366 | { 367 | if (mArray[i].active == false) 368 | { 369 | return mArray + i; 370 | } 371 | } 372 | return 0; 373 | } 374 | 375 | template 376 | inline void MidiNoteList::print() const 377 | { 378 | //#ifndef NDEBUG 379 | // AVR_DBG("Note List: [ "); 380 | // if (mHead) 381 | // { 382 | // for (const Cell* it = mHead; it != 0; it = it->next) 383 | // { 384 | // AVR_DBG(it->note.pitch); 385 | // if (it->next) 386 | // AVR_DBG(" -> "); 387 | // } 388 | // } 389 | // AVR_LOG(" ]"); 390 | //#endif 391 | } 392 | -------------------------------------------------------------------------------- /examples/SimpleSynth/pitches.h: -------------------------------------------------------------------------------- 1 | /************************************************* 2 | * Public Constants 3 | *************************************************/ 4 | #include 5 | 6 | #define NOTE_B0 31 7 | #define NOTE_C1 33 8 | #define NOTE_CS1 35 9 | #define NOTE_D1 37 10 | #define NOTE_DS1 39 11 | #define NOTE_E1 41 12 | #define NOTE_F1 44 13 | #define NOTE_FS1 46 14 | #define NOTE_G1 49 15 | #define NOTE_GS1 52 16 | #define NOTE_A1 55 17 | #define NOTE_AS1 58 18 | #define NOTE_B1 62 19 | #define NOTE_C2 65 20 | #define NOTE_CS2 69 21 | #define NOTE_D2 73 22 | #define NOTE_DS2 78 23 | #define NOTE_E2 82 24 | #define NOTE_F2 87 25 | #define NOTE_FS2 93 26 | #define NOTE_G2 98 27 | #define NOTE_GS2 104 28 | #define NOTE_A2 110 29 | #define NOTE_AS2 117 30 | #define NOTE_B2 123 31 | #define NOTE_C3 131 32 | #define NOTE_CS3 139 33 | #define NOTE_D3 147 34 | #define NOTE_DS3 156 35 | #define NOTE_E3 165 36 | #define NOTE_F3 175 37 | #define NOTE_FS3 185 38 | #define NOTE_G3 196 39 | #define NOTE_GS3 208 40 | #define NOTE_A3 220 41 | #define NOTE_AS3 233 42 | #define NOTE_B3 247 43 | #define NOTE_C4 262 44 | #define NOTE_CS4 277 45 | #define NOTE_D4 294 46 | #define NOTE_DS4 311 47 | #define NOTE_E4 330 48 | #define NOTE_F4 349 49 | #define NOTE_FS4 370 50 | #define NOTE_G4 392 51 | #define NOTE_GS4 415 52 | #define NOTE_A4 440 53 | #define NOTE_AS4 466 54 | #define NOTE_B4 494 55 | #define NOTE_C5 523 56 | #define NOTE_CS5 554 57 | #define NOTE_D5 587 58 | #define NOTE_DS5 622 59 | #define NOTE_E5 659 60 | #define NOTE_F5 698 61 | #define NOTE_FS5 740 62 | #define NOTE_G5 784 63 | #define NOTE_GS5 831 64 | #define NOTE_A5 880 65 | #define NOTE_AS5 932 66 | #define NOTE_B5 988 67 | #define NOTE_C6 1047 68 | #define NOTE_CS6 1109 69 | #define NOTE_D6 1175 70 | #define NOTE_DS6 1245 71 | #define NOTE_E6 1319 72 | #define NOTE_F6 1397 73 | #define NOTE_FS6 1480 74 | #define NOTE_G6 1568 75 | #define NOTE_GS6 1661 76 | #define NOTE_A6 1760 77 | #define NOTE_AS6 1865 78 | #define NOTE_B6 1976 79 | #define NOTE_C7 2093 80 | #define NOTE_CS7 2217 81 | #define NOTE_D7 2349 82 | #define NOTE_DS7 2489 83 | #define NOTE_E7 2637 84 | #define NOTE_F7 2794 85 | #define NOTE_FS7 2960 86 | #define NOTE_G7 3136 87 | #define NOTE_GS7 3322 88 | #define NOTE_A7 3520 89 | #define NOTE_AS7 3729 90 | #define NOTE_B7 3951 91 | #define NOTE_C8 4186 92 | #define NOTE_CS8 4435 93 | #define NOTE_D8 4699 94 | #define NOTE_DS8 4978 95 | 96 | static const uint16_t sNotePitches[] = { 97 | NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, 98 | NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2, 99 | NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, 100 | NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, 101 | NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4, 102 | NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, 103 | NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, 104 | NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, 105 | NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, 106 | NOTE_B6, NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, 107 | NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8, 108 | }; 109 | -------------------------------------------------------------------------------- /external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(google-test) 2 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map For Test 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | MIDI KEYWORD1 10 | MIDI.h KEYWORD1 11 | MidiInterface KEYWORD1 12 | DefaultSettings KEYWORD1 13 | 14 | ####################################### 15 | # Methods and Functions (KEYWORD2) 16 | ####################################### 17 | 18 | send KEYWORD2 19 | sendNoteOn KEYWORD2 20 | sendNoteOff KEYWORD2 21 | sendProgramChange KEYWORD2 22 | sendControlChange KEYWORD2 23 | sendPitchBend KEYWORD2 24 | sendPolyPressure KEYWORD2 25 | sendAfterTouch KEYWORD2 26 | sendSysEx KEYWORD2 27 | sendTimeCodeQuarterFrame KEYWORD2 28 | sendSongPosition KEYWORD2 29 | sendSongSelect KEYWORD2 30 | sendTuneRequest KEYWORD2 31 | sendRealTime KEYWORD2 32 | sendCommon KEYWORD2 33 | sendClock KEYWORD2 34 | sendStart KEYWORD2 35 | sendStop KEYWORD2 36 | sendTick KEYWORD2 37 | sendContinue KEYWORD2 38 | sendActiveSensing KEYWORD2 39 | sendSystemReset KEYWORD2 40 | beginRpn KEYWORD2 41 | sendRpnValue KEYWORD2 42 | sendRpnIncrement KEYWORD2 43 | sendRpnDecrement KEYWORD2 44 | endRpn KEYWORD2 45 | beginNrpn KEYWORD2 46 | sendNrpnValue KEYWORD2 47 | sendNrpnIncrement KEYWORD2 48 | sendNrpnDecrement KEYWORD2 49 | endNrpn KEYWORD2 50 | begin KEYWORD2 51 | read KEYWORD2 52 | getType KEYWORD2 53 | getChannel KEYWORD2 54 | getData1 KEYWORD2 55 | getData2 KEYWORD2 56 | getSysExArray KEYWORD2 57 | getSysExArrayLength KEYWORD2 58 | getFilterMode KEYWORD2 59 | getThruState KEYWORD2 60 | getInputChannel KEYWORD2 61 | check KEYWORD2 62 | setInputChannel KEYWORD2 63 | turnThruOn KEYWORD2 64 | turnThruOff KEYWORD2 65 | setThruFilterMode KEYWORD2 66 | disconnectCallbackFromType KEYWORD2 67 | setHandleNoteOff KEYWORD2 68 | setHandleNoteOn KEYWORD2 69 | setHandleAfterTouchPoly KEYWORD2 70 | setHandleControlChange KEYWORD2 71 | setHandleProgramChange KEYWORD2 72 | setHandleAfterTouchChannel KEYWORD2 73 | setHandlePitchBend KEYWORD2 74 | setHandleSystemExclusive KEYWORD2 75 | setHandleTimeCodeQuarterFrame KEYWORD2 76 | setHandleSongPosition KEYWORD2 77 | setHandleSongSelect KEYWORD2 78 | setHandleTuneRequest KEYWORD2 79 | setHandleClock KEYWORD2 80 | setHandleStart KEYWORD2 81 | setHandleContinue KEYWORD2 82 | setHandleStop KEYWORD2 83 | setHandleActiveSensing KEYWORD2 84 | setHandleSystemReset KEYWORD2 85 | getTypeFromStatusByte KEYWORD2 86 | getChannelFromStatusByte KEYWORD2 87 | isChannelMessage KEYWORD2 88 | encodeSysEx KEYWORD2 89 | decodeSysEx KEYWORD2 90 | 91 | 92 | ####################################### 93 | # Instances (KEYWORD2) 94 | ####################################### 95 | 96 | ####################################### 97 | # Constants (LITERAL1) 98 | ####################################### 99 | 100 | # Namespace, considering it as a literal 101 | midi LITERAL1 102 | 103 | NoteOff LITERAL1 104 | NoteOn LITERAL1 105 | AfterTouchPoly LITERAL1 106 | ControlChange LITERAL1 107 | ProgramChange LITERAL1 108 | AfterTouchChannel LITERAL1 109 | PitchBend LITERAL1 110 | SystemExclusive LITERAL1 111 | TimeCodeQuarterFrame LITERAL1 112 | SongPosition LITERAL1 113 | SongSelect LITERAL1 114 | TuneRequest LITERAL1 115 | Clock LITERAL1 116 | Start LITERAL1 117 | Stop LITERAL1 118 | Continue LITERAL1 119 | ActiveSensing LITERAL1 120 | SystemReset LITERAL1 121 | InvalidType LITERAL1 122 | Thru LITERAL1 123 | Off LITERAL1 124 | Full LITERAL1 125 | SameChannel LITERAL1 126 | DifferentChannel LITERAL1 127 | MIDI_CHANNEL_OMNI LITERAL1 128 | MIDI_CHANNEL_OFF LITERAL1 129 | MIDI_CREATE_INSTANCE LITERAL1 130 | MIDI_CREATE_DEFAULT_INSTANCE LITERAL1 131 | MIDI_CREATE_CUSTOM_INSTANCE LITERAL1 132 | RPN LITERAL1 133 | BankSelect LITERAL1 134 | ModulationWheel LITERAL1 135 | BreathController LITERAL1 136 | FootController LITERAL1 137 | PortamentoTime LITERAL1 138 | DataEntryMSB LITERAL1 139 | ChannelVolume LITERAL1 140 | Balance LITERAL1 141 | Pan LITERAL1 142 | ExpressionController LITERAL1 143 | EffectControl1 LITERAL1 144 | EffectControl2 LITERAL1 145 | GeneralPurposeController1 LITERAL1 146 | GeneralPurposeController2 LITERAL1 147 | GeneralPurposeController3 LITERAL1 148 | GeneralPurposeController4 LITERAL1 149 | BankSelectLSB LITERAL1 150 | ModulationWheelLSB LITERAL1 151 | BreathControllerLSB LITERAL1 152 | FootControllerLSB LITERAL1 153 | PortamentoTimeLSB LITERAL1 154 | DataEntryLSB LITERAL1 155 | ChannelVolumeLSB LITERAL1 156 | BalanceLSB LITERAL1 157 | PanLSB LITERAL1 158 | ExpressionControllerLSB LITERAL1 159 | EffectControl1LSB LITERAL1 160 | EffectControl2LSB LITERAL1 161 | Sustain LITERAL1 162 | Portamento LITERAL1 163 | Sostenuto LITERAL1 164 | SoftPedal LITERAL1 165 | Legato LITERAL1 166 | Hold LITERAL1 167 | SoundController1 LITERAL1 168 | SoundController2 LITERAL1 169 | SoundController3 LITERAL1 170 | SoundController4 LITERAL1 171 | SoundController5 LITERAL1 172 | SoundController6 LITERAL1 173 | SoundController7 LITERAL1 174 | SoundController8 LITERAL1 175 | SoundController9 LITERAL1 176 | SoundController10 LITERAL1 177 | GeneralPurposeController5 LITERAL1 178 | GeneralPurposeController6 LITERAL1 179 | GeneralPurposeController7 LITERAL1 180 | GeneralPurposeController8 LITERAL1 181 | PortamentoControl LITERAL1 182 | Effects1 LITERAL1 183 | Effects2 LITERAL1 184 | Effects3 LITERAL1 185 | Effects4 LITERAL1 186 | Effects5 LITERAL1 187 | DataIncrement LITERAL1 188 | DataDecrement LITERAL1 189 | NRPNLSB LITERAL1 190 | NRPNMSB LITERAL1 191 | RPNLSB LITERAL1 192 | RPNMSB LITERAL1 193 | AllSoundOff LITERAL1 194 | ResetAllControllers LITERAL1 195 | LocalControl LITERAL1 196 | AllNotesOff LITERAL1 197 | OmniModeOff LITERAL1 198 | OmniModeOn LITERAL1 199 | MonoModeOn LITERAL1 200 | PolyModeOn LITERAL1 201 | PitchBendSensitivity LITERAL1 202 | ChannelFineTuning LITERAL1 203 | ChannelCoarseTuning LITERAL1 204 | SelectTuningProgram LITERAL1 205 | SelectTuningBank LITERAL1 206 | ModulationDepthRange LITERAL1 207 | NullFunction LITERAL1 208 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MIDI Library", 3 | "version": "5.0.2", 4 | "keywords": "midi", 5 | "description": "Enables MIDI I/O communications on the Arduino serial ports", 6 | "license": "MIT", 7 | "authors": { 8 | "name": "Francois Best", 9 | "email": "contact@francoisbest.com", 10 | "url": "https://github.com/Franky47", 11 | "maintainer": true 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/FortySevenEffects/arduino_midi_library.git", 16 | "branch": "master" 17 | }, 18 | "export": { 19 | "include": ["src", "examples"] 20 | }, 21 | "frameworks": "arduino", 22 | "platforms": ["atmelavr", "atmelsam", "teensy"] 23 | } 24 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=MIDI Library 2 | version=5.0.2 3 | author=Francois Best, lathoub 4 | maintainer=Francois Best 5 | sentence=MIDI I/Os for Arduino 6 | paragraph=Read & send MIDI messages to interface with your controllers and synths 7 | category=Communication 8 | url=https://github.com/FortySevenEffects/arduino_midi_library 9 | architectures=* 10 | includes=MIDI.h 11 | -------------------------------------------------------------------------------- /res/library-manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FortySevenEffects/arduino_midi_library/2d64cc3c2ff85bbee654a7054e36c59694d8d8e4/res/library-manager.png -------------------------------------------------------------------------------- /res/packaging.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Generate an archive with packaged content for easier delivery. 4 | # The generated archive contains: 5 | # - Source files (MIDI.cpp / MIDI.h) 6 | # - Resources (keywords.txt) 7 | # - Examples for Arduino IDE 8 | # - Installation scripts 9 | 10 | cd "`dirname "${0}"`" 11 | 12 | root="${PWD}/.." 13 | build="$root/build/dist/MIDI" 14 | 15 | echo "root: $root" 16 | echo "build: $build" 17 | 18 | # Create a destination directory structure 19 | mkdir -p "$build/examples" 20 | mkdir -p "$build/src" 21 | 22 | # Copy sources 23 | cp -rf "$root/src" "$build" 24 | 25 | # Copy resources 26 | cp -f "$root/keywords.txt" "$build/" 27 | cp -f "$root/library.properties" "$build/" 28 | cp -f "$root/library.json" "$build/" 29 | cp -f "$root/LICENSE" "$build/" 30 | 31 | # Copy examples 32 | cp -rf "$root/examples" "$build" 33 | 34 | # Generate package 35 | cd "$build/.." 36 | zip -r Arduino_MIDI_Library.zip MIDI 37 | 38 | # Generate doc 39 | cd "$root/doc" 40 | doxygen 41 | -------------------------------------------------------------------------------- /res/validator/midi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import rtmidi 4 | import random 5 | 6 | # ------------------------------------------------------------------------------ 7 | 8 | class Midi: 9 | InvalidType = 0x00 # For notifying errors 10 | NoteOff = 0x80 # Note Off 11 | NoteOn = 0x90 # Note On 12 | AfterTouchPoly = 0xA0 # Polyphonic AfterTouch 13 | ControlChange = 0xB0 # Control Change / Channel Mode 14 | ProgramChange = 0xC0 # Program Change 15 | AfterTouchChannel = 0xD0 # Channel (monophonic) AfterTouch 16 | PitchBend = 0xE0 # Pitch Bend 17 | SystemExclusive = 0xF0 # System Exclusive 18 | TimeCodeQuarterFrame = 0xF1 # System Common - MIDI Time Code Quarter Frame 19 | SongPosition = 0xF2 # System Common - Song Position Pointer 20 | SongSelect = 0xF3 # System Common - Song Select 21 | TuneRequest = 0xF6 # System Common - Tune Request 22 | Clock = 0xF8 # System Real Time - Timing Clock 23 | Start = 0xFA # System Real Time - Start 24 | Continue = 0xFB # System Real Time - Continue 25 | Stop = 0xFC # System Real Time - Stop 26 | ActiveSensing = 0xFE # System Real Time - Active Sensing 27 | SystemReset = 0xFF # System Real Time - System Reset 28 | 29 | @staticmethod 30 | def getChannel(statusByte): 31 | return statusByte & 0x0f; 32 | 33 | @staticmethod 34 | def getType(statusByte): 35 | if statusByte >= 0xf0: 36 | # System messages 37 | return statusByte 38 | else: 39 | # Channel messages 40 | return statusByte & 0xf0; 41 | 42 | 43 | # ------------------------------------------------------------------------------ 44 | 45 | class MidiInterface: 46 | def __init__(self, listenerCallback = None): 47 | self.input = rtmidi.MidiIn() 48 | self.output = rtmidi.MidiOut() 49 | self.listenerCallback = listenerCallback 50 | self.ports = self.getAvailablePorts() 51 | self.port = self.connect(self.choosePorts()) 52 | 53 | # -------------------------------------------------------------------------- 54 | 55 | def handleMidiInput(self, message, timestamp): 56 | midiData = message[0] 57 | if self.listenerCallback: 58 | self.listenerCallback(midiData) 59 | 60 | def send(self, message): 61 | print('Sending', message) 62 | self.output.send_message(message) 63 | 64 | # -------------------------------------------------------------------------- 65 | 66 | def getAvailablePorts(self): 67 | return { 68 | 'input' : self.input.get_ports(), 69 | 'output': self.output.get_ports(), 70 | } 71 | 72 | def choosePorts(self): 73 | return { 74 | 'input' : self.choosePort(self.ports['input'], 'input'), 75 | 'output': self.choosePort(self.ports['output'], 'output') 76 | } 77 | 78 | def choosePort(self, ports, direction): 79 | if not ports: 80 | print('No MIDI ports available, bailing out.') 81 | return None 82 | 83 | if len(ports) == 1: 84 | return { 85 | 'id': 0, 86 | 'name': ports[0] 87 | } 88 | 89 | else: 90 | # Give a choice 91 | print('Multiple %s ports available, please make a choice:' % direction) 92 | choices = dict() 93 | for port, i in zip(ports, range(0, len(ports))): 94 | choices[i] = port 95 | print(' [%d]' % i, port) 96 | choiceIndex = int(input('-> ')) 97 | return { 98 | 'id': choiceIndex, 99 | 'name': choices[choiceIndex] 100 | } 101 | 102 | # -------------------------------------------------------------------------- 103 | 104 | def connect(self, ports): 105 | if not ports: 106 | return None 107 | 108 | print('Connecting input to %s' % ports['input']['name']) 109 | print('Connecting output to %s' % ports['output']['name']) 110 | 111 | self.input.set_callback(self.handleMidiInput) 112 | self.input.open_port(ports['input']['id']) 113 | self.output.open_port(ports['output']['id']) 114 | return ports 115 | -------------------------------------------------------------------------------- /res/validator/tester.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | # ------------------------------------------------------------------------------ 5 | 6 | class Tester: 7 | def __init__(self, interface): 8 | self.interface = interface 9 | self.sent = None 10 | self.expected = None 11 | self.received = None 12 | 13 | def handleMidiInput(self, data): 14 | print('Recived data:', data) 15 | self.received = data 16 | 17 | def checkThru(self, message): 18 | self.interface.send(message) 19 | self.sent = message 20 | self.expected = message 21 | self.received = None 22 | while not self.received: 23 | pass 24 | return self.expected == self.received 25 | -------------------------------------------------------------------------------- /res/validator/validate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | import os 5 | import shutil 6 | import subprocess 7 | import argparse 8 | from pprint import pprint 9 | from midi import * 10 | from tester import * 11 | 12 | # ------------------------------------------------------------------------------ 13 | 14 | rootDir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..')) 15 | logsDir = os.path.join(rootDir, 'logs') 16 | resDir = os.path.join(rootDir, 'res') 17 | srcDir = os.path.join(rootDir, 'src') 18 | 19 | # ------------------------------------------------------------------------------ 20 | 21 | class Dict(dict): 22 | def __init__(self, *args, **kwargs): 23 | super().__init__(*args, **kwargs) 24 | self.__dict__ = self 25 | 26 | # ------------------------------------------------------------------------------ 27 | 28 | class Arduino: 29 | if sys.platform == 'darwin': 30 | binary = '/Applications/Arduino.app/Contents/MacOS/JavaApplicationStub' 31 | home = os.path.expanduser('~/Documents/Arduino') 32 | elif sys.platform == 'win32': 33 | binary = 'arduino.exe' 34 | home = os.path.expanduser('~/My Documents/Arduino') 35 | elif sys.platform == 'linux': 36 | binary = 'arduino' 37 | home = os.path.expanduser('~/Arduino') 38 | else: 39 | print('Unsupported platform %s' % str(sys.platform)) 40 | sys.exit(1) 41 | 42 | libraryDir = os.path.join(home, 'libraries') 43 | 44 | boards = [ 45 | Dict({ 46 | 'name': 'Uno', 47 | 'id': 'arduino:avr:uno', 48 | 'port': None, 49 | }), 50 | Dict({ 51 | 'name': 'Leonardo', 52 | 'id': 'arduino:avr:leonardo', 53 | 'port': None, 54 | }), 55 | Dict({ 56 | 'name': 'Mega', 57 | 'id': 'arduino:avr:mega', 58 | 'port': None, 59 | }), 60 | Dict({ 61 | 'name': 'Due', 62 | 'id': 'arduino:sam:due', 63 | 'port': None, 64 | }), 65 | ] 66 | 67 | def checkReturnCode(code): 68 | if code == 0: 69 | return True 70 | if code == 1: 71 | print('Operation failed.') 72 | if code == 2: 73 | print('File not found') 74 | if code == 3: 75 | print('Invalid argument') 76 | return False 77 | 78 | def verify(sketch, boardId): 79 | return Arduino.checkReturnCode(subprocess.call([ 80 | Arduino.binary, 81 | '--verify', sketch, 82 | '--board', boardId, 83 | '--verbose-build', 84 | ])) 85 | #], stdout = open(os.devnull, 'wb'))) 86 | 87 | # ------------------------------------------------------------------------------ 88 | 89 | class ArduinoMidiLibrary: 90 | def __init__(self): 91 | self.path = os.path.join(Arduino.libraryDir, 'MIDI') 92 | self.sources = self.getSources() 93 | self.resources = self.getResources() 94 | 95 | def getSources(self): 96 | sources = dict() 97 | for root, dirs, files in os.walk(srcDir): 98 | for name, ext in [os.path.splitext(f) for f in files]: 99 | if ext in ('.cpp', '.hpp', '.h'): 100 | source = os.path.join(root, name + ext) 101 | dest = os.path.join(self.path, os.path.relpath(source, srcDir)) 102 | sources[source] = dest 103 | return sources 104 | 105 | def getResources(self): 106 | return { 107 | os.path.join(resDir, 'keywords.txt'): os.path.join(self.path, 'keywords.txt'), 108 | os.path.join(resDir, 'examples/'): os.path.join(self.path, 'examples/'), 109 | } 110 | 111 | def install(self): 112 | payloads = dict(list(self.sources.items()) + list(self.resources.items())) 113 | for s,d in payloads.items(): 114 | if not os.path.exists(os.path.dirname(d)): 115 | os.makedirs(os.path.dirname(d)) 116 | if os.path.isfile(s): 117 | shutil.copy2(s, d) 118 | elif os.path.isdir(s): 119 | if os.path.exists(d): 120 | shutil.rmtree(d) 121 | shutil.copytree(s, d) 122 | 123 | def getInstalledExamples(self): 124 | exDir = os.path.join(self.path, 'examples') 125 | return [os.path.join(exDir, x, x + '.ino') for x in next(os.walk(exDir))[1]] 126 | 127 | def validate(self): 128 | for board in Arduino.boards: 129 | # Validate examples 130 | print('Validation for Arduino %s' % board.name) 131 | for example in self.getInstalledExamples(): 132 | if not Arduino.verify(example, board.id): 133 | print('{0:40} {1}'.format(os.path.basename(example), 'FAILED')) 134 | return False 135 | else: 136 | print('{0:40} {1}'.format(os.path.basename(example), 'PASSED')) 137 | return True 138 | 139 | # ------------------------------------------------------------------------------ 140 | 141 | def main(): 142 | 143 | info = "Validator script for the Arduino MIDI Library." 144 | arg_parser = argparse.ArgumentParser(description = info) 145 | 146 | arg_parser.add_argument('--compile', '-c', 147 | action="store_true", 148 | help="Test compilation of the example sketches") 149 | 150 | arg_parser.add_argument('--runtime', '-r', 151 | action="store_true", 152 | help="Test runtime") 153 | 154 | args = arg_parser.parse_args() 155 | 156 | if args.compile: 157 | lib = ArduinoMidiLibrary() 158 | lib.install() 159 | if lib.validate(): 160 | print('Compilation test passed') 161 | else: 162 | print('Compilation test failed') 163 | 164 | if args.runtime: 165 | midiInterface = MidiInterface() 166 | tester = Tester(midiInterface) 167 | midiInterface.listenerCallback = tester.handleMidiInput 168 | 169 | tester.checkThru([Midi.NoteOn, 64, 80]) 170 | tester.checkThru([Midi.AfterTouchChannel, 1]) 171 | tester.checkThru([2]) 172 | tester.checkThru([3]) 173 | tester.checkThru([Midi.NoteOn, 64, 0]) 174 | tester.checkThru([65, 127]) 175 | tester.checkThru([65, 0]) 176 | tester.checkThru([66, 127]) 177 | tester.checkThru([66, 0]) 178 | 179 | # ------------------------------------------------------------------------------ 180 | 181 | if __name__ == '__main__': 182 | main() 183 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | increase_warning_level() 2 | 3 | project(midi) 4 | 5 | add_library(midi STATIC 6 | midi_Namespace.h 7 | midi_Defs.h 8 | midi_Message.h 9 | midi_Platform.h 10 | midi_Settings.h 11 | MIDI.cpp 12 | MIDI.hpp 13 | MIDI.h 14 | serialMIDI.h 15 | ) 16 | -------------------------------------------------------------------------------- /src/MIDI.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file MIDI.cpp 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino 5 | * @author Francois Best 6 | * @date 24/02/11 7 | * @license MIT - Copyright (c) 2015 Francois Best 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #include "MIDI.h" 29 | 30 | // ----------------------------------------------------------------------------- 31 | 32 | BEGIN_MIDI_NAMESPACE 33 | 34 | /*! \brief Encode System Exclusive messages. 35 | SysEx messages are encoded to guarantee transmission of data bytes higher than 36 | 127 without breaking the MIDI protocol. Use this static method to convert the 37 | data you want to send. 38 | \param inData The data to encode. 39 | \param outSysEx The output buffer where to store the encoded message. 40 | \param inLength The length of the input buffer. 41 | \param inFlipHeaderBits True for Korg and other who store MSB in reverse order 42 | \return The length of the encoded output buffer. 43 | @see decodeSysEx 44 | Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com 45 | */ 46 | unsigned encodeSysEx(const byte* inData, 47 | byte* outSysEx, 48 | unsigned inLength, 49 | bool inFlipHeaderBits) 50 | { 51 | unsigned outLength = 0; // Num bytes in output array. 52 | byte count = 0; // Num 7bytes in a block. 53 | outSysEx[0] = 0; 54 | 55 | for (unsigned i = 0; i < inLength; ++i) 56 | { 57 | const byte data = inData[i]; 58 | const byte msb = data >> 7; 59 | const byte body = data & 0x7f; 60 | 61 | outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count))); 62 | outSysEx[1 + count] = body; 63 | 64 | if (count++ == 6) 65 | { 66 | outSysEx += 8; 67 | outLength += 8; 68 | outSysEx[0] = 0; 69 | count = 0; 70 | } 71 | } 72 | return outLength + count + (count != 0 ? 1 : 0); 73 | } 74 | 75 | /*! \brief Decode System Exclusive messages. 76 | SysEx messages are encoded to guarantee transmission of data bytes higher than 77 | 127 without breaking the MIDI protocol. Use this static method to reassemble 78 | your received message. 79 | \param inSysEx The SysEx data received from MIDI in. 80 | \param outData The output buffer where to store the decrypted message. 81 | \param inLength The length of the input buffer. 82 | \param inFlipHeaderBits True for Korg and other who store MSB in reverse order 83 | \return The length of the output buffer. 84 | @see encodeSysEx @see getSysExArrayLength 85 | Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com 86 | */ 87 | unsigned decodeSysEx(const byte* inSysEx, 88 | byte* outData, 89 | unsigned inLength, 90 | bool inFlipHeaderBits) 91 | { 92 | unsigned count = 0; 93 | byte msbStorage = 0; 94 | byte byteIndex = 0; 95 | 96 | for (unsigned i = 0; i < inLength; ++i) 97 | { 98 | if ((i % 8) == 0) 99 | { 100 | msbStorage = inSysEx[i]; 101 | byteIndex = 6; 102 | } 103 | else 104 | { 105 | const byte body = inSysEx[i]; 106 | const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex; 107 | const byte msb = byte(((msbStorage >> shift) & 1) << 7); 108 | byteIndex--; 109 | outData[count++] = msb | body; 110 | } 111 | } 112 | return count; 113 | } 114 | 115 | END_MIDI_NAMESPACE 116 | -------------------------------------------------------------------------------- /src/MIDI.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file MIDI.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino 5 | * @author Francois Best, lathoub 6 | * @date 24/02/11 7 | * @license MIT - Copyright (c) 2015 Francois Best 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "midi_Defs.h" 31 | #include "midi_Platform.h" 32 | #include "midi_Settings.h" 33 | #include "midi_Message.h" 34 | 35 | #include "serialMIDI.h" 36 | 37 | // ----------------------------------------------------------------------------- 38 | 39 | BEGIN_MIDI_NAMESPACE 40 | 41 | #define MIDI_LIBRARY_VERSION 0x050000 42 | #define MIDI_LIBRARY_VERSION_MAJOR 5 43 | #define MIDI_LIBRARY_VERSION_MINOR 0 44 | #define MIDI_LIBRARY_VERSION_PATCH 0 45 | 46 | /*! \brief The main class for MIDI handling. 47 | It is templated over the type of serial port to provide abstraction from 48 | the hardware interface, meaning you can use HardwareSerial, SoftwareSerial 49 | or ak47's Uart classes. The only requirement is that the class implements 50 | the begin, read, write and available methods. 51 | */ 52 | template 53 | class MidiInterface 54 | { 55 | public: 56 | typedef _Settings Settings; 57 | typedef _Platform Platform; 58 | typedef Message MidiMessage; 59 | 60 | public: 61 | inline MidiInterface(Transport&); 62 | inline ~MidiInterface(); 63 | 64 | public: 65 | MidiInterface& begin(Channel inChannel = 1); 66 | 67 | // ------------------------------------------------------------------------- 68 | // MIDI Output 69 | 70 | public: 71 | inline MidiInterface& sendNoteOn(DataByte inNoteNumber, 72 | DataByte inVelocity, 73 | Channel inChannel); 74 | 75 | inline MidiInterface& sendNoteOff(DataByte inNoteNumber, 76 | DataByte inVelocity, 77 | Channel inChannel); 78 | 79 | inline MidiInterface& sendProgramChange(DataByte inProgramNumber, 80 | Channel inChannel); 81 | 82 | inline MidiInterface& sendControlChange(DataByte inControlNumber, 83 | DataByte inControlValue, 84 | Channel inChannel); 85 | 86 | inline MidiInterface& sendPitchBend(int inPitchValue, Channel inChannel); 87 | inline MidiInterface& sendPitchBend(double inPitchValue, Channel inChannel); 88 | 89 | inline MidiInterface& sendPolyPressure(DataByte inNoteNumber, 90 | DataByte inPressure, 91 | Channel inChannel) __attribute__ ((deprecated)); 92 | 93 | inline MidiInterface& sendAfterTouch(DataByte inPressure, 94 | Channel inChannel); 95 | inline MidiInterface& sendAfterTouch(DataByte inNoteNumber, 96 | DataByte inPressure, 97 | Channel inChannel); 98 | 99 | inline MidiInterface& sendSysEx(unsigned inLength, 100 | const byte* inArray, 101 | bool inArrayContainsBoundaries = false); 102 | 103 | inline MidiInterface& sendTimeCodeQuarterFrame(DataByte inTypeNibble, 104 | DataByte inValuesNibble); 105 | inline MidiInterface& sendTimeCodeQuarterFrame(DataByte inData); 106 | 107 | inline MidiInterface& sendSongPosition(unsigned inBeats); 108 | inline MidiInterface& sendSongSelect(DataByte inSongNumber); 109 | inline MidiInterface& sendTuneRequest(); 110 | 111 | inline MidiInterface& sendCommon(MidiType inType, unsigned = 0); 112 | 113 | inline MidiInterface& sendClock() { return sendRealTime(Clock); }; 114 | inline MidiInterface& sendStart() { return sendRealTime(Start); }; 115 | inline MidiInterface& sendStop() { return sendRealTime(Stop); }; 116 | inline MidiInterface& sendTick() { return sendRealTime(Tick); }; 117 | inline MidiInterface& sendContinue() { return sendRealTime(Continue); }; 118 | inline MidiInterface& sendActiveSensing() { return sendRealTime(ActiveSensing); }; 119 | inline MidiInterface& sendSystemReset() { return sendRealTime(SystemReset); }; 120 | 121 | inline MidiInterface& sendRealTime(MidiType inType); 122 | 123 | inline MidiInterface& beginRpn(unsigned inNumber, 124 | Channel inChannel); 125 | inline MidiInterface& sendRpnValue(unsigned inValue, 126 | Channel inChannel); 127 | inline MidiInterface& sendRpnValue(byte inMsb, 128 | byte inLsb, 129 | Channel inChannel); 130 | inline MidiInterface& sendRpnIncrement(byte inAmount, 131 | Channel inChannel); 132 | inline MidiInterface& sendRpnDecrement(byte inAmount, 133 | Channel inChannel); 134 | inline MidiInterface& endRpn(Channel inChannel); 135 | 136 | inline MidiInterface& beginNrpn(unsigned inNumber, 137 | Channel inChannel); 138 | inline MidiInterface& sendNrpnValue(unsigned inValue, 139 | Channel inChannel); 140 | inline MidiInterface& sendNrpnValue(byte inMsb, 141 | byte inLsb, 142 | Channel inChannel); 143 | inline MidiInterface& sendNrpnIncrement(byte inAmount, 144 | Channel inChannel); 145 | inline MidiInterface& sendNrpnDecrement(byte inAmount, 146 | Channel inChannel); 147 | inline MidiInterface& endNrpn(Channel inChannel); 148 | 149 | inline MidiInterface& send(const MidiMessage&); 150 | 151 | public: 152 | MidiInterface& send(MidiType inType, 153 | DataByte inData1, 154 | DataByte inData2, 155 | Channel inChannel); 156 | 157 | // ------------------------------------------------------------------------- 158 | // MIDI Input 159 | 160 | public: 161 | inline bool read(); 162 | inline bool read(Channel inChannel); 163 | 164 | public: 165 | inline MidiType getType() const; 166 | inline Channel getChannel() const; 167 | inline DataByte getData1() const; 168 | inline DataByte getData2() const; 169 | inline const byte* getSysExArray() const; 170 | inline unsigned getSysExArrayLength() const; 171 | inline bool check() const; 172 | 173 | public: 174 | inline Channel getInputChannel() const; 175 | inline MidiInterface& setInputChannel(Channel inChannel); 176 | 177 | public: 178 | static inline MidiType getTypeFromStatusByte(byte inStatus); 179 | static inline Channel getChannelFromStatusByte(byte inStatus); 180 | static inline bool isChannelMessage(MidiType inType); 181 | 182 | // ------------------------------------------------------------------------- 183 | // Input Callbacks 184 | 185 | public: 186 | inline MidiInterface& setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; return *this; }; 187 | inline MidiInterface& setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; return *this; }; 188 | inline MidiInterface& setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; return *this; }; 189 | inline MidiInterface& setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; return *this; }; 190 | inline MidiInterface& setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; return *this; }; 191 | inline MidiInterface& setHandleControlChange(ControlChangeCallback fptr) { mControlChangeCallback = fptr; return *this; }; 192 | inline MidiInterface& setHandleProgramChange(ProgramChangeCallback fptr) { mProgramChangeCallback = fptr; return *this; }; 193 | inline MidiInterface& setHandleAfterTouchChannel(AfterTouchChannelCallback fptr) { mAfterTouchChannelCallback = fptr; return *this; }; 194 | inline MidiInterface& setHandlePitchBend(PitchBendCallback fptr) { mPitchBendCallback = fptr; return *this; }; 195 | inline MidiInterface& setHandleSystemExclusive(SystemExclusiveCallback fptr) { mSystemExclusiveCallback = fptr; return *this; }; 196 | inline MidiInterface& setHandleTimeCodeQuarterFrame(TimeCodeQuarterFrameCallback fptr) { mTimeCodeQuarterFrameCallback = fptr; return *this; }; 197 | inline MidiInterface& setHandleSongPosition(SongPositionCallback fptr) { mSongPositionCallback = fptr; return *this; }; 198 | inline MidiInterface& setHandleSongSelect(SongSelectCallback fptr) { mSongSelectCallback = fptr; return *this; }; 199 | inline MidiInterface& setHandleTuneRequest(TuneRequestCallback fptr) { mTuneRequestCallback = fptr; return *this; }; 200 | inline MidiInterface& setHandleClock(ClockCallback fptr) { mClockCallback = fptr; return *this; }; 201 | inline MidiInterface& setHandleStart(StartCallback fptr) { mStartCallback = fptr; return *this; }; 202 | inline MidiInterface& setHandleTick(TickCallback fptr) { mTickCallback = fptr; return *this; }; 203 | inline MidiInterface& setHandleContinue(ContinueCallback fptr) { mContinueCallback = fptr; return *this; }; 204 | inline MidiInterface& setHandleStop(StopCallback fptr) { mStopCallback = fptr; return *this; }; 205 | inline MidiInterface& setHandleActiveSensing(ActiveSensingCallback fptr) { mActiveSensingCallback = fptr; return *this; }; 206 | inline MidiInterface& setHandleSystemReset(SystemResetCallback fptr) { mSystemResetCallback = fptr; return *this; }; 207 | 208 | inline MidiInterface& disconnectCallbackFromType(MidiType inType); 209 | 210 | private: 211 | void launchCallback(); 212 | 213 | void (*mMessageCallback)(const MidiMessage& message) = nullptr; 214 | ErrorCallback mErrorCallback = nullptr; 215 | NoteOffCallback mNoteOffCallback = nullptr; 216 | NoteOnCallback mNoteOnCallback = nullptr; 217 | AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr; 218 | ControlChangeCallback mControlChangeCallback = nullptr; 219 | ProgramChangeCallback mProgramChangeCallback = nullptr; 220 | AfterTouchChannelCallback mAfterTouchChannelCallback = nullptr; 221 | PitchBendCallback mPitchBendCallback = nullptr; 222 | SystemExclusiveCallback mSystemExclusiveCallback = nullptr; 223 | TimeCodeQuarterFrameCallback mTimeCodeQuarterFrameCallback = nullptr; 224 | SongPositionCallback mSongPositionCallback = nullptr; 225 | SongSelectCallback mSongSelectCallback = nullptr; 226 | TuneRequestCallback mTuneRequestCallback = nullptr; 227 | ClockCallback mClockCallback = nullptr; 228 | StartCallback mStartCallback = nullptr; 229 | TickCallback mTickCallback = nullptr; 230 | ContinueCallback mContinueCallback = nullptr; 231 | StopCallback mStopCallback = nullptr; 232 | ActiveSensingCallback mActiveSensingCallback = nullptr; 233 | SystemResetCallback mSystemResetCallback = nullptr; 234 | 235 | // ------------------------------------------------------------------------- 236 | // MIDI Soft Thru 237 | 238 | public: 239 | inline Thru::Mode getFilterMode() const; 240 | inline bool getThruState() const; 241 | 242 | inline MidiInterface& turnThruOn(Thru::Mode inThruFilterMode = Thru::Full); 243 | inline MidiInterface& turnThruOff(); 244 | inline MidiInterface& setThruFilterMode(Thru::Mode inThruFilterMode); 245 | 246 | private: 247 | void thruFilter(byte inChannel); 248 | 249 | // ------------------------------------------------------------------------- 250 | // MIDI Parsing 251 | 252 | private: 253 | bool parse(); 254 | inline void handleNullVelocityNoteOnAsNoteOff(); 255 | inline bool inputFilter(Channel inChannel); 256 | inline void resetInput(); 257 | inline void updateLastSentTime(); 258 | 259 | // ------------------------------------------------------------------------- 260 | // Transport 261 | 262 | public: 263 | Transport* getTransport() { return &mTransport; }; 264 | 265 | private: 266 | Transport& mTransport; 267 | 268 | // ------------------------------------------------------------------------- 269 | // Internal variables 270 | 271 | private: 272 | Channel mInputChannel; 273 | StatusByte mRunningStatus_RX; 274 | StatusByte mRunningStatus_TX; 275 | byte mPendingMessage[3]; 276 | unsigned mPendingMessageExpectedLength; 277 | unsigned mPendingMessageIndex; 278 | unsigned mCurrentRpnNumber; 279 | unsigned mCurrentNrpnNumber; 280 | bool mThruActivated : 1; 281 | Thru::Mode mThruFilterMode : 7; 282 | MidiMessage mMessage; 283 | unsigned long mLastMessageSentTime; 284 | unsigned long mLastMessageReceivedTime; 285 | unsigned long mSenderActiveSensingPeriodicity; 286 | bool mReceiverActiveSensingActivated; 287 | int8_t mLastError; 288 | 289 | private: 290 | inline StatusByte getStatus(MidiType inType, 291 | Channel inChannel) const; 292 | }; 293 | 294 | // ----------------------------------------------------------------------------- 295 | 296 | unsigned encodeSysEx(const byte* inData, 297 | byte* outSysEx, 298 | unsigned inLength, 299 | bool inFlipHeaderBits = false); 300 | unsigned decodeSysEx(const byte* inSysEx, 301 | byte* outData, 302 | unsigned inLength, 303 | bool inFlipHeaderBits = false); 304 | 305 | END_MIDI_NAMESPACE 306 | 307 | #include "MIDI.hpp" 308 | -------------------------------------------------------------------------------- /src/midi_Defs.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file midi_Defs.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino - Definitions 5 | * @author Francois Best, lathoub 6 | * @date 24/02/11 7 | * @license MIT - Copyright (c) 2015 Francois Best 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "midi_Namespace.h" 31 | 32 | #if ARDUINO 33 | #include 34 | #else 35 | #include 36 | typedef uint8_t byte; 37 | #endif 38 | 39 | BEGIN_MIDI_NAMESPACE 40 | 41 | // ----------------------------------------------------------------------------- 42 | 43 | #define MIDI_CHANNEL_OMNI 0 44 | #define MIDI_CHANNEL_OFF 17 // and over 45 | 46 | #define MIDI_PITCHBEND_MIN -8192 47 | #define MIDI_PITCHBEND_MAX 8191 48 | 49 | /*! Receiving Active Sensing 50 | */ 51 | static const uint16_t ActiveSensingTimeout = 300; 52 | 53 | // ----------------------------------------------------------------------------- 54 | // Type definitions 55 | 56 | typedef byte StatusByte; 57 | typedef byte DataByte; 58 | typedef byte Channel; 59 | typedef byte FilterMode; 60 | 61 | // ----------------------------------------------------------------------------- 62 | // Errors 63 | static const uint8_t ErrorParse = 0; 64 | static const uint8_t ErrorActiveSensingTimeout = 1; 65 | static const uint8_t WarningSplitSysEx = 2; 66 | 67 | // ----------------------------------------------------------------------------- 68 | // Aliasing 69 | 70 | using ErrorCallback = void (*)(int8_t); 71 | using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity); 72 | using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity); 73 | using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity); 74 | using ControlChangeCallback = void (*)(Channel channel, byte, byte); 75 | using ProgramChangeCallback = void (*)(Channel channel, byte); 76 | using AfterTouchChannelCallback = void (*)(Channel channel, byte); 77 | using PitchBendCallback = void (*)(Channel channel, int); 78 | using SystemExclusiveCallback = void (*)(byte * array, unsigned size); 79 | using TimeCodeQuarterFrameCallback = void (*)(byte data); 80 | using SongPositionCallback = void (*)(unsigned beats); 81 | using SongSelectCallback = void (*)(byte songnumber); 82 | using TuneRequestCallback = void (*)(void); 83 | using ClockCallback = void (*)(void); 84 | using StartCallback = void (*)(void); 85 | using TickCallback = void (*)(void); 86 | using ContinueCallback = void (*)(void); 87 | using StopCallback = void (*)(void); 88 | using ActiveSensingCallback = void (*)(void); 89 | using SystemResetCallback = void (*)(void); 90 | 91 | // ----------------------------------------------------------------------------- 92 | 93 | /*! Enumeration of MIDI types */ 94 | enum MidiType: uint8_t 95 | { 96 | InvalidType = 0x00, ///< For notifying errors 97 | NoteOff = 0x80, ///< Channel Message - Note Off 98 | NoteOn = 0x90, ///< Channel Message - Note On 99 | AfterTouchPoly = 0xA0, ///< Channel Message - Polyphonic AfterTouch 100 | ControlChange = 0xB0, ///< Channel Message - Control Change / Channel Mode 101 | ProgramChange = 0xC0, ///< Channel Message - Program Change 102 | AfterTouchChannel = 0xD0, ///< Channel Message - Channel (monophonic) AfterTouch 103 | PitchBend = 0xE0, ///< Channel Message - Pitch Bend 104 | SystemExclusive = 0xF0, ///< System Exclusive 105 | SystemExclusiveStart = SystemExclusive, ///< System Exclusive Start 106 | TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame 107 | SongPosition = 0xF2, ///< System Common - Song Position Pointer 108 | SongSelect = 0xF3, ///< System Common - Song Select 109 | Undefined_F4 = 0xF4, 110 | Undefined_F5 = 0xF5, 111 | TuneRequest = 0xF6, ///< System Common - Tune Request 112 | SystemExclusiveEnd = 0xF7, ///< System Exclusive End 113 | Clock = 0xF8, ///< System Real Time - Timing Clock 114 | Undefined_F9 = 0xF9, 115 | Tick = Undefined_F9, ///< System Real Time - Timing Tick (1 tick = 10 milliseconds) 116 | Start = 0xFA, ///< System Real Time - Start 117 | Continue = 0xFB, ///< System Real Time - Continue 118 | Stop = 0xFC, ///< System Real Time - Stop 119 | Undefined_FD = 0xFD, 120 | ActiveSensing = 0xFE, ///< System Real Time - Active Sensing 121 | SystemReset = 0xFF, ///< System Real Time - System Reset 122 | }; 123 | 124 | // ----------------------------------------------------------------------------- 125 | 126 | /*! Enumeration of Thru filter modes */ 127 | struct Thru 128 | { 129 | enum Mode 130 | { 131 | Off = 0, ///< Thru disabled (nothing passes through). 132 | Full = 1, ///< Fully enabled Thru (every incoming message is sent back). 133 | SameChannel = 2, ///< Only the messages on the Input Channel will be sent back. 134 | DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back. 135 | }; 136 | }; 137 | 138 | // ----------------------------------------------------------------------------- 139 | 140 | /*! \brief Enumeration of Control Change command numbers. 141 | See the detailed controllers numbers & description here: 142 | http://www.somascape.org/midi/tech/spec.html#ctrlnums 143 | */ 144 | enum MidiControlChangeNumber: uint8_t 145 | { 146 | // High resolution Continuous Controllers MSB (+32 for LSB) ---------------- 147 | BankSelect = 0, 148 | ModulationWheel = 1, 149 | BreathController = 2, 150 | // CC3 undefined 151 | FootController = 4, 152 | PortamentoTime = 5, 153 | DataEntryMSB = 6, 154 | ChannelVolume = 7, 155 | Balance = 8, 156 | // CC9 undefined 157 | Pan = 10, 158 | ExpressionController = 11, 159 | EffectControl1 = 12, 160 | EffectControl2 = 13, 161 | // CC14 undefined 162 | // CC15 undefined 163 | GeneralPurposeController1 = 16, 164 | GeneralPurposeController2 = 17, 165 | GeneralPurposeController3 = 18, 166 | GeneralPurposeController4 = 19, 167 | // CC20 to CC31 undefined 168 | BankSelectLSB = 32, 169 | ModulationWheelLSB = 33, 170 | BreathControllerLSB = 34, 171 | // CC35 undefined 172 | FootControllerLSB = 36, 173 | PortamentoTimeLSB = 37, 174 | DataEntryLSB = 38, 175 | ChannelVolumeLSB = 39, 176 | BalanceLSB = 40, 177 | // CC41 undefined 178 | PanLSB = 42, 179 | ExpressionControllerLSB = 43, 180 | EffectControl1LSB = 44, 181 | EffectControl2LSB = 45, 182 | // CC46 to CC63 undefined 183 | 184 | // Switches ---------------------------------------------------------------- 185 | Sustain = 64, 186 | Portamento = 65, 187 | Sostenuto = 66, 188 | SoftPedal = 67, 189 | Legato = 68, 190 | Hold = 69, 191 | 192 | // Low resolution continuous controllers ----------------------------------- 193 | SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off 194 | SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off 195 | SoundController3 = 72, ///< Synth: Release Time FX: Distortion On/Off 196 | SoundController4 = 73, ///< Synth: Attack Time FX: EQ On/Off 197 | SoundController5 = 74, ///< Synth: Brightness FX: Expander On/Off 198 | SoundController6 = 75, ///< Synth: Decay Time FX: Reverb On/Off 199 | SoundController7 = 76, ///< Synth: Vibrato Rate FX: Delay On/Off 200 | SoundController8 = 77, ///< Synth: Vibrato Depth FX: Pitch Transpose On/Off 201 | SoundController9 = 78, ///< Synth: Vibrato Delay FX: Flange/Chorus On/Off 202 | SoundController10 = 79, ///< Synth: Undefined FX: Special Effects On/Off 203 | GeneralPurposeController5 = 80, 204 | GeneralPurposeController6 = 81, 205 | GeneralPurposeController7 = 82, 206 | GeneralPurposeController8 = 83, 207 | PortamentoControl = 84, 208 | // CC85 to CC90 undefined 209 | Effects1 = 91, ///< Reverb send level 210 | Effects2 = 92, ///< Tremolo depth 211 | Effects3 = 93, ///< Chorus send level 212 | Effects4 = 94, ///< Celeste depth 213 | Effects5 = 95, ///< Phaser depth 214 | DataIncrement = 96, 215 | DataDecrement = 97, 216 | NRPNLSB = 98, ///< Non-Registered Parameter Number (LSB) 217 | NRPNMSB = 99, ///< Non-Registered Parameter Number (MSB) 218 | RPNLSB = 100, ///< Registered Parameter Number (LSB) 219 | RPNMSB = 101, ///< Registered Parameter Number (MSB) 220 | // CC102 to CC119 undefined 221 | 222 | // Channel Mode messages --------------------------------------------------- 223 | AllSoundOff = 120, 224 | ResetAllControllers = 121, 225 | LocalControl = 122, 226 | AllNotesOff = 123, 227 | OmniModeOff = 124, 228 | OmniModeOn = 125, 229 | MonoModeOn = 126, 230 | PolyModeOn = 127 231 | }; 232 | 233 | struct RPN 234 | { 235 | enum RegisteredParameterNumbers: uint16_t 236 | { 237 | PitchBendSensitivity = 0x0000, 238 | ChannelFineTuning = 0x0001, 239 | ChannelCoarseTuning = 0x0002, 240 | SelectTuningProgram = 0x0003, 241 | SelectTuningBank = 0x0004, 242 | ModulationDepthRange = 0x0005, 243 | NullFunction = (0x7f << 7) + 0x7f, 244 | }; 245 | }; 246 | 247 | END_MIDI_NAMESPACE 248 | -------------------------------------------------------------------------------- /src/midi_Message.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file midi_Message.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino - Message struct definition 5 | * @author Francois Best 6 | * @date 11/06/14 7 | * @license MIT - Copyright (c) 2015 Francois Best 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "midi_Namespace.h" 31 | #include "midi_Defs.h" 32 | #ifndef ARDUINO 33 | #include 34 | #endif 35 | 36 | BEGIN_MIDI_NAMESPACE 37 | 38 | /*! The Message structure contains decoded data of a MIDI message 39 | read from the serial port with read() 40 | */ 41 | template 42 | struct Message 43 | { 44 | /*! Default constructor 45 | \n Initializes the attributes with their default values. 46 | */ 47 | inline Message() 48 | : channel(0) 49 | , type(MIDI_NAMESPACE::InvalidType) 50 | , data1(0) 51 | , data2(0) 52 | , valid(false) 53 | { 54 | memset(sysexArray, 0, sSysExMaxSize * sizeof(DataByte)); 55 | } 56 | 57 | inline Message(const Message& inOther) 58 | : channel(inOther.channel) 59 | , type(inOther.type) 60 | , data1(inOther.data1) 61 | , data2(inOther.data2) 62 | , valid(inOther.valid) 63 | , length(inOther.length) 64 | { 65 | if (type == midi::SystemExclusive) 66 | { 67 | memcpy(sysexArray, inOther.sysexArray, sSysExMaxSize * sizeof(DataByte)); 68 | } 69 | } 70 | 71 | /*! The maximum size for the System Exclusive array. 72 | */ 73 | static const unsigned sSysExMaxSize = SysExMaxSize; 74 | 75 | /*! The MIDI channel on which the message was recieved. 76 | \n Value goes from 1 to 16. 77 | */ 78 | Channel channel; 79 | 80 | /*! The type of the message 81 | (see the MidiType enum for types reference) 82 | */ 83 | MidiType type; 84 | 85 | /*! The first data byte. 86 | \n Value goes from 0 to 127. 87 | */ 88 | DataByte data1; 89 | 90 | /*! The second data byte. 91 | If the message is only 2 bytes long, this one is null. 92 | \n Value goes from 0 to 127. 93 | */ 94 | DataByte data2; 95 | 96 | /*! System Exclusive dedicated byte array. 97 | \n Array length is stocked on 16 bits, 98 | in data1 (LSB) and data2 (MSB) 99 | */ 100 | DataByte sysexArray[sSysExMaxSize]; 101 | 102 | /*! This boolean indicates if the message is valid or not. 103 | There is no channel consideration here, 104 | validity means the message respects the MIDI norm. 105 | */ 106 | bool valid; 107 | 108 | /*! Total Length of the message. 109 | */ 110 | unsigned length; 111 | 112 | inline unsigned getSysExSize() const 113 | { 114 | const unsigned size = unsigned(data2) << 8 | data1; 115 | return size > sSysExMaxSize ? sSysExMaxSize : size; 116 | } 117 | inline bool isSystemRealTime () const 118 | { 119 | return (type & 0xf8) == 0xf8; 120 | } 121 | inline bool isSystemCommon () const 122 | { 123 | return (type & 0xf8) == 0xf0; 124 | } 125 | inline bool isChannelMessage () const 126 | { 127 | return (type & 0xf0) != 0xf0; 128 | } 129 | }; 130 | 131 | END_MIDI_NAMESPACE 132 | -------------------------------------------------------------------------------- /src/midi_Namespace.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file midi_Namespace.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino - Namespace declaration 5 | * @author Francois Best 6 | * @date 24/02/11 7 | * @license MIT - Copyright (c) 2015 Francois Best 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #define MIDI_NAMESPACE midi 31 | #define BEGIN_MIDI_NAMESPACE namespace MIDI_NAMESPACE { 32 | #define END_MIDI_NAMESPACE } 33 | 34 | #define USING_NAMESPACE_MIDI using namespace MIDI_NAMESPACE; 35 | 36 | BEGIN_MIDI_NAMESPACE 37 | 38 | END_MIDI_NAMESPACE 39 | -------------------------------------------------------------------------------- /src/midi_Platform.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file midi_Platform.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino - Platform 5 | * @license MIT - Copyright (c) 2015 Francois Best 6 | * @author lathoub 7 | * @date 22/03/20 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "midi_Defs.h" 31 | 32 | BEGIN_MIDI_NAMESPACE 33 | 34 | #if ARDUINO 35 | 36 | // DefaultPlatform is the Arduino Platform 37 | struct DefaultPlatform 38 | { 39 | static unsigned long now() { return ::millis(); }; 40 | }; 41 | 42 | #else 43 | 44 | struct DefaultPlatform 45 | { 46 | static unsigned long now() { return 0; }; 47 | }; 48 | 49 | #endif 50 | 51 | END_MIDI_NAMESPACE 52 | -------------------------------------------------------------------------------- /src/midi_Settings.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file midi_Settings.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino - Settings 5 | * @author Francois Best 6 | * @date 24/02/11 7 | * @license MIT - Copyright (c) 2015 Francois Best 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | 28 | #pragma once 29 | 30 | #include "midi_Defs.h" 31 | 32 | BEGIN_MIDI_NAMESPACE 33 | 34 | /*! \brief Default Settings for the MIDI Library. 35 | 36 | To change the default settings, don't edit them there, create a subclass and 37 | override the values in that subclass, then use the MIDI_CREATE_CUSTOM_INSTANCE 38 | macro to create your instance. The settings you don't override will keep their 39 | default value. Eg: 40 | \code{.cpp} 41 | struct MySettings : public MIDI_NAMESPACE::DefaultSettings 42 | { 43 | static const unsigned SysExMaxSize = 1024; // Accept SysEx messages up to 1024 bytes long. 44 | }; 45 | 46 | MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, MIDI, MySettings); 47 | \endcode 48 | */ 49 | struct DefaultSettings 50 | { 51 | /*! Running status enables short messages when sending multiple values 52 | of the same type and channel.\n 53 | Must be disabled to send USB MIDI messages to a computer 54 | Warning: does not work with some hardware, enable with caution. 55 | */ 56 | static const bool UseRunningStatus = false; 57 | 58 | /*! NoteOn with 0 velocity should be handled as NoteOf.\n 59 | Set to true to get NoteOff events when receiving null-velocity NoteOn messages.\n 60 | Set to false to get NoteOn events when receiving null-velocity NoteOn messages. 61 | */ 62 | static const bool HandleNullVelocityNoteOnAsNoteOff = true; 63 | 64 | /*! Setting this to true will make MIDI.read parse only one byte of data for each 65 | call when data is available. This can speed up your application if receiving 66 | a lot of traffic, but might induce MIDI Thru and treatment latency. 67 | */ 68 | static const bool Use1ByteParsing = true; 69 | 70 | /*! Maximum size of SysEx receivable. Decrease to save RAM if you don't expect 71 | to receive SysEx, or adjust accordingly. 72 | */ 73 | static const unsigned SysExMaxSize = 128; 74 | 75 | /*! Global switch to turn on/off sender ActiveSensing 76 | Set to true to send ActiveSensing 77 | Set to false will not send ActiveSensing message (will also save memory) 78 | */ 79 | static const bool UseSenderActiveSensing = false; 80 | 81 | /*! Global switch to turn on/off receiver ActiveSensing 82 | Set to true to check for message timeouts (via ErrorCallback) 83 | Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory) 84 | */ 85 | static const bool UseReceiverActiveSensing = false; 86 | 87 | /*! Active Sensing is intended to be sent 88 | repeatedly by the sender to tell the receiver that a connection is alive. Use 89 | of this message is optional. When initially received, the 90 | receiver will expect to receive another Active Sensing 91 | message each 300ms (max), and if it does not then it will 92 | assume that the connection has been terminated. At 93 | termination, the receiver will turn off all voices and return to 94 | normal (non- active sensing) operation. 95 | 96 | Typical value is 250 (ms) - an Active Sensing command is send every 250ms. 97 | (All Roland devices send Active Sensing every 250ms) 98 | 99 | Setting this field to 0 will disable sending MIDI active sensing. 100 | */ 101 | static const uint16_t SenderActiveSensingPeriodicity = 0; 102 | }; 103 | 104 | END_MIDI_NAMESPACE 105 | -------------------------------------------------------------------------------- /src/serialMIDI.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * @file serialMIDI.h 3 | * Project Arduino MIDI Library 4 | * @brief MIDI Library for the Arduino - Platform 5 | * @license MIT - Copyright (c) 2015 Francois Best 6 | * @author lathoub, Francois Best 7 | * @date 22/03/20 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy 10 | * of this software and associated documentation files (the "Software"), to deal 11 | * in the Software without restriction, including without limitation the rights 12 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | * copies of the Software, and to permit persons to whom the Software is 14 | * furnished to do so, subject to the following conditions: 15 | * 16 | * The above copyright notice and this permission notice shall be included in 17 | * all copies or substantial portions of the Software. 18 | * 19 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | * THE SOFTWARE. 26 | */ 27 | #pragma once 28 | 29 | #include "midi_Namespace.h" 30 | 31 | BEGIN_MIDI_NAMESPACE 32 | 33 | struct DefaultSerialSettings 34 | { 35 | /*! Override the default MIDI baudrate to transmit over USB serial, to 36 | a decoding program such as Hairless MIDI (set baudrate to 115200)\n 37 | http://projectgus.github.io/hairless-midiserial/ 38 | */ 39 | static const long BaudRate = 31250; 40 | }; 41 | 42 | template 43 | class SerialMIDI 44 | { 45 | typedef _Settings Settings; 46 | 47 | public: 48 | SerialMIDI(SerialPort& inSerial) 49 | : mSerial(inSerial) 50 | { 51 | }; 52 | 53 | public: 54 | static const bool thruActivated = true; 55 | 56 | void begin() 57 | { 58 | // Initialise the Serial port 59 | #if defined(AVR_CAKE) 60 | mSerial. template open(); 61 | #else 62 | mSerial.begin(Settings::BaudRate); 63 | #endif 64 | } 65 | 66 | void end() 67 | { 68 | mSerial.end(); 69 | } 70 | 71 | bool beginTransmission(MidiType) 72 | { 73 | return true; 74 | }; 75 | 76 | void write(byte value) 77 | { 78 | mSerial.write(value); 79 | }; 80 | 81 | void endTransmission() 82 | { 83 | }; 84 | 85 | byte read() 86 | { 87 | return mSerial.read(); 88 | }; 89 | 90 | unsigned available() 91 | { 92 | return mSerial.available(); 93 | }; 94 | 95 | private: 96 | SerialPort& mSerial; 97 | }; 98 | 99 | END_MIDI_NAMESPACE 100 | 101 | /*! \brief Create an instance of the library attached to a serial port. 102 | You can use HardwareSerial or SoftwareSerial for the serial port. 103 | Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2); 104 | Then call midi2.begin(), midi2.read() etc.. 105 | */ 106 | #define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \ 107 | MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ 108 | MIDI_NAMESPACE::MidiInterface> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); 109 | 110 | #if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__) 111 | // Leonardo, Due and other USB boards use Serial1 by default. 112 | #define MIDI_CREATE_DEFAULT_INSTANCE() \ 113 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI); 114 | #else 115 | /*! \brief Create an instance of the library with default name, serial port 116 | and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib, 117 | or if you don't bother using custom names, serial port or settings. 118 | */ 119 | #define MIDI_CREATE_DEFAULT_INSTANCE() \ 120 | MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI); 121 | #endif 122 | 123 | /*! \brief Create an instance of the library attached to a serial port with 124 | custom settings. 125 | @see DefaultSettings 126 | @see MIDI_CREATE_INSTANCE 127 | */ 128 | #define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \ 129 | MIDI_NAMESPACE::SerialMIDI serial##Name(SerialPort);\ 130 | MIDI_NAMESPACE::MidiInterface, Settings> Name((MIDI_NAMESPACE::SerialMIDI&)serial##Name); 131 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(mocks) 2 | add_subdirectory(unit-tests) 3 | -------------------------------------------------------------------------------- /test/mocks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(test-mocks) 2 | 3 | add_library(test-mocks STATIC 4 | test-mocks.cpp 5 | test-mocks.h 6 | test-mocks_Namespace.h 7 | test-mocks_SerialMock.cpp 8 | test-mocks_SerialMock.hpp 9 | test-mocks_SerialMock.h 10 | ) 11 | 12 | target_link_libraries(test-mocks 13 | midi 14 | ) 15 | -------------------------------------------------------------------------------- /test/mocks/test-mocks.cpp: -------------------------------------------------------------------------------- 1 | #include "test-mocks.h" 2 | 3 | BEGIN_TEST_MOCKS_NAMESPACE 4 | 5 | END_TEST_MOCKS_NAMESPACE 6 | -------------------------------------------------------------------------------- /test/mocks/test-mocks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test-mocks_Namespace.h" 4 | 5 | BEGIN_TEST_MOCKS_NAMESPACE 6 | 7 | END_TEST_MOCKS_NAMESPACE 8 | -------------------------------------------------------------------------------- /test/mocks/test-mocks_Namespace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define TEST_MOCKS_NAMESPACE test_mocks 4 | #define BEGIN_TEST_MOCKS_NAMESPACE namespace TEST_MOCKS_NAMESPACE { 5 | #define END_TEST_MOCKS_NAMESPACE } 6 | 7 | #define USING_NAMESPACE_TEST_MOCKS using namespace TEST_MOCKS_NAMESPACE; 8 | 9 | BEGIN_TEST_MOCKS_NAMESPACE 10 | 11 | END_TEST_MOCKS_NAMESPACE 12 | -------------------------------------------------------------------------------- /test/mocks/test-mocks_SerialMock.cpp: -------------------------------------------------------------------------------- 1 | #include "test-mocks_SerialMock.h" 2 | 3 | BEGIN_TEST_MOCKS_NAMESPACE 4 | 5 | END_TEST_MOCKS_NAMESPACE 6 | -------------------------------------------------------------------------------- /test/mocks/test-mocks_SerialMock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "test-mocks.h" 4 | #include 5 | 6 | BEGIN_TEST_MOCKS_NAMESPACE 7 | 8 | template 9 | class RingBuffer 10 | { 11 | public: 12 | RingBuffer(); 13 | ~RingBuffer(); 14 | 15 | public: 16 | int getLength() const; 17 | bool isEmpty() const; 18 | 19 | public: 20 | void write(DataType inData); 21 | void write(const DataType* inData, int inSize); 22 | void clear(); 23 | 24 | public: 25 | DataType peek() const; 26 | DataType read(); 27 | void read(DataType* outData, int inSize); 28 | 29 | private: 30 | DataType mData[Size]; 31 | DataType* mWriteHead; 32 | DataType* mReadHead; 33 | }; 34 | 35 | template 36 | class SerialMock 37 | { 38 | public: 39 | SerialMock(); 40 | ~SerialMock(); 41 | 42 | public: // Arduino Serial API 43 | void begin(int inBaudrate); 44 | int available() const; 45 | void write(uint8_t inData); 46 | uint8_t read(); 47 | 48 | public: // Test Helpers API 49 | void moveTxToRx(); // Simulate loopback 50 | 51 | public: 52 | typedef RingBuffer Buffer; 53 | Buffer mTxBuffer; 54 | Buffer mRxBuffer; 55 | int mBaudrate; 56 | }; 57 | 58 | END_TEST_MOCKS_NAMESPACE 59 | 60 | #include "test-mocks_SerialMock.hpp" 61 | -------------------------------------------------------------------------------- /test/mocks/test-mocks_SerialMock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | BEGIN_TEST_MOCKS_NAMESPACE 4 | 5 | template 6 | RingBuffer::RingBuffer() 7 | : mWriteHead(mData) 8 | , mReadHead(mData) 9 | { 10 | memset(mData, DataType(0), Size * sizeof(DataType)); 11 | } 12 | 13 | template 14 | RingBuffer::~RingBuffer() 15 | { 16 | } 17 | 18 | // ----------------------------------------------------------------------------- 19 | 20 | template 21 | int RingBuffer::getLength() const 22 | { 23 | if (mReadHead == mWriteHead) 24 | { 25 | return 0; 26 | } 27 | else if (mWriteHead > mReadHead) 28 | { 29 | return int(mWriteHead - mReadHead); 30 | } 31 | else 32 | { 33 | return int(mWriteHead - mData) + Size - int(mReadHead - mData); 34 | } 35 | } 36 | 37 | template 38 | bool RingBuffer::isEmpty() const 39 | { 40 | return mReadHead == mWriteHead; 41 | } 42 | 43 | // ----------------------------------------------------------------------------- 44 | 45 | template 46 | void RingBuffer::write(DataType inData) 47 | { 48 | *mWriteHead++ = inData; 49 | if (mWriteHead >= mData + Size) 50 | { 51 | mWriteHead = mData; 52 | } 53 | } 54 | 55 | template 56 | void RingBuffer::write(const DataType* inData, int inSize) 57 | { 58 | for (int i = 0; i < inSize; ++i) 59 | { 60 | write(inData[i]); 61 | } 62 | } 63 | 64 | template 65 | void RingBuffer::clear() 66 | { 67 | memset(mData, DataType(0), Size * sizeof(DataType)); 68 | mReadHead = mData; 69 | mWriteHead = mData; 70 | } 71 | 72 | // ----------------------------------------------------------------------------- 73 | 74 | template 75 | DataType RingBuffer::peek() const 76 | { 77 | return *mReadHead; 78 | } 79 | 80 | template 81 | DataType RingBuffer::read() 82 | { 83 | const DataType data = *mReadHead++; 84 | if (mReadHead >= mData + Size) 85 | { 86 | mReadHead = mData; 87 | } 88 | return data; 89 | } 90 | 91 | template 92 | void RingBuffer::read(DataType* outData, int inSize) 93 | { 94 | for (int i = 0; i < inSize; ++i) 95 | { 96 | outData[i] = read(); 97 | } 98 | } 99 | 100 | // ============================================================================= 101 | 102 | template 103 | SerialMock::SerialMock() 104 | { 105 | } 106 | 107 | template 108 | SerialMock::~SerialMock() 109 | { 110 | } 111 | 112 | // ----------------------------------------------------------------------------- 113 | 114 | template 115 | void SerialMock::begin(int inBaudrate) 116 | { 117 | mBaudrate = inBaudrate; 118 | mTxBuffer.clear(); 119 | mRxBuffer.clear(); 120 | } 121 | 122 | template 123 | int SerialMock::available() const 124 | { 125 | return mRxBuffer.getLength(); 126 | } 127 | 128 | template 129 | void SerialMock::write(uint8_t inData) 130 | { 131 | mTxBuffer.write(inData); 132 | } 133 | 134 | template 135 | uint8_t SerialMock::read() 136 | { 137 | return mRxBuffer.read(); 138 | } 139 | 140 | // ----------------------------------------------------------------------------- 141 | 142 | template 143 | void SerialMock::moveTxToRx() 144 | { 145 | mRxBuffer.clear(); 146 | const int size = mTxBuffer.getSize(); 147 | for (int i = 0; i < size; ++i) 148 | { 149 | mRxBuffer.write(mTxBuffer.read()); 150 | } 151 | } 152 | 153 | END_TEST_MOCKS_NAMESPACE 154 | -------------------------------------------------------------------------------- /test/unit-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(CMakeToolsHelpers OPTIONAL) 2 | 3 | project(unit-tests) 4 | 5 | include_directories( 6 | "${unit-tests_SOURCE_DIR}" 7 | "${gtest_SOURCE_DIR}/include" 8 | "${gmock_SOURCE_DIR}/include" 9 | ) 10 | 11 | add_executable(unit-tests 12 | unit-tests.cpp 13 | unit-tests.h 14 | unit-tests_Namespace.h 15 | 16 | tests/unit-tests_MidiMessage.cpp 17 | tests/unit-tests_Settings.cpp 18 | tests/unit-tests_Settings.h 19 | tests/unit-tests_SysExCodec.cpp 20 | tests/unit-tests_MidiInput.cpp 21 | tests/unit-tests_MidiInputCallbacks.cpp 22 | tests/unit-tests_MidiOutput.cpp 23 | tests/unit-tests_MidiThru.cpp 24 | ) 25 | 26 | target_link_libraries(unit-tests 27 | gtest 28 | gmock 29 | midi 30 | test-mocks 31 | ) 32 | 33 | add_test(unit-tests ${unit-tests_BINARY_DIR}/unit-tests --gtest_color=yes) 34 | add_custom_target(build-and-run-unit-tests 35 | COMMAND ${CMAKE_CTEST_COMMAND} -V 36 | DEPENDS unit-tests 37 | ) 38 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_MidiInputCallbacks.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests.h" 2 | #include "unit-tests_Settings.h" 3 | #include 4 | #include 5 | 6 | BEGIN_MIDI_NAMESPACE 7 | 8 | END_MIDI_NAMESPACE 9 | 10 | // ----------------------------------------------------------------------------- 11 | 12 | BEGIN_UNNAMED_NAMESPACE 13 | 14 | using namespace testing; 15 | USING_NAMESPACE_UNIT_TESTS 16 | 17 | template 18 | struct VariableSysExSettings : midi::DefaultSettings 19 | { 20 | static const unsigned SysExMaxSize = Size; 21 | }; 22 | 23 | typedef test_mocks::SerialMock<256> SerialMock; 24 | typedef midi::SerialMIDI Transport; 25 | 26 | typedef VariableSysExSettings<256> Settings; 27 | typedef midi::MidiInterface MidiInterface; 28 | 29 | MidiInterface* midi; 30 | 31 | class MidiInputCallbacks : public Test 32 | { 33 | public: 34 | MidiInputCallbacks() 35 | : mTransport(mSerial) 36 | , mMidi(mTransport) 37 | { 38 | } 39 | virtual ~MidiInputCallbacks() 40 | { 41 | } 42 | 43 | protected: 44 | virtual void SetUp() 45 | { 46 | midi = &mMidi; 47 | } 48 | 49 | virtual void TearDown() 50 | { 51 | midi = nullptr; 52 | } 53 | 54 | protected: 55 | SerialMock mSerial; 56 | Transport mTransport; 57 | MidiInterface mMidi; 58 | }; 59 | 60 | // -- 61 | 62 | void handleNoteOn(byte inChannel, byte inPitch, byte inVelocity) 63 | { 64 | EXPECT_NE(midi, nullptr); 65 | midi->sendNoteOn(inPitch, inVelocity, inChannel); 66 | } 67 | 68 | TEST_F(MidiInputCallbacks, noteOn) 69 | { 70 | mMidi.setHandleNoteOn(handleNoteOn); 71 | mMidi.begin(MIDI_CHANNEL_OMNI); 72 | mMidi.turnThruOff(); 73 | 74 | static const unsigned rxSize = 3; 75 | static const byte rxData[rxSize] = { 0x9b, 12, 34 }; 76 | mSerial.mRxBuffer.write(rxData, rxSize); 77 | 78 | EXPECT_EQ(mMidi.read(), false); 79 | EXPECT_EQ(mMidi.read(), false); 80 | EXPECT_EQ(mMidi.read(), true); 81 | EXPECT_EQ(mMidi.getType(), midi::NoteOn); 82 | EXPECT_EQ(mMidi.getChannel(), 12); 83 | EXPECT_EQ(mMidi.getData1(), 12); 84 | EXPECT_EQ(mMidi.getData2(), 34); 85 | 86 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 87 | byte buffer[3] = { 0 }; 88 | mSerial.mTxBuffer.read(buffer, 3); 89 | EXPECT_THAT(buffer, ContainerEq(rxData)); 90 | 91 | // Test null velocity note on 92 | EXPECT_EQ(MidiInterface::Settings::HandleNullVelocityNoteOnAsNoteOff, true); 93 | mSerial.mRxBuffer.write(0x9b); 94 | mSerial.mRxBuffer.write(12); 95 | mSerial.mRxBuffer.write(0); 96 | 97 | EXPECT_EQ(mMidi.read(), false); 98 | EXPECT_EQ(mMidi.read(), false); 99 | EXPECT_EQ(mMidi.read(), true); 100 | EXPECT_EQ(mMidi.getType(), midi::NoteOff); 101 | EXPECT_EQ(mMidi.getChannel(), 12); 102 | EXPECT_EQ(mMidi.getData1(), 12); 103 | EXPECT_EQ(mMidi.getData2(), 0); 104 | 105 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 0); 106 | } 107 | 108 | // -- 109 | 110 | void handleNoteOff(byte inChannel, byte inPitch, byte inVelocity) 111 | { 112 | EXPECT_NE(midi, nullptr); 113 | midi->sendNoteOff(inPitch, inVelocity, inChannel); 114 | } 115 | 116 | TEST_F(MidiInputCallbacks, noteOff) 117 | { 118 | mMidi.setHandleNoteOff(handleNoteOff); 119 | mMidi.begin(MIDI_CHANNEL_OMNI); 120 | mMidi.turnThruOff(); 121 | 122 | static const unsigned rxSize = 3; 123 | static const byte rxData[rxSize] = { 0x8b, 12, 34 }; 124 | mSerial.mRxBuffer.write(rxData, rxSize); 125 | 126 | EXPECT_EQ(mMidi.read(), false); 127 | EXPECT_EQ(mMidi.read(), false); 128 | EXPECT_EQ(mMidi.read(), true); 129 | EXPECT_EQ(mMidi.getType(), midi::NoteOff); 130 | EXPECT_EQ(mMidi.getChannel(), 12); 131 | EXPECT_EQ(mMidi.getData1(), 12); 132 | EXPECT_EQ(mMidi.getData2(), 34); 133 | 134 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 135 | byte buffer[3] = { 0 }; 136 | mSerial.mTxBuffer.read(buffer, 3); 137 | EXPECT_THAT(buffer, ContainerEq(rxData)); 138 | 139 | // Test null velocity note on 140 | EXPECT_EQ(MidiInterface::Settings::HandleNullVelocityNoteOnAsNoteOff, true); 141 | mSerial.mRxBuffer.write(0x9b); 142 | mSerial.mRxBuffer.write(12); 143 | mSerial.mRxBuffer.write(0); 144 | 145 | EXPECT_EQ(mMidi.read(), false); 146 | EXPECT_EQ(mMidi.read(), false); 147 | EXPECT_EQ(mMidi.read(), true); 148 | EXPECT_EQ(mMidi.getType(), midi::NoteOff); 149 | EXPECT_EQ(mMidi.getChannel(), 12); 150 | EXPECT_EQ(mMidi.getData1(), 12); 151 | EXPECT_EQ(mMidi.getData2(), 0); 152 | 153 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 154 | mSerial.mTxBuffer.read(buffer, 3); 155 | EXPECT_THAT(buffer, ElementsAreArray({ 156 | 0x8b, 12, 0 157 | })); 158 | } 159 | 160 | // -- 161 | 162 | 163 | 164 | void handleAfterTouchPoly(byte inChannel, byte inNote, byte inValue) 165 | { 166 | EXPECT_NE(midi, nullptr); 167 | midi->sendAfterTouch(inNote, inValue, inChannel); 168 | } 169 | 170 | TEST_F(MidiInputCallbacks, afterTouchPoly) 171 | { 172 | mMidi.setHandleAfterTouchPoly(handleAfterTouchPoly); 173 | mMidi.begin(MIDI_CHANNEL_OMNI); 174 | mMidi.turnThruOff(); 175 | 176 | static const unsigned rxSize = 3; 177 | static const byte rxData[rxSize] = { 0xab, 12, 34 }; 178 | mSerial.mRxBuffer.write(rxData, rxSize); 179 | 180 | EXPECT_EQ(mMidi.read(), false); 181 | EXPECT_EQ(mMidi.read(), false); 182 | EXPECT_EQ(mMidi.read(), true); 183 | EXPECT_EQ(mMidi.getType(), midi::AfterTouchPoly); 184 | EXPECT_EQ(mMidi.getChannel(), 12); 185 | EXPECT_EQ(mMidi.getData1(), 12); 186 | EXPECT_EQ(mMidi.getData2(), 34); 187 | 188 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 189 | byte buffer[3] = { 0 }; 190 | mSerial.mTxBuffer.read(buffer, 3); 191 | EXPECT_THAT(buffer, ContainerEq(rxData)); 192 | } 193 | 194 | // -- 195 | 196 | void handleControlChange(byte inChannel, byte inNumber, byte inValue) 197 | { 198 | EXPECT_NE(midi, nullptr); 199 | midi->sendControlChange(inNumber, inValue, inChannel); 200 | } 201 | 202 | TEST_F(MidiInputCallbacks, controlChange) 203 | { 204 | mMidi.setHandleControlChange(handleControlChange); 205 | mMidi.begin(MIDI_CHANNEL_OMNI); 206 | mMidi.turnThruOff(); 207 | 208 | static const unsigned rxSize = 3; 209 | static const byte rxData[rxSize] = { 0xbb, 12, 34 }; 210 | mSerial.mRxBuffer.write(rxData, rxSize); 211 | 212 | EXPECT_EQ(mMidi.read(), false); 213 | EXPECT_EQ(mMidi.read(), false); 214 | EXPECT_EQ(mMidi.read(), true); 215 | EXPECT_EQ(mMidi.getType(), midi::ControlChange); 216 | EXPECT_EQ(mMidi.getChannel(), 12); 217 | EXPECT_EQ(mMidi.getData1(), 12); 218 | EXPECT_EQ(mMidi.getData2(), 34); 219 | 220 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 221 | byte buffer[3] = { 0 }; 222 | mSerial.mTxBuffer.read(buffer, 3); 223 | EXPECT_THAT(buffer, ContainerEq(rxData)); 224 | } 225 | 226 | // -- 227 | 228 | void handleProgramChange(byte inChannel, byte inNumber) 229 | { 230 | EXPECT_NE(midi, nullptr); 231 | midi->sendProgramChange(inNumber, inChannel); 232 | } 233 | 234 | TEST_F(MidiInputCallbacks, programChange) 235 | { 236 | mMidi.setHandleProgramChange(handleProgramChange); 237 | mMidi.begin(MIDI_CHANNEL_OMNI); 238 | mMidi.turnThruOff(); 239 | 240 | static const unsigned rxSize = 2; 241 | static const byte rxData[rxSize] = { 0xcb, 12 }; 242 | mSerial.mRxBuffer.write(rxData, rxSize); 243 | 244 | EXPECT_EQ(mMidi.read(), false); 245 | EXPECT_EQ(mMidi.read(), true); 246 | EXPECT_EQ(mMidi.getType(), midi::ProgramChange); 247 | EXPECT_EQ(mMidi.getChannel(), 12); 248 | EXPECT_EQ(mMidi.getData1(), 12); 249 | EXPECT_EQ(mMidi.getData2(), 0); 250 | 251 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2); 252 | byte buffer[2] = { 0 }; 253 | mSerial.mTxBuffer.read(buffer, 2); 254 | EXPECT_THAT(buffer, ContainerEq(rxData)); 255 | } 256 | 257 | // -- 258 | 259 | void handleAfterTouchChannel(byte inChannel, byte inPressure) 260 | { 261 | EXPECT_NE(midi, nullptr); 262 | midi->sendAfterTouch(inPressure, inChannel); 263 | } 264 | 265 | 266 | TEST_F(MidiInputCallbacks, afterTouchChannel) 267 | { 268 | mMidi.setHandleAfterTouchChannel(handleAfterTouchChannel); 269 | mMidi.begin(MIDI_CHANNEL_OMNI); 270 | mMidi.turnThruOff(); 271 | 272 | static const unsigned rxSize = 2; 273 | static const byte rxData[rxSize] = { 0xdb, 12 }; 274 | mSerial.mRxBuffer.write(rxData, rxSize); 275 | 276 | EXPECT_EQ(mMidi.read(), false); 277 | EXPECT_EQ(mMidi.read(), true); 278 | EXPECT_EQ(mMidi.getType(), midi::AfterTouchChannel); 279 | EXPECT_EQ(mMidi.getChannel(), 12); 280 | EXPECT_EQ(mMidi.getData1(), 12); 281 | EXPECT_EQ(mMidi.getData2(), 0); 282 | 283 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2); 284 | byte buffer[2] = { 0 }; 285 | mSerial.mTxBuffer.read(buffer, 2); 286 | EXPECT_THAT(buffer, ContainerEq(rxData)); 287 | } 288 | 289 | // -- 290 | 291 | void handlePitchBend(byte inChannel, int inValue) 292 | { 293 | EXPECT_NE(midi, nullptr); 294 | midi->sendPitchBend(inValue, inChannel); 295 | } 296 | 297 | TEST_F(MidiInputCallbacks, pitchBend) 298 | { 299 | mMidi.setHandlePitchBend(handlePitchBend); 300 | mMidi.begin(MIDI_CHANNEL_OMNI); 301 | mMidi.turnThruOff(); 302 | 303 | static const unsigned rxSize = 3; 304 | static const byte rxData[rxSize] = { 0xeb, 12, 34 }; 305 | mSerial.mRxBuffer.write(rxData, rxSize); 306 | 307 | EXPECT_EQ(mMidi.read(), false); 308 | EXPECT_EQ(mMidi.read(), false); 309 | EXPECT_EQ(mMidi.read(), true); 310 | EXPECT_EQ(mMidi.getType(), midi::PitchBend); 311 | EXPECT_EQ(mMidi.getChannel(), 12); 312 | EXPECT_EQ(mMidi.getData1(), 12); 313 | EXPECT_EQ(mMidi.getData2(), 34); 314 | 315 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 316 | byte buffer[3] = { 0 }; 317 | mSerial.mTxBuffer.read(buffer, 3); 318 | EXPECT_THAT(buffer, ContainerEq(rxData)); 319 | } 320 | 321 | // -- 322 | 323 | void handleSysEx(byte* inData, unsigned inSize) 324 | { 325 | EXPECT_NE(midi, nullptr); 326 | midi->sendSysEx(inSize, inData, true); 327 | } 328 | 329 | TEST_F(MidiInputCallbacks, sysEx) 330 | { 331 | mMidi.setHandleSystemExclusive(handleSysEx); 332 | mMidi.begin(MIDI_CHANNEL_OMNI); 333 | mMidi.turnThruOff(); 334 | 335 | static const unsigned rxSize = 15; 336 | static const byte rxData[rxSize] = { 337 | 0xf0, 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 0xf7 338 | }; 339 | mSerial.mRxBuffer.write(rxData, rxSize); 340 | 341 | for (unsigned i = 0; i < rxSize - 1; ++i) 342 | { 343 | EXPECT_EQ(mMidi.read(), false); 344 | } 345 | EXPECT_EQ(mMidi.read(), true); 346 | EXPECT_EQ(mMidi.getType(), midi::SystemExclusive); 347 | EXPECT_EQ(mMidi.getChannel(), 0); 348 | EXPECT_EQ(mMidi.getSysExArrayLength(), rxSize); 349 | 350 | EXPECT_EQ(unsigned(mSerial.mTxBuffer.getLength()), rxSize); 351 | const std::vector sysExData(mMidi.getSysExArray(), 352 | mMidi.getSysExArray() + rxSize); 353 | EXPECT_THAT(sysExData, ElementsAreArray(rxData)); 354 | } 355 | 356 | TEST_F(MidiInputCallbacks, sysExLong) 357 | { 358 | mMidi.setHandleSystemExclusive(handleSysEx); 359 | mMidi.begin(MIDI_CHANNEL_OMNI); 360 | mMidi.turnThruOff(); 361 | 362 | static const unsigned rxSize = 210; 363 | static const byte rxData[rxSize] = { 364 | 0xf0, 365 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 366 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 367 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 368 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 369 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 370 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 371 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 372 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 373 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 374 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 375 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 376 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 377 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 378 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 379 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 380 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 381 | 0xf7 382 | }; 383 | mSerial.mRxBuffer.write(rxData, rxSize); 384 | 385 | for (unsigned i = 0; i < rxSize - 1; ++i) 386 | { 387 | EXPECT_EQ(mMidi.read(), false); 388 | } 389 | EXPECT_EQ(mMidi.read(), true); 390 | EXPECT_EQ(mMidi.getType(), midi::SystemExclusive); 391 | EXPECT_EQ(mMidi.getChannel(), 0); 392 | EXPECT_EQ(mMidi.getSysExArrayLength(), rxSize); 393 | 394 | EXPECT_EQ(unsigned(mSerial.mTxBuffer.getLength()), rxSize); 395 | const std::vector sysExData(mMidi.getSysExArray(), 396 | mMidi.getSysExArray() + rxSize); 397 | EXPECT_THAT(sysExData, ElementsAreArray(rxData)); 398 | } 399 | 400 | // -- 401 | 402 | void handleMtcQuarterFrame(byte inData) 403 | { 404 | EXPECT_NE(midi, nullptr); 405 | midi->sendTimeCodeQuarterFrame(inData); 406 | } 407 | 408 | TEST_F(MidiInputCallbacks, mtcQuarterFrame) 409 | { 410 | mMidi.setHandleTimeCodeQuarterFrame(handleMtcQuarterFrame); 411 | mMidi.begin(MIDI_CHANNEL_OMNI); 412 | mMidi.turnThruOff(); 413 | 414 | static const unsigned rxSize = 2; 415 | static const byte rxData[rxSize] = { 0xf1, 12 }; 416 | mSerial.mRxBuffer.write(rxData, rxSize); 417 | 418 | EXPECT_EQ(mMidi.read(), false); 419 | EXPECT_EQ(mMidi.read(), true); 420 | EXPECT_EQ(mMidi.getType(), midi::TimeCodeQuarterFrame); 421 | EXPECT_EQ(mMidi.getChannel(), 0); 422 | EXPECT_EQ(mMidi.getData1(), 12); 423 | EXPECT_EQ(mMidi.getData2(), 0); 424 | 425 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2); 426 | byte buffer[2] = { 0 }; 427 | mSerial.mTxBuffer.read(buffer, 2); 428 | EXPECT_THAT(buffer, ContainerEq(rxData)); 429 | } 430 | 431 | // -- 432 | 433 | void handleSongPosition(unsigned inBeats) 434 | { 435 | EXPECT_NE(midi, nullptr); 436 | midi->sendSongPosition(inBeats); 437 | } 438 | 439 | TEST_F(MidiInputCallbacks, songPosition) 440 | { 441 | mMidi.setHandleSongPosition(handleSongPosition); 442 | mMidi.begin(MIDI_CHANNEL_OMNI); 443 | mMidi.turnThruOff(); 444 | 445 | static const unsigned rxSize = 3; 446 | static const byte rxData[rxSize] = { 0xf2, 12, 34 }; 447 | mSerial.mRxBuffer.write(rxData, rxSize); 448 | 449 | EXPECT_EQ(mMidi.read(), false); 450 | EXPECT_EQ(mMidi.read(), false); 451 | EXPECT_EQ(mMidi.read(), true); 452 | EXPECT_EQ(mMidi.getType(), midi::SongPosition); 453 | EXPECT_EQ(mMidi.getChannel(), 0); 454 | EXPECT_EQ(mMidi.getData1(), 12); 455 | EXPECT_EQ(mMidi.getData2(), 34); 456 | 457 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3); 458 | byte buffer[3] = { 0 }; 459 | mSerial.mTxBuffer.read(buffer, 3); 460 | EXPECT_THAT(buffer, ContainerEq(rxData)); 461 | } 462 | 463 | // -- 464 | 465 | void handleSongSelect(byte inSongNumber) 466 | { 467 | EXPECT_NE(midi, nullptr); 468 | midi->sendSongSelect(inSongNumber); 469 | } 470 | 471 | TEST_F(MidiInputCallbacks, songSelect) 472 | { 473 | mMidi.setHandleSongSelect(handleSongSelect); 474 | mMidi.begin(MIDI_CHANNEL_OMNI); 475 | mMidi.turnThruOff(); 476 | 477 | static const unsigned rxSize = 2; 478 | static const byte rxData[rxSize] = { 0xf3, 12 }; 479 | mSerial.mRxBuffer.write(rxData, rxSize); 480 | 481 | EXPECT_EQ(mMidi.read(), false); 482 | EXPECT_EQ(mMidi.read(), true); 483 | EXPECT_EQ(mMidi.getType(), midi::SongSelect); 484 | EXPECT_EQ(mMidi.getChannel(), 0); 485 | EXPECT_EQ(mMidi.getData1(), 12); 486 | EXPECT_EQ(mMidi.getData2(), 0); 487 | 488 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2); 489 | byte buffer[2] = { 0 }; 490 | mSerial.mTxBuffer.read(buffer, 2); 491 | EXPECT_THAT(buffer, ContainerEq(rxData)); 492 | } 493 | 494 | // -- 495 | 496 | void handleTuneRequest() 497 | { 498 | EXPECT_NE(midi, nullptr); 499 | midi->sendTuneRequest(); 500 | } 501 | 502 | TEST_F(MidiInputCallbacks, tuneRequest) 503 | { 504 | mMidi.setHandleTuneRequest(handleTuneRequest); 505 | mMidi.begin(MIDI_CHANNEL_OMNI); 506 | mMidi.turnThruOff(); 507 | 508 | mSerial.mRxBuffer.write(0xf6); 509 | 510 | EXPECT_EQ(mMidi.read(), true); 511 | EXPECT_EQ(mMidi.getType(), midi::TuneRequest); 512 | EXPECT_EQ(mMidi.getChannel(), 0); 513 | EXPECT_EQ(mMidi.getData1(), 0); 514 | EXPECT_EQ(mMidi.getData2(), 0); 515 | 516 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 1); 517 | EXPECT_EQ(mSerial.mTxBuffer.read(), 0xf6); 518 | } 519 | 520 | // -- 521 | 522 | void handleClock() 523 | { 524 | EXPECT_NE(midi, nullptr); 525 | midi->sendRealTime(midi::Clock); 526 | } 527 | void handleStart() 528 | { 529 | EXPECT_NE(midi, nullptr); 530 | midi->sendRealTime(midi::Start); 531 | } 532 | void handleContinue() 533 | { 534 | EXPECT_NE(midi, nullptr); 535 | midi->sendRealTime(midi::Continue); 536 | } 537 | void handleStop() 538 | { 539 | EXPECT_NE(midi, nullptr); 540 | midi->sendRealTime(midi::Stop); 541 | } 542 | void handleActiveSensing() 543 | { 544 | EXPECT_NE(midi, nullptr); 545 | midi->sendRealTime(midi::ActiveSensing); 546 | } 547 | void handleSystemReset() 548 | { 549 | EXPECT_NE(midi, nullptr); 550 | midi->sendRealTime(midi::SystemReset); 551 | } 552 | 553 | TEST_F(MidiInputCallbacks, realTime) 554 | { 555 | mMidi.setHandleClock(handleClock); 556 | mMidi.setHandleStart(handleStart); 557 | mMidi.setHandleContinue(handleContinue); 558 | mMidi.setHandleStop(handleStop); 559 | mMidi.setHandleActiveSensing(handleActiveSensing); 560 | mMidi.setHandleSystemReset(handleSystemReset); 561 | 562 | mMidi.begin(MIDI_CHANNEL_OMNI); 563 | mMidi.turnThruOff(); 564 | 565 | static const unsigned rxSize = 6; 566 | static const byte rxData[rxSize] = { 567 | 0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff 568 | }; 569 | mSerial.mRxBuffer.write(rxData, rxSize); 570 | static const midi::MidiType types[rxSize] = { 571 | midi::Clock, 572 | midi::Start, 573 | midi::Continue, 574 | midi::Stop, 575 | midi::ActiveSensing, 576 | midi::SystemReset, 577 | }; 578 | 579 | for (unsigned i = 0; i < rxSize; ++i) 580 | { 581 | EXPECT_EQ(mMidi.read(), true); 582 | EXPECT_EQ(mMidi.getType(), types[i]); 583 | EXPECT_EQ(mMidi.getChannel(), 0); 584 | EXPECT_EQ(mMidi.getData1(), 0); 585 | EXPECT_EQ(mMidi.getData2(), 0); 586 | EXPECT_EQ(mSerial.mTxBuffer.getLength(), 1); 587 | 588 | const byte read = mSerial.mTxBuffer.read(); 589 | EXPECT_EQ(read, rxData[i]); 590 | EXPECT_EQ(read, types[i]); 591 | } 592 | } 593 | 594 | END_UNNAMED_NAMESPACE 595 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_MidiMessage.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests.h" 2 | #include 3 | 4 | BEGIN_MIDI_NAMESPACE 5 | 6 | // Declare references: 7 | // http://stackoverflow.com/questions/4891067/weird-undefined-symbols-of-static-constants-inside-a-struct-class 8 | 9 | template 10 | const unsigned Message::sSysExMaxSize; 11 | 12 | END_MIDI_NAMESPACE 13 | 14 | // ----------------------------------------------------------------------------- 15 | 16 | BEGIN_UNNAMED_NAMESPACE 17 | 18 | TEST(MidiMessage, hasTheRightProperties) 19 | { 20 | typedef midi::Message<42> Message; 21 | const Message message = Message(); 22 | EXPECT_EQ(message.channel, 0); 23 | EXPECT_EQ(message.type, 0); 24 | EXPECT_EQ(message.data1, 0); 25 | EXPECT_EQ(message.data2, 0); 26 | EXPECT_EQ(message.valid, false); 27 | EXPECT_EQ(message.getSysExSize(), unsigned(0)); 28 | } 29 | 30 | template 31 | inline void setSysExSize(Message& ioMessage, unsigned inSize) 32 | { 33 | ioMessage.data2 = inSize >> 8; // MSB 34 | ioMessage.data1 = inSize & 0xff; // LSB 35 | } 36 | 37 | TEST(MidiMessage, getSysExSize) 38 | { 39 | // Small message 40 | { 41 | typedef midi::Message<32> Message; 42 | ASSERT_EQ(Message::sSysExMaxSize, unsigned(32)); 43 | Message message = Message(); 44 | 45 | const unsigned sizeUnder = 20; 46 | setSysExSize(message, sizeUnder); 47 | ASSERT_EQ(message.getSysExSize(), sizeUnder); 48 | 49 | const unsigned sizeOver = 64; 50 | setSysExSize(message, sizeOver); 51 | ASSERT_EQ(message.getSysExSize(), unsigned(32)); 52 | } 53 | // Medium message 54 | { 55 | typedef midi::Message<256> Message; 56 | ASSERT_EQ(Message::sSysExMaxSize, unsigned(256)); 57 | Message message = Message(); 58 | 59 | const unsigned sizeUnder = 200; 60 | setSysExSize(message, sizeUnder); 61 | ASSERT_EQ(message.getSysExSize(), sizeUnder); 62 | 63 | const unsigned sizeOver = 300; 64 | setSysExSize(message, sizeOver); 65 | ASSERT_EQ(message.getSysExSize(), unsigned(256)); 66 | } 67 | // Large message 68 | { 69 | typedef midi::Message<1024> Message; 70 | ASSERT_EQ(Message::sSysExMaxSize, unsigned(1024)); 71 | Message message = Message(); 72 | 73 | const unsigned sizeUnder = 1000; 74 | setSysExSize(message, sizeUnder); 75 | ASSERT_EQ(message.getSysExSize(), sizeUnder); 76 | 77 | const unsigned sizeOver = 2000; 78 | setSysExSize(message, sizeOver); 79 | ASSERT_EQ(message.getSysExSize(), unsigned(1024)); 80 | } 81 | } 82 | 83 | END_UNNAMED_NAMESPACE 84 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_MidiOutput.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests.h" 2 | #include "unit-tests_Settings.h" 3 | #include 4 | #include 5 | 6 | BEGIN_MIDI_NAMESPACE 7 | 8 | END_MIDI_NAMESPACE 9 | 10 | // ----------------------------------------------------------------------------- 11 | 12 | BEGIN_UNNAMED_NAMESPACE 13 | 14 | using namespace testing; 15 | USING_NAMESPACE_UNIT_TESTS; 16 | 17 | typedef test_mocks::SerialMock<32> SerialMock; 18 | typedef midi::SerialMIDI Transport; 19 | typedef midi::MidiInterface MidiInterface; 20 | 21 | typedef std::vector Buffer; 22 | 23 | // -- 24 | 25 | TEST(MidiOutput, sendInvalid) 26 | { 27 | SerialMock serial; 28 | Transport transport(serial); 29 | MidiInterface midi((Transport&)transport); 30 | 31 | midi.begin(); 32 | midi.send(midi::NoteOn, 42, 42, 42); // Invalid channel > OFF 33 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 34 | 35 | midi.send(midi::InvalidType, 0, 0, 12); // Invalid type 36 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 37 | 38 | midi.send(midi::NoteOn, 12, 42, MIDI_CHANNEL_OMNI); // OMNI not allowed 39 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 40 | } 41 | 42 | TEST(MidiOutput, sendGenericSingle) 43 | { 44 | SerialMock serial; 45 | Transport transport(serial); 46 | MidiInterface midi((Transport&)transport); 47 | 48 | Buffer buffer; 49 | buffer.resize(3); 50 | 51 | midi.begin(); 52 | midi.send(midi::NoteOn, 47, 42, 12); 53 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 54 | serial.mTxBuffer.read(&buffer[0], 3); 55 | EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42})); 56 | } 57 | 58 | TEST(MidiOutput, sendGenericWithRunningStatus) 59 | { 60 | typedef VariableSettings Settings; 61 | typedef midi::MidiInterface RsMidiInterface; 62 | 63 | SerialMock serial; 64 | Transport transport(serial); 65 | RsMidiInterface midi((Transport&)transport); 66 | 67 | Buffer buffer; 68 | buffer.resize(5); 69 | 70 | midi.begin(); 71 | EXPECT_EQ(RsMidiInterface::Settings::UseRunningStatus, true); 72 | EXPECT_EQ(serial.mTxBuffer.isEmpty(), true); 73 | midi.send(midi::NoteOn, 47, 42, 12); 74 | midi.send(midi::NoteOn, 42, 47, 12); 75 | EXPECT_EQ(serial.mTxBuffer.getLength(), 5); 76 | serial.mTxBuffer.read(&buffer[0], 5); 77 | EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 42, 47})); 78 | } 79 | 80 | TEST(MidiOutput, sendGenericWithoutRunningStatus) 81 | { 82 | typedef VariableSettings Settings; // No running status 83 | typedef midi::MidiInterface NoRsMidiInterface; 84 | 85 | SerialMock serial; 86 | Transport transport(serial); 87 | NoRsMidiInterface midi((Transport&)transport); 88 | 89 | Buffer buffer; 90 | buffer.resize(6); 91 | 92 | // Same status byte 93 | midi.begin(); 94 | EXPECT_EQ(MidiInterface::Settings::UseRunningStatus, false); 95 | EXPECT_EQ(serial.mTxBuffer.isEmpty(), true); 96 | midi.send(midi::NoteOn, 47, 42, 12); 97 | midi.send(midi::NoteOn, 42, 47, 12); 98 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 99 | serial.mTxBuffer.read(&buffer[0], 6); 100 | EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 0x9b, 42, 47})); 101 | 102 | // Different status byte 103 | midi.begin(); 104 | midi.send(midi::NoteOn, 47, 42, 12); 105 | midi.send(midi::NoteOff, 47, 42, 12); 106 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 107 | serial.mTxBuffer.read(&buffer[0], 6); 108 | EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 0x8b, 47, 42})); 109 | } 110 | 111 | TEST(MidiOutput, sendGenericBreakingRunningStatus) 112 | { 113 | SerialMock serial; 114 | Transport transport(serial); 115 | MidiInterface midi((Transport&)transport); 116 | 117 | Buffer buffer; 118 | buffer.resize(6); 119 | 120 | midi.begin(); 121 | midi.send(midi::NoteOn, 47, 42, 12); 122 | midi.send(midi::NoteOff, 47, 42, 12); 123 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 124 | serial.mTxBuffer.read(&buffer[0], 6); 125 | EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 0x8b, 47, 42})); 126 | } 127 | 128 | TEST(MidiOutput, sendGenericRealTimeShortcut) 129 | { 130 | SerialMock serial; 131 | Transport transport(serial); 132 | MidiInterface midi((Transport&)transport); 133 | 134 | Buffer buffer; 135 | buffer.resize(6); 136 | 137 | midi.begin(); 138 | midi.send(midi::Clock, 47, 42, 12); 139 | midi.send(midi::Start, 47, 42, 12); 140 | midi.send(midi::Continue, 47, 42, 12); 141 | midi.send(midi::Stop, 47, 42, 12); 142 | midi.send(midi::ActiveSensing, 47, 42, 12); 143 | midi.send(midi::SystemReset, 47, 42, 12); 144 | 145 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 146 | serial.mTxBuffer.read(&buffer[0], 6); 147 | EXPECT_THAT(buffer, ElementsAreArray({0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff})); 148 | } 149 | 150 | // -- 151 | 152 | TEST(MidiOutput, sendNoteOn) 153 | { 154 | SerialMock serial; 155 | Transport transport(serial); 156 | MidiInterface midi((Transport&)transport); 157 | 158 | Buffer buffer; 159 | buffer.resize(6); 160 | 161 | midi.begin(); 162 | midi.sendNoteOn(10, 11, 12); 163 | midi.sendNoteOn(12, 13, 4); 164 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 165 | serial.mTxBuffer.read(&buffer[0], 6); 166 | EXPECT_THAT(buffer, ElementsAreArray({0x9b, 10, 11, 0x93, 12, 13})); 167 | } 168 | 169 | TEST(MidiOutput, sendNoteOff) 170 | { 171 | SerialMock serial; 172 | Transport transport(serial); 173 | MidiInterface midi((Transport&)transport); 174 | 175 | Buffer buffer; 176 | buffer.resize(6); 177 | 178 | midi.begin(); 179 | midi.sendNoteOff(10, 11, 12); 180 | midi.sendNoteOff(12, 13, 4); 181 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 182 | serial.mTxBuffer.read(&buffer[0], 6); 183 | EXPECT_THAT(buffer, ElementsAreArray({0x8b, 10, 11, 0x83, 12, 13})); 184 | } 185 | 186 | TEST(MidiOutput, sendProgramChange) 187 | { 188 | SerialMock serial; 189 | Transport transport(serial); 190 | MidiInterface midi((Transport&)transport); 191 | 192 | Buffer buffer; 193 | buffer.resize(4); 194 | 195 | midi.begin(); 196 | midi.sendProgramChange(42, 12); 197 | midi.sendProgramChange(47, 4); 198 | EXPECT_EQ(serial.mTxBuffer.getLength(), 4); 199 | serial.mTxBuffer.read(&buffer[0], 4); 200 | EXPECT_THAT(buffer, ElementsAreArray({0xcb, 42, 0xc3, 47})); 201 | } 202 | 203 | TEST(MidiOutput, sendControlChange) 204 | { 205 | SerialMock serial; 206 | Transport transport(serial); 207 | MidiInterface midi((Transport&)transport); 208 | 209 | Buffer buffer; 210 | buffer.resize(6); 211 | 212 | midi.begin(); 213 | midi.sendControlChange(42, 12, 12); 214 | midi.sendControlChange(47, 12, 4); 215 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 216 | serial.mTxBuffer.read(&buffer[0], 6); 217 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 42, 12, 0xb3, 47, 12})); 218 | } 219 | 220 | TEST(MidiOutput, sendPitchBend) 221 | { 222 | SerialMock serial; 223 | Transport transport(serial); 224 | MidiInterface midi((Transport&)transport); 225 | 226 | Buffer buffer; 227 | 228 | // Int signature - arbitrary values 229 | { 230 | buffer.clear(); 231 | buffer.resize(9); 232 | 233 | midi.begin(); 234 | midi.sendPitchBend(0, 12); 235 | midi.sendPitchBend(100, 4); 236 | midi.sendPitchBend(-100, 7); 237 | EXPECT_EQ(serial.mTxBuffer.getLength(), 9); 238 | serial.mTxBuffer.read(&buffer[0], 9); 239 | EXPECT_THAT(buffer, ElementsAreArray({0xeb, 0x00, 0x40, 240 | 0xe3, 0x64, 0x40, 241 | 0xe6, 0x1c, 0x3f})); 242 | } 243 | // Int signature - min/max 244 | { 245 | buffer.clear(); 246 | buffer.resize(9); 247 | 248 | midi.begin(); 249 | midi.sendPitchBend(0, 12); 250 | midi.sendPitchBend(MIDI_PITCHBEND_MAX, 4); 251 | midi.sendPitchBend(MIDI_PITCHBEND_MIN, 7); 252 | EXPECT_EQ(serial.mTxBuffer.getLength(), 9); 253 | serial.mTxBuffer.read(&buffer[0], 9); 254 | EXPECT_THAT(buffer, ElementsAreArray({0xeb, 0x00, 0x40, 255 | 0xe3, 0x7f, 0x7f, 256 | 0xe6, 0x00, 0x00})); 257 | } 258 | // Float signature 259 | { 260 | buffer.clear(); 261 | buffer.resize(9); 262 | 263 | midi.begin(); 264 | midi.sendPitchBend(0.0, 12); 265 | midi.sendPitchBend(1.0, 4); 266 | midi.sendPitchBend(-1.0, 7); 267 | EXPECT_EQ(serial.mTxBuffer.getLength(), 9); 268 | serial.mTxBuffer.read(&buffer[0], 9); 269 | EXPECT_THAT(buffer, ElementsAreArray({0xeb, 0x00, 0x40, 270 | 0xe3, 0x7f, 0x7f, 271 | 0xe6, 0x00, 0x00})); 272 | } 273 | } 274 | 275 | TEST(MidiOutput, sendPolyPressure) 276 | { 277 | // Note: sendPolyPressure is deprecated in favor of sendAfterTouch, which 278 | // now supports both mono and poly AfterTouch messages. 279 | // This test is kept for coverage until removal of sendPolyPressure. 280 | 281 | SerialMock serial; 282 | Transport transport(serial); 283 | MidiInterface midi((Transport&)transport); 284 | 285 | Buffer buffer; 286 | buffer.resize(6); 287 | 288 | midi.begin(); 289 | midi.sendPolyPressure(42, 12, 12); 290 | midi.sendPolyPressure(47, 12, 4); 291 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 292 | serial.mTxBuffer.read(&buffer[0], 6); 293 | EXPECT_THAT(buffer, ElementsAreArray({0xab, 42, 12, 0xa3, 47, 12})); 294 | } 295 | 296 | TEST(MidiOutput, sendAfterTouchMono) 297 | { 298 | SerialMock serial; 299 | Transport transport(serial); 300 | MidiInterface midi((Transport&)transport); 301 | 302 | Buffer buffer; 303 | buffer.resize(4); 304 | 305 | midi.begin(); 306 | midi.sendAfterTouch(42, 12); 307 | midi.sendAfterTouch(47, 4); 308 | EXPECT_EQ(serial.mTxBuffer.getLength(), 4); 309 | serial.mTxBuffer.read(&buffer[0], 4); 310 | EXPECT_THAT(buffer, ElementsAreArray({0xdb, 42, 0xd3, 47})); 311 | } 312 | 313 | TEST(MidiOutput, sendAfterTouchPoly) 314 | { 315 | SerialMock serial; 316 | Transport transport(serial); 317 | MidiInterface midi((Transport&)transport); 318 | 319 | Buffer buffer; 320 | buffer.resize(6); 321 | 322 | midi.begin(); 323 | midi.sendAfterTouch(42, 12, 12); 324 | midi.sendAfterTouch(47, 12, 4); 325 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 326 | serial.mTxBuffer.read(&buffer[0], 6); 327 | EXPECT_THAT(buffer, ElementsAreArray({0xab, 42, 12, 0xa3, 47, 12})); 328 | } 329 | 330 | TEST(MidiOutput, sendSysEx) 331 | { 332 | typedef test_mocks::SerialMock<1024> LargeSerialMock; 333 | typedef midi::SerialMIDI LargeTransport; 334 | typedef midi::MidiInterface LargeMidiInterface; 335 | 336 | LargeSerialMock serial; 337 | LargeTransport transport(serial); 338 | LargeMidiInterface midi((LargeTransport&)transport); 339 | 340 | Buffer buffer; 341 | 342 | // Short frame 343 | { 344 | static const char* frame = "Hello, World!"; 345 | static const int frameLength = strlen(frame); 346 | static const byte expected[] = { 347 | 0xf0, 348 | 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', 349 | 0xf7, 350 | }; 351 | 352 | buffer.clear(); 353 | buffer.resize(frameLength + 2); 354 | 355 | midi.begin(); 356 | midi.sendSysEx(frameLength, reinterpret_cast(frame), false); 357 | EXPECT_EQ(serial.mTxBuffer.getLength(), frameLength + 2); 358 | serial.mTxBuffer.read(&buffer[0], frameLength + 2); 359 | EXPECT_THAT(buffer, ElementsAreArray(expected)); 360 | } 361 | // Long frame 362 | { 363 | static const char* frame = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin maximus dui a massa maximus, a vestibulum mi venenatis. Cras sit amet ex id velit suscipit pharetra eget a turpis. Phasellus interdum metus ac sagittis cursus. Nam quis est at nisl ullamcorper egestas pulvinar eu erat. Duis a elit dignissim, vestibulum eros vel, tempus nisl. Aenean turpis nunc, cursus vel lacinia non, pharetra eget sapien. Duis condimentum, lacus at pulvinar tempor, leo libero volutpat nisl, eget porttitor lorem mi sed magna. Duis dictum, massa vel euismod interdum, lorem mi egestas elit, hendrerit tincidunt est arcu a libero. Interdum et malesuada fames ac ante ipsum primis in faucibus. Curabitur vehicula magna libero, at rhoncus sem ornare a. In elementum, elit et congue pulvinar, massa velit commodo velit, non elementum purus ligula eget lacus. Donec efficitur nisi eu ultrices efficitur. Donec neque dui, ullamcorper id molestie quis, consequat sit amet ligula."; 364 | static const int frameLength = strlen(frame); 365 | static const byte expected[] = { 366 | 0xf0, 367 | 'L','o','r','e','m',' ','i','p','s','u','m',' ','d','o','l','o','r',' ','s','i','t',' ','a','m','e','t',',',' ', 368 | 'c','o','n','s','e','c','t','e','t','u','r',' ','a','d','i','p','i','s','c','i','n','g',' ','e','l','i','t','.',' ','P','r','o','i','n',' ','m','a','x','i','m','u','s',' ','d','u','i',' ','a',' ','m','a','s','s','a',' ','m','a','x','i','m','u','s',',',' ', 369 | 'a',' ','v','e','s','t','i','b','u','l','u','m',' ','m','i',' ','v','e','n','e','n','a','t','i','s','.',' ','C','r','a','s',' ','s','i','t',' ','a','m','e','t',' ','e','x',' ','i','d',' ','v','e','l','i','t',' ','s','u','s','c','i','p','i','t',' ','p','h','a','r','e','t','r','a',' ','e','g','e','t', ' ','a',' ','t','u','r','p','i','s','.',' ','P','h','a','s','e','l','l','u','s',' ','i','n','t','e','r','d','u','m',' ','m','e','t','u','s',' ','a','c',' ','s','a','g','i','t','t','i','s',' ','c','u','r','s','u','s','.',' ','N','a','m',' ','q','u','i','s',' ','e','s','t',' ','a','t',' ','n','i','s', 'l',' ','u','l','l','a','m','c','o','r','p','e','r',' ','e','g','e','s','t','a','s',' ','p','u','l','v','i','n','a','r',' ','e','u',' ','e','r','a','t','.',' ','D','u','i','s',' ','a',' ','e','l','i','t',' ','d','i','g','n','i','s','s','i','m',',',' ', 370 | 'v','e','s','t','i','b','u','l','u','m',' ','e','r','o','s',' ','v','e','l',',',' ', 371 | 't','e','m','p','u','s',' ','n','i','s','l','.',' ','A','e','n','e','a','n',' ','t','u','r','p','i','s',' ','n','u','n','c',',',' ', 372 | 'c','u','r','s','u','s',' ','v','e','l',' ','l','a','c','i','n','i','a',' ','n','o','n',',',' ', 373 | 'p','h','a','r','e','t','r','a',' ','e','g','e','t',' ','s','a','p','i','e','n','.',' ','D','u','i','s',' ','c','o','n','d','i','m','e','n','t','u','m',',',' ', 374 | 'l','a','c','u','s',' ','a','t',' ','p','u','l','v','i','n','a','r',' ','t','e','m','p','o','r',',',' ', 375 | 'l','e','o',' ','l','i','b','e','r','o',' ','v','o','l','u','t','p','a','t',' ','n','i','s','l',',',' ', 376 | 'e','g','e','t',' ','p','o','r','t','t','i','t','o','r',' ','l','o','r','e','m',' ','m','i',' ','s','e','d',' ','m','a','g','n','a','.',' ','D','u','i','s',' ','d','i','c','t','u','m',',',' ', 377 | 'm','a','s','s','a',' ','v','e','l',' ','e','u','i','s','m','o','d',' ','i','n','t','e','r','d','u','m',',',' ', 378 | 'l','o','r','e','m',' ','m','i',' ','e','g','e','s','t','a','s',' ','e','l','i','t',',',' ', 379 | 'h','e','n','d','r','e','r','i','t',' ','t','i','n','c','i','d','u','n','t',' ','e','s','t',' ','a','r','c','u',' ','a',' ','l','i','b','e','r','o','.',' ','I','n','t','e','r','d','u','m',' ','e','t',' ','m','a','l','e','s','u','a','d','a',' ','f','a','m','e','s',' ','a','c',' ','a','n','t','e',' ', 'i','p','s','u','m',' ','p','r','i','m','i','s',' ','i','n',' ','f','a','u','c','i','b','u','s','.',' ','C','u','r','a','b','i','t','u','r',' ','v','e','h','i','c','u','l','a',' ','m','a','g','n','a',' ','l','i','b','e','r','o',',',' ', 380 | 'a','t',' ','r','h','o','n','c','u','s',' ','s','e','m',' ','o','r','n','a','r','e',' ','a','.',' ','I','n',' ','e','l','e','m','e','n','t','u','m',',',' ', 381 | 'e','l','i','t',' ','e','t',' ','c','o','n','g','u','e',' ','p','u','l','v','i','n','a','r',',',' ', 382 | 'm','a','s','s','a',' ','v','e','l','i','t',' ','c','o','m','m','o','d','o',' ','v','e','l','i','t',',',' ', 383 | 'n','o','n',' ','e','l','e','m','e','n','t','u','m',' ','p','u','r','u','s',' ','l','i','g','u','l','a',' ','e','g','e','t',' ','l','a','c','u','s','.',' ','D','o','n','e','c',' ','e','f','f','i','c','i','t','u','r',' ','n','i','s','i',' ','e','u',' ','u','l','t','r','i','c','e','s',' ','e','f','f', 'i','c','i','t','u','r','.',' ','D','o','n','e','c',' ','n','e','q','u','e',' ','d','u','i',',',' ', 384 | 'u','l','l','a','m','c','o','r','p','e','r',' ','i','d',' ','m','o','l','e','s','t','i','e',' ','q','u','i','s',',',' ', 385 | 'c','o','n','s','e','q','u','a','t',' ','s','i','t',' ','a','m','e','t',' ','l','i','g','u','l','a','.', 386 | 0xf7, 387 | }; 388 | 389 | buffer.clear(); 390 | buffer.resize(frameLength + 2); 391 | 392 | midi.begin(); 393 | midi.sendSysEx(frameLength, reinterpret_cast(frame), false); 394 | EXPECT_EQ(serial.mTxBuffer.getLength(), frameLength + 2); 395 | serial.mTxBuffer.read(&buffer[0], frameLength + 2); 396 | EXPECT_THAT(buffer, ElementsAreArray(expected)); 397 | } 398 | // With boundaries included 399 | { 400 | static const byte frame[] = { 401 | 0xf0, 12, 17, 42, 47, 0xf7 402 | }; 403 | 404 | buffer.clear(); 405 | buffer.resize(6); 406 | 407 | midi.begin(); 408 | midi.sendSysEx(6, frame, true); 409 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 410 | serial.mTxBuffer.read(&buffer[0], 6); 411 | EXPECT_THAT(buffer, ElementsAreArray(frame)); 412 | } 413 | } 414 | 415 | TEST(MidiOutput, sendTimeCodeQuarterFrame) 416 | { 417 | SerialMock serial; 418 | Transport transport(serial); 419 | MidiInterface midi((Transport&)transport); 420 | 421 | Buffer buffer; 422 | 423 | // Separate Nibbles 424 | { 425 | buffer.clear(); 426 | buffer.resize(4); 427 | 428 | midi.begin(); 429 | midi.sendTimeCodeQuarterFrame(0x05, 0x0a); 430 | midi.sendTimeCodeQuarterFrame(0xff, 0xff); 431 | EXPECT_EQ(serial.mTxBuffer.getLength(), 4); 432 | serial.mTxBuffer.read(&buffer[0], 4); 433 | EXPECT_THAT(buffer, ElementsAreArray({0xf1, 0x5a, 434 | 0xf1, 0x7f})); 435 | } 436 | // Pre-encoded nibbles 437 | { 438 | buffer.clear(); 439 | buffer.resize(4); 440 | 441 | midi.begin(); 442 | midi.sendTimeCodeQuarterFrame(12); 443 | midi.sendTimeCodeQuarterFrame(42); 444 | EXPECT_EQ(serial.mTxBuffer.getLength(), 4); 445 | serial.mTxBuffer.read(&buffer[0], 4); 446 | EXPECT_THAT(buffer, ElementsAreArray({0xf1, 0x0c, 447 | 0xf1, 0x2a})); 448 | } 449 | } 450 | 451 | TEST(MidiOutput, sendSongPosition) 452 | { 453 | SerialMock serial; 454 | Transport transport(serial); 455 | MidiInterface midi((Transport&)transport); 456 | 457 | Buffer buffer; 458 | buffer.resize(6); 459 | 460 | midi.begin(); 461 | midi.sendSongPosition(1234); 462 | midi.sendSongPosition(4321); 463 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 464 | serial.mTxBuffer.read(&buffer[0], 6); 465 | EXPECT_THAT(buffer, ElementsAreArray({0xf2, 0x52, 0x09, 466 | 0xf2, 0x61, 0x21})); 467 | } 468 | 469 | TEST(MidiOutput, sendSongSelect) 470 | { 471 | SerialMock serial; 472 | Transport transport(serial); 473 | MidiInterface midi((Transport&)transport); 474 | 475 | Buffer buffer; 476 | buffer.resize(4); 477 | 478 | midi.begin(); 479 | midi.sendSongSelect(12); 480 | midi.sendSongSelect(42); 481 | EXPECT_EQ(serial.mTxBuffer.getLength(), 4); 482 | serial.mTxBuffer.read(&buffer[0], 4); 483 | EXPECT_THAT(buffer, ElementsAreArray({0xf3, 12, 0xf3, 42})); 484 | } 485 | 486 | TEST(MidiOutput, sendTuneRequest) 487 | { 488 | SerialMock serial; 489 | Transport transport(serial); 490 | MidiInterface midi((Transport&)transport); 491 | 492 | Buffer buffer; 493 | buffer.resize(1); 494 | 495 | midi.begin(); 496 | midi.sendTuneRequest(); 497 | EXPECT_EQ(serial.mTxBuffer.getLength(), 1); 498 | serial.mTxBuffer.read(&buffer[0], 1); 499 | EXPECT_THAT(buffer, ElementsAreArray({0xf6})); 500 | } 501 | 502 | TEST(MidiOutput, sendRealTime) 503 | { 504 | SerialMock serial; 505 | Transport transport(serial); 506 | MidiInterface midi((Transport&)transport); 507 | 508 | Buffer buffer; 509 | 510 | // Test valid RealTime messages 511 | { 512 | buffer.clear(); 513 | buffer.resize(6); 514 | 515 | midi.begin(); 516 | midi.sendRealTime(midi::Clock); 517 | midi.sendRealTime(midi::Start); 518 | midi.sendRealTime(midi::Continue); 519 | midi.sendRealTime(midi::Stop); 520 | midi.sendRealTime(midi::ActiveSensing); 521 | midi.sendRealTime(midi::SystemReset); 522 | 523 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 524 | serial.mTxBuffer.read(&buffer[0], 6); 525 | EXPECT_THAT(buffer, ElementsAreArray({ 526 | 0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff 527 | })); 528 | } 529 | // Test invalid messages 530 | { 531 | midi.begin(); 532 | midi.sendRealTime(midi::InvalidType); 533 | midi.sendRealTime(midi::NoteOff); 534 | midi.sendRealTime(midi::NoteOn); 535 | midi.sendRealTime(midi::AfterTouchPoly); 536 | midi.sendRealTime(midi::ControlChange); 537 | midi.sendRealTime(midi::ProgramChange); 538 | midi.sendRealTime(midi::AfterTouchChannel); 539 | midi.sendRealTime(midi::PitchBend); 540 | midi.sendRealTime(midi::SystemExclusive); 541 | midi.sendRealTime(midi::TimeCodeQuarterFrame); 542 | midi.sendRealTime(midi::SongPosition); 543 | midi.sendRealTime(midi::SongSelect); 544 | midi.sendRealTime(midi::TuneRequest); 545 | 546 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 547 | } 548 | } 549 | 550 | TEST(MidiOutput, RPN) 551 | { 552 | typedef VariableSettings Settings; 553 | typedef midi::MidiInterface RsMidiInterface; 554 | 555 | SerialMock serial; 556 | Transport transport(serial); 557 | RsMidiInterface midi((Transport&)transport); 558 | 559 | Buffer buffer; 560 | 561 | // 14-bit Value Single Frame 562 | { 563 | buffer.clear(); 564 | buffer.resize(13); 565 | 566 | midi.begin(); 567 | midi.beginRpn(1242, 12); 568 | midi.sendRpnValue(12345, 12); 569 | midi.endRpn(12); 570 | 571 | EXPECT_EQ(serial.mTxBuffer.getLength(), 13); 572 | serial.mTxBuffer.read(&buffer[0], 13); 573 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 574 | 0x64, 0x5a, 575 | 0x65, 0x09, 576 | 0x06, 0x60, 577 | 0x26, 0x39, 578 | 0x64, 0x7f, 579 | 0x65, 0x7f})); 580 | } 581 | // MSB/LSB Single Frame 582 | { 583 | buffer.clear(); 584 | buffer.resize(13); 585 | 586 | midi.begin(); 587 | midi.beginRpn(1242, 12); 588 | midi.sendRpnValue(12, 42, 12); 589 | midi.endRpn(12); 590 | 591 | EXPECT_EQ(serial.mTxBuffer.getLength(), 13); 592 | serial.mTxBuffer.read(&buffer[0], 13); 593 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 594 | 0x64, 0x5a, 595 | 0x65, 0x09, 596 | 0x06, 0x0c, 597 | 0x26, 0x2a, 598 | 0x64, 0x7f, 599 | 0x65, 0x7f})); 600 | } 601 | // Increment Single Frame 602 | { 603 | buffer.clear(); 604 | buffer.resize(11); 605 | 606 | midi.begin(); 607 | midi.beginRpn(1242, 12); 608 | midi.sendRpnIncrement(42, 12); 609 | midi.endRpn(12); 610 | 611 | EXPECT_EQ(serial.mTxBuffer.getLength(), 11); 612 | serial.mTxBuffer.read(&buffer[0], 11); 613 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 614 | 0x64, 0x5a, 615 | 0x65, 0x09, 616 | 0x60, 0x2a, 617 | 0x64, 0x7f, 618 | 0x65, 0x7f})); 619 | } 620 | // Decrement Single Frame 621 | { 622 | buffer.clear(); 623 | buffer.resize(11); 624 | 625 | midi.begin(); 626 | midi.beginRpn(1242, 12); 627 | midi.sendRpnDecrement(42, 12); 628 | midi.endRpn(12); 629 | 630 | EXPECT_EQ(serial.mTxBuffer.getLength(), 11); 631 | serial.mTxBuffer.read(&buffer[0], 11); 632 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 633 | 0x64, 0x5a, 634 | 0x65, 0x09, 635 | 0x61, 0x2a, 636 | 0x64, 0x7f, 637 | 0x65, 0x7f})); 638 | } 639 | // Multi Frame 640 | { 641 | buffer.clear(); 642 | buffer.resize(21); 643 | 644 | midi.begin(); 645 | midi.beginRpn(1242, 12); 646 | midi.sendRpnValue(12345, 12); 647 | midi.sendRpnValue(12, 42, 12); 648 | midi.sendRpnIncrement(42, 12); 649 | midi.sendRpnDecrement(42, 12); 650 | midi.endRpn(12); 651 | 652 | EXPECT_EQ(serial.mTxBuffer.getLength(), 21); 653 | serial.mTxBuffer.read(&buffer[0], 21); 654 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 655 | 0x64, 0x5a, 656 | 0x65, 0x09, 657 | 0x06, 0x60, 658 | 0x26, 0x39, 659 | 0x06, 0x0c, 660 | 0x26, 0x2a, 661 | 0x60, 0x2a, 662 | 0x61, 0x2a, 663 | 0x64, 0x7f, 664 | 0x65, 0x7f})); 665 | } 666 | } 667 | 668 | TEST(MidiOutput, NRPN) 669 | { 670 | typedef VariableSettings Settings; 671 | typedef midi::MidiInterface RsMidiInterface; 672 | 673 | SerialMock serial; 674 | Transport transport(serial); 675 | RsMidiInterface midi((Transport&)transport); 676 | 677 | Buffer buffer; 678 | 679 | // 14-bit Value Single Frame 680 | { 681 | buffer.clear(); 682 | buffer.resize(13); 683 | 684 | midi.begin(); 685 | midi.beginNrpn(1242, 12); 686 | midi.sendNrpnValue(12345, 12); 687 | midi.endNrpn(12); 688 | 689 | EXPECT_EQ(serial.mTxBuffer.getLength(), 13); 690 | serial.mTxBuffer.read(&buffer[0], 13); 691 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 692 | 0x62, 0x5a, 693 | 0x63, 0x09, 694 | 0x06, 0x60, 695 | 0x26, 0x39, 696 | 0x62, 0x7f, 697 | 0x63, 0x7f})); 698 | } 699 | // MSB/LSB Single Frame 700 | { 701 | buffer.clear(); 702 | buffer.resize(13); 703 | 704 | midi.begin(); 705 | midi.beginNrpn(1242, 12); 706 | midi.sendNrpnValue(12, 42, 12); 707 | midi.endNrpn(12); 708 | 709 | EXPECT_EQ(serial.mTxBuffer.getLength(), 13); 710 | serial.mTxBuffer.read(&buffer[0], 13); 711 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 712 | 0x62, 0x5a, 713 | 0x63, 0x09, 714 | 0x06, 0x0c, 715 | 0x26, 0x2a, 716 | 0x62, 0x7f, 717 | 0x63, 0x7f})); 718 | } 719 | // Increment Single Frame 720 | { 721 | buffer.clear(); 722 | buffer.resize(11); 723 | 724 | midi.begin(); 725 | midi.beginNrpn(1242, 12); 726 | midi.sendNrpnIncrement(42, 12); 727 | midi.endNrpn(12); 728 | 729 | EXPECT_EQ(serial.mTxBuffer.getLength(), 11); 730 | serial.mTxBuffer.read(&buffer[0], 11); 731 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 732 | 0x62, 0x5a, 733 | 0x63, 0x09, 734 | 0x60, 0x2a, 735 | 0x62, 0x7f, 736 | 0x63, 0x7f})); 737 | } 738 | // Decrement Single Frame 739 | { 740 | buffer.clear(); 741 | buffer.resize(11); 742 | 743 | midi.begin(); 744 | midi.beginNrpn(1242, 12); 745 | midi.sendNrpnDecrement(42, 12); 746 | midi.endNrpn(12); 747 | 748 | EXPECT_EQ(serial.mTxBuffer.getLength(), 11); 749 | serial.mTxBuffer.read(&buffer[0], 11); 750 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 751 | 0x62, 0x5a, 752 | 0x63, 0x09, 753 | 0x61, 0x2a, 754 | 0x62, 0x7f, 755 | 0x63, 0x7f})); 756 | } 757 | // Multi Frame 758 | { 759 | buffer.clear(); 760 | buffer.resize(21); 761 | 762 | midi.begin(); 763 | midi.beginNrpn(1242, 12); 764 | midi.sendNrpnValue(12345, 12); 765 | midi.sendNrpnValue(12, 42, 12); 766 | midi.sendNrpnIncrement(42, 12); 767 | midi.sendNrpnDecrement(42, 12); 768 | midi.endNrpn(12); 769 | 770 | EXPECT_EQ(serial.mTxBuffer.getLength(), 21); 771 | serial.mTxBuffer.read(&buffer[0], 21); 772 | EXPECT_THAT(buffer, ElementsAreArray({0xbb, 773 | 0x62, 0x5a, 774 | 0x63, 0x09, 775 | 0x06, 0x60, 776 | 0x26, 0x39, 777 | 0x06, 0x0c, 778 | 0x26, 0x2a, 779 | 0x60, 0x2a, 780 | 0x61, 0x2a, 781 | 0x62, 0x7f, 782 | 0x63, 0x7f})); 783 | } 784 | } 785 | 786 | TEST(MidiOutput, runningStatusCancellation) 787 | { 788 | typedef VariableSettings Settings; 789 | typedef midi::MidiInterface RsMidiInterface; 790 | 791 | SerialMock serial; 792 | Transport transport(serial); 793 | RsMidiInterface midi((Transport&)transport); 794 | 795 | Buffer buffer; 796 | 797 | static const unsigned sysExLength = 13; 798 | static const byte sysEx[sysExLength] = { 799 | 'H','e','l','l','o',',',' ','W','o','r','l','d','!' 800 | }; 801 | 802 | midi.begin(); 803 | 804 | midi.sendNoteOn(12, 34, 1); 805 | midi.sendNoteOn(56, 78, 1); 806 | EXPECT_EQ(serial.mTxBuffer.getLength(), 5); 807 | 808 | buffer.clear(); 809 | buffer.resize(5); 810 | serial.mTxBuffer.read(&buffer[0], 5); 811 | EXPECT_THAT(buffer, ElementsAreArray({ 812 | 0x90, 12, 34, 56, 78 813 | })); 814 | 815 | midi.sendRealTime(midi::Clock); // Should not reset running status. 816 | midi.sendNoteOn(12, 34, 1); 817 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 818 | buffer.clear(); 819 | buffer.resize(3); 820 | serial.mTxBuffer.read(&buffer[0], 3); 821 | EXPECT_THAT(buffer, ElementsAreArray({ 822 | 0xf8, 12, 34 823 | })); 824 | 825 | midi.sendSysEx(sysExLength, sysEx); // Should reset running status. 826 | midi.sendNoteOn(12, 34, 1); 827 | EXPECT_EQ(serial.mTxBuffer.getLength(), 18); 828 | buffer.clear(); 829 | buffer.resize(18); 830 | serial.mTxBuffer.read(&buffer[0], 18); 831 | { 832 | static const byte expected[] = { 833 | 0xf0, 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 0xf7, 834 | 0x90, 12, 34 835 | }; 836 | EXPECT_THAT(buffer, ElementsAreArray(expected)); 837 | } 838 | 839 | midi.sendTimeCodeQuarterFrame(42); // Should reset running status. 840 | midi.sendNoteOn(12, 34, 1); 841 | EXPECT_EQ(serial.mTxBuffer.getLength(), 5); 842 | buffer.clear(); 843 | buffer.resize(5); 844 | serial.mTxBuffer.read(&buffer[0], 5); 845 | EXPECT_THAT(buffer, ElementsAreArray({ 846 | 0xf1, 42, 847 | 0x90, 12, 34 848 | })); 849 | 850 | midi.sendSongPosition(42); // Should reset running status. 851 | midi.sendNoteOn(12, 34, 1); 852 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 853 | buffer.clear(); 854 | buffer.resize(6); 855 | serial.mTxBuffer.read(&buffer[0], 6); 856 | EXPECT_THAT(buffer, ElementsAreArray({ 857 | 0xf2, 42, 0, 858 | 0x90, 12, 34 859 | })); 860 | 861 | midi.sendSongSelect(42); // Should reset running status. 862 | midi.sendNoteOn(12, 34, 1); 863 | EXPECT_EQ(serial.mTxBuffer.getLength(), 5); 864 | buffer.clear(); 865 | buffer.resize(5); 866 | serial.mTxBuffer.read(&buffer[0], 5); 867 | EXPECT_THAT(buffer, ElementsAreArray({ 868 | 0xf3, 42, 869 | 0x90, 12, 34 870 | })); 871 | 872 | midi.sendTuneRequest(); // Should reset running status. 873 | midi.sendNoteOn(12, 34, 1); 874 | EXPECT_EQ(serial.mTxBuffer.getLength(), 4); 875 | buffer.clear(); 876 | buffer.resize(4); 877 | serial.mTxBuffer.read(&buffer[0], 4); 878 | EXPECT_THAT(buffer, ElementsAreArray({ 879 | 0xf6, 880 | 0x90, 12, 34 881 | })); 882 | } 883 | 884 | END_UNNAMED_NAMESPACE 885 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_MidiThru.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests.h" 2 | #include "unit-tests_Settings.h" 3 | #include 4 | #include 5 | 6 | BEGIN_MIDI_NAMESPACE 7 | 8 | END_MIDI_NAMESPACE 9 | 10 | // ----------------------------------------------------------------------------- 11 | 12 | BEGIN_UNNAMED_NAMESPACE 13 | 14 | using namespace testing; 15 | USING_NAMESPACE_UNIT_TESTS 16 | typedef test_mocks::SerialMock<32> SerialMock; 17 | typedef midi::SerialMIDI Transport; 18 | typedef midi::MidiInterface MidiInterface; 19 | typedef std::vector Buffer; 20 | 21 | template 22 | struct VariableSysExSettings : midi::DefaultSettings 23 | { 24 | static const unsigned SysExMaxSize = Size; 25 | }; 26 | 27 | // ----------------------------------------------------------------------------- 28 | 29 | TEST(MidiThru, defaultValues) 30 | { 31 | SerialMock serial; 32 | Transport transport(serial); 33 | MidiInterface midi((Transport&)transport); 34 | 35 | EXPECT_EQ(midi.getThruState(), true); 36 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); 37 | midi.begin(); // Should not change the state 38 | EXPECT_EQ(midi.getThruState(), true); 39 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); 40 | } 41 | 42 | TEST(MidiThru, beginEnablesThru) 43 | { 44 | SerialMock serial; 45 | Transport transport(serial); 46 | MidiInterface midi((Transport&)transport); 47 | 48 | midi.turnThruOff(); 49 | EXPECT_EQ(midi.getThruState(), false); 50 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off); 51 | midi.begin(); 52 | EXPECT_EQ(midi.getThruState(), true); 53 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); 54 | } 55 | 56 | TEST(MidiThru, setGet) 57 | { 58 | SerialMock serial; 59 | Transport transport(serial); 60 | MidiInterface midi((Transport&)transport); 61 | 62 | midi.turnThruOff(); 63 | EXPECT_EQ(midi.getThruState(), false); 64 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off); 65 | 66 | midi.turnThruOn(); 67 | EXPECT_EQ(midi.getThruState(), true); 68 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); 69 | midi.turnThruOn(midi::Thru::SameChannel); 70 | EXPECT_EQ(midi.getThruState(), true); 71 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::SameChannel); 72 | midi.turnThruOn(midi::Thru::DifferentChannel); 73 | EXPECT_EQ(midi.getThruState(), true); 74 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::DifferentChannel); 75 | 76 | midi.setThruFilterMode(midi::Thru::Full); 77 | EXPECT_EQ(midi.getThruState(), true); 78 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full); 79 | midi.setThruFilterMode(midi::Thru::SameChannel); 80 | EXPECT_EQ(midi.getThruState(), true); 81 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::SameChannel); 82 | midi.setThruFilterMode(midi::Thru::DifferentChannel); 83 | EXPECT_EQ(midi.getThruState(), true); 84 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::DifferentChannel); 85 | midi.setThruFilterMode(midi::Thru::Off); 86 | EXPECT_EQ(midi.getThruState(), false); 87 | EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off); 88 | } 89 | 90 | TEST(MidiThru, off) 91 | { 92 | SerialMock serial; 93 | Transport transport(serial); 94 | MidiInterface midi((Transport&)transport); 95 | 96 | midi.begin(MIDI_CHANNEL_OMNI); 97 | midi.turnThruOff(); 98 | 99 | static const unsigned rxSize = 5; 100 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 }; 101 | serial.mRxBuffer.write(rxData, rxSize); 102 | EXPECT_EQ(midi.read(), false); 103 | EXPECT_EQ(midi.read(), false); 104 | EXPECT_EQ(midi.read(), true); 105 | EXPECT_EQ(midi.read(), false); 106 | EXPECT_EQ(midi.read(), true); 107 | 108 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 109 | } 110 | 111 | TEST(MidiThru, full) 112 | { 113 | SerialMock serial; 114 | Transport transport(serial); 115 | MidiInterface midi((Transport&)transport); 116 | 117 | Buffer buffer; 118 | 119 | midi.begin(MIDI_CHANNEL_OMNI); 120 | midi.setThruFilterMode(midi::Thru::Full); 121 | 122 | static const unsigned rxSize = 6; 123 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; 124 | serial.mRxBuffer.write(rxData, rxSize); 125 | 126 | EXPECT_EQ(midi.read(), false); 127 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 128 | EXPECT_EQ(midi.read(), false); 129 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 130 | EXPECT_EQ(midi.read(), true); 131 | 132 | buffer.clear(); 133 | buffer.resize(3); 134 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 135 | serial.mTxBuffer.read(&buffer[0], 3); 136 | EXPECT_THAT(buffer, ElementsAreArray({ 137 | 0x9b, 12, 34 138 | })); 139 | 140 | EXPECT_EQ(midi.read(), false); 141 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 142 | EXPECT_EQ(midi.read(), false); 143 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 144 | EXPECT_EQ(midi.read(), true); 145 | 146 | buffer.clear(); 147 | buffer.resize(3); 148 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 149 | serial.mTxBuffer.read(&buffer[0], 3); 150 | EXPECT_THAT(buffer, ElementsAreArray({ 151 | 0x9c, 56, 78 152 | })); 153 | } 154 | 155 | TEST(MidiThru, sameChannel) 156 | { 157 | SerialMock serial; 158 | Transport transport(serial); 159 | MidiInterface midi((Transport&)transport); 160 | 161 | Buffer buffer; 162 | 163 | midi.begin(12); 164 | midi.setThruFilterMode(midi::Thru::SameChannel); 165 | 166 | static const unsigned rxSize = 6; 167 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; 168 | serial.mRxBuffer.write(rxData, rxSize); 169 | 170 | EXPECT_EQ(midi.read(), false); 171 | EXPECT_EQ(midi.read(), false); 172 | EXPECT_EQ(midi.read(), true); 173 | EXPECT_EQ(midi.read(), false); 174 | EXPECT_EQ(midi.read(), false); 175 | EXPECT_EQ(midi.read(), false); 176 | 177 | buffer.clear(); 178 | buffer.resize(3); 179 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 180 | serial.mTxBuffer.read(&buffer[0], 3); 181 | EXPECT_THAT(buffer, ElementsAreArray({ 182 | 0x9b, 12, 34 183 | })); 184 | } 185 | 186 | TEST(MidiThru, sameChannelOmni) // Acts like full 187 | { 188 | SerialMock serial; 189 | Transport transport(serial); 190 | MidiInterface midi((Transport&)transport); 191 | 192 | Buffer buffer; 193 | 194 | midi.begin(MIDI_CHANNEL_OMNI); 195 | midi.setThruFilterMode(midi::Thru::SameChannel); 196 | 197 | static const unsigned rxSize = 6; 198 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; 199 | serial.mRxBuffer.write(rxData, rxSize); 200 | 201 | EXPECT_EQ(midi.read(), false); 202 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 203 | EXPECT_EQ(midi.read(), false); 204 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 205 | EXPECT_EQ(midi.read(), true); 206 | 207 | buffer.clear(); 208 | buffer.resize(3); 209 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 210 | serial.mTxBuffer.read(&buffer[0], 3); 211 | EXPECT_THAT(buffer, ElementsAreArray({ 212 | 0x9b, 12, 34 213 | })); 214 | 215 | buffer.clear(); 216 | buffer.resize(3); 217 | EXPECT_EQ(midi.read(), false); 218 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 219 | EXPECT_EQ(midi.read(), false); 220 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 221 | EXPECT_EQ(midi.read(), true); 222 | 223 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); // Not using TX running status 224 | serial.mTxBuffer.read(&buffer[0], 3); 225 | EXPECT_THAT(buffer, ElementsAreArray({ 226 | 0x9c, 56, 78 227 | })); 228 | } 229 | 230 | TEST(MidiThru, differentChannel) 231 | { 232 | SerialMock serial; 233 | Transport transport(serial); 234 | MidiInterface midi((Transport&)transport); 235 | 236 | Buffer buffer; 237 | 238 | midi.begin(12); 239 | midi.setThruFilterMode(midi::Thru::DifferentChannel); 240 | 241 | static const unsigned rxSize = 6; 242 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; 243 | serial.mRxBuffer.write(rxData, rxSize); 244 | 245 | EXPECT_EQ(midi.read(), false); 246 | EXPECT_EQ(midi.read(), false); 247 | EXPECT_EQ(midi.read(), true); 248 | EXPECT_EQ(midi.read(), false); 249 | EXPECT_EQ(midi.read(), false); 250 | EXPECT_EQ(midi.read(), false); 251 | 252 | buffer.clear(); 253 | buffer.resize(3); 254 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 255 | serial.mTxBuffer.read(&buffer[0], 3); 256 | EXPECT_THAT(buffer, ElementsAreArray({ 257 | 0x9c, 56, 78 258 | })); 259 | } 260 | 261 | TEST(MidiThru, differentChannelOmni) // Acts like off 262 | { 263 | SerialMock serial; 264 | Transport transport(serial); 265 | MidiInterface midi((Transport&)transport); 266 | 267 | Buffer buffer; 268 | 269 | midi.begin(MIDI_CHANNEL_OMNI); 270 | midi.setThruFilterMode(midi::Thru::DifferentChannel); 271 | 272 | static const unsigned rxSize = 6; 273 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; 274 | serial.mRxBuffer.write(rxData, rxSize); 275 | 276 | EXPECT_EQ(midi.read(), false); 277 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 278 | EXPECT_EQ(midi.read(), false); 279 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 280 | EXPECT_EQ(midi.read(), true); 281 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 282 | 283 | EXPECT_EQ(midi.read(), false); 284 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 285 | EXPECT_EQ(midi.read(), false); 286 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 287 | EXPECT_EQ(midi.read(), true); 288 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 289 | } 290 | 291 | TEST(MidiThru, multiByteThru) 292 | { 293 | typedef VariableSettings MultiByteParsing; 294 | typedef midi::MidiInterface MultiByteMidiInterface; 295 | 296 | SerialMock serial; 297 | Transport transport(serial); 298 | MultiByteMidiInterface midi((Transport&)transport); 299 | 300 | Buffer buffer; 301 | 302 | midi.begin(MIDI_CHANNEL_OMNI); 303 | midi.setThruFilterMode(midi::Thru::Full); 304 | 305 | static const unsigned rxSize = 6; 306 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 }; 307 | serial.mRxBuffer.write(rxData, rxSize); 308 | 309 | EXPECT_EQ(midi.read(), true); 310 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 311 | EXPECT_EQ(midi.read(), true); 312 | EXPECT_EQ(serial.mTxBuffer.getLength(), 6); 313 | 314 | buffer.clear(); 315 | buffer.resize(6); 316 | serial.mTxBuffer.read(&buffer[0], 6); 317 | EXPECT_THAT(buffer, ElementsAreArray({ 318 | 0x9b, 12, 34, 0x9b, 56, 78 319 | })); 320 | } 321 | 322 | TEST(MidiThru, withTxRunningStatus) 323 | { 324 | typedef VariableSettings Settings; 325 | typedef midi::MidiInterface RsMidiInterface; 326 | 327 | SerialMock serial; 328 | Transport transport(serial); 329 | RsMidiInterface midi((Transport&)transport); 330 | 331 | Buffer buffer; 332 | 333 | midi.begin(MIDI_CHANNEL_OMNI); 334 | midi.setThruFilterMode(midi::Thru::Full); 335 | 336 | static const unsigned rxSize = 5; 337 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 }; 338 | serial.mRxBuffer.write(rxData, rxSize); 339 | 340 | EXPECT_EQ(midi.read(), false); 341 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 342 | EXPECT_EQ(midi.read(), false); 343 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 344 | EXPECT_EQ(midi.read(), true); 345 | 346 | buffer.clear(); 347 | buffer.resize(3); 348 | EXPECT_EQ(serial.mTxBuffer.getLength(), 3); 349 | serial.mTxBuffer.read(&buffer[0], 3); 350 | EXPECT_THAT(buffer, ElementsAreArray({ 351 | 0x9b, 12, 34 352 | })); 353 | 354 | EXPECT_EQ(midi.read(), false); 355 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 356 | EXPECT_EQ(midi.read(), true); 357 | 358 | buffer.clear(); 359 | buffer.resize(2); 360 | EXPECT_EQ(serial.mTxBuffer.getLength(), 2); 361 | serial.mTxBuffer.read(&buffer[0], 2); 362 | EXPECT_THAT(buffer, ElementsAreArray({ 363 | 56, 78 364 | })); 365 | } 366 | 367 | TEST(MidiThru, invalidMode) 368 | { 369 | SerialMock serial; 370 | Transport transport(serial); 371 | MidiInterface midi((Transport&)transport); 372 | 373 | midi.begin(MIDI_CHANNEL_OMNI); 374 | midi.setThruFilterMode(midi::Thru::Mode(42)); 375 | 376 | static const unsigned rxSize = 6; 377 | static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 }; 378 | serial.mRxBuffer.write(rxData, rxSize); 379 | EXPECT_EQ(midi.read(), false); 380 | EXPECT_EQ(midi.read(), false); 381 | EXPECT_EQ(midi.read(), true); 382 | EXPECT_EQ(midi.read(), false); 383 | EXPECT_EQ(midi.read(), false); 384 | EXPECT_EQ(midi.read(), true); 385 | 386 | EXPECT_EQ(serial.mTxBuffer.getLength(), 0); 387 | } 388 | 389 | END_UNNAMED_NAMESPACE 390 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests_Settings.h" 2 | 3 | BEGIN_MIDI_NAMESPACE 4 | 5 | const bool DefaultSettings::UseRunningStatus; 6 | const bool DefaultSettings::HandleNullVelocityNoteOnAsNoteOff; 7 | const bool DefaultSettings::Use1ByteParsing; 8 | const unsigned DefaultSettings::SysExMaxSize; 9 | 10 | END_MIDI_NAMESPACE 11 | 12 | // ----------------------------------------------------------------------------- 13 | 14 | BEGIN_UNNAMED_NAMESPACE 15 | 16 | TEST(Settings, hasTheRightDefaultValues) 17 | { 18 | EXPECT_EQ(midi::DefaultSettings::UseRunningStatus, false); 19 | EXPECT_EQ(midi::DefaultSettings::HandleNullVelocityNoteOnAsNoteOff, true); 20 | EXPECT_EQ(midi::DefaultSettings::Use1ByteParsing, true); 21 | EXPECT_EQ(midi::DefaultSettings::SysExMaxSize, unsigned(128)); 22 | } 23 | 24 | END_UNNAMED_NAMESPACE 25 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "unit-tests.h" 4 | #include 5 | 6 | BEGIN_UNIT_TESTS_NAMESPACE 7 | 8 | template 9 | struct VariableSettings : public midi::DefaultSettings 10 | { 11 | static const bool UseRunningStatus = RunningStatus; 12 | static const bool Use1ByteParsing = OneByteParsing; 13 | }; 14 | 15 | template 16 | const bool VariableSettings::UseRunningStatus; 17 | template 18 | const bool VariableSettings::Use1ByteParsing; 19 | 20 | END_UNIT_TESTS_NAMESPACE 21 | -------------------------------------------------------------------------------- /test/unit-tests/tests/unit-tests_SysExCodec.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests.h" 2 | #include 3 | 4 | BEGIN_MIDI_NAMESPACE 5 | 6 | END_MIDI_NAMESPACE 7 | 8 | // ----------------------------------------------------------------------------- 9 | 10 | BEGIN_UNNAMED_NAMESPACE 11 | 12 | using namespace testing; 13 | 14 | TEST(SysExCodec, EncoderAscii) 15 | { 16 | const byte input[] = "Hello, World!"; 17 | byte buffer[16]; 18 | memset(buffer, 0, 16 * sizeof(byte)); 19 | const unsigned encodedSize = midi::encodeSysEx(input, buffer, 13); 20 | EXPECT_EQ(encodedSize, unsigned(15)); 21 | const byte expected[16] = { 22 | 0, 'H', 'e', 'l', 'l', 'o', ',', ' ', 23 | 0, 'W', 'o', 'r', 'l', 'd', '!', 0, 24 | }; 25 | EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 26 | EXPECT_THAT(buffer, ContainerEq(expected)); 27 | } 28 | 29 | TEST(SysExCodec, EncoderNonAscii) 30 | { 31 | const byte input[] = { 32 | 182, 236, 167, 177, 61, 91, 120, // 01111000 -> 120 33 | 107, 94, 209, 87, 94 // 000100xx -> 16 34 | }; 35 | byte buffer[16]; 36 | memset(buffer, 0, 16 * sizeof(byte)); 37 | const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12); 38 | EXPECT_EQ(encodedSize, unsigned(14)); 39 | const byte expected[16] = { 40 | // MSB Data 41 | 120, 54, 108, 39, 49, 61, 91, 120, 42 | 16, 107, 94, 81, 87, 94, 0, 0, 43 | }; 44 | EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 45 | EXPECT_THAT(buffer, ContainerEq(expected)); 46 | } 47 | 48 | TEST(SysExCodec, EncoderNonAsciiFlipHeader) 49 | { 50 | const byte input[] = { 51 | 182, 236, 167, 177, 61, 91, 120, // 00011111 -> 15 52 | 107, 94, 209, 87, 94 // 0xx00100 -> 4 53 | }; 54 | byte buffer[16]; 55 | memset(buffer, 0, 16 * sizeof(byte)); 56 | const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12, true); 57 | EXPECT_EQ(encodedSize, unsigned(14)); 58 | const byte expected[16] = { 59 | // MSB Data 60 | 15, 54, 108, 39, 49, 61, 91, 120, 61 | 4, 107, 94, 81, 87, 94, 0, 0, 62 | }; 63 | EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 64 | EXPECT_THAT(buffer, ContainerEq(expected)); 65 | } 66 | 67 | // ----------------------------------------------------------------------------- 68 | 69 | TEST(SysExCodec, DecoderAscii) 70 | { 71 | const byte input[] = { 72 | 0, 'H', 'e', 'l', 'l', 'o', ',', ' ', 73 | 0, 'W', 'o', 'r', 'l', 'd', '!', 74 | }; 75 | byte buffer[16]; 76 | memset(buffer, 0, 16 * sizeof(byte)); 77 | const unsigned decodedSize = midi::decodeSysEx(input, buffer, 15); 78 | EXPECT_EQ(decodedSize, unsigned(13)); 79 | const byte expected[16] = { 80 | 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 81 | 'o', 'r', 'l', 'd', '!', 0, 0, 0, 82 | }; 83 | EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127 84 | EXPECT_THAT(buffer, ContainerEq(expected)); 85 | } 86 | 87 | // Non-ASCII content 88 | TEST(SysExCodec, DecoderNonAscii) 89 | { 90 | const byte input[] = { 91 | // MSB Data 92 | 120, 54, 108, 39, 49, 61, 91, 120, 93 | 16, 107, 94, 81, 87, 94, 94 | }; 95 | byte buffer[16]; 96 | memset(buffer, 0, 16 * sizeof(byte)); 97 | const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14); 98 | EXPECT_EQ(encodedSize, unsigned(12)); 99 | const byte expected[16] = { 100 | 182, 236, 167, 177, 61, 91, 120, 101 | 107, 94, 209, 87, 94, 0, 0, 102 | 0, 0, 103 | }; 104 | EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127 105 | EXPECT_THAT(buffer, ContainerEq(expected)); 106 | } 107 | 108 | TEST(SysExCodec, DecoderNonAsciiFlipHeader) 109 | { 110 | const byte input[] = { 111 | // MSB Data 112 | 15, 54, 108, 39, 49, 61, 91, 120, 113 | 4, 107, 94, 81, 87, 94, 114 | }; 115 | byte buffer[16]; 116 | memset(buffer, 0, 16 * sizeof(byte)); 117 | const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14, true); 118 | EXPECT_EQ(encodedSize, unsigned(12)); 119 | const byte expected[16] = { 120 | 182, 236, 167, 177, 61, 91, 120, 121 | 107, 94, 209, 87, 94, 0, 0, 122 | 0, 0, 123 | }; 124 | EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127 125 | EXPECT_THAT(buffer, ContainerEq(expected)); 126 | } 127 | 128 | // ----------------------------------------------------------------------------- 129 | 130 | TEST(SysExCodec, CodecAscii) 131 | { 132 | const byte input[] = "Hello, World!"; 133 | byte buffer1[16]; 134 | byte buffer2[16]; 135 | memset(buffer1, 0, 16 * sizeof(byte)); 136 | memset(buffer2, 0, 16 * sizeof(byte)); 137 | const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 13); 138 | EXPECT_EQ(encodedSize, unsigned(15)); 139 | const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize); 140 | EXPECT_EQ(decodedSize, unsigned(13)); 141 | EXPECT_STREQ(reinterpret_cast(buffer2), 142 | reinterpret_cast(input)); 143 | } 144 | 145 | TEST(SysExCodec, CodecNonAscii) 146 | { 147 | const byte input[] = { 148 | // MSB Data 149 | 182, 236, 167, 177, 61, 91, 120, 150 | 107, 94, 209, 87, 94 151 | }; 152 | byte buffer1[14]; 153 | byte buffer2[12]; 154 | memset(buffer1, 0, 14 * sizeof(byte)); 155 | memset(buffer2, 0, 12 * sizeof(byte)); 156 | const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12); 157 | EXPECT_EQ(encodedSize, unsigned(14)); 158 | const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize); 159 | EXPECT_EQ(decodedSize, unsigned(12)); 160 | EXPECT_THAT(buffer2, ContainerEq(input)); 161 | } 162 | 163 | TEST(SysExCodec, CodecNonAsciiFlipHeader) 164 | { 165 | const byte input[] = { 166 | // MSB Data 167 | 182, 236, 167, 177, 61, 91, 120, 168 | 107, 94, 209, 87, 94 169 | }; 170 | byte buffer1[14]; 171 | byte buffer2[12]; 172 | memset(buffer1, 0, 14 * sizeof(byte)); 173 | memset(buffer2, 0, 12 * sizeof(byte)); 174 | const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12, true); 175 | EXPECT_EQ(encodedSize, unsigned(14)); 176 | const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize, true); 177 | EXPECT_EQ(decodedSize, unsigned(12)); 178 | EXPECT_THAT(buffer2, ContainerEq(input)); 179 | } 180 | 181 | END_UNNAMED_NAMESPACE 182 | -------------------------------------------------------------------------------- /test/unit-tests/unit-tests.cpp: -------------------------------------------------------------------------------- 1 | #include "unit-tests.h" 2 | 3 | int main(int argc, char **argv) { 4 | ::testing::InitGoogleTest(&argc, argv); 5 | return RUN_ALL_TESTS(); 6 | } 7 | -------------------------------------------------------------------------------- /test/unit-tests/unit-tests.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "unit-tests_Namespace.h" 4 | #include 5 | #include 6 | 7 | BEGIN_UNIT_TESTS_NAMESPACE 8 | 9 | END_UNIT_TESTS_NAMESPACE 10 | -------------------------------------------------------------------------------- /test/unit-tests/unit-tests_Namespace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define UNIT_TESTS_NAMESPACE unit_tests 4 | #define BEGIN_UNIT_TESTS_NAMESPACE namespace UNIT_TESTS_NAMESPACE { 5 | #define END_UNIT_TESTS_NAMESPACE } 6 | #define BEGIN_UNNAMED_NAMESPACE namespace { 7 | #define END_UNNAMED_NAMESPACE } 8 | 9 | #define USING_NAMESPACE_UNIT_TESTS using namespace UNIT_TESTS_NAMESPACE; 10 | 11 | BEGIN_UNIT_TESTS_NAMESPACE 12 | 13 | END_UNIT_TESTS_NAMESPACE 14 | --------------------------------------------------------------------------------