2013/12/14からのアクセス回数 4517 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列のレジスタで揃えました。 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); } } } こんなに簡単にノコギリ波の生成プログラムが出来上がります。 オシロスコープで生成された波形を見ると以下の様になります。 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)データにも余裕で対応できます。 第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の場合の出力波形(上がオリジナル、下が合成波形)です。 次に次数9の波形です。 秋月電子のLCDオシロスコープキット †子供たちでも波形を確認できるように 「秋月電子のLCDオシロスコープキット」 (4,700円)を組み立てました。 この値段で音声合成の波形がきちんと表示できています。 連載に合わせて更新していきます! †コメント †皆様のご意見、ご希望をお待ちしております。 Tweet |