// 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
#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

#define I2S_READ_CHUNK_SIZE 1920

#define I2S_DEFAULT_CFG()                                                                                                                    \
  {                                                                                                                                          \
    .id = I2S_NUM_AUTO, .role = I2S_ROLE_MASTER, .dma_desc_num = 6, .dma_frame_num = 240, .auto_clear = true, .auto_clear_before_cb = false, \
    .intr_priority = 0                                                                                                                       \
  }

#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)

// 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;
}

// Resample the 16bit stereo microphone data into 16bit mono.
static esp_err_t i2s_channel_read_16_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 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 /= 2;
  uint16_t *ds = (uint16_t *)dst;
  uint16_t *src = (uint16_t *)read_buff;
  for (size_t i = 0; i < out_len; i += 2) {
    *ds++ = src[i];
  }
  *bytes_read = out_len;
  return ESP_OK;
}

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

  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;

  _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;
}

// 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();
  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 (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) {
    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));
  }

  // 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("Failed to set all pins bus to I2S_STD");
  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();
  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();
  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();
  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) {
  /* Setup I2S peripheral */
  if (mode >= I2S_MODE_MAX) {
    log_e("Invalid I2S mode selected.");
    return false;
  }
  _mode = mode;

  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;
  }

  //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;
    }
    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) {
  /* 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);
      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));
      return transformRX(transform);
    }
    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;
  }
  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_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;
      }
      rx_fn = i2s_channel_read_16_stereo_to_mono;
      rx_slot_mode = I2S_SLOT_MODE_MONO;
      break;

    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(uint8_t *data, size_t len) {
  pcm_wav_header_t *header = (pcm_wav_header_t *)data;
  if (header->fmt_chunk.audio_format != 1) {
    log_e("Audio format is not PCM!");
    return;
  }
  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 = (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(uint8_t *src, size_t src_len) {
  int16_t outBuf[MAX_NCHAN * MAX_NGRAN * MAX_NSAMP];
  uint8_t *readPtr = NULL;
  int bytesAvailable = 0, err = 0, offset = 0;
  MP3FrameInfo frameInfo;
  HMP3Decoder decoder = NULL;

  bytesAvailable = src_len;
  readPtr = src;

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

  do {
    offset = MP3FindSyncWord(readPtr, bytesAvailable);
    if (offset < 0) {
      break;
    }
    readPtr += offset;
    bytesAvailable -= offset;
    err = MP3Decode(decoder, &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 */
