using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Drawing;
using System.IO;
using FDK;

namespace StrokeStyleT
{
	// Song クラスの定義

	class Song : IDisposable
	{
		public Song()
		{
			// プロパティ

			this._stg曲名 = null;
			this._stg曲ファイルマクロ無しパス = null;
			this._stgサムネイルファイルマクロ無しパス = null;

			// サムネイル

			this._bサムネイルBitmap作成完了 = new CAtom<bool>( false );
			this._bmpサムネイル画像 = null;
			this._bサムネイルTexture作成完了 = new CAtom<bool>( false );
			this._txサムネイル画像 = null;

			// スコア

			this._listチップ = null;
			this._list小節長倍率 = null;
			this._ms背景動画 = null;
			this._n作成時のWASAPIバッファ長bytes = 0;
			this._n作成時のWASAPIチャンネル数 = 2;
			this._n作成時のWASAPI周波数Hz = 44100;
			this._n作成時のWASAPIサンプリングバイト数 = 2;
			this._stg背景動画ファイルパス = null;
			this._sdBGM = null;
			this._dicメモ = new Dictionary<int, string>();
			this._chip背景動画 = null;
		}
		public Song( Song copySrc )
		{
			#region [ プロパティ ]
			//-------------------------
			this._stg曲名 = copySrc.stg曲名;
			this._stg曲ファイルマクロ無しパス = copySrc.stg曲ファイルマクロ無しパス;
			this._stgサムネイルファイルマクロ無しパス = copySrc.stgサムネイルファイルマクロ無しパス;
			//-------------------------
			#endregion
			#region [ サムネイル ]
			//-------------------------
			this._bmpサムネイル画像 = (Bitmap) copySrc.bmpサムネイル画像.Clone();
			this._bサムネイルBitmap作成完了 = new CAtom<bool>( copySrc.bサムネイルBitmap作成完了.Get() );
			Global.tデバイスをロックして処理を行う( ( hDevice ) => {
				this._txサムネイル画像 = new CTexture( hDevice, this._bmpサムネイル画像 );
				this._bサムネイルTexture作成完了 = new CAtom<bool>( copySrc.bサムネイルTexture作成完了.Get() );
			} );
			//-------------------------
			#endregion
			#region [ スコア ]
			//-------------------------
			if( null != copySrc.listチップ )
			{
				this._listチップ = new List<Cチップ>();
				foreach( var chip in copySrc.listチップ )
					this._listチップ.Add( new Cチップ( chip ) );
			}
			else
			{
				this._listチップ = null;
			}

			if( null != copySrc.list小節長倍率 )
			{
				this._list小節長倍率 = new List<double>();
				foreach( var db in copySrc.list小節長倍率 )
					this._list小節長倍率.Add( db );
			}
			else
			{
				this._list小節長倍率 = null;
			}

			this._ms背景動画 = null;
			
			this._n作成時のWASAPIバッファ長bytes = copySrc.n作成時のWASAPIバッファ長bytes;
			this._n作成時のWASAPIチャンネル数 = copySrc.n作成時のWASAPIチャンネル数;
			this._n作成時のWASAPI周波数Hz = copySrc.n作成時のWASAPI周波数Hz;
			this._n作成時のWASAPIサンプリングバイト数 = copySrc.n作成時のWASAPIサンプリングバイト数;

			this._stg背景動画ファイルパス = copySrc.stg背景動画ファイルパス;
			this._sdBGM = null;

			if( null != copySrc.dicメモ )
			{
				this._dicメモ = new Dictionary<int, string>();
				foreach( var kvp in copySrc.dicメモ )
					this._dicメモ.Add( kvp.Key, kvp.Value );
			}
			else
			{
				this._dicメモ = null;
			}

			if( null != copySrc.chip背景動画 )
				this._chip背景動画 = new Cチップ( copySrc.chip背景動画 );
			else
				this._chip背景動画 = null;
			//-------------------------
			#endregion
		}

		#region [ Dispose-Finalize パターン ]
		//-------------------------
		public void Dispose()
		{
			if( this.Disposed.bDispose済みまたは処理中である )
				return;		// 例外を発出させない。

			if( !this.Disposed.t開始を宣言する() )
				return;		// 例外を発出させない。

			this.Dispose( true );
			GC.SuppressFinalize( this );
		}
		//~Song() { this.Dispose( false ); }		Unmanaged がないのでファイナライザは無し
		protected void Dispose( bool bReleaseManaged )
		{
			lock( this )
			{
				if( bReleaseManaged )
				{
					// TODO: ここで Managed を解放する

					#region [ プロパティ ]
					//-------------------------
					// 特になし
					//-------------------------
					#endregion
					#region [ サムネイル ]
					//-----------------
					if( null != this._bサムネイルTexture作成完了 )
					{
						this._bサムネイルTexture作成完了.Dispose();
						this._bサムネイルTexture作成完了 = null;
					}
					if( null != this._txサムネイル画像 )
					{
						this._txサムネイル画像.Dispose();
						this._txサムネイル画像 = null;
					}
					if( null != this._bサムネイルBitmap作成完了 )
					{
						this._bサムネイルBitmap作成完了.Dispose();
						this._bサムネイルBitmap作成完了 = null;
					}
					if( null != this._bmpサムネイル画像 )
					{
						this._bmpサムネイル画像.Dispose();
						this._bmpサムネイル画像 = null;
					}
					//-----------------
					#endregion
					#region [ スコア ]
					//-----------------
					if( null != this._listチップ )
					{
						this._listチップ.Clear();
						this._listチップ = null;
					}

					if( null != this._list小節長倍率 )
					{
						this._list小節長倍率.Clear();
						this._list小節長倍率 = null;
					}

					if( null != this._ms背景動画 )
					{
						this._ms背景動画.Dispose();
						this._ms背景動画 = null;
					}

					if( null != this._sdBGM )
					{
						this._sdBGM.Dispose();
						this._sdBGM = null;
					}

					if( null != this._dicメモ )
					{
						this._dicメモ.Clear();
						this._dicメモ = null;
					}
					//-----------------
					#endregion
				}

				// TODO: ここで Unmanaged を解放する
			}
		}
		//-------------------------
		#endregion

		#region [ プロパティ ]
		//-------------------------
		/// <summary>
		/// <para>1ms あたりの pixel 数。</para>
		/// <para>BPM 150 のとき、1小節が 234 pixel になるように調整。</para>
		/// <para>→ 60秒で150拍のとき、1小節(4拍)が 234 pixel。</para>
		/// <para>→ 60秒の間に、150[拍]÷4[拍]＝37.5[小節]。</para>
		/// <para>→ 60秒の間に、37.5[小節]×234[pixel/小節]＝ 8775[pixel]。</para>
		/// <para>→ 1ms の間に、8775[pixel]÷60000[ms]＝0.14625[pixel/ms]。割り切れて良かった。</para>
		/// </summary>
		public const double db基準譜面速度pxms = 0.14625 * 1.25;	// "* 1.25" は「x1.0はもう少し速くてもいいんではないか？」という感覚的な調整分。

		public string stg曲名
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._stg曲名;
				}
			}
			set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._stg曲名 = value;
				}
			}
		}
		public string stg曲ファイルマクロ無しパス
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._stg曲ファイルマクロ無しパス;
				}
			}
			set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._stg曲ファイルマクロ無しパス = value;
				}
			}
		}
		public string stgサムネイルファイルマクロ無しパス
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._stgサムネイルファイルマクロ無しパス;
				}
			}
			set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._stgサムネイルファイルマクロ無しパス = value;
				}
			}
		}

		private string _stg曲名;
		private string _stg曲ファイルマクロ無しパス;
		private string _stgサムネイルファイルマクロ無しパス;
		//-------------------------
		#endregion
		#region [ サムネイル ]
		//-----------------
		/// <summary>
		/// true の時のみ、bmpサムネイル画像 が利用できる（ただし、bmpサムネイル画像 == null かも知れない）。
		/// </summary>
		public CAtom<bool> bサムネイルBitmap作成完了
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._bサムネイルBitmap作成完了;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._bサムネイルBitmap作成完了 = value;
				}
			}
		}
		public Bitmap bmpサムネイル画像
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._bmpサムネイル画像;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._bmpサムネイル画像 = value;
				}
			}
		}

		/// <summary>
		/// true の時のみ、txサムネイル画像 が利用できる（ただし、txサムネイル画像 == null かも知れない）。
		/// </summary>
		public CAtom<bool> bサムネイルTexture作成完了
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._bサムネイルTexture作成完了;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._bサムネイルTexture作成完了 = value;
				}
			}
		}
		public CTexture txサムネイル画像
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._txサムネイル画像;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._txサムネイル画像 = value;
				}
			}
		}
		
		/// <summary>
		/// 別スレッドで実行可能。
		/// </summary>
		public void tサムネイルのBitmapを作成する()
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );

				if( null != this._bmpサムネイル画像 )
					this._bmpサムネイル画像.Dispose();

				this._bmpサムネイル画像 = null;

				if( File.Exists( this._stgサムネイルファイルマクロ無しパス ) )
					this._bmpサムネイル画像 = new Bitmap( this._stgサムネイルファイルマクロ無しパス );

				this._bサムネイルBitmap作成完了.Set( true );
			}
		}

		/// <summary>
		/// デバイススレッドでのみ実行可能。
		/// </summary>
		public void tサムネイルのTextureを作成する( IntPtr hLockedDevice )
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );

				if( !this._bサムネイルBitmap作成完了.Get() )
					return;		// Bitmap 未完成

				if( null != this._txサムネイル画像 )
					this._txサムネイル画像.Dispose();

				this._txサムネイル画像 = null;

				if( null != this._bmpサムネイル画像 )
					this._txサムネイル画像 = new CTexture( hLockedDevice, this._bmpサムネイル画像 );

				this._bサムネイルTexture作成完了.Set( true );
			}
		}

		private CAtom<bool> _bサムネイルBitmap作成完了;
		private Bitmap _bmpサムネイル画像;
		private CAtom<bool> _bサムネイルTexture作成完了;
		private CTexture _txサムネイル画像;
		//-----------------
		#endregion
		#region [ スコア ]
		//-----------------
		public List<Cチップ> listチップ
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._listチップ;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._listチップ = value;
				}
			}
		}
		public List<double> list小節長倍率
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._list小節長倍率;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._list小節長倍率 = value;
				}
			}
		}
		public CMediaSession ms背景動画		// 描画前＆描画スレッドで生成すること。
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._ms背景動画;
				}
			}
			set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._ms背景動画 = value;
				}
			}
		}
		public int n作成時のWASAPIバッファ長bytes
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._n作成時のWASAPIバッファ長bytes;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._n作成時のWASAPIバッファ長bytes = value;
				}
			}
		}
		public int n作成時のWASAPIチャンネル数
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._n作成時のWASAPIチャンネル数;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._n作成時のWASAPIチャンネル数 = value;
				}
			}
		}
		public int n作成時のWASAPI周波数Hz
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._n作成時のWASAPI周波数Hz;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._n作成時のWASAPI周波数Hz = value;
				}
			}
		}
		public int n作成時のWASAPIサンプリングバイト数 
		{
			get
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
				return this._n作成時のWASAPIサンプリングバイト数;
			}
			private set
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
				this._n作成時のWASAPIサンプリングバイト数 = value;
			}
		}
		public int n最大小節番号
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );

					int n小節番号 = 0;

					foreach( Cチップ chip in this._listチップ )
					{
						if( n小節番号 < chip.n小節番号 )
							n小節番号 = chip.n小節番号;
					}

					return n小節番号;
				}
			}
		}
		public string stg背景動画ファイルパス
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._stg背景動画ファイルパス;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._stg背景動画ファイルパス = value;
				}
			}
		}
		public CSound sdBGM
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._sdBGM;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._sdBGM = value;
				}
			}
		}
		public Dictionary<int, string> dicメモ	// SSTFEditorで使用。
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._dicメモ;
				}
			}
			set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._dicメモ = value;
				}
			}
		}
		public Cチップ chip背景動画
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					return this._chip背景動画;
				}
			}
			private set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );
					this._chip背景動画 = value;
				}
			}
		}

		public void tスコアを読み込む( string stgスコアファイルパス, Options options = null )
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );

				#region [ 初期化 ]
				//-----------------
				this._listチップ = new List<Cチップ>();
				this._list小節長倍率 = new List<double>();
				this._ms背景動画 = null;
				this._n作成時のWASAPIバッファ長bytes = 0;
				this._n作成時のWASAPIチャンネル数 = 2;
				this._n作成時のWASAPI周波数Hz = 44100;
				this._n作成時のWASAPIサンプリングバイト数 = 2;
				this._stg背景動画ファイルパス = null;
				this._sdBGM = null;
				this._dicメモ = new Dictionary<int, string>();
				//-----------------
				#endregion

				#region [ 曲データファイルの読み込み ]
				//-----------------
				var sr = new StreamReader( stgスコアファイルパス, Encoding.UTF8 );

				try
				{
					int n行番号 = 0;
					int n現在の小節番号 = 0;
					int n現在の小節解像度 = 384;
					Eチップ種別 e現在のチップ = Eチップ種別.Unknown;

					while( !sr.EndOfStream )
					{
						// 1行読み込む。

						string stg行 = sr.ReadLine();
						n行番号++;


						// 前処理。

						#region [ 改行は ';' に、TABは空白文字にそれぞれ変換し、先頭末尾の空白を削除する。]
						//-----------------
						stg行 = stg行.Replace( Environment.NewLine, ";" );
						stg行 = stg行.Replace( '\t', ' ' );
						stg行 = stg行.Trim();
						//-----------------
						#endregion

						#region [ 行中の '#' 以降はコメントとして除外する。また、コメントだけの行はスキップする。]
						//-----------------
						int n区切り位置 = 0;

						for( n区切り位置 = 0; n区切り位置 < stg行.Length; n区切り位置++ )
						{
							if( stg行[ n区切り位置 ] == '#' )
								break;
						}

						if( n区切り位置 == 0 )
							continue;		// コメントだけの行はスキップ。


						if( n区切り位置 < stg行.Length )
						{
							// 先頭から # の直前までを切り取る。

							stg行 = stg行.Substring( 0, n区切り位置 - 1 );
							stg行 = stg行.Trim();
						}
						//-----------------
						#endregion

						#region [ 空行ならこの行はスキップする。]
						//-----------------
						if( stg行.Length == 0 )
							continue;
						//-----------------
						#endregion


						// ヘッダコマンド処理。

						#region [ ヘッダコマンドの処理を行う。]
						//-----------------
						if( stg行.StartsWith( "Title", StringComparison.OrdinalIgnoreCase ) )
						{
							#region [ Title コマンド ]
							//-----------------
							string[] items = stg行.Split( '=' );

							if( items.Length != 2 )
							{
								Trace.TraceError( string.Format( "Title の書式が不正です。'=' が複数記述されています。スキップします。[{0}行目]", n行番号 ) );
								continue;
							}

							this.stg曲名 = items[ 1 ].Trim();
							//-----------------
							#endregion

							continue;
						}
						else if( stg行.StartsWith( "WASAPIBufferLength", StringComparison.OrdinalIgnoreCase ) )
						{
							#region [ WASAPIBufferLength コマンド ]
							//-----------------
							string[] items = stg行.Split( '=' );

							if( items.Length != 2 )
							{
								Trace.TraceError( string.Format( "WASAPIBufferLength の書式が不正です。'=' が複数記述されています。スキップします。[{0}行目]", n行番号 ) );
								continue;
							}

							int n = 0;
							if( int.TryParse( items[ 1 ].Trim(), out n ) )
							{
								if( n <= 0 )
								{
									Trace.TraceError( string.Format( "WASAPIBufferLength の値が 0 または負数です。スキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								this.n作成時のWASAPIバッファ長bytes = n;
							}
							//-----------------
							#endregion

							continue;
						}
						else if( stg行.StartsWith( "WASAPIChannel", StringComparison.OrdinalIgnoreCase ) )
						{
							#region [ WASAPIChannel コマンド ]
							//-----------------
							string[] items = stg行.Split( '=' );

							if( items.Length != 2 )
							{
								Trace.TraceError( string.Format( "WASAPIChannel の書式が不正です。'=' が複数記述されています。スキップします。[{0}行目]", n行番号 ) );
								continue;
							}

							int n = 0;
							if( int.TryParse( items[ 1 ].Trim(), out n ) )
							{
								if( n <= 0 )
								{
									Trace.TraceError( string.Format( "WASAPIChannel の値が 0 または負数です。スキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								this.n作成時のWASAPIチャンネル数 = n;
							}
							//-----------------
							#endregion

							continue;
						}
						else if( stg行.StartsWith( "WASAPISamplesPerSecond", StringComparison.OrdinalIgnoreCase ) )
						{
							#region [ WASAPISamplesPerSecond コマンド ]
							//-----------------
							string[] items = stg行.Split( '=' );

							if( items.Length != 2 )
							{
								Trace.TraceError( string.Format( "WASAPISamplesPerSecond の書式が不正です。'=' が複数記述されています。スキップします。[{0}行目]", n行番号 ) );
								continue;
							}

							int n = 0;
							if( int.TryParse( items[ 1 ].Trim(), out n ) )
							{
								if( n <= 0 )
								{
									Trace.TraceError( string.Format( "WASAPISamplesPerSecond の値が 0 または負数です。スキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								this.n作成時のWASAPI周波数Hz = n;
							}
							//-----------------
							#endregion

							continue;
						}
						else if( stg行.StartsWith( "WASAPIBytesPerSample", StringComparison.OrdinalIgnoreCase ) )
						{
							#region [ WASAPIBytesPerSample コマンド ]
							//-----------------
							string[] items = stg行.Split( '=' );

							if( items.Length != 2 )
							{
								Trace.TraceError( string.Format( "WASAPIBytesPerSample の書式が不正です。'=' が複数記述されています。スキップします。[{0}行目]", n行番号 ) );
								continue;
							}

							int n = 0;
							if( int.TryParse( items[ 1 ].Trim(), out n ) )
							{
								if( n <= 0 )
								{
									Trace.TraceError( string.Format( "WASAPIBytesPerSample の値が 0 または負数です。スキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								this.n作成時のWASAPIサンプリングバイト数 = n;
							}
							//-----------------
							#endregion

							continue;
						}
						//-----------------
						#endregion


						// メモ（小節単位）処理。

						#region [ メモ（小節単位）の処理を行う。]
						//-----------------
						else if( stg行.StartsWith( "PartMemo", StringComparison.OrdinalIgnoreCase ) )
						{
							#region [ '=' 以前を除去する。]
							//-----------------
							int n等号位置 = stg行.IndexOf( '=' );

							if( n等号位置 <= 0 )
							{
								Trace.TraceError( string.Format( "PartMemo の書式が不正です。'=' が存在しません。スキップします。[{0}]行目]", n行番号 ) );
								continue;
							}

							stg行 = stg行.Substring( n等号位置 + 1 ).Trim();

							if( stg行.Length == 0 )
							{
								Trace.TraceError( string.Format( "PartMemo の書式が不正です。値が指定されていません。スキップします。[{0}]行目]", n行番号 ) );
								continue;
							}
							//-----------------
							#endregion

							#region [ カンマ位置を取得。]
							//-----------------
							int nカンマ位置 = stg行.IndexOf( ',' );

							if( nカンマ位置 <= 0 )
							{
								Trace.TraceError( string.Format( "PartMemo の書式が不正です。',' が存在しません。スキップします。[{0}]行目]", n行番号 ) );
								continue;
							}
							//-----------------
							#endregion

							#region [ 小節番号を取得。]
							//-----------------
							string stg小節番号 = stg行.Substring( 0, nカンマ位置 );

							int n小節番号;

							if( !int.TryParse( stg小節番号, out n小節番号 ) || n小節番号 < 0 )
							{
								Trace.TraceError( string.Format( "PartMemo の小節番号の取得に失敗しました。スキップします。[{0}]行目]", n行番号 ) );
								continue;
							}
							//-----------------
							#endregion

							#region [ メモを取得。]
							//-----------------
							string stgメモ = stg行.Substring( nカンマ位置 + 1 );
							stgメモ = stgメモ.Replace( @"\n", Environment.NewLine );	// "\n" は改行に復号！
							//-----------------
							#endregion

							#region [ メモが空文字列でないなら dicメモ に登録すると同時に、チップとしても追加する。]
							//-----------------
							if( !string.IsNullOrEmpty( stgメモ ) )
							{
								this.dicメモ.Add( n小節番号, stgメモ );

								this.listチップ.Add(
									new Cチップ() {
										eチップ種別 = Eチップ種別.小節メモ,
										n小節番号 = n小節番号,
										n小節内位置 = 0,
										n小節解像度 = 1,
									} );
							}
							//-----------------
							#endregion

							continue;
						}
						//-----------------
						#endregion


						// 上記行頭コマンド以外は、チップ記述行だと見なす。

						#region [ チップ記述コマンドの処理を行う。]
						//-----------------

						// 行を区切り文字でトークンに分割。

						string[] tokens = stg行.Split( new char[] { ';', ':' } );


						// すべてのトークンについて……

						foreach( string token in tokens )
						{
							// トークンを分割。

							#region [ トークンを区切り文字 '=' で strコマンド と strパラメータ に分割し、それぞれの先頭末尾の空白を削除する。]
							//-----------------
							string[] items = token.Split( '=' );

							if( items.Length != 2 )
							{
								if( token.Trim().Length == 0 )	// 空文字列は不正ではない。（行末など）
									continue;

								Trace.TraceError( string.Format( "コマンドとパラメータの記述書式が不正です。このコマンドをスキップします。[{0}行目]", n行番号 ) );
								continue;
							}

							string stgコマンド = items[ 0 ].Trim();
							string stgパラメータ = items[ 1 ].Trim();
							//-----------------
							#endregion


							// コマンド別に処理。

							if( stgコマンド.Equals( "Part", StringComparison.OrdinalIgnoreCase ) )
							{
								#region [ Part（小節番号指定）コマンド ]
								//-----------------
								string stg小節番号 = this.t指定された文字列の先頭から数字文字列を取り出す( ref stgパラメータ );

								if( string.IsNullOrEmpty( stg小節番号 ) )
								{
									Trace.TraceError( string.Format( "Part（小節番号）コマンドに小節番号の記述がありません。このコマンドをスキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								int n小節番号 = 0;

								if( !int.TryParse( stg小節番号, out n小節番号 ) )
								{
									Trace.TraceError( string.Format( "Part（小節番号）コマンドの小節番号が不正です。このコマンドをスキップします。[{0}行目]", n行番号 ) );
									continue;
								}
								if( n小節番号 < 0 )
								{
									Trace.TraceError( string.Format( "Part（小節番号）コマンドの小節番号が負数です。このコマンドをスキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								n現在の小節番号 = n小節番号;
								//-----------------
								#endregion
								#region [ Part 属性があれば取得する。]
								//-----------------
								while( stgパラメータ.Length > 0 )
								{
									// 属性ID を取得。

									char c属性ID = char.ToLower( stgパラメータ[ 0 ] );


									// Part 属性があれば取得する。

									if( c属性ID == 's' )
									{
										#region [ 小節長倍率(>0) → list小節長倍率 ]
										//-----------------
										stgパラメータ = stgパラメータ.Substring( 1 ).Trim();

										string stg小節長倍率 = this.t指定された文字列の先頭から数字文字列を取り出す( ref stgパラメータ );
										stgパラメータ = stgパラメータ.Trim();

										if( string.IsNullOrEmpty( stg小節長倍率 ) )
										{
											Trace.TraceError( string.Format( "Part（小節番号）コマンドに小節長倍率の記述がありません。この属性をスキップします。[{0}行目]", n行番号 ) );
											continue;
										}

										double db小節長倍率 = 1.0;

										if( !double.TryParse( stg小節長倍率, out db小節長倍率 ) )
										{
											Trace.TraceError( string.Format( "Part（小節番号）コマンドの小節長倍率が不正です。この属性をスキップします。[{0}行目]", n行番号 ) );
											continue;
										}

										if( db小節長倍率 <= 0.0 )
										{
											Trace.TraceError( string.Format( "Part（小節番号）コマンドの小節長倍率が 0.0 または負数です。この属性をスキップします。[{0}行目]", n行番号 ) );
											continue;
										}


										// 小節長倍率辞書に追加 or 上書き更新。

										this.t小節長倍率を設定する( n現在の小節番号, db小節長倍率 );
										//-----------------
										#endregion

										continue;
									}
								}
								//-----------------
								#endregion

								continue;
							}
							if( stgコマンド.Equals( "Lane", StringComparison.OrdinalIgnoreCase ) )
							{
								#region [ Lane（レーン指定）コマンド（ e現在のチップ の仮決定）]
								//-----------------
								if( stgパラメータ.Equals( "LeftCrash", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.LeftCrash;

								else if( stgパラメータ.Equals( "Ride", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Ride;

								else if( stgパラメータ.Equals( "China", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.China;

								else if( stgパラメータ.Equals( "Splash", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Splash;

								else if( stgパラメータ.Equals( "HiHat", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.HiHatClose;

								else if( stgパラメータ.Equals( "Snare", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Snare;

								else if( stgパラメータ.Equals( "Bass", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Bass;

								else if( stgパラメータ.Equals( "Tom1", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Tom1;

								else if( stgパラメータ.Equals( "Tom2", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Tom2;

								else if( stgパラメータ.Equals( "Tom3", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.Tom3;

								else if( stgパラメータ.Equals( "RightCrash", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.RightCrash;

								else if( stgパラメータ.Equals( "BPM", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.BPM;

								else if( stgパラメータ.Equals( "Song", StringComparison.OrdinalIgnoreCase ) )
									e現在のチップ = Eチップ種別.背景動画;
								else
									Trace.TraceError( string.Format( "Lane（レーン指定）コマンドのパラメータ記述 '{1}' が不正です。このコマンドをスキップします。[{0}行目]", n行番号, stgパラメータ ) );
								//-----------------
								#endregion

								continue;
							}
							if( stgコマンド.Equals( "Resolution", StringComparison.OrdinalIgnoreCase ) )
							{
								#region [ Resolution（小節解像度指定）コマンド ]
								//-----------------
								int n解像度 = 1;

								if( !int.TryParse( stgパラメータ, out n解像度 ) )
								{
									Trace.TraceError( string.Format( "Resolution（小節解像度指定）コマンドの解像度が不正です。このコマンドをスキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								if( n解像度 < 1 )
								{
									Trace.TraceError( string.Format( "Resolution（小節解像度指定）コマンドの解像度は 1 以上でなければなりません。このコマンドをスキップします。[{0}行目]", n行番号 ) );
									continue;
								}

								n現在の小節解像度 = n解像度;
								//-----------------
								#endregion

								continue;
							}
							if( stgコマンド.Equals( "Chips", StringComparison.OrdinalIgnoreCase ) )
							{
								#region [ Chips（チップ指定）コマンド ]
								//-----------------

								// パラメータを区切り文字 ',' でチップトークンに分割。

								string[] chipTokens = stgパラメータ.Split( ',' );


								// すべてのチップトークンについて……

								for( int i = 0; i < chipTokens.Length; i++ )
								{
									chipTokens[ i ].Trim();

									#region [ 空文字はスキップ。]
									//-----------------
									if( chipTokens[ i ].Length == 0 )
										continue;
									//-----------------
									#endregion

									#region [ チップを生成。]
									//-----------------
									var chip = new Cチップ() {
										n小節番号 = n現在の小節番号,
										eチップ種別 = e現在のチップ,			// Lane コマンドで指定されている。
										n小節解像度 = n現在の小節解像度,
										n音量 = 4,
									};

									if( chip.eチップ種別 == Eチップ種別.China ) chip.stgチップ内文字列 = "C N";
									if( chip.eチップ種別 == Eチップ種別.Splash ) chip.stgチップ内文字列 = "S P";
									//-----------------
									#endregion

									#region [ チップ位置の取得。]
									//-----------------
									int nチップ位置 = 0;

									string stg位置番号 = this.t指定された文字列の先頭から数字文字列を取り出す( ref chipTokens[ i ] );
									chipTokens[ i ].Trim();

									// 文法チェック。
									if( string.IsNullOrEmpty( stg位置番号 ) )
									{
										Trace.TraceError( string.Format( "チップの位置指定の記述がありません。このチップをスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1 ) );
										continue;
									}

									// 位置を取得。
									if( !int.TryParse( stg位置番号, out nチップ位置 ) )
									{
										Trace.TraceError( string.Format( "チップの位置指定の記述が不正です。このチップをスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1 ) );
										continue;
									}

									// 値域チェック。
									if( nチップ位置 < 0 || nチップ位置 >= n現在の小節解像度 )
									{
										Trace.TraceError( string.Format( "チップの位置が負数であるか解像度(Resolution)以上の値になっています。このチップをスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1 ) );
										continue;
									}

									// チップ位置格納。
									chip.n小節内位置 = nチップ位置;
									//-----------------
									#endregion

									#region [ 共通属性・レーン別属性があれば取得する。]
									//-----------------
									while( chipTokens[ i ].Length > 0 )
									{
										// 属性ID を取得。

										char c属性ID = char.ToLower( chipTokens[ i ][ 0 ] );


										// 共通属性があれば取得。

										if( c属性ID == 'v' )
										{
											#region [ 音量(1～4) ]
											//-----------------
											chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();

											string stg音量 = this.t指定された文字列の先頭から数字文字列を取り出す( ref chipTokens[ i ] );
											chipTokens[ i ].Trim();

											int nチップ音量 = 0;

											// 文法チェック。

											if( string.IsNullOrEmpty( stg音量 ) )
											{
												Trace.TraceError( string.Format( "チップの音量指定の記述がありません。この属性をスキップします。[{0}行目l {1}個目のチップ]", n行番号, i + 1 ) );
												continue;
											}

											// チップ音量の取得。

											if( !int.TryParse( stg音量, out nチップ音量 ) )
											{
												Trace.TraceError( string.Format( "チップの音量指定の記述が不正です。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1 ) );
												continue;
											}

											// 値域チェック。

											if( nチップ音量 < 1 || nチップ音量 > 4 )
											{
												Trace.TraceError( string.Format( "チップの音量が適正範囲（1～4）を超えています。このチップをスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1 ) );
												continue;
											}

											// 音量を格納。

											chip.n音量 = nチップ音量;
											//-----------------
											#endregion

											continue;
										}


										// レーン別属性があれば取得。

										switch( e現在のチップ )
										{
											#region [ case LeftCymbal ]
											//-----------------
											case Eチップ種別.LeftCrash:

												#region [ 未知の属性 ]
												//-----------------
												chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
												Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
												//-----------------
												#endregion

												continue;

											//-----------------
											#endregion
											#region [ case Ride ]
											//-----------------
											case Eチップ種別.Ride:
											case Eチップ種別.RideCup:

												if( c属性ID == 'c' )
												{
													#region [ Ride.カップ ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.RideCup;
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case China ]
											//-----------------
											case Eチップ種別.China:

												#region [ 未知の属性 ]
												//-----------------
												chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
												Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
												//-----------------
												#endregion

												continue;

											//-----------------
											#endregion
											#region [ case Splash ]
											//-----------------
											case Eチップ種別.Splash:

												#region [ 未知の属性 ]
												//-----------------
												chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
												Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
												//-----------------
												#endregion

												continue;

											//-----------------
											#endregion
											#region [ case HiHat ]
											//-----------------
											case Eチップ種別.HiHatClose:
											case Eチップ種別.HiHatHalfOpen:
											case Eチップ種別.HiHatOpen:
											case Eチップ種別.FootPedal:

												if( c属性ID == 'o' )
												{
													#region [ HiHat.オープン ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.HiHatOpen;
													//-----------------
													#endregion
												}
												else if( c属性ID == 'h' )
												{
													#region [ HiHat.ハーフオープン ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.HiHatHalfOpen;
													//-----------------
													#endregion
												}
												else if( c属性ID == 'c' )
												{
													#region [ HiHat.クローズ ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.HiHatClose;
													//-----------------
													#endregion
												}
												else if( c属性ID == 'f' )
												{
													#region [ HiHat.フットスプラッシュ ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.FootPedal;
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case Snare ]
											//-----------------
											case Eチップ種別.Snare:
											case Eチップ種別.SnareClosedRim:
											case Eチップ種別.SnareOpenRim:
											case Eチップ種別.SnareGhost:

												if( c属性ID == 'o' )
												{
													#region [ Snare.オープンリム ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.SnareOpenRim;
													//-----------------
													#endregion
												}
												else if( c属性ID == 'c' )
												{
													#region [ Snare.クローズドリム ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.SnareClosedRim;
													//-----------------
													#endregion
												}
												else if( c属性ID == 'g' )
												{
													#region [ Snare.ゴースト ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.SnareGhost;
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case Bass ]
											//-----------------
											case Eチップ種別.Bass:

												#region [ 未知の属性 ]
												//-----------------
												chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
												Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
												//-----------------
												#endregion

												continue;

											//-----------------
											#endregion
											#region [ case Tom1 ]
											//-----------------
											case Eチップ種別.Tom1:
											case Eチップ種別.Tom1Rim:

												if( c属性ID == 'r' )
												{
													#region [ Tom1.リム ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.Tom1Rim;
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case Tom2 ]
											//-----------------
											case Eチップ種別.Tom2:
											case Eチップ種別.Tom2Rim:

												if( c属性ID == 'r' )
												{
													#region [ Tom2.リム ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.Tom2Rim;
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case Tom3 ]
											//-----------------
											case Eチップ種別.Tom3:
											case Eチップ種別.Tom3Rim:

												if( c属性ID == 'r' )
												{
													#region [ Tom3.リム ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													chip.eチップ種別 = Eチップ種別.Tom3Rim;
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case RightCymbal ]
											//-----------------
											case Eチップ種別.RightCrash:

												#region [ 未知の属性 ]
												//-----------------
												chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
												Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
												//-----------------
												#endregion

												continue;

											//-----------------
											#endregion
											#region [ case BPM ]
											//-----------------
											case Eチップ種別.BPM:

												if( c属性ID == 'b' )
												{
													#region [ BPM値 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();

													string stgBPM = this.t指定された文字列の先頭から数字文字列を取り出す( ref chipTokens[ i ] );
													chipTokens[ i ].Trim();

													if( string.IsNullOrEmpty( stgBPM ) )
													{
														Trace.TraceError( string.Format( "BPM数値の記述がありません。この属性をスキップします。[{0}行目l {1}個目のチップ]", n行番号, i + 1 ) );
														continue;
													}

													double dbBPM = 0.0;

													if( !double.TryParse( stgBPM, out dbBPM ) || dbBPM <= 0.0 )
													{
														Trace.TraceError( string.Format( "BPM数値の記述が不正です。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1 ) );
														continue;
													}

													chip.dbBPM = dbBPM;
													chip.stgチップ内文字列 = dbBPM.ToString( "###.##" );
													//-----------------
													#endregion
												}
												else
												{
													#region [ 未知の属性 ]
													//-----------------
													chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
													Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
													//-----------------
													#endregion
												}
												continue;

											//-----------------
											#endregion
											#region [ case Song ]
											//-----------------
											case Eチップ種別.背景動画:

												#region [ 未知の属性 ]
												//-----------------
												chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
												Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
												//-----------------
												#endregion

												continue;

											//-----------------
											#endregion
										}

										#region [ 未知の属性 ]
										//-----------------
										chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
										Trace.TraceError( string.Format( "未対応の属性「{2}」が指定されています。この属性をスキップします。[{0}行目; {1}個目のチップ]", n行番号, i + 1, c属性ID ) );
										//-----------------
										#endregion
									}
									//-----------------
									#endregion

									#region [ 初めて登場した背景動画チップなら chip背景動画 に格納する。 ]
									//-----------------
									if( chip.eチップ種別 == Eチップ種別.背景動画 && this.chip背景動画 == null )
										this.chip背景動画 = chip;
									//-----------------
									#endregion


									// チップをリストに追加。

									this.listチップ.Add( chip );
								}
								//-----------------
								#endregion

								continue;
							}

							Trace.TraceError( string.Format( "不正なコマンド「{0}」が存在します。[{1}行目]", stgコマンド, n行番号 ) );
						}
						//-----------------
						#endregion
					}
				}
				finally
				{
					sr.Close();
				}
				//-----------------
				#endregion

				#region [ チップ種別が Ride, RideCup, China, Splash のものは、Options に従って Left～ か Right～ に置換する。]
				//-------------------------
				if( options != null )	// オプションが指定されてるときだけ。
				{
					foreach( var chip in this.listチップ )
					{
						switch( chip.eチップ種別 )
						{
							case Eチップ種別.Ride:
								chip.eチップ種別 = ( options.bライドが右にある ) ? Eチップ種別.RightRide : Eチップ種別.LeftRide;
								break;

							case Eチップ種別.RideCup:
								chip.eチップ種別 = ( options.bライドが右にある ) ? Eチップ種別.RightRideCup : Eチップ種別.LeftRideCup;
								break;

							case Eチップ種別.China:
								chip.eチップ種別 = ( options.bチャイナが右にある ) ? Eチップ種別.RightChina : Eチップ種別.LeftChina;
								break;

							case Eチップ種別.Splash:
								chip.eチップ種別 = ( options.bスプラッシュが右にある ) ? Eチップ種別.RightSplash : Eチップ種別.LeftSplash;
								break;
						}
					}
				}
				//-------------------------
				#endregion

				#region [ 拍線の追加。小節線を先に追加すると小節が１つ増えるので、先に拍線から追加する。]
				//-----------------
				int n最大小節番号 = this.n最大小節番号;		// 「this.n最大小節番号」はチップ数に依存してループ中に変化するので、for 文には組み込まない。

				for( int i = 0; i <= n最大小節番号; i++ )
				{
					double db小節長倍率 = this.t小節長倍率を取得する( i );

					for( int n = 1; n * 0.25 < db小節長倍率; n++ )
					{
						this.listチップ.Add(
							new Cチップ() {
								n小節番号 = i,
								eチップ種別 = Eチップ種別.拍線,
								n小節内位置 = (int) ( ( n * 0.25 ) * 100 ),
								n小節解像度 = (int) ( db小節長倍率 * 100 ),
							} );
					}
				}
				//-----------------
				#endregion

				#region [ 小節線の追加。]
				//-----------------
				n最大小節番号 = this.n最大小節番号;

				for( int i = 0; i <= n最大小節番号 + 1; i++ )
				{
					this.listチップ.Add(
						new Cチップ() {
							n小節番号 = i,
							eチップ種別 = Eチップ種別.小節線,
							n小節内位置 = 0,
							n小節解像度 = 1,
						} );
				}
				//-----------------
				#endregion

				this.listチップ.Sort();

				#region [ 全チップの発声/描画時刻と譜面内位置の計算 ]
				//-----------------

				// 1. BPMチップを無視し(初期BPMで固定)、dic小節長倍率, Cチップ.n小節解像度, Cチップ.n小節内位置 から両者を計算する。
				//    以下、listチップが小節番号順にソートされているという前提で。

				double dbチップが存在する小節の先頭時刻ms = 0.0;
				int n現在の小節の番号 = 0;

				foreach( Cチップ chip in this.listチップ )
				{
					#region [ チップの小節番号が現在の小節番号よりも大きい場合、チップが存在する小節に至るまで、「dbチップが存在する小節の先頭時刻ms」を更新する。]
					//-----------------
					while( n現在の小節の番号 < chip.n小節番号 )
					{
						double db現在の小節の小節長倍率 = this.t小節長倍率を取得する( n現在の小節の番号 );
						dbチップが存在する小節の先頭時刻ms += dbBPM初期値固定での1小節4拍の時間ms * db現在の小節の小節長倍率;

						n現在の小節の番号++;	// n現在の小節番号 が chip.n小節番号 に追いつくまでループする。
					}
					//-----------------
					#endregion
					#region [ チップの発声/描画時刻を求める。]
					//-----------------
					double dbチップが存在する小節の小節長倍率 = this.t小節長倍率を取得する( n現在の小節の番号 );

					chip.n発声時刻ms =
					chip.n描画時刻ms =
						(long) ( dbチップが存在する小節の先頭時刻ms + ( dbBPM初期値固定での1小節4拍の時間ms * dbチップが存在する小節の小節長倍率 * chip.n小節内位置 ) / chip.n小節解像度 );
					//-----------------
					#endregion
				}

				// 2. BPMチップを考慮しながら調整する。（譜面内位置grid はBPMの影響を受けないので無視）

				double db現在のBPM = Song.db初期BPM;

				int nチップ数 = this.listチップ.Count;

				for( int i = 0; i < nチップ数; i++ )
				{
					// BPM チップ以外は無視。

					var BPMチップ = this.listチップ[ i ];
					if( BPMチップ.eチップ種別 != Eチップ種別.BPM )
						continue;

					// BPMチップより後続の全チップの n発声/描画時刻ms を、新旧BPMの比率（加速率）で修正する。

					double db加速率 = BPMチップ.dbBPM / db現在のBPM;	// BPMチップ.dbBPM > 0.0 であることは読み込み時に保証済み。

					for( int j = i + 1; j < nチップ数; j++ )
					{
						this.listチップ[ j ].n発声時刻ms =
						this.listチップ[ j ].n描画時刻ms =
							(long) ( BPMチップ.n発声時刻ms + ( ( this.listチップ[ j ].n発声時刻ms - BPMチップ.n発声時刻ms ) / db加速率 ) );
					}

					db現在のBPM = BPMチップ.dbBPM;
				}
				//-----------------
				#endregion

				#region [ スコアファイルと同じ場所に背景動画ファイルが存在すれば、その動画からBGMを抽出する。
				//-----------------
				var path = Utils.t指定した拡張子を持つファイルを検索し最初に見つけたファイルの絶対パスを返す(
								Path.GetDirectoryName( stgスコアファイルパス ), new List<string>() { ".avi", ".mp4", ".flv", ".wmv", ".mpg", "mpeg" } );

				if( !string.IsNullOrEmpty( path ) )		// 動画ファイルが存在する。
				{
					Trace.TraceInformation( "動画ファイルを検出しました。({0})", Path.GetFileName( path ) );

					try
					{
						// 背景動画からBGMを作成する。

						var ms = new MemoryStream();
						C音声ストリーム抽出.tWAVイメージ抽出( path, out ms );
						this.sdBGM = Global.Sound.tサウンドを作成する( ms.ToArray() );
						this.stg背景動画ファイルパス = path;
						Trace.TraceInformation( "動画ファイルからBGMを抽出しました。" );
					}
					catch( Exception e )
					{
						// 作成失敗。

						Trace.TraceWarning( "動画ファイルからBGMを抽出することに失敗しました。" );
						Utils.t例外の詳細をログに出力する( e );
						this.sdBGM = null;
						this.stg背景動画ファイルパス = null;
					}
				}
				//-----------------
				#endregion
			}
		}

		private const double db初期BPM = 120.0;
		private const double dbBPM初期値固定での1小節4拍の時間ms = ( 60.0 * 1000 ) / ( Song.db初期BPM / 4.0 );

		// <summary>
		// <para>取出文字列の先頭にある数字（小数点も有効）の連続した部分を取り出して、戻り値として返す。</para>
		// <para>また、取出文字列から取り出した数字文字列部分を除去した文字列を再度格納する。</para>
		// </summary>
		private string t指定された文字列の先頭から数字文字列を取り出す( ref string str取出文字列 )
		{
			//this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );		private なのでノーチェック。

			int n桁数 = 0;

			while( ( n桁数 < str取出文字列.Length ) && ( char.IsDigit( str取出文字列[ n桁数 ] ) || str取出文字列[ n桁数 ] == '.' ) )
				n桁数++;

			if( n桁数 == 0 )
				return "";

			string str数字文字列 = str取出文字列.Substring( 0, n桁数 );
			str取出文字列 = ( n桁数 == str取出文字列.Length ) ? "" : str取出文字列.Substring( n桁数 );

			return str数字文字列;
		}
		private void t小節長倍率を設定する( int n小節番号, double db倍率 )
		{
			//this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );		private なのでノーチェック。

			// list小節長倍率 が短ければ増設する。

			if( n小節番号 >= this._list小節長倍率.Count )
			{
				int n不足数 = n小節番号 - this._list小節長倍率.Count + 1;

				for( int i = 0; i < n不足数; i++ )
					this._list小節長倍率.Add( 1.0 );
			}

			// 倍率を登録する。

			this._list小節長倍率[ n小節番号 ] = db倍率;
		}
		private double t小節長倍率を取得する( int n小節番号 )
		{
			//this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );		private なのでノーチェック。
			
			// list小節長倍率 が短ければ増設する。

			if( n小節番号 >= this._list小節長倍率.Count )
			{
				int n不足数 = n小節番号 - this._list小節長倍率.Count + 1;
				for( int i = 0; i < n不足数; i++ )
					this._list小節長倍率.Add( 1.0 );
			}


			// 倍率を返す。

			return this._list小節長倍率[ n小節番号 ];
		}

		private List<Cチップ> _listチップ;
		private List<double> _list小節長倍率;
		private CMediaSession _ms背景動画;
		private int _n作成時のWASAPIバッファ長bytes;
		private int _n作成時のWASAPIチャンネル数;
		private int _n作成時のWASAPI周波数Hz;
		private int _n作成時のWASAPIサンプリングバイト数;
		private string _stg背景動画ファイルパス;
		private CSound _sdBGM;
		private Dictionary<int, string> _dicメモ;
		private Cチップ _chip背景動画;
		//-----------------
		#endregion

		public void Load( XmlTextReader reader )
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );

				if( ( XmlNodeType.Element != reader.NodeType ) || !reader.Name.Equals( "Song" ) || reader.IsEmptyElement )
					return;

				while( reader.Read() )
				{
					if( ( XmlNodeType.EndElement == reader.NodeType ) && reader.Name.Equals( "Song" ) )
						break;	// </Song>

					if( ( XmlNodeType.Element == reader.NodeType ) && !reader.IsEmptyElement )
					{
						switch( reader.Name )
						{
							case "曲名":
								Global.tXMLのテキスト要素を読み込む( reader, ( text ) => {
									this._stg曲名 = text;
								} );
								break;

							case "曲ファイルパス":
								Global.tXMLのテキスト要素を読み込む( reader, ( text ) => {
									this._stg曲ファイルマクロ無しパス = Global.Folders.tファイルパス内のマクロを展開する( text );
								} );
								break;

							case "サムネイルファイルパス":
								Global.tXMLのテキスト要素を読み込む( reader, ( text ) => {
									this._stgサムネイルファイルマクロ無しパス = Global.Folders.tファイルパス内のマクロを展開する( text );
									this._txサムネイル画像 = null;		// これらは初めて表示されるときまで作成しない
									this._bmpサムネイル画像 = null;		// 
								} );
								break;
						}
					}
				}
			}
		}
		public void Save( XmlTextWriter writer )
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Song" );

				// スコアファイルではなく、スコア管理用の XML の出力なので listチップ などは出力しない。

				writer.WriteStartElement( null, "Song", null );							// <Song>

				writer.WriteElementString( "曲名", null, this._stg曲名 );				//   <曲名>...</曲名>

				writer.WriteElementString( "曲ファイルパス", null,						//   <曲ファイルパス>...</曲ファイルパス>
					Global.Folders.tファイルパスをマクロ付きパスに変換する( this._stg曲ファイルマクロ無しパス ) );

				writer.WriteElementString( "サムネイルファイルパス", null,				//   <サムネイルファイルパス>...</サムネイルファイルパス>
					Global.Folders.tファイルパスをマクロ付きパスに変換する( this._stgサムネイルファイルマクロ無しパス ) );

				writer.WriteEndElement();												// </Song>
			}
		}

		private readonly CDisposed Disposed = new CDisposed();
	}


	// Songs クラスの定義

	class Songs : List<Song>, IDisposable
	{
		public Songs()
		{
			this._SelectedSongIndex = -1;
		}

		public void Load( XmlTextReader reader )
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Songs" );

				if( ( XmlNodeType.Element != reader.NodeType ) || !reader.Name.Equals( "Songs" ) || reader.IsEmptyElement )
					return;

				while( reader.Read() )
				{
					if( ( XmlNodeType.EndElement == reader.NodeType ) && reader.Name.Equals( "Songs" ) )
						break;	// </Songs>

					if( ( XmlNodeType.Element == reader.NodeType ) && reader.Name.Equals( "Song" ) && !reader.IsEmptyElement )
					{
						var song = new Song();
						song.Load( reader );
						this.Add( song );
					}
				}
			}
		}
		public void Save( XmlTextWriter writer )
		{
			lock( this )
			{
				this.Disposed.tDispose済みまたは実行中なら例外発生( "Songs" );

				writer.WriteStartElement( null, "Songs", null );		// <Songs>

				foreach( var song in this )								//   <Song> ...
					song.Save( writer );								//   </Song>

				writer.WriteEndElement();								// </Songs>
			}
		}

		public int SelectedSongIndex	// これを外部から修正すると
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Songs" );
					return this._SelectedSongIndex;
				}
			}
			set
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Songs" );
					this._SelectedSongIndex = value;
				}
			}
		}
		public Song SelectedSong		// これから Song を取得できる。
		{
			get
			{
				lock( this )
				{
					this.Disposed.tDispose済みまたは実行中なら例外発生( "Songs" );

					if( 0 > this._SelectedSongIndex || this._SelectedSongIndex >= this.Count )
						return null;

					return this[ this._SelectedSongIndex ];
				}
			}
		}

		#region [ Dispose-Finalize パターン ]
		//-------------------------
		public void Dispose()
		{
			if( this.Disposed.bDispose済みまたは処理中である )
				return;		// 例外を発出させない。

			if( !this.Disposed.t開始を宣言する() )
				return;		// 例外を発出させない。

			this.Dispose( true );
			GC.SuppressFinalize( this );
		}
		//~Songs() { this.Dispose( false ); }		Unmanaged がないのでファイナライザは無し
		protected void Dispose( bool bReleaseManaged )
		{
			lock( this )
			{
				if( bReleaseManaged )
				{
					// TODO: ここで Managed を解放する

					foreach( var song in this )
						song.Dispose();

					this.Clear();
				}

				// TODO: ここで Unmanaged を解放する
			}
		}
		//-------------------------
		#endregion

		private readonly CDisposed Disposed = new CDisposed();
		
		private int _SelectedSongIndex;
	}
}
