前の章ではオーディオ・サンプルの再生と取込みの方法について説明しました。サンプルはできるかぎり忠実に、変更せずに(ほかのオーディオ・ラインからのサンプルのミキシングを除く)配送することが暗に求められていました。しかし、信号を修正できることが望ましい場合もあります。ボリュームの調整、より豊かなボリューム、リバーブ、ピッチの変更などがユーザーにより求められることがあります。この章では、このような信号処理のための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
と一致した場合はトグル・ボタンを表示します。
注
現在の実装では、 |
これまで、コントロールにアクセスしてそのタイプを判断する方法について説明しました。このセクションでは、Control
を使ってオーディオ信号の性質を変更する方法について説明します。利用できるすべてのコントロールについて説明するのではなく、いくつかの例を使って概要を説明します。次の例を使用します。
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
が2000を超える値を返すオブジェクトが見つかるまで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章「サウンドの合成」を参照。)