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:
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user