工作と競馬

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

カテゴリ:4.ソフトウェア > 4.4 C/C++

概要

湿温度計DHT22をESP-WROOM-32に接続して、温度と湿度を取得できた。


背景と目的

とある事情で、家の中の温度と湿度を測ってデータを蓄積する必要が出た。そのため、手持ちのESP-WROOM-32に湿温度センサを接続し、動作させる。


詳細

1.使用するデバイス

  • ESP-WROOM-32 DevKitC基板
  • DHT22

DHT22を選択したのは、Amazonで検索したら最初のほうに出てきたのと、ネット上に参考情報がたくさんあったため。(ちなみに、以前DHT11を持っていたのだが、電源逆接続という初歩的なミスで壊してしまったので、どうせならそれより精度の良いものを買ってしまえということで、買ったという経緯もある。)


2.接続方法

DHT22は、動作電圧範囲が最低3.3Vで、ESP-WROOM-32のGPIOレベル(だいたい3.3Vより低くなる)で確実に動作するのかよくわからないので、3.3V/5Vのレベルシフタをかませることにした。ただ、DHT22は独自の単線インターフェースなので、双方向レベルシフタが必要で、いろいろ考えた挙句、以下のような回路になった。(実は、最低動作電圧を多少下回っても動くのかな?やってないのでわからないが)

20180711184326

3.ソフトウェア

3.1 ライブラリのインストール

基本的に、arduino-esp32を使うが、DHT22は、Arduinoライブラリが存在しており、Arduino IDE>スケッチ>ライブラリをインクルード>ライブラリを管理から、インストールできた。

20180710210408

3.2 コーディング

都合もあり詳細は割愛するが、基本的には、以下の簡単なコードで取得できた。

// インクルード
#include "DHTesp.h"

// DHTespクラスインスタンスを作成
DHTesp dht;

// 初期化
dht.setup(GPIOピン番号, DHTesp::DHT22);

// 計測
TempAndHumidity newValues = dht.getTempAndHumidity();

// 結果表示
Serial.println(" T:" + String(newValues.temperature) + " H:" + String(newValues.humidity));

4.動作確認

以下が、動作確認した様子。暑くて30度を越えているが、手持ちの別の温度計もほぼ同じような値であり、湿度も自分の住んでいるエリアのリアルタイム湿度と比較してほぼ同じだったので、正しく動作しているようだ。

 T:30.90 H:54.90


まとめ

DHT22をESP-WROOM-32に接続して、動作させることができた。次は、Web上に蓄積する。

  // macアドレスの表示
  // WiFi.beginを実行してから呼ぶ
  byte mac_addr[6];
  char buf[20];
  WiFi.macAddress(mac_addr);
  sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.println(buf);
  Serial.print("IPAddress = ");
  Serial.println(WiFi.localIP());

概要

ESP-IDFのversion2.1で、WiFiとBluetoothを同時に使用しようとしたら、変数名/関数名が被っていてコンパイルエラーが出たので、どうにか修正してみた。


背景と目的

ESP-IDFのversion2.1(2017/09/06での最新版)において、WiFiとBluetoothを同時に使用しようとすると、

20170907021644

というエラーが起きてしまう。そこで、これを解消する。


詳細

1.状況

ESP-IDFのversion2.1(2017/09/06での最新版)において、WiFiとBluetoothを同時に使用しようとすると、

  • WiFi制御系のesp-idf/components/lwip/include/lwip/apps/dhcpserver.h
  • Bluetooth制御系のesp-idf/components/bt/bluedroid/osi/include/list.h

の2つがインクルードされるが、

  • dhcpserver.hでlist_nodeのという変数の型が定義されている
  • list.hでlist_nodeという関数が定義されている

ため、コンパイルエラーを生じてしまう。

具体的には、

  • dhcpserver.hでは、
typedef struct _list_node {
	void *pnode;
	struct _list_node *pnext;
} list_node;
  • controller.hでは、
// Returns the value stored at the location pointed to by the iterator |node|.
// |node| must not equal the value returned by |list_end|.
void *list_node(const list_node_t *node);

なので、これらの名前が被らないように、変更してやればよい。ライブラリを修正するのは少し気が引けるが、エラーが出ていては始まらないし、どうせ自分の開発環境だから弄るのは自由なので、とにかく修正してみよう。


2.修正

方針としては、どちらかの名前を変えればいいのだが、dhcpserver.hの変数の型名list_nodeを変更するほうが簡単そうなので、こちらを変更する。具体的には、まあ何でもいいのだが、例えばLIST_NODEという大文字にしてみる。

typedef struct _list_node {
	void *pnode;
	struct _list_node *pnext;
} LIST_NODE;

すると、dhcpserver.cでもともとlist_nodeという型の変数を参照しているものがすべて参照不可能になりエラーが出るので、テキストエディタ等で、dhcpserver.c内の"list_node"を"LIST_NODE"に置換する。


3.コンパイルしてエラーが出ないことを確認

2の修正を施したものを、再度コンパイルしてみると、無事エラーが消えてコンパイル成功!というわけで、2の作業によって問題が回避できた。

ところで、このような問題は、そのうち修正版が出るだろうが、とりあえず、今はその場しのぎで対応しておくことにする。


まとめ

ESP-IDFのversion2.1でのコンパイルエラーを解消できた。

概要

ESP-WROOM-32でiBeaconをスキャンすることができた。


背景と目的

ESP-WROOM-32でiBeaconをスキャンする必要が出たため、スキャンするコードを実装してみる。


詳細

1.やること

とりあえず、スキャンできればいいので、最低限UUID、Major、Minorを読み出しシリアルで結果表示できれば良い。


2.コーディング

2.1 ESP-IDFのサンプルコード

ESP-IDFのサンプルで、bluetooth\gatt_clientというのがある。これは、BLEデバイスをスキャンしたり接続したりするサンプルだ。これを改造すればよさそう。


2.2 改造箇所

サンプルのesp_gap_cbという関数は、gap eventというものが起きたときに呼ばれるコールバック関数。(ble_client_appRegisterで、esp_ble_gap_register_callback(esp_gap_cb)という形でコールバックとして登録されている。)この関数の中のswitch文の分岐のうち、ESP_GAP_BLE_SCAN_RESULT_EVTという部分(サンプルでいうと、304行目)が、スキャン結果を受け取ったときに実行される。なので、この部分においてiBeacon検出に必要な処理をコーディングしてやればよさそうだ。


2.3 iBeaconを検出する処理

iBeaconを検出するには、ESP-IDFのAPIリファレンスによれば、アドバタイズされたデータが格納されているscan_result->scan_rst.ble_advというバイト配列を調べればよさそう。iBeaconの場合、こちらこちらこちらあたりを参考にすれば、26バイトのデータの内訳がわかるので、UUID、Major、Minor等を読み出せばよい。

というわけで、以下、読み出すための関数を作成。is_ibeaconは、ibeaconかどうかをアドバタイジングデータの長さとデータの先頭9バイトから判定する。

// iBeaconか調べる
bool is_ibeacon(uint8_t *ble_adv, uint8_t adv_data_len)
{
    bool tf;
    if (adv_data_len < 30) {
        return false; // 30バイト以下のときは不可
    }
    if (ble_adv[3] == 0x1a && ble_adv[4] == 0xff &&
        ble_adv[5] == 0x4c && ble_adv[6] == 0x00 &&
        ble_adv[7] == 0x02 && ble_adv[8] == 0x15) {
        // 4~9バイト目までが特定のデータになっている
        tf = true;
    } else {
        tf = false;
    }
    return tf;
}

// uuid
void get_ibeacon_uuid(uint8_t *ble_adv, char uuid[])
{
    sprintf(uuid, "%x%x%x%x-%x%x-%x%x-%x%x-%x%x%x%x%x%x",
     ble_adv[9], ble_adv[10], ble_adv[11], ble_adv[12], 
     ble_adv[13], ble_adv[14], 
     ble_adv[15], ble_adv[16], 
     ble_adv[17], ble_adv[18],
     ble_adv[19], ble_adv[20], ble_adv[21], ble_adv[22], ble_adv[23], ble_adv[24]);
}

// major
int get_ibeacon_major(uint8_t *ble_adv)
{
    return ble_adv[25] * 256 + ble_adv[26];
}

// minor
int get_ibeacon_minor(uint8_t *ble_adv)
{
    return ble_adv[27] * 256 + ble_adv[28];
}

次に、上記の関数を使って、ibeacon検出結果を表示する部分は以下。

case ESP_GAP_BLE_SCAN_RESULT_EVT: {
    esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
    switch (scan_result->scan_rst.search_evt) {
    case ESP_GAP_SEARCH_INQ_RES_EVT:
        if (is_ibeacon(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len)) {
            // データを取得
            char uuid[8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12 + 1];
            get_ibeacon_uuid(scan_result->scan_rst.ble_adv, uuid); // uuid
            int major = get_ibeacon_major(scan_result->scan_rst.ble_adv); // Major
            int minor = get_ibeacon_minor(scan_result->scan_rst.ble_adv); // Minor
            int rssi = scan_result->scan_rst.rssi; // rssi
            // 表示
            ESP_LOGI(GATTC_TAG, "iBeacon detected!");
            ESP_LOGI(GATTC_TAG, "UUID = %s", uuid);
            ESP_LOGI(GATTC_TAG, "Major = %d, Minor = %d, RSSI = %d", major, minor, rssi);
        }
        break;

という感じ。


3.動作確認

上記をESP32に書き込み動作させたところ、手持ちのiBeaconを正しく検出できた。

20170817230606


まとめ

ESP-WROOM-32を使って、iBeaconの検出ができた。次は、SDカードに結果を保存してみようかと思う。

概要

 ZedGraphをVisual C++で使ってみた。


背景と目的

 ZedGraphは、Visual C++でも使えるようなので、使ってみる。


詳細
1.使用準備

 使用準備としては、VBでやったときと同じくツールボックスで右クリックし、ツールボックスアイテムの選択>参照>ZedGraph.dllの選択をする。そして、実行ファイルと同じフォルダにZedGraph.dllとZedGraph.xmlをおく、の2つを行った。これで、ツールボックスにコントロールが表示され、使用できるようになった。

2.動作確認プログラムを作ってみる

 動作確認プログラムは、ZedGraphコントロールとボタンを配置して、ボタンを押したらZedGraph上にグラフが描画されるというものだ。ソースコードは以下。ボタンのクリックイベント内の処理である。

 int i;
 double f1 = 1, f2 = 2;
 cli::array ^x, ^y1, ^y2;
 System::String ^a1 = "波形1", ^a2 = "波形2";

 x = gcnew cli::array(100);
 y1 = gcnew cli::array(100);
 y2 = gcnew cli::array(100);

 for(i = 0; i < 100; i++){
  x[i] = double(i) / 100;
  y1[i] = System::Math::Sin(2 * Math::PI * f1 * x[i]);
  y2[i] = System::Math::Sin(2 * Math::PI * f2 * x[i]);
 }

 this->zedGraphControl1->GraphPane->AddCurve(a1, x, y1, System::Drawing::Color::Blue);
 this->zedGraphControl1->GraphPane->AddCurve(a2, x, y2, System::Drawing::Color::Red);
 this->zedGraphControl1->GraphPane->XAxis->Title->Text = "時刻[sec]";
 this->zedGraphControl1->GraphPane->YAxis->Title->Text = "電圧[V]";

 //この2つを実行しないと描画されない
 this->zedGraphControl1->GraphPane->AxisChange();
 this->zedGraphControl1->Invalidate();
3.結果

 以下の図のとおり、ボタンを押すとグラフが表示された。VBとほぼ同じ使い勝手で使えることがわかった。

20130730002750
図1 動作確認結果


まとめと今後の課題

 ZedGraphをVisual C++で使うことができた。これを使って何か作れたらと思う。

概要

 C++によるオーディオ信号処理プログラミングの参考書『信号処理のためのプログラミング入門』のソースコードに間違いと思われる部分の発見、修正に関する記事。


背景と目的

 参考書『信号処理のためのプログラミング入門』第11章、第12章に従いプログラムを作成していたときに発見したソースコード間違いと思われる部分を発見、修正し無事正常動作することを確認した。ただし、これは出版社発表の訂正情報ではないため、あくまで私見であり内容は保証しないことを断っておくが、もし同じ問題で悩んでいる人がいれば参考になるようまとめておく。


詳細
1.問題の箇所

 今回発見した『信号処理のためのプログラミング入門』(技術評論社)という本のソースコード間違い箇所は、以下の2つである。
(1)第11章 オーディオを再生するプログラムのストリームクローズ処理 
(2)第12章 オーディオを録音するプログラムの録音バッファ確保処理
それぞれ以下に詳細を示す。

2.第11章 オーディオを再生するプログラムのストリームクローズ処理について

 第11章の再生プログラムを作成していたとき、ASIOドライバを用いるオーディオI/Fをデバイスとして選択するとプログラムが正常に終了せず、2回目以降の起動でそのデバイスが認識されなくなってしまうという問題が発生した。ところが、他のデバイスの場合は正常終了する。なので、portaudioのASIOドライバ制御部分の問題だろうか?Portaudioのバージョンを新しくすれば直るのではないか?などと悩んでいたところ、11-7節にあるストリームクローズ処理が175ページのソースコードにはないことがわかった。
 具体的にいうと、167行目でストリームを停止してから、191行目でportaudio終了処理までの間にストリームをクローズする処理Pa_CloseStreamが必要なのだが、それが抜けていた。そこで、以下のコードを追加し再コンパイルしてみた。

 //ストリームクローズ
 err = Pa_CloseStream(pStream);
 if(err != paNoError){
  fprintf(stderr, "Error : Pa_CloseStream(), %s\n", Pa_GetErrorText(err));
 }

 すると、プログラムは正常に終了し2回目以降の起動でも正しくデバイスを認識できるようになった。
 というわけで、どうやら11章に掲載されたプログラムは、ストリームクローズ処理が抜けているようである。

3.第12章 オーディオを録音するプログラムの録音バッファ確保処理について

 第12章のプログラムをそのまま作成したところ、うまく動作しなかった。これは、もちろん自分のプログラムに間違いがあるだろうと何度も見直したが、間違いが発見できなかった。録音ファイルは作成され、録音内容がずっと無音になので、おそらくcallback関数内で録音データがうまく取得できていないのではないかと思い、ソースコードを眺めていたところ、41、42行目で録音バッファinputBufferを0で埋める処理があることに気づいた。つまり、せっかく録音したものを0で上書きしているということで、無音という現象とつじつまが合いそうだった。そこで、41、42行目の処理を削除してみた。すると、やはり正常に録音できるようになった。
 つまり、41、42行目の記述は間違いだろうと思われる。


まとめと今後の課題

 『信号処理のためのプログラミング入門』のソースコード間違いと思われる部分を発見、修正し、所望の動作を確認できた。

概要

 C++によるオーディオ信号処理プログラミングに関する記事。前回の続きとして、IIRフィルタのフィルタリング処理をリアルタイムに実行するプログラムを実装した。


背景と目的

 前回は、IIRフィルタのフィルタリング処理を実装できた。今回は、IIRフィルタのフィルタリング処理をリアルタイム実行するプログラムの実装をする。


詳細
1.参考プログラム

 リアルタイム処理は、『信号処理のためのプログラミング入門』(技術評論社)という本の第6章で扱われているオーディオファイルの再生プログラムを参考にする。

2.プログラム実装
2.1 Callback関数

 参考プログラムは、Main関数とCallback関数で構成されており、オーディオデバイスが一定量のデータを再生すると、Callback関数が呼ばれる仕組みになっている。リアルタイムでフィルタ処理をするには、Callback関数内で、再生データにフィルタリングを施してから再生バッファへデータを送ればよい。
 ということで、作成したCallback関数は以下のとおり。ファイルからデータをいったんoutという変数に読み出した後、そのまま再生バッファに送らず、IIRFilterProcess(前回作成)という関数でフィルタ処理をして再生バッファに送っている。

//Callback関数
int patestCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, 
       const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData){
 
 //変数の宣言       
 int ret = 0;
 SNDFILE *fp_read = NULL;
 SF_INFO *sf_info_read =NULL;
 CONSOLEAUDIOINFO *pstConsoleAudioInfo = (CONSOLEAUDIOINFO *)userData; //CONSOLEAUDIOINFO型にキャスト
 sf_count_t count;
 void *out; //読み出しデータを仮で受ける

 //ファイルポインタ,ファイル情報を取得
 fp_read = pstConsoleAudioInfo->fp_read;
 sf_info_read =pstConsoleAudioInfo->sf_info_read;

 if(fp_read){

  //ファイルからデータを読み出す
  out = (float *)malloc(framesPerBuffer * sf_info_read->channels * sizeof(float));
  memset(out, 0, framesPerBuffer * sf_info_read->channels * sizeof(float)); //バッファを確保
  count = sf_read_float(fp_read, (float *)out, framesPerBuffer * sf_info_read->channels); //ファイルから1フレームを読み出しoutに入れる

  //繰り返し再生のための処理
  if((unsigned int)count != framesPerBuffer * sf_info_read->channels){
   sf_seek(fp_read, 0, SEEK_SET); //ファイル終端まで行ったら先頭に戻る
  }

  //読み出したデータにLPFをかける, かけたデータを再生バッファに入れる
  IIRFilterProcess((float *)out, (float *)outputBuffer, framesPerBuffer, sf_info_read->channels);
 }

 free(out); //メモリを開放

 return ret;
}
2.2 Main関数

 Main関数では、IIRフィルタのローパスフィルタ係数を算出するための処理とIIRフィルタの遅延RAMを初期化する処理を追加しておく。以下のとおり、前々回作成したLPF係数算出関数を呼び出している。また、前回作成したIIRFILTERDRAM構造体変数を初期化している。

 //IIRフィルタを作る
 lpf_coef = IIR_2d_LPF(1000, 1 / sqrt(2.0), 44100);
 lpf_dram.x1[0] = 0;
 lpf_dram.x1[1] = 0;
 lpf_dram.y1[0] = 0;
 lpf_dram.y1[1] = 0;
 lpf_dram.x2[0] = 0;
 lpf_dram.x2[1] = 0;
 lpf_dram.y2[0] = 0;
 lpf_dram.y2[1] = 0;
3.動作確認

 まず、プログラムを実行したところ、元の音源よりも高域がカットされた音が聞こえ、LPFがかかっていそうなことがわかった。また、実際にフィルタ特性が正しく出ているかホワイトノイズを再生して確認した結果が図3。目的のfc=1kHz、2次LPFが正しくかかっている。
 以上より、リアルタイムフィルタリング処理が達成できた。

20130326233755
図3 LPFをかけたときのスペクトル


まとめと今後の課題

 リアルタイムフィルタリング処理をするプログラムが実装できた。今後は、さらにいろいろなフィルタ、音響エフェクト処理に挑戦していきたい。

概要

 C++によるオーディオ信号処理プログラミングに関する記事。前回の続きとして、IIRフィルタのフィルタリング処理をする関数を作成した。


背景と目的

 前回は、ローパスフィルタの係数算出までを実装できた。今回は、IIRフィルタのフィルタリング処理をする関数の実装をする。


詳細
1.参考書

 前回同様、『C言語で始める音のプログラミング』(オーム社)第6章がC言語でローパスフィルタをかけるプログラムを扱っているため、これを参考書とした。

2.プログラム実装
2.1 フレーム分割

 リアルタイム処理では、入力データすべてに対し一度にフィルタリング処理する時間はない。そこで、フレーム分割が必要である。しかし、IIRフィルタの場合はフレーム間がうまくつながるように工夫が必要である。具体的には2次IIRフィルタでは前フレームの入出力値末尾2サンプルが現フレームの遅延器初期値になる。そこで、本プログラムでは、末尾2サンプルを保持する構造体IIRFILTERDRAMを定義した。

//前フレームの末尾2サンプルを保持するための構造体を定義
typedef struct
{
 double x1[2], x2[2]; //過去の入力
 double y1[2], y2[2]; //過去の出力
}
IIRFILTERDRAM;

 そして、この型の変数lpf_dramをグローバル変数として宣言し、フレーム間のデータのやり取りを行うことにした。

//IIRFILTERDRAM型の変数を宣言
IIRFILTERDRAM lpf_dram;
2.2 フィルタリング処理

 フレーム分割したフィルタリング処理を実装した関数は以下のとおりである。引数は、入力x、出力y、フレーム長さ、チャンネル数である。
 フィルタリング処理は、ループカウンタnが0,1,2以上で場合わけされる。0,1のときは前フレームの入出力値を参照してyを計算する。また、最後に現フレーム末尾2サンプルの入出力値をlpf_dramに格納している。これが次フレームのn=0,1で参照されることで、フレーム間が正しくつながる。

//IIRフィルタの処理
void IIRFilterProcess(float *x, float *y, int framesPerBuffer, int chcnt){

 int n;
 int ch;

 //n < 2では、lpf_dramを参照する
 for(ch = 0; ch < chcnt; ch++){
  //n = 0
  y[chcnt * 0 + ch] = lpf_coef.a[0] * x[chcnt * 0 + ch] + lpf_coef.a[1] * lpf_dram.x1[ch] + lpf_coef.a[2] * lpf_dram.x2[ch]
                                   + lpf_coef.b[1] * lpf_dram.y1[ch] + lpf_coef.b[2] * lpf_dram.y2[ch];
  //n = 1
  y[chcnt * 1 + ch] = lpf_coef.a[0] * x[chcnt * 1 + ch] + lpf_coef.a[1] * x[ch]          + lpf_coef.a[2] * lpf_dram.x1[ch]
                                   + lpf_coef.b[1] * y[ch]          + lpf_coef.b[2] * lpf_dram.y1[ch];
 }

 //n >= 2
 for(n = 2; n < framesPerBuffer; n++){
  for(ch = 0; ch < chcnt; ch++){
   y[chcnt * n + ch] = lpf_coef.a[0] * x[chcnt * n + ch] + lpf_coef.a[1] * x[chcnt * (n - 1) + ch] + lpf_coef.a[2] * x[chcnt * (n - 2) + ch]
                                    + lpf_coef.b[1] * y[chcnt * (n - 1) + ch] + lpf_coef.b[2] * y[chcnt * (n - 2) + ch];
  }
 }

 //次回フレームのため、フレーム末尾2サンプル分を保存
 for(ch = 0; ch < chcnt; ch++){
  lpf_dram.x1[ch] = x[chcnt * (framesPerBuffer - 1) + ch];
  lpf_dram.y1[ch] = y[chcnt * (framesPerBuffer - 1) + ch];
  lpf_dram.x2[ch] = x[chcnt * (framesPerBuffer - 2) + ch];
  lpf_dram.y2[ch] = y[chcnt * (framesPerBuffer - 2) + ch];
 }

}

まとめと今後の課題

 2次IIRフィルタによるローパスフィルタのフィルタリング処理プログラムを作成できた。次回は、これをリアルタイム処理として実装する。

概要

 C++によるオーディオ信号処理プログラミングに関する記事。リアルタイム処理プログラム作成に向けて、手始めとしてローパスフィルタの係数を算出する部分を実装し、動作確認した。


背景と目的

 前回の(1)では、信号処理に必要な環境構築ができた。そこで、念願のリアルタイム処理に手をつけたいと思う。目標は、『再生中の音にローパスフィルタをかける』とする。今回は、構成要素となるローパスフィルタの係数算出プログラムまでを実装してみたい。


詳細
1.参考書

 今回の内容に取り組むにあたり、『C言語で始める音のプログラミング』(オーム社)第6章がC言語でローパスフィルタをかけるプログラムを扱っているため、これを参考書とした。この本は、非リアルタイム処理を扱っているのだが、C言語で各種信号処理のプログラムを実装する方法が示されており、大変参考になる。

IMG_0800
図1 参考書

2.プログラム作成
2.1 IIRフィルタと係数算出方法

 ローパスフィルタは、図2.1に示す2次IIRフィルタとする。このフィルタは係数が5つあるため、パラメータにあわせて設計する必要がある。設計方法は、有名な双一次変換法を用いる。

20130326190836
図2.1 IIRフィルタ

2.2 プログラム実装

 まず、IIRフィルタは係数が5つあるため、それらを保持する変数をまとめた構造体を用意したほうが、便利だろうと考え、以下のIIRFILTERCOEFを定義した。なお、フィードバック側bは2つあればいいのだが、サブスクリプトをaとbでそろえるためあえて3つ用意している。

//IIRフィルタ係数構造体
typedef struct
{
 double a[3]; //フィードフォワード側係数
 double b[3]; //フィードバック側係数
}
IIRFILTERCOEF;

 そして、係数算出関数を以下のように作成した。引数にカットオフ周波数、共振度、サンプリング周波数を指定することで、双一次変換法で係数を算出し、IIRFILTERCOEF型の戻り値が得られる。

//LPFの係数を設計する関数
IIRFILTERCOEF IIR_2d_LPF(double fc, double Q, double fs){

 //fc ディジタルLPFカットオフ周波数
 //Q  クオリティファクタ
 //fs サンプリング周波数

 //変数
 double p1, p2; //計算用
 double wc;  //カットオフ角周波数
 double fc_a; //アナログLPFカットオフ周波数
 IIRFILTERCOEF coef; //係数

 //周波数プリワーピング
 fc_a = tan(pi * fc / fs) / (2 * pi);
 wc = 2 * pi * fc_a;

 //係数計算
    p1 = 1 + wc / Q + wc * wc;
    p2 = 1 - wc / Q + wc * wc;
    coef.a[0] = (wc * wc) / p1;
    coef.a[1] = 2 * (wc * wc) / p1;
    coef.a[2] = coef.a[0];
    coef.b[0] = 1;
    coef.b[1] = -2 * (wc * wc - 1) / p1;
    coef.b[2] = -p2 / p1;

 return coef;

}
3.動作確認

 作成したプログラムにて、fc=1000Hz、Q=1/√2、fs=44100として係数を算出した。その結果、別途用意してある係数算出プログラムと同一の係数が得られ、正しく動作していることが確認できた。


まとめと今後の課題

 2次IIRフィルタによるローパスフィルタの係数算出プログラムを作成できた。次回は、フィルタリング処理プログラムを作成する。

概要

 C++によるオーディオ信号処理プログラミングに関する記事。手始めに書籍『信号処理のためのプログラミング入門』(技術評論社)を参考とし、C++によるオーディオ信号処理プログラミングの開発環境整備とサンプルプログラムの動作確認を行った。


背景と目的

 私はDTMを趣味としているが、その中では各種音響エフェクトを使用している。それらはリアルタイムにオーディオ信号処理を行いいろいろな音を出力していて非常に興味深く、そのようなオーディオ信号処理を自分も実現してみたいと思った。将来的にはPC上でリアルタイムに信号処理を行い自由に音を出してみたい。そこで、まずオーディオ信号処理の世界に足を踏み入れるため、信号処理に必要な環境構築を行い、簡単なプログラムを作成してみる。


活動の詳細
1.方針

 まず、オーディオ信号処理に必要な開発環境がどのようなものかわからなかったのでWeb上で調べてみたところ、『信号処理のためのプログラミング入門』(技術評論社)という本(図1)がC++でリアルタイムオーディオ信号処理に関する内容を扱っていそうだということがわかった。そこで、この本を購入し、この本の内容にしたがって環境構築を行っていくことにした。

IMG_0599
図1 参考とした書籍

2.環境構築

 環境構築は、参考書籍の第3章、第4章を見ながら行った。この本ではVisual C++ Express Editionを用いてプログラムを作成する方法が書かれており、私の環境にはすでにVisual C++がインストールされていたので、オーディオ信号処理に必要ないくつかのライブラリ、SDKをインストールすることで無事環境が構築できた。

3.サンプルプログラムの作成と動作確認

 環境が構築できたので手始めにサンプルプログラムを作成してみた。サンプルプログラムの内容は参考書籍第5章にあるwavファイルのサンプリング周波数とチャンネル数を表示するプログラムである。(以下参照)
 本に書いてある手順に従いプログラムを作成したところ無事コンパイルされプログラムを実行できた。その結果が図3である。読み込ませたwavファイルのサンプリング周波数、チャンネル数が正しく表示されサンプルプログラムが正しく作成できたことがわかった。

作成したプログラム
※以下のプログラムは参考書籍の内容にしたがって環境構築されていないと動作しません。
// consoleplayer.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

#include  //printf関数などを使うため
#include  //exit関数などを使うため
#include  //memset関数などを使うため

#include  //libsndfileのヘッダ

//メイン関数
int _tmain(int argc, _TCHAR* argv[])
{

 //変数の定義
 SNDFILE *fp_read = NULL; //オーディオファイルのファイルポインタ
 SF_INFO sf_info_read; //オーディオファイルの基本的な情報を格納する構造体

 //sf_info_readの初期化
 //void* memset( void* dest, int c, size_t count )
 //destからcountバイトのメモリー領域をcで埋めます
 memset(&sf_info_read, 0, sizeof(SF_INFO));

 //ファイルを読み込みモードでオープン
 if(argc != 2){
  exit(1); //コマンドラインからの第一引数が空であった場合は終了
 }
 fp_read = sf_open(argv[1], SFM_READ, &sf_info_read);
 if(!fp_read){
  exit(2); //ファイルが見つからなかったら終了
 }

 //サンプリング周波数とチャンネル数を表示
 printf("samplerate is %d\n", sf_info_read.samplerate);
 printf("channels is %d\n", sf_info_read.channels);

 //CLOSE処理
 if(fp_read){
  sf_close(fp_read);
  fp_read = NULL;
 }

 printf("press any keys...");
 getchar();

 return 0;
}


fsとch
図3 サンプルプログラムの動作確認


まとめと今後の課題

 『信号処理のためのプログラミング入門』(技術評論社)を参考とし、オーディオ信号処理のための環境構築およびサンプルプログラムの動作確認をすることができた。現段階では信号処理はできていないので今後は参考書籍の内容に従い信号処理ができるようにしていく。

このページのトップヘ