diff --git a/components/flow3r_bsp/flow3r_bsp.h b/components/flow3r_bsp/flow3r_bsp.h index 4cd89a290e99cdca915444a27c9851683db28713..4505a55891da2f245c43cb2bf2448d847bac99ed 100644 --- a/components/flow3r_bsp/flow3r_bsp.h +++ b/components/flow3r_bsp/flow3r_bsp.h @@ -51,7 +51,10 @@ typedef enum { // Headset microphone on left jack. flow3r_bsp_audio_input_source_headset_mic = 2, // Onboard microphone (enabled red LED). - flow3r_bsp_audio_input_source_onboard_mic = 3 + flow3r_bsp_audio_input_source_onboard_mic = 3, + // auto switching depending on availability + // line in preferred to headset mic preferred to onboard mic. + flow3r_bsp_audio_input_source_auto = 4 } flow3r_bsp_audio_input_source_t; // Initialize the audio subsystem of the badge, including the codec and I2S data diff --git a/components/flow3r_bsp/flow3r_bsp_max98091.c b/components/flow3r_bsp/flow3r_bsp_max98091.c index 977c0279643950eee091a2a950c597534d9e4f85..3cde01e4437a54ac6a043246167284e03a03da46 100644 --- a/components/flow3r_bsp/flow3r_bsp_max98091.c +++ b/components/flow3r_bsp/flow3r_bsp_max98091.c @@ -262,6 +262,8 @@ void flow3r_bsp_max98091_input_set_source( max98091_check(MAX98091_LEFT_ADC_MIXER, 0); max98091_check(MAX98091_RIGHT_ADC_MIXER, 0); break; + case flow3r_bsp_audio_input_source_auto: + break; } } diff --git a/components/micropython/usermodule/mp_audio.c b/components/micropython/usermodule/mp_audio.c index e511d5a62d09531bdffdf5a3153e6d24bfd93ebd..a0f5cdb56126ba97049e34b9fe1cdb9f9c60cd2f 100644 --- a/components/micropython/usermodule/mp_audio.c +++ b/components/micropython/usermodule/mp_audio.c @@ -195,22 +195,22 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_get_volume_relative_obj, mp_get_volume_relative); STATIC mp_obj_t mp_headphones_line_in_set_hardware_thru(mp_obj_t enable) { - st3m_audio_headphones_line_in_set_hardware_thru(mp_obj_get_int(enable)); return mp_const_none; + // st3m_audio_headphones_line_in_set_hardware_thru(mp_obj_get_int(enable)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headphones_line_in_set_hardware_thru_obj, mp_headphones_line_in_set_hardware_thru); STATIC mp_obj_t mp_speaker_line_in_set_hardware_thru(mp_obj_t enable) { - st3m_audio_speaker_line_in_set_hardware_thru(mp_obj_get_int(enable)); return mp_const_none; + // st3m_audio_speaker_line_in_set_hardware_thru(mp_obj_get_int(enable)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_line_in_set_hardware_thru_obj, mp_speaker_line_in_set_hardware_thru); STATIC mp_obj_t mp_line_in_set_hardware_thru(mp_obj_t enable) { - st3m_audio_line_in_set_hardware_thru(mp_obj_get_int(enable)); return mp_const_none; + // st3m_audio_line_in_set_hardware_thru(mp_obj_get_int(enable)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_line_in_set_hardware_thru_obj, mp_line_in_set_hardware_thru); @@ -219,29 +219,111 @@ STATIC mp_obj_t mp_line_in_is_connected() { } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_line_in_is_connected_obj, mp_line_in_is_connected); -STATIC mp_obj_t mp_input_set_source(mp_obj_t source) { - st3m_audio_input_set_source(mp_obj_get_int(source)); + +// <INPUT SETUP> + +// engine +STATIC mp_obj_t mp_input_engine_set_source(mp_obj_t source) { + st3m_audio_input_engine_set_source(mp_obj_get_int(source)); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_set_source_obj, mp_input_set_source); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_engine_set_source_obj, + mp_input_engine_set_source); + +STATIC mp_obj_t mp_input_engine_get_source(void) { + return mp_obj_new_int(st3m_audio_input_engine_get_source()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_engine_get_source_obj, + mp_input_engine_get_source); + +STATIC mp_obj_t mp_input_engine_get_target_source(void) { + return mp_obj_new_int(st3m_audio_input_engine_get_target_source()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_engine_get_target_source_obj, + mp_input_engine_get_target_source); + +STATIC mp_obj_t mp_input_engine_get_source_avail(mp_obj_t source) { + return mp_obj_new_int( + st3m_audio_input_engine_get_source_avail(mp_obj_get_int(source))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_engine_get_source_avail_obj, + mp_input_engine_get_source_avail); +// thru +STATIC mp_obj_t mp_input_thru_set_source(mp_obj_t source) { + st3m_audio_input_thru_set_source(mp_obj_get_int(source)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_thru_set_source_obj, + mp_input_thru_set_source); + +STATIC mp_obj_t mp_input_thru_get_source(void) { + return mp_obj_new_int(st3m_audio_input_thru_get_source()); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_thru_get_source_obj, + mp_input_thru_get_source); + +STATIC mp_obj_t mp_input_thru_get_target_source(void) { + return mp_obj_new_int(st3m_audio_input_thru_get_target_source()); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_thru_get_target_source_obj, + mp_input_thru_get_target_source); + +STATIC mp_obj_t mp_input_thru_get_source_avail(mp_obj_t source) { + return mp_obj_new_int( + st3m_audio_input_thru_get_source_avail(mp_obj_get_int(source))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_input_thru_get_source_avail_obj, + mp_input_thru_get_source_avail); + +// actual source STATIC mp_obj_t mp_input_get_source(void) { return mp_obj_new_int(st3m_audio_input_get_source()); } STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_get_source_obj, mp_input_get_source); -STATIC mp_obj_t mp_headset_set_gain_dB(mp_obj_t gain_dB) { - st3m_audio_headset_set_gain_dB(mp_obj_get_int(gain_dB)); - return mp_const_none; +// gain +STATIC mp_obj_t mp_headset_mic_set_gain_dB(mp_obj_t gain_dB) { + float ret = st3m_audio_headset_mic_set_gain_dB(mp_obj_get_float(gain_dB)); + return mp_obj_new_float(ret); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headset_mic_set_gain_dB_obj, + mp_headset_mic_set_gain_dB); + +STATIC mp_obj_t mp_headset_mic_get_gain_dB(void) { + return mp_obj_new_float(st3m_audio_headset_mic_get_gain_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headset_mic_get_gain_dB_obj, + mp_headset_mic_get_gain_dB); + +STATIC mp_obj_t mp_onboard_mic_set_gain_dB(mp_obj_t gain_dB) { + float ret = st3m_audio_onboard_mic_set_gain_dB(mp_obj_get_float(gain_dB)); + return mp_obj_new_float(ret); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_onboard_mic_set_gain_dB_obj, + mp_onboard_mic_set_gain_dB); + +STATIC mp_obj_t mp_onboard_mic_get_gain_dB(void) { + return mp_obj_new_float(st3m_audio_onboard_mic_get_gain_dB()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_onboard_mic_get_gain_dB_obj, + mp_onboard_mic_get_gain_dB); + +STATIC mp_obj_t mp_line_in_set_gain_dB(mp_obj_t gain_dB) { + float ret = st3m_audio_line_in_set_gain_dB(mp_obj_get_float(gain_dB)); + return mp_obj_new_float(ret); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headset_set_gain_dB_obj, - mp_headset_set_gain_dB); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_line_in_set_gain_dB_obj, + mp_line_in_set_gain_dB); -STATIC mp_obj_t mp_headset_get_gain_dB(void) { - return mp_obj_new_int(st3m_audio_headset_get_gain_dB()); +STATIC mp_obj_t mp_line_in_get_gain_dB(void) { + return mp_obj_new_float(st3m_audio_line_in_get_gain_dB()); } -STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headset_get_gain_dB_obj, - mp_headset_get_gain_dB); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_line_in_get_gain_dB_obj, + mp_line_in_get_gain_dB); STATIC mp_obj_t mp_input_thru_set_volume_dB(mp_obj_t vol_dB) { return mp_obj_new_float( @@ -269,6 +351,8 @@ STATIC mp_obj_t mp_input_thru_get_mute() { STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_input_thru_get_mute_obj, mp_input_thru_get_mute); +// eq + STATIC mp_obj_t mp_speaker_get_eq_on() { return mp_obj_new_int(st3m_audio_speaker_get_eq_on()); } @@ -282,6 +366,61 @@ STATIC mp_obj_t mp_speaker_set_eq_on(mp_obj_t eq_on) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_speaker_set_eq_on_obj, mp_speaker_set_eq_on); +// permissions + +STATIC mp_obj_t mp_headset_mic_set_allowed(mp_obj_t allowed) { + st3m_audio_headset_mic_set_allowed(mp_obj_get_int(allowed)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_headset_mic_set_allowed_obj, + mp_headset_mic_set_allowed); + +STATIC mp_obj_t mp_headset_mic_get_allowed() { + return mp_obj_new_int(st3m_audio_headset_mic_get_allowed()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_headset_mic_get_allowed_obj, + mp_headset_mic_get_allowed); + +STATIC mp_obj_t mp_onboard_mic_set_allowed(mp_obj_t allowed) { + st3m_audio_onboard_mic_set_allowed(mp_obj_get_int(allowed)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_onboard_mic_set_allowed_obj, + mp_onboard_mic_set_allowed); + +STATIC mp_obj_t mp_onboard_mic_get_allowed() { + return mp_obj_new_int(st3m_audio_onboard_mic_get_allowed()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_onboard_mic_get_allowed_obj, + mp_onboard_mic_get_allowed); + +STATIC mp_obj_t mp_line_in_set_allowed(mp_obj_t allowed) { + st3m_audio_line_in_set_allowed(mp_obj_get_int(allowed)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_line_in_set_allowed_obj, + mp_line_in_set_allowed); + +STATIC mp_obj_t mp_line_in_get_allowed() { + return mp_obj_new_int(st3m_audio_line_in_get_allowed()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_line_in_get_allowed_obj, + mp_line_in_get_allowed); + +STATIC mp_obj_t mp_onboard_mic_to_speaker_set_allowed(mp_obj_t allowed) { + st3m_audio_onboard_mic_to_speaker_set_allowed(mp_obj_get_int(allowed)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_onboard_mic_to_speaker_set_allowed_obj, + mp_onboard_mic_to_speaker_set_allowed); + +STATIC mp_obj_t mp_onboard_mic_to_speaker_get_allowed() { + return mp_obj_new_int(st3m_audio_onboard_mic_to_speaker_get_allowed()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_onboard_mic_to_speaker_get_allowed_obj, + mp_onboard_mic_to_speaker_get_allowed); +// </INPUT SETUP> + STATIC mp_obj_t mp_codec_i2c_write(mp_obj_t reg_in, mp_obj_t data_in) { #if defined(CONFIG_FLOW3R_HW_GEN_P3) || defined(CONFIG_FLOW3R_HW_GEN_P4) || \ defined(CONFIG_FLOW3R_HW_GEN_C23) @@ -372,15 +511,44 @@ STATIC const mp_rom_map_elem_t mp_module_audio_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_line_in_is_connected), MP_ROM_PTR(&mp_line_in_is_connected_obj) }, - { MP_ROM_QSTR(MP_QSTR_input_set_source), - MP_ROM_PTR(&mp_input_set_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_engine_set_source), + MP_ROM_PTR(&mp_input_engine_set_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_engine_get_source), + MP_ROM_PTR(&mp_input_engine_get_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_engine_get_target_source), + MP_ROM_PTR(&mp_input_engine_get_target_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_engine_get_source_avail), + MP_ROM_PTR(&mp_input_engine_get_source_avail_obj) }, + + { MP_ROM_QSTR(MP_QSTR_input_thru_set_source), + MP_ROM_PTR(&mp_input_thru_set_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_thru_get_source), + MP_ROM_PTR(&mp_input_thru_get_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_thru_get_target_source), + MP_ROM_PTR(&mp_input_thru_get_target_source_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_thru_get_source_avail), + MP_ROM_PTR(&mp_input_thru_get_source_avail_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_get_source), MP_ROM_PTR(&mp_input_get_source_obj) }, + // TODO: DEPRECATE + { MP_ROM_QSTR(MP_QSTR_input_set_source), + MP_ROM_PTR(&mp_input_engine_set_source_obj) }, - { MP_ROM_QSTR(MP_QSTR_headset_set_gain_dB), - MP_ROM_PTR(&mp_headset_set_gain_dB_obj) }, - { MP_ROM_QSTR(MP_QSTR_headset_get_gain_dB), - MP_ROM_PTR(&mp_headset_get_gain_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_headset_mic_set_gain_dB), + MP_ROM_PTR(&mp_headset_mic_set_gain_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_headset_mic_get_gain_dB), + MP_ROM_PTR(&mp_headset_mic_get_gain_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_onboard_mic_set_gain_dB), + MP_ROM_PTR(&mp_onboard_mic_set_gain_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_onboard_mic_get_gain_dB), + MP_ROM_PTR(&mp_onboard_mic_get_gain_dB_obj) }, + + { MP_ROM_QSTR(MP_QSTR_line_in_set_gain_dB), + MP_ROM_PTR(&mp_line_in_set_gain_dB_obj) }, + { MP_ROM_QSTR(MP_QSTR_line_in_get_gain_dB), + MP_ROM_PTR(&mp_line_in_get_gain_dB_obj) }, { MP_ROM_QSTR(MP_QSTR_input_thru_set_volume_dB), MP_ROM_PTR(&mp_input_thru_set_volume_dB_obj) }, @@ -402,11 +570,30 @@ STATIC const mp_rom_map_elem_t mp_module_audio_globals_table[] = { MP_ROM_INT(st3m_audio_input_source_headset_mic) }, { MP_ROM_QSTR(MP_QSTR_INPUT_SOURCE_ONBOARD_MIC), MP_ROM_INT(st3m_audio_input_source_onboard_mic) }, + { MP_ROM_QSTR(MP_QSTR_INPUT_SOURCE_AUTO), + MP_ROM_INT(st3m_audio_input_source_auto) }, { MP_ROM_QSTR(MP_QSTR_speaker_get_eq_on), MP_ROM_PTR(&mp_speaker_get_eq_on_obj) }, { MP_ROM_QSTR(MP_QSTR_speaker_set_eq_on), MP_ROM_PTR(&mp_speaker_set_eq_on_obj) }, + + { MP_ROM_QSTR(MP_QSTR_headset_mic_get_allowed), + MP_ROM_PTR(&mp_headset_mic_get_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_headset_mic_set_allowed), + MP_ROM_PTR(&mp_headset_mic_set_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_onboard_mic_get_allowed), + MP_ROM_PTR(&mp_onboard_mic_get_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_onboard_mic_set_allowed), + MP_ROM_PTR(&mp_onboard_mic_set_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_line_in_get_allowed), + MP_ROM_PTR(&mp_line_in_get_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_line_in_set_allowed), + MP_ROM_PTR(&mp_line_in_set_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_onboard_mic_to_speaker_get_allowed), + MP_ROM_PTR(&mp_onboard_mic_to_speaker_get_allowed_obj) }, + { MP_ROM_QSTR(MP_QSTR_onboard_mic_to_speaker_set_allowed), + MP_ROM_PTR(&mp_onboard_mic_to_speaker_set_allowed_obj) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_audio_globals, diff --git a/components/st3m/st3m_audio.c b/components/st3m/st3m_audio.c index cb7324d0dfaf7ed1a10b9893c003a851250cdeb2..78b10d24d30eea8e5eae25ce67e393f392ae3cc1 100644 --- a/components/st3m/st3m_audio.c +++ b/components/st3m/st3m_audio.c @@ -31,7 +31,7 @@ static bool _headphones_connected(void); const static float headphones_maximum_volume_system_dB = 3; const static float speaker_maximum_volume_system_dB = 14; -// Output, either speakers or headphones. Holds volume/mute state and limits, +// Output, either speaker or headphones. Holds volume/mute state and limits, // and calculated software volume. // // An output's apply function configures the actual physical output, ie. by @@ -184,12 +184,25 @@ typedef struct { 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; - int8_t headset_gain; + float headset_mic_gain_dB; + int16_t headset_mic_gain_software; + float onboard_mic_gain_dB; + int16_t onboard_mic_gain_software; + float line_in_gain_dB; + int16_t line_in_gain_software; uint8_t speaker_eq_on; + bool headset_mic_allowed; + bool onboard_mic_allowed; + bool line_in_allowed; + bool onboard_mic_to_speaker_allowed; + st3m_audio_input_source_t engine_source; + st3m_audio_input_source_t engine_target_source; + st3m_audio_input_source_t thru_source; + st3m_audio_input_source_t thru_target_source; + st3m_audio_input_source_t source; + // Software-based audio pipe settings. int32_t input_thru_vol; int32_t input_thru_vol_int; @@ -230,11 +243,28 @@ static st3m_audio_state_t state = { .apply = _audio_speaker_apply, }, - .source = st3m_audio_input_source_none, - .headset_gain = 0, + .speaker_eq_on = true, + + .headset_mic_allowed = true, + .onboard_mic_allowed = true, + .line_in_allowed = true, + .onboard_mic_to_speaker_allowed = false, + .headset_mic_gain_dB = 0, + .onboard_mic_gain_dB = 0, + .line_in_gain_dB = 0, + .headset_mic_gain_software = 256, + .onboard_mic_gain_software = 256, + .line_in_gain_software = 256, + .input_thru_vol = 0, - .input_thru_vol_int = 0, - .input_thru_mute = true, + .input_thru_vol_int = 32768, + .input_thru_mute = false, // deprecated + + .engine_target_source = st3m_audio_input_source_none, + .engine_source = st3m_audio_input_source_none, + .thru_source = st3m_audio_input_source_none, + .thru_target_source = st3m_audio_input_source_none, + .source = st3m_audio_input_source_none, .function = st3m_audio_player_function_dummy, }; @@ -246,11 +276,243 @@ static bool _headphones_connected(void) { return state.jacksense.headphones || state.headphones_detection_override; } -static void _update_jacksense() { +static void _audio_input_set_source(st3m_audio_input_source_t source) { + LOCK; + st3m_audio_input_source_t prev_source = state.source; + UNLOCK; + if (source == prev_source) return; + + flow3r_bsp_audio_input_source_t fsource; + switch (source) { + case st3m_audio_input_source_line_in: + fsource = flow3r_bsp_audio_input_source_line_in; + break; + case st3m_audio_input_source_onboard_mic: + fsource = flow3r_bsp_audio_input_source_onboard_mic; + break; + case st3m_audio_input_source_headset_mic: + fsource = flow3r_bsp_audio_input_source_headset_mic; + break; + case st3m_audio_input_source_none: + fsource = flow3r_bsp_audio_input_source_none; + break; + case st3m_audio_input_source_auto: + fsource = flow3r_bsp_audio_input_source_none; + break; + } + LOCK; + state.source = source; + UNLOCK; + flow3r_bsp_audio_input_set_source(fsource); +} + +static bool _check_engine_source_avail(st3m_audio_input_source_t source) { + switch (source) { + case st3m_audio_input_source_none: + return true; + case st3m_audio_input_source_auto: + return true; + case st3m_audio_input_source_line_in: + return state.line_in_allowed && state.jacksense.line_in; + case st3m_audio_input_source_headset_mic: + return state.headset_mic_allowed && state.jacksense.headset; + case st3m_audio_input_source_onboard_mic: + return state.onboard_mic_allowed; + } + return false; +} + +bool st3m_audio_input_engine_get_source_avail( + st3m_audio_input_source_t source) { + bool ret = false; + if (source == st3m_audio_input_source_auto) { + LOCK; + ret = + ret || _check_engine_source_avail(st3m_audio_input_source_line_in); + ret = ret || + _check_engine_source_avail(st3m_audio_input_source_headset_mic); + ret = ret || + _check_engine_source_avail(st3m_audio_input_source_onboard_mic); + UNLOCK; + } else { + LOCK; + ret = _check_engine_source_avail(source); + UNLOCK; + } + return ret; +} + +void _update_engine_source() { + st3m_audio_input_source_t source; + LOCK; + source = state.engine_target_source; + if (source == st3m_audio_input_source_auto) { + if (_check_engine_source_avail(st3m_audio_input_source_line_in)) { + source = st3m_audio_input_source_line_in; + } else if (_check_engine_source_avail( + st3m_audio_input_source_headset_mic)) { + source = st3m_audio_input_source_headset_mic; + } else if (_check_engine_source_avail( + st3m_audio_input_source_onboard_mic)) { + source = st3m_audio_input_source_onboard_mic; + } else { + source = st3m_audio_input_source_none; + } + } + bool avail = _check_engine_source_avail(source); + UNLOCK; + source = avail ? source : st3m_audio_input_source_none; + LOCK; + bool return_early = state.source == source; + state.engine_source = source; + UNLOCK; + if (return_early) return; + + if (avail) { + switch (source) { + case st3m_audio_input_source_none: + break; + case st3m_audio_input_source_auto: + break; + case st3m_audio_input_source_line_in: + if (avail) { + _audio_input_set_source(st3m_audio_input_source_line_in); + } + break; + case st3m_audio_input_source_headset_mic: + if (avail) { + _audio_input_set_source( + st3m_audio_input_source_headset_mic); + } + break; + case st3m_audio_input_source_onboard_mic: + if (avail) { + _audio_input_set_source( + st3m_audio_input_source_onboard_mic); + } + break; + } + } +} + +void st3m_audio_input_engine_set_source(st3m_audio_input_source_t source) { + LOCK; + state.engine_target_source = source; + UNLOCK; +} + +static bool _check_thru_source_avail(st3m_audio_input_source_t source) { + bool avail = _check_engine_source_avail(source); + if ((source == st3m_audio_input_source_onboard_mic) && + (!state.onboard_mic_to_speaker_allowed) && + (!state.jacksense.headphones)) { + avail = false; + } + + if ((state.engine_source != st3m_audio_input_source_none) && + (state.engine_source != source)) { + avail = false; + } + return avail; +} + +bool st3m_audio_input_thru_get_source_avail(st3m_audio_input_source_t source) { + bool ret = false; + if (source == st3m_audio_input_source_auto) { + LOCK; + ret = ret || _check_thru_source_avail(st3m_audio_input_source_line_in); + ret = ret || + _check_thru_source_avail(st3m_audio_input_source_headset_mic); + ret = ret || + _check_thru_source_avail(st3m_audio_input_source_onboard_mic); + UNLOCK; + } else { + LOCK; + ret = _check_thru_source_avail(source); + UNLOCK; + } + return ret; +} + +void _update_thru_source() { + st3m_audio_input_source_t source; + + LOCK; + source = state.thru_target_source; + if (source == st3m_audio_input_source_auto) { + if (state.engine_source != st3m_audio_input_source_none) { + source = state.engine_source; + } else if (_check_thru_source_avail(st3m_audio_input_source_line_in)) { + source = st3m_audio_input_source_line_in; + } else if (_check_thru_source_avail( + st3m_audio_input_source_headset_mic)) { + source = st3m_audio_input_source_headset_mic; + } else if (_check_thru_source_avail( + st3m_audio_input_source_onboard_mic)) { + source = st3m_audio_input_source_onboard_mic; + } else { + source = st3m_audio_input_source_none; + } + } + + bool avail = _check_thru_source_avail(source); + UNLOCK; + source = avail ? source : st3m_audio_input_source_none; + + bool return_flag = false; + LOCK; + if (state.engine_source != st3m_audio_input_source_none) { + if (state.engine_source == source) { + state.thru_source = state.engine_source; + } else { + state.thru_source = st3m_audio_input_source_none; + } + return_flag = true; + } + state.thru_source = source; + if (state.thru_source == state.source) { + return_flag = true; + } + UNLOCK; + if (return_flag) return; + + switch (source) { + case st3m_audio_input_source_none: + if (avail) { + _audio_input_set_source(st3m_audio_input_source_none); + } + break; + case st3m_audio_input_source_line_in: + if (avail) { + _audio_input_set_source(st3m_audio_input_source_line_in); + } + break; + case st3m_audio_input_source_headset_mic: + if (avail) { + _audio_input_set_source(st3m_audio_input_source_headset_mic); + } + break; + case st3m_audio_input_source_onboard_mic: + if (avail) { + _audio_input_set_source(st3m_audio_input_source_onboard_mic); + } + break; + case st3m_audio_input_source_auto: + // should not be possible + break; + } +} + +void st3m_audio_input_thru_set_source(st3m_audio_input_source_t source) { + LOCK; + state.thru_target_source = source; + UNLOCK; +} + +void _update_sink() { flow3r_bsp_audio_jacksense_state_t st; flow3r_bsp_audio_read_jacksense(&st); static bool _speaker_eq_on_prev = false; - // Update volume to trigger mutes if needed. But only do that if the // jacks actually changed. LOCK; @@ -268,6 +530,13 @@ static void _update_jacksense() { UNLOCK; } +static void _update_routing() { + // order important + _update_sink(); + _update_engine_source(); + _update_thru_source(); +} + 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; @@ -281,9 +550,7 @@ void st3m_audio_init(void) { flow3r_bsp_audio_init(); - st3m_audio_input_thru_set_volume_dB(-20); - state.speaker_eq_on = true; - _update_jacksense(); + _update_routing(); _output_apply(&state.speaker); _output_apply(&state.headphones); bool _speaker_eq = (!_headphones_connected()) && state.speaker_eq_on; @@ -300,11 +567,13 @@ static void _audio_player_task(void *data) { int16_t buffer_tx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2]; int16_t buffer_rx[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2]; + int16_t buffer_rx_dummy[FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2]; memset(buffer_tx, 0, sizeof(buffer_tx)); memset(buffer_rx, 0, sizeof(buffer_rx)); + memset(buffer_rx_dummy, 0, sizeof(buffer_rx)); size_t count; - bool hwmute = flow3r_bsp_audio_has_hardware_mute(); + st3m_audio_input_source_t source_prev = st3m_audio_input_source_none; while (true) { count = 0; @@ -321,39 +590,88 @@ static void _audio_player_task(void *data) { } LOCK; + st3m_audio_input_source_t source = state.source; + st3m_audio_input_source_t engine_source = state.engine_source; + st3m_audio_input_source_t thru_source = state.thru_source; 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); - for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE; i++) { - st3m_scope_write(buffer_tx[2 * i] >> 2); + // <RX SIGNAL PREPROCESSING> + + int16_t rx_gain = 256; // unity + uint8_t rx_chan = 3; // stereo = 0; left = 1; right = 2; off = 3; + if (source != source_prev) { + // state change: throw away buffer + source_prev = source; + memset(buffer_rx, 0, sizeof(buffer_rx)); + } else if (source == st3m_audio_input_source_headset_mic) { + // headset has its own gain thing going on, leave at unity + rx_chan = 1; + } else if (source == st3m_audio_input_source_line_in) { + LOCK; + int16_t gain = state.line_in_gain_software; + UNLOCK; + rx_gain = gain; + rx_chan = 0; + } else if (source == st3m_audio_input_source_onboard_mic) { + LOCK; + int16_t gain = state.onboard_mic_gain_software; + UNLOCK; + rx_gain = gain; + rx_chan = 1; } - 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; + if (rx_chan == 0) { + // keep stereo image + for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2; + i++) { + buffer_rx[i] = (((int32_t)buffer_rx[i]) * rx_gain) >> 8; } + } else if (rx_chan < 3) { + // mix one of the input channels to both rx stereo chans (easier + // mono sources) + for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2; + i++) { + uint16_t j = (i / 2) * 2 + rx_chan - 1; + buffer_rx[i] = (((int32_t)buffer_rx[j]) * rx_gain) >> 8; + } + } + + int16_t *engine_rx; + + if (engine_source == st3m_audio_input_source_none) { + engine_rx = buffer_rx_dummy; } else { - for (int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2); - i += 1) { - int32_t acc = buffer_tx[i]; + engine_rx = buffer_rx; + } - acc = (acc * software_volume) >> 15; + // <ACTUAL ENGINE CALL> - if (!input_thru_mute) { - acc += (((int32_t)buffer_rx[i]) * input_thru_vol_int) >> 15; - } - buffer_tx[i] = acc; + (*function)(engine_rx, buffer_tx, FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2); + + // </ACTUAL ENGINE CALL> + + for (uint16_t i = 0; i < FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE; i++) { + st3m_scope_write(buffer_tx[2 * i] >> 2); + } + + for (int i = 0; i < (FLOW3R_BSP_AUDIO_DMA_BUFFER_SIZE * 2); i += 1) { + int32_t acc = buffer_tx[i]; + + acc = (acc * software_volume) >> 15; + + if ((thru_source != st3m_audio_input_source_none) && + ((engine_source == thru_source) || + (engine_source == st3m_audio_input_source_none)) && + (!input_thru_mute)) { + acc += (((int32_t)buffer_rx[i]) * input_thru_vol_int) >> 15; } + buffer_tx[i] = acc; } flow3r_bsp_audio_write(buffer_tx, sizeof(buffer_tx), &count, 1000); @@ -371,21 +689,24 @@ static void _jacksense_update_task(void *data) { TickType_t last_wake = xTaskGetTickCount(); while (1) { vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(50)); // 20 Hz - _update_jacksense(); + _update_routing(); } } // BSP wrappers that don't need locking. void st3m_audio_headphones_line_in_set_hardware_thru(bool enable) { + return; flow3r_bsp_audio_headphones_line_in_set_hardware_thru(enable); } void st3m_audio_speaker_line_in_set_hardware_thru(bool enable) { + return; flow3r_bsp_audio_speaker_line_in_set_hardware_thru(enable); } void st3m_audio_line_in_set_hardware_thru(bool enable) { + return; flow3r_bsp_audio_line_in_set_hardware_thru(enable); } @@ -412,44 +733,62 @@ GETTER(float, audio_headphones_get_maximum_volume_dB, 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_engine_get_source, + state.engine_source) +GETTER(st3m_audio_input_source_t, audio_input_thru_get_source, + state.thru_source) +GETTER(st3m_audio_input_source_t, audio_input_engine_get_target_source, + state.engine_target_source) +GETTER(st3m_audio_input_source_t, audio_input_thru_get_target_source, + state.thru_target_source) 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_headset_mic_get_gain_dB, state.headset_mic_gain_dB) +GETTER(float, audio_onboard_mic_get_gain_dB, state.onboard_mic_gain_dB) +GETTER(float, audio_line_in_get_gain_dB, state.line_in_gain_dB) GETTER(float, audio_input_thru_get_volume_dB, state.input_thru_vol) GETTER(bool, audio_input_thru_get_mute, state.input_thru_mute) GETTER(bool, audio_speaker_get_eq_on, state.speaker_eq_on) +GETTER(bool, audio_headset_mic_get_allowed, state.headset_mic_allowed) +GETTER(bool, audio_onboard_mic_get_allowed, state.onboard_mic_allowed) +GETTER(bool, audio_line_in_get_allowed, state.line_in_allowed) +GETTER(bool, audio_onboard_mic_to_speaker_get_allowed, + state.onboard_mic_to_speaker_allowed) #undef GETTER // Locked global API functions. -void st3m_audio_headset_set_gain_dB(int8_t gain_dB) { - gain_dB = flow3r_bsp_audio_headset_set_gain_dB(gain_dB); +float st3m_audio_headset_mic_set_gain_dB(float gain_dB) { + if (gain_dB > 42) gain_dB = 42; + if (gain_dB < -9) gain_dB = -9; + int8_t hw_gain = flow3r_bsp_audio_headset_set_gain_dB(gain_dB); + int16_t software_gain = 256. * expf((gain_dB - hw_gain) * NAT_LOG_DB); LOCK; - state.headset_gain = gain_dB; + state.headset_mic_gain_dB = gain_dB; + state.headset_mic_gain_software = software_gain; 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; - } +float st3m_audio_line_in_set_gain_dB(float gain_dB) { + if (gain_dB > 30) gain_dB = 30; + if (gain_dB < -24) gain_dB = -24; + int16_t software_gain = 256. * expf(gain_dB * NAT_LOG_DB); LOCK; - state.source = source; + state.line_in_gain_dB = gain_dB; + state.line_in_gain_software = software_gain; UNLOCK; + return gain_dB; +} + +float st3m_audio_onboard_mic_set_gain_dB(float gain_dB) { + if (gain_dB > 42) gain_dB = 42; + if (gain_dB < 0) gain_dB = 0; + int16_t software_gain = 256. * expf(gain_dB * NAT_LOG_DB); + LOCK; + state.onboard_mic_gain_dB = gain_dB; + state.onboard_mic_gain_software = software_gain; + UNLOCK; + return gain_dB; } void st3m_audio_speaker_set_eq_on(bool enabled) { @@ -464,10 +803,35 @@ void st3m_audio_input_thru_set_mute(bool mute) { UNLOCK; } +void st3m_audio_headset_mic_set_allowed(bool allowed) { + LOCK; + state.headset_mic_allowed = allowed; + UNLOCK; +} + +void st3m_audio_onboard_mic_set_allowed(bool allowed) { + LOCK; + state.onboard_mic_allowed = allowed; + UNLOCK; +} + +void st3m_audio_line_in_set_allowed(bool allowed) { + LOCK; + state.line_in_allowed = allowed; + UNLOCK; +} + +void st3m_audio_onboard_mic_to_speaker_set_allowed(bool allowed) { + LOCK; + state.onboard_mic_to_speaker_allowed = allowed; + UNLOCK; +} + float st3m_audio_input_thru_set_volume_dB(float vol_dB) { if (vol_dB > 0) vol_dB = 0; + int16_t vol = 32768. * expf(vol_dB * NAT_LOG_DB); LOCK; - state.input_thru_vol_int = (int32_t)(32768. * expf(vol_dB * NAT_LOG_DB)); + state.input_thru_vol_int = vol; state.input_thru_vol = vol_dB; UNLOCK; return vol_dB; diff --git a/components/st3m/st3m_audio.h b/components/st3m/st3m_audio.h index cb8a21275a836e283fbf40f31231bec98a69c195..b8bb4bd8ef4adffcf3a25a4bb9cee9f7dc6694f8 100644 --- a/components/st3m/st3m_audio.h +++ b/components/st3m/st3m_audio.h @@ -10,7 +10,10 @@ typedef enum { // Headset microphone on left jack. st3m_audio_input_source_headset_mic = 2, // Onboard microphone (enabled red LED). - st3m_audio_input_source_onboard_mic = 3 + st3m_audio_input_source_onboard_mic = 3, + // auto switching depending on availability + // line in preferred to headset mic preferred to onboard mic. + st3m_audio_input_source_auto = 4 } st3m_audio_input_source_t; typedef void (*st3m_audio_player_function_t)(int16_t* tx, int16_t* rx, @@ -151,20 +154,31 @@ void st3m_audio_line_in_set_hardware_thru(bool enable); * Note: The onboard digital mic turns on an LED on the top board if it receives * a clock signal which is considered a good proxy for its capability of reading * data. - * - * TODO: check if sources are available */ -void st3m_audio_input_set_source(st3m_audio_input_source_t source); +void st3m_audio_input_engine_set_source(st3m_audio_input_source_t source); +st3m_audio_input_source_t st3m_audio_input_engine_get_source(void); +st3m_audio_input_source_t st3m_audio_input_engine_get_target_source(void); + +void st3m_audio_input_thru_set_source(st3m_audio_input_source_t source); +st3m_audio_input_source_t st3m_audio_input_thru_get_source(void); +st3m_audio_input_source_t st3m_audio_input_thru_get_target_source(void); -/* Returns the currently selected input 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. +/* Gain of headset mic source + */ +float st3m_audio_headset_mic_set_gain_dB(float gain_dB); +float st3m_audio_headset_mic_get_gain_dB(void); + +/* Gain of onboard mic source + */ +float st3m_audio_onboard_mic_set_gain_dB(float gain_dB); +float st3m_audio_onboard_mic_get_gain_dB(void); + +/* Gain of line in source */ -void st3m_audio_headset_set_gain_dB(int8_t gain_dB); -uint8_t st3m_audio_headset_get_gain_dB(void); +float st3m_audio_line_in_set_gain_dB(float gain_dB); +float st3m_audio_line_in_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. @@ -180,6 +194,21 @@ bool st3m_audio_input_thru_get_mute(void); void st3m_audio_speaker_set_eq_on(bool enable); bool st3m_audio_speaker_get_eq_on(void); +void st3m_audio_headset_mic_set_allowed(bool allowed); +bool st3m_audio_headset_mic_get_allowed(void); + +void st3m_audio_onboard_mic_set_allowed(bool allowed); +bool st3m_audio_onboard_mic_get_allowed(void); + +void st3m_audio_line_in_set_allowed(bool allowed); +bool st3m_audio_line_in_get_allowed(void); + +void st3m_audio_onboard_mic_to_speaker_set_allowed(bool allowed); +bool st3m_audio_onboard_mic_to_speaker_get_allowed(void); + +bool st3m_audio_input_engine_get_source_avail(st3m_audio_input_source_t source); +bool st3m_audio_input_thru_get_source_avail(st3m_audio_input_source_t source); + /* HEADPHONE PORT POLICY diff --git a/docs/api/audio.rst b/docs/api/audio.rst index 6225695299fab03dd8b1cecb9907380d40bfeabc..e2cf8103cfceedffdc5bf9cca1e8a8c5c6cc65db 100644 --- a/docs/api/audio.rst +++ b/docs/api/audio.rst @@ -3,9 +3,11 @@ ``audio`` module ================ -Many of these functions are available in three variants: headphone volume, -speaker volume, and volume. If :code:`headphones_are_connected()` returns 1 -the "headphone" variant is chosen, else the "speaker" variant is chosen. +The audio module provides the backbone for handling basic audio bookkeeping such as volume and signal routing. +Actual sound is created by the engines, i.e. bl00mbox and media player at the moment. + +Jack Detection +-------------- .. py:function:: headset_is_connected() -> bool @@ -22,6 +24,132 @@ the "headphone" variant is chosen, else the "speaker" variant is chosen. Returns 1 if the line-in jack was connected at the last call of audio_update_jacksense. +Input Sources +------------- + +.. note:: + The onboard digital mic turns on an LED on the top board if it receives + a clock signal which is considered a good proxy for its capability of reading + data. Access to the onboard mic can be disabled entirely in the audio config + menu. + +The codec can receive data from the line in jack, the headset mic pin in the headphone jack, or the +internal microphone. We distinguish between two use cases: 1) sending the signal to the audio engines +to be mangled with, and 2) directly mix it to the output with a variable volume level. We provide two +different APIs for each use case. + +Sources may or may not be available; for line in and the headset mic they might simply not be plugged in, +but also users can configure in the settings which inputs are available in the first place. To handle this +uncertainity gracefully, instead of momentarily trying (and potentially failing) to set up a connection +you set a desired target and the backend will attempt to connect to it continuously until the target is reset +to none. The target states are not cleared when exiting applications, if you don't intend to also configure +the source for other applications please reset them to whatever state you found them in like so: + +.. code-block:: python + + on_enter(self, vm): + # save original target + self.orig_engine_target_source = audio.input_engine_get_target_source() + # switch to your preferred one + audio.input_engine_set_target_source(audio.INPUT_SOURCE_AUTO) + + on_exit(self): + # restore original target + audio.input_engine_set_source(self.orig_engine_target_source) + +Since the codec can only send data from one source at a time. in case of a disagreement between the engine +source and the thru source, the engine source wins and the through source is temporarily set to none. +For thru to follow the engine source if available and not none use ``audio.input_thru_set_source(audio.INPUT_SOURCE_AUTO)``. + +The available sources for both engine and thru are slightly different: The engine only looks for permissions +and hardware state, while thru can not access the onboard mic if playback is happening through the speakers. +This is set up to prevent accidential feedback loops. However the user can give permission to acces this mode +in the user config. + + +.. py:data:: INPUT_SOURCE_NONE + + No source, datastream suspended. + +.. py:data:: INPUT_SOURCE_LINE_IN + + Stream data from line in if available. + +.. py:data:: INPUT_SOURCE_HEADSET_MIC + + Stream data from headset mic if available and allowed. + +.. py:data:: INPUT_SOURCE_ONBOARD_MIC + + Stream data from onboard mic if allowed. + +.. py:data:: INPUT_SOURCE_AUTO + + Stream data from available input, line in is preferred to headset mic is preferred to onboard mic. + For ``input_thru_source`` matching ``input_engine_source`` is preferred to line in. + +.. py:function:: input_engine_set_source(source : int) -> int + + Set up a continuous connection query for routing the given source to the input for the audio engines. + Check for success with ``input_engine_get_source()`` and clean up by passing ``INPUT_SOURCE_NONE`` + +.. py:function:: input_engine_get_target_source() -> int + + Returns target source last set with input_engine_set_source. + +.. py:function:: input_engine_get_source() -> int + + Returns source currently connected to the audio engines. + +.. py:function:: input_engine_get_source_avail(source : int) -> bool + + Returns true if it is currently possible to connect the audio engines to a given source. + If given ``INPUT_SOURCE_AUTO`` returns true if any source can be connected to the engines. + +.. py:function:: input_thru_set_source(source : int) -> int + + Set up a continuous connection query for routing the given source to the output mixer of the codec. + Check for success with ``input_thru_get_source()`` and clean up by passing ``INPUT_SOURCE_NONE`` + +.. py:function:: input_thru_get_target_source() -> int + + Returns target source last set with input_thru_set_source. + +.. py:function:: input_thru_get_source() -> int + + Returns the source currently mixed directly to output. + +.. py:function:: input_get_source() -> int + + Returns the source the codec is connected to at the moment. + +.. py:function:: input_thru_get_source_avail(source : int) -> bool + + Returns true if it is currently possible to a given source to thru. + If given ``INPUT_SOURCE_AUTO`` returns true if any source can be connected to thru. + +.. py:function:: input_thru_set_volume_dB(vol_dB : float) +.. py:function:: input_thru_get_volume_dB() -> float +.. py:function:: input_thru_set_mute(mute : bool) +.. py:function:: input_thru_get_mute() -> bool + + Volume and mute control for input_thru. Please don't use this as a replacement for terminating + a connection, ``input_thru_set_source(audio.INPUT_SOURCE_NONE)`` instead! + +.. py:function:: input_line_in_get_allowed(mute : bool) +.. py:function:: input_headset_mic_get_allowed(mute : bool) +.. py:function:: input_onboard_mic_get_allowed(mute : bool) +.. py:function:: input_onboard_mic_to_speaker_get_allowed(mute : bool) + + Returns if the user has forbidden access to the resource. + +OS development +-------------- + +Many of these functions are available in three variants: headphone volume, +speaker volume, and volume. If :code:`headphones_are_connected()` returns 1 +the "headphone" variant is chosen, else the "speaker" variant is chosen. + .. py:function:: headphones_detection_override(enable : bool) If a sleeve contact mic doesn't pull the detection pin low enough the @@ -102,49 +230,19 @@ the "headphone" variant is chosen, else the "speaker" variant is chosen. :code:`audio_{headphones_/speaker_/}set_{maximum/minimum}_volume_` and 0 if in a fake mute condition. -.. py:function:: headphones_line_in_set_hardware_thru(enable : bool) -.. py:function:: speaker_line_in_set_hardware_thru(enable : bool) -.. py:function:: line_in_set_hardware_thru(enable : bool) - - These route whatever is on the line in port directly to the headphones or - speaker respectively (enable = 1), or don't (enable = 0). Is affected by mute - and coarse hardware volume settings, however software fine volume is not - applied. - - Good for testing, might deprecate later, idk~ - -.. py:function:: input_set_source(source : int) -.. py:function:: input_get_source() -> int - - The codec can transmit audio data from different sources. This function - enables one or no source as provided by the ``INPUT_SOURCE_*`` constants. - - Note: The onboard digital mic turns on an LED on the top board if it receives - a clock signal which is considered a good proxy for its capability of reading - data. - -.. py:data:: INPUT_SOURCE_NONE -.. py:data:: INPUT_SOURCE_LINE_IN -.. py:data:: INPUT_SOURCE_HEADSET_MIC -.. py:data:: INPUT_SOURCE_ONBOARD_MIC - -.. py:function:: headset_set_gain_dB(gain_dB : int) -.. py:function:: headset_get_gain_dB() -> int - - Hardware preamp gain, 0dB-50dB. TODO: figure out if int/float inconsistency - is a good thing here compared to all other _dB functions. - -.. py:function:: input_thru_set_volume_dB(vol_dB : float) -.. py:function:: input_thru_get_volume_dB() -> float -.. py:function:: input_thru_set_mute(mute : bool) -.. py:function:: input_thru_get_mute() -> bool +.. py:function:: headset_mic_set_gain_dB(gain_dB : float) +.. py:function:: headset_mic_get_gain_dB() -> float +.. py:function:: onboard_mic_set_gain_dB(gain_dB : float) +.. py:function:: onboard_mic_get_gain_dB() -> float +.. py:function:: line_in_set_gain_dB(gain_dB : float) +.. py:function:: line_in_get_gain_dB() -> float - You can route whatever source is selected with input_set_source() to - the audio output. Use these to control volume and mute. + Set and get gain for the respective input channels. .. py:function:: codec_i2c_write(reg : int, data : int) - Write audio codec register. Obviously very unsafe. Have fun. + Write audio codec register. Obviously very unsafe. Do not use in applications that you + distribute to users. This can fry your speakers with DC> Headphone port policy diff --git a/python_payload/apps/audio_config/__init__.py b/python_payload/apps/audio_config/__init__.py index 3045e4fe82bed567775eacd96a451ea7278026c3..3d2e73dbe854066cca76522eb69e3b3a44811db1 100644 --- a/python_payload/apps/audio_config/__init__.py +++ b/python_payload/apps/audio_config/__init__.py @@ -13,49 +13,69 @@ class Drawable: self.y = 0 self.font_size = 20 self.active = False - # override these w ur own val :3 - self.focused_widget = 2 self.mid_x = 30 self.num_widgets = 2 self.overhang = -70 self.line_height = 24 self.ctx = None self.press = press - - def draw_heading(self, label): + self.focus_pos_limit_min = -60 + self.focus_pos_limit_max = 60 + self.focus_pos_limit_first = -60 + self.focus_pos_limit_last = 80 + self.first_widget_pos = 0 + self.last_widget_pos = 0 + self.focus_widget_pos_min = 0 + self.focus_widget_pos_max = 0 + self._focus_widget = 2 + self._focus_widget_prev = 1 + + @property + def focus_widget(self): + return self._focus_widget + + @property + def focus_widget_prev(self): + return self._focus_widget_prev + + @focus_widget.setter + def focus_widget(self, val): + if val < 2: + val = 2 + if val > self.num_widgets - 1: + val = self.num_widgets - 1 + self._focus_widget_prev = self._focus_widget + self._focus_widget = val + + @property + def at_first_widget(self): + return self.focus_widget <= 2 + + @property + def at_last_widget(self): + return self.focus_widget >= (self.num_widgets - 1) + + def draw_heading(self, label, col=(0.8, 0.8, 0.8), embiggen=6, margin=2): ctx = self.ctx if ctx is None: return self.widget_no += 1 - if not self.active: - if self.press.select_pressed and self.focused_widget > 0: - self.active = True - self.press.select_pressed = False - elif self.press.left_pressed: - self.focused_widget -= 1 - if self.focused_widget < 2: - self.focused_widget = 2 - self.press.left_pressed = False - elif self.press.right_pressed: - self.focused_widget += 1 - if self.focused_widget > self.num_widgets - 1: - self.focused_widget = self.num_widgets - 1 - self.press.right_pressed = False - if self.widget_no == self.focused_widget and not self.active: - ctx.rectangle(-130, int(self.y - self.font_size * 0.8), 260, self.font_size) - ctx.line_width = 2.0 - ctx.rgba(*colours.GO_GREEN, 1.0) - ctx.stroke() + self.y += embiggen + margin + if self.widget_no == self.focus_widget: + if self.focus_widget > self.focus_widget_prev: + self.focus_widget += 1 + else: + self.focus_widget -= 1 ctx.gray(1) ctx.move_to(self.mid_x, self.y) ctx.save() - ctx.rgb(0.8, 0.8, 0.8) + ctx.rgb(*col) ctx.move_to(0, self.y) ctx.text_align = ctx.CENTER - ctx.font_size += 6 + ctx.font_size += embiggen ctx.text(label) ctx.restore() - self.y += self.line_height + 8 + self.y += self.line_height + embiggen + margin def draw_widget(self, label): ctx = self.ctx @@ -63,24 +83,25 @@ class Drawable: return self.widget_no += 1 if not self.active: - if self.press.select_pressed and self.focused_widget > 0: + if self.press.select_pressed and self.focus_widget > 0: self.active = True self.press.select_pressed = False elif self.press.left_pressed: - self.focused_widget -= 1 - if self.focused_widget < 2: - self.focused_widget = 2 + self.focus_widget -= 1 self.press.left_pressed = False elif self.press.right_pressed: - self.focused_widget += 1 - if self.focused_widget > self.num_widgets - 1: - self.focused_widget = self.num_widgets - 1 + self.focus_widget += 1 self.press.right_pressed = False - if self.widget_no == self.focused_widget and not self.active: - ctx.rectangle(-130, int(self.y - self.font_size * 0.8), 260, self.font_size) - ctx.line_width = 2.0 - ctx.rgba(*colours.GO_GREEN, 1.0) - ctx.stroke() + if self.widget_no == self.focus_widget: + self.focus_widget_pos_min = self.y + if not self.active: + ctx.rectangle( + -130, int(self.y - self.font_size * 0.8), 260, self.font_size + ) + ctx.line_width = 2.0 + ctx.rgba(*colours.GO_GREEN, 1.0) + ctx.stroke() + self.focus_widget_pos_max = self.y + self.line_height ctx.gray(1) ctx.move_to(self.mid_x, self.y) ctx.save() @@ -95,7 +116,7 @@ class Drawable: if ctx is None: return self.draw_widget(label) - if self.widget_no == self.focused_widget and self.active: + if self.widget_no == self.focus_widget and self.active: if self.press.left_pressed: no -= 1 if no < 0: @@ -108,7 +129,7 @@ class Drawable: self.active = False self.press.select_pressed = False for a in range(len(choices)): - if a == no and self.active and self.widget_no == self.focused_widget: + if a == no and self.active and self.widget_no == self.focus_widget: ctx.save() ctx.rgba(*colours.GO_GREEN, 1.0) ctx.rectangle( @@ -135,13 +156,13 @@ class Drawable: ctx.text(choices[a] + " ") return no - def draw_number(self, label, step_size, no, unit=""): + def draw_number(self, label, step_size, no, unit="", val_col=(0.8, 0.8, 0.8)): ctx = self.ctx if ctx is None: return self.draw_widget(label) ret = no - if self.widget_no == self.focused_widget and self.active: + if self.widget_no == self.focus_widget and self.active: if self.press.left_pressed: ret -= step_size elif self.press.right_pressed: @@ -150,7 +171,7 @@ class Drawable: self.active = False self.press.select_pressed = False - if self.active and self.widget_no == self.focused_widget: + if self.active and self.widget_no == self.focus_widget: ctx.save() ctx.rgba(*colours.GO_GREEN, 1.0) ctx.rectangle( @@ -160,24 +181,57 @@ class Drawable: self.font_size, ).stroke() ctx.restore() - ctx.text(str(no)[:4] + unit) - else: - ctx.text(str(no)[:4] + unit) + + ctx.save() + ctx.rgb(*val_col) + ctx.text(str(no)[:4] + unit) + ctx.restore() return ret - def draw_boolean(self, label, value, on_str="on", off_str="off"): + def draw_boolean( + self, + label, + value, + on_str="on", + off_str="off", + val_col=(0.8, 0.8, 0.8), + on_hint=None, + off_hint=None, + ): ctx = self.ctx if ctx is None: return self.draw_widget(label) - if self.widget_no == self.focused_widget and self.active: + if self.widget_no == self.focus_widget and self.active: value = not value self.active = False + ctx.save() + ctx.rgb(*val_col) if value: ctx.text(on_str) else: ctx.text(off_str) + ctx.restore() + if self.widget_no == self.focus_widget: + if value: + hint = on_hint + else: + hint = off_hint + if hint is not None: + ctx.save() + ctx.font_size -= 4 + ctx.text_align = ctx.CENTER + ctx.rgb(0.9, 0.9, 0.9) + lines = hint.split("\n") + self.y -= 3 + for line in lines: + ctx.move_to(0, self.y) + ctx.text(line) + self.y += self.line_height - 5 + ctx.restore() + if self.y > 115: + self.focus_widget_pos_max = self.y return value def draw_bg(self): @@ -186,25 +240,29 @@ class Drawable: return ctx.gray(1.0) ctx.move_to(-100, -80) - wig = self.focused_widget - 1 - if wig < 2: - wig = 2 - if wig > self.num_widgets - 3: - wig = self.num_widgets - 3 - focus_pos = self.overhang + (wig - 0.5) * self.line_height - if focus_pos > 40: - self.overhang -= 7 - if focus_pos < -40: - self.overhang += 7 + scroll_val = 0 + scroll_speed = 7 + if self.at_last_widget: + if self.focus_widget_pos_max > self.focus_pos_limit_last: + scroll_val = -self.focus_widget_pos_max + self.focus_pos_limit_last + elif self.at_first_widget: + if self.focus_widget_pos_min < self.focus_pos_limit_first: + scroll_val = 9999 + elif self.focus_widget_pos_max > self.focus_pos_limit_max: + scroll_val = -9999 + elif self.focus_widget_pos_min < self.focus_pos_limit_min: + scroll_val = 9999 + + if scroll_val > 0: + self.overhang += min(scroll_val, scroll_speed) + else: + self.overhang += max(scroll_val, -scroll_speed) + self.y = self.overhang self.widget_no = 0 ctx.rectangle(-120, -120, 240, 240) ctx.gray(0) ctx.fill() - ctx.save() - ctx.font_size = 20 - ctx.gray(0.8) - ctx.restore() ctx.font_size = self.font_size @@ -226,9 +284,12 @@ class SpeakerMenu(Submenu): def __init__(self, press): super().__init__(press) self.num_widgets = 6 - self.focused_widget = 2 self.overhang = -40 self.mid_x = 50 + self.focus_pos_limit_min = -100 + self.focus_pos_limit_max = 100 + self.focus_pos_limit_first = -100 + self.focus_pos_limit_last = 100 def _draw(self, ctx): self.ctx = ctx @@ -272,9 +333,12 @@ class HeadphonesMenu(Submenu): def __init__(self, press): super().__init__(press) self.num_widgets = 5 - self.focused_widget = 2 self.overhang = -40 self.mid_x = 50 + self.focus_pos_limit_min = -100 + self.focus_pos_limit_max = 100 + self.focus_pos_limit_first = -100 + self.focus_pos_limit_last = 100 def _draw(self, ctx): self.ctx = ctx @@ -316,9 +380,12 @@ class VolumeControlMenu(Submenu): def __init__(self, press): super().__init__(press) self.num_widgets = 6 - self.focused_widget = 2 self.overhang = -40 self.mid_x = 25 + self.focus_pos_limit_min = -100 + self.focus_pos_limit_max = 100 + self.focus_pos_limit_first = -100 + self.focus_pos_limit_last = 100 def _draw(self, ctx): self.ctx = ctx @@ -370,21 +437,121 @@ class VolumeControlMenu(Submenu): class InputMenu(Submenu): def __init__(self, press): super().__init__(press) - self.num_widgets = 6 - self.focused_widget = 2 - self.overhang = -40 - self.mid_x = 50 + self.num_widgets = 11 + self.overhang = -85 + self.mid_x = 0 def _draw(self, ctx): self.ctx = ctx self.draw_bg() - self.draw_heading("inputs") + + avail_col = (0.0, 0.9, 0.6) + warn_col = (0.9, 0.0, 0.0) + allow_col = (0.0, 0.7, 0.5) + not_allow_col = (0.8, 0.3, 0.3) + not_avail_col = (0.6, 0.6, 0.6) + + self.draw_heading("line in", embiggen=5, margin=0) + if audio.line_in_get_allowed(): + if audio.input_engine_get_source_avail(audio.INPUT_SOURCE_LINE_IN): + col = avail_col + else: + col = allow_col + else: + col = not_allow_col + tmp = self.draw_boolean( + "line in", + settings.onoff_line_in_allowed.value, + on_str="allowed", + off_str="blocked", + val_col=col, + ) + if settings.onoff_line_in_allowed.value != tmp: + audio.line_in_set_allowed(tmp) + settings.onoff_line_in_allowed.set_value(tmp) + + tmp = self.draw_number( + "gain", + 1.5, + float(settings.num_line_in_gain_db.value), + unit="dB", + ) + if settings.num_line_in_gain_db.value != tmp: + audio.line_in_set_gain_dB(tmp) + settings.num_line_in_gain_db.set_value(audio.line_in_get_gain_dB()) + + self.draw_heading("headset mic", embiggen=5, margin=0) + + if audio.headset_mic_get_allowed(): + if audio.input_engine_get_source_avail(audio.INPUT_SOURCE_HEADSET_MIC): + col = avail_col + else: + col = allow_col + else: + col = not_allow_col + tmp = self.draw_boolean( + "access", + settings.onoff_headset_mic_allowed.value, + on_str="allowed", + off_str="blocked", + val_col=col, + ) + if settings.onoff_headset_mic_allowed.value != tmp: + audio.headset_mic_set_allowed(tmp) + settings.onoff_headset_mic_allowed.set_value(tmp) + tmp = self.draw_number( - "headset gain", 1, int(settings.num_headset_gain_db.value), unit="dB" + "gain", + 1.5, + float(settings.num_headset_mic_gain_db.value), + unit="dB", + ) + if settings.num_headset_mic_gain_db.value != tmp: + tmp = audio.headset_mic_set_gain_dB(tmp) + settings.num_headset_mic_gain_db.set_value(tmp) + + self.draw_heading("onboard mic", embiggen=5, margin=0) + + if audio.onboard_mic_get_allowed(): + col = avail_col + else: + col = not_allow_col + tmp = self.draw_boolean( + "access", + settings.onoff_onboard_mic_allowed.value, + on_str="allowed", + off_str="blocked", + val_col=col, + ) + if settings.onoff_onboard_mic_allowed.value != tmp: + audio.onboard_mic_set_allowed(tmp) + settings.onoff_onboard_mic_allowed.set_value(tmp) + + tmp = self.draw_number( + "gain", + 1.5, + float(settings.num_onboard_mic_gain_db.value), + unit="dB", + ) + if settings.num_onboard_mic_gain_db.value != tmp: + tmp = audio.onboard_mic_set_gain_dB(tmp) + settings.num_onboard_mic_gain_db.set_value(tmp) + + if not audio.onboard_mic_to_speaker_get_allowed(): + col = not_allow_col + else: + col = warn_col + tmp = self.draw_boolean( + "thru", + settings.onoff_onboard_mic_to_speaker_allowed.value, + on_str="allow", + off_str="phones", + val_col=col, + on_hint=" /!\ feedback possible /!\ ", ) - if settings.num_headset_gain_db.value != tmp: - audio.headset_set_gain_dB(int(tmp)) - settings.num_headset_gain_db.set_value(audio.headset_get_gain_dB()) + if settings.onoff_onboard_mic_to_speaker_allowed.value != tmp: + audio.onboard_mic_to_speaker_set_allowed(tmp) + settings.onoff_onboard_mic_to_speaker_allowed.set_value(tmp) class Press: diff --git a/python_payload/apps/audio_passthrough/__init__.py b/python_payload/apps/audio_passthrough/__init__.py index 9614e0be48f00f127b69ed7fb7733e76418dab1a..f9802882da492192c98b2a223329ac989181e5c0 100644 --- a/python_payload/apps/audio_passthrough/__init__.py +++ b/python_payload/apps/audio_passthrough/__init__.py @@ -8,14 +8,14 @@ import math # Assume this is an enum -ForceModes = ["AUTO", "FORCE_LINE_IN", "FORCE_LINE_OUT", "FORCE_MIC", "FORCE_NONE"] +ForceModes = ["AUTO", "FORCE_LINE_IN", "FORCE_LINE_OUT", "FORCE_MIC"] STATE_TEXT: dict[int, str] = { - audio.INPUT_SOURCE_HEADSET_MIC: "using headset mic (line out)", - audio.INPUT_SOURCE_LINE_IN: "using line in", - audio.INPUT_SOURCE_ONBOARD_MIC: "using onboard mic", - audio.INPUT_SOURCE_NONE: "plug cable to line in/out", + audio.INPUT_SOURCE_AUTO: "auto", + audio.INPUT_SOURCE_HEADSET_MIC: "headset mic", + audio.INPUT_SOURCE_LINE_IN: "line in", + audio.INPUT_SOURCE_ONBOARD_MIC: "onboard mic", } @@ -25,6 +25,9 @@ class AudioPassthrough(Application): self._button_0_pressed = False self._button_5_pressed = False self._force_mode: str = "AUTO" + self._mute = True + self._source = None + self.target_source = audio.INPUT_SOURCE_AUTO def on_enter(self, vm: Optional[ViewManager]) -> None: super().on_enter(vm) @@ -53,36 +56,42 @@ class AudioPassthrough(Application): ctx.font_size = 25 ctx.move_to(0, 0) ctx.save() - if audio.input_thru_get_mute(): + if self._mute: # 0xff4500, red ctx.rgb(1, 0.41, 0) else: # 0x3cb043, green ctx.rgb(0.24, 0.69, 0.26) - ctx.text("passthrough off" if audio.input_thru_get_mute() else "passthrough on") + ctx.text("passthrough off" if self._mute else "passthrough on") ctx.restore() # bottom text ctx.move_to(0, 25) ctx.save() ctx.font_size = 15 - ctx.text(STATE_TEXT.get(audio.input_get_source(), "")) + ctx.text(STATE_TEXT.get(self.target_source, "")) - # have red text when sleep mode isn't auto - if self._force_mode != "AUTO": + ctx.move_to(0, 40) + if self.source_connected: + # 0x3cb043, green + ctx.rgb(0.24, 0.69, 0.26) + else: # 0xff4500, red ctx.rgb(1, 0.41, 0) - - ctx.move_to(0, 40) - ctx.text("(auto)" if self._force_mode == "AUTO" else "(forced)") - - # mic has a loopback risk so has precautions - # so we warn users about it to not confuse them - if self._force_mode == "FORCE_MIC": - ctx.move_to(0, 55) - ctx.text("headphones only") - ctx.move_to(0, 70) - ctx.text("will not persist app exit") + if self._mute: + ctx.text("standby") + elif self._force_mode == "AUTO": + src = audio.input_thru_get_source() + if src != audio.INPUT_SOURCE_NONE: + ctx.text("connected to") + ctx.move_to(0, 56) + ctx.text(STATE_TEXT.get(src, "")) + else: + ctx.text("waiting...") + elif self._force_mode == "FORCE_MIC": + ctx.text("connected" if self.source_connected else "(headphones only)") + else: + ctx.text("connected" if self.source_connected else "waiting...") ctx.restore() # bottom button @@ -94,48 +103,58 @@ class AudioPassthrough(Application): ctx.restore() ctx.move_to(0, 90) - ctx.text("force line in/out") + ctx.text("next source") - def on_exit(self) -> None: - # Mic passthrough has a loopback risk - if self._force_mode == "FORCE_MIC": - self._force_mode = "FORCE_NONE" - audio.input_set_source(audio.INPUT_SOURCE_NONE) - audio.input_thru_set_mute(True) + @property + def source_connected(self): + if self.source != audio.INPUT_SOURCE_NONE: + return self.source == audio.input_thru_get_source() + else: + return False + + @property + def source(self): + if self._source is None: + self._source = audio.input_thru_get_source() + return self._source + + @source.setter + def source(self, source): + audio.input_thru_set_source(source) + self._source = audio.input_thru_get_source() def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) - headset_connected = audio.headset_is_connected() - if self._force_mode == "FORCE_MIC": - audio.input_set_source(audio.INPUT_SOURCE_ONBOARD_MIC) - elif ( - audio.line_in_is_connected() and self._force_mode == "AUTO" - ) or self._force_mode == "FORCE_LINE_IN": - audio.input_set_source(audio.INPUT_SOURCE_LINE_IN) - elif headset_connected or self._force_mode == "FORCE_LINE_OUT": - audio.input_set_source(audio.INPUT_SOURCE_HEADSET_MIC) - else: - audio.input_set_source(audio.INPUT_SOURCE_NONE) - if ins.captouch.petals[0].pressed: if not self._button_0_pressed: self._button_0_pressed = True - audio.input_thru_set_mute(not audio.input_thru_get_mute()) + self._mute = not self._mute else: self._button_0_pressed = False if ins.captouch.petals[5].pressed: if not self._button_5_pressed: self._button_5_pressed = True - self._force_mode = ForceModes[ForceModes.index(self._force_mode) + 1] - if ForceModes.index(self._force_mode) >= ForceModes.index("FORCE_NONE"): - self._force_mode = "AUTO" + index = ForceModes.index(self._force_mode) + index = (index + 1) % 4 + self._force_mode = ForceModes[index] + else: self._button_5_pressed = False - if self._force_mode == "FORCE_MIC" and not audio.headphones_are_connected(): - self._force_mode = "AUTO" + if self._mute: + self.source = audio.INPUT_SOURCE_NONE + else: + if self._force_mode == "FORCE_MIC": + self.target_source = audio.INPUT_SOURCE_ONBOARD_MIC + elif self._force_mode == "AUTO": + self.target_source = audio.INPUT_SOURCE_AUTO + elif self._force_mode == "FORCE_LINE_IN": + self.target_source = audio.INPUT_SOURCE_LINE_IN + elif self._force_mode == "FORCE_LINE_OUT": + self.target_source = audio.INPUT_SOURCE_HEADSET_MIC + self.source = self.target_source # For running with `mpremote run`: diff --git a/python_payload/apps/tiny_sampler/__init__.py b/python_payload/apps/tiny_sampler/__init__.py index eac6d1bcdaac7ebc3c959145323c283088484b5c..ae63c26338beee239a2bb531ed1be621a7c0018d 100644 --- a/python_payload/apps/tiny_sampler/__init__.py +++ b/python_payload/apps/tiny_sampler/__init__.py @@ -34,7 +34,6 @@ class TinySampler(Application): self.blm = bl00mbox.Channel("tiny sampler") self.samplers: List[bl00mbox.patches._Patch | Any] = [None] * 5 self.line_in = self.blm.new(bl00mbox.plugins.bl00mbox_line_in) - self.line_in.signals.gain = 32000 for i in range(5): self.samplers[i] = self.blm.new(bl00mbox.patches.sampler, 1000) self.samplers[i].signals.output = self.blm.mixer @@ -208,6 +207,20 @@ class TinySampler(Application): elif not ct.petals[i].pressed and self.ct_prev.petals[i].pressed: self.release_event[i] = True + if self.mode == 0: + if self.orig_source == audio.INPUT_SOURCE_NONE: + if audio.input_engine_get_source_avail(audio.INPUT_SOURCE_ONBOARD_MIC): + audio.input_engine_set_source(audio.INPUT_SOURCE_ONBOARD_MIC) + else: + audio.input_engine_set_source(audio.INPUT_SOURCE_AUTO) + else: + if audio.input_engine_get_source_avail(self.orig_source): + audio.input_engine_set_source(audio.self.orig_source) + else: + audio.input_engine_set_source(audio.INPUT_SOURCE_AUTO) + else: + audio.input_engine_set_source(audio.INPUT_SOURCE_NONE) + if self.mode == 0 or release_all: for i in range(5): if not self.is_recording[i]: @@ -268,18 +281,18 @@ class TinySampler(Application): self.ct_prev = ct def on_enter(self, vm) -> None: - self.mode = 0 super().on_enter(vm) - audio.input_set_source(audio.INPUT_SOURCE_ONBOARD_MIC) + self.mode = 0 + self.orig_source = audio.input_engine_get_source() if self.blm is None: self._build_synth() def on_exit(self) -> None: + audio.input_engine_set_source(self.orig_source) for i in range(5): if self.is_recording[i]: self.samplers[i].signals.rec_trigger.stop() self.is_recording[i] = False - audio.input_set_source(audio.INPUT_SOURCE_NONE) if self.blm is not None: self.blm.clear() self.blm.free = True diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py index 9461fb10cd85be0041bfa3b36b7ed5629718ccfc..cad161730a285e4a95e88e09d15aa7a2bc7a3dae 100644 --- a/python_payload/st3m/run.py +++ b/python_payload/st3m/run.py @@ -186,6 +186,18 @@ def run_main() -> None: audio.speaker_set_minimum_volume_dB(settings.num_speaker_min_db.value) audio.headphones_set_maximum_volume_dB(settings.num_headphones_max_db.value) audio.speaker_set_maximum_volume_dB(settings.num_speaker_max_db.value) + + audio.headset_mic_set_gain_dB(settings.num_headset_mic_gain_db.value) + audio.onboard_mic_set_gain_dB(settings.num_onboard_mic_gain_db.value) + audio.line_in_set_gain_dB(settings.num_line_in_gain_db.value) + + audio.headset_mic_set_allowed(settings.onoff_headset_mic_allowed.value) + audio.onboard_mic_set_allowed(settings.onoff_onboard_mic_allowed.value) + audio.line_in_set_allowed(settings.onoff_line_in_allowed.value) + audio.onboard_mic_to_speaker_set_allowed( + settings.onoff_onboard_mic_to_speaker_allowed.value + ) + leds.set_brightness(settings.num_leds_brightness.value) sys_display.set_backlight(settings.num_display_brightness.value) diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py index ddd70372de392db805ddc8cab87f6ff4c77b818c..554a353d32ec2b9d48389e648a36491205039bfe 100644 --- a/python_payload/st3m/settings.py +++ b/python_payload/st3m/settings.py @@ -241,9 +241,29 @@ num_speaker_max_db = StringTunable( ) onoff_speaker_eq_on = StringTunable("Speaker EQ On", "system.audio.speaker_eq_on", True) +onoff_headset_mic_allowed = StringTunable( + "Headset Mic Allowed", "system.audio.headset_mic_allowed", True +) +onoff_onboard_mic_allowed = StringTunable( + "Onboard Mic Allowed", "system.audio.onboard_mic_allowed", True +) +onoff_line_in_allowed = StringTunable( + "Line In Allowed", "system.audio.line_in_allowed", True +) +onoff_onboard_mic_to_speaker_allowed = StringTunable( + "Onboard Mic To Speaker Allowed", + "system.audio.onboard_mic_to_speaker_allowed", + False, +) -num_headset_gain_db = StringTunable( - "Headset Mic Gain dB", "system.audio.headset_gain_dB", 10 +num_headset_mic_gain_db = StringTunable( + "Headset Mic Gain dB", "system.audio.headset_mic_gain_dB", 0 +) +num_onboard_mic_gain_db = StringTunable( + "Onboard Mic Gain dB", "system.audio.onboard_mic_gain_dB", 0 +) +num_line_in_gain_db = StringTunable( + "Line In Gain dB", "system.audio.line_in_gain_dB", 0 ) num_display_brightness = StringTunable( @@ -282,7 +302,13 @@ load_save_settings: List[UnaryTunable] = [ num_headphones_max_db, num_speaker_max_db, onoff_speaker_eq_on, - num_headset_gain_db, + onoff_headset_mic_allowed, + onoff_onboard_mic_allowed, + onoff_line_in_allowed, + onoff_onboard_mic_to_speaker_allowed, + num_headset_mic_gain_db, + num_onboard_mic_gain_db, + num_line_in_gain_db, num_display_brightness, num_leds_brightness, num_leds_speed, diff --git a/sim/fakes/audio.py b/sim/fakes/audio.py index 23dfab45b36c72e99be45efb1c0e0340e1e53d6b..1bcae08891e46314834c1499a25f0b332702f310 100644 --- a/sim/fakes/audio.py +++ b/sim/fakes/audio.py @@ -71,9 +71,57 @@ def get_mute() -> bool: return _muted -def headset_set_gain_dB(v: float) -> None: +def headset_mic_set_gain_dB(v: float) -> None: pass -def headset_get_gain_dB() -> float: +def headset_mic_get_gain_dB() -> float: return 10 + + +def onboard_mic_set_gain_dB(v: float) -> None: + pass + + +def onboard_mic_get_gain_dB() -> float: + return 10 + + +def line_in_set_gain_dB(v: float) -> None: + pass + + +def line_in_get_gain_dB() -> float: + return 10 + + +def headset_mic_set_allowed(v: bool) -> None: + pass + + +def onboard_mic_set_allowed(v: bool) -> None: + pass + + +def line_in_set_allowed(v: bool) -> None: + pass + + +def onboard_mic_to_speaker_set_allowed(v: bool) -> None: + pass + + +def headget_mic_get_allowed() -> bool: + return True + + +def onboard_mic_get_allowed() -> bool: + return True + + +def line_in_get_allowed() -> bool: + return True + + +def onboard_mic_to_speaker_get_allowed() -> bool: + return False