Alchemusica のビルド環境を Xcode 3.2.6 → 8.2.1 に変えるため、あちこち手を入れた。SDK は 10.5 → 10.12, deployment target は 10.6(10.5 用のビルドはできなかった)。あまり必要ない気もするけど、作業内容を一応メモしておく。
Cocoa 関連の API 変更への対応。
NSImage
のcompositePoint
系をdrawInRect
に置き換え。respectFlipped
というパラメータがある。これを使えば、NSImage
のsetFlipped
(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];
NSWindow
のcacheImageInRect
などが deprecated。NSView
のbitmapImageRepForCachingDisplayInRect:
,cacheDisplayInRect:toBitmapImageRep:
を使えば代用できるが、結局キャッシュする際も各サブビューのdrawRect:
を呼び出しているので、それなら普通に描画しても一緒じゃん、ということで、キャッシュイメージを使わない方法で書き直してしまった。NSFont
のdefaultLineHeightForFont
がdeprecated
. これには定石があって、NSLayoutManager
のインスタンスを一つ作っておいてそれに問い合わせる。NSWindowController
の拡張クラスメソッドとしてsharedLayoutManager
を実装した。[font defaultLineHeightForFont]]; → [[NSWindowController sharedLayoutManager] defaultLineHeightForFont:font];
それから、CoreAudio 関連の API 変更に対応。一番大変なのは、AUMIDIController
関連の API の変更だった。10.11 では警告は出るものの一応動いているのだが、だいぶ前から deprecated なので、この機会に大修正。AUMIDIController
は、MusicDevice
を元に作成して、CoreMIDI から見えるようにするもの。CoreMIDI のイベントスケジュール機能が使えるので楽だった。これを使わないとなると、MusicDeviceMIDIEvent()
, MusicDeviceSysEx()
を使って、自分でイベントのスケジューリングをしないといけない。
MusicDevice
の RenderNotify
コールバック関数を作って、それを登録する。
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 アプリが一切動かなくなってから考えてもいいだろう。