前の章ではオーディオサンプルの再生と取り込みの方法について説明しました。 サンプルはできるかぎり忠実に、変更せずに (ほかのオーディオラインからのサンプルのミキシングを除く) 配送することが暗に求められていました。 しかし、信号を修正できることが望ましい場合もあります。 ボリュームの調整、より豊かなボリューム、リバーブ、ピッチの変更などがユーザにより求められることがあります。 この章では、このような信号処理のための JavaTM Sound API 機能について説明します。
Control
オブジェクトに対して問い合わせを行ってから、ユーザの要望に合わせてコントロールを設定します。 ミキサーとラインでは、通常、ゲイン、パン、リバーブの各コントロールがサポートされています。
ミキサーには、一部またはすべてのラインに使用できるさまざまな種類の信号処理コントロールがあります。 たとえば、オーディオの取り込みに使用されるミキサーには、ゲインコントロール付きの入力ポート、ゲインとパンのコントロール付きのターゲットデータラインがあります。 オーディオ再生に使用されるミキサーには、ソースデータラインにサンプリングレートコントロールがあります。 どの場合でも、コントロールにはすべて Line
インタフェースのメソッドを介してアクセスします。
Mixer
インタフェースは Line
を継承しているので、ミキサー自体のコントロールのセットを持つことができます。 これらは、ミキサーのすべてのソースまたはターゲットラインに影響するマスターコントロールとしての役目を果たします。 たとえば、ミキサーにマスターゲインコントロールがあり、そのデシベル値がターゲットデータラインの個々のゲインコントロールに追加される場合などです。
また、ミキサー自体のコントロールには、ソースラインでもターゲットラインでもない、ミキサーが処理のために内部的に使用する特別なラインに影響するものもあります。 たとえば、グローバルのリバーブコントロールで混合入力信号に与えるリバーブの種類を選択し、この「ウェット (リバーブをかけた)」信号を「ドライ」信号に戻してミキシングしてから、ミキサーのターゲットラインに配信します。
ミキサーまたはミキサーのラインのいずれかにコントロールがある場合は、グラフィックオブジェクトによってプログラムのユーザインタフェースに表示し、ユーザがオーディオの特性を調節できるようにしたい場合があります。 コントロール自体はグラフィックではありません。コントロールに対して行うことができる動作は設定の取得と変更のみです。 プログラムでグラフィック表現を使用するかどうか、またどのようなグラフィック表現 (スライダー、ボタンなど) を使用するかは開発者に任されます。
すべてのコントロールは、抽象クラス Control
の具象サブクラスとして実装されます。 一般的なオーディオ処理コントロールの多くは、Control
の抽象サブクラスによりデータ型 (ブール型、列挙型、浮動小数点型など) に基づいて記述されます。 たとえばブール型のコントロールは、ミュートまたはリバーブのオン/オフなど、2 値状態のコントロールを表します。 これに対し、浮動小数点型のコントロールは、パン、バランス、ボリュームなど、連続的に変化するコントロールを表すのに適しています。
Java Sound API は、Control
の次のサブクラスを指定します。
BooleanControl
- 2 値状態 (true または false) のコントロールを表します。 たとえば、ミュート、ソロ、オン/オフスイッチなどを表すのに適しています。
FloatControl
- ある範囲の浮動小数点値でのコントロールを行うデータモデルです。 たとえば、ボリュームとパンは、FloatControls
で表すと、ダイヤルやスライダーにより操作できます。
EnumControl
- 1 組のオブジェクトからの選択を提示します。 たとえば、ユーザインタフェースの中の 1 組のボタンを EnumControl
に関連付けると、プリセットされている複数のリバーブ設定からどれかを選択できるようになります。
CompoundControl
- それぞれが Control
サブクラスのインスタンスである、互いに関連した項目の集合へのアクセスを提供します。 CompoundControls
は、グラフィックイコライザーなどのマルチコントロールモジュールを表します。 グラフィックイコライザーは、一般に 1 組のスライダーとして表され、各スライダーは FloatControl
に効果を与えます。
Control
の各サブクラスには、基本データ型に適したメソッドがあります。 ほとんどのクラスには、コントロールの現在の値の設定と取得、コントロールのラベルの取得などのメソッドがあります。
もちろん、各クラスにはそのクラス専用のメソッドと、そのクラスにより表現されるデータモデルがあります。 たとえば、EnumControl
には設定可能な値の組を取得するためのメソッドがあり、FloatControl
ではコントロールの最小値と最大値、および精度 (増分またはステップサイズ) を取得できます。
Control
の各サブクラスには対応する Control.Type
サブクラスがあり、これには特定のコントロールを識別する static インスタンスが含まれています。
次の表は、Control
サブクラスと対応する Control.Type
サブクラス、およびコントロールの特定の種類を示す static インスタンスの一覧です。
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)
// ...
コントロールをユーザインタフェースに表示する必要のあるアプリケーションプログラムは、利用可能なラインとコントロールを問い合わせ、次に、当該する各ラインに適したユーザインタフェース要素を表示します。 その場合、プログラムで行う必要のあることはユーザに「ハンドル」を提供することだけで、それらのコントロールがオーディオ信号に何を行うかを知る必要はありません。 ラインのコントロールをユーザインタフェース要素に割り当てる方法をプログラムが知っているかぎり、それ以外の処理は、Mixer
、Line
、Control
の 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
と一致したら、グラフィックスライダーなどを表示します。
また、プログラムが、同一クラスのコントロールであっても、各コントロールタイプを区別し、それぞれに異なったユーザインタフェース要素を使う場合もあります。 このためには、Control
の getType
メソッドから返された「インスタンス」をテストする必要があります。 たとえば、コントロールタイプが BooleanControl.Type.APPLY_REVERB
と一致した場合はプログラムはチェックボックスを表示し、BooleanControl.Type.MUTE
と一致した場合はトグルボタンを表示します。
これまで、コントロールにアクセスしてそのタイプを判断する方法について説明しました。ここでは、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
メソッドを使って、これらの各オブジェクトの特定のパラメータ設定にアクセスできます。
たとえば、洞窟での残響に似た設定の 1 つのリバーブだけが必要な場合は、int getDecayTime() int getEarlyReflectionDelay() float getEarlyReflectionIntensity() int getLateReflectionDelay() float getLateReflectionIntensity()
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 章「サウンドの合成」を参照)。