// Disable the automatic pin remapping of the API calls in this file
#define ARDUINO_CORE_BUILD

#include "Arduino.h"
#include "ESP_I2S.h"

#if SOC_I2S_SUPPORTED

#include "esp32-hal-periman.h"
#include "wav_header.h"
#if ARDUINO_HAS_MP3_DECODER
#include "mp3dec.h"
#endif

#if SOC_I2S_HW_VERSION_2
#undef I2S_STD_CLK_DEFAULT_CONFIG
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
#define I2S_STD_CLK_DEFAULT_CONFIG(rate) \
  { .sample_rate_hz = rate, .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, .bclk_div = 0 }
#else
#define I2S_STD_CLK_DEFAULT_CONFIG(rate) \
  { .sample_rate_hz = rate, .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256 }
#endif
#endif

#define I2S_READ_CHUNK_SIZE 1920

typedef decltype(((i2s_chan_config_t *)nullptr)->id) i2s_chan_id_t;

#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
#define I2S_DEFAULT_CFG(_port)                                                                                                                       \
  {                                                                                                                                                  \
    .id = (i2s_chan_id_t)_port, .role = I2S_ROLE_MASTER, .dma_desc_num = 6, .dma_frame_num = 240, .auto_clear = true, .auto_clear_before_cb = false, \
    .allow_pd = 0, .intr_priority = 0                                                                                                                \
  }
#else
#define I2S_DEFAULT_CFG(_port)                                                                                                                       \
  {                                                                                                                                                  \
    .id = (i2s_chan_id_t)_port, .role = I2S_ROLE_MASTER, .dma_desc_num = 6, .dma_frame_num = 240, .auto_clear = true, .auto_clear_before_cb = false, \
    .intr_priority = 0                                                                                                                               \
  }
#endif

#define I2S_STD_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode)                                                                   \
  {                                                                                                                                   \
    .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
    .gpio_cfg = {                                                                                                                     \
      .mclk = (gpio_num_t)_mclk,                                                                                                      \
      .bclk = (gpio_num_t)_bclk,                                                                                                      \
      .ws = (gpio_num_t)_ws,                                                                                                          \
      .dout = (gpio_num_t)_dout,                                                                                                      \
      .din = (gpio_num_t)_din,                                                                                                        \
      .invert_flags =                                                                                                                 \
        {                                                                                                                             \
          .mclk_inv = _mclk_inv,                                                                                                      \
          .bclk_inv = _bclk_inv,                                                                                                      \
          .ws_inv = _ws_inv,                                                                                                          \
        },                                                                                                                            \
    },                                                                                                                                \
  }

#if SOC_I2S_SUPPORTS_TDM
#define I2S_TDM_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode, _mask)                                                                   \
  {                                                                                                                                          \
    .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_TDM_PHILIP_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode, _mask), \
    .gpio_cfg = {                                                                                                                            \
      .mclk = (gpio_num_t)_mclk,                                                                                                             \
      .bclk = (gpio_num_t)_bclk,                                                                                                             \
      .ws = (gpio_num_t)_ws,                                                                                                                 \
      .dout = (gpio_num_t)_dout,                                                                                                             \
      .din = (gpio_num_t)_din,                                                                                                               \
      .invert_flags =                                                                                                                        \
        {                                                                                                                                    \
          .mclk_inv = _mclk_inv,                                                                                                             \
          .bclk_inv = _bclk_inv,                                                                                                             \
          .ws_inv = _ws_inv,                                                                                                                 \
        },                                                                                                                                   \
    },                                                                                                                                       \
  }
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
#if (SOC_I2S_PDM_MAX_TX_LINES > 1)
#define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode)                                                               \
  {                                                                                                                                  \
    .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
    .gpio_cfg = {                                                                                                                    \
      .clk = (gpio_num_t)_tx_clk,                                                                                                    \
      .dout = (gpio_num_t)_tx_dout0,                                                                                                 \
      .dout2 = (gpio_num_t)_tx_dout1,                                                                                                \
      .invert_flags =                                                                                                                \
        {                                                                                                                            \
          .clk_inv = _tx_clk_inv,                                                                                                    \
        },                                                                                                                           \
    },                                                                                                                               \
  }
#else
#define I2S_PDM_TX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode)                                                               \
  {                                                                                                                                  \
    .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
    .gpio_cfg = {                                                                                                                    \
      .clk = (gpio_num_t)_tx_clk,                                                                                                    \
      .dout = (gpio_num_t)_tx_dout0,                                                                                                 \
      .invert_flags =                                                                                                                \
        {                                                                                                                            \
          .clk_inv = _tx_clk_inv,                                                                                                    \
        },                                                                                                                           \
    },                                                                                                                               \
  }
#endif
#endif

#if SOC_I2S_SUPPORTS_PDM_RX
#if (SOC_I2S_PDM_MAX_RX_LINES > 1)
#define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode)                                                               \
  {                                                                                                                                  \
    .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
    .gpio_cfg = {                                                                                                                    \
      .clk = (gpio_num_t)_rx_clk,                                                                                                    \
      .dins =                                                                                                                        \
        {                                                                                                                            \
          (gpio_num_t)_rx_din0,                                                                                                      \
          (gpio_num_t)_rx_din1,                                                                                                      \
          (gpio_num_t)_rx_din2,                                                                                                      \
          (gpio_num_t)_rx_din3,                                                                                                      \
        },                                                                                                                           \
      .invert_flags =                                                                                                                \
        {                                                                                                                            \
          .clk_inv = _rx_clk_inv,                                                                                                    \
        },                                                                                                                           \
    },                                                                                                                               \
  }
#else
#define I2S_PDM_RX_CHAN_CFG(_sample_rate, _data_bit_width, _slot_mode)                                                               \
  {                                                                                                                                  \
    .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(_sample_rate), .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(_data_bit_width, _slot_mode), \
    .gpio_cfg = {                                                                                                                    \
      .clk = (gpio_num_t)_rx_clk,                                                                                                    \
      .din = (gpio_num_t)_rx_din0,                                                                                                   \
      .invert_flags =                                                                                                                \
        {                                                                                                                            \
          .clk_inv = _rx_clk_inv,                                                                                                    \
        },                                                                                                                           \
    },                                                                                                                               \
  }
#endif
#endif

#define I2S_ERROR_CHECK_RETURN(x, r)                   \
  do {                                                 \
    last_error = (x);                                  \
    if (unlikely(last_error != ESP_OK)) {              \
      log_e("ERROR: %s", esp_err_to_name(last_error)); \
      return (r);                                      \
    }                                                  \
  } while (0)
#define I2S_ERROR_CHECK_RETURN_FALSE(x) I2S_ERROR_CHECK_RETURN(x, false)

// I2S_NUM_AUTO == -1 in IDF v6, so it cannot serve as the upper bound for valid ports.
// SOC_I2S_NUM (from soc/soc_caps.h) is hardware-derived in IDF <=5 but was removed in
// IDF v6, so fall back to the chip's physical I2S controller count there.
#ifndef I2S_NUM_MAX
#ifdef SOC_I2S_NUM
#define I2S_NUM_MAX SOC_I2S_NUM
#elif defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3)
#define I2S_NUM_MAX 2
#elif defined(CONFIG_IDF_TARGET_ESP32P4)
#define I2S_NUM_MAX 3
#else
#define I2S_NUM_MAX 1
#endif
#endif

static bool isValidI2SPort(i2s_port_t port) {
  return port == I2S_NUM_AUTO || (port >= I2S_NUM_0 && port < (i2s_port_t)I2S_NUM_MAX);
}

// Default read, no resmpling and temp buffer necessary
static esp_err_t i2s_channel_read_default(i2s_chan_handle_t handle, char *tmp_buf, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read(handle, (char *)dst, len, bytes_read, timeout_ms);
}

// Resample the 32bit SPH0645 microphone data into 16bit. SPH0645 is actually 18 bit, but this trick helps save some space
static esp_err_t i2s_channel_read_32_to_16(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  size_t out_len = 0;
  size_t read_buff_len = len * 2;
  if (read_buff == NULL) {
    log_e("Temp buffer is NULL!");
    return ESP_FAIL;
  }
  esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms);
  if (err != ESP_OK) {
    *bytes_read = 0;
    return err;
  }
  out_len /= 4;
  uint16_t *ds = (uint16_t *)dst;
  uint32_t *src = (uint32_t *)read_buff;
  for (size_t i = 0; i < out_len; i++) {
    ds[i] = src[i] >> 16;
  }
  *bytes_read = out_len * 2;
  return ESP_OK;
}

// Unified stereo-to-mono extraction for the HW v1 mono workaround.
// DMA always delivers interleaved uint16_t pairs (stereo). This function
// de-interleaves by slot_offset (0=left, 1=right, 2=average) and optionally
// unpacks the high byte for 8-bit mode.
// 16-bit: DMA reads 2× user bytes;  output is int16_t per sample.
//  8-bit: DMA reads 4× user bytes;  output is  int8_t per sample (high byte of uint16_t).
static esp_err_t i2s_channel_read_stereo_to_mono(
  i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms, size_t slot_offset, bool unpack_8bit
) {
  size_t out_len = 0;
  size_t read_buff_len = len * (unpack_8bit ? 4 : 2);
  if (read_buff == NULL) {
    log_e("Temp buffer is NULL!");
    return ESP_FAIL;
  }
  esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms);
  if (err != ESP_OK) {
    *bytes_read = 0;
    return err;
  }
  size_t stereo_samples = out_len / 2;
  int16_t *src = (int16_t *)read_buff;
  size_t written = 0;

  if (unpack_8bit) {
    uint8_t *ds = (uint8_t *)dst;
    if (slot_offset < 2) {
      for (size_t i = slot_offset; i < stereo_samples; i += 2) {
        ds[written++] = (uint8_t)((uint16_t)src[i] >> 8);
      }
    } else {
      for (size_t i = 0; i < stereo_samples; i += 2) {
        int16_t l = (int8_t)((uint16_t)src[i] >> 8);
        int16_t r = (int8_t)((uint16_t)src[i + 1] >> 8);
        ds[written++] = (int8_t)((l + r) / 2);
      }
    }
    *bytes_read = written;
  } else {
    int16_t *ds = (int16_t *)dst;
    if (slot_offset < 2) {
      for (size_t i = slot_offset; i < stereo_samples; i += 2) {
        ds[written++] = src[i];
      }
    } else {
      for (size_t i = 0; i < stereo_samples; i += 2) {
        ds[written++] = (int16_t)(((int32_t)src[i] + (int32_t)src[i + 1]) / 2);
      }
    }
    *bytes_read = written * 2;
  }
  return ESP_OK;
}

// 16-bit wrappers (all targets — used by I2S_RX_TRANSFORM_16_STEREO_TO_MONO)
static esp_err_t
  i2s_channel_read_16_stereo_to_mono_left(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read_stereo_to_mono(handle, read_buff, dst, len, bytes_read, timeout_ms, 0, false);
}

static esp_err_t
  i2s_channel_read_16_stereo_to_mono_right(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read_stereo_to_mono(handle, read_buff, dst, len, bytes_read, timeout_ms, 1, false);
}

static esp_err_t
  i2s_channel_read_16_stereo_to_mono_both(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read_stereo_to_mono(handle, read_buff, dst, len, bytes_read, timeout_ms, 2, false);
}

#if SOC_I2S_HW_VERSION_1
// Unpack stereo 8-bit: each DMA uint16_t → one int8_t (high byte)
static esp_err_t i2s_channel_read_8_unpack(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  size_t out_len = 0;
  size_t read_buff_len = len * 2;
  if (read_buff == NULL) {
    log_e("Temp buffer is NULL!");
    return ESP_FAIL;
  }
  esp_err_t err = i2s_channel_read(handle, read_buff, read_buff_len, &out_len, timeout_ms);
  if (err != ESP_OK) {
    *bytes_read = 0;
    return err;
  }
  size_t n_samples = out_len / 2;
  uint8_t *ds = (uint8_t *)dst;
  uint16_t *src = (uint16_t *)read_buff;
  for (size_t i = 0; i < n_samples; i++) {
    ds[i] = (uint8_t)(src[i] >> 8);
  }
  *bytes_read = n_samples;
  return ESP_OK;
}

// 8-bit mono wrappers (ESP32 HW v1 only — mono workaround + uint16_t packing)
static esp_err_t
  i2s_channel_read_8_stereo_to_mono_left(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read_stereo_to_mono(handle, read_buff, dst, len, bytes_read, timeout_ms, 0, true);
}

static esp_err_t
  i2s_channel_read_8_stereo_to_mono_right(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read_stereo_to_mono(handle, read_buff, dst, len, bytes_read, timeout_ms, 1, true);
}

static esp_err_t
  i2s_channel_read_8_stereo_to_mono_both(i2s_chan_handle_t handle, char *read_buff, void *dst, size_t len, size_t *bytes_read, uint32_t timeout_ms) {
  return i2s_channel_read_stereo_to_mono(handle, read_buff, dst, len, bytes_read, timeout_ms, 2, true);
}
#endif  // SOC_I2S_HW_VERSION_1

I2SClass::I2SClass(i2s_port_t port) {
  last_error = ESP_OK;
  _mode = I2S_MODE_MAX;  // Initialize to invalid mode to indicate I2S not started
  _port = port;

  tx_chan = NULL;
  tx_sample_rate = 0;
  tx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
  tx_slot_mode = I2S_SLOT_MODE_STEREO;

  rx_fn = i2s_channel_read_default;
  rx_transform = I2S_RX_TRANSFORM_NONE;
  rx_transform_buf = NULL;
  rx_transform_buf_len = 0;

  rx_chan = NULL;
  rx_sample_rate = 0;
  rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
  rx_slot_mode = I2S_SLOT_MODE_STEREO;

  _role = I2S_ROLE_MASTER;
  _rx_slot_mask = I2S_STD_SLOT_LEFT;
#if SOC_I2S_HW_VERSION_1
  _mono_hw_workaround = false;
  _8bit_hw_packing = false;
#endif

  _mclk = -1;
  _bclk = -1;
  _ws = -1;
  _dout = -1;
  _din = -1;
  _mclk_inv = false;
  _bclk_inv = false;
  _ws_inv = false;

#if SOC_I2S_SUPPORTS_PDM_TX
  _tx_clk = -1;
  _tx_dout0 = -1;
  _tx_dout1 = -1;
  _tx_clk_inv = false;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
  _rx_clk = -1;
  _rx_din0 = -1;
  _rx_din1 = -1;
  _rx_din2 = -1;
  _rx_din3 = -1;
  _rx_clk_inv = false;
#endif
}

I2SClass::~I2SClass() {
  end();
}

bool I2SClass::i2sDetachBus(void *bus_pointer) {
  I2SClass *bus = (I2SClass *)bus_pointer;
  // Only call end() if I2S has been initialized (begin() was called)
  // _mode is set to I2S_MODE_MAX in constructor and to a valid mode in begin()
  if (bus->_mode < I2S_MODE_MAX) {
    bus->end();
  }
  return true;
}

bool I2SClass::setPort(i2s_port_t port) {
  if (_mode < I2S_MODE_MAX) {
    log_e("I2S port must be set before begin()");
    return false;
  }
  if (!isValidI2SPort(port)) {
    log_e("Invalid I2S port selected.");
    return false;
  }
  _port = port;
  return true;
}

i2s_port_t I2SClass::getPort() {
  i2s_chan_handle_t chan = tx_chan != NULL ? tx_chan : rx_chan;
  if (chan != NULL) {
    i2s_chan_info_t info;
    if (i2s_channel_get_info(chan, &info) == ESP_OK) {
      return info.id;
    }
  }
  return _port;
}

// Set pins for STD and TDM mode
void I2SClass::setPins(int8_t bclk, int8_t ws, int8_t dout, int8_t din, int8_t mclk) {
  _mclk = digitalPinToGPIONumber(mclk);
  _bclk = digitalPinToGPIONumber(bclk);
  _ws = digitalPinToGPIONumber(ws);
  _dout = digitalPinToGPIONumber(dout);
  _din = digitalPinToGPIONumber(din);
}

void I2SClass::setInverted(bool bclk, bool ws, bool mclk) {
  _mclk_inv = mclk;
  _bclk_inv = bclk;
  _ws_inv = ws;
}

// Set pins for PDM TX mode
#if SOC_I2S_SUPPORTS_PDM_TX
void I2SClass::setPinsPdmTx(int8_t clk, int8_t dout0, int8_t dout1) {
  _tx_clk = digitalPinToGPIONumber(clk);
  _tx_dout0 = digitalPinToGPIONumber(dout0);
#if (SOC_I2S_PDM_MAX_TX_LINES > 1)
  _tx_dout1 = digitalPinToGPIONumber(dout1);
#endif
}
#endif

// Set pins for PDM RX mode
#if SOC_I2S_SUPPORTS_PDM_RX
void I2SClass::setPinsPdmRx(int8_t clk, int8_t din0, int8_t din1, int8_t din2, int8_t din3) {
  _rx_clk = digitalPinToGPIONumber(clk);
  _rx_din0 = digitalPinToGPIONumber(din0);
#if (SOC_I2S_PDM_MAX_RX_LINES > 1)
  _rx_din1 = digitalPinToGPIONumber(din1);
  _rx_din2 = digitalPinToGPIONumber(din2);
  _rx_din3 = digitalPinToGPIONumber(din3);
#endif
}
#endif

#if SOC_I2S_SUPPORTS_PDM_TX || SOC_I2S_SUPPORTS_PDM_RX
void I2SClass::setInvertedPdm(bool clk) {
#if SOC_I2S_SUPPORTS_PDM_TX
  _tx_clk_inv = clk;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
  _rx_clk_inv = clk;
#endif
}
#endif

bool I2SClass::initSTD(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) {
  // Peripheral manager deinit previous peripheral if pin was used
  if (_mclk >= 0) {
    if (!perimanClearPinBus(_mclk)) {
      return false;
    }
  }
  if (_bclk >= 0) {
    if (!perimanClearPinBus(_bclk)) {
      return false;
    }
  }
  if (_ws >= 0) {
    if (!perimanClearPinBus(_ws)) {
      return false;
    }
  }
  if (_dout >= 0) {
    if (!perimanClearPinBus(_dout)) {
      return false;
    }
  }
  if (_din >= 0) {
    if (!perimanClearPinBus(_din)) {
      return false;
    }
  }

  // Set peripheral manager detach function for I2S
  if (_mclk >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_MCLK, I2SClass::i2sDetachBus);
  }
  if (_bclk >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_BCLK, I2SClass::i2sDetachBus);
  }
  if (_ws >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_WS, I2SClass::i2sDetachBus);
  }
  if (_dout >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_DOUT, I2SClass::i2sDetachBus);
  }
  if (_din >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_STD_DIN, I2SClass::i2sDetachBus);
  }

  // I2S configuration
  i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(_port);
  chan_cfg.role = _role;
  if (_dout >= 0 && _din >= 0) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan));
  } else if (_dout >= 0) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL));
  } else if (_din >= 0) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan));
  }

  i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
  if (slot_mask >= 0 && (i2s_std_slot_mask_t)slot_mask <= I2S_STD_SLOT_BOTH) {
    i2s_config.slot_cfg.slot_mask = (i2s_std_slot_mask_t)slot_mask;
  }
  if (slot_mask == I2S_STD_SLOT_RIGHT) {
    _rx_slot_mask = I2S_STD_SLOT_RIGHT;
  } else if (slot_mask == I2S_STD_SLOT_BOTH || (ch == I2S_SLOT_MODE_STEREO && slot_mask < 0)) {
    _rx_slot_mask = I2S_STD_SLOT_BOTH;
  } else {
    _rx_slot_mask = I2S_STD_SLOT_LEFT;
  }
#if SOC_I2S_HW_VERSION_1
  // ESP32 HW v1: DMA uses 2 bytes per sample for 8-bit data (high byte valid).
  // Mono FIFO packing is also broken for 8/16-bit.  Work around both issues by
  // packing 8-bit in software and forcing stereo for mono.
  _8bit_hw_packing = (bits_cfg == I2S_DATA_BIT_WIDTH_8BIT);
  if (ch == I2S_SLOT_MODE_MONO && (bits_cfg == I2S_DATA_BIT_WIDTH_16BIT || bits_cfg == I2S_DATA_BIT_WIDTH_8BIT)) {
    _mono_hw_workaround = true;
    i2s_config.slot_cfg.slot_mode = I2S_SLOT_MODE_STEREO;
    i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH;
  } else {
    _mono_hw_workaround = false;
  }
#else
  // Newer targets default to SLOT_BOTH even in mono, which doubles each
  // sample in the DMA buffer.  Force SLOT_LEFT for true mono unless the
  // caller explicitly overrode slot_mask.
  if (ch == I2S_SLOT_MODE_MONO && slot_mask < 0) {
    i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
  }
#endif
  if (tx_chan != NULL) {
    tx_sample_rate = rate;
    tx_data_bit_width = bits_cfg;
    tx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(tx_chan, &i2s_config));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
  }
  if (rx_chan != NULL) {
    if (ch == I2S_SLOT_MODE_STEREO) {
      i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH;
      _rx_slot_mask = I2S_STD_SLOT_BOTH;
    }
    rx_sample_rate = rate;
    rx_data_bit_width = bits_cfg;
    rx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_std_mode(rx_chan, &i2s_config));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
#if SOC_I2S_HW_VERSION_1
    if (_mono_hw_workaround) {
      if (_8bit_hw_packing) {
        // 8-bit mono: DMA produces stereo uint16_t (4 bytes per mono sample)
        if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 4)) {
          goto err;
        }
        if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
          rx_fn = i2s_channel_read_8_stereo_to_mono_right;
        } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
          rx_fn = i2s_channel_read_8_stereo_to_mono_both;
        } else {
          rx_fn = i2s_channel_read_8_stereo_to_mono_left;
        }
      } else {
        if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
          goto err;
        }
        if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
          rx_fn = i2s_channel_read_16_stereo_to_mono_right;
        } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
          rx_fn = i2s_channel_read_16_stereo_to_mono_both;
        } else {
          rx_fn = i2s_channel_read_16_stereo_to_mono_left;
        }
      }
      rx_transform = I2S_RX_TRANSFORM_16_STEREO_TO_MONO;
    } else if (_8bit_hw_packing) {
      // 8-bit stereo: DMA produces uint16_t per sample (2× user bytes)
      if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
        goto err;
      }
      rx_fn = i2s_channel_read_8_unpack;
      rx_transform = I2S_RX_TRANSFORM_8_UNPACK;
    }
#endif
  }

  // Peripheral manager set bus type to I2S
  if (_mclk >= 0) {
    if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_STD_MCLK, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_bclk >= 0) {
    if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_STD_BCLK, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_ws >= 0) {
    if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_STD_WS, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_dout >= 0) {
    if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_STD_DOUT, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_din >= 0) {
    if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_STD_DIN, (void *)(this), -1, -1)) {
      goto err;
    }
  }

  return true;
err:
  log_e("I2S_STD initialization failed");
  I2SClass::i2sDetachBus((void *)(this));
  return false;
}

#if SOC_I2S_SUPPORTS_TDM
bool I2SClass::initTDM(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) {
  // Peripheral manager deinit previous peripheral if pin was used
  if (_mclk >= 0) {
    if (!perimanClearPinBus(_mclk)) {
      return false;
    }
  }
  if (_bclk >= 0) {
    if (!perimanClearPinBus(_bclk)) {
      return false;
    }
  }
  if (_ws >= 0) {
    if (!perimanClearPinBus(_ws)) {
      return false;
    }
  }
  if (_dout >= 0) {
    if (!perimanClearPinBus(_dout)) {
      return false;
    }
  }
  if (_din >= 0) {
    if (!perimanClearPinBus(_din)) {
      return false;
    }
  }

  // Set peripheral manager detach function for I2S
  if (_mclk >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_MCLK, I2SClass::i2sDetachBus);
  }
  if (_bclk >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_BCLK, I2SClass::i2sDetachBus);
  }
  if (_ws >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_WS, I2SClass::i2sDetachBus);
  }
  if (_dout >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_DOUT, I2SClass::i2sDetachBus);
  }
  if (_din >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_TDM_DIN, I2SClass::i2sDetachBus);
  }

  // I2S configuration
  i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(_port);
  chan_cfg.role = _role;
  if (_dout >= 0 && _din >= 0) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan));
  } else if (_dout >= 0) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL));
  } else if (_din >= 0) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan));
  }

  i2s_tdm_config_t i2s_tdm_config = I2S_TDM_CHAN_CFG(rate, bits_cfg, ch, (i2s_tdm_slot_mask_t)slot_mask);
  if (tx_chan != NULL) {
    tx_sample_rate = rate;
    tx_data_bit_width = bits_cfg;
    tx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(tx_chan, &i2s_tdm_config));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
  }
  if (rx_chan != NULL) {
    rx_sample_rate = rate;
    rx_data_bit_width = bits_cfg;
    rx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_tdm_mode(rx_chan, &i2s_tdm_config));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
  }

  // Peripheral manager set bus type to I2S
  if (_mclk >= 0) {
    if (!perimanSetPinBus(_mclk, ESP32_BUS_TYPE_I2S_TDM_MCLK, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_bclk >= 0) {
    if (!perimanSetPinBus(_bclk, ESP32_BUS_TYPE_I2S_TDM_BCLK, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_ws >= 0) {
    if (!perimanSetPinBus(_ws, ESP32_BUS_TYPE_I2S_TDM_WS, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_dout >= 0) {
    if (!perimanSetPinBus(_dout, ESP32_BUS_TYPE_I2S_TDM_DOUT, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_din >= 0) {
    if (!perimanSetPinBus(_din, ESP32_BUS_TYPE_I2S_TDM_DIN, (void *)(this), -1, -1)) {
      goto err;
    }
  }

  return true;
err:
  log_e("Failed to set all pins bus to I2S_TDM");
  I2SClass::i2sDetachBus((void *)(this));
  return false;
}
#endif

#if SOC_I2S_SUPPORTS_PDM_TX
bool I2SClass::initPDMtx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch) {
  // Peripheral manager deinit previous peripheral if pin was used
  if (_tx_clk >= 0) {
    if (!perimanClearPinBus(_tx_clk)) {
      return false;
    }
  }
  if (_tx_dout0 >= 0) {
    if (!perimanClearPinBus(_tx_dout0)) {
      return false;
    }
  }
  if (_tx_dout1 >= 0) {
    if (!perimanClearPinBus(_tx_dout1)) {
      return false;
    }
  }

  // Set peripheral manager detach function for I2S
  if (_tx_clk >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_TX_CLK, I2SClass::i2sDetachBus);
  }
  if (_tx_dout0 >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_TX_DOUT0, I2SClass::i2sDetachBus);
  }
  if (_tx_dout1 >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_TX_DOUT1, I2SClass::i2sDetachBus);
  }

  // I2S configuration
  i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(_port);
  chan_cfg.role = _role;
  I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, &tx_chan, NULL));

  i2s_pdm_tx_config_t i2s_pdm_tx_config = I2S_PDM_TX_CHAN_CFG(rate, bits_cfg, ch);
  if (tx_chan != NULL) {
    tx_sample_rate = rate;
    tx_data_bit_width = bits_cfg;
    tx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_tx_mode(tx_chan, &i2s_pdm_tx_config));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
  }

  // Peripheral manager set bus type to I2S
  if (_tx_clk >= 0) {
    if (!perimanSetPinBus(_tx_clk, ESP32_BUS_TYPE_I2S_PDM_TX_CLK, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_tx_dout0 >= 0) {
    if (!perimanSetPinBus(_tx_dout0, ESP32_BUS_TYPE_I2S_PDM_TX_DOUT0, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_tx_dout1 >= 0) {
    if (!perimanSetPinBus(_tx_dout1, ESP32_BUS_TYPE_I2S_PDM_TX_DOUT1, (void *)(this), -1, -1)) {
      goto err;
    }
  }

  return true;
err:
  log_e("Failed to set all pins bus to I2S_TDM");
  I2SClass::i2sDetachBus((void *)(this));
  return false;
}
#endif

#if SOC_I2S_SUPPORTS_PDM_RX
bool I2SClass::initPDMrx(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch) {
  // Peripheral manager deinit previous peripheral if pin was used
  if (_rx_clk >= 0) {
    if (!perimanClearPinBus(_rx_clk)) {
      return false;
    }
  }
  if (_rx_din0 >= 0) {
    if (!perimanClearPinBus(_rx_din0)) {
      return false;
    }
  }
  if (_rx_din1 >= 0) {
    if (!perimanClearPinBus(_rx_din1)) {
      return false;
    }
  }
  if (_rx_din2 >= 0) {
    if (!perimanClearPinBus(_rx_din2)) {
      return false;
    }
  }
  if (_rx_din3 >= 0) {
    if (!perimanClearPinBus(_rx_din3)) {
      return false;
    }
  }

  // Set peripheral manager detach function for I2S
  if (_rx_clk >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_CLK, I2SClass::i2sDetachBus);
  }
  if (_rx_din0 >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN0, I2SClass::i2sDetachBus);
  }
  if (_rx_din1 >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN1, I2SClass::i2sDetachBus);
  }
  if (_rx_din2 >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN2, I2SClass::i2sDetachBus);
  }
  if (_rx_din3 >= 0) {
    perimanSetBusDeinit(ESP32_BUS_TYPE_I2S_PDM_RX_DIN3, I2SClass::i2sDetachBus);
  }

  // I2S configuration
  i2s_chan_config_t chan_cfg = I2S_DEFAULT_CFG(_port);
  chan_cfg.role = _role;
  I2S_ERROR_CHECK_RETURN_FALSE(i2s_new_channel(&chan_cfg, NULL, &rx_chan));

  i2s_pdm_rx_config_t i2s_pdf_rx_config = I2S_PDM_RX_CHAN_CFG(rate, bits_cfg, ch);
  if (rx_chan != NULL) {
    rx_sample_rate = rate;
    rx_data_bit_width = bits_cfg;
    rx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_init_pdm_rx_mode(rx_chan, &i2s_pdf_rx_config));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
  }

  // Peripheral manager set bus type to I2S
  if (_rx_clk >= 0) {
    if (!perimanSetPinBus(_rx_clk, ESP32_BUS_TYPE_I2S_PDM_RX_CLK, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_rx_din0 >= 0) {
    if (!perimanSetPinBus(_rx_din0, ESP32_BUS_TYPE_I2S_PDM_RX_DIN0, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_rx_din1 >= 0) {
    if (!perimanSetPinBus(_rx_din1, ESP32_BUS_TYPE_I2S_PDM_RX_DIN1, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_rx_din2 >= 0) {
    if (!perimanSetPinBus(_rx_din2, ESP32_BUS_TYPE_I2S_PDM_RX_DIN2, (void *)(this), -1, -1)) {
      goto err;
    }
  }
  if (_rx_din3 >= 0) {
    if (!perimanSetPinBus(_rx_din3, ESP32_BUS_TYPE_I2S_PDM_RX_DIN3, (void *)(this), -1, -1)) {
      goto err;
    }
  }

  return true;
err:
  log_e("Failed to set all pins bus to I2S_TDM");
  I2SClass::i2sDetachBus((void *)(this));
  return false;
}
#endif

bool I2SClass::begin(i2s_mode_t mode, uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask, i2s_role_t role) {
  /* Setup I2S peripheral */
  if (mode >= I2S_MODE_MAX) {
    log_e("Invalid I2S mode selected.");
    return false;
  }
  if (!isValidI2SPort(_port)) {
    log_e("Invalid I2S port selected.");
    return false;
  }
  _mode = mode;
  _role = role;

  bool init = false;
  switch (_mode) {
    case I2S_MODE_STD: init = initSTD(rate, bits_cfg, ch, slot_mask); break;
#if SOC_I2S_SUPPORTS_TDM
    case I2S_MODE_TDM: init = initTDM(rate, bits_cfg, ch, slot_mask); break;
#endif
#if SOC_I2S_SUPPORTS_PDM_TX
    case I2S_MODE_PDM_TX: init = initPDMtx(rate, bits_cfg, ch); break;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
    case I2S_MODE_PDM_RX: init = initPDMrx(rate, bits_cfg, ch); break;
#endif
    default: break;
  }

  if (init == false) {
    log_e("I2S initialization failed.");
    return false;
  }
  return true;
}

bool I2SClass::end() {
  // Check if already ended to prevent recursion
  if (_mode >= I2S_MODE_MAX) {
    return true;
  }

  // Save mode and reset it BEFORE clearing pins to prevent recursive calls
  // When perimanClearPinBus() is called, it may trigger i2sDetachBus() again
  i2s_mode_t mode = _mode;
  _mode = I2S_MODE_MAX;

  if (tx_chan != NULL) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(tx_chan));
    tx_chan = NULL;
  }
  if (rx_chan != NULL) {
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_del_channel(rx_chan));
    rx_chan = NULL;
  }
  if (rx_transform_buf != NULL) {
    free(rx_transform_buf);
    rx_transform_buf = NULL;
    rx_transform_buf_len = 0;
  }
  rx_fn = i2s_channel_read_default;
  rx_transform = I2S_RX_TRANSFORM_NONE;
#if SOC_I2S_HW_VERSION_1
  _mono_hw_workaround = false;
  _8bit_hw_packing = false;
#endif

  //Peripheral manager deinit used pins
  switch (mode) {
    case I2S_MODE_STD:
#if SOC_I2S_SUPPORTS_TDM
    case I2S_MODE_TDM:
#endif
      if (_mclk >= 0) {
        perimanClearPinBus(_mclk);
      }
      if (_bclk >= 0) {
        perimanClearPinBus(_bclk);
      }
      if (_ws >= 0) {
        perimanClearPinBus(_ws);
      }
      if (_dout >= 0) {
        perimanClearPinBus(_dout);
      }
      if (_din >= 0) {
        perimanClearPinBus(_din);
      }
      break;
#if SOC_I2S_SUPPORTS_PDM_TX
    case I2S_MODE_PDM_TX:
      perimanClearPinBus(_tx_clk);
      if (_tx_dout0 >= 0) {
        perimanClearPinBus(_tx_dout0);
      }
      if (_tx_dout1 >= 0) {
        perimanClearPinBus(_tx_dout1);
      }
      break;
#endif
#if SOC_I2S_SUPPORTS_PDM_RX
    case I2S_MODE_PDM_RX:
      perimanClearPinBus(_rx_clk);
      if (_rx_din0 >= 0) {
        perimanClearPinBus(_rx_din0);
      }
      if (_rx_din1 >= 0) {
        perimanClearPinBus(_rx_din1);
      }
      if (_rx_din2 >= 0) {
        perimanClearPinBus(_rx_din2);
      }
      if (_rx_din3 >= 0) {
        perimanClearPinBus(_rx_din3);
      }
      break;
#endif
    default: break;
  }
  return true;
}

bool I2SClass::configureTX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, int8_t slot_mask) {
  /* Setup I2S channels */
  if (tx_chan != NULL) {
    if (tx_sample_rate == rate && tx_data_bit_width == bits_cfg && tx_slot_mode == ch) {
      return true;
    }
    i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
    if (slot_mask >= 0 && (i2s_std_slot_mask_t)slot_mask <= I2S_STD_SLOT_BOTH) {
      i2s_config.slot_cfg.slot_mask = (i2s_std_slot_mask_t)slot_mask;
    }
#if SOC_I2S_HW_VERSION_1
    _8bit_hw_packing = (bits_cfg == I2S_DATA_BIT_WIDTH_8BIT);
    if (ch == I2S_SLOT_MODE_MONO && (bits_cfg == I2S_DATA_BIT_WIDTH_16BIT || bits_cfg == I2S_DATA_BIT_WIDTH_8BIT)) {
      _mono_hw_workaround = true;
      i2s_config.slot_cfg.slot_mode = I2S_SLOT_MODE_STEREO;
      i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH;
    } else {
      _mono_hw_workaround = false;
    }
#else
    if (ch == I2S_SLOT_MODE_MONO && slot_mask < 0) {
      i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;
    }
#endif
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(tx_chan));
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(tx_chan, &i2s_config.clk_cfg));
    tx_sample_rate = rate;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(tx_chan, &i2s_config.slot_cfg));
    tx_data_bit_width = bits_cfg;
    tx_slot_mode = ch;
    I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(tx_chan));
    return true;
  }
  return false;
}

bool I2SClass::configureRX(uint32_t rate, i2s_data_bit_width_t bits_cfg, i2s_slot_mode_t ch, i2s_rx_transform_t transform, int8_t slot_mask) {
  /* Setup I2S channels */
  if (rx_chan != NULL) {
    if (rx_sample_rate != rate || rx_data_bit_width != bits_cfg || rx_slot_mode != ch) {
      i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
      if (ch == I2S_SLOT_MODE_STEREO) {
        i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH;
        _rx_slot_mask = I2S_STD_SLOT_BOTH;
      } else if (slot_mask >= 0 && (i2s_std_slot_mask_t)slot_mask <= I2S_STD_SLOT_BOTH) {
        i2s_config.slot_cfg.slot_mask = (i2s_std_slot_mask_t)slot_mask;
        _rx_slot_mask = (i2s_std_slot_mask_t)slot_mask;
      } else {
        // slot_mask = -1 (default) in mono: preserve current _rx_slot_mask,
        // but reset to LEFT if transitioning from stereo (where BOTH was forced).
        if (rx_slot_mode != I2S_SLOT_MODE_MONO && _rx_slot_mask != I2S_STD_SLOT_LEFT) {
          log_w("configureRX: switching from stereo to mono with no explicit slot_mask; defaulting to LEFT");
          _rx_slot_mask = I2S_STD_SLOT_LEFT;
        }
        i2s_config.slot_cfg.slot_mask = _rx_slot_mask;
      }
#if SOC_I2S_HW_VERSION_1
      _8bit_hw_packing = (bits_cfg == I2S_DATA_BIT_WIDTH_8BIT);
      bool new_workaround = (ch == I2S_SLOT_MODE_MONO && (bits_cfg == I2S_DATA_BIT_WIDTH_16BIT || bits_cfg == I2S_DATA_BIT_WIDTH_8BIT));
      if (new_workaround) {
        _mono_hw_workaround = true;
        i2s_config.slot_cfg.slot_mode = I2S_SLOT_MODE_STEREO;
        i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH;
      } else {
        _mono_hw_workaround = false;
      }
#endif
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan));
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_clock(rx_chan, &i2s_config.clk_cfg));
      rx_sample_rate = rate;
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(rx_chan, &i2s_config.slot_cfg));
      rx_data_bit_width = bits_cfg;
      rx_slot_mode = ch;
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
#if SOC_I2S_HW_VERSION_1
      if (_mono_hw_workaround) {
        if (_8bit_hw_packing) {
          if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 4)) {
            return false;
          }
          if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
            rx_fn = i2s_channel_read_8_stereo_to_mono_right;
          } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
            rx_fn = i2s_channel_read_8_stereo_to_mono_both;
          } else {
            rx_fn = i2s_channel_read_8_stereo_to_mono_left;
          }
        } else {
          if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
            return false;
          }
          if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
            rx_fn = i2s_channel_read_16_stereo_to_mono_right;
          } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
            rx_fn = i2s_channel_read_16_stereo_to_mono_both;
          } else {
            rx_fn = i2s_channel_read_16_stereo_to_mono_left;
          }
        }
        rx_transform = I2S_RX_TRANSFORM_16_STEREO_TO_MONO;
        return true;
      } else if (_8bit_hw_packing) {
        if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
          return false;
        }
        rx_fn = i2s_channel_read_8_unpack;
        rx_transform = I2S_RX_TRANSFORM_8_UNPACK;
        return true;
      }
#endif
      return transformRX(transform);
    }
    // Slot-only change: rate/bits/ch unchanged but slot_mask or transform changed.
    // Stereo RX always uses BOTH — the IDF does not reliably ignore slot_mask
    // in stereo mode, so slot changes are only meaningful for mono.
    if (ch != I2S_SLOT_MODE_STEREO && slot_mask >= 0 && (i2s_std_slot_mask_t)slot_mask != _rx_slot_mask) {
      i2s_std_config_t i2s_config = I2S_STD_CHAN_CFG(rate, bits_cfg, ch);
      i2s_config.slot_cfg.slot_mask = (i2s_std_slot_mask_t)slot_mask;
      if (slot_mask == I2S_STD_SLOT_RIGHT) {
        _rx_slot_mask = I2S_STD_SLOT_RIGHT;
      } else if (slot_mask == I2S_STD_SLOT_BOTH) {
        _rx_slot_mask = I2S_STD_SLOT_BOTH;
      } else {
        _rx_slot_mask = I2S_STD_SLOT_LEFT;
      }
#if SOC_I2S_HW_VERSION_1
      if (_mono_hw_workaround) {
        i2s_config.slot_cfg.slot_mode = I2S_SLOT_MODE_STEREO;
        i2s_config.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH;
      }
#endif
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_disable(rx_chan));
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_reconfig_std_slot(rx_chan, &i2s_config.slot_cfg));
      I2S_ERROR_CHECK_RETURN_FALSE(i2s_channel_enable(rx_chan));
#if SOC_I2S_HW_VERSION_1
      if (_mono_hw_workaround) {
        if (_8bit_hw_packing) {
          if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 4)) {
            return false;
          }
          if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
            rx_fn = i2s_channel_read_8_stereo_to_mono_right;
          } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
            rx_fn = i2s_channel_read_8_stereo_to_mono_both;
          } else {
            rx_fn = i2s_channel_read_8_stereo_to_mono_left;
          }
        } else {
          if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
            return false;
          }
          if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
            rx_fn = i2s_channel_read_16_stereo_to_mono_right;
          } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
            rx_fn = i2s_channel_read_16_stereo_to_mono_both;
          } else {
            rx_fn = i2s_channel_read_16_stereo_to_mono_left;
          }
        }
        rx_transform = I2S_RX_TRANSFORM_16_STEREO_TO_MONO;
        return true;
      }
#endif
    }
    if (rx_transform != transform) {
      return transformRX(transform);
    }
    return true;
  }
  return false;
}

size_t I2SClass::readBytes(char *buffer, size_t size) {
  size_t bytes_read = 0;
  size_t bytes_to_read = 0;
  size_t total_size = 0;
  last_error = ESP_FAIL;
  if (rx_chan == NULL) {
    return total_size;
  }
  while (total_size < size) {
    bytes_read = 0;
    bytes_to_read = size - total_size;
    if (rx_transform_buf != NULL && bytes_to_read > I2S_READ_CHUNK_SIZE) {
      bytes_to_read = I2S_READ_CHUNK_SIZE;
    }
    I2S_ERROR_CHECK_RETURN(rx_fn(rx_chan, rx_transform_buf, (char *)(buffer + total_size), bytes_to_read, &bytes_read, _timeout), 0);
    total_size += bytes_read;
  }
  return total_size;
}

size_t I2SClass::write(const uint8_t *buffer, size_t size) {
  size_t written = 0;
  size_t bytes_sent = 0;
  size_t bytes_to_send = 0;
  last_error = ESP_FAIL;
  if (tx_chan == NULL) {
    return written;
  }
  size_t min_size = (tx_data_bit_width == I2S_DATA_BIT_WIDTH_8BIT) ? 1 : (tx_data_bit_width / 8);
  if (size < min_size) {
    last_error = ESP_OK;
    return 0;
  }
#if SOC_I2S_HW_VERSION_1
  if (_8bit_hw_packing && _mono_hw_workaround) {
    // 8-bit mono on HW v1: pack each int8_t sample into the high byte of
    // a uint16_t, then duplicate into L+R stereo for DMA.
    const int8_t *src = (const int8_t *)buffer;
    size_t mono_samples = size;
    size_t mono_written = 0;
    while (mono_written < mono_samples) {
      const size_t MAX_CHUNK = 128;
      uint16_t stereo_buf[MAX_CHUNK * 2];
      size_t chunk = mono_samples - mono_written;
      if (chunk > MAX_CHUNK) {
        chunk = MAX_CHUNK;
      }
      for (size_t i = 0; i < chunk; i++) {
        uint16_t packed = (uint16_t)((uint8_t)src[mono_written + i]) << 8;
        stereo_buf[i * 2] = packed;
        stereo_buf[i * 2 + 1] = packed;
      }
      size_t stereo_bytes = chunk * 2 * sizeof(uint16_t);
      bytes_sent = 0;
      esp_err_t err = i2s_channel_write(tx_chan, (char *)stereo_buf, stereo_bytes, &bytes_sent, _timeout);
      setWriteError(err);
      if (err != ESP_OK) {
        last_error = err;
        break;
      }
      mono_written += bytes_sent / (2 * sizeof(uint16_t));
    }
    if (mono_written == mono_samples) {
      last_error = ESP_OK;
    }
    return mono_written;
  }
  if (_8bit_hw_packing && !_mono_hw_workaround) {
    // 8-bit stereo on HW v1: pack each int8_t into the high byte of a uint16_t.
    const int8_t *src = (const int8_t *)buffer;
    size_t n_samples = size;
    size_t samples_written = 0;
    while (samples_written < n_samples) {
      const size_t MAX_CHUNK = 256;
      uint16_t pack_buf[MAX_CHUNK];
      size_t chunk = n_samples - samples_written;
      if (chunk > MAX_CHUNK) {
        chunk = MAX_CHUNK;
      }
      for (size_t i = 0; i < chunk; i++) {
        pack_buf[i] = (uint16_t)((uint8_t)src[samples_written + i]) << 8;
      }
      size_t pack_bytes = chunk * sizeof(uint16_t);
      bytes_sent = 0;
      esp_err_t err = i2s_channel_write(tx_chan, (char *)pack_buf, pack_bytes, &bytes_sent, _timeout);
      setWriteError(err);
      if (err != ESP_OK) {
        last_error = err;
        break;
      }
      samples_written += bytes_sent / sizeof(uint16_t);
    }
    if (samples_written == n_samples) {
      last_error = ESP_OK;
    }
    return samples_written;
  }
  if (_mono_hw_workaround) {
    const int16_t *src = (const int16_t *)buffer;
    size_t mono_samples = size / sizeof(int16_t);
    size_t mono_written = 0;
    while (mono_written < mono_samples) {
      const size_t MAX_CHUNK = 128;
      int16_t stereo_buf[MAX_CHUNK * 2];
      size_t chunk = mono_samples - mono_written;
      if (chunk > MAX_CHUNK) {
        chunk = MAX_CHUNK;
      }
      for (size_t i = 0; i < chunk; i++) {
        stereo_buf[i * 2] = src[mono_written + i];
        stereo_buf[i * 2 + 1] = src[mono_written + i];
      }
      size_t stereo_bytes = chunk * 2 * sizeof(int16_t);
      bytes_sent = 0;
      esp_err_t err = i2s_channel_write(tx_chan, (char *)stereo_buf, stereo_bytes, &bytes_sent, _timeout);
      setWriteError(err);
      if (err != ESP_OK) {
        last_error = err;
        break;
      }
      mono_written += bytes_sent / (2 * sizeof(int16_t));
    }
    if (mono_written == mono_samples) {
      last_error = ESP_OK;
    }
    return mono_written * sizeof(int16_t);
  }
#endif
  while (written < size) {
    bytes_sent = 0;
    bytes_to_send = size - written;
    esp_err_t err = i2s_channel_write(tx_chan, (char *)(buffer + written), bytes_to_send, &bytes_sent, _timeout);
    setWriteError(err);
    I2S_ERROR_CHECK_RETURN(err, written);
    written += bytes_sent;
  }
  return written;
}

i2s_chan_handle_t I2SClass::txChan() {
  return tx_chan;
}
uint32_t I2SClass::txSampleRate() {
  return tx_sample_rate;
}
i2s_data_bit_width_t I2SClass::txDataWidth() {
  return tx_data_bit_width;
}
i2s_slot_mode_t I2SClass::txSlotMode() {
  return tx_slot_mode;
}

i2s_chan_handle_t I2SClass::rxChan() {
  return rx_chan;
}
uint32_t I2SClass::rxSampleRate() {
  return rx_sample_rate;
}
i2s_data_bit_width_t I2SClass::rxDataWidth() {
  return rx_data_bit_width;
}
i2s_slot_mode_t I2SClass::rxSlotMode() {
  return rx_slot_mode;
}

int I2SClass::lastError() {
  return (int)last_error;
}

int I2SClass::available() {
  if (rx_chan == NULL) {
    return -1;
  }
  return I2S_READ_CHUNK_SIZE;  // / (rx_data_bit_width/8);
};

int I2SClass::peek() {
  return -1;
};

int I2SClass::read() {
  int out = 0;
  if (readBytes((char *)&out, rx_data_bit_width / 8) == (rx_data_bit_width / 8)) {
    return out;
  }
  return -1;
};

size_t I2SClass::write(uint8_t d) {
  return write(&d, 1);
}

bool I2SClass::transformRX(i2s_rx_transform_t transform) {
  switch (transform) {
    case I2S_RX_TRANSFORM_NONE:
      allocTranformRX(0);
      rx_fn = i2s_channel_read_default;
      break;

    case I2S_RX_TRANSFORM_32_TO_16:
      if (rx_data_bit_width != I2S_DATA_BIT_WIDTH_32BIT) {
        log_e("Wrong data width. Should be 32bit");
        return false;
      }
      if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
        return false;
      }
      rx_fn = i2s_channel_read_32_to_16;
      rx_data_bit_width = I2S_DATA_BIT_WIDTH_16BIT;
      break;

    case I2S_RX_TRANSFORM_16_STEREO_TO_MONO:
      if (rx_data_bit_width != I2S_DATA_BIT_WIDTH_16BIT) {
        log_e("I2S_RX_TRANSFORM_16_STEREO_TO_MONO requires 16-bit data width");
        return false;
      }
      if (rx_slot_mode != I2S_SLOT_MODE_STEREO) {
        log_e("Wrong slot mode. Should be Stereo");
        return false;
      }
      if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
        return false;
      }
      if (_rx_slot_mask == I2S_STD_SLOT_RIGHT) {
        rx_fn = i2s_channel_read_16_stereo_to_mono_right;
      } else if (_rx_slot_mask == I2S_STD_SLOT_BOTH) {
        rx_fn = i2s_channel_read_16_stereo_to_mono_both;
      } else {
        rx_fn = i2s_channel_read_16_stereo_to_mono_left;
      }
      rx_slot_mode = I2S_SLOT_MODE_MONO;
      break;

    case I2S_RX_TRANSFORM_8_UNPACK:
#if SOC_I2S_HW_VERSION_1
      if (rx_data_bit_width != I2S_DATA_BIT_WIDTH_8BIT) {
        log_e("I2S_RX_TRANSFORM_8_UNPACK requires 8-bit data width");
        return false;
      }
      if (!allocTranformRX(I2S_READ_CHUNK_SIZE * 2)) {
        return false;
      }
      rx_fn = i2s_channel_read_8_unpack;
      break;
#else
      log_w("I2S_RX_TRANSFORM_8_UNPACK is only needed on ESP32 HW v1; ignoring on this target");
      return true;
#endif

    default: log_e("Unknown RX Transform %d", transform); return false;
  }
  rx_transform = transform;
  return true;
}

bool I2SClass::allocTranformRX(size_t buf_len) {
  char *buf = NULL;
  if (buf_len == 0) {
    if (rx_transform_buf != NULL) {
      free(rx_transform_buf);
      rx_transform_buf = NULL;
      rx_transform_buf_len = 0;
    }
    return true;
  }
  if (rx_transform_buf == NULL || rx_transform_buf_len != buf_len) {
    buf = (char *)malloc(buf_len);
    if (buf == NULL) {
      log_e("malloc %lu failed!", (unsigned long)buf_len);
      return false;
    }
    if (rx_transform_buf != NULL) {
      free(rx_transform_buf);
    }
    rx_transform_buf = buf;
    rx_transform_buf_len = buf_len;
  }
  return true;
}

const int WAVE_HEADER_SIZE = PCM_WAV_HEADER_SIZE;

//Record PCM WAV with current RX settings
uint8_t *I2SClass::recordWAV(size_t rec_seconds, size_t *out_size) {
  uint32_t sample_rate = rxSampleRate();
  uint16_t sample_width = (uint16_t)rxDataWidth();
  uint16_t num_channels = (uint16_t)rxSlotMode();
  size_t rec_size = rec_seconds * ((sample_rate * (sample_width / 8)) * num_channels);
  const pcm_wav_header_t wav_header = PCM_WAV_HEADER_DEFAULT(rec_size, sample_width, sample_rate, num_channels);
  *out_size = 0;

  log_d("Record WAV: rate:%" PRIu32 ", bits:%u, channels:%u, size:%lu", sample_rate, sample_width, (unsigned long)num_channels, rec_size);

  uint8_t *wav_buf = (uint8_t *)malloc(rec_size + WAVE_HEADER_SIZE);
  if (wav_buf == NULL) {
    log_e("Failed to allocate WAV buffer with size %lu", (unsigned long)rec_size + WAVE_HEADER_SIZE);
    return NULL;
  }
  memcpy(wav_buf, &wav_header, WAVE_HEADER_SIZE);
  size_t wav_size = readBytes((char *)(wav_buf + WAVE_HEADER_SIZE), rec_size);
  if (wav_size < rec_size) {
    log_e("Recorded %lu bytes from %lu", (unsigned long)wav_size, (unsigned long)rec_size);
  } else if (lastError()) {
    log_e("Read Failed! %d", lastError());
  } else {
    *out_size = rec_size + WAVE_HEADER_SIZE;
    return wav_buf;
  }
  free(wav_buf);
  return NULL;
}

void I2SClass::playWAV(const uint8_t *data, size_t len) {
  if (data == NULL || len < WAVE_HEADER_SIZE) {
    log_e("Invalid WAV data or length");
    return;
  }
  const pcm_wav_header_t *header = (const pcm_wav_header_t *)data;
  if (header->fmt_chunk.audio_format != 1) {
    log_e("Audio format is not PCM!");
    return;
  }
  const wav_data_chunk_t *data_chunk = &header->data_chunk;
  size_t data_offset = 0;
  while (memcmp(data_chunk->subchunk_id, "data", 4) != 0) {
    log_d(
      "Skip chunk: %c%c%c%c, len: %" PRIu32, data_chunk->subchunk_id[0], data_chunk->subchunk_id[1], data_chunk->subchunk_id[2], data_chunk->subchunk_id[3],
      data_chunk->subchunk_size + 8
    );
    data_offset += data_chunk->subchunk_size + 8;
    data_chunk = (const wav_data_chunk_t *)(data + WAVE_HEADER_SIZE + data_offset - 8);
  }
  log_d(
    "Play WAV: rate:%" PRIu32 ", bits:%u, channels:%u, size:%" PRIu32, header->fmt_chunk.sample_rate, header->fmt_chunk.bits_per_sample,
    header->fmt_chunk.num_of_channels, data_chunk->subchunk_size
  );
  configureTX(header->fmt_chunk.sample_rate, (i2s_data_bit_width_t)header->fmt_chunk.bits_per_sample, (i2s_slot_mode_t)header->fmt_chunk.num_of_channels);
  write(data + WAVE_HEADER_SIZE + data_offset, data_chunk->subchunk_size);
}

#if ARDUINO_HAS_MP3_DECODER
bool I2SClass::playMP3(const uint8_t *src, size_t src_len) {
  int16_t outBuf[MAX_NCHAN * MAX_NGRAN * MAX_NSAMP];
  const uint8_t *readPtr = NULL;
  int bytesAvailable = 0, err = 0, offset = 0;
  MP3FrameInfo frameInfo;
  HMP3Decoder decoder = NULL;

  bytesAvailable = src_len;
  readPtr = (const uint8_t *)src;

  decoder = MP3InitDecoder();
  if (decoder == NULL) {
    log_e("Could not allocate decoder");
    return false;
  }

  do {
    offset = MP3FindSyncWord((unsigned char *)readPtr, bytesAvailable);
    if (offset < 0) {
      break;
    }
    readPtr += offset;
    bytesAvailable -= offset;
    err = MP3Decode(decoder, (unsigned char **)&readPtr, &bytesAvailable, outBuf, 0);
    if (err) {
      log_e("Decode ERROR: %d", err);
      MP3FreeDecoder(decoder);
      return false;
    } else {
      MP3GetLastFrameInfo(decoder, &frameInfo);
      configureTX(frameInfo.samprate, (i2s_data_bit_width_t)frameInfo.bitsPerSample, (i2s_slot_mode_t)frameInfo.nChans);
      write((uint8_t *)outBuf, (size_t)((frameInfo.bitsPerSample / 8) * frameInfo.outputSamps));
    }
  } while (true);
  MP3FreeDecoder(decoder);
  return true;
}
#endif

#endif /* SOC_I2S_SUPPORTED */
