Skip to content
Snippets Groups Projects
Select Git revision
  • 71be1ddeb128e6068d2f1df605ec9c8c10f67091
  • master default protected
  • schneider/ir
  • rahix/user-space-ctx
  • schneider/iaq-python
  • schneider/ble-mini-demo
  • schneider/ble-ecg-stream-visu
  • schneider/mp-exception-print
  • schneider/sleep-display
  • schneider/deepsleep4
  • schneider/deepsleep2
  • schneider/deepsleep
  • schneider/ble-central
  • rahix/bluetooth-app-favorite
  • schneider/v1.17-changelog
  • schneider/ancs
  • schneider/png
  • schneider/freertos-list-debug
  • schneider/212-reset-hardware-when-entering-repl
  • schneider/bonding-fail-if-full
  • schneider/ble-fixes-2020-3
  • v1.18
  • v1.17
  • v1.16
  • v1.15
  • v1.14
  • v1.13
  • v1.12
  • v1.11
  • v1.10
  • v1.9
  • v1.8
  • v1.7
  • v1.6
  • v1.5
  • v1.4
  • v1.3
  • v1.2
  • v1.1
  • v1.0
  • release-1
41 results

group__SPIXFC__SPCTRL.html

Blame
  • mixer.py 12.37 KiB
    import bl00mbox
    from st3m import Responder, settings
    import math
    import media
    
    
    def recall_blm_channel_stats(blm_chan):
        if blm_chan.free:
            return
        if blm_chan.name in session_channel_vol_mute:
            chan = bl00mboxChannel(bl00mbox.SysChannel(blm_chan))
            vol, mute = session_channel_vol_mute[chan.get_name()]
            if mute:
                vol = -math.inf
            chan._set_vol_dB(vol)
    
    
    bl00mbox.set_channel_init_callback(recall_blm_channel_stats)
    
    
    class Channel:
        index = 0
        _vol = 0
    
        def get_name(self):
            return "dummy" + str(self.index)
    
        def get_num_plugins(self):
            return None
    
        def _get_vol_dB(self):
            return self._vol
    
        def _set_vol_dB(self, val):
            self._vol = val
    
        def __init__(self):
            self.mute = False
    
        def get_rms_dB(self):
            return -math.inf
    
        def get_mute(self):
            mute = False
            if self.get_name() in session_channel_vol_mute:
                _, mute = session_channel_vol_mute[self.get_name()]
            return bool(mute)
    
        def set_mute(self, mute):
            if self.get_name() in session_channel_vol_mute:
                vol, _ = session_channel_vol_mute[self.get_name()]
                if mute:
                    vol = self._get_vol_dB()
                    self._set_vol_dB(-math.inf)
                else:
                    self._set_vol_dB(vol)
                session_channel_vol_mute[self.get_name()] = vol, mute
            elif mute:
                vol = self._get_vol_dB()
                session_channel_vol_mute[self.get_name()] = vol, True
                self._set_vol_dB(-math.inf)
    
        def get_vol_dB(self):
            if self.get_mute():
                if self.get_name() in session_channel_vol_mute:
                    vol, _ = session_channel_vol_mute[self.get_name()]
                    return vol
                else:
                    return -math.inf
            else:
                return self._get_vol_dB()
    
        def set_vol_dB(self, vol):
            if not self.get_mute():
                self._set_vol_dB(vol)
            session_channel_vol_mute[self.get_name()] = vol, self.get_mute()
    
    
    class bl00mboxChannel(Channel):
        def __init__(self, chan):
            super().__init__()
            self.blm = chan
            self._name = chan.name
            self._num_plugins = chan.num_plugins
    
        def get_name(self):
            return self._name
    
        def get_num_plugins(self):
            return self._num_plugins
    
        def _get_vol_dB(self):
            return self.blm.sys_gain_dB
    
        def _set_vol_dB(self, vol):
            self.blm.sys_gain_dB = vol
    
        def get_rms_dB(self):
            return self.blm.sys_rms_dB
    
    
    class mediaChannel(Channel):
        def get_name(self):
            return "media"
    
        def _get_vol_dB(self):
            ret = media.get_volume()
            if ret == 0:
                return -math.inf
            else:
                return 20 * math.log(ret, 10)
    
        def _set_vol_dB(self, vol):
            if vol == -math.inf:
                vol = 0
            else:
                vol = 10 ** (vol / 20)
            media.set_volume(vol)
    
    
    session_channel_vol_mute = {}
    
    
    class ChannelColors:
        bg = (0.3, 0.3, 0.3)
        name = (0, 1, 1)
        vol_bar = (0, 1, 0)
        rms_bar = (1, 0, 0)
        mute_bg = ((0.2, 0.2, 0.2), (0.5, 0.0, 0.0))
        mute_fg = ((0.5, 0.5, 0.5), (0.8, 0.8, 0.8))
    
    
    class HighlightColors:
        bg = (0.5, 0.5, 0.5)
        name = (0, 1, 1)
        vol_bar = (0, 1, 0)
        rms_bar = (1, 0, 0)
        mute_bg = ((0.2, 0.2, 0.2), (0.8, 0.0, 0.0))
        mute_fg = ((0.5, 0.5, 0.5), (1.0, 1.0, 1.0))
    
    
    class AudioMixer(Responder):
        def __init__(self, inputcontroller):
            self.full_redraw = True
            self.input = inputcontroller
            self._pos = 0
            self.chan_width = 46  # scaling factor between _pos and draw_pos
            self._editing_channel = 0
            self.colors = ChannelColors()
            self.highlight_colors = HighlightColors()
    
            self._refresh()
            self._draw_pos = [
                self.chan_width * max(min((len(self._chans) - 1) / 2, 1), 0),
                0,
                0,
            ]  # includes also 1st and 2nd derivatives per second(^2)
    
            self.override_os_button_back = False
            repeat = settings.num_volume_repeat_ms.value
            repeat_wait = settings.num_volume_repeat_wait_ms.value
            self.vol_repeat = (repeat_wait, repeat)
            self.channel_select_repeat = (500, 300)
            self.volume_step_dB = settings.num_volume_step_db.value
            self.volume_repeat_step_dB = settings.num_volume_repeat_step_db.value
            self.input.buttons.app.right.repeat_enable(*self.channel_select_repeat)
            self.input.buttons.app.left.repeat_enable(*self.channel_select_repeat)
            self.acc_ms = 0
            # we don't have double-volume here yet so we share it with applications.
            # if it has been changed by an application we accept that as the new value
            # for now.
            if "media" in session_channel_vol_mute:
                vol, mute = session_channel_vol_mute["media"]
                mediavol = media.get_volume()
                if mute:
                    if mediavol != 0:
                        mute = False
                        vol = mediavol
                else:
                    vol = mediavol
                session_channel_vol_mute["media"] = (vol, mute)
    
        def _refresh(self):
            self._chans = [mediaChannel()]
            for c in bl00mbox.Sys.collect_channels(True):
                chan = bl00mboxChannel(c)
                chan.prev_compute_rms = chan.blm.compute_rms
                chan.blm.compute_rms = True
                self._chans += [chan]
            """
            for i in range(5):
                self._chans += [Channel()]
            """
            for i, chan in enumerate(self._chans):
                chan.index = i
            self._pos = min(self._pos, len(self._chans) - 1)
    
        def think(self, ins, delta_ms):
            self.override_os_button_back = bool(self._editing_channel)
            lr_dir = self.input.buttons.app.right.pressed
            lr_dir -= self.input.buttons.app.left.pressed
            num_chans = len(self._chans)
            if self.input.buttons.app.middle.pressed:
                self.full_redraw = True
                self._editing_channel += 1
                self._editing_channel %= 3
                if self._editing_channel == 1:
                    self.input.buttons.app.right.repeat_enable(*self.vol_repeat)
                    self.input.buttons.app.left.repeat_enable(*self.vol_repeat)
                elif not self._editing_channel:
                    self.input.buttons.app.right.repeat_enable(*self.channel_select_repeat)
                    self.input.buttons.app.left.repeat_enable(*self.channel_select_repeat)
            elif self._editing_channel == 1:
                lr_dir_repeat = self.input.buttons.app.right.repeated
                lr_dir_repeat -= self.input.buttons.app.left.repeated
                lr_dir_repeat *= self.volume_repeat_step_dB
                lr_dir *= self.volume_step_dB
                lr_dir += lr_dir_repeat
            if self._editing_channel:
                if self.input.buttons.os.middle.released:
                    self._editing_channel = 0
                elif lr_dir and num_chans > self._pos:
                    chan = self._chans[self._pos]
                    if self._editing_channel == 1:
                        vol = chan.get_vol_dB() + lr_dir
                        vol = min(20, max(-50, vol))
                        chan.set_vol_dB(vol)
                    if self._editing_channel == 2:
                        chan.set_mute(not chan.get_mute())
            else:
                lr_dir += self.input.buttons.app.right.repeated
                lr_dir -= self.input.buttons.app.left.repeated
                self._pos += lr_dir
                self._pos %= num_chans
            if lr_dir:
                self.full_redraw = True
            self.acc_ms += delta_ms
            if num_chans > 2:
                target = min(num_chans - 2, max(self._pos, 1))
            elif num_chans == 2:
                target = 0.5
            else:
                target = 0
            target *= self.chan_width
    
            self.acc_ms += delta_ms
            pos_prev = self._draw_pos[0]
            while self.acc_ms > 0:
                self.acc_ms -= 20
                self._draw_pos[2] = (target - self._draw_pos[0]) / 2
                self._draw_pos[2] -= self._draw_pos[1]
                self._draw_pos[1] += self._draw_pos[2] / 10
                self._draw_pos[0] += self._draw_pos[1] / 10
            self._draw_pos[0] = round(self._draw_pos[0], 1)
            if not self.full_redraw:
                self.full_redraw = self._draw_pos[0] != pos_prev
    
        def draw(self, ctx):
            ctx.text_align = ctx.CENTER
            ctx.rgb(0x81 / 255, 0xCD / 255, 0xC6 / 255)
            if self.full_redraw:
                ctx.rgb(0, 0, 0)
                ctx.rectangle(-120, -120, 240, 240).fill()
                ctx.move_to(0, -90)
                ctx.font_size = 24
                ctx.text("~ mixer ~")
            for x, chan in enumerate(self._chans):
                xpos = x * self.chan_width - self._draw_pos[0]
                if abs(xpos) > 120 + self.chan_width / 2:
                    continue
                highlight = self._pos == x
                if highlight:
                    colors = self.highlight_colors
                else:
                    colors = self.colors
                ctx.save()
                ctx.translate(xpos, (1 - highlight) * 2)
                self.draw_channel(colors, ctx, chan, highlight)
                ctx.restore()
            if self.full_redraw:
                self.full_redraw = False
    
        def draw_channel(self, colors, ctx, chan, highlight):
            ctx.text_align = ctx.RIGHT
            ctx.rgb(*colors.bg)
            chan_len = 160
            chan_upper = -80
            chan_width = self.chan_width - 4
            len_bar = chan_len - 38
            bar_bottom = chan_upper + chan_len - 21
            if self.full_redraw:
                ctx.round_rectangle(
                    -chan_width / 2, chan_upper, chan_width, chan_len, 2
                ).fill()
    
                # draw name
                ctx.font_size = 16
                ctx.save()
                ctx.rgb(*colors.name)
                ctx.translate(0, 0)
                ctx.rotate(-math.tau / 4)
                ctx.move_to(-chan_upper - 4, -chan_width / 2 + 14)
                name = chan.get_name()
                while ctx.text_width(name) > 110:
                    name = name[:-1]
                ctx.text(name)
                ctx.restore()
    
                # draw number of plugins (bl00mbox only)
                num_plugins = chan.get_num_plugins()
                if num_plugins is not None:
                    ctx.font_size = 12
                    ctx.rgb(*[0.7 * x for x in colors.bg])
                    ctx.move_to(chan_width / 2 - 3, chan_upper + 12)
                    ctx.text(str(num_plugins))
    
                # volume
                vol = chan.get_vol_dB()
                ctx.rgb(0, 0, 0)
                ctx.move_to(chan_width / 2 - 3, 75)
                ctx.font_size = 14
                ctx.text(f"{vol:.1f}")
    
                # mute
                mute = int(chan.get_mute())
                ctx.rgb(*(colors.mute_bg[mute]))
                ctx.rectangle(-chan_width / 2 + 3, 41, 18, 18).fill()
                ctx.text_align = ctx.CENTER
                ctx.font_size = 16
                ctx.move_to(-chan_width / 2 + 12, 55)
                ctx.rgb(*(colors.mute_fg[mute]))
                ctx.text("M")
                if self._editing_channel == 2 and highlight:
                    ctx.rgb(0, 1, 0)
                    ctx.round_rectangle(-chan_width / 2 + 3, 41, 18, 18, 2).stroke()
    
                ctx.rgb(0, 0, 0)
                ctx.rectangle(chan_width / 2 - 13, bar_bottom - len_bar, 9, len_bar).fill()
                ctx.move_to(chan_width / 2 - 13, bar_bottom - len_bar * 5 / 7)
                ctx.rel_line_to(-5, 0).stroke()
    
                # vol bar
                ctx.rgb(*colors.vol_bar)
                vol_bar_len = (vol + 50) / 70
                vol_bar_len = min(1, max(0, vol_bar_len))
                vol_bar_len *= len_bar
                ctx.rectangle(
                    chan_width / 2 - 12, bar_bottom - vol_bar_len, 3, vol_bar_len
                ).fill()
    
                if self._editing_channel == 1 and highlight:
                    ctx.rgb(0, 1, 0)
                    ctx.move_to(6, bar_bottom - vol_bar_len)
                    ctx.rel_line_to(-5, -3)
                    ctx.rel_line_to(0, 6)
                    ctx.rel_line_to(5, -3)
                    ctx.fill()
    
            ctx.rgb(0, 0, 0)
            ctx.rectangle(chan_width / 2 - 9, bar_bottom - len_bar, 5, len_bar).fill()
            # rms bar
            # hmmmh do we reuse the 0dB notch for normalization...?
            rms = chan.get_rms_dB() + 12
            ctx.rgb(*colors.rms_bar)
            rms_bar_len = (rms + 50) / 70
            rms_bar_len = min(1, max(0, rms_bar_len))
            rms_bar_len *= len_bar
            ctx.rectangle(
                chan_width / 2 - 8, bar_bottom - rms_bar_len, 3, rms_bar_len
            ).fill()
    
        def on_exit(self):
            for chan in self._chans:
                if isinstance(chan, bl00mboxChannel):
                    chan.blm.compute_rms = chan.prev_compute_rms