udpated BLE impl
This commit is contained in:
Родитель
52fcfbd6f8
Коммит
f73a4ebb41
|
@ -33,257 +33,13 @@ const uint8_t midiServiceUuid[] = {
|
|||
0xa7, 0x51, 0x6c, 0xe3, 0x4e, 0xc4, 0xc7, 0x00
|
||||
};
|
||||
|
||||
void BluetoothMIDIService::onDataWritten(const GattWriteCallbackParams *params) {
|
||||
if (midiCharacteristicHandle != params->handle)
|
||||
return;
|
||||
uint16_t length;
|
||||
ble.readCharacteristicValue(midiCharacteristicHandle, rxBuffer, &length);
|
||||
if (length > 1) {
|
||||
// parse BLE message
|
||||
uint8_t header = rxBuffer[0];
|
||||
for (int i = 1; i < length; i++) {
|
||||
uint8_t midiEvent = rxBuffer[i];
|
||||
|
||||
if (midiState == MIDI_STATE_TIMESTAMP) {
|
||||
if ((midiEvent & 0x80) == 0) {
|
||||
// running status
|
||||
midiState = MIDI_STATE_WAIT;
|
||||
}
|
||||
|
||||
if (midiEvent == 0xf7) {
|
||||
// maybe error
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (midiState == MIDI_STATE_TIMESTAMP) {
|
||||
timestamp = ((header & 0x3f) << 7) | (midiEvent & 0x7f);
|
||||
midiState = MIDI_STATE_WAIT;
|
||||
} else if (midiState == MIDI_STATE_WAIT) {
|
||||
switch (midiEvent & 0xf0) {
|
||||
case 0xf0: {
|
||||
switch (midiEvent) {
|
||||
case 0xf0:
|
||||
sysExBuffer[sysExBufferPos++] = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_SYSEX;
|
||||
break;
|
||||
|
||||
case 0xf1:
|
||||
case 0xf3:
|
||||
// 0xf1 MIDI Time Code Quarter Frame. : 2bytes
|
||||
// 0xf3 Song Select. : 2bytes
|
||||
midiEventKind = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_2BYTES_2;
|
||||
break;
|
||||
|
||||
case 0xf2:
|
||||
// 0xf2 Song Position Pointer. : 3bytes
|
||||
midiEventKind = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_3BYTES_2;
|
||||
break;
|
||||
|
||||
case 0xf6:
|
||||
// 0xf6 Tune Request : 1byte
|
||||
onTuneRequest();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xf8:
|
||||
// 0xf8 Timing Clock : 1byte
|
||||
onTimingClock();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xfa:
|
||||
// 0xfa Start : 1byte
|
||||
onStart();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xfb:
|
||||
// 0xfb Continue : 1byte
|
||||
onContinue();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xfc:
|
||||
// 0xfc Stop : 1byte
|
||||
onStop();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xfe:
|
||||
// 0xfe Active Sensing : 1byte
|
||||
onActiveSensing();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xff:
|
||||
// 0xff Reset : 1byte
|
||||
onReset();
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x80:
|
||||
case 0x90:
|
||||
case 0xa0:
|
||||
case 0xb0:
|
||||
case 0xe0:
|
||||
// 3bytes pattern
|
||||
midiEventKind = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_3BYTES_2;
|
||||
break;
|
||||
case 0xc0: // program change
|
||||
case 0xd0: // channel after-touch
|
||||
// 2bytes pattern
|
||||
midiEventKind = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_2BYTES_2;
|
||||
break;
|
||||
default:
|
||||
// 0x00 - 0x70: running status
|
||||
if ((midiEventKind & 0xf0) != 0xf0) {
|
||||
// previous event kind is multi-bytes pattern
|
||||
midiEventNote = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_3BYTES_3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (midiState == MIDI_STATE_SIGNAL_2BYTES_2) {
|
||||
switch (midiEventKind & 0xf0) {
|
||||
// 2bytes pattern
|
||||
case 0xc0: // program change
|
||||
midiEventNote = midiEvent;
|
||||
onProgramChange(midiEventKind & 0xf, midiEventNote);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xd0: // channel after-touch
|
||||
midiEventNote = midiEvent;
|
||||
onChannelAftertouch(midiEventKind & 0xf, midiEventNote);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xf0: {
|
||||
switch (midiEventKind) {
|
||||
case 0xf1:
|
||||
// 0xf1 MIDI Time Code Quarter Frame. : 2bytes
|
||||
midiEventNote = midiEvent;
|
||||
onTimeCodeQuarterFrame(midiEventNote);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xf3:
|
||||
// 0xf3 Song Select. : 2bytes
|
||||
midiEventNote = midiEvent;
|
||||
onSongSelect(midiEventNote);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
default:
|
||||
// illegal state
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// illegal state
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
}
|
||||
} else if (midiState == MIDI_STATE_SIGNAL_3BYTES_2) {
|
||||
switch (midiEventKind & 0xf0) {
|
||||
case 0x80:
|
||||
case 0x90:
|
||||
case 0xa0:
|
||||
case 0xb0:
|
||||
case 0xe0:
|
||||
case 0xf0:
|
||||
// 3bytes pattern
|
||||
midiEventNote = midiEvent;
|
||||
midiState = MIDI_STATE_SIGNAL_3BYTES_3;
|
||||
break;
|
||||
default:
|
||||
// illegal state
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
}
|
||||
} else if (midiState == MIDI_STATE_SIGNAL_3BYTES_3) {
|
||||
switch (midiEventKind & 0xf0) {
|
||||
// 3bytes pattern
|
||||
case 0x80: // note off
|
||||
midiEventVelocity = midiEvent;
|
||||
onNoteOff(midiEventKind & 0xf, midiEventNote, midiEventVelocity);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0x90: // note on
|
||||
midiEventVelocity = midiEvent;
|
||||
if (midiEventVelocity == 0) {
|
||||
onNoteOff(midiEventKind & 0xf, midiEventNote, midiEventVelocity);
|
||||
} else {
|
||||
onNoteOn(midiEventKind & 0xf, midiEventNote, midiEventVelocity);
|
||||
}
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xa0: // control polyphonic key pressure
|
||||
midiEventVelocity = midiEvent;
|
||||
onPolyphonicAftertouch(midiEventKind & 0xf, midiEventNote, midiEventVelocity);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xb0: // control change
|
||||
midiEventVelocity = midiEvent;
|
||||
onControlChange(midiEventKind & 0xf, midiEventNote, midiEventVelocity);
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xe0: // pitch bend
|
||||
midiEventVelocity = midiEvent;
|
||||
onPitchWheel(midiEventKind & 0xf, (midiEventNote & 0x7f) | ((midiEventVelocity & 0x7f) << 7));
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
case 0xf0: // Song Position Pointer.
|
||||
midiEventVelocity = midiEvent;
|
||||
onSongPositionPointer((midiEventNote & 0x7f) | ((midiEventVelocity & 0x7f) << 7));
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
default:
|
||||
// illegal state
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
break;
|
||||
}
|
||||
} else if (midiState == MIDI_STATE_SIGNAL_SYSEX) {
|
||||
if (midiEvent == 0xf7) {
|
||||
// the end of message
|
||||
// last written uint8_t is for timestamp
|
||||
if (sysExBufferPos > 0) {
|
||||
sysExBuffer[sysExBufferPos - 1] = midiEvent;
|
||||
onSystemExclusive(sysExBuffer, sysExBufferPos, false);
|
||||
}
|
||||
|
||||
sysExBufferPos = 0;
|
||||
midiState = MIDI_STATE_TIMESTAMP;
|
||||
} else {
|
||||
if (sysExBufferPos == 128) {
|
||||
onSystemExclusive(sysExBuffer, sysExBufferPos, true);
|
||||
sysExBufferPos = 0;
|
||||
}
|
||||
sysExBuffer[sysExBufferPos++] = midiEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothMIDIService::BluetoothMIDIService(BLEDevice *dev): ble(*dev) {
|
||||
sysExBufferPos = 0;
|
||||
|
||||
timestamp = 0;
|
||||
midiEventKind = 0;
|
||||
midiEventNote = 0;
|
||||
midiEventVelocity = 0;
|
||||
|
||||
memset(midi, 0, sizeof(midi));
|
||||
memset(sysExBuffer, 0, sizeof(sysExBuffer));
|
||||
memset(rxBuffer, 0, sizeof(rxBuffer));
|
||||
|
||||
GattCharacteristic midiCharacteristic(midiCharacteristicUuid, midi, 0, sizeof(midi),
|
||||
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_READ
|
||||
| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
|
||||
);
|
||||
GattCharacteristic *midiChars[] = {&midiCharacteristic};
|
||||
|
@ -296,7 +52,6 @@ BluetoothMIDIService::BluetoothMIDIService(BLEDevice *dev): ble(*dev) {
|
|||
|
||||
midiCharacteristicHandle = midiCharacteristic.getValueHandle();
|
||||
|
||||
// ble.onDataWritten(this, &BluetoothMIDIService::onDataWritten);
|
||||
tick.start();
|
||||
}
|
||||
|
||||
|
|
|
@ -38,237 +38,18 @@ public:
|
|||
*/
|
||||
bool connected();
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Tune Request` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onTuneRequest();
|
||||
*/
|
||||
inline void attachTuneRequest(void (*fn)()) {
|
||||
onTuneRequest = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Timing Clock` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onTimingClock();
|
||||
*/
|
||||
inline void attachTimingClock(void (*fn)()) {
|
||||
onTimingClock = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Start` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onStart();
|
||||
*/
|
||||
inline void attachStart(void (*fn)()) {
|
||||
onStart = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Continue` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onContinue();
|
||||
*/
|
||||
inline void attachContinue(void (*fn)()) {
|
||||
onContinue = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Stop` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onStop();
|
||||
*/
|
||||
inline void attachStop(void (*fn)()) {
|
||||
onStop = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Active Sensing` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onActiveSensing();
|
||||
*/
|
||||
inline void attachActiveSensing(void (*fn)()) {
|
||||
onActiveSensing = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Reset` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onReset();
|
||||
*/
|
||||
inline void attachReset(void (*fn)()) {
|
||||
onReset = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Program Change` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onProgramChange(uint8_t channel, uint8_t program);
|
||||
*/
|
||||
inline void attachnProgramChange(void (*fn)(uint8_t, uint8_t)) {
|
||||
onProgramChange = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Channel Aftertouch` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onChannelAftertouch(uint8_t channel, uint8_t pressure);
|
||||
*/
|
||||
inline void attachChannelAftertouch(void (*fn)(uint8_t, uint8_t)) {
|
||||
onChannelAftertouch = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Time Code Quarter Frame` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onTimeCodeQuarterFrame(uint8_t timing);
|
||||
*/
|
||||
inline void attachTimeCodeQuarterFrame(void (*fn)(uint8_t)) {
|
||||
onTimeCodeQuarterFrame = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Song Select` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onSongSelect(uint8_t song);
|
||||
*/
|
||||
inline void attachSongSelect(void (*fn)(uint8_t)) {
|
||||
onSongSelect = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Note Off` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onNoteOff(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||
*/
|
||||
inline void attachNoteOff(void (*fn)(uint8_t, uint8_t, uint8_t)) {
|
||||
onNoteOff = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Note On` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onNoteOn(uint8_t channel, uint8_t note, uint8_t velocity);
|
||||
*/
|
||||
inline void attachNoteOn(void (*fn)(uint8_t, uint8_t, uint8_t)) {
|
||||
onNoteOn = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Polyphonic Aftertouch` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onPolyphonicAftertouch(uint8_t channel, uint8_t note, uint8_t pressure);
|
||||
*/
|
||||
inline void attachPolyphonicAftertouch(void (*fn)(uint8_t, uint8_t, uint8_t)) {
|
||||
onPolyphonicAftertouch = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Control Change` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onControlChange(uint8_t channel, uint8_t function, uint8_t value);
|
||||
*/
|
||||
inline void attachControlChange(void (*fn)(uint8_t, uint8_t, uint8_t)) {
|
||||
onControlChange = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Pitch Wheel` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onPitchWheel(uint8_t channel, uint16_t amount);
|
||||
*/
|
||||
inline void attachPitchWheel(void (*fn)(uint8_t, uint16_t)) {
|
||||
onPitchWheel = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `Song Position Pointer` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onSongPositionPointer(uint16_t position);
|
||||
*/
|
||||
inline void attachSongPositionPointer(void (*fn)(uint16_t)) {
|
||||
onSongPositionPointer = fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `System Exclusive` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onSystemExclusive(uint8_t *sysex, uint16_t length, bool hasNextData);
|
||||
*/
|
||||
inline void attachSystemExclusive(void (*fn)(uint8_t *, uint16_t, bool)) {
|
||||
onSystemExclusive = fn;
|
||||
}
|
||||
|
||||
void sendMidiMessage(uint8_t data0);
|
||||
void sendMidiMessage(uint8_t data0, uint8_t data1);
|
||||
void sendMidiMessage(uint8_t data0, uint8_t data1, uint8_t data2);
|
||||
|
||||
private:
|
||||
uint16_t sysExBufferPos;
|
||||
uint8_t sysExBuffer[128];
|
||||
|
||||
private:
|
||||
uint16_t timestamp;
|
||||
|
||||
uint8_t midiEventKind;
|
||||
uint8_t midiEventNote;
|
||||
uint8_t midiEventVelocity;
|
||||
uint8_t midi[20];
|
||||
uint8_t rxBuffer[20];
|
||||
|
||||
BLEDevice &ble;
|
||||
GattAttribute::Handle_t midiCharacteristicHandle;
|
||||
Timer tick;
|
||||
|
||||
enum MIDI_STATE {
|
||||
MIDI_STATE_TIMESTAMP = 0,
|
||||
MIDI_STATE_WAIT,
|
||||
MIDI_STATE_SIGNAL_2BYTES_2,
|
||||
MIDI_STATE_SIGNAL_3BYTES_2,
|
||||
MIDI_STATE_SIGNAL_3BYTES_3,
|
||||
MIDI_STATE_SIGNAL_SYSEX
|
||||
};
|
||||
|
||||
MIDI_STATE midiState;
|
||||
|
||||
void (*onTuneRequest)();
|
||||
void (*onTimingClock)();
|
||||
void (*onStart)();
|
||||
void (*onContinue)();
|
||||
void (*onStop)();
|
||||
void (*onActiveSensing)();
|
||||
void (*onReset)();
|
||||
void (*onProgramChange)(uint8_t, uint8_t);
|
||||
void (*onChannelAftertouch)(uint8_t, uint8_t);
|
||||
void (*onTimeCodeQuarterFrame)(uint8_t);
|
||||
void (*onSongSelect)(uint8_t);
|
||||
void (*onNoteOff)(uint8_t, uint8_t, uint8_t);
|
||||
void (*onNoteOn)(uint8_t, uint8_t, uint8_t);
|
||||
void (*onPolyphonicAftertouch)(uint8_t, uint8_t, uint8_t);
|
||||
void (*onControlChange)(uint8_t, uint8_t, uint8_t);
|
||||
void (*onPitchWheel)(uint8_t, uint16_t);
|
||||
void (*onSongPositionPointer)(uint16_t);
|
||||
void (*onSystemExclusive)(uint8_t *, uint16_t, bool);
|
||||
|
||||
void onDataWritten(const GattWriteCallbackParams *params);
|
||||
};
|
||||
|
||||
#endif /* __BLEMIDI_H__ */
|
||||
|
|
|
@ -24,8 +24,9 @@ Please refer to that project documentation for further details.
|
|||
|
||||
## Apps
|
||||
|
||||
Any app that supports a MIDI keyboard or instrument should work.
|
||||
|
||||
* iPhone/iPad: [Apple GarageBand](https://itunes.apple.com/us/app/garageband/id408709785?mt=8)
|
||||
* iPhone/iPad: [KORG Module Le](https://itunes.apple.com/us/app/korg-module-le/id1048875111)
|
||||
|
||||
## Supported targets
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace bluetooth {
|
|||
function send(buffer: Buffer) {
|
||||
bluetooth.midiSendMessage(buffer);
|
||||
}
|
||||
midi.setInputTransport(send);
|
||||
midi.setTransport(send);
|
||||
bluetooth.midiSendMessage(pins.createBuffer(0));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,4 @@
|
|||
// Auto-generated. Do not edit.
|
||||
|
||||
|
||||
/**
|
||||
* Attach a callback called when the `System Exclusive` event is received
|
||||
*
|
||||
* @param ptr function pointer
|
||||
* prototype: void onSystemExclusive(uint8_t *sysex, uint16_t length, bool hasNextData);
|
||||
*/
|
||||
|
||||
declare enum MIDI_STATE {
|
||||
MIDI_STATE_TIMESTAMP = 0,
|
||||
MIDI_STATE_WAIT = 1,
|
||||
MIDI_STATE_SIGNAL_2BYTES_2 = 2,
|
||||
MIDI_STATE_SIGNAL_3BYTES_2 = 3,
|
||||
MIDI_STATE_SIGNAL_3BYTES_3 = 4,
|
||||
MIDI_STATE_SIGNAL_SYSEX = 5,
|
||||
}
|
||||
declare namespace bluetooth {
|
||||
}
|
||||
|
||||
|
|
4
pxt.json
4
pxt.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "bluetooth-midi",
|
||||
"version": "1.1.13",
|
||||
"version": "2.0.0",
|
||||
"description": "A Bluetooth MIDI service",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core": "*",
|
||||
"bluetooth": "*",
|
||||
"midi": "github:Microsoft/pxt-midi#v1.1.6"
|
||||
"midi": "github:Microsoft/pxt-midi#v2.0.1"
|
||||
},
|
||||
"files": [
|
||||
"README.md",
|
||||
|
|
2
tests.ts
2
tests.ts
|
@ -8,7 +8,7 @@ bluetooth.startMidiService();
|
|||
|
||||
basic.showString("S")
|
||||
basic.forever(() => {
|
||||
midi.inputChannel(1).pitchBend(Math.abs(input.acceleration(Dimension.X)));
|
||||
midi.pitchBend(Math.abs(input.acceleration(Dimension.X)));
|
||||
basic.pause(50);
|
||||
})
|
||||
input.onButtonPressed(Button.A, () => {
|
||||
|
|
Загрузка…
Ссылка в новой задаче