diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..63b3af4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# PlatformIO build files +.pio/ +.pioenvs/ +.piolibdeps/ + +# VS Code settings +.vscode/ + +# PlatformIO package cache +.platformio/ + +# Python virtual environment +venv/ +env/ + +# macOS system files +.DS_Store + +# Windows system files +Thumbs.db + +# Compiled object files +*.o +*.obj + +# Compiled Dynamic libraries +*.dll +*.so +*.dylib + +# Firmware binaries +*.bin +*.elf +*.hex diff --git a/README.md b/README.md index 98b7aaa..b0f17df 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,102 @@ -# audio2midi +# Audio2MIDI +This project is an Arduino-based application that manages WiFi connectivity, LED control, and ESP device information. It uses the [WiFiManager](https://github.com/tzapu/WiFiManager) library to handle WiFi connections with fallback to Access Point (AP) mode and a captive portal for configuration. + +## Features + +- **WiFi Management**: Automatically connects to saved WiFi credentials or starts an AP with a captive portal for configuration if no connection is possible. +- **ESP Information**: Displays ESP device information such as chip ID and flash size. +- **LED Control**: Provides functionality for controlling LEDs (e.g., cycling through colors). +- **Web Server**: (Placeholder for potential web server functionality). + +## Project Structure + +The project is organized as follows: + +``` +src/ +├── esp_info.cpp # Handles ESP device information +├── esp_info.h +├── index.h # Placeholder for web server content +├── led_control.cpp # Handles LED control logic +├── led_control.h +├── main.cpp # Main entry point for the application +├── web_server.cpp # Placeholder for web server functionality +├── web_server.h +├── wifi_manager.cpp # Manages WiFi connectivity +├── wifi_manager.h +``` + +### Key Components + +#### `main.cpp` +The main entry point of the application. It initializes the WiFi manager, prints ESP information, and sets up LED control. + +#### `wifi_manager.cpp` and `wifi_manager.h` +Manages WiFi connectivity using the `WiFiManager` library. It attempts to connect to saved WiFi credentials and falls back to AP mode with a captive portal if the connection fails. + +#### `esp_info.cpp` and `esp_info.h` +Provides functions to retrieve and display ESP device information such as chip ID and flash size. + +#### `led_control.cpp` and `led_control.h` +Handles LED control logic, such as cycling through colors or setting specific patterns. + +#### `web_server.cpp` and `web_server.h` +(Placeholder) Intended for implementing web server functionality. + +## Setup and Usage + +### Prerequisites + +- Arduino IDE or PlatformIO +- ESP8266 or ESP32 microcontroller +- [WiFiManager](https://github.com/tzapu/WiFiManager) library installed + +### Installation + +1. Clone this repository: + ```bash + git clone https://github.com/your-username/audio2midi.git + cd audio2midi + ``` + +2. Open the project in your preferred IDE (e.g., Arduino IDE or VS Code with PlatformIO). + +3. Install the required libraries: + - [WiFiManager](https://github.com/tzapu/WiFiManager) + +4. Configure your microcontroller board and upload the code. + +### Usage + +1. Power on the ESP device. +2. The device will attempt to connect to saved WiFi credentials. +3. If no connection is possible, the device will start an AP named `AudioToMIDI-AP` with the password `password123`. +4. Connect to the AP and configure the WiFi credentials through the captive portal. +5. Once connected, the device will proceed with its functionality (e.g., LED control, ESP info display). + +## Customization + +- **AP Name and Password**: Modify the AP name and password in `wifi_manager.cpp`: + ```cpp + wm.autoConnect("AudioToMIDI-AP", "password123"); + ``` +- **LED Behavior**: Customize LED patterns in `led_control.cpp`. + +## Troubleshooting + +- If the device fails to start the AP or connect to WiFi, ensure the `WiFiManager` library is installed and the ESP device is properly configured. +- Check the serial monitor for debug output. + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- [WiFiManager](https://github.com/tzapu/WiFiManager) for simplifying WiFi configuration. + + +Visit http://[esp-ip]/list to see all files in SPIFFS +Verify that you see /www/index.html and /www/dashboard.html listed +If files are not listed, you need to upload them to SPIFFS using PlatformIO's "Upload Filesystem Image" task \ No newline at end of file diff --git a/data/www/dashboard.html b/data/www/dashboard.html new file mode 100644 index 0000000..f1a534d --- /dev/null +++ b/data/www/dashboard.html @@ -0,0 +1,41 @@ + + + + ESP32 Dashboard + + + +
+ + + diff --git a/data/www/index.html b/data/www/index.html new file mode 100644 index 0000000..6c44a6b --- /dev/null +++ b/data/www/index.html @@ -0,0 +1,122 @@ + + + + Audio2MIDI + + + +
+ + + + + +
+

Danger Zone

+ + + +
+
+
+ +
+ + + + diff --git a/data/www/wifi.html b/data/www/wifi.html new file mode 100644 index 0000000..48aa681 --- /dev/null +++ b/data/www/wifi.html @@ -0,0 +1,61 @@ + + + + WiFi Settings + + + +
+

WiFi Settings

+
+

Current Status

+
+
+
+ + + diff --git a/include/README b/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..9379397 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into the executable file. + +The source code of each library should be placed in a separate directory +("lib/your_library_name/[Code]"). + +For example, see the structure of the following example libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +Example contents of `src/main.c` using Foo and Bar: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +The PlatformIO Library Dependency Finder will find automatically dependent +libraries by scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/notes.ods b/notes.ods new file mode 100644 index 0000000..a121c2a Binary files /dev/null and b/notes.ods differ diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..a0275eb --- /dev/null +++ b/platformio.ini @@ -0,0 +1,40 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:esp32-s3-devkitm-1] +platform = espressif32 +board = esp32-s3-devkitm-1 +framework = arduino ;, espidf + +board_build.arduino.memory_type = qio_qspi +board_build.flash_mode = qio +board_build.psram_type = qio +board_upload.flash_size = 4MB +board_upload.maximum_size = 4194304 +board_build.partitions = default.csv +build_flags = -DARDUINO_USB_CDC_ON_BOOT=1 + -DBOARD_HAS_PSRAM + -DARDUINO_ESP32S3_DEV + -DBOARD_HAS_TEMP_SENSOR + ; -DCONFIG_FREERTOS_HZ=1000 + ; -DESP_PLATFORM + ; -DARDUINO_RUNNING +board_build.filesystem = spiffs +monitor_speed = 115200 +upload_speed = 921600 +monitor_filters = esp32_exception_decoder +lib_deps = + fastled/FastLED@^3.9.4 + https://github.com/tzapu/WiFiManager.git + kosme/arduinoFFT@^2.0.4 + https://github.com/me-no-dev/ESPAsyncWebServer.git + https://github.com/me-no-dev/AsyncTCP.git + ; h2zero/NimBLE-Arduino@^1.4.1 + h2zero/NimBLE-Arduino diff --git a/src/NoteMappings copy.h b/src/NoteMappings copy.h new file mode 100644 index 0000000..b4e8649 --- /dev/null +++ b/src/NoteMappings copy.h @@ -0,0 +1,43 @@ +// NoteMappings.h + +#ifndef NOTEMAPPINGS_H +#define NOTEMAPPINGS_H + +#include +#include + +// Frequency to Note Map (simplified for illustration) +std::map frequencyToNoteMap = { + {16.00, "C1"}, {32.00, "C#1"}, {33.08, "D1"}, {34.65, "D#1"}, {36.71, "E1"}, + {38.89, "F1"}, {41.20, "F#1"}, {43.65, "G1"}, {46.25, "G#1"}, {49.00, "A1"}, + {51.91, "A#1"}, {55.00, "B1"}, {58.27, "C2"}, {61.74, "C#2"}, {65.41, "D2"}, + {69.30, "D#2"}, {73.42, "E2"}, {77.78, "F2"}, {82.41, "F#2"}, {87.31, "G2"}, + {92.50, "G#2"}, {98.00, "A2"}, {103.83, "A#2"}, {110.00, "B2"}, + {116.54, "C3"}, {123.47, "C#3"}, {130.81, "D3"}, {138.59, "D#3"}, + {146.83, "E3"}, {155.56, "F3"}, {164.81, "F#3"}, {174.61, "G3"}, + {184.99, "G#3"}, {195.99, "A3"}, {207.65, "A#3"}, {220.00, "B3"}, + {233.08, "C4"}, {246.94, "C#4"}, {261.63, "D4"}, {277.18, "D#4"}, + {293.66, "E4"}, {311.13, "F4"}, {329.63, "F#4"}, {349.23, "G4"}, + {369.99, "G#4"}, {392.00, "A4"}, {415.30, "A#4"}, {440.00, "B4"}, + {466.16, "C5"}, {493.88, "C#5"}, {523.25, "D5"}, {554.37, "D#5"}, + {587.33, "E5"}, {622.25, "F5"}, {659.25, "F#5"}, {698.46, "G5"}, + {739.99, "G#5"}, {783.99, "A5"}, {830.61, "A#5"}, {880.00, "B5"} +}; + +// Note to MIDI Number Map +std::map noteToMidiMap = { + {"C1", 24}, {"C#1", 25}, {"D1", 26}, {"D#1", 27}, {"E1", 28}, + {"F1", 29}, {"F#1", 30}, {"G1", 31}, {"G#1", 32}, {"A1", 33}, + {"A#1", 34}, {"B1", 35}, {"C2", 36}, {"C#2", 37}, {"D2", 38}, + {"D#2", 39}, {"E2", 40}, {"F2", 41}, {"F#2", 42}, {"G2", 43}, + {"G#2", 44}, {"A2", 45}, {"A#2", 46}, {"B2", 47}, {"C3", 48}, + {"C#3", 49}, {"D3", 50}, {"D#3", 51}, {"E3", 52}, {"F3", 53}, + {"F#3", 54}, {"G3", 55}, {"G#3", 56}, {"A3", 57}, {"A#3", 58}, + {"B3", 59}, {"C4", 60}, {"C#4", 61}, {"D4", 62}, {"D#4", 63}, + {"E4", 64}, {"F4", 65}, {"F#4", 66}, {"G4", 67}, {"G#4", 68}, + {"A4", 69}, {"A#4", 70}, {"B4", 71}, {"C5", 72}, {"C#5", 73}, + {"D5", 74}, {"D#5", 75}, {"E5", 76}, {"F5", 77}, {"F#5", 78}, + {"G5", 79}, {"G#5", 80}, {"A5", 81} +}; + +#endif diff --git a/src/NoteMappings.h b/src/NoteMappings.h new file mode 100644 index 0000000..b4e8649 --- /dev/null +++ b/src/NoteMappings.h @@ -0,0 +1,43 @@ +// NoteMappings.h + +#ifndef NOTEMAPPINGS_H +#define NOTEMAPPINGS_H + +#include +#include + +// Frequency to Note Map (simplified for illustration) +std::map frequencyToNoteMap = { + {16.00, "C1"}, {32.00, "C#1"}, {33.08, "D1"}, {34.65, "D#1"}, {36.71, "E1"}, + {38.89, "F1"}, {41.20, "F#1"}, {43.65, "G1"}, {46.25, "G#1"}, {49.00, "A1"}, + {51.91, "A#1"}, {55.00, "B1"}, {58.27, "C2"}, {61.74, "C#2"}, {65.41, "D2"}, + {69.30, "D#2"}, {73.42, "E2"}, {77.78, "F2"}, {82.41, "F#2"}, {87.31, "G2"}, + {92.50, "G#2"}, {98.00, "A2"}, {103.83, "A#2"}, {110.00, "B2"}, + {116.54, "C3"}, {123.47, "C#3"}, {130.81, "D3"}, {138.59, "D#3"}, + {146.83, "E3"}, {155.56, "F3"}, {164.81, "F#3"}, {174.61, "G3"}, + {184.99, "G#3"}, {195.99, "A3"}, {207.65, "A#3"}, {220.00, "B3"}, + {233.08, "C4"}, {246.94, "C#4"}, {261.63, "D4"}, {277.18, "D#4"}, + {293.66, "E4"}, {311.13, "F4"}, {329.63, "F#4"}, {349.23, "G4"}, + {369.99, "G#4"}, {392.00, "A4"}, {415.30, "A#4"}, {440.00, "B4"}, + {466.16, "C5"}, {493.88, "C#5"}, {523.25, "D5"}, {554.37, "D#5"}, + {587.33, "E5"}, {622.25, "F5"}, {659.25, "F#5"}, {698.46, "G5"}, + {739.99, "G#5"}, {783.99, "A5"}, {830.61, "A#5"}, {880.00, "B5"} +}; + +// Note to MIDI Number Map +std::map noteToMidiMap = { + {"C1", 24}, {"C#1", 25}, {"D1", 26}, {"D#1", 27}, {"E1", 28}, + {"F1", 29}, {"F#1", 30}, {"G1", 31}, {"G#1", 32}, {"A1", 33}, + {"A#1", 34}, {"B1", 35}, {"C2", 36}, {"C#2", 37}, {"D2", 38}, + {"D#2", 39}, {"E2", 40}, {"F2", 41}, {"F#2", 42}, {"G2", 43}, + {"G#2", 44}, {"A2", 45}, {"A#2", 46}, {"B2", 47}, {"C3", 48}, + {"C#3", 49}, {"D3", 50}, {"D#3", 51}, {"E3", 52}, {"F3", 53}, + {"F#3", 54}, {"G3", 55}, {"G#3", 56}, {"A3", 57}, {"A#3", 58}, + {"B3", 59}, {"C4", 60}, {"C#4", 61}, {"D4", 62}, {"D#4", 63}, + {"E4", 64}, {"F4", 65}, {"F#4", 66}, {"G4", 67}, {"G#4", 68}, + {"A4", 69}, {"A#4", 70}, {"B4", 71}, {"C5", 72}, {"C#5", 73}, + {"D5", 74}, {"D#5", 75}, {"E5", 76}, {"F5", 77}, {"F#5", 78}, + {"G5", 79}, {"G#5", 80}, {"A5", 81} +}; + +#endif diff --git a/src/audio_input.cpp b/src/audio_input.cpp new file mode 100644 index 0000000..a7c54cd --- /dev/null +++ b/src/audio_input.cpp @@ -0,0 +1,75 @@ +#include +#include "audio_input.h" +#include "driver/i2s.h" + +float vReal[SAMPLES]; +float vImag[SAMPLES]; + +bool initI2S() { + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), + .sample_rate = SAMPLING_FREQUENCY, + .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 8, + .dma_buf_len = 64, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT, + .bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT + }; + + // Add proper error handling + esp_err_t result = i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); + if (result != ESP_OK) { + Serial.printf("Error installing I2S driver: %d\n", result); + return false; + } + + i2s_pin_config_t pin_config = { + .mck_io_num = I2S_PIN_NO_CHANGE, + .bck_io_num = I2S_BCLK_PIN, + .ws_io_num = I2S_LRCLK_PIN, + .data_out_num = I2S_PIN_NO_CHANGE, + .data_in_num = I2S_DATA_IN_PIN + }; + + result = i2s_set_pin(I2S_NUM_0, &pin_config); + if (result != ESP_OK) { + Serial.printf("Error setting I2S pins: %d\n", result); + i2s_driver_uninstall(I2S_NUM_0); // Cleanup on error + return false; + } + + return true; +} + +void readAudioData() { + size_t bytesRead = 0; + esp_err_t result; + + for (int i = 0; i < SAMPLES; i++) { + int16_t sample; + int retries = 0; + do { + result = i2s_read(I2S_NUM_0, &sample, sizeof(sample), &bytesRead, portMAX_DELAY); + retries++; + if (result != ESP_OK || bytesRead != sizeof(sample)) { + Serial.println("Error reading from I2S, retrying..."); + delay(10); + } + } while ((result != ESP_OK || bytesRead != sizeof(sample)) && retries < MAX_RETRIES); + + if (result != ESP_OK || bytesRead != sizeof(sample)) { + Serial.print("Error reading from I2S after retries: "); + Serial.println(esp_err_to_name(result)); + break; + } + + vReal[i] = (float)sample; + vImag[i] = 0; + } +} diff --git a/src/audio_input.h b/src/audio_input.h new file mode 100644 index 0000000..f954293 --- /dev/null +++ b/src/audio_input.h @@ -0,0 +1,14 @@ +#ifndef AUDIO_INPUT_H +#define AUDIO_INPUT_H + +#include +#include "driver/i2s.h" +#include "config.h" + +extern float vReal[]; +extern float vImag[]; + +bool initI2S(); +void readAudioData(); + +#endif diff --git a/src/ble.cpp b/src/ble.cpp new file mode 100644 index 0000000..1f5bcf8 --- /dev/null +++ b/src/ble.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include "ble.h" + +// Add static instance to prevent memory leaks +static MyServerCallbacks* callbacks = nullptr; +bool deviceConnected; + +void MyServerCallbacks::onConnect(BLEServer* pServer) { + deviceConnected = true; + Serial.println("Device connected"); +} + +void MyServerCallbacks::onDisconnect(BLEServer* pServer) { + deviceConnected = false; + Serial.println("Device disconnected"); + // Restart advertising to allow for reconnection + pServer->startAdvertising(); +} + +void setupBLE() { + if (callbacks == nullptr) { + callbacks = new MyServerCallbacks(); + } + + // Initialize BLE with explicit power settings for ESP32-S3 + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9); + esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV, ESP_PWR_LVL_P9); + + BLECharacteristic *pCharacteristic; + deviceConnected = false; + + // Set up BLE (optional for your project) + BLEDevice::init("ESP32 MIDI Device"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(callbacks); + // retry mechanism for BLE initialization + int retries = 0; + while (!BLEDevice::getInitialized() && retries < 3) { + Serial.println("Retrying BLE initialization..."); + delay(1000); + retries++; + } + + if (!BLEDevice::getInitialized()) { + Serial.println("Failed to initialize BLE"); + return; + } + + BLEService *pService = pServer->createService("12345678-1234-5678-1234-56789abcdef0"); + pCharacteristic = pService->createCharacteristic("87654321-1234-5678-1234-56789abcdef0", BLECharacteristic::PROPERTY_NOTIFY); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + + Serial.println("ESP32 MIDI Device started."); +} + + + +void cleanupBLE() { + if (callbacks != nullptr) { + delete callbacks; + callbacks = nullptr; + } + BLEDevice::deinit(false); +} \ No newline at end of file diff --git a/src/ble.h b/src/ble.h new file mode 100644 index 0000000..b9782b7 --- /dev/null +++ b/src/ble.h @@ -0,0 +1,16 @@ +#ifndef BLE_H +#define BLE_H + +#include + +extern bool deviceConnected; +void setupBLE(); +void cleanupBLE(); + +class MyServerCallbacks : public BLEServerCallbacks { +public: // Make methods public + void onConnect(BLEServer* pServer) override; + void onDisconnect(BLEServer* pServer) override; +}; + +#endif \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..2317a16 --- /dev/null +++ b/src/config.h @@ -0,0 +1,50 @@ +// config.h +#ifndef CONFIG_H +#define CONFIG_H + +#include + +// Pin Definitions +#define I2S_DATA_IN_PIN 41 // Data input pin for INMP441 +#define I2S_BCLK_PIN 42 // Bit clock pin +#define I2S_LRCLK_PIN 40 // Left-Right clock pin + +// Audio Configuration +#define SAMPLES 1024 // Must be a power of 2 +#define SAMPLING_FREQUENCY 16000 +#define MAX_RETRIES 3 +#define NOISE_THRESHOLD 50 + +// BLE Configuration +#define BLE_DEVICE_NAME "ESP32 MIDI Device" +#define BLE_SERVICE_UUID "12345678-1234-5678-1234-56789abcdef0" +#define BLE_CHARACTERISTIC_UUID "87654321-1234-5678-1234-56789abcdef0" + +// Web Server Configuration +#define WEB_SERVER_PORT 80 +#define MAX_LOG_SIZE 512 + +// Watchdog Configuration +#define WDT_TIMEOUT 10000 // Watchdog timeout in milliseconds + +// I2S Configuration +#define I2S_PORT I2S_NUM_0 +#define DMA_BUF_COUNT 8 +#define DMA_BUF_LEN 64 + +// Flash Configuration +#define PROGRAM_PARTITION_SIZE 1310720 // Default app partition size ~1.3MB +#define FLASH_SIZE 0x400000 // 4MB total flash size +#define PROGRAM_SPACE_OFFSET 0x10000 // Program space starts at 64KB offset + +// Debug Configuration +#define SERIAL_BAUD_RATE 115200 +#define SERIAL_INIT_DELAY 1000 // Delay after Serial.begin() in ms + +// Device Configuration +#define DEVICE_HOSTNAME "esp32-midi" // Add this line +// #define MDNS_SERVICE "_midi" // mDNS service type +// #define MDNS_PROTOCOL "_tcp" // mDNS protocol + + +#endif diff --git a/src/esp_info.cpp b/src/esp_info.cpp new file mode 100644 index 0000000..03f6464 --- /dev/null +++ b/src/esp_info.cpp @@ -0,0 +1,154 @@ +// esp_info.cpp +#include +#include "esp_info.h" +#include "driver/temp_sensor.h" + +static bool temp_sensor_initialized = false; + +float readInternalTemperature() { + esp_err_t err; + float temp = 0; + + if (!temp_sensor_initialized) { + Serial.println("Initializing temperature sensor..."); + + // Manual default configuration for ESP32-S3 + temp_sensor_config_t temp_config = { + .dac_offset = TSENS_DAC_L2, // Default offset + .clk_div = 6, // Default clock divider + }; + + err = temp_sensor_set_config(temp_config); + if (err != ESP_OK) { + Serial.printf("Failed to set temp sensor config: %d\n", err); + return NAN; + } + + err = temp_sensor_start(); + if (err != ESP_OK) { + Serial.printf("Failed to start temp sensor: %d\n", err); + return NAN; + } + + temp_sensor_initialized = true; + delay(100); // Allow sensor to stabilize + Serial.println("Temperature sensor initialized"); + } + + err = temp_sensor_read_celsius(&temp); + if (err != ESP_OK) { + Serial.printf("Failed to read temperature: %d\n", err); + return NAN; + } + + return temp; +} + +void prettyPrintBytes(size_t bytes) { + if (bytes < 1024) { + Serial.printf("%d b\n", bytes); + } else if (bytes < (1024 * 1024)) { + Serial.printf("%.2f kb\n", bytes / 1024.0); + } else { + Serial.printf("%.2f mb\n", bytes / (1024.0 * 1024)); + } +} + +String formatBytes(size_t bytes) { + if (bytes < 1024) return String(bytes) + " b"; + else if (bytes < (1024 * 1024)) return String(bytes / 1024.0, 2) + " kb"; + else return String(bytes / (1024.0 * 1024), 2) + " mb"; +} + +String getESPInfoHTML() { + String html = "

ESP32 Information

"; + + html += "

Internal RAM

"; + html += "

Heap Size: " + formatBytes(ESP.getHeapSize()) + "

"; + html += "

Free Heap: " + formatBytes(ESP.getFreeHeap()) + "

"; + + html += "

PSRAM

"; + html += "

PSRAM Size: " + formatBytes(ESP.getPsramSize()) + "

"; + html += "

Free PSRAM: " + formatBytes(ESP.getFreePsram()) + "

"; + + html += "

Flash

"; + size_t usedBytes = ESP.getSketchSize(); + size_t totalBytes = ESP.getFlashChipSize(); + float usagePercent = (usedBytes * 100.0) / totalBytes; + html += "

Flash Size: " + formatBytes(totalBytes) + "

"; + html += "

Used: " + formatBytes(usedBytes) + " (" + String(usagePercent, 1) + "%)

"; + html += "

Flash Speed: " + String(ESP.getFlashChipSpeed() / 1000000) + " MHz

"; + + html += "

CPU

"; + html += "

Model: " + String(ESP.getChipModel()) + "

"; + html += "

Cores: " + String(ESP.getChipCores()) + "

"; + html += "

Frequency: " + String(ESP.getCpuFreqMHz()) + " MHz

"; + + float temperature = readInternalTemperature(); + if (!isnan(temperature)) { + html += "

Temperature Sensor

"; + html += "

Internal Temperature: " + String(temperature, 2) + " °C

"; +} + + + html += "
"; + return html; +} + +String getRAMInfoHTML() { + String html = "

RAM Information

"; + html += "

Heap Size: " + formatBytes(ESP.getHeapSize()) + "

"; + html += "

Free Heap: " + formatBytes(ESP.getFreeHeap()) + "

"; + html += "

Min Free Heap: " + formatBytes(ESP.getMinFreeHeap()) + "

"; + html += "

Max Alloc Heap: " + formatBytes(ESP.getMaxAllocHeap()) + "

"; + html += "

PSRAM

"; + html += "

PSRAM Size: " + formatBytes(ESP.getPsramSize()) + "

"; + html += "

Free PSRAM: " + formatBytes(ESP.getFreePsram()) + "

"; + html += "

Min Free PSRAM: " + formatBytes(ESP.getMinFreePsram()) + "

"; + html += "

Max Alloc PSRAM: " + formatBytes(ESP.getMaxAllocPsram()) + "

"; + html += "
"; + return html; +} +void printESPInfo() { + Serial.println("ESP32-S3 info"); + Serial.println("Internal RAM"); + Serial.print(" getHeapSize: "); prettyPrintBytes(ESP.getHeapSize()); + Serial.print(" getFreeHeap: "); prettyPrintBytes(ESP.getFreeHeap()); + Serial.print(" getMinFreeHeap: "); prettyPrintBytes(ESP.getMinFreeHeap()); + Serial.print(" getMaxAllocHeap: "); prettyPrintBytes(ESP.getMaxAllocHeap()); + Serial.println("PSRAM"); + Serial.print(" getPsramSize: "); prettyPrintBytes(ESP.getPsramSize()); + Serial.print(" getFreePsram: "); prettyPrintBytes(ESP.getFreePsram()); + Serial.print(" getMinFreePsram: "); prettyPrintBytes(ESP.getMinFreePsram()); + Serial.print(" getMaxAllocPsram: "); prettyPrintBytes(ESP.getMaxAllocPsram()); + Serial.println("Flash"); + Serial.print(" getFlashChipSize: "); prettyPrintBytes(ESP.getFlashChipSize()); + Serial.printf(" getFlashChipSpeed: %d MHz\n", ESP.getFlashChipSpeed() / 1000 / 1000); + Serial.print(" getFlashChipMode: "); Serial.println("SKIPPED"); + Serial.println("CPU"); + Serial.printf(" getChipRevision: %d\n", ESP.getChipRevision()); + Serial.printf(" getChipModel: %s\n", ESP.getChipModel()); + Serial.printf(" getChipCores: %d\n", ESP.getChipCores()); + Serial.printf(" getCpuFreqMHz: %d\n", ESP.getCpuFreqMHz()); + Serial.printf(" getEfuseMac: %llX\n", ESP.getEfuseMac()); + float temperature = readInternalTemperature(); + if (!isnan(temperature)) { + Serial.printf(" Internal Temperature: %.2f °C\n", temperature); + } else { + Serial.println(" Internal Temperature: Read failed"); + } + Serial.println("Software"); + Serial.printf(" getSdkVersion: %s\n", ESP.getSdkVersion()); + Serial.print(" getSketchSize: "); prettyPrintBytes(ESP.getSketchSize()); + Serial.printf(" getFreeSketchSpace: %d kb\n", ESP.getFreeSketchSpace() / 1024); + Serial.printf(" getSketchMD5: %s\n", ESP.getSketchMD5().c_str()); + + Serial.print("Internal Temperature: "); + float temp = readInternalTemperature(); + if (isnan(temp)) { + Serial.println("Error reading temperature"); + } else { + Serial.print(temp); + Serial.println("°C"); + } +} diff --git a/src/esp_info.h b/src/esp_info.h new file mode 100644 index 0000000..9e0495f --- /dev/null +++ b/src/esp_info.h @@ -0,0 +1,18 @@ +// esp_info.h +#ifndef ESP_INFO_H +#define ESP_INFO_H + +#pragma once +#include + +void prettyPrintBytes(size_t bytes); +void printESPInfo(); +String getESPInfoHTML(); +float getFlashUsagePercent(); +size_t getProgramFlashSize(); // Add this +size_t getAvailableFlashSize(); // Add this +String getRAMInfoHTML(); +float readInternalTemperature(); // Add this to esp_info.h + + +#endif diff --git a/src/fft_processing.cpp b/src/fft_processing.cpp new file mode 100644 index 0000000..097cc39 --- /dev/null +++ b/src/fft_processing.cpp @@ -0,0 +1,48 @@ +#include +#include "config.h" +#include "fft_processing.h" + +// extern const int SAMPLES; // Declare SAMPLES as external + +ArduinoFFT FFT = ArduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQUENCY); + +void handleFFT() { + // Check for invalid data + bool hasValidData = false; + for (int i = 0; i < SAMPLES; i++) { + if (vReal[i] != 0) { + hasValidData = true; + break; + } + } + + if (!hasValidData) { + Serial.println("No valid audio data for FFT"); + return; + } + + FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD); + FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD); + FFT.complexToMagnitude(vReal, vImag, SAMPLES); +} + +void processFFTData() { + float maxMag = 0; + int maxIndex = 0; + + for (int i = 2; i < SAMPLES / 2; i++) { // Start from 2 to skip DC + if (vReal[i] > maxMag) { + maxMag = vReal[i]; + maxIndex = i; + } + } + + if (maxMag < NOISE_THRESHOLD) { + return; // Skip if signal is too weak + } + + float frequency = (maxIndex * SAMPLING_FREQUENCY) / (float)SAMPLES; + if (frequency > 20 && frequency < 20000) { // Valid audio range + Serial.printf("Detected frequency: %.2f Hz, Magnitude: %.2f\n", frequency, maxMag); + } +} diff --git a/src/fft_processing.h b/src/fft_processing.h new file mode 100644 index 0000000..3bc6188 --- /dev/null +++ b/src/fft_processing.h @@ -0,0 +1,13 @@ +#ifndef FFT_PROCESSING_H +#define FFT_PROCESSING_H + +#include + +extern ArduinoFFT FFT; +extern float vReal[]; +extern float vImag[]; + +void handleFFT(); +void processFFTData(); + +#endif diff --git a/src/led_control.cpp b/src/led_control.cpp new file mode 100644 index 0000000..d594fbf --- /dev/null +++ b/src/led_control.cpp @@ -0,0 +1,23 @@ +#include "led_control.h" +#include + +const uint8_t NUM_LEDS = 1; +const gpio_num_t DATA_PIN = GPIO_NUM_48; + +CRGB leds[NUM_LEDS]; + +void setupLED() { + CFastLED::addLeds(leds, NUM_LEDS); + FastLED.setBrightness(32); +} + +void ledCycle() { + leds[0] = CRGB::Red; + FastLED.delay(500); + leds[0] = CRGB::Green; + FastLED.delay(500); + leds[0] = CRGB::Blue; + FastLED.delay(500); + leds[0] = CRGB::Black; + FastLED.delay(500); +} diff --git a/src/led_control.h b/src/led_control.h new file mode 100644 index 0000000..397596d --- /dev/null +++ b/src/led_control.h @@ -0,0 +1,7 @@ +#ifndef LED_CONTROL_H +#define LED_CONTROL_H + +void setupLED(); +void ledCycle(); + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1ec32ac --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,79 @@ +#include "esp_task_wdt.h" +#include +#include +#include +#include +#include "config.h" +#include "esp_info.h" +#include "led_control.h" +#include "web_server.h" + +WiFiManager wm; // Global WiFiManager instance + +void configModeCallback(WiFiManager *myWiFiManager) { + Serial.println("Entered config mode"); + Serial.println("AP IP: " + WiFi.softAPIP().toString()); + Serial.println("AP SSID: " + myWiFiManager->getConfigPortalSSID()); + // Start web server in AP mode + startAccessPoint(); +} + +bool setupWiFi() { + // Set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode + wm.setAPCallback(configModeCallback); + + // Set config portal timeout (optional) + wm.setConfigPortalTimeout(180); // 3 minutes + + // Set custom hostname (optional) + wm.setHostname("Audio2MIDI"); + + // Automatically connect using saved credentials if they exist + // If connection fails it will start an access point with the name "Audio2MIDI_AP" + if (wm.autoConnect("Audio2MIDI_AP")) { // Removed password parameter + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // Initialize web server in station mode + setupWebServer(); + startWebServer(); + return true; + } + + Serial.println("Failed to connect and hit timeout"); + return false; +} + +void setup() { + Serial.begin(SERIAL_BAUD_RATE); + delay(SERIAL_INIT_DELAY); + + // Initialize SPIFFS + if(!SPIFFS.begin(true)) { + Serial.println("SPIFFS Mount Failed"); + return; + } + + // List files in SPIFFS for debugging + File root = SPIFFS.open("/"); + File file = root.openNextFile(); + Serial.println("Files in SPIFFS:"); + while(file) { + Serial.printf(" - %s (%d bytes)\n", file.name(), file.size()); + file = root.openNextFile(); + } + + printESPInfo(); + setupLED(); + + if (!setupWiFi()) { + ESP.restart(); + } +} + +void loop() { + handleDNS(); // Process DNS requests for captive portal + ledCycle(); + delay(10); // Small delay to prevent watchdog triggers +} \ No newline at end of file diff --git a/src/midi.cpp b/src/midi.cpp new file mode 100644 index 0000000..568d1eb --- /dev/null +++ b/src/midi.cpp @@ -0,0 +1,43 @@ +#include +#include "midi.h" +#include "ble.h" +#include +#include +#include +#include + +#define MIDI_NOTE_ON 0x90 +void sendMIDI(uint8_t note, uint8_t velocity) { + if (!pCharacteristic) { + Serial.println("BLE characteristic not initialized"); + return; + } + if (deviceConnected) { + uint8_t midiMessage[3]; + midiMessage[0] = MIDI_NOTE_ON; + midiMessage[1] = note; + midiMessage[2] = velocity; + + pCharacteristic->setValue(midiMessage, sizeof(midiMessage)); + pCharacteristic->notify(); + Serial.println("MIDI message sent."); + } else { + Serial.println("No BLE device connected, MIDI message not sent."); + } +} + +void connectToBLE() { + BLEDevice::init("ESP32 MIDI Device"); + BLEServer *pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + BLEService *pService = pServer->createService("12345678-1234-5678-1234-56789abcdef0"); + pCharacteristic = pService->createCharacteristic( + "87654321-1234-5678-1234-56789abcdef0", + BLECharacteristic::PROPERTY_NOTIFY + ); + pCharacteristic->addDescriptor(new BLE2902()); + pService->start(); + BLEAdvertising *pAdvertising = pServer->getAdvertising(); + pAdvertising->start(); + Serial.println("ESP32 MIDI Device started."); +} diff --git a/src/midi.h b/src/midi.h new file mode 100644 index 0000000..9509036 --- /dev/null +++ b/src/midi.h @@ -0,0 +1,12 @@ +#ifndef MIDI_H +#define MIDI_H + +#include + +extern BLECharacteristic *pCharacteristic; +extern bool deviceConnected; + +void sendMIDI(uint8_t note, uint8_t velocity); +void connectToBLE(); + +#endif diff --git a/src/web_server.cpp b/src/web_server.cpp new file mode 100644 index 0000000..7ce3f8e --- /dev/null +++ b/src/web_server.cpp @@ -0,0 +1,106 @@ +#include "web_server.h" +#include "esp_info.h" +#include "SPIFFS.h" + +AsyncWebServer server(80); +DNSServer dnsServer; +bool isAPMode = false; + +void startAccessPoint() { + WiFi.softAP("Audio2MIDI_AP"); + IPAddress IP = WiFi.softAPIP(); + Serial.print("AP IP address: "); + Serial.println(IP); + + // Start DNS server for captive portal + dnsServer.start(53, "*", IP); + isAPMode = true; + + // Start the web server + setupWebServer(); + startWebServer(); +} + +void setupWebServer() { + // Add CORS headers to all responses + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + // Root route with debug logging + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { + Serial.print("Handling root request from IP: "); + Serial.println(request->client()->remoteIP()); + + if (SPIFFS.exists("/www/index.html")) { + Serial.println("Serving /www/index.html"); + request->send(SPIFFS, "/www/index.html", "text/html"); + } else { + Serial.println("index.html not found!"); + request->send(200, "text/plain", "Welcome to Audio2MIDI!"); + } + }); + + // Handle static files with detailed error checking + server.serveStatic("/", SPIFFS, "/www/").setDefaultFile("index.html").setFilter([](AsyncWebServerRequest *request) { + String path = request->url(); + Serial.printf("Static request for: %s\n", path.c_str()); + if (SPIFFS.exists("/www" + path)) { + Serial.println("File exists"); + return true; + } + Serial.println("File does not exist"); + return false; + }); + + // Debug - list files endpoint + server.on("/list", HTTP_GET, [](AsyncWebServerRequest *request) { + String output = "Files in SPIFFS:\n"; + File root = SPIFFS.open("/"); + File file = root.openNextFile(); + while(file) { + output += " - "; + output += file.name(); + output += " ("; + output += file.size(); + output += " bytes)\n"; + file = root.openNextFile(); + } + request->send(200, "text/plain", output); + }); + + // API endpoints + server.on("/api/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request) { + String json; + if (WiFi.status() == WL_CONNECTED) { + json = "{\"connected\":true,\"ssid\":\"" + WiFi.SSID() + + "\",\"ip\":\"" + WiFi.localIP().toString() + + "\",\"rssi\":" + String(WiFi.RSSI()) + "}"; + } else { + json = "{\"connected\":false}"; + } + AsyncWebServerResponse *response = request->beginResponse(200, "application/json", json); + response->addHeader("Access-Control-Allow-Origin", "*"); + request->send(response); + }); + + // Catch-all handler for captive portal in AP mode + server.onNotFound([](AsyncWebServerRequest *request) { + if (isAPMode) { + request->redirect("/"); + } else { + request->send(404, "text/plain", "Not Found"); + } + }); +} + +void startWebServer() { + server.begin(); + Serial.println("Web server started on port 80"); +} + +void handleDNS() { + if (isAPMode) { + dnsServer.processNextRequest(); + } +} diff --git a/src/web_server.h b/src/web_server.h new file mode 100644 index 0000000..e65ce7e --- /dev/null +++ b/src/web_server.h @@ -0,0 +1,14 @@ +#ifndef WEB_SERVER_H +#define WEB_SERVER_H + +#include +#include + +void startAccessPoint(); +void setupWebServer(); +void startWebServer(); +void handleDNS(); +extern AsyncWebServer server; +extern DNSServer dnsServer; + +#endif diff --git a/test/README b/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html