//******************************************************************************
//
// MIDITrail / MIDITrailApp
//
// MIDITrail アプリケーションクラス
//
// Copyright (C) 2012-2019 WADA Masashi. All Rights Reserved.
//
//******************************************************************************

#import "MIDITrailApp.h"
#import "YNBaseLib.h"
#import "MTParam.h"
#import "MTConfFile.h"
#import "MTSceneTitle.h"
#import "MTScenePianoRoll3D.h"
#import "MTScenePianoRoll2D.h"
#import "MTScenePianoRollRain.h"
#import "MTScenePianoRollRain2D.h"
#import "MTScenePianoRollRing.h"
#import "MTScenePianoRoll3DLive.h"
#import "MTScenePianoRoll2DLive.h"
#import "MTScenePianoRollRainLive.h"
#import "MTScenePianoRollRain2DLive.h"
#import "MTScenePianoRollRingLive.h"


//******************************************************************************
// プライベートメソッド定義
//******************************************************************************
@interface MIDITrailApp ()

//ビュー生成
- (int)createViews;

//通知受信設定
- (int)initializeNotification;
//ワーカースレッド：シーン生成
- (void)thread_CreateScene;

//設定ファイル初期化
- (int)initConfFile;

//グラフィック設定読み込み
- (int)loadGraphicConf;

//シーン種別読み込み
- (void)loadSceneType;

//視点読み込み
- (int)loadViewpoint;

//視点保存
- (int)saveViewpoint;

//プレーヤー設定読み込み
- (int)loadPlayerConf;

//表示効果反映
- (int)updateEffect;

//演奏状態変更
- (int)changePlayStatus:(PlayStatus)status;

//メインビュー表示
- (int)openMainView;

//メインビュー消去
- (int)closeMainView;

//シーン生成
- (int)createSceneWithType:(SceneType)type SeqData:(SMSeqData*)pSeqData;

//Wavetable設定
- (NSString*)getWavetableFilePath;

//ポートデバイス登録
- (int)setPortDev:(SMSequencer*)pSequencer;

//MIDI IN モニタ情報登録
- (int)setMonitorPortDev:(SMLiveMonitor*)pLiveMonitor scene:(MTScene*)pScene;

//通知送信処理
- (int)postNotificationWithName:(NSString*)pName userInfo:(NSDictionary*)pUserInfo;

//他アプリからのファイル受け取り
- (int)receiveFileFromOtherApp:(NSDictionary*)pLaunchOptions;

//Documentsディレクトリにおけるファイル名生成
- (NSString*)makeFileNameAtDocumentsWithBaseFileName:(NSString*)pBaseFileName;

//指定URLからファイル受け取り
- (int)receiveFileFromURL:(NSURL*)pURL toFilePath:(NSString*)pToFilePath;

//オーディオセッション初期化
- (int)initAudioSession;

//オーディオセッションアクティブ設定
- (int)setAudioSessionActive:(BOOL)isActive;

@end


@implementation MIDITrailApp

//******************************************************************************
// 初期化
//******************************************************************************
- (id)init
{
	//オブジェクト初期化
	self = [super init];
	if (self == nil) {
		goto EXIT;
	}
	
	//エラー制御初期化
	[YNErrCtrl initOnThreadStart];
	
	//ウィンドウ系
	m_pWindow = nil;
	m_pTabBarCtrl = nil;
	m_pFileViewCtrl = nil;
	m_pMonitorViewCtrl = nil;
	m_pSettingViewCtrl = nil;
	m_pHelpViewCtrl = nil;
	
	//レンダリング系
	m_pScene = NULL;
	memset(&m_RendererParam, 0, sizeof(OGLRedererParam));
	
	//演奏状態
	m_PlayStatus = NoData;
	m_isRepeat = false;
	m_isRewind = false;
	m_PlaySpeedRatio = 100;
	
	//表示状態
	m_isEnablePianoKeyboard = true;
	m_isEnableRipple = true;
	m_isEnablePitchBend = true;
	m_isEnableStars = true;
	m_isEnableCounter = true;
	m_isEnableBackgroundImage = true;
	m_isEnableFPS = false;
	
	//シーン種別
	m_SceneType = Title;
	m_SelectedSceneType = PianoRoll3D;
	
	//ユーザ設定
	m_pUserConf = nil;
	
	//アプリケーションアクティブ状態
	m_isAppActive = true;
	
	//リワインド／スキップ制御
	m_SkipBackTimeSpanInMsec = 10000;
	m_SkipForwardTimeSpanInMsec = 10000;
	
	//演奏スピード制御
	m_SpeedStepInPercent = 1;
	m_MaxSpeedInPercent = 400;
	
	//オーディオセッション
	m_pAudioSession = nil;
	
EXIT:;
    return self;
}

//******************************************************************************
// 初期化
//******************************************************************************
- (int)initialize:(NSDictionary*)pLaunchOptions
{
	int result = 0;
	
	//指定ファイル読み込み
	if (@available(iOS 9.0, *)) {
		//iOS9以降は application:openURL:options で受け取るためここでは何もしない
	}
	else {
		result = [self receiveFileFromOtherApp:pLaunchOptions];
		if (result != 0) goto EXIT;
	}
	
	//設定ファイル初期化
	result = [self initConfFile];
	if (result != 0) goto EXIT;
	
	//グラフィック設定読み込み
	result = [self loadGraphicConf];
	if (result != 0) goto EXIT;
	
	//プレーヤー設定読み込み
	result = [self loadPlayerConf];
	if (result != 0) goto EXIT;
	
	//メッセージキュー初期化
	result = m_MsgQueue.Initialize(10000);
	if (result != 0) goto EXIT;
	
	//オーディオセッション初期化
	//  バックグラウンド演奏対応のためオーディオユニット初期化の前にオーディオセッションを生成する
	result = [self initAudioSession];
	if (result != 0) goto EXIT;
	
	//シーケンサ初期化
	//  演奏開始時に毎回初期化するので本来はここで実行する必要はないが、
	//  プロセス起動直後の初回演奏開始に限って約2秒止まる現象を回避するため実施する。
	//  当該現象の原因は、MIDI出力制御の初期化処理でCoreMIDI APIである
	//  MIDIGetNumberOfDevices()の呼び出しに1秒以上かかることにある。
	//  プロセス起動時にこの処理を実施して演奏開始時のユーザへのストレスを低減する。
	result = m_Sequencer.Initialize(&m_MsgQueue);
	if (result != 0) goto EXIT;
	
	//ネットワークセッション初期化
	result = m_NetworkSession.Initialize();
	if (result != 0) goto EXIT;
	
	//ネットワークセッション有効化
	//  CoreMIDIデバイスの検索でネットワークコネクションを表示させる
	m_NetworkSession.Enable();
	
	//ビュー生成
	result = [self createViews];
	if (result != 0) goto EXIT;
	
	//演奏状態変更
	result = [self changePlayStatus:NoData];
	if (result != 0) goto EXIT;
	
	//通知受信設定
	result = [self initializeNotification];
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// ビュー生成
//******************************************************************************
- (int)createViews
{
	int result = 0;
	NSString* pDeviceType = nil;
	NSString* pDeviceNo = @"";
	NSString* pNibNameMTFileView = nil;
	NSString* pNibNameMTMonitorView = nil;
	NSString* pNibNameMTSettingView = nil;
	NSString* pNibNameMTHelpView = nil;
	NSString* pNibNameMTMainView = nil;
	NSArray* pViewArray = nil;
	CGFloat height = 0;
	CGFloat width = 0;
	CGFloat longSide = 0;
	
	//ウィンドウ生成
	m_pWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
	m_pWindow.backgroundColor = [UIColor whiteColor];
	
	//スクリーンサイズ
	height = [[UIScreen mainScreen] bounds].size.height;
	width = [[UIScreen mainScreen] bounds].size.width;
	longSide = height;
	if (height < width) {
		longSide = width;
	}
	
	//デバイス種別
	if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
		//iPhone / iPod touch
		pDeviceType = @"iPhone";
		if (longSide == 480) {
			pDeviceNo = @""; //4,4s
		}
		else if (longSide == 568) {
			pDeviceNo = @"5"; //5,5s,5c,SE
		}
		else if (longSide == 667) {
			pDeviceNo = @"6"; //6,6s,7,8
		}
		else if (longSide == 736) {
			pDeviceNo = @"6Plus"; //6 Plus,6s Plus,7 Plus,8 Plus
		}
		else if (longSide == 812) {
			pDeviceNo = @"X"; //X,Xs,11 Pro
		}
		else if (longSide == 896) {
			pDeviceNo = @"XSMax"; //Xs Max,XR,11,11 Pro Max
		}
		else if (longSide > 896) {
			//iPhone XS Maxより大きい未知のデバイス
			pDeviceNo = @"XSMax";
		}
	} else {
		//iPad
		pDeviceType = @"iPad";
		if (longSide == 1024) {
			pDeviceNo = @""; //iPad,iPad 2,iPad 3,iPad 4,iPad Air,iPad Air 2,iPad Pro 9.7
							 //iPad mini,iPad mini 2,iPad mini 3,iPad mini 4,iPad mini(5th)
		}
		else if (longSide == 1112) {
			pDeviceNo = @"Pro105"; //iPad Pro 10.5,iPad Air(3rd),iPad(7th)
		}
		else if (longSide == 1194) {
			pDeviceNo = @"Pro110"; //iPad Pro 11
		}
		else if (longSide == 1366) {
			pDeviceNo = @"Pro129"; //iPad Pro 12.9
		}
		else if (longSide > 1366) {
			//iPad Pro(12.9inch)より大きい未知のデバイス
			pDeviceNo = @"Pro129";
		}
	}
	
	//Nibファイル名称
	pNibNameMTFileView    = [NSString stringWithFormat:@"MTFileView_%@", pDeviceType];
	pNibNameMTMonitorView = [NSString stringWithFormat:@"MTMonitorView_%@", pDeviceType];
	pNibNameMTSettingView = [NSString stringWithFormat:@"MTSettingView_%@", pDeviceType];
	pNibNameMTHelpView    = [NSString stringWithFormat:@"MTHelpView_%@", pDeviceType];
	pNibNameMTMainView    = [NSString stringWithFormat:@"MTMainView_%@%@", pDeviceType, pDeviceNo];
	
	//ビュー制御生成
	m_pFileViewCtrl    = [[MTFileViewCtrl alloc] initWithNibName:pNibNameMTFileView bundle:nil];
	m_pMonitorViewCtrl = [[MTMonitorViewCtrl alloc] initWithNibName:pNibNameMTMonitorView bundle:nil];
	m_pSettingViewCtrl = [[MTSettingViewCtrl alloc] initWithNibName:pNibNameMTSettingView bundle:nil];
	m_pHelpViewCtrl    = [[MTHelpViewCtrl alloc] initWithNibName:pNibNameMTHelpView bundle:nil];
	m_pMainViewCtrl    = [[MTMainViewCtrl alloc] initWithNibName:pNibNameMTMainView bundle:nil
												   rendererParam:m_RendererParam];
	
	//MTMainViewCtrl生成時にOpenGLコンテキストが初期化される
	//これ以降OpenGLのAPIが利用できる
	
	//ナビゲーション制御の生成
	m_pFileNaviCtrl    = [[MTNavCtrl alloc] initWithRootViewController:m_pFileViewCtrl];
	m_pMonitorNaviCtrl = [[MTNavCtrl alloc] initWithRootViewController:m_pMonitorViewCtrl];
	m_pSettingNaviCtrl = [[MTNavCtrl alloc] initWithRootViewController:m_pSettingViewCtrl];
	m_pHelpNaviCtrl    = [[MTNavCtrl alloc] initWithRootViewController:m_pHelpViewCtrl];
	
	//ナビゲーションバーの設定
	//  ナビゲーションバーのデザインを正常に制御できなくなるためスタイルの設定を廃止する
	//  例えば検索バーを追加したときに色が操作によって変わってしまう
	//m_pFileNaviCtrl.navigationBar.barStyle = UIBarStyleBlack;
	//m_pMonitorNaviCtrl.navigationBar.barStyle = UIBarStyleBlack;
	//m_pSettingNaviCtrl.navigationBar.barStyle = UIBarStyleBlack;
	//m_pHelpNaviCtrl.navigationBar.barStyle = UIBarStyleBlack;
	
	//タブバー制御生成
	m_pTabBarCtrl = [[MTTabBarCtrl alloc] init];
	
	//タブバー制御にビューを登録
	pViewArray = [NSArray arrayWithObjects:m_pFileNaviCtrl,
											m_pMonitorNaviCtrl,
											m_pSettingNaviCtrl,
											m_pHelpNaviCtrl,
											nil];
	m_pTabBarCtrl.viewControllers = pViewArray;
	
	return result;
}

//******************************************************************************
// 通知受信設定
//******************************************************************************
- (int)initializeNotification
{
	int result = 0;
	NSNotificationCenter* pCenter = nil;
	
	//通知先登録：演奏状態変更通知
	pCenter = [NSNotificationCenter defaultCenter];
	
	//ファイル選択
	[pCenter addObserver:self
				selector:@selector(onSelectFile:)
					name:@"onSelectFile"
				  object:nil];
	//クローズボタン押下
	[pCenter addObserver:self
				selector:@selector(onCloseButton:) 
					name:@"onCloseButton"
				  object:nil];
	//再生ボタン押下
	[pCenter addObserver:self
				selector:@selector(onPlayButton:)
					name:@"onPlayButton"
				  object:nil];
	//停止ボタン押下
	[pCenter addObserver:self
				selector:@selector(onStopButton:)
					name:@"onStopButton"
				  object:nil];
	//後方スキップボタン押下
	[pCenter addObserver:self
				selector:@selector(onSkipBackwardButton:)
					name:@"onSkipBackwardButton"
				  object:nil];
	//前方スキップボタン押下
	[pCenter addObserver:self
				selector:@selector(onSkipForwardButton:)
					name:@"onSkipForwardButton"
				  object:nil];
	//再生スピードダウンボタン押下
	[pCenter addObserver:self
				selector:@selector(onPlaySpeedDownButton:)
					name:@"onPlaySpeedDownButton"
				  object:nil];
	//再生スピードアップボタン押下l
	[pCenter addObserver:self
				selector:@selector(onPlaySpeedUpButton:)
					name:@"onPlaySpeedUpButton"
				  object:nil];
	//リピートボタン押下
	[pCenter addObserver:self
				selector:@selector(onRepeatButton:)
					name:@"onRepeatButton"
				  object:nil];
	//再生状態変化通知：一時停止
	[pCenter addObserver:self
				selector:@selector(onChangePlayStatusPause:)
					name:@"onChangePlayStatusPause"
				  object:nil];
	//再生状態変化通知：停止
	[pCenter addObserver:self
				selector:@selector(onChangePlayStatusStop:)
					name:@"onChangePlayStatusStop"
				  object:nil];
	//モニタ開始
	[pCenter addObserver:self
				selector:@selector(onStartMonitoring:)
					name:@"onStartMonitoring"
				  object:nil];
	//シーン生成完了通知
	[pCenter addObserver:self
				selector:@selector(onFinishCreateScene:)
					name:@"onFinishCreateScene"
				  object:nil];
	
	//オーディオセッション中断通知
	[pCenter addObserver:self
				selector:@selector(onAudioSessionInterruption:)
					name:AVAudioSessionInterruptionNotification
				  object:nil];
	
	return result;
}

//******************************************************************************
// 実行
//******************************************************************************
- (int)run
{
	int result = 0;
	
	//ルートビュー登録
	m_pWindow.rootViewController = m_pTabBarCtrl;

	//キーウィンドウ表示
	[m_pWindow makeKeyAndVisible];
	
	return result;
}

//******************************************************************************
// 停止
//******************************************************************************
- (int)terminate
{
	int result = 0;
	
	[m_pMainViewCtrl release];
	[m_pFileViewCtrl release];
	[m_pMonitorViewCtrl release];
	[m_pSettingViewCtrl release];
	[m_pHelpViewCtrl release];
	[m_pTabBarCtrl release];
	[m_pWindow release];
	
	m_pMainViewCtrl = nil;
	m_pFileViewCtrl = nil;
	m_pMonitorViewCtrl = nil;
	m_pSettingViewCtrl = nil;
	m_pHelpViewCtrl = nil;
	m_pTabBarCtrl = nil;
	m_pWindow = nil;
	
	//エラー制御終了
	[YNErrCtrl termOnThreadEnd];
	
	return result;
}

//******************************************************************************
// ファイル選択イベント
//******************************************************************************
- (void)onSelectFile:(NSNotification*)pNotification
{
	int result = 0;
	
	//ファイル読み込み時に再生スピードを100%に戻す：_CreateSceneでカウンタに反映
	m_PlaySpeedRatio = 100;
	
	//メッセージキュークリア
	m_MsgQueue.Clear();
	
	//メインビュー初期化
	result = [m_pMainViewCtrl initialize:&m_MsgQueue];
	if (result != 0) goto EXIT;
	
	//メインビュー表示
	result = [self openMainView];
	if (result != 0) goto EXIT;
	
	//インジケータアニメーション開始
	[m_pMainViewCtrl startActivityIndicator];
	
	//シーン生成スレッド起動
	[NSThread detachNewThreadSelector:@selector(thread_CreateScene) 
							 toTarget:self
						   withObject:nil];
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// ワーカースレッド：シーン生成
//******************************************************************************
- (void)thread_CreateScene
{
	int result = 0;
	NSString* path = nil;
	SMFileReader smfReader;
	NSDictionary* pUserInfo = nil;
	NSAutoreleasePool* pool;
	id pErrInfo = [NSNull null];
	
	//NSLog(@"MIDITrailApp::thread_CreateScene start");
	
	//スレッド開始処理
	pool = [[NSAutoreleasePool alloc]init];
	[YNErrCtrl initOnThreadStart];
	
	//カレントコンテキスト設定
	//OpenGLの処理を担当するスレッドを切り替える
	[m_pMainViewCtrl setCurrentContext];
	
	//ファイルパス取得
	path = [m_pFileViewCtrl selectedFilePath];
	
	//ファイル読み込み
	smfReader.SetEncodingId([m_pSettingViewCtrl selectedEncodingId]);
	result = smfReader.Load(path, &m_SeqData);
	if (result != 0) goto EXIT;
	
	//シーン種別読み込み
	[self loadSceneType];
	
	//シーン生成
	result = [self createSceneWithType:m_SceneType SeqData:&m_SeqData];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) {
		//アラートは呼び出し側スレッドで表示させる
		//本スレッドに登録されているエラー情報を取り出して呼び出し側スレッドに渡す
		pErrInfo = [[YNErrCtrl errInfo] retain];
		
		//呼び出し側に戻ってすぐにメインウィンドウを閉じようとしてもウィンドウが消えない
		//メインウィンドウの表示時間をある程度確保しないとウィンドウが正常に消えないと思われる
		[NSThread sleepForTimeInterval:0.5];
	}
	
	//シーン生成完了通知
	pUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
					[[NSNumber alloc] initWithInt:result], @"result",
					pErrInfo, @"errInfo",
					nil
				 ];
	[self postNotificationWithName:@"onFinishCreateScene" userInfo:pUserInfo];
	
	//スレッド終了処理
	[YNErrCtrl termOnThreadEnd];
	[pool release];
	
	//NSLog(@"MIDITrailApp::thread_CreateScene end");
	return;
}

//******************************************************************************
// シーン生成完了通知
//******************************************************************************
- (void)onFinishCreateScene:(NSNotification*)pNotification
{
	int result = 0;
	NSNumber* pSceneResult = nil;
	YNErrInfo* pErrInfo = nil;
	
	//NSLog(@"MIDITrailApp::onFinishCreateScene");
	
	//カレントコンテキスト設定
	//OpenGLの処理を担当するスレッドを切り替える
	[m_pMainViewCtrl setCurrentContext];
	
	//インジケータアニメーション停止
	[m_pMainViewCtrl stopActivityIndicator];
	
	//シーン生成失敗時は何もせずに終了する
	pSceneResult = [[pNotification userInfo] objectForKey:@"result"];
	if ([pSceneResult intValue] != 0) {
		//メインビュー消去
		result = [self closeMainView];
		if (result != 0) goto EXIT;
		
		//シーン生成スレッドから渡されたエラー情報を本スレッドのエラーとして登録した上でアラート表示する
		pErrInfo = [[pNotification userInfo] objectForKey:@"errInfo"];
		if ((NSNull*)pErrInfo != [NSNull null]) {
			[YNErrCtrl setErr:[pErrInfo errLevel]
					   lineNo:[pErrInfo lineNo]
					 fileName:[pErrInfo fileName]
					  message:[pErrInfo message]
					 errInfo1:[pErrInfo errInfo1]
					 errInfo2:[pErrInfo errInfo2]];
			YN_SHOW_ERR();
			[pErrInfo release];
		}
		goto EXIT;
	}
	
	//演奏状態変更
	result = [self changePlayStatus:Stop];
	if (result != 0) goto EXIT;
	
	m_isRewind = false;
	
	//リピート状態設定
	[m_pMainViewCtrl setRepeatStatus:m_isRepeat];
	
	//シーン開始
	result = [m_pMainViewCtrl startScene:m_pScene isMonitor:NO];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// クローズボタン押下
//******************************************************************************
- (void)onCloseButton:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onCloseButton");
	
	//演奏を止める
	if ((m_PlayStatus == Play) || (m_PlayStatus == Pause)) {
		m_Sequencer.Stop();
	}
	else if (m_PlayStatus == MonitorON) {
		m_LiveMonitor.Stop();
	}
	
	//MIDI出力デバイスを閉じる
	m_Sequencer.ClosePortDevAll();
	
	//演奏状態変更
	result = [self changePlayStatus:NoData];
	if (result != 0) goto EXIT;
	
	//視点保存
	result = [self saveViewpoint];
	if (result != 0) goto EXIT;
	
	//描画停止
	[m_pMainViewCtrl stopScene];
	
	//メインビュー消去
	result = [self closeMainView];
	if (result != 0) goto EXIT;
	
	//シーン破棄
	if (m_pScene != NULL) {
		m_pScene->Release();
		delete m_pScene;
		m_pScene = NULL;
	}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 再生ボタン押下
//******************************************************************************
- (void)onPlayButton:(NSNotification*)pNotification
{
	int result = 0;
	
	NSLog(@"MIDITrailApp::onPlayButton");
	
	if (m_PlayStatus == Stop) {
		//Wavetableファイルパスをシーケンサに設定
		result = m_Sequencer.SetInternalSynthsizerWavetableFilePath([self getWavetableFilePath]);
		if (result != 0) goto EXIT;
		
		//シーケンサ初期化
		result = m_Sequencer.Initialize(&m_MsgQueue);
		if (result != 0) goto EXIT;
		
		//シーケンサにポート情報を登録
		result = [self setPortDev:&m_Sequencer];
		if (result != 0) goto EXIT;
		
		//シーケンサにシーケンスデータを登録
		result = m_Sequencer.SetSeqData(&m_SeqData);
		if (result != 0) goto EXIT;
		
		//巻き戻し
		if (m_isRewind) {
			m_isRewind = false;
			result = [m_pMainViewCtrl scene_Rewind];
			if (result != 0) goto EXIT;
		}
		
		//シーンに演奏開始を通知
		result = [m_pMainViewCtrl scene_PlayStart];
		if (result != 0) goto EXIT;
		
		//演奏速度
		m_Sequencer.SetPlaySpeedRatio(m_PlaySpeedRatio);
		
		//演奏開始
		result = m_Sequencer.Play();
		if (result != 0) goto EXIT;
		
		//演奏状態変更
		result = [self changePlayStatus:Play];
		if (result != 0) goto EXIT;
	}
	else if (m_PlayStatus == Play) {
		//演奏一時停止
		m_Sequencer.Pause();
		if (result != 0) goto EXIT;
		
		//演奏状態変更
		result = [self changePlayStatus:Pause];
		if (result != 0) goto EXIT;
	}
	else if (m_PlayStatus == Pause) {
		//演奏再開
		result = m_Sequencer.Resume();
		if (result != 0) goto EXIT;
		
		//演奏状態変更
		result = [self changePlayStatus:Play];
		if (result != 0) goto EXIT;
	}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 停止ボタン押下
//******************************************************************************
- (void)onStopButton:(NSNotification*)pNotification
{	
	//NSLog(@"MIDITrailApp::onStopButton");
	
	if ((m_PlayStatus == Play) || (m_PlayStatus == Pause)) {
		//演奏停止要求
		m_Sequencer.Stop();
		
		//演奏状態通知が届くまで再生中とみなす
		//ここでは演奏状態を変更しない
		
		//終了後に巻き戻す
		m_isRewind = true;
	}
	
	return;
}

//******************************************************************************
// 後方スキップボタン押下
//******************************************************************************
- (void)onSkipBackwardButton:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onSkipBackwardButton");
	
	result = m_Sequencer.Skip((-1) * m_SkipBackTimeSpanInMsec);
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 前方スキップボタン押下
//******************************************************************************
- (void)onSkipForwardButton:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onSkipForwardButton");
	
	result = m_Sequencer.Skip((+1) * m_SkipForwardTimeSpanInMsec);
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 再生スピードダウンボタン押下
//******************************************************************************
- (void)onPlaySpeedDownButton:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onPlaySpeedDownButton");
	
	//演奏状態確認
	if ((m_PlayStatus == Stop) || (m_PlayStatus == Play) || (m_PlayStatus == Pause)) {
		//変更OK
	}
	else {
		//変更NG
		goto EXIT;
	}
	
	//演奏速度ダウン
	m_PlaySpeedRatio -= m_SpeedStepInPercent;
	
	//リミット
	if (m_PlaySpeedRatio < m_SpeedStepInPercent) {
		m_PlaySpeedRatio = m_SpeedStepInPercent;
	}
	
	//演奏速度設定
	m_Sequencer.SetPlaySpeedRatio(m_PlaySpeedRatio);
	m_pScene->SetPlaySpeedRatio(m_PlaySpeedRatio);
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 再生スピードアップボタン押下
//******************************************************************************
- (void)onPlaySpeedUpButton:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onPlaySpeedUpButton");
	
	//演奏状態確認
	if ((m_PlayStatus == Stop) || (m_PlayStatus == Play) || (m_PlayStatus == Pause)) {
		//変更OK
	}
	else {
		//変更NG
		goto EXIT;
	}	
	
	//演奏速度アップ
	m_PlaySpeedRatio += m_SpeedStepInPercent;
	
	//リミット 400%
	if (m_PlaySpeedRatio > m_MaxSpeedInPercent) {
		m_PlaySpeedRatio = m_MaxSpeedInPercent;
	}
	
	//演奏速度設定
	m_Sequencer.SetPlaySpeedRatio(m_PlaySpeedRatio);
	m_pScene->SetPlaySpeedRatio(m_PlaySpeedRatio);
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// リピートボタン押下
//******************************************************************************
- (void)onRepeatButton:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onRepeatButton");
	
	//リピート切り替え
	m_isRepeat = m_isRepeat ? false : true;
	
	//リピート状態設定
	[m_pMainViewCtrl setRepeatStatus:m_isRepeat];
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 演奏状態変更通知：一時停止
//******************************************************************************
- (void)onChangePlayStatusPause:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onChangePlayStatusPause");
	
	//演奏状態変更
	result = [self changePlayStatus:Pause];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 演奏状態変更通知：停止（演奏終了）
//******************************************************************************
- (void)onChangePlayStatusStop:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onChangePlayStatusStop");
	
	//演奏状態変更
	result = [self changePlayStatus:Stop];
	if (result != 0) goto EXIT;
	
	//視点保存
	result = [self saveViewpoint];
	if (result != 0) goto EXIT;
	
	//シーンに演奏終了を通知
	if (m_pScene != NULL) {
		result = [m_pMainViewCtrl scene_PlayEnd];
		if (result != 0) goto EXIT;
	}
	
	//ユーザーの要求によって停止した場合は巻き戻す
	if ((m_isRewind) && (m_pScene != NULL)) {
		m_isRewind = false;
		result = [m_pMainViewCtrl scene_Rewind];
		if (result != 0) goto EXIT;
	}
	//通常の演奏終了の場合は次回の演奏時に巻き戻す
	else {
		m_isRewind = true;
		//リピート有効なら再生開始
		if (m_isRepeat) {
			[self onPlayButton:nil];
			if (result != 0) goto EXIT;
		}
	}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// モニタ開始イベント
//******************************************************************************
- (void)onStartMonitoring:(NSNotification*)pNotification
{
	int result = 0;
	
	//NSLog(@"MIDITrailApp::onStartMonitoring");
	
	//演奏状態確認
	if ((m_PlayStatus == NoData) || (m_PlayStatus == Stop) || (m_PlayStatus == MonitorOFF)) {
		//モニタ開始OK
	}
	else {
		//モニタ開始NG
		goto EXIT;
	}
	
	//メッセージキュークリア
	m_MsgQueue.Clear();
	
	//メインビュー初期化
	result = [m_pMainViewCtrl initialize:&m_MsgQueue];
	if (result != 0) goto EXIT;
	
	//シーン種別読み込み
	[self loadSceneType];
	
	//シーン生成
	result = [self createSceneWithType:m_SceneType SeqData:NULL];
	if (result != 0) goto EXIT;
	
	//シーン開始
	result = [m_pMainViewCtrl startScene:m_pScene isMonitor:YES];
	if (result != 0) goto EXIT;
	
	//Wavetableファイルパスをライブモニタに設定
	result = m_LiveMonitor.SetInternalSynthsizerWavetableFilePath([self getWavetableFilePath]);
	if (result != 0) goto EXIT;
	
	//ライブモニタ初期化
	result = m_LiveMonitor.Initialize(&m_MsgQueue);
	if (result != 0) goto EXIT;
	
	result = [self setMonitorPortDev:&m_LiveMonitor scene:m_pScene];
	if (result != 0) goto EXIT;
	
	//メインビュー表示
	result = [self openMainView];
	if (result != 0) goto EXIT;
	
	//シーンに演奏開始を通知
	result = [m_pMainViewCtrl scene_PlayStart];
	if (result != 0) goto EXIT;	
	
	//ライブモニタ開始
	result = m_LiveMonitor.Start();
	if (result != 0) goto EXIT;
	
	//演奏状態変更
	result = [self changePlayStatus:MonitorON];
	if (result != 0) goto EXIT;
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 非アクティブ状態遷移
//******************************************************************************
- (void)onApplicationWillResignActive
{
	int result = 0;
	
	//バックグラウンド演奏を実現するため演奏は停止しない
	//if (m_PlayStatus == Play) {
	//	//演奏一時停止
	//	m_Sequencer.Pause();
	//	if (result != 0) goto EXIT;
	//
	//	//演奏状態変更
	//	result = [self changePlayStatus:Pause];
	//	if (result != 0) goto EXIT;
	//}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
}

//******************************************************************************
// バックグラウンド状態遷移
//******************************************************************************
- (void)onApplicationDidEnterBackground
{
	int result = 0;
	
	//バックグラウンド状態をメインビューに通知
	[m_pMainViewCtrl setBackground:true];
	
	//バックグラウド演奏を実現するため演奏は停止しない
	//if (m_PlayStatus == Play) {
	//	//演奏一時停止
	//	m_Sequencer.Pause();
	//	if (result != 0) goto EXIT;
	//
	//	//演奏状態変更
	//	result = [self changePlayStatus:Pause];
	//	if (result != 0) goto EXIT;
	//}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// アクティブ状態遷移
//******************************************************************************
- (void)onApplicationDidBecomeActive
{
	int result = 0;
	
	[m_pMainViewCtrl setBackground:false];
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

//******************************************************************************
// 設定ファイル初期化
//******************************************************************************
- (int)initConfFile
{
	int result = 0;
	
	//ユーザ設定初期化
	m_pUserConf = [[YNUserConf alloc] init];
	if (m_pUserConf == nil) {
		YN_SET_ERR(@"Program error.", 0, 0);
		goto EXIT;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// グラフィック設定読み込み
//******************************************************************************
- (int)loadGraphicConf
{
	int result = 0;
	int enableAntialias = 0;
	
	//カテゴリ／セクション設定
	[m_pUserConf setCategory:MT_CONF_CATEGORY_GRAPHIC];
	[m_pUserConf setSection:MT_CONF_SECTION_AA];
	
	//アンチエリアシング
	enableAntialias = [m_pUserConf intValueForKey:@"EnableAntialias" defaultValue:0];
	if (enableAntialias == 0) {
		//アンチエイリアシング無効
		m_RendererParam.isEnableAntialiasing = false;
		m_RendererParam.sampleMode = 0;	//サンプルモードは使用しない
		m_RendererParam.sampleNum = 0;	//サンプル数は4 固定
	}
	else {
		//アンチエイリアシング有効
		m_RendererParam.isEnableAntialiasing = true;
		m_RendererParam.sampleMode = 0;	//サンプルモードは使用しない
		m_RendererParam.sampleNum = 4;	//サンプル数は4 固定
	}
	
	return result;
}

//******************************************************************************
// シーン種別読み込み
//******************************************************************************
- (void)loadSceneType
{
	NSString* pType = nil;
	
	//カテゴリ／セクション設定
	[m_pUserConf setCategory:MT_CONF_CATEGORY_VIEW];
	[m_pUserConf setSection:MT_CONF_SECTION_SCENE];
	
	//ユーザ設定値取得：シーン種別
	//TAG:シーン追加
	pType = [m_pUserConf strValueForKey:@"Type" defaultValue:@""];
	
	if ([pType isEqualToString:@"PianoRoll3D"]) {
		m_SceneType = PianoRoll3D;
	}
	else if ([pType isEqualToString:@"PianoRoll2D"]) {
		m_SceneType = PianoRoll2D;
	}
	else if ([pType isEqualToString:@"PianoRollRain"]) {
		m_SceneType = PianoRollRain;
	}
	else if ([pType isEqualToString:@"PianoRollRain2D"]) {
		m_SceneType = PianoRollRain2D;
	}
	else if ([pType isEqualToString:@"PianoRollRing"]) {
		m_SceneType = PianoRollRing;
	}
	else {
		m_SceneType = PianoRoll3D;
	}
	
	return;
}

//******************************************************************************
// 視点読み込み
//******************************************************************************
- (int)loadViewpoint
{
	int result = 0;
	MTScene::MTViewParamMap defParamMap;
	MTScene::MTViewParamMap viewParamMap;
	MTScene::MTViewParamMap::iterator itr;
	NSString* pSection = nil;
	NSString* pKey = nil;
	float param = 0.0f;
	
	//シーンからデフォルトの視点を取得
	m_pScene->GetDefaultViewParam(&defParamMap);
	
	//カテゴリ／セクション設定
	[m_pUserConf setCategory:MT_CONF_CATEGORY_VIEW];
	pSection = [NSString stringWithFormat:@"%@%@", MT_CONF_SECTION_VIEWPOINT, m_pScene->GetName()];
	[m_pUserConf setSection:pSection];
	
	//パラメータをユーザデフォルトから取得
	for (itr = defParamMap.begin(); itr != defParamMap.end(); itr++) {
		pKey = [NSString stringWithCString:(itr->first).c_str() encoding:NSASCIIStringEncoding];
		param = [m_pUserConf floatValueForKey:pKey defaultValue:(itr->second)];
		
		if (result != 0) goto EXIT;
		viewParamMap.insert(MTScene::MTViewParamMapPair((itr->first).c_str(), param));
	}
	
	//シーンに視点を登録
	m_pScene->SetViewParam(&viewParamMap);
	
EXIT:;
	return result;
}

//******************************************************************************
// 視点保存
//******************************************************************************
- (int)saveViewpoint
{
	int result = 0;
	MTScene::MTViewParamMap viewParamMap;
	MTScene::MTViewParamMap::iterator itr;
	NSString* pSection = nil;
	NSString* pKey = nil;
	
	//シーンから現在の視点を取得
	[m_pMainViewCtrl scene_GetViewpoint:&viewParamMap];
	
	//カテゴリ／セクション設定
	[m_pUserConf setCategory:MT_CONF_CATEGORY_VIEW];
	pSection = [NSString stringWithFormat:@"%@%@", MT_CONF_SECTION_VIEWPOINT, m_pScene->GetName()];
	[m_pUserConf setSection:pSection];
	
	//パラメータを設定ファイルに登録
	for (itr = viewParamMap.begin(); itr != viewParamMap.end(); itr++) {
		pKey = [NSString stringWithCString:(itr->first).c_str() encoding:NSASCIIStringEncoding];
		[m_pUserConf setFloat:itr->second forKey:pKey];
	}
	
	//視点が切り替えられたことをシーンに伝達
	[m_pMainViewCtrl scene_SetViewpoint:&viewParamMap];
	
EXIT:;
	return result;
}

//******************************************************************************
// プレーヤー設定読み込み
//******************************************************************************
- (int)loadPlayerConf
{
	int result = 0;
	MTConfFile confFile;
	int timeSpan = 400;
	
	result = confFile.Initialize(@"Player");
	if (result != 0) goto EXIT;
	
	//----------------------------------
	//リワインド／スキップ制御
	//----------------------------------
	result = confFile.SetCurSection(@"SkipControl");
	if (result != 0) goto EXIT;
	result = confFile.GetInt(@"SkipBackTimeSpanInMsec", &m_SkipBackTimeSpanInMsec, 10000);
	if (result != 0) goto EXIT;
	result = confFile.GetInt(@"SkipForwardTimeSpanInMsec", &m_SkipForwardTimeSpanInMsec, 10000);
	if (result != 0) goto EXIT;
	result = confFile.GetInt(@"MovingTimeSpanInMsec", &timeSpan, 400);
	if (result != 0) goto EXIT;
	
	//シーケンサにリワインド／スキップ移動時間を設定
	m_Sequencer.SetMovingTimeSpanInMsec(timeSpan);
	
	//----------------------------------
	//演奏スピード制御
	//----------------------------------
	result = confFile.SetCurSection(@"PlaybackSpeedControl");
	if (result != 0) goto EXIT;
	result = confFile.GetInt(@"SpeedStepInPercent", &m_SpeedStepInPercent, 1);
	if (result != 0) goto EXIT;
	result = confFile.GetInt(@"MaxSpeedInPercent", &m_MaxSpeedInPercent, 400);
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// 表示効果反映
//******************************************************************************
- (int)updateEffect
{
	int result = 0;
	int value = 0;
	
	m_isEnablePianoKeyboard = false;
	m_isEnableRipple = false;
	m_isEnablePitchBend = false;
	m_isEnableStars = false;
	m_isEnableCounter = false;
    m_isEnableBackgroundImage = false;
	m_isEnableFPS = false;
	
	//表示項目の表示設定を取得
	[m_pUserConf setCategory:MT_CONF_CATEGORY_VIEW];
	[m_pUserConf setSection:MT_CONF_SECTION_DISPLAY];
	
	value = [m_pUserConf intValueForKey:@"PianoKeyboard" defaultValue:1];
	if (value == 1) m_isEnablePianoKeyboard = true;
	
	value = [m_pUserConf intValueForKey:@"Ripple" defaultValue:1];
	if (value == 1) m_isEnableRipple = true;
	
	value = [m_pUserConf intValueForKey:@"PitchBendMotion" defaultValue:1];
	if (value == 1) m_isEnablePitchBend = true;
	
	value = [m_pUserConf intValueForKey:@"Stars" defaultValue:1];
	if (value == 1) m_isEnableStars = true;
	
	value = [m_pUserConf intValueForKey:@"Counter" defaultValue:1];
	if (value == 1) m_isEnableCounter = true;
	
	value = [m_pUserConf intValueForKey:@"BackgroundImage" defaultValue:1];
	if (value == 1) m_isEnableBackgroundImage = true;
	
	value = [m_pUserConf intValueForKey:@"FPS" defaultValue:0];  //FPSはデフォルトOFF
	if (value == 1) m_isEnableFPS = true;
	
	//シーンに反映
	m_pScene->SetEffect(MTScene::EffectPianoKeyboard, m_isEnablePianoKeyboard);
	m_pScene->SetEffect(MTScene::EffectRipple, m_isEnableRipple);
	m_pScene->SetEffect(MTScene::EffectPitchBend, m_isEnablePitchBend);
	m_pScene->SetEffect(MTScene::EffectStars, m_isEnableStars);
	m_pScene->SetEffect(MTScene::EffectCounter, m_isEnableCounter);
	m_pScene->SetEffect(MTScene::EffectBackgroundImage, m_isEnableBackgroundImage);
	m_pScene->SetEffect(MTScene::EffectFPS, m_isEnableFPS);
	
EXIT:;
	return result;
}

//******************************************************************************
// 演奏状態変更
//******************************************************************************
- (int)changePlayStatus:(PlayStatus)status
{
	int result = 0;
	
	//演奏状態変更
	m_PlayStatus = status;
	
	//演奏状態設定
	[m_pMainViewCtrl setPlayStatus:status];
	
	//自動ロック抑止
	//  演奏中またはモニタ中は自動ロックを抑止する
	if ((status == Play) || (status == MonitorON)) {
		[UIApplication sharedApplication].idleTimerDisabled = YES;
	}
	else {
		[UIApplication sharedApplication].idleTimerDisabled = NO;		
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// メインビュー表示
//******************************************************************************
- (int)openMainView
{
	int result = 0;
	
	//ステータスバー消去
	[[UIApplication sharedApplication] setStatusBarHidden:YES];
	
	//メインビュー表示
	m_pMainViewCtrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
	m_pMainViewCtrl.modalPresentationStyle = UIModalPresentationFullScreen;
	[m_pTabBarCtrl presentViewController:m_pMainViewCtrl animated:YES completion:nil];
		
	//バックグラウンド演奏対応：オーディオセッションアクティブ化
	result = [self setAudioSessionActive:YES];
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// メインビュー消去
//******************************************************************************
- (int)closeMainView
{
	int result = 0;
	
	//メインビュー消去
	m_pMainViewCtrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
	[m_pMainViewCtrl dismissViewControllerAnimated:YES completion:nil];
	
	//ステータスバー表示
	[[UIApplication sharedApplication] setStatusBarHidden:NO];
	
	//ナビゲーションバーの配置を更新する
	//  これを行わないとナビゲーションバーがステータスバーに重なってしまう
	//  -> 現状この対策の意味はなく、ナビゲーションバーが検索バーを隠してしまうのでやめる
	//[m_pFileNaviCtrl setNavigationBarHidden:YES];
	//[m_pFileNaviCtrl setNavigationBarHidden:NO];
	//[m_pMonitorNaviCtrl setNavigationBarHidden:YES];
	//[m_pMonitorNaviCtrl setNavigationBarHidden:NO];
	
	//バックグラウンド演奏対応：オーディオセッション非アクティブ化
	result = [self setAudioSessionActive:NO];
	if (result != 0) goto EXIT;
	
EXIT:;
	return result;
}

//******************************************************************************
// シーン生成
//******************************************************************************
- (int)createSceneWithType:(SceneType)type
				   SeqData:(SMSeqData*)pSeqData  //ライブモニタ時はNULL
{
	int result = 0;
	OGLDevice* pDevice = NULL;
	
	//シーン破棄
	if (m_pScene != NULL) {
		m_pScene->Release();
		delete m_pScene;
		m_pScene = NULL;
	}
	
	//シーンオブジェクト生成
	//TAG:シーン追加
	try {
		if (type == Title) {
			m_pScene = new MTSceneTitle();
		}
		else {
			//プレイヤ用シーン生成
			if (pSeqData != NULL) {
				if (type == PianoRoll3D) {
					m_pScene = new MTScenePianoRoll3D();
				}
				else if (type == PianoRoll2D) {
					m_pScene = new MTScenePianoRoll2D();
				}
				else if (type == PianoRollRain) {
					m_pScene = new MTScenePianoRollRain();
				}
				else if (type == PianoRollRain2D) {
					m_pScene = new MTScenePianoRollRain2D();
				}
				else if (type == PianoRollRing) {
					m_pScene = new MTScenePianoRollRing();
				}
			}
			//ライブモニタ用シーン生成
			else {
				if (type == PianoRoll3D) {
					m_pScene = new MTScenePianoRoll3DLive();
				}
				else if (type == PianoRoll2D) {
					m_pScene = new MTScenePianoRoll2DLive();
				}
				else if (type == PianoRollRain) {
					m_pScene = new MTScenePianoRollRainLive();
				}
				else if (type == PianoRollRain2D) {
					m_pScene = new MTScenePianoRollRain2DLive();
				}
				else if (type == PianoRollRing) {
					m_pScene = new MTScenePianoRollRingLive();
				}
			}
		}
	}
	catch (std::bad_alloc) {
		result = YN_SET_ERR(@"Could not allocate memory.", type, 0);
		goto EXIT;
	}
	
	if (m_pScene == NULL) {
		result = YN_SET_ERR(@"Program error.", type, 0);
		goto EXIT;
	}
	
	//シーンの生成	
	pDevice = [m_pMainViewCtrl getDevice];
	result = m_pScene->Create([m_pMainViewCtrl getView], pDevice, pSeqData);
	if (result != 0) goto EXIT;
	
	//保存されている視点をシーンに反映する
	if (type != Title) {
		result = [self loadViewpoint];
		if (result != 0) goto EXIT;
	}
	
	//表示項目読み込み
	[self updateEffect];
	
	//演奏速度設定
	m_pScene->SetPlaySpeedRatio(m_PlaySpeedRatio);
	
EXIT:;
	return result;
}

//******************************************************************************
// Wavetableファイルパス取得
//******************************************************************************
- (NSString*)getWavetableFilePath
{
	NSArray* pPathList = nil;
	NSString* pDocDirPath = nil;
	NSString* pWavetableFileName = nil;
	NSString* pWavetableFilePath = nil;
	
	//Wavetableファイル名を取得
	[m_pUserConf setCategory:MT_CONF_CATEGORY_SYNTHESIZER];
	[m_pUserConf setSection:MT_CONF_SECTION_WAVETABLE];
	pWavetableFileName = [m_pUserConf strValueForKey:@"WavetableFileName" defaultValue:@""];
	
	//Wavetableファイルのパス
	if ([pWavetableFileName length] == 0) {
		pWavetableFilePath = @"";
	}
	else {
		pPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
		pDocDirPath = [pPathList objectAtIndex:0];
		pWavetableFilePath = [pDocDirPath stringByAppendingPathComponent:pWavetableFileName];
	}
	
	return pWavetableFilePath;
}

//******************************************************************************
// ポートデバイス登録
//******************************************************************************
- (int)setPortDev:(SMSequencer*)pSequencer
{
	int result = 0;
    unsigned char portNo = 0;
	NSString* pDevIdName = nil;
    NSString* portName[] = {@"PortA", @"PortB", @"PortC", @"PortD", @"PortE", @"PortF"};
	NSString* pPortDevDisplayNameKey = nil;
	NSString* pDevDisplayName = nil;
	
	//MIDI OUTの設定を取得
	[m_pUserConf setCategory:MT_CONF_CATEGORY_MIDI];
	[m_pUserConf setSection:MT_CONF_SECTION_MIDIOUT];
	
	//設定ファイルからユーザ選択デバイス名を取得してシーケンサに登録
	for (portNo = 0; portNo < SM_MIDIOUT_PORT_NUM_MAX; portNo++) {
		//選択デバイス識別名称取得：キー文字列 ex."PortA"
		pDevIdName = [m_pUserConf strValueForKey:portName[portNo] defaultValue:@""];
		
		//選択デバイス表示名称取得：キー文字列 ex."PrtA_DevDisplayName"
		pPortDevDisplayNameKey = [NSString stringWithFormat:@"%@_DevDisplayName", portName[portNo]];
		pDevDisplayName = [m_pUserConf strValueForKey:pPortDevDisplayNameKey defaultValue:@""];
		
		//選択デバイスをシーケンサに登録
		if ([pDevIdName length] > 0) {
			result = pSequencer->SetPortDev(portNo, pDevIdName, pDevDisplayName);
			if (result != 0) goto EXIT;
		}
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// MIDI IN モニタ情報登録
//******************************************************************************
- (int)setMonitorPortDev:(SMLiveMonitor*)pLiveMonitor
				   scene:(MTScene*)pScene
{
	int result = 0;
	NSString* pDevIdName = nil;
	NSString* pDevDisplayName = nil;
	int checkMIDITHRU = 0;
	bool isMIDITHRU = false;
	
	//--------------------------------------
	// MIDI IN
	//--------------------------------------
	//カテゴリ／セクション設定
	[m_pUserConf setCategory:MT_CONF_CATEGORY_MIDI];
	[m_pUserConf setSection:MT_CONF_SECTION_MIDIIN];
	
	//設定ファイルからユーザ選択デバイス名を取得してシーケンサに登録
	pDevIdName = [m_pUserConf strValueForKey:@"PortA" defaultValue:@""];
	pDevDisplayName = [m_pUserConf strValueForKey:@"PortA_DevDisplayName" defaultValue:@""];
	checkMIDITHRU = [m_pUserConf intValueForKey:@"MIDITHRU" defaultValue:1];
	if (checkMIDITHRU > 0) {
		isMIDITHRU = true;
	}
	if ([pDevIdName length] > 0) {
		result = pLiveMonitor->SetInPortDev(pDevIdName, pDevDisplayName, isMIDITHRU);
		if (result != 0) goto EXIT;
	}
	
	//シーンに MIDI IN デバイス名を登録
	pDevDisplayName = pLiveMonitor->GetInPortDevDisplayName(pDevIdName);
	result = pScene->SetParam(@"MIDI_IN_DEVICE_NAME", pDevDisplayName);
	if (result != 0) goto EXIT;	
	
	//--------------------------------------
	// MIDI OUT (MIDITHRU)
	//--------------------------------------
	//カテゴリ／セクション設定
	[m_pUserConf setCategory:MT_CONF_CATEGORY_MIDI];
	[m_pUserConf setSection:MT_CONF_SECTION_MIDIOUT];
	
	//設定ファイルからユーザ選択デバイス名を取得してシーケンサに登録
	pDevIdName = [m_pUserConf strValueForKey:@"PortA" defaultValue:@""];
	pDevDisplayName = [m_pUserConf strValueForKey:@"PortA_DevDisplayName" defaultValue:@""];
	if (result != 0) goto EXIT;
	
	if (([pDevIdName length] > 0) && (isMIDITHRU)) {
		result = pLiveMonitor->SetOutPortDev(pDevIdName, pDevDisplayName);
		if (result != 0) goto EXIT;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// 通知送信処理
//******************************************************************************
- (int)postNotificationWithName:(NSString*)pName userInfo:(NSDictionary*)pUserInfo
{
	int result = 0;
	NSNotification* pNotification = nil;
	NSNotificationCenter* pCenter = nil;
	
	//通知オブジェクトを作成
    pNotification = [NSNotification notificationWithName:pName
												  object:self
												userInfo:pUserInfo];
	//通知する
	pCenter = [NSNotificationCenter defaultCenter];
	
	//通知に対応する処理をメインスレッドに処理させる場合
	[pCenter performSelectorOnMainThread:@selector(postNotification:)
							  withObject:pNotification
						   waitUntilDone:NO];
	
	return result;
}

//******************************************************************************
// 他アプリからのファイル受け取り：iOS9未満用
//******************************************************************************
- (int)receiveFileFromOtherApp:(NSDictionary*)pLaunchOptions
{
	int result = 0;
	int i = 0;
	NSURL *pUrl = nil;
	NSArray* pPathList = nil;
	NSString* pDocDirPath = nil;
	NSString* pDestFilePath = nil;
	NSString* pBaseFileName = nil;
	NSString* pFileName = nil;
	NSFileManager* pFileManager = nil;
	NSError* pError = nil;
	BOOL apiresult = NO;
	
	//オプション指定なしなら何もしない
	if (pLaunchOptions == nil) goto EXIT;
	
	//指定ファイルを確認
	pUrl = [pLaunchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
	
	//ファイル指定なしならば何もしない
	if (pUrl == nil) goto EXIT;
	
	//Documentsディレクトリパスを取得
	pPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	pDocDirPath = [pPathList objectAtIndex:0];
	
	//移動先のファイルパスを作成
	pFileManager = [NSFileManager defaultManager];
	pBaseFileName = [pUrl lastPathComponent];
	for (i = 0; i < 1000; i++) {
		//移動先ファイル名
		if (i == 0) {
			//1回目 ex: original.mid
			pFileName = pBaseFileName;
		}
		else {
			//2回目以降 ex: original_1.mid
			pFileName = [NSString stringWithFormat:@"%@_%d.%@",
							[pBaseFileName stringByDeletingPathExtension], i,
							[pBaseFileName pathExtension]];
		}
		//移動先ファイルパス
		pDestFilePath = [pDocDirPath stringByAppendingPathComponent:pFileName];
		
		//移動先ファイルがすでに存在するならやり直す
		if ([pFileManager fileExistsAtPath:pDestFilePath]) {
			pDestFilePath = nil;
		}
		else {
			break;
		}
	}
	
	//重複しないファイル名が作成できなかった場合は何もしない
	if (pDestFilePath == nil) {
		NSLog(@"warning: no dest file path.");
		goto EXIT;
	}
	
	//指定ファイルをDocuments直下に移動
	apiresult = [pFileManager moveItemAtPath:[pUrl path] toPath:pDestFilePath error:&pError];
	if (!apiresult) {
		//エラーが発生しても処理は続行する
		NSLog(@"error: moveItemAtPath");
	}
	
EXIT:
	return result;
}

//******************************************************************************
// 指定URLオープン：iOS9以降
//******************************************************************************
- (int)openURL:(NSURL*)pURL
{
	int result = 0;
	NSArray* pPathList = nil;
	NSString* pDocDirPath = nil;
	NSString* pInboxDirPath = nil;
	NSString* pDestFilePath = nil;
	NSString* pBaseFileName = nil;
	NSString* pFileName = nil;
	NSError* pError = nil;
	BOOL apiresult = NO;
	
	//URL指定なしなら何もしない
	if (pURL == nil) goto EXIT;
	
	//指定URLのファイル名
	pBaseFileName = [pURL lastPathComponent];
	
	//MIDITrailのDocumentsディレクトリとInboxディレクトリのパスを作成
	pPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	pDocDirPath = [pPathList objectAtIndex:0];
	pInboxDirPath = [pDocDirPath stringByAppendingPathComponent:@"Inbox"];
	
	//MIDITrailのDocumentsディレクトリ内のファイルを指定された場合
	//  /private/var/mobile/Containers/Data/Application/(ID)/Documents/xxxx.mid
	//  /private/var/mobile/Containers/Data/Application/(ID)/Documents/Inbox/xxxx.mid
	if ([pURL.path rangeOfString:pDocDirPath options:NSCaseInsensitiveSearch].location != NSNotFound) {
		//Documents/Inboxディレクトリ内のファイルを指定された場合はDocuments直下に移動する
		if ([pURL.path rangeOfString:pInboxDirPath options:NSCaseInsensitiveSearch].location != NSNotFound) {
			//移動先ファイルパスを生成
			pFileName = [self makeFileNameAtDocumentsWithBaseFileName:pBaseFileName];
			if (pFileName == nil) {
				result = YN_SET_ERR(@"File name error.", 0, 0);
				goto EXIT;
			}
			pDestFilePath = [pDocDirPath stringByAppendingPathComponent:pFileName];
			
			//指定ファイルをDocuments直下に移動
			apiresult = [[NSFileManager defaultManager] moveItemAtPath:pURL.path toPath:pDestFilePath error:&pError];
			if (!apiresult) {
				result = YN_SET_ERR(@"File move error.", pError.code, 0);
				goto EXIT;
			}
		}
		//Documentsディレクトリ直下のファイルを指定された場合
		else {
			pFileName = pBaseFileName;
		}
	}
	//MIDITrailのDocumentsディレクトリ外のファイルを指定された場合（以下ファイルパスの例）
	//  /private/var/mobile/Containers/Data/Application/(ID)/Documents/xxxx.mid
	//  /private/var/mobile/Containers/Shared/AppGroup/(ID)/File%20Provider%20Storage/Downloads/xxxx.mid
	//  /private/var/mobile/Library/Mobile%20Documents/com~apple~CloudDocs/Downloads/xxxx.mid
	else {
		//移動先ファイルパスを作成
		pFileName = [self makeFileNameAtDocumentsWithBaseFileName:pBaseFileName];
		if (pFileName == nil) {
			result = YN_SET_ERR(@"File name error.", 0, 0);
			goto EXIT;
		}
		pDestFilePath = [pDocDirPath stringByAppendingPathComponent:pFileName];
		
		//指定URLからDocumentsディレクトリにファイル受け取り
		result = [self receiveFileFromURL:pURL toFilePath:pDestFilePath];
		if (result != 0) goto EXIT;
	}
	
	//ファイル一覧更新
	result = [m_pFileViewCtrl updateFileList];
	if (result != 0) goto EXIT;
	
	//画像ファイルとWavetableファイルの一覧更新
	result = [m_pSettingViewCtrl updateFileList];
	if (result != 0) goto EXIT;
	
	//ファイル一覧ビューを表示
	m_pTabBarCtrl.selectedIndex = 0;
	
	//ファイル一覧ビューで指定ファイルまでスクロール
	//画像ファイルやWavetableファイルの場合はファイル一覧に見つからないのでスクロールしない
	result = [m_pFileViewCtrl scroolToFile:pFileName];
	if (result != 0) goto EXIT;
	
EXIT:
	return result;
}

//******************************************************************************
// Documentsディレクトリにおけるファイル名生成
//******************************************************************************
- (NSString*)makeFileNameAtDocumentsWithBaseFileName:(NSString*)pBaseFileName
{
	NSArray* pPathList = nil;
	NSString* pDocDirPath = nil;
	NSFileManager* pFileManager = nil;
	NSString* pFileName = nil;
	NSString* pDestFilePath = nil;
	int i = 0;
	
	//MIDITrailのDocumentsディレクトリとInboxディレクトリのパスを取得
	pPathList = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	pDocDirPath = [pPathList objectAtIndex:0];
	
	//移動先のファイルパスを作成
	pFileManager = [NSFileManager defaultManager];
	for (i = 0; i < 1000; i++) {
		//移動先ファイル名
		if (i == 0) {
			//1回目 ex: original.mid
			pFileName = pBaseFileName;
		}
		else {
			//2回目以降 ex: original_1.mid
			pFileName = [NSString stringWithFormat:@"%@_%d.%@",
							[pBaseFileName stringByDeletingPathExtension], i,
							[pBaseFileName pathExtension]];
		}
		//移動先ファイルパス
		pDestFilePath = [pDocDirPath stringByAppendingPathComponent:pFileName];
		
		//移動先ファイルがすでに存在するならやり直す
		if ([pFileManager fileExistsAtPath:pDestFilePath]) {
			pDestFilePath = nil;
		}
		else {
			break;
		}
	}
	//1000回繰り返しても重複しないファイル名が作成できなかった場合はnilのまま返す
	
	return pFileName;
}

//******************************************************************************
// 指定URLからファイル受け取り
//******************************************************************************
- (int)receiveFileFromURL:(NSURL*)pURL toFilePath:(NSString*)pToFilePath
{
	int result = 0;
	BOOL apiresult = NO;
	NSFileCoordinator* pFileCoordinator = nil;
	NSError* pError = nil;
	bool isStartAccessing = false;
	__block NSData* pData = nil;
	__block NSError* pDataError = nil;
	__block BOOL dataResult = NO;
	
	//指定URLセキュリティスコープリソースアクセス開始
	apiresult = [pURL startAccessingSecurityScopedResource];
	if (!apiresult) {
		result = YN_SET_ERR(@"Data access error.", 0, 0);
		goto EXIT;
	}
	isStartAccessing = true;
	
	//指定URLのファイルをDocumentsフォルダにコピー
	pFileCoordinator = [[NSFileCoordinator alloc] initWithFilePresenter:nil];
	[pFileCoordinator coordinateReadingItemAtURL:pURL
										 options:NSFileCoordinatorReadingWithoutChanges
										   error:&pError
									  byAccessor:^(NSURL* pNewURL) {
		//指定URLのデータ読み込み
		//サイズが小さいファイルを前提とした同期処理であることに注意
		pData = [NSData dataWithContentsOfURL:pNewURL options:0 error:&pDataError];
		if (pDataError != nil) {
			YN_SET_ERR(@"Data access error.", pDataError.code, 0);
			YN_SHOW_ERR();
			return;
		}
		//Documentsフォルダにファイルを書き込む
		dataResult = [pData writeToFile:pToFilePath options:0 error:&pDataError];
		if (!dataResult) {
			YN_SET_ERR(@"File write error.", pDataError.code, 0);
			YN_SHOW_ERR();
			return;
		}
	}];
	if (pError != nil) {
		result = YN_SET_ERR(@"Data access error.", pError.code, 0);
		goto EXIT;
	}
	
EXIT:;
	if (isStartAccessing) {
		//指定URLセキュリティスコープリソースアクセス終了
		[pURL stopAccessingSecurityScopedResource];
	}
	return result;
}

//******************************************************************************
// オーディオセッション初期化
//******************************************************************************
- (int)initAudioSession
{
	int result = 0;
	BOOL apiresult = NO;
	NSError* pError = nil;
	
	m_pAudioSession = [AVAudioSession sharedInstance];
	apiresult = [m_pAudioSession setCategory:AVAudioSessionCategoryPlayback
								 withOptions:AVAudioSessionCategoryOptionMixWithOthers
									   error:&pError];
	if (!apiresult) {
		result = YN_SET_ERR(@"Audio session error.", pError.code, 0);
		goto EXIT;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// オーディオセッションアクティブ設定
//******************************************************************************
- (int)setAudioSessionActive:(BOOL)isActive
{
	int result = 0;
	BOOL apiresult = NO;
	NSError* pError = nil;
	
	apiresult = [m_pAudioSession setActive:isActive error:&pError];
	if (!apiresult) {
		result = YN_SET_ERR(@"Audio session error.", pError.code, 0);
		goto EXIT;
	}
	
EXIT:;
	return result;
}

//******************************************************************************
// オーディオセッション中断通知
//******************************************************************************
- (void)onAudioSessionInterruption:(NSNotification*)pNotification
{
	int result = 0;
	NSNumber* pInterruptionType = 0;
	
	//割り込み種別を確認
	pInterruptionType = [pNotification.userInfo objectForKey:AVAudioSessionInterruptionTypeKey];
	
	//割り込み開始
	if ([pInterruptionType unsignedIntegerValue] == AVAudioSessionInterruptionTypeBegan) {
		NSLog(@"AVAudioSessionInterruptionTypeBegan");
		//演奏中の場合
		if (m_PlayStatus == Play) {
			//演奏一時停止
			m_Sequencer.Pause();
			if (result != 0) goto EXIT;
			//演奏状態変更
			result = [self changePlayStatus:Pause];
			if (result != 0) goto EXIT;
		}
		//モニタ中の場合
		else if (m_PlayStatus == MonitorON) {
			//何もしない
		}
	}
	//割り込み終了
	else if ([pInterruptionType unsignedIntegerValue] == AVAudioSessionInterruptionTypeEnded) {
		NSLog(@"AVAudioSessionInterruptionTypeEnded");
		//何もしない
		//TODO: モニタ停止すべきだが現状は一時停止機能が存在しない
	}
	
EXIT:;
	if (result != 0) YN_SHOW_ERR();
	return;
}

@end

