波形の更新機能を追加する【実践的Macintoshプログラミング解説】

印刷用表示 |テキストサイズ 小 |中 |大 |

CoreData版 Repeating Motif Generator の開発 Repeating Motif Wonderland CoreData 実践的 Macintosh プログラミング解説

LinkIconホーム

更新日 2009-05-24

モデルを作る

波形の更新機能を追加する

 このままではwaveformが全く変化しないので、Oscillatorのパラメータが変更された時に、波形を更新する機能を追加します。

Oscillator出力を計算する

 旧バージョンではOscillator出力を計算するメソッドは以下の様になっていました。

- (double)output:(double)normalizedPhase
{
    return  amplitude*sin(2*pi*(frequency*normalizedPhase + normalizedPhaseLag));
}

 引数のnormalizedPhaseは0から2πまで変化する位相を0から1までに規格化した値です。同様にnormalizedPhaseLagは位相のずれであるphaseLagを規格化した値です。

 前はパラメータの値がインスタンス変数に入っていたので、インスタンス変数をそのまま使って計算すればよかったのですが、今はそうはいきません。アクセサメソッドを使って読み出す必要があります。

 また以前は円周率πの値をインスタンス変数に予め入れておいたのですが、面倒なのでマクロで対応する事にします。以下のマクロをOscillatorCD.mに追加します。

#define     PI      acos(-1)

 normalizedPhaseLagはphaseLagを変更する際に一緒に変更していたのですが、インスタンス変数を用意するのはやめて都度計算する事にします。
 するとoutput:メソッドは以下の様になります

// 
//  OscillatorCD.m
//

- (double)output:(double)normalizedPhase
{
    int     amplitude = [[self amplitude] intValue];
    int     frequency = [[self frequency] intValue];
    double  normalizedPhaseLag = [[self phaseLag] intValue]/360.0;

    return  amplitude*sin(2*PI*(frequency*normalizedPhase + normalizedPhaseLag));
}

波形を更新する

 波形を更新するメソッドupdateWaveformは、旧バージョンではこうなっていました。 normalizedPhaseを0から1まで変化させて発振器出力を計算し、その結果をつなげているわけです。

- (void)updateWaveform
{
    int     i;
    int     resolution = [mixer resolution];
    double  output,normalizedPhase;

    NSBezierPath *path = [NSBezierPath bezierPath];

    for(i=0;i<resolution;i++)
    {
        normalizedPhase = i/(double)resolution;
        output = [self output:normalizedPhase];
        if(i == 0)
            [path moveToPoint:NSMakePoint(i,output)];
        else
            [path lineToPoint:NSMakePoint(i,output)];
    }
    [self setWaveform:path];
}

 ここでmixerからresolutionというパラメータを得ていますが、ミキサーはいずれ追加するクラスではあるものの、まだ存在しません。そこで、ここは後で修正する事にして、とりあえず固定値にしておきます。

// 
//  OscillatorCD.m
//

- (void)updateWaveform
{
    int     i;
    int     resolution = 200;
    double  output,normalizedPhase;

    NSBezierPath *path = [NSBezierPath bezierPath];

    for(i=0;i<resolution;i++)
    {
        normalizedPhase = i/(double)resolution;
        output = [self output:normalizedPhase];
        if(i == 0)
            [path moveToPoint:NSMakePoint(i,output)];
        else
            [path lineToPoint:NSMakePoint(i,output)];
    }
    [self setWaveform:path];
}

 updateWameformメソッドは他のクラスからも呼び出される事になる予定なので、インターフェイスファイルに追加しておきます。

// 
//  OscillatorCD.h
//

#import <CoreData/CoreData.h>
#import "RMGRoot.h"


@interface OscillatorCD :  RMGRoot  
{
}

- (void)updateWaveform;

- (NSNumber *)amplitude;
- (void)setAmplitude:(NSNumber *)value;

- (NSNumber *)frequency;
- (void)setFrequency:(NSNumber *)value;

- (NSNumber *)phaseLag;
- (void)setPhaseLag:(NSNumber *)value;

- (NSBezierPath *)waveform;
- (void)setWaveform:(NSBezierPath *)value;

@end

Setterで波形を更新する

 パラメータが変更された時に波形を更新したいので、OscillatorパラメータのSetterで、波形を更新するメソッドを呼び出します。 

// 
//  OscillatorCD.m
//

- (void)setAmplitude:(NSNumber *)value
{
    [self setValue:value forKey:@"amplitude" action:@"setAmplitude"];
    [self updateWaveform];
}

- (void)setFrequency:(NSNumber *)value 
{
    [self setValue:value forKey:@"frequency" action:@"setFrequency"];
    [self updateWaveform];
}

- (void)setPhaseLag:(NSNumber *)value 
{
    [self setValue:value forKey:@"phaseLag" action:@"setPhaseLag"];
    [self updateWaveform];
}

awakeFromInsertとawakeFromFetch

 管理対象オブジェクトの初期化コードはawakeFromInsertかawakeFromFetchに記述します。awakeFromInsertは管理対象オブジェクトがオブジェクトグラフに挿入された時、すなわち管理対象オブジェクトが新規作成された時に呼び出されます。
 属性の初期値はモデリングツールでデフォルト値を設定できるので、わざわざここで初期化コードを書く必要はありません。ただしwaveform属性はデータ型を未定義としているので、デフォルト値がありません。したがって初期化のためのコードが必要です。

 awakeFromFetchは管理対象オブジェクトが永続ストアから読み込まれた時に呼び出されます。したがって「一時」がチェックされていない属性は永続ストアに保存されていた値がセットされており、初期化する必要はありません。
 「一時」がチェックされている属性は、ここで適切な値で初期化する必要があります。Oscillatorのwaveform属性は一時がチェックされているのでawakeFromFetchで初期化します。

awakeFromInsertとawakeFromFetchで初期化を実行

 awakeFromInsertでもawakeFromFetchでもwaveform属性の初期化が必要です。そこでcommonAwakeというメソッドを作って、そこで共通の初期化処理を実行します。

 waveformを更新するにはupdateWaveformメソッドを呼びだすだけでOKです。よってOscillatorの初期化コードは以下の様になります。

 重要事項としてデベロッパドキュメントに載っていますが、サブクラスは自分の初期化コードを実行する前にスーパークラスのawakeFromInsertを呼び出さないといけません。これはawakeFromFetchでも同じです。

// 
//  OscillatorCD.m
//

#pragma mark -
#pragma mark Initializer

- (void)commonAwake
{
    [self updateWaveform];
}

- (void)awakeFromInsert
{
    [super awakeFromInsert];
    [self commonAwake];
}

- (void)awakeFromFetch
{
    [super awakeFromFetch];
    [self commonAwake];
}