//--------------------------------------------------------------------------------------
// 音楽の全ch分の処理
//--------------------------------------------------------------------------------------
// 番号の値変換は外部で行っておくこと;この中ではch/trkともに0〜

#import "Music8ch.h"
#import "libs.h"
#import "malloc2.h"
#import <AVFoundation/AVFoundation.h>
#import "FileControl2.h"
#import "XB.h"

@interface Music8ch()
{
	MML_RESULT 	mr;
	NSTimer		*tickTimer;
	BOOL		fsyncStart;
	int			fetchCnt;	// isFetch中CHの数
	//
	volatile
	u_int64_t	tickUp;		// 同期用カウントアップタイマー;64bitにしているのは、オーバーフローのチェックを省くため
							// カウント周期はMML_1TICK_TIME ≒ 1/1000なので
							// ≒1/1024=1/２^10なので 64-10=54ビット秒なのでまず起動中にオーバーフローはしない(と言うかできない)
}
@end

@implementation Music8ch

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

// 初期化
-(instancetype)init
{
	printSelf();
	self=[super init];
	if (self!=nil) {
		tones=[[MusicTones alloc]init];
		for (int ch=0;ch<MML_CHANNELS;ch++) {
			nslog(@"--ch=%d--",ch);
			channel[ch]=[[Music1ch alloc]initWithTones:tones];
			channel[ch].tickUp=&tickUp;
		}
		// 初期化
		[self m_init];
	}
	return self;
}

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

// 実行ループにウエイトカウントアップ用タイマーを追加する(メインスレッドでしか追加できない)
-(void)setTimerInMainThread
{
	printSelf();
#if 1
	tickTimer=[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval)MML_1TICK_TIME
									  target:self
									selector:@selector(tickCountUp:)
									userInfo:nil
									 repeats:YES // 繰り返し
			   ];
#else
	tickTimer=[NSTimer timerWithTimeInterval:(NSTimeInterval)MML_1TICK_TIME
									  target:self
									selector:@selector(tickCountUp:)
									userInfo:nil
									 repeats:YES // 繰り返し
			   ];
	[[NSRunLoop currentRunLoop] addTimer:tickTimer forMode:NSDefaultRunLoopMode];
#endif
	nslog(@"tickTimer=%@:isValid=%d",tickTimer,[tickTimer isValid]);
}

// 周期起動カウントアップタイマー
-(void)tickCountUp:(NSTimer*)timer
{
	tickUp++;
//	if ((tickUp%1000)==0) {
//		EnterRunLoop(0.0);
//		nslog2(@"tickUp=%lld",tickUp);
//	}
}

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

-(void)fetchAll_main
{
	printSelf();
	@autoreleasepool {
		nslog(@"演奏開始");
		while (fetchCnt>0) {
			for (int ch=0;ch<MML_CHANNELS;ch++) {
				if (!channel[ch].isFetch) {
					nslog(@"ch:%dはフェッチ対象外",ch);
					continue;
				}
				nslog(@"ch:%dはフェッチ対象",ch);
				
				if (fsyncStart) {
					nslog(@"初期全ch同期待ち");
					int syncCnt=MML_CHANNELS;
					for (int i=0;i<MML_CHANNELS;i++) {
						if (channel[i].fsync==FSYNC_NOW) {
							//nslog(@"fsync==FSYNC_NOW:ch%d",i);
							syncCnt--; // 同期待ちが終了したCHを引いていく
						}
					}
					nslog(@"syncCnt=%d",syncCnt);
					if (syncCnt==0) {
						// 全CH同期完了
						for (int i=0;i<MML_CHANNELS;i++) {
							channel[i].fsync=FSYNC_NONE;
						}
						fsyncStart=NO;
						nslog(@"全CH同期完了→演奏開始\n*************");
					}
				}
				if (channel[ch].fwaitEnd) {
					nslog(@"音の終了を待っている:ch%d",ch);
					if (channel[ch].isWaitEnd) {
						// 終了
						[channel[ch] stop];	// 音を止める
						channel[ch].isFetch=NO;
						channel[ch].fsync=FSYNC_NOW; // 同期待ちも終えさせる
						fetchCnt--;
						nslog(@"演奏終了ch=%d(count=%d)",ch,channel[ch].count);
					}
					continue; // for
				}
				
				// 一時停止/停止判定;外部からかかる
				switch (channel[ch].fpause) {
					case FPAUSE_PAUSE:
						nslog(@"一時停止中:ch%d",ch);
						// 外部から解除がかかるのを待つ
						EnterRunLoop(0.0);
						continue; // for
					case FPAUSE_STOP:
						nslog(@"即時演奏停止:ch%d",ch);
						[channel[ch] clearWaitEnd];	// ウエイト強制停止
						channel[ch].fwaitEnd=YES;
						continue; // for
				} // switch (fpause)
				
				// 1コマンドフェッチ
				mr=[channel[ch] fetch1chOnce];
				switch (mr) {
					case MML_RESULT_EOT: // MML終了かFINEで終了
						nslog(@"演奏終了:ch%d",ch);
						// 正常終了時は最後の発声または休符の終了を待つ
						channel[ch].fwaitEnd=YES; // 音の終了を待ち始める
					case MML_RESULT_OK:
						break;
					default:
						nslog(@"Error %d:ch%d",mr,ch);
						// エラー発生時は即終了
						[channel[ch] stop];	// 音を止める
						fetchCnt--;
						channel[ch].isFetch=NO; // フェッチ対象から外す
						channel[ch].fsync=FSYNC_NOW; // 同期待ちも終えさせる
						break;
				} // switch (mr)
			} // for (ch)
		} // while (fetchCnt)
	} // @autorelease
	
	[tickTimer invalidate];
	tickTimer=nil;
	
	nslog(@"全ch演奏終了");
}

// bchには全CH指定ではなく正確にトラックが存在するところを指定すること
-(MML_RESULT)fetchAll:(int)bch
{
	printSelf();
	nslog(@"bch=%02x",bch);
	fetchCnt=0;
	int b=MUSIC_bCH0;
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (bch&b) {
			nslog(@"ch%d start",ch);
			mr=[channel[ch] fetch1chStart];
			if (mr!=MML_RESULT_OK) return(mr);	// エラーが出ているときは終了
			channel[ch].fsync	= FSYNC_WAIT; 	// 全CH同期待ちを行う
			channel[ch].isFetch	= YES; 			// 演奏中にする
			fetchCnt++;							// fetch中のCH数
		} else {
			channel[ch].fsync	= FSYNC_NOW;	// 1CH同期待ち完了
			channel[ch].isFetch	= NO;
		}
		channel[ch].fpause=FPAUSE_NONE;
		channel[ch].fwaitEnd= NO;
		b<<=1;
	}
	// 実行ループにウエイトカウントダウン用タイマーを追加する(メインスレッドでしか追加できない)
	[self performSelectorOnMainThread:@selector(setTimerInMainThread) withObject:nil waitUntilDone:YES];
	// 追加したタイマーを削除するには[tickTimer invalidate]すれば良いらしい

	// 実処理はバックグラウンドに任せる
	fsyncStart=YES; // 同期待ちを行う印
	[self performSelectorInBackground:@selector(fetchAll_main) withObject:nil];
	return MML_RESULT_OK;
}

-(void)fetchStop:(int)ch
{
	printSelf();
	nslog(@"%dch終了命令発行",ch);
	channel[ch].fpause=FPAUSE_STOP;
	nslog(@"%dCH終了待ち",ch);
	while (channel[ch].isFetch) {
		EnterRunLoop(0.0);
	}
	nslog(@"%dCH終了",ch);
}

//----------------------------------------------------------
/*　全体の流れ
 m_trk(trk,mmlSource)	トラックにMMLのテキストを格納する
		->track[trk]
 
 m_tempo(tempo)			テンポを設定する
 						コンパイル時にテンポ情報が書き込まれるので、この時点で発行しないといけない
 m_compile(trk)			トラックをコンパイルする
 						この後にm_trk()してもm_play()では無視されるので注意
  */
//----------------------------------------------------------
// 全CH一斉操作

// setter
-(void)setTempo:(int)tempo
{
	printSelf();
	_tempo=tempo; // ここでは値の記録だけ
}

//----------------------------------------------------------
// 複数ch別操作

// 指定チャンネルを先頭から演奏
// bch : MUSIC_bCH0=ch0 〜 MUSIC_bCH7=ch7
-(MML_RESULT)play:(int)bch
{
	printSelf();
	if (bch==0) bch=MUSIC_all_CH;
	int bb=0,b=MUSIC_bCH0;
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (bch&b) {
			nslog(@"\nplay ch%d",ch);
			int trk=ch_trk[ch];
			if (trk!=NO_TRACK) {
				// トラック割り当てがある
				if (track[trk]!=nil) {
					// トラック内容があるCHのみ;CH指定があってもトラック内容がないCHは当然演奏されない
					if (!fcompiled[trk]) {
						// このチャンネルに割り付けられたトラックはコンパイルされてない
						nslog(@"コンパイル直前 ch%d<-trk%d",ch,trk);
						mr=[self compile:trk];
						if (mr!=MML_RESULT_OK) {
							nslog(@"コンパイルエラーがあった:%d",mr);
							return mr;
						}
					} else {
						nslog(@"コンパイル済み->ワーク初期化");
						// MML内ループ用ワークを初期化
						[channel[ch] workinit];
					}
					if (channel[ch].isFetch) {
						nslog(@"演奏中の時は先に前の演奏を止める:ch%d",ch);
						[self fetchStop:ch];
					}
					channel[ch].mmlObject=btrack[trk]; // chに割り付けられているトラックからMMLを読み取る
					mr=channel[ch].mr;
					if (mr!=MML_RESULT_OK) {
						nslog(@"mmlObject設定でエラーが発生しているので演奏できない:%d",mr);
						return mr;
					}
					bb|=b;
				} else {
					nslog(@"このCHのトラックには内容がない:ch%d<-trk%d",ch,trk);
				}
			} else {
				nslog(@"このCHにはトラックが割り付けられてない:ch%d",ch);
			}
		}
		b<<=1;
	}
	return [self fetchAll:bb];
}

// 指定チャンネルを演奏一時停止
-(void)pause:(int)bch
{
	printSelf();
	if (bch==0) bch=MUSIC_all_CH; // 0だと全CH
	int b=MUSIC_bCH0;
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (bch&b) {
			channel[ch].fpause=FPAUSE_PAUSE;
		}
		b<<=1;
	}
}

// チャンネルの演奏再開
-(void)cont:(int)bch
{
	printSelf();
	if (bch==0) bch=MUSIC_all_CH; // 0だと全CH
	int b=MUSIC_bCH0;
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (bch&b) {
			channel[ch].fpause=FPAUSE_NONE;
		}
		b<<=1;
	}
}

// 指定チャンネルを演奏完全停止
-(void)stop:(int)bch
{
	printSelf();
	if (bch==0) bch=MUSIC_all_CH; // 0だと全CH
	int b=MUSIC_bCH0;
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (bch&b) {
			channel[ch].fpause=FPAUSE_STOP;
		}
		b<<=1;
	}
}

// 指定チャンネルのミュートをON/OFFする
-(void)mute:(int)bch onoff:(BOOL)onoff
{
	printSelf();
	if (bch==0) bch=MUSIC_all_CH; // 0だと全CH
	// 消音
	int b=MUSIC_bCH0;
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (bch&b) {
			[channel[ch] mute:onoff];
		}
		b<<=1;
	}
}

//----------------------------------------------------------
// 単一ch別操作

// 指定チャンネル以外をミュートする
-(void)solo:(int)ch
{
	printSelf();
	for (int i=0;i<MML_CHANNELS;i++) {
		[channel[i] mute:(i!=ch)];
	}
}

// 指定チャンネルのパンポットを設定する
-(void)panpot:(int)ch panpot:(int)pan
{
	printSelf();
	channel[ch].panpot=pan;
}

// 指定チャンネルのパンポットを取得する
-(int)panpot:(int)ch
{
	printSelf();
	return channel[ch].panpot;
}

// 指定チャンネルのボリュームを設定する
-(void)volume:(int)ch volume:(int)vol
{
	printSelf();
	channel[ch].volume=vol;
}

// 指定チャンネルのボリュームを取得する
-(int)volume:(int)ch
{
	printSelf();
	return channel[ch].volume;
}

// 指定チャンネルの演奏状態
// ch<=0 : 全ch一括取得 -> BIT?=チャンネルで 1=演奏中 , 0=停止中
// ch>0: 指定chの状態
-(int)stat:(int)ch
{
	printSelf();
	int stat=0;
	if (ch<=0) {
		// 全ch一括取得
		int b=MUSIC_bCH0;
		for (int i=0;i<MML_CHANNELS;i++) {
			if (channel[i].isFetch) {
				if (channel[i].fpause==FPAUSE_NONE) {
					stat|=b;
				}
			} // fetchが終了している時も演奏はしていない
			b<<=1;
		}
	} else {
		// 指定chの状態
		if (channel[ch].isFetch) {
			if (channel[ch].fpause==FPAUSE_NONE) {
				stat=1;
			}
		} // fetchが終了している時も演奏はしていない
	}
	return (stat);
}

// 直前のコメントを返す
-(NSString *)comment:(int)ch
{
	return (channel[ch].comment);
}

// 総発声数を返す
-(int)count:(int)ch
{
	return (channel[ch].count);
}

//----------------------------------------------------------
// トラック別操作

// m_trk(trk,mml)		MMLテキストをトラックワークに追加する
-(void)track:(int)trk mmlSource:(NSString *)mmlSource
{
	printSelf();
	nslog(@"trk%d:%@",trk,mmlSource);
	// trackは追加
	if (track[trk]==nil) {
		track[trk]=[[NSMutableString alloc]init];
	} else {
		// 使われている
		// すでにコンパイルもされているときは再コンパイルが必要なので一旦開放する
		[self freeBeforeBtrack:trk];
	}
	// mmlは元が文字列定数("〜")だけど変数領域に入ってるか文字列変数なので、消えると困るので内容コピーが必要
	[track[trk] appendString:mmlSource];
}

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

// mml_compile()はその過程でreallocすることがあるが、これがバックグラウンドで発行されるとiOS9で警告を発するのでコンパイル処理自体をメインスレッドで行うようにした
-(void)compileInMainThread:(NSNumber *)trkValue
{
	printSelf();
	nslog(@"track=%@",trkValue);
	int trk=(int)[trkValue integerValue];
	
	[self freeBeforeBtrack:trk]; // そのトラックがコンパイル済みの時は、前のものを開放してから

	MML handle;
	mml_compile_init(&handle);
	mml_tempo(&handle,_tempo);

	mr=mml_compile(&handle,nsStringTocString(track[trk]));
#if	1	//def DEBUG	//!!test!!debug!!
	mml_compile_dump(&handle);
#endif
	if (mr!=MML_RESULT_OK) {
		nslog(@"コンパイル中にエラーが発生している:%d",mr);
		// オブジェクトエリアを開放しておく
		mml_compile_free(&handle);
	} else {
		btrack[trk]=mml_compile_object(&handle,NULL);
		fcompiled[trk]=YES; // コンパイル済み
	}
}

// m_compile(trk)	トラック内のMMLをコンパイルする
// テンポの設定は（必要なら）事前に行うこと
-(MML_RESULT)compile:(int)trk
{
	printSelf();
	nslog(@"compile:trk%d",trk);
	// メインスレッドで実行
	[self performSelectorOnMainThread:@selector(compileInMainThread:) withObject:@(trk) waitUntilDone:YES];
	// 終了待ちしてリターン値を得る
	return mr;
}

// MMLオブジェクトの内部構造は将来的に変わる可能性もあるので、それを取り出す機能は公開しない

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

// m_assign(ch,trk)		チャンネルにトラックを割り当て
// そのトラックに内容があるかどうかはここでは関知しない
-(void)assign:(int)ch trk:(int)trk
{
	printSelf();
	nslog(@"assign ch%d<-trk%d",ch,trk);
	ch_trk[ch]=trk;
}

-(NSString *)trackString:(int)trk
{
	return track[trk];
}

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

// m_free(trk)			トラックワークを解放
-(void)free:(int)trk
{
	printSelf();
	nslog(@"free trk%d",trk);

	// そのトラックを割り当てているCHがあればまずそれを解除
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		if (ch_trk[ch]==trk) {
			nslog(@"ch%d+1=trk%d+1 free",ch,ch_trk[ch]);
			ch_trk[ch]=NO_TRACK;
			if (channel[ch].isFetch) {
				nslog(@"そのCHは演奏中なので先に演奏を止める:ch%d",ch);
				[self fetchStop:ch];
			}
		}
	}
	track[trk]=nil;
	[self freeBeforeBtrack:trk]; // コンパイルオブジェクトがあるときはそれも開放
}

//----------------------------------------------------------
// その他

// m_tone(音色番号,"音色ファイル",rate,octave,scale,floop)
// rateはADPCMまたはiOS標準ファイルで有効
// tno=0〜
-(int)tone:(int)tno path:(NSString *)path rate:(int)rate octave:(int)octave scale:(int)scale floop:(BOOL)floop
{
	printSelf();
	return [tones setFileToNo:tno path:path rate:rate octave:octave scale:scale floop:floop];
}

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

// 指定トラックがコンパイル済みの時は、前のものを開放する
-(void)freeBeforeBtrack:(int)trk
{
	printSelf();
	if (fcompiled[trk]) {
		free2(btrack[trk]);
		btrack[trk]=NULL;  // 開放した印
		fcompiled[trk]=NO; // コンパイルしてない；エラー発生のため開放は記録しておく
	}
}

// 指定chに割り付けられているトラックが利用中の時は、前のものを開放する
// 複数chに同一トラックが割り付けられていることもある
-(void)freeBeforeTrack:(int)ch
{
	printSelf();
	int trk=ch_trk[ch];
	if (trk!=NO_TRACK) {
		// トラック割り当てがある
		if (track[trk]!=nil) {
			track[trk]=nil; // トラック開放
			// オブジェクトも開放
			[self freeBeforeBtrack:trk];
		}
	}
}

// 初期化
-(void)m_init
{
	printSelf();
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		nslog(@"--ch=%d--",ch);
		channel[ch].volume=MML_OPTION_DEFAULT_VOLUME;
		channel[ch].panpot=MML_OPTION_DEFAULT_PANPOT;
		[self freeBeforeTrack:ch]; // 一旦使用中からm_initされた時のため開放処理も行う
		ch_trk[ch]=NO_TRACK; // トラックは未割り当て
	}
	self.tempo =MML_OPTION_DEFAULT_TEMPO;
}

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

-(void)terminate
{
	printSelf();
	
	[tickTimer invalidate];
	tickTimer=nil;
	
	for (int ch=0;ch<MML_CHANNELS;ch++) {
		int trk=ch_trk[ch];
		if (trk!=NO_TRACK) {
			// トラック割り当てがある
			if (track[trk]!=nil) {
				track[trk]=nil; // トラック開放
			}
			// オブジェクトも開放
			[self freeBeforeBtrack:trk];
		}
		[channel[ch] terminate];
		channel[ch]=nil;
	}
	tones=nil;
}


-(void)dealloc
{
	printSelf();
	[self terminate];
}

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

@end
