feat(VoiceRecorder): native 44.1kHz recording, band-pass filter, amp mute, 3x boost

- Keep 44.1kHz native sample rate to avoid linear resampler alias noise
- MIC_BOOST 300%% (3x) with clipping, after filtering per user request
- Low-pass 0.25 (~2.5kHz) to remove MCLK high-pitch hiss
- High-pass 0.985 (~100Hz) to remove DC/rumble/pop
- Chain: raw -> high-pass -> low-pass -> boost
- Disable FM8002E speaker amp GPIO1 during recording (active-low) to eliminate pop/hiss, re-enable after
- Add esp_driver_gpio requirement for gpio_set_level
- Previous UI improvements (date+duration list, green highlight, volume slider, full-screen layout) included in history
This commit is contained in:
Adolfo
2026-07-31 12:10:42 -04:00
parent 1999d2c510
commit 709444d771
2 changed files with 47 additions and 11 deletions
+1 -1
View File
@@ -2,5 +2,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c)
idf_component_register( idf_component_register(
SRCS ${SOURCE_FILES} SRCS ${SOURCE_FILES}
REQUIRES TactilitySDK REQUIRES TactilitySDK esp_driver_gpio
) )
+46 -10
View File
@@ -20,18 +20,22 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "esp_log.h" #include "esp_log.h"
#include "driver/gpio.h"
#define TAG "VoiceRecorder" #define TAG "VoiceRecorder"
#define SAMPLE_RATE 16000 #define SAMPLE_RATE 44100
#define BITS_PER_SAMPLE 16 #define BITS_PER_SAMPLE 16
#define CHUNK_SAMPLES 1024 #define CHUNK_SAMPLES 1024
#define CHUNK_BYTES (CHUNK_SAMPLES * (BITS_PER_SAMPLE / 8)) // 2048 bytes #define CHUNK_BYTES (CHUNK_SAMPLES * (BITS_PER_SAMPLE / 8)) // 2048 bytes
#define AUDIO_BUF_SIZE 8192 #define AUDIO_BUF_SIZE 8192
// Software mic boost (digital gain) to compensate quiet MEMS mic even at 100% hw gain (24dB) // 3x boost + low-pass + high-pass (band-pass for voice)
// 200 = 2x, 300 = 3x – clipping protection via int16 limits #define MIC_BOOST_PERCENT 300
#define MIC_BOOST_PERCENT 250 #define ENABLE_LOWPASS 1
#define LOWPASS_ALPHA 0.25f // ~2.5kHz cutoff, reduces high-pitch MCLK hiss
#define ENABLE_HIGHPASS 1
#define HIGHPASS_ALPHA 0.985f // ~100Hz cutoff, removes DC/rumble/pop
// ─── Scroll animation flags ─── // ─── Scroll animation flags ───
// LVGL label scroll animation (LV_LABEL_LONG_SCROLL_*) causes continuous // LVGL label scroll animation (LV_LABEL_LONG_SCROLL_*) causes continuous
@@ -264,10 +268,15 @@ static void record_task(void* arg) {
error_t err = audio_stream_open_input(ctx->stream_dev, &in_cfg, &ctx->input_handle); error_t err = audio_stream_open_input(ctx->stream_dev, &in_cfg, &ctx->input_handle);
if (err == ERROR_NONE) { if (err == ERROR_NONE) {
// Ensure mic is unmuted and at max gain (ES8311 0..24dB) // Disable speaker amp (GPIO1 active-low) to avoid FM8002E pop/hiss during recording
// Board es3c35p/es3c28p: amp_enable on GPIO1 low = enabled
gpio_set_level(GPIO_NUM_1, 1);
// Also mute output codec path
audio_stream_set_mute(ctx->stream_dev, AUDIO_CODEC_DIR_OUTPUT, true);
// Ensure mic is unmuted and at max gain (42dB HW max)
audio_stream_set_mute(ctx->stream_dev, AUDIO_CODEC_DIR_INPUT, false); audio_stream_set_mute(ctx->stream_dev, AUDIO_CODEC_DIR_INPUT, false);
audio_stream_set_volume(ctx->stream_dev, AUDIO_CODEC_DIR_INPUT, 100.0f); audio_stream_set_volume(ctx->stream_dev, AUDIO_CODEC_DIR_INPUT, 100.0f);
ESP_LOGI(TAG, "Mic unmuted, gain 100%%"); ESP_LOGI(TAG, "Amp disabled (GPIO1 HIGH), mic unmuted 42dB");
} }
if (err != ERROR_NONE) { if (err != ERROR_NONE) {
ESP_LOGE(TAG, "audio_stream_open_input failed: %d", err); ESP_LOGE(TAG, "audio_stream_open_input failed: %d", err);
@@ -289,22 +298,45 @@ static void record_task(void* arg) {
uint32_t start_time = xTaskGetTickCount(); uint32_t start_time = xTaskGetTickCount();
uint32_t last_ui_sec = 0; uint32_t last_ui_sec = 0;
bool first_ui = true; bool first_ui = true;
#if ENABLE_LOWPASS
float lp_prev = 0.0f;
#endif
#if ENABLE_HIGHPASS
float hp_x_prev = 0.0f;
float hp_y_prev = 0.0f;
#endif
while (ctx->state == STATE_RECORDING) { while (ctx->state == STATE_RECORDING) {
size_t bytes_read = 0; size_t bytes_read = 0;
err = audio_stream_read(ctx->input_handle, ctx->audio_buf, AUDIO_BUF_SIZE, &bytes_read, pdMS_TO_TICKS(200)); err = audio_stream_read(ctx->input_handle, ctx->audio_buf, AUDIO_BUF_SIZE, &bytes_read, pdMS_TO_TICKS(200));
if (err == ERROR_NONE && bytes_read > 0) { if (err == ERROR_NONE && bytes_read > 0) {
// Software boost: 16-bit mono, apply MIC_BOOST_PERCENT with clipping
#if MIC_BOOST_PERCENT != 100
int16_t* samples = (int16_t*)ctx->audio_buf; int16_t* samples = (int16_t*)ctx->audio_buf;
size_t sample_cnt = bytes_read / sizeof(int16_t); size_t sample_cnt = bytes_read / sizeof(int16_t);
for (size_t i = 0; i < sample_cnt; i++) { for (size_t i = 0; i < sample_cnt; i++) {
int32_t boosted = (int32_t)samples[i] * MIC_BOOST_PERCENT / 100; float in = (float)samples[i];
#if ENABLE_HIGHPASS
// 1st-order high-pass ~100Hz to remove DC, rumble, pop
float hp_out = HIGHPASS_ALPHA * (hp_y_prev + in - hp_x_prev);
hp_x_prev = in;
hp_y_prev = hp_out;
in = hp_out;
#endif
#if ENABLE_LOWPASS
float lp_out = LOWPASS_ALPHA * in + (1.0f - LOWPASS_ALPHA) * lp_prev;
lp_prev = lp_out;
in = lp_out;
#endif
#if MIC_BOOST_PERCENT != 100
int32_t boosted = (int32_t)(in * MIC_BOOST_PERCENT / 100);
if (boosted > 32767) boosted = 32767; if (boosted > 32767) boosted = 32767;
if (boosted < -32768) boosted = -32768; if (boosted < -32768) boosted = -32768;
samples[i] = (int16_t)boosted; samples[i] = (int16_t)boosted;
} #else
if (in > 32767) in = 32767;
if (in < -32768) in = -32768;
samples[i] = (int16_t)in;
#endif #endif
}
size_t written = fwrite(ctx->audio_buf, 1, bytes_read, f); size_t written = fwrite(ctx->audio_buf, 1, bytes_read, f);
if (written != bytes_read) { if (written != bytes_read) {
ESP_LOGE(TAG, "SD write error: wrote %d of %d", (int)written, (int)bytes_read); ESP_LOGE(TAG, "SD write error: wrote %d of %d", (int)written, (int)bytes_read);
@@ -335,6 +367,10 @@ static void record_task(void* arg) {
audio_stream_close(ctx->input_handle); audio_stream_close(ctx->input_handle);
ctx->input_handle = NULL; ctx->input_handle = NULL;
// Re-enable speaker amp and unmute output after recording
audio_stream_set_mute(ctx->stream_dev, AUDIO_CODEC_DIR_OUTPUT, false);
gpio_set_level(GPIO_NUM_1, 0);
ESP_LOGI(TAG, "Recording done, amp re-enabled (GPIO1 LOW)");
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
write_wav_header(f, SAMPLE_RATE, BITS_PER_SAMPLE, 1, total_written); write_wav_header(f, SAMPLE_RATE, BITS_PER_SAMPLE, 1, total_written);