【SD音声入門】M5Stackで音の再生・生成・録音で遊んでみた♪

今回は、前回に引き続いてM5StackのSD利用して、音の再生・生成・録音で遊んでみました。
やりきっていませんが、現状をまとめておきます。

###やったこと
・環境構築
・SDカードのwavを再生
・関数生成波形ドレミ音として再生
・マイク音をSDに録音
###・環境構築
多分SDを持っているM5Stack製品では、適用できると思うが、今回はCore2を利用しました。
参考は、以下のとおりですが、環境はPlatformIOは利用せず、通常のArduinoIDEで実施しました。
Libは、通常のCore2と音の処理のために
・ESP8266audio
を利用します。これは、普通にツールーライブラリを管理でインストールでESP8266Audioでインストールできます。
ESP32,RaspiPicoなどにも対応しているようです。
また、ボタン処理のために以下のVButtonを利用しています。今回は、VButton.cppとVButton.hのコードをAruduinoIDEでタグを追加して貼り付けて利用しました。
・[furuya02/M5Stack_Core2_VirtualButton](https://github.com/furuya02/M5Stack_Core2_VirtualButton)

【参考】
[[M5Stack Core2 for AWS] Arduinoでオーディオファイルを再生してみました](https://dev.classmethod.jp/articles/m5stack-core2-for-aws-arduino-audio/)

###・SDカードのwavを再生
このコードは、ほぼ上記参考のとおりです。
変更したのは、Wav再生に変更し、SDにaudioW.wav等をあらかじめ保存しているので、pathを合わせています。
ボタンの表示は、オリジナルのまま（bach, beethoven, chopin）としています。

```再生.c
#include <M5Core2.h>
#include <driver/i2s.h>
#include "AudioFileSourceSD.h"
#include "AudioGeneratorWAV.h"
//#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"

#include "VButton.h"

AudioGeneratorWAV *wav = NULL;
//AudioGeneratorMP3 *mp3 = NULL;
AudioFileSourceSD *file;
AudioOutputI2S *out;

#define BCLK_PIN 12
#define LRCK_PIN 0
#define SADTA_PIN 2
#define EXTERNAL_I2S 0
#define OUTPUT_GAIN 80

VButton * button_bach;
VButton * button_beethoven;
VButton * button_chopin;

uint16_t getColor(uint8_t red, uint8_t green, uint8_t blue){
  return ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);
}

void button_callback(char *name, bool use_toggle, bool is_toggled){
  char path[128];
  
  // 再生中の場合は、停止する
  if(wav != NULL){
    wav->stop();
  }
  
  //sprintf(path,"/wav/%s.wav",name);
  sprintf(path,"/%s.wav",name);
  file = new AudioFileSourceSD(path);
  out = new AudioOutputI2S(I2S_NUM_0, EXTERNAL_I2S); 
  out->SetPinout(BCLK_PIN, LRCK_PIN, SADTA_PIN);
  out->SetOutputModeMono(true);
  out->SetGain((float)OUTPUT_GAIN/100.0);
  wav = new AudioGeneratorWAV();
  wav->begin(file, out);
}

void setup() {
  M5.begin();
  M5.Lcd.setBrightness(255);
  M5.Lcd.fillScreen(WHITE);
  // スピーカーの初期化
  M5.Axp.SetSpkEnable(true); 
  // 操作ボタン
  ushort dark_gray = getColor(80, 80, 80);
  button_bach = new VButton("audioW", button_callback, false, -70, dark_gray);  
  button_beethoven = new VButton("merody", button_callback, false, 0, dark_gray);  
  button_chopin = new VButton("Chopin", button_callback, false, 70, dark_gray);  
}

void loop() {
  button_bach->loop();
  button_beethoven->loop();
  button_chopin->loop();

  if(wav != NULL){
    if (wav->isRunning()) {
      if (!wav->loop()) wav->stop();
    } else {
      Serial.printf("WAV done\n");
      delay(1000);
    }
  }
}
```
###・関数生成波形ドレミ音として再生
このコードは、ESP8266audioのスケッチ例PlayWAVFromFunctionと上記のコードを融合して作成しました。
スケッチ例は `out = new AudioOutputI2SNoDAC();`を利用していますが、ここでは上記のCore2のスピーカーである  `out = new AudioOutputI2S(I2S_NUM_0, EXTERNAL_I2S);` を利用しています。
また、ボタンで再生するのは同様です。関数は、ドレミの音を生成していますが、表示は当初440Hz, 1000Hz,2000Hzを計算していたので、その表示になっています。
実は、ここCallback中のif文が苦労しています。
最終的に  
`sprintf(path,"%s",name);`
`f=atof(path);`
したあと、ifで判別しています。
pathには正しく文字（char）がはいっているので、そのままファイル名に使えそうですが、なぜか使えません。ということで、上記のようにatofで数値変換してif文という処理でごまかしています。

```生成.c
#include <M5Core2.h>
#include <driver/i2s.h>
#include "AudioFileSourceFunction.h"
#include "AudioGeneratorWAV.h"
#include "AudioOutputI2S.h"

#include "VButton.h"

AudioGeneratorWAV *wav;
AudioFileSourceFunction* file;
AudioOutputI2S *out;

#define BCLK_PIN 12
#define LRCK_PIN 0
#define SADTA_PIN 2
#define EXTERNAL_I2S 0
#define OUTPUT_GAIN 80

VButton * button_bach;
VButton * button_beethoven;
VButton * button_chopin;

uint16_t getColor(uint8_t red, uint8_t green, uint8_t blue){
  return ((red>>3)<<11) | ((green>>2)<<5) | (blue>>3);
}

float sine_wave0440(const float time) {
  float hz = 2*261.626f;  //440.f;
  float v = sin(TWO_PI * hz * time);  // C
  v *= fmod(time, 1.f);               // change linear
  v *= 0.5;                           // scale
  return v;
};
float sine_wave1000(const float time) {
  float hz = 2*293.665f;
  float v = sin(TWO_PI * hz * time);  // C
  v *= fmod(time, 1.f);               // change linear
  v *= 0.5;                           // scale
  return v;
};
float sine_wave2000(const float time) {
  float hz = 2*329.628f;
  float v = sin(TWO_PI * hz * time);  // C
  v *= fmod(time, 1.f);               // change linear
  v *= 0.5;                           // scale
  return v;
};

void button_callback(char *name, bool use_toggle, bool is_toggled){
  char path[14]="";
  float f=0.44;
  file = new AudioFileSourceFunction(8.);
  file->addAudioGenerators(sine_wave0440);
  // 再生中の場合は、停止する
  if(wav != NULL){
    wav->stop();
  }

  sprintf(path,"%s",name);
  f=atof(path);
  if(f==0.44){
    file = new AudioFileSourceFunction(8.);
    file->addAudioGenerators(sine_wave0440);
    Serial.print(f);
  }
  if(f==1.00){
    file = new AudioFileSourceFunction(8.);
    file->addAudioGenerators(sine_wave1000);
    Serial.print(f);
  }
  if(f==2.00){
    file = new AudioFileSourceFunction(8.);
    file->addAudioGenerators(sine_wave2000);
    Serial.print(f);
  }  
  //Serial.print(f);
 
  out = new AudioOutputI2S(I2S_NUM_0, EXTERNAL_I2S); 
  out->SetPinout(BCLK_PIN, LRCK_PIN, SADTA_PIN);
  out->SetOutputModeMono(true);
  out->SetGain((float)OUTPUT_GAIN/100.0);
  wav = new AudioGeneratorWAV();
  wav->begin(file, out);
}

void setup() {
  M5.begin();
  M5.Lcd.setBrightness(255);
  M5.Lcd.fillScreen(WHITE);
  M5.Axp.SetSpkEnable(true);

  // 操作ボタン
  ushort dark_gray = getColor(80, 80, 80);
  button_bach = new VButton("0.44", button_callback, false, -70, dark_gray);  
  button_beethoven = new VButton("1.00", button_callback, false, 0, dark_gray);  
  button_chopin = new VButton("2.00", button_callback, false, 70, dark_gray); 
}

void loop() {
  M5.update();
  button_bach->loop();
  button_beethoven->loop();
  button_chopin->loop();

  if(wav != NULL){
    if (wav->isRunning()) {
      if (!wav->loop()) wav->stop();
    } else {
      Serial.printf("function done\n");
      delay(1000);
    }
  }
}
```
###・マイク音をSDに録音
もともと録音した音声を再生するがしたかったのですが、参考のままになっています。
つまり、音声録音してAudacityでRawデータ再生までです。
また、再生に利用したAudacityはWindowsにインストールして利用しました。
※RasPi4ではうまく動きませんでした
【参考】
・[[M5Stack Core2 for AWS] Arduinoでマイクの利用方法を確認してみました](https://dev.classmethod.jp/articles/m5stack-core2-for-aws-arduino-mic-spm1423/)

```録音.c
include <M5Core2.h>
#include <driver/i2s.h>

#include "VButton.h"
//#include "Led.h"

#define SAMPLE_RATE 16000 //サンプリングレート
#define CHUNK 1024 // I2Sバッファサイズ
#define PERIAD 10 // 録音時間（sec）

#define CONFIG_I2S_LRCK_PIN 0 // I2Sクロック
#define CONFIG_I2S_DATA_IN_PIN 34 // I2Sデータ

bool InitMic(){
    i2s_config_t i2s_config = {
        .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM),
        .sample_rate = SAMPLE_RATE,
        .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
        .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
        .communication_format = I2S_COMM_FORMAT_I2S,
        .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
        .dma_buf_count = 2,
        .dma_buf_len = CHUNK
    };
    i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NU・LL);

    i2s_pin_config_t tx_pin_config = {
      .bck_io_num = I2S_PIN_NO_CHANGE,//未使用
      .ws_io_num = CONFIG_I2S_LRCK_PIN,
      .data_out_num = I2S_PIN_NO_CHANGE, // 未使用
      .data_in_num = CONFIG_I2S_DATA_IN_PIN
    };
    i2s_set_pin(I2S_NUM_0, &tx_pin_config);
}

VButton *button;
//Led *led;

void button_callback(char *title, bool use_toggle, bool is_toggled){
  //led->set(255, 0, 0);

  //File f = SD.open("/audio.dat", FILE_WRITE);
  File f = SD.open("/audio.wav", FILE_WRITE);
  if (f) {
    uint8_t buffer[CHUNK];
    size_t byte_read;
    
    // サンプリングレート * 16bit * 1ch * 録音時間 (bit数)
    // /8 (byte数)
    // /CHANK (chank数)
    int chank_max = (int)((SAMPLE_RATE*16*1*PERIAD)/8/CHUNK);

    for(int i=0; i < chank_max; i++){
      i2s_read(I2S_NUM_0, buffer, CHUNK, &byte_read, (100 / portTICK_RATE_MS));
      Serial.printf(".");
      f.write(buffer, CHUNK);
    }
    f.close();
  }
  //led->set(0, 0, 0);
}

void setup() {
    M5.begin(true, true, true, true);
    M5.Lcd.setBrightness(200);
    M5.Lcd.fillScreen(WHITE);

    button = new VButton("REC", button_callback);
    //led = new Led();
    InitMic();
}

void loop() {
    button->loop();
}

```
###まとめ
・SD利用して音の再生、関数生成、録音で遊んでみた

・関数生成音声のSD保管
・Wavファイルに変換し保管
・FFT分析などpythonでやってきたことを実現したいと思う