全般 – Mac」カテゴリーアーカイブ

テンポラリファイル名を取得する

テンポラリのディレクトリの中のテンポラリのファイル名を取得するサンプル。

NSTemporaryDirectory()でテンポラリフォルダを取得。mkstempsでテンポラリファイル名が取得できるが、一旦書き込み可能な文字列の配列にコピーしないといけない。NSStringのlengthでは日本語とか含まれている場合、取得した長さが配列の長さと違うので、strlenで配列の長さを取得してコピーする。

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = 
        [[NSAutoreleasePool alloc] init];

    NSString *tempDir = NSTemporaryDirectory();
    NSString *filePath = 
        [tempDir stringByAppendingPathComponent:
            @"prefixXXXXXXsuffix"];
    
    size_t bufferSize = 
        strlen([filePath fileSystemRepresentation]) + 1;
    char buffer[bufferSize];
    if ([filePath getFileSystemRepresentation:buffer 
            maxLength:bufferSize]) {
        if (mkstemps(buffer, 6) != -1) {
            NSLog(@"TemporaryFile = '%s'", buffer);
        }
    }
    
    [pool drain];
    return 0;
}

NSInvocationの作成

NSInvocationOperationとか使っていて、NSInvocationの作り方が良くわからなかったので、自分で作る方法を調べてみました。以下のコードがNSInvocationを使ってtest:というメソッドを呼び出してみたものです。

#import <Cocoa/Cocoa.h>

@interface InvocationTest : NSObject {
    
}

@end

@implementation InvocationTest

- (void)awakeFromNib
{
    SEL selector;
    NSMethodSignature *signature;
    NSInvocation *invocation;
    
    selector = @selector(test:);
    signature = [[self class] 
        instanceMethodSignatureForSelector:selector];
    
    if (signature) {
        invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setSelector:selector];
        [invocation setTarget:self];
        
        NSString *sendString = @"send";
        [invocation setArgument:&sendString atIndex:2];
        
        [invocation invoke];

        id returnValue;
        [invocation getReturnValue:&returnValue];
        
        NSLog(@"%@", returnValue);
    }
    
    [NSApp terminate:self];
}

- (id)test:(id)object
{
    NSLog(@"call test with %@", object);
    return @"return";
}

@end

まず、NSMethodSignatureというのがないとNSInvocationがインスタンス化できないので、instanceMethodSignatureForSelector:で作成します。これはNSObjectのクラスオブジェクトですが、呼び出すクラスにお目当てのメソッドが無いとNSMethodSignatureの作成に成功しません。

NSMethodSignatureのインスタンスが作成できたら、NSInvocationのクラスメソッドであるinvocationWithMethodSignature:でNSInvocationのインスタンスを作成します。

ターゲットとなるオブジェクトと、ここでもセレクタを設定します。さらに引数も渡せますが、setArgument:index:メソッドのインデックスに渡す数字は2からになります。リファレンスには0はselfで、1は_cmdがすでに使われているというような事が書いてあります。

invokeで登録したメソッドが呼び出されます。返り値がある場合はNSInvocationのインスタンスに保持されていますのでgetReturnValueで受け取ります。

動き続けるCALayer

CoreAnimationでCALayerを動かし続けたい時の方法です。プロパティ変更での暗黙的なアニメーションだといろいろと不都合なので、CABasicAnimationを使ってやってみました。

以下がそのサンプルです。カスタムビューにアウトレットをつなげて実行してください。

//
//  EndlessCoreAnimationTest.h
//

#import <Cocoa/Cocoa.h>
#import <QuartzCore/QuartzCore.h>

@interface EndlessCoreAnimationTest : NSObject {

    IBOutlet NSView *view;
    CALayer *moveLayer;
    NSTimer *timer;
    NSDate *startDate;
}

- (void)changeAnimation;

@end


//
//  EndlessCoreAnimationTest.m
//

#import "EndlessCoreAnimationTest.h"


@implementation EndlessCoreAnimationTest

- (void)awakeFromNib
{
    NSRect viewRect = [view bounds];
    CGFloat viewWidth = viewRect.size.width;
    CGFloat viewHeight = viewRect.size.height;
    CGFloat minSize = (viewWidth > viewHeight) ? viewHeight : viewWidth;
    
    CALayer *baseLayer = [CALayer layer];
    CGColorRef baseColor = CGColorCreateGenericRGB(0.0, 0.0, 0.0, 1.0);
    baseLayer.backgroundColor = baseColor;
    CGColorRelease(baseColor);
    
    moveLayer = [CALayer layer];
    moveLayer.anchorPoint = CGPointMake(0.5, - 9.0);
    moveLayer.bounds = CGRectMake(0, 0, minSize * 0.05, minSize * 0.05);
    moveLayer.position = CGPointMake(viewWidth * 0.5, viewHeight * 0.5);
    CGColorRef moveColor = CGColorCreateGenericRGB(1.0, 0.0, 0.0, 1.0);
    moveLayer.backgroundColor = moveColor;
    CGColorRelease(moveColor);
    
    [view setLayer:baseLayer];
    [view setWantsLayer:YES];
    [baseLayer addSublayer:moveLayer];
    
    [self performSelector:@selector(startTimer) 
        withObject:nil afterDelay:1.0];
}

- (void)startTimer
{
    startDate = [[NSDate date] retain];
    [self changeAnimation];
    timer = [NSTimer scheduledTimerWithTimeInterval:1.0 
        target:self selector:@selector(changeAnimation) 
        userInfo:nil repeats:YES];
}

- (void)changeAnimation
{
    CGFloat currentTime = [startDate timeIntervalSinceNow];
    CGFloat duration = 2.0;
    
    CABasicAnimation *anim = 
        [CABasicAnimation animationWithKeyPath:@"transform"];
    CATransform3D fromTrans = 
        CATransform3DMakeRotation(currentTime * M_PI / 4.0, 0.0, 0.0, 1.0);
    CATransform3D toTrans = 
        CATransform3DMakeRotation((currentTime - duration) * M_PI / 4.0, 
        0.0, 0.0, 1.0);
	
    anim.fromValue = [NSValue valueWithCATransform3D:fromTrans];
    anim.toValue = [NSValue valueWithCATransform3D:toTrans];
    anim.duration = duration;
    
    [moveLayer addAnimation:anim forKey:@"transformAnimation"];
}


- (void) dealloc
{
    [timer invalidate];
    [startDate release];
    
    [super dealloc];
}

@end

durationを2秒にしてtoValueを2秒後の値にしたCABasicAnimationを、NSTimerで1秒ごとに途切れる事無く追加しています。追加する時に動いているアニメーションの位置が新たなアニメーションのfromValueと一致するようになっていればスムーズにつながります。

再生スライダー

iPodとかQuickTimePlayerのような、再生する位置を表示・変更できるようなスライダーを使いたいと思って、NSSliderにジャカジャカ位置を送っていたら、うまいことクリックして変更できなくて、あれこれ調べたらNSSliderからNSSliderCellを取得してmouseDownFlagsってのをチェックすれば良い事が分かりました。

そこらへんを検証してみたコードが以下のような感じです。タイマーはNSEventTrackingRunLoopModeでスライダーを変更中も関係なく動かしていますが、マウスで操作中のときは値を送らないようにしています。

#import <Cocoa/Cocoa.h>

@interface Controller : NSObject {
	
    IBOutlet NSSlider *slider;
    float sliderValue;
}

- (IBAction)setValue:(id)sender;

@end

@implementation Controller

- (void)awakeFromNib
{
    [slider setMinValue:0];
    [slider setMaxValue:100];
    [slider setContinuous:NO];
	
    NSTimer *timer = 
        [NSTimer scheduledTimerWithTimeInterval:0.05
                                         target:self 
                                       selector:@selector(sendSliderValue) 
                                       userInfo:nil 
                                        repeats:YES];
	
    [[NSRunLoop currentRunLoop] addTimer:timer
        forMode:NSEventTrackingRunLoopMode];
}

- (void)sendSliderValue
{
    NSSliderCell *cell = [slider cell];

    if (![cell mouseDownFlags]) {
		
        sliderValue++;
		
        if (sliderValue > 100) {
            sliderValue = 0;
        }
		
        [slider setFloatValue:sliderValue];
    }
}

- (IBAction)setValue:(id)sender
{
    sliderValue = [sender floatValue];
}

@end

dBとリニア値を変換する

0.0 = -inf dB、1.0 = 0dBとした場合。

リニア値からdBへ変換するには、

dBVolume = 20.0*log10(linearVolume);

dBからリニア値へ変換するには、

linearVolume = pow(10.0, dBVolume/20.0);

で、できる

ちなみにvDSPにはvDSP_vdbconという関数があり、リニア値からdBへの変換はできる。

void  vDSP_vdbcon (
   float * A,
   vDSP_Stride I,
   float * B,
   float * C,
   vDSP_Stride K,
   vDSP_Length N,
   unsigned int F);

Aはインプットする配列、Cはアウトプットされる配列(Aと同じでも可)、IとKにはそれぞれのストライド、Nには変換する配列の長さ、Bはzero referenceなので1.0を、Fには元のデータがpowerなら0、amplitudeなら1を指定となっているので1を指定する。