Help with visualising FFT - sine wave is visualised as noise #527
-
Hi 👋 I would really appreciate some help trying to visualise the result of an FFT. I've been trying for a few days now, and I just can't crack the code. All I have ever seen is just noise, like there's no difference between any of the frequencies. The magnitudes for all frequencies bounce around the same values. The same output occurs whether I use a microphone, or generated sine wave. A quick summary
I've tried connecting a real mic. I've verified that the mic does work using a server that streams the audio via a WAVEncoder. Complete codeNote: add #define WIFI_SSID "my-wifi-ssid"
#define WIFI_PASS "password123" I'm using PlatformIO: [env]
platform = espressif32
board = esp32dev
framework = arduino
monitor_port = /dev/cu.SLAB_USBtoUART
monitor_speed = 115200
upload_port = /dev/cu.SLAB_USBtoUART
build_type = debug
monitor_filters = esp32_exception_decoder
lib_deps =
links2004/WebSockets@^2.3.7
Wire
me-no-dev/AsyncTCP @ ^1.1.1
https://github.com/me-no-dev/ESPAsyncWebServer.git
https://github.com/pschatzmann/arduino-audio-tools#main
build_flags =
-DCONFIG_COMPILER_CXX_EXCEPTIONS
-DPIO_FRAMEWORK_ESP_IDF_ENABLE_EXCEPTIONS
-fexceptions
-Wno-unused-variable
-Wno-unused-but-set-variable
-Wno-unused-function
-Wno-format-extra-args
build_unflags = -fno-exceptions -fcolor-diagnostics
[env:esp32dev]
monitor_filters = esp32_exception_decoder #include <ESPAsyncWebServer.h>
#include <sstream>
#include "WiFiCredentials.h"
#include "AudioTools.h"
#include "AudioLibs/AudioRealFFT.h"
#include "AudioLibs/MemoryManager.h"
#include <ctime>
#include <cstdint>
static const long DEVICE_SERIAL_BAUD = 115200;
static const int LED_PIN = 2;
static const int AUDIO_CHANNELS = 1;
static const int AUDIO_SAMPLE_RATE = 44100;
static const int AUDIO_BITS_PER_SAMPLE = 16;
static const int AUDIO_FFT_LENGTH = 64;// *must* be power of 2
static const int AUDIO_FFT_BINS = AUDIO_FFT_LENGTH / 2;
static const char webpage[] PROGMEM = /* language=HTML */ R"=====(
<html lang="en">
<!-- Adding a data chart using Chart.js -->
<!--https://github.com/squix78/esp32-mic-fft-->
<head>
<title>ESP32 Audio Visualiser</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js'></script>
</head>
<body onload="javascript:init()">
<div>
<canvas id="chart" width="600" height="400"></canvas>
</div>
<!-- Adding a websocket to the client (webpage) -->
<script>
let webSocket, dataPlot;
// const maxDataPoints = 200;
const maxValue = 100000;
const maxLow = maxValue * 0.5;
const maxMedium = maxValue * 0.2;
const maxHigh = maxValue * 0.3;
function init() {
webSocket = new WebSocket('ws://' + window.location.hostname + ':80/ws');
dataPlot = new Chart(document.getElementById("chart"), {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
label: "Low",
backgroundColor: "#D6E9C6"
},
{
data: [],
label: "Moderate",
backgroundColor: "#FAEBCC"
},
{
data: [],
label: "High",
backgroundColor: "#EBCCD1"
},
]
},
options: {
responsive: false,
animation: false,
scales: {
xAxes: [{ stacked: true }],
yAxes: [{
display: true,
stacked: true,
ticks: {
beginAtZero: true,
steps: 100,
stepValue: 50,
max: maxValue
}
}]
}
}
});
webSocket.onmessage = function(event) {
const data = JSON.parse(event.data);
dataPlot.data.labels = [];
dataPlot.data.datasets[0].data = [];
dataPlot.data.datasets[1].data = [];
dataPlot.data.datasets[2].data = [];
data.forEach(function(element) {
dataPlot.data.labels.push(element.bin);
const lowValue = Math.min(maxLow, element.value);
dataPlot.data.datasets[0].data.push(lowValue);
const mediumValue = Math.min(Math.max(0, element.value - lowValue), maxMedium);
dataPlot.data.datasets[1].data.push(mediumValue);
const highValue = Math.max(0, element.value - lowValue - mediumValue);
dataPlot.data.datasets[2].data.push(highValue);
});
dataPlot.update();
}
}
</script>
</body>
</html>
)=====";
/// Toggle the LED from off->on, or on->off
static void toggleLed() {
digitalWrite(LED_PIN, digitalRead(LED_PIN) ^ HIGH);
}
SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> generatedSource(sineWave);
AnalogAudioStream audioSource;
AudioRealFFT audioFFT;
StreamCopy fftCopier(audioFFT, generatedSource);
AsyncWebServer httpServer(80);
AsyncWebSocket wsServer("/ws");
void initWiFi() {
Serial.print("\nConnecting to WiFi...");
WiFiClass::mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint8_t connectAttempts = 0;
while (WiFiClass::status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
if (connectAttempts++ % 10 == 0) {
Serial.print(" retrying...");
WiFi.reconnect();
}
}
Serial.println(" connected!");
WiFi.setSleep(WIFI_PS_NONE);
}
void initWebServer() {
Serial.print("Configuring HTTP server...");
httpServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(
200,
"text/html",
webpage
);
});
httpServer.addHandler(&wsServer);
Serial.print(" starting...");
httpServer.begin();
Serial.println(" started!");
}
bool wsEnabled = false;
float fftBinFrequencies[AUDIO_FFT_BINS] = {0};
void handleFftResult(AudioFFTBase &fft) {
if (wsEnabled) {
String json = "[";
// skip the first bin, it's the fft total
for (int bin = 1; bin < fft.size(); bin++) {
// scale, because the magnitudes are huge
float mag = fft.magnitude(bin) / 1000000.0f;
float freq = fft.frequency(bin);
// send the bin label, and stored magnitude
if (bin > 1) { json += ", "; }
json += "{\"bin\":";
json += "\"" + String((int) freq) + "\"";
json += ",\"value\":";
json += String((long) mag);
json += "}";
}
json += "]";
wsServer.textAll(json.c_str());
toggleLed();
}
yield();
}
[[noreturn]]
void updateWebSocketServerTask(void *) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(250);
while (true) {
wsServer.cleanupClients();
wsEnabled = wsServer.count() > 0 && wsServer.availableForWriteAll();
xTaskDelayUntil(&xLastWakeTime, xFrequency);
yield();
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(DEVICE_SERIAL_BAUD);
initWiFi();
configTime(0, 0, "pool.ntp.org");
initWebServer();
TaskHandle_t updateWebSocketServerTaskHandle;
xTaskCreatePinnedToCore(
updateWebSocketServerTask,
"websocket task",
2048,
nullptr,
5,
&updateWebSocketServerTaskHandle,
tskNO_AFFINITY
);
// AudioLogger::instance().begin(Serial, AudioLogger::Info);
AudioBaseInfo baseAudioConf = AudioBaseInfo();
baseAudioConf.bits_per_sample = AUDIO_BITS_PER_SAMPLE;
baseAudioConf.channels = AUDIO_CHANNELS;
baseAudioConf.sample_rate = AUDIO_SAMPLE_RATE;
Serial.print("Starting input stream...");
auto audioSourceConf = audioSource.defaultConfig(RX_MODE);
audioSourceConf.copyFrom(baseAudioConf);
audioSourceConf.mode_internal = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN;
audioSourceConf.port_no = I2S_NUM_0;
// audioSourceConf.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
audioSourceConf.use_apll = false;
audioSourceConf.buffer_count = 2;
audioSourceConf.buffer_size = 1024;
audioSourceConf.setInputPin1(GPIO_NUM_35);
audioSource.begin(audioSourceConf);
Serial.println(" Input stream started");
Serial.print("Starting FFT...");
auto fftConf = audioFFT.defaultConfig();
fftConf.copyFrom(baseAudioConf);
fftConf.length = AUDIO_FFT_LENGTH;
fftConf.callback = &handleFftResult;
// fftConf.channel_used = 1;
fftConf.window_function = new BufferedWindow(new Hamming());
audioFFT.begin(fftConf);
Serial.println(" FFT started");
// compute the upper frequency for each bin
for (int bin = 1; bin <= AUDIO_FFT_BINS; bin++) {
// formula from AudioFFT.h
auto freq = static_cast<float>(bin) * static_cast<float>(fftConf.sample_rate) / static_cast<float>(fftConf.length);
fftBinFrequencies[bin - 1] = freq;
}
sineWave.begin(AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, N_FS6);
}
void loop() {
if (wsEnabled) {
fftCopier.copy();
}
yield();
} |
Beta Was this translation helpful? Give feedback.
Replies: 9 comments 3 replies
-
I can only recommend to solve the problem step by step.
ps. how come that your chart has so many bars, but in your code it is supposed to have 32 only? |
Beta Was this translation helpful? Give feedback.
-
I guess you're right: Most likely the issue lies in the output handling and you need to decouple the fft output from the rendering of the output:
ps. |
Beta Was this translation helpful? Give feedback.
-
Here's another simpler experiment. A generated sign wave (frequency set to #include <sstream>
#include "WiFiCredentials.h"
#include "AudioTools.h"
#include "AudioLibs/AudioRealFFT.h"
#include "AudioLibs/MemoryManager.h"
static const long DEVICE_SERIAL_BAUD = 115200;
static const int LED_PIN = 2;
static const int AUDIO_CHANNELS = 1;
static const int AUDIO_SAMPLE_RATE = 44100;
static const int AUDIO_BITS_PER_SAMPLE = 16;
static const int AUDIO_FFT_LENGTH = 1024; // *must* be power of 2
static const int AUDIO_FFT_BINS = AUDIO_FFT_LENGTH / 2;
SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> generatedAudioSource(sineWave);
AudioRealFFT audioFFT;
StreamCopy fftCopier(audioFFT, generatedAudioSource);
// display fft result
void handleFftResult(AudioFFTBase &fft) {
int diff;
auto result = fft.result();
if (result.magnitude > 100) {
Serial.print(result.frequency);
Serial.print(" ");
Serial.print(result.magnitude);
Serial.print(" => ");
Serial.print(result.frequencyAsNote(diff));
Serial.print(" diff: ");
Serial.print(diff);
Serial.print(" - time ms ");
Serial.print(fft.resultTime() - fft.resultTimeBegin());
Serial.println();
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(DEVICE_SERIAL_BAUD);
sineWave.setFrequency(N_D8);
AudioBaseInfo baseAudioConf = AudioBaseInfo();
baseAudioConf.bits_per_sample = AUDIO_BITS_PER_SAMPLE;
baseAudioConf.channels = AUDIO_CHANNELS;
baseAudioConf.sample_rate = AUDIO_SAMPLE_RATE;
auto generatedSourceConf = generatedAudioSource.defaultConfig();
generatedSourceConf.copyFrom(baseAudioConf);
generatedAudioSource.begin(generatedSourceConf);
Serial.print("Starting FFT...");
auto fftConf = audioFFT.defaultConfig();
fftConf.copyFrom(baseAudioConf);
fftConf.length = AUDIO_FFT_LENGTH;
fftConf.callback = &handleFftResult;
// fftConf.channel_used = 1;
fftConf.window_function = new BufferedWindow(new Hamming());
audioFFT.begin(fftConf);
Serial.println(" FFT started");
}
void loop() {
fftCopier.copy();
} The text output shows B8, but I would expect to see D8 I see There's also occasional F1 notes in there.
If I change the note sineWave.setFrequency(N_A0); then the output changes, but it doesn't match the generated tone.
|
Beta Was this translation helpful? Give feedback.
-
Hmm, It seems there might be some bug in the window function: If you take it out you will get the expected result. Without it I am getting
|
Beta Was this translation helpful? Give feedback.
-
Yes!!! Removing the windowing produces something a lot more reasonable. Here's a screenshot, but viewing it live shows a sine wave! I'm not sure why the lower frequencies are maxing out... Here's an updated code sample. Changes
#include <ESPAsyncWebServer.h>
#include <sstream>
#include "WiFiCredentials.h"
#include "AudioTools.h"
#include "AudioLibs/AudioRealFFT.h"
#include "AudioLibs/MemoryManager.h"
#include <ctime>
#include <cstdint>
static const long DEVICE_SERIAL_BAUD = 115200;
static const int LED_PIN = 2;
static const int AUDIO_CHANNELS = 1;
static const int AUDIO_SAMPLE_RATE = 44100;
static const int AUDIO_BITS_PER_SAMPLE = 16;
static const int AUDIO_FFT_LENGTH = 64;// *must* be power of 2
static const int AUDIO_FFT_BINS = AUDIO_FFT_LENGTH / 2;
static const char webpage[] PROGMEM = /* language=HTML */ R"=====(<html lang="en">
<!-- Adding a data chart using Chart.js -->
<!--https://github.com/squix78/esp32-mic-fft-->
<head>
<title>ESP32 Audio Visualiser</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js'></script>
</head>
<body onload="javascript:init()">
<div>
<canvas id="chart" width="600" height="400"></canvas>
</div>
<!-- Adding a websocket to the client (webpage) -->
<script>
let dataPlot;
const maxValue = 100000;
const maxLow = maxValue * 0.5;
const maxMedium = maxValue * 0.2;
const maxHigh = maxValue * 0.3;
function init() {
dataPlot = new Chart(document.getElementById("chart"), {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
label: "Low",
backgroundColor: "#D6E9C6"
},
{
data: [],
label: "Moderate",
backgroundColor: "#FAEBCC"
},
{
data: [],
label: "High",
backgroundColor: "#EBCCD1"
},
]
},
options: {
responsive: false,
animation: false,
scales: {
xAxes: [{stacked: true}],
yAxes: [{
display: true,
stacked: true,
ticks: {
beginAtZero: true,
steps: 100,
stepValue: 50,
max: maxValue
}
}]
}
}
});
if (!!window.EventSource) {
const source = new EventSource('/events');
source.addEventListener('open', function (e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function (e) {
if (e.target.readyState !== EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('fft', function (e) {
const data = JSON.parse(e.data);
dataPlot.data.labels = [];
dataPlot.data.datasets[0].data = [];
dataPlot.data.datasets[1].data = [];
dataPlot.data.datasets[2].data = [];
data.forEach(function (element) {
dataPlot.data.labels.push(element.bin);
const lowValue = Math.min(maxLow, element.value);
dataPlot.data.datasets[0].data.push(lowValue);
const mediumValue = Math.min(Math.max(0, element.value - lowValue), maxMedium);
dataPlot.data.datasets[1].data.push(mediumValue);
const highValue = Math.max(0, element.value - lowValue - mediumValue);
dataPlot.data.datasets[2].data.push(highValue);
});
dataPlot.update();
}, false);
}
}
</script>
</body>
</html>
)=====";
/// Toggle the LED from off->on, or on->off
static void toggleLed() {
digitalWrite(LED_PIN, digitalRead(LED_PIN) ^ HIGH);
}
SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> generatedSource(sineWave);
AudioRealFFT audioFFT;
StreamCopy fftCopier(audioFFT, generatedSource);
AsyncWebServer httpServer(80);
AsyncEventSource events("/events");
void initWiFi() {
Serial.print("\nConnecting to WiFi...");
WiFiClass::mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint8_t connectAttempts = 1;
while (WiFiClass::status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
if (connectAttempts++ % 10 == 0) {
Serial.print(" retrying...");
WiFi.reconnect();
}
}
Serial.println(" connected!");
WiFi.setSleep(WIFI_PS_NONE);
}
void initWebServer() {
Serial.print("Configuring HTTP server...");
httpServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(
200,
"text/html",
webpage
);
});
events.onConnect([](AsyncEventSourceClient *client) {
if (client->lastId()) {
Serial.printf("Client reconnected! Last message ID: %u\n", client->lastId());
}
//send event with message "hello!", id current millis, reconnect delay of 1 second
client->send("hello!", NULL, millis(), 1000);
});
httpServer.addHandler(&events);
Serial.print(" starting...");
httpServer.begin();
Serial.println(" started!");
}
void handleFftResult(AudioFFTBase &fft) {
String json = "[";
// skip the first bin, it's the fft total
for (int bin = 1; bin < fft.size(); bin++) {
auto mag = (long) fft.magnitude(bin);
auto freq = (int) fft.frequency(bin);
// send the bin label, and stored magnitude
if (bin > 1) { json += ", "; }
json += "{\"bin\":";
json += "\"" + String(freq) + "\"";
json += ",\"value\":";
json += String(mag);
json += "}";
}
json += "]";
events.send(json.c_str(), "fft", millis());
toggleLed();
delay(100);
yield();
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(DEVICE_SERIAL_BAUD);
initWiFi();
configTime(0, 0, "pool.ntp.org");
initWebServer();
// AudioLogger::instance().begin(Serial, AudioLogger::Info);
AudioBaseInfo baseAudioConf = AudioBaseInfo();
baseAudioConf.bits_per_sample = AUDIO_BITS_PER_SAMPLE;
baseAudioConf.channels = AUDIO_CHANNELS;
baseAudioConf.sample_rate = AUDIO_SAMPLE_RATE;
Serial.print("Starting FFT...");
auto fftConf = audioFFT.defaultConfig();
fftConf.copyFrom(baseAudioConf);
fftConf.length = AUDIO_FFT_LENGTH;
fftConf.callback = &handleFftResult;
// fftConf.channel_used = 1;
// fftConf.window_function = new BufferedWindow(new Hamming());
audioFFT.begin(fftConf);
Serial.println(" FFT started");
sineWave.begin(AUDIO_CHANNELS, AUDIO_SAMPLE_RATE, N_FS6);
}
void loop() {
fftCopier.copy();
yield();
} |
Beta Was this translation helpful? Give feedback.
-
Not sure what this is the result of the fft gives
|
Beta Was this translation helpful? Give feedback.
-
Here's another example with my MAX9814 kind of working... I ran the below code and played a 1900Hz tone through my speakers, and held the esp32's mic up against it While I could see a clear peak at ~1.9KHz, there's also another peak ~20KHz, mirrored on the central frequency. The mirrored peak is more apparent when I scrub through higher frequencies. I'm not sure why this is, maybe because there's no windowing? Changes:
#include <ESPAsyncWebServer.h>
#include <sstream>
#include "WiFiCredentials.h"
#include "AudioTools.h"
#include "AudioLibs/AudioRealFFT.h"
#include "AudioLibs/MemoryManager.h"
#include <ctime>
#include <cstdint>
static const long DEVICE_SERIAL_BAUD = 115200;
static const int LED_PIN = 2;
static const int AUDIO_CHANNELS = 1;
static const int AUDIO_SAMPLE_RATE = 44100;
static const int AUDIO_BITS_PER_SAMPLE = 16;
static const int AUDIO_FFT_LENGTH = 256;// *must* be power of 2
static const int AUDIO_FFT_BINS = AUDIO_FFT_LENGTH / 2;
static const char webpage[] PROGMEM = /* language=HTML */ R"=====(<html lang="en">
<!-- Adding a data chart using Chart.js -->
<!--https://github.com/squix78/esp32-mic-fft-->
<head>
<title>ESP32 Audio Visualiser</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js'></script>
</head>
<body onload="javascript:init()">
<div>
<canvas id="chart" width="600" height="400"></canvas>
</div>
<!-- Adding a websocket to the client (webpage) -->
<script>
let dataPlot;
const maxValue = 100000;
const maxLow = maxValue * 0.5;
const maxMedium = maxValue * 0.2;
const maxHigh = maxValue * 0.3;
function init() {
dataPlot = new Chart(document.getElementById("chart"), {
type: 'bar',
data: {
labels: [],
datasets: [{
data: [],
label: "Low",
backgroundColor: "#D6E9C6"
},
{
data: [],
label: "Moderate",
backgroundColor: "#FAEBCC"
},
{
data: [],
label: "High",
backgroundColor: "#EBCCD1"
},
]
},
options: {
responsive: false,
animation: false,
scales: {
xAxes: [{stacked: true}],
yAxes: [{
display: true,
stacked: true,
ticks: {
beginAtZero: true,
steps: 100,
stepValue: 50,
max: maxValue
}
}]
}
}
});
if (!!window.EventSource) {
const source = new EventSource('/events');
source.addEventListener('open', function (e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function (e) {
if (e.target.readyState !== EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('fft', function (e) {
dataPlot.data.labels = [];
dataPlot.data.datasets[0].data = [];
dataPlot.data.datasets[1].data = [];
dataPlot.data.datasets[2].data = [];
const data = JSON.parse(e.data);
data.forEach(function (element) {
const frequency = element[0];
const magnitude = element[1];
dataPlot.data.labels.push(frequency);
const lowValue = Math.min(maxLow, magnitude);
dataPlot.data.datasets[0].data.push(lowValue);
const mediumValue = Math.min(Math.max(0, magnitude - lowValue), maxMedium);
dataPlot.data.datasets[1].data.push(mediumValue);
const highValue = Math.max(0, magnitude - lowValue - mediumValue);
dataPlot.data.datasets[2].data.push(highValue);
});
dataPlot.update();
}, false);
}
}
</script>
</body>
</html>
)=====";
/// Toggle the LED from off->on, or on->off
static void toggleLed() {
digitalWrite(LED_PIN, digitalRead(LED_PIN) ^ HIGH);
}
AnalogAudioStream audioSource;
AudioRealFFT audioFFT;
StreamCopy fftCopier(audioFFT, audioSource);
AsyncWebServer httpServer(80);
AsyncEventSource events("/events");
void initWiFi() {
Serial.print("\nConnecting to WiFi...");
WiFiClass::mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
uint8_t connectAttempts = 1;
while (WiFiClass::status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
if (connectAttempts++ % 10 == 0) {
Serial.print(" retrying...");
WiFi.reconnect();
}
}
Serial.println(" connected!");
WiFi.setSleep(WIFI_PS_NONE);
}
void initWebServer() {
Serial.print("Configuring HTTP server...");
httpServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(
200,
"text/html",
webpage
);
});
events.onConnect([](AsyncEventSourceClient *client) {
if (client->lastId()) {
Serial.printf("Client reconnected! Last message ID: %u\n", client->lastId());
}
// send event with message "hello!", id current millis, reconnect delay of 1 second
client->send("hello!", NULL, millis(), 1000);
});
httpServer.addHandler(&events);
Serial.print(" starting...");
httpServer.begin();
Serial.println(" started!");
}
void handleFftResult(AudioFFTBase &fft) {
String json = "[";
// skip bin 0, it's sum of all fft bins
const int FIRST_BIN = 1;
auto minMagnitude = 0;
auto maxMagnitude = 0;
for (int bin = FIRST_BIN; bin < fft.size(); bin++) {
auto mag = (long) fft.magnitude(bin);
if (minMagnitude == 0 || mag < minMagnitude) {
minMagnitude = mag;
} else if (mag > maxMagnitude) {
maxMagnitude = mag;
}
}
for (int bin = FIRST_BIN; bin < fft.size(); bin++) {
auto freq = (int) fft.frequency(bin);
auto mag = ((long) fft.magnitude(bin)) - minMagnitude;
// send the frequency and magnitude
if (bin > FIRST_BIN) { json += ", "; }
json += "[" + String(freq) + "," + String(mag) + "]";
}
json += "]";
events.send(json.c_str(), "fft", millis());
toggleLed();
delay(100);
yield();
}
void setup() {
pinMode(LED_PIN, OUTPUT);
Serial.begin(DEVICE_SERIAL_BAUD);
initWiFi();
configTime(0, 0, "pool.ntp.org");
initWebServer();
// AudioLogger::instance().begin(Serial, AudioLogger::Info);
AudioBaseInfo baseAudioConf = AudioBaseInfo();
baseAudioConf.bits_per_sample = AUDIO_BITS_PER_SAMPLE;
baseAudioConf.channels = AUDIO_CHANNELS;
baseAudioConf.sample_rate = AUDIO_SAMPLE_RATE;
Serial.print("Starting input stream...");
auto audioSourceConf = audioSource.defaultConfig(RX_MODE);
audioSourceConf.copyFrom(baseAudioConf);
audioSourceConf.mode_internal = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN;
audioSourceConf.port_no = I2S_NUM_0;
audioSourceConf.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
audioSourceConf.use_apll = false;
audioSourceConf.buffer_count = 2;
audioSourceConf.buffer_size = 1024;
audioSourceConf.setInputPin1(GPIO_NUM_35);
audioSource.begin(audioSourceConf);
Serial.println(" Input stream started");
Serial.print("Starting FFT...");
auto fftConf = audioFFT.defaultConfig();
fftConf.copyFrom(baseAudioConf);
fftConf.length = AUDIO_FFT_LENGTH;
fftConf.callback = &handleFftResult;
// fftConf.channel_used = 1;
// fftConf.window_function = new BufferedWindow(new Hamming());
audioFFT.begin(fftConf);
Serial.println(" FFT started");
}
void loop() {
fftCopier.copy();
} |
Beta Was this translation helpful? Give feedback.
-
I would would have expected to see some increased amounts on any harmonics of the signal. |
Beta Was this translation helpful? Give feedback.
-
Thanks @pschatzmann! I've picked my project back up again with the newer version and it's working much better. Thanks for the fixes! However, I think I've found a minor issue with the FFT frequency computation. The arduino-audio-tools/src/AudioLibs/AudioFFT.h Lines 195 to 202 in 6303c74 I believe this is because it is using the FFT length. However, there are only length/2 bins. I can manually compute the frequency as follows: static const int AUDIO_SAMPLE_RATE = 44100;
static const int AUDIO_FFT_LENGTH = 4096; // ~10Hz per bin resolution
void handleFftResult(AudioFFTBase &fft) {
// skip bin 0, it's sum of all fft bins
const int FIRST_BIN = 1;
auto minMagnitude = fft.magnitude(FIRST_BIN);
auto maxMagnitude = minMagnitude;
int binWithMaxMagnitude = 0;
for (int bin = FIRST_BIN; bin < AUDIO_FFT_BINS; bin++) {
auto mag = fft.magnitude(bin);
if (mag > maxMagnitude) {
maxMagnitude = mag;
binWithMaxMagnitude = bin;
}
}
//float frequencyWithMaxMagnitude = fft.frequency(binWithMaxMagnitude);
float frequencyWithMaxMagnitude = (2.f * static_cast<float>(binWithMaxMagnitude) * static_cast<float>(AUDIO_SAMPLE_RATE)) / static_cast<float>(AUDIO_FFT_LENGTH);
Serial.print(" binWithMaxMagnitude:");
Serial.print(binWithMaxMagnitude);
Serial.print(", frequencyWithMaxMagnitude:");
Serial.println(frequencyWithMaxMagnitude);
} (I'm not sure if the This seems to work as expected. I could be completely wrong though and |
Beta Was this translation helpful? Give feedback.
Hmm, It seems there might be some bug in the window function: If you take it out you will get the expected result. Without it I am getting