OSX+XCode+swift環境でエミュレータ向けサウンドルーチンを作った話です。
音を鳴らすサンプルは比較的簡単に見つかると思いますが、多くは静的なWAVファイル・MP3ファイルを鳴らすというものでエミュレータのようにリアルタイムで波形を生成する用途には向かないものでした。この辺の情報が少なかったのでメモ。
注意:古いAPIを使っています。警告が出ますがとりあえず動くようです。
swiftと書いてますがこのソース自体はCです。
コンパイルした環境:xcode11.3.1
//
// audio.c
#include "audio.h"
#include <CoreAudio/AudioHardware.h>
unsigned long deviceBufferSize;
AudioDeviceID device;
Boolean initialized; // successful init?
Boolean soundPlaying; // playing now?
AudioStreamBasicDescription deviceFormat; // info about the default device
float amp;
float pan;
PSG *myPSG;
int sTestCount=0;
// コールバック関数です 外部から直接この関数を呼び出すことはありません
OSStatus appIOProc (AudioDeviceID inDevice, const AudioTimeStamp* inNow, const AudioBufferList* inInputData,
const AudioTimeStamp* inInputTime, AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime,
void* defptr)
{
int i;
// load instance vars into registers
int numSamples = deviceBufferSize / deviceFormat.mBytesPerFrame;
// assume floats for now....
float *out = (float*)outOutputData->mBuffers[0].mData;
float wave;
float pzl=1.0*amp;//左ボリューム
float pzr=1.0*amp;//右ボリューム
for (i=0; i<numSamples; ++i) {
//サウンド生成のコア部分 約440Hzの矩形波を生成
wave+=(sTestCount<55)?5000:0;
sTestCount++;
if (sTestCount>109){sTestCount=0;}
wave = wave/32768.0;
// write output
*out++ = wave * pzl; // left channel
*out++ = wave * pzr; // right channel
}
return kAudioHardwareNoError;
}
// 初期設定です。ViewのviewDidLoad()などから呼び出すものです
// 大昔のネットのサンプルからのコピペです
void audioSetup(void)
{
OSStatus err = kAudioHardwareNoError;
UInt32 count;
device = kAudioDeviceUnknown;
initialized = false;
// get the default output device for the HAL
count = sizeof(device); // it is required to pass the size of the data to be returned
err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &count, (void *) &device);
if (err != kAudioHardwareNoError) {
fprintf(stderr, "get kAudioHardwarePropertyDefaultOutputDevice error %d\n", err);
return;
}
// get the buffersize that the default device uses for IO
count = sizeof(deviceBufferSize); // it is required to pass the size of the data to be returned
err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyBufferSize, &count, &deviceBufferSize);
if (err != kAudioHardwareNoError) {
fprintf(stderr, "get kAudioDevicePropertyBufferSize error %d\n", err);
return;
}
fprintf(stderr, "deviceBufferSize = %ld\n", deviceBufferSize);
// get a description of the data format used by the default device
count = sizeof(deviceFormat); // it is required to pass the size of the data to be returned
err = AudioDeviceGetProperty(device, 0, false, kAudioDevicePropertyStreamFormat, &count, &deviceFormat);
if (err != kAudioHardwareNoError) {
fprintf(stderr, "get kAudioDevicePropertyStreamFormat error %d\n", err);
return;
}
if (deviceFormat.mFormatID != kAudioFormatLinearPCM) {
fprintf(stderr, "mFormatID != kAudioFormatLinearPCM\n");
return;
}
if (!(deviceFormat.mFormatFlags & kLinearPCMFormatFlagIsFloat)) {
fprintf(stderr, "Sorry, currently only works with float format....\n");
return;
}
initialized = true;
amp=1.0;
pan=1.0;
err = AudioDeviceAddIOProc(device, appIOProc, nil); // setup our device with an IO proc
if (err != kAudioHardwareNoError) {
fprintf(stderr, "get AudioDeviceAddIOProc error %d\n", err);
return;
}
err = AudioDeviceStart(device, appIOProc); // start playing sound through the device
if (err != kAudioHardwareNoError) {
fprintf(stderr, "get AudioDeviceStart error %d\n", err);
return;
}
soundPlaying = true; // set the playing status global to true
}