diff --git a/src/audio_input.rs b/src/audio_input.rs
index f006f78..a7ac0bc 100644
--- a/src/audio_input.rs
+++ b/src/audio_input.rs
@@ -1,45 +1,49 @@
 use bytemuck::bytes_of_mut;
+use embassy_sync::blocking_mutex::raw::NoopRawMutex;
 use embassy_time::Duration;
 use esp_idf_svc::hal::{
     gpio::{AnyIOPin, Gpio4, Gpio5, Gpio6},
-    i2s::{self, I2sRx, I2S0},
+    i2s::{config, I2sDriver, I2sRx, I2S0},
 };
 
-use crate::{audio_process::AudioBuffer, config::AUDIO_SAMPLES_PER_BUF};
+use crate::{audio_process::AudioBuffer, config::AUDIO_SAMPLES_PER_BUF, triple_buffer::Sender};
 
 #[embassy_executor::task]
-pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) {
-    let std_cfg = i2s::config::StdConfig::new(
-        i2s::config::Config::new().role(i2s::config::Role::Controller),
-        i2s::config::StdClkConfig::new(
+pub async fn mic_input_task(
+    i2s_per: I2S0,
+    sd: Gpio5,
+    sclk: Gpio4,
+    ws: Gpio6,
+    mut sender: Sender<'static, NoopRawMutex, AudioBuffer>,
+) {
+    let std_cfg = config::StdConfig::new(
+        config::Config::new().role(config::Role::Controller),
+        config::StdClkConfig::new(
             24000,
-            i2s::config::ClockSource::default(),
-            i2s::config::MclkMultiple::M256,
+            config::ClockSource::default(),
+            config::MclkMultiple::M256,
         ),
-        i2s::config::StdSlotConfig::msb_slot_default(
-            i2s::config::DataBitWidth::Bits32,
-            i2s::config::SlotMode::Stereo,
-        )
-        .data_bit_width(i2s::config::DataBitWidth::Bits32)
-        .slot_bit_width(i2s::config::SlotBitWidth::Bits32)
-        .slot_mode_mask(
-            i2s::config::SlotMode::Stereo,
-            i2s::config::StdSlotMask::Both,
+        config::StdSlotConfig::msb_slot_default(
+            config::DataBitWidth::Bits32,
+            config::SlotMode::Stereo,
         )
+        .data_bit_width(config::DataBitWidth::Bits32)
+        .slot_bit_width(config::SlotBitWidth::Bits32)
+        .slot_mode_mask(config::SlotMode::Stereo, config::StdSlotMask::Both)
         .ws_width(32)
         .ws_polarity(false)
         .bit_shift(true)
         .left_align(true)
         .big_endian(false)
         .bit_order_lsb(false),
-        i2s::config::StdGpioConfig::new(false, false, false),
+        config::StdGpioConfig::new(false, false, false),
     );
     let mut mic_drv =
-        i2s::I2sDriver::new_std_rx(i2s_per, &std_cfg, sclk, sd, AnyIOPin::none(), ws).unwrap();
+        I2sDriver::new_std_rx(i2s_per, &std_cfg, sclk, sd, AnyIOPin::none(), ws).unwrap();
 
     mic_drv.rx_enable().expect("rx not enabled");
 
-    async fn ignore_mic_startup(mic: &mut i2s::I2sDriver<'_, I2sRx>) {
+    async fn ignore_mic_startup(mic: &mut I2sDriver<'_, I2sRx>) {
         let mut tmp_buf: [u8; 128] = [0; 128];
         loop {
             mic.read_async(tmp_buf.as_mut_slice()).await.unwrap();
@@ -51,7 +55,7 @@ pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) {
         .expect_err("ignore died early");
 
     loop {
-        let audio_in_buf: &mut AudioBuffer = audio_sender.send().await;
+        let audio_in_buf: &mut AudioBuffer = sender.send().await;
 
         let buffer = bytes_of_mut(audio_in_buf);
         let mut total_bytes_read: usize = 0;
@@ -68,6 +72,6 @@ pub async fn mic_input_task(i2s_per: I2S0, sd: Gpio5, sclk: Gpio4, ws: Gpio6) {
             log::warn!("buffer underflow: {}", total_bytes_read);
         }
 
-        audio_sender.send_done();
+        sender.send_done();
     }
 }
diff --git a/src/audio_process.rs b/src/audio_process.rs
index c7da8a3..aafe6be 100644
--- a/src/audio_process.rs
+++ b/src/audio_process.rs
@@ -1,11 +1,11 @@
 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
 
-use embassy_sync::zerocopy_channel::{Receiver, Sender};
 use esp_idf_svc::sys::{esp_dsp, esp_nofail};
 use static_cell::StaticCell;
 
 use crate::config::{AUDIO_BANDS, AUDIO_SAMPLES_PER_BUF};
 use crate::helpers::{falloff, falloff_f};
+use crate::triple_buffer::{Receiver, Sender};
 
 pub type AudioBuffer = [i32; AUDIO_SAMPLES_PER_BUF];
 pub type DspBuffer = [f32; AUDIO_SAMPLES_PER_BUF];
@@ -129,11 +129,7 @@ pub async fn process_audio(
     let mut processor = AudioProcessor::default();
 
     loop {
-        let in_buf = input.receive().await;
-
-        if output.is_full() {
-            output.clear();
-        }
+        let in_buf = input.receive_new().await;
 
         let (out_buf, stats) = output.send().await;
 
@@ -141,6 +137,5 @@ pub async fn process_audio(
         *stats = processor.stats;
 
         output.send_done();
-        input.receive_done();
     }
 }
diff --git a/src/effects/bass_sparks.rs b/src/effects/bass_sparks.rs
index c6e2374..598745b 100644
--- a/src/effects/bass_sparks.rs
+++ b/src/effects/bass_sparks.rs
@@ -1,11 +1,10 @@
-use embassy_sync::zerocopy_channel::Sender;
-
-use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver};
+use embassy_sync::blocking_mutex::raw::NoopRawMutex;
 use embassy_time::{Duration, Ticker};
 
 use crate::audio_process::{AudioStats, DspBuffer};
 use crate::effects::led_effect::{LedColors, LedData, LedEffect, Rgbv};
 use crate::helpers::random_at_most;
+use crate::triple_buffer::{Receiver, Sender};
 use crate::LED_COUNT;
 
 pub struct LedEffectBassSparks {
@@ -19,7 +18,13 @@ impl Default for LedEffectBassSparks {
     }
 }
 impl LedEffect for LedEffectBassSparks {
-    fn render(&mut self, _: &DspBuffer, stats: &AudioStats, leds: &mut LedColors) -> Duration {
+    fn render(
+        &mut self,
+        _: bool,
+        _: &DspBuffer,
+        stats: &AudioStats,
+        leds: &mut LedColors,
+    ) -> Duration {
         if stats.floating_max > 10100000 && (stats.current_powers[0] > 1.25 * stats.avg_powers[0]) {
             self.bass_color = Rgbv::new(127, 0, 255, 4)
         }
@@ -38,7 +43,7 @@ impl LedEffect for LedEffectBassSparks {
             }
         }
 
-        Duration::from_hz(20)
+        Duration::from_hz(50)
     }
 
     async fn process_led_effect(
@@ -49,12 +54,10 @@ impl LedEffect for LedEffectBassSparks {
         let mut set_interval = Duration::from_hz(100);
         let mut ticker = Ticker::every(set_interval);
         loop {
-            let (fft, stats) = input.receive().await;
+            let ((fft, stats), was_new) = input.receive_cached().await;
             let leds = output.send().await;
 
-            let interval = self.render(fft, stats, &mut leds.leds);
-
-            input.receive_done();
+            let interval = self.render(was_new, fft, stats, &mut leds.leds);
             output.send_done();
 
             if set_interval != interval {
diff --git a/src/effects/led_effect.rs b/src/effects/led_effect.rs
index 03204cf..a359386 100644
--- a/src/effects/led_effect.rs
+++ b/src/effects/led_effect.rs
@@ -1,15 +1,23 @@
 use anyhow::{bail, Result};
 use bytemuck::{Pod, Zeroable};
-use embassy_sync::{
-    blocking_mutex::raw::NoopRawMutex,
-    zerocopy_channel::{Receiver, Sender},
-};
+use embassy_sync::blocking_mutex::raw::NoopRawMutex;
 use embassy_time::Duration;
 
-use crate::{audio_process::DspBuffer, config::LED_COUNT, AudioStats};
+use crate::{
+    audio_process::DspBuffer,
+    config::LED_COUNT,
+    triple_buffer::{Receiver, Sender},
+    AudioStats,
+};
 
 pub trait LedEffect {
-    fn render(&mut self, fft: &DspBuffer, stats: &AudioStats, leds: &mut LedColors) -> Duration;
+    fn render(
+        &mut self,
+        new_data: bool,
+        fft: &DspBuffer,
+        stats: &AudioStats,
+        leds: &mut LedColors,
+    ) -> Duration;
 
     fn process_led_effect(
         &mut self,
diff --git a/src/led_output.rs b/src/led_output.rs
index 6406fbf..b35fff0 100644
--- a/src/led_output.rs
+++ b/src/led_output.rs
@@ -1,8 +1,13 @@
-use embassy_sync::{blocking_mutex::raw::NoopRawMutex, zerocopy_channel::Receiver};
-use esp_idf_svc::hal::{gpio::{Gpio11, Gpio12}, spi::SPI2};
+use bytemuck::bytes_of;
+use embassy_sync::blocking_mutex::raw::NoopRawMutex;
+use esp_idf_svc::hal::units::FromValueType;
+use esp_idf_svc::hal::{
+    gpio::{AnyIOPin, Gpio11, Gpio12},
+    spi::{config, SpiDeviceDriver, SPI2},
+};
 
 use crate::effects::led_effect::LedData;
-
+use crate::triple_buffer::Receiver;
 
 #[embassy_executor::task]
 pub async fn output_leds(
@@ -11,27 +16,26 @@ pub async fn output_leds(
     sck: Gpio12,
     mut receiver: Receiver<'static, NoopRawMutex, LedData>,
 ) {
-    let mut led_drv = spi::SpiDeviceDriver::new_single(
+    let mut led_drv = SpiDeviceDriver::new_single(
         spi_per,
         sck,
         sdo,
         AnyIOPin::none(),
         AnyIOPin::none(),
-        &spi::config::DriverConfig::new(),
-        &spi::config::Config::new()
+        &config::DriverConfig::new(),
+        &config::Config::new()
             .baudrate(1.MHz().into())
-            .data_mode(spi::config::MODE_3),
+            .data_mode(config::MODE_3),
     )
     .unwrap();
 
     loop {
-        let led_data = receiver.receive().await;
+        let led_data = receiver.receive_new().await;
 
         let output_buffer = bytes_of(led_data);
         led_drv
             .write_async(output_buffer)
             .await
             .expect("spi write failed");
-        receiver.receive_done();
     }
-}
\ No newline at end of file
+}
diff --git a/src/main.rs b/src/main.rs
index 0a47f88..eaa45bd 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -12,20 +12,18 @@ use esp_idf_svc::{
 };
 
 use embassy_executor::Spawner;
-use embassy_sync::{
-    blocking_mutex::raw::NoopRawMutex,
-    zerocopy_channel::{Channel, Receiver, Sender},
-};
+use embassy_sync::blocking_mutex::raw::NoopRawMutex;
 use static_cell::StaticCell;
 
 use audio_input::mic_input_task;
 use audio_process::{process_audio, AudioBuffer, AudioStats, DspBuffer};
-use config::{AUDIO_BUFFERS, AUDIO_SAMPLES_PER_BUF, LED_COUNT};
+use config::{AUDIO_SAMPLES_PER_BUF, LED_COUNT};
 use effects::{
     bass_sparks::LedEffectBassSparks,
     led_effect::{LedData, LedEffect},
 };
 use led_output::output_leds;
+use triple_buffer::{Receiver, Sender, TripleBuffer};
 
 #[embassy_executor::task]
 async fn effect_task(
@@ -45,35 +43,33 @@ async fn main(spawner: Spawner) {
     // Bind the log crate to the ESP Logging facilities
     esp_idf_svc::log::EspLogger::initialize_default();
 
-    // static PERIPHERALS: StaticCell<Peripherals> = StaticCell::new();
-    // let peripherals = PERIPHERALS.init(Peripherals::take().unwrap());
     let peripherals = Peripherals::take().unwrap();
 
     // mic buffers
-    static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; AUDIO_BUFFERS]> = StaticCell::new();
-    let audio_buffer = AUDIO_BUFFER_CELL.init([[0; AUDIO_SAMPLES_PER_BUF]; AUDIO_BUFFERS]);
+    static AUDIO_BUFFER_CELL: StaticCell<[AudioBuffer; 3]> = StaticCell::new();
+    let audio_buffer = AUDIO_BUFFER_CELL.init([[0; AUDIO_SAMPLES_PER_BUF]; 3]);
 
-    static AUDIO_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, AudioBuffer>> =
+    static AUDIO_CHANNEL_CELL: StaticCell<TripleBuffer<'_, NoopRawMutex, AudioBuffer>> =
         StaticCell::new();
-    let audio_channel = AUDIO_CHANNEL_CELL.init(Channel::new(audio_buffer));
-    let (mut audio_sender, audio_receiver) = audio_channel.split();
+    let audio_channel = AUDIO_CHANNEL_CELL.init(TripleBuffer::new(audio_buffer));
+    let (audio_sender, audio_receiver) = audio_channel.split();
 
     // fft buffers
-    static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); AUDIO_BUFFERS]> =
-        StaticCell::new();
-    let fft_buffer = FFT_BUFFER_CELL
-        .init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); AUDIO_BUFFERS]);
+    static FFT_BUFFER_CELL: StaticCell<[(DspBuffer, AudioStats); 3]> = StaticCell::new();
+    let fft_buffer =
+        FFT_BUFFER_CELL.init([([0f32; AUDIO_SAMPLES_PER_BUF], AudioStats::default()); 3]);
 
-    static FFT_CHANNEL_CELL: StaticCell<Channel<'_, NoopRawMutex, (DspBuffer, AudioStats)>> =
+    static FFT_CHANNEL_CELL: StaticCell<TripleBuffer<'_, NoopRawMutex, (DspBuffer, AudioStats)>> =
         StaticCell::new();
-    let fft_channel = FFT_CHANNEL_CELL.init(Channel::new(fft_buffer));
+    let fft_channel = FFT_CHANNEL_CELL.init(TripleBuffer::new(fft_buffer));
     let (fft_sender, fft_receiver) = fft_channel.split();
 
     // leds
-    static LED_DATA: StaticCell<[LedData; 1]> = StaticCell::new();
-    let leds = LED_DATA.init([LedData::default(); 1]);
-    static LED_CHANNEL: StaticCell<Channel<'_, NoopRawMutex, LedData>> = StaticCell::new();
-    let led_channel = LED_CHANNEL.init(Channel::new(leds));
+    static LED_DATA: StaticCell<[LedData; 3]> = StaticCell::new();
+    let leds = LED_DATA.init([LedData::default(); 3]);
+
+    static LED_CHANNEL: StaticCell<TripleBuffer<'_, NoopRawMutex, LedData>> = StaticCell::new();
+    let led_channel = LED_CHANNEL.init(TripleBuffer::new(leds));
     let (led_sender, led_receiver) = led_channel.split();
 
     unsafe {
@@ -93,6 +89,7 @@ async fn main(spawner: Spawner) {
             peripherals.pins.gpio5,
             peripherals.pins.gpio4,
             peripherals.pins.gpio6,
+            audio_sender,
         ))
         .expect("spawn failed");