プログラミング > Core Audio - Mac >

Core Audio Clock その3 MTCスレーブ改


前回の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;
    }
   
    //シンクモードを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, tSeconds, tFrames, tPlayRate];
   
    [textField setStringValue:tSMPTEString];
}

@end

トラックバック(0)

このブログ記事を参照しているブログ一覧: Core Audio Clock その3 MTCスレーブ改

このブログ記事に対するトラックバックURL: http://objective-audio.jp/oa80/mt-tb.cgi/63

コメントする


画像の中に見える文字を入力してください。