2017年09月16日

Alchemusica のリビルド詳細

 Alchemusica のビルド環境を Xcode 3.2.6 → 8.2.1 に変えるため、あちこち手を入れた。SDK は 10.5 → 10.12, deployment target は 10.6(10.5 用のビルドはできなかった)。あまり必要ない気もするけど、作業内容を一応メモしておく。

 Cocoa 関連の API 変更への対応。

  • NSImagecompositePoint 系を drawInRect に置き換え。respectFlipped というパラメータがある。これを使えば、NSImagesetFlipped (deprecated) を使わなくてもよい。
    [[MyPopUpButton triangleImage] compositeToPoint: NSMakePoint(theRect.origin.x + theRect.size.width - 7, theRect.origin.y + theRect.size.height - 2) operation: NSCompositeSourceAtop fraction: fraction];
    →
    NSRect r;
    r.origin.x = theRect.origin.x + theRect.size.width - 7;
    r.origin.y = theRect.origin.y + theRect.size.height - 7;
    r.size.width = 5;
    r.size.height = 5;
    [[MyPopUpButton triangleImage] drawInRect:r fromRect:NSZeroRect operation:NSCompositeSourceAtop fraction:fraction respectFlipped:YES hints:nil];
    
  • NSWindowcacheImageInRect などが deprecated。NSViewbitmapImageRepForCachingDisplayInRect:, cacheDisplayInRect:toBitmapImageRep: を使えば代用できるが、結局キャッシュする際も各サブビューの drawRect: を呼び出しているので、それなら普通に描画しても一緒じゃん、ということで、キャッシュイメージを使わない方法で書き直してしまった。
  • NSFontdefaultLineHeightForFontdeprecated. これには定石があって、NSLayoutManager のインスタンスを一つ作っておいてそれに問い合わせる。NSWindowController の拡張クラスメソッドとして sharedLayoutManager を実装した。
    [font defaultLineHeightForFont]];
    →
    [[NSWindowController sharedLayoutManager] defaultLineHeightForFont:font];
    

 それから、CoreAudio 関連の API 変更に対応。一番大変なのは、AUMIDIController 関連の API の変更だった。10.11 では警告は出るものの一応動いているのだが、だいぶ前から deprecated なので、この機会に大修正。AUMIDIController は、MusicDevice を元に作成して、CoreMIDI から見えるようにするもの。CoreMIDI のイベントスケジュール機能が使えるので楽だった。これを使わないとなると、MusicDeviceMIDIEvent(), MusicDeviceSysEx() を使って、自分でイベントのスケジューリングをしないといけない。

 まず、MusicDeviceRenderNotify コールバック関数を作って、それを登録する。

static OSStatus
sMDAudioSendMIDIProc(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
{ ... }
/*  登録方法  */
CHECK_ERR(result, AudioUnitAddRenderNotify(ip->unit, sMDAudioSendMIDIProc, ip));
/*  登録解除方法  */
CHECK_ERR(result, AudioUnitRemoveRenderNotify(ip->unit, sMDAudioSendMIDIProc, ip));

 このコールバック関数の中で、MusicDeviceMIDIEvent() を使ってイベントを送る。

OSStatus MusicDeviceMIDIEvent(MusicDeviceComponent inUnit, UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame);

 inOffsetSampleFrame は、コールバック関数に渡される inTimeStamp からのオフセット。これで、発音を開始するタイミングを指定することができる。サンプリング間隔の整数倍しか指定出来ないのが残念。ここは double で設計して欲しかった。(どうせ内部的には補間処理をしているはず)

 また、MusicDeviceSysEx() にはこの引数がない。SysEx を使って発音を制御する場合もあるので、ここもオフセットが指定できるように設計すべきだったと思う。

OSStatus MusicDeviceSysEx(MusicDeviceComponent inUnit, const UInt8 *inData, UInt32 inLength);

 コールバック関数は CoreAudio から呼ばれる。一方、MIDI イベントを送る関数は独立した pthread から呼ばれる。間にリングバッファを置いて、送り側からタイムスタンプ+MIDI データをバッファに書き出し、コールバック関数ではそのデータを取り出して処理するようにする。送り側の pthread はだいたい 0.1 sec 間隔ぐらいでまとめて処理しているので、リングバッファが一杯になることがある。そのとき、送ろうとしたMIDIイベントを取り下げて、次の処理のときにもう一度送れるようにしないといけない。また、コールバック関数の方は、基本的にはオーディオバッファ1回分の処理しかしないので、リングバッファ中のイベントがずっと先のタイムスタンプを持つ場合には、そのイベントを待たせないといけない。このあたりの処理がちょっとあいまいだったので、CoreMIDI を使うところも含めてかなり書き直した。

 一応 32bit では動くようになった。喜ばしいことに、前におかしかった SoundCanvas VA もちゃんと動作するようになった。Alchemusica 側で何か変な処理をしていたに違いない。

 64bit に移行しようとしていろいろ苦労したが、結局断念した。断念したのは、CoreAudio プラグインの CarbonView が表示できないんじゃないか、という懸念から。やってみたらできたのかもしれないけど、そのために必要な作業量を考えると、別に 32bit のままでいいんじゃない、と思った。10.11 でも 32bit で普通に動いてるしな。32bit アプリが一切動かなくなってから考えてもいいだろう。

タグ:Mac Alchemusica
Posted at 2017年09月16日 20:33:27
email.png