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付近になります。