リングバッファとは
ここではディレイやコーラスなどのエフェクターで利用されるリングバッファについて簡単に説明いたします。
リングバッファとはデータ確保領域が環状に配置されたエンドレスなバッファです。
リング状のバッファため通常の配列とは違って「先頭から○○番目のデータ」のような指定はできず、「読み込み位置から○○番目のデータ」と指定します。
そのため、リングバッファは内部的に読み取りと書き込みを行うバッファの位置の情報を持ちます。この読み取り位置・書き込み位置を動かす(進める)ことで必要なデータの読み取り・書き込みをします。
実装に当たっては下記の図のように、1次元のバッファを用意し、読み取り位置や書き込み位置がバッファの大きさを超えるとバッファ先頭へ戻るという処理を行えば実装できます。
【リングバッファの実装イメージ】
リングバッファの実装例
リングバッファは下記のように実装いたします。
【CRingBuffur.h】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
#pragma once #ifndef __RINGBUF__ #define __RINGBUF__ #include <string.h> // リングバッファのサイズ // エフェクター(ディレイなど)で使用する想定なので // とりあえず4秒分確保している(サンプリングレート 44,100Hz) #define RB_SIZE (44100 * 4) // =================================================================================== // リングバッファクラス // =================================================================================== class CRingBuffur { private: int rpos; // 読み込み位置 int wpos; // 書き込み位置 float buf[RB_SIZE]; // 内部バッファ public: inline CRingBuffur(); // 読み込み位置と書き込み位置の間隔を設定する関数 // ディレイエフェクターの場合はそのまま遅延時間(ディレイタイム)になる inline void SetInterval(int interval); // 内部バッファの読み込み位置(rpos)のデータを読み込む関数 // 引数のposは読み込み位置(rpos)からの相対位置 // (相対位置(pos)はコーラスやピッチシフタなどのエフェクターで利用する) inline float Read(int pos = 0); // 内部バッファの書き込み位置(wpos)にデータを書き込む関数 inline void Write(float in); // 内部バッファの読み込み位置(rpos)、書き込み位置(wpos)を一つ進める関数 inline void Update(); }; // コンストラクタ CRingBuffur::CRingBuffur() { // 初期化を行う rpos = 0; wpos = RB_SIZE / 2; // とりあえずバッファサイズの半分ぐらいにしておく memset(buf, 0, sizeof(float) * RB_SIZE); } // 読み込み位置と書き込み位置の間隔を設定する関数 void CRingBuffur::SetInterval(int interval) { // 読み込み位置と書き込み位置の間隔を設定 // 値が0以下やバッファサイズ以上にならないよう処理 interval = interval % RB_SIZE; if(interval <= 0) { interval = 1; } // 書き込み位置を読み込み位置からinterval分だけ離して設定 wpos = (rpos + interval) % RB_SIZE; } // 内部バッファの読み込み位置(rpos)のデータを読み込む関数 float CRingBuffur::Read(int pos) { // 読み込み位置(rpos)と相対位置(pos)から実際に読み込む位置を計算する。 int tmp = rpos + pos; while (tmp < 0) { tmp += RB_SIZE; } tmp = tmp % RB_SIZE; // バッファサイズ以上にならないよう処理 // 読み込み位置の値を返す return buf[tmp]; } // 内部バッファの書き込み位置(wpos)にデータを書き込む関数 void CRingBuffur::Write(float in) { // 書き込み位置(wpos)に値を書き込む buf[wpos] = in; } // 内部バッファの読み込み位置(rpos)、書き込み位置(wpos)を一つ進める関数 void CRingBuffur::Update() { // 内部バッファの読み込み位置(rpos)、書き込み位置(wpos)を一つ進める rpos = (rpos + 1) % RB_SIZE; wpos = (wpos + 1) % RB_SIZE; } #endif |
リングバッファの読み込み位置・書き込み位置
上記の実装例ではリングバッファの読み込み位置・書き込み位置の距離(interval)はそのまま入力信号の遅れる時間(=ディレイタイム)となります。
たとえば入力信号を0.5秒遅らせたい場合、読み込み位置と書き込み位置の距離(interval)を22050バッファ分とします。(サンプリング周波数44100の場合)
使用例
使用例は下記のようになります。(簡単なディレイの場合)
【使用例】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// それぞれの変数は下記のとおりとする // float input[] …44100Hzでサンプリングされた入力信号の格納されたバッファ。 // float output[] …エフェクト処理した値を書き出す出力信号のバッファ。 // int size …入力信号・出力信号のバッファのサイズ。 CRingBuffur ringbuf; for (int i = 0; i < size; i++) { float tmp; // 入力信号にディレイ信号を加える tmp = input[i] + ringbuf.Read(); // 入力信号をリングバッファに書き込み ringbuf.Write(input[i]); // リングバッファの状態を更新する ringbuf.Update(); // 出力信号に書き込む output[i] = tmp; } |
最後に
上記のリングバッファを使って実際にディレイやコーラスは下記にまとめております。
なお、リングバッファのソースコードは上記ですべてですのでご利用の際はコピペしてお使いください。
質問はコメント欄や掲示板、Twitterでいただけばとおもいます。
■掲示板
■Twitterアカウント:@vstcpp URL:https://twitter.com/vstcpp