Core Audio ClockをMTCのスレーブにするサンプルです。
あれこれMIDIの設定とかしなくちゃいけないのかと思っていたら、意外と簡単でした。MIDIClientとかPortとかつくらずに、MIDIEndPointを直接渡してしまえばいいだけみたいです。
設定の順番としては、
・CAClockを作成する
・SyncModeをMTCTransportにする
・SyncSourceにMTCが送られてくるMIDIEndPointを設定する
・タイムコードのフォーマットを送信側と合わせておく
・CAClockArmでMTCを受信できる状態にする
・同期する必要がなくなったらCAClockDisarmで解除する
といった感じです。
Cocoaアプリケーションを作成して、AudioToolboxとCoreMIDIのフレームワークをインポートし、以下のクラスを作成し、タイムコード表示用のテキストフィールドをアウトレットに設定します。MIDIのインプットは一つしか無い事を想定していますので、Audio MIDI 設定でうまいこと設定してください。同じMac上のMIDIシーケンサから受け取る場合は、IACドライバの「装置はオンライン」にチェックして、シーケンサのMTCのアウトをIACのバスを通して送られてくるように設定します。
// // CAClockMTCSlaveTest.h // #import <Cocoa/Cocoa.h> #import <CoreMIDI/CoreMIDI.h> #import <AudioToolbox/CoreAudioClock.h> @interface CAClockMTCSlaveTest : NSObject { CAClockRef clockRef; MIDIEndpointRef srcPointRef; BOOL isStart; NSTimer *timer; IBOutlet NSTextField *textField; } - (void)clockListener:(CAClockMessage)message parameter:(const void *)param; - (void)checkTime:(NSTimer *)timr; @end // // CAClockMTCSlaveTest.m // #import "CAClockMTCSlaveTest.h" @implementation CAClockMTCSlaveTest #pragma mark - #pragma mark -- コールバック -- static void ClockListener(void *userData, CAClockMessage message, const void *param) { [(id)userData clockListener:message parameter:param]; } - (void)clockListener:(CAClockMessage)message parameter:(const void *)param { switch (message) { case kCAClockMessage_Started: isStart = YES; NSLog(@"started"); break; case kCAClockMessage_Stopped: isStart = NO; NSLog(@"stoped"); break; case kCAClockMessage_Armed: NSLog(@"armed"); break; case kCAClockMessage_Disarmed: NSLog(@"disarmed"); break; case kCAClockMessage_WrongSMPTEFormat: NSLog(@"wrongSMPTEFormat"); break; default: break; } } #pragma mark - #pragma mark -- 初期化など -- - (void)awakeFromNib { OSStatus err = noErr; UInt32 size; //MIDIエンドポイントを取得する srcPointRef = MIDIGetSource(0); //MIDIエンドポイントから名前を取得して表示 CFStringRef strSrcRef; err = MIDIObjectGetStringProperty( srcPointRef, kMIDIPropertyDisplayName, &strSrcRef); if (err != noErr) { NSLog(@"MIDI Get sourceName err = %d", err); goto end; } NSLog(@"connect = %@", strSrcRef); CFRelease(strSrcRef); //CAClockを作成する err = CAClockNew(0, &clockRef); if (err != noErr) { NSLog(@"CAClockNew err = %d", err); goto end; } //シンクモードをMTCにする UInt32 tSyncMode = kCAClockSyncMode_MTCTransport; size = sizeof(tSyncMode); err = CAClockSetProperty( clockRef, kCAClockProperty_SyncMode, size, &tSyncMode); if (err != noErr) { NSLog(@"set syncmode Err = %d", err); goto end; } //CAClockの同期元にMIDIエンドポイントを設定する size = sizeof(srcPointRef); err = CAClockSetProperty( clockRef, kCAClockProperty_SyncSource, size, &srcPointRef); if (err != noErr) { NSLog(@"caclock setSyncSourct err = %d", err); goto end; } //SMPTEを30fpsに設定する UInt32 tSMPTEType = kSMPTETimeType30; size = sizeof(tSMPTEType); err = CAClockSetProperty( clockRef, kCAClockProperty_SMPTEFormat, size, &tSMPTEType); if (err != noErr) { NSLog(@"set smptetype Err = %d", err); goto end; } //CAClockからの通知を受け取る関数を設定する err = CAClockAddListener(clockRef, ClockListener, self); if (err != noErr) { NSLog(@"caclock addListener err = %d", err); goto end; } err = CAClockArm(clockRef); if (err != noErr) { NSLog(@"CAClock arm err = %d", err); goto end; } //タイマーを開始する timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(checkTime:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode]; return; end: [NSApp terminate:self]; return; } - (void) dealloc { [timer invalidate]; OSStatus err; err = CAClockDisarm(clockRef); if (err != noErr) NSLog(@"clock disarm Err"); err = CAClockDispose(clockRef); if (err != noErr) NSLog(@"CAClockDispose err"); [super dealloc]; } #pragma mark - #pragma mark -- タイムの表示 -- //現在のタイムを表示する - (void)checkTime:(NSTimer *)timr { OSStatus err; CAClockTime secondTime; //再生中か停止中かで取得するタイムを変える if (isStart) { //カレントタイムを取得する err = CAClockGetCurrentTime( clockRef, kCAClockTimeFormat_Seconds, &secondTime); if (err != noErr) { NSLog(@"CAClock GetCurrenttime err = %d", err); return; } } else { //スタートタイムを取得する err = CAClockGetStartTime( clockRef, kCAClockTimeFormat_Seconds, &secondTime); if (err != noErr) { NSLog(@"CAClock GetCurrenttime err = %d", err); return; } } CAClockSeconds seconds = secondTime.time.seconds ; //秒数からタイムコードに変換する SMPTETime tSMPTETime; err = CAClockSecondsToSMPTETime(clockRef, seconds, 80, &tSMPTETime); if (err != noErr) { NSLog(@"secondsToSMPTE err = %d", err); return; } SInt16 tHours = tSMPTETime.mHours; SInt16 tMinutes = tSMPTETime.mMinutes; SInt16 tSeconds = tSMPTETime.mSeconds; SInt16 tFrames = tSMPTETime.mFrames; Float64 tPlayRate; err = CAClockGetPlayRate(clockRef, &tPlayRate); if (err != noErr) { NSLog(@"getPlayRate err = %d", err); return; } //タイムを表示する NSString *tSMPTEString = [NSString stringWithFormat: @"seconds = %f / SMPTE = %2.2hi.%2.2hi.%2.2hi.%2.2hi / PlayRate = %f" , seconds, tHours, tMinutes, tSeconds, tFrames, tPlayRate]; [textField setStringValue:tSMPTEString]; } @end
実行して、シーケンサ等からMTCを送り込まれると、受信してタイムを表示するはずです。ただ、SMPTETimeTypeに29.97を設定しても受信出来ませんでした。29.97を受信する時は30にSMPTETimeTypeを設定しておいて、同期するとPlayRateが0.999付近になります。