From 2741ff5a27d862d09d029bef3c2989cbafb6bc2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 29 Jul 2019 15:44:43 +0200 Subject: [PATCH 01/14] Config management mode, to send/receive config via usb midi sysex --- NuEVI/NuEVI.ino | 43 ++++--- NuEVI/midi.cpp | 26 ++++- NuEVI/midi.h | 9 ++ NuEVI/settings.cpp | 213 ++++++++++++++++++++++++++++++++-- NuEVI/settings.h | 16 ++- simulation/include/Arduino.h | 4 +- simulation/src/nuevisim.cpp | 24 +++- simulation/src/simusbmidi.cpp | 9 ++ 8 files changed, 316 insertions(+), 28 deletions(-) diff --git a/NuEVI/NuEVI.ino b/NuEVI/NuEVI.ino index 2f22029..ff86900 100644 --- a/NuEVI/NuEVI.ino +++ b/NuEVI/NuEVI.ino @@ -236,6 +236,8 @@ byte subOctaveDouble = 0; Adafruit_MPR121 touchSensor = Adafruit_MPR121(); // This is the 12-input touch sensor FilterOnePole breathFilter; +bool configManagementMode = false; + //_______________________________________________________________________________________________ SETUP @@ -260,9 +262,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; @@ -273,8 +285,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){ @@ -329,6 +339,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(); @@ -356,7 +373,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)) @@ -364,9 +381,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 @@ -408,7 +425,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); @@ -854,7 +871,7 @@ void pitch_bend() { vibSignal = vibSignal * 0.5; } } else { //lever vibrato - vibRead = touchRead(vibratoPin); // SENSOR PIN 15 - built in var cap + vibRead = touchRead(vibratoPin); // SENSOR PIN 15 - built in var cap if (vibRead < vibThr) { if (UPWD == vibDirection) { vibSignal = vibSignal * 0.5 + 0.5 * map(constrain(vibRead, (vibZero - vibMax), vibThr), vibThr, (vibZero - vibMax), 0, calculatedPBdepth * vibDepth[vibrato]); @@ -1031,7 +1048,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 (!vibControl){ @@ -1096,7 +1113,7 @@ void portOff() { //*********************************************************** void readSwitches() { - + // Read touch pads (MPR121), compare against threshold value bool touchKeys[12]; for (byte i = 0; i < 12; i++) { @@ -1123,11 +1140,11 @@ void readSwitches() { K6 = touchKeys[K6Pin]; K7 = touchKeys[K7Pin]; - pinkyKey = (touchRead(halfPitchBendKeyPin) > touch_Thr); // SENSOR PIN 1 - PCB PIN "S1" + pinkyKey = (touchRead(halfPitchBendKeyPin) > touch_Thr); // SENSOR PIN 1 - PCB PIN "S1" int qTransp = pinkyKey ? 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" @@ -1146,7 +1163,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; } diff --git a/NuEVI/midi.cpp b/NuEVI/midi.cpp index 56e8d74..396d3e3 100644 --- a/NuEVI/midi.cpp +++ b/NuEVI/midi.cpp @@ -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>7 | (realdata & 0x007F) <<8; +} + +uint16_t midi14to16(uint16_t mididata) { + return (mididata & 0x7F00) >> 8 | (mididata & 0x007F) <<7 ; +} + +//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 midi32to28(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; +} diff --git a/NuEVI/midi.h b/NuEVI/midi.h index 800d1db..d663169 100644 --- a/NuEVI/midi.h +++ b/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,10 @@ 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 midi16to14(uint16_t realdata); +uint16_t midi14to16(uint16_t mididata); +uint32_t midi32to28(uint32_t realdata); + #endif diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 913e06f..109a959 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -1,15 +1,18 @@ #include #include +#include + #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(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); @@ -98,7 +101,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); @@ -199,3 +202,199 @@ 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(uint8_t *message, size_t length) { + size_t pos=0; + uint32_t crc=0xFFFFFFFF; + + while (pos> 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) = midi16to14(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, std::function); @@ -39,6 +39,8 @@ static const int scale = 3; static SDL_Window *window; +bool no_delay = false; + void _reboot_Teensyduino_() { // TODO: reboot @@ -81,6 +83,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); @@ -508,7 +512,7 @@ static void SimLoop(std::function continue_predicate, std::function bool { return true; }, loop ); SimQuit(); @@ -583,7 +595,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); } @@ -616,6 +628,8 @@ int main(int argc, const char** argv) args::ValueFlag eepromFile(parser, "eeprom-write", "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"}); parser.ParseCLI(argc, argv); @@ -628,5 +642,7 @@ int main(int argc, const char** argv) eepromFileName += "eeprom.bin"; } - return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset)); + no_delay = args::get(nodelay); + + return SimRun(eepromFileName, args::get(eepromWrite), args::get(factoryReset), args::get(configMode)); } diff --git a/simulation/src/simusbmidi.cpp b/simulation/src/simusbmidi.cpp index 42d1aa1..a240fc0 100644 --- a/simulation/src/simusbmidi.cpp +++ b/simulation/src/simusbmidi.cpp @@ -58,3 +58,12 @@ bool SimUsbMidi::read(uint8_t __unused channel) { return false; } +//Regular sysex handler +void SimUsbMidi::setHandleSystemExclusive(__unused void (*fptr) (const uint8_t *array, uint8_t size)) { + +} + +//"Chunked" sysex handler (teensy extension), for large messages +void SimUsbMidi::setHandleSystemExclusive(__unused void (*fptr) (const uint8_t *array, uint16_t size, bool last)) { + +} From 075905f7ea8acaa910a267553ef6c94e94097bc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 29 Jul 2019 17:23:38 +0200 Subject: [PATCH 02/14] Take exception to exceptions to handle argparser errors and help --- NuEVI/settings.h | 3 ++- simulation/src/nuevisim.cpp | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/NuEVI/settings.h b/NuEVI/settings.h index c1ba891..31ceee8 100644 --- a/NuEVI/settings.h +++ b/NuEVI/settings.h @@ -133,7 +133,8 @@ void sendSysexSettings(); void sendSysexMessage(const char* messageCode); void sendSysexVersion(); -void handleSysex(uint8_t *data, uint8_t length); +void handleSysex(const uint8_t *data, uint8_t length); +void handleSysexChunk(const uint8_t *data, uint16_t length, bool last); uint32_t crc32(uint8_t *message, size_t length); void configInitScreen(); diff --git a/simulation/src/nuevisim.cpp b/simulation/src/nuevisim.cpp index 305121f..127163f 100644 --- a/simulation/src/nuevisim.cpp +++ b/simulation/src/nuevisim.cpp @@ -1,5 +1,6 @@ #include #include +#include #include @@ -16,6 +17,7 @@ #include +#define ARGS_NOEXCEPT #include "args.hxx" // Forward declarations @@ -622,10 +624,10 @@ 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 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 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"}); @@ -633,6 +635,17 @@ int main(int argc, const char** argv) 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); //Use a default EEPROM file if none is provided. From 72b305830f2fed7773820b296ef365625c91da10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Tue, 30 Jul 2019 11:55:20 +0200 Subject: [PATCH 03/14] Add facility to receive config via sysex --- NuEVI/settings.cpp | 112 ++++++++++++++++++++++++++++++++++++++++----- NuEVI/settings.h | 14 +++--- 2 files changed, 107 insertions(+), 19 deletions(-) diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 109a959..b9a6ab8 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -12,7 +12,7 @@ #include "led.h" //Read settings from eeprom. Returns wether or not anything was written (due to factory reset or upgrade) -void readEEPROM(bool factoryReset) { +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); @@ -174,7 +174,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; @@ -184,7 +184,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; @@ -194,7 +194,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; @@ -207,7 +207,7 @@ uint16_t readSettingBounded(uint16_t address, uint16_t min, uint16_t max, uint16 //Functions to send and receive config (and other things) via USB MIDI SysEx messages -uint32_t crc32(uint8_t *message, size_t length) { +uint32_t crc32(const uint8_t *message, const size_t length) { size_t pos=0; uint32_t crc=0xFFFFFFFF; @@ -290,6 +290,86 @@ void sendSysexMessage(const char* messageCode) { 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(lengthbreathHiLimit) continue; + } + + if(addr == PORTAM_THR_ADDR || addr == PORTAM_MAX_ADDR) { + if(valportamHiLimit) continue; + } + + if(addr == PITCHB_THR_ADDR || addr == PITCHB_MAX_ADDR) { + if(valpitchbHiLimit) continue; + } + + if(addr == EXTRAC_THR_ADDR || addr == EXTRAC_MAX_ADDR) { + if(valextracHiLimit) continue; + } + + if(addr == CTOUCH_THR_ADDR) { + if(valctouchHiLimit) continue; + } + + writeSetting(i, val); + } + + //All went well + return true; +} + //Send EEPROM and firmware versions void sendSysexVersion() { char sysexMessage[] = "vvvNuEVIc04eevvvvvvvv"; //Placeholders for vendor and code @@ -318,7 +398,7 @@ void configShowMessage(const char* message) { uint8_t* sysex_rcv_buffer = NULL; -void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) { +void handleSysexChunk(const uint8_t *data, const uint16_t length, const bool last) { size_t pos = 0; if(!sysex_rcv_buffer) { @@ -342,24 +422,25 @@ void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) { } -void handleSysex(const uint8_t *data, uint8_t length) { +void handleSysex(const uint8_t *data, const 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<3) return; + if(length<4) return; //Verify vendor - if(strncmp((char*)data, sysex_id, 3)) return; //Silently ignore different vendor id + 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<11 || strncmp((char*)(data+3), "NuEVI", 3)) { - configShowMessage("Invalid message received."); + 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+8), 3); + strncpy(messageCode, (char*)(data+9), 3); if(!strncmp(messageCode, "c00", 3)) { //Config dump request configShowMessage("Sending config..."); @@ -368,6 +449,11 @@ void handleSysex(const uint8_t *data, uint8_t length) { } 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 @@ -391,6 +477,8 @@ void configModeSetup() { statusLedFlash(500); + sendSysexVersion(); //Friendly hello + configShowMessage("Ready."); } diff --git a/NuEVI/settings.h b/NuEVI/settings.h index 31ceee8..636a5bb 100644 --- a/NuEVI/settings.h +++ b/NuEVI/settings.h @@ -122,20 +122,20 @@ #define DAC_MODE_FACTORY DAC_MODE_BREATH -void readEEPROM(bool factoryReset); +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(const uint8_t *data, uint8_t length); -void handleSysexChunk(const uint8_t *data, uint16_t length, bool last); -uint32_t crc32(uint8_t *message, size_t length); +void handleSysex(const uint8_t *data, const unsigned int length); +void handleSysexChunk(const uint8_t *data, const uint16_t length, const bool last); +uint32_t crc32(const uint8_t *message, const size_t length); void configInitScreen(); void configShowMessage(const char* message); From 1cdface168fe5cdf436db81902a511729aeabc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Sun, 4 Aug 2019 10:51:48 +0200 Subject: [PATCH 04/14] Handle receiving of sysex conig data --- NuEVI/midi.cpp | 14 ++++-- NuEVI/midi.h | 7 +-- NuEVI/settings.cpp | 31 ++++++++----- simulation/include/Arduino.h | 10 +++- simulation/src/simusbmidi.cpp | 86 +++++++++++++++++++++++++++++++++-- 5 files changed, 124 insertions(+), 24 deletions(-) diff --git a/NuEVI/midi.cpp b/NuEVI/midi.cpp index 396d3e3..3dd09d7 100644 --- a/NuEVI/midi.cpp +++ b/NuEVI/midi.cpp @@ -184,16 +184,24 @@ void sendWLChannel(const uint8_t channel) { //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 midi16to14(uint16_t realdata) { +uint16_t midi16to14(const uint16_t realdata) { return (realdata & 0x3F80) >>7 | (realdata & 0x007F) <<8; } -uint16_t midi14to16(uint16_t mididata) { +uint16_t midi14to16(const uint16_t mididata) { return (mididata & 0x7F00) >> 8 | (mididata & 0x007F) <<7 ; } +//Read from a memory location, such as MIDI receive buffer +uint16_t midi14to16(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 midi32to28(uint32_t realdata) { +uint32_t midi32to28(const uint32_t realdata) { uint8_t* p = (uint8_t*)&realdata; uint32_t r=0; diff --git a/NuEVI/midi.h b/NuEVI/midi.h index d663169..a3af25b 100644 --- a/NuEVI/midi.h +++ b/NuEVI/midi.h @@ -38,8 +38,9 @@ void sendWLChannel(const uint8_t channel); //Convert things between "regular data" and MIDI data (byte order and 7-bits-per-byte) -uint16_t midi16to14(uint16_t realdata); -uint16_t midi14to16(uint16_t mididata); -uint32_t midi32to28(uint32_t realdata); +uint16_t midi16to14(const uint16_t realdata); +uint16_t midi14to16(const uint16_t mididata); +uint16_t midi14to16(const uint8_t* mididata); +uint32_t midi32to28(const uint32_t realdata); #endif diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index b9a6ab8..c8da141 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -305,6 +305,7 @@ bool receiveSysexSettings(const uint8_t* data, const uint16_t length) { //Make sure length of receive buffer is enough to read all we need to. We can accept extra junk at the end though. if(lengthctouchHiLimit) continue; } - writeSetting(i, val); + writeSetting(addr, val); } //All went well @@ -397,17 +399,21 @@ void configShowMessage(const char* message) { } uint8_t* sysex_rcv_buffer = NULL; +uint16_t sysex_buf_size = 0; -void handleSysexChunk(const uint8_t *data, const uint16_t length, const bool last) { - size_t pos = 0; +void handleSysexChunk(const uint8_t *data, const uint16_t length, const uint8_t last) { + uint16_t pos; if(!sysex_rcv_buffer) { //Start out with an empty buffer - sysex_rcv_buffer = (uint8_t *)malloc(length); + pos = 0; + sysex_buf_size = length; + sysex_rcv_buffer = (uint8_t *)malloc(sysex_buf_size); } else { //Increase size of current buffer - size_t pos = sizeof(sysex_rcv_buffer); - sysex_rcv_buffer = (uint8_t *)realloc(sysex_rcv_buffer, pos + length); + 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 @@ -415,9 +421,12 @@ void handleSysexChunk(const uint8_t *data, const uint16_t length, const bool las //If it's the last one, call the regular handler to process it if(last) { - handleSysex(sysex_rcv_buffer, pos+length); + handleSysex(sysex_rcv_buffer, sysex_buf_size); + + //Discard the buffer free(sysex_rcv_buffer); sysex_rcv_buffer = NULL; + sysex_buf_size = 0; } } diff --git a/simulation/include/Arduino.h b/simulation/include/Arduino.h index 8d0beff..ba160fe 100644 --- a/simulation/include/Arduino.h +++ b/simulation/include/Arduino.h @@ -85,8 +85,14 @@ public: 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(__unused void (*fptr) (const uint8_t *array, uint8_t size)); - void setHandleSystemExclusive(__unused void (*fptr) (const uint8_t *data, uint16_t length, bool complete)); + void setHandleSystemExclusive(void (*fptr) (const uint8_t *array, unsigned int size)); + void setHandleSystemExclusive(void (*fptr) (const uint8_t *data, uint16_t length, uint8_t complete)); + + void receiveMidiData(const uint8_t *data, const uint16_t length); //Send midi data "into simulator" +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); }; extern SimSerial Serial; diff --git a/simulation/src/simusbmidi.cpp b/simulation/src/simusbmidi.cpp index a240fc0..dfa5661 100644 --- a/simulation/src/simusbmidi.cpp +++ b/simulation/src/simusbmidi.cpp @@ -8,7 +8,6 @@ * 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); @@ -54,16 +53,93 @@ void SimUsbMidi::sendSysEx(uint16_t length, const uint8_t __unused *data, bool _ printf( "[usbMIDI::sysEx] len %d\n", length); } +//Set a low chunk size on purpose just to let the receiver work for it +#define MIDI_SYSEX_CHUNK_SIZE 32 + +/* Test data for config mode + +//Carefully crafted config command chunk to send via midi +static const uint8_t midimessage[] = { + 0xf0, //Sysex start + 0x00, 0x3e, 0x7f, //Vendor + 'N', 'u', 'E', 'V', 'I', //header + 'c', '0', '2', //message code + 0, 102, //length + + //Payload + 0x00, 0x20, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, //00 + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x06, 0x07, //08 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //10 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //18 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //20 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //28 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //30 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //38 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //40 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //48 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //50 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //58 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, //60 + 0x2a, 0x11, 0x32, 0x5a, //crc32 + 0xf7 //sysex end marker +}; + +static bool midisent = false; + +//On first midi read, send a message +bool SimUsbMidi::read(uint8_t __unused channel) { + if(!midisent) { + this->receiveMidiData(midimessage, sizeof(midimessage)); + midisent=true; + } + return false; +} + +*/ + bool SimUsbMidi::read(uint8_t __unused channel) { return false; } -//Regular sysex handler -void SimUsbMidi::setHandleSystemExclusive(__unused void (*fptr) (const uint8_t *array, uint8_t size)) { +//Provide midi data for simulation to receive +void SimUsbMidi::receiveMidiData(const uint8_t *data, const uint16_t length) { + 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(posusb_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 + } +} + +//Regular sysex handler. For some reason the data pointer is not const, but we'll set it as such to not be dumb. +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(__unused void (*fptr) (const uint8_t *array, uint16_t size, bool last)) { - +void SimUsbMidi::setHandleSystemExclusive(void (*fptr) (const uint8_t *array, uint16_t size, uint8_t last)) { + this->usb_midi_handleSysExPartial = fptr; } From 8e6effaa4189bb9c65eaed142d7cc58341b49235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Sun, 4 Aug 2019 10:53:54 +0200 Subject: [PATCH 05/14] no printf() in teensy code --- NuEVI/settings.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index c8da141..d6d92d6 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -268,11 +268,6 @@ void sendSysexSettings() { uint32_t checksum = crc32(sysex_data, checksum_pos); -/* - printf("CRC len: %d\n", checksum_pos); - printf("CRC32: %X | %u\n", checksum, checksum); -*/ - *(uint32_t*)(sysex_data+checksum_pos) = midi32to28(checksum); usbMIDI.sendSysEx(sysex_size, sysex_data); @@ -305,7 +300,6 @@ bool receiveSysexSettings(const uint8_t* data, const uint16_t length) { //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 Date: Sun, 4 Aug 2019 11:20:17 +0200 Subject: [PATCH 06/14] Actually match teensy/arduino signatures --- NuEVI/settings.cpp | 5 +++-- NuEVI/settings.h | 5 +++-- simulation/include/Arduino.h | 2 +- simulation/src/simusbmidi.cpp | 6 +++--- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index d6d92d6..2403a97 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -393,7 +393,8 @@ void configShowMessage(const char* message) { uint8_t* sysex_rcv_buffer = NULL; uint16_t sysex_buf_size = 0; -void handleSysexChunk(const uint8_t *data, const uint16_t length, const uint8_t last) { + +void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) { uint16_t pos; if(!sysex_rcv_buffer) { @@ -423,7 +424,7 @@ void handleSysexChunk(const uint8_t *data, const uint16_t length, const uint8_t } -void handleSysex(const uint8_t *data, const unsigned int length) { +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. diff --git a/NuEVI/settings.h b/NuEVI/settings.h index 636a5bb..15fe0c6 100644 --- a/NuEVI/settings.h +++ b/NuEVI/settings.h @@ -133,8 +133,9 @@ void sendSysexSettings(); void sendSysexMessage(const char* messageCode); void sendSysexVersion(); -void handleSysex(const uint8_t *data, const unsigned int length); -void handleSysexChunk(const uint8_t *data, const uint16_t length, const bool last); +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(); diff --git a/simulation/include/Arduino.h b/simulation/include/Arduino.h index ba160fe..534f404 100644 --- a/simulation/include/Arduino.h +++ b/simulation/include/Arduino.h @@ -86,7 +86,7 @@ public: 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, uint8_t complete)); + void setHandleSystemExclusive(void (*fptr) (const uint8_t *data, uint16_t length, bool complete)); void receiveMidiData(const uint8_t *data, const uint16_t length); //Send midi data "into simulator" private: diff --git a/simulation/src/simusbmidi.cpp b/simulation/src/simusbmidi.cpp index dfa5661..e22cea7 100644 --- a/simulation/src/simusbmidi.cpp +++ b/simulation/src/simusbmidi.cpp @@ -134,12 +134,12 @@ void SimUsbMidi::receiveMidiData(const uint8_t *data, const uint16_t length) { } } -//Regular sysex handler. For some reason the data pointer is not const, but we'll set it as such to not be dumb. +//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, uint8_t last)) { - this->usb_midi_handleSysExPartial = fptr; +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; } From 3b405adebb717a728b3cc7810c3293d756b58e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 5 Aug 2019 14:10:41 +0200 Subject: [PATCH 07/14] Add example files for sysex commands --- simulation/mididata/c00_config_request.mid | Bin 0 -> 13 bytes simulation/mididata/c02_new_config.mid | Bin 0 -> 121 bytes simulation/mididata/c03_version_request.mid | Bin 0 -> 13 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 simulation/mididata/c00_config_request.mid create mode 100644 simulation/mididata/c02_new_config.mid create mode 100644 simulation/mididata/c03_version_request.mid diff --git a/simulation/mididata/c00_config_request.mid b/simulation/mididata/c00_config_request.mid new file mode 100644 index 0000000000000000000000000000000000000000..751b6768905206bdf0e0e3ccfb5206cca9488360 GIT binary patch literal 13 UcmeysU{~)~>Kf*mY+&#m03!+o4*&oF literal 0 HcmV?d00001 diff --git a/simulation/mididata/c02_new_config.mid b/simulation/mididata/c02_new_config.mid new file mode 100644 index 0000000000000000000000000000000000000000..1e33d3fe16e84bdac4496e3242827bfec7a3f7bf GIT binary patch literal 121 pcmeysU{~)~>Kf*mY+%HY#-LD-1lZUa7@3$^SgB2G2^vLx2LPoI4Tb;! literal 0 HcmV?d00001 diff --git a/simulation/mididata/c03_version_request.mid b/simulation/mididata/c03_version_request.mid new file mode 100644 index 0000000000000000000000000000000000000000..c2210f36eaef0b26bfab86e55534ae4a00365fbb GIT binary patch literal 13 UcmeysU{~)~>Kf*mY+(Ey03#3u5&!@I literal 0 HcmV?d00001 From 4dbca53871198c5bc362366c5c85eb0ee3ed432d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Mon, 5 Aug 2019 14:11:25 +0200 Subject: [PATCH 08/14] Add functionality to use files as MIDI data to send to simulated device --- simulation/include/Arduino.h | 23 +--------- simulation/include/simusbmidi.h | 35 +++++++++++++++ simulation/src/nuevisim.cpp | 18 +++++++- simulation/src/simeeprom.cpp | 2 +- simulation/src/simusbmidi.cpp | 79 ++++++++++++++++----------------- 5 files changed, 91 insertions(+), 66 deletions(-) create mode 100644 simulation/include/simusbmidi.h diff --git a/simulation/include/Arduino.h b/simulation/include/Arduino.h index 534f404..a8bced0 100644 --- a/simulation/include/Arduino.h +++ b/simulation/include/Arduino.h @@ -8,6 +8,7 @@ #include #include "Wiring.h" +#include "simusbmidi.h" #include "core_pins.h" @@ -72,28 +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); - void setHandleSystemExclusive(void (*fptr) (const uint8_t *array, unsigned int size)); - void setHandleSystemExclusive(void (*fptr) (const uint8_t *data, uint16_t length, bool complete)); - - void receiveMidiData(const uint8_t *data, const uint16_t length); //Send midi data "into simulator" -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); -}; extern SimSerial Serial; extern SimSerial Serial3; //Used for MIDI serial putput with default hardware diff --git a/simulation/include/simusbmidi.h b/simulation/include/simusbmidi.h new file mode 100644 index 0000000..05c31cb --- /dev/null +++ b/simulation/include/simusbmidi.h @@ -0,0 +1,35 @@ +#ifndef __SIMUSBMIDI_H__ +#define __SIMUSBMIDI_H__ + +#include + +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 \ No newline at end of file diff --git a/simulation/src/nuevisim.cpp b/simulation/src/nuevisim.cpp index 127163f..f7b3429 100644 --- a/simulation/src/nuevisim.cpp +++ b/simulation/src/nuevisim.cpp @@ -14,6 +14,7 @@ #include "examples/imgui_impl_sdl.h" #include "examples/imgui_impl_opengl3.h" #include "EEPROM.h" +#include "simusbmidi.h" #include @@ -413,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 continue_predicate, std::function loopFunc) { @@ -455,6 +459,7 @@ static void SimLoop(std::function continue_predicate, std::function 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); @@ -647,14 +654,21 @@ int main(int argc, const char** argv) } 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"; } + 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)); diff --git a/simulation/src/simeeprom.cpp b/simulation/src/simeeprom.cpp index c3fb3c5..1f0b27f 100644 --- a/simulation/src/simeeprom.cpp +++ b/simulation/src/simeeprom.cpp @@ -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) { diff --git a/simulation/src/simusbmidi.cpp b/simulation/src/simusbmidi.cpp index e22cea7..4560225 100644 --- a/simulation/src/simusbmidi.cpp +++ b/simulation/src/simusbmidi.cpp @@ -1,8 +1,10 @@ #include #include +#include +#include -#include "Arduino.h" +#include "simusbmidi.h" /************************************* * Stub simulation of Teensy usbMidi @@ -56,54 +58,41 @@ void SimUsbMidi::sendSysEx(uint16_t length, const uint8_t __unused *data, bool _ //Set a low chunk size on purpose just to let the receiver work for it #define MIDI_SYSEX_CHUNK_SIZE 32 -/* Test data for config mode - -//Carefully crafted config command chunk to send via midi -static const uint8_t midimessage[] = { - 0xf0, //Sysex start - 0x00, 0x3e, 0x7f, //Vendor - 'N', 'u', 'E', 'V', 'I', //header - 'c', '0', '2', //message code - 0, 102, //length - - //Payload - 0x00, 0x20, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, //00 - 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x06, 0x07, //08 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //10 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //18 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //20 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //28 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //30 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //38 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //40 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //48 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //50 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, //58 - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, //60 - 0x2a, 0x11, 0x32, 0x5a, //crc32 - 0xf7 //sysex end marker -}; - -static bool midisent = false; - -//On first midi read, send a message bool SimUsbMidi::read(uint8_t __unused channel) { - if(!midisent) { - this->receiveMidiData(midimessage, sizeof(midimessage)); - midisent=true; + 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; -} -*/ - -bool SimUsbMidi::read(uint8_t __unused channel) { 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) { @@ -115,7 +104,7 @@ void SimUsbMidi::receiveMidiData(const uint8_t *data, const uint16_t length) { int pos=0; while(posusb_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; +} From e264b01dfda984c781c8f9901c7eba0fa12c53fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Wed, 7 Aug 2019 17:36:41 +0200 Subject: [PATCH 09/14] Use wiring.h with proper case, so building hopefully works on Linux --- NuEVI/globals.h | 2 +- NuEVI/menu.h | 2 +- simulation/include/{Wiring.h => wiring.h} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename simulation/include/{Wiring.h => wiring.h} (100%) diff --git a/NuEVI/globals.h b/NuEVI/globals.h index 03fabd6..caa14cf 100644 --- a/NuEVI/globals.h +++ b/NuEVI/globals.h @@ -1,7 +1,7 @@ #ifndef __GLOBALS_H #define __GLOBALS_H -#include "Wiring.h" +#include "wiring.h" // The three states of our main state machine diff --git a/NuEVI/menu.h b/NuEVI/menu.h index 20373ab..8fd4016 100644 --- a/NuEVI/menu.h +++ b/NuEVI/menu.h @@ -1,7 +1,7 @@ #ifndef __MENU_H #define __MENU_H -#include "Wiring.h" +#include "wiring.h" #include "numenu.h" #define MENU_ROW_HEIGHT 9 diff --git a/simulation/include/Wiring.h b/simulation/include/wiring.h similarity index 100% rename from simulation/include/Wiring.h rename to simulation/include/wiring.h From fc36e5939becd3ed452c057913345aba7e054721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Fri, 9 Aug 2019 08:19:02 +0200 Subject: [PATCH 10/14] Add "magic" CRC value that skips verification (for testing purposes) --- NuEVI/settings.cpp | 2 +- NuEVI/settings.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 2403a97..0dbc1a8 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -310,7 +310,7 @@ bool receiveSysexSettings(const uint8_t* data, const uint16_t length) { uint32_t crc=midi32to28(crc32(data, checksum_pos)); uint32_t crc_rcv; memcpy(&crc_rcv, data+checksum_pos, 4); - if(crc != crc_rcv) { + if(crc != crc_rcv && crc_rcv != NO_CHECKSUM) { configShowMessage("Invalid checksum"); return false; } diff --git a/NuEVI/settings.h b/NuEVI/settings.h index 15fe0c6..32f64df 100644 --- a/NuEVI/settings.h +++ b/NuEVI/settings.h @@ -121,6 +121,7 @@ #define TRILL3_INTERVAL_FACTORY 4 #define DAC_MODE_FACTORY DAC_MODE_BREATH +#define NO_CHECKSUM 0x7F007F00 void readEEPROM(const bool factoryReset); void setBit(uint16_t &bitfield, const uint8_t pos, const uint16_t value); From 186be9ceb62d2fa6988bf4f97d3380fba4e2e01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Thu, 15 Aug 2019 07:53:04 +0200 Subject: [PATCH 11/14] Proper bounds for breath cc value --- NuEVI/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 0dbc1a8..3f5fe53 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -111,7 +111,7 @@ void readEEPROM(const bool factoryReset) { 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); From 660d6583b3af067cd027531865bcded30b10606c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Thu, 15 Aug 2019 08:04:14 +0200 Subject: [PATCH 12/14] Rename midi value conversion functions to something that possibly makes more sense --- NuEVI/midi.cpp | 8 ++++---- NuEVI/midi.h | 8 ++++---- NuEVI/settings.cpp | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/NuEVI/midi.cpp b/NuEVI/midi.cpp index 3dd09d7..9d6ca30 100644 --- a/NuEVI/midi.cpp +++ b/NuEVI/midi.cpp @@ -184,16 +184,16 @@ void sendWLChannel(const uint8_t channel) { //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 midi16to14(const uint16_t realdata) { +uint16_t convertToMidiValue(const uint16_t realdata) { return (realdata & 0x3F80) >>7 | (realdata & 0x007F) <<8; } -uint16_t midi14to16(const uint16_t mididata) { +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 midi14to16(const uint8_t* mididata) { +uint16_t convertFromMidiValue(const uint8_t* mididata) { uint8_t msb = *mididata; uint8_t lsb = *(mididata+1); @@ -201,7 +201,7 @@ uint16_t midi14to16(const uint8_t* mididata) { } //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 midi32to28(const uint32_t realdata) { +uint32_t convertToMidiCRC(const uint32_t realdata) { uint8_t* p = (uint8_t*)&realdata; uint32_t r=0; diff --git a/NuEVI/midi.h b/NuEVI/midi.h index a3af25b..36edd83 100644 --- a/NuEVI/midi.h +++ b/NuEVI/midi.h @@ -38,9 +38,9 @@ void sendWLChannel(const uint8_t channel); //Convert things between "regular data" and MIDI data (byte order and 7-bits-per-byte) -uint16_t midi16to14(const uint16_t realdata); -uint16_t midi14to16(const uint16_t mididata); -uint16_t midi14to16(const uint8_t* mididata); -uint32_t midi32to28(const uint32_t realdata); +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 diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 3f5fe53..4562bff 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -255,7 +255,7 @@ void sendSysexSettings() { memcpy(sysex_data+header_pos, header, strlen(header)); //Payload length - *(uint16_t*)(sysex_data+size_pos) = midi16to14(EEPROM_SIZE); + *(uint16_t*)(sysex_data+size_pos) = convertToMidiValue(EEPROM_SIZE); //Config data uint16_t* config_buffer_start = (uint16_t*)(sysex_data+payload_pos); @@ -263,12 +263,12 @@ void sendSysexSettings() { //Read one settings item at a time, change data format, and put in send buffer for(uint16_t idx=0; idx Date: Thu, 15 Aug 2019 08:04:41 +0200 Subject: [PATCH 13/14] Upper bound on length of version string for sysex message --- NuEVI/settings.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NuEVI/settings.cpp b/NuEVI/settings.cpp index 4562bff..d3eab09 100644 --- a/NuEVI/settings.cpp +++ b/NuEVI/settings.cpp @@ -367,13 +367,14 @@ bool receiveSysexSettings(const uint8_t* data, const uint16_t length) { //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, min(strlen(FIRMWARE_VERSION), 8)); + memcpy(sysexMessage+13, FIRMWARE_VERSION, fwStrLen); *(uint16_t*)(sysexMessage+11) = convertToMidiValue(EEPROM_VERSION); - uint8_t message_length = 13+strlen(FIRMWARE_VERSION); + uint8_t message_length = 13+fwStrLen; usbMIDI.sendSysEx(message_length, (const uint8_t *)sysexMessage); } From 11cdbc4ec3b1e4bb440773021c04caf9ca98d942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20St=C3=A4ck?= Date: Thu, 15 Aug 2019 08:05:27 +0200 Subject: [PATCH 14/14] Simulator: Print out full sysex message, with very clever line ending --- simulation/src/simusbmidi.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/simulation/src/simusbmidi.cpp b/simulation/src/simusbmidi.cpp index 4560225..12c604f 100644 --- a/simulation/src/simusbmidi.cpp +++ b/simulation/src/simusbmidi.cpp @@ -50,9 +50,12 @@ 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