diff --git a/README.md b/README.md
index 78150a0af9a5b5043b678192709044affc783e22..66d022cfaf1cc73b6eeee3dd46ae5e358c1751d8 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,33 @@
 plays an infinite scripted note sequence and provides a basic micropython repl
 makes extra noise when Crtl+D'd in the repl (importing py libraries works too)
 
+some fun commands to try:
+'''
+import badge_audio
+#turn on sound
+badge_audio.set_global_volume_dB(-10)
+#turn off all demo oscillators
+badge_audio.dump_all_sources()
+
+import synth
+a=synth.tinysynth(440,1);
+a.start();
+b=synth.tinysynth(660,0);
+b.start();
+b.stop();
+
+#tiny issue with garbage collect:
+badge_audio.count_sources();
+a.__del__();
+badge_audio.count_sources();
+import gc
+del b
+gc.collect()
+badge_audio.count_sources();
+#...don't know how to hook up gc to __del__, maybe wrong approach
+
+'''
+
 ## how to build
 
 1. install esp-idf v4.4:
@@ -51,7 +78,7 @@ $ picocom -b 115200 /dev/ttyACM0
 
 ## how to modify
 
-general info
+### general info
 global + micropython entry point: app_main() in micropython/ports/esp32/main.c (includes badge23/espan.h)
 c entry point, called by^: os_app_main() in badge23/espan.c
 register new c files for compilation: add to set(BADGE23_LIB) in micropython/ports/esp32/main/CMakelists.txt
diff --git a/badge23/audio.c b/badge23/audio.c
index f7952636aff3edfe32eae92d4d0af7f33f7b1063..b71d09f92b3ef1cf01bf8df344d8d97b74f9a20d 100644
--- a/badge23/audio.c
+++ b/badge23/audio.c
@@ -10,6 +10,8 @@
 #include <stdio.h>
 #include <math.h>
 #include <string.h>
+#include "../../py/mphal.h"
+
 
 #define DRUMS_TOP 0
 
@@ -22,51 +24,6 @@ static void audio_player_task(void* arg);
 #define DMA_BUFFER_COUNT    2
 #define I2S_PORT 0
 
-#if 0
-static i2s_chan_handle_t                tx_chan;        // I2S tx channel handler
-static i2s_chan_handle_t                rx_chan;        // I2S rx channel handler
-
-static void i2s_init_std_duplex(void)
-{
-    /* Setp 1: Determine the I2S channel configuration and allocate both channels
-     * The default configuration can be generated by the helper macro,
-     * it only requires the I2S controller id and I2S role */
-    i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
-    chan_cfg.dma_desc_num = DMA_BUFFER_COUNT;
-    chan_cfg.dma_frame_num = DMA_BUFFER_SIZE;
-
-    // Play silence after all DMA buffers are empty
-    chan_cfg.auto_clear = true;
-
-    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan));
-
-    /* Step 2: Setting the configurations of standard mode, and initialize rx & tx channels
-     * The slot configuration and clock configuration can be generated by the macros
-     * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode.
-     * They can help to specify the slot and clock configurations for initialization or re-configuring */
-    i2s_std_config_t std_cfg = {
-        .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
-        //.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
-        .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
-        .gpio_cfg = {
-            .mclk = 11,
-            .bclk = 13,
-            .ws   = 12,
-            .dout = 14,
-            .din  = I2S_GPIO_UNUSED,
-            .invert_flags = {
-                .mclk_inv = false,
-                .bclk_inv = false,
-                .ws_inv   = false,
-            },
-        },
-    };
-    /* Initialize the channels */
-    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg));
-    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
-}
-#endif
-
 static void i2s_init_idk_lol(void){
     
     static const i2s_config_t i2s_config = {
@@ -123,6 +80,69 @@ static float FREQ_TABLE[] = {
 };
 #endif
 
+typedef struct _audio_source_t{
+    void * render_data;
+    float (* render_function)(void *);
+    uint16_t index;
+    struct _audio_source_t * next;
+} audio_source_t;
+
+static audio_source_t * _audio_sources = NULL;
+
+uint16_t add_audio_source(void * render_data, void * render_function){
+    audio_source_t * src = malloc(sizeof(audio_source_t));
+    if(src == NULL) return;
+    src->render_data = render_data;
+    src->render_function = render_function;
+    src->next = NULL;
+    src->index = 0;
+
+    audio_source_t * audio_source = _audio_sources;
+    if(audio_source == NULL){
+        
+        _audio_sources = src;
+    }
+    while(audio_source != NULL){
+        src->index++;
+        if(audio_source->next == NULL){
+            audio_source->next = src;
+            break;
+        } else {
+        audio_source = audio_source->next;
+        }
+    }
+    return src->index;
+}
+
+void remove_audio_source(uint16_t index){
+    audio_source_t * audio_source = _audio_sources;
+    audio_source_t * start_gap = NULL;
+
+    while(audio_source != NULL){
+        if(index == audio_source->index){
+            if(start_gap == NULL){
+                _audio_sources = audio_source->next;
+            } else {
+                start_gap->next = audio_source->next;
+            }
+            free(audio_source);
+            break;
+        }
+        start_gap = audio_source;
+        audio_source = audio_source->next;
+    }
+}
+
+uint16_t count_audio_sources(){
+    uint16_t ret = 0;
+    audio_source_t * audio_source = _audio_sources;
+    while(audio_source != NULL){
+        audio_source = audio_source->next;
+        ret++;
+    }
+    return ret;
+}
+
 static void _audio_init(void) {
     init_scope(241);
     //i2s_init_std_duplex();
@@ -171,8 +191,8 @@ static void _audio_init(void) {
             synths[i].waveform = 8;
             synths[i].noise_reg = 1;
         }
+        add_audio_source(&(synths[i]), trad_osc);
     }
-
     TaskHandle_t handle;
     xTaskCreate(&audio_player_task, "Audio player", 20000, NULL, configMAX_PRIORITIES - 1, &handle);
 }
@@ -182,6 +202,7 @@ static void _audio_init(void) {
 #define NAT_LOG_DB 0.1151292546497023
 
 static uint16_t _global_vol = 3000;
+static void * _extra_synth = NULL;
 
 void set_global_vol_dB(int8_t vol_dB){
     if(vol_dB < (BADGE_MIN_VOLUME_DB)){
@@ -194,6 +215,14 @@ void set_global_vol_dB(int8_t vol_dB){
     }
 }
 
+void set_extra_synth(void * synth){
+    _extra_synth = synth;
+}
+
+void clear_extra_synth(){
+    _extra_synth = NULL;
+}
+
 static void audio_player_task(void* arg) {
     int16_t buffer[DMA_BUFFER_SIZE * 2];
     //memset(buffer, 0, sizeof(buffer));
@@ -202,9 +231,19 @@ static void audio_player_task(void* arg) {
 
         for(int i = 0; i < (DMA_BUFFER_SIZE * 2); i += 2){
             float sample = 0;
+            /*
             for(int j = 0; j<NUM_SYNTH; j++){
                 sample += trad_osc(&(synths[j]));
             }
+            if(_extra_synth != NULL){
+                sample += trad_osc((trad_osc_t * )_extra_synth);
+            }
+            */
+            audio_source_t * audio_source = _audio_sources;
+            while(audio_source != NULL){
+                sample += (*(audio_source->render_function))(audio_source->render_data);
+                audio_source = audio_source->next;
+            }
             write_to_scope((int16_t) (1600. * sample));
             sample = _global_vol * sample;
             if(sample > 32767) sample = 32767;
diff --git a/badge23/audio.h b/badge23/audio.h
index f6586c526fab5fc1f54eca2b31770edd00d3cc74..dddb44cfe6166edb5bc30345396e43e0aa2f8021 100644
--- a/badge23/audio.h
+++ b/badge23/audio.h
@@ -17,4 +17,9 @@ void synth_fullstop(int i);
 float synth_get_env(int i);
 
 void set_global_vol_dB(int8_t vol_dB);
+void set_extra_synth(void * synth);
+void clear_extra_synth();
+uint16_t count_audio_sources();
 
+uint16_t add_audio_source(void * render_data, void * render_function);
+void remove_audio_source(uint16_t index);
diff --git a/badge23/espan.c b/badge23/espan.c
index d7169d03bab5dd3f13e9042ada00acb21123729b..19dca30bd9c949bcfeb2310b9cd4f710abb94005 100644
--- a/badge23/espan.c
+++ b/badge23/espan.c
@@ -95,7 +95,7 @@ void os_app_main(void)
     ESP_LOGI(TAG, "I2C initialized successfully");
 
     vTaskDelay(1000 / portTICK_PERIOD_MS);
-    set_global_vol_dB(0);
+    set_global_vol_dB(-90);
 
     audio_init();
     leds_init();
diff --git a/micropython/ports/esp32/badge23_mp_audio.c b/micropython/ports/esp32/badge23_mp_audio.c
new file mode 100644
index 0000000000000000000000000000000000000000..c6aa49277e3249c7db9739d0c7cbab3f43fda2fa
--- /dev/null
+++ b/micropython/ports/esp32/badge23_mp_audio.c
@@ -0,0 +1,54 @@
+// probably doesn't need all of these idk
+#include <stdio.h>
+#include <string.h>
+
+#include "py/runtime.h"
+#include "py/mphal.h"
+#include "mphalport.h"
+#include "modmachine.h"
+#include "extmod/virtpin.h"
+#include "machine_rtc.h"
+#include "py/builtin.h"
+#include "py/runtime.h"
+#include "../badge23/audio.h"
+
+STATIC mp_obj_t mp_set_global_volume_dB(size_t n_args, const mp_obj_t *args) {
+    mp_float_t x = mp_obj_get_float(args[0]);
+    int8_t d = x;
+    set_global_vol_dB(d);
+    mp_float_t l = x;
+    return mp_obj_new_float(l);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_set_global_volume_dB_obj, 1, 2, mp_set_global_volume_dB);
+
+STATIC mp_obj_t mp_count_sources(size_t n_args, const mp_obj_t *args) {
+    uint16_t d = count_audio_sources();
+    return mp_obj_new_int(d);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_count_sources_obj, 0, 2, mp_count_sources);
+
+STATIC mp_obj_t mp_dump_all_sources(size_t n_args, const mp_obj_t *args) {
+    uint16_t d = count_audio_sources();
+    for(uint16_t i = 0; i < d; i++){
+        remove_audio_source(i);
+    }
+    return mp_obj_new_int(d);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_dump_all_sources_obj, 0, 2, mp_dump_all_sources);
+
+STATIC const mp_rom_map_elem_t mp_module_badge_audio_globals_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_badge_audio) },
+    { MP_ROM_QSTR(MP_QSTR_set_global_volume_dB), MP_ROM_PTR(&mp_set_global_volume_dB_obj) },
+    { MP_ROM_QSTR(MP_QSTR_count_sources), MP_ROM_PTR(&mp_count_sources_obj) },
+    { MP_ROM_QSTR(MP_QSTR_dump_all_sources), MP_ROM_PTR(&mp_dump_all_sources_obj) },
+};
+
+STATIC MP_DEFINE_CONST_DICT(mp_module_badge_audio_globals, mp_module_badge_audio_globals_table);
+
+const mp_obj_module_t mp_module_badge_audio = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t *)&mp_module_badge_audio_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_badge_audio, mp_module_badge_audio);
+
diff --git a/micropython/ports/esp32/badge23_mp_tinysynth.c b/micropython/ports/esp32/badge23_mp_tinysynth.c
new file mode 100644
index 0000000000000000000000000000000000000000..a1364c8be21187be658959f922d79dbef22b81b9
--- /dev/null
+++ b/micropython/ports/esp32/badge23_mp_tinysynth.c
@@ -0,0 +1,101 @@
+#include <stdio.h>
+#include "py/runtime.h"
+#include "py/obj.h"
+#include "../../../badge23/synth.h"
+#include "../../../badge23/audio.h"
+
+typedef struct _synth_tinysynth_obj_t {
+    mp_obj_base_t base;
+    trad_osc_t osc;
+    uint16_t source_index;
+} synth_tinysynth_obj_t;
+
+const mp_obj_type_t synth_tinysynth_type;
+
+STATIC void tinysynth_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+    (void)kind;
+    synth_tinysynth_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    mp_print_str(print, "tinysynth(");
+    mp_obj_print_helper(print, mp_obj_new_int(self->source_index), PRINT_REPR);
+    mp_print_str(print, ")");
+}
+
+STATIC mp_obj_t tinysynth_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+    mp_arg_check_num(n_args, n_kw, 2, 2, true);
+    synth_tinysynth_obj_t *self = m_new_obj(synth_tinysynth_obj_t);
+    self->base.type = &synth_tinysynth_type;
+    self->osc.decay_steps = 50;
+    self->osc.attack_steps = 3;
+    self->osc.vol = 1.;
+    self->osc.gate = 0.01;
+    self->osc.freq = mp_obj_get_int(args[0]);
+    self->osc.counter = 0;
+    self->osc.bend = 1;
+    self->osc.noise_reg = 1;
+    self->osc.waveform = 1;
+    self->osc.skip_hold = mp_obj_get_int(args[1]);
+    //set_extra_synth(&(self->osc));
+    self->source_index = add_audio_source(&(self->osc), trad_osc);
+
+    return MP_OBJ_FROM_PTR(self);
+}
+
+// Class methods
+STATIC mp_obj_t tinysynth_start(mp_obj_t self_in) {
+    synth_tinysynth_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    self->osc.env_phase = 1;
+    return mp_obj_new_int(1);
+}
+MP_DEFINE_CONST_FUN_OBJ_1(tinysynth_start_obj, tinysynth_start);
+
+STATIC mp_obj_t tinysynth_stop(mp_obj_t self_in) {
+    synth_tinysynth_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    if(self->osc.env_phase){
+        self->osc.env_phase = 3;
+    }
+    return mp_obj_new_int(1);
+}
+MP_DEFINE_CONST_FUN_OBJ_1(tinysynth_stop_obj, tinysynth_stop);
+
+STATIC mp_obj_t tinysynth_deinit(mp_obj_t self_in) {
+    synth_tinysynth_obj_t *self = MP_OBJ_TO_PTR(self_in);
+    remove_audio_source(self->source_index);
+    return mp_obj_new_int(1);
+}
+
+MP_DEFINE_CONST_FUN_OBJ_1(tinysynth_deinit_obj, tinysynth_deinit);
+
+STATIC const mp_rom_map_elem_t tinysynth_locals_dict_table[] = {
+    { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&tinysynth_start_obj) },
+    { MP_ROM_QSTR(MP_QSTR_stop), MP_ROM_PTR(&tinysynth_stop_obj) },
+    { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&tinysynth_deinit_obj) },
+};
+
+STATIC MP_DEFINE_CONST_DICT(tinysynth_locals_dict, tinysynth_locals_dict_table);
+
+
+MP_DEFINE_CONST_OBJ_TYPE(
+    synth_tinysynth_type,
+    MP_QSTR_synth,
+    MP_TYPE_FLAG_NONE,
+    make_new, tinysynth_make_new,
+    print, tinysynth_print,
+    locals_dict, &tinysynth_locals_dict
+    );    
+
+STATIC const mp_map_elem_t synth_globals_table[] = {
+    { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_synth) },
+    { MP_OBJ_NEW_QSTR(MP_QSTR_tinysynth), (mp_obj_t)&synth_tinysynth_type },
+};
+
+STATIC MP_DEFINE_CONST_DICT (
+    mp_module_synth_globals,
+    synth_globals_table
+);
+
+const mp_obj_module_t synth_user_cmodule = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t*)&mp_module_synth_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_synth, synth_user_cmodule);
diff --git a/micropython/ports/esp32/main/CMakeLists.txt b/micropython/ports/esp32/main/CMakeLists.txt
index 6a7dbc97feeed956f0605d7cf13c9af1e04f696c..d518187e1dd7702489f23f4a3348b27c78f16b40 100644
--- a/micropython/ports/esp32/main/CMakeLists.txt
+++ b/micropython/ports/esp32/main/CMakeLists.txt
@@ -60,6 +60,8 @@ set(MICROPY_SOURCE_DRIVERS
 )
 
 set(MICROPY_SOURCE_PORT
+    ${PROJECT_DIR}/badge23_mp_audio.c
+    ${PROJECT_DIR}/badge23_mp_tinysynth.c
     ${PROJECT_DIR}/main.c
     ${PROJECT_DIR}/uart.c
     ${PROJECT_DIR}/usb.c