< 目次

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

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

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

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

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

これまで、コントロールにアクセスしてそのタイプを判断する方法について説明しました。このセクションでは、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章「サウンドの合成」を参照。)

 


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.