Core Audio Clock その2 MTCスレーブ

Pocket

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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です