カエルの合唱を鳴らしてみる

PWMで波形を作る

カラーLEDの調光やサーボモーターを動かすときにはPWM(パルス幅変調)を用いました。
この場合は下図のようにデューティ比を変化させていました。
ここでは、デューティ比を50%に固定し、パルス間隔、つまり周期を変化させ(周波数を変化させることと同義)音階を作ります。


音階について



ラの音は440Hzで上のラは880Hz、そしてさらにその上のラは1760Hzとなります。つまり、1オクターブは2倍の周波数のことです。その間の音階は半音で12段階になっています。どの様な間隔かと言うと、2n/12です。
1オクターブの比を求める
#include "mbed.h"

Serial pc(USBTX, USBRX); // tx, rx

int main()
{
  pc.baud(115200);
  for(int n=0; n<=12; n++)
  {
    float scale = powf(2.0, (float)n/12.0f);
    pc.printf("%2d %8.5f\n", n, scale);
  }
}


実行結果
 0  1.00000
 1  1.05946
 2  1.12246
 3  1.18921
 4  1.25992
 5  1.33484
 6  1.41421
 7  1.49831
 8  1.58740
 9  1.68179
10  1.78180
11  1.88775
12  2.00000
右側の数字に440を掛ければ各々の周波数になります。もしイ長調だとすると。 ドは440、レは493.88、ミは554.37、ファは587.33Hz・・・・となります。
波形を作る時には基本的には周波数ではなく、上記で説明したようにPWMで作りますから周期の時間が必要になります。
周期をT(秒)、周波数をf(Hz)とすると、その関係は逆数ですから、以下の式が成り立ちます。
T = 1 / f
もし、440Hzzだとしたら、周期は2.2727msとなります。音階を使うプログラムは、 これらの周期、あるいは周波数の表を持つ必要があります。

カエルの合唱

楽譜

圧電スピーカーをp21とグラウンドの間に取り付けます。
Cはド、Dはレです。
Bは楽譜には無いので、手を抜いて休符に流用しています。
数字の2は4分音符。
数字の1は8分音符のように音の長さを示します。

メインプログラム(main.cpp)
#include "mbed.h"
#include "Sound.h"

int main() {
    Sound sound(p21);
    char kaeru[] =  
"C2D2E2F2E2D2C2B2E2F2G2A2G2F2E2B2C1B3C1B3C1B3C1B3C1C1D1D1E1E1F1F1E1B1D1B1C1B3";
    char snd;
    int Length=strlen(kaeru);
    while (true)
    {
        for (int i = 0; i< Length; i++)
        {
            snd = kaeru[i];
            if (('A' <= snd) && (snd <= 'G'))
            {
                if (snd == 'B') sound.stop(0.01);
                else            sound.Tone(snd-'A');
            }
            else if ('1' <= snd && snd <= '9')
            { 
                double w = (snd - '0') * 0.2;
                wait(w);
            }
        }
        sound.stop(0.01);
    }
}
 1 mbed用ヘッダーファイル
 2 この音程用ヘッダーファイル
 3 
 4 
 5 音はPWMを用いるのでp21に割り当てる
 6 音程と音の長さデータ
 7 
 8 音程データを一つ入れる変数
 9 音程データの文字列の長さを求める
10 永久ループ
11 
12 音程データの最初から最後までスキャンする
13 
14 一文字取り出す
15 そのデータが音程だったら
16 
17 それが'シ'だったら便宜的に休符にしてしまう
18 そうでなければ、その音程の音を出す
19 
20 1~9の長さデータだったら
21 
22 伸ばす時間を計算する
23 音をその時間鳴らす
24 
25 
26 音を止める
27 
28 


ヘッダーファイルは基本的にクラスの構造を記述したものです。ここでは、Soundというクラスのインスタンス変数とメソッドを宣言しています。クラスの中身のインスタンス変数は基本的にはプライベートにし、また、メソッドはパブリックで宣言します。メソッドはプロトタイプ宣言のみで、本体は後で記述するcppファイルの中で書きます。

ヘッダーファイル(Sound.h)
#include "mbed.h"

class Sound
{
private:
    PwmOut* pwm;
    static int period[];
    int tone;
    double dura;
public:
    Sound(PinName pin);
    void Tone(int n);
    void Tone(int n, double dura);
    void stop(double w);
};
 1 mbed用ヘッダーファイル
 2 
 3 クラスの宣言
 4 
 5 プライベートなインスタンス変数の宣言
 6 PWMのクラスオブジェクトのアドレスを入れる変数
 7 ドレミファソラシドの周期データ、クラス変数にしている
 8 音程データ
 9 音の長さ
10 
11 コンストラクタ
12 音を出す
13 duraの長さで音を出す
14 wミリ秒間音を止める
15 セミコロンを忘れずに


上で宣言したクラスの本体の記述です。

C++クラスの本体(Sound.cpp)
#include "Sound.h"
int Sound::period[]=
    {4545,4050,3817,3401,3034,2864,2551,2273,2025,
     1911,1703,1517,1432,1276,1136,1012,
      956, 851, 758, 716, 638, 568, 506, 478};

Sound::Sound(PinName pin)
{
    pwm = new PwmOut(pin);
}

void Sound::Tone(int n)
{
    int p = period[n+7];
    int half = p/2;
    pwm->period_us(p);
    pwm->pulsewidth_us(half);
}
void Sound::Tone(int n, double dura)
{
    Tone(n);
    wait(dura);
}
void Sound::stop(double w)
{
    pwm->pulsewidth_us(0);
    wait(w);
} 
 1 このクラスを宣言しているヘッダーファイルを読み込む
 2 ドレミファソラシドの周期データ
 3 どのオブジェクトでも同じなので、静的変数となっている
 4 
 5 
 6 
 7 コンストラクタ
 8 pinをPWM出力とするオブジェクトを作り、その先頭アドレスを
 9 pwmに格納する。
10 
11 
12 Toneメソッド
13 
14 周期データを取り出す
15 半周期を計算する
16 PWM周期を設定する
17 デューティサイクル50%となるように設定
18 
19 Toneメソッド
20 
21 音を出して
22 duraミリ秒時間待ちする
23 
24 stopメソッド
25 
26 デューティサイクルを0%として波形を出さないように設定
27 wミリ秒時間待ちする
28 

ダウンロード
mbed.org用
Code::Blocks用