前回のCore Audio Clock その2で、MTCを受信してCore Audio Clockをスレーブで動かすというのをやりましたが、シンクソースにMTCを設定するだけではクォーターフレームメッセージしか受信してくれないので、フルタイムコードメッセージも受け取って再生時以外のタイムコードにも対応できるようにしてみたいと思います。
あれこれ試してみたところ、フルタイムコードをCore Audio Clockが勝手に解析してくれるような機能はないようなので、一旦フルタイムコードメッセージからタイムコードを抜き出してCore Audio ClockのCAClockSetCurrentTimeで設定します。
ただ、CAClockSetCurrentTimeは停止中でないと受け付けてくれないので、MTCを送信してきているシーケンサーが停止時にフルタイムコードを送ってきても、Core Audio ClockはMTCFreewheelTimeで設定されている時間の後に停止するので、そのタイミングでセットしないと受け付けてくれません。
そんな感じで作り直してみたのが以下のコードです。前回プラスCoreMIDI.Frameworkになります。
// // CAClockMTCSlaveTest.h // #import <Cocoa/Cocoa.h> #import <CoreMIDI/CoreMIDI.h> #import <AudioToolbox/AudioToolbox.h> @interface CAClockMTCSlaveTest : NSObject { CAClockRef clockRef; MIDIEndpointRef srcPointRef; BOOL isStart; NSTimer *timer; IBOutlet NSTextField *textField; MIDIClientRef clientRef; MIDIPortRef inputPortRef; CAClockSeconds keepSeconds; } - (void)clockListener:(CAClockMessage)message parameter:(const void *)param; - (void)checkTime:(NSTimer *)timr; - (void)setCurrentTime:(NSNumber *)secondsNumber; - (void)setFullTimecode:(MIDIPacket *)packet; @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 { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; switch (message) { case kCAClockMessage_Started: isStart = YES; NSLog(@"started"); break; case kCAClockMessage_Stopped: isStart = NO; [self setCurrentTime: [NSNumber numberWithDouble:keepSeconds]]; NSLog(@"stoped"); break; case kCAClockMessage_Armed: NSLog(@"armed"); break; case kCAClockMessage_Disarmed: NSLog(@"disarmed"); break; case kCAClockMessage_WrongSMPTEFormat: NSLog(@"wrongSMPTEFormat"); break; default: break; } [pool drain]; } static void MIDIInputProc(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; //MIDIパケットリストの先頭のMIDIPacketのポインタを取得 MIDIPacket *packet = (MIDIPacket *)&(pktlist->packet[0]); //パケットリストからパケットの数を取得 UInt32 packetCount = pktlist->numPackets; for (NSInteger i = 0; i < packetCount; i++) { //フルタイムコードであれば処理をする if ((packet->data[0] == 0xF0) && (packet->data[1] == 0x7F) && (packet->data[2] == 0x7F) && (packet->data[3] == 0x01) && (packet->data[4] == 0x01)) { [(id)readProcRefCon setFullTimecode:packet]; } //次のパケットへ進む packet = MIDIPacketNext(packet); } [pool drain]; } - (void)setFullTimecode:(MIDIPacket *)packet { OSStatus err; SMPTETime smpteTime; smpteTime.mType = kSMPTETimeType30; smpteTime.mHours = packet->data[5] & 0x0F; smpteTime.mMinutes = packet->data[6]; smpteTime.mSeconds = packet->data[7]; smpteTime.mFrames = packet->data[8]; smpteTime.mSubframeDivisor = 80; smpteTime.mSubframes = 0; CAClockSeconds seconds; err = CAClockSMPTETimeToSeconds( clockRef, &smpteTime, &seconds); if (err != noErr) { NSLog(@"SMPTETimeToSecond err = %d", err); return; } NSNumber *secondsNumber = [NSNumber numberWithDouble:seconds]; [self performSelectorOnMainThread:@selector(setCurrentTime:) withObject:secondsNumber waitUntilDone:NO]; } #pragma mark - #pragma mark -- タイムコードをセット -- - (void)setCurrentTime:(NSNumber *)secondsNumber { CAClockSeconds seconds = [secondsNumber doubleValue]; if (!isStart) { CAClockTime time; time.format = kCAClockTimeFormat_Seconds; time.time.seconds = seconds; OSStatus err = CAClockSetCurrentTime(clockRef, &time); if (err != noErr) { NSLog(@"set setCurrentTime err"); } } else { keepSeconds = seconds; } } #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; } < br /> //シンクモードを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; } //MTCが停止しても動き続ける時間を設定する CAClockSeconds freeWheelTime = 0.2; size = sizeof(freeWheelTime); err = CAClockSetProperty( clockRef, kCAClockProperty_MTCFreewheelTime, size, &freeWheelTime); if (err != noErr) { NSLog(@"set MTCFreewheelTime 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; } // // フルタイムコードを受信するための設定 // //MIDIクライアントを作成する NSString *clientName = @"inputClient"; err = MIDIClientCreate( (CFStringRef)clientName, NULL, NULL, &clientRef); if (err != noErr) { NSLog(@"MIDIClientCreate err = %d", err); goto end; } //MIDIポートを作成する NSString *inputPortName = @"inputPort"; err = MIDIInputPortCreate( clientRef, (CFStringRef)inputPortName, MIDIInputProc, self, &inputPortRef); if (err != noErr) { NSLog(@"MIDIInputPortCreate err = %d", err); goto end; } //MIDIエンドポイントをポートに接続する err = MIDIPortConnectSource(inputPortRef, srcPointRef, NULL); if (err != noErr) { NSLog(@"MIDIPortConnectSource 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 = MIDIPortDisconnectSource(inputPortRef, srcPointRef); if (err != noErr) NSLog(@"MIDIPortDisconnectSource Err"); err = MIDIPortDispose(inputPortRef); if (err != noErr) NSLog(@"MIDIPortDispose Err"); err = MIDIClientDispose(clientRef); if (err != noErr) NSLog(@"MIDIClientDispose 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, tSecond s, tFrames, tPlayRate]; [textField setStringValue:tSMPTEString]; } @end