From dfc7f0529c1a691593bf803bec25bb84e4b47114 Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Thu, 17 Aug 2023 13:44:34 +0000
Subject: [PATCH] bl00mbox: slow line in

---
 components/bl00mbox/CMakeLists.txt            |  2 +
 components/bl00mbox/bl00mbox_audio.c          |  5 +-
 .../bl00mbox/bl00mbox_plugin_registry.c       |  2 +
 components/bl00mbox/bl00mbox_user.c           |  3 +
 components/bl00mbox/include/bl00mbox_audio.h  |  2 +
 components/bl00mbox/plugins/ampliverter.c     |  4 +-
 .../plugins/bl00mbox/bl00mbox_line_in.c       | 48 ++++++++++
 .../plugins/bl00mbox/bl00mbox_line_in.h       |  9 ++
 components/bl00mbox/plugins/delay.c           |  3 +-
 components/bl00mbox/plugins/distortion.c      |  3 +-
 components/bl00mbox/plugins/env_adsr.c        | 13 +--
 components/bl00mbox/plugins/flanger.c         |  3 +-
 components/bl00mbox/plugins/lowpass.c         |  5 +-
 components/bl00mbox/plugins/mixer.c           |  4 +-
 components/bl00mbox/plugins/multipitch.c      | 25 ++----
 components/bl00mbox/plugins/noise.c           |  6 +-
 components/bl00mbox/plugins/noise_burst.c     |  8 +-
 components/bl00mbox/plugins/osc_fm.c          |  3 +-
 components/bl00mbox/plugins/sampler.c         | 74 +++++++++++----
 components/bl00mbox/plugins/sampler.h         |  5 ++
 components/bl00mbox/plugins/sequencer.c       | 11 +--
 .../bl00mbox/plugins/slew_rate_limiter.c      |  3 +-
 components/bl00mbox/radspa/radspa.h           | 31 +++++--
 components/bl00mbox/radspa/radspa_helpers.c   | 38 ++++++--
 components/bl00mbox/radspa/radspa_helpers.h   |  7 ++
 python_payload/apps/tiny_sampler/__init__.py  | 90 +++++++++++++++++++
 python_payload/apps/tiny_sampler/flow3r.toml  | 11 +++
 python_payload/bl00mbox/_patches.py           | 66 ++++++++------
 python_payload/mypystubs/audio.pyi            | 11 +++
 python_payload/mypystubs/bl00mbox/_user.pyi   |  1 +
 30 files changed, 370 insertions(+), 126 deletions(-)
 create mode 100644 components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c
 create mode 100644 components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h
 create mode 100644 python_payload/apps/tiny_sampler/__init__.py
 create mode 100644 python_payload/apps/tiny_sampler/flow3r.toml

diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt
index 487573d07d..905d3ea301 100644
--- a/components/bl00mbox/CMakeLists.txt
+++ b/components/bl00mbox/CMakeLists.txt
@@ -21,11 +21,13 @@ idf_component_register(
         plugins/lowpass.c
         plugins/mixer.c
         plugins/slew_rate_limiter.c
+        plugins/bl00mbox/bl00mbox_line_in.c
         radspa/radspa_helpers.c
         extern/xoroshiro64star.c
     INCLUDE_DIRS
         include
         plugins
+        plugins/bl00mbox
         radspa
         extern
     REQUIRES
diff --git a/components/bl00mbox/bl00mbox_audio.c b/components/bl00mbox/bl00mbox_audio.c
index 3664d64c7b..4b8ff07fec 100644
--- a/components/bl00mbox/bl00mbox_audio.c
+++ b/components/bl00mbox/bl00mbox_audio.c
@@ -8,6 +8,8 @@ void bl00mbox_audio_disable(){ bl00mbox_audio_run = false; }
 
 static uint32_t render_pass_id;
 
+int16_t * bl00mbox_line_in_interlaced = NULL;
+
 // fixed-length list of channels
 static bl00mbox_channel_t channels[BL00MBOX_CHANNELS];
 static int8_t last_chan_event = 0;
@@ -97,7 +99,7 @@ bool bl00mbox_channel_set_free(uint8_t channel_index, bool free){
 }
 
 uint8_t bl00mbox_channel_get_free_index(){
-    uint8_t ret = 1;
+    uint8_t ret = 1; // never return system channel
     for(; ret < BL00MBOX_CHANNELS; ret++){
         if(bl00mbox_get_channel(ret)->is_free){
             bl00mbox_get_channel(ret)->is_free = false;
@@ -226,6 +228,7 @@ void bl00mbox_audio_render(int16_t * rx, int16_t * tx, uint16_t len){
 
     render_pass_id++; // fresh pass, all relevant sources must be recomputed
     uint16_t mono_len = len/2;
+    bl00mbox_line_in_interlaced = rx;
     int16_t acc[mono_len];
     // system channel always runs non-adding
     bl00mbox_audio_channel_render(&(channels[0]), acc, mono_len, 0);
diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c
index 46edee771b..80edc3152b 100644
--- a/components/bl00mbox/bl00mbox_plugin_registry.c
+++ b/components/bl00mbox/bl00mbox_plugin_registry.c
@@ -101,6 +101,7 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index)
 #include "mixer.h"
 #include "multipitch.h"
 #include "slew_rate_limiter.h"
+#include "bl00mbox_line_in.h"
 
 void bl00mbox_plugin_registry_init(void){
     if(bl00mbox_plugin_registry_is_initialized) return;
@@ -118,4 +119,5 @@ void bl00mbox_plugin_registry_init(void){
     plugin_add(&mixer_desc);
     plugin_add(&slew_rate_limiter_desc);
     plugin_add(&multipitch_desc);
+    plugin_add(&bl00mbox_line_in_desc);
 }
diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c
index 3c31abad16..db84045d04 100644
--- a/components/bl00mbox/bl00mbox_user.c
+++ b/components/bl00mbox/bl00mbox_user.c
@@ -705,6 +705,9 @@ int16_t bl00mbox_channel_bud_get_signal_value(uint8_t channel, uint32_t bud_inde
     radspa_signal_t * sig = radspa_signal_get_by_index(bud->plugin, bud_signal_index);
     if(sig == NULL) return false;
 
+    if((sig->hints & RADSPA_SIGNAL_HINT_OUTPUT) && (sig->buffer != NULL)){
+        return sig->buffer[0];
+    }
     return sig->value;
 }
 
diff --git a/components/bl00mbox/include/bl00mbox_audio.h b/components/bl00mbox/include/bl00mbox_audio.h
index 84d7fd59c4..a28eb9bc5b 100644
--- a/components/bl00mbox/include/bl00mbox_audio.h
+++ b/components/bl00mbox/include/bl00mbox_audio.h
@@ -21,6 +21,8 @@ struct _bl00mbox_connection_source_t;
 struct _bl00mbox_channel_root_t;
 struct _bl00mbox_channel_t;
 
+extern int16_t * bl00mbox_line_in_interlaced;
+
 typedef struct _bl00mbox_bud_t{
     radspa_t * plugin; // plugin
     char * name;
diff --git a/components/bl00mbox/plugins/ampliverter.c b/components/bl00mbox/plugins/ampliverter.c
index 4dfdaf564f..23b3dced39 100644
--- a/components/bl00mbox/plugins/ampliverter.c
+++ b/components/bl00mbox/plugins/ampliverter.c
@@ -50,10 +50,8 @@ void ampliverter_run(radspa_t * ampliverter, uint16_t num_samples, uint32_t rend
             ret = radspa_mult_shift(ret, gain);
             ret = radspa_add_sat(ret, bias);
         }
-        (output_sig->buffer)[i] = ret;
-
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * ampliverter_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c
new file mode 100644
index 0000000000..a977a158cb
--- /dev/null
+++ b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.c
@@ -0,0 +1,48 @@
+#include "bl00mbox_line_in.h"
+
+radspa_descriptor_t bl00mbox_line_in_desc = {
+    .name = "line_in",
+    .id = 4001,
+    .description = "connects to the line input of bl00mbox",
+    .create_plugin_instance = bl00mbox_line_in_create,
+    .destroy_plugin_instance = radspa_standard_plugin_destroy
+};
+
+void bl00mbox_line_in_run(radspa_t * line_in, uint16_t num_samples, uint32_t render_pass_id){
+    if(bl00mbox_line_in_interlaced == NULL) return;
+    radspa_signal_t * left_sig = radspa_signal_get_by_index(line_in, 0);
+    radspa_signal_t * right_sig = radspa_signal_get_by_index(line_in, 1);
+    radspa_signal_t * mid_sig = radspa_signal_get_by_index(line_in, 2);
+    radspa_signal_t * gain_sig = radspa_signal_get_by_index(line_in, 3);
+
+    for(uint16_t i = 0; i < num_samples; i++){
+        int32_t gain = gain_sig->get_value(gain_sig, i, num_samples, render_pass_id);
+        if(left_sig->buffer != NULL){
+            int16_t left = radspa_gain(bl00mbox_line_in_interlaced[2*i], gain);
+            left_sig->set_value(left_sig, i, left, num_samples, render_pass_id);
+        }
+
+        if(right_sig->buffer != NULL){
+                int16_t right = radspa_gain(bl00mbox_line_in_interlaced[2*i], gain);
+                right_sig->set_value(right_sig, i, right, num_samples, render_pass_id);
+        }
+
+        if(mid_sig->buffer != NULL){
+            int16_t mid = bl00mbox_line_in_interlaced[2*i]>>1;
+            mid += bl00mbox_line_in_interlaced[2*i]>>1;
+            mid = radspa_gain(mid, gain);
+            mid_sig->set_value(mid_sig, i, mid, num_samples, render_pass_id);
+        }
+    }
+}
+
+radspa_t * bl00mbox_line_in_create(uint32_t init_var){
+    radspa_t * line_in = radspa_standard_plugin_create(&bl00mbox_line_in_desc, 4, 0, 0);
+    if(line_in == NULL) return NULL;
+    line_in->render = bl00mbox_line_in_run;
+    radspa_signal_set(line_in, 0, "left", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(line_in, 1, "right", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(line_in, 2, "mid", RADSPA_SIGNAL_HINT_OUTPUT, 0);
+    radspa_signal_set(line_in, 3, "gain", RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN);
+    return line_in;
+}
diff --git a/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h
new file mode 100644
index 0000000000..1a895b6463
--- /dev/null
+++ b/components/bl00mbox/plugins/bl00mbox/bl00mbox_line_in.h
@@ -0,0 +1,9 @@
+#pragma once
+#include "radspa.h"
+#include "radspa_helpers.h"
+// SPECIAL REQUIREMENTS
+#include "bl00mbox_audio.h"
+
+extern radspa_descriptor_t bl00mbox_line_in_desc;
+radspa_t * bl00mbox_line_in_create(uint32_t init_var);
+void bl00mbox_line_in_run(radspa_t * line_in, uint16_t num_samples, uint32_t render_pass_id);
diff --git a/components/bl00mbox/plugins/delay.c b/components/bl00mbox/plugins/delay.c
index d87218e401..dbf5d6c873 100644
--- a/components/bl00mbox/plugins/delay.c
+++ b/components/bl00mbox/plugins/delay.c
@@ -67,9 +67,8 @@ void delay_run(radspa_t * delay, uint16_t num_samples, uint32_t render_pass_id){
         
         ret = radspa_add_sat(radspa_mult_shift(dry_vol,dry), radspa_mult_shift(wet,level));
 
-        (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * delay_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/distortion.c b/components/bl00mbox/plugins/distortion.c
index 7effbea2d2..3a542b94c6 100644
--- a/components/bl00mbox/plugins/distortion.c
+++ b/components/bl00mbox/plugins/distortion.c
@@ -29,9 +29,8 @@ void distortion_run(radspa_t * distortion, uint16_t num_samples, uint32_t render
         int32_t blend = input & ((1<<7)-1);
         ret = dist[index]*((1<<7)-blend) + dist[index+1]*blend;
         ret = ret >> 7;
-        (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * distortion_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/env_adsr.c b/components/bl00mbox/plugins/env_adsr.c
index d4c04942cd..d3f4fc8950 100644
--- a/components/bl00mbox/plugins/env_adsr.c
+++ b/components/bl00mbox/plugins/env_adsr.c
@@ -129,10 +129,9 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
     radspa_signal_t * release_sig = NULL;
     radspa_signal_t * gate_sig = NULL;
 
-    int16_t ret = output_sig->value;
-
+    int16_t env = 0;
     for(uint16_t i = 0; i < num_samples; i++){
-        static int16_t env = 0;
+        int16_t ret = 0;
 
         int16_t trigger = trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id);
         int16_t vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev));
@@ -214,12 +213,8 @@ void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pas
         if(env){
             int16_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
             ret = radspa_mult_shift(env, input);
-        } else {
-            ret = 0;
         }
-        if(phase_sig->buffer != NULL) (phase_sig->buffer)[i] = plugin_data->env_phase;
-        if(output_sig->buffer != NULL) (output_sig->buffer)[i] = ret;
+        phase_sig->set_value(phase_sig, i, plugin_data->env_phase, num_samples, render_pass_id);
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    phase_sig->value = plugin_data->env_phase;
-    output_sig->value = ret;
 }
diff --git a/components/bl00mbox/plugins/flanger.c b/components/bl00mbox/plugins/flanger.c
index 1f68d48188..4a4f8571d4 100644
--- a/components/bl00mbox/plugins/flanger.c
+++ b/components/bl00mbox/plugins/flanger.c
@@ -78,9 +78,8 @@ void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_
 
         ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix));
         ret = radspa_clip(radspa_gain(ret, level));
-        (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * flanger_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/lowpass.c b/components/bl00mbox/plugins/lowpass.c
index f2087ed488..fbc8b9dae0 100644
--- a/components/bl00mbox/plugins/lowpass.c
+++ b/components/bl00mbox/plugins/lowpass.c
@@ -98,8 +98,6 @@ void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_
     radspa_signal_t * q_sig = radspa_signal_get_by_index(lowpass, LOWPASS_Q);
     radspa_signal_t * gain_sig = radspa_signal_get_by_index(lowpass, LOWPASS_GAIN);
 
-    static int16_t ret = 0;
-    
     for(uint16_t i = 0; i < num_samples; i++){
         int16_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
         int32_t freq = freq_sig->get_value(freq_sig, i, num_samples, render_pass_id);
@@ -114,9 +112,8 @@ void lowpass_run(radspa_t * lowpass, uint16_t num_samples, uint32_t render_pass_
     
         float out = apply_lowpass(data, input) + 0.5;
         int16_t ret = radspa_clip(radspa_gain((int32_t) out, gain));
-        (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * lowpass_create(uint32_t real_init_var){
diff --git a/components/bl00mbox/plugins/mixer.c b/components/bl00mbox/plugins/mixer.c
index 397fd142cc..6a68364660 100644
--- a/components/bl00mbox/plugins/mixer.c
+++ b/components/bl00mbox/plugins/mixer.c
@@ -31,9 +31,9 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){
         // remove dc
         (* dc_acc) = (ret + (* dc_acc)*1023) >> 10;
         ret -= (* dc_acc);
-        (output_sig->buffer)[i] = radspa_clip(radspa_gain(ret, gain));
+        ret = radspa_clip(radspa_gain(ret, gain));
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * mixer_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/multipitch.c b/components/bl00mbox/plugins/multipitch.c
index 50ff80ec20..e07d1a8b8f 100644
--- a/components/bl00mbox/plugins/multipitch.c
+++ b/components/bl00mbox/plugins/multipitch.c
@@ -18,40 +18,27 @@ void multipitch_run(radspa_t * multipitch, uint16_t num_samples, uint32_t render
     radspa_signal_t * output_sigs[num_outputs];
     radspa_signal_t * pitch_sigs[num_outputs];
     for(uint8_t j = 0; j < num_outputs; j++){
-        output_sigs[j] = radspa_signal_get_by_index(multipitch, 2 + j);
-        pitch_sigs[j] = radspa_signal_get_by_index(multipitch, 3 + j);
+        output_sigs[j] = radspa_signal_get_by_index(multipitch, 2 + 2 * j);
+        pitch_sigs[j] = radspa_signal_get_by_index(multipitch, 3 + 2 * j);
         if(output_sigs[j]->buffer != NULL) output_request = true;
     }
     if(!output_request) return;
 
-    int32_t ret = 0;
-    int32_t rets[num_outputs];
-
     for(uint16_t i = 0; i < num_samples; i++){
+        int32_t ret = 0;
         int32_t input = input_sig->get_value(input_sig, i, num_samples, render_pass_id);
         ret = input;
 
         if(thru_sig->buffer != NULL) (thru_sig->buffer)[i] = ret;
+        thru_sig->set_value(thru_sig, i, ret, num_samples, render_pass_id);
 
         int32_t pitch;
         for(uint8_t j = 0; j < num_outputs; j++){
             pitch = pitch_sigs[j]->get_value(pitch_sigs[j], i, num_samples, render_pass_id);
-            rets[j] = pitch + input - RADSPA_SIGNAL_VAL_SCT_A440;
-            if(output_sigs[j]->buffer != NULL) (output_sigs[j]->buffer)[i] = rets[j];
-        }
-    }
-    
-    // clang-tidy only, num_samples is always nonzero
-    if(!num_samples){
-        for(uint8_t j = 0; j < num_outputs; j++){
-            rets[j] = 0;
+            ret = pitch + input - RADSPA_SIGNAL_VAL_SCT_A440;
+            output_sigs[j]->set_value(output_sigs[j], i, ret, num_samples, render_pass_id);
         }
     }
-
-    for(uint8_t j = 0; j < num_outputs; j++){
-        output_sigs[j]->value = rets[j];
-    }
-    thru_sig->value = ret;
 }
 
 radspa_t * multipitch_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/noise.c b/components/bl00mbox/plugins/noise.c
index 364456f517..032aec1c2a 100644
--- a/components/bl00mbox/plugins/noise.c
+++ b/components/bl00mbox/plugins/noise.c
@@ -16,12 +16,10 @@ void noise_run(radspa_t * noise, uint16_t num_samples, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(noise, NOISE_OUTPUT);
     if(output_sig->buffer == NULL) return;
 
-    static int16_t ret = 0;
     for(uint16_t i = 0; i < num_samples; i++){
-        (output_sig->buffer)[i] = radspa_random();
-
+        int16_t ret = radspa_random();
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 radspa_t * noise_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/noise_burst.c b/components/bl00mbox/plugins/noise_burst.c
index 13c16138f6..97010fdf5a 100644
--- a/components/bl00mbox/plugins/noise_burst.c
+++ b/components/bl00mbox/plugins/noise_burst.c
@@ -38,9 +38,8 @@ void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t rend
     radspa_signal_t * trigger_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_TRIGGER);
     radspa_signal_t * length_ms_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_LENGTH_MS);
 
-    int16_t ret = output_sig->value;
-
     for(uint16_t i = 0; i < num_samples; i++){
+        int16_t ret = 0;
 
         int16_t trigger = trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id);
         int16_t vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev));
@@ -55,10 +54,7 @@ void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t rend
         if(plugin_data->counter < plugin_data->limit){
             ret = radspa_random();
             plugin_data->counter++;
-        } else {
-            ret = 0;
         }
-        if(output_sig->buffer != NULL) (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
diff --git a/components/bl00mbox/plugins/osc_fm.c b/components/bl00mbox/plugins/osc_fm.c
index 750a8ee3bb..66cf21a251 100644
--- a/components/bl00mbox/plugins/osc_fm.c
+++ b/components/bl00mbox/plugins/osc_fm.c
@@ -52,9 +52,8 @@ void osc_fm_run(radspa_t * osc_fm, uint16_t num_samples, uint32_t render_pass_id
         int32_t tmp = (plugin_data->counter) >> 17;
         tmp = (tmp*2) - 32767;
         ret = waveshaper(tmp, wave);
-        (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
 static inline int16_t triangle(int16_t saw){
diff --git a/components/bl00mbox/plugins/sampler.c b/components/bl00mbox/plugins/sampler.c
index e5ed7ac51f..e4ff33b72d 100644
--- a/components/bl00mbox/plugins/sampler.c
+++ b/components/bl00mbox/plugins/sampler.c
@@ -4,14 +4,16 @@ radspa_t * sampler_create(uint32_t init_var);
 radspa_descriptor_t sampler_desc = {
     .name = "_sampler_ram",
     .id = 696969,
-    .description = "simple sampler that stores a copy of the sample in ram",
+    .description = "simple sampler that stores a copy of the sample in ram and has basic recording functionality",
     .create_plugin_instance = sampler_create,
     .destroy_plugin_instance = radspa_standard_plugin_destroy
 };
 
-#define SAMPLER_NUM_SIGNALS 2
+#define SAMPLER_NUM_SIGNALS 4
 #define SAMPLER_OUTPUT 0
 #define SAMPLER_TRIGGER 1
+#define SAMPLER_REC_IN 2
+#define SAMPLER_REC_TRIGGER 3
 
 void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_id){
     radspa_signal_t * output_sig = radspa_signal_get_by_index(sampler, SAMPLER_OUTPUT);
@@ -19,35 +21,61 @@ void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_
     sampler_data_t * data = sampler->plugin_data;
     int16_t * buf = sampler->plugin_table;
     radspa_signal_t * trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_TRIGGER);
+    radspa_signal_t * rec_in_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_IN);
+    radspa_signal_t * rec_trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_TRIGGER);
 
     static int32_t ret = 0;
     
     uint32_t buffer_size = sampler->plugin_table_len;
     
     for(uint16_t i = 0; i < num_samples; i++){
+        int16_t rec_trigger = radspa_trigger_get(rec_trigger_sig->get_value(rec_trigger_sig, i, num_samples, render_pass_id), &(data->rec_trigger_prev));
+        int16_t trigger = radspa_trigger_get(trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id), &(data->trigger_prev));
 
-        int16_t trigger = trigger_sig->get_value(trigger_sig, i, num_samples, render_pass_id);
-        
-        int16_t vel = radspa_trigger_get(trigger, &(data->trigger_prev));
-
-        if(vel > 0){
-            data->read_head_position = 0;
-            data->volume = vel;
-        } else if(vel < 0){
-            data->read_head_position = buffer_size;
+        if(rec_trigger > 0){
+            if(!(data->rec_active)){
+                data->read_head_position = data->sample_len; // reset sample player into off
+                data->write_head_position = 0;
+                data->sample_len = 0;
+                data->rec_active = true;
+            }
+        } else if(rec_trigger < 0){
+            if(data->rec_active){
+                if(data->sample_len == buffer_size){
+                    data->sample_start = data->write_head_position;
+                } else {
+                    data->sample_start = 0;
+                }
+                data->rec_active = false;
+            }
         }
 
-        if(data->read_head_position < buffer_size){
-            ret = radspa_mult_shift(buf[data->read_head_position], data->volume);
-            data->read_head_position++;
+        if(data->rec_active){
+            int16_t rec_in = rec_in_sig->get_value(rec_in_sig, i, num_samples, render_pass_id);
+            buf[data->write_head_position] = rec_in;
+            data->write_head_position++;
+            if(data->write_head_position >= buffer_size) data->write_head_position = 0;
+            if(data->sample_len < buffer_size) data->sample_len++;
         } else {
-            //ret = (ret * 255)>>8; // avoid dc clicks with bad samples
-            ret = 0;
-        }
+            if(trigger > 0){
+                data->read_head_position = 0;
+                data->volume = trigger;
+            } else if(trigger < 0){
+                data->read_head_position = data->sample_len;
+            }
 
-        (output_sig->buffer)[i] = ret;
+            if(data->read_head_position < data->sample_len){
+                uint32_t sample_offset_pos = data->read_head_position + data->sample_start;
+                if(sample_offset_pos >= data->sample_len) sample_offset_pos -= data->sample_len;
+                ret = radspa_mult_shift(buf[sample_offset_pos], data->volume);
+                data->read_head_position++;
+            } else {
+                //ret = (ret * 255)>>8; // avoid dc clicks with bad samples
+                ret = 0;
+            }
+            output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
+        }
     }
-    output_sig->value = ret;
 }
 
 radspa_t * sampler_create(uint32_t init_var){
@@ -58,5 +86,13 @@ radspa_t * sampler_create(uint32_t init_var){
     sampler->render = sampler_run;
     radspa_signal_set(sampler, SAMPLER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0);
     radspa_signal_set(sampler, SAMPLER_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
+    radspa_signal_set(sampler, SAMPLER_REC_IN, "rec_in", RADSPA_SIGNAL_HINT_INPUT, 0);
+    radspa_signal_set(sampler, SAMPLER_REC_TRIGGER, "rec_trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0);
+    sampler_data_t * data = sampler->plugin_data;
+    data->trigger_prev = 0;
+    data->rec_trigger_prev = 0;
+    data->rec_active = false;
+    data->sample_start = 0;
+    data->sample_len = sampler->plugin_table_len;
     return sampler;
 }
diff --git a/components/bl00mbox/plugins/sampler.h b/components/bl00mbox/plugins/sampler.h
index 53bedfbacd..9958d9b9f2 100644
--- a/components/bl00mbox/plugins/sampler.h
+++ b/components/bl00mbox/plugins/sampler.h
@@ -4,8 +4,13 @@
 
 typedef struct {
     uint32_t read_head_position;
+    uint32_t write_head_position;
+    uint32_t sample_start;
+    uint32_t sample_len;
     int16_t trigger_prev;
+    int16_t rec_trigger_prev;
     int16_t volume;
+    bool rec_active;
 } sampler_data_t;
 
 extern radspa_descriptor_t sampler_desc;
diff --git a/components/bl00mbox/plugins/sequencer.c b/components/bl00mbox/plugins/sequencer.c
index 5345e2d96e..c642ae98fd 100644
--- a/components/bl00mbox/plugins/sequencer.c
+++ b/components/bl00mbox/plugins/sequencer.c
@@ -98,17 +98,12 @@ void sequencer_run(radspa_t * sequencer, uint16_t num_samples, uint32_t render_p
             }
           
             for(uint8_t j = 0; j < data->num_tracks; j++){
-                if(track_sigs[j]->buffer != NULL) (track_sigs[j]->buffer)[i] = data->tracks[j].track_fill;
+                track_sigs[j]->set_value(track_sigs[j], i, data->tracks[j].track_fill, num_samples, render_pass_id);
             }
-            if(sync_out_sig->buffer != NULL) (sync_out_sig->buffer)[i] = data->sync_out;
-            if(step_sig->buffer != NULL) (step_sig->buffer)[i] = data->step;
+            sync_out_sig->set_value(sync_out_sig, i, data->sync_out, num_samples, render_pass_id);
+            step_sig->set_value(step_sig, i, data->step, num_samples, render_pass_id);
         }
     }
-    for(uint8_t j = 0; j < data->num_tracks; j++){
-        track_sigs[j]->value = data->tracks[j].track_fill;
-    }
-    sync_out_sig->value = data->sync_out;
-    step_sig->value = data->step;
 }
 
 radspa_t * sequencer_create(uint32_t init_var){
diff --git a/components/bl00mbox/plugins/slew_rate_limiter.c b/components/bl00mbox/plugins/slew_rate_limiter.c
index f1907b7c74..576bd51ae2 100644
--- a/components/bl00mbox/plugins/slew_rate_limiter.c
+++ b/components/bl00mbox/plugins/slew_rate_limiter.c
@@ -44,8 +44,7 @@ void slew_rate_limiter_run(radspa_t * slew_rate_limiter, uint16_t num_samples, u
         } else {
             ret = input;
         }
-        (output_sig->buffer)[i] = ret;
+        output_sig->set_value(output_sig, i, ret, num_samples, render_pass_id);
     }
-    output_sig->value = ret;
 }
 
diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h
index 6e018c4a1a..a49d860eed 100644
--- a/components/bl00mbox/radspa/radspa.h
+++ b/components/bl00mbox/radspa/radspa.h
@@ -4,7 +4,7 @@
 // this file, kindly append "-modified" to the version string below so it is not mistaken
 // for an official release.
 
-// Version 0.1.0+
+// Version 0.2.0
 
 /* Realtime Audio Developer's Simple Plugin Api
  *
@@ -43,6 +43,10 @@
 #define RADSPA_SIGNAL_HINT_TRIGGER (1<<2)
 #define RADSPA_SIGNAL_HINT_GAIN (1<<3)
 #define RADSPA_SIGNAL_HINT_SCT (1<<5)
+#define RADSPSA_SIGNAL_HINT_REDUCED_RANGE (1<<6)
+#define RADSPSA_SIGNAL_HINT_POS_SHIFT (1<<7)
+// 6 bit number, 0 for non-stepped, else number of steps
+#define RADSPSA_SIGNAL_HINT_STEPPED_LSB (1<<8)
 
 #define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400)
 #define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<12)
@@ -60,21 +64,30 @@ typedef struct _radspa_descriptor_t{
 } radspa_descriptor_t;
 
 typedef struct _radspa_signal_t{
+    // this bitfield determines the type of the signal, see RADSPA_SIGNAL_HINTS_*
     uint32_t hints;
+    // this is the name of the signal as shown to the user.
+    // allowed characters: lowercase, numbers, underscore, may not start with number
+    // if name_multplex >= 0: may not end with number
     char * name;
+    // arbitrary formatted string to describe what the signal is for
     char * description;
+    // unit that corresponds to value, may be empty. note: some RADSPA_SIGNAL_HINTS_*
+    // imply units. field may be empty.
     char * unit;
+    // -1 to disable signal multiplexing
     int8_t name_multiplex;
-
-    int16_t * buffer; // full buffer of num_samples. may be NULL.
-
-    // used for input channels only
-    int16_t value;  //static value, should be used if buffer is NULL.
+    // buffer full of samples, may be NULL
+    int16_t * buffer;
+    // static value to be used when buffer is NULL for input signals only
+    int16_t value;
     uint32_t render_pass_id;
-    // function to retrieve value. radspa_helpers provides an example.
+    // function for input signals to retrieve their value at a buffer index. radspa_helpers provides an example. this function should be set by the host.
     int16_t (* get_value)(struct _radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id);
-
-    struct _radspa_signal_t * next; //signals are in a linked list
+    // function for output signals to set a value at a buffer index. radspa_helpers provides an example. this function should be set by the host.
+    void (* set_value)(struct _radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id);
+    // linked list pointer
+    struct _radspa_signal_t * next;
 } radspa_signal_t;
 
 typedef struct _radspa_t{
diff --git a/components/bl00mbox/radspa/radspa_helpers.c b/components/bl00mbox/radspa/radspa_helpers.c
index 7bfdbbd586..a3c1a17995 100644
--- a/components/bl00mbox/radspa/radspa_helpers.c
+++ b/components/bl00mbox/radspa/radspa_helpers.c
@@ -77,6 +77,7 @@ int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_
     sig->value = value;
     sig->name_multiplex = -1;
     sig->get_value = radspa_signal_get_value;
+    sig->set_value = radspa_signal_set_value;
     
     //find end of linked list
     uint16_t list_index = 0;
@@ -97,16 +98,41 @@ int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_
 }
 
 int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id){
-    if(sig->buffer != NULL){
-        if(sig->render_pass_id != render_pass_id){
-            radspa_host_request_buffer_render(sig->buffer, num_samples); //, render_pass_id);
-            sig->render_pass_id = render_pass_id;
-        }
-        return sig->buffer[index];
+    if(sig->buffer == NULL){
+        return radspa_signal_get_value_disconnected(sig, index, num_samples, render_pass_id);
+    } else {
+        return radspa_signal_get_value_connected(sig, index, num_samples, render_pass_id);
+    }
+}
+
+inline int16_t radspa_signal_get_value_connected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id){
+    if(sig->render_pass_id != render_pass_id){
+        radspa_host_request_buffer_render(sig->buffer, num_samples); //, render_pass_id);
+        sig->render_pass_id = render_pass_id;
     }
+    return sig->buffer[index];
+}
+
+inline int16_t radspa_signal_get_value_disconnected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id){
     return sig->value;
 }
 
+void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id){
+    if(sig->buffer == NULL){
+        radspa_signal_set_value_disconnected(sig, index, value, num_samples, render_pass_id);
+    } else {
+        radspa_signal_set_value_connected(sig, index, value, num_samples, render_pass_id);
+    }
+}
+
+inline void radspa_signal_set_value_connected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id){
+    sig->buffer[index] = value;
+}
+
+inline void radspa_signal_set_value_disconnected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id){
+    sig->value = value;
+}
+
 radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num_signals, size_t plugin_data_size, uint32_t plugin_table_size){
     radspa_t * ret = calloc(1, sizeof(radspa_t));
     if(ret == NULL) return NULL;
diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h
index c8e3717629..29f1ebf97f 100644
--- a/components/bl00mbox/radspa/radspa_helpers.h
+++ b/components/bl00mbox/radspa/radspa_helpers.h
@@ -28,3 +28,10 @@ void radspa_signals_free(radspa_t * plugin);
  * of radspa_host_request_buffer_render.
  */
 int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id);
+
+int16_t radspa_signal_get_value_connected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id);
+int16_t radspa_signal_get_value_disconnected(radspa_signal_t * sig, int16_t index, uint16_t num_samples, uint32_t render_pass_id);
+
+void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id);
+void radspa_signal_set_value_connected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id);
+void radspa_signal_set_value_disconnected(radspa_signal_t * sig, int16_t index, int16_t value, uint16_t num_samples, uint32_t render_pass_id);
diff --git a/python_payload/apps/tiny_sampler/__init__.py b/python_payload/apps/tiny_sampler/__init__.py
new file mode 100644
index 0000000000..7b2f38c337
--- /dev/null
+++ b/python_payload/apps/tiny_sampler/__init__.py
@@ -0,0 +1,90 @@
+import bl00mbox
+import captouch
+import leds
+import audio
+
+from st3m.application import Application, ApplicationContext
+from st3m.input import InputState
+from st3m.goose import Tuple, Iterator, Optional, Callable, List, Any, TYPE_CHECKING
+from ctx import Context
+from st3m.ui.view import View, ViewManager
+
+
+class TinySampler(Application):
+    def __init__(self, app_ctx: ApplicationContext) -> None:
+        super().__init__(app_ctx)
+        self.blm = bl00mbox.Channel("tiny sampler")
+
+        self.samplers: List[bl00mbox.patches._Patch | Any] = [None] * 5
+        self.line_in = self.blm.new(bl00mbox.plugins.line_in)
+        self.blm.volume = (
+            30000  # TODO: increase onboard mic line in gain and remove this
+        )
+        self.line_in.signals.gain = 30000
+        for i in range(5):
+            self.samplers[i] = self.blm.new(bl00mbox.patches.sampler, 5000)
+            self.samplers[i].signals.output = self.blm.mixer
+            self.samplers[i].signals.rec_in = self.line_in.signals.right
+        self.is_recording = [False] * 5
+        audio.input_set_source(audio.INPUT_SOURCE_ONBOARD_MIC)
+
+        self.ct_prev = captouch.read()
+
+    def _highlight_petal(self, num: int, r: int, g: int, b: int) -> None:
+        for i in range(7):
+            leds.set_rgb((4 * num - i + 3) % 40, r, g, b)
+
+    def draw(self, ctx: Context) -> None:
+        dist = 90
+        ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
+
+        ctx.font = ctx.get_font_name(0)
+        ctx.text_align = ctx.MIDDLE
+        ctx.font_size = 24
+        for i in range(5):
+            ctx.rgb(0.8, 0.8, 0.8)
+            ctx.move_to(0, -dist)
+            ctx.text("play" + str(i))
+            ctx.move_to(0, 0)
+            ctx.rotate(6.28 / 10)
+            ctx.move_to(0, -dist)
+            if self.is_recording[i]:
+                ctx.rgb(1, 0, 0)
+            ctx.text("rec" + str(i))
+            ctx.move_to(0, 0)
+            ctx.rotate(6.28 / 10)
+
+    def think(self, ins: InputState, delta_ms: int) -> None:
+        super().think(ins, delta_ms)
+
+        leds.set_all_rgb(0, 0, 0)
+        for i in range(5):
+            if self.is_recording[i]:
+                self._highlight_petal(i * 2, 255, 0, 0)
+            else:
+                self._highlight_petal(i * 2, 0, 255, 0)
+        leds.update()
+
+        ct = captouch.read()
+        for i in range(5):
+            if ct.petals[i * 2].pressed and not self.ct_prev.petals[i * 2].pressed:
+                if not self.is_recording[i]:
+                    self.samplers[i].signals.trigger.start()
+
+        for i in range(5):
+            if (
+                ct.petals[(i * 2) + 1].pressed
+                and not self.ct_prev.petals[(i * 2) + 1].pressed
+            ):
+                if not self.is_recording[i]:
+                    self.samplers[i].signals.rec_trigger.start()
+                    self.is_recording[i] = True
+            if (
+                not ct.petals[(i * 2) + 1].pressed
+                and self.ct_prev.petals[(i * 2) + 1].pressed
+            ):
+                if self.is_recording[i]:
+                    self.samplers[i].signals.rec_trigger.stop()
+                    self.is_recording[i] = False
+
+        self.ct_prev = ct
diff --git a/python_payload/apps/tiny_sampler/flow3r.toml b/python_payload/apps/tiny_sampler/flow3r.toml
new file mode 100644
index 0000000000..18917097fc
--- /dev/null
+++ b/python_payload/apps/tiny_sampler/flow3r.toml
@@ -0,0 +1,11 @@
+[app]
+name = "tiny sampler"
+menu = "Music"
+
+[entry]
+class = "TinySampler"
+
+[metadata]
+author = "Flow3r Badge Authors"
+license = "LGPL-3.0-only"
+url = "https://git.flow3r.garden/flow3r/flow3r-firmware"
diff --git a/python_payload/bl00mbox/_patches.py b/python_payload/bl00mbox/_patches.py
index d60317b6e6..3942399fe1 100644
--- a/python_payload/bl00mbox/_patches.py
+++ b/python_payload/bl00mbox/_patches.py
@@ -103,39 +103,53 @@ class sampler(_Patch):
     requires a wave file. default path: /sys/samples/
     """
 
-    def __init__(self, chan, filename):
+    def __init__(self, chan, init_var):
+        # init can be filename to load into ram
         super().__init__(chan)
-        if filename.startswith("/flash/") or filename.startswith("/sd/"):
-            f = wave.open(filename, "r")
-        elif filename.startswith("/"):
-            f = wave.open("/flash/" + filename, "r")
-        else:
-            f = wave.open("/flash/sys/samples/" + filename, "r")
+        if type(init_var) == str:
+            filename = init_var
+            if filename.startswith("/flash/") or filename.startswith("/sd/"):
+                f = wave.open(filename, "r")
+            elif filename.startswith("/"):
+                f = wave.open("/flash/" + filename, "r")
+            else:
+                f = wave.open("/flash/sys/samples/" + filename, "r")
 
-        self.len_frames = f.getnframes()
-        self.plugins.sampler = chan.new(bl00mbox.plugins._sampler_ram, self.len_frames)
+            self.len_frames = f.getnframes()
+            self.plugins.sampler = chan.new(
+                bl00mbox.plugins._sampler_ram, self.len_frames
+            )
 
-        assert f.getsampwidth() == 2
-        assert f.getnchannels() in (1, 2)
-        assert f.getcomptype() == "NONE"
+            assert f.getsampwidth() == 2
+            assert f.getnchannels() in (1, 2)
+            assert f.getcomptype() == "NONE"
 
-        if f.getnchannels() == 1:
-            # fast path for mono
-            table = self.plugins.sampler.table_bytearray
-            for i in range(0, self.len_frames * 2, 100):
-                table[i : i + 100] = f.readframes(50)
+            if f.getnchannels() == 1:
+                # fast path for mono
+                table = self.plugins.sampler.table_bytearray
+                for i in range(0, self.len_frames * 2, 100):
+                    table[i : i + 100] = f.readframes(50)
+            else:
+                # somewhat fast path for stereo
+                table = self.plugins.sampler.table_int16_array
+                for i in range(self.len_frames):
+                    frame = f.readframes(1)
+                    value = int.from_bytes(frame[0:2], "little")
+                    table[i] = value
+
+            f.close()
+            self._filename = filename
         else:
-            # somewhat fast path for stereo
-            table = self.plugins.sampler.table_int16_array
-            for i in range(self.len_frames):
-                frame = f.readframes(1)
-                value = int.from_bytes(frame[0:2], "little")
-                table[i] = value
-
-        f.close()
-        self._filename = filename
+            self.len_frames = int(48 * init_var)
+            self.plugins.sampler = chan.new(
+                bl00mbox.plugins._sampler_ram, self.len_frames
+            )
+            self._filename = ""
+
         self.signals.trigger = self.plugins.sampler.signals.trigger
         self.signals.output = self.plugins.sampler.signals.output
+        self.signals.rec_in = self.plugins.sampler.signals.rec_in
+        self.signals.rec_trigger = self.plugins.sampler.signals.rec_trigger
 
     @property
     def filename(self):
diff --git a/python_payload/mypystubs/audio.pyi b/python_payload/mypystubs/audio.pyi
index e084c7dc41..40570a450a 100644
--- a/python_payload/mypystubs/audio.pyi
+++ b/python_payload/mypystubs/audio.pyi
@@ -18,3 +18,14 @@ def set_mute(v: bool) -> None:
 
 def get_mute() -> bool:
     pass
+
+def input_set_source(source: int) -> None:
+    pass
+
+def input_get_source() -> int:
+    pass
+
+INPUT_SOURCE_NONE: int
+INPUT_SOURCE_LINE_IN: int
+INPUT_SOURCE_HEADSET_MIC: int
+INPUT_SOURCE_ONBOARD_MIC: int
diff --git a/python_payload/mypystubs/bl00mbox/_user.pyi b/python_payload/mypystubs/bl00mbox/_user.pyi
index 37956c8d43..1332563d5b 100644
--- a/python_payload/mypystubs/bl00mbox/_user.pyi
+++ b/python_payload/mypystubs/bl00mbox/_user.pyi
@@ -48,6 +48,7 @@ class Channel:
     mixer: "ChannelMixer"
     foreground: bool
     free: bool
+    volume: int
 
     def __init__(self, name: str): ...
     def clear(self) -> None: ...
-- 
GitLab