Compare commits

...

5 Commits

Author SHA1 Message Date
2c5fa44b6b style 💎: Update I2S pin definitions for INMP441
Updated pin definitions to use SD pins for data input, bit clock, and left-right clock. Also updated noise threshold value.
2025-11-09 08:52:06 +01:00
438fd59037 Changes to be committed:
modified:   data/www/index.html
	new file:   data/www/spectrum.html
	new file:   partitions.csv
	modified:   platformio.ini
	deleted:    src/NoteMappings copy.h
	modified:   src/main.cpp
	modified:   src/web_server.cpp
	modified:   src/web_server.h
2025-04-16 18:16:20 +02:00
3031f75836 Moved buttons to wifi.html
modified:   data/www/index.html
	modified:   data/www/wifi.html
2025-04-13 20:33:15 +02:00
777640b7df modified: data/www/index.html
modified:   data/www/wifi.html
2025-04-13 12:40:07 +02:00
1080d3025c Removed duplicated ram info
modified:   data/www/dashboard.html
	modified:   src/esp_info.cpp
	modified:   src/esp_info.h
	modified:   src/web_server.cpp
2025-04-13 12:00:19 +02:00
14 changed files with 237 additions and 117 deletions

View File

@@ -31,21 +31,13 @@
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // Expect JSON response
return response.text(); // Changed from json() to text()
})
.then(data => {
console.log('Data received:', data);
if (!data) throw new Error('Empty response');
let html = '';
Object.entries(data).forEach(([key, value]) => {
html += `
<div class="card">
<h3>${key}</h3>
<p>${value}</p>
</div>`;
});
document.getElementById('dashboard').innerHTML = html;
document.getElementById('dashboard').innerHTML = data;
})
.catch(error => {
console.error('Error:', error);

View File

@@ -34,9 +34,7 @@
overflow-y: auto;
}
.danger-zone {
margin-top: auto;
padding: 20px;
border-top: 1px solid #34495e;
display: none; /* Hide the danger zone since it's no longer needed */
}
.danger-btn {
background: #e74c3c;
@@ -59,13 +57,6 @@
<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>
@@ -76,7 +67,8 @@
dashboard: '/dashboard.html',
wifi: '/wifi.html',
midi: '/midi.html',
system: '/system.html'
system: '/system.html',
spectrum: '/spectrum.html' // Added spectrum page
};
document.querySelectorAll('.menu-item').forEach(item => {
@@ -88,32 +80,18 @@
});
});
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));
}
}
// Add spectrum menu item
const spectrumMenuItem = document.createElement('div');
spectrumMenuItem.className = 'menu-item';
spectrumMenuItem.dataset.page = 'spectrum';
spectrumMenuItem.textContent = 'Spectrum';
document.querySelector('.sidemenu').appendChild(spectrumMenuItem);
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));
}
}
spectrumMenuItem.addEventListener('click', () => {
document.querySelectorAll('.menu-item').forEach(i => i.classList.remove('active'));
spectrumMenuItem.classList.add('active');
document.getElementById('contentFrame').src = pages.spectrum;
});
// Load dashboard by default
document.getElementById('contentFrame').src = pages.dashboard;

66
data/www/spectrum.html Normal file
View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<title>Audio Spectrum</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #f8f9fa;
font-family: Arial, sans-serif;
}
canvas {
border: 1px solid #ccc;
}
</style>
</head>
<body>
<canvas id="spectrumCanvas" width="800" height="400"></canvas>
<script>
const canvas = document.getElementById('spectrumCanvas');
const ctx = canvas.getContext('2d');
function drawSpectrum(data) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw axis labels
ctx.fillStyle = 'black';
ctx.font = '16px Arial';
ctx.fillText('Frequency/Note', canvas.width / 2 - 50, canvas.height - 10);
ctx.save();
ctx.translate(10, canvas.height / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText('Amplitude', -50, 0);
ctx.restore();
// Draw spectrum bars
const barWidth = canvas.width / data.length;
data.forEach((value, index) => {
const barHeight = value * canvas.height;
ctx.fillStyle = 'rgb(50, 150, 250)';
ctx.fillRect(index * barWidth, canvas.height - barHeight, barWidth, barHeight);
});
// Draw legend
ctx.fillStyle = 'black';
ctx.fillRect(10, 10, 20, 20);
ctx.fillStyle = 'rgb(50, 150, 250)';
ctx.fillRect(12, 12, 16, 16);
ctx.fillStyle = 'black';
ctx.font = '14px Arial';
ctx.fillText('Amplitude', 40, 25);
}
// Simulate spectrum data for testing
setInterval(() => {
const testData = Array.from({ length: 64 }, () => Math.random());
drawSpectrum(testData);
}, 100);
</script>
</body>
</html>

View File

@@ -20,6 +20,31 @@
.status-disconnected {
color: #c0392b;
}
.danger-zone {
margin-top: 30px;
padding: 20px;
border: 2px solid #e74c3c;
border-radius: 5px;
}
.danger-btn {
background: #e74c3c;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
width: 100%;
margin: 10px 0;
}
.danger-btn:hover {
background: #c0392b;
}
.danger-section {
margin-bottom: 20px;
}
.danger-section h4 {
margin-bottom: 10px;
}
</style>
</head>
<body>
@@ -29,6 +54,18 @@
<h3>Current Status</h3>
<div id="wifiStatus"></div>
</div>
<div class="danger-zone">
<div class="danger-section">
<h4>WiFi Controls</h4>
<button class="danger-btn" onclick="resetWiFi()">Reset WiFi Settings</button>
<button class="danger-btn" onclick="forgetNetwork()">Forget Current Network</button>
</div>
<div class="danger-section">
<h4>System Controls</h4>
<button class="danger-btn" onclick="factoryReset()">Factory Reset</button>
</div>
</div>
</div>
<script>
function updateWiFiStatus() {
@@ -53,6 +90,45 @@
});
}
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);
updateWiFiStatus();
})
.catch(error => alert('Error: ' + error));
}
}
function forgetNetwork() {
if(confirm('Are you sure you want to forget the current network?')) {
fetch('/api/wifi/forget', { method: 'POST' })
.then(response => response.json())
.then(data => {
alert(data.message);
updateWiFiStatus();
})
.catch(error => alert('Error: ' + error));
}
}
function factoryReset() {
if(confirm('Are you sure you want to perform a factory reset? This will erase ALL settings and restart the device.')) {
// Using GET method to avoid HTTP method redefinition issues with WiFiManager
fetch('/api/system/factory-reset')
.then(response => response.json())
.then(data => {
alert(data.message || 'Factory reset initiated. Device will restart.');
setTimeout(() => {
window.location.href = '/';
}, 3000);
})
.catch(error => alert('Error: ' + error));
}
}
// Update status every 5 seconds
updateWiFiStatus();
setInterval(updateWiFiStatus, 5000);

BIN
notes.ods

Binary file not shown.

5
partitions.csv Normal file
View File

@@ -0,0 +1,5 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x2F0000,
spiffs, data, spiffs, 0x300000,0x100000
1 # Name, Type, SubType, Offset, Size, Flags
2 nvs, data, nvs, 0x9000, 0x5000,
3 otadata, data, ota, 0xe000, 0x2000,
4 app0, app, ota_0, 0x10000, 0x2F0000,
5 spiffs, data, spiffs, 0x300000,0x100000

View File

@@ -18,7 +18,7 @@ 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
board_build.partitions = partitions.csv # Use custom partition table
build_flags = -DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM
-DARDUINO_ESP32S3_DEV
@@ -33,7 +33,7 @@ monitor_filters = esp32_exception_decoder
lib_deps =
fastled/FastLED@^3.9.4
https://github.com/tzapu/WiFiManager.git
kosme/arduinoFFT@^2.0.4
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

View File

@@ -1,43 +0,0 @@
// 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

View File

@@ -5,15 +5,22 @@
#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
#define I2S_DATA_IN_PIN 10 // Data input pin for INMP441 (SD)
#define I2S_BCLK_PIN 8 // Bit clock pin (SCK)
#define I2S_LRCLK_PIN 9 // Left-Right clock pin (WS)
// L/R (Left/Right Select) connect to GND for Left channel, VDD for right channel
// I2S Configuration
#define I2S_PORT I2S_NUM_0
#define DMA_BUF_COUNT 8
#define DMA_BUF_LEN 64
// Audio Configuration
#define SAMPLES 1024 // Must be a power of 2
#define SAMPLING_FREQUENCY 16000
#define MAX_RETRIES 3
#define NOISE_THRESHOLD 50
// #define NOISE_THRESHOLD 50
#define NOISE_THRESHOLD 750000
// BLE Configuration
#define BLE_DEVICE_NAME "ESP32 MIDI Device"
@@ -27,10 +34,6 @@
// 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

View File

@@ -116,19 +116,19 @@ String getESPInfoHTML() {
String html = "<div class='esp-info'><h3>ESP32 Information</h3>";
html += getTemperatureInfo();
html += getCPUInfo();
html += getRAMInfo();
html += getPSRAMInfo();
html += getFlashInfo();
html += "</div>";
return html;
}
String getRAMInfoHTML() {
String html = "<div class='ram-info'><h3>RAM Information</h3>";
String getMemoryInfoHTML() {
String html = "<div class='memory-info'><h3>Memory Information</h3>";
html += "<h4>Internal RAM</h4>";
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>";
@@ -137,6 +137,7 @@ String getRAMInfoHTML() {
html += "</div>";
return html;
}
void printESPInfo() {
Serial.println("ESP32-S3 info");
Serial.println("Internal RAM");

View File

@@ -10,15 +10,14 @@ void prettyPrintBytes(size_t bytes);
String formatBytes(size_t bytes);
// Individual info getters
String getCPUInfo();
String getTemperatureInfo();
String getRAMInfo();
String getPSRAMInfo();
String getFlashInfo();
String getCPUInfo(); // CPU related info only
String getTemperatureInfo(); // Temperature info
String getMemoryInfo(); // RAM and PSRAM info
String getFlashInfo(); // Flash memory info
// HTML generators
String getESPInfoHTML();
String getRAMInfoHTML();
String getESPInfoHTML(); // Combines CPU, Temperature, and Flash info
String getMemoryInfoHTML(); // Memory specific info (replaces getRAMInfoHTML)
// Debug print
void printESPInfo();

View File

@@ -7,6 +7,11 @@
#include "esp_info.h"
#include "led_control.h"
#include "web_server.h"
#include "audio_input.h"
#include "fft_processing.h"
#include "midi.h"
#include "ble.h"
WiFiManager wm; // Global WiFiManager instance
@@ -14,8 +19,9 @@ 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();
// Start WiFiManager's configuration portal
wm.startConfigPortal("Audio2MIDI_AP");
}
bool setupWiFi() {
@@ -73,10 +79,20 @@ void setup() {
if (!setupWiFi()) {
ESP.restart();
}
if (!initI2S()) {
Serial.println("I2S initialization failed!");
return;
}
setupBLE();
}
void loop() {
handleDNS(); // Process DNS requests for captive portal
ledCycle();
delay(10); // Small delay to prevent watchdog triggers
readAudioData();
handleFFT();
processFFTData();
}

View File

@@ -6,6 +6,12 @@ AsyncWebServer server(80);
DNSServer dnsServer;
bool isAPMode = false;
void restartWebServer() {
server.end(); // Stop the current server
setupWebServer(); // Reinitialize the server
startWebServer(); // Start the server again
}
void startAccessPoint() {
WiFi.softAP("Audio2MIDI_AP");
IPAddress IP = WiFi.softAPIP();
@@ -17,11 +23,16 @@ void startAccessPoint() {
isAPMode = true;
// Start the web server
setupWebServer();
startWebServer();
restartWebServer();
}
void setupWebServer() {
// Ensure SPIFFS is mounted
if (!SPIFFS.begin(true)) {
Serial.println("[ERROR] Failed to mount SPIFFS");
return;
}
// 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");
@@ -29,7 +40,7 @@ void setupWebServer() {
// ESP Info endpoint
server.on("/api/espinfo", HTTP_GET, [](AsyncWebServerRequest *request) {
String htmlContent = getESPInfoHTML() + getRAMInfoHTML();
String htmlContent = getESPInfoHTML() + getMemoryInfoHTML(); // Updated function name
AsyncWebServerResponse *response = request->beginResponse(200, "text/html", htmlContent);
request->send(response);
});
@@ -49,6 +60,19 @@ void setupWebServer() {
request->send(response);
});
// API endpoint to reset WiFi settings
server.on("/api/wifi/reset", HTTP_POST, [](AsyncWebServerRequest *request) {
wm.resetSettings(); // Call resetSettings directly since it returns void
request->send(200, "application/json", "{\"message\":\"WiFi settings reset successfully.\"}");
});
// API endpoint for system factory reset
server.on("/api/system/factory-reset", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "application/json", "{\"message\":\"Factory reset initiated. Device will restart.\"}");
delay(1000); // Allow time for the response to be sent
ESP.restart(); // Restart the device
});
// Root route with debug logging
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
Serial.print("Handling root request from IP: ");
@@ -59,7 +83,7 @@ void setupWebServer() {
request->send(SPIFFS, "/www/index.html", "text/html");
} else {
Serial.println("index.html not found!");
request->send(200, "text/plain", "Welcome to Audio2MIDI!");
request->send(404, "text/plain", "index.html not found");
}
});
@@ -108,6 +132,7 @@ void setupWebServer() {
// Catch-all handler for captive portal in AP mode
server.onNotFound([](AsyncWebServerRequest *request) {
Serial.printf("Handle Not Found: %s\n", request->url().c_str());
if (isAPMode) {
request->redirect("/");
} else {

View File

@@ -1,6 +1,7 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include <WiFiManager.h>
#include <ESPAsyncWebServer.h>
#include <DNSServer.h>
@@ -10,5 +11,6 @@ void startWebServer();
void handleDNS();
extern AsyncWebServer server;
extern DNSServer dnsServer;
extern WiFiManager wm; // Declare the global WiFiManager instance
#endif