FL Studio Edison Script - DTMプログラミング言語探訪

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

# Edison Script

## 概要

波形編集ツールは定型的な操作を自動化するためのスクリプト機能を持っていることが多いです。
FL Studio付属の波形エディタプラグインEdisonもスクリプト機能を持っているので機能や使用方法を説明します。なお、ドキュメントによるとスクリプティング機能はWindows版のみとのことです。
## 用途

- カスタム波形編集機能の作成

作成したスクリプトは、通常の機能と同様にメニューから簡単に呼び出せるため、ツールに新機能を追加する感覚に近いです。
ファイル入力は可能ですが、スクリプトから保存はできないようです。またフォルダ内のファイル一覧も取得できないので、複数ファイルを一括処理する用途には向いていません。

## Fluity Convolver

ちなみにFL Studio付属のIRリバーブプラグインFluity ConvolverでもEdison Scriptを動かすことができます。というよりFluity ConvolverはEdisonにリバーブ機能を付加したようなツールで、波形編集に関してはほとんど同じメニューが用意されています。

## 言語仕様

Edisonは初期のFL Studioから付属しているプラグインであるためスクリプトの設計も比較的古い印象です。
スクリプトエンジンにpaxCompilerを使用しており、JavaScript、Pascal、Basicの言語で記述することができます。paxCompilerはすでに配布が終了している古いエンジンなので、現代的なJavaScriptにくらべると微妙に違和感があるところもあります。

APIの特徴としては、ツールのメニューにある機能を呼び出すだけでなく、サンプル単位で信号処理をおこなうこともできる点が挙げられます。

- JavaScript/Pascal/Basic
- 波形編集機能実行
- サンプル単位の波形加工
- 入力ダイアログ表示
- ファイル読み込み

[公式APIドキュメント](https://www.image-line.com/fl-studio-learning/fl-studio-online-manual/html/plugins/editortool_run.htm)

わずか1ページのテキストファイルにすべてのAPIが書かれていることでわかるとおり、コンパクトな仕様です。


ローカルのドキュメントは以下の場所にあります。
　(install folder)\Image-Line\FL Studio 20\Plugins\Fruity\Effects\Edison\Data\Scripts\Reference.txt

Fruity Convolverの方にもドキュメントがありますが、上とまったく同じ内容です。
　(install folder)\Image-Line\FL Studio 20\Plugins\Fruity\Effects\Fruity Convolver\Data\Scripts\Reference.txt

## プログラム例

JavaScriptの例を示します。

### 選択位置の表示

以下のプログラムは選択されている開始位置と終了位置を、サンプル位置と時間で表示します。
1行目に必ずスクリプト名と言語種別を記述します。

Editor.GetSelectionS()はWebのJavaScriptに慣れていると違和感があるかもしれません。波形の選択されている位置を、C++の参照渡しのように引数の変数に返します。引数に与える変数は宣言だけではだめで、p1 = 0のように事前に値を代入しておく必要があります。メモリ確保の関係だと思いますがドキュメントに記述されていないので最初は戸惑いました。どこも選択されていないときは波形全体の先頭（つまり0）と終端位置を返します。

```javascript:ShowSelectedPosition.edscript
script "Show Selected Position" language "javascript";

var p1 = 0;
var p2 = 0;
Editor.GetSelectionS(p1, p2);

var t1 = EditorSample.SamplesToMS(p1);
var t2 = EditorSample.SamplesToMS(p2);

ShowMessage("Selection: "
  + IntToStr(p1)   + "-" + IntToStr(p2)   + " samples  "
  + FloatToStr(t1) + "-" + FloatToStr(t2) + " msec");
```

![ed1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/287bb3c7-4ed4-b25d-46d7-5865a3b8d34d.png)

### ノーマライズとトリム

波形をノーマライズした後に、先頭と末尾をそれぞれスキャンして空白をトリムするスクリプトです。

```javascript:ShowSelectedPosition.edscript
script "Normalize and Trim" language "javascript";

// 音の開始位置をスキャン
function DetectStart(p1, p2, threshold) {
  var pos = -1;
  for (var p = p1; p <= p2; p++) {
    for (var ch = 0; ch < EditorSample.NumChans; ch++) {
      if (Math.abs(EditorSample.GetSampleAt(p, ch)) > threshold) {
        pos = p;
        return pos;
      }
	}
  }
  return pos;
}

// 音の終了位置をスキャン
function DetectEnd(p1, p2, threshold) {
  var pos = -1;
  for (var p = p2; p >= p1; p--) {
    for (var ch = 0; ch < EditorSample.NumChans; ch++) {
      if (Math.abs(EditorSample.GetSampleAt(p, ch)) > threshold) {
        pos = p;
        return pos;
      }
	}
  }
  return pos;
}

// メイン処理
var p1 = 0;
var p2 = 0;
Editor.GetSelectionS(p1, p2);

var dialog = CreateScriptDialog("NormalizeTrim", "Input trim threshold");
try
{
  dialog.AddInputKnob("Normalize", 0.99, 0.5, 1.0);
  dialog.AddInputKnob("Threshold", 0.001, 0.001, 0.1);
  if (dialog.Execute()) {
    var normalize = dialog.GetInputValue("Normalize");
    EditorSample.NormalizeFromTo(p1, p2, normalize);

    var threshold = dialog.GetInputValue("Threshold");
    var startPos = DetectStart(p1, p2, threshold);
    var endPos   = DetectEnd(p1, p2, threshold);
    if ((startPos >= 0) && (endPos >= 0)) {
      EditorSample.TrimFromTo(startPos, endPos);
    }
  }
}
finally
{
  dialog.Free();
}
```
![ed2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/79168e6b-a5eb-2f6d-8621-0cf8530eec52.png)


### リバースと元の音を結合

シンバルをリバース加工するときにリバース前の音のリリースを結合するとシンバルロールみたいに聞こえるので、それを一発でおこなうスクリプトの例です。

この波形が、
![ed_old1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/fb169735-92c9-a1b8-b6b3-a67602e7af77.jpeg)

こうなります。
![ed_old2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/115964bc-751b-ec65-0815-b0a4ddd95916.jpeg)


```javascript:ReverseAndRelease.edscript.edscript
script "Reverse and Release" language "javascript";

// 最大音量の位置を検出
function DetectPeak(x1, x2) {
    var max = 0.0;
    var pos = 0;

    for (var n = x1; n <= x2; n++) {
        var x = n - x1;
        if (x % 10000 == 0)
            ProgressMsg("Processing", x, x2 - x1);
  
        for (var c = 0; c < EditorSample.NumChans; c++) {
            var s = Math.abs(EditorSample.GetSampleAt(n, c));
            if (s > max) {
                max = s;
                pos = n;
            }
        }
    }
    return pos;
}

// src位置のサンプルのリバースをdst位置にペースト
function ReverseCopy(src, dst, length) {
    for (var n = 0; n <= length; n++) {
        if (n % 10000 == 0)
            ProgressMsg("Processing", n, length);

        var spos = src + n;
        var dpos = dst + length - n;
        for (var c = 0; c < EditorSample.NumChans; c++)
            EditorSample.SetSampleAt(dpos, c, EditorSample.GetSampleAt(spos, c));
    }
}

var x1 = 0;
var x2 = 0;
// 選択位置の取得（無選択時はサンプル全体が対象）
Editor.GetSelectionS(x1, x2);
// ピーク位置の検出
var peak = DetectPeak(x1, x2);
// サンプルが読み込まれていないとき誤動作を防止するための条件文
if (peak > 0) {
    // 最初からピーク位置まで削除
    EditorSample.DeleteFromTo(x1, peak);
    // 選択位置の再取得
    Editor.GetSelectionS(x1, x2);
    // 選択範囲と同じ長さの無音領域をインサート
    EditorSample.InsertSilence(x1, x2);
    // 無音領域に反転しながらペースト
    ReverseCopy(x2 + 1, x1, x2 - x1);
}
```

## スクリプトの使用方法

スクリプトを記述して以下の場所に保存します。拡張子は.edscriptとします。
(install folder)\Image-Line\FL Studio 20\Plugins\Fruity\Effects\Edison\Data\Scripts\User

Run Scriptアイコンから実行できます。

![ed3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/08126089-1570-9ddf-0e56-5a68e6aafdf5.png)

Scriptフォルダ以下には、メニューに表示されるすべての公式スクリプトも置いてあるので参考に見ることができます。公式スクリプトはほとんどがPascalで記述されています。


## 感想

仕様がコンパクトかつ機能呼び出しも細かい信号処理もできるという点では非常に良いのですが、言語がかなり旧世代的という古さが気になります。前述のとおりpaxCompilerは配布が終了し、公式サイトももうないようなので、今後このまま行くのかどこかでスクリプトエンジンを置き換えるのかも気になります。Edisonは枯れたソフトウェアなのでこのままでも良い気もしますが。
Edison自体も軽いのでAPIの使い方がある程度頭に入ると、ちょっとした処理を迅速に書けて便利だとは思います。

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