/*!
 * @file  SDmusic.ino
 * @brief  SD卡音乐播放示例
 * @details  本示例演示如何播放SD卡中的音频文件（目前仅支持.wav格式）
 * @n  按照下方init函数的注释，使用杜邦线或其他线缆连接模块，并插入SD卡
 * @n  如果SD卡中有正确格式的音乐文件，
 * @n  （目前仅支持英文文件名和路径名，尽量避免使用空格，音频格式仅支持.wav）
 * @n  您可以通过setup中的配置函数设置音量、滤波器、扫描、播放、暂停和切换歌曲
 * @n   并在程序运行时根据loop函数中的代码注释通过串口命令调整配置
 * @note  串口操作可以直接改为按钮或旋钮等其他操作，以实现真正的蓝牙音箱
 * @copyright  Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com)
 * @license  The MIT License (MIT)
 * @author  [qsjhyy](yihuan.huang@dfrobot.com)
 * @version  V1.0
 * @date  2022-02-16
 * @url  https://github.com/DFRobot/DFRobot_MAX98357A
 */
#include <DFRobot_MAX98357A.h>

DFRobot_MAX98357A amplifier;   // 实例化一个对象来控制放大器

String musicList[100];   // SD卡音乐列表

/**************************************************************
                      初始化与主循环                          
**************************************************************/

void setup(void)
{
  Serial.begin(115200);

  /**
   * @brief 初始化I2S
   * @param _bclk - I2S通信引脚号，串行时钟(SCK)，也称位时钟(BCK)
   * @param _lrclk - I2S通信引脚号，字选择(WS)，即命令(通道)选择，用于切换左右声道数据
   * @param _din - I2S通信引脚号，串行数据信号(SD)，用于以二进制补码格式传输音频数据
   * @return 成功返回true，失败返回false
   */
  while ( !amplifier.initI2S(/*_bclk=*/GPIO_NUM_25, /*_lrclk=*/GPIO_NUM_26, /*_din=*/GPIO_NUM_27) ){
    Serial.println("初始化I2S失败！");
    delay(3000);
  }
  /**
   * @fn initSDCard
   * @brief 初始化SD卡
   * @param csPin SD卡模块SPI通信的片选引脚号
   * @return 成功返回true，失败返回false
   * @note SD卡模块引脚连接
   * @n  SD卡 |  ESP32
   * @n    +5V   |   VCC(5V)
   * @n    GND   |   GND
   * @n    SS    |   csPin (默认为IO5，可通过init函数自定义)
   * @n    SCK   |   SCK(IO18)
   * @n    MISO  |   MISO(IO19)
   * @n    MOSI  |   MOSI(IO23)
   * @n  可在www.dfrobot.com搜索MicroSD卡读卡器模块
   */
  while (!amplifier.initSDCard(/*csPin=*/GPIO_NUM_5)){
    Serial.println("初始化SD卡失败！");
    delay(3000);
  }
  Serial.println("初始化成功！");

  /**
   * @fn reverseLeftRightChannels
   * @brief 反转左右声道，当您发现左右声道播放相反时，可以调用此接口进行调整
   */
  // amplifier.reverseLeftRightChannels();

  /**
   * @brief 扫描SD卡中WAV格式的音乐文件
   * @param musicList - 从SD卡扫描到的.wav格式音乐文件，类型为字符串数组
   * @return 无
   * @note 目前仅支持英文文件名和路径名，尽量避免使用空格，音频格式仅支持.wav
   */
  amplifier.scanSDMusic(musicList);
  /**
   * 打印扫描到的可播放音乐文件列表
   */
  printMusicList();

  /**
   * @brief 设置音量
   * @param vol - 设置音量，范围可设置为0-9
   * @note 5为音频数据的原始音量，不增不减
   */
  amplifier.setVolume(5);

  /**
   * @brief 关闭音频滤波器
   * @note 高通滤波器和低通滤波器将同时关闭，因为它们同时工作
   * @n    当您设置的范围超出人耳可分辨的值时，视为关闭相应的滤波器
   * @n    滤波器的初始值为bq_type_highpass(2)和bq_type_lowpass(20000)
   * @n    此时认为滤波器已关闭
   */
  amplifier.closeFilter();

  /**
   * @brief 打开音频滤波器
   * @param type - bq_type_highpass：开启高通滤波；bq_type_lowpass：开启低通滤波
   * @param fc - 滤波阈值，范围：2-20000
   * @note 例如，设置高通滤波模式且阈值为500，表示滤除500以下的音频信号；高通滤波器和低通滤波器将同时工作
   */
  amplifier.openFilter(bq_type_highpass, 500);

  /**
   * @brief SD卡音乐播放控制接口
   * @param CMD - 播放控制命令：
   * @n SD_AMPLIFIER_PLAY：开始播放音乐，可以从之前暂停的位置继续播放
   * @n   如果没有通过playSDMusic()选择音乐文件，默认播放列表中的第一首
   * @n   如果SD卡中扫描的音乐文件格式不正确，可能会出现播放错误（目前仅支持英文路径名和WAV格式）
   * @n SD_AMPLIFIER_PAUSE：暂停播放，保持当前音乐文件的播放位置
   * @n SD_AMPLIFIER_STOP：停止播放，结束当前音乐播放
   * @return 无
   */
  amplifier.SDPlayerControl(SD_AMPLIFIER_PLAY);
  delay(5000);   // 先播放5秒，与下一个切歌操作清晰区分

  /**
   * @brief 播放SD卡中的音乐文件
   * @param Filename - 音乐文件名，目前仅支持.wav格式的音乐文件
   * @note 音乐文件名必须是绝对路径，如/musicDir/music.wav
   * @return 无
   */
  if(musicList[1].length()){
    Serial.println("正在切换音乐...\n");
    amplifier.playSDMusic(musicList[1].c_str());
  }else{
    Serial.println("当前选择的音乐文件不正确！\n");
  }

}

void loop()
{
  /**
   * @brief 串口命令的解析与实现，即在程序运行时通过串口命令调用上述配置函数
   * @note  可以在串口输入相应命令来配置放大器，格式为：cmd-value（命令-设置值，某些不需要赋值的命令可省略）
   * @n  当前可用命令：
   * @n    开始播放：如 start-
   * @n    暂停播放：如 pause-
   * @n    停止播放：如 stop-
   * @n    打印音乐列表：如 musicList-
   * @n    根据音乐列表切歌：如 changeMusic-1
   * @n    设置并开启高通滤波器：如 hp-500
   * @n    设置并开启低通滤波器：如 lp-15000
   * @n    关闭滤波器：如 closeFilter-
   * @n    设置音量：如 vol-5.0
   * @n    其他命令将打印此说明
   * @n  命令的详细含义请参考相应函数的注释
   */
  parseSerialCommand();
  delay(500);
}

/**************************************************************
              打印扫描到的可播放音乐文件列表                  
**************************************************************/

void printMusicList(void)
{
  uint8_t i = 0;

  if(musicList[i].length()){
    Serial.println("\n音乐列表：");
  }else{
    Serial.println("SD卡音频文件扫描为空，请检查SD卡中是否有符合格式的音频文件！");
  }

  while(musicList[i].length()){
    Serial.print("\t");
    Serial.print(i);
    Serial.print("  -  ");
    Serial.println(musicList[i]);
    i++;
  }
}

/**************************************************************
                    串口命令的解析与实现                        
**************************************************************/

void parseSerialCommand(void)
{
  String cmd;   // 保存在串口读取的命令类型
  float value;   // 保存在串口读取的命令值

  /**
   * 命令格式：cmd-value
   * cmd：表示命令类型
   * value：表示命令类型对应的设置值，某些命令可以为空
   * 例如：(1) 设置高通滤波器，滤除500以下的音频数据：hp-500
   *      (2) 关闭滤波器：closeFilter-
   */
  if(Serial.available()){   // 检测是否有可用的串口命令
    cmd = Serial.readStringUntil('-');   // 读取指定的终止符字符串，用于切割和识别串口命令，后续相同注释不再重复

    if(cmd.equals("hp")){   // 判断是否为设置高通滤波器的命令类型
      Serial.println("正在设置高通滤波器...\n");
      value =Serial.parseFloat();   // 解析字符串并返回浮点数

      /**
       * @brief 打开音频滤波器
       * @param type - bq_type_highpass：开启高通滤波；bq_type_lowpass：开启低通滤波
       * @param fc - 滤波阈值，范围：2-20000
       * @note 例如，设置高通滤波模式且阈值为500，表示滤除500以下的音频信号；高通滤波器和低通滤波器可同时工作
       */
      amplifier.openFilter(bq_type_highpass, value);


    }else if(cmd.equals("lp")){   // 判断是否为设置低通滤波器的命令类型
      Serial.println("正在设置低通滤波器...\n");
      value =Serial.parseFloat();

      amplifier.openFilter(bq_type_lowpass, value);

    }else if(cmd.equals("closeFilter")){   // 判断是否为关闭滤波器的命令类型
      Serial.println("正在关闭滤波器...\n");

      /**
       * @brief 关闭音频滤波器
       */
      amplifier.closeFilter();

    }else if(cmd.equals("vol")){   // 判断是否为设置音量的命令类型
      Serial.println("正在设置音量...\n");
      value =Serial.parseFloat();

      /**
       * @brief 设置音量
       * @param vol - 设置音量，范围可设置为0-9
       * @note 5为音频数据的原始音量，不增不减
       */
      amplifier.setVolume(value);

    }else if(cmd.equals("start")){   // 判断是否为开始播放的命令类型
      Serial.println("开始播放...\n");

      /**
       * @brief SD卡音乐播放控制接口
       * @param CMD - 播放控制命令：
       * @n SD_AMPLIFIER_PLAY：开始播放音乐，可以从之前暂停的位置继续播放
       * @n   如果没有通过playSDMusic()选择音乐文件，默认播放列表中的第一首
       * @n   如果SD卡中扫描的音乐文件格式不正确，可能会出现播放错误（目前仅支持英文路径名和WAV格式）
       * @n SD_AMPLIFIER_PAUSE：暂停播放，保持当前音乐文件的播放位置
       * @n SD_AMPLIFIER_STOP：停止播放，结束当前音乐播放
       * @return 无
       */
      amplifier.SDPlayerControl(SD_AMPLIFIER_PLAY);

    }else if(cmd.equals("pause")){   // 判断是否为暂停播放的命令类型
      Serial.println("暂停播放...\n");

      // 同上
      amplifier.SDPlayerControl(SD_AMPLIFIER_PAUSE);

    }else if(cmd.equals("stop")){   // 判断是否为停止播放的命令类型
      Serial.println("停止播放...\n");

      // 同上
      amplifier.SDPlayerControl(SD_AMPLIFIER_STOP);

    }else if(cmd.equals("musicList")){   // 判断是否为打印当前可播放音乐文件列表的命令类型
      Serial.println("正在扫描音乐列表...\n");

      /**
       * @brief 扫描SD卡中WAV格式的音乐文件
       * @param musicList - 从SD卡扫描到的.wav格式音乐文件，类型为字符串数组
       * @return 无
       * @note 目前仅支持英文文件名和路径名，尽量避免使用空格，音频格式仅支持.wav
       */
      amplifier.scanSDMusic(musicList);
      /**
       * 打印扫描到的可播放音乐文件列表
       */
      printMusicList();

    }else if(cmd.equals("changeMusic")){   // 判断是否为根据音乐列表切歌的命令类型
      cmd = musicList[Serial.parseInt()];

      /**
       * @brief 播放SD卡中的音乐文件
       * @param Filename - 音乐文件名，目前仅支持.wav格式的音乐文件
       * @note 音乐文件名必须是绝对路径，如/musicDir/music.wav
       * @return 无
       */
      if(cmd.length()){
        Serial.println("正在切换音乐...\n");
        amplifier.playSDMusic(cmd.c_str());
      }else{
        Serial.println("当前选择的音乐文件不正确！\n");
      }

    }else{   // 未知命令类型
      Serial.println("帮助：\n \
      当前可用命令（格式：cmd-value）：\n \
        开始播放：如 start-\n \
        暂停播放：如 pause-\n \
        停止播放：如 stop-\n \
        打印音乐列表：如 musicList-\n \
        根据音乐列表切歌：如 changeMusic-1\n \
        设置并开启高通滤波器：如 hp-500\n \
        设置并开启低通滤波器：如 lp-15000\n \
        关闭滤波器：如 closeFilter-\n \
        设置音量：如 vol-5.0\n \
      详细含义请参考本示例的代码注释。\n");
    }
    while(Serial.read() >= 0);   // 清除串口中剩余的数据
  }
}
