Changes committed:

new file:   .gitignore
	modified:   README.md
	new file:   data/www/dashboard.html
	new file:   data/www/index.html
	new file:   data/www/wifi.html
	new file:   include/README
	new file:   lib/README
	new file:   notes.ods
	new file:   platformio.ini
	new file:   src/NoteMappings copy.h
	new file:   src/NoteMappings.h
	new file:   src/audio_input.cpp
	new file:   src/audio_input.h
	new file:   src/ble.cpp
	new file:   src/ble.h
	new file:   src/config.h
	new file:   src/esp_info.cpp
	new file:   src/esp_info.h
	new file:   src/fft_processing.cpp
	new file:   src/fft_processing.h
	new file:   src/led_control.cpp
	new file:   src/led_control.h
	new file:   src/main.cpp
	new file:   src/midi.cpp
	new file:   src/midi.h
	new file:   src/web_server.cpp
	new file:   src/web_server.h
	new file:   test/README
This commit is contained in:
2025-04-12 21:23:24 +02:00
parent f823e04dc1
commit 6d55c2315a
28 changed files with 1321 additions and 1 deletions

34
.gitignore vendored Normal file
View File

@@ -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

102
README.md
View File

@@ -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

41
data/www/dashboard.html Normal file
View File

@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>ESP32 Dashboard</title>
<style>
.card {
background: #f0f0f0;
padding: 20px;
margin: 10px;
border-radius: 10px;
}
</style>
</head>
<body>
<div id="dashboard"></div>
<script>
function updateValues() {
console.log('Fetching ESP info...');
fetch('/api/espinfo')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then(data => {
console.log('Data received');
document.getElementById('dashboard').innerHTML = data;
})
.catch(error => {
console.error('Error:', error);
document.getElementById('dashboard').innerHTML = 'Error loading data';
});
}
// Initial load
updateValues();
// Update every 2 seconds
setInterval(updateValues, 2000);
</script>
</body>
</html>

122
data/www/index.html Normal file
View File

@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html>
<head>
<title>Audio2MIDI</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
height: 100vh;
font-family: Arial, sans-serif;
}
.sidemenu {
width: 250px;
background: #2c3e50;
color: white;
padding: 20px 0;
}
.menu-item {
padding: 15px 25px;
cursor: pointer;
transition: background 0.3s;
}
.menu-item:hover {
background: #34495e;
}
.menu-item.active {
background: #3498db;
}
.content {
flex: 1;
padding: 20px;
background: #f8f9fa;
overflow-y: auto;
}
.danger-zone {
margin-top: auto;
padding: 20px;
border-top: 1px solid #34495e;
}
.danger-btn {
background: #e74c3c;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin: 5px 0;
}
.danger-btn:hover {
background: #c0392b;
}
</style>
</head>
<body>
<div class="sidemenu">
<div class="menu-item active" data-page="dashboard">Dashboard</div>
<div class="menu-item" data-page="wifi">WiFi Settings</div>
<div class="menu-item" data-page="midi">MIDI Config</div>
<div class="menu-item" data-page="system">System</div>
<div class="danger-zone">
<h4>Danger Zone</h4>
<button class="danger-btn" onclick="resetWiFi()">Reset WiFi Settings</button>
<button class="danger-btn" onclick="forgetNetwork()">Forget Current Network</button>
<button class="danger-btn" onclick="factoryReset()">Factory Reset</button>
</div>
</div>
<div class="content" id="mainContent">
<iframe id="contentFrame" style="width:100%;height:100%;border:none;"></iframe>
</div>
<script>
const pages = {
dashboard: '/dashboard.html',
wifi: '/wifi.html',
midi: '/midi.html',
system: '/system.html'
};
document.querySelectorAll('.menu-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.menu-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
const page = item.dataset.page;
document.getElementById('contentFrame').src = pages[page];
});
});
function resetWiFi() {
if(confirm('Are you sure you want to reset all WiFi settings?')) {
fetch('/api/wifi/reset', { method: 'POST' })
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => alert('Error: ' + error));
}
}
function forgetNetwork() {
if(confirm('Are you sure you want to forget the current WiFi network?')) {
fetch('/api/wifi/forget', { method: 'POST' })
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => alert('Error: ' + error));
}
}
function factoryReset() {
if(confirm('WARNING: This will reset all settings to factory defaults. Are you sure?')) {
fetch('/api/system/reset', { method: 'POST' })
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => alert('Error: ' + error));
}
}
// Load dashboard by default
document.getElementById('contentFrame').src = pages.dashboard;
</script>
</body>
</html>

61
data/www/wifi.html Normal file
View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html>
<head>
<title>WiFi Settings</title>
<style>
.wifi-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.wifi-status {
background: #f0f0f0;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
}
.status-connected {
color: #27ae60;
}
.status-disconnected {
color: #c0392b;
}
</style>
</head>
<body>
<div class="wifi-container">
<h2>WiFi Settings</h2>
<div class="wifi-status" id="statusContainer">
<h3>Current Status</h3>
<div id="wifiStatus"></div>
</div>
</div>
<script>
function updateWiFiStatus() {
fetch('/api/wifi/status')
.then(response => response.json())
.then(data => {
const statusHtml = `
<p class="status-${data.connected ? 'connected' : 'disconnected'}">
Status: ${data.connected ? 'Connected' : 'Disconnected'}
</p>
${data.connected ? `
<p>SSID: ${data.ssid}</p>
<p>IP Address: ${data.ip}</p>
<p>Signal Strength: ${data.rssi}dBm</p>
` : ''}
`;
document.getElementById('wifiStatus').innerHTML = statusHtml;
})
.catch(error => {
document.getElementById('wifiStatus').innerHTML =
`<p class="status-disconnected">Error: ${error}</p>`;
});
}
// Update status every 5 seconds
updateWiFiStatus();
setInterval(updateWiFiStatus, 5000);
</script>
</body>
</html>

37
include/README Normal file
View File

@@ -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

46
lib/README Normal file
View File

@@ -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 <Foo.h>
#include <Bar.h>
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

BIN
notes.ods Normal file

Binary file not shown.

40
platformio.ini Normal file
View File

@@ -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

43
src/NoteMappings copy.h Normal file
View File

@@ -0,0 +1,43 @@
// NoteMappings.h
#ifndef NOTEMAPPINGS_H
#define NOTEMAPPINGS_H
#include <map>
#include <string>
// Frequency to Note Map (simplified for illustration)
std::map<double, std::string> 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<std::string, byte> 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

43
src/NoteMappings.h Normal file
View File

@@ -0,0 +1,43 @@
// NoteMappings.h
#ifndef NOTEMAPPINGS_H
#define NOTEMAPPINGS_H
#include <map>
#include <string>
// Frequency to Note Map (simplified for illustration)
std::map<double, std::string> 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<std::string, byte> 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

75
src/audio_input.cpp Normal file
View File

@@ -0,0 +1,75 @@
#include <Arduino.h>
#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;
}
}

14
src/audio_input.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef AUDIO_INPUT_H
#define AUDIO_INPUT_H
#include <Wire.h>
#include "driver/i2s.h"
#include "config.h"
extern float vReal[];
extern float vImag[];
bool initI2S();
void readAudioData();
#endif

70
src/ble.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include <Arduino.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#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);
}

16
src/ble.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef BLE_H
#define BLE_H
#include <BLEServer.h>
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

50
src/config.h Normal file
View File

@@ -0,0 +1,50 @@
// config.h
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
// 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

154
src/esp_info.cpp Normal file
View File

@@ -0,0 +1,154 @@
// esp_info.cpp
#include <Arduino.h>
#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 = "<div class='esp-info'><h3>ESP32 Information</h3>";
html += "<h4>Internal RAM</h4>";
html += "<p>Heap Size: " + formatBytes(ESP.getHeapSize()) + "</p>";
html += "<p>Free Heap: " + formatBytes(ESP.getFreeHeap()) + "</p>";
html += "<h4>PSRAM</h4>";
html += "<p>PSRAM Size: " + formatBytes(ESP.getPsramSize()) + "</p>";
html += "<p>Free PSRAM: " + formatBytes(ESP.getFreePsram()) + "</p>";
html += "<h4>Flash</h4>";
size_t usedBytes = ESP.getSketchSize();
size_t totalBytes = ESP.getFlashChipSize();
float usagePercent = (usedBytes * 100.0) / totalBytes;
html += "<p>Flash Size: " + formatBytes(totalBytes) + "</p>";
html += "<p>Used: " + formatBytes(usedBytes) + " (" + String(usagePercent, 1) + "%)</p>";
html += "<p>Flash Speed: " + String(ESP.getFlashChipSpeed() / 1000000) + " MHz</p>";
html += "<h4>CPU</h4>";
html += "<p>Model: " + String(ESP.getChipModel()) + "</p>";
html += "<p>Cores: " + String(ESP.getChipCores()) + "</p>";
html += "<p>Frequency: " + String(ESP.getCpuFreqMHz()) + " MHz</p>";
float temperature = readInternalTemperature();
if (!isnan(temperature)) {
html += "<h4>Temperature Sensor</h4>";
html += "<p>Internal Temperature: " + String(temperature, 2) + " °C</p>";
}
html += "</div>";
return html;
}
String getRAMInfoHTML() {
String html = "<div class='ram-info'><h3>RAM Information</h3>";
html += "<p>Heap Size: " + formatBytes(ESP.getHeapSize()) + "</p>";
html += "<p>Free Heap: " + formatBytes(ESP.getFreeHeap()) + "</p>";
html += "<p>Min Free Heap: " + formatBytes(ESP.getMinFreeHeap()) + "</p>";
html += "<p>Max Alloc Heap: " + formatBytes(ESP.getMaxAllocHeap()) + "</p>";
html += "<h4>PSRAM</h4>";
html += "<p>PSRAM Size: " + formatBytes(ESP.getPsramSize()) + "</p>";
html += "<p>Free PSRAM: " + formatBytes(ESP.getFreePsram()) + "</p>";
html += "<p>Min Free PSRAM: " + formatBytes(ESP.getMinFreePsram()) + "</p>";
html += "<p>Max Alloc PSRAM: " + formatBytes(ESP.getMaxAllocPsram()) + "</p>";
html += "</div>";
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");
}
}

18
src/esp_info.h Normal file
View File

@@ -0,0 +1,18 @@
// esp_info.h
#ifndef ESP_INFO_H
#define ESP_INFO_H
#pragma once
#include <Arduino.h>
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

48
src/fft_processing.cpp Normal file
View File

@@ -0,0 +1,48 @@
#include <ArduinoFFT.h>
#include "config.h"
#include "fft_processing.h"
// extern const int SAMPLES; // Declare SAMPLES as external
ArduinoFFT<float> FFT = ArduinoFFT<float>(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);
}
}

13
src/fft_processing.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef FFT_PROCESSING_H
#define FFT_PROCESSING_H
#include <ArduinoFFT.h>
extern ArduinoFFT<float> FFT;
extern float vReal[];
extern float vImag[];
void handleFFT();
void processFFTData();
#endif

23
src/led_control.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "led_control.h"
#include <FastLED.h>
const uint8_t NUM_LEDS = 1;
const gpio_num_t DATA_PIN = GPIO_NUM_48;
CRGB leds[NUM_LEDS];
void setupLED() {
CFastLED::addLeds<WS2811, DATA_PIN, GRB>(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);
}

7
src/led_control.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef LED_CONTROL_H
#define LED_CONTROL_H
void setupLED();
void ledCycle();
#endif

79
src/main.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "esp_task_wdt.h"
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <SPIFFS.h>
#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
}

43
src/midi.cpp Normal file
View File

@@ -0,0 +1,43 @@
#include <Arduino.h>
#include "midi.h"
#include "ble.h"
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLE2902.h>
#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.");
}

12
src/midi.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef MIDI_H
#define MIDI_H
#include <BLEDevice.h>
extern BLECharacteristic *pCharacteristic;
extern bool deviceConnected;
void sendMIDI(uint8_t note, uint8_t velocity);
void connectToBLE();
#endif

106
src/web_server.cpp Normal file
View File

@@ -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();
}
}

14
src/web_server.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include <ESPAsyncWebServer.h>
#include <DNSServer.h>
void startAccessPoint();
void setupWebServer();
void startWebServer();
void handleDNS();
extern AsyncWebServer server;
extern DNSServer dnsServer;
#endif

11
test/README Normal file
View File

@@ -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