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:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal 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
102
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
|
||||
41
data/www/dashboard.html
Normal file
41
data/www/dashboard.html
Normal 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
122
data/www/index.html
Normal 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
61
data/www/wifi.html
Normal 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
37
include/README
Normal 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
46
lib/README
Normal 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
|
||||
40
platformio.ini
Normal file
40
platformio.ini
Normal 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
43
src/NoteMappings copy.h
Normal 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
43
src/NoteMappings.h
Normal 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
75
src/audio_input.cpp
Normal 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
14
src/audio_input.h
Normal 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
70
src/ble.cpp
Normal 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
16
src/ble.h
Normal 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
50
src/config.h
Normal 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
154
src/esp_info.cpp
Normal 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
18
src/esp_info.h
Normal 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
48
src/fft_processing.cpp
Normal 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
13
src/fft_processing.h
Normal 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
23
src/led_control.cpp
Normal 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
7
src/led_control.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#ifndef LED_CONTROL_H
|
||||
#define LED_CONTROL_H
|
||||
|
||||
void setupLED();
|
||||
void ledCycle();
|
||||
|
||||
#endif
|
||||
79
src/main.cpp
Normal file
79
src/main.cpp
Normal 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
43
src/midi.cpp
Normal 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
12
src/midi.h
Normal 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
106
src/web_server.cpp
Normal 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
14
src/web_server.h
Normal 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
11
test/README
Normal 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
|
||||
Reference in New Issue
Block a user