From 03b86b62926b2fee84a475b6d58fd3c9919eb669 Mon Sep 17 00:00:00 2001 From: Serge Bazanski <q3k@q3k.org> Date: Mon, 26 Jun 2023 01:41:30 +0200 Subject: [PATCH] st3m/audio: clean up 1. Add locking (st3m_audio_* functions are now thread/task-safe). 2. Remove some duplicate code around speaker/headphones output. 3. Simplify applying audio volume to codec ('apply' function). 4. Small style stuff (bool, etc). --- components/st3m/st3m_audio.c | 677 +++++++++++++++++++++-------------- components/st3m/st3m_audio.h | 56 +-- 2 files changed, 437 insertions(+), 296 deletions(-) diff --git a/components/st3m/st3m_audio.c b/components/st3m/st3m_audio.c index d59029cf13..8818a70ebd 100644 --- a/components/st3m/st3m_audio.c +++ b/components/st3m/st3m_audio.c @@ -8,14 +8,15 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/queue.h" +#include "freertos/semphr.h" #include "esp_log.h" static const char *TAG = "st3m-audio"; #define TIMEOUT_MS 1000 -static void st3m_audio_player_task(void* arg); +static void _audio_player_task(void* arg); +static bool _headphones_connected(void); // used for exp(vol_dB * NAT_LOG_DB) #define NAT_LOG_DB 0.1151292546497023 @@ -23,332 +24,260 @@ static void st3m_audio_player_task(void* arg); // placeholder for "fake mute" -inf dB (we know floats can do that but we have trust issues when using NAN) #define SILLY_LOW_VOLUME_DB (-10000.) -static bool headphones_connected = 0; -static bool headset_connected = 0; -static bool line_in_connected = 0; -static bool headphones_detection_override = 0; -static int32_t software_volume = 0; - -// maybe struct these someday but eh it works -static float headphones_volume_dB = 0; -static bool headphones_mute = 0; const static float headphones_maximum_volume_system_dB = 3; -static float headphones_maximum_volume_user_dB = headphones_maximum_volume_system_dB; -static float headphones_minimum_volume_user_dB = headphones_maximum_volume_system_dB - 70; - -static float speaker_volume_dB = 0; -static bool speaker_mute = 0; const static float speaker_maximum_volume_system_dB = 14; -static float speaker_maximum_volume_user_dB = speaker_maximum_volume_system_dB; -static float speaker_minimum_volume_user_dB = speaker_maximum_volume_system_dB - 60; - -static st3m_audio_input_source_t input_source = st3m_audio_input_source_none; -static uint8_t headset_gain = 0; - -static int32_t input_thru_vol; -static int32_t input_thru_vol_int; -static int32_t input_thru_mute = 1; - -uint8_t st3m_audio_headset_is_connected(){ return headset_connected; } -uint8_t st3m_audio_headphones_are_connected(){ return headphones_connected || headphones_detection_override; } -float st3m_audio_headphones_get_volume_dB(){ return headphones_volume_dB; } -float st3m_audio_speaker_get_volume_dB(){ return speaker_volume_dB; } -float st3m_audio_headphones_get_minimum_volume_dB(){ return headphones_minimum_volume_user_dB; } -float st3m_audio_speaker_get_minimum_volume_dB(){ return speaker_minimum_volume_user_dB; } -float st3m_audio_headphones_get_maximum_volume_dB(){ return headphones_maximum_volume_user_dB; } -float st3m_audio_speaker_get_maximum_volume_dB(){ return speaker_maximum_volume_user_dB; } -uint8_t st3m_audio_headphones_get_mute(){ return headphones_mute ? 1 : 0; } -uint8_t st3m_audio_speaker_get_mute(){ return speaker_mute ? 1 : 0; } -uint8_t st3m_audio_input_get_source(){ return input_source; } -uint8_t st3m_audio_headset_get_gain_dB(){ return headset_gain; } - - -static void _audio_headphones_set_volume_dB(float vol_dB, bool mute){ - float hardware_volume_dB = flow3r_bsp_audio_headphones_set_volume(mute, vol_dB); - // note: didn't check if chan physically mapped to l/r or flipped. - - // do the fine steps in software - // note: synchronizing both hw and software volume changes is somewhat tricky - float software_volume_dB = vol_dB - hardware_volume_dB; - if(software_volume_dB > 0) software_volume_dB = 0; - //if(!software_volume_enabled) software_volume_dB = 0; // breaks p1, might add option once it is retired - software_volume = (int32_t) (32768 * exp(software_volume_dB * NAT_LOG_DB)); - headphones_volume_dB = hardware_volume_dB + software_volume_dB; -} - -static void _audio_speaker_set_volume_dB(float vol_dB, bool mute){ - float hardware_volume_dB = flow3r_bsp_audio_speaker_set_volume(mute, vol_dB); - //note: didn't check if chan physically mapped to l/r or flipped. - - // do the fine steps in software - // note: synchronizing both hw and software volume changes is somewhat tricky - float software_volume_dB = vol_dB - hardware_volume_dB; - if(software_volume_dB > 0) software_volume_dB = 0; - //if(!software_volume_enabled) software_volume_dB = 0; // breaks p1, might add option once it is retired - software_volume = (int32_t) (32768. * exp(software_volume_dB * NAT_LOG_DB)); - speaker_volume_dB = hardware_volume_dB + software_volume_dB; -} -void st3m_audio_headphones_set_mute(uint8_t mute){ - headphones_mute = mute; - st3m_audio_headphones_set_volume_dB(headphones_volume_dB); -} - -void st3m_audio_speaker_set_mute(uint8_t mute){ - speaker_mute = mute; - st3m_audio_speaker_set_volume_dB(speaker_volume_dB); -} +// Output, either speakers or headphones. Holds volume/mute state and limits, +// and calculated software volume. +// +// An output's apply function configures the actual physical output, ie. by +// programming the codec. +typedef struct st3m_audio_output { + float volume; + float volume_max; + float volume_min; + float volume_max_system; + int32_t volume_software; + bool mute; -uint8_t st3m_audio_headset_set_gain_dB(uint8_t gain_dB){ - flow3r_bsp_audio_headset_set_gain_dB(gain_dB); - headset_gain = gain_dB; - return headset_gain; -} - -void st3m_audio_input_set_source(st3m_audio_input_source_t source) { - switch (source) { - case st3m_audio_input_source_none: - flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_none); - break; - case st3m_audio_input_source_line_in: - flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_line_in); - break; - case st3m_audio_input_source_headset_mic: - flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_headset_mic); - break; - case st3m_audio_input_source_onboard_mic: - flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_onboard_mic); - break; - } - input_source = source; -} + void (*apply)(struct st3m_audio_output *out); +} st3m_audio_output_t; -void st3m_audio_headphones_line_in_set_hardware_thru(bool enable) { - flow3r_bsp_audio_headphones_line_in_set_hardware_thru(enable); -} +// All _output_* functions are not thread-safe, access must be synchronized via +// locks. -void st3m_audio_speaker_line_in_set_hardware_thru(bool enable) { - flow3r_bsp_audio_speaker_line_in_set_hardware_thru(enable); +// Apply output settings to actual output channel, calculate software volume if +// output is active. +static void _output_apply(st3m_audio_output_t *out) { + out->apply(out); } -void st3m_audio_line_in_set_hardware_thru(bool enable) { - flow3r_bsp_audio_line_in_set_hardware_thru(enable); +static void _output_set_mute(st3m_audio_output_t *out, bool mute) { + out->mute = mute; + _output_apply(out); } -float st3m_audio_speaker_set_volume_dB(float vol_dB){ - bool mute = speaker_mute || st3m_audio_headphones_are_connected(); - if(vol_dB > speaker_maximum_volume_user_dB) vol_dB = speaker_maximum_volume_user_dB; - if(vol_dB < speaker_minimum_volume_user_dB){ - vol_dB = SILLY_LOW_VOLUME_DB; // fake mute - mute = 1; +static float _output_set_volume(st3m_audio_output_t *out, float vol_dB) { + if (vol_dB > out->volume_max) { + vol_dB = out->volume_max; } - _audio_speaker_set_volume_dB(vol_dB, mute); - return speaker_volume_dB; -} - -float st3m_audio_headphones_set_volume_dB(float vol_dB){ - bool mute = headphones_mute || (!st3m_audio_headphones_are_connected()); - if(vol_dB > headphones_maximum_volume_user_dB) vol_dB = headphones_maximum_volume_user_dB; - if(vol_dB < headphones_minimum_volume_user_dB){ - vol_dB = SILLY_LOW_VOLUME_DB; // fake mute - mute = 1; + if (vol_dB < out->volume_min) { + vol_dB = SILLY_LOW_VOLUME_DB; + out->mute = true; } - _audio_headphones_set_volume_dB(vol_dB, mute); - return headphones_volume_dB; -} - -void st3m_audio_headphones_detection_override(uint8_t enable){ - headphones_detection_override = enable; - st3m_audio_headphones_set_volume_dB(headphones_volume_dB); - st3m_audio_speaker_set_volume_dB(speaker_volume_dB); + out->volume = vol_dB; + _output_apply(out); + return vol_dB; } -float st3m_audio_headphones_adjust_volume_dB(float vol_dB){ - if(st3m_audio_headphones_get_volume_dB() < headphones_minimum_volume_user_dB){ //fake mute - if(vol_dB > 0){ - return st3m_audio_headphones_set_volume_dB(headphones_minimum_volume_user_dB); - } else { - return st3m_audio_headphones_get_volume_dB(); +static float _output_adjust_volume(st3m_audio_output_t *out, float vol_dB) { + if (out->volume < out->volume_min) { + if (vol_dB > 0) { + _output_set_volume(out, out->volume_min); } - } else { - return st3m_audio_headphones_set_volume_dB(headphones_volume_dB + vol_dB); + } else { + _output_set_volume(out, out->volume + vol_dB); } + return out->volume; } -float st3m_audio_speaker_adjust_volume_dB(float vol_dB){ - if(st3m_audio_speaker_get_volume_dB() < speaker_minimum_volume_user_dB){ //fake mute - if(vol_dB > 0){ - return st3m_audio_speaker_set_volume_dB(speaker_minimum_volume_user_dB); - } else { - return st3m_audio_speaker_get_volume_dB(); - } - } else { - return st3m_audio_speaker_set_volume_dB(speaker_volume_dB + vol_dB); +static float _output_set_maximum_volume(st3m_audio_output_t *out, float vol_dB) { + if (vol_dB > out->volume_max_system) { + vol_dB = out->volume_max_system; } -} - -float st3m_audio_adjust_volume_dB(float vol_dB){ - if(st3m_audio_headphones_are_connected()){ - return st3m_audio_headphones_adjust_volume_dB(vol_dB); - } else { - return st3m_audio_speaker_adjust_volume_dB(vol_dB); + if (vol_dB < out->volume_min) { + vol_dB = out->volume_min; } -} - -float st3m_audio_set_volume_dB(float vol_dB){ - if(st3m_audio_headphones_are_connected()){ - return st3m_audio_headphones_set_volume_dB(vol_dB); - } else { - return st3m_audio_speaker_set_volume_dB(vol_dB); + out->volume_max = vol_dB; + if (out->volume > out->volume_max) { + out->volume = out->volume_max; } + _output_apply(out); + return vol_dB; } -float st3m_audio_get_volume_dB(){ - if(st3m_audio_headphones_are_connected()){ - return st3m_audio_headphones_get_volume_dB(); - } else { - return st3m_audio_speaker_get_volume_dB(); +static float _output_set_minimum_volume(st3m_audio_output_t *out, float vol_dB) { + if (vol_dB > out->volume_max) { + vol_dB = out->volume_max; } -} - -void st3m_audio_set_mute(uint8_t mute){ - if(st3m_audio_headphones_are_connected()){ - st3m_audio_headphones_set_mute(mute); - } else { - st3m_audio_speaker_set_mute(mute); + if (vol_dB+1 < SILLY_LOW_VOLUME_DB) { + vol_dB = SILLY_LOW_VOLUME_DB + 1.; } -} - -uint8_t st3m_audio_get_mute(){ - if(st3m_audio_headphones_are_connected()){ - return st3m_audio_headphones_get_mute(); - } else { - return st3m_audio_speaker_get_mute(); + out->volume_min = vol_dB; + if (out->volume < out->volume_min) { + out->volume = out->volume_min; + } + _output_apply(out); + return vol_dB; +} + +static float _output_get_volume_relative(st3m_audio_output_t *out) { + float ret = out->volume; + // fake mute + if(ret < out->volume_min) return 0; + float vol_range = out->volume_max - out->volume_min; + // shift to above zero + ret -= out->volume_min; + // restrict to 0..1 range + ret /= vol_range; + // shift to 0.01 to 0.99 range to distinguish from fake mute + return (ret*0.99) + 0.01; +} + +// Output apply function for headphones. +static void _audio_headphones_apply(st3m_audio_output_t *out){ + bool mute = out->mute; + float vol_dB = out->volume; + + bool headphones = _headphones_connected(); + if (!headphones) { + mute = true; } -} - -float st3m_audio_headphones_set_maximum_volume_dB(float vol_dB){ - if(vol_dB > headphones_maximum_volume_system_dB) vol_dB = headphones_maximum_volume_system_dB; - if(vol_dB < headphones_minimum_volume_user_dB) vol_dB = headphones_minimum_volume_user_dB; - headphones_maximum_volume_user_dB = vol_dB; - return headphones_maximum_volume_user_dB; -} - -float st3m_audio_headphones_set_minimum_volume_dB(float vol_dB){ - if(vol_dB > headphones_maximum_volume_user_dB) vol_dB = headphones_maximum_volume_user_dB; - if((vol_dB + 1) < SILLY_LOW_VOLUME_DB) vol_dB = SILLY_LOW_VOLUME_DB + 1.; - headphones_minimum_volume_user_dB = vol_dB; - return headphones_minimum_volume_user_dB; -} - -float st3m_audio_speaker_set_maximum_volume_dB(float vol_dB){ - if(vol_dB > speaker_maximum_volume_system_dB) vol_dB = speaker_maximum_volume_system_dB; - if(vol_dB < speaker_minimum_volume_user_dB) vol_dB = speaker_minimum_volume_user_dB; - speaker_maximum_volume_user_dB = vol_dB; - return speaker_maximum_volume_user_dB; -} -float st3m_audio_speaker_set_minimum_volume_dB(float vol_dB){ - if(vol_dB > speaker_maximum_volume_user_dB) vol_dB = speaker_maximum_volume_user_dB; - if((vol_dB + 1) < SILLY_LOW_VOLUME_DB) vol_dB = SILLY_LOW_VOLUME_DB + 1.; - speaker_minimum_volume_user_dB = vol_dB; - return speaker_minimum_volume_user_dB; -} + float hardware_volume_dB = flow3r_bsp_audio_headphones_set_volume(mute, vol_dB); -float st3m_audio_speaker_get_volume_relative(){ - float ret = st3m_audio_speaker_get_volume_dB(); - if(ret < speaker_minimum_volume_user_dB) return 0; // fake mute - float vol_range = speaker_maximum_volume_user_dB - speaker_minimum_volume_user_dB; - ret -= speaker_minimum_volume_user_dB; // shift to above zero - ret /= vol_range; // restrict to 0..1 range - return (ret*0.99) + 0.01; // shift to 0.01 to 0.99 range to distinguish from fake mute + // do the fine steps in software + // note: synchronizing both hw and software volume changes is somewhat tricky + float software_volume_dB = vol_dB - hardware_volume_dB; + if(software_volume_dB > 0) software_volume_dB = 0; + out->volume_software = (int32_t) (32768 * exp(software_volume_dB * NAT_LOG_DB)); } -float st3m_audio_headphones_get_volume_relative(){ - float ret = st3m_audio_headphones_get_volume_dB(); - if(ret < headphones_minimum_volume_user_dB) return 0; // fake mute - float vol_range = headphones_maximum_volume_user_dB - headphones_minimum_volume_user_dB; - ret -= headphones_minimum_volume_user_dB; // shift to above zero - ret /= vol_range; // restrict to 0..1 range - return (ret*0.99) + 0.01; // shift to 0.01 to 0.99 range to distinguish from fake mute -} +// Output apply function for speaker. +static void _audio_speaker_apply(st3m_audio_output_t *out){ + bool mute = out->mute; + float vol_dB = out->volume; -float st3m_audio_get_volume_relative(){ - if(st3m_audio_headphones_are_connected()){ - return st3m_audio_headphones_get_volume_relative(); - } else { - return st3m_audio_speaker_get_volume_relative(); + bool headphones = _headphones_connected(); + if (headphones) { + mute = true; } -} -void st3m_audio_update_jacksense(){ - static uint8_t jck_prev = 255; // unreachable value -> initial comparision always untrue + float hardware_volume_dB = flow3r_bsp_audio_speaker_set_volume(mute, vol_dB); + // do the fine steps in software + // note: synchronizing both hw and software volume changes is somewhat tricky + float software_volume_dB = vol_dB - hardware_volume_dB; + if(software_volume_dB > 0) software_volume_dB = 0; + out->volume_software = (int32_t) (32768. * exp(software_volume_dB * NAT_LOG_DB)); +} + +// Global state structure. Guarded by state_mutex. +typedef struct { + flow3r_bsp_audio_jacksense_state_t jacksense; + + // True if system should pretend headphones are plugged in. + bool headphones_detection_override; + + // The two output channels. + st3m_audio_output_t headphones; + st3m_audio_output_t speaker; + + // Denormalized setting data that can be read back by user. + st3m_audio_input_source_t source; + uint8_t headset_gain; + + // Software-based audio pipe settings. + int32_t input_thru_vol; + int32_t input_thru_vol_int; + bool input_thru_mute; + + // Main player function callback. + st3m_audio_player_function_t function; +} st3m_audio_state_t; + +SemaphoreHandle_t state_mutex; +#define LOCK xSemaphoreTakeRecursive(state_mutex, portMAX_DELAY) +#define UNLOCK xSemaphoreGiveRecursive(state_mutex) + +static st3m_audio_state_t state = { + .jacksense = { + .headphones = false, + .headset = false, + .line_in = false, + }, + .headphones_detection_override = false, + .headphones = { + .volume = 0, + .mute = false, + .volume_max = headphones_maximum_volume_system_dB, + .volume_min = headphones_maximum_volume_system_dB - 70, + .volume_max_system = headphones_maximum_volume_system_dB, + .apply = _audio_headphones_apply, + }, + .speaker = { + .volume = 0, + .mute = false, + .volume_max = speaker_maximum_volume_system_dB, + .volume_min = speaker_maximum_volume_system_dB - 60, + .volume_max_system = speaker_maximum_volume_system_dB, + .apply = _audio_speaker_apply, + }, + + .source = st3m_audio_input_source_none, + .headset_gain = 0, + .input_thru_vol = 0, + .input_thru_vol_int = 0, + .input_thru_mute = true, + .function = st3m_audio_player_function_dummy, +}; + +// Returns whether we should be outputting audio through headphones. If not, +// audio should be output via speaker. +// +// Lock must be taken. +static bool _headphones_connected(void) { + return state.jacksense.headphones || state.headphones_detection_override; +} + +void st3m_audio_update_jacksense() { flow3r_bsp_audio_jacksense_state_t st; flow3r_bsp_audio_read_jacksense(&st); - headphones_connected = st.headphones; - headset_connected = st.headset; - line_in_connected = st.line_in; - // Update volume to trigger mutes if needed. But only do that if the // jacks actually changed. - uint8_t jck = (st.headphones ? 1 : 0) | (st.headset ? 2 : 0) | (st.line_in ? 4 : 0); - if(jck != jck_prev){ - st3m_audio_speaker_set_volume_dB(speaker_volume_dB); - st3m_audio_headphones_set_volume_dB(headphones_volume_dB); + LOCK; + if (memcmp(&state.jacksense, &st, sizeof(flow3r_bsp_audio_jacksense_state_t)) != 0) { + memcpy(&state.jacksense, &st, sizeof(flow3r_bsp_audio_jacksense_state_t)); + _output_apply(&state.speaker); + _output_apply(&state.headphones); } - jck_prev = jck; + UNLOCK; } - void st3m_audio_player_function_dummy(int16_t * rx, int16_t * tx, uint16_t len){ for(uint16_t i = 0; i < len; i++){ tx[i] = 0; } } -static st3m_audio_player_function_t st3m_audio_player_function = st3m_audio_player_function_dummy; - -void st3m_audio_set_player_function(st3m_audio_player_function_t fun){ - // ...wonder how unsafe this is - st3m_audio_player_function = fun; -} - void st3m_audio_init(void) { - // TODO: this assumes I2C is already initialized + state_mutex = xSemaphoreCreateRecursiveMutex(); + assert(state_mutex != NULL); + state.function = st3m_audio_player_function_dummy; flow3r_bsp_audio_init(); - // mute is on by default - st3m_audio_input_thru_set_volume_dB(-20); + st3m_audio_input_thru_set_volume_dB(-20); st3m_audio_update_jacksense(); - TaskHandle_t handle; - xTaskCreate(&st3m_audio_player_task, "audio", 3000, NULL, configMAX_PRIORITIES - 1, &handle); - st3m_audio_player_function = st3m_audio_player_function_dummy; -} + _output_apply(&state.speaker); + _output_apply(&state.headphones); -float st3m_audio_input_thru_set_volume_dB(float vol_dB){ - if(vol_dB > 0) vol_dB = 0; - input_thru_vol_int = (int32_t) (32768. * exp(vol_dB * NAT_LOG_DB)); - input_thru_vol = vol_dB; - return input_thru_vol; + TaskHandle_t handle; + xTaskCreate(&_audio_player_task, "audio", 3000, NULL, configMAX_PRIORITIES - 1, &handle); + ESP_LOGI(TAG, "Audio task started"); } -float st3m_audio_input_thru_get_volume_dB(){ return input_thru_vol; } -void st3m_audio_input_thru_set_mute(bool mute){ input_thru_mute = mute; } -bool st3m_audio_input_thru_get_mute(){ return input_thru_mute; } - -static void st3m_audio_player_task(void* arg) { +static void _audio_player_task(void *arg) { int16_t buffer_tx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2]; int16_t buffer_rx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2]; memset(buffer_tx, 0, sizeof(buffer_tx)); memset(buffer_rx, 0, sizeof(buffer_rx)); size_t count; - while(true) { - count = 0; + bool hwmute = flow3r_bsp_audio_has_hardware_mute(); + while(true) { count = 0; flow3r_bsp_audio_read(buffer_rx, sizeof(buffer_rx), &count, 1000); if (count != sizeof(buffer_rx)) { @@ -356,10 +285,18 @@ static void st3m_audio_player_task(void* arg) { abort(); } - (*st3m_audio_player_function)(buffer_rx, buffer_tx, FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE*2); + LOCK; + bool headphones = _headphones_connected(); + st3m_audio_player_function_t function = state.function; + int32_t software_volume = headphones ? state.headphones.volume_software : state.speaker.volume_software; + bool software_mute = headphones ? state.headphones.mute : state.speaker.mute; + bool input_thru_mute = state.input_thru_mute; + int32_t input_thru_vol_int = state.input_thru_vol_int; + UNLOCK; + + (*function)(buffer_rx, buffer_tx, FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE*2); - bool hwmute = flow3r_bsp_audio_has_hardware_mute(); - if (!hwmute && !(!speaker_mute || !headphones_mute)) { + if (!hwmute && software_mute) { // Software muting needed. Only used on P1. for(int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2); i += 2){ buffer_tx[i] = 0; @@ -385,3 +322,207 @@ static void st3m_audio_player_task(void* arg) { } } + +// BSP wrappers that don't need locking. + +void st3m_audio_headphones_line_in_set_hardware_thru(bool enable) { + flow3r_bsp_audio_headphones_line_in_set_hardware_thru(enable); +} + +void st3m_audio_speaker_line_in_set_hardware_thru(bool enable) { + flow3r_bsp_audio_speaker_line_in_set_hardware_thru(enable); +} + +void st3m_audio_line_in_set_hardware_thru(bool enable) { + flow3r_bsp_audio_line_in_set_hardware_thru(enable); +} + +// Locked global state getters. + +#define GETTER(ty, name, accessor) ty st3m_##name(void) {\ + LOCK; \ + ty res = accessor; \ + UNLOCK; \ + return res; \ + } + +GETTER(bool, audio_headset_is_connected, state.jacksense.headset) +GETTER(bool, audio_headphones_is_connected, state.jacksense.headphones || state.headphones_detection_override) +GETTER(float, audio_headphones_get_volume_dB, state.headphones.volume) +GETTER(float, audio_speaker_get_volume_dB, state.speaker.volume) +GETTER(float, audio_headphones_get_minimum_volume_dB, state.headphones.volume_min) +GETTER(float, audio_speaker_get_minimum_volume_dB, state.speaker.volume_min) +GETTER(float, audio_headphones_get_maximum_volume_dB, state.headphones.volume_max) +GETTER(float, audio_speaker_get_maximum_volume_dB, state.speaker.volume_max) +GETTER(bool, audio_headphones_get_mute, state.headphones.mute) +GETTER(bool, audio_speaker_get_mute, state.speaker.mute) +GETTER(st3m_audio_input_source_t, audio_input_get_source, state.source) +GETTER(uint8_t, audio_headset_get_gain_dB, state.headset_gain) +GETTER(float, audio_input_thru_get_volume_dB, state.input_thru_vol) +GETTER(bool, audio_input_thru_get_mute, state.input_thru_mute) +#undef GETTER + + +// Locked global API functions. + +uint8_t st3m_audio_headset_set_gain_dB(uint8_t gain_dB){ + flow3r_bsp_audio_headset_set_gain_dB(gain_dB); + LOCK; + state.headset_gain = gain_dB; + UNLOCK; + return gain_dB; +} + +void st3m_audio_input_set_source(st3m_audio_input_source_t source) { + switch (source) { + case st3m_audio_input_source_none: + flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_none); + break; + case st3m_audio_input_source_line_in: + flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_line_in); + break; + case st3m_audio_input_source_headset_mic: + flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_headset_mic); + break; + case st3m_audio_input_source_onboard_mic: + flow3r_bsp_audio_input_set_source(flow3r_bsp_audio_input_source_onboard_mic); + break; + } + LOCK; + state.source = source; + UNLOCK; +} + +void st3m_audio_input_thru_set_mute(bool mute) { + LOCK; + state.input_thru_mute = mute; + UNLOCK; +} + +float st3m_audio_input_thru_set_volume_dB(float vol_dB){ + if(vol_dB > 0) vol_dB = 0; + LOCK; + state.input_thru_vol_int = (int32_t) (32768. * exp(vol_dB * NAT_LOG_DB)); + state.input_thru_vol = vol_dB; + UNLOCK; + return vol_dB; +} + +void st3m_audio_set_player_function(st3m_audio_player_function_t fun){ + LOCK; + state.function = fun; + UNLOCK; +} + +bool st3m_audio_headphones_are_connected(void) { + LOCK; + bool res = _headphones_connected(); + UNLOCK; + return res; +} + +// Locked output getters/setters. + +#define LOCKED0(body) LOCK; body; UNLOCK +#define LOCKED(ty, body) LOCK; ty res = body; UNLOCK; return res + +void st3m_audio_headphones_set_mute(bool mute){ + LOCKED0(_output_set_mute(&state.headphones, mute)); +} + +void st3m_audio_speaker_set_mute(bool mute){ + LOCKED0(_output_set_mute(&state.speaker, mute)); +} + +float st3m_audio_speaker_set_volume_dB(float vol_dB){ + LOCKED(float, _output_set_volume(&state.speaker, vol_dB)); +} + +float st3m_audio_headphones_set_volume_dB(float vol_dB){ + LOCKED(float, _output_set_volume(&state.headphones, vol_dB)); +} + +void st3m_audio_headphones_detection_override(bool enable){ + LOCK; + state.headphones_detection_override = enable; + _output_apply(&state.headphones); + _output_apply(&state.speaker); + UNLOCK; +} + +float st3m_audio_headphones_adjust_volume_dB(float vol_dB){ + LOCKED(float, _output_adjust_volume(&state.headphones, vol_dB)); +} + +float st3m_audio_speaker_adjust_volume_dB(float vol_dB){ + LOCKED(float, _output_adjust_volume(&state.speaker, vol_dB)); +} + +float st3m_audio_headphones_set_maximum_volume_dB(float vol_dB){ + LOCKED(float, _output_set_maximum_volume(&state.headphones, vol_dB)); +} + +float st3m_audio_headphones_set_minimum_volume_dB(float vol_dB){ + LOCKED(float, _output_set_minimum_volume(&state.headphones, vol_dB)); +} + +float st3m_audio_speaker_set_maximum_volume_dB(float vol_dB){ + LOCKED(float, _output_set_maximum_volume(&state.speaker, vol_dB)); +} + +float st3m_audio_speaker_set_minimum_volume_dB(float vol_dB){ + LOCKED(float, _output_set_minimum_volume(&state.speaker, vol_dB)); +} + +float st3m_audio_speaker_get_volume_relative(void) { + LOCKED(float, _output_get_volume_relative(&state.speaker)); +} + +float st3m_audio_headphones_get_volume_relative(){ + LOCKED(float, _output_get_volume_relative(&state.headphones)); +} + +// Automatic output detection wrappers. We need a recursive mutex here to make +// sure we don't race between output detection and applying the function to the +// current output. + +#define DISPATCH_TY_TY(ty, ty2, name) ty st3m_audio_##name(ty2 arg) { \ + ty res; \ + LOCK; \ + if (_headphones_connected()) { \ + res = st3m_audio_headphones_##name(arg); \ + } else { \ + res = st3m_audio_speaker_##name(arg); \ + } \ + UNLOCK; \ + return res; \ + } + +#define DISPATCH_TY_VOID(ty, name) ty st3m_audio_##name(void) { \ + ty res; \ + LOCK; \ + if (_headphones_connected()) { \ + res = st3m_audio_headphones_##name(); \ + } else { \ + res = st3m_audio_speaker_##name(); \ + } \ + UNLOCK; \ + return res; \ + } + +#define DISPATCH_VOID_TY(ty, name) void st3m_audio_##name(ty arg) { \ + LOCK; \ + if (_headphones_connected()) { \ + st3m_audio_headphones_##name(arg); \ + } else { \ + st3m_audio_speaker_##name(arg); \ + } \ + UNLOCK; \ + } + +DISPATCH_TY_TY(float, float, adjust_volume_dB) +DISPATCH_TY_TY(float, float, set_volume_dB) +DISPATCH_TY_VOID(float, get_volume_dB) +DISPATCH_VOID_TY(bool, set_mute) +DISPATCH_TY_VOID(bool, get_mute) +DISPATCH_TY_VOID(float, get_volume_relative) \ No newline at end of file diff --git a/components/st3m/st3m_audio.h b/components/st3m/st3m_audio.h index 5392a299ec..b3216391c5 100644 --- a/components/st3m/st3m_audio.h +++ b/components/st3m/st3m_audio.h @@ -30,7 +30,7 @@ void st3m_audio_player_function_dummy(int16_t * rx, int16_t * tx, uint16_t len); /* Initializes I2S bus, the audio task and required data structures. * Expects an initialized I2C bus, will fail ungracefully otherwise (TODO). */ -void st3m_audio_init(); +void st3m_audio_init(void); /* Polls hardware to check if headphones, headset or line in are plugged * into the 3.5mm jacks. If it detects a plug in the headphone jack, speakers @@ -41,22 +41,22 @@ void st3m_audio_init(); */ void st3m_audio_update_jacksense(void); -/* Returns 1 if headphones with or without microphone were connected to the +/* Returns true if headphones with or without microphone were connected to the * headphone jack at the last call of st3m_audio_update_jacksense. */ -uint8_t st3m_audio_headphones_are_connected(); +bool st3m_audio_headphones_are_connected(void); -/* Returns 1 if headphones with microphone were connected to the headphone jack - * at the last call of audio_update_jacksense. +/* Returns true if headphones with microphone were connected to the headphone + * jack at the last call of audio_update_jacksense. */ -uint8_t st3m_audio_headset_is_connected(); +bool st3m_audio_headset_is_connected(void); /* If a sleeve contact mic doesn't pull the detection pin low enough the * codec's built in headphone detection might fail. Calling this function * with 'enable = 1' overrides the detection and assumes there's headphones * plugged in. Call with 'enable = 0' to revert to automatic detection. */ -void st3m_audio_headphones_detection_override(uint8_t enable); +void st3m_audio_headphones_detection_override(bool enable); /* Attempts to set target volume for the headphone output/onboard speakers * respectively, clamps/rounds if necessary and returns the actual volume. @@ -84,9 +84,9 @@ float st3m_audio_adjust_volume_dB(float vol_dB); /* Returns volume as set with st3m_audio_{headphones/speaker}_set_volume_dB. The * unspecified variant automatically chooses the adequate channel (**). */ -float st3m_audio_headphones_get_volume_dB(); -float st3m_audio_speaker_get_volume_dB(); -float st3m_audio_get_volume_dB(); +float st3m_audio_headphones_get_volume_dB(void); +float st3m_audio_speaker_get_volume_dB(void); +float st3m_audio_get_volume_dB(void); /* Mutes (mute = 1) or unmutes (mute = 0) the specified channel. * The unspecified variant automatically chooses the adequate channel (**). @@ -95,16 +95,16 @@ float st3m_audio_get_volume_dB(); * the return value of st3m_audio_headphone_are_connected. There is no override * for this (see HEADPHONE PORT POLICY below). */ -void st3m_audio_headphones_set_mute(uint8_t mute); -void st3m_audio_speaker_set_mute(uint8_t mute); -void st3m_audio_set_mute(uint8_t mute); +void st3m_audio_headphones_set_mute(bool mute); +void st3m_audio_speaker_set_mute(bool mute); +void st3m_audio_set_mute(bool mute); -/* Returns 1 if channel is muted, 0 if channel is unmuted. +/* Returns true if channel is muted, false otherwise. * The unspecified variant automatically chooses the adequate channel (**). */ -uint8_t st3m_audio_headphones_get_mute(); -uint8_t st3m_audio_speaker_get_mute(); -uint8_t st3m_audio_get_mute(); +bool st3m_audio_headphones_get_mute(void); +bool st3m_audio_speaker_get_mute(void); +bool st3m_audio_get_mute(void); /* Set the minimum and maximum allowed volume levels for speakers and headphones * respectively. Clamps with hardware limitations. Maximum clamps below the minimum @@ -119,10 +119,10 @@ float st3m_audio_speaker_set_maximum_volume_dB(float vol_dB); * respectively. Change with * st3m_audio_{headphones/speaker}_set_{minimum/maximum}_volume_dB. */ -float st3m_audio_headphones_get_minimum_volume_dB(); -float st3m_audio_headphones_get_maximum_volume_dB(); -float st3m_audio_speaker_get_minimum_volume_dB(); -float st3m_audio_speaker_get_maximum_volume_dB(); +float st3m_audio_headphones_get_minimum_volume_dB(void); +float st3m_audio_headphones_get_maximum_volume_dB(void); +float st3m_audio_speaker_get_minimum_volume_dB(void); +float st3m_audio_speaker_get_maximum_volume_dB(void); /* Syntactic sugar for drawing UI: Returns channel volume in a 0..1 range, * scaled into a 0.01..1 range according to the values set with @@ -131,9 +131,9 @@ float st3m_audio_speaker_get_maximum_volume_dB(); * * The unspecified variant automatically chooses the adequate channel (**). */ -float st3m_audio_headphones_get_volume_relative(); -float st3m_audio_speaker_get_volume_relative(); -float st3m_audio_get_volume_relative(); +float st3m_audio_headphones_get_volume_relative(void); +float st3m_audio_speaker_get_volume_relative(void); +float st3m_audio_get_volume_relative(void); /* (**) if st3m_audio_headphones_are_connected returns 1 the "headphone" variant * is chosen, else the "speaker" variant is chosen. @@ -161,21 +161,21 @@ void st3m_audio_input_set_source(st3m_audio_input_source_t source); /* Returns the currently selected input source. */ -uint8_t st3m_audio_input_get_source(); +st3m_audio_input_source_t st3m_audio_input_get_source(void); /* Hardware preamp gain, 0dB-50dB. TODO: figure out if int/float inconsistency * is a good thing here compared to all other _dB functions. */ uint8_t st3m_audio_headset_set_gain_dB(uint8_t gain_dB); -uint8_t st3m_audio_headset_get_gain_dB(); +uint8_t st3m_audio_headset_get_gain_dB(void); /* You can route whatever source is selected with st3m_audio_input_set_source to * the audio output. Use these to control volume and mute. */ float st3m_audio_input_thru_set_volume_dB(float vol_dB); -float st3m_audio_input_thru_get_volume_dB(); +float st3m_audio_input_thru_get_volume_dB(void); void st3m_audio_input_thru_set_mute(bool mute); -bool st3m_audio_input_thru_get_mute(); +bool st3m_audio_input_thru_get_mute(void); /* HEADPHONE PORT POLICY -- GitLab