diff --git a/esp32/Makefile b/esp32/Makefile
index fefaced23..8a43e3429 100644
--- a/esp32/Makefile
+++ b/esp32/Makefile
@@ -92,6 +92,18 @@ INC += -I../../components/ed25519/include
 INC += -I../../components/redundancy
 INC += -I../../components/badge-first-run
 INC += -I../../components/png
+INC += -I../../components/audio_pipeline/include
+INC += -I../../components/audio_hal/board
+INC += -I../../components/audio_sal/include
+INC += -I../../components/audio_stream/include
+INC += -I../../components/esp_http_client/include
+INC += -I../../components/esp_http_client/lib/include
+INC += -I../../components/esp_peripherals/include
+INC += -I../../components/esp_peripherals/lib/blufi
+INC += -I../../components/tcp_transport/include
+INC += -I../../components/esp-adf-libs/esp_codec/include/codec
+INC += -I../../components/esp-adf-libs/esp_codec/include/processing
+INC += -I../../components/esp-tls
 INC += -I../../main
 INC += -I../../ugfx/src/gdisp/mcufont
 INC += -I../../ugfx
@@ -190,6 +202,7 @@ SRC_C = \
 	modsocket.c \
 	modesp.c \
 	modbpp.c \
+	modaudio.c \
 	modbadge.c \
 	modugfx.c \
 	modfreedomgfx.c \
@@ -323,6 +336,8 @@ ESPIDF_ESP32_O = $(addprefix $(ESPCOMP)/esp32/,\
 	ets_timer_legacy.o \
 	esp_err_to_name.o \
 	dbg_stubs.o \
+	spiram.o \
+	spiram_psram.o \
 	)
 
 ESPIDF_PTHREAD_O = $(addprefix $(ESPCOMP)/pthread/,\
@@ -682,6 +697,28 @@ ESPIDF_WPA_SUPPLICANT_O = $(addprefix $(ESPCOMP)/wpa_supplicant/,\
 	port/os_xtensa.o \
 	)
 
+ESPADF_O = $(addprefix $(PROJECT_PATH)/components/,\
+	audio_pipeline/audio_pipeline.o \
+	audio_pipeline/audio_element.o \
+	audio_pipeline/audio_event_iface.o \
+	audio_pipeline/ringbuf.o \
+	audio_sal/audio_mem.o \
+	audio_stream/fatfs_stream.o \
+	audio_stream/http_stream.o \
+	audio_stream/i2s_stream.o \
+	esp_http_client/esp_http_client.o \
+	esp_http_client/lib/http_header.o \
+	esp_http_client/lib/http_auth.o \
+	esp_http_client/lib/http_utils.o \
+	tcp_transport/transport.o \
+	tcp_transport/transport_ssl.o \
+	tcp_transport/transport_tcp.o \
+	tcp_transport/transport_utils.o \
+	esp-tls/esp_tls.o \
+	esp_peripherals/periph_wifi.o \
+	esp_peripherals/esp_peripherals.o \
+	)
+
 OBJ_ESPIDF =
 OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_NEWLIB_O))
 OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_DRIVER_O))
@@ -712,6 +749,7 @@ OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_WPA_SUPPLICANT_O))
 OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_APP_UPDATE_O))
 OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_BOOTLOADER_O))
 OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SDMMC_O))
+OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPADF_O))
 
 ####################
 # Badge magic
@@ -721,6 +759,7 @@ BADGE_COMPONENTS_O = $(addprefix $(PROJECT_PATH)/components/,\
 	badge/badge_base.o \
 	badge/badge_eink.o \
 	badge/badge_i2c.o \
+	badge/badge_spi.o \
 	badge/badge_leds.o \
 	badge/badge_mpr121.o \
 	badge/badge_disobey_samd.o \
@@ -779,9 +818,9 @@ $(BUILD)/firmware.bin: $(BUILD)/bootloader.bin $(BUILD)/partitions.bin $(BUILD)/
 	$(ECHO) "Create $@"
 	$(Q)$(PYTHON) makeimg.py $^ $@
 
-deploy: $(BUILD)/firmware.bin
-	$(ECHO) "Writing $^ to the board"
-	$(Q)$(ESPTOOL) --chip esp32 --port $(PORT) --baud $(BAUD) write_flash -z --flash_mode $(FLASH_MODE) --flash_freq $(FLASH_FREQ) 0x1000 $^
+deploy: $(BUILD)/application.bin
+	$(ECHO) "Writing full firmware to the board"
+	$(Q)$(ESPTOOL) --chip esp32 --port $(PORT) --baud $(BAUD) write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect --flash_mode $(FLASH_MODE) --flash_freq $(FLASH_FREQ) 0x1000 ../../build/bootloader/bootloader.bin 0x8000 ../../build/partitions-16MB.bin 0x10000 build/application.bin
 
 flash: $(BUILD)/firmware.bin
 	$(ECHO) "Writing $^ to the board"
@@ -819,6 +858,7 @@ APP_LD_ARGS += -lgcov
 APP_LD_ARGS += $(ESPCOMP)/newlib/lib/libc.a
 APP_LD_ARGS += $(ESPCOMP)/newlib/lib/libm.a
 APP_LD_ARGS += $(ESPCOMP)/esp32/libhal.a
+APP_LD_ARGS += $(PROJECT_PATH)/components/esp-adf-libs/esp_codec/lib/libesp_codec.a
 APP_LD_ARGS += $(BADGE_LIBS)
 APP_LD_ARGS += -L$(ESPCOMP)/esp32/lib -lcore -lnet80211 -lmesh -lphy -lrtc -lpp -lwpa -lwpa2 -lwps -lsmartconfig -lcoexist
 APP_LD_ARGS += $(OBJ)
diff --git a/esp32/modaudio.c b/esp32/modaudio.c
new file mode 100644
index 000000000..b4f81f087
--- /dev/null
+++ b/esp32/modaudio.c
@@ -0,0 +1,386 @@
+#include <string.h>
+
+#include "py/mperrno.h"
+#include "py/mphal.h"
+#include "py/runtime.h"
+
+#include "esp_log.h"
+#include "audio_element.h"
+#include "audio_pipeline.h"
+#include "audio_event_iface.h"
+#include "audio_common.h"
+#include "fatfs_stream.h"
+#include "http_stream.h"
+#include "i2s_stream.h"
+#include "mp3_decoder.h"
+#include "badge_pins.h"
+#include "badge_power.h"
+
+#include "modaudio.h"
+
+#ifdef IIS_SCLK
+
+#define TAG "esp32/modaudio"
+
+STATIC mp_obj_t audio_volume(mp_uint_t n_args, const mp_obj_t *args) {
+    if (n_args > 0){
+        int v = mp_obj_get_int(args[0]);
+        if (v < 0) v = 0;
+        else if (v > 128) v = 128;
+        i2s_stream_volume = v;
+    }
+
+    return mp_obj_new_int(i2s_stream_volume);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(audio_volume_obj, 0, 1, audio_volume);
+
+/* current active stream */
+static bool audio_stream_active = false;
+static audio_event_iface_handle_t evt;
+static audio_pipeline_handle_t pipeline = NULL;
+static audio_element_handle_t fatfs_stream_reader = NULL;
+static audio_element_handle_t http_stream_reader = NULL;
+static audio_element_handle_t wav_decoder = NULL;
+static audio_element_handle_t mp3_decoder = NULL;
+static audio_element_handle_t i2s_stream_writer = NULL;
+
+static void
+_modaudio_stream_cleanup(void)
+{
+    ESP_LOGI(TAG, "[ 6 ] Stop audio_pipeline");
+    audio_pipeline_terminate(pipeline);
+
+    /* Terminate the pipeline before removing the listener */
+    if (fatfs_stream_reader != NULL) {
+        audio_pipeline_unregister(pipeline, fatfs_stream_reader);
+    } else if (http_stream_reader != NULL) {
+        audio_pipeline_unregister(pipeline, http_stream_reader);
+    }
+
+    audio_pipeline_unregister(pipeline, i2s_stream_writer);
+
+    if (wav_decoder != NULL) {
+        audio_pipeline_unregister(pipeline, wav_decoder);
+    } else if (mp3_decoder != NULL) {
+        audio_pipeline_unregister(pipeline, mp3_decoder);
+    }
+
+    audio_pipeline_remove_listener(pipeline);
+
+    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
+    audio_event_iface_destroy(evt);
+    evt = NULL;
+
+    /* Release all resources */
+    audio_pipeline_deinit(pipeline);
+    pipeline = NULL;
+
+    if (fatfs_stream_reader != NULL) {
+        audio_element_deinit(fatfs_stream_reader);
+        fatfs_stream_reader = NULL;
+    } else if (http_stream_reader != NULL) {
+        audio_element_deinit(http_stream_reader);
+        http_stream_reader = NULL;
+    }
+
+    audio_element_deinit(i2s_stream_writer);
+    i2s_stream_writer = NULL;
+
+    if (wav_decoder != NULL) {
+        audio_element_deinit(wav_decoder);
+        wav_decoder = NULL;
+    } else if (mp3_decoder != NULL) {
+        audio_element_deinit(mp3_decoder);
+        mp3_decoder = NULL;
+    }
+
+    audio_stream_active = false;
+}
+
+static void
+_modaudio_event_listener_task(void *arg)
+{
+    while (1) {
+        audio_event_iface_msg_t msg;
+        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
+            continue;
+        }
+
+        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
+            && msg.source == (void *) mp3_decoder
+            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
+            audio_element_info_t music_info = {0};
+            audio_element_getinfo(mp3_decoder, &music_info);
+
+            ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
+                     music_info.sample_rates, music_info.bits, music_info.channels);
+
+            audio_element_setinfo(i2s_stream_writer, &music_info);
+            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
+            continue;
+        }
+
+        /* Stop when the last pipeline element (i2s_stream_writer in this case) receives stop event */
+        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) i2s_stream_writer
+            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_STATE_STOPPED) {
+            ESP_LOGW(TAG, "[ * ] Stop event received");
+            break;
+        }
+    }
+
+    _modaudio_stream_cleanup();
+
+    vTaskDelete(NULL);
+}
+
+static void _init_event(void) {
+    static const audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
+    evt = audio_event_iface_init(&evt_cfg);
+    assert( evt != NULL );
+}
+
+static void _init_pipeline(void) {
+    static const audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
+    pipeline = audio_pipeline_init(&pipeline_cfg);
+    assert( pipeline != NULL );
+}
+
+static void _init_fatfs_stream(void) {
+    static fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
+    fatfs_cfg.type = AUDIO_STREAM_READER; // FIXME: to make const structure
+    fatfs_stream_reader = fatfs_stream_init(&fatfs_cfg);
+    assert( fatfs_stream_reader != NULL );
+}
+
+static void _init_http_stream(void) {
+    http_stream_cfg_t http_cfg = HTTP_STREAM_CFG_DEFAULT();
+    http_stream_reader = http_stream_init(&http_cfg);
+    assert( http_stream_reader != NULL );
+}
+
+static void _init_mp3_decoder(void) {
+    static const mp3_decoder_cfg_t mp3_cfg = DEFAULT_MP3_DECODER_CONFIG();
+    mp3_decoder = mp3_decoder_init(&mp3_cfg);
+    assert( mp3_decoder != NULL );
+}
+
+static void _init_i2s_stream(void) {
+    static const i2s_stream_cfg_t i2s_cfg = {
+        .type           = AUDIO_STREAM_WRITER,
+        .task_prio      = I2S_STREAM_TASK_PRIO,
+        .task_core      = I2S_STREAM_TASK_CORE,
+        .task_stack     = I2S_STREAM_TASK_STACK,
+        .out_rb_size    = I2S_STREAM_RINGBUFFER_SIZE,
+        .i2s_config     = {
+            .mode                 = I2S_MODE_MASTER | I2S_MODE_TX, // write only
+            .sample_rate          = 44100, // set to 48000 ?
+            .bits_per_sample      = 16,
+            .channel_format       = I2S_CHANNEL_FMT_RIGHT_LEFT,
+            .communication_format = I2S_COMM_FORMAT_I2S,
+            .dma_buf_count        = 3,
+            .dma_buf_len          = 300,
+            .use_apll             = 0, // real sample-rate is too far off when enabled
+            .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL2,
+        },
+        .i2s_pin_config = {
+            .bck_io_num   = IIS_SCLK,
+            .ws_io_num    = IIS_LCLK,
+            .data_out_num = IIS_DSIN,
+            .data_in_num  = IIS_DOUT,
+        },
+        .i2s_port       = 0,
+    };
+
+    i2s_stream_writer = i2s_stream_init(&i2s_cfg);
+    assert( i2s_stream_writer != NULL );
+}
+
+STATIC mp_obj_t audio_play_mp3_file(mp_obj_t _file) {
+    const char *file = mp_obj_str_get_str(_file);
+
+    if (audio_stream_active) {
+        ESP_LOGE(TAG, "another audio stream is already playing");
+        return mp_const_none;
+    }
+
+    audio_stream_active = true;
+
+#ifdef AUDIO_NEEDS_EXT_POWER
+    badge_power_sdcard_enable();
+#endif // AUDIO_NEEDS_EXT_POWER
+
+    _init_pipeline();
+    _init_fatfs_stream();
+    _init_i2s_stream();
+    _init_mp3_decoder();
+
+    // configure pipeline
+    audio_pipeline_register(pipeline, fatfs_stream_reader,"file");
+    audio_pipeline_register(pipeline, mp3_decoder,        "mp3");
+    audio_pipeline_register(pipeline, i2s_stream_writer,  "i2s");
+
+    audio_pipeline_link(pipeline, (const char *[]) {"file", "mp3", "i2s"}, 3);
+
+    // start stream
+    if (*file == 0) { // empty string; keep as hack to test audio
+        audio_element_set_uri(fatfs_stream_reader, "/sdcard/audio/ff-16b-2c-44100hz.mp3");
+    } else {
+        audio_element_set_uri(fatfs_stream_reader, file);
+    }
+
+    _init_event();
+
+    audio_pipeline_set_listener(pipeline, evt);
+
+    audio_pipeline_run(pipeline);
+
+    while (1) {
+        audio_event_iface_msg_t msg;
+        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
+            continue;
+        }
+
+        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
+            && msg.source == (void *) mp3_decoder
+            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
+            audio_element_info_t music_info = {0};
+            audio_element_getinfo(mp3_decoder, &music_info);
+
+            ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
+                     music_info.sample_rates, music_info.bits, music_info.channels);
+
+            audio_element_setinfo(i2s_stream_writer, &music_info);
+            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
+
+            // stream is playing; return
+            xTaskCreate(&_modaudio_event_listener_task, "modaudio event-listener task", 4096, NULL, 10, NULL);
+            return mp_const_none;
+        }
+
+        /* Stop when the last pipeline element (i2s_stream_writer in this case) receives stop event */
+        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) i2s_stream_writer
+            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_STATE_STOPPED) {
+            ESP_LOGW(TAG, "[ * ] Stop event received");
+            break;
+        }
+    }
+
+    _modaudio_stream_cleanup();
+
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(audio_play_mp3_file_obj, audio_play_mp3_file);
+
+STATIC mp_obj_t audio_play_mp3_stream(mp_obj_t _url) {
+    const char *url = mp_obj_str_get_str(_url);
+
+    if (audio_stream_active) {
+        ESP_LOGE(TAG, "another audio stream is already playing");
+        return mp_const_none;
+    }
+
+    audio_stream_active = true;
+
+#ifdef AUDIO_NEEDS_EXT_POWER
+    badge_power_sdcard_enable();
+#endif // AUDIO_NEEDS_EXT_POWER
+
+    _init_pipeline();
+    _init_http_stream();
+    _init_i2s_stream();
+    _init_mp3_decoder();
+
+    // configure pipeline
+    audio_pipeline_register(pipeline, http_stream_reader, "http");
+    audio_pipeline_register(pipeline, mp3_decoder,        "mp3");
+    audio_pipeline_register(pipeline, i2s_stream_writer,  "i2s");
+
+    audio_pipeline_link(pipeline, (const char *[]) {"http", "mp3", "i2s"}, 3);
+
+    // start stream
+    if (*url == 0) { // empty string; keep as hack to test audio
+        audio_element_set_uri(http_stream_reader, "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.mp3");
+    } else {
+        audio_element_set_uri(http_stream_reader, url);
+    }
+
+    _init_event();
+
+    audio_pipeline_set_listener(pipeline, evt);
+
+    audio_pipeline_run(pipeline);
+
+    while (1) {
+        audio_event_iface_msg_t msg;
+        esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
+        if (ret != ESP_OK) {
+            ESP_LOGE(TAG, "[ * ] Event interface error : %d", ret);
+            continue;
+        }
+
+        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
+            && msg.source == (void *) mp3_decoder
+            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
+            audio_element_info_t music_info = {0};
+            audio_element_getinfo(mp3_decoder, &music_info);
+
+            ESP_LOGI(TAG, "[ * ] Receive music info from mp3 decoder, sample_rates=%d, bits=%d, ch=%d",
+                     music_info.sample_rates, music_info.bits, music_info.channels);
+
+            audio_element_setinfo(i2s_stream_writer, &music_info);
+            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
+
+            // stream is playing; return
+            xTaskCreate(&_modaudio_event_listener_task, "modaudio event-listener task", 4096, NULL, 10, NULL);
+            return mp_const_none;
+        }
+
+        /* Stop when the last pipeline element (i2s_stream_writer in this case) receives stop event */
+        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) i2s_stream_writer
+            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && (int) msg.data == AEL_STATUS_STATE_STOPPED) {
+            ESP_LOGW(TAG, "[ * ] Stop event received");
+            break;
+        }
+    }
+
+    _modaudio_stream_cleanup();
+
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(audio_play_mp3_stream_obj, audio_play_mp3_stream);
+
+STATIC mp_obj_t audio_stop(void) {
+    if (pipeline != NULL) {
+        audio_pipeline_stop(pipeline);
+    }
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_0(audio_stop_obj, audio_stop);
+
+#endif // IIS_SCLK
+
+
+// Module globals
+STATIC const mp_rom_map_elem_t audio_module_globals_table[] = {
+    {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_audio)},
+
+#ifdef IIS_SCLK
+    {MP_OBJ_NEW_QSTR(MP_QSTR_volume), (mp_obj_t)&audio_volume_obj},
+
+    {MP_OBJ_NEW_QSTR(MP_QSTR_play_mp3_file), (mp_obj_t)&audio_play_mp3_file_obj},
+    {MP_OBJ_NEW_QSTR(MP_QSTR_play_mp3_stream), (mp_obj_t)&audio_play_mp3_stream_obj},
+
+    {MP_OBJ_NEW_QSTR(MP_QSTR_stop), (mp_obj_t)&audio_stop_obj},
+#endif // IIS_SCLK
+};
+
+STATIC MP_DEFINE_CONST_DICT(audio_module_globals, audio_module_globals_table);
+
+const mp_obj_module_t audio_module = {
+    .base = {&mp_type_module},
+    .globals = (mp_obj_dict_t *)&audio_module_globals,
+};
diff --git a/esp32/modaudio.h b/esp32/modaudio.h
new file mode 100644
index 000000000..1a702c776
--- /dev/null
+++ b/esp32/modaudio.h
@@ -0,0 +1,3 @@
+#include <stdbool.h>
+#include <stdio.h>
+
diff --git a/esp32/modules_disobey2019/ir.py b/esp32/modules_disobey2019/ir.py
index d6ead382b..34bf0eccd 100644
--- a/esp32/modules_disobey2019/ir.py
+++ b/esp32/modules_disobey2019/ir.py
@@ -1,84 +1,164 @@
-from machine import Pin, PWM
-from time import sleep_ms
+import gc
+import machine
+import time
 
-optics_sleep = 50
+# Basics for receiving:
+# * Startup by enabling the IR receiver to eat power
+# * Enable an interrupt (hardware) for receiving the initial pulse
+# * When the interrupt is fired disable the rest of the interrups for the duration of this read
+# * Read the pulses with time_pulse_us (it's extremely fast)
+# * Store this in a buffer with a fixed size, (256 empty arrays)
+# * Set a timer to decode the data instantly after interrupts are enabled again.
+# * a class can inherit this class and just define a decoder and specify a send pattern 
+# * if a send pattern is not the way to go (some totally different protocols) you can just implement another tx function
 
-pin_rx_enable = Pin(21, Pin.OUT)
-pin_rx        = Pin(18, Pin.IN)
-pin_tx        = Pin(19, Pin.OUT)
-pwm_tx        = PWM(pin_tx, freq=38000, duty=0)
+class BadgeIr():
+    freq = 38000
+    ticks = 500
+    start = 4000
+    rxpin = 18
+    txpin = 19
+    rxenablepin = 21
+    rxtimer = None
+    readcallback = None
+    bitform = []
+    pwm_tx = None
 
-# Functions for switching power to the receiver on and off
+    def initbuffer(self):
+        self.buffer=[[]] * 256
+        self.bufpos = 0
+    # If it returns 1 the buffer gets emptied out and initialized.
+    # This is the default, protocol implementations should choose if they want to.
+    def decoder(self):
+        return 1
+    def real_decoder(self,timer):
+        self.rxtimer.deinit()
+        self.rxtimer = None
+        if self.decoder():
+            self.initbuffer()
+    def cleanbuffer(self,i):
+        self.buffer=self.buffer[i:256]+[[]]*i
+        self.bufpos-=i
+        gc.collect()
+    def mr(self):
+        if self.bufpos == len(self.buffer):
+            return 0
+        waitfor=0 if self.pin_rx.value() else 1
+        while True:
+            t=machine.time_pulse_us(self.pin_rx,waitfor,50*1000)
+            if t<0:
+                if t == -2:
+                    return 1
+                return 0
+            elif t>0:
+                waitfor = 0 if waitfor else 1
+                self.buffer[self.bufpos]=[waitfor,round(t/self.ticks)]
+                self.bufpos+=1
+                if self.bufpos == len(self.buffer):
+                    return 1
+    def callback(self,pin):
+        irqs = machine.disable_irq()
+        hasdata = self.mr()
+        machine.enable_irq(irqs)
+        if hasdata and not self.rxtimer:
+            self.rxtimer = machine.Timer(1)
+            self.rxtimer.init(mode=machine.Timer.ONE_SHOT, period=1,callback=self.real_decoder)
+    def rx_enable(self):
+        self.pin_rx_enable = machine.Pin(self.rxenablepin, machine.Pin.OUT)
+        self.pin_rx        = machine.Pin(self.rxpin, machine.Pin.IN)
+        self.initbuffer()
+        self.pin_rx_enable.value(True)
+        self.pin_rx.irq(trigger=machine.Pin.IRQ_FALLING, handler=self.callback)
+    def rx_disable(self):
+        self.pin_rx.irq(trigger=0, handler=self.callback)
+        self.pin_rx_enable.value(False)
+    def tx_enable(self):
+        if not self.pwm_tx:
+            self.pin_tx        = machine.Pin(self.txpin, machine.Pin.OUT)
+            self.pwm_tx        = machine.PWM(self.pin_tx, freq = self.freq, duty = 0)
+    def tx_disable(self):
+        self.pwm_tx.duty(0)
+    def tx_setduty(self,duty):
+        self.pwm_tx.duty(512 * duty)
+    def txBit(self,bit):
+        for (onoff,tijd) in self.bitform[bit]:
+            self.tx_setduty(onoff)
+            time.sleep_us(tijd)
+    def txByte(self,byte):
+        for bit in range(8):
+                self.txBit( ( byte >> ( 7 - bit ) ) & 1 ) # MSB
 
-def enableRx(state=True):
-	pin_rx_enable.value(state)
-	
-def disableRx():
-	pin_rx_enable.value(False)
+class NecIR(BadgeIr):
+    # Implements NEC Infrared 
+    # Example:
+    #    IR=NecIR()
+    #    NecIR.command= <function (address,command)>
+    #    NecIR.repeat=  <function ()>
+    #    NecIR.rx_enable()
+    #  To stop receiving:
+    #    NecIR.rx_disable()
+    #  To send:
+    #    NecIR.tx(<byte address>,<byte command>)
+    #    NecIR.tx_repeat()
+    command = None
+    repeat = None
+    bitform = { 0: [[1,562],[0,562]], 1: [[1,562],[0,1687]], 's': [[1,9000],[0,4500]], 'e': [[1,562],[0,100]], 'r': [[1,9000],[0,2500],[1,562],[0,100]] }
 
-# Functions for directly talking to the receiver and LED
+    def tx(self,addr,cmd):
+        self.tx_enable()
+        self.txBit('s')
+        self.txByte(addr)
+        self.txByte(addr ^ 0xFF)
+        self.txByte(cmd)
+        self.txByte(cmd ^ 0xFF)
+        self.txBit('e')
+        self.tx_disable()
 
-def rawTx(p):
-	pwm_tx.duty(512*p)
-	
-def rawRx():
-	return not pin_rx.value()
+    def tx_repeat(self):
+        self.txBit('r')
 
-# Helper functions for the (proof of concept) text transmission feature
-
-def _txByte(byte):
-	for bit in range(8):
-		rawTx((byte>>(7-bit))&1)
-		sleep_ms(optics_sleep)
-
-buf = 0
-
-def _rxBit():
-	global buf
-	buf = ((buf<<1) + rawRx()*1)&0xFFFF
-	sleep_ms(optics_sleep)
-
-def _rxByte():
-	global buf
-	for i in range(8):
-		_rxBit()
-	return buf & 0xFF
-
-def _rxWait(timeout=100):
-	global buf
-	cnt = 0
-	while True:
-		_rxBit()
-		if buf&0xFFFF == 0b1110101010101011:
-			buf = 0
-			return True
-		#rxDebug()
-		cnt+=1
-		if cnt > timeout:
-			return False
-
-# Functions for text transmission (proof of concept)
-
-def tx(data):
-	rawTx(False)
-	sleep_ms(optics_sleep*8)
-	_txByte(0b11101010)
-	_txByte(0b10101011)
-	for char in data:
-		txByte(ord(char))
-	rawTx(False)
-
-def rx(timeout=100):
-	pin_rx_enable.value(True)
-	global buf
-	string = ""
-	buf = 0
-	if not _rxWait(timeout):
-		pin_rx_enable.value(False)
-		return None
-	while True:
-		b = _rxByte()
-		if b < 1:
-			pin_rx_enable.value(False)
-			return string
-		string += chr(b)
+    def decoder(self):
+        decoded=0
+        i=0
+	while True and self.bufpos-i>0:
+            (val,time)=self.buffer[i]
+            i+=1
+            if val==0 and time==9:
+                if self.bufpos<66: return(0) # Not yet complete....
+                p1=None
+                p2=None
+                bits=0
+                while True and self.bufpos-i>0:
+                    (val,time)=self.buffer[i]
+                    i+=1
+                    if time>0:
+                      if p1==None:
+                          p1=(val,time)
+                          if bits==32 and p1[1]==1:
+                              self.cleanbuffer(i)
+                              if (decoded >> 24 & 0xFF) == (0xFF ^ (decoded >> 16 & 0xFF)) and (decoded >> 8 & 0xFF) == (0xFF ^ (decoded >> 0 & 0xFF)) and self.command:
+                                   self.command(decoded >> 24 & 0xFF,decoded >> 8 & 0xFF)
+                              return(0)
+                      else:
+                          p2=(val,time)
+                          if p1[1]==1 and p2[1]==3:
+                             decoded=decoded<<1 | 1
+                             bits+=1
+                          elif p1[1]==1 and p2[1]==1:
+                             decoded=decoded<<1
+                             bits+=1
+                          if bits==32 and p2==None:
+                              self.cleanbuffer(i)
+                              return(0)
+                          p1=None
+                          p2=None
+                    elif time<0:
+                        self.cleanbuffer(i)
+                        return(0)
+            elif val==1 and time==18:
+                if self.buffer[i] == [0,4] and self.buffer[i+1] == [1,1]:
+                    i+=2
+                    if self.repeat: self.repeat()
+                    return(0)
+        self.cleanbuffer(i)
+        return(0)
diff --git a/esp32/mpconfigport.h b/esp32/mpconfigport.h
index c6a4c221a..e41776b64 100644
--- a/esp32/mpconfigport.h
+++ b/esp32/mpconfigport.h
@@ -175,6 +175,7 @@ extern const struct _mp_obj_module_t uos_module;
 extern const struct _mp_obj_module_t mp_module_usocket;
 extern const struct _mp_obj_module_t mp_module_machine;
 extern const struct _mp_obj_module_t mp_module_network;
+extern const struct _mp_obj_module_t audio_module;
 extern const struct _mp_obj_module_t badge_module;
 extern const struct _mp_obj_module_t bpp_module;
 extern const struct _mp_obj_module_t ugfx_module;
@@ -188,6 +189,7 @@ extern const struct _mp_obj_module_t mp_module_onewire;
     { MP_OBJ_NEW_QSTR(MP_QSTR_usocket), (mp_obj_t)&mp_module_usocket }, \
     { MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \
     { MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \
+    { MP_OBJ_NEW_QSTR(MP_QSTR_audio), (mp_obj_t)&audio_module }, \
     { MP_OBJ_NEW_QSTR(MP_QSTR_badge), (mp_obj_t)&badge_module }, \
     { MP_OBJ_NEW_QSTR(MP_QSTR_bpp), (mp_obj_t)&bpp_module }, \
     { MP_OBJ_NEW_QSTR(MP_QSTR_ugfx), (mp_obj_t)&ugfx_module }, \