/*
  Playback of sound data by PWM
  Converts sound data stored on the built-in flash memory to an arbitrary frequency, and simultaneously pronounces multiple sounds.

  Copyright (C) 2024 T.Uemitsu
*/
#pragma once

#include <PWMAudio.h>

#define _NEW_PICO_LIB

class CRAWAudioPlayer {
private:
  bool noinit = true;
  static CRAWAudioPlayer *anchor;
  int16_t vol = 32;

#define _AUDIO_PWM_R 26
#define _AUDIO_PWM_L 27

#define _DMA_BUFSIZE 50

  PWMAudio *apwm;

public:
  typedef struct {
    int8_t *raw;   // PCM data pointer
    int32_t size;  // PCM data byte size
    int32_t ind;   // -1:stop, 0:start
    int16_t freq;  // 256:no conversion
    uint8_t vol;   // 127:max volume
  } TPCMInfo, *PPCMInfo;

private:
  std::vector<TPCMInfo> *ppcminfo;

  // Process frequency and volume
  void mixer(int16_t *v) {
    int32_t vv = *v;
    for (uint8_t i = 0; i < ppcminfo->size(); i++) {
      if (ppcminfo->data()[i].ind >= 0) {
        ppcminfo->data()[i].ind += ppcminfo->data()[i].freq;
        if ((ppcminfo->data()[i].ind >> 8) >= ppcminfo->data()[i].size) ppcminfo->data()[i].ind = -1;
        else vv += (((ppcminfo->data()[i].raw[ppcminfo->data()[i].ind >> 8]) << 2) * ppcminfo->data()[i].vol) >> 7;
      }
    }
    *v = MIN(MAX(vv, SHRT_MIN), SHRT_MAX);
  }

  void __not_in_flash_func(cb)() {
    static int16_t v;
    static uint8_t updcyc = 0;
    int16_t d[4] = { 0 };
    for (int i = 0; i < _DMA_BUFSIZE; i++) {
      v = 0;
      mixer(&v);
      d[0] = d[1] = (int16_t)(((int32_t)v * (int32_t)vol) >> 2);
      apwm->write((uint8_t *)&d, sizeof(d));
    }
  }
  static void _cb(void) {
    anchor->cb();
  };

public:

  // Excitation of PCM reproduction (playback is performed by MRT and DAC)
  void play(uint8_t ind, uint8_t tone, uint8_t vol) {
    if (noinit) return;
    const uint16_t pcm_tone_table[12] = { 8192, 8679, 9195, 9742, 10321, 10935, 11585, 12274, 13004, 13777, 14596, 15464 };  // oct=9
    if ((ind < ppcminfo->size()) && (tone <= 127)) {
      div_t o_t = div(tone, 12);
      ppcminfo->data()[ind].ind = -1;
      ppcminfo->data()[ind].freq = pcm_tone_table[o_t.rem] >> (9 - o_t.quot);
      ppcminfo->data()[ind].vol = vol;
      ppcminfo->data()[ind].ind = 0;
    }
  }

  void stop(uint8_t ind) {
    if (noinit) return;
    if (ind < ppcminfo->size() - 1) ppcminfo->data()[ind].ind = -1;
  }

  int16_t get_volume (void) {
    return vol;
  }

  int16_t set_volume (int16_t v) {
    if (!noinit) vol = MAX(MIN(v,1023),0);
    return vol;
  }

  void begin(std::vector<TPCMInfo> *p) {
    anchor = this;
    ppcminfo = p;
    apwm = new PWMAudio(_AUDIO_PWM_R, true);
    apwm->onTransmit(_cb);
    apwm->setBuffers(4, _DMA_BUFSIZE);
    apwm->begin(44100, 192000);
    noinit = false;
  }
};
CRAWAudioPlayer *CRAWAudioPlayer::anchor = nullptr;
