lbed

2013/12/14からのアクセス回数 1459

Interface 2013/11から連載が始まった「実験で入門!音声合成のメカニズム」をLM4F120 LaunchPadで試してみます。

ここでは、LM4F120 LaunchPadを使って実際に音声合成をし、理論的な部分は、 sage/音声合成のメカニズム in Sage で説明していきます。こちらも参考にしてください。

DACモジュールの作成

LM4F120 Lauchpadには、DAC(デジタルからアナログへの変換)モジュールが付属していませんので、 SPIインタフェースを持つ、12-bit DAC(デジタル・アナログ変換) MCP4922 を使用します。

MCP4922の使い方は、arduino/DACを試すを参照してください。

MCP4922クラスを作成

MCP4922にアクセスするために、SPIクラスのサブクラスとしてMCP4922を作成します。

#include "MCP4922.h"
#include "lbed.h"

#define     HIGHT     (1)
#define LOW          (0)

MCP4922::MCP4922(PinName mosi, PinName miso, PinName sclk, PinName cs, PinName ldac)
     : SPI(mosi, miso, sclk), _ldac(ldac), _cs(cs)
{
}

void MCP4922::write(int value) {
     _ldac = HIGHT;
     _cs = LOW;
     SPI::write((value >> 8) | 0x30);
     SPI::write(value & 0xFF);
     _cs = HIGHT;
     _ldac = LOW;
}

void MCP4922::write(int valueA, int valueB) {
     _ldac = HIGHT;
     _cs = LOW;
     SPI::write((valueA >> 8) | 0x70);
     SPI::write(valueA & 0xFF);
     _cs = HIGHT;
     _ldac = LOW;
     wait_us(1);
     _ldac = HIGHT;
     _cs = LOW;
     SPI::write((valueB >> 8) | 0xF0);
     SPI::write(valueB & 0xFF);
     _cs = HIGHT;
     _ldac = LOW;
}

void MCP4922::frequency(int hz) {
     SPI::frequency(hz);
}

テストプログラムで動作を確認

SPIの通信には、SSI1を使用することにして、MOSI(PD3), MISO(PD2), SCLK(PD0)とつなぎます。 CS(PE1), LDAC(PE2)としてJ3列のレジスタで揃えました。

LaunchPad-DAC-setting.png

TestMCP4922.cppは、以下の通りです。

#include "lbed.h"
#include "MCP4922.h"

int main(void) {
     // PD_2は使用していないので、未接続で実行
     MCP4922     mcp4922(PD_3, PD_2, PD_0, PE_1, PE_2);     // mosi, miso, sclk, cs, ldac
     // 16MHzにセット
     mcp4922.frequency(16000000);
     while(1) {
          for (int i=0; i < 4096; i+=4) {
               mcp4922.write(i);
          }
     }
}

こんなに簡単にノコギリ波の生成プログラムが出来上がります。

オシロスコープで生成された波形を見ると以下の様になります。

saw-wave.png

LM4F120 LaunchPadは、FPUが付いているので、sine波を計算するとどうなるか試してみました。

#include "lbed.h"
#include "MCP4922.h"
#include "math.h"
#define     PI     3.1415926
#define     SAMPLE 4096
#define     sin(x)     sinf(x)
 
int main(void) {
     // PD_2は使用していないので、未接続で実行
     MCP4922     mcp4922(PD_3, PD_2, PD_0, PE_1, PE_2);     // mosi, miso, sclk, cs, ldac
     // 16MHzにセット
     mcp4922.frequency(16000000);
     while(1) {
          for (int i=0; i < 4096; i+=4) {
               int sineValue = (int)(0xFFF*(1 + sin(2*PI*i/SAMPLE))/2);
               mcp4922.write(sineValue);
          }
     }
}

sine波の出力でも、39.39Hzで1024ポイント(1ポイント: 1/39.39/1024=0.025ms)を出力できています。 これなら音声合成で使用する8KHzサンプリング(1ポイント: 1/8000=0.125ms)データにも余裕で対応できます。

sine-wave.png

第1回の課題

Interface 2013/11号の第1回目の課題をLauchpadで実験してみましょう。

「あ」を合成するマイコン・プログラム

Synthesisという関数を作成して、フーリエ係数から「あ」を合成してみるというものです。 *1

母音の「あ」の合成には、以下のようなフーリエ関数を使っています。

$$ f\left( \frac{n}{8000} \right) = a_0 + \sum_{k=1}^K \left( a_k cos \frac{2 \pi k}{T_0} n + b_k sin \frac{2 \pi k}{T_0} n \right), n =0, 1, …, N-1 $$

synthesis関数

上記の式に合わせて、合成関数synthesisを作ってみます。 ほとんどInterfaceの記事と同じですが、vnの計算が少し違います。 *2

// データ:     元の音声とそのフーリエ係数
// 母音 /ア/,サンプリング周波数: 8 KHz
const int     N_DATA     = 65;
const short     snOrg[N_DATA] = {
          -56,     139,     439,     785,     1041,     1277,     1180,     934,     564,     133,
          -147,     -131,     -9,          343,     684,     860,     991,     926,     773,     634,
          501,     481,     695,     961,     1293,     1459,     1356,     1022,     613,     229,
          118,     206,     437,     698,     794,     680,     430,     68,          -192,     -325,
          -365,     -312,     -278,     -299,     -350,     -494,     -731,     -939,     -1077,     -1211,
          -1289,     -1382,     -1446,     -1511,     -1639,     -1645,     -1580,     -1425,     -1139,     -808,
          -608,     -420,     -362,     -288,     -68
};
// フーリエ係数
const int     HARMO_MAX = 15;
const float     an[HARMO_MAX + 1] = {
          20.61,     -376.22,     305.13,     257.95,     45.26,     -47.79,     -205.22,     -94.12,
          -8.32,     -19.58,          19.21,     -0.73,     -2.51,     7.29,     0.97,          -2.58
};
const float     bn[HARMO_MAX + 1] = {
          0.00,     949.63,          246.53,     181.33,     111.38,     38.06,     143.08,          -269.01,
          51.38,     -34.27,          19.66,     13.44,     5.81,     5.36,     -0.37,          3.62
};

void synthesis(const float an[], const float bn[], short vn[], int order, int nData) {
     const float     PI2 = 2.0*3.1415926;
     const float     PI2_T = PI2/(float)nData;

     for (int n = 0; n < nData; n++) {
          vn[n] = (short)an[0];
          for (int k = 1; k <= order; k++) {
               float kPi2T = k*PI2_T;
               vn[n] += (short)(an[k]*cos(kPi2T*n) + bn[k]*sin(kPi2T*n));
          }
     }
}

実際に「ア」を合成してみる

準備ができたので、実際に「ア」を合成してみます。

メインのプログラムは次のようになります。 *3

// 8KHzのタイミングでデータをDACに送るためにok変数とTickerを使用する
volatile bool     ok = false;
void getTiming() {
     ok = true;
}

#define     N_TO      (N_DATA - 1)
#define FS          (8000)
#define     COUNT     (5)
#define     DC          (2048)          // 12bit分は、4096でその半分の値を0とする

int main(void) {
     Serial pc(PA_1, PA_0);
     pc.baud(19200);
     Ticker timer;
     timer.attach(&getTiming, 1.0/FS);

     // PD_2は使用していないので、未接続で実行
     MCP4922     mcp4922(PD_3, PD_2, PD_0, PE_1, PE_2);     // mosi, miso, sclk, cs, ldac
     // 10MHzにセット
     mcp4922.frequency(16000000);

     short     sn[N_TO];
     short     vn[N_TO];
     int          order = 15;
     sn[0] = (snOrg[0] + snOrg[N_TO])/2;     // サンプリングデータの先頭と最後の平均をsn[0]の値とする
     // サンプリングデータをsnにセット
     for (int n = 1; n < N_TO; n++)
          sn[n] = snOrg[n];
     // フーリエ係数から「ア」を音声合成
     synthesis(an, bn, vn, order, N_TO);
     pc.println("Input Order:[1-9]");
     while(1) {
          if (pc.available()) {
               // フーリエ係数の次数を変更する
               char c = pc.read();
               order = c - '0';
               synthesis(an, bn, vn, order, N_TO);
          }
          for (int count = 0; count < COUNT; count++) {     // 5回繰り返す
               for (int n = 0; n < N_TO; n++) {
                    // 出力タイミングまで待つ
                    while(!ok) continue;
                    mcp4922.write(sn[n] + DC, vn[n] + DC);
                    ok = false;
               }
          }
     }
}

計算結果

オシロスコープで、次数5と次数9の波形を出力してみました。 意外だったのが、次数7から結構いい感じの出力波形が出ていたことです。

次数5の場合の出力波形(上がオリジナル、下が合成波形)です。

out_5order.png

次に次数9の波形です。

out_9order.png

秋月電子のLCDオシロスコープキット

子供たちでも波形を確認できるように 「秋月電子のLCDオシロスコープキット」 (4,700円)を組み立てました。

この値段で音声合成の波形がきちんと表示できています。

aki-LCD-scope.png

連載に合わせて更新していきます!

コメント

選択肢 投票
おもしろかった 0  
そうでもない 0  
わかりずらい 0  

皆様のご意見、ご希望をお待ちしております。


(Input image string)


*1 例題では、サンプリングの不連続をちょっと補正するために、サンプリングデータの最初と最後のデータの平均値を先頭の値にセットしています。
*2 オリジナルは、きっと学生さんが作ったプログラムなのでしょう。計算の方法もちょっと違和感を覚えます
*3 オリジナルよりもすっきりしています。

添付ファイル: fileaki-LCD-scope.png 278件 [詳細] fileout_9order.png 259件 [詳細] fileout_5order.png 267件 [詳細] fileLaunchPad-DAC-setting.png 285件 [詳細] filesaw-wave.png 286件 [詳細] filesine-wave.png 279件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-12-23 (月) 15:33:18 (1190d)
SmartDoc