Blue Cat's Plug'n Script - DTMプログラミング言語探訪

[DTMプログラミング言語探訪](https://qiita.com/aike@github/items/77b3eccc6861de09513a)

# Blue Cat's Plug'n Script

## 概要

Blue Cat Audioの[Blue Cat's Plug'n Script](https://www.bluecataudio.com/Products/Product_PlugNScript/)は、VSTやAUプラグインを作成するためのスクリプティング環境です。DAWを終了したりプラグインを読みなおすことなくトライ＆エラーでプラグイン開発が可能という特長があります。スクリプト自体高速で動くほか、C++ネイティブコードのサポート、ビルドしたバイナリ形式で配布可能など、商用レベルのプラグイン開発にも使用できる能力を持っています。

## 用途

DAWのプラグインすなわち信号処理アプリケーションの開発環境です。

- インストゥルメントプラグイン開発
- エフェクトプラグイン開発

プラグインだけでなく、スタンドアロンインストゥルメントとしての実行環境も用意されています。

## 言語仕様

- [AngelScript](https://www.angelcode.com/angelscript/)
- イベントコールバック
- 信号処理API
- GUI API

AngelScriptはC++と非常に似たシンタックスを持つ言語です。一見するとC++と見分けがつきにくいこともあります。
コールバックや信号処理の書き方はVSTSDKやJUCEに触れたことがある人なら迷うことなく扱うことができます。

公式マニュアル　[Blue Cat's Plug'n Script User Manual](https://www.bluecataudio.com/Doc/Product_PlugNScript/)


## GUI仕様

可変パラメータを定義するとデフォルトで高品質のGUIが表示されます。
独自のGUIをデザインして[KUIML言語](https://www.bluecataudio.com/Vault/Skins/KUIML/)で定義したものを使用することもできます。

## プログラム例

### モノシンセ

サイン波を鳴らすモノシンセの例です。
AngelScriptは拡張子が.asも使われるようですが、なぜかサンプルプログラムの拡張子はすべて.cxxなのでそれに従いました。.asでも.cxxでも読み込み可能です。

```c++:Sine.cxx
#include "../library/Midi.hxx"
#include "../library/Constants.hxx"

string name = "Sine Oscillator";
string description = "Sinewave mono synth";

double amplitude = 0;
double phase = 0;
double omega = 0;
uint8  noteNum = 0;
const  double pi2 = 2 * PI;

// MIDI event handler
void handleMidiEvent(const MidiEvent& e)
{
  switch(MidiEventUtils::getType(e))
  {
    case kMidiNoteOn:
    {
      amplitude = double(MidiEventUtils::getNoteVelocity(e)) / 127.0;
      noteNum = MidiEventUtils::getNote(e);
      phase = 0;
      omega = pi2 * pow(2,((double(noteNum - 69.0)) / 12.0)) * 440.0 / sampleRate;
      break;
    }
    case kMidiNoteOff:
    {
      if(noteNum == MidiEventUtils::getNote(e))
        amplitude=0;
      break;
    }
  }
}


// Audio generator
void processBlock(BlockData& data)
{
  uint nextEvent = 0;

  for(uint i = 0; i < data.samplesToProcess; i++)
  {
    while(nextEvent != data.inputMidiEvents.length && data.inputMidiEvents[nextEvent].timeStamp <= double(i))
    {
      handleMidiEvent(data.inputMidiEvents[nextEvent]);
      nextEvent++;
    }

    double val = amplitude * sin(phase);
    for(uint ch=0; ch < audioOutputsCount; ch++)
    {
      data.samples[ch][i] = val;
    }

    phase += omega;
  }

  while(phase > pi2)
    phase -= pi2;
}

int getTailSize()
{
  return -1;
}

```
name、descriptionで設定した文字列がプラグイン画面に表示されます。

getTailSize()は、空間系エフェクトのように入力オーディオに対して出力オーディオのリリースが長いようなときに長さを指定する関数とのことです。シンセサイザーのような入力オーディオがない場合は-1を返します。
![bc1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/fb5fa8fd-9147-81b4-436b-f934cc298242.png)


### GUI

上記のモノシンセを2オシレーターにしてノブを追加した例です。

```c++:Sine2.cxx
#include "../library/Midi.hxx"
#include "../library/Constants.hxx"

string name = "2 Sine Oscillators";
string description = "Sinewave mono synth";

string[] inputParametersNames = {"Osc1", "Osc2"};
double[] inputParameters(inputParametersNames.length);
double[] inputParametersDefault = {0.5, 0.5};

enum Params
{
    kOsc1 = 0,
    kOsc2
};

double amplitude = 0;
double phase1 = 0;
double phase2 = 0;
double omega1 = 0;
double omega2 = 0;
uint8  noteNum = 0;
const  double pi2 = 2 * PI;

// MIDI event handler
void handleMidiEvent(const MidiEvent& e)
{
  switch(MidiEventUtils::getType(e))
  {
    case kMidiNoteOn:
    {
      amplitude = double(MidiEventUtils::getNoteVelocity(e)) / 127.0;
      noteNum = MidiEventUtils::getNote(e);
      phase1 = 0;
      phase2 = 0;
      omega1 = pi2 *pow(2,((double(noteNum - 69.0)) / 12.0)) * 440.0 / sampleRate;
      omega2 = omega1 * 2.0;
      break;
    }
    case kMidiNoteOff:
    {
      if(noteNum == MidiEventUtils::getNote(e))
        amplitude=0;
      break;
    }
  }
}

// Audio generator
void processBlock(BlockData& data)
{
  uint nextEvent = 0;

  for(uint i = 0; i < data.samplesToProcess; i++)
  {
    while(nextEvent != data.inputMidiEvents.length && data.inputMidiEvents[nextEvent].timeStamp <= double(i))
    {
      handleMidiEvent(data.inputMidiEvents[nextEvent]);
      nextEvent++;
    }

    double val = amplitude * (
      sin(phase1) * inputParameters[kOsc1] + 
      sin(phase2) * inputParameters[kOsc2]);
    for(uint ch=0; ch < audioOutputsCount; ch++)
    {
      data.samples[ch][i] = val;
    }

    phase1 += omega1;
    phase2 += omega2;
  }

  while(phase1 > pi2)
    phase1 -= pi2;
  while(phase2 > pi2)
    phase2 -= pi2;
}

int getTailSize()
{
  return -1;
}
```

inputParametersNames、inputParameters、inputParametersDefaultを定義すると適切な位置に適切なコントローラーが表示されます。値はinputParameters[パラメータ連番]で参照できます。
![bc2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/cb79ba0e-0f1e-f5ca-5402-b7cffec8be5c.png)

右側にあるGUI Options、GUI ControlsからさまざまなGUIの種類を選ぶことができます。
![bc9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/f03309de-5fe7-4012-8953-4c431b4bd058.png)



### エフェクター

次にエフェクターの例です。これもVSTSDKやJUCEとあまりかわりありません。

```c++:Fuzz.cxx
string name="Fuzz";
string description="Fuzz Fx";

array<string> inputParametersNames = {"Clip"};
array<double> inputParameters(inputParametersNames.length);
array<double> inputParametersDefault = {0.5};

void processSample(array<double>& sig)
{
    for(uint ch = 0; ch <audioInputsCount; ch++)
    {
        double val = sig[ch];
        val *= 20.0 * inputParameters[0];

        if(val > 1.0)
            val = 1.0;
        else if(val < -1.0)
            val = -1.0;

        val /= 10.0;

        sig[ch] = val;
    }
}
```
一旦ゲインを20倍にブーストした後に±1.0でクリップさせ、1/10に下げる処理をしています。
バイパスしたときに聴感上の不自然さを抑えるために1/20ではなく1/10にしてみました。
![bc8_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/478e4df4-ca58-9711-5b5d-6aedae4036b3.png)


## 実行方法

ユーザースクリプトは以下の場所に置いておくことが想定されているようです。ライブラリを相対パスで利用する関係上、他の場所に保存することはおすすめしません。
`C:\Users\ユーザ名\Documents\Blue Cat Audio\Blue Cat's Plug'n Script\Scripts`

サンプルスクリプトはVST3の場合、以下の場所に格納されています。
`C:\Program Files\Common Files\VST3\BC Plug'n Script VST3 data\Resources\dsp-scripts\`

エフェクターの場合は「BC Plug'n Script」を、インストゥルメントの場合は「BC Plug'n Script Synth」をDAWのプラグインとして挿入します。

一見グレーアウトしているように見える鉛筆アイコンをクリックすると編集モードになります。ちなみに虫眼鏡アイコンは検索ではなくズームです。
![bc3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/824e7314-14b5-9083-8b89-7a7996e5213d.png)

右側のスクリプト名をクリックしてスクリプトを選択します。
![bc4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/f93b1759-e6f2-cc0c-2e14-44cf15bf6447.png)

スクリプトにエラーがある場合はプラグイン画面の下段に表示されます。ここをクリックすると過去のエラー履歴も含めたログファイルが開かれます。
![bc5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/a725a696-5bff-75f3-660f-44cabed2fe9e.png)

スクリプトファイル名の上にある鉛筆アイコンをクリックするとエディタでスクリプトを編集できます。
![bc6_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/852204e6-d2d3-7b8e-fbd6-28b37a6f60b1.png)

スクリプトが完成したらプラグイン画面のタイトル部分をクリックしてプリセット(.preset)として保存します。プリセットはスクリプトの場所やGUIの設定値などが記述されたXMLファイルです。
![bc7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/5b272950-9119-5d05-12d0-2db05900ceb4.png)


## 感想

信号処理の書きやすさという点では、JUCEやVSTSDKとさほど変わりません。AngelScriptなのでC++の煩雑さがわずかに軽減されている感じはありますがほとんど同じ感覚です。ただ、自動的に高品質のGUIが配置される点や、定型的なプロジェクト構成を用意する必要がないのは非常に楽です。
Blue Cat's Plug'n Scriptとテキストエディタさえあれば、大きなSDKやライブラリや統合環境を用意する必要がないのは好印象です。またトライ＆エラーで書いていけるところも気軽で良かったです。
$99という価格なので普及の限界はあるように思います。なお、デモ版の制限は起動時にウィンドウが出るのと、毎分0.5秒間バイパスされる程度なので、実際に開発を経験して実用性を確認してから購入することが可能です。


[DTMプログラミング言語探訪](https://qiita.com/aike@github/items/77b3eccc6861de09513a)