/* OSC2MIDI OSC2MIDI is a heavily extended port of https://github.com/tadas-s/OSC2Midi (c)2020 H. Wirtz This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // Use from 0 to 4. Higher number, more debugging messages and memory usage. #define _WIFIMGR_LOGLEVEL_ 1 #define DEBUG 1 #include #include #include #include "debug.h" #include #include #include #include #include #include #include "OSC2Midi.h" #include #include #include #include #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 WIFI_CONNECT_TIMEOUT 30 #define CFG_PORTAL_TIMEOUT 90 #define SOFT_SERIAL_RX 18 #define SOFT_SERIAL_TX 19 #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 #define MIDI_SOLO_VOLUME 100 #define MIDI_IGNORE_VOLUME_CC 150 #define ENC_BUTTON_PIN 32 #define ENC_A_PIN 34 #define ENC_B_PIN 35 #define MAX_CLIENTS 5 #define KRATE_CHECK_CLIENTIP 2000 #define CLIENT_TIMEOUT 10000 void OSCToMidiCC(OSCMessage &msg, int offset); void OSCMixerMuteToMidiCC(OSCMessage &msg, int offset); void OSCMixerSoloToMidiCC(OSCMessage &msg, int offset); void MidiCCToOSC(uint8_t channel, uint8_t number, uint8_t value); void ConfigAPWeb(void); void ConfigAPStarted(WiFiManager* wm); /*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[MAX_CLIENTS]; uint32_t clientIP_time[MAX_CLIENTS]; LiquidCrystal_I2C lcd(LCD_I2C_ADDR, LCD_COL, LCD_ROW); HardwareSerial midi1(2); // RX: 16, TX: 17 #ifndef D5 #define D5 (SOFT_SERIAL_RX) #define D6 (SOFT_SERIAL_TX) //#define D7 (23) //#define D8 (5) //#define TX (1) #endif SoftwareSerial midi2; bool ap_mode_state; int8_t midistate_cc[16 * 128]; int8_t midistate_mute[16]; int8_t midistate_solo[16]; bool last_reset_ap_check = false; looper sched; bool write_state = false; uint32_t last_usage = millis(); bool broadcast_send[MAX_CLIENTS]; uint32_t midi_ignore_volume_cc = 0; ESP32Encoder enc; MIDI_CREATE_INSTANCE(HardwareSerial, midi1, MIDI1); void setup() { bool enc_but_pin_reset; pinMode(AP_MODE_PIN, INPUT_PULLDOWN); ap_mode_state = digitalRead(AP_MODE_PIN); pinMode(ENC_BUTTON_PIN, INPUT); //ESP32Encoder::useInternalWeakPullResistors = UP; enc.attachSingleEdge(ENC_A_PIN, ENC_B_PIN); enc.setCount(0); enc_but_pin_reset = digitalRead(ENC_BUTTON_PIN); Serial.begin(115200); DEBUG_MSG("\n"); memset(midistate_cc, -1, 16 * 128 * sizeof(uint8_t)); memset(midistate_mute, -1, 16 * sizeof(uint8_t)); memset(midistate_solo, -1, 16 * sizeof(uint8_t)); memset(clientIP, 0, MAX_CLIENTS * sizeof(IPAddress)); memset(clientIP_time, 0, MAX_CLIENTS * sizeof(uint8_t)); memset(broadcast_send, false, MAX_CLIENTS * sizeof(uint8_t)); Serial.setDebugOutput(true); Serial.println(F("OSC2MIDI (c)2020 H. Wirtz ")); lcd.init(); lcd.backlight(); lcd.clear(); lcd.noCursor(); lcd.setCursor(2, 0); lcd.print(F("* OSC2MIDI *")); lcd.setCursor(0, 1); lcd.print(F("(c)parasiTstudio")); delay(1000); if (enc_but_pin_reset == LOW && digitalRead(ENC_BUTTON_PIN) == LOW) { reset_ap_data(); } 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"); if (!WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0))) { DEBUG_MSG("AP Config Failed\n"); } if (!WiFi.softAP(AP_SSID_NAME, AP_PASSWORD)) { DEBUG_MSG("Failed to start AP\n"); lcd.print(F("Failed ")); delay(1000); lcd.print(F("- restart")); delay(1000); ESP.restart(); } lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("Mode AP")); lcd.setCursor(0, 1); lcd.print(WiFi.softAPIP()); } else { DEBUG_MSG("Mode Client\n"); lcd.setCursor(0, 1); lcd.print(F("Connecting... ")); WiFiManager wm; wm.setWebServerCallback(ConfigAPWeb); wm.setAPCallback(ConfigAPStarted); wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT); wm.setConfigPortalTimeout(CFG_PORTAL_TIMEOUT); wm.setBreakAfterConfig(true); wm.setRemoveDuplicateAPs(true); wm.setWiFiAutoReconnect(false); wm.setEnableConfigPortal(false); if (!wm.autoConnect(AP_SSID_CONFIG_NAME, AP_CONFIG_PASSWORD)) { DEBUG_MSG("Failed to connect\n"); lcd.print(F("Failed")); delay(1000); lcd.print(F("- restart")); delay(1000); ESP.restart(); } else { DEBUG_MSG("Connected\n"); if (!MDNS.begin(MDNS_NAME)) { DEBUG_MSG("Error setting up MDNS responder!\n"); } else { DEBUG_MSG("mDNS started.\n"); } } lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("Mode WiFi client")); lcd.setCursor(0, 1); lcd.print(WiFi.localIP()); } udp.begin(UDP_RECV_PORT); DEBUG_MSG("Listening for UDP packets on port %d\n", UDP_RECV_PORT); midi1.begin(31250); // 16,17 midi2.begin(31250, SWSERIAL_8N1, D5, D6, false, 95, 11); midi2.enableIntTx(false); MIDI1.begin(MIDI_CHANNEL_OMNI); MIDI1.setHandleControlChange(MidiCCToOSC); MIDI1.turnThruOff(); sched.addJob(check_mode, KRATE_MODE); sched.addJob(check_reset_ap_data, KRATE_RESET_AP_DATA); sched.addJob(check_write_state, KRATE_CHECK_WRITE_STATE); sched.addJob(check_clientIPs, KRATE_CHECK_CLIENTIP); sched.addJob(enc_show, 1000); sched.addJob(show_clientIP, 5000); //sched.addJob(show_midi_state, KRATE_STATE); listDir(SPIFFS, "/", 1); read_midistate(1); show_midistate(); DEBUG_MSG("\n"); } void loop() { OSCMessage msg; uint8_t buffer[1024]; uint16_t outPort; size_t size = udp.parsePacket(); if (udp.available()) { IPAddress tmpIP = udp.remoteIP(); // Keep track of the client IP address for "talking back" uint8_t slot; if (slot = check_for_clientIP(tmpIP) < 0) { slot = add_clientIP(tmpIP); DEBUG_MSG("New connection from: %d.%d.%d.%d\n", tmpIP[0], tmpIP[1], tmpIP[2], tmpIP[3]); } // Check if there are any OSC packets to handle udp.read(buffer, size); msg.fill(buffer, size); clientIP_time[slot] = millis(); if (!msg.hasError()) { DEBUG_OSC_MESSAGE(msg); msg.route("/midi/cc", OSCToMidiCC); msg.route("/midi/mixer/mute", OSCMixerMuteToMidiCC); msg.route("/midi/mixer/solo", OSCMixerSoloToMidiCC); msg.route("/ping", ping); //msg.route("/midi/sysex", OSCToMidiSYSEX); //msg.route("/midi/note", OSCToMidiNote); char address[100]; msg.getAddress(address, 0, sizeof(address)); if (strcmp(address, "/ping")) { DEBUG_MSG("Broadcasting [%s] from %d.%d.%d.%d to\n", address, tmpIP[0], tmpIP[1], tmpIP[2], tmpIP[3]); for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP_time[i] > 0 && clientIP[i]) { if (!same_ip(tmpIP, clientIP[i])) { udp.beginPacket(clientIP[i], UDP_SEND_PORT); udp.write(buffer, size); udp.endPacket(); udp.flush(); DEBUG_MSG("\t%d.%d.%d.%d\n", clientIP[i][0], clientIP[i][1], clientIP[i][2], clientIP[i][3]); } } } } else { int8_t slot = check_for_clientIP(tmpIP); if (slot >= 0) clientIP_time[slot] = millis(); } } else { DEBUG_MSG("Error parsing OSC message: %d\n", msg.getError()); } } // Check if there are any CC messages from synth itself if (MIDI1.read()) { DEBUG_MSG("MIDI-IN[1] Type: "); DEBUG_MSG("%3d", MIDI1.getType()); DEBUG_MSG(" Data1: "); DEBUG_MSG("%3d", MIDI1.getData1()); DEBUG_MSG(" Data2: "); DEBUG_MSG("%3d", MIDI1.getData2()); DEBUG_MSG(" Channel: "); DEBUG_MSG("%0d", MIDI1.getChannel()); DEBUG_MSG("\n"); } // MIDI-Merger from (Soft-)MIDI2 to MIDI1 if (midi2.available() > 0) { while (midi2.available() > 0) { DEBUG_MSG("MIDI-IN[2]: %03d\n", midi2.peek()); midi1.write(midi2.read()); } } sched.scheduler(); } void OSCToMidiCC(OSCMessage & msg, int offset) { char address[100] = { 0 }; uint8_t cc, value; uint8_t midichannel; msg.getAddress(address, offset, sizeof(address)); midichannel = getMIDIChannel(address); if (msg.size() == 1 && msg.isFloat(0)) { // Single or multi control with sending one value cc = getCC(address); value = round(msg.getFloat(0)); value = value > 127 ? 127 : value; DEBUG_MSG("MSG: %s\tChannel: %u\t\tCC: %u\tValue: %u\n", address, midichannel, cc, value); if (cc == 7) { if (midistate_mute[cc - 1] < 1 && midistate_solo[cc - 1] < 1) MIDI1.sendControlChange(cc, value, midichannel); else DEBUG_MSG("No volume change - only state change to %d\n", value); } else MIDI1.sendControlChange(cc, value, midichannel); change_midistate_cc(midichannel, cc, value); } else if (msg.size() == 2 && msg.isFloat(0) && msg.isFloat(1)) { // XY pad, two values cc = getVar(address, 1); value = round(msg.getFloat(0)); 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_midistate_cc(midichannel, cc, value); cc = getVar(address, 2); value = round(msg.getFloat(1)); 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_midistate_cc(midichannel, cc, value); } else { DEBUG_MSG("Cannot handle: %s\n", address); } } void OSCMixerMuteToMidiCC(OSCMessage & msg, int offset) { char address[100] = { 0 }; uint8_t value; uint8_t midichannel; msg.getAddress(address, offset, sizeof(address)); if (msg.size() == 1 && msg.isFloat(0)) { // Single or multi control with sending one value midichannel = getVar(address, 0); value = constrain(msg.getFloat(0), 0, 1); DEBUG_MSG("MixerMute MSG: %s\t Channel: %u\tMute \tValue: %u\n", address, midichannel, value); if (value == 1) MIDI1.sendControlChange(7, 0, midichannel); else MIDI1.sendControlChange(7, midistate_cc[(midichannel - 1) * 128 + 6], midichannel); midi_ignore_volume_cc = millis(); change_midistate_mute(midichannel, value); set_midi_solo_mode(); } } void OSCMixerSoloToMidiCC(OSCMessage & msg, int offset) { char address[100] = { 0 }; uint8_t value; uint8_t midichannel; msg.getAddress(address, offset, sizeof(address)); if (msg.size() == 1 && msg.isFloat(0)) { // Single or multi control with sending one value midichannel = getVar(address, 0); value = constrain(msg.getFloat(0), 0, 1); DEBUG_MSG("MixerSolo MSG: %s\t Channel: %u\tMute \tValue: %u\n", address, midichannel, value); /* if (value == 1) MIDI1.sendControlChange(7, 0, midichannel); else MIDI1.sendControlChange(7, midistate_cc[(midichannel - 1) * 128 + 6], midichannel); */ midi_ignore_volume_cc = millis(); change_midistate_solo(midichannel, value); set_midi_solo_mode(); } } void MidiCCToOSC(uint8_t channel, uint8_t number, uint8_t val) { char buffer[1024]; if (channel < 1 && channel > 16) return; if (number > 127) return; val = constrain(val, 0, 127); if (number == 7) if (millis() - MIDI_IGNORE_VOLUME_CC < midi_ignore_volume_cc) return; snprintf(buffer, sizeof(buffer), "/midi/cc/%d/%d", channel, number); change_midistate_cc(channel, number, val); OSCMessage msg = OSCMessage(buffer); msg.add(val); for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP_time[i] > 0 && clientIP[i]) { udp.beginPacket(clientIP[i], UDP_SEND_PORT); msg.send(udp); udp.endPacket(); udp.flush(); DEBUG_MSG("Sending MidiCCToOsc: %s %f to %d.%d.%d.%d:%d\n", buffer, val, clientIP[i][0], clientIP[i][1], clientIP[i][2], clientIP[i][3], UDP_SEND_PORT); } } //msg.empty(); } void MidiMuteToOSC(uint8_t channel, uint8_t val) { char buffer[1024]; if (channel < 1 && channel > 16) return; val = constrain(val, 0, 1); snprintf(buffer, sizeof(buffer), "/midi/mixer/mute/%d", channel); OSCMessage msg = OSCMessage(buffer); msg.add(val); for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP_time[i] > 0 && clientIP[i]) { udp.beginPacket(clientIP[i], UDP_SEND_PORT); msg.send(udp); udp.endPacket(); udp.flush(); DEBUG_MSG("Sending MidiMuteToOsc: %s %f to %d.%d.%d.%d\n", buffer, val, clientIP[i][0], clientIP[i][1], clientIP[i][2], clientIP[i][3]); } } //msg.empty(); } void MidiSoloToOSC(uint8_t channel, uint8_t val) { char buffer[1024]; if (channel < 1 && channel > 16) return; if (val > 1) val = 1; snprintf(buffer, sizeof(buffer), "/midi/mixer/solo/%d", channel); OSCMessage msg = OSCMessage(buffer); msg.add(val); for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP_time[i] > 0 && clientIP[i]) { udp.beginPacket(clientIP[i], UDP_SEND_PORT); msg.send(udp); udp.endPacket(); udp.flush(); DEBUG_MSG("Sending MidiSoloToOsc: %s %f to %d.%d.%d.%d\n", buffer, val, clientIP[i][0], clientIP[i][1], clientIP[i][2], clientIP[i][3]); } } //msg.empty(); } void change_midistate_cc(uint8_t midichannel, uint8_t cc, uint8_t 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_cc[(midichannel - 1) * 128 + cc - 1] != int8_t(value)) { midistate_cc[(midichannel - 1) * 128 + cc - 1] = int8_t(value); write_state = true; lcd.setCursor(LCD_COL - 1, LCD_ROW - 1); lcd.print("*"); } } void change_midistate_mute(uint8_t midichannel, bool value) { last_usage = millis(); DEBUG_MSG("Setting internal mute state of MIDI Channel %2d to %d\n", midichannel, int8_t(value)); if (midistate_mute[(midichannel - 1)] != value) { midistate_mute[(midichannel - 1)] = value; write_state = true; lcd.setCursor(LCD_COL - 1, LCD_ROW - 1); lcd.print("*"); } } void change_midistate_solo(uint8_t midichannel, bool value) { last_usage = millis(); DEBUG_MSG("Setting internal solo state of MIDI Channel %2d to %d\n", midichannel, int8_t(value)); if (midistate_solo[(midichannel - 1)] != value) { midistate_solo[(midichannel - 1)] = value; write_state = true; lcd.setCursor(LCD_COL - 1, LCD_ROW - 1); lcd.print("*"); } } void show_midistate(void) { uint8_t m, c; DEBUG_MSG("Current MIDI state: \n"); listDir(SPIFFS, "/", 1); for (m = 0; m < 16; m++) { DEBUG_MSG("MIDI - Channel %2d\n", m + 1); for (c = 0; c < 128; c++) { if (midistate_cc[m * 128 + c] >= 0) { DEBUG_MSG("\tCC: %3d = %3d\n", c + 1, midistate_cc[m * 128 + c]); } } if (midistate_mute[m] > 0) { DEBUG_MSG("\tMUTE\n"); } if (midistate_solo[m] > 0) { DEBUG_MSG("\tSOLO\n"); } } } void set_midi_solo_mode(void) { bool all_solo_off = true; DEBUG_MSG("Set MIDI solo mode\n"); for (uint8_t m = 0; m < 16; m++) { if (midistate_solo[m] == 1) { all_solo_off = false; MIDI1.sendControlChange(7, MIDI_SOLO_VOLUME, m + 1); DEBUG_MSG("\tChannel: %d: SOLO Volume: %d\n", m + 1, MIDI_SOLO_VOLUME); } else if (midistate_solo[m] == 0) { MIDI1.sendControlChange(7, 0, m + 1); DEBUG_MSG("\tChannel: %d: Volume: %d\n", m + 1, 0); } } if (all_solo_off == true) { set_midi_mute_mode(); } } void set_midi_mute_mode(void) { for (uint8_t m = 0; m < 16; m++) { if (midistate_mute[m] == 1) { DEBUG_MSG("\t Channel: %d: MUTE\n", m); MIDI1.sendControlChange(7, 0, m + 1); } else if (midistate_mute[m] == 0) MIDI1.sendControlChange(7, constrain(midistate_cc[m * 128 + 6], 0, 127), m + 1); } } 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) { write_midistate(1); write_state = false; show_midistate(); } } void write_midistate_cc(uint8_t setup_number) { DEBUG_MSG("Write MIDI CC state\n"); for (uint8_t m = 0; m < 16; m++) { for (uint8_t c = 0; c < 128; c++) { if (midistate_cc[m * 128 + c] >= 0) { DEBUG_MSG("Found MIDI Channel %2d, CC#%2d = %3d\n", m, c, midistate_cc[m * 128 + c]); char midi_cc_name[24]; int16_t tmp_val; sprintf(midi_cc_name, "/%d/midistate_cc/%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_cc[m * 128 + c] != tmp_val) { DEBUG_MSG(" Change from %d to %d detected\n", tmp_val, midistate_cc[m * 128 + c]); File midi_cc = SPIFFS.open(midi_cc_name, "w"); if (midi_cc) { midi_cc.write(midistate_cc[m * 128 + c]); midi_cc.flush(); DEBUG_MSG("Wrote %d to %s.\n", midistate_cc[m * 128 + c], midi_cc_name); } else DEBUG_MSG("Cannot write to %s.\n", midi_cc_name); close(midi_cc); } } } } lcd.setCursor(LCD_COL - 1, LCD_ROW - 1); lcd.print(" "); } void read_midistate_cc(uint8_t setup_number) { char setup_dir_name[24]; DEBUG_MSG("Read MIDI CC state\n"); sprintf(setup_dir_name, "/%d/midistate_cc", setup_number); File midistate_cc_dir = SPIFFS.open(setup_dir_name); if (!midistate_cc_dir) { DEBUG_MSG("Failed to open directory %s.\n", setup_dir_name); return; } if (!midistate_cc_dir.isDirectory()) { DEBUG_MSG("%s is not a directory.\n", setup_dir_name); return; } File channel_cc = midistate_cc_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_cc[midi_channel * 128 + midi_cc] = val; cc.close(); } } } else { DEBUG_MSG("Removing %s : not the right size.\n", channel_cc.name()); SPIFFS.remove(channel_cc.name()); } channel_cc = midistate_cc_dir.openNextFile(); } } void write_midistate_mute(uint8_t setup_number) { DEBUG_MSG("Write MIDI MUTE state\n"); for (uint8_t m = 0; m < 16; m++) { if (midistate_mute[m] >= 0) { DEBUG_MSG("Found MIDI Channel %2d = %3d\n", m, midistate_mute[m]); char midi_mute_name[21]; int8_t tmp_val = -1; sprintf(midi_mute_name, "/%d/midistate_mute/%d", setup_number, m); if (SPIFFS.exists(midi_mute_name)) { File midi_mute = SPIFFS.open(midi_mute_name, "r"); if (midi_mute) { tmp_val = midi_mute.read(); DEBUG_MSG("Mute data for MIDI Channel %d exists : %d\n", m, tmp_val); } close(midi_mute); } if (midistate_mute[m] != tmp_val) { DEBUG_MSG(" Change from %d to %d detected\n", tmp_val, midistate_mute[m]); File midi_mute = SPIFFS.open(midi_mute_name, "w"); if (midi_mute) { midi_mute.write(midistate_mute[m]); midi_mute.flush(); DEBUG_MSG("Wrote %d to %s.\n", midistate_mute[m], midi_mute_name); } else DEBUG_MSG("Cannot write to %s.\n", midi_mute_name); close(midi_mute); } } } } void read_midistate_mute(uint8_t setup_number) { char setup_dir_name[21]; DEBUG_MSG("Read MIDI MUTE state\n"); sprintf(setup_dir_name, "/%d/midistate_mute", setup_number); File midistate_mute_dir = SPIFFS.open(setup_dir_name); if (!midistate_mute_dir) { DEBUG_MSG("Failed to open directory %s.\n", setup_dir_name); return; } File channel_mute = midistate_mute_dir.openNextFile(); while (channel_mute) { DEBUG_MSG("Trying %s (Size : %d)\n", channel_mute.name(), channel_mute.size()); if (uint8_t(channel_mute.size()) == 1) { if (!channel_mute.isDirectory()) { char tmp_name[21]; uint8_t midi_channel; uint8_t count = 0; DEBUG_MSG("Using %s\n", channel_mute.name()); strcpy(tmp_name, channel_mute.name()); for (String part = strtok(tmp_name, "/"); part; part = strtok(NULL, "/")) { count++; if (count == 3) midi_channel = atoi(part.c_str()); } File mute = SPIFFS.open(channel_mute.name(), "r"); if (mute) { int8_t val = mute.read(); DEBUG_MSG(" MIDI - Channel MUTE %d = %d\n", midi_channel + 1, val); midistate_mute[midi_channel] = val; mute.close(); } } } else { DEBUG_MSG("Removing %s : not the right size.\n", channel_mute.name()); SPIFFS.remove(channel_mute.name()); } channel_mute = midistate_mute_dir.openNextFile(); } } void write_midistate_solo(uint8_t setup_number) { DEBUG_MSG("Write MIDI SOLO state\n"); for (uint8_t m = 0; m < 16; m++) { if (midistate_solo[m] >= 0) { DEBUG_MSG("Found MIDI Channel %2d = %3d\n", m, midistate_solo[m]); char midi_solo_name[21]; int8_t tmp_val = -1; sprintf(midi_solo_name, "/%d/midistate_solo/%d", setup_number, m); if (SPIFFS.exists(midi_solo_name)) { File midi_solo = SPIFFS.open(midi_solo_name, "r"); if (midi_solo) { tmp_val = midi_solo.read(); DEBUG_MSG("Solo data for MIDI Channel %d exists : %d\n", m, tmp_val); } close(midi_solo); } if (midistate_solo[m] != tmp_val) { DEBUG_MSG(" Change from %d to %d detected\n", tmp_val, midistate_solo[m]); File midi_solo = SPIFFS.open(midi_solo_name, "w"); if (midi_solo) { midi_solo.write(midistate_solo[m]); midi_solo.flush(); DEBUG_MSG("Wrote %d to %s.\n", midistate_solo[m], midi_solo_name); } else DEBUG_MSG("Cannot write to %s.\n", midi_solo_name); close(midi_solo); } } } } void read_midistate_solo(uint8_t setup_number) { char setup_dir_name[21]; DEBUG_MSG("Read MIDI SOLO state\n"); sprintf(setup_dir_name, "/%d/midistate_solo", setup_number); File midistate_solo_dir = SPIFFS.open(setup_dir_name); if (!midistate_solo_dir) { DEBUG_MSG("Failed to open directory %s.\n", setup_dir_name); return; } File channel_solo = midistate_solo_dir.openNextFile(); while (channel_solo) { DEBUG_MSG("Trying %s (Size : %d)\n", channel_solo.name(), channel_solo.size()); if (uint8_t(channel_solo.size()) == 1) { if (!channel_solo.isDirectory()) { char tmp_name[20]; uint8_t midi_channel; uint8_t count = 0; DEBUG_MSG("Using %s\n", channel_solo.name()); strcpy(tmp_name, channel_solo.name()); for (String part = strtok(tmp_name, "/"); part; part = strtok(NULL, "/")) { count++; if (count == 3) midi_channel = atoi(part.c_str()); } File solo = SPIFFS.open(channel_solo.name(), "r"); if (solo) { int8_t val = solo.read(); DEBUG_MSG(" MIDI - Channel SOLO %d = %d\n", midi_channel + 1, val); midistate_solo[midi_channel] = val; solo.close(); } } } else { DEBUG_MSG("Removing %s : not the right size.\n", channel_solo.name()); SPIFFS.remove(channel_solo.name()); } channel_solo = midistate_solo_dir.openNextFile(); } set_midi_solo_mode(); } void read_midistate(uint8_t setup_number) { read_midistate_cc(setup_number); read_midistate_mute(setup_number); read_midistate_solo(setup_number); } void write_midistate(uint8_t setup_number) { write_midistate_cc(setup_number); write_midistate_mute(setup_number); write_midistate_solo(setup_number); lcd.setCursor(LCD_COL - 1, LCD_ROW - 1); lcd.print(" "); } void broadcast_midistate(void) { uint8_t m, c; DEBUG_MSG("Broadcast MIDI CC state:\n"); for (m = 0; m < 16; m++) { DEBUG_MSG("MIDI - Channel %2d\n", m + 1); for (c = 0; c < 128; c++) { if (midistate_cc[m * 128 + c] >= 0) { DEBUG_MSG("\tCC : %3d = %3d\n", c + 1, midistate_cc[m * 128 + c]); MidiCCToOSC(m + 1, c + 1, midistate_cc[m * 128 + c]); MIDI1.sendControlChange(c + 1, midistate_cc[m * 128 + c], m + 1); } } if (midistate_mute[m] >= 0) { DEBUG_MSG("\tMUTE : %3d\n", midistate_solo[m]); MidiMuteToOSC(m + 1, midistate_mute[m]); } if (midistate_solo[m] >= 0) { DEBUG_MSG("\SOLO : %3d\n", midistate_solo[m]); MidiSoloToOSC(m + 1, midistate_solo[m]); } } set_midi_solo_mode(); } void check_reset_ap_data(void) { if (digitalRead(ENC_BUTTON_PIN) == LOW && last_reset_ap_check == true) { reset_ap_data(); } else if (digitalRead(ENC_BUTTON_PIN) == LOW) { if (digitalRead(ENC_BUTTON_PIN) == LOW) DEBUG_MSG("Reset AP data pressed\n"); last_reset_ap_check = true; } else last_reset_ap_check = false; } void ping(OSCMessage & msg, int offset) { for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP[i] && broadcast_send[i] == false) { broadcast_send[i] = true; broadcast_midistate(); } } } void ConfigAPWeb(void) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(F("Mode Config-AP")); lcd.setCursor(0, 1); lcd.print(F("192.168.4.1")); } void ConfigAPStarted(WiFiManager * wm) { lcd.clear(); DEBUG_MSG("Config-AP started\n"); } void reset_ap_data(void) { WiFiManager wm; DEBUG_MSG("Reset AP data\n"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Reset AP data..."); wm.resetSettings(); lcd.setCursor(0, 1); lcd.print("done."); delay(500); lcd.clear(); ESP.restart(); } void firmware_reset(void) { WiFiManager wm; lcd.setCursor(0, 3); lcd.print(F("Firmware reset...")); DEBUG_MSG("Reset AP data\n"); wm.resetSettings(); DEBUG_MSG("FORMAT SPIFFS..."); SPIFFS.format(); DEBUG_MSG("done.\n"); lcd.setCursor(0, 1); lcd.print(F("done.")); delay(500); lcd.clear(); ESP.restart(); } void enc_show(void) { DEBUG_MSG("Encoder count = %d", enc.getCount()); DEBUG_MSG(" button[%d] = %d\n", ENC_BUTTON_PIN, (uint8_t)digitalRead(ENC_BUTTON_PIN)); } void check_clientIPs(void) { for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP_time[i] > 0) { if (millis() - clientIP_time[i] > CLIENT_TIMEOUT) { DEBUG_MSG("Removing client slot %d: %d.%d.%d.%d\n", i, clientIP[i][0], clientIP[i][1], clientIP[i][2], clientIP[i][3]); clientIP[i] = {0, 0, 0, 0}; clientIP_time[i] = 0; broadcast_send[i] = false; } } } } int8_t check_for_clientIP(IPAddress ip) { for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (same_ip(clientIP[i], ip)) return (i); } return (-1); } uint8_t add_clientIP(IPAddress ip) { uint8_t oldest = 0; for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP_time[i] > 0) { for (uint8_t n = 0; n < MAX_CLIENTS; n++) { if (clientIP_time[i] < clientIP_time[n]) oldest = i; } } else { oldest = i; break; } } clientIP[oldest] = ip; clientIP_time[oldest] = millis(); DEBUG_MSG("Adding client slot %d: %d.%d.%d.%d\n", oldest, clientIP[oldest][0], clientIP[oldest][1], clientIP[oldest][2], clientIP[oldest][3]); return (oldest); } void show_clientIP(void) { DEBUG_MSG("Connected clients:\n"); for (uint8_t i = 0; i < MAX_CLIENTS; i++) { if (clientIP[i]) DEBUG_MSG("Slot %d: %d.%d.%d.%d - %ld\n", i, clientIP[i][0], clientIP[i][1], clientIP[i][2], clientIP[i][3], clientIP_time[i]); } } bool same_ip(IPAddress ip1, IPAddress ip2) { if (ip1[0] == ip2[0] && ip1[1] == ip2[1] && ip1[2] == ip2[2] && ip1[3] == ip2[3]) return (true); else return (false); } void listDir(fs::FS & fs, const char * dirname, uint8_t levels) { DEBUG_MSG("Listing directory : %s\n", dirname); File root = fs.open(dirname); if (!root) { DEBUG_MSG("* Failed to open directory\n"); return; } if (!root.isDirectory()) { DEBUG_MSG("* Not a directory\n"); return; } File file = root.openNextFile(); while (file) { if (file.isDirectory()) { DEBUG_MSG(" DIR: "); DEBUG_MSG("%s\n", file.name()); if (levels) { listDir(fs, file.name(), levels - 1); } } else DEBUG_MSG(" FILE: %s\t\tSIZE: %d\n", file.name(), file.size()); file = root.openNextFile(); } }