diff --git a/OSC2MIDI.ino b/OSC2MIDI.ino index 88c3da4..2c4539c 100644 --- a/OSC2MIDI.ino +++ b/OSC2MIDI.ino @@ -17,30 +17,38 @@ #include #include #include - -#define MDNS_NAME "osc2midi" -#define AP_SSID_NAME "OSC2MIDI" -#define AP_PASSWORD "osc2midi" -#define AP_SSID_CONFIG_NAME "OSC2MIDI-Config" -#define AP_CONFIG_PASSWORD "osc2midi" -#define SOFT_SERIAL_RX 18 -#define SOFT_SERIAL_TX 19 -#define AP_DATA_RESET_PIN 35 -#define AP_MODE_PIN 34 -#define LCD_I2C_ADDR 0x27 -#define LCD_COL 16 -#define LCD_ROW 2 -#define UDP_RECV_PORT 8000 -#define UDP_SEND_PORT 9000 -#define KRATE_MODE 200 -#define KRATE_STATE 2000 +#include +#include + +#define MDNS_NAME "osc2midi" +#define AP_SSID_NAME "OSC2MIDI" +#define AP_PASSWORD "osc2midi" +#define AP_SSID_CONFIG_NAME "OSC2MIDI-Config" +#define AP_CONFIG_PASSWORD "osc2midi" +#define WIFI_CONNECT_TIMEOUT 30 +#define CFG_PORTAL_TIMEOUT 90 +#define SOFT_SERIAL_RX 18 +#define SOFT_SERIAL_TX 19 +#define AP_DATA_RESET_PIN 25 +#define AP_MODE_PIN 26 +#define LCD_I2C_ADDR 0x27 +#define LCD_COL 16 +#define LCD_ROW 2 +#define UDP_RECV_PORT 8000 +#define UDP_SEND_PORT 9000 +#define KRATE_MODE 200 +#define KRATE_STATE 2000 +#define KRATE_RESET_AP_DATA 5000 +#define KRATE_CHECK_WRITE_STATE 10000 +#define LAST_USAGE_TIMER 5000 +#define FORMAT_SPIFFS_IF_FAILED true void OSCToMidiCC(OSCMessage &msg, int offset); void MidiCCToOSC(uint8_t channel, uint8_t number, uint8_t value); -void change_midi_state(uint8_t midichannel, uint8_t cc, uint8_t value); -void show_midi_state(void); -void set_midi_state(void); -void check_mode(void); +/*void change_midi_state(uint8_t midichannel, uint8_t cc, uint8_t value); + void show_midi_state(void); + void set_midi_state(void); + void check_mode(void);*/ WiFiUDP udp; IPAddress clientIP; @@ -54,19 +62,29 @@ HardwareSerial midi1(2); // RX: 16, TX: 17 //#define TX (1) #endif SoftwareSerial midi2; -bool ap_mode_state = digitalRead(AP_MODE_PIN); -int8_t state[16][128]; - +bool ap_mode_state; +int8_t midistate[16 * 128]; +bool last_reset_ap_check = false; looper sched; +bool write_state = false; +uint32_t last_usage = millis(); MIDI_CREATE_INSTANCE(HardwareSerial, midi1, MIDI1); void setup() { - pinMode(AP_DATA_RESET_PIN, INPUT_PULLUP); - pinMode(AP_MODE_PIN, INPUT_PULLUP); - Serial.begin(115200); + + DEBUG_MSG("\n"); + + //Serial.print("FORMAT SPIFFS..."); SPIFFS.format(); Serial.println("done."); while (1); + + memset(midistate, -1, 16 * 128); + + pinMode(AP_DATA_RESET_PIN, INPUT_PULLDOWN); + pinMode(AP_MODE_PIN, INPUT_PULLDOWN); + ap_mode_state = digitalRead(AP_MODE_PIN); + Serial.setDebugOutput(true); Serial.println(F("OSC2MIDI (c)2020 H. Wirtz ")); @@ -80,6 +98,20 @@ void setup() lcd.print(F("(c)parasiTstudio")); delay(1000); + WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP + + if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) + { + DEBUG_MSG("An Error has occurred while mounting SPIFFS!"); + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print(F("SPIFFS ERROR")); + lcd.setCursor(0, 1); + lcd.print(F("Restarting...")); + delay(1000); + ESP.restart(); + } + if (ap_mode_state == LOW) { DEBUG_MSG("Mode Access-Point\n"); @@ -108,31 +140,18 @@ void setup() { DEBUG_MSG("Mode Client\n"); - WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP - WiFiManager wm; - if (digitalRead(AP_DATA_RESET_PIN) != LOW) - { - wm.resetSettings(); - DEBUG_MSG("Resetting AP data\n"); - lcd.clear(); - lcd.setCursor(0, 0); - lcd.print(F("Resetting AP data")); - delay(2000); - } - - // Automatically connect using saved credentials, - // if connection fails, it starts an access point with the specified name ( "AutoConnectAP"), - // if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect()) - // then goes into a blocking loop awaiting configuration and will return success result - lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("Mode Config-AP")); lcd.setCursor(0, 1); lcd.print(F("192.168.4.1")); + wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT); + wm.setConfigPortalTimeout(CFG_PORTAL_TIMEOUT); + wm.setBreakAfterConfig(true); + if (!wm.autoConnect(AP_SSID_CONFIG_NAME, AP_CONFIG_PASSWORD)) { DEBUG_MSG("Failed to connect\n"); @@ -163,8 +182,6 @@ void setup() lcd.print(WiFi.localIP()); } - memset(state, -1, 16 * 128); - udp.begin(UDP_RECV_PORT); DEBUG_MSG("Listening for UDP packets on port %d\n", UDP_RECV_PORT); @@ -176,10 +193,16 @@ void setup() MIDI1.setHandleControlChange(MidiCCToOSC); MIDI1.turnThruOff(); - //set_midi_state(); - sched.addJob(check_mode, KRATE_MODE); - sched.addJob(show_midi_state, KRATE_STATE); + sched.addJob(check_reset_ap_data, KRATE_RESET_AP_DATA); + sched.addJob(check_write_state, KRATE_CHECK_WRITE_STATE); + //sched.addJob(show_midi_state, KRATE_STATE); + + listDir(SPIFFS, "/", 1); + //read_midistate(1); + show_midi_state(); + + DEBUG_MSG("\n"); } void loop() @@ -190,10 +213,15 @@ void loop() size_t size = udp.parsePacket(); - while (udp.available()) + if (udp.available()) { IPAddress tmpIP = udp.remoteIP(); - + // Keep track of the client IP address for "talking back" + if (clientIP != tmpIP) + { + clientIP = tmpIP; + DEBUG_MSG("New connection from: %d.%d.%d.%d\n", clientIP[0],clientIP[1],clientIP[2],clientIP[3]); + } // Check if there are any OSC packets to handle udp.read(buffer, size); msg.fill(buffer, size); @@ -202,6 +230,7 @@ void loop() { DEBUG_OSC_MESSAGE(msg); msg.route("/midi/cc", OSCToMidiCC); + msg.route("/ping", ping); //msg.route("/midi/sysex", OSCToMidiSYSEX); //msg.route("/midi/note", OSCToMidiNote); } @@ -209,13 +238,6 @@ void loop() { DEBUG_MSG("Error parsing OSC message: %d\n", msg.getError()); } - - // Keep track of the client IP address for "talking back" - if (clientIP == tmpIP) - { - clientIP = tmpIP; - DEBUG_MSG("Connection from: %s", clientIP); - } } // Check if there are any CC messages from synth itself @@ -269,13 +291,13 @@ void OSCToMidiCC(OSCMessage & msg, int offset) // XY pad, two values cc = getVar(address, 1); value = round(msg.getFloat(0)); - value = value > 127 ? 127 : value; + value = constrain(value, 0, 127); DEBUG_MSG("MSG: % s\tChannel: % u\t\tCC: % u\tValue: % u\n", address, midichannel, cc, value); MIDI1.sendControlChange(cc, value, midichannel); change_midi_state(midichannel, cc, value); cc = getVar(address, 2); value = round(msg.getFloat(1)); - value = value > 127 ? 127 : value; + value = constrain(value, 0, 127); DEBUG_MSG("MSG: % s\tChannel: % u\t\tCC: % u\tValue: % u\n", address, midichannel, cc, value); MIDI1.sendControlChange(cc, value, midichannel); change_midi_state(midichannel, cc, value); @@ -290,9 +312,17 @@ void MidiCCToOSC(uint8_t channel, uint8_t number, uint8_t val) { char buffer[1024]; - snprintf(buffer, sizeof(buffer), " / midi / cc / % u / % u", channel, number); + if (channel < 1 && channel > 16) + return; + if (number > 127) + return; + val = constrain(val, 0, 127); + + snprintf(buffer, sizeof(buffer), "/midi/cc/%d/%d", channel, number); + + DEBUG_MSG("MidiCCToOsc: %s %f\n", buffer, val); - DEBUG_MSG("MidiCCToOsc: % s % f\n", buffer, val * 1.0); + change_midi_state(channel, number, val); if (clientIP) { @@ -302,12 +332,26 @@ void MidiCCToOSC(uint8_t channel, uint8_t number, uint8_t val) udp.beginPacket(clientIP, UDP_SEND_PORT); msg.send(udp); udp.endPacket(); + + msg.empty(); + } + else + { + DEBUG_MSG("No client IP.\n"); } } void change_midi_state(uint8_t midichannel, uint8_t cc, uint8_t value) { - state[midichannel - 1][cc - 1] = value; + last_usage = millis(); + + DEBUG_MSG("Setting internal state of MIDI Channel %2d CC#%02d to %d\n", midichannel, cc, int8_t(value)); + + if (midistate[(midichannel - 1) * 128 + cc - 1] != int8_t(value)) + { + midistate[(midichannel - 1) * 128 + cc - 1] = int8_t(value); + write_state = true; + } } void show_midi_state(void) @@ -316,52 +360,227 @@ void show_midi_state(void) DEBUG_MSG("Current MIDI state:\n"); + listDir(SPIFFS, "/", 1); + for (m = 0; m < 16; m++) { - DEBUG_MSG("MIDI-Channel %d\n", m + 1); + DEBUG_MSG("MIDI-Channel %2d\n", m + 1); for (c = 0; c < 128; c++) { - if (state[m][c] >= 0) + if (midistate[m * 128 + c] >= 0) { - DEBUG_MSG("\tCC: %03d = %03d\n", c, state[m][c]); + DEBUG_MSG("\tCC: %3d = %3d\n", c + 1, midistate[m * 128 + c]); } } } } -/* - void set_midi_state(void) +void check_mode(void) +{ + if (ap_mode_state != digitalRead(AP_MODE_PIN)) + { + lcd.clear(); + lcd.setCursor(0, 0); + lcd.print("Changing mode..."); + delay(500); + ESP.restart(); + } +} + +void check_write_state(void) +{ + DEBUG_MSG("Checking midi state.\n"); + + if (write_state == true && millis() - last_usage > LAST_USAGE_TIMER) { - uint8_t tmp; - uint8_t m; - uint16_t i; + write_midistate(1); + write_state = false; + show_midi_state(); + } +} - DEBUG_MSG("Send MIDI state:\n"); +void write_midistate(uint8_t setup_number) +{ + DEBUG_MSG("Write MIDI state\n"); - for (m = 0; m < 16; m++); + for (uint8_t m = 0; m < 16; m++) { - if (state[m].size() > 0) + for (uint8_t c = 0; c < 128; c++) { - DEBUG_MSG("MIDI-Channel %d\n"); - for (i = 0; i < state[m].size(); i++) + if (midistate[m * 128 + c] >= 0) { - tmp = state[m].get(i); - MIDI1.sendControlChange(tmp >> 4, tmp & 0x0f, m); - DEBUG_MSG("\tSet state of CC: %03d = %03d\n", tmp >> 4, tmp & 0x0f); + DEBUG_MSG("Found MIDI Channel %2d, CC#%2d = %3d\n", m, c, midistate[m * 128 + c]); + + char midi_cc_name[33]; + int16_t tmp_val; + + sprintf(midi_cc_name, "/%d/midistate/%d/%d", setup_number, m, c); + + if (SPIFFS.exists(midi_cc_name)) + { + File midi_cc = SPIFFS.open(midi_cc_name, "r"); + if (midi_cc) + { + tmp_val = midi_cc.read(); + DEBUG_MSG("Data for MIDI Channel %d, CC %d exists: %d\n", m, c, tmp_val); + } + close(midi_cc); + } + else + { + tmp_val = -1; + } + + if (midistate[m * 128 + c] != tmp_val) + { + DEBUG_MSG(" Change from %d to %d detected\n", tmp_val, midistate[m * 128 + c]); + + File midi_cc = SPIFFS.open(midi_cc_name, "w"); + if (midi_cc) + { + midi_cc.write(midistate[m * 128 + c]); + midi_cc.flush(); + DEBUG_MSG("Wrote %d to %s.\n", midistate[m * 128 + c], midi_cc_name); + } + else + DEBUG_MSG("Cannot write to %s.\n", midi_cc_name); + close(midi_cc); + } } } } +} + +void read_midistate(uint8_t setup_number) +{ + char setup_dir_name[33]; + + DEBUG_MSG("Read MIDI state\n"); + + sprintf(setup_dir_name, "/%d/midistate", setup_number); + + File midistate_dir = SPIFFS.open(setup_dir_name); + + if (!midistate_dir) + { + DEBUG_MSG("Failed to open directory %s.\n", setup_dir_name); + return; } -*/ -void check_mode(void) + if (!midistate_dir.isDirectory()) + { + DEBUG_MSG("%s is not a directory.\n", setup_dir_name); + return; + } + + File channel_cc = midistate_dir.openNextFile(); + + while (channel_cc) + { + DEBUG_MSG("Trying %s (Size: %d)\n", channel_cc.name(), channel_cc.size()); + + if (uint8_t(channel_cc.size()) == 1) + { + if (!channel_cc.isDirectory()) + { + char tmp_name[33]; + uint8_t midi_channel; + uint8_t midi_cc; + uint8_t count = 0; + + DEBUG_MSG("Using %s\n", channel_cc.name()); + + strcpy(tmp_name, channel_cc.name()); + for (String part = strtok(tmp_name, "/"); part; part = strtok(NULL, "/")) + { + count++; + if (count == 3) + midi_channel = atoi(part.c_str()); + else if (count == 4) + midi_cc = atoi(part.c_str()); + } + + File cc = SPIFFS.open(channel_cc.name(), "r"); + if (cc) + { + int8_t val = cc.read(); + DEBUG_MSG(" MIDI-Channel %d CC#%d = %d\n", midi_channel + 1, midi_cc + 1, val); + midistate[midi_channel * 128 + midi_cc] = val; + MidiCCToOSC(midi_channel + 1, midi_cc + 1, val); + cc.close(); + } + } + } + else + { + DEBUG_MSG("Removing %s: not the right size.\n", channel_cc.name()); + SPIFFS.remove(channel_cc.name()); + } + channel_cc = midistate_dir.openNextFile(); + } +} + +void check_reset_ap_data(void) { - if (ap_mode_state != digitalRead(AP_MODE_PIN)) + if (digitalRead(AP_DATA_RESET_PIN) == HIGH && last_reset_ap_check == true) { + DEBUG_MSG("Reset AP data\n"); + + WiFiManager wm; + lcd.clear(); lcd.setCursor(0, 0); - lcd.print("Changing mode..."); - delay(500); + lcd.print("Firmware reset"); + wm.resetSettings(); + SPIFFS.format(); + lcd.setCursor(0, 1); + lcd.print("Done."); + delay(1000); ESP.restart(); } + else if (digitalRead(AP_DATA_RESET_PIN) == HIGH) + { + if (digitalRead(AP_DATA_RESET_PIN) == HIGH) + DEBUG_MSG("Reset AP data pressed\n"); + last_reset_ap_check = true; + } + else + last_reset_ap_check = false; +} + +void ping(OSCMessage & msg, int offset) +{ + if (clientIP) + read_midistate(1); +} + +void listDir(fs::FS & fs, const char * dirname, uint8_t levels) { + Serial.printf("Listing directory: %s\r\n", dirname); + + File root = fs.open(dirname); + if (!root) { + Serial.println("- failed to open directory"); + return; + } + if (!root.isDirectory()) { + Serial.println(" - not a directory"); + return; + } + + File file = root.openNextFile(); + while (file) { + if (file.isDirectory()) { + Serial.print(" DIR : "); + Serial.println(file.name()); + if (levels) { + listDir(fs, file.name(), levels - 1); + } + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print("\tSIZE: "); + Serial.println(file.size()); + } + file = root.openNextFile(); + } }