From b98d5b1f8e687b6b7c920d1e0a70f99bbbf52e9f Mon Sep 17 00:00:00 2001 From: moon2 <moon2protonmail@protonmail.com> Date: Thu, 2 May 2024 22:22:23 +0200 Subject: [PATCH] bl00mbox update: - system volume per channel - rms tracking per channel --- components/bl00mbox/bl00mbox_audio.c | 65 +++++++++-- components/bl00mbox/include/bl00mbox_audio.h | 8 ++ .../bl00mbox/micropython/bl00mbox/_user.py | 110 +++++++++++++++++- .../bl00mbox/micropython/mp_sys_bl00mbox.c | 46 ++++++++ 4 files changed, 214 insertions(+), 15 deletions(-) diff --git a/components/bl00mbox/bl00mbox_audio.c b/components/bl00mbox/bl00mbox_audio.c index 5f224e1eab..d7e0f9a549 100644 --- a/components/bl00mbox/bl00mbox_audio.c +++ b/components/bl00mbox/bl00mbox_audio.c @@ -129,6 +129,7 @@ void bl00mbox_channels_init(){ for(uint8_t i = 0; i < BL00MBOX_CHANNELS; i++){ bl00mbox_channel_t * chan = bl00mbox_get_channel(i); chan->volume = BL00MBOX_DEFAULT_CHANNEL_VOLUME; + chan->sys_gain = 4096; chan->root_list = NULL; chan->buds = NULL; chan->connections = NULL; @@ -153,6 +154,37 @@ void bl00mbox_channel_disable(uint8_t chan){ ch->is_active = false; } +void bl00mbox_channel_set_compute_mean_square(uint8_t chan, bool compute){ + if(chan >= (BL00MBOX_CHANNELS)) return; + bl00mbox_channel_t * ch = bl00mbox_get_channel(chan); + ch->compute_mean_square = compute; + if(!compute) ch->mean_square = 0; +} + +bool bl00mbox_channel_get_compute_mean_square(uint8_t chan){ + if(chan >= (BL00MBOX_CHANNELS)) return 0; + bl00mbox_channel_t * ch = bl00mbox_get_channel(chan); + return ch->compute_mean_square; +} + +uint32_t bl00mbox_channel_get_mean_square(uint8_t chan){ + if(chan >= (BL00MBOX_CHANNELS)) return 0; + bl00mbox_channel_t * ch = bl00mbox_get_channel(chan); + return ch->mean_square; +} + +void bl00mbox_channel_set_sys_gain(uint8_t chan, int16_t volume){ + if(chan >= (BL00MBOX_CHANNELS)) return; + bl00mbox_channel_t * ch = bl00mbox_get_channel(chan); + ch->sys_gain = volume; +} + +int16_t bl00mbox_channel_get_sys_gain(uint8_t chan){ + if(chan >= (BL00MBOX_CHANNELS)) return 0; + bl00mbox_channel_t * ch = bl00mbox_get_channel(chan); + return ch->sys_gain; +} + void bl00mbox_channel_set_volume(uint8_t chan, uint16_t volume){ if(chan >= (BL00MBOX_CHANNELS)) return; bl00mbox_channel_t * ch = bl00mbox_get_channel(chan); @@ -180,19 +212,22 @@ static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * o if(render_pass_id == chan->render_pass_id) return false; chan->render_pass_id = render_pass_id; - bl00mbox_bud_list_t * always = chan->always_render; - while(always != NULL){ - bl00mbox_audio_bud_render(always->bud); - always = always ->next; - } - bl00mbox_channel_root_t * root = chan->root_list; - // early exit when no sources: - if((root == NULL) || (!chan->is_active)){ + int32_t vol = radspa_mult_shift(chan->volume, chan->sys_gain); + + // early exit when no sources or muted: + if((root == NULL) || (!chan->is_active) || (!vol)){ return false; } + // turns out always is conditional too :D + bl00mbox_bud_list_t * always = chan->always_render; + while(always != NULL){ + bl00mbox_audio_bud_render(always->bud); + always = always->next; + } + int32_t acc[full_buffer_len]; bool acc_init = false; @@ -235,14 +270,22 @@ static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * o acc[i] -= (chan->dc >> 12); } - if(adding){ for(uint16_t i = 0; i < full_buffer_len; i++){ - out[i] = radspa_add_sat(radspa_mult_shift(acc[i], chan->volume), out[i]); + out[i] = radspa_add_sat(radspa_gain(acc[i], vol), out[i]); } } else { for(uint16_t i = 0; i < full_buffer_len; i++){ - out[i] = radspa_mult_shift(acc[i], chan->volume); + out[i] = radspa_gain(acc[i], vol); + } + } + if(chan->compute_mean_square){ + uint32_t decay = 1000000UL; + for(uint16_t i = 0; i < full_buffer_len; i++){ + int32_t sq = acc[i]; + sq *= sq; + chan->mean_square = (((uint64_t) chan->mean_square) * (UINT32_MAX-decay-1)) >> 32; + chan->mean_square += (((uint64_t) sq)*decay) >> 32; } } return true; diff --git a/components/bl00mbox/include/bl00mbox_audio.h b/components/bl00mbox/include/bl00mbox_audio.h index d532b591cc..18cd250e1f 100644 --- a/components/bl00mbox/include/bl00mbox_audio.h +++ b/components/bl00mbox/include/bl00mbox_audio.h @@ -64,8 +64,11 @@ typedef struct _bl00mbox_channel_root_t{ typedef struct{ bool is_active; // rendering can be skipped if false bool is_free; + bool compute_mean_square; + uint32_t mean_square; char * name; int32_t volume; + int32_t sys_gain; int32_t dc; struct _bl00mbox_channel_root_t * root_list; // list of all roots associated with channels uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary @@ -81,6 +84,11 @@ void bl00mbox_channel_enable(uint8_t chan); void bl00mbox_channel_disable(uint8_t chan); void bl00mbox_channel_set_volume(uint8_t chan, uint16_t volume); int16_t bl00mbox_channel_get_volume(uint8_t chan); +void bl00mbox_channel_set_sys_gain(uint8_t chan, int16_t volume); +int16_t bl00mbox_channel_get_sys_gain(uint8_t chan); +void bl00mbox_channel_set_compute_mean_square(uint8_t chan, bool compute); +bool bl00mbox_channel_get_compute_mean_square(uint8_t chan); +uint32_t bl00mbox_channel_get_mean_square(uint8_t chan); void bl00mbox_channel_event(uint8_t chan); uint8_t bl00mbox_channel_get_free_index(); void bl00mbox_channels_init(); diff --git a/components/bl00mbox/micropython/bl00mbox/_user.py b/components/bl00mbox/micropython/bl00mbox/_user.py index 1885cef2b4..698be81259 100644 --- a/components/bl00mbox/micropython/bl00mbox/_user.py +++ b/components/bl00mbox/micropython/bl00mbox/_user.py @@ -555,6 +555,15 @@ class SignalList: raise AttributeError("signal does not exist") +_channel_init_callback = None +_rms_base = 3 - 10 * math.log(32767 * 32767, 10) + + +def set_channel_init_callback(callback): + global _channel_init_callback + _channel_init_callback = callback + + class Channel: def __init__(self, name=None): if name == None: @@ -564,12 +573,16 @@ class Channel: self._channel_num = sys_bl00mbox.channel_get_free_index() self.name = name elif type(name) == int: - if (int(name) < sys_bl00mbox.NUM_CHANNELS) and (int(name >= 0)): - self._channel_num = int(name) + num = int(name) + if num < sys_bl00mbox.NUM_CHANNELS and num >= 0: + self._channel_num = num else: self._channel_num = sys_bl00mbox.NUM_CHANNELS - 1 # garbage channel else: self._channel_num = sys_bl00mbox.NUM_CHANNELS - 1 # garbage channel + global _channel_init_callback + if _channel_init_callback is not None: + _channel_init_callback(self) def __repr__(self): ret = "[channel " + str(self.channel_num) @@ -580,8 +593,8 @@ class Channel: ret += " (foreground)" if self.background_mute_override: ret += " (background mute override)" - ret += "\n volume: " + str(self.volume) - b = sys_bl00mbox.channel_buds_num(self.channel_num) + ret += "\n gain: " + str(self.gain_dB) + "dB" + b = self.num_plugins ret += "\n plugins: " + str(b) if len(self.plugins) != b: ret += " (desync" + str(len(self.plugins)) + ")" @@ -591,6 +604,10 @@ class Channel: def clear(self): sys_bl00mbox.channel_clear(self.channel_num) + @property + def num_plugins(self): + return sys_bl00mbox.channel_buds_num(self.channel_num) + @property def name(self): if self._channel_num == 0: @@ -673,8 +690,49 @@ class Channel: @volume.setter def volume(self, value): + value = min(32767, max(-32767, value)) sys_bl00mbox.channel_set_volume(self.channel_num, value) + @property + def gain_dB(self): + ret = sys_bl00mbox.channel_get_volume(self.channel_num) + if ret == 0: + return -math.inf + else: + return 20 * math.log(abs(ret) / 32768, 10) + + @gain_dB.setter + def gain_dB(self, value): + if value == -math.inf: + value = 0 + else: + value = int(32768 * (10 ** (value / 20))) + value = min(32767, max(0, value)) + sys_bl00mbox.channel_set_volume(self.channel_num, value) + + @property + def compute_rms(self): + return sys_bl00mbox.channel_get_compute_mean_square(self.channel_num) + + @compute_rms.setter + def compute_rms(self, value): + sys_bl00mbox.channel_set_compute_mean_square(self.channel_num, value) + + @property + def rms_dB(self): + if self.compute_rms: + ret = sys_bl00mbox.channel_get_mean_square(self.channel_num) + if ret == 0: + return -math.inf + else: + mult = abs(sys_bl00mbox.channel_get_volume(self.channel_num)) / 32768 + if mult == 0: + return -math.inf + ret *= mult * mult + return 10 * math.log(ret, 10) + _rms_base + else: + return None + @property def mixer(self): return ChannelMixer(self) @@ -706,3 +764,47 @@ class Channel: sys_bl00mbox.channel_set_foreground(self.channel_num) elif sys_bl00mbox.channel_get_foreground() == self.channel_num: sys_bl00mbox.channel_set_foreground(0) + + +class SysChannel(Channel): + def __init__(self, index=0): + if index < sys_bl00mbox.NUM_CHANNELS and index >= 0: + self._channel_num = index + else: + raise Bl00mboxError(f"channel index {index} not found") + + @property + def sys_gain_dB(self): + ret = sys_bl00mbox.channel_get_sys_gain(self.channel_num) + if ret == 0: + return -math.inf + else: + try: + return 20 * math.log(abs(ret) / 4096, 10) + except: + return -math.inf + + @sys_gain_dB.setter + def sys_gain_dB(self, value): + if value == -math.inf: + value = 0 + else: + value = int(4096 * (10 ** (value / 20))) + value = min(32767, max(0, value)) + sys_bl00mbox.channel_set_sys_gain(self.channel_num, value) + + @property + def sys_rms_dB(self): + if self.compute_rms: + ret = sys_bl00mbox.channel_get_mean_square(self.channel_num) + if ret == 0: + return -math.inf + else: + mult = abs(sys_bl00mbox.channel_get_volume(self.channel_num)) / 32768 + mult *= abs(sys_bl00mbox.channel_get_sys_gain(self.channel_num)) / 4096 + if mult == 0: + return -math.inf + ret *= mult * mult + return 10 * math.log(ret, 10) + _rms_base + else: + return None diff --git a/components/bl00mbox/micropython/mp_sys_bl00mbox.c b/components/bl00mbox/micropython/mp_sys_bl00mbox.c index 4cac2890cb..7324036ef0 100644 --- a/components/bl00mbox/micropython/mp_sys_bl00mbox.c +++ b/components/bl00mbox/micropython/mp_sys_bl00mbox.c @@ -132,6 +132,42 @@ STATIC mp_obj_t mp_channel_get_volume(mp_obj_t chan) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_volume_obj, mp_channel_get_volume); +STATIC mp_obj_t mp_channel_set_sys_gain(mp_obj_t chan, mp_obj_t vol) { + bl00mbox_channel_set_sys_gain(mp_obj_get_int(chan), mp_obj_get_int(vol)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_sys_gain_obj, + mp_channel_set_sys_gain); + +STATIC mp_obj_t mp_channel_get_sys_gain(mp_obj_t chan) { + return mp_obj_new_int(bl00mbox_channel_get_sys_gain(mp_obj_get_int(chan))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_sys_gain_obj, + mp_channel_get_sys_gain); + +STATIC mp_obj_t mp_channel_set_compute_mean_square(mp_obj_t chan, + mp_obj_t vol) { + bl00mbox_channel_set_compute_mean_square(mp_obj_get_int(chan), + mp_obj_get_int(vol)); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_set_compute_mean_square_obj, + mp_channel_set_compute_mean_square); + +STATIC mp_obj_t mp_channel_get_compute_mean_square(mp_obj_t chan) { + return mp_obj_new_bool( + bl00mbox_channel_get_compute_mean_square(mp_obj_get_int(chan))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_compute_mean_square_obj, + mp_channel_get_compute_mean_square); + +STATIC mp_obj_t mp_channel_get_mean_square(mp_obj_t chan) { + return mp_obj_new_int( + bl00mbox_channel_get_mean_square(mp_obj_get_int(chan))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_channel_get_mean_square_obj, + mp_channel_get_mean_square); + STATIC mp_obj_t mp_channel_set_name(mp_obj_t chan, mp_obj_t name) { char *tmp = strdup(mp_obj_str_get_str(name)); bl00mbox_channel_set_name(mp_obj_get_int(chan), tmp); @@ -534,6 +570,16 @@ STATIC const mp_map_elem_t bl00mbox_globals_table[] = { MP_ROM_PTR(&mp_channel_set_volume_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_get_volume), MP_ROM_PTR(&mp_channel_get_volume_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_set_sys_gain), + MP_ROM_PTR(&mp_channel_set_sys_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_get_sys_gain), + MP_ROM_PTR(&mp_channel_get_sys_gain_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_set_compute_mean_square), + MP_ROM_PTR(&mp_channel_set_compute_mean_square_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_get_compute_mean_square), + MP_ROM_PTR(&mp_channel_get_compute_mean_square_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_get_mean_square), + MP_ROM_PTR(&mp_channel_get_mean_square_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_set_name), MP_ROM_PTR(&mp_channel_set_name_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_get_name), -- GitLab