OscillatorParameterViewを作る
発振器のパラメータをアニメーションで表すビューOscillatorParameterViewを作ります。アニメーションを実行するためにMacOS X 10.4で追加されたNSAnimationクラスを使います。10.4をサポートしたいのでCore Animationは使いません。
OscillatorParameterViewのインターフェイスファイル
OscillatorParameterViewはNSViewのサブクラスとします。NSAnimationはサブクラスを作らずにそのまま使い、自分をデリゲートに指定する事で定期的にメッセージを受ける様にします。
increasing |
行ったり来たりするアニメーションを実行したいので、値が増えているのか減っているのかを表すパラメータが必要です。increasingがYESなら値が増えている事を表します。
|
|---|---|
kind |
振幅、周波数、位相のずれのどのアニメーションを表示するのかを表します。
|
theAnimation |
NSAnimationクラスのインスタンスです。
|
path |
表示に使うベジエパスです。
|
//
// OscillatorParameterView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface OscillatorParameterView : NSView
{
BOOL increasing;
int kind;
NSAnimation *theAnimation;
NSBezierPath *path;
}
- (void)setKind:(int)aKind;
@end
初期化メソッド
OscillatorViewをドラッグする事ができますよ、という事をアピールするため、マウスポインタがOscillatorViewの中に入ってきたらマウスカーソルの形を変える事にします。マウスカーソルの形を変えるにはNSViewのresetCursorRectsメソッドを使います。デベロッパドキュメントには以下の様に書いてあります。
//
// OscillatorParameterView.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import "OscillatorParameterView.h"
#define RESOLUTION 20
#define PI acos(-1)
@implementation OscillatorParameterView
#pragma mark -
#pragma mark Initializer and Deallocator
- (id)initWithFrame:(NSRect)frame
{
int i;
self = [super initWithFrame:frame];
if (self)
{
increasing = YES;
theAnimation = [[NSAnimation alloc] initWithDuration:2
animationCurve:NSAnimationLinear];
for(i=0;i<=10;i++)
[theAnimation addProgressMark:i*0.1];
[theAnimation setDelegate:self];
[theAnimation setAnimationBlockingMode:NSAnimationNonblocking];
}
return self;
}
- (void)dealloc
{
[theAnimation stopAnimation];
[theAnimation release];
[super dealloc];
}
振幅パラメータを表すパス
振幅を変化させるパラメータである事を表すために、正弦波の振幅が変化するアニメーションを表示します。そのために引数のaProgressに応じた大きさの振幅で描かれたベジエパスを作成し、返します。
2を引いているのは線の太さ分縮めている意味で、最後に1を足しているのはセンタリングの意味です。
//
// OscillatorParameterView.m
//
#pragma mark -
#pragma mark Animation Path
- (NSBezierPath *)newAmplitudePath:(double)aProgress
{
int i;
NSBezierPath *newPath = [NSBezierPath bezierPath];
[newPath moveToPoint:NSMakePoint(0, (NSHeight([self bounds])-2)/2+1)];
for(i=1;i<=RESOLUTION;i++)
{
[newPath lineToPoint:
NSMakePoint(i/(double)RESOLUTION*(NSWidth([self bounds])-2)+1,
(NSHeight([self bounds])-2)/2*(1+sin(2*PI*i/(double)RESOLUTION)*aProgress)+1)];
}
[newPath setLineWidth:2];
return newPath;
}
周波数パラメータを表すパス
周波数を変化させるパラメータである事を表すために、正弦波の周波数が変化するアニメーションを表示します。そのために引数のaProgressに応じた大きさの周波数で描かれたベジエパスを作成し、返します。
2を引いているのは線の太さ分縮めている意味で、最後に1を足しているのはセンタリングの意味です。
//
// OscillatorParameterView.m
//
- (NSBezierPath *)newFrequencyPath:(double)aProgress
{
int i;
NSBezierPath *newPath = [NSBezierPath bezierPath];
[newPath moveToPoint:NSMakePoint(0, (NSHeight([self bounds])-2)/2+1)];
for(i=1;i<=RESOLUTION;i++)
{
[newPath lineToPoint:
NSMakePoint(i/(double)RESOLUTION*(NSWidth([self bounds])-2)+1,
(NSHeight([self bounds])-2)/2*(1+sin(2*PI*i/(double)RESOLUTION*(1+aProgress)))+1)];
}
[newPath setLineWidth:2];
return newPath;
}
位相ずれパラメータを表すパス
位相のずれを変化させるパラメータである事を表すために、正弦波の位相が変化するアニメーションを表示します。そのために引数のaProgressに応じた大きさの初期位相で描かれたベジエパスを作成し、返します。
2を引いているのは線の太さ分縮めている意味で、最後に1を足しているのはセンタリングの意味です。
//
// OscillatorParameterView.m
//
- (NSBezierPath *)newPhaseLagPath:(double)aProgress
{
int i;
double x,y;
NSBezierPath *newPath = [NSBezierPath bezierPath];
for(i=0;i<=RESOLUTION;i++)
{
x = i/(double)RESOLUTION*(NSWidth([self bounds])-2)+1;
y = (NSHeight([self bounds])-2)/2*(1+sin(2*PI*i/(double)RESOLUTION+aProgress*1.2*PI))+1;
if(i==0)
[newPath moveToPoint:NSMakePoint(x,y)];
else
[newPath lineToPoint:NSMakePoint(x,y)];
}
[newPath setLineWidth:2];
return newPath;
}
デリゲートメソッドを実装する
アニメーションが終了するとanimationDidEnd:メッセージが送られてきます。ここで何もしないとアニメーションを一回実行しただけで終わってしまうので、再度スタートをかけます。再スタートをかける前にincreasingフラグを反転して、アニメーションが行ったり来たりする様にします。これでムービーでいうパリンドローム再生(再生、逆再生を繰り返す)の様になります。
//
// OscillatorParameterView.m
//
#pragma mark -
#pragma mark Animation Delegate methods
- (void)animation:(NSAnimation *)animation didReachProgressMark:(NSAnimationProgress)progress
{
double aProgress;
NSBezierPath *newPath;
aProgress = increasing ? progress : 1 - progress;
switch(kind)
{
case 0:newPath = [self newAmplitudePath:aProgress]; break;
case 1:newPath = [self newFrequencyPath:aProgress]; break;
case 2:newPath = [self newPhaseLagPath:aProgress];
}
[path release];
path = [newPath retain];
[self setNeedsDisplay:YES];
}
- (void)animationDidEnd:(NSAnimation *)animation
{
increasing = !increasing;
[animation startAnimation];
}
描画、アクセサメソッド
後はたいした事はないので、特に説明する事はありません。
//
// OscillatorParameterView.m
//
#pragma mark -
#pragma mark Event Handler
- (void)drawRect:(NSRect)rect
{
[path stroke];
}
#pragma mark -
#pragma mark Accessor methods
- (void)setKind:(int)aKind
{
kind = aKind;
[theAnimation startAnimation];
}
EnclosureViewを変更する
発振器のパラメータを設定するスライダーを削除したので、EnclosureViewが変更になります。また発振器のパラメータを設定するビューをミキサーのビューと発振器のビューの隙間に押し込む事にしたので、ビューがリサイズされた時の並べ替えをしているコードに追加が発生します。
OscillatorParameterViewのkindを設定する作業も必要です。
EnclosureViewのインターフェイスファイル
発振器のパラメータを設定するビューが入ったNSBoxのアウトレットを追加します。これはビューがリサイズされた時の並べ替えをしているコードで使います。
OscillatorParameterViewのアウトレットを追加します。kindを設定するのに使います。
振幅、周波数、位相ずれを設定するスライダーを削除するので、そのアウトレットも削除します。
//
// EnclosureView.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@class OscillatorView, BezierPathView, RMGDocument, OscillatorParameterView;
@interface EnclosureView : NSView
{
IBOutlet OscillatorView *x1View,*x2View,*x3View;
IBOutlet OscillatorView *y1View,*y2View,*y3View;
IBOutlet BezierPathView *viewX,*viewY,*viewMaster;
IBOutlet NSView *lineX,*lineY;
IBOutlet NSView *lineX1,*lineX2,*lineX3;
IBOutlet NSView *lineY1,*lineY2,*lineY3;
IBOutlet NSArrayController *controller;
IBOutlet NSButton *randomizeButton;
IBOutlet NSTextField *amplitudeTextField, *frequencyTextField, *phaseLagTextField;
IBOutlet NSSlider *amplitudeSlider, *frequencySlider, *phaseLagSlider;
IBOutlet NSStepper *amplitudeStepper, *frequencyStepper, *phaseLagStepper;
IBOutlet RMGDocument *document;
IBOutlet NSBox *oscParam;
IBOutlet OscillatorParameterView *amplitudeParameter, *frequencyParameter, *phaseLagParameter;
int whiteSpace,viewMargin,lineLength;
OscillatorView *selectedView;
}
- (void)selectOscillator:(OscillatorView *)sender;
@end
OscillatorParamaterViewのkindを設定する
awakeFromNibでアウトレットが有効になるので、ここでOscillatorParamaterViewのkindを設定します。
//
// EnclosureView.m
//
- (void)awakeFromNib
{
NSSize sizeLineX = NSMakeSize(1,lineLength);
NSSize sizeLineY = NSMakeSize(lineLength,1);
selectedView = nil;
[self selectOscillator:x1View];
[lineX1 setFrameSize:sizeLineX];
[lineX3 setFrameSize:sizeLineX];
[lineY1 setFrameSize:sizeLineY];
[lineY2 setFrameSize:sizeLineY];
[self frameDidChange:nil];
[viewMaster setKeyPath:@"selection" isXView:NO];
[viewX setKeyPath:@"selection.x" isXView:YES];
[viewY setKeyPath:@"selection.y" isXView:NO];
[x1View setKeyPath:@"selection.x.osc1" isXView:YES];
[x2View setKeyPath:@"selection.x.osc2" isXView:YES];
[x3View setKeyPath:@"selection.x.osc3" isXView:YES];
[y1View setKeyPath:@"selection.y.osc1" isXView:NO];
[y2View setKeyPath:@"selection.y.osc2" isXView:NO];
[y3View setKeyPath:@"selection.y.osc3" isXView:NO];
NSArray *views = [NSArray arrayWithObjects:
viewMaster, viewX, viewY, x1View, x2View, x3View, y1View, y2View, y3View, nil];
NSEnumerator *enumerator = [views objectEnumerator];
NSView *view;
while(view = [enumerator nextObject])
[view bind:@"markerPosition" toObject:document withKeyPath:@"markerPosition" options:nil];
[amplitudeParameter setKind:0];
[frequencyParameter setKind:1];
[phaseLagParameter setKind:2];
}
スライダー削除に伴う変更
発振器を切替える際にスライダーのバインディングを切替えているコードがありますが、そこを削除します。
//
// EnclosureView.m
//
- (void)selectOscillator:(OscillatorView *)sender
{
if(sender == selectedView) return;
[selectedView setSelected:NO];
[sender setSelected:YES];
selectedView = sender;
[self bind:amplitudeTextField withKeyPath:[self amplitudeKeyPath]];
[self bind:amplitudeSlider withKeyPath:[self amplitudeKeyPath]];
[self bind:amplitudeStepper withKeyPath:[self amplitudeKeyPath]];
[self bind:frequencyTextField withKeyPath:[self frequencyKeyPath]];
[self bind:frequencySlider withKeyPath:[self frequencyKeyPath]];
[self bind:frequencyStepper withKeyPath:[self frequencyKeyPath]];
[self bind:phaseLagTextField withKeyPath:[self phaseLagKeyPath]];
[self bind:phaseLagSlider withKeyPath:[self phaseLagKeyPath]];
[self bind:phaseLagStepper withKeyPath:[self phaseLagKeyPath]];
}
リサイズ時にビューを並べ替える
発振器のパラメータを設定するビューを小型化して、ミキサーのビューと発振器のビューに囲まれる位置に配置します。追加したNSBoxのアウトレットを使って配置するコードを追加します。
//
// EnclosureView.m
//
- (void)frameDidChange:(NSNotification *)aNotification
{
int width,height,viewSize,marginWidth,marginHeight;
NSSize sizeOfView;
NSSize sizeLineX,sizeLineY,longSizeLineX,longSizeLineY;
NSPoint originLineX,originLineY;
NSPoint originViewX,originViewY,originViewMaster,originRandomize;
NSPoint originViewX1,originViewX2,originViewX3;
NSPoint originViewY1,originViewY2,originViewY3;
NSPoint originLineX1,originLineX2,originLineX3;
NSPoint originLineY1,originLineY2,originLineY3;
NSPoint originOscParam;
// 接続線にアンチエイリアスがかかってぼやけない様に、widthとheightを偶数にする
width = ([self bounds].size.width - whiteSpace*2 - viewMargin*3 - lineLength*2)/8;
width *= 2;
height = ([self bounds].size.height - whiteSpace*2 - viewMargin*2 - lineLength*2)/6;
height *= 2;
viewSize = width < height ? width : height;
marginWidth = ([self bounds].size.width - viewSize*4 - viewMargin*3 - lineLength*2)/2;
marginHeight = ([self bounds].size.height - viewSize*3 - viewMargin*2 - lineLength*2)/2;
// Y軸のOscillatorViewを配置する
originViewY1.x = originViewY2.x = originViewY3.x = marginWidth;
originViewY3.y = marginHeight;
originViewY2.y = originViewY3.y + viewSize + viewMargin;
originViewY1.y = originViewY2.y + viewSize + viewMargin;
[y1View setFrameOrigin:originViewY1];
[y2View setFrameOrigin:originViewY2];
[y3View setFrameOrigin:originViewY3];
// Y軸のOscillatorViewにつながる接続線を配置する
originLineY1.x = originLineY2.x = originLineY3.x = originViewY1.x + viewSize + viewMargin;
originLineY1.y = originViewY1.y + viewSize*0.5;
originLineY2.y = originViewY2.y + viewSize*0.5;
originLineY3.y = originViewY3.y + viewSize*0.5;
[lineY1 setFrameOrigin:originLineY1];
[lineY2 setFrameOrigin:originLineY2];
[lineY3 setFrameOrigin:originLineY3];
// 一番下の接続線を長くする
longSizeLineY = NSMakeSize(lineLength*2,1);
[lineY3 setFrameSize:longSizeLineY];
// Y軸の縦の接続線を配置する
originLineY.x = originLineY3.x + lineLength;
originLineY.y = originLineY3.y;
sizeLineY = NSMakeSize(1,viewMargin*2+viewSize*2);
[lineY setFrameOrigin:originLineY];
[lineY setFrameSize:sizeLineY];
// Y軸のMixerViewを配置する
originViewY.x = originLineY3.x + longSizeLineY.width;
originViewY.y = originViewY3.y;
[viewY setFrameOrigin:originViewY];
// MasterMotifViewを配置する
originViewMaster.x = originViewY.x + viewSize + viewMargin;
originViewMaster.y = originViewY.y;
[viewMaster setFrameOrigin:originViewMaster];
// Buttonを配置する
originRandomize.x = originViewMaster.x + viewSize + viewMargin;
originRandomize.y = originViewMaster.y;
[randomizeButton setFrameOrigin:originRandomize];
// X軸のMixerViewを配置する
originViewX.x = originViewMaster.x;
originViewX.y = originViewMaster.y + viewSize + viewMargin;
[viewX setFrameOrigin:originViewX];
// 発振器のパラメータを配置する
originOscParam.x = originViewY.x + (viewSize - NSWidth([oscParam bounds]))/2;
originOscParam.y = originLineY2.y - NSHeight([oscParam bounds])/2;
[oscParam setFrameOrigin:originOscParam];
// X軸の横の接続線を配置する
originLineX.x = originViewX.x - viewSize*0.5 - viewMargin;
originLineX.y = originViewX.y + viewSize + lineLength;
[lineX setFrameOrigin:originLineX];
sizeLineX = NSMakeSize(viewMargin*2+viewSize*2,1);
[lineX setFrameSize:sizeLineX];
// X軸の縦の接続線を配置する
originLineX1.x = originLineX.x;
originLineX2.x = originLineX1.x + viewSize + viewMargin;
originLineX3.x = originLineX2.x + viewSize + viewMargin;
originLineX1.y = originLineX3.y = originLineX.y;
originLineX2.y = originLineX.y - lineLength;
[lineX1 setFrameOrigin:originLineX1];
[lineX2 setFrameOrigin:originLineX2];
[lineX3 setFrameOrigin:originLineX3];
// 真ん中の接続線を長くする
longSizeLineX = NSMakeSize(1,lineLength*2);
[lineX2 setFrameSize:longSizeLineX];
// X軸のOscillatorViewを配置する
originViewX1.x = originViewX.x - viewSize - viewMargin;
originViewX2.x = originViewX.x;
originViewX3.x = originViewX.x + viewSize + viewMargin;
originViewX1.y = originViewX2.y = originViewX3.y = originLineX.y + lineLength + viewMargin;
[x1View setFrameOrigin:originViewX1];
[x2View setFrameOrigin:originViewX2];
[x3View setFrameOrigin:originViewX3];
// サイズをセットする
sizeOfView = NSMakeSize(viewSize,viewSize);
[viewMaster setFrameSize:sizeOfView];
[viewX setFrameSize:sizeOfView];
[viewY setFrameSize:sizeOfView];
[x1View setFrameSize:sizeOfView];
[x2View setFrameSize:sizeOfView];
[x3View setFrameSize:sizeOfView];
[y1View setFrameSize:sizeOfView];
[y2View setFrameSize:sizeOfView];
[y3View setFrameSize:sizeOfView];
[randomizeButton setFrameSize:sizeOfView];
}

ホーム
前へ