OSXでエミュレータ向けサウンドルーチンを作る

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

}

ゼビウスに「総攻撃」が存在したことが2020年に判明

[ゼビウス] AC版ゼビウスに総攻撃がプログラムされていた (6) : してログ

詳細はこちらのブログに。敵の配置テーブルを解析中に発見されたそうです。

ゼビウスの敵配置テーブルは128エントリあるのですが特定のプレイで128を超えるオーバーフローが発生。異常な攻撃パターンが読み込まれ「総攻撃」が発生するそうです。

驚くのはこれが改造でなくて、正規のROMで発生する(理論上)ということ。

ゼビウスが登場してから40年に近付いていますが、今まで発見されていなかったというのが驚きです。解析され尽くされた…と思われたのですが、敵配置テーブルのポインタの増減が複雑で条件に気付きにくい構造になっていました。

ブログ主の方がUPされたYoutube動画がこちら

敵の数に圧倒されます。ゼビウスってこんなキャラ数動かせたのか…

手順はエリア12の同じ場所で何度も自爆するというもので、偶然ではほぼ無理でしょうね。

解析され尽くしたと思われるゲームにも新しい発見があるのですね。

イベント参加について

 昨今の感染症による一連の自粛の影響で、同人誌即売会も開催見込みが無い状態です。
 流線堂のイベント参加予定も現在たてられない状態です。
 こんなご時世ですのでじっくりネタを溜めたいと思います。