commit
dcd4862616
16 changed files with 574 additions and 59 deletions
|
|
@ -243,6 +243,8 @@ byte subOctaveDouble = 0;
|
|||
Adafruit_MPR121 touchSensor = Adafruit_MPR121(); // This is the 12-input touch sensor
|
||||
FilterOnePole breathFilter;
|
||||
|
||||
bool configManagementMode = false;
|
||||
|
||||
|
||||
//_______________________________________________________________________________________________ SETUP
|
||||
|
||||
|
|
@ -267,9 +269,19 @@ void setup() {
|
|||
pinMode(biteJumperGndPin, OUTPUT); //PBITE
|
||||
digitalWrite(biteJumperGndPin, LOW); //PBITE
|
||||
|
||||
bool factoryReset = !digitalRead(ePin) && !digitalRead(mPin);
|
||||
configManagementMode = !factoryReset && !digitalRead(uPin) && !digitalRead(dPin);
|
||||
|
||||
initDisplay(); //Start up display and show logo
|
||||
|
||||
//If going into config management mode, stop here before we even touch the EEPROM.
|
||||
if(configManagementMode) {
|
||||
configModeSetup();
|
||||
return;
|
||||
}
|
||||
|
||||
//Read eeprom data into global vars
|
||||
readEEPROM();
|
||||
readEEPROM(factoryReset);
|
||||
|
||||
activePatch = patch;
|
||||
|
||||
|
|
@ -280,8 +292,6 @@ void setup() {
|
|||
}
|
||||
|
||||
breathFilter.setFilter(LOWPASS, filterFreq, 0.0); // create a one pole (RC) lowpass filter
|
||||
|
||||
initDisplay(); //Start up display and show logo
|
||||
|
||||
biteJumper = !digitalRead(biteJumperPin);
|
||||
if (biteJumper){
|
||||
|
|
@ -340,6 +350,13 @@ void setup() {
|
|||
//_______________________________________________________________________________________________ MAIN LOOP
|
||||
|
||||
void loop() {
|
||||
|
||||
//If in config mgmt loop, do that and nothing else
|
||||
if(configManagementMode) {
|
||||
configModeLoop();
|
||||
return;
|
||||
}
|
||||
|
||||
breathFilter.input(analogRead(breathSensorPin));
|
||||
pressureSensor = constrain((int) breathFilter.output(), 0, 4095); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP
|
||||
readSwitches();
|
||||
|
|
@ -367,7 +384,7 @@ void loop() {
|
|||
|
||||
bool bothPB = (pbUp > ((pitchbMaxVal + pitchbThrVal) / 2)) && (pbDn > ((pitchbMaxVal + pitchbThrVal) / 2));
|
||||
bool brSuck = analogRead(breathSensorPin) < (breathCalZero - (bcasMode?900:800));
|
||||
|
||||
|
||||
if (
|
||||
(bothPB && legacy) ||
|
||||
(brSuck && legacyBrAct && (bothPB || bcasMode))
|
||||
|
|
@ -375,9 +392,9 @@ void loop() {
|
|||
|
||||
|
||||
fingeredNoteUntransposed = patchLimit(fingeredNoteUntransposed + 1);
|
||||
if (exSensor >= ((extracThrVal + extracMaxVal) / 2)) { // instant midi setting
|
||||
if (exSensor >= ((extracThrVal + extracMaxVal) / 2)) { // instant midi setting
|
||||
if ((fingeredNoteUntransposed >= 73) && (fingeredNoteUntransposed <= 88)) {
|
||||
MIDIchannel = fingeredNoteUntransposed - 72; // Mid C and up
|
||||
MIDIchannel = fingeredNoteUntransposed - 72; // Mid C and up
|
||||
}
|
||||
} else {
|
||||
if (!pinkyKey) { // note number to patch number
|
||||
|
|
@ -419,7 +436,7 @@ void loop() {
|
|||
doPatchUpdate = 1;
|
||||
}
|
||||
|
||||
if (!K1 && !K2 && K3 && !K4) { //send reverb pitchlatch value
|
||||
if (!K1 && !K2 && K3 && !K4) { //send reverb pitchlatch value
|
||||
reverb = ((pitchlatch - 36) * 2);
|
||||
reverb = constrain(reverb, 0, 127);
|
||||
|
||||
|
|
@ -1104,7 +1121,7 @@ void portamento_() {
|
|||
|
||||
if (biteJumper) { //PBITE (if pulled low with jumper, use pressure sensor instead of capacitive bite sensor)
|
||||
biteSensor=analogRead(bitePressurePin); // alternative kind bite sensor (air pressure tube and sensor) PBITE
|
||||
} else {
|
||||
} else {
|
||||
biteSensor = touchRead(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right)
|
||||
}
|
||||
if (0 == vibControl) {
|
||||
|
|
@ -1180,7 +1197,7 @@ void readTeensySwitches() { //these seem to slow things down, so do it less ofte
|
|||
}
|
||||
|
||||
void readSwitches() {
|
||||
|
||||
|
||||
// Read touch pads (MPR121), compare against threshold value
|
||||
bool touchKeys[12];
|
||||
for (byte i = 0; i < 12; i++) {
|
||||
|
|
@ -1207,11 +1224,12 @@ void readSwitches() {
|
|||
K6 = touchKeys[K6Pin];
|
||||
K7 = touchKeys[K7Pin];
|
||||
|
||||
pinkyKey = (touchRead(halfPitchBendKeyPin) > touch_Thr); // SENSOR PIN 1 - PCB PIN "S1"
|
||||
|
||||
|
||||
int qTransp = (pinkyKey && (pinkySetting < 25)) ? pinkySetting-12 : 0;
|
||||
|
||||
// Calculate midi note number from pressed keys
|
||||
// Calculate midi note number from pressed keys
|
||||
|
||||
fingeredNoteUntransposed = startNote
|
||||
- 2*K1 - K2 - 3*K3 //"Trumpet valves"
|
||||
|
|
@ -1230,7 +1248,7 @@ void readSwitches() {
|
|||
if ((millis() - lastDeglitchTime) > deglitch) {
|
||||
// whatever the reading is at, it's been there for longer
|
||||
// than the debounce delay, so take it as the actual current state
|
||||
fingeredNote = fingeredNoteRead;
|
||||
fingeredNote = fingeredNoteRead;
|
||||
}
|
||||
lastFingering = fingeredNoteRead;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef __GLOBALS_H
|
||||
#define __GLOBALS_H
|
||||
|
||||
#include "Wiring.h"
|
||||
#include "wiring.h"
|
||||
|
||||
|
||||
// The three states of our main state machine
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef __MENU_H
|
||||
#define __MENU_H
|
||||
|
||||
#include "Wiring.h"
|
||||
#include "wiring.h"
|
||||
#include "numenu.h"
|
||||
|
||||
#define MENU_ROW_HEIGHT 9
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ void dinMIDIsendProgramChange(uint8_t value, uint8_t ch) {
|
|||
midiSend2B((0xC0 | ch), value);
|
||||
}
|
||||
|
||||
// Send sysex commands to wireless module
|
||||
void dinMIDIsendSysex(const uint8_t data[], const uint8_t length) {
|
||||
MIDI_SERIAL.write(0xF0); //Sysex command
|
||||
for(int i=0; i<length; ++i) {
|
||||
|
|
@ -149,7 +150,6 @@ void dinMIDIsendSysex(const uint8_t data[], const uint8_t length) {
|
|||
MIDI_SERIAL.write(0xF7); //Sysex end
|
||||
}
|
||||
|
||||
|
||||
void sendWLPower(const uint8_t level) {
|
||||
uint8_t buf[6] = {
|
||||
0x00, 0x21, 0x11, //Manufacturer id
|
||||
|
|
@ -165,7 +165,6 @@ void sendWLPower(const uint8_t level) {
|
|||
|
||||
}
|
||||
|
||||
|
||||
void sendWLChannel(const uint8_t channel) {
|
||||
uint8_t buf[6] = {
|
||||
0x00, 0x21, 0x11, //Manufacturer id
|
||||
|
|
@ -180,3 +179,34 @@ void sendWLChannel(const uint8_t channel) {
|
|||
dinMIDIsendSysex(buf, 6);
|
||||
|
||||
}
|
||||
|
||||
//Translate between "midi data" (only use 7 LSB per byte, big endian) and "teensy data" (little endian)
|
||||
//Only 14 LSB of int value are used (2MSB are discarded), so only works for unsigned data 0-16383
|
||||
|
||||
//NOTE: This assumes code is running on a little-endian CPU, both for real device (Teensy) and simulator.
|
||||
uint16_t convertToMidiValue(const uint16_t realdata) {
|
||||
return (realdata & 0x3F80) >>7 | (realdata & 0x007F) <<8;
|
||||
}
|
||||
|
||||
uint16_t convertFromMidiValue(const uint16_t mididata) {
|
||||
return (mididata & 0x7F00) >> 8 | (mididata & 0x007F) <<7 ;
|
||||
}
|
||||
|
||||
//Read from a memory location, such as MIDI receive buffer
|
||||
uint16_t convertFromMidiValue(const uint8_t* mididata) {
|
||||
uint8_t msb = *mididata;
|
||||
uint8_t lsb = *(mididata+1);
|
||||
|
||||
return (msb & 0x007F) <<7 | (lsb & 0x007F);
|
||||
}
|
||||
|
||||
//This is a bit different. MSB of each byte is just discarded (instead of discarding MSB for whole value). Just used for CRC (easier to compare)
|
||||
uint32_t convertToMidiCRC(const uint32_t realdata) {
|
||||
uint8_t* p = (uint8_t*)&realdata;
|
||||
|
||||
uint32_t r=0;
|
||||
for(int i=0; i<4; ++i) {
|
||||
r = r<<8 | (p[i] & 0x7F);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
|
|
|||
10
NuEVI/midi.h
10
NuEVI/midi.h
|
|
@ -1,6 +1,9 @@
|
|||
#ifndef __MIDI_H
|
||||
#define __MIDI_H
|
||||
|
||||
//This is a completely made up "European" SysEx manufacturer ID.
|
||||
static const char sysex_id[] = { 0x00, 0x3e, 0x7f };
|
||||
|
||||
//Enable use of USB and serial MIDI
|
||||
#define USE_MIDI_USB
|
||||
#define USE_MIDI_SERIAL
|
||||
|
|
@ -33,4 +36,11 @@ void dinMIDIsendSysex(const uint8_t data[], const uint8_t length);
|
|||
void sendWLPower(const uint8_t level);
|
||||
void sendWLChannel(const uint8_t channel);
|
||||
|
||||
|
||||
//Convert things between "regular data" and MIDI data (byte order and 7-bits-per-byte)
|
||||
uint16_t convertToMidiValue(const uint16_t realdata);
|
||||
uint16_t convertFromMidiValue(const uint16_t mididata);
|
||||
uint16_t convertFromMidiValue(const uint8_t* mididata);
|
||||
uint32_t convertToMidiCRC(const uint32_t realdata);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
#include <Arduino.h>
|
||||
#include <EEPROM.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
|
||||
|
||||
#include "settings.h"
|
||||
#include "globals.h"
|
||||
#include "menu.h"
|
||||
#include "hardware.h"
|
||||
#include "config.h"
|
||||
#include "midi.h"
|
||||
#include "led.h"
|
||||
|
||||
//Read settings from eeprom. Returns wether or not anything was written (due to factory reset or upgrade)
|
||||
void readEEPROM() {
|
||||
bool factoryReset = !digitalRead(ePin) && !digitalRead(mPin);
|
||||
void readEEPROM(const bool factoryReset) {
|
||||
|
||||
// if stored settings are not for current version, or Enter+Menu are pressed at startup, they are replaced by factory settings
|
||||
uint16_t settingsVersion = readSetting(VERSION_ADDR);
|
||||
|
|
@ -26,11 +29,11 @@ void readEEPROM() {
|
|||
writeSetting(BREATH_THR_ADDR, BREATH_THR_FACTORY);
|
||||
writeSetting(BREATH_MAX_ADDR, BREATH_MAX_FACTORY);
|
||||
if (digitalRead(biteJumperPin)){ //PBITE (if pulled low with jumper, pressure sensor is used instead of capacitive bite sensing)
|
||||
writeSetting(PORTAM_THR_ADDR, PORTAM_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTAM_MAX_FACTORY);
|
||||
writeSetting(PORTAM_THR_ADDR, PORTAM_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTAM_MAX_FACTORY);
|
||||
} else {
|
||||
writeSetting(PORTAM_THR_ADDR, PORTPR_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTPR_MAX_FACTORY);
|
||||
writeSetting(PORTAM_THR_ADDR, PORTPR_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTPR_MAX_FACTORY);
|
||||
}
|
||||
writeSetting(PITCHB_THR_ADDR, PITCHB_THR_FACTORY);
|
||||
writeSetting(PITCHB_MAX_ADDR, PITCHB_MAX_FACTORY);
|
||||
|
|
@ -104,7 +107,7 @@ void readEEPROM() {
|
|||
|
||||
writeSetting(VERSION_ADDR, EEPROM_VERSION);
|
||||
}
|
||||
|
||||
|
||||
// read all settings from EEPROM
|
||||
breathThrVal = readSettingBounded(BREATH_THR_ADDR, breathLoLimit, breathHiLimit, BREATH_THR_FACTORY);
|
||||
breathMaxVal = readSettingBounded(BREATH_MAX_ADDR, breathLoLimit, breathHiLimit, BREATH_MAX_FACTORY);
|
||||
|
|
@ -114,7 +117,7 @@ void readEEPROM() {
|
|||
pitchbMaxVal = readSettingBounded(PITCHB_MAX_ADDR, pitchbLoLimit, pitchbHiLimit, PITCHB_MAX_FACTORY);
|
||||
transpose = readSettingBounded(TRANSP_ADDR, 0, 24, TRANSP_FACTORY);
|
||||
MIDIchannel = readSettingBounded(MIDI_ADDR, 1, 16, MIDI_FACTORY);
|
||||
breathCC = readSettingBounded(BREATH_CC_ADDR, 0, 127, BREATH_CC_FACTORY);
|
||||
breathCC = readSettingBounded(BREATH_CC_ADDR, 0, 10, BREATH_CC_FACTORY);
|
||||
breathAT = readSettingBounded(BREATH_AT_ADDR, 0, 1, BREATH_AT_FACTORY);
|
||||
velocity = readSettingBounded(VELOCITY_ADDR, 0, 127, VELOCITY_FACTORY);
|
||||
portamento = readSettingBounded(PORTAM_ADDR, 0, 2, PORTAM_FACTORY);
|
||||
|
|
@ -180,7 +183,7 @@ void setBit(uint16_t &bitfield, const uint8_t pos, const uint16_t value) {
|
|||
|
||||
|
||||
//Read and write EEPROM data
|
||||
void writeSetting(uint16_t address, uint16_t value) {
|
||||
void writeSetting(const uint16_t address, const uint16_t value) {
|
||||
union {
|
||||
uint8_t v[2];
|
||||
uint16_t val;
|
||||
|
|
@ -190,7 +193,7 @@ void writeSetting(uint16_t address, uint16_t value) {
|
|||
EEPROM.update(address+1, data.v[1]);
|
||||
}
|
||||
|
||||
uint16_t readSetting(uint16_t address) {
|
||||
uint16_t readSetting(const uint16_t address) {
|
||||
union {
|
||||
uint8_t v[2];
|
||||
uint16_t val;
|
||||
|
|
@ -200,7 +203,7 @@ uint16_t readSetting(uint16_t address) {
|
|||
return data.val;
|
||||
}
|
||||
|
||||
uint16_t readSettingBounded(uint16_t address, uint16_t min, uint16_t max, uint16_t defaultValue) {
|
||||
uint16_t readSettingBounded(const uint16_t address, const uint16_t min, const uint16_t max, const uint16_t defaultValue) {
|
||||
uint16_t val = readSetting(address);
|
||||
if(val < min || val > max) {
|
||||
val = defaultValue;
|
||||
|
|
@ -208,3 +211,290 @@ uint16_t readSettingBounded(uint16_t address, uint16_t min, uint16_t max, uint16
|
|||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Functions to send and receive config (and other things) via USB MIDI SysEx messages
|
||||
uint32_t crc32(const uint8_t *message, const size_t length) {
|
||||
size_t pos=0;
|
||||
uint32_t crc=0xFFFFFFFF;
|
||||
|
||||
while (pos<length) {
|
||||
crc ^= message[pos++]; //Get next byte and increment position
|
||||
for (uint8_t j=0; j<8; ++j) { //Mask off 8 next bits
|
||||
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Send EEPROM config dump as sysex message. Message format is structured like this:
|
||||
|
||||
+------------------------------------------------------------------------------------+
|
||||
| vendor(3) | "NuEVIc01" (8) | Payload size (2) | EEPROM data (variable) | crc32 (4) |
|
||||
+------------------------------------------------------------------------------------+
|
||||
|
||||
Payload size is for the EEPROM data chunk (not including anything else before or after
|
||||
CRC32 covers the entire buffer up to and including the eeprom data (but not the checksum itself)
|
||||
|
||||
This currently operates under the assumption that the whole EEPROM chunk only consists of unsigned 16 bit ints, only using the range 0-16383
|
||||
|
||||
*/
|
||||
void sendSysexSettings() {
|
||||
const char *header = "NuEVIc01"; //NuEVI config dump 01
|
||||
|
||||
//Build a send buffer of all the things
|
||||
size_t sysex_size = 3 + strlen(header) + 2 + EEPROM_SIZE + 4;
|
||||
uint8_t *sysex_data = (uint8_t*)malloc(sysex_size);
|
||||
|
||||
//Positions (offsets) of parts in send buffer
|
||||
int header_pos = 3;
|
||||
int size_pos = header_pos + strlen(header);
|
||||
int payload_pos = size_pos + 2;
|
||||
int checksum_pos = payload_pos + EEPROM_SIZE;
|
||||
|
||||
//SysEX manufacturer ID
|
||||
memcpy(sysex_data, sysex_id, 3);
|
||||
|
||||
//Header with command code
|
||||
memcpy(sysex_data+header_pos, header, strlen(header));
|
||||
|
||||
//Payload length
|
||||
*(uint16_t*)(sysex_data+size_pos) = convertToMidiValue(EEPROM_SIZE);
|
||||
|
||||
//Config data
|
||||
uint16_t* config_buffer_start = (uint16_t*)(sysex_data+payload_pos);
|
||||
|
||||
//Read one settings item at a time, change data format, and put in send buffer
|
||||
for(uint16_t idx=0; idx<EEPROM_SIZE/2; idx++) {
|
||||
uint16_t eepromval = readSetting(idx*2);
|
||||
config_buffer_start[idx] = convertToMidiValue(eepromval);
|
||||
}
|
||||
|
||||
uint32_t checksum = crc32(sysex_data, checksum_pos);
|
||||
|
||||
*(uint32_t*)(sysex_data+checksum_pos) = convertToMidiCRC(checksum);
|
||||
|
||||
usbMIDI.sendSysEx(sysex_size, sysex_data);
|
||||
|
||||
free(sysex_data);
|
||||
}
|
||||
|
||||
//Send a simple 3-byte message code as sysex
|
||||
void sendSysexMessage(const char* messageCode) {
|
||||
char sysexMessage[] = "vvvNuEVIccc"; //Placeholders for vendor and code
|
||||
|
||||
memcpy(sysexMessage, sysex_id, 3);
|
||||
memcpy(sysexMessage+8, messageCode, 3);
|
||||
|
||||
usbMIDI.sendSysEx(11, (const uint8_t *)sysexMessage);
|
||||
}
|
||||
|
||||
|
||||
bool receiveSysexSettings(const uint8_t* data, const uint16_t length) {
|
||||
|
||||
//Expected size of data (vendor+NuEVIc02+len+payload+crc32)
|
||||
uint16_t expected_size = 3 + 8 + 2 + EEPROM_SIZE + 4;
|
||||
|
||||
|
||||
//Positions (offsets) of parts in buffer
|
||||
int size_pos = 11;
|
||||
int payload_pos = size_pos + 2;
|
||||
int checksum_pos = payload_pos + EEPROM_SIZE;
|
||||
|
||||
//Make sure length of receive buffer is enough to read all we need to. We can accept extra junk at the end though.
|
||||
if(length<expected_size) {
|
||||
configShowMessage("Invalid config format");
|
||||
return false;
|
||||
}
|
||||
|
||||
//No need to verify vendor or header/command, already done before we get here.
|
||||
|
||||
//Calculate checksum of stuff received (everything before checksum), transform to midi format
|
||||
//(being a one-way operation, we can't do the reverse anyway)
|
||||
uint32_t crc=convertToMidiCRC(crc32(data, checksum_pos));
|
||||
uint32_t crc_rcv;
|
||||
memcpy(&crc_rcv, data+checksum_pos, 4);
|
||||
if(crc != crc_rcv && crc_rcv != NO_CHECKSUM) {
|
||||
configShowMessage("Invalid checksum");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Verify that payload size matches the size of our EEPROM config
|
||||
uint16_t payload_size = convertFromMidiValue(data+size_pos);
|
||||
if(payload_size != EEPROM_SIZE) {
|
||||
configShowMessage("Invalid config size");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint16_t eeprom_version_rcv = convertFromMidiValue(data+(payload_pos+VERSION_ADDR));
|
||||
if(eeprom_version_rcv != EEPROM_VERSION) {
|
||||
configShowMessage("Invalid config version");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Grab all the items in payload and save to EEPROM
|
||||
for(uint16_t i=0; i<payload_size/2; i++) {
|
||||
uint16_t addr = i*2;
|
||||
uint16_t val;
|
||||
val = convertFromMidiValue(data+(payload_pos+addr));
|
||||
|
||||
//Skip sensor calibration values if they are "out of bounds". This makes it possible to send a config that does
|
||||
//not overwrite sensor calibration.
|
||||
if(addr == BREATH_THR_ADDR || addr == BREATH_MAX_ADDR) {
|
||||
if(val<breathLoLimit || val>breathHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == PORTAM_THR_ADDR || addr == PORTAM_MAX_ADDR) {
|
||||
if(val<portamLoLimit || val>portamHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == PITCHB_THR_ADDR || addr == PITCHB_MAX_ADDR) {
|
||||
if(val<pitchbLoLimit || val>pitchbHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == EXTRAC_THR_ADDR || addr == EXTRAC_MAX_ADDR) {
|
||||
if(val<extracLoLimit || val>extracHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == CTOUCH_THR_ADDR) {
|
||||
if(val<ctouchLoLimit || val>ctouchHiLimit) continue;
|
||||
}
|
||||
|
||||
writeSetting(addr, val);
|
||||
}
|
||||
|
||||
//All went well
|
||||
return true;
|
||||
}
|
||||
|
||||
//Send EEPROM and firmware versions
|
||||
void sendSysexVersion() {
|
||||
char sysexMessage[] = "vvvNuEVIc04eevvvvvvvv"; //Placeholders for vendor and code
|
||||
uint8_t fwStrLen = min(strlen(FIRMWARE_VERSION), 8); //Limit firmware version string to 8 bytes
|
||||
|
||||
memcpy(sysexMessage, sysex_id, 3);
|
||||
memcpy(sysexMessage+13, FIRMWARE_VERSION, fwStrLen);
|
||||
|
||||
*(uint16_t*)(sysexMessage+11) = convertToMidiValue(EEPROM_VERSION);
|
||||
|
||||
uint8_t message_length = 13+fwStrLen;
|
||||
|
||||
usbMIDI.sendSysEx(message_length, (const uint8_t *)sysexMessage);
|
||||
}
|
||||
|
||||
extern Adafruit_SSD1306 display;
|
||||
|
||||
void configShowMessage(const char* message) {
|
||||
display.fillRect(0,32,128,64,BLACK);
|
||||
display.setCursor(0,32);
|
||||
display.setTextColor(WHITE);
|
||||
|
||||
display.print(message);
|
||||
|
||||
display.display();
|
||||
}
|
||||
|
||||
uint8_t* sysex_rcv_buffer = NULL;
|
||||
uint16_t sysex_buf_size = 0;
|
||||
|
||||
|
||||
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) {
|
||||
uint16_t pos;
|
||||
|
||||
if(!sysex_rcv_buffer) {
|
||||
//Start out with an empty buffer
|
||||
pos = 0;
|
||||
sysex_buf_size = length;
|
||||
sysex_rcv_buffer = (uint8_t *)malloc(sysex_buf_size);
|
||||
} else {
|
||||
//Increase size of current buffer
|
||||
pos = sysex_buf_size;
|
||||
sysex_buf_size += length;
|
||||
sysex_rcv_buffer = (uint8_t *)realloc(sysex_rcv_buffer, sysex_buf_size);
|
||||
}
|
||||
|
||||
//Append this chunk to buffer
|
||||
memcpy(sysex_rcv_buffer + pos, data, length);
|
||||
|
||||
//If it's the last one, call the regular handler to process it
|
||||
if(last) {
|
||||
handleSysex(sysex_rcv_buffer, sysex_buf_size);
|
||||
|
||||
//Discard the buffer
|
||||
free(sysex_rcv_buffer);
|
||||
sysex_rcv_buffer = NULL;
|
||||
sysex_buf_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void handleSysex(uint8_t *data, unsigned int length) {
|
||||
//Note: Sysex data as received here contains sysex start and end markers (0xF0 and 0xF7)
|
||||
|
||||
//Too short to even contain a 3-byte vendor id is not for us.
|
||||
if(length<4) return;
|
||||
|
||||
//Verify vendor
|
||||
if(strncmp((char*)(data+1), sysex_id, 3)) return; //Silently ignore different vendor id
|
||||
|
||||
//Verify header. Min length is 3+5+3 bytes (vendor+header+message code)
|
||||
if(length<12 || strncmp((char*)(data+4), "NuEVI", 5)) {
|
||||
configShowMessage("Invalid message.");
|
||||
sendSysexMessage("e00");
|
||||
return;
|
||||
}
|
||||
|
||||
//Get message code
|
||||
char messageCode[3];
|
||||
strncpy(messageCode, (char*)(data+9), 3);
|
||||
|
||||
if(!strncmp(messageCode, "c00", 3)) { //Config dump request
|
||||
configShowMessage("Sending config...");
|
||||
sendSysexSettings();
|
||||
configShowMessage("Config sent.");
|
||||
} else if(!strncmp(messageCode, "c03", 3)) { //Version info request
|
||||
configShowMessage("Sending version.");
|
||||
sendSysexVersion();
|
||||
} else if(!strncmp(messageCode, "c02", 3)) { //New config incoming
|
||||
configShowMessage("Receiving config...");
|
||||
|
||||
//Tell receiveSysexSettings about what's between sysex start and end markers
|
||||
if(receiveSysexSettings(data+1, length-2)) configShowMessage("New config saved.");
|
||||
} else {
|
||||
configShowMessage("Unknown message.");
|
||||
sendSysexMessage("e01"); //Unimplemented message code
|
||||
}
|
||||
}
|
||||
|
||||
void configModeSetup() {
|
||||
statusLedFlash(500);
|
||||
|
||||
display.clearDisplay();
|
||||
display.setCursor(0,0);
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(0);
|
||||
|
||||
display.println("Config mgmt");
|
||||
display.println("Power off NuEVI");
|
||||
display.println("to exit");
|
||||
display.display();
|
||||
|
||||
usbMIDI.setHandleSystemExclusive(handleSysexChunk);
|
||||
|
||||
statusLedFlash(500);
|
||||
|
||||
sendSysexVersion(); //Friendly hello
|
||||
|
||||
configShowMessage("Ready.");
|
||||
}
|
||||
|
||||
//"Main loop". Just sits and wait for midi messages and lets the sysex handler do all the work.
|
||||
void configModeLoop() {
|
||||
usbMIDI.read();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
#define LEVEL_CC_ADDR 104
|
||||
#define LEVEL_VAL_ADDR 106
|
||||
|
||||
#define EEPROM_SIZE 102
|
||||
|
||||
|
||||
//DAC output modes
|
||||
|
|
@ -125,12 +126,28 @@
|
|||
#define LEVEL_CC_FACTORY 11
|
||||
#define LEVEL_VAL_FACTORY 127
|
||||
|
||||
#define NO_CHECKSUM 0x7F007F00
|
||||
|
||||
void readEEPROM();
|
||||
void readEEPROM(const bool factoryReset);
|
||||
void setBit(uint16_t &bitfield, const uint8_t pos, const uint16_t value);
|
||||
uint16_t readSetting(uint16_t address);
|
||||
void writeSetting(uint16_t address, uint16_t value);
|
||||
uint16_t readSettingBounded(uint16_t address, uint16_t min, uint16_t max, uint16_t defaultValue);
|
||||
uint16_t readSetting(const uint16_t address);
|
||||
void writeSetting(const uint16_t address, const uint16_t value);
|
||||
uint16_t readSettingBounded(const uint16_t address, const uint16_t min, const uint16_t max, const uint16_t defaultValue);
|
||||
|
||||
//Functions for config management mode
|
||||
void sendSysexSettings();
|
||||
void sendSysexMessage(const char* messageCode);
|
||||
void sendSysexVersion();
|
||||
|
||||
void handleSysex(uint8_t *data, unsigned int length);
|
||||
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last);
|
||||
|
||||
uint32_t crc32(const uint8_t *message, const size_t length);
|
||||
|
||||
void configInitScreen();
|
||||
void configShowMessage(const char* message);
|
||||
|
||||
void configModeSetup();
|
||||
void configModeLoop();
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <inttypes.h>
|
||||
|
||||
#include "Wiring.h"
|
||||
#include "simusbmidi.h"
|
||||
|
||||
#include "core_pins.h"
|
||||
|
||||
|
|
@ -15,7 +16,7 @@
|
|||
|
||||
#ifndef _BV
|
||||
#define _BV(x) (1u<<(x))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// SPI CTRL REG BITS
|
||||
#define SPIE 7
|
||||
|
|
@ -72,20 +73,6 @@ public:
|
|||
|
||||
};
|
||||
|
||||
class SimUsbMidi
|
||||
{
|
||||
public:
|
||||
void sendNoteOff(uint8_t note, uint8_t velocity, uint8_t channel, uint8_t cable=0);
|
||||
void sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel, uint8_t cable=0);
|
||||
void sendPolyPressure(uint8_t note, uint8_t pressure, uint8_t channel, uint8_t cable=0);
|
||||
void sendAfterTouchPoly(uint8_t note, uint8_t pressure, uint8_t channel, uint8_t cable=0);
|
||||
void sendControlChange(uint8_t control, uint8_t value, uint8_t channel, uint8_t cable=0);
|
||||
void sendProgramChange(uint8_t program, uint8_t channel, uint8_t cable=0);
|
||||
void sendAfterTouch(uint8_t pressure, uint8_t channel, uint8_t cable=0);
|
||||
void sendPitchBend(int value, uint8_t channel, uint8_t cable=0);
|
||||
void sendSysEx(uint16_t length, const uint8_t *data, bool hasTerm=false, uint8_t cable=0);
|
||||
bool read(uint8_t channel=0);
|
||||
};
|
||||
|
||||
extern SimSerial Serial;
|
||||
extern SimSerial Serial3; //Used for MIDI serial putput with default hardware
|
||||
|
|
|
|||
35
simulation/include/simusbmidi.h
Normal file
35
simulation/include/simusbmidi.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef __SIMUSBMIDI_H__
|
||||
#define __SIMUSBMIDI_H__
|
||||
|
||||
#include <string>
|
||||
|
||||
class SimUsbMidi
|
||||
{
|
||||
public:
|
||||
void sendNoteOff(uint8_t note, uint8_t velocity, uint8_t channel, uint8_t cable=0);
|
||||
void sendNoteOn(uint8_t note, uint8_t velocity, uint8_t channel, uint8_t cable=0);
|
||||
void sendPolyPressure(uint8_t note, uint8_t pressure, uint8_t channel, uint8_t cable=0);
|
||||
void sendAfterTouchPoly(uint8_t note, uint8_t pressure, uint8_t channel, uint8_t cable=0);
|
||||
void sendControlChange(uint8_t control, uint8_t value, uint8_t channel, uint8_t cable=0);
|
||||
void sendProgramChange(uint8_t program, uint8_t channel, uint8_t cable=0);
|
||||
void sendAfterTouch(uint8_t pressure, uint8_t channel, uint8_t cable=0);
|
||||
void sendPitchBend(int value, uint8_t channel, uint8_t cable=0);
|
||||
void sendSysEx(uint16_t length, const uint8_t *data, bool hasTerm=false, uint8_t cable=0);
|
||||
bool read(uint8_t channel=0);
|
||||
void setHandleSystemExclusive(void (*fptr) (const uint8_t *array, unsigned int size));
|
||||
void setHandleSystemExclusive(void (*fptr) (const uint8_t *data, uint16_t length, bool complete));
|
||||
|
||||
//Things not part of Teensy USBMidi, but used to simulate sending data to it
|
||||
void receiveMidiData(const uint8_t *data, const uint16_t length); //Send midi data "into simulator"
|
||||
void setMidiFile(std::string filename); //MIDI data to send to device
|
||||
void triggerMidi(); //"Arm" so data is sent to device next time it tries to read anything
|
||||
private:
|
||||
//Handlers registered to receive MIDI
|
||||
void (*usb_midi_handleSysExPartial)(const uint8_t *data, uint16_t length, uint8_t complete);
|
||||
void (*usb_midi_handleSysExComplete)(const uint8_t *data, unsigned int size);
|
||||
std::string midiFile;
|
||||
bool sendMidi;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
BIN
simulation/mididata/c00_config_request.mid
Normal file
BIN
simulation/mididata/c00_config_request.mid
Normal file
Binary file not shown.
BIN
simulation/mididata/c02_new_config.mid
Normal file
BIN
simulation/mididata/c02_new_config.mid
Normal file
Binary file not shown.
BIN
simulation/mididata/c03_version_request.mid
Normal file
BIN
simulation/mididata/c03_version_request.mid
Normal file
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
#include <functional>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
|
@ -13,15 +14,17 @@
|
|||
#include "examples/imgui_impl_sdl.h"
|
||||
#include "examples/imgui_impl_opengl3.h"
|
||||
#include "EEPROM.h"
|
||||
#include "simusbmidi.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define ARGS_NOEXCEPT
|
||||
#include "args.hxx"
|
||||
|
||||
// Forward declarations
|
||||
static void SimQuit(void);
|
||||
static int SimInit(void);
|
||||
static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset);
|
||||
static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset, bool ConfigMode);
|
||||
static void SimLoop(std::function<bool()>, std::function<void()>);
|
||||
|
||||
|
||||
|
|
@ -39,6 +42,8 @@ static const int scale = 3;
|
|||
|
||||
static SDL_Window *window;
|
||||
|
||||
bool no_delay = false;
|
||||
|
||||
void _reboot_Teensyduino_()
|
||||
{
|
||||
// TODO: reboot
|
||||
|
|
@ -81,6 +86,8 @@ uint8_t digitalRead(uint8_t pin) {
|
|||
|
||||
void delay(unsigned int ms)
|
||||
{
|
||||
if(no_delay) return;
|
||||
|
||||
uint32_t endTick = SDL_GetTicks() + ms;
|
||||
auto checktime = [endTick]() -> bool { return endTick > SDL_GetTicks(); };
|
||||
SimLoop(checktime,NULL);
|
||||
|
|
@ -407,6 +414,9 @@ static void toggleAnalogAnimation() {
|
|||
printf("Analog input variations: %s\n", animateAnalogs ? "ON": "OFF");
|
||||
}
|
||||
|
||||
static void sendMidiData() {
|
||||
usbMIDI.triggerMidi();
|
||||
}
|
||||
|
||||
static void SimLoop(std::function<bool()> continue_predicate, std::function<void()> loopFunc)
|
||||
{
|
||||
|
|
@ -449,6 +459,7 @@ static void SimLoop(std::function<bool()> continue_predicate, std::function<void
|
|||
case SDLK_UP: digitalInputs[uPin] = 1; break;
|
||||
case SDLK_DOWN: digitalInputs[dPin] = 1; break;
|
||||
case SDLK_w: toggleAnalogAnimation(); break;
|
||||
case SDLK_m: sendMidiData(); break;
|
||||
|
||||
case SDLK_1: touchSensor.mockFilteredData(K1Pin, ctouchThrVal +100); break;
|
||||
case SDLK_2: touchSensor.mockFilteredData(K2Pin, ctouchThrVal +100); break;
|
||||
|
|
@ -508,7 +519,7 @@ static void SimLoop(std::function<bool()> continue_predicate, std::function<void
|
|||
}
|
||||
}
|
||||
|
||||
static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset)
|
||||
static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset, bool configMode)
|
||||
{
|
||||
if( 0 != SimInit() ) { return 1; }
|
||||
|
||||
|
|
@ -523,11 +534,19 @@ static int SimRun(std::string eepromFile, bool eepromWrite, bool factoryReset)
|
|||
digitalInputs[ePin] = 0;
|
||||
}
|
||||
|
||||
//Go into config management mode. This should not happen (in NuEVI) if factory reset is done
|
||||
if(configMode) {
|
||||
digitalInputs[uPin] = 0;
|
||||
digitalInputs[dPin] = 0;
|
||||
}
|
||||
|
||||
setup();
|
||||
|
||||
//Let it go, let it go, not resetting any more
|
||||
digitalInputs[mPin] = 1;
|
||||
digitalInputs[ePin] = 1;
|
||||
digitalInputs[uPin] = 1;
|
||||
digitalInputs[dPin] = 1;
|
||||
|
||||
SimLoop( []() -> bool { return true; }, loop );
|
||||
SimQuit();
|
||||
|
|
@ -583,7 +602,7 @@ static int SimInit()
|
|||
|
||||
analogInputs[vMeterPin] = 3025;
|
||||
|
||||
// Initialize touch sensors to not be poked
|
||||
// Initialize touch sensors to not be poked
|
||||
for(int i = 0; i < 12; ++i) {
|
||||
touchSensor.mockFilteredData(i, 4095);
|
||||
}
|
||||
|
|
@ -610,23 +629,47 @@ static void SimQuit()
|
|||
int main(int argc, const char** argv)
|
||||
{
|
||||
|
||||
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
|
||||
args::ArgumentParser parser("NuEVI simulator.");
|
||||
|
||||
|
||||
args::ValueFlag<std::string> eepromFile(parser, "eeprom-write", "File to use for EEPROM data", {'e', "eeprom-file"});
|
||||
args::HelpFlag help(parser, "help", "Display this help menu", {'h', "help"});
|
||||
args::ValueFlag<std::string> eepromFile(parser, "filename", "File to use for EEPROM data", {'e', "eeprom-file"});
|
||||
args::Flag eepromWrite(parser, "eeprom-write", "Write EEPROM changes to file", {'w', "eeprom-write"});
|
||||
args::Flag factoryReset(parser, "factory-reset", "Trigger factory reset", {'r', "factory-reset"});
|
||||
args::Flag configMode(parser, "config-mode", "Trigger config-management mode", {'c', "config-mode"});
|
||||
args::Flag nodelay(parser, "nodelay", "Skip all delays when running", {'n', "nodelay"});
|
||||
args::ValueFlag<std::string> midiFile(parser, "filename", "Name of file with raw MIDI data to send to NuEVI", {'m', "midi-file"});
|
||||
args::Flag midiSend(parser, "midi-send", "Trigger automatic sending of MIDI data", {'M', "midi-send"});
|
||||
|
||||
parser.ParseCLI(argc, argv);
|
||||
|
||||
if(parser.GetError() != args::Error::None) {
|
||||
|
||||
if(parser.GetError() == args::Error::Help) {
|
||||
std::cout << parser << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cerr << parser.GetErrorMsg() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string eepromFileName = args::get(eepromFile);
|
||||
std::string midiFileName = args::get(midiFile);
|
||||
|
||||
//Use a default EEPROM file if none is provided.
|
||||
if(eepromFileName.length()==0)
|
||||
{
|
||||
if(eepromFileName.length()==0) {
|
||||
eepromFileName = SDL_GetPrefPath("Vulk Data System", "NuEVI Simulator");
|
||||
eepromFileName += "eeprom.bin";
|
||||
}
|
||||
|
||||
return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset));
|
||||
if(midiFileName.length()>0) {
|
||||
usbMIDI.setMidiFile(midiFileName);
|
||||
if(args::get(midiSend)) {
|
||||
usbMIDI.triggerMidi();
|
||||
}
|
||||
}
|
||||
|
||||
no_delay = args::get(nodelay);
|
||||
|
||||
return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset), args::get(configMode));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ int16_t EEPROMClass::setStorage(const char* filename, bool write)
|
|||
autoUpdate = write;
|
||||
|
||||
storage = fopen(filename, "rb");
|
||||
|
||||
|
||||
|
||||
//If only reading, fail if file does not exist (makes no sense otherwise)
|
||||
if(!storage && !autoUpdate) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "simusbmidi.h"
|
||||
|
||||
/*************************************
|
||||
* Stub simulation of Teensy usbMidi
|
||||
*/
|
||||
|
||||
|
||||
void SimUsbMidi::sendNoteOff(uint8_t note, uint8_t velocity, uint8_t channel, uint8_t __unused cable)
|
||||
{
|
||||
printf( "[usbMIDI::noteOff] note %03d vel %03d ch %02d\n", note, velocity, channel);
|
||||
|
|
@ -49,12 +50,96 @@ void SimUsbMidi::sendPitchBend(int value, uint8_t channel, uint8_t __unused cabl
|
|||
printf( "[usbMIDI::pitchBend] pb %05d ch %02d\n", value, channel);
|
||||
}
|
||||
|
||||
void SimUsbMidi::sendSysEx(uint16_t length, const uint8_t __unused *data, bool __unused hasTerm, uint8_t __unused cable)
|
||||
void SimUsbMidi::sendSysEx(uint16_t length, const uint8_t *data, bool __unused hasTerm, uint8_t __unused cable)
|
||||
{
|
||||
printf( "[usbMIDI::sysEx] len %d\n", length);
|
||||
printf( "[usbMIDI::sysEx] Sending %d bytes\n", length);
|
||||
for(int i=0; i<length; i++) {
|
||||
printf("%02x%c", data[i], (i==length-1)?'\n':':');
|
||||
}
|
||||
}
|
||||
|
||||
//Set a low chunk size on purpose just to let the receiver work for it
|
||||
#define MIDI_SYSEX_CHUNK_SIZE 32
|
||||
|
||||
bool SimUsbMidi::read(uint8_t __unused channel) {
|
||||
if(this->sendMidi) {
|
||||
|
||||
printf("[SimUsbMidi::read] Attempting to send midi data\n");
|
||||
|
||||
std::ifstream file(this->midiFile, std::ios::binary | std::ios::ate);
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
uint8_t *buffer = (uint8_t*)malloc(size);
|
||||
|
||||
if (file.read((char*)buffer, size))
|
||||
{
|
||||
printf("[SimUsbMidi::read] Sending %lu bytes.\n", size);
|
||||
|
||||
this->receiveMidiData(buffer, size);
|
||||
|
||||
}
|
||||
free(buffer);
|
||||
|
||||
this->sendMidi = false;
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//Provide midi data for simulation to receive
|
||||
void SimUsbMidi::receiveMidiData(const uint8_t *data, const uint16_t length) {
|
||||
if(length==0) return; //There is no data, what's even the point
|
||||
uint8_t midi_message = data[0]; //First byte of data
|
||||
|
||||
if(midi_message != 0xF0) return; //Only sysex data supported (no other handlers available)
|
||||
|
||||
if(this->usb_midi_handleSysExPartial) {
|
||||
//Chunked sysex receiver set, use that.
|
||||
if(length<=MIDI_SYSEX_CHUNK_SIZE) {
|
||||
//Send all in one go
|
||||
printf( "[SimUsbMidi::receiveMidiData] usb_midi_handleSysExPartial(complete) %d B\n", length);
|
||||
(this->usb_midi_handleSysExPartial)(data, length, true);
|
||||
} else {
|
||||
uint8_t* buf = (uint8_t*)malloc(MIDI_SYSEX_CHUNK_SIZE);
|
||||
int pos=0;
|
||||
while(pos<length) {
|
||||
int remaining = length-pos;
|
||||
int bytesToSend = std::min(remaining, MIDI_SYSEX_CHUNK_SIZE);
|
||||
bool complete = (bytesToSend == remaining);
|
||||
|
||||
memcpy(buf, data+pos, bytesToSend);
|
||||
printf( "[SimUsbMidi::receiveMidiData] usb_midi_handleSysExPartial(complete: %d) %d B\n", complete, bytesToSend);
|
||||
(this->usb_midi_handleSysExPartial)(buf, bytesToSend, complete);
|
||||
pos=pos+bytesToSend;
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
} else if(this->usb_midi_handleSysExComplete) {
|
||||
printf( "[SimUsbMidi::receiveMidiData] usb_midi_handleSysExComplete() %d B\n", length);
|
||||
(this->usb_midi_handleSysExComplete)(data, length);
|
||||
} else {
|
||||
//Nobody listening
|
||||
}
|
||||
}
|
||||
|
||||
//MIDI SysEx handlers. Choice of data types is a bit odd, but done to match Arduino/Teensy libraries
|
||||
void SimUsbMidi::setHandleSystemExclusive(void (*fptr) (const uint8_t *array, unsigned int size)) {
|
||||
this->usb_midi_handleSysExComplete = fptr;
|
||||
}
|
||||
|
||||
//"Chunked" sysex handler (teensy extension), for large messages
|
||||
void SimUsbMidi::setHandleSystemExclusive(void (*fptr) (const uint8_t *array, uint16_t size, bool last)) {
|
||||
this->usb_midi_handleSysExPartial = (void (*)(const uint8_t *, uint16_t, uint8_t))fptr;
|
||||
}
|
||||
|
||||
void SimUsbMidi::setMidiFile(std::string filename) {
|
||||
this->midiFile = filename;
|
||||
}
|
||||
|
||||
void SimUsbMidi::triggerMidi() {
|
||||
this->sendMidi = true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue