\82¹ã\82³ã\82¢.cs - RSS feed" href="/view?p=strokestylet/CsWin10Desktop3.git;a=rss;f=SSTFormat/%C3%A3%C2%82%C2%B9%C3%A3%C2%82%C2%B3%C3%A3%C2%82%C2%A2.cs" type="application/rss+xml" /> \82¹ã\82³ã\82¢.cs - RSS feed (no merges)" href="/view?p=strokestylet/CsWin10Desktop3.git;a=rss;f=SSTFormat/%C3%A3%C2%82%C2%B9%C3%A3%C2%82%C2%B3%C3%A3%C2%82%C2%A2.cs;opt=--no-merges" type="application/rss+xml" /> \82¹ã\82³ã\82¢.cs - Atom feed" href="/view?p=strokestylet/CsWin10Desktop3.git;a=atom;f=SSTFormat/%C3%A3%C2%82%C2%B9%C3%A3%C2%82%C2%B3%C3%A3%C2%82%C2%A2.cs" type="application/atom+xml" /> \82¹ã\82³ã\82¢.cs - Atom feed (no merges)" href="/view?p=strokestylet/CsWin10Desktop3.git;a=atom;f=SSTFormat/%C3%A3%C2%82%C2%B9%C3%A3%C2%82%C2%B3%C3%A3%C2%82%C2%A2.cs;opt=--no-merges" type="application/atom+xml" />

OSDN Git Service

Merge branch 'sstfeditor-refactoring' into develop
[strokestylet/CsWin10Desktop3.git] / SSTFormat / ã\82¹ã\82³ã\82¢.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.IO;
5 using System.Linq;
6 using System.Text;
7 using System.Text.RegularExpressions;
8
9 namespace SSTFormat
10 {
11         public class スコア : IDisposable
12         {
13                 // ヘルパ
14
15                 /// <summary>
16                 /// 指定されたコマンド名が対象文字列内で使用されている場合に、パラメータ部分の文字列を返す。
17                 /// </summary>
18                 /// <remarks>
19                 /// .dtx や box.def 等で使用されている "#<コマンド名>[:]<パラメータ>[;コメント]" 形式の文字列(対象文字列)について、
20                 /// 指定されたコマンドを使用する行であるかどうかを判別し、使用する行であるなら、そのパラメータ部分の文字列を引数に格納し、true を返す。
21                 /// 対象文字列のコマンド名が指定したコマンド名と異なる場合には、パラメータ文字列に null を格納して false を返す。
22                 /// コマンド名は正しくてもパラメータが存在しない場合には、空文字列("") を格納して true を返す。
23                 /// </remarks>
24                 /// <param name="対象文字列">調べる対象の文字列。(例: "#TITLE: 曲名 ;コメント")</param>
25                 /// <param name="コマンド名">調べるコマンドの名前(例:"TITLE")。#は不要、大文字小文字は区別されない。</param>
26                 /// <returns>パラメータ文字列の取得に成功したら true、異なるコマンドだったなら false。</returns>
27                 public static bool コマンドのパラメータ文字列部分を返す( string 対象文字列, string コマンド名, out string パラメータ文字列 )
28                 {
29                         // コメント部分を除去し、両端をトリムする。なお、全角空白はトリムしない。
30                         対象文字列 = 対象文字列.Split( ';' )[ 0 ].Trim( ' ', '\t' );
31
32                         string 正規表現パターン = $@"^\s*#{コマンド名}(:|\s)+(.*)\s*$";  // \s は空白文字。
33                         var m = Regex.Match( 対象文字列, 正規表現パターン, RegexOptions.IgnoreCase );
34
35                         if( m.Success && ( 3 <= m.Groups.Count ) )
36                         {
37                                 パラメータ文字列 = m.Groups[ 2 ].Value;
38                                 return true;
39                         }
40                         else
41                         {
42                                 パラメータ文字列 = null;
43                                 return false;
44                         }
45                 }
46
47                 // 定数プロパティ
48
49                 public const double db初期BPM = 120.0;
50                 public const double db初期小節解像度 = 480.0;
51                 public const double dbBPM初期値固定での1小節4拍の時間ms = ( 60.0 * 1000 ) / ( スコア.db初期BPM / 4.0 );
52                 public const double dbBPM初期値固定での1小節4拍の時間sec = 60.0 / ( スコア.db初期BPM / 4.0 );
53
54                 /// <summary>
55                 /// 1ms あたりの設計ピクセル数 [dpx] 。
56                 /// </summary>
57                 /// <remarks>
58                 /// BPM 150 のとき、1小節が 234 dpx になるように調整。
59                 /// → 60秒で150拍のとき、1小節(4拍)が 234 dpx。
60                 /// → 60秒の間に、150[拍]÷4[拍]=37.5[小節]。
61                 /// → 60秒の間に、37.5[小節]×234[dpx/小節]= 8775[dpx]。
62                 /// → 1ms の間に、8775[dpx]÷60000[ms]=0.14625[dpx/ms]。割り切れて良かった。
63                 /// </remarks>
64                 public const double 基準譜面速度dpxms = 0.14625 * 2.25;  // "* 2.25" は「x1.0はもう少し速くてもいいんではないか?」という感覚的な調整分。
65
66                 /// <summary>
67                 /// 1秒あたりの設計ピクセル数 [dpx] 。
68                 /// </summary>
69                 public const double db基準譜面速度dpxsec = 基準譜面速度dpxms * 1000.0;
70
71                 public static readonly Dictionary<レーン種別, List<チップ種別>> dicSSTFレーンチップ対応表 = new Dictionary<レーン種別, List<チップ種別>>() {
72                         #region " *** "
73                         //-----------------
74                         { レーン種別.Bass, new List<チップ種別>() { チップ種別.Bass } },
75                         { レーン種別.BPM, new List<チップ種別>() { チップ種別.BPM } },
76                         { レーン種別.China, new List<チップ種別>() { チップ種別.China } },
77                         { レーン種別.HiHat, new List<チップ種別>() { チップ種別.HiHat_Close, チップ種別.HiHat_Foot, チップ種別.HiHat_HalfOpen, チップ種別.HiHat_Open } },
78                         { レーン種別.LeftCrash, new List<チップ種別>() { チップ種別.LeftCrash } },
79                         { レーン種別.Ride, new List<チップ種別>() { チップ種別.Ride, チップ種別.Ride_Cup } },
80                         { レーン種別.RightCrash, new List<チップ種別>() { チップ種別.RightCrash } },
81                         { レーン種別.Snare, new List<チップ種別>() { チップ種別.Snare, チップ種別.Snare_ClosedRim, チップ種別.Snare_Ghost, チップ種別.Snare_OpenRim } },
82                         { レーン種別.Song, new List<チップ種別>() { チップ種別.背景動画 } },
83                         { レーン種別.Splash, new List<チップ種別>() { チップ種別.Splash } },
84                         { レーン種別.Tom1, new List<チップ種別>() { チップ種別.Tom1, チップ種別.Tom1_Rim } },
85                         { レーン種別.Tom2, new List<チップ種別>() { チップ種別.Tom2, チップ種別.Tom2_Rim } },
86                         { レーン種別.Tom3, new List<チップ種別>() { チップ種別.Tom3, チップ種別.Tom3_Rim } },
87                         //-----------------
88                         #endregion
89                 };
90
91                 // 背景動画のデフォルト拡張子
92                 public static readonly List<string> listデフォルト拡張子 = new List<string>() {
93                                 ".avi", ".flv", ".mp4", ".wmv", ".mpg", ".mpeg"
94                         };
95
96                 // プロパティ;読み込み時または編集時に設定される
97
98                 public string 背景動画ファイル名 = "";
99
100                 public class CHeader
101                 {
102                         public string str曲名 = "(no title)";
103                         public string str説明文 = "";
104                 }
105                 public CHeader Header = new CHeader();
106
107                 public List<チップ> listチップ
108                 {
109                         get;
110                         protected set;
111                 }
112                 public List<double> list小節長倍率
113                 {
114                         get;
115                         protected set;
116                 }
117                 public int 最大小節番号
118                 {
119                         get
120                         {
121                                 int n最大小節番号 = 0;
122
123                                 foreach( チップ chip in this.listチップ )
124                                 {
125                                         if( chip.小節番号 > n最大小節番号 )
126                                                 n最大小節番号 = chip.小節番号;
127                                 }
128
129                                 return n最大小節番号;
130                         }
131                 }
132                 public Dictionary<int, string> dicメモ = new Dictionary<int, string>();
133
134                 // メソッド
135
136                 public スコア()
137                 {
138                         this.listチップ = new List<チップ>();
139                         this.list小節長倍率 = new List<double>();
140                 }
141                 public スコア( string str曲データファイル名, bool 左ライド = false, bool 左チャイナ = false, bool 左スプラッシュ = true )
142                         : this()
143                 {
144                         this.t曲データファイルを読み込む( str曲データファイル名, 左ライド, 左チャイナ, 左スプラッシュ );
145                 }
146                 public void Dispose()
147                 {
148                 }
149
150                 /// <summary>
151                 /// 指定された曲データファイルを読み込む。
152                 /// </summary>
153                 /// <remarks>
154                 /// 失敗すれば何らかの例外を発出する。
155                 /// </remarks>
156                 public void t曲データファイルを読み込む( string str曲データファイル名, bool 左ライド = false, bool 左チャイナ = false, bool 左スプラッシュ = true )
157                 {
158                         #region " 初期化する。"
159                         //-----------------
160                         this.list小節長倍率 = new List<double>();
161                         this.dicメモ = new Dictionary<int, string>();
162                         //-----------------
163                         #endregion
164                         #region " 曲データファイルを読み込む。"
165                         //-----------------
166                         var sr = new StreamReader( str曲データファイル名, Encoding.UTF8 );
167                         try
168                         {
169                                 int n行番号 = 0;
170                                 int n現在の小節番号 = 0;
171                                 int n現在の小節解像度 = 384;
172                                 //double db現在の小節長倍率 = 1.0;
173                                 チップ種別 e現在のチップ = チップ種別.Unknown;
174
175                                 while( !sr.EndOfStream )
176                                 {
177                                         // 1行ずつ読み込む。
178                                         n行番号++;
179                                         string str行 = sr.ReadLine();
180
181                                         // 前処理。
182
183                                         #region " 改行は ';' に、TABは空白文字にそれぞれ変換し、先頭末尾の空白を削除する。"
184                                         //-----------------
185                                         str行 = str行.Replace( Environment.NewLine, ";" );
186                                         str行 = str行.Replace( '\t', ' ' );
187                                         str行 = str行.Trim();
188                                         //-----------------
189                                         #endregion
190                                         #region " 行中の '#' 以降はコメントとして除外する。また、コメントだけの行はスキップする。"
191                                         //-----------------
192                                         int n区切り位置 = 0;
193                                         for( n区切り位置 = 0; n区切り位置 < str行.Length; n区切り位置++ )
194                                         {
195                                                 if( str行[ n区切り位置 ] == '#' )
196                                                         break;
197                                         }
198                                         if( n区切り位置 == 0 )
199                                                 continue;       // コメントだけの行はスキップ。
200                                         if( n区切り位置 < str行.Length )
201                                         {
202                                                 str行 = str行.Substring( 0, n区切り位置 - 1 );
203                                                 str行 = str行.Trim();
204                                         }
205                                         //-----------------
206                                         #endregion
207                                         #region " 空行ならこの行はスキップする。"
208                                         //-----------------
209                                         if( string.IsNullOrEmpty( str行 ) )
210                                                 continue;
211                                         //-----------------
212                                         #endregion
213
214                                         // ヘッダコマンド処理。
215
216                                         #region " ヘッダコマンドの処理を行う。"
217                                         //-----------------
218                                         if( str行.StartsWith( "Title", StringComparison.OrdinalIgnoreCase ) )
219                                         {
220                                                 #region " Title コマンド "
221                                                 //-----------------
222                                                 string[] items = str行.Split( '=' );
223
224                                                 if( items.Length != 2 )
225                                                 {
226                                                         FDK.Log.ERROR( $"Title の書式が不正です。スキップします。[{n行番号}行目]" );
227                                                         continue;
228                                                 }
229
230                                                 this.Header.str曲名 = items[ 1 ].Trim();
231                                                 //-----------------
232                                                 #endregion
233
234                                                 continue;
235                                         }
236                                         if( str行.StartsWith( "Description", StringComparison.OrdinalIgnoreCase ) )
237                                         {
238                                                 #region " Description コマンド "
239                                                 //-----------------
240                                                 string[] items = str行.Split( '=' );
241
242                                                 if( items.Length != 2 )
243                                                 {
244                                                         FDK.Log.ERROR( $"Description の書式が不正です。スキップします。[{n行番号}行目]" );
245                                                         continue;
246                                                 }
247
248                                                 // 2文字のリテラル "\n" は改行に復号。
249                                                 this.Header.str説明文 = items[ 1 ].Trim().Replace( @"\n", Environment.NewLine );
250                                                 //-----------------
251                                                 #endregion
252
253                                                 continue;
254                                         }
255                                         //-----------------
256                                         #endregion
257
258                                         // メモ(小節単位)処理。
259
260                                         #region " メモ(小節単位)の処理を行う。"
261                                         //-----------------
262                                         if( str行.StartsWith( "PartMemo", StringComparison.OrdinalIgnoreCase ) )
263                                         {
264                                                 #region " '=' 以前を除去する。"
265                                                 //-----------------
266                                                 int n等号位置 = str行.IndexOf( '=' );
267                                                 if( n等号位置 <= 0 )
268                                                 {
269                                                         FDK.Log.ERROR( $"PartMemo の書式が不正です。スキップします。[{n行番号}]行目]" );
270                                                         continue;
271                                                 }
272                                                 str行 = str行.Substring( n等号位置 + 1 ).Trim();
273                                                 if( string.IsNullOrEmpty( str行 ) )
274                                                 {
275                                                         FDK.Log.ERROR( $"PartMemo の書式が不正です。スキップします。[{n行番号}]行目]" );
276                                                         continue;
277                                                 }
278                                                 //-----------------
279                                                 #endregion
280                                                 #region " カンマ位置を取得する。"
281                                                 //-----------------
282                                                 int nカンマ位置 = str行.IndexOf( ',' );
283                                                 if( nカンマ位置 <= 0 )
284                                                 {
285                                                         FDK.Log.ERROR( $"PartMemo の書式が不正です。スキップします。[{n行番号}]行目]" );
286                                                         continue;
287                                                 }
288                                                 //-----------------
289                                                 #endregion
290                                                 #region " 小節番号を取得する。"
291                                                 //-----------------
292                                                 string str小節番号 = str行.Substring( 0, nカンマ位置 );
293                                                 int n小節番号;
294                                                 if( !int.TryParse( str小節番号, out n小節番号 ) || n小節番号 < 0 )
295                                                 {
296                                                         FDK.Log.ERROR( $"PartMemo の小節番号が不正です。スキップします。[{n行番号}]行目]" );
297                                                         continue;
298                                                 }
299                                                 //-----------------
300                                                 #endregion
301                                                 #region " メモを取得する。"
302                                                 //-----------------
303                                                 string strメモ = str行.Substring( nカンマ位置 + 1 );
304
305                                                 // 2文字のリテラル文字列 "\n" は改行に復号。
306                                                 strメモ = strメモ.Replace( @"\n", Environment.NewLine );
307                                                 //-----------------
308                                                 #endregion
309                                                 #region " メモが空文字列でないなら dicメモ に登録すると同時に、チップとしても追加する。"
310                                                 //-----------------
311                                                 if( !string.IsNullOrEmpty( strメモ ) )
312                                                 {
313                                                         this.dicメモ.Add( n小節番号, strメモ );
314
315                                                         this.listチップ.Add(
316                                                                 new チップ() {
317                                                                         チップ種別 = チップ種別.小節メモ,
318                                                                         小節番号 = n小節番号,
319                                                                         小節内位置 = 0,
320                                                                         小節解像度 = 1,
321                                                                 } );
322                                                 }
323                                                 //-----------------
324                                                 #endregion
325
326                                                 continue;
327                                         }
328                                         //-----------------
329                                         #endregion
330
331                                         // 上記行頭コマンド以外は、チップ記述行だと見なす。
332
333                                         #region " チップ記述コマンドの処理を行う。"
334                                         //-----------------
335
336                                         // 行を区切り文字でトークンに分割。
337                                         string[] tokens = str行.Split( new char[] { ';', ':' } );
338
339                                         // すべてのトークンについて……
340                                         foreach( string token in tokens )
341                                         {
342                                                 // トークンを分割。
343
344                                                 #region " トークンを区切り文字 '=' で strコマンド と strパラメータ に分割し、それぞれの先頭末尾の空白を削除する。"
345                                                 //-----------------
346                                                 string[] items = token.Split( '=' );
347
348                                                 if( items.Length != 2 )
349                                                 {
350                                                         if( token.Trim().Length == 0 )  // 空文字列(行末など)は不正じゃない。
351                                                                 continue;
352
353                                                         FDK.Log.ERROR( $"コマンドとパラメータの記述書式が不正です。このコマンドをスキップします。[{n行番号}行目]" );
354                                                         continue;
355                                                 }
356
357                                                 string strコマンド = items[ 0 ].Trim();
358                                                 string strパラメータ = items[ 1 ].Trim();
359                                                 //-----------------
360                                                 #endregion
361
362                                                 // コマンド別に処理。
363
364                                                 if( strコマンド.Equals( "Part", StringComparison.OrdinalIgnoreCase ) )
365                                                 {
366                                                         #region " Part(小節番号指定)コマンド "
367                                                         //-----------------
368
369                                                         #region " 小節番号を取得・設定。"
370                                                         //-----------------
371                                                         string str小節番号 = this.t指定された文字列の先頭から数字文字列を取り出す( ref strパラメータ );
372                                                         if( string.IsNullOrEmpty( str小節番号 ) )
373                                                         {
374                                                                 FDK.Log.ERROR( $"Part(小節番号)コマンドに小節番号の記述がありません。このコマンドをスキップします。[{n行番号}行目]" );
375                                                                 continue;
376                                                         }
377
378                                                         int n小節番号 = 0;
379                                                         if( !int.TryParse( str小節番号, out n小節番号 ) )
380                                                         {
381                                                                 FDK.Log.ERROR( $"Part(小節番号)コマンドの小節番号が不正です。このコマンドをスキップします。[{n行番号}行目]" );
382                                                                 continue;
383                                                         }
384                                                         if( n小節番号 < 0 )
385                                                         {
386                                                                 FDK.Log.ERROR( $"Part(小節番号)コマンドの小節番号が負数です。このコマンドをスキップします。[{n行番号}行目]" );
387                                                                 continue;
388                                                         }
389
390                                                         n現在の小節番号 = n小節番号;
391                                                         //-----------------
392                                                         #endregion
393                                                         #region " Part 属性があれば取得する。"
394                                                         //-----------------
395
396                                                         while( strパラメータ.Length > 0 )
397                                                         {
398                                                                 // 属性ID を取得。
399
400                                                                 char c属性ID = char.ToLower( strパラメータ[ 0 ] );
401
402
403                                                                 // Part 属性があれば取得する。
404
405                                                                 if( c属性ID == 's' )
406                                                                 {
407                                                                         #region " 小節長倍率(>0) → list小節長倍率 "
408                                                                         //-----------------
409
410                                                                         strパラメータ = strパラメータ.Substring( 1 ).Trim();
411
412                                                                         string str小節長倍率 = this.t指定された文字列の先頭から数字文字列を取り出す( ref strパラメータ );
413                                                                         strパラメータ = strパラメータ.Trim();
414
415                                                                         if( string.IsNullOrEmpty( str小節長倍率 ) )
416                                                                         {
417                                                                                 FDK.Log.ERROR( $"Part(小節番号)コマンドに小節長倍率の記述がありません。この属性をスキップします。[{n行番号}行目]" );
418                                                                                 continue;
419                                                                         }
420
421                                                                         double db小節長倍率 = 1.0;
422
423                                                                         if( !double.TryParse( str小節長倍率, out db小節長倍率 ) )
424                                                                         {
425                                                                                 FDK.Log.ERROR( $"Part(小節番号)コマンドの小節長倍率が不正です。この属性をスキップします。[{n行番号}行目]" );
426                                                                                 continue;
427                                                                         }
428
429                                                                         if( db小節長倍率 <= 0.0 )
430                                                                         {
431                                                                                 FDK.Log.ERROR( $"Part(小節番号)コマンドの小節長倍率が 0.0 または負数です。この属性をスキップします。[{n行番号}行目]" );
432                                                                                 continue;
433                                                                         }
434
435
436                                                                         // 小節長倍率辞書に追加 or 上書き更新。
437
438                                                                         this.小節長倍率を設定する( n現在の小節番号, db小節長倍率 );
439
440                                                                         //-----------------
441                                                                         #endregion
442
443                                                                         continue;
444                                                                 }
445                                                         }
446
447                                                         //-----------------
448                                                         #endregion
449
450                                                         //-----------------
451                                                         #endregion
452
453                                                         continue;
454                                                 }
455                                                 if( strコマンド.Equals( "Lane", StringComparison.OrdinalIgnoreCase ) )
456                                                 {
457                                                         #region " Lane(レーン指定)コマンド(チップ種別の仮決め)"
458                                                         //-----------------
459                                                         if( strパラメータ.Equals( "LeftCrash", StringComparison.OrdinalIgnoreCase ) )
460                                                                 e現在のチップ = チップ種別.LeftCrash;
461
462                                                         else if( strパラメータ.Equals( "Ride", StringComparison.OrdinalIgnoreCase ) )
463                                                                 e現在のチップ = チップ種別.Ride;
464
465                                                         else if( strパラメータ.Equals( "China", StringComparison.OrdinalIgnoreCase ) )
466                                                                 e現在のチップ = チップ種別.China;
467
468                                                         else if( strパラメータ.Equals( "Splash", StringComparison.OrdinalIgnoreCase ) )
469                                                                 e現在のチップ = チップ種別.Splash;
470
471                                                         else if( strパラメータ.Equals( "HiHat", StringComparison.OrdinalIgnoreCase ) )
472                                                                 e現在のチップ = チップ種別.HiHat_Close;
473
474                                                         else if( strパラメータ.Equals( "Snare", StringComparison.OrdinalIgnoreCase ) )
475                                                                 e現在のチップ = チップ種別.Snare;
476
477                                                         else if( strパラメータ.Equals( "Bass", StringComparison.OrdinalIgnoreCase ) )
478                                                                 e現在のチップ = チップ種別.Bass;
479
480                                                         else if( strパラメータ.Equals( "Tom1", StringComparison.OrdinalIgnoreCase ) )
481                                                                 e現在のチップ = チップ種別.Tom1;
482
483                                                         else if( strパラメータ.Equals( "Tom2", StringComparison.OrdinalIgnoreCase ) )
484                                                                 e現在のチップ = チップ種別.Tom2;
485
486                                                         else if( strパラメータ.Equals( "Tom3", StringComparison.OrdinalIgnoreCase ) )
487                                                                 e現在のチップ = チップ種別.Tom3;
488
489                                                         else if( strパラメータ.Equals( "RightCrash", StringComparison.OrdinalIgnoreCase ) )
490                                                                 e現在のチップ = チップ種別.RightCrash;
491
492                                                         else if( strパラメータ.Equals( "BPM", StringComparison.OrdinalIgnoreCase ) )
493                                                                 e現在のチップ = チップ種別.BPM;
494
495                                                         else if( strパラメータ.Equals( "Song", StringComparison.OrdinalIgnoreCase ) )
496                                                                 e現在のチップ = チップ種別.背景動画;
497                                                         else
498                                                                 FDK.Log.ERROR( $"Lane(レーン指定)コマンドのパラメータ記述 '{strパラメータ}' が不正です。このコマンドをスキップします。[{n行番号}行目]" );
499                                                         //-----------------
500                                                         #endregion
501
502                                                         continue;
503                                                 }
504                                                 if( strコマンド.Equals( "Resolution", StringComparison.OrdinalIgnoreCase ) )
505                                                 {
506                                                         #region " Resolution(小節解像度指定)コマンド "
507                                                         //-----------------
508                                                         int n解像度 = 1;
509                                                         if( !int.TryParse( strパラメータ, out n解像度 ) )
510                                                         {
511                                                                 FDK.Log.ERROR( $"Resolution(小節解像度指定)コマンドの解像度が不正です。このコマンドをスキップします。[{n行番号}行目]" );
512                                                                 continue;
513                                                         }
514                                                         if( n解像度 < 1 )
515                                                         {
516                                                                 FDK.Log.ERROR( $"Resolution(小節解像度指定)コマンドの解像度は 1 以上でなければなりません。このコマンドをスキップします。[{n行番号}行目]" );
517                                                                 continue;
518                                                         }
519                                                         n現在の小節解像度 = n解像度;
520                                                         //-----------------
521                                                         #endregion
522
523                                                         continue;
524                                                 }
525                                                 if( strコマンド.Equals( "Chips", StringComparison.OrdinalIgnoreCase ) )
526                                                 {
527                                                         #region " Chips(チップ指定)コマンド "
528                                                         //-----------------
529
530                                                         // パラメータを区切り文字 ',' でチップトークンに分割。
531                                                         string[] chipTokens = strパラメータ.Split( ',' );
532
533                                                         // すべてのチップトークンについて……
534                                                         for( int i = 0; i < chipTokens.Length; i++ )
535                                                         {
536                                                                 chipTokens[ i ].Trim();
537
538                                                                 #region " 空文字はスキップ。"
539                                                                 //-----------------
540                                                                 if( chipTokens[ i ].Length == 0 )
541                                                                         continue;
542                                                                 //-----------------
543                                                                 #endregion
544                                                                 #region " チップを生成する。"
545                                                                 //-----------------
546                                                                 var chip = new チップ() {
547                                                                         小節番号 = n現在の小節番号,
548                                                                         チップ種別 = e現在のチップ,
549                                                                         小節解像度 = n現在の小節解像度,
550                                                                         音量 = 4,
551                                                                 };
552                                                                 if( chip.チップ種別 == チップ種別.China ) chip.チップ内文字列 = "C N";
553                                                                 if( chip.チップ種別 == チップ種別.Splash ) chip.チップ内文字列 = "S P";
554
555                                                                 if( ( e現在のチップ == チップ種別.BPM ) ||
556                                                                         ( e現在のチップ == チップ種別.背景動画 ) ||
557                                                                         ( e現在のチップ == チップ種別.小節メモ ) ||
558                                                                         ( e現在のチップ == チップ種別.Unknown ) )
559                                                                 {
560                                                                         chip.可視 = false;
561                                                                 }
562                                                                 //-----------------
563                                                                 #endregion
564
565                                                                 #region " チップ位置を取得する。"
566                                                                 //-----------------
567                                                                 int nチップ位置 = 0;
568
569                                                                 string str位置番号 = this.t指定された文字列の先頭から数字文字列を取り出す( ref chipTokens[ i ] );
570                                                                 chipTokens[ i ].Trim();
571
572                                                                 #region " 文法チェック。"
573                                                                 //-----------------
574                                                                 if( string.IsNullOrEmpty( str位置番号 ) )
575                                                                 {
576                                                                         FDK.Log.ERROR( $"チップの位置指定の記述がありません。このチップをスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
577                                                                         continue;
578                                                                 }
579                                                                 //-----------------
580                                                                 #endregion
581                                                                 #region " 位置を取得。"
582                                                                 //-----------------
583                                                                 if( !int.TryParse( str位置番号, out nチップ位置 ) )
584                                                                 {
585                                                                         FDK.Log.ERROR( $"チップの位置指定の記述が不正です。このチップをスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
586                                                                         continue;
587                                                                 }
588                                                                 //-----------------
589                                                                 #endregion
590                                                                 #region " 値域チェック。"
591                                                                 //-----------------
592                                                                 if( ( nチップ位置 < 0 ) || ( nチップ位置 >= n現在の小節解像度 ) )
593                                                                 {
594                                                                         FDK.Log.ERROR( $"チップの位置が負数であるか解像度(Resolution)以上の値になっています。このチップをスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
595                                                                         continue;
596                                                                 }
597                                                                 //-----------------
598                                                                 #endregion
599
600                                                                 chip.小節内位置 = nチップ位置;
601                                                                 //-----------------
602                                                                 #endregion
603                                                                 #region " 共通属性・レーン別属性があれば取得する。"
604                                                                 //-----------------
605                                                                 while( chipTokens[ i ].Length > 0 )
606                                                                 {
607                                                                         // 属性ID を取得。
608                                                                         char c属性ID = char.ToLower( chipTokens[ i ][ 0 ] );
609
610                                                                         // 共通属性があれば取得。
611                                                                         if( c属性ID == 'v' )
612                                                                         {
613                                                                                 #region " 音量 "
614                                                                                 //-----------------
615                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
616
617                                                                                 string str音量 = this.t指定された文字列の先頭から数字文字列を取り出す( ref chipTokens[ i ] );
618                                                                                 chipTokens[ i ].Trim();
619
620                                                                                 int nチップ音量 = 0;
621
622                                                                                 #region " 文法チェック。"
623                                                                                 //-----------------
624                                                                                 if( string.IsNullOrEmpty( str音量 ) )
625                                                                                 {
626                                                                                         FDK.Log.ERROR( $"チップの音量指定の記述がありません。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
627                                                                                         continue;
628                                                                                 }
629                                                                                 //-----------------
630                                                                                 #endregion
631                                                                                 #region " チップ音量の取得。"
632                                                                                 //-----------------
633                                                                                 if( !int.TryParse( str音量, out nチップ音量 ) )
634                                                                                 {
635                                                                                         FDK.Log.ERROR( $"チップの音量指定の記述が不正です。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
636                                                                                         continue;
637                                                                                 }
638                                                                                 //-----------------
639                                                                                 #endregion
640                                                                                 #region " 値域チェック。"
641                                                                                 //-----------------
642                                                                                 if( nチップ音量 < 1 || nチップ音量 > チップ.最大音量 )
643                                                                                 {
644                                                                                         FDK.Log.ERROR( $"チップの音量が適正範囲(1~{チップ.最大音量})を超えています。このチップをスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
645                                                                                         continue;
646                                                                                 }
647                                                                                 //-----------------
648                                                                                 #endregion
649
650                                                                                 chip.音量 = nチップ音量;
651                                                                                 //-----------------
652                                                                                 #endregion
653
654                                                                                 continue;
655                                                                         }
656
657
658                                                                         // レーン別属性があれば取得。
659
660                                                                         switch( e現在のチップ )
661                                                                         {
662                                                                                 #region " case LeftCymbal "
663                                                                                 //-----------------
664                                                                                 case チップ種別.LeftCrash:
665
666                                                                                         #region " 未知の属性 "
667                                                                                         //-----------------
668                                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
669                                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
670                                                                                         //-----------------
671                                                                                         #endregion
672
673                                                                                         continue;
674
675                                                                                 //-----------------
676                                                                                 #endregion
677                                                                                 #region " case Ride "
678                                                                                 //-----------------
679                                                                                 case チップ種別.Ride:
680                                                                                 case チップ種別.Ride_Cup:
681
682                                                                                         if( c属性ID == 'c' )
683                                                                                         {
684                                                                                                 #region " Ride.カップ "
685                                                                                                 //-----------------
686                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
687                                                                                                 chip.チップ種別 = チップ種別.Ride_Cup;
688                                                                                                 //-----------------
689                                                                                                 #endregion
690                                                                                         }
691                                                                                         else
692                                                                                         {
693                                                                                                 #region " 未知の属性 "
694                                                                                                 //-----------------
695                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
696                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
697                                                                                                 //-----------------
698                                                                                                 #endregion
699                                                                                         }
700                                                                                         continue;
701
702                                                                                 //-----------------
703                                                                                 #endregion
704                                                                                 #region " case China "
705                                                                                 //-----------------
706                                                                                 case チップ種別.China:
707
708                                                                                         #region " 未知の属性 "
709                                                                                         //-----------------
710                                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
711                                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
712                                                                                         //-----------------
713                                                                                         #endregion
714
715                                                                                         continue;
716
717                                                                                 //-----------------
718                                                                                 #endregion
719                                                                                 #region " case Splash "
720                                                                                 //-----------------
721                                                                                 case チップ種別.Splash:
722
723                                                                                         #region " 未知の属性 "
724                                                                                         //-----------------
725                                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
726                                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
727                                                                                         //-----------------
728                                                                                         #endregion
729
730                                                                                         continue;
731
732                                                                                 //-----------------
733                                                                                 #endregion
734                                                                                 #region " case HiHat "
735                                                                                 //-----------------
736                                                                                 case チップ種別.HiHat_Close:
737                                                                                 case チップ種別.HiHat_HalfOpen:
738                                                                                 case チップ種別.HiHat_Open:
739                                                                                 case チップ種別.HiHat_Foot:
740
741                                                                                         if( c属性ID == 'o' )
742                                                                                         {
743                                                                                                 #region " HiHat.オープン "
744                                                                                                 //-----------------
745                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
746                                                                                                 chip.チップ種別 = チップ種別.HiHat_Open;
747                                                                                                 //-----------------
748                                                                                                 #endregion
749                                                                                         }
750                                                                                         else if( c属性ID == 'h' )
751                                                                                         {
752                                                                                                 #region " HiHat.ハーフオープン "
753                                                                                                 //-----------------
754                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
755                                                                                                 chip.チップ種別 = チップ種別.HiHat_HalfOpen;
756                                                                                                 //-----------------
757                                                                                                 #endregion
758                                                                                         }
759                                                                                         else if( c属性ID == 'c' )
760                                                                                         {
761                                                                                                 #region " HiHat.クローズ "
762                                                                                                 //-----------------
763                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
764                                                                                                 chip.チップ種別 = チップ種別.HiHat_Close;
765                                                                                                 //-----------------
766                                                                                                 #endregion
767                                                                                         }
768                                                                                         else if( c属性ID == 'f' )
769                                                                                         {
770                                                                                                 #region " HiHat.フットスプラッシュ "
771                                                                                                 //-----------------
772                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
773                                                                                                 chip.チップ種別 = チップ種別.HiHat_Foot;
774                                                                                                 //-----------------
775                                                                                                 #endregion
776                                                                                         }
777                                                                                         else
778                                                                                         {
779                                                                                                 #region " 未知の属性 "
780                                                                                                 //-----------------
781                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
782                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
783                                                                                                 //-----------------
784                                                                                                 #endregion
785                                                                                         }
786                                                                                         continue;
787
788                                                                                 //-----------------
789                                                                                 #endregion
790                                                                                 #region " case Snare "
791                                                                                 //-----------------
792                                                                                 case チップ種別.Snare:
793                                                                                 case チップ種別.Snare_ClosedRim:
794                                                                                 case チップ種別.Snare_OpenRim:
795                                                                                 case チップ種別.Snare_Ghost:
796
797                                                                                         if( c属性ID == 'o' )
798                                                                                         {
799                                                                                                 #region " Snare.オープンリム "
800                                                                                                 //-----------------
801                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
802                                                                                                 chip.チップ種別 = チップ種別.Snare_OpenRim;
803                                                                                                 //-----------------
804                                                                                                 #endregion
805                                                                                         }
806                                                                                         else if( c属性ID == 'c' )
807                                                                                         {
808                                                                                                 #region " Snare.クローズドリム "
809                                                                                                 //-----------------
810                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
811                                                                                                 chip.チップ種別 = チップ種別.Snare_ClosedRim;
812                                                                                                 //-----------------
813                                                                                                 #endregion
814                                                                                         }
815                                                                                         else if( c属性ID == 'g' )
816                                                                                         {
817                                                                                                 #region " Snare.ゴースト "
818                                                                                                 //-----------------
819                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
820                                                                                                 chip.チップ種別 = チップ種別.Snare_Ghost;
821                                                                                                 //-----------------
822                                                                                                 #endregion
823                                                                                         }
824                                                                                         else
825                                                                                         {
826                                                                                                 #region " 未知の属性 "
827                                                                                                 //-----------------
828                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
829                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
830                                                                                                 //-----------------
831                                                                                                 #endregion
832                                                                                         }
833                                                                                         continue;
834
835                                                                                 //-----------------
836                                                                                 #endregion
837                                                                                 #region " case Bass "
838                                                                                 //-----------------
839                                                                                 case チップ種別.Bass:
840
841                                                                                         #region " 未知の属性 "
842                                                                                         //-----------------
843                                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
844                                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
845                                                                                         //-----------------
846                                                                                         #endregion
847
848                                                                                         continue;
849
850                                                                                 //-----------------
851                                                                                 #endregion
852                                                                                 #region " case Tom1 "
853                                                                                 //-----------------
854                                                                                 case チップ種別.Tom1:
855                                                                                 case チップ種別.Tom1_Rim:
856
857                                                                                         if( c属性ID == 'r' )
858                                                                                         {
859                                                                                                 #region " Tom1.リム "
860                                                                                                 //-----------------
861                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
862                                                                                                 chip.チップ種別 = チップ種別.Tom1_Rim;
863                                                                                                 //-----------------
864                                                                                                 #endregion
865                                                                                         }
866                                                                                         else
867                                                                                         {
868                                                                                                 #region " 未知の属性 "
869                                                                                                 //-----------------
870                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
871                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
872                                                                                                 //-----------------
873                                                                                                 #endregion
874                                                                                         }
875                                                                                         continue;
876
877                                                                                 //-----------------
878                                                                                 #endregion
879                                                                                 #region " case Tom2 "
880                                                                                 //-----------------
881                                                                                 case チップ種別.Tom2:
882                                                                                 case チップ種別.Tom2_Rim:
883
884                                                                                         if( c属性ID == 'r' )
885                                                                                         {
886                                                                                                 #region " Tom2.リム "
887                                                                                                 //-----------------
888                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
889                                                                                                 chip.チップ種別 = チップ種別.Tom2_Rim;
890                                                                                                 //-----------------
891                                                                                                 #endregion
892                                                                                         }
893                                                                                         else
894                                                                                         {
895                                                                                                 #region " 未知の属性 "
896                                                                                                 //-----------------
897                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
898                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
899                                                                                                 //-----------------
900                                                                                                 #endregion
901                                                                                         }
902                                                                                         continue;
903
904                                                                                 //-----------------
905                                                                                 #endregion
906                                                                                 #region " case Tom3 "
907                                                                                 //-----------------
908                                                                                 case チップ種別.Tom3:
909                                                                                 case チップ種別.Tom3_Rim:
910
911                                                                                         if( c属性ID == 'r' )
912                                                                                         {
913                                                                                                 #region " Tom3.リム "
914                                                                                                 //-----------------
915                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
916                                                                                                 chip.チップ種別 = チップ種別.Tom3_Rim;
917                                                                                                 //-----------------
918                                                                                                 #endregion
919                                                                                         }
920                                                                                         else
921                                                                                         {
922                                                                                                 #region " 未知の属性 "
923                                                                                                 //-----------------
924                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
925                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
926                                                                                                 //-----------------
927                                                                                                 #endregion
928                                                                                         }
929                                                                                         continue;
930
931                                                                                 //-----------------
932                                                                                 #endregion
933                                                                                 #region " case RightCymbal "
934                                                                                 //-----------------
935                                                                                 case チップ種別.RightCrash:
936
937                                                                                         #region " 未知の属性 "
938                                                                                         //-----------------
939                                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
940                                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
941                                                                                         //-----------------
942                                                                                         #endregion
943
944                                                                                         continue;
945
946                                                                                 //-----------------
947                                                                                 #endregion
948                                                                                 #region " case BPM "
949                                                                                 //-----------------
950                                                                                 case チップ種別.BPM:
951
952                                                                                         if( c属性ID == 'b' )
953                                                                                         {
954                                                                                                 #region " BPM値 "
955                                                                                                 //-----------------
956
957                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
958
959                                                                                                 string strBPM = this.t指定された文字列の先頭から数字文字列を取り出す( ref chipTokens[ i ] );
960                                                                                                 chipTokens[ i ].Trim();
961
962                                                                                                 if( string.IsNullOrEmpty( strBPM ) )
963                                                                                                 {
964                                                                                                         FDK.Log.ERROR( $"BPM数値の記述がありません。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
965                                                                                                         continue;
966                                                                                                 }
967
968                                                                                                 double dbBPM = 0.0;
969
970                                                                                                 if( !double.TryParse( strBPM, out dbBPM ) || dbBPM <= 0.0 )
971                                                                                                 {
972                                                                                                         FDK.Log.ERROR( $"BPM数値の記述が不正です。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
973                                                                                                         continue;
974                                                                                                 }
975
976                                                                                                 chip.BPM = dbBPM;
977                                                                                                 chip.チップ内文字列 = dbBPM.ToString( "###.##" );
978                                                                                                 //-----------------
979                                                                                                 #endregion
980                                                                                         }
981                                                                                         else
982                                                                                         {
983                                                                                                 #region " 未知の属性 "
984                                                                                                 //-----------------
985                                                                                                 chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
986                                                                                                 FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
987                                                                                                 //-----------------
988                                                                                                 #endregion
989                                                                                         }
990                                                                                         continue;
991
992                                                                                 //-----------------
993                                                                                 #endregion
994                                                                                 #region " case Song "
995                                                                                 //-----------------
996                                                                                 case チップ種別.背景動画:
997
998                                                                                         #region " 未知の属性 "
999                                                                                         //-----------------
1000                                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
1001                                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
1002                                                                                         //-----------------
1003                                                                                         #endregion
1004
1005                                                                                         continue;
1006
1007                                                                                         //-----------------
1008                                                                                         #endregion
1009                                                                         }
1010
1011                                                                         #region " 未知の属性 "
1012                                                                         //-----------------
1013                                                                         chipTokens[ i ] = chipTokens[ i ].Substring( 1 ).Trim();
1014                                                                         FDK.Log.ERROR( $"未対応の属性「{c属性ID}」が指定されています。この属性をスキップします。[{n行番号}行目; {i + 1}個目のチップ]" );
1015                                                                         //-----------------
1016                                                                         #endregion
1017                                                                 }
1018                                                                 //-----------------
1019                                                                 #endregion
1020
1021                                                                 // チップをリストに追加。
1022                                                                 this.listチップ.Add( chip );
1023                                                         }
1024                                                         //-----------------
1025                                                         #endregion
1026
1027                                                         continue;
1028                                                 }
1029
1030                                                 FDK.Log.ERROR( $"不正なコマンド「{strコマンド}」が存在します。[{n行番号}行目]" );
1031                                         }
1032                                         //-----------------
1033                                         #endregion
1034                                 }
1035                         }
1036                         finally
1037                         {
1038                                 sr.Close();
1039                         }
1040                         //-----------------
1041                         #endregion
1042                         #region " 拍線の追加。小節線を先に追加すると小節が1つ増えるので、先に拍線から追加する。"
1043                         //-----------------
1044
1045                         // 「this.n最大小節番号」はチップ数に依存して変化するので、for 文には組み込まないこと。
1046                         int n最大小節番号 = this.最大小節番号;
1047
1048                         for( int i = 0; i <= n最大小節番号; i++ )
1049                         {
1050                                 double db小節長倍率 = this.小節長倍率を取得する( i );
1051                                 for( int n = 1; n * 0.25 < db小節長倍率; n++ )
1052                                 {
1053                                         this.listチップ.Add(
1054                                                 new チップ() {
1055                                                         小節番号 = i,
1056                                                         チップ種別 = チップ種別.拍線,
1057                                                         小節内位置 = (int) ( ( n * 0.25 ) * 100 ),
1058                                                         小節解像度 = (int) ( db小節長倍率 * 100 ),
1059                                                 } );
1060                                 }
1061                         }
1062                         //-----------------
1063                         #endregion
1064                         #region " 小節線の追加。"
1065                         //-----------------
1066                         n最大小節番号 = this.最大小節番号;
1067
1068                         for( int i = 0; i <= n最大小節番号 + 1; i++ )
1069                         {
1070                                 this.listチップ.Add(
1071                                         new チップ() {
1072                                                 小節番号 = i,
1073                                                 チップ種別 = チップ種別.小節線,
1074                                                 小節内位置 = 0,
1075                                                 小節解像度 = 1,
1076                                         } );
1077                         }
1078                         //-----------------
1079                         #endregion
1080
1081                         this.listチップ.Sort();
1082
1083                         #region " 全チップの発声/描画時刻と譜面内位置を計算する。"
1084                         //-----------------
1085
1086                         // 1. BPMチップを無視し(初期BPMで固定)、dic小節長倍率, Cチップ.n小節解像度, Cチップ.n小節内位置 から両者を計算する。
1087                         //    以下、listチップが小節番号順にソートされているという前提で。
1088
1089                         double dbチップが存在する小節の先頭時刻ms = 0.0;
1090                         int n現在の小節の番号 = 0;
1091
1092                         foreach( チップ chip in this.listチップ )
1093                         {
1094                                 #region " チップの小節番号が現在の小節番号よりも大きい場合、チップが存在する小節に至るまで、「dbチップが存在する小節の先頭時刻ms」を更新する。"
1095                                 //-----------------
1096                                 while( n現在の小節の番号 < chip.小節番号 )
1097                                 {
1098                                         double db現在の小節の小節長倍率 = this.小節長倍率を取得する( n現在の小節の番号 );
1099                                         dbチップが存在する小節の先頭時刻ms += dbBPM初期値固定での1小節4拍の時間ms * db現在の小節の小節長倍率;
1100
1101                                         n現在の小節の番号++;    // n現在の小節番号 が chip.n小節番号 に追いつくまでループする。
1102                                 }
1103                                 //-----------------
1104                                 #endregion
1105                                 #region " チップの発声/描画時刻を求める。"
1106                                 //-----------------
1107                                 double dbチップが存在する小節の小節長倍率 = this.小節長倍率を取得する( n現在の小節の番号 );
1108
1109                                 chip.発声時刻ms =
1110                                         chip.描画時刻ms =
1111                                                 (long) ( dbチップが存在する小節の先頭時刻ms + ( dbBPM初期値固定での1小節4拍の時間ms * dbチップが存在する小節の小節長倍率 * chip.小節内位置 ) / chip.小節解像度 );
1112                                 //-----------------
1113                                 #endregion
1114                         }
1115
1116                         // 2. BPMチップを考慮しながら調整する。(譜面内位置grid はBPMの影響を受けないので無視)
1117
1118                         double db現在のBPM = スコア.db初期BPM;
1119                         int nチップ数 = this.listチップ.Count;
1120                         for( int i = 0; i < nチップ数; i++ )
1121                         {
1122                                 // BPM チップ以外は無視。
1123                                 var BPMチップ = this.listチップ[ i ];
1124                                 if( BPMチップ.チップ種別 != チップ種別.BPM )
1125                                         continue;
1126
1127                                 // BPMチップより後続の全チップの n発声/描画時刻ms を、新旧BPMの比率(加速率)で修正する。
1128                                 double db加速率 = BPMチップ.BPM / db現在のBPM; // BPMチップ.dbBPM > 0.0 であることは読み込み時に保証済み。
1129                                 for( int j = i + 1; j < nチップ数; j++ )
1130                                 {
1131                                         this.listチップ[ j ].発声時刻ms =
1132                                                 this.listチップ[ j ].描画時刻ms =
1133                                                         (long) ( BPMチップ.発声時刻ms + ( ( this.listチップ[ j ].発声時刻ms - BPMチップ.発声時刻ms ) / db加速率 ) );
1134                                 }
1135
1136                                 db現在のBPM = BPMチップ.BPM;
1137                         }
1138                         //-----------------
1139                         #endregion
1140                 }
1141                 public void t曲データファイルを読み込む_ヘッダだけ( string str曲データファイル名 )
1142                 {
1143                         this.list小節長倍率 = new List<double>();
1144
1145                         #region " 曲データファイルを読み込む。"
1146                         //-----------------
1147                         var sr = new StreamReader( str曲データファイル名, Encoding.UTF8 );
1148                         try
1149                         {
1150                                 int n行番号 = 0;
1151                                 //int n現在の小節番号 = 0;
1152                                 //int n現在の小節解像度 = 384;
1153                                 //double db現在の小節長倍率 = 1.0;
1154                                 //チップ種別 e現在のチップ = チップ種別.Unknown;
1155
1156                                 while( !sr.EndOfStream )
1157                                 {
1158                                         // 1行ずつ読み込む。
1159
1160                                         n行番号++;
1161
1162                                         string str行 = sr.ReadLine();
1163
1164                                         // 前処理。
1165
1166                                         #region " 文字列に前処理を行う。"
1167                                         //-----------------
1168
1169                                         // 改行は ';' に、TABは空白文字にそれぞれ変換し、先頭末尾の空白を削除する。
1170
1171                                         str行 = str行.Replace( Environment.NewLine, ";" );
1172                                         str行 = str行.Replace( '\t', ' ' );
1173                                         str行 = str行.Trim();
1174
1175
1176                                         // 行中の '#' 以降はコメントとして除外する。また、コメントだけの行はスキップする。
1177
1178                                         int n区切り位置 = 0;
1179
1180                                         for( n区切り位置 = 0; n区切り位置 < str行.Length; n区切り位置++ )
1181                                         {
1182                                                 if( str行[ n区切り位置 ] == '#' )
1183                                                         break;
1184                                         }
1185
1186                                         if( n区切り位置 == 0 )
1187                                                 continue;       // コメントだけの行はスキップ。
1188
1189                                         if( n区切り位置 < str行.Length )
1190                                         {
1191                                                 str行 = str行.Substring( 0, n区切り位置 - 1 );
1192                                                 str行 = str行.Trim();
1193                                         }
1194
1195
1196                                         // 空行ならこの行はスキップする。
1197
1198                                         if( string.IsNullOrEmpty( str行 ) )
1199                                                 continue;
1200
1201                                         //-----------------
1202                                         #endregion
1203
1204                                         // ヘッダコマンド処理。
1205
1206                                         #region " ヘッダコマンドの処理を行う。"
1207                                         //-----------------
1208
1209                                         if( str行.StartsWith( "Title", StringComparison.OrdinalIgnoreCase ) )
1210                                         {
1211                                                 #region " Title コマンド "
1212                                                 //-----------------
1213                                                 string[] items = str行.Split( '=' );
1214
1215                                                 if( items.Length != 2 )
1216                                                 {
1217                                                         FDK.Log.ERROR( $"Title の書式が不正です。スキップします。[{n行番号}行目]" );
1218                                                         continue;
1219                                                 }
1220
1221                                                 this.Header.str曲名 = items[ 1 ].Trim();
1222                                                 //-----------------
1223                                                 #endregion
1224
1225                                                 continue;
1226                                         }
1227                                         if( str行.StartsWith( "Description", StringComparison.OrdinalIgnoreCase ) )
1228                                         {
1229                                                 #region " Description コマンド "
1230                                                 //-----------------
1231                                                 string[] items = str行.Split( '=' );
1232
1233                                                 if( items.Length != 2 )
1234                                                 {
1235                                                         FDK.Log.ERROR( $"Description の書式が不正です。スキップします。[{n行番号}行目]" );
1236                                                         continue;
1237                                                 }
1238
1239                                                 // 2文字のリテラル "\n" は改行に復号。
1240                                                 this.Header.str説明文 = items[ 1 ].Trim().Replace( @"\n", Environment.NewLine );
1241                                                 //-----------------
1242                                                 #endregion
1243
1244                                                 continue;
1245                                         }
1246                                         //-----------------
1247                                         #endregion
1248
1249                                         // 上記行頭コマンド以外は無視。
1250                                 }
1251                         }
1252                         finally
1253                         {
1254                                 sr.Close();
1255                         }
1256                         //-----------------
1257                         #endregion
1258                 }
1259                 /// <summary>
1260                 /// 現在の Cスコア の内容をデータファイル(*.sstf)に書き出す。
1261                 /// </summary>
1262                 /// <remarks>
1263                 /// 小節線、拍線、Unknown チップは出力しない。
1264                 /// 失敗すれば何らかの例外を発出する。
1265                 /// </remarks>
1266                 public void t曲データファイルを書き出す( string str曲データファイル名, string strヘッダ行 )
1267                 {
1268                         var sw = new StreamWriter( str曲データファイル名, false, Encoding.UTF8 );
1269                         try
1270                         {
1271                                 // ヘッダ行の出力
1272
1273                                 sw.WriteLine( $"{strヘッダ行}" );    // strヘッダ行に"{...}"が入ってても大丈夫なようにstring.Format()で囲む。
1274                                 sw.WriteLine( "" );
1275
1276                                 // ヘッダコマンド行の出力
1277
1278                                 sw.WriteLine( "Title={0}", ( string.IsNullOrEmpty( this.Header.str曲名 ) ) ? "(no title)" : this.Header.str曲名 );
1279                                 if( !string.IsNullOrEmpty( this.Header.str説明文 ) )
1280                                 {
1281                                         // 改行コードは、2文字のリテラル "\n" に置換。
1282                                         sw.WriteLine( "Description=" + this.Header.str説明文.Replace( Environment.NewLine, @"\n" ) );
1283                                 }
1284                                 sw.WriteLine( "" );
1285
1286                                 // 全チップの出力
1287
1288                                 #region " 全チップの最終小節番号を取得する。"
1289                                 //-----------------
1290                                 int n最終小節番号 = 0;
1291                                 foreach( var cc in this.listチップ )
1292                                 {
1293                                         if( cc.小節番号 > n最終小節番号 )
1294                                                 n最終小節番号 = cc.小節番号;
1295                                 }
1296                                 //-----------------
1297                                 #endregion
1298
1299                                 for( int n小節番号 = 0; n小節番号 <= n最終小節番号; n小節番号++ )
1300                                 {
1301                                         #region " dicレーン別チップリストの初期化。"
1302                                         //-----------------
1303                                         Dictionary<レーン種別, List<チップ>> dicレーン別チップリスト = new Dictionary<レーン種別, List<チップ>>();
1304
1305                                         foreach( var lane in Enum.GetValues( typeof( レーン種別 ) ) )
1306                                                 dicレーン別チップリスト[ (レーン種別) lane ] = new List<チップ>();
1307                                         //-----------------
1308                                         #endregion
1309                                         #region " dicレーン別チップリストの構築; n小節番号 の小節に存在するチップのみをレーン別に振り分けて格納する。"
1310                                         //-----------------
1311                                         foreach( var cc in this.listチップ )
1312                                         {
1313                                                 if( cc.小節番号 > n小節番号 )
1314                                                         break;      // listチップは昇順に並んでいるので、これ以上検索しても無駄。
1315
1316                                                 if( cc.チップ種別 == チップ種別.小節線 ||
1317                                                         cc.チップ種別 == チップ種別.拍線 ||
1318                                                         cc.チップ種別 == チップ種別.小節メモ ||
1319                                                         cc.チップ種別 == チップ種別.Unknown )
1320                                                 {
1321                                                         continue;   // これらは出力しないので無視。
1322                                                 }
1323
1324                                                 if( cc.小節番号 == n小節番号 )
1325                                                 {
1326                                                         var lane = レーン種別.Bass;   // 対応するレーンがなかったら Bass でも返しておく。
1327
1328                                                         foreach( var kvp in dicSSTFレーンチップ対応表 )
1329                                                         {
1330                                                                 if( kvp.Value.Contains( cc.チップ種別 ) )
1331                                                                 {
1332                                                                         lane = kvp.Key;
1333                                                                         break;
1334                                                                 }
1335                                                         }
1336
1337                                                         dicレーン別チップリスト[ lane ].Add( cc );
1338                                                 }
1339                                         }
1340                                         //-----------------
1341                                         #endregion
1342
1343                                         #region " Part行 出力。"
1344                                         //-----------------
1345                                         sw.Write( "Part = {0}", n小節番号.ToString() );
1346
1347                                         if( this.list小節長倍率[ n小節番号 ] != 1.0 )
1348                                                 sw.Write( "s" + this.list小節長倍率[ n小節番号 ].ToString() );
1349
1350                                         sw.WriteLine( ";" );
1351                                         //-----------------
1352                                         #endregion
1353                                         #region " Lane, Resolution, Chip 行 出力。"
1354                                         //-----------------
1355                                         foreach( var laneObj in Enum.GetValues( typeof( レーン種別 ) ) )
1356                                         {
1357                                                 var lane = (レーン種別) laneObj;
1358
1359                                                 if( dicレーン別チップリスト[ lane ].Count > 0 )
1360                                                 {
1361                                                         sw.Write( "Lane={0}; ", lane.ToString() );
1362
1363                                                         #region " 新しい解像度を求める。"
1364                                                         //-----------------
1365                                                         int n新しい解像度 = 1;
1366                                                         foreach( var cc in dicレーン別チップリスト[ lane ] )
1367                                                                 n新しい解像度 = FDK.Utilities.最小公倍数を返す( n新しい解像度, cc.小節解像度 );
1368                                                         //-----------------
1369                                                         #endregion
1370                                                         #region " dicレーン別チップリスト[ lane ] 要素の n小節解像度 と n小節内位置 を n新しい解像度 に合わせて修正する。 "
1371                                                         //-----------------
1372                                                         foreach( var cc in dicレーン別チップリスト[ lane ] )
1373                                                         {
1374                                                                 int n倍率 = n新しい解像度 / cc.小節解像度;      // n新しい解像度は n小節解像度 の最小公倍数なので常に割り切れる
1375
1376                                                                 cc.小節解像度 *= n倍率;
1377                                                                 cc.小節内位置 *= n倍率;
1378                                                         }
1379                                                         //-----------------
1380                                                         #endregion
1381
1382                                                         sw.Write( "Resolution = {0}; ", n新しい解像度 );
1383                                                         sw.Write( "Chips = " );
1384
1385                                                         for( int i = 0; i < dicレーン別チップリスト[ lane ].Count; i++ )
1386                                                         {
1387                                                                 チップ cc = dicレーン別チップリスト[ lane ][ i ];
1388
1389                                                                 // 位置を出力。
1390                                                                 sw.Write( cc.小節内位置.ToString() );
1391
1392                                                                 // 属性を出力(あれば)。
1393
1394                                                                 #region " (1) 共通属性 "
1395                                                                 //-----------------
1396                                                                 if( cc.音量 < チップ.最大音量 )
1397                                                                         sw.Write( "v" + cc.音量.ToString() );
1398                                                                 //-----------------
1399                                                                 #endregion
1400                                                                 #region " (2) 専用属性 "
1401                                                                 //-----------------
1402                                                                 switch( cc.チップ種別 )
1403                                                                 {
1404                                                                         case チップ種別.Ride_Cup:
1405                                                                                 sw.Write( 'c' );
1406                                                                                 break;
1407
1408                                                                         case チップ種別.HiHat_Open:
1409                                                                                 sw.Write( 'o' );
1410                                                                                 break;
1411
1412                                                                         case チップ種別.HiHat_HalfOpen:
1413                                                                                 sw.Write( 'h' );
1414                                                                                 break;
1415
1416                                                                         case チップ種別.HiHat_Foot:
1417                                                                                 sw.Write( 'f' );
1418                                                                                 break;
1419
1420                                                                         case チップ種別.Snare_OpenRim:
1421                                                                                 sw.Write( 'o' );
1422                                                                                 break;
1423
1424                                                                         case チップ種別.Snare_ClosedRim:
1425                                                                                 sw.Write( 'c' );
1426                                                                                 break;
1427
1428                                                                         case チップ種別.Snare_Ghost:
1429                                                                                 sw.Write( 'g' );
1430                                                                                 break;
1431
1432                                                                         case チップ種別.Tom1_Rim:
1433                                                                                 sw.Write( 'r' );
1434                                                                                 break;
1435
1436                                                                         case チップ種別.Tom2_Rim:
1437                                                                                 sw.Write( 'r' );
1438                                                                                 break;
1439
1440                                                                         case チップ種別.Tom3_Rim:
1441                                                                                 sw.Write( 'r' );
1442                                                                                 break;
1443
1444                                                                         case チップ種別.BPM:
1445                                                                                 sw.Write( "b" + cc.BPM.ToString() );
1446                                                                                 break;
1447                                                                 }
1448                                                                 //-----------------
1449                                                                 #endregion
1450
1451                                                                 // 区切り文字 または 終端文字 を出力
1452                                                                 sw.Write( ( i == dicレーン別チップリスト[ lane ].Count - 1 ) ? ";" : "," );
1453                                                         }
1454
1455                                                         sw.WriteLine( "" ); // 改行
1456                                                 }
1457                                         }
1458                                         //-----------------
1459                                         #endregion
1460
1461                                         sw.WriteLine( "" ); // 次の Part 前に1行開ける
1462                                 }
1463
1464                                 // メモ(小節単位)の出力
1465
1466                                 #region " dicメモ を小節番号で昇順にソートし直した dic昇順メモ を作成する。"
1467                                 //-----------------
1468                                 var dic昇順メモ = new Dictionary<int, string>();
1469                                 int n最大小節番号 = this.最大小節番号;
1470
1471                                 for( int i = 0; i <= n最大小節番号; i++ )
1472                                 {
1473                                         if( this.dicメモ.ContainsKey( i ) )
1474                                                 dic昇順メモ.Add( i, this.dicメモ[ i ] );
1475                                 }
1476                                 //-----------------
1477                                 #endregion
1478                                 #region " dic昇順メモを出力する。"
1479                                 //-----------------
1480                                 foreach( var kvp in dic昇順メモ )
1481                                 {
1482                                         int n小節番号 = kvp.Key;
1483
1484                                         // 改行コードは、2文字のリ照られる "\n" に置換。
1485                                         string strメモ = kvp.Value.Replace( Environment.NewLine, @"\n" );
1486
1487                                         sw.WriteLine( "PartMemo={0},{1}", n小節番号, strメモ );
1488                                 }
1489                                 sw.WriteLine( "" );
1490                                 //-----------------
1491                                 #endregion
1492                         }
1493                         finally
1494                         {
1495                                 sw.Close();
1496                         }
1497                 }
1498                 /// <summary>
1499                 /// 指定された Config.Speed を考慮し、指定された時間[ms]の間に流れるピクセル数[dpx]を算出して返す。</para>
1500                 /// </summary>
1501                 [Obsolete]
1502                 public int n指定された時間msに対応する符号付きピクセル数を返す( double speed, long n指定時間ms )
1503                 {
1504                         return (int) ( n指定時間ms * スコア.基準譜面速度dpxms * speed );
1505                 }
1506                 /// <summary>
1507                 /// 指定された Config.Speed を考慮し、指定された時間[秒]の間に流れるピクセル数[dpx]を算出して返す。
1508                 /// </summary>
1509                 public double n指定された時間secに対応する符号付きピクセル数を返す( double speed, double 指定時間sec )
1510                 {
1511                         return ( 指定時間sec * スコア.db基準譜面速度dpxsec * speed );
1512                 }
1513                 public double 小節長倍率を取得する( int n小節番号 )
1514                 {
1515                         // list小節長倍率 が短ければ増設する。
1516                         if( n小節番号 >= this.list小節長倍率.Count )
1517                         {
1518                                 int n不足数 = n小節番号 - this.list小節長倍率.Count + 1;
1519                                 for( int i = 0; i < n不足数; i++ )
1520                                         this.list小節長倍率.Add( 1.0 );
1521                         }
1522
1523                         // 小節番号に対応する倍率を返す。
1524                         return this.list小節長倍率[ n小節番号 ];
1525                 }
1526                 public void 小節長倍率を設定する( int n小節番号, double db倍率 )
1527                 {
1528                         // list小節長倍率 が短ければ増設する。
1529                         if( n小節番号 >= this.list小節長倍率.Count )
1530                         {
1531                                 int n不足数 = n小節番号 - this.list小節長倍率.Count + 1;
1532                                 for( int i = 0; i < n不足数; i++ )
1533                                         this.list小節長倍率.Add( 1.0 );
1534                         }
1535
1536                         // 小節番号に対応付けて倍率を登録する。
1537                         this.list小節長倍率[ n小節番号 ] = db倍率;
1538                 }
1539
1540                 /// <summary>
1541                 /// 取出文字列の先頭にある数字(小数点も有効)の連続した部分を取り出して、戻り値として返す。
1542                 /// また、取出文字列から取り出した数字文字列部分を除去した文字列を再度格納する。
1543                 /// </summary>
1544                 protected string t指定された文字列の先頭から数字文字列を取り出す( ref string str取出文字列 )
1545                 {
1546                         int n桁数 = 0;
1547                         while( ( n桁数 < str取出文字列.Length ) && ( char.IsDigit( str取出文字列[ n桁数 ] ) || str取出文字列[ n桁数 ] == '.' ) )
1548                                 n桁数++;
1549
1550                         if( n桁数 == 0 )
1551                                 return "";
1552
1553                         string str数字文字列 = str取出文字列.Substring( 0, n桁数 );
1554                         str取出文字列 = ( n桁数 == str取出文字列.Length ) ? "" : str取出文字列.Substring( n桁数 );
1555
1556                         return str数字文字列;
1557                 }
1558         }
1559 }