Audacity
Audacity は多くのユーザーを持つサウンドレコーダ・エディタであり、機能は豊富でありながらも操作は簡単です。ユーザーの多くは Windows ですが、ソースコードは Linux と Mac でもコンパイル・実行できます。
Audacity のオリジナルのバージョンは当時カーネギーメロン大学の研究生だった Dominic Mazzoni によって 1999 年に書かれました。もともとは音声処理アルゴリズムを開発・デバッグするためのプラットフォームとして開発されましたが、現在の Audacity はそれ以外の様々な用途にも使うことができます。
オープンソースソフトウェアとしてリリースされて以来、Audacity は開発者を惹き付けてきました。熱心な開発者からなる小さなチームは少しずつメンバーを変えながら、Audacity を改良し、保守し、テストし、アップデートし、ドキュメントを書き、ユーザーを助け、インターフェースを他の言語に翻訳してきました。
Audacity の目標の一つは、ユーザーインターフェースを発見可能 (discoverable) に保つことです。つまり、どんな人が Audacity を使ったとしても、マニュアルを全く読まずに使い始めることができ、使っている間に機能を見つけていけるということです。この原則は Audacity のユーザーインターフェースを一貫したものに保つために重要な役割を担っています。たくさんの人が開発に関わる Audacity のようなプロジェクトでは、このような統一された原則を持つことは思いのほか重要です。
Audacity のコードのアーキテクチャにも発見可能性と似たような原則があればよいのですが、残念ながらありません。ただ近いものとして、“やってみて、周りと合わせる”というのがあります。つまり、開発者が新しいコードを追加するときには、周りのコードのスタイルと慣習に従うのです。この原則の結果とも言えるのですが、Audacity の実際のコードには上手く構造化された部分とそうでもない部分が両方含まれています。そのためソフトウェア全体を建築として例えるよりは、小さな都市として例えた方が正確かもしれません: 素晴らしい建物もいくつかありますが、スラム街のような荒れた地区もあります。
Audacity の構造
Audacity はいくつかのライブラリが積み重なってできています。Audacity に新しいコードを追加するのに全てのライブラリの動作を正確に知っておく必要はありませんが、API に慣れておくことは重要です。最も重要な二つのライブラリは、クロスプラットフォームの低レベルオーディオインターフェースである PortAudio と、クロスプラットフォームの GUI コンポーネントを提供する wxWidgets です。
Audacity のコードを読むときには、本質的に重要なのはコードの一部分だけであることを意識するとよいでしょう。ライブラリにはオプショナルな機能 (その機能を使う人は、“オプショナル”だなどとは考えないかもしれませんが!) がたくさん含まれているのです。このような機能の例としては、Audacity が組み込みのオーディオエフェクトとは別にサポートする LADSPA (Linux Audio Developer's Simple Plugin API) があり、これを使うとオーディオエフェクトのプラグインを動的に読み込むことができるようになります。また Audacity の VAMP API もオーディオを解析するプラグインに対して同じことをします。このような API が利用できない場合には使える機能が減りますが、これが無いと Audacity が動作しないわけではありません。
Audacity が使うオプショナルなライブラリの例としては、オーディオの圧縮フォーマットに関する機能を提供する libFLAC, libogg, libvorbis があります。また MP3 フォーマットは LAME または FFmpeg ライブラリを動的に読み込むことで対応しています。これらの圧縮フォーマットが非常によく使われているにもかかわらずライブラリを組み込みにできないのは、ライセンス上の制約があるためです。
ライセンスの問題は Audacity のライブラリと構造の他の部分にも影響を及ぼしています。例えば、VST プラグインはライセンスの問題からサポートされていません。またとても高速なことで知られる FFTW 高速フーリエ変換コードを Audacity のいくつかの部分で使うことができるのですが、私たちにできるのは Audacity を自分でコンパイルできるユーザーに FFTW を使えるようにするオプションを用意することだけであり、通常の環境では低速な高速フーリエ変換コードが使われます。FFTW の作者たちは自身のコードが任意のコードから利用されうる一般的なサービスの一部として使われることを望んでいないために、Audacity がプラグインという仕組みを取っている限り、Audacity は FFTW を使うことができない (可能性があり、そうだとして開発が続いてきた) のです。つまり、プラグイン機能をサポートするというアーキテクチャ上の判断の結果、配布できるプログラムには制限がかかったということです。プラグイン機能によって LADSPA プラグインが使えるようになりますが、配布される実行形式に FFTW を組み込むことはできません。
貴重な開発者の時間をどうしたら一番上手く使えるかという観点からも、アーキテクチャは形作られました。私たちのような開発者の数が少ないチームでは、リソースは限られてきます。例えば Firefox や Thunderbird を開発するチームが行っているような、プログラムのセキュリティホールの詳細な解析は不可能です。そうは言っても Audacity がファイアウォールをバイパスできる経路を含んでいてはまずいので、Audacity は TCP/IP 接続を外向きにも内向きにも利用できないことにしています。これによってセキュリティ上の懸案事項を大きく減らせます。ここではリソースが限られていることを認識することで、より良い設計にたどり着くことができました。さらに開発者の時間を食ってしまう機能を切れば、開発者は本質的な機能に集中できます。
開発者の時間に関する似た議論はスクリプト言語についても成り立ちます。スクリプトを行いたいのは事実なのですが、Audacity に言語の実装を含む必要はありません。ユーザーが使いたい全ての言語の実装をコンパイルして Audacity に組み込むのは非現実的だからです1。そうする代わりに、スクリプトは一つのプラグインモジュールとパイプを使ってスクリプトを実装されます。これについては後述します。
図 2.1 に Audacity のレイヤーとモジュールを示します。図には wxWidgets に含まれる三つの重要なクラスが示されており、これらに対応するクラスが Audacity にも含まれます。高レベルな抽象モジュールは、対応する低レベルなライブラリから作られます。例えば BlockFiles システムは wxWidgets の wxFiles から作られています。どこかの段階で、BlockFiles, ShuttleGUI, コマンドハンドリングを中間ライブラリとして切り出した方が良いかもしれません。そうすれば、ライブラリがより汎用的になります。
図の下部には薄い“プラットフォーム固有の実装レイヤー”があります。wxWidgets と PortAudio はどちらも OS を抽象化するレイヤーなので、ターゲットのプラットフォームに合わせて異なる実装が使われます。
“その他の補助ライブラリ” には様々なライブラリが含まれます。興味深いことに、その多くが動的にロードされるモジュールに依存しており、このようなモジュールは wxWidgets について何も知らずに動作します。
かつては Windows プラットフォームで Audacity をコンパイルすると、wxWidgets と Audacity を含んだモノリシックな実行形式が作成されました。しかし 2008 年にプログラムの構造が変更され、wxWidgets は Audacity 本体とは切り離された DLL として扱われるようになりました。これによってプログラムの汎用性が増し、追加のオプショナルな DLL を実行時に読み込み、さらにその DLL が wxWidgets を直接使うことが可能になりました。図中で破線よりも上にあるプラグインは wxWidgets を使うことができます。
wxWidgets を DLL として扱う決断には欠点もあります。例えば、モノリシックにビルドしていれば最適化によって取り除かれていたであろう未使用の関数は DLL を使った場合には取り除くことができず、これによって配布するプログラムの全体容量が大きくなります。また Audacity は DLL を別々に読み込むので、起動時間も長くなります。
しかしこの決断には豊富な利点があります。私たちは、Apache のモジュールが持つような柔軟性と利便性を Audacity のモジュールにも持たせたいと考えています。Apache のモジュールは Apache のコアをとても安定なものにすると同時に、新しいモジュールを試したり、特殊な機能や新しいアイデアの実装を容易にしていると私たちは考えていて、Audacity のモジュールでも同じことを実現しようと思っています。加えてモジュールはプロジェクトをフォークして新しく始める誘惑を振り払うのにとても役立っており、これはとても重要なアーキテクチャの変更だと考えています。期待されていた利点はまだ確認できていませんが、wxWidgets 関数をプラグインから使えるようにすることは柔軟でモジュール化されたシステムに向けた最初の一歩に過ぎません。
Audacity のようなプログラムの構造は、前持って設計されていたわけでは決してありません。プログラムの構造は時間をかけて開発されるものです。長年の開発の結果、全体的に見て、Audacity のアーキテクチャはうまく動いていると私たちは考えています。アーキテクチャについて必死になって考えるのは、たくさんのソースファイルに影響する機能を追加するときです。例えば、現在の Audacity はステレオトラックとモノトラックを特殊ケースとして扱っていますが、もし Audacity にサラウンドのサポートを追加するとしたら、たくさんのクラスを変更しなければならないでしょう。
GetLink
の話
Audacity はチャンネルの数を抽象化しません。その代わり、オーディオチャンネルを一列に並べる抽象化をしています。 GetLink
という関数はチャンネルが二つあるならもう一方のチャンネルを返し、モノラルならば NULL を返します。 GetLink
を使うコードは最初モノラルで動くように書かれ、その後 (GetLink() != NULL)
というテストを追加することでステレオに対応させたように見えます。本当にそうなのかは知りませんが、おそらくそうだと私は思っています。そしてこのために、連結リストに収められた全てのチャンネルを訪れるループは GetLink
を使って書くことができません。そのため描画、ミキシング、読み込み、書き出しの処理にはステレオであるかどうかのテストが含まれていて、n 個のチャンネルを一般的に扱えるコードにはなっていません。n の値はほとんどの場合 1 か 2 なので大きな問題ではありませんが、もしコードを一般的なものにするとなると、少なくとも 26 個のファイルに及ぶ 100 回近くの GetLink
関数の呼び出しを変更しなければなりません。
ただ実際のところ GetLink
が呼び出されている場所を検索するのは簡単で、必要となる変更もそれほど複雑ではありません。そのためこの“問題”は一見したよりも簡単に解決できます。この GetLink
の話は構造的な欠陥の直し辛さの話というよりも、比較的小さな欠陥であっても条件さえ整えばコードのそこら中に散らばってしまうという話といった方がいいでしょう。
今考えれば、 GetLink
関数はプライベートにして、全てのチャンネルを反復するイテレータを提供した方が良かったと思います。こうすればステレオに関する特殊ケースを書かずに済み、さらにチャンネルのリストを使うコードはリスト構造の実装について知る必要もありません。
このように設計のモジュール化が進むと、開発者は内部の構造を上手く隠すようになります。外部 API を定義、拡張するときにはその関数について詳細に考える必要があり、結果として外部 API を固定してしまうような抽象化は避けられるようになります。
wxWidgets GUI ライブラリ
wxWidgets GUI ライブラリ は Audacity のユーザーインターフェースを作るプログラマーにとって一番重要な単一のライブラリです。このライブラリはボタン、スライダー、チェックボックス、ウィンドウ、ダイアログなどの、Audacity の目に見える部分をクロスプラットフォームな形で提供します。wxWidgets はオペレーティングシステムによって提供される GUI オブジェクトの上に敷かれた、比較的薄いレイヤーです。wxWidgets ライブラリには固有の文字列クラス wxString
があり、他にもスレッド、ファイルシステム、フォントに対するクロスプラットフォームな抽象化が提供されます。さらに他言語への翻訳のための機構もあり、Audacity はこれら全てを使っています。Audacity の開発に新しく加わろうとする人には、まず wxWidgets をダウンロード・コンパイルしてみて、いくつかのサンプルをいじってみることをお勧めしています。
複雑なダイアログを作るために、wxWidgets は個別のウィジェットだけではなくそれらのサイズと位置を調整するためのサイザー (sizer) も提供しています。サイザーを使うと描画要素の絶対位置を指定するよりもはるかにクオリティの高い見た目になります。例えばユーザーがウィンドウのサイズを変更したとき、あるいはフォントのサイズがユーザーによって異なるときにも、ダイアログは自然な形で変形されます。サイザーはクロスプラットフォームのアプリケーションでとても重要であり、これが無ければプラットフォームごとに個別のレイアウトを用意することになっていたでしょう。
ダイアログのデザインをリソースファイルで指定して実行時にプログラムが読み込むようにもできます。しかし Audacity では、サイザーと wxWidgets の関数の呼び出しを含んだプログラムを使って全てのダイアログのデザインをコンパイル時に確定させています。こうすることで柔軟性が最大になります: ダイアログの正確な内容と動作をアプリケーションレベルのコードが決められるようになるからです。
かつての Audacity のコードには、GUI ダイアログ生成ツールを使って作られたことが明らかな部分が含まれていました。そのようなツールは基本的なデザインを作るのには役立ちましたが、時間がたつにつれコードはハックされ、新しい機能が追加され、さらにそのコードがあちこちにコピペされました。
長い間そのような開発を続けてきた結果、Audacity のソースコードの大部分、中でも特にユーザー設定のためのダイアログのコードは、似た処理が続く入り組んだコードとなってしまいました。そういったコードがしている処理は単純なのですが、読むのが驚くほど困難です。この問題の原因の一つは、ダイアログの作られる順序が定まっていないことにありました。つまり、小さい要素から大きい要素が作られ、最終的に全体のダイアログとなるのですが、要素が作られる順番はスクリーンに表示される順番と同じではなかった (そうである必要もなかった) のです。そのようなコードは冗長であり、似たような処理が増えてしまいます。例えば GUI に関連したコードで、ディスクに保存された設定を一時変数に読み込むコード、GUI から設定を一時変数に保存するコード、一時変数にある設定をディスクに保存するコードが別々に存在していました。このコードには // this is a mess
(これはめちゃくちゃだ) というコメントが付いており、ずいぶん長い間編集されていませんでした。
ShuttleGui レイヤー
そのようなコードを単純にするために新しいクラス ShuttleGUI が作られました。結果としてダイアログを指定するためのコードの行数が格段に減少し、コードはより読みやすくなりました。ShuttleGUI は wxWidgets ライブラリと Audacity の間にある追加のレイヤーであり、両者の間の情報をやり取りします。次に示すのは、図 2.2 のダイアログを作るためのコードです。
ShuttleGui S;
// GUI Structure
S.StartStatic("Some Title",...);
{
S.AddButton("Some Button",...);
S.TieCheckbox("Some Checkbox",...);
}
S.EndStatic();
このコードはダイアログの中にボタンとチェックボックスを含んだスタティックボックスを定義します。コードとダイアログの間の対応関係は明確なはずです。 StartStatic
と EndStatic
が組になっていますが、この他にも StartSomething
と EndSomething
が組になっている関数がいくつか存在します。中央の中括弧は、この部分が無くてもコードは正しく動くことを示します。この慣習は組になるべき関数呼び出しが組になっていることを分かりやすくするために導入されました。コードが大きくなってくると、この中括弧によって可読性が大きく向上します。
示したコードではダイアログの作成以外のことも行えます。 //GUI Structure
の後のコードでは、ダイアログとユーザー設定の間でデータを行ったり来たり (shuttle) させることができます。以前は同じことを行うのに似たようなコードを大量に書く必要がありましたが、現在では共通部分が ShuttleGUI
クラスに一度だけ書かれるようになっています。
Audacity には他にも wxWidgets の基本ウィジェットを拡張しています。例えばツールバーを管理するための独自のクラスがあります。なぜ wxWidgets 組み込みのツールバークラスを使わないのか? そこには歴史的な理由があります: Audacity のツールバーは wxWidgets がツールバークラスをサポートするよりも前に書かれたのです。
TrackPanel
Audacity のメインパネルでオーディオの波形を表示している部分は TrackPanel と呼ばれ、Audacity によって細かな制御を受けます。内部にはパネルがいくつもあり、例えばトラック情報、時間の目盛り、振幅の目盛り、波形やスペクトルあるいはテキストラベルを表示するトラックなどがあります。テキストラベルを含むトラックは wxWidgets 組み込みのテキストボックスではなく、私たちが再実装したものです。パネル、トラック、目盛りは wxWidgets のものを使った方がいいと思うかもしれませんが、そうはなっていません。
図 2.3 に Audacity のユーザーインターフェースを示します。ラベルの付いている部分は Audacity によって直接管理され、wxWidgets からは TrackPanel 全体が一つのコンポーネントとして見えます。TrackPanel 内の要素の配置や描画を受け持つのは wxWidgets ではなくて Audacity のコードです。
TrackPanel のコンポーネントを組み合わせるコードは本当にひどいものです (ひどいのはコードであって、ユーザーに見える最終結果はまともですが)。GUI のコードとアプリケーションに固有のコードが混じっており、綺麗に分離されていません。良い設計では、アプリケーションごとに異なるコードだけが、左右のチャンネル、デシベル、ミュート、ソロなどについて知っているべきで、GUI 要素はオーディオ以外のアプリケーションでも再利用できるようなものであるべきです。TrackPanel の GUI 部分だけを考えてみたとしても、コードには絶対位置や絶対サイズを使った特殊ケースを処理するためのパッチワークがあちこちに含まれており、十分に抽象化されているとは言えません。これらの特殊なコンポーネントを一つの GUI 要素として扱って、さらに wxWidgets のサイザーのようなインターフェースを使っていたら、TrackPanel は今よりもはるかに素敵で、綺麗で、一貫したものになっていたでしょう。
そのような TrackPanel を本当に実装するとなると、wxWidgets コンポーネントに対する新しいサイザーであってトラックをはじめとした全てのウィジェットを移動・リサイズできるようなものが必要になるのですが、wxWidgets のサイザーはこれができるほど柔軟ではありません。もしそのようなサイザーを自分たちで作ることができれば、他の所でも使うことができます。例えばツールバーにボタンを持たせて、ボタンの順序をドラッグで変えられるようにできます。
このサイザーを作るための実験がいくらか行われましたが、十分な結果が出ていません。GUI コンポーネントを完全に独立した wxWidgets コンポーネントとすることも試されましたが、問題にぶつかりました。このようにするとウィジェットの再描画を細かく制御できず、コンポーネントをリサイズしたときや移動させたときにちらつきが発生してしまうのです。ちらついた部分の再描画を行うためには、wxWidgets を大幅に変更して、リサイズのステップと再描画のステップを上手く切り離すしかありません。
このアプローチについて慎重にならなければならないもう一つの理由は、私たちは経験から知っているのですが、ウィジェットの数が多くなると wxWidgets の実行がとても遅くなるからです。これは wxWidgets の外側からどうにかできる問題ではありません。wxWidgets のボタンやテキストボックスといったウィジェットはどれもウィンドウシステムのリソースを利用し、リソースにアクセスするためのハンドルも持っているので、数が多くなると処理に時間がかかるのです。ウィジェットの大部分が隠れていたり、スクリーン上で見えていない場合でさえ処理は遅くなります。そのため TrackPanel のウィジェットは少ない方が望ましいのです。
この問題に対する一番の解法は flyweight パターンを使うことです。つまり、軽量なウィジェットには描画を自分でやってもらい、ウィンドウシステムのリソースやハンドルを消費するオブジェクトを作らないようにする方法です。wxWidgets のサイザーやコンポーネントウィジェットのような構造を作り、コンポーネントを提供する wxWidgets に似た API を作れば wxWidgets のクラスを継承しないで済みます。今ある TrackPanel のリファクタを通した構造の改善もできるかもしれせんが、もし簡単にできるのであれば既に行われているでしょう。つまり、最終的に得たいコードの形が人によって異なるために、今までに述べてきた計画は頓挫しているのです。現在のアドホックなアプローチを一般化するのは、設計的にもコーディング的にも並大抵の仕事ではありません。さらに言えば、現在特に問題なく動いている複雑なコードはそのままにしておいた方が良いのではないかという、抗いがたい誘惑もあります。
PortAudio ライブラリ: 録音と再生
PortAudio はクロスプラットフォームのオーディオライブラリであり、Audacity ではオーディオの録音と再生に使われています。このライブラリが無ければ Audacity はデバイスのサウンドカードを使うことができないでしょう。PortAudio はリングバッファ、録音/再生時のサンプルレート変換を提供し、さらに重要なこととして、Max, Linux, Windows の間の API の違いを吸収します。PortAudio の中を見れば、各プラットフォームに対する API の実装があります。
私自身、PortAudio で何が起こっているのかを理解するために内部のコードを覗いてみたことはありません。しかしそれでも、PortAudio とのインターフェースについては知っておいたほうがよいでしょう。Audacity は PortAudio からの録音用データパケットを受け取り、再生用データパケットを PortAudio に送ります。このデータのやり取りが正確にどのように起こるのか、そしてディスクからの読み込み・書き込みやスクリーンのアップデートとどのように協調するかについてこれから説明します。
Audacity では、いくつかの異なる処理を同時に行う必要があります。少量のデータを頻繁に素早く転送する処理もあれば、たまにゆっくりと大量のデータを転送する処理もあります。つまりプロセスの間ではインピーダンスミスマッチが起きており、バッファを使ってこれを解消しなければならないのです。また処理するデータを送受信するのはオーディオデバイス、ハードディスク、スクリーンです。こういったハードウェアを回線レベルで制御することはできず、利用可能な API を使わなければなりません。全てを wxThread から実行するなどの方法でプロセスを統一的に管理できればうれしいのですが、そうする余裕はありません (図 2.4)。
オーディオスレッドは PortAudio のコードによって起動され、オーディオデバイスと直接やり取りをします。このやり取りによって実際の録音と再生が行われます。このスレッドがきちんとパケットを処理しなければ、パケットは録音・再生されることなく失われてしまいます。録音をしている間、PortAudio が管理するこのスレッドは audacityAudioCallback
関数を実行し、サウンドデバイスからの小さなパケットを大きな (五秒間分の) キャプチャバッファに保存します。PortAudio ライブラリは wxWidgets とは一切関わらずに動作するので、PortAudio によって作られるオーディオスレッドは pthread です。
二つ目のスレッドは Audacity の AudioIO クラスによって起動されます。録音をしている間、AudioIO はキャプチャバッファからデータを取ってそれを Audacity のトラックに付け加えます。これによって画面に録音された音声が表示されるようになります。また AudioIO は十分なデータが集まった段階でディスクへの書き込みも行います。さらに再生のためにディスクからデータを読むのもこのスレッドです。このスレッドの処理で鍵となっているのは AudioIO::FillBuffers
関数であり、この関数は真偽値変数の値に応じて録音と再生の両方を一つの関数で行います。両方行うというのがポイントで、ちょうどいま録音されたデータを再度別の場所にコピーするという、ソフトウェアの世界における“再生”処理はこの関数で同時に行われます。AudioIO スレッドにおいては OS のディスク IO が頻繁に起こるので、任意の長さのストールが起こる可能性があります。そのためこのスレッドが行うデータの読み書きを、俊敏な反応を求められる audacityAudioCallback
関数の中で行うことはできません。
二つのスレッドの間の通信は共有変数を通して行われます。どちらのスレッドがいつ共有変数に書き込むかは Audacity によって制御されており、コストの高いミューテックスは不必要です。
録音と再生の両方において、Audacity は GUI の更新も行わなくてはなりません。GUI の処理は時間の制約が最も緩い処理です。そのため GUI の更新は GUI のメインスレッドで行われ、その制御に使われるのは一秒間に 20 回という頻度でティックするタイマーです。タイマーのティックによって TrackPanel::OnTimer
が呼ばれ、更新が必要な GUI 要素が検索・更新されます。GUI スレッドを起動するのは Audacity のコードではなく wxWidgets のコードであるため、他のスレッドは GUI の要素を直接更新できません。GUI スレッドによる画面を更新をタイマーで制御することで、再描画の回数を遅延が感じられない程度まで抑えつつも、描画のための処理時間を短く抑えています。
オーディオデバイス用のスレッド、バッファ/ディスク用のスレッド、タイマー付きの GUI 用のスレッドという三つのスレッド使ってオーディオデータを転送するのは、はたして良い設計なのでしょうか? 三つのスレッドが共通の抽象基底クラスから派生していないというのは多少アドホックな感じがしますが、こうなったのは主に使っているライブラリが原因なので、仕方のない面があります。バッファを埋めるスレッドが必要なのは、オーディオデバイスからの頻度の高い小さなパケットと、ディスクドライブへの頻度の小さな大きなパケットの間のインピーダンスミスマッチを解消するためです。
ライブラリを使うことはとても便利ですが、その代償としてライブラリの持つ抽象化を使うことを余儀なくされます。結果として、メモリ上でデータをコピーする回数が理想よりも多くなってしまいます。私が目にしたことがある高速なデータスイッチでは、スレッドを一切使うことなく割り込みを使ってこの種のインピーダンスミスマッチを解消する、とても効率の良いコードが使用されていました。このコードではデータをコピーする代わりにバッファへのポインタがやり取りされていたのですが、このようなことができるのはライブラリがバッファというより精密な抽象化を使って設計されているときに限られます。今あるインターフェースを使わなければいけない以上、Audacity のコードにおけるデータのコピーは避けられません。
BlockFiles
Audacity が直面した課題の一つとして、長さが数時間に及ぶ可能性のある録音されたオーディオに対する挿入・削除があります。録音は簡単に利用可能な RAM よりも大きくなってしまうのです。もし録音されたオーディオがディスク上の一つのファイルに保存されていた場合、最初の方にオーディオを挿入するという処理を行うには、それより後ろの多くのデータを動かさなければなりません。ディスク上のデータのコピーは時間のかかる処理であり、Audacity は単純な編集処理でさえ高速に反応できなくなります。
Audacity はこの問題を解決するために、オーディオファイルを複数の BlockFile に分割しています。ここで各 BlockFile のサイズは 1 MB 程度です。Audacity には BlockFile を束ねるための独自のオーディオファイルフォーマット .aup
があり、実体は BlockFile への参照を並べた XML ファイルです。こうすることで、長いオーディオの録音の最初の部分を編集したとしても、変更されるのはマスターの .aup
ファイルの一ブロックだけで済みます。
BlockFile は衝突しあう二つのことのバランスを取っています。つまり、オーディオに対して挿入・削除をしても大きなデータのコピーが行われることはなく、さらにディスクに対してデータを読み書きするときにはそれなりに大きなチャンクを通して行われることが保証されているのです。ブロックのサイズが小さければ、それだけ同じ大きさのオーディオデータをフェッチするのに必要となるディスクへのリクエストの回数が増え、ブロックのサイズが大きければ、それだけ挿入や削除を行ったときにコピーされるデータの量が増えます。
Audacity の BlockFile は実際に使われるデータとは別に空き空間を持つことはなく、最大ブロックサイズを超えてデータを保持することもありません。そのため、オーディオの挿入・削除を行うと最大で BlockFile 一つ分のデータのコピーが発生します。必要なくなった BlockFile は削除されますが、BlockFile は参照カウントを使って管理され、undo 機能はオーディオに対しても有効なので、オーディオを削除しても対応する BlockFile は完全に削除されません。ファイルを保存するときに初めて削除されます。BlockFile の空き容量に対するガベージコレクトは決して必要になりません。もし全てを一つのファイルに収めるアプローチをとっていたら、ガベージコレクトが必要になっていたでしょう。
大きなデータチャンクをマージしたり分割したりする処理は、データ管理システムにおいて最も基本的な処理です。このときに使われるデータ構造には B-木やグーグルの BigTable におけるタブレット、unrolled linked list などがあります。オーディオの先頭に近い部分を削除するときに何が起こるかを 図 2.5 に示します。
BlockFile は生のオーディオデータに対してだけ使われるわけではなく、オーディオの要約データをキャッシュするときにも使われます。例えば四時間の録音を画面に表示するとき、画面を切り替えるたびにオーディオ全体を処理するというのは受け入れられません。その代わりある範囲における最大・最小振幅を保持する要約データが使われます。ズームインしたときには実際のサンプルが読み込まれ、ズームアウトしたときには要約データが使われるのです。
BlockFile システムの優れている点は、ファイルが Audacity によって作られるものでなくてもよく、 .wav
フォーマットで保存されているファイルなどへの参照であっても構わない点です。ユーザーが Audacity プロジェクトを作成し、 .wav
ファイルをインポートし、トラックをいくつかミックスしたとしても、BlockFile は要約データに関するものしか作成されません。これによってディスクの空間とオーディオをコピーする時間が節約できます。ただし、実際にこうするのは良いアイデアではなく、どちらかと言えば悪いアイデアでした。なぜなら、実際にこうした結果あまりにも多くのユーザーがオリジナルのオーディオが入った .wav
ファイルを削除してしまったからです。ユーザーはオーディオファイルがプロジェクトフォルダにコピーされただろうと思って削除をするのですが、そうすると .wav
を再生できなくなってしまいます。そのため現在の Audacity では、オーディオをインポートするときにそのファイルを必ずコピーして新しい BlockFile を作ることがデフォルトとなっています。
BlockFile がぶつかった問題として、Windows において BlockFile の数が莫大になったときにパフォーマンスが極端に低下するというのがあります。どうやらこの原因は、ウィジェットの数が増えたときに処理が遅くなる問題と同じように、フォルダの中のファイル数がとても多くなると Windows による処理が一気に遅くなることにあるようです。この問題は後に、サブディレクトリの階層を使って一つのフォルダに含まれるファイルの数を 100 個以下とすることで解消されました。
BlockFile という構造に関する一番の問題はこのデータがエンドユーザーに見えている点です。 .aup
ファイルを移動したときには全ての BlockFile を含んだフォルダも移動させなくてはならないことに気づいていないユーザーをよく目にします。Audacity のプロジェクトファイルを一つの大きなファイルにして、その読み方は Audacity だけが知っているようにした方が良かったでしょう。どちらかと言えば、これによってパフォーマンスは向上するはずです。この機能を実装するときに必要となるであろうコードで一番複雑なのはガベージコレクションのためのコードです。BlockFile に含まれるデータの未使用の割合がある値よりも大きいくなったら使われているデータだけを新しいブロックにデータを移動させるという方法が一番単純でしょう。
スクリプト
Audacity にはスクリプト言語をサポートするための実験的プラグインがあります。このプラグインは名前付きパイプを使ったスクリプトインターフェースを提供します。スクリプトを使って呼ぶことのできるコマンドは、その名前の通り、テキスト形式で公開されます。スクリプト言語が名前付きパイプを使ったテキストの読み書きさえサポートしていれば、ユーザーはそのスクリプト言語を使って Audacity を制御できます。オーディオなどの容量の大きいデータはパイプを通さないやり取りもできます (図 2.6)。
プラグイン自体はやり取りされるテキストの内容について一切関知せず、ただテキストを受け渡すだけです。スクリプトプラグインが使うプラグインインターフェース (および拡張のための基本的な機能) が、Audacity のコマンドをテキスト形式で公開しています。そのためスクリプトプラグインは小さく、パイプの管理のためのコードが大部分を占めます。
残念なことに、パイプを使うと TCP/IP 接続と同じようなセキュリティ上のリスクが生じます ──TCP/IP はセキュリティを考慮して使わないようにしているのに。スクリプトプラグインがオプショナルな DLL として提供されているのは、このリスクを低減するためです。プラグインの入手・使用はよく考えてから行わなければならず、使用時にはセキュリティの警告が表示されます。
スクリプト機能が実装された後、wiki の機能リクエストページに、TCP/IP を使ったプロセス間通信メカニズムを KDE の D-Bus という規格を使って実装してはどうかという提案がありました。これとは違う方法が現在は取られていますが、最終的に D-Bus をサポートすることになるというのも全くあり得ない話ではありません。
スクリプトの機能は熱心なプログラマーがある目的のために Audacity を改造していった結果として生まれたものであり、改造された Audacity はフォークになりかけていました。この機能はキリスト教の説教を mp3 に変換するために作られたので、CleanSpeech と呼ばれます。CleanSpeech にはオーディオから長い間音が無い部分を探して削除するエフェクトや、決められたノイズ除去処理を行う機能、オーディオ全体に対する正規化と mp3 への変換機能が備わっていました。
私たちは CleanSpeech の素晴らしい機能をいくつか取り入れたいと思っていたのですが、書かれ方が Audacity あまりにも違っていたのでそのまま取り入れるわけにもいきませんでした。CleanSpeech をメインストリームの Audacity 取り入れると、固定された処理列ではなく自由な処理列がプログラムできるようになってしまうのです。ルックアップテーブルからコマンドの名前で引くことで任意のエフェクトを使うことができ、Shuttle クラスを使ってエフェクトへの引数をユーザー設定の中にテキスト形式で埋め込むことができました。この機能はバッチチェイン (batch chain) と呼ばれます。バッチチェインを Audacity に組み込むときには、アドホックなスクリプト言語を作ってしまわないように、条件文や計算式は意図的に除かれました。
今考えると、フォークを避けるための努力は無駄ではありませんでした。CleanSpeech モードは現在の Audacity にも埋め込まれており、設定を変更することで有効にできます。CleanSpeech を取り込んだ結果としてユーザーインターフェースは単純化され、高度な機能は削除されました。こうして単純化されたバージョンの Audacity を学校を始めとする他の用途でも使いたいという要望が聞かれます。
ユーザーインターフェースを単純化するときに問題となるのが、どの機能が“高度”でどの機能が“基本的”なのかが人によって異なる点です。後になって翻訳のための機構がハックされ、メニューのアイテムの翻訳文が“#”で始まるときにはそのアイテムがメニューに表示されないようになりました。こうすればユーザーは再コンパイルをせずにメニューの項目数を減らせます ──Audacity 本体の mCleanspeech
変数よりも柔軟で、他の部分への影響も少ないです。 mCleanspeech
はいつか完全に削除できるようになるかもしれません。
CleanSpeech を取り込んだことでバッチチェインと無音を削除する機能が手に入りましたが、これらはコアチーム外からのさらなる改善を引き付けました。バッチチェインはスクリプト機能となり、さらに汎用プラグインのサポートへとつながったのです。
リアルタイムエフェクト
Audacity にはリアルタイムエフェクトがありません。つまり、オーディオが再生されるのと同じタイミングでエフェクトをかけることはできません。Audacity がオーディオにエフェクトをかけるときには処理が全て終了するまで待つ必要があります。リアルタイムエフェクトの実装とユーザーインターフェースをフリーズさせないオーディオエフェクトの適用は、Audacity に対して最も頻繁に要望される機能です。
問題となるのは、あるマシンでリアルタイムエフェクトとなるエフェクトであっても、遅いマシンではリアルタイムにならないことです。Audacity は様々なマシンで実行されるので、きちんとしたフォールバックを準備しておきたいのです。遅いマシンでトラック全体に対してエフェクトを適用した場合には、ユーザーがカーソルを置いた場所からエフェクトの処理が始まり、小さな待ち時間でエフェクトのかかったオーディオを聞けるというのが望ましいでしょう。またリアルタイムの処理が全く追いつかないマシンでも、処理が終わった部分まではオーディオを聞けるのが望ましいです。この機能を実装するには、オーディオエフェクトの処理中にユーザーインターフェースがフリーズするという制限と、エフェクトの処理が最初からしか行えないという制限を撤廃する必要があります。
比較的に最近になって Audacity に追加されたオンデマンドローディング (on demand loading) という機能は、リアルタイムエフェクトのために作られたのではないにもかかわらず、リアルタイムエフェクトに必要となる要素がいくつも含まれています。Audacity にオーディオファイルをインポートすると、要約データを含む BlockFile はバックグラウンドで作成されます。青とグレーの縞模様によってオーディオの未処理の部分が表され、読み込み処理中であっても多くのコマンドを使うことができます。またブロックを左から右に処理するわけでもありません。私たちはいずれ同じコードをリアルタイムエフェクトの実装に使うつもりでいます。
オンデマンドローディングは、リアルタイムエフェクトへ向けた一歩とみなすことができます。つまり、エフェクトをリアルタイムにすることを妨げている複雑さをいくつか無視できるようになる一歩です。ただリアルタイムエフェクトがブロック内で重ならないようにするためのコードが追加で必要になります。そうしないとエフェクトが二重になったり正しく組み合わされないことになるからです。またオーディオが再生するに従って変化するパラメータを使えるようにする必要もあるでしょう。最初にオンデマンドローディングに取り組んだことで、速い段階でコードを使うことが可能になり、実際に使用した結果からのフィードバックや改善点を見つけることができました。
まとめ
この章の前半部分では、良い構造がプログラムの発展にどれだけ貢献するか、そして悪い構造が発展をどれだけ妨げるかを見ました。
-
PortAudio や wxWidgets などのサードパーティ API からはとてつもない利益がもたらされました。プラットフォームの違いを抽象化し、組み立てるときのパーツとなるコードを提供してくれるからです。ただサードパーティ API を使うことによる代償の一つは、抽象化に柔軟性がなくなることです。Audacity ではスレッドを三つの異なる方法で管理しなければならないために、録音・再生のコードは綺麗だとは言えません。また強制される抽象化によってデータのコピーの回数も増えます。
-
wxWidgets は、冗長で読みにくいアプリケーションコードを書きたくなるような API をしています。このため私たちは wxWidgets の上にコードを追加し、綺麗なアプリケーションコードを書けるようにしました。
-
Audacity の TrackPanel では wxWidgets のウィジェットが提供する以上の機能が必要になり、結果的に独自のアドホックなシステムを実装することになりました。そのため Audacity にはウィジェットとサイザーからなる整ったシステムと、それとは論理的に異なるアプリケーションレベルにあるオブジェクトがあり、後者は TrackPanel と独立したライブラリになるための必死の作業が続いています。
-
構造に関する決断とは、新しい機能をどこにどう収めるかを決断することだけではありません。何をプログラムに入れないかを決断するのも同じように重要です。これによってコードはより綺麗に、安全になります。Perl などのスクリプト言語からの恩恵を、自分たちのコピーをメンテナンスすることなく得られるというのは最高です。構造に関する決断は将来の成長計画によっても影響されます。生まれたばかりのモジュール化された Audacity のシステムは、実験を安全にすることで実験の数を増やすことを期待しています。またオンデマンドローディングはリアルタイムエフェクトのオンデマンド処理に向けた一歩になると期待しています。
見れば見るほど、Audacity がコミュニティの貢献によって作られたことが明らかになります。ここでいうコミュニティとは Audacity に直接貢献した人々だけではなく、依存しているライブラリのコミュニティに所属するその分野の専門家たちも含みます。ここまで読んで Audacity が様々な構造が合わさってできていることを理解した人には驚くに値しないでしょうが、Audacity の開発者コミュニティは新しい開発者を歓迎し、様々なスキルレベルの開発者に適切に仕事を割り振っています。
Audacity のコミュニティの気質がコードの強みと弱みに現れていたとしても、私は全く不思議に思いません。もっと閉じたグループであればより高いクオリティのコードをより一貫して書くことができるでしょうが、そのようなグループでは貢献者が少なくなり、現在の Audacity が持つような様々な分野における機能を持つことは難しくなるでしょう。
-
唯一の例外は Lisp ベースの Nyquist 言語であり、この言語は開発のごく初期から Audacity に組み込まれていました。個別のモジュールとして切り出して Audacity にバンドルするようにしたいとは思っているのですが、そのための時間が取れていません。[return]