Audio Unit:サイン波の再生

Audio Unitを使用してサイン波を再生するプログラムにチャレンジしました。
画面は以下の通りで周波数と音量をスライダーで変更できるようになっています。

手順としては以下の通りです。
・デフォルトの出力Audio Unitを獲得
・波形データを供給するコールバック関数の登録
・Audio Unitの初期化
・再生の開始
・定期的にコールバック関数が呼ばれるので波形データの供給を行う

●コールバック関数

パラメータで渡されるバッファに指定されたフレーム数分位相をずらしながらsin関数で求めた値を書き込んでいきます。

//
// コールバック関数
//
OSStatus	RenderCallback(void	 *inRefCon,
						   AudioUnitRenderActionFlags *ioActionFlags,
						   const AudioTimeStamp *inTimeStamp,
						   UInt32				inBusNumber,
						   UInt32				inNumberFrames,
						   AudioBufferList		*ioData)
{
	//NSLog(@"inNumberFrames = %d",inNumberFrames);
	
	UCSinWave *oSinWave = inRefCon;
	
	float *outL = ioData->mBuffers[0].mData;
	float *outR = ioData->mBuffers[1].mData;
	
	int i;
	for (i=0; i < inNumberFrames; i++){
		float wave = sin( [oSinWave phase] );
		*outL++ = wave;
		*outR++ = wave;
		
		[oSinWave incrementPhase];
    }
	
	return noErr;
}

●サイン波形再生クラス(UCSinWave)

サイン波形再生クラスのコードを以下に示します。

UCSinWave.h:サイン波生成クラスインターフェースファイル
#import <Cocoa/Cocoa.h>
#import <AudioUnit/AudioUnit.h>

@interface UCSinWave : NSObject {
	AudioUnit		mOutUnit;		// Audio Unit(Default Output)
	Float64			mSamplingRate;	// サンプリングレート
	double			mPhase;			// 現在の位相
	double			mDeltaTheta;	// 位相の増分
	double			mFreq;			// 周波数
	Float32			mVolume;		// 音量
	BOOL			mPlaying;		// 再生中フラグ
	
}
- (id)initWithFrequency:(double)dFreq volume:(float)fVol;
- (double)phase;
- (void)incrementPhase;
- (void)setFreq:(double)dFreq;
- (void)setVolume:(float)fVol;
- (void)start;
- (void)stop;
- (void)dealloc;

// 内部メソッド
- (BOOL)initAU;

@end

UCSinWave.m:サイン波生成クラスインプリメントファイル
#import "UCSinWave.h"

@implementation UCSinWave
//
// 初期化メソッド
//
- (id)initWithFrequency:(double)dFreq volume:(float)fVol
{
	self = [super init];
	if ( self == nil ) {
		return nil;
	}

	// Audio Unitの初期化
	BOOL bRet = [self initAU];
	if ( bRet != YES ) {
		[self release];
		return nil;
	}

	// メンバー変数の初期化
	mPhase = 0.0;
	mFreq = dFreq;
	mDeltaTheta = 2.0 * M_PI * dFreq / mSamplingRate;
	[self setVolume:fVol];
	mPlaying = NO;

	return self;
}

//
// 後処理
//
- (void)dealloc
{
	AudioUnitUninitialize(mOutUnit);
	CloseComponent(mOutUnit);
	
	[super dealloc];
}

//
// 現在の位相を取得する
//
- (double)phase
{
	return mPhase;
}

//
// 位相を進める
//
- (void)incrementPhase
{
	mPhase += mDeltaTheta;
}

//
// 周波数を設定する
//
- (void)setFreq:(double)dFreq
{
	mFreq = dFreq;
	mDeltaTheta = 2.0 * M_PI * mFreq / mSamplingRate;
}

//
// 音量を設定する
//
- (void)setVolume:(float)fVol
{
	mVolume = fVol;
	OSStatus err = AudioUnitSetParameter( mOutUnit,
								kHALOutputParam_Volume,
								kAudioUnitScope_Global,
								0,
								mVolume,
								0 );
	if ( err != noErr ) {
		NSLog( @"AudioUnitSetParameter(kHALOutputParam_Volume) failed. err=%d\n", err );
	}
}

//
// 再生
//
- (void)start
{
	AudioOutputUnitStart(mOutUnit);
	mPlaying = YES;
}

//
// 停止
//
- (void)stop
{
	AudioOutputUnitStop(mOutUnit);
	mPlaying = NO;
}

//
// Audio Unitの初期化
//
- (BOOL)initAU
{
	// デフォルト出力のAudio Unitの取得
	ComponentDescription cd;
	cd.componentType = kAudioUnitType_Output;
	cd.componentSubType = kAudioUnitSubType_DefaultOutput;
	cd.componentManufacturer = kAudioUnitManufacturer_Apple;
	cd.componentFlags = 0;
	cd.componentFlagsMask = 0;
	
	Component comp = FindNextComponent(NULL, &cd);
	OpenAComponent(comp, &mOutUnit);

	// サンプリングレートの取得
	AudioStreamBasicDescription tASBD;
	UInt32 nSize = sizeof(tASBD);
	OSStatus err = AudioUnitGetProperty( mOutUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &tASBD, &nSize );
	if ( err != noErr ) {
		NSLog( @"AudioUnitGetProperty(kAudioUnitProperty_StreamFormat) failed. err=%d\n", err );
		return NO;
	}
	mSamplingRate = tASBD.mSampleRate;
	
	NSLog( @"mSampleRate=%f\n", tASBD.mSampleRate );
	NSLog( @"mFormatID=0x%x\n", tASBD.mFormatID );
	NSLog( @"mSampleRate=%d\n", tASBD.mFormatFlags );
	NSLog( @"mBytesPerPacket=%d\n", tASBD.mBytesPerPacket );
	NSLog( @"mFramesPerPacket=%d\n", tASBD.mFramesPerPacket );
	NSLog( @"mBytesPerFrame=%d\n", tASBD.mBytesPerFrame );
	NSLog( @"mChannelsPerFrame=%d\n", tASBD.mChannelsPerFrame );
	NSLog( @"mBitsPerChannel=%d\n", tASBD.mBitsPerChannel );
	
	// コールバック関数の登録
    AURenderCallbackStruct input;
	input.inputProc = RenderCallback;
	input.inputProcRefCon = self;
	
	AudioUnitSetProperty (mOutUnit,
						  kAudioUnitProperty_SetRenderCallback,
						  kAudioUnitScope_Input,
						  0,
						  &input,
						  sizeof(input));

	// Audio Unitの初期化
	AudioUnitInitialize(mOutUnit);

	return YES;
}

@end

●Application delegateクラス

アプリケーションの動きを制御するdelegateクラスのコードを以下に示します。

SinWaveAppDelegate.h:App. Delegateクラスインターフェースファイル
#import <Cocoa/Cocoa.h>
#import "UCSinWave.h"

@interface SinWaveAppDelegate : NSObject <NSApplicationDelegate> {
    NSWindow *window;
	NSSlider *outSldFreq;
	NSSlider *outSldVol;
	NSTextField *outTxtFreq;
	NSTextField *outTxtVol;
	NSButton *outBtnStartStop;

@private
	UCSinWave *mSinWave;
	BOOL	mPlaying;
}

@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet NSSlider *outSldFreq;
@property (assign) IBOutlet NSSlider *outSldVol;
@property (assign) IBOutlet NSTextField *outTxtFreq;
@property (assign) IBOutlet NSTextField *outTxtVol;
@property (assign) IBOutlet NSButton *outBtnStartStop;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *) theApplication;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *) sender;
- (IBAction)onFreqSliderDidChange:(id)sender;
- (IBAction)onVolSliderDidChange:(id)sender;
- (IBAction)onBtnStartStop:(id)sender;

@end

SinWaveAppDelegate.m:App. Delegateクラスインプリメントファイル
#import "SinWaveAppDelegate.h"

@implementation SinWaveAppDelegate

@synthesize window;
@synthesize outSldFreq;
@synthesize outSldVol;
@synthesize outTxtFreq;
@synthesize outTxtVol;
@synthesize outBtnStartStop;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	// Insert code here to initialize your application 
	double dFreq, dVol;
	dFreq = [outSldFreq doubleValue];
	dVol = [outSldVol doubleValue];
	[outTxtFreq setDoubleValue:dFreq];
	[outTxtVol setDoubleValue:dVol];

	mSinWave = [[UCSinWave alloc] initWithFrequency:dFreq volume:dVol];
	mPlaying = NO;
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *) theApplication
{
	return YES;
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *) sender
{
	[mSinWave release];
	return NSTerminateNow;
}

- (IBAction)onFreqSliderDidChange:(id)sender
{
	double dVal = [outSldFreq doubleValue];
	[mSinWave setFreq:dVal];
	[outTxtFreq setDoubleValue:dVal];
}

- (IBAction)onVolSliderDidChange:(id)sender
{
	double dVal = [outSldVol doubleValue];
	[mSinWave setVolume:dVal];
	[outTxtVol setDoubleValue:dVal];
}

- (IBAction)onBtnStartStop:(id)sender
{
	if ( mPlaying == YES ) {
		[mSinWave stop];
		[outBtnStartStop setTitle:@"Start"];
		mPlaying = NO;
	} else {
		[mSinWave start];
		[outBtnStartStop setTitle:@"Stop"];
		mPlaying = YES;
	}
}

@end

●余談

MacOS X 10.5からAudio関連のAPIが変わっていて、音量の設定のAPIを見つけるのに苦労しました。TN2223(http://developer.apple.com/mac/library/technotes/tn2010/tn2223.html)に従ってAudioObjectSetPropertyDataで設定を行おうとしたのですが音量を示すプロパティIDが不正というエラーでうまくいきませんでした。
ネットをさまよった結果Norihisa Naganoさんのサイト(http://nagano.monalisa-au.org/?p=161)に出会い、AudioUnitSetParameterで行うことがわかりました。感謝です。

サンプルプログラムのダウンロード(33KB)

【作成・確認環境】
MacOS X v10.6.4
Xcode v3.2.3

一覧に戻る