M5Stack ATOM EchoにWaveファイルを埋め込んで再生する

概要

ATOM Echoにフリー素材のmp3音声を利用して、Waveファイルに変換して、スケッチに埋め込んで再生する手順を解説します。

音素材

効果音ラボさんのデータを利用させていただきました。音声はどんなものでも構いませんが、埋め込む関係でファイルサイズに上限があります。

ファイルサイズ

再生する音声ファイルは、1サンプリングあたり16ビット(2バイト)必要です。1秒間にどれだけサンプリングするかでサンプリング周波数を決定します。

今回は16,000を利用していますが8,000や他のサンプリング周波数でも問題ありません。周波数を増減することでファイルサイズが変わりますので、いろいろ変えながら試すのもいいと思います。

周波数1秒あたりの容量1MBで保存できる秒数備考
16,00032,00033音声での最低限?
22,05044,10024
32,00064,00016あまり使わない
44,10088,20012CD音源で利用
48,00096,00011映像などで使う

フラッシュサイズ

ATOMは標準だと1.3M程度のフラッシュしか使えませんので、16,000だと30秒程度しか埋め込むことができません。

コンパイルが終わると「最大1966080バイトのフラッシュメモリのうち、スケッチが280887バイト(14%)を使っています。」などの文字が表示されると思います。

ここの最大サイズは「ツール」から「Partition Scheme」を変更することで設定を変更することができます。ほぼ空のスケッチをコンパイルしたときの数値が以下です。

Partition Schemeフラッシュ利用済み未使用
初期値1,310,720280,8871,029,833
No OTA2,097,152280,8871,816,265
Minimal SPIFFS1,966,080280,8871,685,193

OTAを利用しない場合には「No OTA」を選択するとたくさんのファイルを利用できます。また、自分で設定ファイルを編集することで最大3Mまで拡張が可能です。

音声ファイルの変換

利用するファイルと、サンプリング周波数を決定したあとには音声ファイルを変換する必要があります。

細かい調整をおこないたい人には、上記のオープンソースの音声編集ソフトがおすすめです。

今回はもっとかんたんにブラウザで変換できるページを利用してみたいと思います。

上記のページを利用させていただきました。

変換したいファイルを開き、保存形式を「WAV」、詳細設定を開き「サンプリングレート」を利用するもの(16000など)、チャンネル数を「1」にして変換します。複数のファイルを同時に変換することも可能でした。

非常にかんたんに使えるのでおすすめです。

WAVファイルから埋め込み形式に変換

ここもいろいろ面倒なので、ブラウザで変換できるツールを私が作りました。

このブログのメニューにある「TOOLS」の中に「音声ファイル変換」がありますので、このページになります。

上記が変換ページです。音声ファイルを選択して、変数名を「データ名」に入れてから送信すると変換されます。

ボリュームを100%から変更することで大きさも同時に変更されます。ATOM Echoは大音量を再生するとスピーカーが壊れる事例が報告されていますので、必要最低限の音量まで調整することをおすすめします。

目安はまだ確定していませんが、最大56%ぐらいとの情報もあります。

スケッチ例(GitHub)

/*Press button to record,released button to playback*/
// https://github.com/m5stack/M5-ProductExampleCodes/blob/master/Core/Atom/AtomEcho/Arduino/Repeater/Repeater.ino

#include <driver/i2s.h>
#include <M5Atom.h>

#include "wav1.h"
#include "wav2.h"
#include "wav3.h"
#include "wav4.h"
#include "wav5.h"

const unsigned char *wavList[] = {wav1, wav2, wav3, wav4, wav5};
const size_t wavSize[] = {sizeof(wav1), sizeof(wav2), sizeof(wav3), sizeof(wav4), sizeof(wav5)};

#define CONFIG_I2S_BCK_PIN      19
#define CONFIG_I2S_LRCK_PIN     33
#define CONFIG_I2S_DATA_PIN     22
#define CONFIG_I2S_DATA_IN_PIN  23

#define SPEAKER_I2S_NUMBER      I2S_NUM_0

#define MODE_MIC                0
#define MODE_SPK                1

void InitI2SSpeakerOrMic(int mode)
{
  esp_err_t err = ESP_OK;

  i2s_driver_uninstall(SPEAKER_I2S_NUMBER);
  i2s_config_t i2s_config = {
    .mode                 = (i2s_mode_t)(I2S_MODE_MASTER),
    .sample_rate          = 16000,
    .bits_per_sample      = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format       = I2S_CHANNEL_FMT_ALL_RIGHT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags     = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count        = 6,
    .dma_buf_len          = 60,
    .use_apll             = false,
    .tx_desc_auto_clear   = true,
    .fixed_mclk           = 0
  };
  if (mode == MODE_MIC)
  {
    i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM);
  }
  else
  {
    i2s_config.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX);
  }

  err += i2s_driver_install(SPEAKER_I2S_NUMBER, &i2s_config, 0, NULL);

  i2s_pin_config_t tx_pin_config = {
    .bck_io_num           = CONFIG_I2S_BCK_PIN,
    .ws_io_num            = CONFIG_I2S_LRCK_PIN,
    .data_out_num         = CONFIG_I2S_DATA_PIN,
    .data_in_num          = CONFIG_I2S_DATA_IN_PIN,
  };
  err += i2s_set_pin(SPEAKER_I2S_NUMBER, &tx_pin_config);

  if (mode != MODE_MIC) {
    err += i2s_set_clk(SPEAKER_I2S_NUMBER, 16000, I2S_BITS_PER_SAMPLE_16BIT, I2S_CHANNEL_MONO);
  }

  i2s_zero_dma_buffer(SPEAKER_I2S_NUMBER);
}

void setup() {
  M5.begin(true, false, true);
  delay(50);
  Serial.println();
  M5.dis.drawpix(0, CRGB(128, 128, 0));
  InitI2SSpeakerOrMic(MODE_MIC);
  delay(2000);
}

void loop() {
  if (M5.Btn.isPressed())
  {
    size_t bytes_written;

    M5.dis.drawpix(0, CRGB(0, 128, 0));

    InitI2SSpeakerOrMic(MODE_SPK);

    // Random Play
    int wav = random(5);

    // Write Speaker
    i2s_write(SPEAKER_I2S_NUMBER, wavList[wav], wavSize[wav], &bytes_written, portMAX_DELAY);
    i2s_zero_dma_buffer(SPEAKER_I2S_NUMBER);

    // Set Mic Mode
    InitI2SSpeakerOrMic(MODE_MIC);

    M5.dis.drawpix(0, CRGB(128, 128, 0));
  }

  M5.update();
}

スケッチ例です。ランダムで5つの音声が順番に再生されます。音声自体はwav1.hからwav5.hに保存されています。

スケッチ自体はシンプルです。

まとめ

短い音声を再生する場合には、Waveファイルをソースに埋め込むのが楽だと思います。思ったよりクリアな音がでると思いますので、みなさん活用してみてください。

ちょっと長めの音声を埋め込みたい場合にはmp3にしたり、8ビットで保存したのを、再生時に16ビットにするなど他の方法を検証する必要がありそうです。

コメント

  1. Ao より:

    このサイトを参考にさせていただいたおかげで、Tiny LED Clipができました!とっても感謝しております。これからの投稿も楽しみにしております 🙂
    ・ちんまりLEDクリップ:Tiny LED clip
    https://protopedia.net/prototype/3264

    • たなかまさゆき より:

      おー、きれいにまとまってますね!
      音楽再生はArduinoライブラリのバージョンが変わって、いろいろ変更がありそうなので確認してみたいと思います
      音声系の開発であれば情報は少ないのですがM5Unifiedがおすすめです

  2. Aoya-Uta より:

    返信ありがとうございます!
    確かにArduinoのバージョンが変わっていて、プログラムもそれに合わせて修正した気がしましたw

    M5Unified、各機種別に整理、ご苦労様です。
    音声制御は、下記のページで会話している内容ですかね。音声と画像を非同期実行できるように見えます。
    https://togetter.com/li/1909993
    https://twitter.com/m5stack/status/1494284839515979784