#include <freertos/stream_buffer.h>

/********************************* 一些公共函数,不用管 ***********************************/
#define BUZZER_PIN 4        // 蜂鸣器的引脚
#define BUZZER_CHANNEL 0    // PWM通道

/**
 * @brief 音频解码算法
 * 对输入的字符串进行音频解码,并通过PWM控制蜂鸣器播放
 * @param[in] music 音频字符串,格式为 音符,八度,间隔时间;下一个音符,
 * @怕扰民[in] size 长度
 */
static void decode(char *notes, size_t size) {
  ledcAttachPin(BUZZER_PIN, BUZZER_CHANNEL);  // 初始化PWM,指定使用引脚和PWM通道
  size_t len = size/3;
  for(int i=0; i<len; i++){
    int note = notes[i*3];
    int octave = notes[i*3+1];
    int rest = notes[i*3+2]*5;
    ledcWriteNote(BUZZER_CHANNEL, (note_t)note, octave);
    vTaskDelay(rest);
  }
  ledcDetachPin(BUZZER_PIN);
}

/**
 * @brief 随机生成音符
 * param[out] notes 音符序列
 * @return 返回字符串长度
 */
static size_t randomMusic(char **notes) {
  uint8_t len = random(1, 21);    // 生成1~20 组音符,每组有3位
  char *p =(char *)pvPortMalloc(sizeof(char)*len*3);   // 动态分配内存
  // 随机生成数据,第一个字节数据是0~12,第二个是5~9,第三个是20~200的5倍
  for(int i=0; i<len; i++){
    p[i*3] = random(0,13);
    p[i*3+1] = random(5,10);
    p[i*3+2] = random(20,200);
  }
  *notes = p;
  return len*3;
}
/**
 * @brief 测试用打印音乐
 * @param[in] notes 音乐数据序列
 * @param[in] size 音乐长度,3个一组打印
 **/
static void printMusic(char *notes, size_t size){
  // taskENTER_CRITICAL();
  printf("[");
  for(int i=0; i<size; i++){
    if(i % 3 ==0){printf("  ");}
    printf("%d,", notes[i]);
  }
  printf("]\n");
  // taskEXIT_CRITICAL();
}

/********************************* 留缓冲区例程 ***********************************/
#define BUFFER_SIZE   600   // 留缓冲区大小
#define TRIGGER_LEVEL 3     // 最小读出单位
#define READ_SIZE     90    // 最大读出宽度
StreamBufferHandle_t xStreamMusic = NULL; // 声明一个流缓冲区指针
uint16_t read_counter=0, write_counter=0; // 读出和写入次数计数器,纯计数没卵用

// 模拟下载线程
void download_task(void* param_t){
  printf("[DOWN] 下载器启动...\n");
  char *music= NULL;  // 下载待写入的数据
  size_t music_size;  // 随机生成的音乐大小
  size_t size;        // 存放实际写入数据的大小
  while(1){
    // 模拟从网上下载随机长度的音乐
    music_size = randomMusic(&music);
    printf("[DOWN] 随机生成音乐长度: %d \n", music_size);
    // printMusic(music, music_size);
    // 并写入到流缓冲区中,并返回实际写入的数据长度
    size = xStreamBufferSend(xStreamMusic,    // 缓冲区句柄
                            (void *)music,   // 要写入的数据
                            music_size,   // 写入的数据大小
                            portMAX_DELAY);   // 当缓冲区满后的等待时间
    vPortFree(music);

    if(size != music_size){
      // 如果实际写入的大小与音乐本身大小不一致,说明缓冲区写入满了,无法再继续写入
      printf("[DOWN] 音乐写入失败,缓冲区已满!%d - %d\n",size, music_size);
    }else{
      printf("[DOWN] 第 %d 次写入,大小 %d\n", ++write_counter, size);
    }
    vTaskDelay(pdMS_TO_TICKS(random(100,500)));
  }
}

// 模拟播放线程
void paly_task(void* param_t){
  printf("[PLAY] 播放器启动...\n");
  size_t size;                // 存放实际读出数据的大小
  char music[READ_SIZE];      // 读出数据的存放位置
  while(1){
    printf("[PLAY] 准备读取音乐\n");
    size = xStreamBufferReceive(xStreamMusic,   // 流缓冲区指针
                              (void *)music,   // 读出数据的存放变量指针
                              READ_SIZE,        // 预读出大小
                              portMAX_DELAY);   // 等待时间
    if(size>0){
      // 解码播放音乐
      printf("[PLAY] 第 %d 次读出,大小 %d\n", ++read_counter, size);
      // printMusic(music, size);
      decode(music, size);
      printf("[PLAY] 第 %d 段播放完毕!\n",read_counter);
    }
  }
}

// 数据监控线程
void monitor_task(void* param_t){
  while(1){
    if (xStreamBufferIsFull(xStreamMusic) == pdTRUE){
      printf("[MONI] 缓冲区已满!\n");
    }else{
      printf("[MONI] 已用 : %d , 剩余 : %d\n", xStreamBufferBytesAvailable(xStreamMusic), 
                                              xStreamBufferSpacesAvailable(xStreamMusic));
    }
    vTaskDelay(1000);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Hello, ESP32-S3!");
  pinMode(BUZZER_PIN, OUTPUT);
  ledcSetup(0, 5000, 16);   // 初始化PWM

  xStreamMusic = xStreamBufferCreate(BUFFER_SIZE, TRIGGER_LEVEL);
  if ( xStreamMusic == NULL ){
    printf("流缓冲区初始化失败,请检查内存是否够用!\n");
  }
  
  // // 创建线程
  xTaskCreate(download_task, "Downloader", 10240, NULL, 1, NULL);
  xTaskCreate(monitor_task, "Monitor", 10240, NULL, 1, NULL);
  xTaskCreate(paly_task, "Player", 10240, NULL, 1, NULL);
}

void loop() {
  delay(10);
}
esp:0
esp:1
esp:2
esp:3
esp:4
esp:5
esp:6
esp:7
esp:8
esp:9
esp:10
esp:11
esp:12
esp:13
esp:14
esp:15
esp:16
esp:17
esp:18
esp:19
esp:20
esp:21
esp:35
esp:36
esp:37
esp:38
esp:39
esp:40
esp:41
esp:42
esp:45
esp:46
esp:47
esp:48
esp:3V3.1
esp:3V3.2
esp:RST
esp:5V
esp:GND.1
esp:GND.2
esp:TX
esp:RX
esp:GND.3
esp:GND.4
bz1:1
bz1:2