読者です 読者をやめる 読者になる 読者になる

JUCE Frameworkを使ってディレイを作ってみた

VST C++ JUCE

手軽にVSTが作れるJUCE Frameworkを使ってディレイを作ってみました。
JUCE JAPANという入門書(良書!)を読んで始めたばかりなので、解説というか製作記のノリで書きます。

ソース

JUCEは手探り&C++は自信無い感じなのでソースを抜粋して載せます。

PluginProcessor.h

class DelayAudioProcessor  : public AudioProcessor
{
// 中略
private:
    int sampleNumPerDelayTime;
    int repeatNum;
    float delayTimeMiliSec;
    std::vector<boost::circular_buffer<float>> delayLine;
};

パラメータとしてメンバに以下を設定。

  • 1回ディレイするのに必要なサンプル数(sampleNumPerDelayTime)
  • 何回繰り返すか(repeatNum)
  • 何ミリ秒ディレイするか(delayTimeMiliSec)
  • ディレイ用のバッファ(delayLine)

ディレイは簡単に言ってしまえばバッファに音を詰め込んでそこから取り出すだけなので、データ構造として何を選ぶかがキモになります。
FIFOかつ決まった秒数のサンプル数を保持できればいいので、サーキュラーバッファ(リングバッファ)がよく選ばれるそうです。
素のC++だとサーキュラーバッファを扱えないので、別途Boostを入れます。
サイズの管理を自分でやるのであればstd::dequeとかでも良い気がします。
あとJUCEにはAudioBufferクラスっていうのが存在するみたいなので、それが一番いいのかもしれません(不勉強)。

バッファはチャンネル毎に必要なので、vectorに入れてみる。

余談:Boost

Boostはネットで拾ってきてうまいことコンパイルする。
自分は↓をみてやった。
programming-aip.blogspot.jp
コンパイルに関して、

Visual Studioコマンドプロンプトを開くか,普通のコマンドプロンプトを開いて64ビット版をビルドしたければ,"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"を実行する.

と書いてるけど、コンパイラへのパスが通っていないといけないので前者が良いと思う。
開発者コマンドプロンプトってやつです。

あとはProjucerでDebugやReleaseのHeader search pathsとExtra library search pathsにそれぞれのパスを設定すれば使える。

余談:DelayLine

DelayLineはプログラム的にはただのバッファなんだけど、アナログの世界だとほんとに線なので面白いです。
Analog delay line - Wikipedia

PluginProcessor.cpp

void DelayAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    sampleNumPerDelayTime = delayTimeMiliSec / 1000.0f * sampleRate;
    while (delayLine.size() < getTotalNumInputChannels())
    {
        delayLine.push_back(boost::circular_buffer<float>(sampleNumPerDelayTime * repeatNum));
    }
}

prepareToPlayでVSTホストの情報を使った初期化処理を書く。
sampleRateは単位秒ごとのサンプル数(44,100とか)なので遅延時間を掛けると確保すべきバッファ長が分かります。
更に繰り返し回数を掛けてバッファを確保する。

void DelayAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
    const int totalNumInputChannels  = getTotalNumInputChannels();
    const int totalNumOutputChannels = getTotalNumOutputChannels();

    for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    {
        buffer.clear (i, 0, buffer.getNumSamples());
    }

    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        float* channelData = buffer.getWritePointer (channel);
        for (long buffNum = 0; buffNum < buffer.getNumSamples(); buffNum++)
        {
            delayLine[channel].push_front(channelData[buffNum]);
            for (int i = 1; i <= repeatNum; i++)
            {
                if(delayLine[channel].size() < sampleNumPerDelayTime * i) break;
                channelData[buffNum] += (0.5f / i) * delayLine[channel][sampleNumPerDelayTime * i - 1];
            }
        }
    }
}

一番深いネストの部分がディレイの部分。
(クラスを作ったほうが良いという意見も当然あると思うけど、短かったので。。。)

push_frontでサンプルをバッファに入れているので、begin側が新しいサンプル、end側が過去のサンプルになります。
例として、遅延時間を1秒、繰り返し回数を3回、サンプリングレートを44,100とすると、バッファの中身は以下の様になります。
delayLine[channel][44099] :1秒前のサンプル
delayLine[channel][88199] :2秒前のサンプル
delayLine[channel][132299]:3秒前のサンプル
なのでこれらを原音に足し込んでいくとディレイするようになります。

足し込む際の減衰はボリュームカーブの実装をちゃんと考えないといけないです。
さすがにコレ(0.5f/i)は適当すぎる。
ちゃん作り込むとなると、繰り返し回数が全部聞こえるようにカーブを設定しないといけないと思う。

サンプル

鳴らしてみるとこんな感じになります。

参考

JUCE JAPAN vol.1: JUCEではじめるVST/AUプラグイン制作(Windows/MacOS対応)

JUCE JAPAN vol.1: JUCEではじめるVST/AUプラグイン制作(Windows/MacOS対応)

www39.atwiki.jp


またなにか作ったら書きます。