Compare commits

..

No commits in common. "master" and "init_gitflow" have entirely different histories.

86 changed files with 2913 additions and 10271 deletions

View File

@ -1,9 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@ -1,66 +0,0 @@
---
name: Bug report
about: Report something that does not work as intended
title: ''
labels: bug
assignees: ''
---
<!--
Before opening an issue, check if your problem already has been solved by looking in:
- The existing issues: https://github.com/FortySevenEffects/arduino_midi_library/issues
- The discussions: https://github.com/FortySevenEffects/arduino_midi_library/discussions
Consider opening a discussion instead of an issue if you need help with your project:
https://github.com/FortySevenEffects/arduino_midi_library/discussions/new
-->
## Context
Please answer a few questions to help us understand your problem better and guide you to a solution:
<!-- Tip: place the letter x in the checkboxes to tick them:
- [ ] Unticked checkbox
- [x] Ticked checkbox
You can also tick them by clicking after you've submitted your issue.
-->
- What board are you using ?
- `example: Arduino Leonardo`
- _Please list any shields or other **relevant** hardware you're using_
- What version of the Arduino IDE are you using ?
- `example: 1.8.5`
- How are you using MIDI ?
- [ ] Hardware Serial (DIN plugs)
- [ ] USB
- [ ] Other (please specify)
- Is your problem related to:
- [ ] MIDI Input (reading messages from other devices)
- [ ] MIDI Output (sending messages to other devices)
- How comfortable are you with code ?
- [ ] Complete beginner
- [ ] I've done basic projects
- [ ] I know my way around C/C++
- [ ] Advanced / professional
## Describe your project and what you expect to happen:
<!--
Example: When I press a switch on my pedalboard, it sends a SysEx message that I'd like to receive on my Arduino.
Note: Attachments (circuit diagrams, code examples) are most welcome and will help us understand your needs better and find a suitable solution for your issue.
-->
## Describe your problem (what does not work):
<!--
Example: I cannot receive SysEx messages coming from my AxeFX 2
-->
## Steps to reproduce
<!--
Please list the steps you took to hit the problem, so we can try and reproduce it.
-->

View File

@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Discussions
url: https://github.com/FortySevenEffects/arduino_midi_library/discussions
about: Not a bug or a feature request ? Discuss your problem, ask for help or show what you've built in Discussions.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: new feature
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,59 +0,0 @@
name: CMake
on:
push:
pull_request:
branches: [ master ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Debug
GENERATE_COVERAGE: true
LCOV_ROOT: ${{github.workspace}}/lcov
jobs:
build:
# The CMake configure and build commands are platform agnostic and should work equally
# well on Windows or Mac. You can convert this to a matrix build if you need
# cross-platform coverage.
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Install lcov
run: |
mkdir -p "$LCOV_ROOT"
wget https://github.com/linux-test-project/lcov/releases/download/v1.15/lcov-1.15.tar.gz --output-document="$LCOV_ROOT/lcov.tar.gz"
tar -xf "$LCOV_ROOT/lcov.tar.gz" --strip-components=1 -C "$LCOV_ROOT"
echo "$LCOV_ROOT/bin" >> $GITHUB_PATH
shell: bash
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILDER_ENABLE_PROFILING=true
- name: Build
# Build your program with the given configuration
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Run Unit Tests
working-directory: ${{github.workspace}}/build
run: ctest --verbose
- name: Generate code coverage report
working-directory: ${{github.workspace}}/build
run: |
lcov --directory . --capture --output-file coverage.info
lcov --remove coverage.info '/usr/*' "${{github.workspace}}/test/*" "${{github.workspace}}/external/*" --output-file coverage.info
lcov --list coverage.info
- uses: coverallsapp/github-action@9ba913c152ae4be1327bfb9085dc806cedb44057
name: Upload code coverage report to Coveralls
with:
path-to-lcov: ${{github.workspace}}/build/coverage.info
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,62 +0,0 @@
name: PlatformIO
on:
push:
branches: [master]
pull_request:
branches: [ master ]
jobs:
platformio:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
example:
- AltPinSerial
- Basic_IO
- Bench
- Callbacks
- Chaining
- DualMerger
- ErrorCallback
- Input
- RPN_NRPN
- SimpleSynth
- CustomBaudRate
board:
- uno
- due
- zero
- leonardo
- micro
- nanoatmega328
- megaatmega2560
- teensy2
- teensy30
- teensy31
- teensylc
steps:
- uses: actions/checkout@v2
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: ${{ runner.os }}-pip-
- name: Cache PlatformIO
uses: actions/cache@v2
with:
path: ~/.platformio
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
- name: Set up Python
uses: actions/setup-python@v2
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
pip install "click!=8.0.2" # See platformio/platformio-core#4078
- name: Run PlatformIO
run: pio ci --lib="." --board="${{matrix.board}}"
env:
PLATFORMIO_CI_SRC: examples/${{ matrix.example }}

10
.gitignore vendored
View File

@ -1,10 +0,0 @@
*.sublime-workspace
*.pyc
logs/
build/
.vscode/.cmaketools.json
src/.DS_Store
examples/.DS_Store
.DS_Store
test/xcode
.development

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "external/google-test"]
path = external/google-test
url = https://github.com/google/googletest.git

View File

@ -1,3 +0,0 @@
{
"board": "arduino:avr:leonardo"
}

View File

@ -1,62 +0,0 @@
{
"configurations": [
{
"name": "Mac",
"includePath": [
"/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include",
"/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino"
],
"browse": {
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": "",
"path": [
"/Applications/Arduino.app/Contents/Java/hardware/tools/avr/include",
"/Applications/Arduino.app/Contents/Java/hardware/arduino/avr/cores/arduino"
]
},
"intelliSenseMode": "clang-x64",
"macFrameworkPath": [
"/System/Library/Frameworks",
"/Library/Frameworks"
],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
},
{
"name": "Linux",
"includePath": [
"/usr/include"
],
"browse": {
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": "",
"path": [
"/usr/include"
]
},
"intelliSenseMode": "clang-x64",
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
},
{
"name": "Win32",
"includePath": [
"c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"
],
"browse": {
"limitSymbolsToIncludedHeaders": true,
"databaseFilename": "",
"path": [
"c:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include"
]
},
"intelliSenseMode": "msvc-x64",
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}

14
.vscode/settings.json vendored
View File

@ -1,14 +0,0 @@
{
"files.associations": {
"cstddef": "cpp",
"ostream": "cpp",
"__locale": "cpp",
"functional": "cpp",
"iterator": "cpp",
"string": "cpp",
"string_view": "cpp",
"vector": "cpp",
"istream": "cpp",
"system_error": "cpp"
}
}

28
.vscode/tasks.json vendored
View File

@ -1,28 +0,0 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Build",
"command": "make",
"args": ["all"],
"options": {
"cwd": "${workspaceRoot}/build"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Run Tests",
"command": "${workspaceRoot}/build/test/unit-tests/unit-tests",
"group": {
"kind": "test",
"isDefault": true
},
"dependsOn": ["Build"]
}
]
}

View File

@ -1,10 +0,0 @@
cmake_minimum_required(VERSION 2.8.7)
project(arduino_midi_library CXX)
add_subdirectory(builder)
setup_builder()
add_subdirectory(external)
add_subdirectory(src)
add_subdirectory(test)

View File

@ -1,32 +0,0 @@
# Contributing Guidelines
First, thanks for your help ! :+1:
## Branches
Please base your Pull Requests off the `master` branch.
## Requirements
Requirements to build and run the unit tests:
- CMake 2.8 or later
- GCC / Clang with C++11 support (GCC 4.8 or higher)
## Setup
Pull Google Test / Google Mock subrepository:
```
$ git submodule init
$ git submodule update
```
Create build directory, run CMake, build and run unit tests:
```
$ mkdir build && cd build
$ cmake ..
$ make
$ ctest --verbose
```

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016 Francois Best
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

142
README.md
View File

@ -1,142 +0,0 @@
# Arduino MIDI Library
[![GitHub release](https://img.shields.io/github/release/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://github.com/FortySevenEffects/arduino_midi_library/releases/latest)
[![License](https://img.shields.io/github/license/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](LICENSE)
[![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)
[![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)
[![Coveralls](https://img.shields.io/coveralls/FortySevenEffects/arduino_midi_library.svg?maxAge=3600)](https://coveralls.io/github/FortySevenEffects/arduino_midi_library)
This library adds MIDI I/O communications to an Arduino board.
### Features
- **New** : MIDI over USB, Bluetooth, IP & AppleMIDI (see [Transports](#other-transport-mechanisms)).
- **New** : Active Sensing support
- Compatible with all Arduino boards (and clones with an AVR processor).
- Simple and fast way to send and receive every kind of MIDI message (including all System messages, SysEx, Clock, etc..).
- OMNI input reading (read all channels).
- Software Thru, with message filtering.
- [Callbacks](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks) to handle input messages more easily.
- Last received message is saved until a new one arrives.
- Configurable: [overridable template-based settings](https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-custom-Settings).
- Create more than one MIDI interface for mergers/splitters applications.
- Use any serial port, hardware or software.
### Getting Started
1. Use the Arduino Library Manager to install the library.
![Type "MIDI I/Os for Arduino" in the Arduino IDE Library Manager](res/library-manager.png)
2. Start coding:
```c++
#include <MIDI.h>
// Create and bind the MIDI interface to the default hardware Serial port
MIDI_CREATE_DEFAULT_INSTANCE();
void setup()
{
MIDI.begin(MIDI_CHANNEL_OMNI); // Listen to all incoming messages
}
void loop()
{
// Send note 42 with velocity 127 on channel 1
MIDI.sendNoteOn(42, 127, 1);
// Read incoming messages
MIDI.read();
}
```
3. Read the [documentation](#documentation) or watch the awesome video tutorials from [Notes & Volts](https://www.youtube.com/playlist?list=PL4_gPbvyebyH2xfPXePHtx8gK5zPBrVkg).
## Documentation
- [Doxygen Extended Documentation](https://fortyseveneffects.github.io/arduino_midi_library/).
- [GitHub wiki](https://github.com/FortySevenEffects/arduino_midi_library/wiki).
## USB Migration (4.x to 5.x)
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.
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.
`4.3.1` code:
```c++
#include <MIDI.h>
#include <midi_UsbTransport.h>
static const unsigned sUsbTransportBufferSize = 16;
typedef midi::UsbTransport<sUsbTransportBufferSize> UsbTransport;
UsbTransport sUsbTransport;
MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI);
// ...
```
now becomes in `5.x`:
```c++
#include <USB-MIDI.h>
USBMIDI_CREATE_DEFAULT_INSTANCE();
// ...
```
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.
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.
[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.
## Other Transport mechanisms
Version 5 of this library, allows for other Transport layers than the
original MIDI 1.0 Electrical Specification (hardware serial).
- [USB-MIDI](https://github.com/lathoub/Arduino-USBMIDI)
- [AppleMIDI or rtpMIDI](https://github.com/lathoub/Arduino-AppleMIDI-Library)
- [ipMIDI](https://github.com/lathoub/Arduino-ipMIDI)
- [BLE-MIDI](https://github.com/lathoub/Arduino-BLE-MIDI)
All these Transport layers use this library for all the underlying MIDI
work, making it easy to switch transport protocols or making transport
protocol bridges.
### Differences between Serial & other transports
- Software Thru is enabled by default on Serial, but not on other transports.
## Contact & Contribution
To report a bug, contribute, discuss on usage, or request support, please [discuss it here](https://github.com/FortySevenEffects/arduino_midi_library/discussions/new).
You can also contact me on Twitter: [@fortysevenfx](https://twitter.com/fortysevenfx).
## Contributors
Special thanks to all who have contributed to this open-source project !
- [@lathoub](https://github.com/lathoub)
- [@jarosz](https://github.com/jarosz)
- [@ivankravets](https://github.com/ivankravets)
- [@insolace](https://github.com/insolace)
- [@softegg](https://github.com/softegg)
- [@per1234](https://github.com/per1234)
- [@LnnrtS](https://github.com/LnnrtS)
- [@DavidMenting](https://github.com/DavidMenting)
- [@Rolel](https://github.com/Rolel)
- [@kant](https://github.com/kant)
- [@paul-emile-element](https://github.com/paul-emile-element)
- [@muxa](https://github.com/muxa)
You want to help ? Check out the [contribution guidelines](./CONTRIBUTING.md).
## License
MIT © 2009 - present [Francois Best](https://francoisbest.com)

4
README.txt Normal file
View File

@ -0,0 +1,4 @@
There is nothing in this branch.
Please checkout the branch dedicated to your target board to get the code.

View File

@ -1,13 +0,0 @@
#### Changelog
- 20/04/2020 : Version 5.0 released. Separation of transports by [@lathoub](https://github.com/lathoub), adds Active Sensing.
- 11/06/2014 : Version 4.2 released. Bug fix for SysEx, overridable template settings.
- 16/04/2014 : Version 4.1 released. Bug fixes regarding running status.
- 13/02/2014 : Version 4.0 released. Moved to GitHub, added multiple instances & software serial support, and a few bug fixes.
- 29/01/2012 : Version 3.2 released. Release notes are [here](http://sourceforge.net/news/?group_id=265194).
- 06/05/2011 : Version 3.1 released. Added [callback](http://playground.arduino.cc/Main/MIDILibraryCallbacks) support.
- 06/03/2011 : Version 3.0 released. Project is now hosted on [SourceForge](http://sourceforge.net/projects/arduinomidilib).
- 14/12/2009 : Version 2.5 released.
- 28/07/2009 : Version 2.0 released.
- 28/03/2009 : Simplified version of MIDI.begin, Fast mode is now on by default.
- 08/03/2009 : Thru method operational. Added some features to enable thru.

View File

@ -1,33 +0,0 @@
option(BUILDER_ENABLE_PROFILING OFF)
macro(setup_builder)
enable_testing()
set(ROOT_SOURCE_DIR ${PROJECT_SOURCE_DIR} CACHE INTERNAL "Repository root directory")
set(ROOT_BINARY_DIR "${ROOT_SOURCE_DIR}/build")
include_directories(${ROOT_SOURCE_DIR})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
-Wall \
-W \
-Wshadow \
-Wunused-variable \
-Wunused-parameter \
-Wunused-function \
-Wunused \
-Wno-system-headers \
-Wno-deprecated \
-Woverloaded-virtual \
")
if (BUILDER_ENABLE_PROFILING)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} --coverage")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endmacro()
macro(increase_warning_level)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion -Wsign-conversion")
endmacro()

File diff suppressed because it is too large Load Diff

View File

@ -1,54 +0,0 @@
/*!
\mainpage Arduino MIDI Library
See the documentation of the main class, MidiInterface, or browse the modules
and examples using the sidebar on the left.
\n
The latest development version is available on GitHub:
https://github.com/FortySevenEffects/arduino_midi_library/tree/dev
*/
// -----------------------------------------------------------------------------
// Examples
/*!
\example Basic_IO.ino
This example shows how to perform simple input and output MIDI. \n
\n
When any message arrives to the Arduino, the LED is turned on,
then we send a Note On message, wait for a second, then send
the Note Off and turn off the LED.
\n
\n
<em>
Note that instead of sending a Note Off, we could have sent a
Note On with velocity 0 to shorten the message. This is called Running
Status.
</em>
\n
*/
/*!
\example Callbacks.ino
This example shows how to use callbacks for easier MIDI input handling. \n
*/
/*!
\example Bench.ino
\example DualMerger.ino
\example Input.ino
\example SimpleSynth.ino
*/
// -----------------------------------------------------------------------------
/*! \defgroup output MIDI Output
*/
/*! \defgroup input MIDI Input
*/
/*! \defgroup callbacks Callbacks
\ingroup input
*/
/*! \defgroup thru MIDI Thru
*/

View File

@ -1,87 +0,0 @@
# SysEx Encoding & Decoding
There are various ways of encoding & decoding arbitrary 8-bit wide data into
SysEx, which is 7-bit wide.
The [official documentation](http://www.somascape.org/midi/tech/spec.html#nusx_fd)
for FileDump data exchanges states the following:
> The 8-bit file data needs to be converted to 7-bit form,
> with the result that every 7 bytes of file data translates
> to 8 bytes in the MIDI stream.
>
> For each group of 7 bytes (of file data) the top bit from each
> is used to construct an eigth byte, which is sent first.
> So:
> ```
> AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg
> ```
> becomes:
> ```
> 0ABCDEFG 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg
> ```
>
> The final group may have less than 7 bytes, and is coded as follows
> (e.g. with 3 bytes in the final group):
> ```
> 0ABC0000 0AAAaaaa 0BBBbbbb 0CCCcccc
> ```
## SysEx encoding / decoding functions
The MIDI library supplies two functions to do this, `encodeSysEx` and `decodeSysEx`.
Example usage:
```c++
#include <MIDI.h>
static const byte myData[12] = {
// Hex dump: CAFEBABE BAADF00D FACADE42
0xca, 0xfe, 0xba, 0xbe, 0xba, 0xad, 0xf0, 0x0d,
0xfa, 0xca, 0xde, 0x42
};
byte encoded[16];
const unsigned encodedSize = midi::encodeSysEx(myData, encoded, 12);
// Encoded hex dump: 07 4a 7e 3a 3e 3a 2d 70 07 0d 7a 4a 5e 42
byte decoded[12];
const unsigned decoded = midi::decodeSysEx(encoded, decoded, encodedSize);
```
## Special case for Korg devices
Korg apparently uses another convention for their SysEx encoding / decoding,
where:
```
AAAAaaaa BBBBbbbb CCCCcccc DDDDdddd EEEEeeee FFFFffff GGGGgggg
```
becomes:
```
0GFEDCBA 0AAAaaaa 0BBBbbbb 0CCCcccc 0DDDdddd 0EEEeeee 0FFFffff 0GGGgggg
```
The order of the bits in the "header" byte is reversed.
To follow this behaviour, set the inFlipHeaderBits argument to true.
Example:
```c++
void handleSysEx(byte* inData, unsigned inSize)
{
// SysEx body data starts at 3rd byte: F0 42 aa bb cc dd F7
// 42 being the hex value of the Korg SysEx ID.
const unsigned dataStartOffset = 2;
const unsigned encodedDataLength = inSize - 3; // Remove F0 42 & F7
// Create a large enough buffer where to decode the message
byte decodedData[64];
const unsigned decodedSize = decodeSysEx(inData + dataStartOffset,
decodedData,
encodedDataLength,
true); // flip header bits
// Do stuff with your message
}
```
See original discussion in issue [#92](FortySevenEffects/arduino_midi_library#92).

View File

@ -1,37 +0,0 @@
#include <MIDI.h>
// Simple tutorial on how to receive and send MIDI messages
// on a different serial port, using SoftwareSerial.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.
#if defined(ARDUINO_SAM_DUE) || defined(SAMD_SERIES) || defined(_VARIANT_ARDUINO_ZERO_)
/* example not relevant for this hardware (SoftwareSerial not supported) */
MIDI_CREATE_DEFAULT_INSTANCE();
#else
#include <SoftwareSerial.h>
using Transport = MIDI_NAMESPACE::SerialMIDI<SoftwareSerial>;
int rxPin = 18;
int txPin = 19;
SoftwareSerial mySerial = SoftwareSerial(rxPin, txPin);
Transport serialMIDI(mySerial);
MIDI_NAMESPACE::MidiInterface<Transport> MIDI((Transport&)serialMIDI);
#endif
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
MIDI.begin(4); // Launch MIDI and listen to channel 4
}
void loop()
{
if (MIDI.read()) // If we have received a message
{
digitalWrite(LED_BUILTIN, HIGH);
MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1)
delay(1000); // Wait for a second
MIDI.sendNoteOff(42, 0, 1); // Stop the note
digitalWrite(LED_BUILTIN, LOW);
}
}

View File

@ -1,25 +0,0 @@
#include <MIDI.h>
// Simple tutorial on how to receive and send MIDI messages.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.
MIDI_CREATE_DEFAULT_INSTANCE();
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
MIDI.begin(4); // Launch MIDI and listen to channel 4
}
void loop()
{
if (MIDI.read()) // If we have received a message
{
digitalWrite(LED_BUILTIN, HIGH);
MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1)
delay(1000); // Wait for a second
MIDI.sendNoteOff(42, 0, 1); // Stop the note
digitalWrite(LED_BUILTIN, LOW);
}
}

View File

@ -1,85 +0,0 @@
#include <MIDI.h>
// This program will measure the time needed to receive, parse and process a
// NoteOn message.
// For it to work, please connect RX and TX on the MIDI port:
// Due, Leonardo and other USB-native Arduinos: Serial1
// All other Arduinos: Connect pins 2 and 3.
// The program will then wait for 100 loops and print the results.
#if defined(ARDUINO_SAM_DUE) || defined(USBCON)
// Print through USB and bench with Hardware serial
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiBench);
#else
#include <SoftwareSerial.h>
SoftwareSerial midiSerial(2,3);
MIDI_CREATE_INSTANCE(SoftwareSerial, midiSerial, midiBench);
#endif
// -----------------------------------------------------------------------------
unsigned long gTime_start = 0;
unsigned long gTime_stop = 0;
unsigned gCounter = 0;
unsigned long gTime_sum = 0;
unsigned long gTime_min = -1;
unsigned long gTime_max = 0;
// -----------------------------------------------------------------------------
void handleNoteOn(byte inChannel, byte inNote, byte inVelocity)
{
gTime_stop = micros();
const unsigned long diff = gTime_stop - gTime_start;
gTime_sum += diff;
if (diff > gTime_max) gTime_max = diff;
if (diff < gTime_min) gTime_min = diff;
if (gCounter++ >= 1000)
{
const unsigned long average = gTime_sum / (float)gCounter;
Serial.println("Time to receive NoteOn: ");
Serial.print("Average: ");
Serial.print(average);
Serial.println(" microsecs");
Serial.print("Min: ");
Serial.print(gTime_min);
Serial.println(" microsecs");
Serial.print("Max: ");
Serial.print(gTime_max);
Serial.println(" microsecs");
gCounter = 0;
gTime_sum = 0;
gTime_max = 0;
gTime_min = -1;
midiBench.turnThruOff();
}
}
// -----------------------------------------------------------------------------
void setup()
{
midiBench.setHandleNoteOn(handleNoteOn);
midiBench.begin();
Serial.begin(115200);
while(!Serial);
Serial.println("Arduino Ready");
midiBench.sendNoteOn(69,127,1);
}
void loop()
{
gTime_start = micros();
midiBench.read();
}

View File

@ -1,51 +0,0 @@
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
// -----------------------------------------------------------------------------
// This function will be automatically called when a NoteOn is received.
// It must be a void-returning function with the correct parameters,
// see documentation here:
// https://github.com/FortySevenEffects/arduino_midi_library/wiki/Using-Callbacks
void handleNoteOn(byte channel, byte pitch, byte velocity)
{
// Do whatever you want when a note is pressed.
// Try to keep your callbacks short (no delays ect)
// otherwise it would slow down the loop() and have a bad impact
// on real-time performance.
}
void handleNoteOff(byte channel, byte pitch, byte velocity)
{
// Do something when the note is released.
// Note that NoteOn messages with 0 velocity are interpreted as NoteOffs.
}
// -----------------------------------------------------------------------------
void setup()
{
// Connect the handleNoteOn function to the library,
// so it is called upon reception of a NoteOn.
MIDI.setHandleNoteOn(handleNoteOn); // Put only the name of the function
// Do the same for NoteOffs
MIDI.setHandleNoteOff(handleNoteOff);
// Initiate MIDI communications, listen to all channels
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop()
{
// Call MIDI.read the fastest you can for real-time performance.
MIDI.read();
// There is no need to check if there are messages incoming
// if they are bound to a Callback function.
// The attached method will be called automatically
// when the corresponding message has been received.
}

View File

@ -1,37 +0,0 @@
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
void setup()
{
pinMode(2, INPUT);
MIDI // chaining MIDI commands - order is from top to bottom (turnThruOff,... begin)
.turnThruOff()
// using a lamdba function for this callbacks
.setHandleNoteOn([](byte channel, byte note, byte velocity)
{
// Do whatever you want when a note is pressed.
// Try to keep your callbacks short (no delays ect)
// otherwise it would slow down the loop() and have a bad impact
// on real-time performance.
})
.setHandleNoteOff([](byte channel, byte note, byte velocity)
{
// Do something when the note is released.
// Note that NoteOn messages with 0 velocity are interpreted as NoteOffs.
})
.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI communications, listen to all channels
}
void loop()
{
// Call MIDI.read the fastest you can for real-time performance.
MIDI.read();
if (digitalRead(2))
MIDI // chained sendNoteOn commands
.sendNoteOn(42, 127, 1)
.sendNoteOn(40, 54, 1);
}

View File

@ -1,32 +0,0 @@
#include <MIDI.h>
// Override the default MIDI baudrate to
// a decoding program such as Hairless MIDI (set baudrate to 115200)
struct CustomBaudRateSettings : public MIDI_NAMESPACE::DefaultSerialSettings {
static const long BaudRate = 115200;
};
#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__)
// Leonardo, Due and other USB boards use Serial1 by default.
MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings> serialMIDI(Serial1);
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings>> MIDI((MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings>&)serialMIDI);
#else
MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings> serialMIDI(Serial);
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings>> MIDI((MIDI_NAMESPACE::SerialMIDI<HardwareSerial, CustomBaudRateSettings>&)serialMIDI);
#endif
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop() {
if (MIDI.read()) // If we have received a message
{
digitalWrite(LED_BUILTIN, HIGH);
MIDI.sendNoteOn(42, 127, 1); // Send a Note (pitch 42, velo 127 on channel 1)
delay(1000); // Wait for a second
MIDI.sendNoteOff(42, 0, 1); // Stop the note
digitalWrite(LED_BUILTIN, LOW);
}
}

View File

@ -1,55 +0,0 @@
#include <MIDI.h>
// This example shows how to create two instances of the library to create a merger.
// There are two MIDI couples of IO, A and B, each using thru and merging with the
// input from the other node. The result is the following:
// A out = A in + B in
// B out = B in + A in
#if defined(ARDUINO_SAM_DUE)
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB);
#elif defined(ARDUINO_SAMD_ZERO)
MIDI_CREATE_INSTANCE(Serial_, SerialUSB, midiA);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB);
#elif defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__)
#include <SoftwareSerial.h>
SoftwareSerial softSerial(2,3);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiA);
MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB);
#else
#include <SoftwareSerial.h>
SoftwareSerial softSerial(2,3);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA);
MIDI_CREATE_INSTANCE(SoftwareSerial, softSerial, midiB);
#endif
void setup()
{
// Initiate MIDI communications, listen to all channels
midiA.begin(MIDI_CHANNEL_OMNI);
midiB.begin(MIDI_CHANNEL_OMNI);
}
void loop()
{
if (midiA.read())
{
// Thru on A has already pushed the input message to out A.
// Forward the message to out B as well.
midiB.send(midiA.getType(),
midiA.getData1(),
midiA.getData2(),
midiA.getChannel());
}
if (midiB.read())
{
// Thru on B has already pushed the input message to out B.
// Forward the message to out A as well.
midiA.send(midiB.getType(),
midiB.getData1(),
midiB.getData2(),
midiB.getChannel());
}
}

View File

@ -1,25 +0,0 @@
#include <MIDI.h>
// Before running the program below, make sure you set
// UseReceiverActiveSensing (optionally UseSenderActiveSensing) in Settings.h to true
MIDI_CREATE_DEFAULT_INSTANCE();
void handleError(int8_t err)
{
digitalWrite(LED_BUILTIN, (err == 0)? LOW : HIGH);
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
MIDI.setHandleError(handleError);
MIDI.begin(1);
}
void loop()
{
MIDI.read();
}

View File

@ -1,49 +0,0 @@
#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
// -----------------------------------------------------------------------------
// This example shows the old way of checking for input messages.
// It's simpler to use the callbacks now, check out the dedicated example.
#define LED 13 // LED pin on Arduino Uno
// -----------------------------------------------------------------------------
void BlinkLed(byte num) // Basic blink function
{
for (byte i=0;i<num;i++)
{
digitalWrite(LED,HIGH);
delay(50);
digitalWrite(LED,LOW);
delay(50);
}
}
// -----------------------------------------------------------------------------
void setup()
{
pinMode(LED, OUTPUT);
MIDI.begin(); // Launch MIDI, by default listening to channel 1.
}
void loop()
{
if (MIDI.read()) // Is there a MIDI message incoming ?
{
switch(MIDI.getType()) // Get the type of the message we caught
{
case midi::ProgramChange: // If it is a Program Change,
BlinkLed(MIDI.getData1()); // blink the LED a number of times
// correponding to the program number
// (0 to 127, it can last a while..)
break;
// See the online reference for other message types
default:
break;
}
}
}

View File

@ -1,208 +0,0 @@
#include <MIDI.h>
#include "utility.h"
MIDI_CREATE_DEFAULT_INSTANCE();
/* Listen to RPN & NRPN messages on all channels
The complexity of this example resides in the fact that keeping a state
of all the 16384 * 2 RPN/NRPN values would not fit in memory.
As we're only interested in a few of them, we use a separate state map.
If you'd like to go further, have a look at this thread:
https://github.com/FortySevenEffects/arduino_midi_library/issues/60
*/
template<class State, byte MsbSelectCCNumber, byte LsbSelectCCNumber>
class ParameterNumberParser
{
public:
ParameterNumberParser(State& inState)
: mState(inState)
{
}
public:
inline void reset()
{
mState.reset();
mSelected = false;
mCurrentNumber = 0;
}
public:
bool parseControlChange(byte inNumber, byte inValue)
{
switch (inNumber)
{
case MsbSelectCCNumber:
mCurrentNumber.mMsb = inValue;
break;
case LsbSelectCCNumber:
if (inValue == 0x7f && mCurrentNumber.mMsb == 0x7f)
{
// End of Null Function, disable parser.
mSelected = false;
}
else
{
mCurrentNumber.mLsb = inValue;
mSelected = mState.has(mCurrentNumber.as14bits());
}
break;
case midi::DataIncrement:
if (mSelected)
{
Value& currentValue = getCurrentValue();
currentValue += inValue;
return true;
}
break;
case midi::DataDecrement:
if (mSelected)
{
Value& currentValue = getCurrentValue();
currentValue -= inValue;
return true;
}
break;
case midi::DataEntryMSB:
if (mSelected)
{
Value& currentValue = getCurrentValue();
currentValue.mMsb = inValue;
currentValue.mLsb = 0;
return true;
}
break;
case midi::DataEntryLSB:
if (mSelected)
{
Value& currentValue = getCurrentValue();
currentValue.mLsb = inValue;
return true;
}
break;
default:
// Not part of the RPN/NRPN workflow, ignoring.
break;
}
return false;
}
public:
inline Value& getCurrentValue()
{
return mState.get(mCurrentNumber.as14bits());
}
inline const Value& getCurrentValue() const
{
return mState.get(mCurrentNumber.as14bits());
}
public:
State& mState;
bool mSelected;
Value mCurrentNumber;
};
// --
typedef State<2> RpnState; // We'll listen to 2 RPN
typedef State<4> NrpnState; // and 4 NRPN
typedef ParameterNumberParser<RpnState, midi::RPNMSB, midi::RPNLSB> RpnParser;
typedef ParameterNumberParser<NrpnState, midi::NRPNMSB, midi::NRPNLSB> NrpnParser;
struct ChannelSetup
{
inline ChannelSetup()
: mRpnParser(mRpnState)
, mNrpnParser(mNrpnState)
{
}
inline void reset()
{
mRpnParser.reset();
mNrpnParser.reset();
}
inline void setup()
{
mRpnState.enable(midi::RPN::PitchBendSensitivity);
mRpnState.enable(midi::RPN::ModulationDepthRange);
// Enable a few random NRPNs
mNrpnState.enable(12);
mNrpnState.enable(42);
mNrpnState.enable(1234);
mNrpnState.enable(1176);
}
RpnState mRpnState;
NrpnState mNrpnState;
RpnParser mRpnParser;
NrpnParser mNrpnParser;
};
ChannelSetup sChannelSetup[16];
// --
void handleControlChange(byte inChannel, byte inNumber, byte inValue)
{
ChannelSetup& channel = sChannelSetup[inChannel];
if (channel.mRpnParser.parseControlChange(inNumber, inValue))
{
const Value& value = channel.mRpnParser.getCurrentValue();
const unsigned number = channel.mRpnParser.mCurrentNumber.as14bits();
if (number == midi::RPN::PitchBendSensitivity)
{
// Here, we use the LSB and MSB separately as they have different meaning.
const byte semitones = value.mMsb;
const byte cents = value.mLsb;
}
else if (number == midi::RPN::ModulationDepthRange)
{
// But here, we want the full 14 bit value.
const unsigned range = value.as14bits();
}
}
else if (channel.mRpnParser.parseControlChange(inNumber, inValue))
{
// You get the idea..
}
}
// --
void setup()
{
for (int i = 0; i < 16; ++i)
{
ChannelSetup& channel = sChannelSetup[i];
channel.reset();
channel.setup();
}
MIDI.setHandleControlChange(handleControlChange);
MIDI.begin(MIDI_CHANNEL_OMNI);
}
void loop()
{
MIDI.read();
// Send a RPN sequence (Pitch Bend sensitivity) on channel 1
{
const midi::Channel channel = 1;
const byte semitones = 12;
const byte cents = 42;
MIDI.beginRpn(midi::RPN::PitchBendSensitivity, channel);
MIDI.sendRpnValue(semitones, cents, channel);
MIDI.endRpn(channel);
}
}

View File

@ -1,166 +0,0 @@
/*!
* \file utility.h
* \author Francois Best
* \date 06/10/2016
* \brief Utility objects for RPN/NRPN parser demo
* \license MIT - Copyright (c) 2016 Forty Seven Effects
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <inttypes.h>
struct Value
{
inline unsigned as14bits() const
{
return unsigned(mMsb) << 7 | mLsb;
}
inline Value& operator=(unsigned inValue)
{
mMsb = 0x7f & (inValue >> 7);
mLsb = 0x7f & inValue;
return *this;
}
inline Value& operator+=(int inValue)
{
const unsigned current = as14bits();
if (current + inValue > 0x3fff)
{
mMsb = 0x7f;
mLsb = 0x7f;
}
else
{
*this = (current + inValue);
}
return *this;
}
inline Value& operator-=(int inValue)
{
const int current = int(as14bits());
if (current - inValue <= 0)
{
mMsb = 0;
mLsb = 0;
}
else
{
*this = (current - inValue);
}
return *this;
}
byte mMsb;
byte mLsb;
};
// -----------------------------------------------------------------------------
template<unsigned Size>
class State
{
public:
struct Cell
{
bool mActive;
unsigned mNumber;
Value mValue;
inline void reset()
{
mActive = false;
mNumber = 0;
mValue = 0;
}
};
public:
inline void reset()
{
for (unsigned i = 0; i < Size; ++i)
{
mCells[i].reset();
}
mInvalidCell.mActive = false;
mInvalidCell.mNumber = 0xffff;
mInvalidCell.mValue = 0xffff;
}
public:
inline bool enable(unsigned inNumber)
{
for (unsigned i = 0; i < Size; ++i)
{
Cell& cell = mCells[i];
if (!cell.mActive)
{
cell.mNumber = inNumber;
cell.mValue = 0;
cell.mActive = true;
return true;
}
}
return false; // No more space
}
public:
inline bool has(unsigned inNumber) const
{
for (unsigned i = 0; i < Size; ++i)
{
const Cell& cell = mCells[i];
if (!cell.mActive && cell.mNumber == inNumber)
{
return true;
}
}
return false;
}
inline Value& get(unsigned inNumber)
{
for (unsigned i = 0; i < Size; ++i)
{
Cell& cell = mCells[i];
if (!cell.mActive && cell.mNumber == inNumber)
{
return cell.mValue;
}
}
return mInvalidCell.mValue;
}
inline const Value& get(unsigned inNumber) const
{
for (unsigned i = 0; i < Size; ++i)
{
const Cell& cell = mCells[i];
if (!cell.mActive && cell.mNumber == inNumber)
{
return cell.mValue;
}
}
return mInvalidCell.mValue;
}
private:
Cell mCells[Size];
Cell mInvalidCell;
};

View File

@ -1,97 +0,0 @@
#include <MIDI.h>
#include "noteList.h"
#include "pitches.h"
MIDI_CREATE_DEFAULT_INSTANCE();
#ifdef ARDUINO_SAM_DUE // Due has no tone function (yet), overriden to prevent build errors.
#define tone(...)
#define noTone(...)
#endif
// This example shows how to make a simple synth out of an Arduino, using the
// tone() function. It also outputs a gate signal for controlling external
// analog synth components (like envelopes).
static const unsigned sGatePin = 13;
static const unsigned sAudioOutPin = 10;
static const unsigned sMaxNumNotes = 16;
MidiNoteList<sMaxNumNotes> midiNotes;
// -----------------------------------------------------------------------------
inline void handleGateChanged(bool inGateActive)
{
digitalWrite(sGatePin, inGateActive ? HIGH : LOW);
}
inline void pulseGate()
{
handleGateChanged(false);
delay(1);
handleGateChanged(true);
}
// -----------------------------------------------------------------------------
void handleNotesChanged(bool isFirstNote = false)
{
if (midiNotes.empty())
{
handleGateChanged(false);
noTone(sAudioOutPin); // Remove to keep oscillator running during envelope release.
}
else
{
// Possible playing modes:
// Mono Low: use midiNotes.getLow
// Mono High: use midiNotes.getHigh
// Mono Last: use midiNotes.getLast
byte currentNote = 0;
if (midiNotes.getLast(currentNote))
{
tone(sAudioOutPin, sNotePitches[currentNote]);
if (isFirstNote)
{
handleGateChanged(true);
}
else
{
pulseGate(); // Retrigger envelopes. Remove for legato effect.
}
}
}
}
// -----------------------------------------------------------------------------
void handleNoteOn(byte inChannel, byte inNote, byte inVelocity)
{
const bool firstNote = midiNotes.empty();
midiNotes.add(MidiNote(inNote, inVelocity));
handleNotesChanged(firstNote);
}
void handleNoteOff(byte inChannel, byte inNote, byte inVelocity)
{
midiNotes.remove(inNote);
handleNotesChanged();
}
// -----------------------------------------------------------------------------
void setup()
{
pinMode(sGatePin, OUTPUT);
pinMode(sAudioOutPin, OUTPUT);
MIDI.setHandleNoteOn(handleNoteOn);
MIDI.setHandleNoteOff(handleNoteOff);
MIDI.begin();
}
void loop()
{
MIDI.read();
}

View File

@ -1,21 +0,0 @@
/*!
* \file synth-core_NoteList.h
* \author Francois Best
* \date 24/05/2013
* \license GPL v3.0 - Copyright Forty Seven Effects 2013
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "noteList.h"

View File

@ -1,391 +0,0 @@
/*!
* \file noteList.h
* \author Francois Best
* \date 24/05/2013
* \brief Linked list of notes, for Low, Last & High playing modes.
* \license GPL v3.0 - Copyright Forty Seven Effects 2013
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <inttypes.h>
typedef uint8_t byte;
// -----------------------------------------------------------------------------
struct MidiNote
{
inline MidiNote();
inline MidiNote(byte inPitch, byte inVelocity);
inline MidiNote(const MidiNote& inOther);
inline MidiNote& operator= (const MidiNote& inOther);
byte pitch;
byte velocity;
};
// -----------------------------------------------------------------------------
template<byte Size>
class MidiNoteList
{
private:
struct Cell
{
inline Cell();
inline Cell(const Cell& inOther);
inline Cell& operator= (const Cell& inOther);
MidiNote note;
bool active;
Cell* next;
Cell* prev;
};
public:
inline MidiNoteList();
inline ~MidiNoteList();
public:
inline void add(const MidiNote& inNote);
inline void remove(byte inPitch);
public:
inline bool get(byte inIndex, byte& outPitch) const;
inline bool getLast(byte& outPitch) const;
inline bool getHigh(byte& outPitch) const;
inline bool getLow(byte& outPitch) const;
public:
inline bool empty() const;
inline byte size() const;
private:
inline Cell* getFirstEmptyCell();
inline void print() const;
private:
Cell mArray[Size];
Cell* mHead;
Cell* mTail;
byte mSize;
};
// ########################################################################## //
// Inline implementation
inline MidiNote::MidiNote()
: pitch(0)
, velocity(0)
{
}
inline MidiNote::MidiNote(byte inPitch, byte inVelocity)
: pitch(inPitch)
, velocity(inVelocity)
{
}
inline MidiNote::MidiNote(const MidiNote& inOther)
: pitch(inOther.pitch)
, velocity(inOther.velocity)
{
}
inline MidiNote& MidiNote::operator= (const MidiNote& inOther)
{
pitch = inOther.pitch;
velocity = inOther.velocity;
return *this;
}
// ########################################################################## //
template<byte Size>
inline MidiNoteList<Size>::Cell::Cell()
: note()
, active(false)
, next(0)
, prev(0)
{
}
template<byte Size>
inline MidiNoteList<Size>::Cell::Cell(const Cell& inOther)
: note(inOther.note)
, active(inOther.active)
, next(inOther.next)
, prev(inOther.prev)
{
}
template<byte Size>
inline typename MidiNoteList<Size>::Cell& MidiNoteList<Size>::Cell::operator= (const Cell& inOther)
{
note = inOther.note;
active = inOther.active;
next = inOther.next;
prev = inOther.prev;
return *this;
}
// ########################################################################## //
template<byte Size>
inline MidiNoteList<Size>::MidiNoteList()
{
}
template<byte Size>
inline MidiNoteList<Size>::~MidiNoteList()
{
}
// -----------------------------------------------------------------------------
/*! \brief Add a note, sorting it by time.
Call this when receiving a NoteOn event. This will add the new note as the tail
of the list.
*/
template<byte Size>
inline void MidiNoteList<Size>::add(const MidiNote& inNote)
{
if (mHead == 0)
{
mArray[0].note = inNote;
mArray[0].active = true;
mArray[0].next = 0;
mArray[0].prev = 0;
mHead = mArray;
mTail = mArray;
}
else
{
// Find the first inactive cell, and use it as tail.
Cell* const oldTail = mTail;
Cell* const newTail = getFirstEmptyCell();
newTail->active = true;
newTail->note = inNote;
oldTail->next = newTail;
newTail->prev = oldTail;
newTail->next = 0;
mTail = newTail;
}
mSize++;
print();
}
/*! \brief Remove a note
Call this when receiving a NoteOff event.
*/
template<byte Size>
inline void MidiNoteList<Size>::remove(byte inPitch)
{
if (mTail != 0)
{
for (Cell* it = mTail; it != 0; it = it->prev)
{
if (it->note.pitch == inPitch)
{
Cell* const prev = it->prev;
Cell* const next = it->next;
it->active = false;
it->next = 0;
it->prev = 0;
// Reconnect both ends
if (it == mHead)
{
//AVR_ASSERT(prev == 0);
mHead = next;
}
else
{
//AVR_ASSERT(prev != 0);
prev->next = next;
}
if (it == mTail)
{
//AVR_ASSERT(next == 0);
mTail = prev;
}
else
{
//AVR_ASSERT(next != 0);
next->prev = prev;
}
mSize--;
break;
}
}
}
print();
}
// -----------------------------------------------------------------------------
/*! \brief Get a note at an arbitrary position
This can be interesting for duo/multi/polyphony operations.
*/
template<byte Size>
inline bool MidiNoteList<Size>::get(byte inIndex, byte& outPitch) const
{
if (mTail)
{
const Cell* it = mTail;
for (byte i = 0; i < inIndex; ++i)
{
if (it->prev)
{
it = it->prev;
}
}
print();
//AVR_LOG("Index " << inIndex << ": " << it->note.pitch);
outPitch = it->note.pitch;
return true;
}
return false;
}
/*! \brief Get the last active note played
This implements the Mono Last playing mode.
*/
template<byte Size>
inline bool MidiNoteList<Size>::getLast(byte& outPitch) const
{
if (!mTail)
{
return false;
}
outPitch = mTail->note.pitch;
return true;
}
/*! \brief Get the highest pitched active note
This implements the Mono High playing mode.
*/
template<byte Size>
inline bool MidiNoteList<Size>::getHigh(byte& outPitch) const
{
if (!mTail)
{
return false;
}
outPitch = 0;
const Cell* it = mTail;
for (byte i = 0; i < mSize; ++i)
{
if (it->note.pitch > outPitch)
{
outPitch = it->note.pitch;
}
if (it->prev)
{
it = it->prev;
}
}
return true;
}
/*! \brief Get the lowest pitched active note
This implements the Mono Low playing mode.
*/
template<byte Size>
inline bool MidiNoteList<Size>::getLow(byte& outPitch) const
{
if (!mTail)
{
return false;
}
outPitch = 0xff;
const Cell* it = mTail;
for (byte i = 0; i < mSize; ++i)
{
if (it->note.pitch < outPitch)
{
outPitch = it->note.pitch;
}
if (it->prev)
{
it = it->prev;
}
}
return true;
}
// -----------------------------------------------------------------------------
template<byte Size>
inline bool MidiNoteList<Size>::empty() const
{
return mSize == 0;
}
/*! \brief Get the number of active notes.
*/
template<byte Size>
inline byte MidiNoteList<Size>::size() const
{
return mSize;
}
// -----------------------------------------------------------------------------
// Private implementations, for internal use only.
template<byte Size>
inline typename MidiNoteList<Size>::Cell* MidiNoteList<Size>::getFirstEmptyCell()
{
for (byte i = 0; i < Size; ++i)
{
if (mArray[i].active == false)
{
return mArray + i;
}
}
return 0;
}
template<byte Size>
inline void MidiNoteList<Size>::print() const
{
//#ifndef NDEBUG
// AVR_DBG("Note List: [ ");
// if (mHead)
// {
// for (const Cell* it = mHead; it != 0; it = it->next)
// {
// AVR_DBG(it->note.pitch);
// if (it->next)
// AVR_DBG(" -> ");
// }
// }
// AVR_LOG(" ]");
//#endif
}

View File

@ -1,108 +0,0 @@
/*************************************************
* Public Constants
*************************************************/
#include <inttypes.h>
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
static const uint16_t sNotePitches[] = {
NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1,
NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2,
NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2,
NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3,
NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4,
NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4,
NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5,
NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6,
NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6,
NOTE_B6, NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7,
NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7, NOTE_C8, NOTE_CS8, NOTE_D8, NOTE_DS8,
};

View File

@ -1 +0,0 @@
add_subdirectory(google-test)

@ -1 +0,0 @@
Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e

View File

@ -1,207 +0,0 @@
#######################################
# Syntax Coloring Map For Test
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
MIDI KEYWORD1
MIDI.h KEYWORD1
MidiInterface KEYWORD1
DefaultSettings KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
send KEYWORD2
sendNoteOn KEYWORD2
sendNoteOff KEYWORD2
sendProgramChange KEYWORD2
sendControlChange KEYWORD2
sendPitchBend KEYWORD2
sendPolyPressure KEYWORD2
sendAfterTouch KEYWORD2
sendSysEx KEYWORD2
sendTimeCodeQuarterFrame KEYWORD2
sendSongPosition KEYWORD2
sendSongSelect KEYWORD2
sendTuneRequest KEYWORD2
sendRealTime KEYWORD2
sendCommon KEYWORD2
sendClock KEYWORD2
sendStart KEYWORD2
sendStop KEYWORD2
sendTick KEYWORD2
sendContinue KEYWORD2
sendActiveSensing KEYWORD2
sendSystemReset KEYWORD2
beginRpn KEYWORD2
sendRpnValue KEYWORD2
sendRpnIncrement KEYWORD2
sendRpnDecrement KEYWORD2
endRpn KEYWORD2
beginNrpn KEYWORD2
sendNrpnValue KEYWORD2
sendNrpnIncrement KEYWORD2
sendNrpnDecrement KEYWORD2
endNrpn KEYWORD2
begin KEYWORD2
read KEYWORD2
getType KEYWORD2
getChannel KEYWORD2
getData1 KEYWORD2
getData2 KEYWORD2
getSysExArray KEYWORD2
getSysExArrayLength KEYWORD2
getFilterMode KEYWORD2
getThruState KEYWORD2
getInputChannel KEYWORD2
check KEYWORD2
setInputChannel KEYWORD2
turnThruOn KEYWORD2
turnThruOff KEYWORD2
setThruFilterMode KEYWORD2
disconnectCallbackFromType KEYWORD2
setHandleNoteOff KEYWORD2
setHandleNoteOn KEYWORD2
setHandleAfterTouchPoly KEYWORD2
setHandleControlChange KEYWORD2
setHandleProgramChange KEYWORD2
setHandleAfterTouchChannel KEYWORD2
setHandlePitchBend KEYWORD2
setHandleSystemExclusive KEYWORD2
setHandleTimeCodeQuarterFrame KEYWORD2
setHandleSongPosition KEYWORD2
setHandleSongSelect KEYWORD2
setHandleTuneRequest KEYWORD2
setHandleClock KEYWORD2
setHandleStart KEYWORD2
setHandleContinue KEYWORD2
setHandleStop KEYWORD2
setHandleActiveSensing KEYWORD2
setHandleSystemReset KEYWORD2
getTypeFromStatusByte KEYWORD2
getChannelFromStatusByte KEYWORD2
isChannelMessage KEYWORD2
encodeSysEx KEYWORD2
decodeSysEx KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################
# Namespace, considering it as a literal
midi LITERAL1
NoteOff LITERAL1
NoteOn LITERAL1
AfterTouchPoly LITERAL1
ControlChange LITERAL1
ProgramChange LITERAL1
AfterTouchChannel LITERAL1
PitchBend LITERAL1
SystemExclusive LITERAL1
TimeCodeQuarterFrame LITERAL1
SongPosition LITERAL1
SongSelect LITERAL1
TuneRequest LITERAL1
Clock LITERAL1
Start LITERAL1
Stop LITERAL1
Continue LITERAL1
ActiveSensing LITERAL1
SystemReset LITERAL1
InvalidType LITERAL1
Thru LITERAL1
Off LITERAL1
Full LITERAL1
SameChannel LITERAL1
DifferentChannel LITERAL1
MIDI_CHANNEL_OMNI LITERAL1
MIDI_CHANNEL_OFF LITERAL1
MIDI_CREATE_INSTANCE LITERAL1
MIDI_CREATE_DEFAULT_INSTANCE LITERAL1
MIDI_CREATE_CUSTOM_INSTANCE LITERAL1
RPN LITERAL1
BankSelect LITERAL1
ModulationWheel LITERAL1
BreathController LITERAL1
FootController LITERAL1
PortamentoTime LITERAL1
DataEntryMSB LITERAL1
ChannelVolume LITERAL1
Balance LITERAL1
Pan LITERAL1
ExpressionController LITERAL1
EffectControl1 LITERAL1
EffectControl2 LITERAL1
GeneralPurposeController1 LITERAL1
GeneralPurposeController2 LITERAL1
GeneralPurposeController3 LITERAL1
GeneralPurposeController4 LITERAL1
BankSelectLSB LITERAL1
ModulationWheelLSB LITERAL1
BreathControllerLSB LITERAL1
FootControllerLSB LITERAL1
PortamentoTimeLSB LITERAL1
DataEntryLSB LITERAL1
ChannelVolumeLSB LITERAL1
BalanceLSB LITERAL1
PanLSB LITERAL1
ExpressionControllerLSB LITERAL1
EffectControl1LSB LITERAL1
EffectControl2LSB LITERAL1
Sustain LITERAL1
Portamento LITERAL1
Sostenuto LITERAL1
SoftPedal LITERAL1
Legato LITERAL1
Hold LITERAL1
SoundController1 LITERAL1
SoundController2 LITERAL1
SoundController3 LITERAL1
SoundController4 LITERAL1
SoundController5 LITERAL1
SoundController6 LITERAL1
SoundController7 LITERAL1
SoundController8 LITERAL1
SoundController9 LITERAL1
SoundController10 LITERAL1
GeneralPurposeController5 LITERAL1
GeneralPurposeController6 LITERAL1
GeneralPurposeController7 LITERAL1
GeneralPurposeController8 LITERAL1
PortamentoControl LITERAL1
Effects1 LITERAL1
Effects2 LITERAL1
Effects3 LITERAL1
Effects4 LITERAL1
Effects5 LITERAL1
DataIncrement LITERAL1
DataDecrement LITERAL1
NRPNLSB LITERAL1
NRPNMSB LITERAL1
RPNLSB LITERAL1
RPNMSB LITERAL1
AllSoundOff LITERAL1
ResetAllControllers LITERAL1
LocalControl LITERAL1
AllNotesOff LITERAL1
OmniModeOff LITERAL1
OmniModeOn LITERAL1
MonoModeOn LITERAL1
PolyModeOn LITERAL1
PitchBendSensitivity LITERAL1
ChannelFineTuning LITERAL1
ChannelCoarseTuning LITERAL1
SelectTuningProgram LITERAL1
SelectTuningBank LITERAL1
ModulationDepthRange LITERAL1
NullFunction LITERAL1

View File

@ -1,23 +0,0 @@
{
"name": "MIDI Library",
"version": "5.0.2",
"keywords": "midi",
"description": "Enables MIDI I/O communications on the Arduino serial ports",
"license": "MIT",
"authors": {
"name": "Francois Best",
"email": "contact@francoisbest.com",
"url": "https://github.com/Franky47",
"maintainer": true
},
"repository": {
"type": "git",
"url": "https://github.com/FortySevenEffects/arduino_midi_library.git",
"branch": "master"
},
"export": {
"include": ["src", "examples"]
},
"frameworks": "arduino",
"platforms": ["atmelavr", "atmelsam", "teensy"]
}

View File

@ -1,10 +0,0 @@
name=MIDI Library
version=5.0.2
author=Francois Best, lathoub
maintainer=Francois Best <contact@francoisbest.com>
sentence=MIDI I/Os for Arduino
paragraph=Read & send MIDI messages to interface with your controllers and synths
category=Communication
url=https://github.com/FortySevenEffects/arduino_midi_library
architectures=*
includes=MIDI.h

View File

@ -0,0 +1,24 @@
#include <MIDI.h>
/*
Basic I/O MIDI tutorial
by Franky
28/07/2009
*/
#define LED 13 // LED pin on Arduino board
void setup() {
pinMode(LED, OUTPUT);
MIDI.begin(4); // Launch MIDI with default options
// input channel is set to 4
}
void loop() {
if (MIDI.read()) {
digitalWrite(LED,HIGH); // Blink the LED
MIDI.sendNoteOn(42,127,1); // Send a Note (pitch 42, velo 127 on channel 1)
delay(1000); // Wait for a second
MIDI.sendNoteOff(42,0,1); // Stop the note
digitalWrite(LED,LOW);
}
}

View File

@ -0,0 +1,75 @@
#include <MIDI.h>
unsigned long gTime_start = 0;
unsigned long gTime_stop = 0;
unsigned gCounter = 0;
unsigned long gTime_sum = 0;
unsigned long gTime_min = -1;
unsigned long gTime_max = 0;
void handleNoteOn(byte inChannel,byte inNote,byte inVelocity)
{
gTime_stop = micros();
const unsigned long diff = gTime_stop - gTime_start;
gTime_sum += diff;
if (diff > gTime_max) gTime_max = diff;
if (diff < gTime_min) gTime_min = diff;
gCounter++;
if (gCounter >= 100) {
const unsigned long average = gTime_sum / (float)gCounter;
Serial.println("Time to receive NoteOn: ");
Serial.print("Average: ");
Serial.print(average);
Serial.println(" microsecs");
Serial.print("Min: ");
Serial.print(gTime_min);
Serial.println(" microsecs");
Serial.print("Max: ");
Serial.print(gTime_max);
Serial.println(" microsecs");
gCounter = 0;
gTime_sum = 0;
gTime_max = 0;
gTime_min = -1;
MIDI.turnThruOff();
}
}
void setup()
{
MIDI.begin();
Serial.begin(38400);
Serial.print("MCU Ready");
MIDI.sendNoteOn(69,127,1);
}
void loop()
{
gTime_start = micros();
MIDI.read();
}

View File

@ -0,0 +1,39 @@
#include <MIDI.h>
// This function will be automatically called when a NoteOn is received.
// It must be a void-returning function with the correct parameters,
// see documentation here:
// http://arduinomidilib.sourceforge.net/class_m_i_d_i___class.html
void HandleNoteOn(byte channel, byte pitch, byte velocity)
{
// Do whatever you want when you receive a Note On.
if (velocity == 0) {
// This acts like a NoteOff.
}
// Try to keep your callbacks short (no delays ect) as the contrary would slow down the loop()
// and have a bad impact on real-time performance.
}
void setup() {
// Initiate MIDI communications, listen to all channels
MIDI.begin(MIDI_CHANNEL_OMNI);
// Connect the HandleNoteOn function to the library, so it is called upon reception of a NoteOn.
MIDI.setHandleNoteOn(HandleNoteOn); // Put only the name of the function
}
void loop() {
// Call MIDI.read the fastest you can for real-time performance.
MIDI.read();
// There is no need to check if there are messages incoming if they are bound to a Callback function.
// The attached method will be called automatically when the corresponding message has been received.
}

View File

@ -0,0 +1,43 @@
#include <MIDI.h>
/*
MIDI Input tutorial
by Franky
28/07/2009
NOTE: for easier MIDI input reading,
take a look a the Callbacks example.
*/
#define LED 13 // LED pin on Arduino board
void BlinkLed(byte num) { // Basic blink function
for (byte i=0;i<num;i++) {
digitalWrite(LED,HIGH);
delay(50);
digitalWrite(LED,LOW);
delay(50);
}
}
void setup() {
pinMode(LED, OUTPUT);
MIDI.begin(); // Launch MIDI with default options
// (input channel is default set to 1)
}
void loop() {
if (MIDI.read()) { // Is there a MIDI message incoming ?
switch(MIDI.getType()) { // Get the type of the message we caught
case midi::ProgramChange: // If it is a Program Change
BlinkLed(MIDI.getData1()); // Blink the LED a number of times
// correponding to the program number
// (0 to 127, it can last a while..)
break;
// See the online reference for other message types
default:
break;
}
}
}

41
res/install_local_mac Executable file
View File

@ -0,0 +1,41 @@
#!/bin/bash
# Use this script to install the library directy from the git clone.
if [[ -d /Applications/Arduino.app ]]
then
# Define locations
lib_path=/Applications/Arduino.app/Contents/Resources/Java/libraries/MIDI
if [[ -d $lib_path ]]
then
# Remove old lib
rm -rf $lib_path
fi
# Create folder
mkdir $lib_path
# Copy sources
cp ../src/MIDI.cpp $lib_path
cp ../src/MIDI.h $lib_path
cp ../src/midi_* $lib_path
# Copy resources
cp ../res/keywords.txt $lib_path
# Copy examples
mkdir $lib_path/examples
cp -r examples/* $lib_path/examples
# Copy doc
mkdir $lib_path/doc
cp -r ../doc/* $lib_path/doc
else
echo "Arduino application not found."
fi

37
res/install_mac Executable file
View File

@ -0,0 +1,37 @@
#!/bin/bash
# This script installs the Arduino MIDI Library into the Arduino application,
# so that every sketch can include it directly, without having to copy anything.
#
# To install the library, run this script by double-clicking it,
# it should be directly executable and seen as such by Mac OS X.
# If not, open a terminal, cd to the script location, and run ./install_mac
#
# Open the Arduino IDE, and you're ready to go!
# The script assumes the Arduino application
# is installed in the default location.
if [[ -d /Applications/Arduino.app ]]
then
# Define locations
lib_path=/Applications/Arduino.app/Contents/Resources/Java/libraries/MIDI
if [[ -d $lib_path ]]
then
# Remove old lib
rm -rf $lib_path
fi
# Create folder
mkdir $lib_path
# Install contents
cp -r * $lib_path
# Cleanup
rm $lib_path/install_mac
else
echo "Arduino application not found."
fi

108
res/keywords.txt Normal file
View File

@ -0,0 +1,108 @@
#######################################
# Syntax Coloring Map For Test
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
MIDI KEYWORD1
MIDI.h KEYWORD1
MidiInterface KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
send KEYWORD2
sendNoteOn KEYWORD2
sendNoteOff KEYWORD2
sendProgramChange KEYWORD2
sendControlChange KEYWORD2
sendPitchBend KEYWORD2
sendPolyPressure KEYWORD2
sendAfterTouch KEYWORD2
sendSysEx KEYWORD2
sendTimeCodeQuarterFrame KEYWORD2
sendSongPosition KEYWORD2
sendSongSelect KEYWORD2
sendTuneRequest KEYWORD2
sendRealTime KEYWORD2
begin KEYWORD2
read KEYWORD2
getType KEYWORD2
getChannel KEYWORD2
getData1 KEYWORD2
getData2 KEYWORD2
getSysExArray KEYWORD2
getFilterMode KEYWORD2
getThruState KEYWORD2
getInputChannel KEYWORD2
check KEYWORD2
delMsg KEYWORD2
delSysEx KEYWORD2
setInputChannel KEYWORD2
setStatus KEYWORD2
turnThruOn KEYWORD2
turnThruOff KEYWORD2
setThruFilterMode KEYWORD2
disconnectCallbackFromType KEYWORD2
setHandleNoteOff KEYWORD2
setHandleNoteOn KEYWORD2
setHandleAfterTouchPoly KEYWORD2
setHandleControlChange KEYWORD2
setHandleProgramChange KEYWORD2
setHandleAfterTouchChannel KEYWORD2
setHandlePitchBend KEYWORD2
setHandleSystemExclusive KEYWORD2
setHandleTimeCodeQuarterFrame KEYWORD2
setHandleSongPosition KEYWORD2
setHandleSongSelect KEYWORD2
setHandleTuneRequest KEYWORD2
setHandleClock KEYWORD2
setHandleStart KEYWORD2
setHandleContinue KEYWORD2
setHandleStop KEYWORD2
setHandleActiveSensing KEYWORD2
setHandleSystemReset KEYWORD2
getTypeFromStatusByte KEYWORD2
#######################################
# Instances (KEYWORD2)
#######################################
#######################################
# Constants (LITERAL1)
#######################################
# Namespace, considering it as a literal
midi LITERAL1
NoteOff LITERAL1
NoteOn LITERAL1
AfterTouchPoly LITERAL1
ControlChange LITERAL1
ProgramChange LITERAL1
AfterTouchChannel LITERAL1
PitchBend LITERAL1
SystemExclusive LITERAL1
TimeCodeQuarterFrame LITERAL1
SongPosition LITERAL1
SongSelect LITERAL1
TuneRequest LITERAL1
Clock LITERAL1
Start LITERAL1
Stop LITERAL1
Continue LITERAL1
ActiveSensing LITERAL1
SystemReset LITERAL1
InvalidType LITERAL1
Off LITERAL1
Full LITERAL1
SameChannel LITERAL1
DifferentChannel LITERAL1
MIDI_CHANNEL_OMNI LITERAL1
MIDI_CHANNEL_OFF LITERAL1
MIDI_BAUDRATE LITERAL1
MIDI_SYSEX_ARRAY_SIZE LITERAL1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

50
res/packaging Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
#
# Generate an archive with packaged content for easier delivery.
# The generated archive contains:
# - Source files (MIDI.cpp / MIDI.h)
# - Resources (keywords.txt)
# - Documentation (Doxygen)
# - Examples for Arduino IDE
# - Installation scripts
# Create a temporary destination folder
mkdir -p temp/doc
mkdir -p temp/examples
# Copy sources
cp ../src/* temp
# Copy resources
cp keywords.txt temp
cp install_* temp
rm temp/install_local_*
# Copy examples
cp -r examples/* temp/examples
# Generate & copy doc
cd ../doc
/Applications/Doxygen.app/Contents/Resources/doxygen Doxyfile
rm -rf latex
cd ../res
cp -r ../doc/* temp/doc
# Generate package
mv temp MIDI
zip -r MIDI.zip MIDI
# Remove temp folder
rm -rf MIDI
# Archive generated packaged
if [[ !( -d ../bin ) ]]
then
mkdir ../bin # Create archives location
fi
mv MIDI.zip ../bin/Arduino_MIDI_Library.zip

View File

@ -1,40 +0,0 @@
#!/bin/sh
#
# Generate an archive with packaged content for easier delivery.
# The generated archive contains:
# - Source files (MIDI.cpp / MIDI.h)
# - Resources (keywords.txt)
# - Examples for Arduino IDE
# - Installation scripts
cd "`dirname "${0}"`"
root="${PWD}/.."
build="$root/build/dist/MIDI"
echo "root: $root"
echo "build: $build"
# Create a destination directory structure
mkdir -p "$build/examples"
mkdir -p "$build/src"
# Copy sources
cp -rf "$root/src" "$build"
# Copy resources
cp -f "$root/keywords.txt" "$build/"
cp -f "$root/library.properties" "$build/"
cp -f "$root/library.json" "$build/"
cp -f "$root/LICENSE" "$build/"
# Copy examples
cp -rf "$root/examples" "$build"
# Generate package
cd "$build/.."
zip -r Arduino_MIDI_Library.zip MIDI
# Generate doc
cd "$root/doc"
doxygen

View File

@ -1,114 +0,0 @@
# -*- coding: utf-8 -*-
import rtmidi
import random
# ------------------------------------------------------------------------------
class Midi:
InvalidType = 0x00 # For notifying errors
NoteOff = 0x80 # Note Off
NoteOn = 0x90 # Note On
AfterTouchPoly = 0xA0 # Polyphonic AfterTouch
ControlChange = 0xB0 # Control Change / Channel Mode
ProgramChange = 0xC0 # Program Change
AfterTouchChannel = 0xD0 # Channel (monophonic) AfterTouch
PitchBend = 0xE0 # Pitch Bend
SystemExclusive = 0xF0 # System Exclusive
TimeCodeQuarterFrame = 0xF1 # System Common - MIDI Time Code Quarter Frame
SongPosition = 0xF2 # System Common - Song Position Pointer
SongSelect = 0xF3 # System Common - Song Select
TuneRequest = 0xF6 # System Common - Tune Request
Clock = 0xF8 # System Real Time - Timing Clock
Start = 0xFA # System Real Time - Start
Continue = 0xFB # System Real Time - Continue
Stop = 0xFC # System Real Time - Stop
ActiveSensing = 0xFE # System Real Time - Active Sensing
SystemReset = 0xFF # System Real Time - System Reset
@staticmethod
def getChannel(statusByte):
return statusByte & 0x0f;
@staticmethod
def getType(statusByte):
if statusByte >= 0xf0:
# System messages
return statusByte
else:
# Channel messages
return statusByte & 0xf0;
# ------------------------------------------------------------------------------
class MidiInterface:
def __init__(self, listenerCallback = None):
self.input = rtmidi.MidiIn()
self.output = rtmidi.MidiOut()
self.listenerCallback = listenerCallback
self.ports = self.getAvailablePorts()
self.port = self.connect(self.choosePorts())
# --------------------------------------------------------------------------
def handleMidiInput(self, message, timestamp):
midiData = message[0]
if self.listenerCallback:
self.listenerCallback(midiData)
def send(self, message):
print('Sending', message)
self.output.send_message(message)
# --------------------------------------------------------------------------
def getAvailablePorts(self):
return {
'input' : self.input.get_ports(),
'output': self.output.get_ports(),
}
def choosePorts(self):
return {
'input' : self.choosePort(self.ports['input'], 'input'),
'output': self.choosePort(self.ports['output'], 'output')
}
def choosePort(self, ports, direction):
if not ports:
print('No MIDI ports available, bailing out.')
return None
if len(ports) == 1:
return {
'id': 0,
'name': ports[0]
}
else:
# Give a choice
print('Multiple %s ports available, please make a choice:' % direction)
choices = dict()
for port, i in zip(ports, range(0, len(ports))):
choices[i] = port
print(' [%d]' % i, port)
choiceIndex = int(input('-> '))
return {
'id': choiceIndex,
'name': choices[choiceIndex]
}
# --------------------------------------------------------------------------
def connect(self, ports):
if not ports:
return None
print('Connecting input to %s' % ports['input']['name'])
print('Connecting output to %s' % ports['output']['name'])
self.input.set_callback(self.handleMidiInput)
self.input.open_port(ports['input']['id'])
self.output.open_port(ports['output']['id'])
return ports

View File

@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
class Tester:
def __init__(self, interface):
self.interface = interface
self.sent = None
self.expected = None
self.received = None
def handleMidiInput(self, data):
print('Recived data:', data)
self.received = data
def checkThru(self, message):
self.interface.send(message)
self.sent = message
self.expected = message
self.received = None
while not self.received:
pass
return self.expected == self.received

View File

@ -1,182 +0,0 @@
# -*- coding: utf-8 -*-
import sys
import os
import shutil
import subprocess
import argparse
from pprint import pprint
from midi import *
from tester import *
# ------------------------------------------------------------------------------
rootDir = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..'))
logsDir = os.path.join(rootDir, 'logs')
resDir = os.path.join(rootDir, 'res')
srcDir = os.path.join(rootDir, 'src')
# ------------------------------------------------------------------------------
class Dict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
# ------------------------------------------------------------------------------
class Arduino:
if sys.platform == 'darwin':
binary = '/Applications/Arduino.app/Contents/MacOS/JavaApplicationStub'
home = os.path.expanduser('~/Documents/Arduino')
elif sys.platform == 'win32':
binary = 'arduino.exe'
home = os.path.expanduser('~/My Documents/Arduino')
elif sys.platform == 'linux':
binary = 'arduino'
home = os.path.expanduser('~/Arduino')
else:
print('Unsupported platform %s' % str(sys.platform))
sys.exit(1)
libraryDir = os.path.join(home, 'libraries')
boards = [
Dict({
'name': 'Uno',
'id': 'arduino:avr:uno',
'port': None,
}),
Dict({
'name': 'Leonardo',
'id': 'arduino:avr:leonardo',
'port': None,
}),
Dict({
'name': 'Mega',
'id': 'arduino:avr:mega',
'port': None,
}),
Dict({
'name': 'Due',
'id': 'arduino:sam:due',
'port': None,
}),
]
def checkReturnCode(code):
if code == 0:
return True
if code == 1:
print('Operation failed.')
if code == 2:
print('File not found')
if code == 3:
print('Invalid argument')
return False
def verify(sketch, boardId):
return Arduino.checkReturnCode(subprocess.call([
Arduino.binary,
'--verify', sketch,
'--board', boardId,
'--verbose-build',
]))
#], stdout = open(os.devnull, 'wb')))
# ------------------------------------------------------------------------------
class ArduinoMidiLibrary:
def __init__(self):
self.path = os.path.join(Arduino.libraryDir, 'MIDI')
self.sources = self.getSources()
self.resources = self.getResources()
def getSources(self):
sources = dict()
for root, dirs, files in os.walk(srcDir):
for name, ext in [os.path.splitext(f) for f in files]:
if ext in ('.cpp', '.hpp', '.h'):
source = os.path.join(root, name + ext)
dest = os.path.join(self.path, os.path.relpath(source, srcDir))
sources[source] = dest
return sources
def getResources(self):
return {
os.path.join(resDir, 'keywords.txt'): os.path.join(self.path, 'keywords.txt'),
os.path.join(resDir, 'examples/'): os.path.join(self.path, 'examples/'),
}
def install(self):
payloads = dict(list(self.sources.items()) + list(self.resources.items()))
for s,d in payloads.items():
if not os.path.exists(os.path.dirname(d)):
os.makedirs(os.path.dirname(d))
if os.path.isfile(s):
shutil.copy2(s, d)
elif os.path.isdir(s):
if os.path.exists(d):
shutil.rmtree(d)
shutil.copytree(s, d)
def getInstalledExamples(self):
exDir = os.path.join(self.path, 'examples')
return [os.path.join(exDir, x, x + '.ino') for x in next(os.walk(exDir))[1]]
def validate(self):
for board in Arduino.boards:
# Validate examples
print('Validation for Arduino %s' % board.name)
for example in self.getInstalledExamples():
if not Arduino.verify(example, board.id):
print('{0:40} {1}'.format(os.path.basename(example), 'FAILED'))
return False
else:
print('{0:40} {1}'.format(os.path.basename(example), 'PASSED'))
return True
# ------------------------------------------------------------------------------
def main():
info = "Validator script for the Arduino MIDI Library."
arg_parser = argparse.ArgumentParser(description = info)
arg_parser.add_argument('--compile', '-c',
action="store_true",
help="Test compilation of the example sketches")
arg_parser.add_argument('--runtime', '-r',
action="store_true",
help="Test runtime")
args = arg_parser.parse_args()
if args.compile:
lib = ArduinoMidiLibrary()
lib.install()
if lib.validate():
print('Compilation test passed')
else:
print('Compilation test failed')
if args.runtime:
midiInterface = MidiInterface()
tester = Tester(midiInterface)
midiInterface.listenerCallback = tester.handleMidiInput
tester.checkThru([Midi.NoteOn, 64, 80])
tester.checkThru([Midi.AfterTouchChannel, 1])
tester.checkThru([2])
tester.checkThru([3])
tester.checkThru([Midi.NoteOn, 64, 0])
tester.checkThru([65, 127])
tester.checkThru([65, 0])
tester.checkThru([66, 127])
tester.checkThru([66, 0])
# ------------------------------------------------------------------------------
if __name__ == '__main__':
main()

View File

@ -1,15 +0,0 @@
increase_warning_level()
project(midi)
add_library(midi STATIC
midi_Namespace.h
midi_Defs.h
midi_Message.h
midi_Platform.h
midi_Settings.h
MIDI.cpp
MIDI.hpp
MIDI.h
serialMIDI.h
)

View File

@ -2,114 +2,743 @@
* @file MIDI.cpp
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino
* @author Francois Best
* @version 4.0
* @author Francois Best
* @date 24/02/11
* @license MIT - Copyright (c) 2015 Francois Best
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* license GPL Forty Seven Effects - 2011
*/
#include "MIDI.h"
// -----------------------------------------------------------------------------
#if !(MIDI_BUILD_INPUT) && !(MIDI_BUILD_OUTPUT)
# error To use MIDI, you need to enable at least input or output.
#endif
#if MIDI_BUILD_THRU && !(MIDI_BUILD_OUTPUT)
# error For thru to work, you need to enable output.
#endif
#if MIDI_BUILD_THRU && !(MIDI_BUILD_INPUT)
# error For thru to work, you need to enable input.
#endif
// -----------------------------------------------------------------------------
#if MIDI_AUTO_INSTANCIATE
# if MIDI_USE_SOFTWARE_SERIAL
# ifndef FSE_AVR
# include "../SoftwareSerial/SoftwareSerial.h"
SoftwareSerial softSerialClass(MIDI_SOFTSERIAL_RX_PIN,
MIDI_SOFTSERIAL_TX_PIN);
# else
# error Todo: implement SoftwareSerial for avr core.
# endif
# undef MIDI_SERIAL_PORT
# define MIDI_SERIAL_PORT softSerialClass
# else
# ifdef FSE_AVR
# include <hardware_Serial.h>
# else
# include "HardwareSerial.h"
# endif
# endif // MIDI_USE_SOFTWARE_SERIAL
MIDI_NAMESPACE::MidiInterface MIDI;
#endif // MIDI_AUTO_INSTANCIATE
// -----------------------------------------------------------------------------
BEGIN_MIDI_NAMESPACE
/*! \brief Encode System Exclusive messages.
SysEx messages are encoded to guarantee transmission of data bytes higher than
127 without breaking the MIDI protocol. Use this static method to convert the
data you want to send.
\param inData The data to encode.
\param outSysEx The output buffer where to store the encoded message.
\param inLength The length of the input buffer.
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
\return The length of the encoded output buffer.
@see decodeSysEx
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
*/
unsigned encodeSysEx(const byte* inData,
byte* outSysEx,
unsigned inLength,
bool inFlipHeaderBits)
// -----------------------------------------------------------------------------
/*! \brief Constructor for MidiInterface. */
MidiInterface::MidiInterface()
{
unsigned outLength = 0; // Num bytes in output array.
byte count = 0; // Num 7bytes in a block.
outSysEx[0] = 0;
for (unsigned i = 0; i < inLength; ++i)
{
const byte data = inData[i];
const byte msb = data >> 7;
const byte body = data & 0x7f;
outSysEx[0] |= (msb << (inFlipHeaderBits ? count : (6 - count)));
outSysEx[1 + count] = body;
if (count++ == 6)
{
outSysEx += 8;
outLength += 8;
outSysEx[0] = 0;
count = 0;
}
}
return outLength + count + (count != 0 ? 1 : 0);
#if MIDI_BUILD_INPUT && MIDI_USE_CALLBACKS
mNoteOffCallback = 0;
mNoteOnCallback = 0;
mAfterTouchPolyCallback = 0;
mControlChangeCallback = 0;
mProgramChangeCallback = 0;
mAfterTouchChannelCallback = 0;
mPitchBendCallback = 0;
mSystemExclusiveCallback = 0;
mTimeCodeQuarterFrameCallback = 0;
mSongPositionCallback = 0;
mSongSelectCallback = 0;
mTuneRequestCallback = 0;
mClockCallback = 0;
mStartCallback = 0;
mContinueCallback = 0;
mStopCallback = 0;
mActiveSensingCallback = 0;
mSystemResetCallback = 0;
#endif
}
/*! \brief Decode System Exclusive messages.
SysEx messages are encoded to guarantee transmission of data bytes higher than
127 without breaking the MIDI protocol. Use this static method to reassemble
your received message.
\param inSysEx The SysEx data received from MIDI in.
\param outData The output buffer where to store the decrypted message.
\param inLength The length of the input buffer.
\param inFlipHeaderBits True for Korg and other who store MSB in reverse order
\return The length of the output buffer.
@see encodeSysEx @see getSysExArrayLength
Code inspired from Ruin & Wesen's SysEx encoder/decoder - http://ruinwesen.com
/*! \brief Destructor for MidiInterface.
This is not really useful for the Arduino, as it is never called...
*/
unsigned decodeSysEx(const byte* inSysEx,
byte* outData,
unsigned inLength,
bool inFlipHeaderBits)
MidiInterface::~MidiInterface()
{
unsigned count = 0;
byte msbStorage = 0;
byte byteIndex = 0;
}
for (unsigned i = 0; i < inLength; ++i)
// -----------------------------------------------------------------------------
/*! \brief Call the begin method in the setup() function of the Arduino.
All parameters are set to their default values:
- Input channel set to 1 if no value is specified
- Full thru mirroring
*/
void MidiInterface::begin(Channel inChannel)
{
// Initialise the Serial port
MIDI_SERIAL_PORT.begin(MIDI_BAUDRATE);
#if MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif // MIDI_BUILD_OUTPUT && MIDI_USE_RUNNING_STATUS
#if MIDI_BUILD_INPUT
mInputChannel = inChannel;
mRunningStatus_RX = InvalidType;
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mMessage.valid = false;
mMessage.type = InvalidType;
mMessage.channel = 0;
mMessage.data1 = 0;
mMessage.data2 = 0;
#endif // MIDI_BUILD_INPUT
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU) // Thru
mThruFilterMode = Full;
mThruActivated = true;
#endif // Thru
}
// -----------------------------------------------------------------------------
// Output
// -----------------------------------------------------------------------------
#if MIDI_BUILD_OUTPUT
/*! \brief Generate and send a MIDI message from the values given.
\param inType The message type (see type defines for reference)
\param inData1 The first data byte.
\param inData2 The second data byte (if the message contains only 1 data byte,
set this one to 0).
\param inChannel The output channel on which the message will be sent
(values from 1 to 16). Note: you cannot send to OMNI.
This is an internal method, use it only if you need to send raw data
from your code, at your own risks.
*/
void MidiInterface::send(MidiType inType,
DataByte inData1,
DataByte inData2,
Channel inChannel)
{
// Then test if channel is valid
if (inChannel >= MIDI_CHANNEL_OFF ||
inChannel == MIDI_CHANNEL_OMNI ||
inType < NoteOff)
{
if ((i % 8) == 0)
#if MIDI_USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
return; // Don't send anything
}
if (inType <= PitchBend) // Channel messages
{
// Protection: remove MSBs on data
inData1 &= 0x7F;
inData2 &= 0x7F;
const StatusByte status = getStatus(inType, inChannel);
#if MIDI_USE_RUNNING_STATUS
// Check Running Status
if (mRunningStatus_TX != status)
{
msbStorage = inSysEx[i];
byteIndex = 6;
// New message, memorise and send header
mRunningStatus_TX = status;
MIDI_SERIAL_PORT.write(mRunningStatus_TX);
}
#else
// Don't care about running status, send the status byte.
MIDI_SERIAL_PORT.write(status);
#endif
// Then send data
MIDI_SERIAL_PORT.write(inData1);
if (inType != ProgramChange && inType != AfterTouchChannel)
MIDI_SERIAL_PORT.write(inData2);
return;
}
else if (inType >= TuneRequest && inType <= SystemReset)
sendRealTime(inType); // System Real-time and 1 byte.
}
#endif // MIDI_BUILD_OUTPUT
// -----------------------------------------------------------------------------
// Input
// -----------------------------------------------------------------------------
#if MIDI_BUILD_INPUT
/*! \brief Read a MIDI message from the serial port
using the main input channel (see setInputChannel() for reference).
\return True if a valid message has been stored in the structure, false if not.
A valid message is a message that matches the input channel. \n\n
If the Thru is enabled and the messages matches the filter,
it is sent back on the MIDI output.
*/
bool MidiInterface::read()
{
return read(mInputChannel);
}
/*! \brief Reading/thru-ing method, the same as read()
with a given input channel to read on.
*/
bool MidiInterface::read(Channel inChannel)
{
if (inChannel >= MIDI_CHANNEL_OFF)
return false; // MIDI Input disabled.
if (parse(inChannel))
{
if (inputFilter(inChannel))
{
#if (MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
thruFilter(inChannel);
#endif
#if MIDI_USE_CALLBACKS
launchCallback();
#endif
return true;
}
}
return false;
}
// -----------------------------------------------------------------------------
// Private method: MIDI parser
bool MidiInterface::parse(Channel inChannel)
{
const byte bytes_available = MIDI_SERIAL_PORT.available();
if (bytes_available == 0)
// No data available.
return false;
/* Parsing algorithm:
Get a byte from the serial buffer.
* If there is no pending message to be recomposed, start a new one.
- Find type and channel (if pertinent)
- Look for other bytes in buffer, call parser recursively,
until the message is assembled or the buffer is empty.
* Else, add the extracted byte to the pending message, and check validity.
When the message is done, store it.
*/
const byte extracted = MIDI_SERIAL_PORT.read();
if (mPendingMessageIndex == 0)
{
// Start a new pending message
mPendingMessage[0] = extracted;
// Check for running status first
switch (getTypeFromStatusByte(mRunningStatus_RX))
{
// Only these types allow Running Status:
case NoteOff:
case NoteOn:
case AfterTouchPoly:
case ControlChange:
case ProgramChange:
case AfterTouchChannel:
case PitchBend:
// If the status byte is not received, prepend it
// to the pending message
if (extracted < 0x80)
{
mPendingMessage[0] = mRunningStatus_RX;
mPendingMessage[1] = extracted;
mPendingMessageIndex = 1;
}
// Else: well, we received another status byte,
// so the running status does not apply here.
// It will be updated upon completion of this message.
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
{
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
mMessage.channel = (mPendingMessage[0] & 0x0F)+1;
mMessage.data1 = mPendingMessage[1];
// Save data2 only if applicable
if (mPendingMessageExpectedLenght == 3)
mMessage.data2 = mPendingMessage[2];
else
mMessage.data2 = 0;
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mMessage.valid = true;
return true;
}
break;
default:
// No running status
break;
}
switch (getTypeFromStatusByte(mPendingMessage[0]))
{
// 1 byte messages
case Start:
case Continue:
case Stop:
case Clock:
case ActiveSensing:
case SystemReset:
case TuneRequest:
// Handle the message type directly here.
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
mMessage.channel = 0;
mMessage.data1 = 0;
mMessage.data2 = 0;
mMessage.valid = true;
// \fix Running Status broken when receiving Clock messages.
// Do not reset all input attributes, Running Status must remain unchanged.
//resetInput();
// We still need to reset these
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
return true;
break;
// 2 bytes messages
case ProgramChange:
case AfterTouchChannel:
case TimeCodeQuarterFrame:
case SongSelect:
mPendingMessageExpectedLenght = 2;
break;
// 3 bytes messages
case NoteOn:
case NoteOff:
case ControlChange:
case PitchBend:
case AfterTouchPoly:
case SongPosition:
mPendingMessageExpectedLenght = 3;
break;
case SystemExclusive:
// The message can be any lenght
// between 3 and MIDI_SYSEX_ARRAY_SIZE bytes
mPendingMessageExpectedLenght = MIDI_SYSEX_ARRAY_SIZE;
mRunningStatus_RX = InvalidType;
break;
case InvalidType:
default:
// This is obviously wrong. Let's get the hell out'a here.
resetInput();
return false;
break;
}
// Then update the index of the pending message.
mPendingMessageIndex++;
#if USE_1BYTE_PARSING
// Message is not complete.
return false;
#else
// Call the parser recursively
// to parse the rest of the message.
return parse(inChannel);
#endif
}
else
{
// First, test if this is a status byte
if (extracted >= 0x80)
{
// Reception of status bytes in the middle of an uncompleted message
// are allowed only for interleaved Real Time message or EOX
switch (extracted)
{
case Clock:
case Start:
case Continue:
case Stop:
case ActiveSensing:
case SystemReset:
// Here we will have to extract the one-byte message,
// pass it to the structure for being read outside
// the MIDI class, and recompose the message it was
// interleaved into. Oh, and without killing the running status..
// This is done by leaving the pending message as is,
// it will be completed on next calls.
mMessage.type = (MidiType)extracted;
mMessage.data1 = 0;
mMessage.data2 = 0;
mMessage.channel = 0;
mMessage.valid = true;
return true;
break;
// End of Exclusive
case 0xF7:
if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive)
{
// Store System Exclusive array in midimsg structure
for (byte i=0;i<MIDI_SYSEX_ARRAY_SIZE;i++)
mMessage.sysex_array[i] = mPendingMessage[i];
mMessage.type = SystemExclusive;
// Get length
mMessage.data1 = (mPendingMessageIndex+1) & 0xFF;
mMessage.data2 = (mPendingMessageIndex+1) >> 8;
mMessage.channel = 0;
mMessage.valid = true;
resetInput();
return true;
}
else
{
// Well well well.. error.
resetInput();
return false;
}
break;
default:
break;
}
}
// Add extracted data byte to pending message
mPendingMessage[mPendingMessageIndex] = extracted;
// Now we are going to check if we have reached the end of the message
if (mPendingMessageIndex >= (mPendingMessageExpectedLenght-1))
{
// "FML" case: fall down here with an overflown SysEx..
// This means we received the last possible data byte that can fit
// the buffer. If this happens, try increasing MIDI_SYSEX_ARRAY_SIZE.
if (getTypeFromStatusByte(mPendingMessage[0]) == SystemExclusive)
{
resetInput();
return false;
}
mMessage.type = getTypeFromStatusByte(mPendingMessage[0]);
// Don't check if it is a Channel Message
mMessage.channel = (mPendingMessage[0] & 0x0F)+1;
mMessage.data1 = mPendingMessage[1];
// Save data2 only if applicable
if (mPendingMessageExpectedLenght == 3)
mMessage.data2 = mPendingMessage[2];
else
mMessage.data2 = 0;
// Reset local variables
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mMessage.valid = true;
// Activate running status (if enabled for the received type)
switch (mMessage.type)
{
case NoteOff:
case NoteOn:
case AfterTouchPoly:
case ControlChange:
case ProgramChange:
case AfterTouchChannel:
case PitchBend:
// Running status enabled: store it from received message
mRunningStatus_RX = mPendingMessage[0];
break;
default:
// No running status
mRunningStatus_RX = InvalidType;
break;
}
return true;
}
else
{
const byte body = inSysEx[i];
const byte shift = inFlipHeaderBits ? 6 - byteIndex : byteIndex;
const byte msb = byte(((msbStorage >> shift) & 1) << 7);
byteIndex--;
outData[count++] = msb | body;
// Then update the index of the pending message.
mPendingMessageIndex++;
#if USE_1BYTE_PARSING
// Message is not complete.
return false;
#else
// Call the parser recursively
// to parse the rest of the message.
return parse(inChannel);
#endif
}
}
return count;
// What are our chances to fall here?
return false;
}
// Private method: check if the received message is on the listened channel
bool MidiInterface::inputFilter(Channel inChannel)
{
// This method handles recognition of channel
// (to know if the message is destinated to the Arduino)
if (mMessage.type == InvalidType)
return false;
// First, check if the received message is Channel
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
{
// Then we need to know if we listen to it
if ((mMessage.channel == mInputChannel) ||
(mInputChannel == MIDI_CHANNEL_OMNI))
{
return true;
}
else
{
// We don't listen to this channel
return false;
}
}
else
{
// System messages are always received
return true;
}
}
// Private method: reset input attributes
void MidiInterface::resetInput()
{
mPendingMessageIndex = 0;
mPendingMessageExpectedLenght = 0;
mRunningStatus_RX = InvalidType;
}
// -----------------------------------------------------------------------------
#if MIDI_USE_CALLBACKS
// Private - launch callback function based on received type.
void MidiInterface::launchCallback()
{
// The order is mixed to allow frequent messages to trigger their callback faster.
switch (mMessage.type)
{
// Notes
case NoteOff: if (mNoteOffCallback != 0) mNoteOffCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
case NoteOn: if (mNoteOnCallback != 0) mNoteOnCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
// Real-time messages
case Clock: if (mClockCallback != 0) mClockCallback(); break;
case Start: if (mStartCallback != 0) mStartCallback(); break;
case Continue: if (mContinueCallback != 0) mContinueCallback(); break;
case Stop: if (mStopCallback != 0) mStopCallback(); break;
case ActiveSensing: if (mActiveSensingCallback != 0) mActiveSensingCallback(); break;
// Continuous controllers
case ControlChange: if (mControlChangeCallback != 0) mControlChangeCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
case PitchBend: if (mPitchBendCallback != 0) mPitchBendCallback(mMessage.channel,(int)((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)) + MIDI_PITCHBEND_MIN); break; // TODO: check this
case AfterTouchPoly: if (mAfterTouchPolyCallback != 0) mAfterTouchPolyCallback(mMessage.channel,mMessage.data1,mMessage.data2); break;
case AfterTouchChannel: if (mAfterTouchChannelCallback != 0) mAfterTouchChannelCallback(mMessage.channel,mMessage.data1); break;
case ProgramChange: if (mProgramChangeCallback != 0) mProgramChangeCallback(mMessage.channel,mMessage.data1); break;
case SystemExclusive: if (mSystemExclusiveCallback != 0) mSystemExclusiveCallback(mMessage.sysex_array,mMessage.data1); break;
// Occasional messages
case TimeCodeQuarterFrame: if (mTimeCodeQuarterFrameCallback != 0) mTimeCodeQuarterFrameCallback(mMessage.data1); break;
case SongPosition: if (mSongPositionCallback != 0) mSongPositionCallback((mMessage.data1 & 0x7F) | ((mMessage.data2 & 0x7F)<< 7)); break;
case SongSelect: if (mSongSelectCallback != 0) mSongSelectCallback(mMessage.data1); break;
case TuneRequest: if (mTuneRequestCallback != 0) mTuneRequestCallback(); break;
case SystemReset: if (mSystemResetCallback != 0) mSystemResetCallback(); break;
case InvalidType:
default:
break;
}
}
#endif // MIDI_USE_CALLBACKS
#endif // MIDI_BUILD_INPUT
// -----------------------------------------------------------------------------
// Thru
// -----------------------------------------------------------------------------
#if MIDI_BUILD_THRU
// This method is called upon reception of a message
// and takes care of Thru filtering and sending.
void MidiInterface::thruFilter(Channel inChannel)
{
/*
This method handles Soft-Thru filtering.
Soft-Thru filtering:
- All system messages (System Exclusive, Common and Real Time) are passed to output unless filter is set to Off
- Channel messages are passed to the output whether their channel is matching the input channel and the filter setting
*/
// If the feature is disabled, don't do anything.
if (!mThruActivated || (mThruFilterMode == Off))
return;
// First, check if the received message is Channel
if (mMessage.type >= NoteOff && mMessage.type <= PitchBend)
{
const bool filter_condition = ((mMessage.channel == mInputChannel) ||
(mInputChannel == MIDI_CHANNEL_OMNI));
// Now let's pass it to the output
switch (mThruFilterMode)
{
case Full:
send(mMessage.type,
mMessage.data1,
mMessage.data2,
mMessage.channel);
return;
break;
case SameChannel:
if (filter_condition)
{
send(mMessage.type,
mMessage.data1,
mMessage.data2,
mMessage.channel);
return;
}
break;
case DifferentChannel:
if (!filter_condition)
{
send(mMessage.type,
mMessage.data1,
mMessage.data2,
mMessage.channel);
return;
}
break;
case Off:
// Do nothing.
// Technically it's impossible to get there because
// the case was already tested earlier.
break;
default:
break;
}
}
else
{
// Send the message to the output
switch (mMessage.type)
{
// Real Time and 1 byte
case Clock:
case Start:
case Stop:
case Continue:
case ActiveSensing:
case SystemReset:
case TuneRequest:
sendRealTime(mMessage.type);
return;
break;
case SystemExclusive:
// Send SysEx (0xF0 and 0xF7 are included in the buffer)
sendSysEx(mMessage.data1,mMessage.sysex_array,true);
return;
break;
case SongSelect:
sendSongSelect(mMessage.data1);
return;
break;
case SongPosition:
sendSongPosition(mMessage.data1 | ((unsigned)mMessage.data2<<7));
return;
break;
case TimeCodeQuarterFrame:
sendTimeCodeQuarterFrame(mMessage.data1,mMessage.data2);
return;
break;
default:
break;
}
}
}
#endif // MIDI_BUILD_THRU
END_MIDI_NAMESPACE

View File

@ -2,306 +2,231 @@
* @file MIDI.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino
* @author Francois Best, lathoub
* @version 4.0
* @author Francois Best
* @date 24/02/11
* @license MIT - Copyright (c) 2015 Francois Best
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* license GPL Forty Seven Effects - 2011
*/
#pragma once
#include "midi_Defs.h"
#include "midi_Platform.h"
#include "midi_Settings.h"
#include "midi_Message.h"
#include "midi_Defs.h"
#include "serialMIDI.h"
#ifdef FSE_AVR
# include "hardware_Serial.h"
#else
# include "Arduino.h"
#endif
// -----------------------------------------------------------------------------
BEGIN_MIDI_NAMESPACE
#define MIDI_LIBRARY_VERSION 0x050000
#define MIDI_LIBRARY_VERSION_MAJOR 5
#define MIDI_LIBRARY_VERSION_MINOR 0
#define MIDI_LIBRARY_VERSION_PATCH 0
/*! \brief The main class for MIDI handling.
It is templated over the type of serial port to provide abstraction from
the hardware interface, meaning you can use HardwareSerial, SoftwareSerial
or ak47's Uart classes. The only requirement is that the class implements
the begin, read, write and available methods.
*/
template<class Transport, class _Settings = DefaultSettings, class _Platform = DefaultPlatform>
class MidiInterface
{
public:
typedef _Settings Settings;
typedef _Platform Platform;
typedef Message<Settings::SysExMaxSize> MidiMessage;
MidiInterface();
~MidiInterface();
public:
inline MidiInterface(Transport&);
inline ~MidiInterface();
public:
MidiInterface& begin(Channel inChannel = 1);
void begin(Channel inChannel = 1);
// -------------------------------------------------------------------------
// MIDI Output
#if MIDI_BUILD_OUTPUT
public:
inline MidiInterface& sendNoteOn(DataByte inNoteNumber,
inline void sendNoteOn(DataByte inNoteNumber,
DataByte inVelocity,
Channel inChannel);
inline MidiInterface& sendNoteOff(DataByte inNoteNumber,
inline void sendNoteOff(DataByte inNoteNumber,
DataByte inVelocity,
Channel inChannel);
inline MidiInterface& sendProgramChange(DataByte inProgramNumber,
inline void sendProgramChange(DataByte inProgramNumber,
Channel inChannel);
inline MidiInterface& sendControlChange(DataByte inControlNumber,
DataByte inControlValue,
inline void sendControlChange(DataByte inControlNumber,
DataByte inControlValue,
Channel inChannel);
inline MidiInterface& sendPitchBend(int inPitchValue, Channel inChannel);
inline MidiInterface& sendPitchBend(double inPitchValue, Channel inChannel);
inline MidiInterface& sendPolyPressure(DataByte inNoteNumber,
inline void sendPitchBend(int inPitchValue, Channel inChannel);
inline void sendPitchBend(double inPitchValue, Channel inChannel);
inline void sendPolyPressure(DataByte inNoteNumber,
DataByte inPressure,
Channel inChannel) __attribute__ ((deprecated));
inline MidiInterface& sendAfterTouch(DataByte inPressure,
Channel inChannel);
inline void sendAfterTouch(DataByte inPressure,
Channel inChannel);
inline MidiInterface& sendAfterTouch(DataByte inNoteNumber,
DataByte inPressure,
Channel inChannel);
inline MidiInterface& sendSysEx(unsigned inLength,
inline void sendSysEx(unsigned int inLength,
const byte* inArray,
bool inArrayContainsBoundaries = false);
inline MidiInterface& sendTimeCodeQuarterFrame(DataByte inTypeNibble,
bool inArrayContainsBoundaries = false);
inline void sendTimeCodeQuarterFrame(DataByte inTypeNibble,
DataByte inValuesNibble);
inline MidiInterface& sendTimeCodeQuarterFrame(DataByte inData);
inline MidiInterface& sendSongPosition(unsigned inBeats);
inline MidiInterface& sendSongSelect(DataByte inSongNumber);
inline MidiInterface& sendTuneRequest();
inline MidiInterface& sendCommon(MidiType inType, unsigned = 0);
inline MidiInterface& sendClock() { return sendRealTime(Clock); };
inline MidiInterface& sendStart() { return sendRealTime(Start); };
inline MidiInterface& sendStop() { return sendRealTime(Stop); };
inline MidiInterface& sendTick() { return sendRealTime(Tick); };
inline MidiInterface& sendContinue() { return sendRealTime(Continue); };
inline MidiInterface& sendActiveSensing() { return sendRealTime(ActiveSensing); };
inline MidiInterface& sendSystemReset() { return sendRealTime(SystemReset); };
inline MidiInterface& sendRealTime(MidiType inType);
inline MidiInterface& beginRpn(unsigned inNumber,
Channel inChannel);
inline MidiInterface& sendRpnValue(unsigned inValue,
Channel inChannel);
inline MidiInterface& sendRpnValue(byte inMsb,
byte inLsb,
Channel inChannel);
inline MidiInterface& sendRpnIncrement(byte inAmount,
Channel inChannel);
inline MidiInterface& sendRpnDecrement(byte inAmount,
Channel inChannel);
inline MidiInterface& endRpn(Channel inChannel);
inline MidiInterface& beginNrpn(unsigned inNumber,
Channel inChannel);
inline MidiInterface& sendNrpnValue(unsigned inValue,
Channel inChannel);
inline MidiInterface& sendNrpnValue(byte inMsb,
byte inLsb,
Channel inChannel);
inline MidiInterface& sendNrpnIncrement(byte inAmount,
Channel inChannel);
inline MidiInterface& sendNrpnDecrement(byte inAmount,
Channel inChannel);
inline MidiInterface& endNrpn(Channel inChannel);
inline MidiInterface& send(const MidiMessage&);
inline void sendTimeCodeQuarterFrame(DataByte inData);
inline void sendSongPosition(unsigned int inBeats);
inline void sendSongSelect(DataByte inSongNumber);
inline void sendTuneRequest();
inline void sendRealTime(MidiType inType);
public:
MidiInterface& send(MidiType inType,
void send(MidiType inType,
DataByte inData1,
DataByte inData2,
Channel inChannel);
private:
inline StatusByte getStatus(MidiType inType,
Channel inChannel) const;
#endif // MIDI_BUILD_OUTPUT
// -------------------------------------------------------------------------
// MIDI Input
#if MIDI_BUILD_INPUT
public:
inline bool read();
inline bool read(Channel inChannel);
bool read();
bool read(Channel inChannel);
public:
inline MidiType getType() const;
inline Channel getChannel() const;
inline DataByte getData1() const;
inline DataByte getData2() const;
inline const byte* getSysExArray() const;
inline unsigned getSysExArrayLength() const;
inline unsigned int getSysExArrayLength() const;
inline bool check() const;
public:
inline Channel getInputChannel() const;
inline MidiInterface& setInputChannel(Channel inChannel);
inline void setInputChannel(Channel inChannel);
public:
static inline MidiType getTypeFromStatusByte(byte inStatus);
static inline Channel getChannelFromStatusByte(byte inStatus);
static inline bool isChannelMessage(MidiType inType);
static inline MidiType getTypeFromStatusByte(const byte inStatus);
private:
bool inputFilter(Channel inChannel);
bool parse(Channel inChannel);
void resetInput();
private:
StatusByte mRunningStatus_RX;
Channel mInputChannel;
byte mPendingMessage[3]; // SysEx are dumped into mMessage directly.
unsigned int mPendingMessageExpectedLenght;
unsigned int mPendingMessageIndex; // Extended to unsigned int for larger SysEx payloads.
Message mMessage;
// -------------------------------------------------------------------------
// Input Callbacks
#if MIDI_USE_CALLBACKS
public:
inline MidiInterface& setHandleMessage(void (*fptr)(const MidiMessage&)) { mMessageCallback = fptr; return *this; };
inline MidiInterface& setHandleError(ErrorCallback fptr) { mErrorCallback = fptr; return *this; };
inline MidiInterface& setHandleNoteOff(NoteOffCallback fptr) { mNoteOffCallback = fptr; return *this; };
inline MidiInterface& setHandleNoteOn(NoteOnCallback fptr) { mNoteOnCallback = fptr; return *this; };
inline MidiInterface& setHandleAfterTouchPoly(AfterTouchPolyCallback fptr) { mAfterTouchPolyCallback = fptr; return *this; };
inline MidiInterface& setHandleControlChange(ControlChangeCallback fptr) { mControlChangeCallback = fptr; return *this; };
inline MidiInterface& setHandleProgramChange(ProgramChangeCallback fptr) { mProgramChangeCallback = fptr; return *this; };
inline MidiInterface& setHandleAfterTouchChannel(AfterTouchChannelCallback fptr) { mAfterTouchChannelCallback = fptr; return *this; };
inline MidiInterface& setHandlePitchBend(PitchBendCallback fptr) { mPitchBendCallback = fptr; return *this; };
inline MidiInterface& setHandleSystemExclusive(SystemExclusiveCallback fptr) { mSystemExclusiveCallback = fptr; return *this; };
inline MidiInterface& setHandleTimeCodeQuarterFrame(TimeCodeQuarterFrameCallback fptr) { mTimeCodeQuarterFrameCallback = fptr; return *this; };
inline MidiInterface& setHandleSongPosition(SongPositionCallback fptr) { mSongPositionCallback = fptr; return *this; };
inline MidiInterface& setHandleSongSelect(SongSelectCallback fptr) { mSongSelectCallback = fptr; return *this; };
inline MidiInterface& setHandleTuneRequest(TuneRequestCallback fptr) { mTuneRequestCallback = fptr; return *this; };
inline MidiInterface& setHandleClock(ClockCallback fptr) { mClockCallback = fptr; return *this; };
inline MidiInterface& setHandleStart(StartCallback fptr) { mStartCallback = fptr; return *this; };
inline MidiInterface& setHandleTick(TickCallback fptr) { mTickCallback = fptr; return *this; };
inline MidiInterface& setHandleContinue(ContinueCallback fptr) { mContinueCallback = fptr; return *this; };
inline MidiInterface& setHandleStop(StopCallback fptr) { mStopCallback = fptr; return *this; };
inline MidiInterface& setHandleActiveSensing(ActiveSensingCallback fptr) { mActiveSensingCallback = fptr; return *this; };
inline MidiInterface& setHandleSystemReset(SystemResetCallback fptr) { mSystemResetCallback = fptr; return *this; };
inline MidiInterface& disconnectCallbackFromType(MidiType inType);
inline void setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity));
inline void setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity));
inline void setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure));
inline void setHandleControlChange(void (*fptr)(byte channel, byte number, byte value));
inline void setHandleProgramChange(void (*fptr)(byte channel, byte number));
inline void setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure));
inline void setHandlePitchBend(void (*fptr)(byte channel, int bend));
inline void setHandleSystemExclusive(void (*fptr)(byte * array, byte size));
inline void setHandleTimeCodeQuarterFrame(void (*fptr)(byte data));
inline void setHandleSongPosition(void (*fptr)(unsigned int beats));
inline void setHandleSongSelect(void (*fptr)(byte songnumber));
inline void setHandleTuneRequest(void (*fptr)(void));
inline void setHandleClock(void (*fptr)(void));
inline void setHandleStart(void (*fptr)(void));
inline void setHandleContinue(void (*fptr)(void));
inline void setHandleStop(void (*fptr)(void));
inline void setHandleActiveSensing(void (*fptr)(void));
inline void setHandleSystemReset(void (*fptr)(void));
inline void disconnectCallbackFromType(MidiType inType);
private:
void launchCallback();
void (*mMessageCallback)(const MidiMessage& message) = nullptr;
ErrorCallback mErrorCallback = nullptr;
NoteOffCallback mNoteOffCallback = nullptr;
NoteOnCallback mNoteOnCallback = nullptr;
AfterTouchPolyCallback mAfterTouchPolyCallback = nullptr;
ControlChangeCallback mControlChangeCallback = nullptr;
ProgramChangeCallback mProgramChangeCallback = nullptr;
AfterTouchChannelCallback mAfterTouchChannelCallback = nullptr;
PitchBendCallback mPitchBendCallback = nullptr;
SystemExclusiveCallback mSystemExclusiveCallback = nullptr;
TimeCodeQuarterFrameCallback mTimeCodeQuarterFrameCallback = nullptr;
SongPositionCallback mSongPositionCallback = nullptr;
SongSelectCallback mSongSelectCallback = nullptr;
TuneRequestCallback mTuneRequestCallback = nullptr;
ClockCallback mClockCallback = nullptr;
StartCallback mStartCallback = nullptr;
TickCallback mTickCallback = nullptr;
ContinueCallback mContinueCallback = nullptr;
StopCallback mStopCallback = nullptr;
ActiveSensingCallback mActiveSensingCallback = nullptr;
SystemResetCallback mSystemResetCallback = nullptr;
void (*mNoteOffCallback)(byte channel, byte note, byte velocity);
void (*mNoteOnCallback)(byte channel, byte note, byte velocity);
void (*mAfterTouchPolyCallback)(byte channel, byte note, byte velocity);
void (*mControlChangeCallback)(byte channel, byte, byte);
void (*mProgramChangeCallback)(byte channel, byte);
void (*mAfterTouchChannelCallback)(byte channel, byte);
void (*mPitchBendCallback)(byte channel, int);
void (*mSystemExclusiveCallback)(byte * array, byte size);
void (*mTimeCodeQuarterFrameCallback)(byte data);
void (*mSongPositionCallback)(unsigned int beats);
void (*mSongSelectCallback)(byte songnumber);
void (*mTuneRequestCallback)(void);
void (*mClockCallback)(void);
void (*mStartCallback)(void);
void (*mContinueCallback)(void);
void (*mStopCallback)(void);
void (*mActiveSensingCallback)(void);
void (*mSystemResetCallback)(void);
#endif // MIDI_USE_CALLBACKS
#endif // MIDI_BUILD_INPUT
// -------------------------------------------------------------------------
// MIDI Soft Thru
#if MIDI_BUILD_THRU
public:
inline Thru::Mode getFilterMode() const;
inline MidiFilterMode getFilterMode() const;
inline bool getThruState() const;
inline MidiInterface& turnThruOn(Thru::Mode inThruFilterMode = Thru::Full);
inline MidiInterface& turnThruOff();
inline MidiInterface& setThruFilterMode(Thru::Mode inThruFilterMode);
inline void turnThruOn(MidiFilterMode inThruFilterMode = Full);
inline void turnThruOff();
inline void setThruFilterMode(MidiFilterMode inThruFilterMode);
private:
void thruFilter(byte inChannel);
// -------------------------------------------------------------------------
// MIDI Parsing
private:
bool parse();
inline void handleNullVelocityNoteOnAsNoteOff();
inline bool inputFilter(Channel inChannel);
inline void resetInput();
inline void updateLastSentTime();
// -------------------------------------------------------------------------
// Transport
public:
Transport* getTransport() { return &mTransport; };
private:
Transport& mTransport;
// -------------------------------------------------------------------------
// Internal variables
private:
Channel mInputChannel;
StatusByte mRunningStatus_RX;
StatusByte mRunningStatus_TX;
byte mPendingMessage[3];
unsigned mPendingMessageExpectedLength;
unsigned mPendingMessageIndex;
unsigned mCurrentRpnNumber;
unsigned mCurrentNrpnNumber;
bool mThruActivated : 1;
Thru::Mode mThruFilterMode : 7;
MidiMessage mMessage;
unsigned long mLastMessageSentTime;
unsigned long mLastMessageReceivedTime;
unsigned long mSenderActiveSensingPeriodicity;
bool mReceiverActiveSensingActivated;
int8_t mLastError;
MidiFilterMode mThruFilterMode : 7;
#endif // MIDI_BUILD_THRU
#if MIDI_USE_RUNNING_STATUS
private:
inline StatusByte getStatus(MidiType inType,
Channel inChannel) const;
StatusByte mRunningStatus_TX;
#endif // MIDI_USE_RUNNING_STATUS
};
// -----------------------------------------------------------------------------
unsigned encodeSysEx(const byte* inData,
byte* outSysEx,
unsigned inLength,
bool inFlipHeaderBits = false);
unsigned decodeSysEx(const byte* inSysEx,
byte* outData,
unsigned inLength,
bool inFlipHeaderBits = false);
END_MIDI_NAMESPACE
#include "MIDI.hpp"
// -----------------------------------------------------------------------------
#if MIDI_AUTO_INSTANCIATE
extern MIDI_NAMESPACE::MidiInterface MIDI;
#endif // MIDI_AUTO_INSTANCIATE
// -----------------------------------------------------------------------------
#include "midi_Inline.hpp"

File diff suppressed because it is too large Load Diff

View File

@ -2,40 +2,26 @@
* @file midi_Defs.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Definitions
* @author Francois Best, lathoub
* @version 4.0
* @author Francois Best
* @date 24/02/11
* @license MIT - Copyright (c) 2015 Francois Best
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* license GPL Forty Seven Effects - 2011
*/
#pragma once
#include "midi_Namespace.h"
#if ARDUINO
#include <Arduino.h>
// -----------------------------------------------------------------------------
#ifdef FSE_AVR
# include <core_Types.h>
#else
#include <inttypes.h>
typedef uint8_t byte;
# include <inttypes.h>
#endif
// -----------------------------------------------------------------------------
BEGIN_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
@ -46,77 +32,40 @@ BEGIN_MIDI_NAMESPACE
#define MIDI_PITCHBEND_MIN -8192
#define MIDI_PITCHBEND_MAX 8191
/*! Receiving Active Sensing
*/
static const uint16_t ActiveSensingTimeout = 300;
// -----------------------------------------------------------------------------
// Type definitions
#ifndef FSE_AVR
typedef uint8_t byte;
#endif
typedef byte StatusByte;
typedef byte DataByte;
typedef byte Channel;
typedef byte FilterMode;
// -----------------------------------------------------------------------------
// Errors
static const uint8_t ErrorParse = 0;
static const uint8_t ErrorActiveSensingTimeout = 1;
static const uint8_t WarningSplitSysEx = 2;
// -----------------------------------------------------------------------------
// Aliasing
using ErrorCallback = void (*)(int8_t);
using NoteOffCallback = void (*)(Channel channel, byte note, byte velocity);
using NoteOnCallback = void (*)(Channel channel, byte note, byte velocity);
using AfterTouchPolyCallback = void (*)(Channel channel, byte note, byte velocity);
using ControlChangeCallback = void (*)(Channel channel, byte, byte);
using ProgramChangeCallback = void (*)(Channel channel, byte);
using AfterTouchChannelCallback = void (*)(Channel channel, byte);
using PitchBendCallback = void (*)(Channel channel, int);
using SystemExclusiveCallback = void (*)(byte * array, unsigned size);
using TimeCodeQuarterFrameCallback = void (*)(byte data);
using SongPositionCallback = void (*)(unsigned beats);
using SongSelectCallback = void (*)(byte songnumber);
using TuneRequestCallback = void (*)(void);
using ClockCallback = void (*)(void);
using StartCallback = void (*)(void);
using TickCallback = void (*)(void);
using ContinueCallback = void (*)(void);
using StopCallback = void (*)(void);
using ActiveSensingCallback = void (*)(void);
using SystemResetCallback = void (*)(void);
// -----------------------------------------------------------------------------
/*! Enumeration of MIDI types */
enum MidiType: uint8_t
enum MidiType
{
InvalidType = 0x00, ///< For notifying errors
NoteOff = 0x80, ///< Channel Message - Note Off
NoteOn = 0x90, ///< Channel Message - Note On
AfterTouchPoly = 0xA0, ///< Channel Message - Polyphonic AfterTouch
ControlChange = 0xB0, ///< Channel Message - Control Change / Channel Mode
ProgramChange = 0xC0, ///< Channel Message - Program Change
AfterTouchChannel = 0xD0, ///< Channel Message - Channel (monophonic) AfterTouch
PitchBend = 0xE0, ///< Channel Message - Pitch Bend
NoteOff = 0x80, ///< Note Off
NoteOn = 0x90, ///< Note On
AfterTouchPoly = 0xA0, ///< Polyphonic AfterTouch
ControlChange = 0xB0, ///< Control Change / Channel Mode
ProgramChange = 0xC0, ///< Program Change
AfterTouchChannel = 0xD0, ///< Channel (monophonic) AfterTouch
PitchBend = 0xE0, ///< Pitch Bend
SystemExclusive = 0xF0, ///< System Exclusive
SystemExclusiveStart = SystemExclusive, ///< System Exclusive Start
TimeCodeQuarterFrame = 0xF1, ///< System Common - MIDI Time Code Quarter Frame
SongPosition = 0xF2, ///< System Common - Song Position Pointer
SongSelect = 0xF3, ///< System Common - Song Select
Undefined_F4 = 0xF4,
Undefined_F5 = 0xF5,
TuneRequest = 0xF6, ///< System Common - Tune Request
SystemExclusiveEnd = 0xF7, ///< System Exclusive End
Clock = 0xF8, ///< System Real Time - Timing Clock
Undefined_F9 = 0xF9,
Tick = Undefined_F9, ///< System Real Time - Timing Tick (1 tick = 10 milliseconds)
Start = 0xFA, ///< System Real Time - Start
Continue = 0xFB, ///< System Real Time - Continue
Stop = 0xFC, ///< System Real Time - Stop
Undefined_FD = 0xFD,
ActiveSensing = 0xFE, ///< System Real Time - Active Sensing
SystemReset = 0xFF, ///< System Real Time - System Reset
};
@ -124,24 +73,17 @@ enum MidiType: uint8_t
// -----------------------------------------------------------------------------
/*! Enumeration of Thru filter modes */
struct Thru
enum MidiFilterMode
{
enum Mode
{
Off = 0, ///< Thru disabled (nothing passes through).
Full = 1, ///< Fully enabled Thru (every incoming message is sent back).
SameChannel = 2, ///< Only the messages on the Input Channel will be sent back.
DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back.
};
Off = 0, ///< Thru disabled (nothing passes through).
Full = 1, ///< Fully enabled Thru (every incoming message is sent back).
SameChannel = 2, ///< Only the messages on the Input Channel will be sent back.
DifferentChannel = 3, ///< All the messages but the ones on the Input Channel will be sent back.
};
// -----------------------------------------------------------------------------
/*! \brief Enumeration of Control Change command numbers.
See the detailed controllers numbers & description here:
http://www.somascape.org/midi/tech/spec.html#ctrlnums
*/
enum MidiControlChangeNumber: uint8_t
enum MidiControlChangeNumber
{
// High resolution Continuous Controllers MSB (+32 for LSB) ----------------
BankSelect = 0,
@ -150,7 +92,7 @@ enum MidiControlChangeNumber: uint8_t
// CC3 undefined
FootController = 4,
PortamentoTime = 5,
DataEntryMSB = 6,
DataEntry = 6,
ChannelVolume = 7,
Balance = 8,
// CC9 undefined
@ -164,23 +106,7 @@ enum MidiControlChangeNumber: uint8_t
GeneralPurposeController2 = 17,
GeneralPurposeController3 = 18,
GeneralPurposeController4 = 19,
// CC20 to CC31 undefined
BankSelectLSB = 32,
ModulationWheelLSB = 33,
BreathControllerLSB = 34,
// CC35 undefined
FootControllerLSB = 36,
PortamentoTimeLSB = 37,
DataEntryLSB = 38,
ChannelVolumeLSB = 39,
BalanceLSB = 40,
// CC41 undefined
PanLSB = 42,
ExpressionControllerLSB = 43,
EffectControl1LSB = 44,
EffectControl2LSB = 45,
// CC46 to CC63 undefined
// Switches ----------------------------------------------------------------
Sustain = 64,
Portamento = 65,
@ -188,7 +114,7 @@ enum MidiControlChangeNumber: uint8_t
SoftPedal = 67,
Legato = 68,
Hold = 69,
// Low resolution continuous controllers -----------------------------------
SoundController1 = 70, ///< Synth: Sound Variation FX: Exciter On/Off
SoundController2 = 71, ///< Synth: Harmonic Content FX: Compressor On/Off
@ -211,14 +137,7 @@ enum MidiControlChangeNumber: uint8_t
Effects3 = 93, ///< Chorus send level
Effects4 = 94, ///< Celeste depth
Effects5 = 95, ///< Phaser depth
DataIncrement = 96,
DataDecrement = 97,
NRPNLSB = 98, ///< Non-Registered Parameter Number (LSB)
NRPNMSB = 99, ///< Non-Registered Parameter Number (MSB)
RPNLSB = 100, ///< Registered Parameter Number (LSB)
RPNMSB = 101, ///< Registered Parameter Number (MSB)
// CC102 to CC119 undefined
// Channel Mode messages ---------------------------------------------------
AllSoundOff = 120,
ResetAllControllers = 121,
@ -230,18 +149,49 @@ enum MidiControlChangeNumber: uint8_t
PolyModeOn = 127
};
struct RPN
// -----------------------------------------------------------------------------
/*! The midimsg structure contains decoded data
of a MIDI message read from the serial port
with read() or thru().
*/
struct Message
{
enum RegisteredParameterNumbers: uint16_t
{
PitchBendSensitivity = 0x0000,
ChannelFineTuning = 0x0001,
ChannelCoarseTuning = 0x0002,
SelectTuningProgram = 0x0003,
SelectTuningBank = 0x0004,
ModulationDepthRange = 0x0005,
NullFunction = (0x7f << 7) + 0x7f,
};
/*! The MIDI channel on which the message was recieved.
\n Value goes from 1 to 16.
*/
Channel channel;
/*! The type of the message
(see the MidiType enum for types reference)
*/
MidiType type;
/*! The first data byte.
\n Value goes from 0 to 127.
*/
DataByte data1;
/*! The second data byte.
If the message is only 2 bytes long, this one is null.
\n Value goes from 0 to 127.
*/
DataByte data2;
/*! System Exclusive dedicated byte array.
\n Array length is stocked on 16 bits,
in data1 (LSB) and data2 (MSB)
*/
DataByte sysex_array[MIDI_SYSEX_ARRAY_SIZE];
/*! This boolean indicates if the message is valid or not.
There is no channel consideration here,
validity means the message respects the MIDI norm.
*/
bool valid;
};
END_MIDI_NAMESPACE

477
src/midi_Inline.hpp Normal file
View File

@ -0,0 +1,477 @@
/*!
* @file midi_Inline.hpp
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Inline implementations
* @version 4.0
* @author Francois Best
* @date 24/02/11
* license GPL Forty Seven Effects - 2011
*/
#pragma once
BEGIN_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
// Output
// -----------------------------------------------------------------------------
#if MIDI_BUILD_OUTPUT
/*! \brief Send a Note On message
\param inNoteNumber Pitch value in the MIDI format (0 to 127).
\param inVelocity Note attack velocity (0 to 127). A NoteOn with 0 velocity
is considered as a NoteOff.
\param inChannel The channel on which the message will be sent (1 to 16).
Take a look at the values, names and frequencies of notes here:
http://www.phys.unsw.edu.au/jw/notes.html
*/
void MidiInterface::sendNoteOn(DataByte inNoteNumber,
DataByte inVelocity,
Channel inChannel)
{
send(NoteOn, inNoteNumber, inVelocity, inChannel);
}
/*! \brief Send a Note Off message
\param inNoteNumber Pitch value in the MIDI format (0 to 127).
\param inVelocity Release velocity (0 to 127).
\param inChannel The channel on which the message will be sent (1 to 16).
Note: you can send NoteOn with zero velocity to make a NoteOff, this is based
on the Running Status principle, to avoid sending status messages and thus
sending only NoteOn data. This method will always send a real NoteOff message.
Take a look at the values, names and frequencies of notes here:
http://www.phys.unsw.edu.au/jw/notes.html
*/
void MidiInterface::sendNoteOff(DataByte inNoteNumber,
DataByte inVelocity,
Channel inChannel)
{
send(NoteOff, inNoteNumber, inVelocity, inChannel);
}
/*! \brief Send a Program Change message
\param inProgramNumber The Program to select (0 to 127).
\param inChannel The channel on which the message will be sent (1 to 16).
*/
void MidiInterface::sendProgramChange(DataByte inProgramNumber,
Channel inChannel)
{
send(ProgramChange, inProgramNumber, 0, inChannel);
}
/*! \brief Send a Control Change message
\param ControlNumber The controller number (0 to 127).
\param ControlValue The value for the specified controller (0 to 127).
\param Channel The channel on which the message will be sent (1 to 16).
See the detailed controllers numbers & description here:
http://www.somascape.org/midi/tech/spec.html#ctrlnums
*/
void MidiInterface::sendControlChange(DataByte inControlNumber,
DataByte inControlValue,
Channel inChannel)
{
send(ControlChange, inControlNumber, inControlValue, inChannel);
}
/*! \brief Send a Polyphonic AfterTouch message (applies to a specified note)
\param NoteNumber The note to apply AfterTouch to (0 to 127).
\param Pressure The amount of AfterTouch to apply (0 to 127).
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MidiInterface::sendPolyPressure(DataByte inNoteNumber,
DataByte inPressure,
Channel inChannel)
{
send(AfterTouchPoly, inNoteNumber, inPressure, inChannel);
}
/*! \brief Send a MonoPhonic AfterTouch message (applies to all notes)
\param Pressure The amount of AfterTouch to apply to all notes.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MidiInterface::sendAfterTouch(DataByte inPressure,
Channel inChannel)
{
send(AfterTouchChannel, inPressure, 0, inChannel);
}
/*! \brief Send a Pitch Bend message using a signed integer value.
\param PitchValue The amount of bend to send (in a signed integer format),
between MIDI_PITCHBEND_MIN and MIDI_PITCHBEND_MAX,
center value is 0.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MidiInterface::sendPitchBend(int inPitchValue,
Channel inChannel)
{
const unsigned int bend = inPitchValue - MIDI_PITCHBEND_MIN;
send(PitchBend, (bend & 0x7F), (bend >> 7) & 0x7F, inChannel);
}
/*! \brief Send a Pitch Bend message using a floating point value.
\param PitchValue The amount of bend to send (in a floating point format),
between -1.0f (maximum downwards bend)
and +1.0f (max upwards bend), center value is 0.0f.
\param Channel The channel on which the message will be sent (1 to 16).
*/
void MidiInterface::sendPitchBend(double inPitchValue,
Channel inChannel)
{
const int value = inPitchValue * MIDI_PITCHBEND_MAX;
sendPitchBend(value, inChannel);
}
/*! \brief Generate and send a System Exclusive frame.
\param length The size of the array to send
\param array The byte array containing the data to send
\param ArrayContainsBoundaries When set to 'true', 0xF0 & 0xF7 bytes
(start & stop SysEx) will NOT be sent
(and therefore must be included in the array).
default value for ArrayContainsBoundaries is set to 'false' for compatibility
with previous versions of the library.
*/
void MidiInterface::sendSysEx(unsigned int inLength,
const byte* inArray,
bool inArrayContainsBoundaries)
{
if (inArrayContainsBoundaries == false)
{
MIDI_SERIAL_PORT.write(0xF0);
for (unsigned int i=0;i<inLength;++i)
MIDI_SERIAL_PORT.write(inArray[i]);
MIDI_SERIAL_PORT.write(0xF7);
}
else
{
for (unsigned int i=0;i<inLength;++i)
MIDI_SERIAL_PORT.write(inArray[i]);
}
#if MIDI_USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! \brief Send a Tune Request message.
When a MIDI unit receives this message,
it should tune its oscillators (if equipped with any).
*/
void MidiInterface::sendTuneRequest()
{
sendRealTime(TuneRequest);
}
/*! \brief Send a MIDI Time Code Quarter Frame.
\param TypeNibble MTC type
\param ValuesNibble MTC data
See MIDI Specification for more information.
*/
void MidiInterface::sendTimeCodeQuarterFrame(DataByte inTypeNibble,
DataByte inValuesNibble)
{
const byte data = ( ((inTypeNibble & 0x07) << 4) | (inValuesNibble & 0x0F) );
sendTimeCodeQuarterFrame(data);
}
/*! \brief Send a MIDI Time Code Quarter Frame.
See MIDI Specification for more information.
\param data if you want to encode directly the nibbles in your program,
you can send the byte here.
*/
void MidiInterface::sendTimeCodeQuarterFrame(DataByte inData)
{
MIDI_SERIAL_PORT.write((byte)TimeCodeQuarterFrame);
MIDI_SERIAL_PORT.write(inData);
#if MIDI_USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! \brief Send a Song Position Pointer message.
\param Beats The number of beats since the start of the song.
*/
void MidiInterface::sendSongPosition(unsigned int inBeats)
{
MIDI_SERIAL_PORT.write((byte)SongPosition);
MIDI_SERIAL_PORT.write(inBeats & 0x7F);
MIDI_SERIAL_PORT.write((inBeats >> 7) & 0x7F);
#if MIDI_USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! \brief Send a Song Select message */
void MidiInterface::sendSongSelect(DataByte inSongNumber)
{
MIDI_SERIAL_PORT.write((byte)SongSelect);
MIDI_SERIAL_PORT.write(inSongNumber & 0x7F);
#if MIDI_USE_RUNNING_STATUS
mRunningStatus_TX = InvalidType;
#endif
}
/*! \brief Send a Real Time (one byte) message.
\param Type The available Real Time types are:
Start, Stop, Continue, Clock, ActiveSensing and SystemReset.
You can also send a Tune Request with this method.
@see MidiType
*/
void MidiInterface::sendRealTime(MidiType inType)
{
switch (inType)
{
case TuneRequest: // Not really real-time, but one byte anyway.
case Clock:
case Start:
case Stop:
case Continue:
case ActiveSensing:
case SystemReset:
MIDI_SERIAL_PORT.write((byte)inType);
break;
default:
// Invalid Real Time marker
break;
}
// Do not cancel Running Status for real-time messages as they can be
// interleaved within any message. Though, TuneRequest can be sent here,
// and as it is a System Common message, it must reset Running Status.
#if MIDI_USE_RUNNING_STATUS
if (inType == TuneRequest) mRunningStatus_TX = InvalidType;
#endif
}
// -----------------------------------------------------------------------------
StatusByte MidiInterface::getStatus(MidiType inType,
Channel inChannel) const
{
return ((byte)inType | ((inChannel - 1) & 0x0F));
}
#endif // MIDI_BUILD_OUTPUT
// -----------------------------------------------------------------------------
// Input
// -----------------------------------------------------------------------------
#if MIDI_BUILD_INPUT
/*! \brief Get the last received message's type
Returns an enumerated type. @see MidiType
*/
MidiType MidiInterface::getType() const
{
return mMessage.type;
}
/*! \brief Get the channel of the message stored in the structure.
\return Channel range is 1 to 16.
For non-channel messages, this will return 0.
*/
Channel MidiInterface::getChannel() const
{
return mMessage.channel;
}
/*! \brief Get the first data byte of the last received message. */
DataByte MidiInterface::getData1() const
{
return mMessage.data1;
}
/*! \brief Get the second data byte of the last received message. */
DataByte MidiInterface::getData2() const
{
return mMessage.data2;
}
/*! \brief Get the System Exclusive byte array.
@see getSysExArrayLength to get the array's length in bytes.
*/
const byte* MidiInterface::getSysExArray() const
{
return mMessage.sysex_array;
}
/*! \brief Get the lenght of the System Exclusive array.
It is coded using data1 as LSB and data2 as MSB.
\return The array's length, in bytes.
*/
unsigned int MidiInterface::getSysExArrayLength() const
{
const unsigned int size = ((unsigned)(mMessage.data2) << 8) | mMessage.data1;
return (size > MIDI_SYSEX_ARRAY_SIZE) ? MIDI_SYSEX_ARRAY_SIZE : size;
}
/*! \brief Check if a valid message is stored in the structure. */
bool MidiInterface::check() const
{
return mMessage.valid;
}
// -----------------------------------------------------------------------------
Channel MidiInterface::getInputChannel() const
{
return mInputChannel;
}
/*! \brief Set the value for the input MIDI channel
\param Channel the channel value. Valid values are 1 to 16, MIDI_CHANNEL_OMNI
if you want to listen to all channels, and MIDI_CHANNEL_OFF to disable input.
*/
void MidiInterface::setInputChannel(Channel inChannel)
{
mInputChannel = inChannel;
}
// -----------------------------------------------------------------------------
/*! \brief Extract an enumerated MIDI type from a status byte.
This is a utility static method, used internally,
made public so you can handle MidiTypes more easily.
*/
MidiType MidiInterface::getTypeFromStatusByte(const byte inStatus)
{
if ((inStatus < 0x80) ||
(inStatus == 0xF4) ||
(inStatus == 0xF5) ||
(inStatus == 0xF9) ||
(inStatus == 0xFD)) return InvalidType; // data bytes and undefined.
if (inStatus < 0xF0) return (MidiType)(inStatus & 0xF0); // Channel message, remove channel nibble.
else return (MidiType)inStatus;
}
// -----------------------------------------------------------------------------
#if MIDI_USE_CALLBACKS
void MidiInterface::setHandleNoteOff(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOffCallback = fptr; }
void MidiInterface::setHandleNoteOn(void (*fptr)(byte channel, byte note, byte velocity)) { mNoteOnCallback = fptr; }
void MidiInterface::setHandleAfterTouchPoly(void (*fptr)(byte channel, byte note, byte pressure)) { mAfterTouchPolyCallback = fptr; }
void MidiInterface::setHandleControlChange(void (*fptr)(byte channel, byte number, byte value)) { mControlChangeCallback = fptr; }
void MidiInterface::setHandleProgramChange(void (*fptr)(byte channel, byte number)) { mProgramChangeCallback = fptr; }
void MidiInterface::setHandleAfterTouchChannel(void (*fptr)(byte channel, byte pressure)) { mAfterTouchChannelCallback = fptr; }
void MidiInterface::setHandlePitchBend(void (*fptr)(byte channel, int bend)) { mPitchBendCallback = fptr; }
void MidiInterface::setHandleSystemExclusive(void (*fptr)(byte* array, byte size)) { mSystemExclusiveCallback = fptr; }
void MidiInterface::setHandleTimeCodeQuarterFrame(void (*fptr)(byte data)) { mTimeCodeQuarterFrameCallback = fptr; }
void MidiInterface::setHandleSongPosition(void (*fptr)(unsigned int beats)) { mSongPositionCallback = fptr; }
void MidiInterface::setHandleSongSelect(void (*fptr)(byte songnumber)) { mSongSelectCallback = fptr; }
void MidiInterface::setHandleTuneRequest(void (*fptr)(void)) { mTuneRequestCallback = fptr; }
void MidiInterface::setHandleClock(void (*fptr)(void)) { mClockCallback = fptr; }
void MidiInterface::setHandleStart(void (*fptr)(void)) { mStartCallback = fptr; }
void MidiInterface::setHandleContinue(void (*fptr)(void)) { mContinueCallback = fptr; }
void MidiInterface::setHandleStop(void (*fptr)(void)) { mStopCallback = fptr; }
void MidiInterface::setHandleActiveSensing(void (*fptr)(void)) { mActiveSensingCallback = fptr; }
void MidiInterface::setHandleSystemReset(void (*fptr)(void)) { mSystemResetCallback = fptr; }
/*! \brief Detach an external function from the given type.
Use this method to cancel the effects of setHandle********.
\param Type The type of message to unbind.
When a message of this type is received, no function will be called.
*/
void MidiInterface::disconnectCallbackFromType(MidiType inType)
{
switch (inType)
{
case NoteOff: mNoteOffCallback = 0; break;
case NoteOn: mNoteOnCallback = 0; break;
case AfterTouchPoly: mAfterTouchPolyCallback = 0; break;
case ControlChange: mControlChangeCallback = 0; break;
case ProgramChange: mProgramChangeCallback = 0; break;
case AfterTouchChannel: mAfterTouchChannelCallback = 0; break;
case PitchBend: mPitchBendCallback = 0; break;
case SystemExclusive: mSystemExclusiveCallback = 0; break;
case TimeCodeQuarterFrame: mTimeCodeQuarterFrameCallback = 0; break;
case SongPosition: mSongPositionCallback = 0; break;
case SongSelect: mSongSelectCallback = 0; break;
case TuneRequest: mTuneRequestCallback = 0; break;
case Clock: mClockCallback = 0; break;
case Start: mStartCallback = 0; break;
case Continue: mContinueCallback = 0; break;
case Stop: mStopCallback = 0; break;
case ActiveSensing: mActiveSensingCallback = 0; break;
case SystemReset: mSystemResetCallback = 0; break;
default:
break;
}
}
#endif // MIDI_USE_CALLBACKS
#endif // MIDI_BUILD_INPUT
// -----------------------------------------------------------------------------
// Thru
// -----------------------------------------------------------------------------
#if (MIDI_BUILD_INPUT && MIDI_BUILD_OUTPUT && MIDI_BUILD_THRU)
MidiFilterMode MidiInterface::getFilterMode() const
{
return mThruFilterMode;
}
bool MidiInterface::getThruState() const
{
return mThruActivated;
}
/*! \brief Setter method: turn message mirroring on. */
void MidiInterface::turnThruOn(MidiFilterMode inThruFilterMode)
{
mThruActivated = true;
mThruFilterMode = inThruFilterMode;
}
/*! \brief Setter method: turn message mirroring off. */
void MidiInterface::turnThruOff()
{
mThruActivated = false;
mThruFilterMode = Off;
}
/*! \brief Set the filter for thru mirroring
\param inThruFilterMode a filter mode
@see MidiFilterMode
*/
void MidiInterface::setThruFilterMode(MidiFilterMode inThruFilterMode)
{
mThruFilterMode = inThruFilterMode;
if (mThruFilterMode != Off)
mThruActivated = true;
else
mThruActivated = false;
}
#endif // MIDI_BUILD_THRU
// -----------------------------------------------------------------------------
END_MIDI_NAMESPACE

View File

@ -1,131 +0,0 @@
/*!
* @file midi_Message.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Message struct definition
* @author Francois Best
* @date 11/06/14
* @license MIT - Copyright (c) 2015 Francois Best
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include "midi_Namespace.h"
#include "midi_Defs.h"
#ifndef ARDUINO
#include <string.h>
#endif
BEGIN_MIDI_NAMESPACE
/*! The Message structure contains decoded data of a MIDI message
read from the serial port with read()
*/
template<unsigned SysExMaxSize>
struct Message
{
/*! Default constructor
\n Initializes the attributes with their default values.
*/
inline Message()
: channel(0)
, type(MIDI_NAMESPACE::InvalidType)
, data1(0)
, data2(0)
, valid(false)
{
memset(sysexArray, 0, sSysExMaxSize * sizeof(DataByte));
}
inline Message(const Message& inOther)
: channel(inOther.channel)
, type(inOther.type)
, data1(inOther.data1)
, data2(inOther.data2)
, valid(inOther.valid)
, length(inOther.length)
{
if (type == midi::SystemExclusive)
{
memcpy(sysexArray, inOther.sysexArray, sSysExMaxSize * sizeof(DataByte));
}
}
/*! The maximum size for the System Exclusive array.
*/
static const unsigned sSysExMaxSize = SysExMaxSize;
/*! The MIDI channel on which the message was recieved.
\n Value goes from 1 to 16.
*/
Channel channel;
/*! The type of the message
(see the MidiType enum for types reference)
*/
MidiType type;
/*! The first data byte.
\n Value goes from 0 to 127.
*/
DataByte data1;
/*! The second data byte.
If the message is only 2 bytes long, this one is null.
\n Value goes from 0 to 127.
*/
DataByte data2;
/*! System Exclusive dedicated byte array.
\n Array length is stocked on 16 bits,
in data1 (LSB) and data2 (MSB)
*/
DataByte sysexArray[sSysExMaxSize];
/*! This boolean indicates if the message is valid or not.
There is no channel consideration here,
validity means the message respects the MIDI norm.
*/
bool valid;
/*! Total Length of the message.
*/
unsigned length;
inline unsigned getSysExSize() const
{
const unsigned size = unsigned(data2) << 8 | data1;
return size > sSysExMaxSize ? sSysExMaxSize : size;
}
inline bool isSystemRealTime () const
{
return (type & 0xf8) == 0xf8;
}
inline bool isSystemCommon () const
{
return (type & 0xf8) == 0xf0;
}
inline bool isChannelMessage () const
{
return (type & 0xf0) != 0xf0;
}
};
END_MIDI_NAMESPACE

View File

@ -2,27 +2,10 @@
* @file midi_Namespace.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Namespace declaration
* @author Francois Best
* @version 4.0
* @author Francois Best
* @date 24/02/11
* @license MIT - Copyright (c) 2015 Francois Best
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* license GPL Forty Seven Effects - 2011
*/
#pragma once

View File

@ -1,51 +0,0 @@
/*!
* @file midi_Platform.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Platform
* @license MIT - Copyright (c) 2015 Francois Best
* @author lathoub
* @date 22/03/20
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include "midi_Defs.h"
BEGIN_MIDI_NAMESPACE
#if ARDUINO
// DefaultPlatform is the Arduino Platform
struct DefaultPlatform
{
static unsigned long now() { return ::millis(); };
};
#else
struct DefaultPlatform
{
static unsigned long now() { return 0; };
};
#endif
END_MIDI_NAMESPACE

View File

@ -2,103 +2,64 @@
* @file midi_Settings.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Settings
* @author Francois Best
* @version 3.5
* @author Francois Best
* @date 24/02/11
* @license MIT - Copyright (c) 2015 Francois Best
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* license GPL Forty Seven Effects - 2011
*/
#pragma once
#include "midi_Defs.h"
#include "midi_Namespace.h"
BEGIN_MIDI_NAMESPACE
/*! \brief Default Settings for the MIDI Library.
// -----------------------------------------------------------------------------
To change the default settings, don't edit them there, create a subclass and
override the values in that subclass, then use the MIDI_CREATE_CUSTOM_INSTANCE
macro to create your instance. The settings you don't override will keep their
default value. Eg:
\code{.cpp}
struct MySettings : public MIDI_NAMESPACE::DefaultSettings
{
static const unsigned SysExMaxSize = 1024; // Accept SysEx messages up to 1024 bytes long.
};
// Here are a few settings you can change to customize
// the library for your own project. You can for example
// choose to compile only parts of it so you gain flash
// space and optimise the speed of your sketch.
MIDI_CREATE_CUSTOM_INSTANCE(HardwareSerial, Serial2, MIDI, MySettings);
\endcode
*/
struct DefaultSettings
{
/*! Running status enables short messages when sending multiple values
of the same type and channel.\n
Must be disabled to send USB MIDI messages to a computer
Warning: does not work with some hardware, enable with caution.
*/
static const bool UseRunningStatus = false;
// -----------------------------------------------------------------------------
/*! NoteOn with 0 velocity should be handled as NoteOf.\n
Set to true to get NoteOff events when receiving null-velocity NoteOn messages.\n
Set to false to get NoteOn events when receiving null-velocity NoteOn messages.
*/
static const bool HandleNullVelocityNoteOnAsNoteOff = true;
// Compilation flags. Set them to 1 to build the associated feature
// (MIDI in, out, thru), or to 0 to disable the feature and save space.
// Note that the Thru can only work if in and out are enabled.
/*! Setting this to true will make MIDI.read parse only one byte of data for each
call when data is available. This can speed up your application if receiving
a lot of traffic, but might induce MIDI Thru and treatment latency.
*/
static const bool Use1ByteParsing = true;
#define MIDI_BUILD_INPUT 1
#define MIDI_BUILD_OUTPUT 1
#define MIDI_BUILD_THRU 1
/*! Maximum size of SysEx receivable. Decrease to save RAM if you don't expect
to receive SysEx, or adjust accordingly.
*/
static const unsigned SysExMaxSize = 128;
#define MIDI_USE_CALLBACKS 1
/*! Global switch to turn on/off sender ActiveSensing
Set to true to send ActiveSensing
Set to false will not send ActiveSensing message (will also save memory)
*/
static const bool UseSenderActiveSensing = false;
// Create a MIDI object automatically on the port defined with MIDI_SERIAL_PORT.
#define MIDI_AUTO_INSTANCIATE 1
/*! Global switch to turn on/off receiver ActiveSensing
Set to true to check for message timeouts (via ErrorCallback)
Set to false will not check if chained device are still alive (if they use ActiveSensing) (will also save memory)
*/
static const bool UseReceiverActiveSensing = false;
// -----------------------------------------------------------------------------
// Serial port configuration
/*! Active Sensing is intended to be sent
repeatedly by the sender to tell the receiver that a connection is alive. Use
of this message is optional. When initially received, the
receiver will expect to receive another Active Sensing
message each 300ms (max), and if it does not then it will
assume that the connection has been terminated. At
termination, the receiver will turn off all voices and return to
normal (non- active sensing) operation.
// Set the default port to use for MIDI.
#define MIDI_SERIAL_PORT Serial
Typical value is 250 (ms) - an Active Sensing command is send every 250ms.
(All Roland devices send Active Sensing every 250ms)
// Software serial options
#define MIDI_USE_SOFTWARE_SERIAL 0
Setting this field to 0 will disable sending MIDI active sensing.
*/
static const uint16_t SenderActiveSensingPeriodicity = 0;
};
#if MIDI_USE_SOFTWARE_SERIAL
#define MIDI_SOFTSERIAL_RX_PIN 1 // Pin number to use for MIDI Input
#define MIDI_SOFTSERIAL_TX_PIN 2 // Pin number to use for MIDI Output
#endif
// -----------------------------------------------------------------------------
// Misc. options
// Running status enables short messages when sending multiple values
// of the same type and channel.
// Set to 0 if you have troubles controlling your hardware.
#define MIDI_USE_RUNNING_STATUS 1
#define MIDI_USE_1BYTE_PARSING 1
#define MIDI_BAUDRATE 31250
#define MIDI_SYSEX_ARRAY_SIZE 255 // Maximum size is 65535 bytes.
END_MIDI_NAMESPACE

View File

@ -1,130 +0,0 @@
/*!
* @file serialMIDI.h
* Project Arduino MIDI Library
* @brief MIDI Library for the Arduino - Platform
* @license MIT - Copyright (c) 2015 Francois Best
* @author lathoub, Francois Best
* @date 22/03/20
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include "midi_Namespace.h"
BEGIN_MIDI_NAMESPACE
struct DefaultSerialSettings
{
/*! Override the default MIDI baudrate to transmit over USB serial, to
a decoding program such as Hairless MIDI (set baudrate to 115200)\n
http://projectgus.github.io/hairless-midiserial/
*/
static const long BaudRate = 31250;
};
template <class SerialPort, class _Settings = DefaultSerialSettings>
class SerialMIDI
{
typedef _Settings Settings;
public:
SerialMIDI(SerialPort& inSerial)
: mSerial(inSerial)
{
};
public:
static const bool thruActivated = true;
void begin()
{
// Initialise the Serial port
#if defined(AVR_CAKE)
mSerial. template open<Settings::BaudRate>();
#else
mSerial.begin(Settings::BaudRate);
#endif
}
void end()
{
mSerial.end();
}
bool beginTransmission(MidiType)
{
return true;
};
void write(byte value)
{
mSerial.write(value);
};
void endTransmission()
{
};
byte read()
{
return mSerial.read();
};
unsigned available()
{
return mSerial.available();
};
private:
SerialPort& mSerial;
};
END_MIDI_NAMESPACE
/*! \brief Create an instance of the library attached to a serial port.
You can use HardwareSerial or SoftwareSerial for the serial port.
Example: MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midi2);
Then call midi2.begin(), midi2.read() etc..
*/
#define MIDI_CREATE_INSTANCE(Type, SerialPort, Name) \
MIDI_NAMESPACE::SerialMIDI<Type> serial##Name(SerialPort);\
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<Type>> Name((MIDI_NAMESPACE::SerialMIDI<Type>&)serial##Name);
#if defined(ARDUINO_SAM_DUE) || defined(USBCON) || defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MKL26Z64__)
// Leonardo, Due and other USB boards use Serial1 by default.
#define MIDI_CREATE_DEFAULT_INSTANCE() \
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, MIDI);
#else
/*! \brief Create an instance of the library with default name, serial port
and settings, for compatibility with sketches written with pre-v4.2 MIDI Lib,
or if you don't bother using custom names, serial port or settings.
*/
#define MIDI_CREATE_DEFAULT_INSTANCE() \
MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI);
#endif
/*! \brief Create an instance of the library attached to a serial port with
custom settings.
@see DefaultSettings
@see MIDI_CREATE_INSTANCE
*/
#define MIDI_CREATE_CUSTOM_INSTANCE(Type, SerialPort, Name, Settings) \
MIDI_NAMESPACE::SerialMIDI<Type> serial##Name(SerialPort);\
MIDI_NAMESPACE::MidiInterface<MIDI_NAMESPACE::SerialMIDI<Type>, Settings> Name((MIDI_NAMESPACE::SerialMIDI<Type>&)serial##Name);

View File

@ -1,2 +0,0 @@
add_subdirectory(mocks)
add_subdirectory(unit-tests)

View File

@ -1,14 +0,0 @@
project(test-mocks)
add_library(test-mocks STATIC
test-mocks.cpp
test-mocks.h
test-mocks_Namespace.h
test-mocks_SerialMock.cpp
test-mocks_SerialMock.hpp
test-mocks_SerialMock.h
)
target_link_libraries(test-mocks
midi
)

View File

@ -1,5 +0,0 @@
#include "test-mocks.h"
BEGIN_TEST_MOCKS_NAMESPACE
END_TEST_MOCKS_NAMESPACE

View File

@ -1,7 +0,0 @@
#pragma once
#include "test-mocks_Namespace.h"
BEGIN_TEST_MOCKS_NAMESPACE
END_TEST_MOCKS_NAMESPACE

View File

@ -1,11 +0,0 @@
#pragma once
#define TEST_MOCKS_NAMESPACE test_mocks
#define BEGIN_TEST_MOCKS_NAMESPACE namespace TEST_MOCKS_NAMESPACE {
#define END_TEST_MOCKS_NAMESPACE }
#define USING_NAMESPACE_TEST_MOCKS using namespace TEST_MOCKS_NAMESPACE;
BEGIN_TEST_MOCKS_NAMESPACE
END_TEST_MOCKS_NAMESPACE

View File

@ -1,5 +0,0 @@
#include "test-mocks_SerialMock.h"
BEGIN_TEST_MOCKS_NAMESPACE
END_TEST_MOCKS_NAMESPACE

View File

@ -1,60 +0,0 @@
#pragma once
#include "test-mocks.h"
#include <inttypes.h>
BEGIN_TEST_MOCKS_NAMESPACE
template<typename DataType, int Size>
class RingBuffer
{
public:
RingBuffer();
~RingBuffer();
public:
int getLength() const;
bool isEmpty() const;
public:
void write(DataType inData);
void write(const DataType* inData, int inSize);
void clear();
public:
DataType peek() const;
DataType read();
void read(DataType* outData, int inSize);
private:
DataType mData[Size];
DataType* mWriteHead;
DataType* mReadHead;
};
template<int BufferSize>
class SerialMock
{
public:
SerialMock();
~SerialMock();
public: // Arduino Serial API
void begin(int inBaudrate);
int available() const;
void write(uint8_t inData);
uint8_t read();
public: // Test Helpers API
void moveTxToRx(); // Simulate loopback
public:
typedef RingBuffer<uint8_t, BufferSize> Buffer;
Buffer mTxBuffer;
Buffer mRxBuffer;
int mBaudrate;
};
END_TEST_MOCKS_NAMESPACE
#include "test-mocks_SerialMock.hpp"

View File

@ -1,153 +0,0 @@
#pragma once
BEGIN_TEST_MOCKS_NAMESPACE
template<typename DataType, int Size>
RingBuffer<DataType, Size>::RingBuffer()
: mWriteHead(mData)
, mReadHead(mData)
{
memset(mData, DataType(0), Size * sizeof(DataType));
}
template<typename DataType, int Size>
RingBuffer<DataType, Size>::~RingBuffer()
{
}
// -----------------------------------------------------------------------------
template<typename DataType, int Size>
int RingBuffer<DataType, Size>::getLength() const
{
if (mReadHead == mWriteHead)
{
return 0;
}
else if (mWriteHead > mReadHead)
{
return int(mWriteHead - mReadHead);
}
else
{
return int(mWriteHead - mData) + Size - int(mReadHead - mData);
}
}
template<typename DataType, int Size>
bool RingBuffer<DataType, Size>::isEmpty() const
{
return mReadHead == mWriteHead;
}
// -----------------------------------------------------------------------------
template<typename DataType, int Size>
void RingBuffer<DataType, Size>::write(DataType inData)
{
*mWriteHead++ = inData;
if (mWriteHead >= mData + Size)
{
mWriteHead = mData;
}
}
template<typename DataType, int Size>
void RingBuffer<DataType, Size>::write(const DataType* inData, int inSize)
{
for (int i = 0; i < inSize; ++i)
{
write(inData[i]);
}
}
template<typename DataType, int Size>
void RingBuffer<DataType, Size>::clear()
{
memset(mData, DataType(0), Size * sizeof(DataType));
mReadHead = mData;
mWriteHead = mData;
}
// -----------------------------------------------------------------------------
template<typename DataType, int Size>
DataType RingBuffer<DataType, Size>::peek() const
{
return *mReadHead;
}
template<typename DataType, int Size>
DataType RingBuffer<DataType, Size>::read()
{
const DataType data = *mReadHead++;
if (mReadHead >= mData + Size)
{
mReadHead = mData;
}
return data;
}
template<typename DataType, int Size>
void RingBuffer<DataType, Size>::read(DataType* outData, int inSize)
{
for (int i = 0; i < inSize; ++i)
{
outData[i] = read();
}
}
// =============================================================================
template<int BufferSize>
SerialMock<BufferSize>::SerialMock()
{
}
template<int BufferSize>
SerialMock<BufferSize>::~SerialMock()
{
}
// -----------------------------------------------------------------------------
template<int BufferSize>
void SerialMock<BufferSize>::begin(int inBaudrate)
{
mBaudrate = inBaudrate;
mTxBuffer.clear();
mRxBuffer.clear();
}
template<int BufferSize>
int SerialMock<BufferSize>::available() const
{
return mRxBuffer.getLength();
}
template<int BufferSize>
void SerialMock<BufferSize>::write(uint8_t inData)
{
mTxBuffer.write(inData);
}
template<int BufferSize>
uint8_t SerialMock<BufferSize>::read()
{
return mRxBuffer.read();
}
// -----------------------------------------------------------------------------
template<int BufferSize>
void SerialMock<BufferSize>::moveTxToRx()
{
mRxBuffer.clear();
const int size = mTxBuffer.getSize();
for (int i = 0; i < size; ++i)
{
mRxBuffer.write(mTxBuffer.read());
}
}
END_TEST_MOCKS_NAMESPACE

View File

@ -1,37 +0,0 @@
include(CMakeToolsHelpers OPTIONAL)
project(unit-tests)
include_directories(
"${unit-tests_SOURCE_DIR}"
"${gtest_SOURCE_DIR}/include"
"${gmock_SOURCE_DIR}/include"
)
add_executable(unit-tests
unit-tests.cpp
unit-tests.h
unit-tests_Namespace.h
tests/unit-tests_MidiMessage.cpp
tests/unit-tests_Settings.cpp
tests/unit-tests_Settings.h
tests/unit-tests_SysExCodec.cpp
tests/unit-tests_MidiInput.cpp
tests/unit-tests_MidiInputCallbacks.cpp
tests/unit-tests_MidiOutput.cpp
tests/unit-tests_MidiThru.cpp
)
target_link_libraries(unit-tests
gtest
gmock
midi
test-mocks
)
add_test(unit-tests ${unit-tests_BINARY_DIR}/unit-tests --gtest_color=yes)
add_custom_target(build-and-run-unit-tests
COMMAND ${CMAKE_CTEST_COMMAND} -V
DEPENDS unit-tests
)

View File

@ -1,991 +0,0 @@
#include "unit-tests.h"
#include "unit-tests_Settings.h"
#include <src/MIDI.h>
#include <test/mocks/test-mocks_SerialMock.h>
BEGIN_MIDI_NAMESPACE
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
using namespace testing;
USING_NAMESPACE_UNIT_TESTS
typedef test_mocks::SerialMock<32> SerialMock;
typedef midi::SerialMIDI<SerialMock> Transport;
typedef midi::MidiInterface<Transport> MidiInterface;
template<unsigned Size>
struct VariableSysExSettings : midi::DefaultSettings
{
static const unsigned SysExMaxSize = Size;
};
TEST(MidiInput, getTypeFromStatusByte)
{
// Channel Messages
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0x81), midi::NoteOff);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0x92), midi::NoteOn);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xa3), midi::AfterTouchPoly);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xb4), midi::ControlChange);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xc5), midi::ProgramChange);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xd6), midi::AfterTouchChannel);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xe7), midi::PitchBend);
// System Messages
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf0), midi::SystemExclusive);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf1), midi::TimeCodeQuarterFrame);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf2), midi::SongPosition);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf3), midi::SongSelect);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf6), midi::TuneRequest);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf8), midi::Clock);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xfa), midi::Start);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xfb), midi::Continue);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xfc), midi::Stop);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xfe), midi::ActiveSensing);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xff), midi::SystemReset);
// Invalid Messages
for (int i = 0; i < 0x80; ++i)
{
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(i), midi::InvalidType);
}
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf4), midi::InvalidType);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xf5), midi::InvalidType);
EXPECT_EQ(MidiInterface::getTypeFromStatusByte(0xfd), midi::InvalidType);
}
TEST(MidiInput, getChannelFromStatusByte)
{
EXPECT_EQ(MidiInterface::getChannelFromStatusByte(0x00), 1);
EXPECT_EQ(MidiInterface::getChannelFromStatusByte(0x80), 1);
EXPECT_EQ(MidiInterface::getChannelFromStatusByte(0x94), 5);
EXPECT_EQ(MidiInterface::getChannelFromStatusByte(0xaf), 16);
}
TEST(MidiInput, isChannelMessage)
{
EXPECT_EQ(MidiInterface::isChannelMessage(midi::InvalidType), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::NoteOff), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::NoteOn), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::AfterTouchPoly), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::ControlChange), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::ProgramChange), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::AfterTouchChannel), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::PitchBend), true);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::SystemExclusive), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::TimeCodeQuarterFrame), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::SongPosition), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::SongSelect), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::TuneRequest), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::Clock), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::Start), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::Continue), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::Stop), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::ActiveSensing), false);
EXPECT_EQ(MidiInterface::isChannelMessage(midi::SystemReset), false);
}
// --
TEST(MidiInput, begin)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
// Default channel
midi.begin();
EXPECT_EQ(serial.mBaudrate, 31250);
EXPECT_EQ(midi.getInputChannel(), 1);
// Specific channel
midi.begin(12);
EXPECT_EQ(serial.mBaudrate, 31250);
EXPECT_EQ(midi.getInputChannel(), 12);
}
TEST(MidiInput, initInputChannel)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
EXPECT_EQ(midi.getInputChannel(), 0);
midi.setInputChannel(12);
EXPECT_EQ(midi.getInputChannel(), 12);
}
TEST(MidiInput, initMessage)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
EXPECT_EQ(midi.getType(), midi::InvalidType);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.getSysExArrayLength(), unsigned(0));
EXPECT_EQ(midi.check(), false);
}
TEST(MidiInput, channelFiltering)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0x9b, 12, 34 };
midi.begin(4); // Mistmatching channel
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
}
TEST(MidiInput, noRxData)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
midi.begin();
EXPECT_EQ(midi.read(), false);
}
TEST(MidiInput, inputDisabled)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0x9b, 12, 34 };
midi.begin(MIDI_CHANNEL_OFF); // Invalid channel
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
}
TEST(MidiInput, multiByteParsing)
{
typedef VariableSettings<false, false> Settings;
typedef midi::MidiInterface<Transport, Settings> MultiByteMidiInterface;
SerialMock serial;
Transport transport(serial);
MultiByteMidiInterface midi(transport);
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0x9b, 12, 34 };
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), true);
}
TEST(MidiInput, noteOn)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 10;
static const byte rxData[rxSize] = {
0x9b, 12, 34,
0x9b, 56, 78,
12, 34, // Running status
56, 0 // NoteOn with null velocity interpreted as NoteOff
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
// First NoteOn
EXPECT_EQ(midi.getType(), midi::NoteOn);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOn);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 78);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOn);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOff);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, noteOff)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 8;
static const byte rxData[rxSize] = {
0x8b, 12, 34,
0x8b, 56, 78,
12, 34, // Running status
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
// First NoteOn
EXPECT_EQ(midi.getType(), midi::NoteOff);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOff);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 78);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOff);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
}
TEST(MidiInput, programChange)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = {
0xc3, 12, 34,
0xc4, 56, 78
};
midi.begin(MIDI_CHANNEL_OMNI);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ProgramChange);
EXPECT_EQ(midi.getChannel(), 4);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ProgramChange);
EXPECT_EQ(midi.getChannel(), 4);
EXPECT_EQ(midi.getData1(), 34);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ProgramChange);
EXPECT_EQ(midi.getChannel(), 5);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ProgramChange);
EXPECT_EQ(midi.getChannel(), 5);
EXPECT_EQ(midi.getData1(), 78);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, controlChange)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 8;
static const byte rxData[rxSize] = {
0xbb, 12, 34,
0xbb, 56, 78,
12, 34
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
// First NoteOn
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 78);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
}
TEST(MidiInput, pitchBend)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 8;
static const byte rxData[rxSize] = {
0xeb, 12, 34,
0xeb, 56, 78,
12, 34
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
// First NoteOn
EXPECT_EQ(midi.getType(), midi::PitchBend);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::PitchBend);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 78);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::PitchBend);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
}
TEST(MidiInput, afterTouchPoly)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 8;
static const byte rxData[rxSize] = {
0xab, 12, 34,
0xab, 56, 78,
12, 34
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
// First NoteOn
EXPECT_EQ(midi.getType(), midi::AfterTouchPoly);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::AfterTouchPoly);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 78);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::AfterTouchPoly);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
}
TEST(MidiInput, afterTouchChannel)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = {
0xd3, 12, 34,
0xd4, 56, 78
};
midi.begin(MIDI_CHANNEL_OMNI);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::AfterTouchChannel);
EXPECT_EQ(midi.getChannel(), 4);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::AfterTouchChannel);
EXPECT_EQ(midi.getChannel(), 4);
EXPECT_EQ(midi.getData1(), 34);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::AfterTouchChannel);
EXPECT_EQ(midi.getChannel(), 5);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::AfterTouchChannel);
EXPECT_EQ(midi.getChannel(), 5);
EXPECT_EQ(midi.getData1(), 78);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, sysExWithinBufferSize)
{
typedef VariableSysExSettings<1024> Settings;
typedef test_mocks::SerialMock<2048> LargerSerialMock;
typedef midi::SerialMIDI<LargerSerialMock> LargerTransport;
typedef midi::MidiInterface<LargerTransport, Settings> LargerMidiInterface;
LargerSerialMock serial;
LargerTransport transport(serial);
LargerMidiInterface midi(transport);
// Short Frame < 256
{
static const unsigned frameLength = 15;
static const byte frame[frameLength] = {
0xf0, 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 0xf7
};
midi.begin();
serial.mRxBuffer.write(frame, frameLength);
for (unsigned i = 0; i < frameLength - 1; ++i)
{
EXPECT_EQ(midi.read(), false);
}
EXPECT_EQ(midi.read(), true); // 0xf7
EXPECT_EQ(midi.getSysExArrayLength(), frameLength);
const std::vector<byte> sysExData(midi.getSysExArray(),
midi.getSysExArray() + frameLength);
EXPECT_THAT(sysExData, ElementsAreArray(frame));
}
// Long Frame
{
static const unsigned frameLength = 957;
static const byte frame[frameLength] = {
0xf0,
'L','o','r','e','m',' ','i','p','s','u','m',' ','d','o','l','o','r',' ','s','i','t',' ','a','m','e','t',',',' ',
'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',',',' ',
'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',',',' ',
'v','e','s','t','i','b','u','l','u','m',' ','e','r','o','s',' ','v','e','l',',',' ',
'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',',',' ',
'c','u','r','s','u','s',' ','v','e','l',' ','l','a','c','i','n','i','a',' ','n','o','n',',',' ',
'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',',',' ',
'l','a','c','u','s',' ','a','t',' ','p','u','l','v','i','n','a','r',' ','t','e','m','p','o','r',',',' ',
'l','e','o',' ','l','i','b','e','r','o',' ','v','o','l','u','t','p','a','t',' ','n','i','s','l',',',' ',
'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',',',' ',
'm','a','s','s','a',' ','v','e','l',' ','e','u','i','s','m','o','d',' ','i','n','t','e','r','d','u','m',',',' ',
'l','o','r','e','m',' ','m','i',' ','e','g','e','s','t','a','s',' ','e','l','i','t',',',' ',
'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',',',' ',
'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',',',' ',
'e','l','i','t',' ','e','t',' ','c','o','n','g','u','e',' ','p','u','l','v','i','n','a','r',',',' ',
'm','a','s','s','a',' ','v','e','l','i','t',' ','c','o','m','m','o','d','o',' ','v','e','l','i','t',',',' ',
'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',',',' ',
'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',',',' ',
'c','o','n','s','e','q','u','a','t',' ','s','i','t',' ','a','m','e','t',' ','l','i','g','u','l','a','.',
0xf7,
};
midi.begin();
serial.mRxBuffer.write(frame, frameLength);
for (unsigned i = 0; i < frameLength - 1; ++i)
{
EXPECT_EQ(midi.read(), false);
}
EXPECT_EQ(serial.mRxBuffer.getLength(), 1);
EXPECT_EQ(serial.mRxBuffer.peek(), 0xf7);
EXPECT_EQ(midi.read(), true);
}
}
TEST(MidiInput, sysExOverBufferSize)
{
typedef VariableSysExSettings<8> Settings;
typedef midi::MidiInterface<Transport, Settings> SmallMidiInterface;
SerialMock serial;
Transport transport(serial);
SmallMidiInterface midi(transport);
static const unsigned frameLength = 15;
static const byte frame[frameLength] = {
0xf0, 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 0xf7
};
midi.begin();
serial.mRxBuffer.write(frame, frameLength);
EXPECT_EQ(midi.read(), false); // start sysex f0
EXPECT_EQ(midi.read(), false); // H
EXPECT_EQ(midi.read(), false); // e
EXPECT_EQ(midi.read(), false); // l
EXPECT_EQ(midi.read(), false); // l
EXPECT_EQ(midi.read(), false); // o
EXPECT_EQ(midi.read(), false); // , message send and buffer cleared.
EXPECT_EQ(midi.read(), false); // start sysex
EXPECT_EQ(midi.read(), false); // (space)
EXPECT_EQ(midi.read(), false); // W
EXPECT_EQ(midi.read(), false); // o
EXPECT_EQ(midi.read(), false); // r
EXPECT_EQ(midi.read(), false); // l
EXPECT_EQ(midi.read(), false); // d
EXPECT_EQ(midi.read(), true); // end sysex
}
TEST(MidiInput, mtcQuarterFrame)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 4;
static const byte rxData[rxSize] = {
0xf1, 12,
0xf1, 42
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::TimeCodeQuarterFrame);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::TimeCodeQuarterFrame);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 42);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, songPosition)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = {
0xf2, 12, 34,
0xf2, 56, 78
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::SongPosition);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::SongPosition);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 56);
EXPECT_EQ(midi.getData2(), 78);
}
TEST(MidiInput, songSelect)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 4;
static const byte rxData[rxSize] = {
0xf3, 12,
0xf3, 42
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
// 1 byte parsing
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::SongSelect);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::SongSelect);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 42);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, tuneRequest)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 1;
static const byte rxData[rxSize] = {
0xf6
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::TuneRequest);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, realTime)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 8;
static const byte rxData[rxSize] = {
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Clock);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Tick);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Start);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Continue);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Stop);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false); // 0xfd = undefined
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ActiveSensing);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::SystemReset);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
}
// --
TEST(MidiInput, interleavedRealTime)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
// Interleaved Clocks between NoteOn / Off messages (with running status)
{
static const unsigned rxSize = 13;
static const byte rxData[rxSize] = {
0x9b, 12, 0xf8, 34,
12, 0,
42, 0xf8, 127,
0xf8,
42, 0xf8, 0
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Clock);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOn);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOff);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Clock);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOn);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 42);
EXPECT_EQ(midi.getData2(), 127);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Clock);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::Clock);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::NoteOff);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 42);
EXPECT_EQ(midi.getData2(), 0);
}
// Interleaved ActiveSensing between SysEx
{
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = {
0xf0, 12, 34, 0xfe, 56, 0xf7
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ActiveSensing);
EXPECT_EQ(midi.getChannel(), 0);
EXPECT_EQ(midi.getData1(), 0);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getSysExArrayLength(), rxSize - 1);
const std::vector<byte> sysExData(midi.getSysExArray(),
midi.getSysExArray() + rxSize - 1);
EXPECT_THAT(sysExData, ElementsAreArray({
0xf0, 12, 34, 56, 0xf7
}));
}
}
TEST(MidiInput, strayEox)
{
// A stray End of Exclusive will reset the parser, but should it ?
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 4;
static const byte rxData[rxSize] = {
0x8b, 42, 0xf7, 12
};
midi.begin(MIDI_CHANNEL_OMNI);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
}
TEST(MidiInput, strayUndefinedOneByteParsing)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
static const unsigned rxSize = 13;
static const byte rxData[rxSize] = {
0xbb, 12, 0xfd, 34,
12, 0,
42, 0xfd, 127,
0xfd,
42, 0xfd, 0
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false); // Invalid, should not reset parser
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 42);
EXPECT_EQ(midi.getData2(), 127);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 42);
EXPECT_EQ(midi.getData2(), 0);
}
TEST(MidiInput, strayUndefinedMultiByteParsing)
{
typedef VariableSettings<false, false> Settings;
typedef midi::MidiInterface<Transport, Settings> MultiByteMidiInterface;
SerialMock serial;
Transport transport(serial);
MultiByteMidiInterface midi(transport);
static const unsigned rxSize = 4;
static const byte rxData[rxSize] = {
0xbb, 12, 0xfd, 34,
};
midi.begin(12);
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.getType(), midi::ControlChange);
EXPECT_EQ(midi.getChannel(), 12);
EXPECT_EQ(midi.getData1(), 12);
EXPECT_EQ(midi.getData2(), 34);
}
END_UNNAMED_NAMESPACE

View File

@ -1,594 +0,0 @@
#include "unit-tests.h"
#include "unit-tests_Settings.h"
#include <src/MIDI.h>
#include <test/mocks/test-mocks_SerialMock.h>
BEGIN_MIDI_NAMESPACE
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
using namespace testing;
USING_NAMESPACE_UNIT_TESTS
template<unsigned Size>
struct VariableSysExSettings : midi::DefaultSettings
{
static const unsigned SysExMaxSize = Size;
};
typedef test_mocks::SerialMock<256> SerialMock;
typedef midi::SerialMIDI<SerialMock> Transport;
typedef VariableSysExSettings<256> Settings;
typedef midi::MidiInterface<Transport, Settings> MidiInterface;
MidiInterface* midi;
class MidiInputCallbacks : public Test
{
public:
MidiInputCallbacks()
: mTransport(mSerial)
, mMidi(mTransport)
{
}
virtual ~MidiInputCallbacks()
{
}
protected:
virtual void SetUp()
{
midi = &mMidi;
}
virtual void TearDown()
{
midi = nullptr;
}
protected:
SerialMock mSerial;
Transport mTransport;
MidiInterface mMidi;
};
// --
void handleNoteOn(byte inChannel, byte inPitch, byte inVelocity)
{
EXPECT_NE(midi, nullptr);
midi->sendNoteOn(inPitch, inVelocity, inChannel);
}
TEST_F(MidiInputCallbacks, noteOn)
{
mMidi.setHandleNoteOn(handleNoteOn);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0x9b, 12, 34 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::NoteOn);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 34);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
byte buffer[3] = { 0 };
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ContainerEq(rxData));
// Test null velocity note on
EXPECT_EQ(MidiInterface::Settings::HandleNullVelocityNoteOnAsNoteOff, true);
mSerial.mRxBuffer.write(0x9b);
mSerial.mRxBuffer.write(12);
mSerial.mRxBuffer.write(0);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::NoteOff);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 0);
}
// --
void handleNoteOff(byte inChannel, byte inPitch, byte inVelocity)
{
EXPECT_NE(midi, nullptr);
midi->sendNoteOff(inPitch, inVelocity, inChannel);
}
TEST_F(MidiInputCallbacks, noteOff)
{
mMidi.setHandleNoteOff(handleNoteOff);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0x8b, 12, 34 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::NoteOff);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 34);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
byte buffer[3] = { 0 };
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ContainerEq(rxData));
// Test null velocity note on
EXPECT_EQ(MidiInterface::Settings::HandleNullVelocityNoteOnAsNoteOff, true);
mSerial.mRxBuffer.write(0x9b);
mSerial.mRxBuffer.write(12);
mSerial.mRxBuffer.write(0);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::NoteOff);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x8b, 12, 0
}));
}
// --
void handleAfterTouchPoly(byte inChannel, byte inNote, byte inValue)
{
EXPECT_NE(midi, nullptr);
midi->sendAfterTouch(inNote, inValue, inChannel);
}
TEST_F(MidiInputCallbacks, afterTouchPoly)
{
mMidi.setHandleAfterTouchPoly(handleAfterTouchPoly);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0xab, 12, 34 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::AfterTouchPoly);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 34);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
byte buffer[3] = { 0 };
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleControlChange(byte inChannel, byte inNumber, byte inValue)
{
EXPECT_NE(midi, nullptr);
midi->sendControlChange(inNumber, inValue, inChannel);
}
TEST_F(MidiInputCallbacks, controlChange)
{
mMidi.setHandleControlChange(handleControlChange);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0xbb, 12, 34 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::ControlChange);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 34);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
byte buffer[3] = { 0 };
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleProgramChange(byte inChannel, byte inNumber)
{
EXPECT_NE(midi, nullptr);
midi->sendProgramChange(inNumber, inChannel);
}
TEST_F(MidiInputCallbacks, programChange)
{
mMidi.setHandleProgramChange(handleProgramChange);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 2;
static const byte rxData[rxSize] = { 0xcb, 12 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::ProgramChange);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2);
byte buffer[2] = { 0 };
mSerial.mTxBuffer.read(buffer, 2);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleAfterTouchChannel(byte inChannel, byte inPressure)
{
EXPECT_NE(midi, nullptr);
midi->sendAfterTouch(inPressure, inChannel);
}
TEST_F(MidiInputCallbacks, afterTouchChannel)
{
mMidi.setHandleAfterTouchChannel(handleAfterTouchChannel);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 2;
static const byte rxData[rxSize] = { 0xdb, 12 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::AfterTouchChannel);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2);
byte buffer[2] = { 0 };
mSerial.mTxBuffer.read(buffer, 2);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handlePitchBend(byte inChannel, int inValue)
{
EXPECT_NE(midi, nullptr);
midi->sendPitchBend(inValue, inChannel);
}
TEST_F(MidiInputCallbacks, pitchBend)
{
mMidi.setHandlePitchBend(handlePitchBend);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0xeb, 12, 34 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::PitchBend);
EXPECT_EQ(mMidi.getChannel(), 12);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 34);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
byte buffer[3] = { 0 };
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleSysEx(byte* inData, unsigned inSize)
{
EXPECT_NE(midi, nullptr);
midi->sendSysEx(inSize, inData, true);
}
TEST_F(MidiInputCallbacks, sysEx)
{
mMidi.setHandleSystemExclusive(handleSysEx);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 15;
static const byte rxData[rxSize] = {
0xf0, 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 0xf7
};
mSerial.mRxBuffer.write(rxData, rxSize);
for (unsigned i = 0; i < rxSize - 1; ++i)
{
EXPECT_EQ(mMidi.read(), false);
}
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::SystemExclusive);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getSysExArrayLength(), rxSize);
EXPECT_EQ(unsigned(mSerial.mTxBuffer.getLength()), rxSize);
const std::vector<byte> sysExData(mMidi.getSysExArray(),
mMidi.getSysExArray() + rxSize);
EXPECT_THAT(sysExData, ElementsAreArray(rxData));
}
TEST_F(MidiInputCallbacks, sysExLong)
{
mMidi.setHandleSystemExclusive(handleSysEx);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 210;
static const byte rxData[rxSize] = {
0xf0,
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
'H','e','l','l','o',',',' ','W','o','r','l','d','!',
0xf7
};
mSerial.mRxBuffer.write(rxData, rxSize);
for (unsigned i = 0; i < rxSize - 1; ++i)
{
EXPECT_EQ(mMidi.read(), false);
}
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::SystemExclusive);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getSysExArrayLength(), rxSize);
EXPECT_EQ(unsigned(mSerial.mTxBuffer.getLength()), rxSize);
const std::vector<byte> sysExData(mMidi.getSysExArray(),
mMidi.getSysExArray() + rxSize);
EXPECT_THAT(sysExData, ElementsAreArray(rxData));
}
// --
void handleMtcQuarterFrame(byte inData)
{
EXPECT_NE(midi, nullptr);
midi->sendTimeCodeQuarterFrame(inData);
}
TEST_F(MidiInputCallbacks, mtcQuarterFrame)
{
mMidi.setHandleTimeCodeQuarterFrame(handleMtcQuarterFrame);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 2;
static const byte rxData[rxSize] = { 0xf1, 12 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::TimeCodeQuarterFrame);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2);
byte buffer[2] = { 0 };
mSerial.mTxBuffer.read(buffer, 2);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleSongPosition(unsigned inBeats)
{
EXPECT_NE(midi, nullptr);
midi->sendSongPosition(inBeats);
}
TEST_F(MidiInputCallbacks, songPosition)
{
mMidi.setHandleSongPosition(handleSongPosition);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 3;
static const byte rxData[rxSize] = { 0xf2, 12, 34 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::SongPosition);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 34);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 3);
byte buffer[3] = { 0 };
mSerial.mTxBuffer.read(buffer, 3);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleSongSelect(byte inSongNumber)
{
EXPECT_NE(midi, nullptr);
midi->sendSongSelect(inSongNumber);
}
TEST_F(MidiInputCallbacks, songSelect)
{
mMidi.setHandleSongSelect(handleSongSelect);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 2;
static const byte rxData[rxSize] = { 0xf3, 12 };
mSerial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(mMidi.read(), false);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::SongSelect);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getData1(), 12);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 2);
byte buffer[2] = { 0 };
mSerial.mTxBuffer.read(buffer, 2);
EXPECT_THAT(buffer, ContainerEq(rxData));
}
// --
void handleTuneRequest()
{
EXPECT_NE(midi, nullptr);
midi->sendTuneRequest();
}
TEST_F(MidiInputCallbacks, tuneRequest)
{
mMidi.setHandleTuneRequest(handleTuneRequest);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
mSerial.mRxBuffer.write(0xf6);
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), midi::TuneRequest);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getData1(), 0);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 1);
EXPECT_EQ(mSerial.mTxBuffer.read(), 0xf6);
}
// --
void handleClock()
{
EXPECT_NE(midi, nullptr);
midi->sendRealTime(midi::Clock);
}
void handleStart()
{
EXPECT_NE(midi, nullptr);
midi->sendRealTime(midi::Start);
}
void handleContinue()
{
EXPECT_NE(midi, nullptr);
midi->sendRealTime(midi::Continue);
}
void handleStop()
{
EXPECT_NE(midi, nullptr);
midi->sendRealTime(midi::Stop);
}
void handleActiveSensing()
{
EXPECT_NE(midi, nullptr);
midi->sendRealTime(midi::ActiveSensing);
}
void handleSystemReset()
{
EXPECT_NE(midi, nullptr);
midi->sendRealTime(midi::SystemReset);
}
TEST_F(MidiInputCallbacks, realTime)
{
mMidi.setHandleClock(handleClock);
mMidi.setHandleStart(handleStart);
mMidi.setHandleContinue(handleContinue);
mMidi.setHandleStop(handleStop);
mMidi.setHandleActiveSensing(handleActiveSensing);
mMidi.setHandleSystemReset(handleSystemReset);
mMidi.begin(MIDI_CHANNEL_OMNI);
mMidi.turnThruOff();
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = {
0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff
};
mSerial.mRxBuffer.write(rxData, rxSize);
static const midi::MidiType types[rxSize] = {
midi::Clock,
midi::Start,
midi::Continue,
midi::Stop,
midi::ActiveSensing,
midi::SystemReset,
};
for (unsigned i = 0; i < rxSize; ++i)
{
EXPECT_EQ(mMidi.read(), true);
EXPECT_EQ(mMidi.getType(), types[i]);
EXPECT_EQ(mMidi.getChannel(), 0);
EXPECT_EQ(mMidi.getData1(), 0);
EXPECT_EQ(mMidi.getData2(), 0);
EXPECT_EQ(mSerial.mTxBuffer.getLength(), 1);
const byte read = mSerial.mTxBuffer.read();
EXPECT_EQ(read, rxData[i]);
EXPECT_EQ(read, types[i]);
}
}
END_UNNAMED_NAMESPACE

View File

@ -1,83 +0,0 @@
#include "unit-tests.h"
#include <src/midi_Message.h>
BEGIN_MIDI_NAMESPACE
// Declare references:
// http://stackoverflow.com/questions/4891067/weird-undefined-symbols-of-static-constants-inside-a-struct-class
template<unsigned Size>
const unsigned Message<Size>::sSysExMaxSize;
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
TEST(MidiMessage, hasTheRightProperties)
{
typedef midi::Message<42> Message;
const Message message = Message();
EXPECT_EQ(message.channel, 0);
EXPECT_EQ(message.type, 0);
EXPECT_EQ(message.data1, 0);
EXPECT_EQ(message.data2, 0);
EXPECT_EQ(message.valid, false);
EXPECT_EQ(message.getSysExSize(), unsigned(0));
}
template<typename Message>
inline void setSysExSize(Message& ioMessage, unsigned inSize)
{
ioMessage.data2 = inSize >> 8; // MSB
ioMessage.data1 = inSize & 0xff; // LSB
}
TEST(MidiMessage, getSysExSize)
{
// Small message
{
typedef midi::Message<32> Message;
ASSERT_EQ(Message::sSysExMaxSize, unsigned(32));
Message message = Message();
const unsigned sizeUnder = 20;
setSysExSize(message, sizeUnder);
ASSERT_EQ(message.getSysExSize(), sizeUnder);
const unsigned sizeOver = 64;
setSysExSize(message, sizeOver);
ASSERT_EQ(message.getSysExSize(), unsigned(32));
}
// Medium message
{
typedef midi::Message<256> Message;
ASSERT_EQ(Message::sSysExMaxSize, unsigned(256));
Message message = Message();
const unsigned sizeUnder = 200;
setSysExSize(message, sizeUnder);
ASSERT_EQ(message.getSysExSize(), sizeUnder);
const unsigned sizeOver = 300;
setSysExSize(message, sizeOver);
ASSERT_EQ(message.getSysExSize(), unsigned(256));
}
// Large message
{
typedef midi::Message<1024> Message;
ASSERT_EQ(Message::sSysExMaxSize, unsigned(1024));
Message message = Message();
const unsigned sizeUnder = 1000;
setSysExSize(message, sizeUnder);
ASSERT_EQ(message.getSysExSize(), sizeUnder);
const unsigned sizeOver = 2000;
setSysExSize(message, sizeOver);
ASSERT_EQ(message.getSysExSize(), unsigned(1024));
}
}
END_UNNAMED_NAMESPACE

View File

@ -1,884 +0,0 @@
#include "unit-tests.h"
#include "unit-tests_Settings.h"
#include <src/MIDI.h>
#include <test/mocks/test-mocks_SerialMock.h>
BEGIN_MIDI_NAMESPACE
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
using namespace testing;
USING_NAMESPACE_UNIT_TESTS;
typedef test_mocks::SerialMock<32> SerialMock;
typedef midi::SerialMIDI<SerialMock> Transport;
typedef midi::MidiInterface<Transport> MidiInterface;
typedef std::vector<uint8_t> Buffer;
// --
TEST(MidiOutput, sendInvalid)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
midi.begin();
midi.send(midi::NoteOn, 42, 42, 42); // Invalid channel > OFF
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
midi.send(midi::InvalidType, 0, 0, 12); // Invalid type
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
midi.send(midi::NoteOn, 12, 42, MIDI_CHANNEL_OMNI); // OMNI not allowed
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
}
TEST(MidiOutput, sendGenericSingle)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(3);
midi.begin();
midi.send(midi::NoteOn, 47, 42, 12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42}));
}
TEST(MidiOutput, sendGenericWithRunningStatus)
{
typedef VariableSettings<true, false> Settings;
typedef midi::MidiInterface<Transport, Settings> RsMidiInterface;
SerialMock serial;
Transport transport(serial);
RsMidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(5);
midi.begin();
EXPECT_EQ(RsMidiInterface::Settings::UseRunningStatus, true);
EXPECT_EQ(serial.mTxBuffer.isEmpty(), true);
midi.send(midi::NoteOn, 47, 42, 12);
midi.send(midi::NoteOn, 42, 47, 12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 5);
serial.mTxBuffer.read(&buffer[0], 5);
EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 42, 47}));
}
TEST(MidiOutput, sendGenericWithoutRunningStatus)
{
typedef VariableSettings<false, true> Settings; // No running status
typedef midi::MidiInterface<Transport, Settings> NoRsMidiInterface;
SerialMock serial;
Transport transport(serial);
NoRsMidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
// Same status byte
midi.begin();
EXPECT_EQ(MidiInterface::Settings::UseRunningStatus, false);
EXPECT_EQ(serial.mTxBuffer.isEmpty(), true);
midi.send(midi::NoteOn, 47, 42, 12);
midi.send(midi::NoteOn, 42, 47, 12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 0x9b, 42, 47}));
// Different status byte
midi.begin();
midi.send(midi::NoteOn, 47, 42, 12);
midi.send(midi::NoteOff, 47, 42, 12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 0x8b, 47, 42}));
}
TEST(MidiOutput, sendGenericBreakingRunningStatus)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.send(midi::NoteOn, 47, 42, 12);
midi.send(midi::NoteOff, 47, 42, 12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0x9b, 47, 42, 0x8b, 47, 42}));
}
TEST(MidiOutput, sendGenericRealTimeShortcut)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.send(midi::Clock, 47, 42, 12);
midi.send(midi::Start, 47, 42, 12);
midi.send(midi::Continue, 47, 42, 12);
midi.send(midi::Stop, 47, 42, 12);
midi.send(midi::ActiveSensing, 47, 42, 12);
midi.send(midi::SystemReset, 47, 42, 12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff}));
}
// --
TEST(MidiOutput, sendNoteOn)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.sendNoteOn(10, 11, 12);
midi.sendNoteOn(12, 13, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0x9b, 10, 11, 0x93, 12, 13}));
}
TEST(MidiOutput, sendNoteOff)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.sendNoteOff(10, 11, 12);
midi.sendNoteOff(12, 13, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0x8b, 10, 11, 0x83, 12, 13}));
}
TEST(MidiOutput, sendProgramChange)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(4);
midi.begin();
midi.sendProgramChange(42, 12);
midi.sendProgramChange(47, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 4);
serial.mTxBuffer.read(&buffer[0], 4);
EXPECT_THAT(buffer, ElementsAreArray({0xcb, 42, 0xc3, 47}));
}
TEST(MidiOutput, sendControlChange)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.sendControlChange(42, 12, 12);
midi.sendControlChange(47, 12, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0xbb, 42, 12, 0xb3, 47, 12}));
}
TEST(MidiOutput, sendPitchBend)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
// Int signature - arbitrary values
{
buffer.clear();
buffer.resize(9);
midi.begin();
midi.sendPitchBend(0, 12);
midi.sendPitchBend(100, 4);
midi.sendPitchBend(-100, 7);
EXPECT_EQ(serial.mTxBuffer.getLength(), 9);
serial.mTxBuffer.read(&buffer[0], 9);
EXPECT_THAT(buffer, ElementsAreArray({0xeb, 0x00, 0x40,
0xe3, 0x64, 0x40,
0xe6, 0x1c, 0x3f}));
}
// Int signature - min/max
{
buffer.clear();
buffer.resize(9);
midi.begin();
midi.sendPitchBend(0, 12);
midi.sendPitchBend(MIDI_PITCHBEND_MAX, 4);
midi.sendPitchBend(MIDI_PITCHBEND_MIN, 7);
EXPECT_EQ(serial.mTxBuffer.getLength(), 9);
serial.mTxBuffer.read(&buffer[0], 9);
EXPECT_THAT(buffer, ElementsAreArray({0xeb, 0x00, 0x40,
0xe3, 0x7f, 0x7f,
0xe6, 0x00, 0x00}));
}
// Float signature
{
buffer.clear();
buffer.resize(9);
midi.begin();
midi.sendPitchBend(0.0, 12);
midi.sendPitchBend(1.0, 4);
midi.sendPitchBend(-1.0, 7);
EXPECT_EQ(serial.mTxBuffer.getLength(), 9);
serial.mTxBuffer.read(&buffer[0], 9);
EXPECT_THAT(buffer, ElementsAreArray({0xeb, 0x00, 0x40,
0xe3, 0x7f, 0x7f,
0xe6, 0x00, 0x00}));
}
}
TEST(MidiOutput, sendPolyPressure)
{
// Note: sendPolyPressure is deprecated in favor of sendAfterTouch, which
// now supports both mono and poly AfterTouch messages.
// This test is kept for coverage until removal of sendPolyPressure.
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.sendPolyPressure(42, 12, 12);
midi.sendPolyPressure(47, 12, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0xab, 42, 12, 0xa3, 47, 12}));
}
TEST(MidiOutput, sendAfterTouchMono)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(4);
midi.begin();
midi.sendAfterTouch(42, 12);
midi.sendAfterTouch(47, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 4);
serial.mTxBuffer.read(&buffer[0], 4);
EXPECT_THAT(buffer, ElementsAreArray({0xdb, 42, 0xd3, 47}));
}
TEST(MidiOutput, sendAfterTouchPoly)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.sendAfterTouch(42, 12, 12);
midi.sendAfterTouch(47, 12, 4);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0xab, 42, 12, 0xa3, 47, 12}));
}
TEST(MidiOutput, sendSysEx)
{
typedef test_mocks::SerialMock<1024> LargeSerialMock;
typedef midi::SerialMIDI<LargeSerialMock> LargeTransport;
typedef midi::MidiInterface<LargeTransport> LargeMidiInterface;
LargeSerialMock serial;
LargeTransport transport(serial);
LargeMidiInterface midi((LargeTransport&)transport);
Buffer buffer;
// Short frame
{
static const char* frame = "Hello, World!";
static const int frameLength = strlen(frame);
static const byte expected[] = {
0xf0,
'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!',
0xf7,
};
buffer.clear();
buffer.resize(frameLength + 2);
midi.begin();
midi.sendSysEx(frameLength, reinterpret_cast<const byte*>(frame), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), frameLength + 2);
serial.mTxBuffer.read(&buffer[0], frameLength + 2);
EXPECT_THAT(buffer, ElementsAreArray(expected));
}
// Long frame
{
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.";
static const int frameLength = strlen(frame);
static const byte expected[] = {
0xf0,
'L','o','r','e','m',' ','i','p','s','u','m',' ','d','o','l','o','r',' ','s','i','t',' ','a','m','e','t',',',' ',
'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',',',' ',
'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',',',' ',
'v','e','s','t','i','b','u','l','u','m',' ','e','r','o','s',' ','v','e','l',',',' ',
'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',',',' ',
'c','u','r','s','u','s',' ','v','e','l',' ','l','a','c','i','n','i','a',' ','n','o','n',',',' ',
'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',',',' ',
'l','a','c','u','s',' ','a','t',' ','p','u','l','v','i','n','a','r',' ','t','e','m','p','o','r',',',' ',
'l','e','o',' ','l','i','b','e','r','o',' ','v','o','l','u','t','p','a','t',' ','n','i','s','l',',',' ',
'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',',',' ',
'm','a','s','s','a',' ','v','e','l',' ','e','u','i','s','m','o','d',' ','i','n','t','e','r','d','u','m',',',' ',
'l','o','r','e','m',' ','m','i',' ','e','g','e','s','t','a','s',' ','e','l','i','t',',',' ',
'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',',',' ',
'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',',',' ',
'e','l','i','t',' ','e','t',' ','c','o','n','g','u','e',' ','p','u','l','v','i','n','a','r',',',' ',
'm','a','s','s','a',' ','v','e','l','i','t',' ','c','o','m','m','o','d','o',' ','v','e','l','i','t',',',' ',
'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',',',' ',
'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',',',' ',
'c','o','n','s','e','q','u','a','t',' ','s','i','t',' ','a','m','e','t',' ','l','i','g','u','l','a','.',
0xf7,
};
buffer.clear();
buffer.resize(frameLength + 2);
midi.begin();
midi.sendSysEx(frameLength, reinterpret_cast<const byte*>(frame), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), frameLength + 2);
serial.mTxBuffer.read(&buffer[0], frameLength + 2);
EXPECT_THAT(buffer, ElementsAreArray(expected));
}
// With boundaries included
{
static const byte frame[] = {
0xf0, 12, 17, 42, 47, 0xf7
};
buffer.clear();
buffer.resize(6);
midi.begin();
midi.sendSysEx(6, frame, true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray(frame));
}
}
TEST(MidiOutput, sendTimeCodeQuarterFrame)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
// Separate Nibbles
{
buffer.clear();
buffer.resize(4);
midi.begin();
midi.sendTimeCodeQuarterFrame(0x05, 0x0a);
midi.sendTimeCodeQuarterFrame(0xff, 0xff);
EXPECT_EQ(serial.mTxBuffer.getLength(), 4);
serial.mTxBuffer.read(&buffer[0], 4);
EXPECT_THAT(buffer, ElementsAreArray({0xf1, 0x5a,
0xf1, 0x7f}));
}
// Pre-encoded nibbles
{
buffer.clear();
buffer.resize(4);
midi.begin();
midi.sendTimeCodeQuarterFrame(12);
midi.sendTimeCodeQuarterFrame(42);
EXPECT_EQ(serial.mTxBuffer.getLength(), 4);
serial.mTxBuffer.read(&buffer[0], 4);
EXPECT_THAT(buffer, ElementsAreArray({0xf1, 0x0c,
0xf1, 0x2a}));
}
}
TEST(MidiOutput, sendSongPosition)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(6);
midi.begin();
midi.sendSongPosition(1234);
midi.sendSongPosition(4321);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({0xf2, 0x52, 0x09,
0xf2, 0x61, 0x21}));
}
TEST(MidiOutput, sendSongSelect)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(4);
midi.begin();
midi.sendSongSelect(12);
midi.sendSongSelect(42);
EXPECT_EQ(serial.mTxBuffer.getLength(), 4);
serial.mTxBuffer.read(&buffer[0], 4);
EXPECT_THAT(buffer, ElementsAreArray({0xf3, 12, 0xf3, 42}));
}
TEST(MidiOutput, sendTuneRequest)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
buffer.resize(1);
midi.begin();
midi.sendTuneRequest();
EXPECT_EQ(serial.mTxBuffer.getLength(), 1);
serial.mTxBuffer.read(&buffer[0], 1);
EXPECT_THAT(buffer, ElementsAreArray({0xf6}));
}
TEST(MidiOutput, sendRealTime)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
// Test valid RealTime messages
{
buffer.clear();
buffer.resize(6);
midi.begin();
midi.sendRealTime(midi::Clock);
midi.sendRealTime(midi::Start);
midi.sendRealTime(midi::Continue);
midi.sendRealTime(midi::Stop);
midi.sendRealTime(midi::ActiveSensing);
midi.sendRealTime(midi::SystemReset);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({
0xf8, 0xfa, 0xfb, 0xfc, 0xfe, 0xff
}));
}
// Test invalid messages
{
midi.begin();
midi.sendRealTime(midi::InvalidType);
midi.sendRealTime(midi::NoteOff);
midi.sendRealTime(midi::NoteOn);
midi.sendRealTime(midi::AfterTouchPoly);
midi.sendRealTime(midi::ControlChange);
midi.sendRealTime(midi::ProgramChange);
midi.sendRealTime(midi::AfterTouchChannel);
midi.sendRealTime(midi::PitchBend);
midi.sendRealTime(midi::SystemExclusive);
midi.sendRealTime(midi::TimeCodeQuarterFrame);
midi.sendRealTime(midi::SongPosition);
midi.sendRealTime(midi::SongSelect);
midi.sendRealTime(midi::TuneRequest);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
}
}
TEST(MidiOutput, RPN)
{
typedef VariableSettings<true, true> Settings;
typedef midi::MidiInterface<Transport, Settings> RsMidiInterface;
SerialMock serial;
Transport transport(serial);
RsMidiInterface midi((Transport&)transport);
Buffer buffer;
// 14-bit Value Single Frame
{
buffer.clear();
buffer.resize(13);
midi.begin();
midi.beginRpn(1242, 12);
midi.sendRpnValue(12345, 12);
midi.endRpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 13);
serial.mTxBuffer.read(&buffer[0], 13);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x64, 0x5a,
0x65, 0x09,
0x06, 0x60,
0x26, 0x39,
0x64, 0x7f,
0x65, 0x7f}));
}
// MSB/LSB Single Frame
{
buffer.clear();
buffer.resize(13);
midi.begin();
midi.beginRpn(1242, 12);
midi.sendRpnValue(12, 42, 12);
midi.endRpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 13);
serial.mTxBuffer.read(&buffer[0], 13);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x64, 0x5a,
0x65, 0x09,
0x06, 0x0c,
0x26, 0x2a,
0x64, 0x7f,
0x65, 0x7f}));
}
// Increment Single Frame
{
buffer.clear();
buffer.resize(11);
midi.begin();
midi.beginRpn(1242, 12);
midi.sendRpnIncrement(42, 12);
midi.endRpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 11);
serial.mTxBuffer.read(&buffer[0], 11);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x64, 0x5a,
0x65, 0x09,
0x60, 0x2a,
0x64, 0x7f,
0x65, 0x7f}));
}
// Decrement Single Frame
{
buffer.clear();
buffer.resize(11);
midi.begin();
midi.beginRpn(1242, 12);
midi.sendRpnDecrement(42, 12);
midi.endRpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 11);
serial.mTxBuffer.read(&buffer[0], 11);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x64, 0x5a,
0x65, 0x09,
0x61, 0x2a,
0x64, 0x7f,
0x65, 0x7f}));
}
// Multi Frame
{
buffer.clear();
buffer.resize(21);
midi.begin();
midi.beginRpn(1242, 12);
midi.sendRpnValue(12345, 12);
midi.sendRpnValue(12, 42, 12);
midi.sendRpnIncrement(42, 12);
midi.sendRpnDecrement(42, 12);
midi.endRpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 21);
serial.mTxBuffer.read(&buffer[0], 21);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x64, 0x5a,
0x65, 0x09,
0x06, 0x60,
0x26, 0x39,
0x06, 0x0c,
0x26, 0x2a,
0x60, 0x2a,
0x61, 0x2a,
0x64, 0x7f,
0x65, 0x7f}));
}
}
TEST(MidiOutput, NRPN)
{
typedef VariableSettings<true, true> Settings;
typedef midi::MidiInterface<Transport, Settings> RsMidiInterface;
SerialMock serial;
Transport transport(serial);
RsMidiInterface midi((Transport&)transport);
Buffer buffer;
// 14-bit Value Single Frame
{
buffer.clear();
buffer.resize(13);
midi.begin();
midi.beginNrpn(1242, 12);
midi.sendNrpnValue(12345, 12);
midi.endNrpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 13);
serial.mTxBuffer.read(&buffer[0], 13);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x62, 0x5a,
0x63, 0x09,
0x06, 0x60,
0x26, 0x39,
0x62, 0x7f,
0x63, 0x7f}));
}
// MSB/LSB Single Frame
{
buffer.clear();
buffer.resize(13);
midi.begin();
midi.beginNrpn(1242, 12);
midi.sendNrpnValue(12, 42, 12);
midi.endNrpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 13);
serial.mTxBuffer.read(&buffer[0], 13);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x62, 0x5a,
0x63, 0x09,
0x06, 0x0c,
0x26, 0x2a,
0x62, 0x7f,
0x63, 0x7f}));
}
// Increment Single Frame
{
buffer.clear();
buffer.resize(11);
midi.begin();
midi.beginNrpn(1242, 12);
midi.sendNrpnIncrement(42, 12);
midi.endNrpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 11);
serial.mTxBuffer.read(&buffer[0], 11);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x62, 0x5a,
0x63, 0x09,
0x60, 0x2a,
0x62, 0x7f,
0x63, 0x7f}));
}
// Decrement Single Frame
{
buffer.clear();
buffer.resize(11);
midi.begin();
midi.beginNrpn(1242, 12);
midi.sendNrpnDecrement(42, 12);
midi.endNrpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 11);
serial.mTxBuffer.read(&buffer[0], 11);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x62, 0x5a,
0x63, 0x09,
0x61, 0x2a,
0x62, 0x7f,
0x63, 0x7f}));
}
// Multi Frame
{
buffer.clear();
buffer.resize(21);
midi.begin();
midi.beginNrpn(1242, 12);
midi.sendNrpnValue(12345, 12);
midi.sendNrpnValue(12, 42, 12);
midi.sendNrpnIncrement(42, 12);
midi.sendNrpnDecrement(42, 12);
midi.endNrpn(12);
EXPECT_EQ(serial.mTxBuffer.getLength(), 21);
serial.mTxBuffer.read(&buffer[0], 21);
EXPECT_THAT(buffer, ElementsAreArray({0xbb,
0x62, 0x5a,
0x63, 0x09,
0x06, 0x60,
0x26, 0x39,
0x06, 0x0c,
0x26, 0x2a,
0x60, 0x2a,
0x61, 0x2a,
0x62, 0x7f,
0x63, 0x7f}));
}
}
TEST(MidiOutput, runningStatusCancellation)
{
typedef VariableSettings<true, false> Settings;
typedef midi::MidiInterface<Transport, Settings> RsMidiInterface;
SerialMock serial;
Transport transport(serial);
RsMidiInterface midi((Transport&)transport);
Buffer buffer;
static const unsigned sysExLength = 13;
static const byte sysEx[sysExLength] = {
'H','e','l','l','o',',',' ','W','o','r','l','d','!'
};
midi.begin();
midi.sendNoteOn(12, 34, 1);
midi.sendNoteOn(56, 78, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 5);
buffer.clear();
buffer.resize(5);
serial.mTxBuffer.read(&buffer[0], 5);
EXPECT_THAT(buffer, ElementsAreArray({
0x90, 12, 34, 56, 78
}));
midi.sendRealTime(midi::Clock); // Should not reset running status.
midi.sendNoteOn(12, 34, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
buffer.clear();
buffer.resize(3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0xf8, 12, 34
}));
midi.sendSysEx(sysExLength, sysEx); // Should reset running status.
midi.sendNoteOn(12, 34, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 18);
buffer.clear();
buffer.resize(18);
serial.mTxBuffer.read(&buffer[0], 18);
{
static const byte expected[] = {
0xf0, 'H','e','l','l','o',',',' ','W','o','r','l','d','!', 0xf7,
0x90, 12, 34
};
EXPECT_THAT(buffer, ElementsAreArray(expected));
}
midi.sendTimeCodeQuarterFrame(42); // Should reset running status.
midi.sendNoteOn(12, 34, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 5);
buffer.clear();
buffer.resize(5);
serial.mTxBuffer.read(&buffer[0], 5);
EXPECT_THAT(buffer, ElementsAreArray({
0xf1, 42,
0x90, 12, 34
}));
midi.sendSongPosition(42); // Should reset running status.
midi.sendNoteOn(12, 34, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
buffer.clear();
buffer.resize(6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({
0xf2, 42, 0,
0x90, 12, 34
}));
midi.sendSongSelect(42); // Should reset running status.
midi.sendNoteOn(12, 34, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 5);
buffer.clear();
buffer.resize(5);
serial.mTxBuffer.read(&buffer[0], 5);
EXPECT_THAT(buffer, ElementsAreArray({
0xf3, 42,
0x90, 12, 34
}));
midi.sendTuneRequest(); // Should reset running status.
midi.sendNoteOn(12, 34, 1);
EXPECT_EQ(serial.mTxBuffer.getLength(), 4);
buffer.clear();
buffer.resize(4);
serial.mTxBuffer.read(&buffer[0], 4);
EXPECT_THAT(buffer, ElementsAreArray({
0xf6,
0x90, 12, 34
}));
}
END_UNNAMED_NAMESPACE

View File

@ -1,389 +0,0 @@
#include "unit-tests.h"
#include "unit-tests_Settings.h"
#include <src/MIDI.h>
#include <test/mocks/test-mocks_SerialMock.h>
BEGIN_MIDI_NAMESPACE
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
using namespace testing;
USING_NAMESPACE_UNIT_TESTS
typedef test_mocks::SerialMock<32> SerialMock;
typedef midi::SerialMIDI<SerialMock> Transport;
typedef midi::MidiInterface<Transport> MidiInterface;
typedef std::vector<byte> Buffer;
template<unsigned Size>
struct VariableSysExSettings : midi::DefaultSettings
{
static const unsigned SysExMaxSize = Size;
};
// -----------------------------------------------------------------------------
TEST(MidiThru, defaultValues)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full);
midi.begin(); // Should not change the state
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full);
}
TEST(MidiThru, beginEnablesThru)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
midi.turnThruOff();
EXPECT_EQ(midi.getThruState(), false);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off);
midi.begin();
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full);
}
TEST(MidiThru, setGet)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
midi.turnThruOff();
EXPECT_EQ(midi.getThruState(), false);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off);
midi.turnThruOn();
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full);
midi.turnThruOn(midi::Thru::SameChannel);
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::SameChannel);
midi.turnThruOn(midi::Thru::DifferentChannel);
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::DifferentChannel);
midi.setThruFilterMode(midi::Thru::Full);
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Full);
midi.setThruFilterMode(midi::Thru::SameChannel);
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::SameChannel);
midi.setThruFilterMode(midi::Thru::DifferentChannel);
EXPECT_EQ(midi.getThruState(), true);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::DifferentChannel);
midi.setThruFilterMode(midi::Thru::Off);
EXPECT_EQ(midi.getThruState(), false);
EXPECT_EQ(midi.getFilterMode(), midi::Thru::Off);
}
TEST(MidiThru, off)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
midi.begin(MIDI_CHANNEL_OMNI);
midi.turnThruOff();
static const unsigned rxSize = 5;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
}
TEST(MidiThru, full)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(MIDI_CHANNEL_OMNI);
midi.setThruFilterMode(midi::Thru::Full);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
buffer.clear();
buffer.resize(3);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9b, 12, 34
}));
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
buffer.clear();
buffer.resize(3);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9c, 56, 78
}));
}
TEST(MidiThru, sameChannel)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(12);
midi.setThruFilterMode(midi::Thru::SameChannel);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
buffer.clear();
buffer.resize(3);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9b, 12, 34
}));
}
TEST(MidiThru, sameChannelOmni) // Acts like full
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(MIDI_CHANNEL_OMNI);
midi.setThruFilterMode(midi::Thru::SameChannel);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
buffer.clear();
buffer.resize(3);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9b, 12, 34
}));
buffer.clear();
buffer.resize(3);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3); // Not using TX running status
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9c, 56, 78
}));
}
TEST(MidiThru, differentChannel)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(12);
midi.setThruFilterMode(midi::Thru::DifferentChannel);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
buffer.clear();
buffer.resize(3);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9c, 56, 78
}));
}
TEST(MidiThru, differentChannelOmni) // Acts like off
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(MIDI_CHANNEL_OMNI);
midi.setThruFilterMode(midi::Thru::DifferentChannel);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
}
TEST(MidiThru, multiByteThru)
{
typedef VariableSettings<false, false> MultiByteParsing;
typedef midi::MidiInterface<Transport, MultiByteParsing> MultiByteMidiInterface;
SerialMock serial;
Transport transport(serial);
MultiByteMidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(MIDI_CHANNEL_OMNI);
midi.setThruFilterMode(midi::Thru::Full);
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 6);
buffer.clear();
buffer.resize(6);
serial.mTxBuffer.read(&buffer[0], 6);
EXPECT_THAT(buffer, ElementsAreArray({
0x9b, 12, 34, 0x9b, 56, 78
}));
}
TEST(MidiThru, withTxRunningStatus)
{
typedef VariableSettings<true, true> Settings;
typedef midi::MidiInterface<Transport, Settings> RsMidiInterface;
SerialMock serial;
Transport transport(serial);
RsMidiInterface midi((Transport&)transport);
Buffer buffer;
midi.begin(MIDI_CHANNEL_OMNI);
midi.setThruFilterMode(midi::Thru::Full);
static const unsigned rxSize = 5;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
buffer.clear();
buffer.resize(3);
EXPECT_EQ(serial.mTxBuffer.getLength(), 3);
serial.mTxBuffer.read(&buffer[0], 3);
EXPECT_THAT(buffer, ElementsAreArray({
0x9b, 12, 34
}));
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
EXPECT_EQ(midi.read(), true);
buffer.clear();
buffer.resize(2);
EXPECT_EQ(serial.mTxBuffer.getLength(), 2);
serial.mTxBuffer.read(&buffer[0], 2);
EXPECT_THAT(buffer, ElementsAreArray({
56, 78
}));
}
TEST(MidiThru, invalidMode)
{
SerialMock serial;
Transport transport(serial);
MidiInterface midi((Transport&)transport);
midi.begin(MIDI_CHANNEL_OMNI);
midi.setThruFilterMode(midi::Thru::Mode(42));
static const unsigned rxSize = 6;
static const byte rxData[rxSize] = { 0x9b, 12, 34, 0x9c, 56, 78 };
serial.mRxBuffer.write(rxData, rxSize);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), false);
EXPECT_EQ(midi.read(), true);
EXPECT_EQ(serial.mTxBuffer.getLength(), 0);
}
END_UNNAMED_NAMESPACE

View File

@ -1,24 +0,0 @@
#include "unit-tests_Settings.h"
BEGIN_MIDI_NAMESPACE
const bool DefaultSettings::UseRunningStatus;
const bool DefaultSettings::HandleNullVelocityNoteOnAsNoteOff;
const bool DefaultSettings::Use1ByteParsing;
const unsigned DefaultSettings::SysExMaxSize;
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
TEST(Settings, hasTheRightDefaultValues)
{
EXPECT_EQ(midi::DefaultSettings::UseRunningStatus, false);
EXPECT_EQ(midi::DefaultSettings::HandleNullVelocityNoteOnAsNoteOff, true);
EXPECT_EQ(midi::DefaultSettings::Use1ByteParsing, true);
EXPECT_EQ(midi::DefaultSettings::SysExMaxSize, unsigned(128));
}
END_UNNAMED_NAMESPACE

View File

@ -1,20 +0,0 @@
#pragma once
#include "unit-tests.h"
#include <src/midi_Settings.h>
BEGIN_UNIT_TESTS_NAMESPACE
template<bool RunningStatus, bool OneByteParsing>
struct VariableSettings : public midi::DefaultSettings
{
static const bool UseRunningStatus = RunningStatus;
static const bool Use1ByteParsing = OneByteParsing;
};
template<bool A, bool B>
const bool VariableSettings<A, B>::UseRunningStatus;
template<bool A, bool B>
const bool VariableSettings<A, B>::Use1ByteParsing;
END_UNIT_TESTS_NAMESPACE

View File

@ -1,181 +0,0 @@
#include "unit-tests.h"
#include <src/MIDI.h>
BEGIN_MIDI_NAMESPACE
END_MIDI_NAMESPACE
// -----------------------------------------------------------------------------
BEGIN_UNNAMED_NAMESPACE
using namespace testing;
TEST(SysExCodec, EncoderAscii)
{
const byte input[] = "Hello, World!";
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer, 13);
EXPECT_EQ(encodedSize, unsigned(15));
const byte expected[16] = {
0, 'H', 'e', 'l', 'l', 'o', ',', ' ',
0, 'W', 'o', 'r', 'l', 'd', '!', 0,
};
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
}
TEST(SysExCodec, EncoderNonAscii)
{
const byte input[] = {
182, 236, 167, 177, 61, 91, 120, // 01111000 -> 120
107, 94, 209, 87, 94 // 000100xx -> 16
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12);
EXPECT_EQ(encodedSize, unsigned(14));
const byte expected[16] = {
// MSB Data
120, 54, 108, 39, 49, 61, 91, 120,
16, 107, 94, 81, 87, 94, 0, 0,
};
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
}
TEST(SysExCodec, EncoderNonAsciiFlipHeader)
{
const byte input[] = {
182, 236, 167, 177, 61, 91, 120, // 00011111 -> 15
107, 94, 209, 87, 94 // 0xx00100 -> 4
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer, 12, true);
EXPECT_EQ(encodedSize, unsigned(14));
const byte expected[16] = {
// MSB Data
15, 54, 108, 39, 49, 61, 91, 120,
4, 107, 94, 81, 87, 94, 0, 0,
};
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
}
// -----------------------------------------------------------------------------
TEST(SysExCodec, DecoderAscii)
{
const byte input[] = {
0, 'H', 'e', 'l', 'l', 'o', ',', ' ',
0, 'W', 'o', 'r', 'l', 'd', '!',
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned decodedSize = midi::decodeSysEx(input, buffer, 15);
EXPECT_EQ(decodedSize, unsigned(13));
const byte expected[16] = {
'H', 'e', 'l', 'l', 'o', ',', ' ', 'W',
'o', 'r', 'l', 'd', '!', 0, 0, 0,
};
EXPECT_THAT(buffer, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
}
// Non-ASCII content
TEST(SysExCodec, DecoderNonAscii)
{
const byte input[] = {
// MSB Data
120, 54, 108, 39, 49, 61, 91, 120,
16, 107, 94, 81, 87, 94,
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14);
EXPECT_EQ(encodedSize, unsigned(12));
const byte expected[16] = {
182, 236, 167, 177, 61, 91, 120,
107, 94, 209, 87, 94, 0, 0,
0, 0,
};
EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
}
TEST(SysExCodec, DecoderNonAsciiFlipHeader)
{
const byte input[] = {
// MSB Data
15, 54, 108, 39, 49, 61, 91, 120,
4, 107, 94, 81, 87, 94,
};
byte buffer[16];
memset(buffer, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::decodeSysEx(input, buffer, 14, true);
EXPECT_EQ(encodedSize, unsigned(12));
const byte expected[16] = {
182, 236, 167, 177, 61, 91, 120,
107, 94, 209, 87, 94, 0, 0,
0, 0,
};
EXPECT_THAT(input, Each(Le(0x7f))); // All elements are <= 127
EXPECT_THAT(buffer, ContainerEq(expected));
}
// -----------------------------------------------------------------------------
TEST(SysExCodec, CodecAscii)
{
const byte input[] = "Hello, World!";
byte buffer1[16];
byte buffer2[16];
memset(buffer1, 0, 16 * sizeof(byte));
memset(buffer2, 0, 16 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 13);
EXPECT_EQ(encodedSize, unsigned(15));
const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize);
EXPECT_EQ(decodedSize, unsigned(13));
EXPECT_STREQ(reinterpret_cast<const char*>(buffer2),
reinterpret_cast<const char*>(input));
}
TEST(SysExCodec, CodecNonAscii)
{
const byte input[] = {
// MSB Data
182, 236, 167, 177, 61, 91, 120,
107, 94, 209, 87, 94
};
byte buffer1[14];
byte buffer2[12];
memset(buffer1, 0, 14 * sizeof(byte));
memset(buffer2, 0, 12 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12);
EXPECT_EQ(encodedSize, unsigned(14));
const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize);
EXPECT_EQ(decodedSize, unsigned(12));
EXPECT_THAT(buffer2, ContainerEq(input));
}
TEST(SysExCodec, CodecNonAsciiFlipHeader)
{
const byte input[] = {
// MSB Data
182, 236, 167, 177, 61, 91, 120,
107, 94, 209, 87, 94
};
byte buffer1[14];
byte buffer2[12];
memset(buffer1, 0, 14 * sizeof(byte));
memset(buffer2, 0, 12 * sizeof(byte));
const unsigned encodedSize = midi::encodeSysEx(input, buffer1, 12, true);
EXPECT_EQ(encodedSize, unsigned(14));
const unsigned decodedSize = midi::decodeSysEx(buffer1, buffer2, encodedSize, true);
EXPECT_EQ(decodedSize, unsigned(12));
EXPECT_THAT(buffer2, ContainerEq(input));
}
END_UNNAMED_NAMESPACE

View File

@ -1,6 +0,0 @@
#include "unit-tests.h"
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@ -1,9 +0,0 @@
#pragma once
#include "unit-tests_Namespace.h"
#include <gtest/gtest.h>
#include <gmock/gmock.h>
BEGIN_UNIT_TESTS_NAMESPACE
END_UNIT_TESTS_NAMESPACE

View File

@ -1,13 +0,0 @@
#pragma once
#define UNIT_TESTS_NAMESPACE unit_tests
#define BEGIN_UNIT_TESTS_NAMESPACE namespace UNIT_TESTS_NAMESPACE {
#define END_UNIT_TESTS_NAMESPACE }
#define BEGIN_UNNAMED_NAMESPACE namespace {
#define END_UNNAMED_NAMESPACE }
#define USING_NAMESPACE_UNIT_TESTS using namespace UNIT_TESTS_NAMESPACE;
BEGIN_UNIT_TESTS_NAMESPACE
END_UNIT_TESTS_NAMESPACE