feat(firmware): volume control goes to eleven
Rework the speaker volume from a 0-100 percentage to an 11-step level (0..11, mapped to the codec's percent), with mute that remembers the prior level so unmute restores it. Any non-silent change plays the gong as feedback; a new change cuts the in-flight gong off and restarts it rather than queueing another. The tap overlay shows the level number and 0..11 bar, and a "11" drifts up off the bar when you hit maximum. Also lands the device side of gateway volume commands: gw_client routes a "command" message to face_volume_command, which applies the change and shows a compact auto-hiding volume HUD (a centered bar) — the gateway half that sends these lives in a following commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKPbR6DY2JygHbyLjxm7Uu
This commit is contained in:
+55
-15
@@ -29,13 +29,20 @@ static const char *TAG = "audio";
|
||||
|
||||
#define GONG_SECONDS 5
|
||||
#define GONG_SAMPLES (RATE * GONG_SECONDS)
|
||||
#define GONG_VOLUME 75
|
||||
#define GONG_PEAK 14000.0f
|
||||
|
||||
/* Speaker volume is an 11-step level (0..AUDIO_VOL_MAX). This one goes to eleven. */
|
||||
#define VOL_DEFAULT_LEVEL 8 /* ~73%, matches the previous 75 default */
|
||||
|
||||
static esp_codec_dev_handle_t s_spk;
|
||||
static esp_codec_dev_handle_t s_mic;
|
||||
static int s_vol = GONG_VOLUME; /* speaker out volume 0..100 */
|
||||
static int s_level = VOL_DEFAULT_LEVEL; /* speaker volume, 0..AUDIO_VOL_MAX */
|
||||
static bool s_muted;
|
||||
static int level_to_pct(int level) { return level * 100 / AUDIO_VOL_MAX; }
|
||||
static int16_t *s_gong;
|
||||
#define GONG_CHUNK 1600 /* 100 ms — cut-off granularity for a restart */
|
||||
static volatile uint32_t s_gong_gen; /* bumped per request; a change mid-play restarts */
|
||||
static volatile bool s_gong_active;
|
||||
|
||||
static uint8_t *s_reply;
|
||||
static volatile size_t s_reply_len;
|
||||
@@ -153,7 +160,20 @@ static void audio_task(void *arg)
|
||||
* ourselves. The tail delay covers the DMA that plays after write()
|
||||
* returns, preventing a playback-boundary false wake. */
|
||||
if (job == JOB_GONG && s_gong != NULL) {
|
||||
esp_codec_dev_write(s_spk, s_gong, GONG_SAMPLES * sizeof(int16_t));
|
||||
/* Play in chunks so a new request (a fresh volume change) cuts the
|
||||
* current gong off and restarts, instead of queueing another 5 s. */
|
||||
s_playing = true;
|
||||
uint32_t gen;
|
||||
do {
|
||||
gen = s_gong_gen;
|
||||
for (size_t off = 0; off < GONG_SAMPLES && s_gong_gen == gen; off += GONG_CHUNK) {
|
||||
size_t n = GONG_SAMPLES - off;
|
||||
if (n > GONG_CHUNK) n = GONG_CHUNK;
|
||||
esp_codec_dev_write(s_spk, s_gong + off, n * sizeof(int16_t));
|
||||
}
|
||||
} while (s_gong_gen != gen); /* a new request arrived mid-play → restart */
|
||||
s_playing = false;
|
||||
s_gong_active = false;
|
||||
} else if (job == JOB_CHIME && s_chime != NULL) {
|
||||
s_playing = true;
|
||||
esp_codec_dev_write(s_spk, s_chime, CHIME_SAMPLES * sizeof(int16_t));
|
||||
@@ -277,7 +297,7 @@ void audio_init(void)
|
||||
};
|
||||
s_spk = bsp_audio_codec_speaker_init();
|
||||
if (s_spk != NULL) {
|
||||
esp_codec_dev_set_out_vol(s_spk, s_vol);
|
||||
esp_codec_dev_set_out_vol(s_spk, level_to_pct(s_level));
|
||||
esp_codec_dev_open(s_spk, &fs);
|
||||
}
|
||||
s_mic = bsp_audio_codec_microphone_init();
|
||||
@@ -304,8 +324,14 @@ void audio_play_chime(void)
|
||||
|
||||
void audio_play_gong(void)
|
||||
{
|
||||
job_t job = JOB_GONG;
|
||||
xQueueSend(s_jobs, &job, 0);
|
||||
s_gong_gen++; /* (re)start; cuts any in-flight gong */
|
||||
if (!s_gong_active) {
|
||||
s_gong_active = true;
|
||||
job_t job = JOB_GONG;
|
||||
if (xQueueSend(s_jobs, &job, 0) != pdTRUE) {
|
||||
s_gong_active = false; /* queue full — nothing will run it */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Streaming = the detect_task forwards AFE audio upstream. Wake word and touch
|
||||
@@ -350,20 +376,34 @@ bool audio_is_playing(void)
|
||||
return s_playing;
|
||||
}
|
||||
|
||||
void audio_volume_set(int vol)
|
||||
void audio_level_set(int level)
|
||||
{
|
||||
if (vol < 0) {
|
||||
vol = 0;
|
||||
} else if (vol > 100) {
|
||||
vol = 100;
|
||||
if (level < 0) {
|
||||
level = 0;
|
||||
} else if (level > AUDIO_VOL_MAX) {
|
||||
level = AUDIO_VOL_MAX;
|
||||
}
|
||||
s_vol = vol;
|
||||
s_level = level;
|
||||
s_muted = false; /* setting a level unmutes */
|
||||
if (s_spk != NULL) {
|
||||
esp_codec_dev_set_out_vol(s_spk, s_vol);
|
||||
esp_codec_dev_set_out_vol(s_spk, level_to_pct(s_level));
|
||||
}
|
||||
}
|
||||
|
||||
int audio_volume_get(void)
|
||||
int audio_level_get(void)
|
||||
{
|
||||
return s_vol;
|
||||
return s_level;
|
||||
}
|
||||
|
||||
void audio_mute(bool on)
|
||||
{
|
||||
s_muted = on; /* gate output; s_level is preserved */
|
||||
if (s_spk != NULL) {
|
||||
esp_codec_dev_set_out_vol(s_spk, on ? 0 : level_to_pct(s_level));
|
||||
}
|
||||
}
|
||||
|
||||
bool audio_is_muted(void)
|
||||
{
|
||||
return s_muted;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user