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
+46 -10
View File
@@ -20,18 +20,22 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/gpio.h"
#define TAG "VoiceRecorder"
#define SAMPLE_RATE 16000
#define SAMPLE_RATE 44100
#define BITS_PER_SAMPLE 16
#define CHUNK_SAMPLES 1024
#define CHUNK_BYTES (CHUNK_SAMPLES * (BITS_PER_SAMPLE / 8)) // 2048 bytes
#define AUDIO_BUF_SIZE 8192
// Software mic boost (digital gain) to compensate quiet MEMS mic even at 100% hw gain (24dB)
// 200 = 2x, 300 = 3x – clipping protection via int16 limits
#define MIC_BOOST_PERCENT 250
// 3x boost + low-pass + high-pass (band-pass for voice)
#define MIC_BOOST_PERCENT 300
#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 ───
// 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);
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_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) {
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 last_ui_sec = 0;
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) {
size_t bytes_read = 0;
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) {
// Software boost: 16-bit mono, apply MIC_BOOST_PERCENT with clipping
#if MIC_BOOST_PERCENT != 100
int16_t* samples = (int16_t*)ctx->audio_buf;
size_t sample_cnt = bytes_read / sizeof(int16_t);
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 < -32768) boosted = -32768;
samples[i] = (int16_t)boosted;
}
#else
if (in > 32767) in = 32767;
if (in < -32768) in = -32768;
samples[i] = (int16_t)in;
#endif
}
size_t written = fwrite(ctx->audio_buf, 1, bytes_read, f);
if (written != 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);
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);
write_wav_header(f, SAMPLE_RATE, BITS_PER_SAMPLE, 1, total_written);