SSブログ

Arduinoとスピーカだけで音声を出す [Arduino]

ATmega328はフラッシュメモリーが32kBあるので、非圧縮 8bit 8kHz モノラルならプログラムに使う領域を除くと約4秒弱再生できるはずです。
短いように思いますが、ちょっとした効果音や音声くらいなら意外といけます。
探せば同様のプログラムはありそうですが練習も兼ねてちょっと作ってみました。
ATmega328搭載で16MHz駆動のArduinoで、8bit, モノラル, 4/8/16kHzのみの対応です。
hello4816.jpg
再生音声の素材は以下から。

効果音ラボ - フリー、商用無料、報告不用の効果音素材をダウンロード:
http://soundeffect-lab.info/

から、

日常セリフ(元気な女の子)|声素材|効果音ラボ:
http://soundeffect-lab.info/sound/voice/line-girl1.html

の「こんにちは」をいただきました。ありがとうございました。
これを、フリーのサウンド編集ソフト「Audacity」をつかって変換します。

Audacity:
http://www.audacityteam.org/

無音の部分があるので、波形のあるところだけトリミングして、左下にある「プロジェクトのサンプリング周波数(Hz)」を16000 / 8000 / 4000 のいずれかにします。(4000の場合は手入力)
「ファイル」→「オーディオの書き出し...」→「ファイルの種類:その他の非圧縮ファイル」→「ヘッダ:WAV (Microsoft)」→「エンコーディング:Unsigned 8-bit PCM」として「保存」します。ファイルの拡張子がaiffになりますが、中身はWAVファイルになっているようです。
これを先日の「PROGMEM作蔵さん」で変換します。変数名だけ適当に変えておきます。
それをヘッダファイルにして、以下のスケッチと同じフォルダに入れておきます。

・メインのスケッチ
// WAVファイル(8bit, 4/8/16kHz, monoral) を配列化したものを再生する。
#include <avr/pgmspace.h>       // PROGMEM使用時のお約束だが、実はなぜだか書かなくても動く
#include "hello16.h"            // 音声データ hello16[] =「こんにちは」8bit, 16kHz,  monoral
#include "hello8.h"             // 音声データ hello8[]  =「こんにちは」8bit,  8kHz,  monoral
#include "hello4.h"             // 音声データ hello4[]  =「こんにちは」8bit,  4kHz,  monoral

const uint8_t C16kHz[] = { 3,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4 };   // サンプリングレート調整用配列 (16kHz)
const uint8_t C8kHz[]  = { 7,8,8,8,8,7,8,8,8,8,7,8,8,8,8,8 };                                   // サンプリングレート調整用配列 (8kHz)
const uint8_t C4kHz[]  = { 15,16,16,15,16,16,15,16 };                                           // サンプリングレート調整用配列 (4kHz)

void setup() {
  pinMode( 9, OUTPUT);          // OC1A(PB1)
  pinMode(10, OUTPUT);          // OC1B(PB2)   逆位相波で出力を上げるより、同出力の2ピンをまとめることで電流を上げる
    // 8bit高速PWM(TOP=0xff), 分周なし, コンペアマッチでLow (256clock毎に割り込み)
  TCCR1A = B10100001;           // コンペアマッチでLow(COM1A1=1, COM1A0=0, COM1B1=1, COM1B0=0), タイマモード設定(一部)(WGM11=0, WGM10=1) 
  TCCR1B = B00001001;           // タイマモード設定(一部)(WGM13=0, WGM12=1), クロック分周の設定(CS12=0, CS11=0, CS10=1)
}

void loop() {
  playWav( hello16 );
  delay(1000);
  playWav( hello8 );
  delay(1000);
  playWav( hello4 );
  delay(2000);
}

void playWav( const uint8_t d[] ) { 
  uint8_t  c = 0, cyc = 1;                      // 次の1byte読み込むまでのサイクル数カウンタ(cyc)とサイクル数調整用配列のポインタ(c)
  uint16_t i, rate, len;
  rate = pgm_read_word_near(&d[ 0x18 ]);        // サンプリングレート (本来は32bitのデータだが下位の16bitで十分)
  len  = pgm_read_word_near(&d[ 0x28 ]);        // データサイズ       (本来は32bitのデータだがATmega328では32kB以下なので十分)
  for( i=0; i<128;) {  while(!(TIFR1 & _BV(TOV1)));  TIFR1|=_BV(TOV1);  OCR1A=OCR1B=i++;  }     // ポップノイズ軽減
  for( i = 0x2c; i < len;) {
    if( --cyc == 0 ) {                          // サイクル(16usec)のカウントダウンが終わったら次のデータへ
      OCR1A = OCR1B = pgm_read_byte_near( &d[ i++ ] ); 
      cyc = ( rate == 16000 ) ? C16kHz[ c++ & B11111 ] : ( rate == 8000 ) ? C8kHz[ c++ & B1111 ] : C4kHz[ c++ & B111 ];
    }
    while( !(TIFR1 & _BV(TOV1)) );              // タイマーがオーバーフローするのを待つ (256cycle @ 16MHz = 16usec毎)
    TIFR1 |= _BV(TOV1);                         // Timer/Counter1 Overflow Flag をクリア (論理1を書くことによってもTOV1は解除(0)できる)
  }
  for( i=127; i;  ) {  while(!(TIFR1 & _BV(TOV1)));  TIFR1|=_BV(TOV1);  OCR1A=OCR1B=--i;  }     // ポップノイズ軽減
}

・音声のヘッダファイル(.h) (ファイルを変換後、変数bindata[]のところは適当に名前を付ける)
// file name : hello16k.aiff
// file size : 12043 bytes

const uint8_t hello16[] PROGMEM = {
  0x52,0x49,0x46,0x46,0x03,0x2f,0x00,0x00,0x57,0x41,0x56,0x45,0x66,0x6d,0x74,0x20,
  0x10,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x80,0x3e,0x00,0x00,0x80,0x3e,0x00,0x00,
  0x01,0x00,0x08,0x00,0x64,0x61,0x74,0x61,0xdf,0x2e,0x00,0x00,0x80,0x80,0x80,0x80,
 ~ 略 ~ 
  0x80,0x80,0x80,0x80,0x80,0x7f,0x7f,0x7f,0x7f,0x7f,0x80
};

16 / 8 / 4 kHzの聞き比べでは、16kHzがいいのはもちろんですが、8kHzでもやや高音のキレがないものの十分聞くに堪えられます。4kHzはもごもごした感じでやや厳しいです。
タグ:音声 wav
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。