//--------------------------------------------------------------------------------------
// 音楽の1ch分の処理
//--------------------------------------------------------------------------------------
// 此処から先はコンパイル済みMMLで動作する

#import "Music1ch.h"
#import "libs.h"

@interface Music1ch()
{
	// MMLインタープリター制御構造体
	MML_INTERPRETER	mml;
	MML_RESULT 		mr;

	__strong NSString *comment;

	// 現在の情報
	BOOL	fmute;		// mute中かどうか

	// 現在利用中音色情報(単音用)
	MusicPlayTone	*playTone1;
	
	// 和音
	NSMutableArray	<MusicPlayTone *> *playTones;
	BOOL	fwaon;
	int 	 waon;
}
@end

@implementation Music1ch

//----------------------------------------------------------

// 初期化
// MusicTonesは別途確保してそれを持ってこちらを初期化すること
-(instancetype)initWithTones:(MusicTones *)_tones
	{
	printSelf();
	self=[super init];
	if (self!=nil) {
		// mmlパーサー初期化
		mml_init(&mml, NULL , NULL);
		
		// 音色管理
		tones=_tones;
		
		// 和音を考慮して確保
		playTones=[[NSMutableArray alloc]init];
		for (int i=0;i<MML_MAX_WAON;i++) {
			MusicPlayTone *playTone=[[MusicPlayTone alloc]init];
			[playTones addObject:playTone];
		}
		playTone1=playTones[0]; // 単音時は先頭を利用
		
		// 初期音色設定
		_toneNo=-1; // 最初に初期化する印
		self.panpot=MML_OPTION_DEFAULT_PANPOT;
		self.volume=MML_OPTION_DEFAULT_VOLUME;

		self.isFetch=NO; // 演奏してない
		_fpause=NO;
		[self mute:NO];
		comment=@""; // nilでは返せないので空文字列(最初のコメントが見つかるまで)

		// KVO設定
		[self addObserver:self forKeyPath:OBSERVE_FPAUSE options:NSKeyValueObservingOptionNew context:NULL];
	}
	return self;
}

//----------------------------------------------------------

// KVOによる変更通知受信
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
	// OS経由で受信するだけ
//	nslog(@"self=%@ KVO:%@=%@",self,keyPath,object);
//	nslog(@"fpause=%d",_fpause);
}

//----------------------------------------------------------
// 対外的setter&getter

-(void)setMmlObject:(char *)mmlObject
{
	printSelf();
	if (!self.isFetch) {
		// 今は演奏中ではないので即設定可能
		mml_setup(&mml, NULL, mmlObject);
		mr=MML_RESULT_OK;
	} else {
		nslog(@"fetch中なので設定できない");
		// このメソッドはsetterなのでエラーコードは返せないので
		mr=MML_RESULT_IN_FETCH;
	}
}

-(NSString *)comment
{
	return comment;
}

-(int)count
{
	return mml.count;
}

-(void)setTickUp:(volatile u_int64_t *)tickUp
{
	mml.tickUp=tickUp;
}

-(MML_RESULT)mr
{
	return mr;
}

-(void)setFpause:(int)fpause
{
	printSelf();
	_fpause=fpause;
	if (_fpause!=FPAUSE_NONE) {
		nslog(@"実停止までには時間がかかるので、先に音を消す");
		for (int i=0;i<MML_MAX_WAON;i++){
			MusicPlayTone *playTone=playTones[i];
			[playTone stop];
		}
	} else {
		nslog(@"演奏再開");
		// 外部でmuteがかけられているときは音は出ない
	}
}

//----------------------------------------------------------

-(void)mute:(BOOL)foff
{
	printSelf();
	if (foff) {
		nslog(@"消音");
		[playTone1 mute:YES];
		fmute=YES;
	} else {
		nslog(@"発音");
		[playTone1 mute:NO];
		fmute=NO;
	}
}

//----------------------------------------------------------
// 対内setter&getter

// toneNo=MML_MIN_TONE〜MML_MAX_TONE
-(void)setToneNo:(int)toneNo
{
	printSelf();
	if (toneNo<MML_MIN_TONE) toneNo=MML_MIN_TONE;
	if (toneNo>MML_MAX_TONE) toneNo=MML_MAX_TONE;
	toneNo-=MML_MIN_TONE;
	
	// 一番最初はinit内で_toneNo<0に設定しているので、どれとも一致しない
	if (_toneNo!=toneNo) {
		// 音色が変わる
		MusicTone1 *tone1=[tones getTone:toneNo]; // 新しい音色
		if (tone1==nil) {
			// この音色は未登録なのでデフォルト音にする
			toneNo=MML_OPTION_DEFAULT_SOUND;
			tone1=[tones getTone:toneNo]; // 新しい音色
		} // この音色は登録あり
		for (int i=0;i<MML_MAX_WAON;i++) {
			MusicPlayTone *playTone=playTones[i];
			playTone.tone1 =tone1;
			playTone.toneNo=toneNo;
		}
		_toneNo=toneNo;
	} else {
		nslog(@"前と同じなので切り替えなし");
	}
	nslog(@"音色=%d+1:%@(%@)",toneNo,playTone1,playTone1.tone1);
}

-(void)setPanpot:(int)panpot
{
	printSelf();
//	playTone1.panpot=panpot;
	for (int i=0;i<MML_MAX_WAON;i++) {
		MusicPlayTone *playTone=playTones[i];
		playTone.panpot=panpot;
	}
}

-(int)panpot
{
	// 現在のパンポットはMMLによって変化することがあるので常に問い合わせる
	// 和音では全てで共通なので
	return playTone1.panpot;
}

-(void)setVolume:(int)volume
{
	printSelf();
//	playTone1.volume=volume;
	for (int i=0;i<MML_MAX_WAON;i++) {
		MusicPlayTone *playTone=playTones[i];
		playTone.volume=volume;
	}
}

-(int)volume
{
	// 現在の音量はMMLによって変化することがあるので常に問い合わせる
	// 和音では全てで共通なので
	return playTone1.volume;
}

//----------------------------------------------------------
// MML解釈
// 	バックグラウンドで動く
//----------------------------------------------------------

// waitを強制的に止める
-(void)clearWaitEnd
{
	clearWaitEnd(&mml);
}

// usingTimers=2ならtim2だけ経過していればOK
// usingTimers=1ならtim1が　経過していればOK
-(BOOL)isWaitEnd
{
	return isWaitEnd(&mml);
}

// 終了待ち
-(BOOL)waitTickEnd
{
	printSelf();
	// ratioTickがあるときは、それで消音しなければならない
	// 1でも2でも経過したら終われば良い
	while (checkTimer(&mml)>0) {
		if (self.fpause!=FPAUSE_NONE) {
			// 待っている間に一時停止系が来た時は即時終了(あとは一時停止処理に任せる)
			return YES;
		}
		EnterRunLoop(0.0); // これがないと、他の並行動作・イベント取得処理が全て無視されておかしくなる(TouchEndedとか)
	}
	// 待ち終了
	return NO;
}

//----------------------------------------------------------

-(void)setFsync:(FSYNC_TYPE)fsync
{
	mml.fsync=fsync;
}

-(FSYNC_TYPE)fsync
{
	return mml.fsync;
}

//----------------------------------------------------------
#// 全CH同時処理用

-(MML_RESULT)fetch1chStart
{
	printSelf();
	if (mml.mmlObjTop==NULL) {
		nslog(@"MMLオブジェクトが存在しない");
		mr=MML_RESULT_NO_MML;
	} else {
		mr=mml_fetch_start(&mml);
	}
	return mr;
}

// 1コマンドフェッチ
-(MML_RESULT)fetch1chOnce
{
	printSelf();
	mr=mml_fetch_once(&mml,self,@selector(mml_callback:));
	return mr;
}

// 前音を止める
-(void)toneStop
{
	printSelf();
	[playTone1 toneStop];
}

// 全音を止める
-(void)stop
{
	printSelf();
	[playTone1 stop];
}

//----------------------------------------------------------

-(MML_RESULT)workinit
{
	printSelf();
	return mml_init_work(&mml);
}

//----------------------------------------------------------

// mml_fetch_once()からのcallback
-(void)mml_callback:(NSValue *)value
{
	printSelf();
	@autoreleasepool {
		
	MML_INFO *info;
	[value getValue:&info];

	nslog(@"type=%d",info->type);
	switch (info->type) {
		case MML_TYPE_NOTE_FINISH:
		{
			nslog(@"CLBK:発声ウエイト終了");
			MusicTone1 *tone1=[tones getTone:_toneNo]; // 新しい音色
			int ftie=info->subValue;
			if (ftie && tone1.floopSound) ftie=NO;
			if (!ftie) {
				// 和音はすべてを一気に消す
				for (int i=0;i<MML_MAX_WAON;i++){
					[playTones[i] toneStop];
				}
			}
		}
			break;

		case MML_TYPE_REST:
			nslog(@"CLBK:休符準備");
			// iOSでは特にすることはない
			break;
			
		case MML_TYPE_REST_GO:
			nslog(@"CLBK:実休符");
			// 休符に入った時点で音を止める(すべての和音に利く)
			for (int i=0;i<MML_MAX_WAON;i++){
				MusicPlayTone *playTone=playTones[i];
				[playTone stop];
			}
			break;

		case MML_TYPE_NOTE:
		{
			nslog(@"CLBK:単音発声準備");
			MML_ARGS_NOTE *args = &(info->args.note);
			int scale;
			int octave=numberToOctaveAndScale(args->number,&scale);
			nslog(@"[NOTE : octave=%d,scale=%2s(%3d)]", octave,scale_string[scale],args->number);
			[playTone1 noplay:octave scale:scale];
		}
			break;

		case MML_TYPE_NOTE_GO:
			nslog(@"CLBK:実発声");
			if (fmute) {
				nslog(@"mute中なので音は出さない");
			} else {
				[playTone1 voice];
			}
			break;
			
		case MML_TYPE_NOTEW:
		{
			nslog(@"CLBK:和音一括発声準備");
			// info=&ninfo[0]
			int waonCnt=info->subValue; // ninfo[0].subValue
			for (int i=0;i<waonCnt;i++) {
				MML_ARGS_NOTE *argsNote = &(info->args.note);	// ninfo[i].args.note
				int scale;
				int octave=numberToOctaveAndScale(argsNote->number,&scale);
				nslog(@"[NOTE%d : octave=%d,scale=%2s(%3d)]", i,octave,scale_string[scale],argsNote->number);
				// 貯めこむ
				[playTones[i] noplay:octave scale:scale];
				//
				info++;
			}
		}
			break;

		case MML_TYPE_NOTEW_GO:
		{
			nslog(@"CLBK:実和音一括発声");
			int waonCnt=info->subValue; // ninfo[0].subValue
			if (fmute) {
				nslog(@"mute中なので音は出さない");
			} else {
				for (int i=0;i<waonCnt;i++){
					[playTones[i] voice];
				}
			}
		}
			break;
			
		case MML_TYPE_SOUND:
		{
			MML_ARGS_SOUND *args = &(info->args.sound);
			nslog(@"[SOUND : %3d]", args->value);
			self.toneNo=args->value;
		}
			break;
			
		case MML_TYPE_TEMPO:
#ifdef DEBUG
		{
			MML_ARGS_TEMPO *args = &(info->args.tempo);
			nslog(@"[TEMPO : %3d]", args->value);
			// iOSでは外部で何かすることはない
		}
#endif
			break;
			
		case MML_TYPE_OCTAVE:
#ifdef DEBUG
		{
			MML_ARGS_OCTAVE *args = &(info->args.octave);
			nslog(@"[OCTAVE: %3d]", args->value);
			// iOSでは外部で何かすることはない
		}
#endif
			break;
			
		case MML_TYPE_VOLUME:
		{
			MML_ARGS_VOLUME *args = &(info->args.volume);
			nslog(@"[VOLUME: %3d]", args->value);
			self.volume=args->value;
		}
			break;
			
		case MML_TYPE_PANPOT:
		{
			MML_ARGS_PANPOT *args = &(info->args.panpot);
			nslog(@"[PANPOT: %3d]", args->value);
			self.panpot=args->value;
		}
			break;
			
		case MML_TYPE_COMMENT:
		{
			MML_ARGS_COMMENT *args = &(info->args.comment);
			nslog(@"[COMMENT : %@]",@(args->name));
			comment=@(args->name);
		}
			break;
			
	} // switch
		
	} // @autoreleasepool
}

//----------------------------------------------------------
// なぜかmusic1ch=nilしてもdeallocが走らないので強制呼び出しするため

-(void)terminate
{
	printSelf();
	[self stop];
	for (int i=0;i<MML_MAX_WAON;i++) {
		MusicPlayTone *playTone=playTones[i];
		playTone=nil;
	}
	playTones=nil;
}

-(void)dealloc
{
	[self terminate];
	
	// key-value通知解除
	[self removeObserver:self forKeyPath:OBSERVE_FPAUSE];
}

//----------------------------------------------------------

@end
