Utility class for MIDI over Bluetooth LE
Dependencies: BLE_API mbed nRF51822
Using MIDI over Bluetooth LE
This class enables sending and receiving MIDI packets.
Set up an "BLEMIDI" class instance
Create an instance, and set up to start using it.
Initialization
#include "mbed.h" #include "BLEDevice.h" #include "BLEMIDI.h" BLEDevice device; BLEMIDI bleMidi(&device); void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason) { bleMidi.onBleDisconnection(handle, reason); } void connectionCallback(Gap::Handle_t handle, Gap::addr_type_t type, const Gap::address_t addr, const Gap::ConnectionParams_t *params) { bleMidi.onBleConnection(handle, type, addr, params); } int main(void) { device.onConnection(connectionCallback); device.onDisconnection(disconnectionCallback); for (;;) { device.waitForEvent(); } }
Changing the device name
The default device name is 'MIDI'.
If the another name is preferred, use this constructor. The maximum length of device name is 4 bytes.
Constructor with device name
BLEDevice device; // BLEMIDI bleMidi(&device); BLEMIDI bleMidi(&device, "NAME");
Sending MIDI events
For example, send a note on event:
send a note on event
bleMidi.sendNoteOn(0, 63, 127);
About other events, see the BLEMIDI class reference.
Receiving MIDI events
For example, receive note on events:
receive note on events
void onNoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { // do something with Note On event } bleMidi.attachNoteOn(onNoteOn);
About other events, see the BLEMIDI class reference.
BLEMIDI.cpp@3:2b71bfbaa458, 2015-04-07 (annotated)
- Committer:
- kshoji
- Date:
- Tue Apr 07 09:24:31 2015 +0000
- Revision:
- 3:2b71bfbaa458
- Parent:
- 2:dbc6f81b9ba0
Removed main.cpp
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
kshoji | 0:83889dc90473 | 1 | /* Copyright (c) 2014 mbed.org, MIT License |
kshoji | 0:83889dc90473 | 2 | * |
kshoji | 0:83889dc90473 | 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software |
kshoji | 0:83889dc90473 | 4 | * and associated documentation files (the "Software"), to deal in the Software without |
kshoji | 0:83889dc90473 | 5 | * restriction, including without limitation the rights to use, copy, modify, merge, publish, |
kshoji | 0:83889dc90473 | 6 | * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the |
kshoji | 0:83889dc90473 | 7 | * Software is furnished to do so, subject to the following conditions: |
kshoji | 0:83889dc90473 | 8 | * |
kshoji | 0:83889dc90473 | 9 | * The above copyright notice and this permission notice shall be included in all copies or |
kshoji | 0:83889dc90473 | 10 | * substantial portions of the Software. |
kshoji | 0:83889dc90473 | 11 | * |
kshoji | 0:83889dc90473 | 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING |
kshoji | 0:83889dc90473 | 13 | * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
kshoji | 0:83889dc90473 | 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
kshoji | 0:83889dc90473 | 15 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
kshoji | 0:83889dc90473 | 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
kshoji | 0:83889dc90473 | 17 | */ |
kshoji | 0:83889dc90473 | 18 | |
kshoji | 0:83889dc90473 | 19 | #include "mbed.h" |
kshoji | 0:83889dc90473 | 20 | #include "BLEMIDI.h" |
kshoji | 0:83889dc90473 | 21 | |
kshoji | 0:83889dc90473 | 22 | void BLEMIDI::onBleDisconnection(Gap::Handle_t handle, Gap::DisconnectionReason_t reason) { |
kshoji | 0:83889dc90473 | 23 | device->startAdvertising(); |
kshoji | 0:83889dc90473 | 24 | isConnected = false; |
kshoji | 0:83889dc90473 | 25 | } |
kshoji | 0:83889dc90473 | 26 | |
kshoji | 0:83889dc90473 | 27 | void BLEMIDI::onBleConnection(Gap::Handle_t handle, Gap::addr_type_t type, const Gap::address_t addr, const Gap::ConnectionParams_t *params) { |
kshoji | 0:83889dc90473 | 28 | isConnected = true; |
kshoji | 0:83889dc90473 | 29 | } |
kshoji | 0:83889dc90473 | 30 | |
kshoji | 0:83889dc90473 | 31 | void BLEMIDI::dataWrittenCallback(const GattCharacteristicWriteCBParams *params) { |
kshoji | 0:83889dc90473 | 32 | uint16_t length; |
kshoji | 0:83889dc90473 | 33 | uint8_t rxBuffer[20]; |
kshoji | 0:83889dc90473 | 34 | |
kshoji | 0:83889dc90473 | 35 | device->readCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), rxBuffer, &length); |
kshoji | 0:83889dc90473 | 36 | |
kshoji | 0:83889dc90473 | 37 | if (length > 1) { |
kshoji | 0:83889dc90473 | 38 | // parse BLE message |
kshoji | 0:83889dc90473 | 39 | uint8_t header = rxBuffer[0]; |
kshoji | 0:83889dc90473 | 40 | for (int i = 1; i < length; i++) { |
kshoji | 0:83889dc90473 | 41 | uint8_t midiEvent = rxBuffer[i]; |
kshoji | 0:83889dc90473 | 42 | |
kshoji | 0:83889dc90473 | 43 | if (midiState == MIDI_STATE_TIMESTAMP) { |
kshoji | 0:83889dc90473 | 44 | if ((midiEvent & 0x80) == 0) { |
kshoji | 0:83889dc90473 | 45 | // running status |
kshoji | 0:83889dc90473 | 46 | midiState = MIDI_STATE_WAIT; |
kshoji | 0:83889dc90473 | 47 | } |
kshoji | 0:83889dc90473 | 48 | |
kshoji | 0:83889dc90473 | 49 | if (midiEvent == 0xf7) { |
kshoji | 0:83889dc90473 | 50 | // maybe error |
kshoji | 0:83889dc90473 | 51 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 52 | continue; |
kshoji | 0:83889dc90473 | 53 | } |
kshoji | 0:83889dc90473 | 54 | } |
kshoji | 0:83889dc90473 | 55 | if (midiState == MIDI_STATE_TIMESTAMP) { |
kshoji | 0:83889dc90473 | 56 | timestamp = ((header & 0x3f) << 7) | (midiEvent & 0x7f); |
kshoji | 0:83889dc90473 | 57 | midiState = MIDI_STATE_WAIT; |
kshoji | 0:83889dc90473 | 58 | } else if (midiState == MIDI_STATE_WAIT) { |
kshoji | 0:83889dc90473 | 59 | switch (midiEvent & 0xf0) { |
kshoji | 0:83889dc90473 | 60 | case 0xf0: { |
kshoji | 0:83889dc90473 | 61 | switch (midiEvent) { |
kshoji | 0:83889dc90473 | 62 | case 0xf0: |
kshoji | 0:83889dc90473 | 63 | sysExBuffer[sysExBufferPos++] = midiEvent; |
kshoji | 0:83889dc90473 | 64 | midiState = MIDI_STATE_SIGNAL_SYSEX; |
kshoji | 0:83889dc90473 | 65 | break; |
kshoji | 0:83889dc90473 | 66 | |
kshoji | 0:83889dc90473 | 67 | case 0xf1: |
kshoji | 0:83889dc90473 | 68 | case 0xf3: |
kshoji | 0:83889dc90473 | 69 | // 0xf1 MIDI Time Code Quarter Frame. : 2bytes |
kshoji | 0:83889dc90473 | 70 | // 0xf3 Song Select. : 2bytes |
kshoji | 0:83889dc90473 | 71 | midiEventKind = midiEvent; |
kshoji | 0:83889dc90473 | 72 | midiState = MIDI_STATE_SIGNAL_2BYTES_2; |
kshoji | 0:83889dc90473 | 73 | break; |
kshoji | 0:83889dc90473 | 74 | |
kshoji | 0:83889dc90473 | 75 | case 0xf2: |
kshoji | 0:83889dc90473 | 76 | // 0xf2 Song Position Pointer. : 3bytes |
kshoji | 0:83889dc90473 | 77 | midiEventKind = midiEvent; |
kshoji | 0:83889dc90473 | 78 | midiState = MIDI_STATE_SIGNAL_3BYTES_2; |
kshoji | 0:83889dc90473 | 79 | break; |
kshoji | 0:83889dc90473 | 80 | |
kshoji | 0:83889dc90473 | 81 | case 0xf6: |
kshoji | 0:83889dc90473 | 82 | // 0xf6 Tune Request : 1byte |
kshoji | 0:83889dc90473 | 83 | onTuneRequest(); |
kshoji | 0:83889dc90473 | 84 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 85 | break; |
kshoji | 0:83889dc90473 | 86 | case 0xf8: |
kshoji | 0:83889dc90473 | 87 | // 0xf8 Timing Clock : 1byte |
kshoji | 0:83889dc90473 | 88 | onTimingClock(); |
kshoji | 0:83889dc90473 | 89 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 90 | break; |
kshoji | 0:83889dc90473 | 91 | case 0xfa: |
kshoji | 0:83889dc90473 | 92 | // 0xfa Start : 1byte |
kshoji | 0:83889dc90473 | 93 | onStart(); |
kshoji | 0:83889dc90473 | 94 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 95 | break; |
kshoji | 0:83889dc90473 | 96 | case 0xfb: |
kshoji | 0:83889dc90473 | 97 | // 0xfb Continue : 1byte |
kshoji | 0:83889dc90473 | 98 | onContinue(); |
kshoji | 0:83889dc90473 | 99 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 100 | break; |
kshoji | 0:83889dc90473 | 101 | case 0xfc: |
kshoji | 0:83889dc90473 | 102 | // 0xfc Stop : 1byte |
kshoji | 0:83889dc90473 | 103 | onStop(); |
kshoji | 0:83889dc90473 | 104 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 105 | break; |
kshoji | 0:83889dc90473 | 106 | case 0xfe: |
kshoji | 0:83889dc90473 | 107 | // 0xfe Active Sensing : 1byte |
kshoji | 0:83889dc90473 | 108 | onActiveSensing(); |
kshoji | 0:83889dc90473 | 109 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 110 | break; |
kshoji | 0:83889dc90473 | 111 | case 0xff: |
kshoji | 0:83889dc90473 | 112 | // 0xff Reset : 1byte |
kshoji | 0:83889dc90473 | 113 | onReset(); |
kshoji | 0:83889dc90473 | 114 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 115 | break; |
kshoji | 0:83889dc90473 | 116 | |
kshoji | 0:83889dc90473 | 117 | default: |
kshoji | 0:83889dc90473 | 118 | break; |
kshoji | 0:83889dc90473 | 119 | } |
kshoji | 0:83889dc90473 | 120 | } |
kshoji | 0:83889dc90473 | 121 | break; |
kshoji | 0:83889dc90473 | 122 | case 0x80: |
kshoji | 0:83889dc90473 | 123 | case 0x90: |
kshoji | 0:83889dc90473 | 124 | case 0xa0: |
kshoji | 0:83889dc90473 | 125 | case 0xb0: |
kshoji | 0:83889dc90473 | 126 | case 0xe0: |
kshoji | 0:83889dc90473 | 127 | // 3bytes pattern |
kshoji | 0:83889dc90473 | 128 | midiEventKind = midiEvent; |
kshoji | 0:83889dc90473 | 129 | midiState = MIDI_STATE_SIGNAL_3BYTES_2; |
kshoji | 0:83889dc90473 | 130 | break; |
kshoji | 0:83889dc90473 | 131 | case 0xc0: // program change |
kshoji | 0:83889dc90473 | 132 | case 0xd0: // channel after-touch |
kshoji | 0:83889dc90473 | 133 | // 2bytes pattern |
kshoji | 0:83889dc90473 | 134 | midiEventKind = midiEvent; |
kshoji | 0:83889dc90473 | 135 | midiState = MIDI_STATE_SIGNAL_2BYTES_2; |
kshoji | 0:83889dc90473 | 136 | break; |
kshoji | 0:83889dc90473 | 137 | default: |
kshoji | 0:83889dc90473 | 138 | // 0x00 - 0x70: running status |
kshoji | 0:83889dc90473 | 139 | if ((midiEventKind & 0xf0) != 0xf0) { |
kshoji | 0:83889dc90473 | 140 | // previous event kind is multi-bytes pattern |
kshoji | 0:83889dc90473 | 141 | midiEventNote = midiEvent; |
kshoji | 0:83889dc90473 | 142 | midiState = MIDI_STATE_SIGNAL_3BYTES_3; |
kshoji | 0:83889dc90473 | 143 | } |
kshoji | 0:83889dc90473 | 144 | break; |
kshoji | 0:83889dc90473 | 145 | } |
kshoji | 0:83889dc90473 | 146 | } else if (midiState == MIDI_STATE_SIGNAL_2BYTES_2) { |
kshoji | 0:83889dc90473 | 147 | switch (midiEventKind & 0xf0) { |
kshoji | 0:83889dc90473 | 148 | // 2bytes pattern |
kshoji | 0:83889dc90473 | 149 | case 0xc0: // program change |
kshoji | 0:83889dc90473 | 150 | midiEventNote = midiEvent; |
kshoji | 0:83889dc90473 | 151 | onProgramChange(midiEventKind & 0xf, midiEventNote); |
kshoji | 0:83889dc90473 | 152 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 153 | break; |
kshoji | 0:83889dc90473 | 154 | case 0xd0: // channel after-touch |
kshoji | 0:83889dc90473 | 155 | midiEventNote = midiEvent; |
kshoji | 0:83889dc90473 | 156 | onChannelAftertouch(midiEventKind & 0xf, midiEventNote); |
kshoji | 0:83889dc90473 | 157 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 158 | break; |
kshoji | 0:83889dc90473 | 159 | case 0xf0: { |
kshoji | 0:83889dc90473 | 160 | switch (midiEventKind) { |
kshoji | 0:83889dc90473 | 161 | case 0xf1: |
kshoji | 0:83889dc90473 | 162 | // 0xf1 MIDI Time Code Quarter Frame. : 2bytes |
kshoji | 0:83889dc90473 | 163 | midiEventNote = midiEvent; |
kshoji | 0:83889dc90473 | 164 | onTimeCodeQuarterFrame(midiEventNote); |
kshoji | 0:83889dc90473 | 165 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 166 | break; |
kshoji | 0:83889dc90473 | 167 | case 0xf3: |
kshoji | 0:83889dc90473 | 168 | // 0xf3 Song Select. : 2bytes |
kshoji | 0:83889dc90473 | 169 | midiEventNote = midiEvent; |
kshoji | 0:83889dc90473 | 170 | onSongSelect(midiEventNote); |
kshoji | 0:83889dc90473 | 171 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 172 | break; |
kshoji | 0:83889dc90473 | 173 | default: |
kshoji | 0:83889dc90473 | 174 | // illegal state |
kshoji | 0:83889dc90473 | 175 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 176 | break; |
kshoji | 0:83889dc90473 | 177 | } |
kshoji | 0:83889dc90473 | 178 | } |
kshoji | 0:83889dc90473 | 179 | break; |
kshoji | 0:83889dc90473 | 180 | default: |
kshoji | 0:83889dc90473 | 181 | // illegal state |
kshoji | 0:83889dc90473 | 182 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 183 | break; |
kshoji | 0:83889dc90473 | 184 | } |
kshoji | 0:83889dc90473 | 185 | } else if (midiState == MIDI_STATE_SIGNAL_3BYTES_2) { |
kshoji | 0:83889dc90473 | 186 | switch (midiEventKind & 0xf0) { |
kshoji | 0:83889dc90473 | 187 | case 0x80: |
kshoji | 0:83889dc90473 | 188 | case 0x90: |
kshoji | 0:83889dc90473 | 189 | case 0xa0: |
kshoji | 0:83889dc90473 | 190 | case 0xb0: |
kshoji | 0:83889dc90473 | 191 | case 0xe0: |
kshoji | 0:83889dc90473 | 192 | case 0xf0: |
kshoji | 0:83889dc90473 | 193 | // 3bytes pattern |
kshoji | 0:83889dc90473 | 194 | midiEventNote = midiEvent; |
kshoji | 0:83889dc90473 | 195 | midiState = MIDI_STATE_SIGNAL_3BYTES_3; |
kshoji | 0:83889dc90473 | 196 | break; |
kshoji | 0:83889dc90473 | 197 | default: |
kshoji | 0:83889dc90473 | 198 | // illegal state |
kshoji | 0:83889dc90473 | 199 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 200 | break; |
kshoji | 0:83889dc90473 | 201 | } |
kshoji | 0:83889dc90473 | 202 | } else if (midiState == MIDI_STATE_SIGNAL_3BYTES_3) { |
kshoji | 0:83889dc90473 | 203 | switch (midiEventKind & 0xf0) { |
kshoji | 0:83889dc90473 | 204 | // 3bytes pattern |
kshoji | 0:83889dc90473 | 205 | case 0x80: // note off |
kshoji | 0:83889dc90473 | 206 | midiEventVelocity = midiEvent; |
kshoji | 0:83889dc90473 | 207 | onNoteOff(midiEventKind & 0xf, midiEventNote, midiEventVelocity); |
kshoji | 0:83889dc90473 | 208 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 209 | break; |
kshoji | 0:83889dc90473 | 210 | case 0x90: // note on |
kshoji | 0:83889dc90473 | 211 | midiEventVelocity = midiEvent; |
kshoji | 0:83889dc90473 | 212 | if (midiEventVelocity == 0) { |
kshoji | 0:83889dc90473 | 213 | onNoteOff(midiEventKind & 0xf, midiEventNote, midiEventVelocity); |
kshoji | 0:83889dc90473 | 214 | } else { |
kshoji | 0:83889dc90473 | 215 | onNoteOn(midiEventKind & 0xf, midiEventNote, midiEventVelocity); |
kshoji | 0:83889dc90473 | 216 | } |
kshoji | 0:83889dc90473 | 217 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 218 | break; |
kshoji | 0:83889dc90473 | 219 | case 0xa0: // control polyphonic key pressure |
kshoji | 0:83889dc90473 | 220 | midiEventVelocity = midiEvent; |
kshoji | 0:83889dc90473 | 221 | onPolyphonicAftertouch(midiEventKind & 0xf, midiEventNote, midiEventVelocity); |
kshoji | 0:83889dc90473 | 222 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 223 | break; |
kshoji | 0:83889dc90473 | 224 | case 0xb0: // control change |
kshoji | 0:83889dc90473 | 225 | midiEventVelocity = midiEvent; |
kshoji | 0:83889dc90473 | 226 | onControlChange(midiEventKind & 0xf, midiEventNote, midiEventVelocity); |
kshoji | 0:83889dc90473 | 227 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 228 | break; |
kshoji | 0:83889dc90473 | 229 | case 0xe0: // pitch bend |
kshoji | 0:83889dc90473 | 230 | midiEventVelocity = midiEvent; |
kshoji | 0:83889dc90473 | 231 | onPitchWheel(midiEventKind & 0xf, (midiEventNote & 0x7f) | ((midiEventVelocity & 0x7f) << 7)); |
kshoji | 0:83889dc90473 | 232 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 233 | break; |
kshoji | 0:83889dc90473 | 234 | case 0xf0: // Song Position Pointer. |
kshoji | 0:83889dc90473 | 235 | midiEventVelocity = midiEvent; |
kshoji | 0:83889dc90473 | 236 | onSongPositionPointer((midiEventNote & 0x7f) | ((midiEventVelocity & 0x7f) << 7)); |
kshoji | 0:83889dc90473 | 237 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 238 | break; |
kshoji | 0:83889dc90473 | 239 | default: |
kshoji | 0:83889dc90473 | 240 | // illegal state |
kshoji | 0:83889dc90473 | 241 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 242 | break; |
kshoji | 0:83889dc90473 | 243 | } |
kshoji | 0:83889dc90473 | 244 | } else if (midiState == MIDI_STATE_SIGNAL_SYSEX) { |
kshoji | 0:83889dc90473 | 245 | if (midiEvent == 0xf7) { |
kshoji | 0:83889dc90473 | 246 | // the end of message |
kshoji | 0:83889dc90473 | 247 | // last written uint8_t is for timestamp |
kshoji | 1:cba2eba64f5c | 248 | if (sysExBufferPos > 0) { |
kshoji | 1:cba2eba64f5c | 249 | sysExBuffer[sysExBufferPos - 1] = midiEvent; |
kshoji | 2:dbc6f81b9ba0 | 250 | onSystemExclusive(sysExBuffer, sysExBufferPos, false); |
kshoji | 1:cba2eba64f5c | 251 | } |
kshoji | 0:83889dc90473 | 252 | |
kshoji | 0:83889dc90473 | 253 | sysExBufferPos = 0; |
kshoji | 0:83889dc90473 | 254 | midiState = MIDI_STATE_TIMESTAMP; |
kshoji | 0:83889dc90473 | 255 | } else { |
kshoji | 2:dbc6f81b9ba0 | 256 | if (sysExBufferPos == 128) { |
kshoji | 2:dbc6f81b9ba0 | 257 | onSystemExclusive(sysExBuffer, sysExBufferPos, true); |
kshoji | 2:dbc6f81b9ba0 | 258 | sysExBufferPos = 0; |
kshoji | 2:dbc6f81b9ba0 | 259 | } |
kshoji | 0:83889dc90473 | 260 | sysExBuffer[sysExBufferPos++] = midiEvent; |
kshoji | 0:83889dc90473 | 261 | } |
kshoji | 0:83889dc90473 | 262 | } |
kshoji | 0:83889dc90473 | 263 | } |
kshoji | 0:83889dc90473 | 264 | } |
kshoji | 0:83889dc90473 | 265 | } |
kshoji | 0:83889dc90473 | 266 | |
kshoji | 0:83889dc90473 | 267 | BLEMIDI::BLEMIDI(BLEDevice *dev) { |
kshoji | 2:dbc6f81b9ba0 | 268 | BLEMIDI(dev, "MIDI"); |
kshoji | 2:dbc6f81b9ba0 | 269 | } |
kshoji | 2:dbc6f81b9ba0 | 270 | |
kshoji | 2:dbc6f81b9ba0 | 271 | BLEMIDI::BLEMIDI(BLEDevice *dev, char *deviceName) { |
kshoji | 0:83889dc90473 | 272 | device = dev; |
kshoji | 0:83889dc90473 | 273 | isConnected = false; |
kshoji | 0:83889dc90473 | 274 | sysExBufferPos = 0; |
kshoji | 0:83889dc90473 | 275 | |
kshoji | 1:cba2eba64f5c | 276 | timestamp = 0; |
kshoji | 1:cba2eba64f5c | 277 | midiEventKind = 0; |
kshoji | 1:cba2eba64f5c | 278 | midiEventNote = 0; |
kshoji | 1:cba2eba64f5c | 279 | midiEventVelocity = 0; |
kshoji | 1:cba2eba64f5c | 280 | |
kshoji | 0:83889dc90473 | 281 | // MIDI characteristic |
kshoji | 0:83889dc90473 | 282 | LongUUIDBytes_t characteristicUuid = { |
kshoji | 0:83889dc90473 | 283 | 0x77, 0x72, 0xe5, 0xdb, 0x38, 0x68, 0x41, 0x12, |
kshoji | 0:83889dc90473 | 284 | 0xa1, 0xa9, 0xf2, 0x66, 0x9d, 0x10, 0x6b, 0xf3 |
kshoji | 0:83889dc90473 | 285 | }; |
kshoji | 0:83889dc90473 | 286 | |
kshoji | 1:cba2eba64f5c | 287 | midiCharacteristic = new GattCharacteristic(UUID(characteristicUuid), midi, 0, 20, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY); |
kshoji | 0:83889dc90473 | 288 | GattCharacteristic *midiChars[] = {midiCharacteristic}; |
kshoji | 0:83889dc90473 | 289 | |
kshoji | 0:83889dc90473 | 290 | // MIDI service |
kshoji | 0:83889dc90473 | 291 | LongUUIDBytes_t serviceUuid = { |
kshoji | 0:83889dc90473 | 292 | 0x03, 0xb8, 0x0e, 0x5a, 0xed, 0xe8, 0x4b, 0x33, |
kshoji | 0:83889dc90473 | 293 | 0xa7, 0x51, 0x6c, 0xe3, 0x4e, 0xc4, 0xc7, 0x00 |
kshoji | 0:83889dc90473 | 294 | }; |
kshoji | 0:83889dc90473 | 295 | |
kshoji | 0:83889dc90473 | 296 | GattService midiService(UUID(serviceUuid), midiChars, sizeof(midiChars) / sizeof(GattCharacteristic *)); |
kshoji | 0:83889dc90473 | 297 | uint8_t uuid128_list[] = { |
kshoji | 0:83889dc90473 | 298 | 0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, 0x51, 0xa7, |
kshoji | 0:83889dc90473 | 299 | 0x33, 0x4b, 0xe8, 0xed, 0x5a, 0x0e, 0xb8, 0x03 |
kshoji | 0:83889dc90473 | 300 | }; |
kshoji | 0:83889dc90473 | 301 | |
kshoji | 0:83889dc90473 | 302 | device->init(); |
kshoji | 0:83889dc90473 | 303 | device->reset(); |
kshoji | 0:83889dc90473 | 304 | |
kshoji | 0:83889dc90473 | 305 | /* setup callbacks */ |
kshoji | 0:83889dc90473 | 306 | device->onDataWritten(this, &BLEMIDI::dataWrittenCallback); |
kshoji | 0:83889dc90473 | 307 | |
kshoji | 0:83889dc90473 | 308 | device->addService(midiService); |
kshoji | 0:83889dc90473 | 309 | |
kshoji | 0:83889dc90473 | 310 | /* setup advertising */ |
kshoji | 0:83889dc90473 | 311 | device->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, (uint8_t*)uuid128_list, sizeof(uuid128_list)); |
kshoji | 0:83889dc90473 | 312 | device->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); |
kshoji | 2:dbc6f81b9ba0 | 313 | device->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)deviceName, sizeof(deviceName)); |
kshoji | 0:83889dc90473 | 314 | |
kshoji | 0:83889dc90473 | 315 | device->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); |
kshoji | 0:83889dc90473 | 316 | device->setAdvertisingInterval(160); /* 100ms; in multiples of 0.625ms. */ |
kshoji | 0:83889dc90473 | 317 | |
kshoji | 0:83889dc90473 | 318 | device->startAdvertising(); |
kshoji | 0:83889dc90473 | 319 | tick.start(); |
kshoji | 0:83889dc90473 | 320 | } |
kshoji | 0:83889dc90473 | 321 | |
kshoji | 0:83889dc90473 | 322 | bool BLEMIDI::connected() { |
kshoji | 0:83889dc90473 | 323 | return isConnected; |
kshoji | 0:83889dc90473 | 324 | } |
kshoji | 0:83889dc90473 | 325 | |
kshoji | 0:83889dc90473 | 326 | void BLEMIDI::sendMidiMessage(uint8_t data0) { |
kshoji | 0:83889dc90473 | 327 | if (isConnected) { |
kshoji | 0:83889dc90473 | 328 | unsigned int ticks = tick.read_ms() & 0x1fff; |
kshoji | 1:cba2eba64f5c | 329 | midi[0] = 0x80 | ((ticks >> 7) & 0x3f); |
kshoji | 1:cba2eba64f5c | 330 | midi[1] = 0x80 | (ticks & 0x7f); |
kshoji | 0:83889dc90473 | 331 | midi[2] = data0; |
kshoji | 0:83889dc90473 | 332 | |
kshoji | 0:83889dc90473 | 333 | device->updateCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), midi, 3); |
kshoji | 0:83889dc90473 | 334 | } |
kshoji | 0:83889dc90473 | 335 | } |
kshoji | 0:83889dc90473 | 336 | |
kshoji | 0:83889dc90473 | 337 | void BLEMIDI::sendMidiMessage(uint8_t data0, uint8_t data1) { |
kshoji | 0:83889dc90473 | 338 | if (isConnected) { |
kshoji | 0:83889dc90473 | 339 | unsigned int ticks = tick.read_ms() & 0x1fff; |
kshoji | 1:cba2eba64f5c | 340 | midi[0] = 0x80 | ((ticks >> 7) & 0x3f); |
kshoji | 1:cba2eba64f5c | 341 | midi[1] = 0x80 | (ticks & 0x7f); |
kshoji | 0:83889dc90473 | 342 | midi[2] = data0; |
kshoji | 0:83889dc90473 | 343 | midi[3] = data1; |
kshoji | 0:83889dc90473 | 344 | |
kshoji | 0:83889dc90473 | 345 | device->updateCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), midi, 4); |
kshoji | 0:83889dc90473 | 346 | } |
kshoji | 0:83889dc90473 | 347 | } |
kshoji | 0:83889dc90473 | 348 | |
kshoji | 0:83889dc90473 | 349 | void BLEMIDI::sendMidiMessage(uint8_t data0, uint8_t data1, uint8_t data2) { |
kshoji | 0:83889dc90473 | 350 | if (isConnected) { |
kshoji | 0:83889dc90473 | 351 | unsigned int ticks = tick.read_ms() & 0x1fff; |
kshoji | 1:cba2eba64f5c | 352 | midi[0] = 0x80 | ((ticks >> 7) & 0x3f); |
kshoji | 1:cba2eba64f5c | 353 | midi[1] = 0x80 | (ticks & 0x7f); |
kshoji | 0:83889dc90473 | 354 | midi[2] = data0; |
kshoji | 0:83889dc90473 | 355 | midi[3] = data1; |
kshoji | 0:83889dc90473 | 356 | midi[4] = data2; |
kshoji | 0:83889dc90473 | 357 | |
kshoji | 0:83889dc90473 | 358 | device->updateCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), midi, 5); |
kshoji | 0:83889dc90473 | 359 | } |
kshoji | 0:83889dc90473 | 360 | } |
kshoji | 0:83889dc90473 | 361 | |
kshoji | 0:83889dc90473 | 362 | void BLEMIDI::sendTuneRequest() { |
kshoji | 0:83889dc90473 | 363 | sendMidiMessage(0xf6); |
kshoji | 0:83889dc90473 | 364 | } |
kshoji | 0:83889dc90473 | 365 | void BLEMIDI::sendTimingClock() { |
kshoji | 0:83889dc90473 | 366 | sendMidiMessage(0xf8); |
kshoji | 0:83889dc90473 | 367 | } |
kshoji | 0:83889dc90473 | 368 | void BLEMIDI::sendStart() { |
kshoji | 0:83889dc90473 | 369 | sendMidiMessage(0xfa); |
kshoji | 0:83889dc90473 | 370 | } |
kshoji | 0:83889dc90473 | 371 | void BLEMIDI::sendContinue() { |
kshoji | 0:83889dc90473 | 372 | sendMidiMessage(0xfb); |
kshoji | 0:83889dc90473 | 373 | } |
kshoji | 0:83889dc90473 | 374 | void BLEMIDI::sendStop() { |
kshoji | 0:83889dc90473 | 375 | sendMidiMessage(0xfc); |
kshoji | 0:83889dc90473 | 376 | } |
kshoji | 0:83889dc90473 | 377 | void BLEMIDI::sendActiveSensing() { |
kshoji | 0:83889dc90473 | 378 | sendMidiMessage(0xfe); |
kshoji | 0:83889dc90473 | 379 | } |
kshoji | 0:83889dc90473 | 380 | void BLEMIDI::sendReset() { |
kshoji | 0:83889dc90473 | 381 | sendMidiMessage(0xff); |
kshoji | 0:83889dc90473 | 382 | } |
kshoji | 0:83889dc90473 | 383 | void BLEMIDI::sendProgramChange(uint8_t channel, uint8_t program) { |
kshoji | 0:83889dc90473 | 384 | sendMidiMessage(0xc0 | (channel & 0xf), program); |
kshoji | 0:83889dc90473 | 385 | } |
kshoji | 0:83889dc90473 | 386 | void BLEMIDI::sendChannelAftertouch(uint8_t channel, uint8_t pressure) { |
kshoji | 0:83889dc90473 | 387 | sendMidiMessage(0xd0 | (channel & 0xf), pressure); |
kshoji | 0:83889dc90473 | 388 | } |
kshoji | 0:83889dc90473 | 389 | void BLEMIDI::sendTimeCodeQuarterFrame(uint8_t timing) { |
kshoji | 0:83889dc90473 | 390 | sendMidiMessage(0xf1, timing & 0x7f); |
kshoji | 0:83889dc90473 | 391 | } |
kshoji | 0:83889dc90473 | 392 | void BLEMIDI::sendSongSelect(uint8_t song) { |
kshoji | 0:83889dc90473 | 393 | sendMidiMessage(0xf3, song & 0x7f); |
kshoji | 0:83889dc90473 | 394 | } |
kshoji | 0:83889dc90473 | 395 | void BLEMIDI::sendNoteOff(uint8_t channel, uint8_t note, uint8_t velocity) { |
kshoji | 0:83889dc90473 | 396 | sendMidiMessage(0x80 | (channel & 0xf), note, velocity); |
kshoji | 0:83889dc90473 | 397 | } |
kshoji | 0:83889dc90473 | 398 | void BLEMIDI::sendNoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { |
kshoji | 0:83889dc90473 | 399 | sendMidiMessage(0x90 | (channel & 0xf), note, velocity); |
kshoji | 0:83889dc90473 | 400 | } |
kshoji | 0:83889dc90473 | 401 | void BLEMIDI::sendPolyphonicAftertouch(uint8_t channel, uint8_t note, uint8_t pressure) { |
kshoji | 0:83889dc90473 | 402 | sendMidiMessage(0xa0 | (channel & 0xf), note, pressure); |
kshoji | 0:83889dc90473 | 403 | } |
kshoji | 0:83889dc90473 | 404 | void BLEMIDI::sendControlChange(uint8_t channel, uint8_t function, uint8_t value) { |
kshoji | 0:83889dc90473 | 405 | sendMidiMessage(0xb0 | (channel & 0xf), function, value); |
kshoji | 0:83889dc90473 | 406 | } |
kshoji | 0:83889dc90473 | 407 | void BLEMIDI::sendPitchWheel(uint8_t channel, uint16_t amount) { |
kshoji | 0:83889dc90473 | 408 | sendMidiMessage(0xe0 | (channel & 0xf), amount & 0x7f, (amount >> 7) & 0x7f); |
kshoji | 0:83889dc90473 | 409 | } |
kshoji | 0:83889dc90473 | 410 | void BLEMIDI::sendSongPositionPointer(uint16_t position) { |
kshoji | 0:83889dc90473 | 411 | sendMidiMessage(0xf2, position & 0x7f, (position >> 7) & 0x7f); |
kshoji | 0:83889dc90473 | 412 | } |
kshoji | 0:83889dc90473 | 413 | void BLEMIDI::sendSystemExclusive(uint8_t * sysex, uint16_t length) { |
kshoji | 0:83889dc90473 | 414 | if (isConnected) { |
kshoji | 0:83889dc90473 | 415 | uint8_t position = 0; |
kshoji | 0:83889dc90473 | 416 | |
kshoji | 0:83889dc90473 | 417 | // header |
kshoji | 1:cba2eba64f5c | 418 | uint16_t ticks = tick.read_ms() & 0x1fff; |
kshoji | 1:cba2eba64f5c | 419 | midi[position++] = 0x80 | ((ticks >> 7) & 0x3f); |
kshoji | 1:cba2eba64f5c | 420 | midi[position++] = 0x80 | (ticks & 0x7f); |
kshoji | 0:83889dc90473 | 421 | |
kshoji | 0:83889dc90473 | 422 | for (int i = 0; i < length; i++) { |
kshoji | 0:83889dc90473 | 423 | if (i == length - 1) { |
kshoji | 1:cba2eba64f5c | 424 | // modify last byte |
kshoji | 1:cba2eba64f5c | 425 | midi[position++] = 0x80 | (ticks & 0x7f); |
kshoji | 0:83889dc90473 | 426 | |
kshoji | 0:83889dc90473 | 427 | if (position == 20) { |
kshoji | 0:83889dc90473 | 428 | device->updateCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), midi, 20); |
kshoji | 0:83889dc90473 | 429 | |
kshoji | 0:83889dc90473 | 430 | position = 0; |
kshoji | 0:83889dc90473 | 431 | // header |
kshoji | 0:83889dc90473 | 432 | midi[position++] = 0x80 | (ticks >> 7) & 0x3f; |
kshoji | 0:83889dc90473 | 433 | } |
kshoji | 0:83889dc90473 | 434 | } |
kshoji | 0:83889dc90473 | 435 | midi[position++] = sysex[i]; |
kshoji | 0:83889dc90473 | 436 | if (position == 20) { |
kshoji | 0:83889dc90473 | 437 | device->updateCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), midi, 20); |
kshoji | 0:83889dc90473 | 438 | |
kshoji | 0:83889dc90473 | 439 | position = 0; |
kshoji | 0:83889dc90473 | 440 | // header |
kshoji | 0:83889dc90473 | 441 | midi[position++] = 0x80 | (ticks >> 7) & 0x3f; |
kshoji | 0:83889dc90473 | 442 | } |
kshoji | 0:83889dc90473 | 443 | |
kshoji | 0:83889dc90473 | 444 | ticks = tick.read_ms() & 0x1fff; |
kshoji | 0:83889dc90473 | 445 | } |
kshoji | 0:83889dc90473 | 446 | |
kshoji | 0:83889dc90473 | 447 | if (position > 0) { |
kshoji | 0:83889dc90473 | 448 | // send remains |
kshoji | 0:83889dc90473 | 449 | device->updateCharacteristicValue(midiCharacteristic->getValueAttribute().getHandle(), midi, position); |
kshoji | 0:83889dc90473 | 450 | } |
kshoji | 0:83889dc90473 | 451 | } |
kshoji | 0:83889dc90473 | 452 | } |