高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先

Technote 1108

Unknown Sound Features


目次

ほとんど知られていなかった Sound Manager の機能

マルチサウンド出力チャネル

マルチサウンド入力チャネル

ボリュームとパニング

サウンドチャネルのモニタ

要約
Sound Manager には本当にほしいと思う機能がない」 これまで多くのデベロッパがこのような不満をもらしてきました。しかし、欠けていると信じられていた機能の多くが実はすでに用意されていました。ただ、きちんとマニュアル化されていなかっただけです。この TECHNOTE では、Sound Manager のこれまであまり知られていなかった機能について詳しく説明します。

この TECHNOTE は Sound Manager にかかわるアプリケーションおよびハードウェアデベロッパを対象としており、読者が Sound Manager と Sound Manager を利用した製品に十分に習熟していることを前提にしています。


ほとんど知られていなかった Sound Manager の機能

この TECHNOTE では、次の Sound Manager の機能について説明します。

マルチサウンド出力チャネル
ステレオサウンドを 1 つだけでなく複数出力する方法。ここで説明するテクニックを使えば、アプリケーションは Macintosh が持っているチャネルと同数のチャネルで同時にサウンドを出力できるようになります。

マルチサウンド入力チャネル
ステレオサウンドを 1 つだけでなく複数録音する方法。ここで説明するテクニックを使えば、アプリケーションは Macintosh が持っている入力ソースと同数の入力ソース経由で同時にサウンドを録音できるようになります。

ボリュームとパニング
効果的なオーディオエフェクトを生み出す方法。

サウンドチャネルのモニタ
サウンドコンポーネントを使って、特定のサウンドチャネルのサウンド出力レベルをモニタする方法。

マルチサウンド出力チャネル

この機能は、System 7.0 とともに出荷された Sound Manager 3.0 からサポートされていました。実際、『Inside Macintosh: Sound』の 2-128 ページにはこの機能の説明があります。しかし、この機能の使い方については説明されていません。

SndNewChannel を呼び出すとき、渡されるパラメータの 1 つは long であり、これがチャネルの初期化パラメータを指定します。通常、デベロッパはこのパラメータに nilinitMono、または initStereo を渡すだけですが、実は kUseOptionalOutputDevice (これは -1 と定義されています) を渡すこともできます。

kUseOptionalOutputDevice を使用すると、サウンドを再生するための異なる出力コンポーネントを指定できるようになります。つまり、kUseOptionalOutputDevice により、インストールされている出力デバイスと同数の出力デバイスで同時にサウンドを再生できるようになるということです。たとえば、6 チャネルカードを持っているハードウェアデベロッパであれば、3 つのステレオ出力コンポーネントを作成できることになり (6 つの出力チャネルを 2 つ 1 組にして)、kUseOptionalOutputDevice セレクタを使ってこのカードに対応したソフトウェアを書き、3 つのステレオサウンドを同時に再生できるようになるはずです。

kUseOptionalOutputDevice セレクタは次のように動作します。まず、使用したい出力コンポーネントのコンポーネントインスタンスを検出する必要があります (次の例では、AIFF Writer サンプル出力コンポーネントを検出します)。
     // 出力コンポーネントのインスタンスを検出する
     outputDev.componentType = 'sdev';
     outputDev.componentSubType = 'AIFW';
     outputDev.componentManufacturer = 'appl';
     outputDev.componentFlags = 0;
     outputDev.componentFlagsMask = 0;

     theAIFWComponent = FindNextComponent (0, &outputDev);


すべてのサウンド出力コンポーネントを検出するジェネリックなルーチンは次のようになります。
 long FindAllsdevs (Component ** componentsArray) {
     Component                   foundComponent,
                                 aComponent;
     ComponentDescription        looking;
     long                        numComponents,
                                 i;

     aComponent                      = 0;
     looking.componentType           = kSoundOutputDeviceType; // 'sdev'
     looking.componentSubType        = 0;
     looking.componentManufacturer   = 0;
     looking.componentFlags          = 0;
     looking.componentFlagsMask      = 0;

     numComponents = CountComponents (&looking);

     *componentsArray = (Component*)NewPtr (sizeof (Component) * numComponents);
     if (componentsArray == nil) {
         numComponents = 0;      // サウンド出力チャネルのリストを作成できなくなる
     }

     for (i = 0; i < numComponents; i++) {
         foundComponent = FindNextComponent (aComponent, &looking);
         (*componentsArray)[i] = foundComponent;
         aComponent = foundComponent;    // 検出を継続する
     }

     return numComponents;
 }


このルーチンを使用すると、サウンド出力チャネルのリストをユーザに提示でき、サウンドのチャネル数をミキシングによって 2 チャネルに減らすことなく、ユーザはマルチチャネル (2 チャネルを超える) サウンドを出力できるようになります。

ここで残されている処理といえば、選択された出力デバイスを使用する新しいサウンドチャネルを作成することだけです。
 err = SndNewChannel (&theOptionalOutputChan, kUseOptionalOutputDevice, 
                        (long)theAIFWComponent, nil);


これはまったく簡単な話です。theOptionalOutputChan 経由でサウンドを再生するたびに、そのサウンドは組み込みハードウェアへは送られず、AIFF Writer 出力デバイス (これがマルチチャネル出力カードの第 3 および第 4 のチャネルであることは容易にわかるはずです) に送られます。

それでは、theOptionalOutputChan セレクタの使用を可能にするために、マルチチャネルハードウェアのデベロッパが行うべきこととはいったい何でしょう。

それほど多くのことを行う必要はありません。デベロッパがすべきことは、ハードウェアがサポートする出力チャネルの各ペアに対して出力コンポーネントを作成することだけです。ハードウェアに 2 つのチャネルしかない場合、必要となるのは 1 つの出力コンポーネントだけです。また、ハードウェアに 20 個の出力チャネルがある場合は、10 個の出力コンポーネントが必要になります。

ここで、「えっ、10 個の出力コンポーネント。ちょっとおかしいのでは。」というデベロッパの言葉が聞こえてくるようです。

でも何もおかしなところはありません。コンポーネントはもともと再利用可能であるため、実際に必要なのは、あたかも 10 個の異なる出力コンポーネントが存在するかのように、同じ出力コンポーネントを 10 回登録する 10 個の 'thng' リソースなのです。

出力コンポーネントは、出力先となる現実のハードウェアチャネルが抽象化されるように書いてください。つまり、1 つのコードベースで使用可能なすべてのチャネルに対応するような方法です。そうすれば、出力コンポーネントが登録を行うときに、制御の対象となる出力チャネルを決定し、この情報をそのグローバルに格納しているように見えます。さらに必要となる追加のコードはごくわずかです。どのチャネルに出力が行われているかを追跡するコードがあれば十分です。

注意:
オプションの出力チャネル経由でサウンドを出すために Speech Manager を使用することもできます。通常どおり NewSpeechChannel を呼び出し、さらに soSoundOutput セレクタと出力コンポーネントのコンポーネントインスタンスを使って SetSpeechInfo を呼び出します。
 err = SetSpeechInfo (theAIFWSpeechChan, soSoundOutput, &theAIFWComponent);

マルチサウンド入力チャネル

この機能はマルチサウンド出力チャネルとほぼ同様に動作します。サウンド入力で異なるのは、コンポーネントではなくドライバが必要な点だけです。

アプリケーションでは使用可能なサウンド入力ドライバを必要な数だけオープンできるため、アプリケーションが行うべきことは、それぞれのサウンド入力ドライバの名前を使ってSPBOpenDevice を何度も呼び出すことだけです。

Sound Manager には、使用可能なすべてのサウンド入力ドライバを簡単に列挙するための呼び出しが用意されています。
 SPBGetIndexedDevice (index, drvrName, &drvrIcon);


1 から始まるインデックスを渡し、エラーが返されるまでそれを 1 つずつインクリメントしていくだけで、迅速かつ容易に使用可能なすべてのサウンド入力ドライバを含むリストを構築することができます。


QuickTime による方法
また、単純に QuickTime を使用し、入力ソースの設定と録音を行う Sequence Grabber を使って録音することもできます。

次のコードでは、QuickTime の Sequence Grabber サウンド入力パネルを利用することになります。
  ComponentResult     err;
  SGChannel           sgSoundChanRef;
  SeqGrabComponent    sgComponent;

     sgComponent = OpenDefaultComponent (SeqGrabComponentType, 0);

     err = SGInitialize (sgComponent);

     if (err == noErr) {
         err = SGNewChannel (sgComponent, SoundMediaType, &sgSoundChanRef);
     }

     if (err == noErr) {
         err = SGSettingsDialog (sgComponent, sgSoundChanRef, 0, nil, 0L, nil, nil);
     }

     return err;
 }

Sound Device
QuickTime を使って録音する利点の 1 つは、QuickTime が自動的にレートを設定する、つまりサウンドを変換するという点です。その結果、特定のサウンド入力ドライバが提供するサンプリングレートによる録音に限定されることなく、任意のサンプリングレートで効果的に録音することができます。

ハードウェアベンダがマルチサウンド入力ドライバを作成しようとすると、デベロッパよりわずかに多くの作業が必要になります。つまり、ドライバを完璧に複製しなければなりませんが、必要なのはこれだけです。

ボリュームとパニング

Sound Manager 経由で再生されているサウンドのボリュームを調整するには、次のコードのように、SndDoImmediate呼び出しで発行される volumeCmd を使用します。
     SndCommand      theCmd;
     UInt16      rightVol, leftVol;

     theCmd.cmd = volumeCmd;
     theCmd.param2 = (rightVol << 16) | leftVol;
     err = SndDoImmediate(chan, &theCmd);


左右のボリュームは実際には 16 ビットの固定小数点数です。32 ビットの固定小数点数と同様に、上位 8 ビットはボリュームの整数部分を表し、下位 8 ビットはボリュームの小数部分を表します。たとえば、ボリュームの値を0x01000100 と設定すると両方のチャネルがフルボリュームになります。一方、0x01000080という値を設定すると、右チャネルはフルボリュームになりますが、左チャネルはちょうど半分のボリュームになります。

時間の経過とともに、左のボリュームをだんだん大きくし、右のボリュームをだんだん小さくするような呼び出しを行うと、サウンドを左から右にパンさせることができます。

ところで、一部のデベロッパから「1 つのチャネルに対して 0x0100 を超えるボリュームを設定するとどうなりますか」という質問を受けることがあります。答えは簡単です。サウンドがだんだん大きくなるだけです。volumeCmd を使って、ハードロックさながらの耳をつんざくようなフルボリュームに対応したサウンドレベルを設定することができます。


QuickTime による方法
サウンドのボリュームとパニングを制御するもう 1 つの (しかもよりよい) 方法として、伝家の宝刀である QuickTime を使うという手があります。QuickTime 2.1 のモディファイアトラックを使用すると、複雑なエフェクトを含むサウンドを再生することができます。


Tween Media ハンドラ
QuickTime 2.5 で提供されている Tween Media ハンドラを使用すると、それぞれのサウンドチャネルについて開始ボリュームと終了ボリュームだけを指定するだけで簡単にパニングを実現することができます。Tween コンポーネントが中間のボリューム値をすべて自動的に生成します。これは、QuickTime 2.1 とは大きく異なる点です。QuickTime 2.1 は Tween Media ハンドラを持たず、ボリュームのパニングを行おうとすると、多数の中間のボリューム値を設定する必要がありました。Tween Media ハンドラは、デベロッパが単純に開始ボリュームと終了ボリュームを指定できるようにすることで、処理を単純化します。Tween Media ハンドラは、ムービー (サウンド) の進行に伴い、ボリュームの設定が正しく行われているかどうかを自動的にチェックします。

Tween Media ハンドラの使い方については、『Developer's Guide: QuickTime for Macintosh version 2.5』の第 13 章を参照してください。

モディファイアトラックについては、『Developer's Guide: QuickTime for Macintosh version 2.5』の 1-21 ページ以降を参照してください。

サウンドチャネルのモニタ

一部のデベロッパは、レベルの計測など、さまざまな目的でサウンド出力チャネルをモニタしたいと考えています。これまで、Sound Manager のバッファを簡単に取得する方法がなかったため、これはかなりむずかしい処理でした。そのため、現在再生されているサウンドの位置を推測するしかありませんでした。

Sound Manager 3.2.1 ではプレミキサコンポーネントをインストールできるようになり、この問題の解決が容易になりました。これは、コンポーネントチェーンの中で Apple Mixer コンポーネントの直前にインストールされるコンポーネントです。

プレミキサコンポーネントは、それがインストールされているチャネルを対象に変換されたサウンドデータをチェックします。つまり、Apple Mixer が現在再生中の別のサウンドとミキシングしようとしている、圧縮されていなくて、レート変換、チャネル変換、およびサイズ変換されたデータをチェックします。

現在のところ、すべてのサウンドチャネルでミキシングされた結果をチェックするポストミキサコンポーネントをインストールする方法はありません。しかし、このようなコンポーネントはすでに存在しており、サウンド出力コンポーネントと呼ばれています。

プレミキサコンポーネントを書く方法は、他のサウンドコンポーネントの場合とまったく同様です。サウンドコンポーネントがサポートする必要のあるセレクタについては、『Inside Macintosh: Sound』の第 4 章を参照してください。

プレミキサコンポーネントをインストールするには、新しい SPBSetDeviceInfo セレクタの siPreMixerSoundComponent を使い、インストールしたいプレミキサコンポーネントを記述するSoundComponentLink にポインタを渡します。

次のサンプル関数は、単純なサウンドチャネルを作成し、そのサウンドチャネルに指定したプレミキサコンポーネントをインストールする方法を具体的に示しています。
SndChannelPtr CreateChannelWithPreMixer (SndCallBackUPP callbackRoutine, OSType pmcSubTye)
 {
     SoundComponentLink  preMixerCmp;
     SndChannelPtr       theChannel = nil;
     OSErr               err;

     /* 新しいサウンドチャネルを作成する */
     err = SndNewChannel (&theChannel, sampledSynth, 0, callbackRoutine);

     if (err == noErr) {
         /* プレミキサコンポーネントを定義する */
         preMixerCmp.description.componentType = kSoundEffectsType;
         preMixerCmp.description.componentSubType = pmcSubTye;
         preMixerCmp.description.componentManufacturer = 0;
         preMixerCmp.description.componentFlags = 0;
         preMixerCmp.description.componentFlagsMask = 0;
         preMixerCmp.mixerID = nil;
         preMixerCmp.linkID = nil;
     }

     if (err == noErr) {
         /* プレミキサコンポーネントを Apple Mixer の前にインストールする */
         err = SndSetInfo (theChannel, siPreMixerSoundComponent, &preMixerCmp);
     }

     if (err != noErr) {
         theChannel = nil;
     }

     return (theChannel);
 }


プレミキサコンポーネントとの間で情報をやり取りするには、SndSetInfo およびSndGetInfo 関数を使います。たとえば、次の呼び出しは、レベルメータコンポーネントから現在の値を取得します。
err = SndGetInfo (theChannel, LMValue, &level);


重要な制限事項
プレミキサコンポーネントには 1 つの重要な制限事項があります。つまり、プレミキサコンポーネントはサウンドの長さを長くできないという制限です。リバーブまたはフェードコンポーネントを書いている場合、この制限は非常に重要です。これらのタイプのサウンドエフェクトを正常に動作させるには、コンポーネントによるエフェクトとの置き換えができるように、再生されるサウンドが十分に長い無音のエンディングを持っていなければなりません。プレミキサコンポーネントはサウンドの長さを短くすることはできますが、長くすることはできません。

要約
この TECHNOTE では、これまであまり知られていなかった Sound Manager の機能について説明しました。これらの機能が理解できれば、それらを十分に利用して、これまでにない優れたサウンドアプリケーションを開発できるようになるはずです。

参考文献


更新日: 1997 年 10 月 6 日