第 6 章: コントロールを使ったオーディオ処理


 

前の章ではオーディオサンプルの再生と取り込みの方法について説明しました。サンプルはできるかぎり忠実に、変更せずに (ほかのオーディオラインからのサンプルのミキシングを除く) 配送することが暗に求められていました。しかし、信号を修正できることが望ましい場合もあります。ボリュームの調整、より豊かなボリューム、リバーブ、ピッチの変更などがユーザにより求められることがあります。この章では、このような信号処理のための JavaTM Sound API 機能について説明します。

信号処理を行う方法は 2 種類あります。

上の 2 つのうち、後者の技法のための特別な API はないので、この章では主に前者の技法について説明します。

コントロールについて

ミキサーには、一部またはすべてのラインに使用できるさまざまな種類の信号処理コントロールがあります。たとえば、オーディオの取り込みに使用されるミキサーには、ゲインコントロール付きの入力ポート、ゲインとパンのコントロール付きのターゲットデータラインがあります。オーディオ再生に使用されるミキサーには、ソースデータラインにサンプリングレートコントロールがあります。どの場合でも、コントロールにはすべて Line インタフェースのメソッドを介してアクセスします。

Mixer インタフェースは Line を継承しているので、ミキサー自体のコントロールのセットを持つことができます。これらは、ミキサーのすべてのソースまたはターゲットラインに影響するマスターコントロールとしての役目を果たします。たとえば、ミキサーにマスターゲインコントロールがあり、そのデシベル値がターゲットデータラインの個々のゲインコントロールに追加される場合などです。

また、ミキサー自体のコントロールには、ソースラインでもターゲットラインでもない、ミキサーが処理のために内部的に使用する特別なラインに影響するものもあります。たとえば、グローバルのリバーブコントロールで混合入力信号に与えるリバーブの種類を選択し、この「ウェット (リバーブをかけた)」信号を「ドライ」信号に戻してミキシングしてから、ミキサーのターゲットラインに配信します。

ミキサーまたはミキサーのラインのいずれかにコントロールがある場合は、グラフィックオブジェクトによってプログラムのユーザインタフェースに表示し、ユーザがオーディオの特性を調節できるようにしたい場合があります。コントロール自体はグラフィックではありません。コントロールに対して行うことができる動作は設定の取得と変更のみです。プログラムでグラフィック表現を使用するかどうか、またどのようなグラフィック表現 (スライダー、ボタンなど) を使用するかは開発者に任されます。

すべてのコントロールは、抽象クラス Control の具象サブクラスとして実装されます。一般的なオーディオ処理コントロールの多くは、Control の抽象サブクラスによりデータ型 (ブール型、列挙型、浮動小数点型など) に基づいて記述されます。たとえばブール型のコントロールは、ミュートまたはリバーブのオン/オフなど、2 値状態のコントロールを表します。これに対し、浮動小数点型のコントロールは、パン、バランス、ボリュームなど、連続的に変化するコントロールを表すのに適しています。

Java Sound API は、Control の次のサブクラスを指定します。

上記の Control の各サブクラスには、基本データ型に適したメソッドがあります。ほとんどのクラスには、コントロールの現在の値の設定と取得、コントロールのラベルの取得などのメソッドがあります。

もちろん、各クラスにはそのクラス専用のメソッドと、そのクラスにより表現されるデータモデルがあります。たとえば、EnumControl には設定可能な値の組を取得するためのメソッドがあり、FloatControl ではコントロールの最小値と最大値、および精度 (増分またはステップサイズ) を取得できます。

Control の各サブクラスには対応する Control.Type サブクラスがあり、これには特定のコントロールを識別する static インスタンスが含まれています。

次の表は、Control サブクラスと対応する Control.Type サブクラス、およびコントロールの特定の種類を示す static インスタンスの一覧です。

Control Control.Type Control.Type インスタンス
BooleanControl BooleanControl.Type

MUTE

ラインのミュート状態

APPLY_REVERB

リバーブのオン/オフ

CompoundControl CompoundControl.Type (なし)
EnumControl EnumControl.Type REVERB

リバーブの各設定 (それぞれが ReverbType のインスタンス) へのアクセス

FloatControl FloatControl.Type AUX_RETURN

ライン上の補助戻りゲイン

AUX_SEND

ライン上の補助送りゲイン

BALANCE

左右のボリュームバランス

MASTER_GAIN

ライン上の全ゲイン

PAN

左右の位置

REVERB_RETURN

リバーブ処理後のライン上のゲイン

REVERB_SEND

リバーブ処理前のライン上のゲイン

SAMPLE_RATE

再生サンプリングレート

VOLUME

ライン上のボリューム

Java Sound API の実装では、これらのコントロールタイプのどれかまたはすべてをミキサーとラインに提供できます。Java Sound API で定められていないコントロールタイプを追加することもできます。このようなコントロールタイプは、4 つの抽象サブクラスの具象サブクラスを通じて、または、4 つの抽象サブクラスを継承しない Control サブクラスを通じて実装できます。アプリケーションプログラムは、サポートされているコントロールを各ラインに問い合わせることができます。

目的のコントロールを持つラインの取得

多くの場合、アプリケーションプログラムは、そのラインでサポートされているコントロールがあれば、それを表示します。ラインにコントロールがなければ表示しません。しかし、特定のコントロールを持つラインを探すことが重要な場合もあります。その場合は、第 3 章「オーディオシステムリソースへのアクセス」「目的の種類のラインの取得」で説明したように、Line.Info を使って適切な性質を持つラインを取得できます。

たとえば、ユーザがサウンド入力のボリュームを調節できる入力ポートを探す場合を考えます。次のコード (抜粋) は、デフォルトのミキサーが目的のポートとコントロールを持っているかどうか問い合わせる方法を示します。

Port lineIn;
FloatControl volCtrl;
try {
  mixer = AudioSystem.getMixer(null);
  lineIn = (Port)mixer.getLine(Port.Info.LINE_IN);
  lineIn.open();
  volCtrl = (FloatControl) lineIn.getControl(
FloatControl.Type.VOLUME);
// Assuming getControl call succeeds, // we now have our LINE_IN VOLUME control. } catch (Exception e) { System.out.println("Failed trying to find LINE_IN" + " VOLUME control: exception = " + e); } if (volCtrl != null) // ...

ラインからのコントロールの取得

コントロールをユーザインタフェースに表示する必要のあるアプリケーションプログラムは、利用可能なラインとコントロールを問い合わせ、次に、当該する各ラインに適したユーザインタフェース要素を表示します。その場合、プログラムで行う必要のあることはユーザに「ハンドル」を提供することだけで、それらのコントロールがオーディオ信号に何を行うかを知る必要はありません。ラインのコントロールをユーザインタフェース要素に割り当てる方法をプログラムが知っているかぎり、それ以外の処理は、MixerLineControl の Java Sound API アーキテクチャが全般的に引き受けます。

たとえば、プログラムでサウンドを再生する場合を考えます。プログラムは、第 3 章「オーディオシステムリソースへのアクセス」「目的の種類のラインの取得」で説明した方法で取得した、SourceDataLine を使用しています。ラインのコントロールには、Line メソッドを呼び出すことによりアクセスできます。

Control[] getControls()
次に、返された各配列に対して、次の Control メソッドを使って、コントロールタイプを取得します。
Control.Type getType()
特定の Control.Type インスタンスがわかれば、プログラムは対応するユーザインタフェース要素を表示することができます。もちろん、特定の Control.Type に対応するユーザインタフェース要素を選択する方法は、プログラムが採用する手法により異なります。一方、同じクラスのすべての Control.Type インスタンスを表すために同種の要素を使う場合があります。このためには、Control.Type インスタンスの「クラス」を、Object.getClass メソッドなどを使って問い合わせる必要があります。結果が BooleanControl.Type と一致したとしましょう。この場合、プログラムは汎用のチェックボックスやトグルボタンを表示し、クラスが FloatControl.Type と一致したら、グラフィックスライダーなどを表示します。

また、プログラムが異なるタイプのコントロール (同一クラスのものであっても) を区別して、それぞれに異なったユーザインタフェース要素を使う場合もあります。このためには、ControlgetType メソッドから返された「インスタンス」をテストする必要があります。たとえば、コントロールタイプが BooleanControl.Type.APPLY_REVERB と一致した場合はプログラムはチェックボックスを表示し、BooleanControl.Type.MUTE と一致した場合はトグルボタンを表示します。

コントロールを使ったオーディオ信号の変更

注:

現在の実装では、Control の値を変更するには Line がオープンでなければなりません。

これまで、コントロールにアクセスしてそのタイプを判断する方法について説明しました。ここでは、Controls を使ってオーディオ信号の性質を変更する方法について説明します。利用できるすべてのコントロールについて説明するのではなく、いくつかの例を使って概要を説明します。次の例を使用します。

プログラムはすでに、すべてのミキサーとライン、それらのライン上のすべてのコントロールにアクセスしており、コントロールとそれに対応するユーザインタフェースとの間の論理的関係を管理するためのデータ構造を持っているものとします。このような場合、これらのコントロールに対するユーザの操作を対応する Control メソッドに変換するのは簡単です。

次に、特定のコントロールへの変更に反映させるために呼び出す必要のあるいくつかのメソッドについて説明します。

ラインのミュート状態の制御

ラインのミュート状態を制御するために必要なのは、次の BooleanControl メソッドを呼び出すことだけです。

void setValue(boolean value)
プログラムは、コントロールと管理の対応データ構造を参照することにより、ミュートが BooleanControl のインスタンスであることを知っていると仮定します。ラインを通過する信号をミュートするには、プログラムはパラメータに true を指定して上記のメソッドを呼び出します。ミュートをオフにして信号がラインを流れるようにするには、プログラムはパラメータに false を指定してこのメソッドを呼び出します。

ラインのボリュームの変更

プログラムが、あるラインのボリュームコントロールにグラフィックスライダーを関連付けているとします。ボリュームコントロール (FloatControl.Type.VOLUME) の値は、次の FloatControl メソッドを使って設定します。

void setValue(float newValue)
ユーザがスライダーを動かしたことを検出すると、プログラムはスライダーの現在の値を取得し、その値を newValue パラメータとして上記のメソッドに渡します。これにより、そのコントロールが所属するラインを流れる信号のボリュームが変更されます。

さまざまなリバーブのプリセットからの選択

プログラムで使用するミキサーに、EnumControl.Type.REVERB というタイプのコントロールを持つラインがあるとします。そのコントロールに対して EnumControl メソッドを呼び出します。

java.lang.Objects[] getValues() 
ReverbType オブジェクトの配列が返されます。必要に応じて、次の ReverbType メソッドを使って、これらの各オブジェクトの特定のパラメータ設定にアクセスできます。
int getDecayTime() 
int getEarlyReflectionDelay() 
float getEarlyReflectionIntensity() 
int getLateReflectionDelay() 
float getLateReflectionIntensity() 
たとえば、洞窟での残響に似た設定の 1 つのリバーブだけが必要な場合は、getDecayTime が 2,000 以上の値を返すオブジェクトが見つかるまで、ReverbType オブジェクトの配列を繰り返し調べます。これらのメソッドの詳細については、javax.sound.sampled.ReverbType の API リファレンスドキュメントを参照してください。代表的な戻り値の表も記載されています。

しかし、一般的にはプログラムは getValues メソッドから返された配列内の ReverbType オブジェクトのそれぞれに対して、ラジオボタンなどのユーザインタフェース要素を作成します。ユーザがこれらのラジオボタンのどれかをクリックすると、プログラムは EnumControl メソッドを呼び出します。

void setValue(java.lang.Object value) 
ここで、value は、新しく作成されたボタンに対応する ReverbType に設定されます。この EnumControl が所属するラインを通じて送信されるオーディオ信号は、コントロールの現在の ReverbType (setValue メソッドの value 引数に指定される特定の ReverbType) を指定するパラメータ設定に従ってリバーブ処理が行われます。

したがって、ユーザがあるリバーブのプリセット (ReverbType) を別のプリセットに変更できるようにすることは、アプリケーションプログラム側から見ると、getValues から返された各配列の要素を別々のラジオボタンに接続することを指します。

オーディオデータの直接加工

Control API を使うと、Java Sound API の実装またはミキサーのサードパーティプロバイダは、コントロールを使って任意の信号処理を行うことができます。ただし、必要な信号処理の種類をミキサーが提示しない場合があります。その場合でも、作業は増えますが、信号処理をプログラムに実装することは可能です。Java Sound API ではオーディオデータへのアクセスはバイト配列として与えられるので、これらのバイトを任意の方法で修正することができます。

入力サウンドを処理する場合は、TargetDataLine からバイトを読み込んでから加工することができます。アルゴリズムとしては平凡ですが面白い効果を生む例に、フレームを逆の順序に配列することによりサウンドを後ろから再生する手法があります。このような平凡な手法はあまり役に立たないかもしれませんが、プログラムに使用できるより洗練された数多くのデジタル信号処理 (DSP) 技術があります。たとえば、イコライゼーション、ダイナミックレンジ圧縮、ピーク制限、時間軸の圧縮または伸張などや、ディレイ、コーラス、フランジング、ディストーション (歪み) などの特殊効果などです。

処理したサウンドを再生するには、加工したバイト列を SourceDataLine または Clip に書き込みます。もちろん、バイト列は既存のサウンドから取り出したものでなくても構いません。サウンドはゼロから合成することもできますが、音響学の知識や音響合成機能の知識が必要です。加工と合成のどちらの場合でも、オーディオ DSP の教本を調べてアルゴリズムを探したり、サードパーティ製の信号処理関数ライブラリを自分のプログラムにインポートすることができます。合成音の再生の場合は、javax.sound.midi パッケージの Synthesizer API を使用できるかどうかを検討してください(第 12 章「サウンドの合成」を参照)。