Arduino Dueでマスターする音声認識と音声合成
必要な部品と材料
| Arduino Due | ×1 |
| SparkFun エレクトレットマイク ブレークアウト | ×1 |
| SparkFun モノオーディオアンプ ブレークアウト | ×1 |
| スピーカー: 0.25W, 8Ω | ×1 |
| ブレッドボード(汎用) | ×1 |
| 5mm赤色LED | ×3 |
| 抵抗 330Ω | ×3 |
| ジャンパーワイヤー(汎用) | ×1 |
必要な工具と機器
- はんだごて(汎用)
アプリとオンラインサービス
- BitVoicer Server 1.0
このプロジェクトについて
前回のプロジェクトでは、ArduinoとBitVoicer Serverを使っていくつかのLEDを制御する方法を紹介しました。今回はさらに一歩進めて、Arduino Dueの内蔵DAC(デジタル-アナログ変換器)を使用して音声合成も行います。Arduino Dueをお持ちでない場合は他のArduinoボードでも可能ですが、外部DACとそれを操作する追加コードが必要になります(BVSSpeakerライブラリでは対応できません)。
以下の動画では、Arduinoが短い曲を再生し、LEDがピアノの鍵盤のように点滅する様子が見られます。私のピアノスキルはお察しください(笑)。LEDは実際のド、レ、ミの鍵盤と同じ順序・タイミングで点滅するので、ピアノがあればLEDに合わせて同じ曲を演奏できます。これはかつて存在した老舗小売店(Mappin)のジングルです。
音声コマンドからLED動作と音声合成への変換フロー
- SparkFunエレクトレットマイクブレークアウトで音声波を取得・増幅
- 増幅信号をArduinoのADC(アナログ-デジタル変換器)でデジタル化・バッファリング
- 音声サンプルをシリアルポート経由でBitVoicer Serverにストリーミング
- BitVoicer Serverで音声ストリームを処理し、音声を認識
- 認識された音声を事前定義コマンドにマッピングし、Arduinoに送信。音声合成コマンドの場合、BitVoicer Serverが音声ストリームを準備してArduinoに送信
- Arduinoがコマンドを識別し適切なアクション実行。音声ストリーム受信時はBVSSpeakerクラスにキューイングし、DueのDACとDMAで再生
- SparkFunモノオーディオアンプでDAC信号を増幅し、8Ωスピーカーを駆動
材料リストと概算コスト
- Arduino Due: 約50ドル
- SparkFunエレクトレットマイクブレークアウト: 7.95ドル
- SparkFunモノオーディオアンプブレークアウト: 7.95ドル
- BitVoicer Server 1.0: 9.90ドル
- 8Ωスピーカー: 約2ドル
- ブレッドボード: 約10ドル
- LED ×3: 約1ドル
- 330Ω抵抗 ×3: 約0.75ドル
- ジャンパーワイヤー: 約0.50ドル
ステップ1: 配線
以下の写真のようにArduinoとブレッドボードに部品を配線します。スピーカーは振動が大きいため、下に小さなゴムを敷かないと音質が大幅に低下します。




前回プロジェクトからの小さく重要な違い:ほとんどのArduinoは5V動作ですが、Dueは3.3V動作です。SparkFunエレクトレットブレークアウトは3.3Vでより良い結果が得られたため、5V Arduinoボードを使用する場合は3.3VピンとAREFピンの間にジャンパーを追加することを推奨します。Dueはすでに3.3Vアナログリファレンスを使用しているため、AREFピンへのジャンパーは不要です。実際、DueのAREFピンは抵抗ブリッジ経由でマイコンに接続されており、AREFを使用するには基板上の抵抗BR1を取り外す必要があります。
ステップ2: Arduinoへのコードアップロード
以下のコードをArduinoにアップロードします。便宜上、Arduinoスケッチはこの投稿下部の添付ファイルセクションにもあります。アップロード前に、BitVoicer ServerライブラリをArduino IDEに正しくインストールしてください(.zipライブラリのインポート)。
Arduinoスケッチ: BVS_Demo2.ino
このスケッチは7つの主要部分で構成されます:
- ライブラリ参照と変数宣言: BVSP、BVSMic、BVSSpeaker、DACライブラリを参照。これらはBitSophia提供で、BitVoicer Serverインストールフォルダにあります。DACライブラリはBVSSpeaker参照時に自動インクルード。BVSPクラスでBitVoicer Serverと通信、BVSMicクラスで音声サンプル取得・蓄積、BVSSpeakerクラスでDueのDACを使用した音声再生を行います。
- setup()関数: ピンモードと初期状態設定、シリアル通信初期化、BVSP/BVSMic/BVSSpeakerクラス初期化。BVSPクラスの
frameReceived,modeChanged,streamReceivedイベントのハンドラー(関数ポインター)を設定。 - loop()関数: 5つの重要アクションを実行:
- サーバーにステータス情報要求(
keepAlive()) - サーバーからのデータ受信チェックと処理(
receive()) - 音声ストリームの録音と送信制御(
isSREAvailable(),startRecording(),stopRecording(),sendStream()) - BVSSpeakerクラスにキューイングされた音声サンプル再生(
play()) playNextLEDNote()呼び出しで、playLEDNotesコマンド受信後のLED点滅制御
- サーバーにステータス情報要求(
- BVSP_frameReceived関数:
receive()が完全なフレーム受信を検出すると呼び出される。BitVoicer Serverから送信されたコマンドを実行。LED制御コマンドは2バイト(第1バイト: ピン番号、第2バイト: ピン値)。analogWrite()でピン値設定。playLEDNotesコマンド(バイト型、値255)受信時はフラグを立て、現在時刻を記録。この時刻をplayNextLEDNote関数で楽曲とLEDの同期に使用。 - BVSP_modeChanged関数:
receive()がアウトバウンド方向(サーバー→Arduino)のモード変更を検出すると呼び出される。BitVoicer Serverはフレームデータまたは音声ストリームをArduinoに送信可能。通信モード切り替え前にシグナルを送信し、BVSPクラスが検知してmodeChangedイベント発火。ストリームモードからフレームモードに戻った場合、音声終了と判断しBVSSpeakerクラスに再生停止指示。 - BVSP_streamReceived関数:
receive()が音声サンプル受信を検出すると呼び出される。サンプルを取得しBVSSpeakerクラスにキューイング、play()関数で再生。 - playNextLEDNote関数:
BVSP_frameReceivedでplayLEDNotesコマンド検出時のみ実行。BitVoicer Serverから送信される音声に合わせてLEDを制御・同期。正確なタイミング把握には Sonic Visualiser を使用(無料)。音声波形を見てピアノ鍵が押される瞬間を特定、タイムラインからミリ秒単位のタイミングを取得。少々強引な手法だが効果的。音声ストリームを解析して対応LEDを点灯させる方法も理論上可能だが、私の手には負えない。
ステップ3: BitVoicer Serverソリューションオブジェクトのインポート
BitVoicer ServerをArduino連携用に設定します。主要ソリューションオブジェクトは4つ:Locations(場所)、Devices(デバイス)、BinaryData(バイナリデータ)、Voice Schemas(音声スキーマ)。
- Locations: デバイス設置場所を表す。ここでは「Home」を作成。
- Devices: BitVoicer Serverクライアント。Mixedデバイスとして「ArduinoDUE」を作成し通信設定を入力。重要: Arduino Dueには音声サンプル蓄積用のメモリが少量しかないため、帯域幅を制限しないと巨大バッファが必要になる。バッファオーバーフロー対策で、通信設定のData Rateを8000サンプル/秒に制限。
- BinaryData: BitVoicer Serverがクライアントデバイスに送信可能なコマンドタイプ。バイト配列で、コマンドにリンク可能。音声認識時に対象デバイスに送信される。ピン値ごとに1つずつBinaryDataオブジェクトを作成(ArduinoDUEGreenLedOn, ArduinoDUEGreenLedOff等)。合計18個作成したため、以下の
VoiceSchema.sofファイルからインポート推奨。 - Voice Schemas: すべてを統合。認識すべき文と実行コマンドを定義。1文に複数コマンドを順序付きで定義可能、コマンド間遅延も設定可能。動画のアクションシーケンスはこれで実現。
音声スキーマの一例「play a little song」には2つのコマンドを含む:
- 次のコマンドが音声ストリームであることを示すバイトを送信。ArduinoはLED演奏を開始しながら音声受信待機
- 私が自分で録音したピアノジングルを音声ソースとして設定。BitVoicer Serverは8-bitモノラルPCM(8000サンプル/秒)のみサポート。音声ファイル変換には オンライン変換ツール 推奨
以下のファイルから本プロジェクトで使用したすべてのソリューションオブジェクトをインポート可能(ソリューションオブジェクトのインポート参照):
- Device.sof(Dueデバイス定義)
- VoiceSchema.sof(音声スキーマとコマンド)
ステップ4: 結び
以上で完成です!すべてを起動すれば、動画と同じことができます。
前回同様、BitVoicer Server ManagerでArduinoデバイスを有効化して音声認識を開始。有効化されると、Arduinoが利用可能な音声認識エンジンを検出し、BitVoicer Serverへ音声ストリーミングを開始。今回はBitVoicer ServerからArduinoへ音声ストリームが送信される際、ArduinoのRX LEDがかなり激しく点滅するのがわかります。
次回プロジェクトではさらに野心的に、1台のArduinoにWiFi通信を追加し、他の2台のArduinoをまとめて音声制御する計画です。何かゲームのような仕組みを考えています。アイデア募集中!
コード
Arduino Sketch (Arduino)
#include <BVSP.h>
#include <BVSMic.h>
#include <BVSSpeaker.h>
#include <DAC.h>
// 音声入力ピン
#define BVSM_AUDIO_INPUT 7
// LEDピン
#define RED_LED_PIN 6
#define YELLOW_LED_PIN 9
#define GREEN_LED_PIN 10
// BVSP.begin パラメータ用定数
const unsigned long STATUS_REQUEST_TIMEOUT = 3000;
const unsigned long STATUS_REQUEST_INTERVAL = 4000;
// バッファサイズ
const int MIC_BUFFER_SIZE = 64;
const int SPEAKER_BUFFER_SIZE = 128;
const int RECEIVE_BUFFER_SIZE = 2;
// グローバルインスタンス
BVSP bvsp = BVSP();
BVSMic bvsm = BVSMic();
BVSSpeaker bvss = BVSSpeaker();
// バッファ
byte micBuffer[MIC_BUFFER_SIZE];
byte speakerBuffer[SPEAKER_BUFFER_SIZE];
byte receiveBuffer[RECEIVE_BUFFER_SIZE]; // Byte 0 = ピン番号, Byte 1 = ピン値
// LEDノート再生制御用
bool playLEDNotes = false;
unsigned int playStartTime = 0;
void setup() {
pinMode(RED_LED_PIN, OUTPUT);
pinMode(YELLOW_LED_PIN, OUTPUT);
pinMode(GREEN_LED_PIN, OUTPUT);
digitalWrite(RED_LED_PIN, LOW);
digitalWrite(YELLOW_LED_PIN, LOW);
digitalWrite(GREEN_LED_PIN, LOW);
Serial.begin(115200);
bvsp.begin(Serial, STATUS_REQUEST_TIMEOUT, STATUS_REQUEST_INTERVAL);
bvsp.frameReceived = BVSP_frameReceived;
bvsp.modeChanged = BVSP_modeChanged;
bvsp.streamReceived = BVSP_streamReceived;
bvsm.begin();
bvss.begin(DAC);
}
void loop() {
bvsp.keepAlive();
bvsp.receive();
if (bvsp.isSREAvailable()) {
if (!bvsm.isRecording) {
bvsm.setAudioInput(BVSM_AUDIO_INPUT, DEFAULT);
bvsm.startRecording();
}
if (bvsm.available) {
if (bvsp.inboundMode == FRAMED_MODE)
bvsp.setInboundMode(STREAM_MODE);
int bytesRead = bvsm.read(micBuffer, MIC_BUFFER_SIZE);
bvsp.sendStream(micBuffer, bytesRead);
}
} else {
if (bvsm.isRecording)
bvsm.stopRecording();
}
bvss.play();
if (playLEDNotes)
playNextLEDNote();
}
void BVSP_frameReceived(byte dataType, int payloadSize) {
if (dataType == DATA_TYPE_BINARY) {
if (bvsp.getReceivedBytes(receiveBuffer, RECEIVE_BUFFER_SIZE) == RECEIVE_BUFFER_SIZE) {
analogWrite(receiveBuffer[0], receiveBuffer[1]);
}
} else if (dataType == DATA_TYPE_BYTE) {
if (bvsp.getReceivedByte() == 255) {
playLEDNotes = true;
playStartTime = millis();
}
}
}
void BVSP_modeChanged() {
if (bvsp.outboundMode == FRAMED_MODE)
bvss.finishPlaying();
}
void BVSP_streamReceived(int size) {
int bytesRead = bvsp.getReceivedStream(speakerBuffer, SPEAKER_BUFFER_SIZE);
bvss.enqueue(speakerBuffer, bytesRead);
}
void playNextLEDNote() {
unsigned long elapsed = millis() - playStartTime;
allLEDsOff();
if (elapsed >= 11500) {
analogWrite(RED_LED_PIN, 0);
playLEDNotes = false;
} else if (elapsed >= 9900) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 9370) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 8900) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 8610) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 8230) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 7970) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 7470) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 6760) analogWrite(GREEN_LED_PIN, 255); // E
else if (elapsed >= 6350) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 5880) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 5560) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 5180) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 4890) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 4420) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 3810) analogWrite(GREEN_LED_PIN, 255); // E
else if (elapsed >= 3420) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 2930) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 2560) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 2200) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 1930) analogWrite(YELLOW_LED_PIN, 255); // D
else if (elapsed >= 1470) analogWrite(RED_LED_PIN, 255); // C
else if (elapsed >= 1000) analogWrite(GREEN_LED_PIN, 255); // E
}
void allLEDsOff() {
analogWrite(RED_LED_PIN, 0);
analogWrite(YELLOW_LED_PIN, 0);
analogWrite(GREEN_LED_PIN, 0);
}
回路図

製造プロセス
- Python連携で信頼性向上: Arduino RFID勤怠管理システム
- DHT11温湿度センサーで作る温度アラート – LEDとピエゾブザーで視覚/聴覚通知
- NeoPixelリングとArduinoで作るインタラクティブジャイロスコープLEDディスプレイ
- Arduino Dueと3.2インチTFTで作る温度監視付きリアルタイムクロック
- ArduinoとAndroidでRoomba Create 2をリモコン操作 ─ Bluetooth経由のステップバイステップガイド
- ArduinoとNokia 5110ディスプレイで自作電圧計:ステップバイステップガイド
- Arduino & MPU6050:SG90 & MG996Rサーボの精密制御
- u-blox LEA-6H 02 GPSモジュール: Arduino & Python 統合ガイド
- DHT11 と Blynk で温湿度をリアルタイム監視する Arduino チュートリアル
- Arduino Unoと1Sheeldで作る4x4x4 LEDキューブ – インタラクティブLEDプロジェクト
- Arduino GPS+TFTタッチシールドで作るリアルタイム位置表示デバイス