Audacity Script - DTMプログラミング言語探訪

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

# Audacity Script

## 概要

波形編集ソフトを使う作業は定型的な操作が多いため、多くのソフトは手順を自動化するためのバッチ機能を搭載しています。また、そのうちいくつかのソフトはテキスト言語でバッチ処理を書くスクリプト機能を持っています。
Audacityもバッチ処理とスクリプトの両方を持っているので機能や使用方法を説明します。[^1]
[^1]: Audacityがライセンスとユーザープライバシー保護問題でもめた結果、フォークされた[tenacity](https://github.com/tenacityteam/tenacity)が生まれましたが、こちらのスクリプト機能対応状況は未確認です。

## 用途

- 定型的手順の自動化
- 複数ファイルの一括処理

## バッチ処理

本題ではありませんが、スクリプトを使わないバッチ処理の手順も比較のために説明しておきます。

### バッチジョブ作成

Audacityのバッチジョブはマクロと呼ばれています。
Toolsメニュー → Macros... を選択してManage Macrosウィンドウを表示します。
![ac1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/44d1567c-160a-7c82-cf8f-d48ac32b8dd9.png)

Newボタンを押してマクロ名を設定します。
Insertボタンを押してエフェクトを追加していきます。
![ac2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/60e698d7-07c9-8c04-154a-c64b6a4a3710.png)
Edit Parametersボタンを押すとエフェクトのパラメータを設定することができます。
Manage MacrosウィンドウでSaveボタンを押すと作成したマクロが保存されます。

### バッチジョブ実行

現在開いているプロジェクトにマクロを適用するにはToolsメニュー → Apply Macroから実行します。

![ac4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/510f96d2-6cf5-0bc0-9796-c61a1a2ac5c4.png)

ファイルに対してマクロを適用するにはToolsメニュー → Macros... を選択してManage Macrosウィンドウを表示した後に、下部のFiles...ボタンからファイルを選択すると実行されます。このとき複数ファイルを選択可能です。
![ac3_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/d7290418-e685-1220-a675-25f48bfefea2.png)

## 言語仕様

Audacityのスクリプティング機能の実装は非常に独特です。マクロと同等のコマンドを受け付ける[名前付きパイプ](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BB%98%E3%81%8D%E3%83%91%E3%82%A4%E3%83%97)インタフェースが用意されているだけです。名前付きパイプで「CommandName: Param1=value1 Param2=value2」のような文字列を送信すればコマンド名で指定した処理を実行してくれます。そのため名前付きパイプが扱える言語であればなんでもAudacityを制御することができます。フォルダの探索はPythonなどの汎用言語で柔軟におこない、見つかったファイルを処理対象としてAudacityに渡すようなことができます。

また、名前付きパイプインタフェースは双方向なので、Audacityへ送ったコマンドの戻り値の情報を得てそれをもとにPython側で処理を変えるようなことも可能です。こういった柔軟性はマクロでは実現できないスクリプトの優位性だと思います。

- 名前付きパイプが扱える言語であれば使用可能
- Audacity API

[Audacity Scriptドキュメント](https://manual.audacityteam.org/man/scripting.html)
[Audacity Script APIリファレンス](https://manual.audacityteam.org/man/scripting_reference.html)


## プログラム例

ドキュメントのサンプルでもPythonが使用されているので、Pythonで例を示します。
指定フォルダにある全.wavファイルにノーマライズとトリム（前後の空白を削除）をおこなうスクリプトです。
スクリプトの前半は名前付きパイプを扱うための定型的な初期化、接続処理。それと送受信用の関数です。

「###～」以降がコマンド送信本体です。
フォルダ内の.wavファイル一覧を取得して全ファイルについてループで回しています。


```NormalizeTrim.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import glob

if sys.platform == 'win32':
    TONAME = '\\\\.\\pipe\\ToSrvPipe'
    FROMNAME = '\\\\.\\pipe\\FromSrvPipe'
    EOL = '\r\n\0'
else:
    TONAME = '/tmp/audacity_script_pipe.to.' + str(os.getuid())
    FROMNAME = '/tmp/audacity_script_pipe.from.' + str(os.getuid())
    EOL = '\n'

TOFILE = open(TONAME, 'w')
FROMFILE = open(FROMNAME, 'rt')

def send_command(command):
    TOFILE.write(command + EOL)
    TOFILE.flush()

def get_response():
    result = ''
    line = ''
    while True:
        result += line
        line = FROMFILE.readline()
        if line == '\n' and len(result) > 0:
            break
    return result

def do_command(command):
    send_command(command)
    response = get_response()
    return response

########################

in_folder = "D:\\work\\samples\\"
out_folder = "D:\\work\\processed\\"
files = "*.wav"

num_channel    = 2      # 1:mono 2:stereo
normalize_peak = -1.0   # dB
trim_level     = -80.0  # dB

files = glob.glob(in_folder + files)
for file in files:
    print(file)
    do_command('Import2: Filename="' + file + '"')
    do_command('Normalize: PeakLevel=' + str(normalize_peak))
    do_command('TruncateSilence: Threshold=' + str(trim_level) + ' Minimum=0.01 Truncate=0.0')
    do_command('Export2: Filename="' + out_folder + os.path.basename(file) + '" NumChannels=' + str(num_channel))
    do_command('TrackClose:')

TOFILE.close()
FROMFILE.close()
```

処理後の保存にはExport2コマンドを使用していますが、元の.wavファイルからサンプルレートや量子化ビット数を変更せずに保存する機能がないのは残念に思います。これはスクリプトというよりAudacityの設計ではありますが。

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

[公式ドキュメント](https://manual.audacityteam.org/man/scripting.html)にもあるとおり、名前付きパイプはセキュリティを弱める可能性があります。そのためデフォルトではスクリプティング機能は無効になっています。また、Windowsの場合、送信側プログラムも管理者権限で実行する必要があります。スクリプティングの効果とデメリットの両方をよく理解した上で使用するようにしてください。

### スクリプティング機能の有効化

Editメニュー → Preferences から設定ウィンドウを開きます。
左側のツリーでModulesを選択、mod-script-pipeをEnabledに変更します。
Audacityを一旦終了して、再度起動します。
なお、名前付きパイプで送受信するテキストもUIの言語設定の影響を受けるため、英語UIにしておいた方が文字コード関係のトラブルが少なくおすすめです。
![ac5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22455/b0cd9a86-193b-5474-c671-0c7a00ba4cdc.png)

### スクリプト作成

普通にテキストエディタでプログラムを書いて保存します。
APIは[公式リファレンス](https://manual.audacityteam.org/man/scripting_reference.html)を参照しつつ、マクロ作成画面でエフェクト名とパラメータ設定範囲を確認するのがおすすめです。

### スクリプト実行

Windowsの場合、管理者権限で実行しないと名前付きパイプにアクセスできません。
Windowsメニューの「Windowsシステムツール」→「コマンドプロンプト」を右クリックして「その他」→「管理者として実行」を選択してコマンドプロンプトを起動します。

Pythonの場合、以下のようにしてコマンドラインから実行します。

```
> python my_script.py
```

スクリプトのバグやアプリの状態によって名前付きパイプに接続できないことがあります。すべてのAudacityを一旦終了して数秒時間をおいてから再度起動してスクリプトを実行すると大抵の場合直ると思います。
終了したつもりでもタスクマネージャーから見ると死んだはずのAudacityプロセスが残っているかもしれません。どこかのプロセスが使用していると同名の名前付きパイプは新規に作成できないので要注意です。

## 感想

ドキュメントも比較的整備されていて、慣れたら使いやすいスクリプティング環境という印象です。個人的には、指定フォルダの一括処理などはマクロよりも使いやすいと思っています。

しかしながら、名前付きパイプという実装はあまりにも特殊で使いづらさを感じる人も多いと思います。
また、これは内蔵言語というよりRPA（Robotic Process Automation）のインタフェースに近く、それを名前付きパイプという古い技術で実現したというアンバランスさを少し感じます。

言語ではなく解析精度の問題ですが、トリム処理の空白検知はTruncate Silence、Silence Finder、Sound Finderなどの選択肢があるものの、どれを使っても音源によるばらつきが大きく、キックとハットのトリム後の空白の長さを完全に同じようにするのは難しい印象でした。今回はどちらかというとアタック検知精度のばらつきが少ないTruncate Silenceを使いました。
サンプラー音源制作などアタック時のタイミングがシビアな処理を一括で行う場合、スクリプトで作業は楽になるものの処理結果のチェックは必須だと感じました。


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