見出し画像

プログラムと電子工作・楽曲演奏(1)内蔵スピーカー

M5StickC Plus の内蔵スピーカーを使用して、楽曲を演奏します。M5StickC Plus で音を出す方法はいくつかありますが、五線譜の音符を一つひとつ鳴らして曲を奏でます。

曲は「どんぐりころころ」と「チューリップ」です。譜面があれば演奏できると思いますが、音量が変えられないので無味乾燥な演奏になります。また内蔵スピーカーの音量はそんなに大きくないです。

この方式は、チャイムやブザーなどの用途向き、楽曲を演奏するには不向きです。


目標

  • M5StickC Plus の内蔵スピーカーで音を鳴らします。

  • 「どんぐりころころ」と「チューリップ」を演奏します。

部品・機材

使用する部品は次のとおりです。「Hello, World!」と全く同じです。

電子部品

  • M5StickC Plus 1台

開発用機材

  • PC(Windows10 または 11)、開発環境 Arduino-IDE 導入ずみ

  • USB-A・USB-C ケーブル

開発手順

  1. PC と M5StickC Plus を USBケーブルで接続する。

  2. Arduino-IDE でスケッチ beep.ino を開く。

  3. 検証・コンパイルする。

  4. M5StickC Plus に書き込む。

  5. 上ボタン(押しボタンA)を押して、演奏を開始する。

  6. 曲が流れることを確認する。

スケッチ

beep.ino

#include <M5StickCPlus.h>

#define C3 130.813  /*ド*/
#define D3 146.832  /*レ*/
#define E3 164.814  /*ミ*/
#define F3 174.614  /*ファ*/
#define G3 195.998  /*ソ*/
#define A3 220.000  /*ラ*/
#define B3 246.942  /*シ*/

#define C4 261.626  /*ド*/
#define D4 293.665  /*レ*/
#define E4 329.628  /*ミ*/
#define F4 349.228  /*ファ*/
#define G4 391.995  /*ソ*/
#define A4 440.000  /*ラ*/
#define B4 493.883  /*シ*/

#define C5 523.251  /*ド*/
#define D5 587.330  /*レ*/
#define E5 659.255  /*ミ*/
#define F5 698.456  /*ファ*/
#define G5 783.991  /*ソ*/
#define A5 880.000  /*ラ*/
#define B5 987.767  /*シ*/

#define C6 1046.502 /*ド*/

#define SS 0.0      /*休符*/

#define T8  200  /*8分休符*/
#define T4  400  /*4分休符*/
#define T2  800  /*2分休符*/
#define T15 1200 /*2.5分休符*/
#define T1  1600 /*全休符*/

typedef struct {
    float freq;
    uint16_t period;
} tone_t;

const tone_t donguri[] = {
    {G4, T4}, {E4, T8}, {E4, T8}, {F4, T8}, {E4, T8}, {D4, T8}, {C4, T8},
    {G4, T4}, {E4, T8}, {E4, T8}, {D4, T4}, {SS, T4},
    {E4, T8}, {E4, T8}, {G4, T8}, {G4, T8}, {A4, T8}, {A4, T8}, {SS, T8}, {A4, T8},
    {C5, T4}, {E4, T8}, {E4, T8}, {G4, T4}, {SS, T4},
    {G4, T8}, {G4, T8}, {E4, T8}, {E4, T8}, {F4, T8}, {E4, T8}, {D4, T8}, {C4, T8},
    {G4, T4}, {E4, T8}, {E4, T8}, {D4, T4}, {SS, T4},
    {G4, T4}, {E4, T4}, {A4, T4}, {G4, T8}, {G4, T8},
    {A4, T8}, {A4, T8}, {B4, T8}, {B4, T8}, {C5, T4}, {SS, T4}
};

const tone_t tulip[] = {
    {C4, T4}, {D4, T4}, {E4, T2},
    {C4, T4}, {D4, T4}, {E4, T2},
    {G4, T4}, {E4, T4}, {D4, T4}, {C4, T4},
    {D4, T4}, {E4, T4}, {D4, T2},
    {C4, T4}, {D4, T4}, {E4, T2},
    {C4, T4}, {D4, T4}, {E4, T2},
    {G4, T4}, {E4, T4}, {D4, T4}, {C4, T4},
    {D4, T4}, {E4, T4}, {C4, T2},
    {G4, T4}, {G4, T4}, {E4, T4}, {G4, T4},
    {A4, T4}, {A4, T4}, {G4, T2},
    {E4, T4}, {E4, T4}, {D4, T4}, {D4, T4},
    {C4, T15}, {SS, T4}
};

//------------------------------------------------------------------------------
//  setup()
void setup() {
    //  電源ON時に 1回だけ実行する処理をここに書く。
    M5.begin();               /*M5を初期化する*/
    M5.Axp.ScreenBreath(20);  /*画面の輝度を少し下げる*/
    M5.Lcd.setTextSize(2);    /*文字サイズはちょっと小さめ*/
    M5.Lcd.setRotation(3);    /*上スイッチが左になる向き*/
    M5.Lcd.println("beep");

    Serial.begin(115200);   /*デバッグ用のシリアル通信を初期化する*/

    M5.Lcd.println("BtnA to play");
    M5.Beep.begin();
}
//------------------------------------------------------------------------------
//  loop()
void loop() {
    //  自動的に繰り返し実行する処理をここに書く。
    M5.update();

    if (M5.BtnA.wasPressed()) {
        //  「どんぐりころころ」を演奏する。
        M5.Lcd.println("donguri");
        playSound(donguri, sizeof(donguri)/sizeof(donguri[0]));
        delay(1000);

        //  「チューリップ」を演奏する。
        M5.Lcd.println("tulip");
        playSound(tulip, sizeof(tulip)/sizeof(tulip[0]));
    }
    delay(1);
}
//------------------------------------------------------------------------------
//  playSound
//  楽曲データを演奏する。
//  ■  演奏が終了するまでこの関数はブロックされる。
//      in: const uint16_t* tone 音程データ
//          uint32_t length 音程データ数
void playSound(const tone_t* tone, uint32_t length)
{
    Serial.println("*** playSound()");
    for (int i = 0; i < length; i++) {
        Serial.printf("  freq: %f, period: %d\n", tone[i].freq, tone[i].period);

        //  指定した周波数の音を出す。
        if (0 < tone[i].freq) {
            M5.Beep.tone(tone[i].freq);  /*set duty*/
        }
        else {
            M5.Beep.mute();
        }
        //  指定した時間待つ。
        delayMicroseconds((uint32_t)(tone[i].period) * 1000);
    }
    M5.Beep.mute();
}

音符に対応する周波数と継続時間を求めて、周波数は M5.Beep.tone() で鳴らし、継続時間は delayMicroseconds() で計っています。休符は M5.Beep.mute() で音を止めます。

結果

上ボタン(押しボタンA)を押すと、曲が流れます。

このスケッチでは、演奏中は他の処理ができないので、あまり実用性がないです。

写真1 beep.ino の実行結果

参考

  • Beep オブジェクトは次のヘッダファイルで、SPEAKER Beep; とアサインされているので、M5.Beep.XXX(...); のように使用します。
    C:\Users\<ユーザ>\Documents\Arduino\libraries\M5StickCPlus\src\M5StickCPlus.h

  • スケッチで使用した関数 M5.Beep.tone() や M5.Beep.mute() は、次のファイルで定義されています。
    C:\Users\<ユーザ>\Documents\Arduino\libraries\M5StickCPlus\src\utility\Speaker.h
    C:\Users\<ユーザ>\Documents\Arduino\libraries\M5StickCPlus\src\utility\Speaker.cpp

コードを解釈して整理すると、下記のようになると思います。M5.Beep.setVolume() とM5.Beep.playMusic() はなんか変です。うまく動作しないように思います。

/**
 *
 *  o  Beep関数一覧:
 *      void M5.Beep.begin();
 *      //  使用準備、setup()内で 1回実行する。
 *      void M5.Beep.end();
 *      //  使用終了、これ以上 Beepを使用しないときに実行する。通常は使用することはない。
 *      void M5.Beep.tone(uint16_t frequency);
 *      //  指定された周波数を鳴らす。
 *      //  音を止めるには M5.Beep.mute() を実行する必要がある。
 *      //      in: uint16_t frequency 周波数(Hz)
 *      //          uint16_t duration 時間(ms)
 *      void M5.Beep.tone(uint16_t frequency, uint32_t duration);
 *      //  指定された周波数を指定された時間だけ鳴らす。
 *      //  ただし loop()内で M5.Beep.update() を実行する必要がある。実行しないと鳴りっぱなしになる。
 *      //      in: uint16_t frequency 周波数(Hz)
 *      //          uint16_t duration 時間(ms)
 *      void M5.Beep.beep();
 *      //  あらかじめ設定された周波数をあらかじめ設定された時間だけ鳴らす。
 *      //  周波数と時間は M5.Beep.setBeep() で設定する。デフォルトは、400Hz、100ms。
 *      //  ただし loop()内で M5.Beep.update() を実行する必要がある。実行しないと鳴りっぱなしになる。
 *      void M5.Beep.setBeep(uint16_t frequency, uint16_t duration);
 *      //  周波数と時間を設定する。
 *      //      in: uint16_t frequency 周波数(Hz)
 *      //          uint16_t duration 時間(ms)
 *      void M5.Beep.mute();
 *      //  音を休止する。
 *      void M5.Beep.update();
 *      //  あらかじめ設定された時間が経過していたら、音を休止する。
 *      void M5.Beep.setVolume(uint8_t volume);
 *      //  音量を設定する。
 *      //  ■  M5.Beep.playMusic() に対してのみ作用する。
 *      //      in: uint8_t volume 音量、最大 11、最小 0、デフォルト 3
 *      void M5.Beep.playMusic(const uint8_t* music_data, uint16_t sample_rate);
 *      //  楽曲データを演奏する。
 *      //  ■  演奏が終了するまでこの関数はブロックされる。
 *      //      in: const uint8_t* music_data 8ビット符号なし整数の音の振幅データ
 *      //          uint16_t sample_rate 楽曲データのサンプリング周波数
 *      //  ※  M5StickC Plus の SPEAKER_PIN には DA変換機能がないので、この関数は動作しない
 *      //      (内部で dacWrite() を呼んでいる)。
 *      //  ※  _volumeや music_dataの扱いが明らかに変。前者は ZeroDivisionエラーが発生する。
 *      //      後者は、0x00の値があるとデータの最後として扱ってしまう。
 *
 */

ライセンス

このページのソースコードは、複製・改変・配布が自由です。営利目的にも使用してかまいませんが、何ら責任を負いません。


この記事が気に入ったらサポートをしてみませんか?