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