/*
  Playback of sound data by PWM
  Simply play the sound data file stored on the micro SD card.
  File reading should not be done in the callback because of the delicate chase between file reading and the PWM's DMA buffer.

  Copyright (C) 2024 T.Uemitsu
*/

#pragma once

#include <PWMAudio.h>
#define SDFAT_FILE_TYPE 1
#define USE_SPI_ARRAY_TRANSFER 1
#include <SdFat.h>

class CSDAudioPlayer {

#define _AUDIO_PWM_R 26
#define _AUDIO_PWM_L 27

#define _SD_RX 8
#define _SD_CS 9
#define _SD_SCK 10
#define _SD_TX 11
#define _SD_SPI SPI1

  static CSDAudioPlayer *anchor;

  typedef struct {
    int16_t L;
    int16_t R;
  } __attribute__((packed)) TTlr;

  TTlr *filebuff = nullptr;

  File32 file;

  PWMAudio *apwm;

  void __not_in_flash_func(cb)() {
    int n = 0, j = 0;
    int16_t d[4] = { 0 };

    if (doplay) {
      if (file.isOpen()) _playing = ((n = file.read(filebuff, filebufbyte)) > 0);
      else _playing = false;
    } else _playing = false;

    for (unsigned int i = 0; i < dmabufsize; i++) {
      if (_playing) {
        d[0] = (int16_t)(((int32_t)filebuff[j].L * (int32_t)volume) >> 8);
        d[1] = (int16_t)(((int32_t)filebuff[j].R * (int32_t)volume) >> 8);
        if (++j > n) _playing = false;
      } else
        d[0] = d[1] = 0;
      apwm->write((uint8_t *)&d, sizeof(d));
    }
  }
  static void __not_in_flash_func(_cb)() {
    anchor->cb();
  }

  uint32_t dmabufsize;
  uint32_t filebufbyte;
  uint16_t volume;
  int GetValue(void);
  void SetValue(int v);
  //    __declspec(property(get=GetValue, put=SetValue)) int Value;
  volatile bool _playing;
  volatile bool doplay;
  bool initialized;
public:
#if SDFAT_FILE_TYPE == 0
  SdFat sd;
#elif SDFAT_FILE_TYPE == 1
  SdFat32 sd;
#elif SDFAT_FILE_TYPE == 2
  SdExFat sd;
#elif SDFAT_FILE_TYPE == 3
  SdFs sd;
#else  // SD_FAT_TYPE
#error invalid SD_FAT_TYPE
#endif  // SD_FAT_TYPE

  CSDAudioPlayer()
    : initialized(false) {
    anchor = this;
  }
  ~CSDAudioPlayer() {
    end();
    anchor = nullptr;
  }

  bool play(const char *fn) {
    if (initialized) {
      stop();
      doplay = file.open(fn, O_RDONLY);
    }
    return doplay;
  }
  void stop() {
    if (initialized) {
      doplay = false;
      while (_playing) delay(1);
      if (file.isOpen()) file.close();
    }
  }
  int16_t get_volume(void) {
    return volume;
  }
  int16_t set_volume(int16_t v) {
    if (initialized) volume = MAX(MIN(v, 1023), 0);
    return volume;
  }

  void begin(int len = 200) {
    if (!initialized) {
      volume = 32;
      doplay = false;
      _playing = false;

      dmabufsize = len;
      filebufbyte = (len) * sizeof(TTlr);
      filebuff = new TTlr[len];
      memset(filebuff, 0, filebufbyte);
      apwm = new PWMAudio(_AUDIO_PWM_R, true);
      apwm->setBuffers(4, len);
      apwm->onTransmit(_cb);
      apwm->begin(44100, 192000);

      gpio_pull_up(_SD_RX);
      _SD_SPI.setCS(_SD_CS);
      _SD_SPI.setTX(_SD_TX);
      _SD_SPI.setRX(_SD_RX);
      _SD_SPI.setSCK(_SD_SCK);
      sd.begin(SdSpiConfig(_SD_CS, 0, 24000000, &_SD_SPI));

      initialized = true;
    }
  }
  //Calling the end and it's gone
  void end(void) {
    if (initialized) {
      stop();
      _playing = false;
      apwm->end();
      delete apwm;
      delete filebuff;
      dmabufsize = filebufbyte = 0;
      initialized = false;
    }
  }

  bool playing(void) {
    return _playing;
  }
};
CSDAudioPlayer *CSDAudioPlayer::anchor = nullptr;
