/*
 * setpeak.js
 * DC オフセットを修正した後、振幅の正規化をする.
 * 対象は開かれているファイル (Document オブジェクト).
 *
 * 例: 瞬間ピーク 1.0 で正規化するには (オプションなし)
 *   setpeak.js
 *
 * 例: 瞬間ピークが 1.0 を超えない条件下で rms を最大 -12 dB にするには
 *   setpeak.js /rms -12
 *
 * 例: rms を必ず -12 dB にするには
 *   setpeak.js /rms /force -12
 *
 * options:
 * /rms
 * /srms
 * /force
 */

@set @EXIT_SUCCESS = 0
@set @EXIT_FAILURE = 1

@set @DEFAULT_RMS_PEAK_DB = -12     // in dB

var Opt = new struct_options();

main();
exit();

// =======================================================================

// 文字端末にテキストを表示.

function echo(text)
{
    Script.Echo(text);
}

// スクリプトの終了関数. 引数は省略可.

function exit(exit_code)
{
    Script.Quit(exit_code);
}

// スクリプトの使用方法を表示.

function print_usage(error_msg)
{
    echo(Script.ScriptName);
    echo("  [peak_level] [/rms | /srms] [/force]");
    echo();
    echo("  peak_level  ピーク レベル指定 (dB)。既定は瞬間ピークなら 0,");
    echo("              rms なら " + @DEFAULT_RMS_PEAK_DB + "。");
    echo("  /rms        rms ピーク レベルを指定レベルにする。");
    echo("  /srms       局所 rms ピーク レベルを指定レベルにする。");
    echo("  /force      瞬間ピーク レベルが 1 を超えるのを容認する。");
    echo();
}

// コマンドラインオプションの既定値

function struct_options()
{
    this.Peak   = 1.0;          // 真数
    this.Rms    = false;
    this.SRms   = false;
    this.Force  = false;
}

// コマンドラインのオプションからグローバル変数 Opt.XXX を設定する.

function read_options()
{
    var args = Script.Arguments;

    // ヘルプオプションがあれば使用方法を表示して終了.

    if (args.Named.Exists("?")) {
        print_usage();
        exit(@EXIT_FAILURE);
    }

    // 名前付きオプションを調べる.
    var e, key;

    for (e = new Enumerator(args.Named); !e.atEnd(); e.moveNext()) {
        key = e.item().toLowerCase();

        // 瞬間値 / rms / 局所的 rms の種類
        if (key == "rms") {
            Opt.Rms  = true;
            Opt.SRms = false;
        }
        else if (key == "srms") {
            Opt.Rms  = false;
            Opt.SRms = true;
        }

        // 瞬間ピークが 1 を超えるのを容認する指定
        else if (key == "force")
            Opt.Force = true;

        else {
            echo("エラー: 不明なオプション " + e.item());
            exit(@EXIT_FAILURE);
        }
    }

    // ピークレベルの指定. コマンドラインでは dB.

    var peak_db = 0;
    if (Opt.Rms || Opt.SRms)
        peak_db = @DEFAULT_RMS_PEAK_DB;

    if (args.Unnamed.length) {
        if (args.Unnamed.length > 1) {
            echo("エラー: コマンド ライン オプションが多すぎます。");
            exit(@EXIT_FAILURE);
        }

        peak_db = parseFloat(args.Unnamed(0));
        if (isNaN(peak_db) || (peak_db > 0)) {
            echo("エラー: ピーク レベルは負数の dB で指定してください。");
            exit(@EXIT_FAILURE);
        }
    }
    Opt.Peak = Math.pow(10, peak_db / 20);      // 真数
}

function main()
{
    // コマンドライン読み取り
    read_options();

    var e, doc, wi;
    var ch, peak, gain, tmp;

    for (e = new Enumerator(Application.Documents); !e.atEnd(); e.moveNext()) {
        doc = e.item();
        echo(doc.Title);

        // 局所ピーク音量指定の場合はここでオフセット修正。2 パスになる.
        if (Opt.SRms)
            doc.AddOffset();

        // 波形全体を選択
        doc.SelectionFrom = 0;
        doc.SelectionTo = doc.Length;

        // 統計情報取得
        wi = doc.GetWaveformInfo();

        // DC オフセット修正後の最大サンプルを求める (abs).
        peak = 0;
        for (ch = 0; ch < doc.Channels; ++ch) {
            tmp = Math.abs(wi("InstantMax", ch) - wi("Offset", ch));
            if (peak < tmp)
                peak = tmp;
            tmp = Math.abs(wi("InstantMin", ch) - wi("Offset", ch));
            if (peak < tmp)
                peak = tmp;
        }

        // 振幅変更の前にオフセットを修正.
        if (!Opt.SRms)
            doc.AddOffset(-wi("Offset", 0), -wi("Offset", 1));

        // 波形が非常に小さい場合は無音と見て振幅の変更はしない.
        if (peak < 1e-5)
            continue;

        if (Opt.Rms) {      // 全体の rms
            tmp = 0;
            for (ch = 0; ch < doc.Channels; ++ch)
                tmp += wi("Variance", ch);
            gain = Opt.Peak / Math.sqrt(tmp / doc.Channels);
        }
        else if (Opt.SRms)      // 局所 rms ピーク
            gain = Opt.Peak / wi("ShortRmsPeak");
        else    // 瞬間ピーク
            gain = Opt.Peak / peak;

        // force オプション非指定時は波形がクリップされない範囲に抑える.
        if (!Opt.Force) {
            if (peak * gain > 1)
                gain = 1 / peak;
        }

        // 振幅変更. 引数は真数での倍率 (dB でない).
        doc.AddGain(gain);
    }

    exit(@EXIT_SUCCESS);
}