前の章ではオーディオサンプルの再生と取り込みの方法について説明しました。サンプルはできるかぎり忠実に、変更せずに (ほかのオーディオラインからのサンプルのミキシングを除く) 配送することが暗に求められていました。しかし、信号を修正できることが望ましい場合もあります。ボリュームの調整、より豊かなボリューム、リバーブ、ピッチの変更などがユーザーにより求められることがあります。この章では、このような信号処理のための Java Sound API 機能について説明します。
Control
オブジェクトに対して問い合わせを行ってから、ユーザーの要望に合わせてコントロールを設定します。ミキサーとラインでは、通常、ゲイン、パン、リバーブの各コントロールがサポートされています。 ミキサーには、一部またはすべてのラインに使用できるさまざまな種類の信号処理コントロールがあります。たとえば、オーディオの取り込みに使用されるミキサーには、ゲインコントロール付きの入力ポート、ゲインとパンのコントロール付きのターゲットデータラインがあります。オーディオ再生に使用されるミキサーには、ソースデータラインにサンプリングレートコントロールがあります。どの場合でも、コントロールにはすべて Line
インタフェースのメソッドを介してアクセスします。
Mixer
インタフェースは Line
を継承しているので、ミキサー自体のコントロールのセットを持つことができます。これらは、ミキサーのすべてのソースまたはターゲットラインに影響するマスターコントロールとしての役目を果たします。たとえば、ミキサーにマスターゲインコントロールがあり、そのデシベル値がターゲットラインの個々のゲインコントロールの値に追加される場合などです。
また、ミキサー自体のコントロールには、ソースラインでもターゲットラインでもない、ミキサーが処理のために内部的に使用する特別なラインに影響するものもあります。たとえば、グローバルのリバーブコントロールで混合入力信号に与えるリバーブの種類を選択し、この「ウェット (リバーブをかけた)」信号を「ドライ」信号に戻してミキシングしてから、ミキサーのターゲットラインに配信します。
ミキサーまたはミキサーのラインのいずれかにコントロールがある場合は、グラフィックオブジェクトによってプログラムのユーザーインタフェースに表示し、ユーザーがオーディオの特性を調節できるようにしたい場合があります。コントロール自体はグラフィックではありません。コントロールに対して行うことができる動作は設定の取得と変更のみです。プログラムでグラフィック表現を使用するかどうか、またどのようなグラフィック表現 (スライダー、ボタンなど) を使用するかは開発者に任されます。
すべてのコントロールは、抽象クラス Control
の具象サブクラスとして実装されます。一般的なオーディオ処理コントロールの多くは、Control
の抽象サブクラスによりデータ型 (ブール型、列挙型、浮動小数点型など) に基づいて記述されます。たとえばブール型のコントロールは、ミュートまたはリバーブのオン/オフなど、2 値状態のコントロールを表します。これに対し、浮動小数点型のコントロールは、パン、バランス、ボリュームなど、連続的に変化するコントロールを表すのに適しています。
Java Sound API は、Control
の次の抽象サブクラスを指定します。
BooleanControl
— 2 値状態 (true または false) のコントロールを表します。たとえば、ミュート、ソロ、オン/オフスイッチなどが BooleanControls
に適しています。 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
メソッドを使って、これらの各オブジェクトの特定のパラメータ設定にアクセスできます。
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 章「サウンドの合成」を参照。)