71歳の... microSDに保存した高音質WAVファイルの連続（シャッフル）再生ができました

　昔、レコードからの出力をパソコンに取り込んで、多くのWAVファイルを作成しました。これまでは市販の音楽プレーヤーに入れて聴いてました。今回、ESP32でダラダラ聴き流せるようになりました（MP3のときのようにバグることも少ない）。それも音質を落とさず、録音時のままで❕さらに、SD内でのルート／サブディレクトリを問いません。さらにさらに、漢字・かなの長いファイル名にも対応しています（私の環境では）。

 ![KIMG0860.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/16c1c9ba-27dc-4bac-88e3-53a8ff4972da.jpeg)
　このような音質です。
 
    チャンネル　２（ステレオ）
    サンプルレート 44100Hz
    サンプル当たりのビット数 16
    
    ビットレート 44100Hz × 16bit × 2 ÷ 1000 = 1411.2kbps
    
　元ネタはここです。

https://www.xtronical.com/i2s-ep3/

　上記サイトの <INS>SD WAV CODE</INS> を下敷きに工夫しました。環境や構成は以下。

:::note info
１．Windows 11 Pro
２．Arduino IDE 2.3.6
３．ESP32 Dev Module（Arduino IDE 内でのボード名）
４．SPI接続 microSDカードスロットモジュール
５．32GBまでの適当なmicroSDカード
６．PCM5102A使用 32bit/384kHz DAC モジュール
　　https://store.shopping.yahoo.co.jp/nfj/h135.html
７．ボードマネージャは
　　```esp32 by Espressif Systems version 3.0.1```
　　にバージョンダウン
８．```ツール``` > ```Partition Scheme:"Custom"```
９．ステレオミニジャックに外部アクティブスピーカーを接続。イヤホンを繋ぐ場合は音量にご注意。
:::
　ピンの接続です。
 
|ESP32|	PCM5102A|	microSDスロット|
|-----|---------|-----------------|
|25|	DATA||	
|26|	LRCK||	
|27|	BICK||	
|5V|	VCC||	
|5|		|SC(CS)|
|23|	|MOSI|
|19|	|MISO|
|18|	|SCK(CLK)|
|3V3|	|3V3|
|GND|	GND|GND|

　Arduino IDE での```ツール```の設定は次のようにしました。

![CompileOp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/da929d50-b75b-4257-8e2c-7797e2136abe.jpeg)

　再生中のシリアルモニタの様子です。
 ![PlayWave.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3189926/4c9c9e08-7bd0-49d2-98d3-07a44c2bef7e.jpeg)
　スケッチです。

```c:Wav_SerialPlay.ino
//------------------------------------------------------------------------------------------------------------------------
//
// Title: SD Card Wav Player
//
// Description:
//    Simple example to demonstrate the fundementals of playing WAV files (digitised sound) from an SD Card via the I2S
//    interface of the ESP32. Plays WAV file from SD card. To keep this simple the WAV must be stereo and 16bit samples.
//    The Samples Per second can be anything. On the SD Card the wav file must be in root and called wavfile.wav
//    Libraries are available to play WAV's on ESP32, this code does not use these so that we can see what is happening.
//    This is part 3 in a tutorial series on using I2S on ESP32. See the accompanying web page (which will also include
//    a tutorial video).
//
// Boring copyright/usage information:
//    (c) XTronical, www.xtronical.com
//    Use as you wish for personal or monatary gain, or to rule the world (if that sort of thing spins your bottle)
//    However you use it, no warrenty is provided etc. etc. It is not listed as fit for any purpose you perceive
//    It may damage your house, steal your lover, drink your beers and more.
//
//    http://www.xtronical.com/i2s-ep3
//
//------------------------------------------------------------------------------------------------------------------------

// Includes
#include "SD.h"          // SD Card library, usually part of the standard install
#include "driver/i2s.h"  // Library of I2S routines, comes with ESP32 standard install
#include <string.h>
#include <vector>

//    SD Card
#define SD_CS 5  // SD Card chip select
#define SPI_MOSI 23
#define SPI_MISO 19
#define SPI_SCK 18

//    I2S
#define I2S_DOUT 25  // i2S Data out oin
#define I2S_BCLK 27  // Bit clock
#define I2S_LRC 26   // Left/Right clock, also known as Frame clock or word select
#define I2S_NUM 0    // i2s port number

// Wav File reading
#define NUM_BYTES_TO_READ_FROM_FILE 1024 * 4  // How many bytes to read from wav file at a time

//------------------------------------------------------------------------------------------------------------------------
// structures and also variables
//  I2S configuration
static const i2s_config_t i2s_config = {
  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
  .sample_rate = 44100,  // Note, this will be changed later
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
  .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,  // high interrupt priority
  .dma_buf_count = 8,                        // 8 buffers
  .dma_buf_len = 64,                         // 64 bytes per buffer, so 8K of buffer space
  .use_apll = 0,
  .tx_desc_auto_clear = true,
  .fixed_mclk = -1
};

// These are the physical wiring connections to our I2S decoder board/chip from the esp32, there are other connections
// required for the chips mentioned at the top (but not to the ESP32), please visit the page mentioned at the top for
// further information regarding these other connections.
static const i2s_pin_config_t pin_config = {
  .bck_io_num = I2S_BCLK,           // The bit clock connectiom, goes to pin 27 of ESP32
  .ws_io_num = I2S_LRC,             // Word select, also known as word select or left right clock
  .data_out_num = I2S_DOUT,         // Data out from the ESP32, connect to DIN on 38357A
  .data_in_num = I2S_PIN_NO_CHANGE  // (-1)we are not interested in I2S data into the ESP32
};

struct WavHeader_Struct {
  //   RIFF Section
  char RIFFSectionID[4];  // Letters "RIFF"
  uint32_t Size;          // Size of entire file less 8
  char RiffFormat[4];     // Letters "WAVE"

  //   Format Section
  char FormatSectionID[4];  // letters "fmt"
  uint32_t FormatSize;      // Size of format section less 8
  uint16_t FormatID;        // 1=uncompressed PCM
  uint16_t NumChannels;     // 1=mono,2=stereo
  uint32_t SampleRate;      // 44100, 16000, 8000 etc.
  uint32_t ByteRate;        // =SampleRate * Channels * (BitsPerSample/8)
  uint16_t BlockAlign;      // =Channels * (BitsPerSample/8)
  uint16_t BitsPerSample;   // 8,16,24 or 32

  // Data Section
  char DataSectionID[4];  // The letters "data"
  uint32_t DataSize;      // Size of the data that follows
} WavHeader;
//------------------------------------------------------------------------------------------------------------------------

//  Global Variables/objects
static const i2s_port_t i2s_num = I2S_NUM_0;  // i2s port number
// The file we're currently playing
File f, WavFile;                              // Object for root of SD card directory
std::vector<String> mfList;
static uint16_t _BytesRead = 0;
long nmax = 0;

// Recursively scan the card and make a list of all MP3 files
// in all dirs
void scanDirectory(const char* dirname) {
  File root = SD.open(dirname);
  while (true) {
    f = root.openNextFile();
    if (!f) break;

    String n = f.name();
    n.toLowerCase();
    String path = dirname;
    path += f.name();
    if (f.isDirectory()) {
      if (f.name()[0] == '.') {  // Ignore . and ..
        continue;
      }
      String sub = dirname;
      sub += f.name();
      sub += "/";
      scanDirectory(sub.c_str());
    } else if (strstr(n.c_str(), ".wav")) {
      mfList.push_back(path);
      Serial.printf("WAV: %s\n\r", path.c_str());
    } else {
      //Serial.printf("SKP: %s\n\r", path.c_str());
    }
    f.close();
  }
  root.close();
}

//------------------------------------------------------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);                   // Used for info/debug
  SD.begin(SD_CS, SPI, 24000000, "/sd");  //24000000
  scanDirectory("/");
  nmax = mfList.size();
  Serial.println("Music List ----------");
  for (int i = 0; i < nmax; i++) Serial.println(mfList[i].c_str());
  Serial.println("---------------------");

  i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
  i2s_set_pin(i2s_num, &pin_config);
}

long n = -1;
void loop() {
  if (PlayWav() > 0)
    ;  // Have to keep calling this to keep the wav file playing
  else {
    // get the wav file from the SD card
    n = random(nmax); // shuffle play <- n++;
    WavFile = SD.open(mfList[n].c_str());  // Open the wav file
    if (WavFile == false)
      Serial.println("Could not open 'wavfile(*.wav)'");
    else {
      Serial.println("--------------------------------------");
      Serial.println(mfList[n].c_str());
      WavFile.read((byte*)&WavHeader, 44);                    // Read in the WAV header, which is first 44 bytes of the file.
                                                              // We have to typecast to bytes for the "read" function
      DumpWAVHeader(&WavHeader);                              // Dump the header data to serial, optional!
      if (ValidWavData(&WavHeader))                           // optional if your sure the WAV file will be valid.
        i2s_set_sample_rates(i2s_num, WavHeader.SampleRate);  //set sample rate
    };                                                        // Your normal code to do your task can go here
  }
}

uint16_t PlayWav() {
  static bool ReadingFile = true;                   // True if reading file from SD. false if filling I2S buffer
  static byte Samples[NUM_BYTES_TO_READ_FROM_FILE]; // Memory allocated to store the data read in from the wav file
                                                    // Num bytes actually read from the wav file which will either be
                                                    // NUM_BYTES_TO_READ_FROM_FILE or less than this if we are very
                                                    // near the end of the file. i.e. we can't read beyond the file.
  if (ReadingFile)  // Read next chunk of data in from file if needed
  {
    _BytesRead = ReadFile(Samples);  // Read data into our memory buffer, return num bytes read in
    if (_BytesRead == 0) return 0;
    ReadingFile = false;  // Switch to sending the buffer to the I2S
  } else
    ReadingFile = FillI2SBuffer(Samples, _BytesRead);  // We keep calling this routine until it returns true, at which point
                                                       // this will swap us back to Reading the next block of data from the file.
                                                       // Reading true means it has managed to push all the data to the I2S
                                                       // Handler, false means there still more to do and you should call this
                                                       // routine again and again until it returns true.
  return _BytesRead;
}

uint16_t ReadFile(byte* Samples) {
  static uint32_t BytesReadSoFar = 0;  // Number of bytes read from file so far
  uint16_t BytesToRead;                // Number of bytes to read from the file

  if (BytesReadSoFar + NUM_BYTES_TO_READ_FROM_FILE > WavHeader.DataSize)  // If next read will go past the end then adjust the
    BytesToRead = WavHeader.DataSize - BytesReadSoFar;                    // amount to read to whatever is remaining to read
  else
    BytesToRead = NUM_BYTES_TO_READ_FROM_FILE;  // Default to max to read

  WavFile.read(Samples, BytesToRead);  // Read in the bytes from the file
  BytesReadSoFar += BytesToRead;       // Update the total bytes red in so far

  if (BytesReadSoFar >= WavHeader.DataSize)  // Have we read in all the data?
  {
    // WavFile.seek(44); // Reset to start of wav data.(to Endless Loop)
    BytesReadSoFar = 0;  // Clear to no bytes read in so far
    BytesToRead = 0;
  }
  return BytesToRead;  // return the number of bytes read into buffer
}

bool FillI2SBuffer(byte* Samples, uint16_t BytesInBuffer) {
  // Writes bytes to buffer, returns true if all bytes sent else false, keeps track itself of how many left
  // to write, so just keep calling this routine until returns true to know they've all been written, then
  // you can re-fill the buffer

  size_t BytesWritten;            // Returned by the I2S write routine,
  static uint16_t BufferIdx = 0;  // Current pos of buffer to output next
  uint8_t* DataPtr;               // Point to next data to send to I2S
  uint16_t BytesToSend;           // Number of bytes to send to I2S

  // To make the code eaier to understand I'm using to variables to some calculations, normally I'd write this calcs
  // directly into the line of code where they belong, but this make it easier to understand what's happening

  DataPtr = Samples + BufferIdx;                               // Set address to next byte in buffer to send out
  BytesToSend = BytesInBuffer - BufferIdx;                     // This is amount to send (total less what we've already sent)
  i2s_write(i2s_num, DataPtr, BytesToSend, &BytesWritten, 1);  // Send the bytes, wait 1 RTOS tick to complete
  BufferIdx += BytesWritten;                                   // increasue by number of bytes actually written

  if (BufferIdx >= BytesInBuffer) {
    // sent out all bytes in buffer, reset and return true to indicate this
    BufferIdx = 0;
    return true;
  } else
    return false;  // Still more data to send to I2S so return false to indicate this
}

bool ValidWavData(WavHeader_Struct* Wav) {
  if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0) {
    Serial.print("Invalid data - Not RIFF format");
    return false;
  }
  if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0) {
    Serial.print("Invalid data - Not Wave file");
    return false;
  }
  if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0) {
    Serial.print("Invalid data - No format section found");
    return false;
  }
  if (memcmp(Wav->DataSectionID, "data", 4) != 0) {
    Serial.print("Invalid data - data section not found");
    return false;
  }
  if (Wav->FormatID != 1) {
    Serial.print("Invalid data - format Id must be 1");
    return false;
  }
  if (Wav->FormatSize != 16) {
    Serial.print("Invalid data - format section size must be 16.");
    return false;
  }
  if ((Wav->NumChannels != 1) & (Wav->NumChannels != 2)) {
    Serial.print("Invalid data - only mono or stereo permitted.");
    return false;
  }
  if (Wav->SampleRate > 48000) {
    Serial.print("Invalid data - Sample rate cannot be greater than 48000");
    return false;
  }
  if ((Wav->BitsPerSample != 8) & (Wav->BitsPerSample != 16)) {
    Serial.print("Invalid data - Only 8 or 16 bits per sample permitted.");
    return false;
  }
  return true;
}

void DumpWAVHeader(WavHeader_Struct* Wav) {
  if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0) {
    Serial.print("Not a RIFF format file - ");
    PrintData(Wav->RIFFSectionID, 4);
    return;
  }
  if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0) {
    Serial.print("Not a WAVE file - ");
    PrintData(Wav->RiffFormat, 4);
    return;
  }
  if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0) {
    Serial.print("fmt ID not present - ");
    PrintData(Wav->FormatSectionID, 3);
    return;
  }
  if (memcmp(Wav->DataSectionID, "data", 4) != 0) {
    Serial.print("data ID not present - ");
    PrintData(Wav->DataSectionID, 4);
    return;
  }
  // All looks good, dump the data
  Serial.print("Total size :");
  Serial.println(Wav->Size);
  Serial.print("Format section size :");
  Serial.println(Wav->FormatSize);
  Serial.print("Wave format :");
  Serial.println(Wav->FormatID);
  Serial.print("Channels :");
  Serial.println(Wav->NumChannels);
  Serial.print("Sample Rate :");
  Serial.println(Wav->SampleRate);
  Serial.print("Byte Rate :");
  Serial.println(Wav->ByteRate);
  Serial.print("Block Align :");
  Serial.println(Wav->BlockAlign);
  Serial.print("Bits Per Sample :");
  Serial.println(Wav->BitsPerSample);
  Serial.print("Data Size :");
  Serial.println(Wav->DataSize);
}

void PrintData(const char* Data, uint8_t NumBytes) {
  for (uint8_t i = 0; i < NumBytes; i++)
    Serial.print(Data[i]);
  Serial.println();
}
```
　最後まで見ていただきありがとうございました。