//--------------------------------------------------------------------------------------
// 1音の発声
//--------------------------------------------------------------------------------------

#import "MusicPlayTone.h"
#import "MusicTone1.h"
#import "wavMusic.h"
#import "mml.h"
#import <AVFoundation/AVFoundation.h>
#import "libs.h"
#import "FileControl2.h"

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

typedef struct
{
	int	octave;
	int	scale;
	int toneNo;
} OctaveScale;

@interface MusicPlayTone()
//<AVAudioPlayerDelegate>
{
	// 音情報
	float	panpotf;
	float	volumef;
	BOOL	fmute;
	BOOL	ftie;
	
#define MAX_AUDIOS	4	// これが音声データのキャッシュになる
	
	int		noAudio;	// audioPlay[]のどれを使っているか
	
	// audioPlay実体
	NSMutableArray	<AVAudioPlayer *> *audioPlayArray;
	OctaveScale		octaveScale[MAX_AUDIOS];
	
	// audioPlayArray[]のアドレス参照
	AVAudioPlayer __weak *audioPlay;
	AVAudioPlayer __weak *audioPlay0;
	AVAudioPlayer __weak *audioPlayLatest;	// 最後に発声した方
	
	AVAudioSession	*audioSession;
	
	// 音色変化に対応するため
	MusicTone1 *tone0;
}
// 音色データ本体
#ifdef USE_MUTABLEDATA
@property (nonatomic,strong) NSMutableData *data;
#else
@property (nonatomic,strong) NSData *data;
#endif
@end


@implementation MusicPlayTone

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

// 音色データをもって初期化する
-(instancetype)init
{
	printSelf();
	self=[super init];
	if (self!=nil) {
		nslog(@"self=%@",self);
		// 正常なときは前の音情報を初期化
		// initだけで生成したオブジェクトに対して.panとかすると落ちるので仮ファイルで生成する
		NSURL *url=[NSURL fileURLWithPath:getResourceOrSavedataPath(@"click.aiff")];
		AVAudioPlayer *ap=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:nil];
		audioPlayArray=[[NSMutableArray alloc]init];
		for (int i=0;i<MAX_AUDIOS;i++) {
			[audioPlayArray addObject:ap];
			octaveScale[i].octave=-1;
			octaveScale[i].scale =-1;
		}
		nslog(@"audioPlayArray=%@",audioPlayArray);
		audioPlayLatest=audioPlay=nil;
		noAudio	= 0;
		
		// これを入れたおかげでサブスレッドからでもplayで音が出るようになった・・・ような気がする
		audioSession = [AVAudioSession sharedInstance];
		[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
		[audioSession setActive:YES error:nil];
	}
	return self;
}

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

// AudioPlay内のwavデータのヘッダーを指定note用にする
-(void)remakeWaveHeaderForNote:(int)octave scale:(int)scale
{
	printSelf();
	
	// データ配列のポインタを得る
#ifdef USE_MUTABLEDATA
	UByte *wavBuff = (UByte *)[_data mutableBytes]; // 内容書き換え可能なポインターを得る
#else
	UByte *wavBuff = (UByte *)[_data bytes];
#endif
	[_tone1 remakeWaveHeaderForNote:wavBuff octave:octave scale:scale];
}

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

// setter
-(void)setTone1:(MusicTone1 *)tone1
{
	printSelf();
	// データを書き換えるため、内部にコピーする
#ifdef USE_MUTABLEDATA
	_data=[[NSMutableData alloc]initWithData:tone1.data];
#else
	_data=[[NSData        alloc]initWithData:tone1.data];
#endif
	nslog(@"tone1=%@",tone1);
	_tone1=tone1;
}
// 演奏中に設定された場合、その時演奏していた_audioPlay1または2の他方から音が変わる

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

// 発声データ作成
// 一旦AudioPlayerに渡された音は、同じオブジェクトに対して操作しない限り音が出続ける
-(void)noplay:(int)octave scale:(int)scale
{
	printSelf();

	int i;
#if 1
	for (i=0;i<MAX_AUDIOS;i++) {
		if (octaveScale[i].octave==octave
		 && octaveScale[i].scale ==scale
		 && octaveScale[i].toneNo==self.toneNo) {
			nslog(@"1つ前と同じ音の時");
			// 今あるものをそのまま使う
			audioPlay=audioPlayArray[i];
			audioPlay.currentTime=0; // 先頭に戻して演奏
			break;
			// 同じ音色番号で実音色が異なる場合も考えられるが（m_tone()で音色データを書き換える)、
			// トラック内で実音色の変更はできない(MMLに音色データ設定はない)のでこれでよい
		}
	}
#else
	i=MAX_AUDIOS;
#endif
	if (i>=MAX_AUDIOS) {
		// 直前の音と同じではない時は新規作成
		[self remakeWaveHeaderForNote:octave scale:scale];
		nslog(@"octave=%d,scale=%s",octave,scale_string[scale]);
		
		octaveScale[noAudio].octave=octave;
		octaveScale[noAudio].scale =scale ;
		octaveScale[noAudio].toneNo=self.toneNo;
		
		// 多段バッファーを使い音が途切れないようにする。
		audioPlay0=audioPlay; 	// 初回だけはnil
		
		// iOSの音声再生機能の構造については http://news.mynavi.jp/column/iphone/012/ 参照。
		// dataの中を書き換えても再生音が変わらない理由もこれでわかる
		
		AVAudioPlayer *ap=audioPlayArray[noAudio];
		ap=nil;
		NSError *error;
		ap=[[AVAudioPlayer alloc]initWithData:self.data error:&error];
		if (error!=nil) nslog(@"error=%@",error);
		[audioPlayArray replaceObjectAtIndex:noAudio withObject:ap];
		audioPlay=ap;

	//	[audioPlay prepareToPlay];	// これを入れると発声が遅れまくる
		
		// 連続音の時はループをかける
		// ループの必要回数は本来は
		// ファイルサイズ/(サンプリングレート*１サンプルあたりのバイト数)で決まるので、tickの時間がそれより長い時に必要な時間だけ回せばよいが、
		// 計算が面倒なので適当に長いループ回数を与えておき、ぶち切るようにする
		// 最長はT32のL1=T120のL4が0.5sなので、L1は0.5s*4、T32は0.5*4*120/32=7.5s, 0.5秒のデータなら7.5/0.5=15ループで良い
		audioPlay.numberOfLoops=(_tone1.floopSound? 256:0); // この回数で足りないことはあるまい(あるなら、そのデータは短すぎて使えないということ)

		// 音色ごとにループで戻るべきポイントを決定できるなら(Audacityなどで手動で調べる)、
		// numberOfLoops=0にして、AVAudioPlayerDelegateで戻す
		// 一応その準備はしてある
		
		nslog(@"audioPlay%d=%@",noAudio,audioPlay);
		if (++noAudio>=MAX_AUDIOS) noAudio=0;

		// パンポットと音量が初期化されてしまうので再設定
	}	// パンポットと音量は変更されるかもしれないので設定
	nslog(@"panpot=%f,volume=%f",panpotf,volumef);
	audioPlay.volume=volumef;
	audioPlay.pan	=panpotf;
}

// 発生データ作成から実発声まで
-(void)play:(int)octave scale:(int)scale
{
	printSelf();
	[self noplay:octave scale:scale];
	nslog(@"発声:%@,octave=%d,scale=%s",_tone1.toneName,octave,scale_string[scale]);
	[self voice];
}

// 発声
// audioPlay=今回 , audioPlay0=前回
-(void)voice
{
	printSelf();
	audioPlayLatest=audioPlay;
	[audioPlay play]; // 発声
	if (_tone1!=tone0) {
		nslog2(@"前回と今回で音色が違う時");
		audioPlay0=nil; // 前の音色の音を消す(その方が綺麗に聞こえるから)
	}
	tone0=_tone1; // 今回の音色;アドレスのみ記録
}

#if 0
//#pragma mark - AVAudioPlayerDelegate
// 再生終了
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
	if ( flag )	{
		if (_tone1.floopSound) {
			nslog2(@"ループ音の戻し");
			audioPlay.currentTime=0.01;
			[audioPlay play];
		}
	}
}
#endif

// 前消音
-(void)toneStop
{
	printSelf();
	[audioPlayLatest stop];
	nslog(@"toneStop audioPlay=%@",audioPlayLatest);
}

// 全消音
-(void)stop
{
	printSelf();
	for (int i=0;i<MAX_AUDIOS;i++) {
		[audioPlayArray[i] stop];
	}
}

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

-(void)mute:(BOOL)foff
{
	printSelf();
	float vol;
	if (foff) {
		nslog(@"消音");
		vol=0;
		fmute=YES;
	} else {
		nslog(@"発音");
		vol=volumef; // 今の音量に戻す
		fmute=NO;
	}
	for (int i=0;i<MAX_AUDIOS;i++) {
		audioPlayArray[i].volume=vol;
	}
}

-(void)loopcut
{
	for (int i=0;i<MAX_AUDIOS;i++) {
		audioPlayArray[i].numberOfLoops=0;
	}
}

//--------------------------------------------------------------------------------------
// setter

-(void)setPanpot:(int)panpot
{
	printSelf();
	_panpot=panpot;
	// ここの変換は少しややこしい
	// 0   ->0.0
	// 64  ->0.5;厳密には63.5が中央だけど、整数なので64で代用させる
	// 127 ->1.0
	// 全部で128段階なので1段階は(1.0/128.0)。なのでval*(1.0/128.0)のような気がするが、この式では127->1.0にならないので補正する
	if (panpot==127) panpot=1.0;
	panpotf=(float)(panpot)/128.0; //=(1.0/128.0)*(float)(panpot);
	nslog(@"パンポット=%d->%f",panpot,panpotf);
	for (int i=0;i<MAX_AUDIOS;i++) {
		audioPlayArray[i].pan=panpotf;
	}
}

-(void)setVolume:(int)volume
{
	printSelf();
	_volume=volume;
	if (volume==127) volume=1.0;
	volumef=(float)(volume)/128.0;
	nslog(@"音量=%d->%f",volume,volumef);
	for (int i=0;i<MAX_AUDIOS;i++) {
		audioPlayArray[i].volume=volumef;
	}
}

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

-(void)dealloc
{
	printSelf();
	// 以下で消音もされる
	for (int i=0;i<MAX_AUDIOS;i++) {
		AVAudioPlayer *ap=audioPlayArray[i];
		ap=nil;
	}
	audioPlayArray=nil;
}

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

@end
