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