Introduction to Audio Unit Development

第7回. リリース作業(Sinewave AU完成)

Posted in Introduction to Audio Unit Development on 11月 25th, 2007 by nagano – 8 Comments

Audio Unit開発入門,7回目.
7回もかかりましたが,今回でSinewave AU編は終りです.

前回でSinewave AUの”機能”は完成しました.
今回はリリース作業を行います.

auval

いきなりですが,auvalを使います.
auvalはAU Validation Toolという名前から分かるように
Audio Unitの動作チェックをやってくれるコマンドラインアプリケーションです.

Terminal.appを起動してでauval -hと入力してみます.

Helpがずらずら出ますが

-v TYPE SUBT MANU
に注目します.

TYPE SUBT MANUは
第2回.Audio Unitの実体は何か
でやりましたが
TYPE (カテゴリー)
SUBTYPE (Audio Unitの名前)
MANUFACTURE(デベロッパー名)
です.

試しに,auval -aと入力してみると
今インストールされているAudio Unitのリストが出ます.
auval1.png

augn pink appl – Apple: AUPinkNoise
がAUPinkNoiseです.

augnはそのままでいいとして
pinkは,sineにすることにします.
MANUは,Appleにクリエータータイプを申請しないといけません.

http://developer.apple.com/jp/faq/datatype.html

僕はMLAUを申請済みなのでMLAUにします.
(以下,MLAUとmonalisa-au.orgは自分の環境に合わせてください)

TYPE augn
SUBT sine
MANU MLAU

に決定しました.

では,AUPinkNoseのTYPE SUBT MANUを実際に変更してみます.

AUPinkNoise.rに

#define RES_ID			kAudioUnitResID_AUPinkNoise
#define COMP_TYPE		kAudioUnitType_Generator
#define COMP_SUBTYPE	'pink'
#define COMP_MANUF		kAudioUnitManufacturer_Apple

#define VERSION			0x00010000
#define NAME			"Apple: AUPinkNoise"
#define DESCRIPTION		"Audio Unit Pink Noise Generator"
#define ENTRY_POINT		"AUPinkNoiseEntry"

とあります

#define RES_ID			kAudioUnitResID_AUPinkNoise
#define COMP_TYPE		kAudioUnitType_Generator
#define COMP_SUBTYPE	'sine'
#define COMP_MANUF		'MLAU'

#define VERSION			0x00010000
#define NAME			"Monalisa-au.org: AUSinewave"
#define DESCRIPTION		"Audio Unit Sinewave Generator"
#define ENTRY_POINT		"AUSinewaveEntry"

に変更してます.

AUPinkNoiseVersion.hも同じように変更します.

一回ビルドしてみます.その前に一回プロジェクトをクリアしておきましょう.
⌘ + Shift + Kを押す.

再びauval -a
を実行すると
augn sine MLAU – Monalisa-au.org: AUSinewave

出ました.
ビルド実行してみましょう.
AUPinkNoiseが無くなったので,AULabがアラートを出すでしょう.
AUSinewaveを選択して動かしてみます.
動きますね.

(AULab 1.0.3だと最初の選択時にviewが出ません.これはしょうがない.2.0だとちゃんと出ますので,これ以降は2.0を使いましょう,)

Info.plistに

<string>com.apple.audiounit.AUPinkNoise</string>

があるので,

<string>org.monalisa-au.audiounit.AUSinewave</string>
<key>CFBundleExecutable</key>
<string>AUPinkNoise</string>

<key>CFBundleExecutable</key>
<string>AUSinewave</string>

に.

ターゲットの設定も変更します.
targetsetting.png
のAUPinkNoiseをダブルクリックして

target.png
のプロダクト名を
AUSinewaveにします.

これで,componentの名前がAUSinewaveに変わります.

このままだと,AUPinkNoise.componentとバッティングするので
/Library/Audio/Plug-Ins/Components/AUPinkNoise.component
を削除します.

auval -aでリストに出るか再度確認してみます.

出ますね.
(何か変更したらauvalで確認するようにするといいです)

InfoPlist.stringsも環境に合わせて変更しましょう.

auvalを実行する

さて,これで一通り修正が終わったので動作の確認にはいります.
auvalは本来これが目的のコマンドラインアプリケーションです.

-v TYPE SUBT MANUで実行できます.
auval -v augn sine MLAU
を実行すると,いろいろなSampling Rate/channelで動くか等々勝手にテストしてくれます.

最後に

--------------------------------------------------
AU VALIDATION SUCCEEDED.
--------------------------------------------------

と出れば成功で,このAudio UnitはAudio Unit Host Applicationで使えることが保証されます.

あとはUniversal Binaryにしたいので
ARCHS = ppc i386

をcopyして,先ほどのターゲット設定の一番上のアーキテクチャーの行にpasteします.
そしたら,
ppc i386
に表示が変わります.(便利悪いなぁ)

もう一回auval -v augn sine MLAUで確認します.
OKですね.(PPCで動くかはチェックしてませんが)

さて,これで完成です.
(残念ながら 10.5以降しか動きませんが)

volumeもzipper対応済み + Class名等もAUSinewaveに変更した.xcodeprojectはこちら.

Download->AUSinewave.zip(Xcode3.0 ProjectFile)

Download->AUSinewave.component.zip(Audio Unit)

まとめ

いかがだったでしょうか.
以外と簡単にできることが分かったと思います.
実質的にコードを書いたのはRender()内の変更とParameterの追加だけです.
Parameterの追加とその取得方法,Audio Signalの計算後の数値をどこに渡すのかさえ分かっていれば,Audio Unitを作るのはさほど難しくありません.
今回やった内容だけでも色々なAudio Unitが作れると思います.
難しいのはデジタル信号処理のルーチン(今回で言えばSine波の計算方法)ですが,それは本を読むか,ソースコードを探してきて頑張って移植すればOK.

何か作ったら是非教えてください.

Sinewave編はひとまずここまで.
気が向いたらAudio Unit Effect編をやります.

続きはそのうち.

第6回. 周波数を変更可能にする

Posted in Introduction to Audio Unit Development on 11月 23rd, 2007 by nagano – Be the first to comment

Audio Unit開発入門,第6回目.

前回は440Hzのサイン波を鳴らしてみました.
今回は440Hz固定から,自由に変更できるようにParameterを追加して,スライダーで変更できるようにします.

周波数を変化可能にするためにParameterを追加する

AUPinkNoise.hの一番上のPrameterについての記述を

static CFStringRef kParameterVolumeName = CFSTR("Volume");
static CFStringRef kParameterOnName = CFSTR("On/Off");
static CFStringRef kParameterFrequencyName = CFSTR("Frequency");

enum {
	kParam_Frequency=0,
	kParam_Volume =1,
	kParam_On=2,
	kNumberOfParameters=3
};

に変更します.
kDefaultValue_Volumeと同じようにDefaultのfrequencyを追加してもいいですが
冗長なのでやめておきます.(やってもいいです)

次に

AUPinkNoise::GetParameterInfo

case kParam_Frequency:
    AUBase::FillInParameterName (outParameterInfo, kParameterFrequencyName, false);
    outParameterInfo.unit = kAudioUnitParameterUnit_Hertz;
    outParameterInfo.minValue = 40.0;
    outParameterInfo.maxValue = 12000.0;
    outParameterInfo.defaultValue = 440.0;
    break;

を追加します.
人間の可聴域は最高で20kHzぐらいらしいですが,今回は12kHzぐらいまでにしておきましょう.

AUPinkNoise::Render内の

float frequency = 440.0;

float frequency = Globals()->GetParameter(kParam_Frequency);
に変更して,ビルド実行してみましょう.

周波数が変更できるようになっています.

追加したコードでは
outParameterInfo.unit = kAudioUnitParameterUnit_Hertz;
が気になるところですが,これは,Hzを使うという意味です.
Sliderの横のText Fieldの横の名称がHzになっているのが確認できます.

さて,このスライダーによる周波数の変更には問題があります.
たとえば,400Hzから500Hzに変更した場合,聴感上ピッチが大きく変わりますが
9000Hzから9100Hzに変更した場合,同じように100Hz周波数が上がったのに,ピッチの変化はあまり感じられません.
人間のピッチに対する感覚はlogになっていて,周波数が高くなる程,変化量を大きくしないといけないわけです.
なので,この場合,スライダーの変化をそのように変更すべきなのですが,GUI上の数値との同期が簡単に出来なかったので今回は省略します.
(Cocoa UIなら比較的簡単にできるので,機会があればその時にでも)

周波数の変化を滑らかに

さて,あとはスライダーを動かしたときにサイン波が滑らかに変化しないのが気になりますので修正します.
滑らかに変化させるには滑らかに周波数を変えればいいのです.

ジェームスマッカートニーのsinewave demoに
freqz = 0.001 * freq + 0.999 * freqz;
というマジックコードがあります.

こういったテストコードを書くと分かりやすいでしょう.

float freq = 0.3;
float freqz = 0.2;

while(1){
	freqz = 0.001 * freq + 0.999 * freqz;
	NSLog(@"freqz = %f", freqz);
	if((freq - freqz) < 0.0001){
		break;
	}
}

freqzは徐々に0.3に近づきます.(上記codeでは,freqz = 0.2999でbreakします)
AUPinkNoiseでこれを行うには,freqzにあたる値を保存しておく必要があります.
そこで,AUPinkNoise.hに
double frequency_zipper;
を追加します.

AUPinkNoise::Initialize()で
frequency_zipper = Globals()->GetParameter(kParam_Frequency) * 2 * M_PI / mSamplingRate;
としてfrequency_zipperの初期値を設定しておきます.(上記例で見ると,最初はfreqとfreqzは同じにしておくということ)

最終的な計算はsin(phase)なので
sin()に渡すphaseを滑らかに変化させるには
phase += freq;
の部分を
phase += freqz;
にして,マジックコードを使って,freqzを徐々に変化するようにします.

最終的なコードはこうなります.

ComponentResult     AUPinkNoise::Render(AudioUnitRenderActionFlags &ioActionFlags,
                                        const AudioTimeStamp &        inTimeStamp,
                                        UInt32                        nFrames)
{
    AUOutputElement* outputBus = GetOutput(0);
    outputBus->PrepareBuffer(nFrames);

    AudioBufferList& outputBufList = outputBus->GetBufferList();
    AUBufferList::ZeroBuffer(outputBufList);

    double frequency = Globals()->GetParameter(kParam_Frequency);
    double freq = frequency * 2 * M_PI / mSamplingRate;
    double freqz = frequency_zipper;
    double volume = Globals()->GetParameter(kParam_Volume);

    if (Globals()->GetParameter(kParam_On))
    {
        double phase = mPhase;
        for(UInt32 i = 0; i < nFrames; i++){
            double wave = sin(phase);
            wave *= volume;
            phase += freqz;
            freqz = 0.001 * freq + 0.999 * freqz;

            for (UInt32 j=0; j < outputBufList.mNumberBuffers; j++){
                Float32 *out = (Float32*)outputBufList.mBuffers[j].mData;
                out[i] = wave;
            }
        }
        mPhase = phase;
        frequency_zipper = freqz;
    }

    return noErr;
}

他には,waveの計算結果を,bufferの数だけ書き込むように修正しました.
(こうしないとマジックコードの使い方がめんどくさくなる)

これでスライダーをがんがん動かしても飛び飛びではなく,キュイーンと変化するようになりました.
Volumeも同じように滑らかに変化するようにしてもいいです.(試しにやってみてください)

さて,Sinewave AU,完成しました.
Audio Unitを作るというとすごく難しそうですが,何を鳴らすか,その計算と必要なParameterが分かれば
1. Parameterをその分使えるようにして,
2. RenderのところでParameterの値を取得してAudio Signalを計算して
3. bufferに書く

だけです.

最後に名前等を変更して,配布可能なAudio Unitに仕上げます.また,auvalを使ってAudio Unit Hostで読み込み可能なAudio Unitなのかを確認します.

続きは次回.

第5回. 440HzのSinewaveに改造する

Posted in Introduction to Audio Unit Development on 11月 18th, 2007 by nagano – Be the first to comment

Audio Unit開発入門5回目.

前回はWhite Noiseに改造しました.
今回は440Hzのサイン波を鳴らしてみます.

サイン波の計算には何が必要か

White Noiseは計算するために必要なParameterがありませんでした.

しかし,第5回.Audio Unitを使って音を鳴らす(完結)でやったように
サイン波では,Sampling RateとPhaseとFrequency(周波数)が必要になります.
最初はFrequencyは440固定にするとして,Sampling RateとPhaseはインスタンス変数に(C++ではメンバー変数と言う?)
追加しないといけません.

さっそく
AUPinkNoise.hのprivate:に
double mPhase;
Float64 mSamplingRate;

を追加します.

さて,mPhaseは最初0から始めるし,計算の毎にfreqを足していくのでよしとして
mSamplingRateはどこから取得するか.

前回,削除してしまったのですが

ComponentResult        AUPinkNoise::Initialize()
{
  const CAStreamBasicDescription& theDesc = GetStreamFormat(kAudioUnitScope_Output, 0);
  mPink = new PinkNoiseGenerator::PinkNoiseGenerator(theDesc.mSampleRate);
  return noErr;
}

で,theDesc.mSampleRateが取得できていたんですね.

ということで,

ComponentResult        AUPinkNoise::Initialize()
{
  mPhase = 0.0;
  const CAStreamBasicDescription &theDesc = GetStreamFormat(kAudioUnitScope_Output, 0);
  mSamplingRate = theDesc.mSampleRate;
  return noErr;
}

に変更します.

CAStreamBasicDescriptionは,AudioStreamBasicDescription構造体を継承したClassです.
(C++は構造体を継承できる!!)
AudioStreamBasicDescription は第11回.Audio Fileを再生する(1)で使いました.

Initializeは,名前の通り初期化の際に呼ばれるmethodです.
初期化の際とは,HostがAudioUnitInitializeを実行した時です.
このときにmPhaseも0にしておきます.
(ついでに.コンストラクタ(AUPinkNoise:: AUPinkNoiseはOpenAComponentの時に呼ばれます)

440Hzのサイン波を鳴らすように変更

さて,計算に必要なParameterは揃ったので早速サイン波を鳴らしてみます

ComponentResult     AUPinkNoise::Render(AudioUnitRenderActionFlags &ioActionFlags,
                                        const AudioTimeStamp &        inTimeStamp,
                                        UInt32                        nFrames)
{
    AUOutputElement* outputBus = GetOutput(0);
    outputBus->PrepareBuffer(nFrames);

    AudioBufferList& outputBufList = outputBus->GetBufferList();
    AUBufferList::ZeroBuffer(outputBufList);

    float frequency = 440.0;
    float freq = frequency * 2 * M_PI / mSamplingRate;

    if (Globals()->GetParameter(kParam_On))
    {
        float volume = Globals()->GetParameter(kParam_Volume);

        double phase;
        for (UInt32 j=0; j < outputBufList.mNumberBuffers; j++){
            phase = mPhase;
            Float32 *out = (Float32*)outputBufList.mBuffers[j].mData;
            for(UInt32 i = 0; i < nFrames; i++){
                double wave = sin(phase);
                *out++ = wave * volume;
                phase += freq;
            }
        }
        mPhase = phase;
    }
    return noErr;
}

注意したいのは,outputBufList.mNumberBuffersがいくつあっても対応できるように
for(UInt32 i = 0; i < nFrames; i++)のループに入る前に
phase = mPhaseで,mPhaseの値をcopyしておいて
ループ内では,この値から始めて計算.
計算終了後にmPhaseの値をupdateしておくところぐらいでしょうか.
こうすることで,ループ内は全て同じ計算が行われます.
これで,monoでもstereoでも(それ以上でも)対応できます.

(Audio Signalの計算結果は全部同じなので,先にAudio Signalを計算してmNumberBuffersの数だけループを回してbufferに書き込むという方法も可能.)

Getting~でやったのとほとんど変わりませんね.

あとはfrequencyをsliderで変更できるようにすれば,SinewaveAUは完成です.
Volumeをぐりぐり変えた時にプチプチ鳴るのも解消したいですね.

続きは次回.

第4回.Pink NoiseをWhite Noiseに改造する

Posted in Introduction to Audio Unit Development on 11月 15th, 2007 by nagano – Be the first to comment

Audio Unit開発入門4回目.今回は音も変更してしまいます.
今回はかなり変更するので,今のうちにAUPinkNoiseフォルダをbackupしておきましょう.
snapshotを残すのもありです.

前回はParameterをちょっとだけ変更してみました.
いろいろAUPinkNoiseを眺めてみて,Pink.hに実際のAudio Signalの計算が書いてあることが分かりました.

目標とするのはSine波を鳴らすAudio Unitなので,Sinewave.hでも作ってClassを書くか・・と考えるのが普通ですが,そんな複雑なことはしません.
ともかくファイル数は少なく,コードも最小限にやるのがポリシー.
なので,改造しながらどんどんシンプルにしていきましょう.

Sinewave AUで使わないものはとことん消す

Pink.hは使わないので消したい.
AUPinkNoise.hを見てみると,Pink.hの中で使っているのは
private:
PinkNoiseGenerator *mPink;

の部分だけです.mPinkが使われているのは

void                AUPinkNoise::Cleanup()
{
    delete mPink;
    mPink = NULL;
}
ComponentResult        AUPinkNoise::Initialize()
{
    const CAStreamBasicDescription &theDesc = GetStreamFormat(kAudioUnitScope_Output, 0);

    mPink = new PinkNoiseGenerator::PinkNoiseGenerator(theDesc.mSampleRate);

    return noErr;
}

ComponentResult     AUPinkNoise::Render(略...

の部分だけです.
Cleanup()とInitialize()はどちらも生成に関わる部分で,
実際に処理を行っているのは,Render()だけです.

じゃぁ,Render()からmPinkを消せばPink.hは消せます.
ついでにRender()内の処理を見てみます.

AUPinkNoise::Renderでは何が行われているのか

AUOutputElement* outputBus = GetOutput(0);
outputBus->PrepareBuffer(nFrames); // prepare the output buffer list
AudioBufferList& outputBufList = outputBus->GetBufferList();
AUBufferList::ZeroBuffer(outputBufList);

AUOutputElementが何者かは分かりませんが,ともかく最終的には
AudioBufferListを取得してたぶん0で埋めているだけです.
なぜ0で埋めるかというと,on/offボタンがoffの時(音を鳴らさないとき)
音が鳴らないようにしているんでしょう.(たぶん)

その後の

if (Globals()->GetParameter(kParam_On))

は,kParam_Onが1か0か,つまりボタンがonかoffかを取得している様子.
Globals()->GetParameter(parameter_name)でParameterの値が取得できることが分かります.
同じく,Globals()->GetParameter(kParam_Volume)で前回変更したVolumeを取得しています.
で,mPink->Renderが何をやっているかというと,

nFrames分,このVolumeで(Float32*)outputBufList.mBuffers[i].mDataにPinkNoiseの数値を書き込め!
とやっているようです.

じゃぁ,(Float32*)outputBufList.mBuffers[i].mDataにnFrames分,何か数値を渡してやれば
その数値の音が鳴るのでは?
と予測できます.

for (UInt32 i=0; i < outputBufList.mNumberBuffers; i++)

となっているのは,mNumberBuffersの数分(Monoなら1回,Stereoなら2回)処理を行っている
ということですね.

じゃぁ,Pink NoiseをWhite Noiseにしてみましょう.
しかも,mPink等使わず,ここに直接書いてしまいます.

White NoiseはランダムなAudio Signalなので
random()関数を使います.
で,-1.0 ~ 1.0の値を渡したいので
適当な範囲(ここでは65536)をその範囲の半分で割ると,0.0〜2.0の値が得られるので
その値から-1.0するという,小学生レベルの算数で計算します.
(random() % 65536) / 32768.0) - 1.0
こういう計算.
65536というのは,16bitです.
(srand(time(0))しなくていいんですか?と思う人も居るかもしれませんが,やったところで差は分かりません.笑)

もっと賢い計算方法があると思うので,興味のある人はリサーチしてください.
というか,Pink.hのPinkNoiseGenerator::Render()の前半でまさにその計算が行われているのですが.
もっと言うと,Pink NoiseはWhite Noiseに何かFilterをかけているのだなというのがここから分かります.

せっかくvolumeが使えるので,その値にvolumeをかけてあげます.

AUPinkNoise::Renderをこのように書き換えて,ビルドして実行してみましょう.

ComponentResult     AUPinkNoise::Render(AudioUnitRenderActionFlags &ioActionFlags,
                                        const AudioTimeStamp &        inTimeStamp,
                                        UInt32                        nFrames)
{
    AUOutputElement* outputBus = GetOutput(0);
    outputBus->PrepareBuffer(nFrames);

    AudioBufferList& outputBufList = outputBus->GetBufferList();
    AUBufferList::ZeroBuffer(outputBufList);

    if (Globals()->GetParameter(kParam_On))
    {
        float volume = Globals()->GetParameter(kParam_Volume);

        for (UInt32 j=0; j < outputBufList.mNumberBuffers; j++){
            Float32 *out = (Float32*)outputBufList.mBuffers[j].mData;
            for(UInt32 i = 0; i < nFrames; i++){
                *out++ = (((random() % 65536) / 32768.0) - 1.0) * volume;
            }
        }
    }
    return noErr;
}

Pink Noiseの音圧の強い感じの音から,シャーという感じの音に変わるでしょう.

えー,なんだ,音鳴らすなんて簡単じゃーんという感じですね.
これでWhite Noise AUができ上がりました.

ついでにmPinkを使っている部分を消してしまいましょう.

AUPinkNoise::AUPinkNoise(AudioUnit component)
    : AUBase(component, 0, 1),
      mPink (NULL)

AUPinkNoise::AUPinkNoise(AudioUnit component)
    : AUBase(component, 0, 1)

void                AUPinkNoise::Cleanup()
{
    delete mPink;
    mPink = NULL;
}

void                AUPinkNoise::Cleanup()
{
}

ComponentResult        AUPinkNoise::Initialize()
{
    const CAStreamBasicDescription & theDesc = GetStreamFormat(kAudioUnitScope_Output, 0);

    mPink = new PinkNoiseGenerator::PinkNoiseGenerator(theDesc.mSampleRate);

    return noErr;
}

ComponentResult        AUPinkNoise::Initialize()
{
    return noErr;
}

AUPinkNoise.hの

#include “Pink.h”

PinkNoiseGenerator *mPink;

も削除

Pink.hも削除しちゃいます.

これでだいぶすっきりしました.

で,冷静に,AUPinkNoise::Renderを眺めると・・・

Getting Started With Audio Unitでやった
第5回.Audio Unitを使って音を鳴らす(完結)
のRenderCallback関数に似ていませんか.
やっていることの本質は同じです.

もう完成形が見えてきました.

続きは次回.

第3回.ソースコードをざっと見てParameterをちょっと改造

Posted in Introduction to Audio Unit Development on 11月 14th, 2007 by nagano – Be the first to comment

Audio Unit開発入門.2日目で早くも3回目.

さて,前回は色々設定した結果,ビルドして実行でAUPinkNoiseがビルドされ
Audio Unitインストール用ディレクトリーにcopyされ
AU Labが実行可能ファイルとして立ち上がるようになりました.
これで何かを変更したら⌘ + Rですぐに確認できます.

おおまかな構造を把握する

今回は,ソースを見ていきます.
まず,階層を見ると
Source
-PublicUtility
-AUPublic
-AU Source
となっていると思いますが,この連載では多分AU Sourceしか見ません.
ここが自分で書くべきところです.

ざっと眺めていくと,AUPinkNoise.h/cppがメインのClassで,Pink.hを見ると何やら計算しているような感じです.
AUPinkNoiseVersion.h
AUPinkNoise.r
AUPinkNoise.exp
は定数がいろいろ書いてあるだけです.

AUPinkNoise.cppの一番下を見ると,AUPinkNoise::Render
というmethodがあります.
Getting Started〜で何度も登場したAudioBufferListがあります.
しかも,renderという名前ということはここでAudio Signalを書き込んでいる(渡している)様子.

もう少し見ると.mPink->Render
が実際にAudioBufferListのmBuffers[i].mDataに書き込んでいるようです.
(Core Audioの基本はAudioBufferListに計算したAudio Signalを書き込む!です)

mPinkとは何か見てみると.(mPinkを⌘ + double click!)

AUPinkNoise.hに
PinkNoiseGenerator *mPink;
とメンバー変数宣言があります.
PinkNoiseGeneratorの定義を探すと
Pink.hにClass定義があります.

ということはおおまかな流れは

AUPinkNoise Classが色々とHostとのやり取りをして(Controller)
PinkNoiseGeneratorが実際のAudio Signalの計算をしている(Generator)

のでしょう.

AUPinkNoise.cppの他の部分をなんとなく眺めてみても,ParameterがどうだとかPropertyがどうだとかやってるだけみたいです.

色々と処理が書いてあるし,ファイルもいっぱいありますが,結局,そんなに大したことはやってない模様.
(もちろん,裏では色々な処理が行われるのですが)

最大Volumeを0.5に変更してみる

さて,せっかく変更をすぐに確認できるように開発環境構築が出来ているので,試しにAUPinkNoiseを改造してみます.
PinkNoiseがVolume 1.0(最大)で鳴るととにかくうるさい.
なので,最大Volumeを0.5にしてみましょう.

Parameterの設定はどこで行われるのでしょうか.

AUPinkNoise.cppを眺めると,AUPinkNoise::GetParameterInfoにmaxValueとかdefaultValueとかがあります.
第9回.Audio UnitのParameter情報を取得する(1)
でやりましたが,AudioUnitParameterInfoがAudio UnitのParameter情報を扱う構造体でした.

AUPinkNoise::GetParameterInfoでもAudioUnitParameterInfo &outParameterInfoに数値をセットしていることが確認できます.

渡されるAudioUnitParameterID inParameterIDで,どのparameterを要求しているのかを判断していることが分かります.

switch(inParameterID)
{
  case kParam_Volume:
  .....
  case kParam_On:
.........

そう,HostがkAudioUnitProperty_ParameterInfoを引数にしてAudioUnitGetPropertyを実行すると,このmethodが呼ばれるのです.
なので,ここの数値を変更してやれば,AUPinkNoiseのParameter情報を変更できます.
(Hostはこの情報に応じてSlider等を作ります)

kParam_Volumeとあるから,Volumeでしょう.
試しに,
outParameterInfo.maxValue = 1

outParameterInfo.maxValue = 0.5
に変更してみます.

outParameterInfo.defaultValue = kDefaultValue_Volume;
という部分も気になります.

kDefaultValue_Volumeは,AUPinkNoise.hで
static const float kDefaultValue_Volume = 0.7071;
と定義されているので
static const float kDefaultValue_Volume = 0.3;
と変更してみます.

2つの変更が済んだら早速ビルド実行してみましょう.

pinknoizevolume03.png

このように,maxValueが変更できています.
試しに,AU Labで新しくAUPinkNoiseを追加したら,defaultが0.3になっているのも確認できます.

いろいろごちゃごちゃ書いてありますが,仕組みは簡単ですね.
Parameterを追加してmaxValueを22050にしたら,Sine波の周波数に使えそうです.

次は,目標のSine波を鳴らすAudio UnitではPinkNoiseGeneratorは使わないので
Pink.hを削除してしまいます.

続きは次回.

第2回. 開発環境の整備

Posted in Introduction to Audio Unit Development on 11月 13th, 2007 by nagano – Be the first to comment

Audio Unit開発入門 2回目.
今回のフローを10.5で試していたら,AU LabのBugを発見してしまって,Core Audio API MLに初投稿することになりました.カタコト英語,通じるだろうか.

今回は開発環境を整備しますが.Audio Unitを作ったことがあって
自分はこのやり方が一番便利なんだ!という人は飛ばして大丈夫です.
Audio UnitをビルドしてCopyしてHost アプリケーションを実行というフローを自動化するだけです.
これがビルドして実行(⌘ + R)一発でできると,開発速度が違います.
というか自動化しないと面倒くさくてやってられません.
他に便利な方法を御存知の方は教えてください.

さて,まずは,AUPinkNoiseをBuild実行して,どんなAudio Unitか試してみます.
/Developer/Examples/CoreAudio/AudioUnits/AUPinkNoise
をbuildして
/Library/Audio/Plug-Ins/Componentsにcopy

/Developer/Applications/Audio/AULab.appを起動してみましょう.
色々Dialogが出ますが,OK OK next nextをばんばん押して

この画面が出ます.

Menuから

を選んで,


この後,ゴーと鳴るのでVolume注意.

AUPinkNoiseを選びます.

この画面が出ます.

このDocumentをPinkNoiseTestという名前でSaveします.
その後,

で,Open a specific documentに設定しておきます.

これで,AU Labを起動したら,このdocumentが開きます.

次はXcode(AUPinkNoise)に移ります.

Audio Unit等のPluginは,ビルドして実行で実行できるアプリケーションが無いので
ビルドする度にHostアプリケーションを起動して動作チェックしないといけません.
これじゃ開発がさくさく進みません.

そこで,実行可能ファイルを登録します.
Xcode -> プロジェクトから

を選びます.


AU Labを選びます.

こうなります.

これで,AU Labが先ほどのPinkNoiseTest documentを勝手に開いてくれます.

追記 : Bugがあったのですが, 解決方法を調べました .1.0.3を使う部分は読み飛ばしてOKです

便利!と思ったら・・・・
10.5(AU Lab 2.0)から,この手法が通用しません.
理由はAU LabのBugだと思います.
普通にdouble clickで起動しないと,Audio Unitを読み込んでくれません.(AppleのMLに投稿済み)

なので,ひとまず10.4時代のAU Lab(version 1.0.3)を使って乗り切ります.
10.4を消した人はなんとか旧AU Labを入手してください.(Xcode 2.5に入ってます)

先ほどと同じ手順でAU Lab1.0.3を登録します.

アクティブな実行可能ファイルをAULab 1.0.3にします.


をダブルクリックして


を開いて引数 tabを選んで
+ ボタンをクリックして,引数を追加します.

さっきのPinkNoiseTestを引数にします.
これで,AU Lab(1.0.3)が起動するときにPinkNoiseTestを開いてくれます.

さて,ビルドして実行の時には,ビルドしたファイルを
/Library/Audio/Plug-Ins/Componentsにcopyしてくれないと便利が悪いですね.

ということでコピーファイルを追加します.
ターゲットをcontrol + click

デスティネーションを絶対パス
パスを/Library/Audio/Plug-Ins/Componentsにします.
(/Library/Audio/Plug-Ins/Componentsはアクセス権が無いかもしれないので,アクセス権を許可するか,/Users/ユーザー名/Library/Audio/Plug-Ins/ComponentsにするのもOK)

追加されたファイルをコピーのところに,ProductsからAUPinkNoise.componentをdrag -> dropします.
これでAUPinkNoiseをビルド実行すると,勝手にコピーされてAULab が起動します.便利.

結構大変でしたが,設定はこれだけで当分OK.
次はAUPinkNoiseのソースを見てみます.

続きは次回.

第1回. Overview

Posted in Introduction to Audio Unit Development on 11月 12th, 2007 by nagano – Be the first to comment

Getting Started With Audio Unitの続編として新連載 Introduction to Audio Unit Developmentを始めます.
(邦題 Audio Unit開発入門)
Getting Started With Audio Unitとは違い,Audio Unitを使うのではなく,実際にAudio Unitを作ります.

Introduction

10.5からAUPinkNoiseというExampleが追加されました.
/Developer/Examples/CoreAudio/AudioUnits/AUPinkNoise

これはGenerator Audio Unitです.
Generator Audio UnitはMIDIを受けない,ともかく音を出す(だけの)Audio Unitです.
じゃぁ,これを改造して,Sine波を鳴らすだけのAudio Unitを作ってみようじゃないですか.

Audio Unitを作るときの問題点

まず,問題点から

1. C++で書く必要がある
2. Frameworkを理解しないといけない

です.
1.については,ともかくC++をそれなりに覚えるしかありません.
Objective-Cができればすぐでしょう.
1週間ぐらい独習C++でもやって覚えましょう.
Templateなんかはほとんど使われていないし,基礎が分かれば大丈夫でしょう.
(僕はC++はさっぱり詳しくないですが,Audio Unit作れました.)

2.については,Audio Unitは,AUBaseというClassを継承して作るのですが
AUBaseは,ComponentBaseとAUElementCreatorを多重継承している等,結構ややこしいです.(もちろんC++)
Image Unitみたいに簡単には作れません.

さて,じゃぁ,Audio Unitを作るのはとんでもなく難しいのか.
そんなことはないのです.

実装すべき処理

Audio Unit(Generator)が行う処理を考えてみると

  1. Audio Signalを計算する
  2. 結果を返す

これだけです.

AUPinkNoiseというExampleがあるのだから 1. Audio Signalを計算する
の部分だけ変更すればいいのです.
しかも,Getting Started With Audio Unitで,Sine波を鳴らす計算は既に習得済み!

2.の部分も想像通り,AudioBufferListにAudio Signal(float)を渡してやるだけです.

そう,C++で書く以外は,Getting Started With Audio UnitでやったSine波を鳴らすのとほとんど同じです.
違う部分は,Audio Unit(自身)のパラメーターの操作に対応する部分(たとえばSliderを出す)のAPIを覚えないといけません.
それぐらいなら簡単そうですね.

方針としては

  1. Getting Started With Audio Unitでやった内容は理解してもらっているという前提で
  2. C++は最小限に
  3. とにかく必要な処理(実装)に焦点を当てて
  4. 動かしながら覚える
  5. 成果物は使えるものにする

で行きます.

というわけで,次回からは実際にAudio Unitの開発を始めてみます.

流れとしては

  1. Audio Unit開発環境の整備
  2. 仕組みを理解する
  3. AUPinkNoiseを改造してみる
  4. auvalでvalidation(テスト)
  5. Sine波を鳴らすコードを移植する
  6. パラメーターを調整する
  7. 完成

という感じになります.

その後は,”オープンソースのVSTをAUに移植する”をやるやもしれません.(徳井さんリクエスト)

続きは次回.