OSDN Git Service

結果ステージ.BGMを停止する Action が未接続だったミスを修正。
[strokestylet/CsWin10Desktop3.git] / StrokeStyleT / StrokeStyleT.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.IO;
5 using System.Linq;
6 using System.Windows.Forms;
7 using Microsoft.VisualBasic.ApplicationServices;
8 using FDK;      // for string 拡張
9
10 namespace SST
11 {
12         class StrokeStyleT : FDK.ApplicationBase
13         {
14                 // グローバルリソース (static) 
15                 public static SST.フォルダ フォルダ => ( StrokeStyleT.bs_フォルダ );
16                 public static FDK.入力.Keyboard キーボード入力 => ( StrokeStyleT.bs_キーボード入力 );
17                 public static FDK.入力.MidiIn MIDI入力 => ( StrokeStyleT.bs_MIDI入力 );
18                 public static FDK.メディア.サウンド.WASAPI排他.ExclusiveDevice Wasapiデバイス => ( StrokeStyleT.bs_Wasapiデバイス );
19                 public static Random 乱数 => ( StrokeStyleT.bs_乱数 );
20                 public static SST.ユーザ.ユーザ管理 ユーザ管理 => ( StrokeStyleT.bs_ユーザ管理 );
21                 public static SST.曲.曲ツリー管理 曲ツリー管理 => ( StrokeStyleT.bs_曲ツリー管理 );
22                 public static SSTFormat.スコア 演奏スコア { get; set; } = null;
23                 public static SST.設定.Config Config => ( StrokeStyleT.bs_Config );
24
25                 /// <summary>
26                 /// ビュアーモード用のプロパティ。(static)
27                 /// 各プロパティの値は、起動時のコマンドライン引数を解析して得られる。
28                 /// </summary>
29                 public class ViewerMode
30                 {
31                         public bool ビュアーモードである => ( this.曲ファイルパス.Nullでも空でもない() );
32                         public string 曲ファイルパス { get; set; } = null;
33                         public int 演奏開始小節番号 { get; set; } = 0;
34                         public bool 演奏停止 { get; set; } = false;
35                         public bool ドラム音を使う { get; set; } = false;
36                 }
37                 public static ViewerMode ビュアーモード => ( StrokeStyleT.bs_ビュアーモード );
38
39                 public static void すべての入力デバイスをポーリングする()
40                 {
41                         // hack: 追加の入力デバイスができたら、ここにポーリングコードを追加すること。
42                         StrokeStyleT.キーボード入力?.ポーリングする();
43                         StrokeStyleT.MIDI入力?.ポーリングする();
44                 }
45
46                 // get only static property の初期化。
47                 static StrokeStyleT()
48                 {
49                         // フォルダ変数を真っ先に登録する。(ほかのメンバのコンストラクタでフォルダ変数を利用できるようにするため。)
50                         StrokeStyleT.bs_フォルダ = new SST.フォルダ();
51                         SST.フォルダ.フォルダ変数を追加する( "Static", StrokeStyleT.フォルダ.StaticFolder );
52                         SST.フォルダ.フォルダ変数を追加する( "AppData", StrokeStyleT.フォルダ.AppDataFolder );
53                         SST.フォルダ.フォルダ変数を追加する( "User", null );
54
55                         // その他の static の生成。
56                         StrokeStyleT.bs_ユーザ管理 = new ユーザ.ユーザ管理();
57                         StrokeStyleT.bs_曲ツリー管理 = new 曲.曲ツリー管理();
58                 }
59
60                 protected override void 初期化する()
61                 {
62                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
63                         Debug.Assert( null == this.スレッド排他領域.デバイスリソース, "デバイスリソースの作成前であること。" );
64
65                         StrokeStyleT.bs_ビュアーモード = this.コマンドライン引数を解析する(
66                                 Environment.GetCommandLineArgs().Skip( 1 ) );   // 最初の要素は exe ファイル名なのでスキップする。
67
68                         this.MainForm.Text = $"StrokeStyle<T> {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()}";
69                         this.設計画面サイズdpx = new SharpDX.Size2F( 1920, 1080 );     // 設計画面サイズdpx(固定)
70
71                         #region " コンフィグ を初期化する。"
72                         //----------------
73                         FDK.Log.Info( "コンフィグを初期化します。" );
74                         StrokeStyleT.bs_Config = new 設定.Config();
75                         StrokeStyleT.bs_Config.ConfigXmlを読み込む();
76                         //----------------
77                         #endregion
78                         #region " コンフィグで指定されたウィンドウサイズに変更。"
79                         //----------------
80                         this.MainForm.ClientSize = new System.Drawing.Size( StrokeStyleT.Config.物理画面サイズpx.Width, StrokeStyleT.Config.物理画面サイズpx.Height );
81                         //----------------
82                         #endregion
83
84                         #region " System.Stopwatch が高解像度タイマを使わないならエラー。"
85                         //-----------------
86                         if( false == System.Diagnostics.Stopwatch.IsHighResolution )
87                                 throw new SSTException( "このシステムは、高解像度タイマをサポートしていません。" );
88                         //-----------------
89                         #endregion
90                         #region " MediaFoundation を起動する。"
91                         //-----------------
92                         SharpDX.MediaFoundation.MediaManager.Startup();
93                         //-----------------
94                         #endregion
95                         #region " Sleep 精度を上げる。"
96                         //-----------------
97                         StrokeStyleT.timeBeginPeriod( 1 );
98                         //-----------------
99                         #endregion
100
101                         #region " ステージのActionを接続する。"
102                         //----------------
103                         this.結果ステージ.演奏ステージインスタンスを取得する = () => this.演奏ステージ;
104                         this.結果ステージ.BGMを終了する = () => this.演奏ステージ.BGMを解放する();
105                         //----------------
106                         #endregion
107                         #region " ユーザを初期化する。"
108                         //-----------------
109                         FDK.Log.Info( "ユーザ情報を初期化します。" );
110                         StrokeStyleT.ユーザ管理.UsersXmlを読み込む();
111
112                         // ユーザ別の初期化。
113                         foreach( var ユーザ in StrokeStyleT.ユーザ管理.ユーザリスト )
114                                 ユーザ.SourcesXmlを読み込む();
115                         //-----------------
116                         #endregion
117                         #region " WASAPI デバイスを初期化する。"
118                         //----------------
119                         StrokeStyleT.bs_Wasapiデバイス = new FDK.メディア.サウンド.WASAPI排他.ExclusiveDevice();
120                         StrokeStyleT.bs_Wasapiデバイス.初期化する( 15.0f );
121                         //----------------
122                         #endregion
123                         #region " キーボード入力 を初期化する。"
124                         //-----------------
125                         FDK.Log.Info( "キーボード入力デバイスを初期化します。" );
126                         StrokeStyleT.bs_キーボード入力 = new FDK.入力.Keyboard( this.MainForm.Handle );
127                         //-----------------
128                         #endregion
129                         #region " MIDI入力 を初期化する。"
130                         //-----------------
131                         FDK.Log.Info( "MIDI入力デバイスを初期化します。" );
132                         StrokeStyleT.bs_MIDI入力 = new FDK.入力.MidiIn();
133                         //-----------------
134                         #endregion
135
136                         this.現在のステージ = this.最初のダミーステージ;
137
138 //#warning 全画面モード切替えを KeyDown で仮実装。
139                         this.MainForm.KeyDown += ( target, arg ) => {
140                                 // Alt+Enter → 画面モードの切り替え
141 //                              if( ( arg.KeyCode == System.Windows.Forms.Keys.Return ) && ( arg.Modifiers == Keys.Alt ) )
142 //                                      this.全画面モードとウィンドウモードを切り替える();
143                         };
144
145                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
146                 }
147                 protected override void 終了する()
148                 {
149                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
150
151                         Debug.Assert( null != this.スレッド排他領域.デバイスリソース, "デバイスリソースが解放される前であること。" );
152
153                         #region " ステージを終了し、解放する。"
154                         //----------------
155                         if( ( null != this.現在のステージ ) && ( this.現在のステージ.活性化している ) )
156                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
157
158                         this.最初のダミーステージ = null;
159                         this.起動ステージ = null;
160                         this.タイトルステージ = null;
161                         this.ログインステージ = null;
162                         this.選曲ステージ = null;
163                         this.曲読込ステージ = null;
164                         this.演奏ステージ = null;
165                         this.結果ステージ = null;
166                         this.ビュアーステージ = null;
167                         //----------------
168                         #endregion
169                         #region " MIDI入力 を解放する。"
170                         //-----------------
171                         FDK.Log.Info( "MIDI入力デバイスを解放します。" );
172                         FDK.Utilities.解放する( ref StrokeStyleT.bs_MIDI入力 );
173                         //-----------------
174                         #endregion
175                         #region " キーボード入力 を解放する。"
176                         //-----------------
177                         FDK.Log.Info( "キーボード入力デバイスを解放します。" );
178                         FDK.Utilities.解放する( ref StrokeStyleT.bs_キーボード入力 );
179                         //-----------------
180                         #endregion
181                         #region " WASAPIデバイスを解放する。"
182                         //----------------
183                         FDK.Log.Info( "WASAPIデバイスを解放します。" );
184                         FDK.Utilities.解放する( ref StrokeStyleT.bs_Wasapiデバイス );
185                         //----------------
186                         #endregion
187                         #region " コンフィグを解放する。"
188                         //----------------
189                         FDK.Log.Info( "コンフィグを解放します。" );
190                         StrokeStyleT.bs_Config = null;
191                         //----------------
192                         #endregion
193                         #region " MediaFoundation を終了する。"
194                         //-----------------
195                         SharpDX.MediaFoundation.MediaManager.Shutdown();
196                         //-----------------
197                         #endregion
198
199                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
200                 }
201                 protected override void シーンを描画する()
202                 {
203                         // このメソッドは、GUIスレッドではなく進行描画スレッドから呼び出されるので注意。(FDK.ApplicationBase.進行描画スレッド処理() を参照。)
204
205                         this.スレッド排他領域.WriteLock( () => {
206
207                                 #region " 描画の準備を行う。"
208                                 //----------------
209                                 var dr = this.スレッド排他領域.デバイスリソース;
210                                 var d3dDevice = (SharpDX.Direct3D11.Device) null;
211                                 using( var d3dLock = new FDK.同期.AutoD3DDeviceLock( dr.DXGIDeviceManager, out d3dDevice ) )
212                                 using( d3dDevice )
213                                 using( var d3dContext = d3dDevice.ImmediateContext )
214                                 {
215                                         // 既定のD3Dレンダーターゲットビューを黒でクリアする。
216                                         d3dContext.ClearRenderTargetView( dr.D3DRenderTargetView, SharpDX.Color4.Black );
217
218                                         // 深度バッファを 1.0f でクリアする。
219                                         d3dContext.ClearDepthStencilView(
220                                                 dr.D3DDepthStencilView,
221                                                 SharpDX.Direct3D11.DepthStencilClearFlags.Depth,
222                                                 depth: 1.0f,
223                                                 stencil: 0 );
224                                 }
225                                 //----------------
226                                 #endregion
227
228                                 this.現在のステージ?.進行描画する( this.スレッド排他領域.デバイスリソース );
229
230                         } );
231
232                         // スワップチェーンを表示する。垂直帰線待ちなどで時間がかかるので、この部分はスレッド排他領域の外に配置すること。
233                         if( false == this.スレッド排他領域.アプリを終了せよ )
234                         {
235                                 this.スレッド排他領域.デバイスリソース.SwapChain.Present(
236                                         ( StrokeStyleT.Config.垂直帰線待ちを行う ) ? 1 : 0,
237                                         SharpDX.DXGI.PresentFlags.None );
238                         }
239
240                         // ステージの状態をチェックし、必要あれば遷移またはアプリを終了する。
241                         this.スレッド排他領域.WriteLock( () => {
242
243                                 if( null != this.現在のステージ )
244                                 {
245                                         switch( this.現在のステージ.GetType().Name )
246                                         {
247                                                 case nameof( ステージ.ステージ ):
248                                                         #region " ビュアーモード → ビュアーステージへ。"
249                                                         //----------------
250                                                         if( StrokeStyleT.ビュアーモード.ビュアーモードである )
251                                                         {
252                                                                 this.ビュアーステージ.活性化する( this.スレッド排他領域.デバイスリソース );
253                                                                 this.現在のステージ = this.ビュアーステージ;
254                                                         }
255                                                         //----------------
256                                                         #endregion
257                                                         #region " 通常モード → 起動ステージへ。"
258                                                         //----------------
259                                                         else
260                                                         {
261                                                                 this.起動ステージ.活性化する( this.スレッド排他領域.デバイスリソース );
262                                                                 this.現在のステージ = this.起動ステージ;
263                                                         }
264                                                         //----------------
265                                                         #endregion
266                                                         break;
267
268                                                 case nameof( ステージ.起動.起動ステージ ):
269                                                         #region " 終了 → タイトルステージへ。"
270                                                         //---------------
271                                                         if( this.起動ステージ.現在のフェーズ == ステージ.起動.起動ステージ.フェーズ.終了 )
272                                                         {
273                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
274                                                                 this.現在のステージ = this.タイトルステージ;
275                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
276                                                         }
277                                                         //---------------
278                                                         #endregion
279                                                         break;
280
281                                                 case nameof( ステージ.タイトル.タイトルステージ ):
282                                                         #region " 確定 → ログインステージへ。"
283                                                         //---------------
284                                                         if( this.タイトルステージ.現在のフェーズ == ステージ.タイトル.タイトルステージ.フェーズ.確定 )
285                                                         {
286                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
287                                                                 this.現在のステージ = this.ログインステージ;
288                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
289                                                         }
290                                                         //---------------
291                                                         #endregion
292                                                         #region " キャンセル → アプリを終了する。"
293                                                         //---------------
294                                                         else if( this.タイトルステージ.現在のフェーズ == ステージ.タイトル.タイトルステージ.フェーズ.キャンセル )
295                                                         {
296                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
297                                                                 this.現在のステージ = null;
298                                                                 this.スレッド排他領域.アプリを終了せよ = true;
299                                                         }
300                                                         //---------------
301                                                         #endregion
302                                                         break;
303
304                                                 case nameof( ステージ.ログイン.ログインステージ ):
305                                                         #region " 確定 → ログイン処理を行って、選曲ステージへ。"
306                                                         //---------------
307                                                         if( this.ログインステージ.現在のフェーズ == ステージ.ログイン.ログインステージ.フェーズ.確定 )
308                                                         {
309                                                                 // ログイン処理(1) ユーザリストに対してユーザを選択する。
310                                                                 StrokeStyleT.ユーザ管理.ユーザを選択する( this.ログインステージ.ユーザインデックス );
311                                                                 FDK.Log.Info( $"ユーザが選択されました。[{StrokeStyleT.ユーザ管理.現在選択されているユーザ.名前}]" );
312
313                                                                 // ログイン処理(2) 選択ユーザの曲ツリーを構築する。
314                                                                 StrokeStyleT.ユーザ管理.現在選択されているユーザ.曲を検索して曲ツリーを構築する();
315
316                                                                 // ログイン処理(3) 構築した曲ツリーを曲リスト管理に登録する。
317                                                                 StrokeStyleT.曲ツリー管理.現在の管理対象ツリー = StrokeStyleT.ユーザ管理.現在選択されているユーザ.曲ツリーのルートノード;
318
319                                                                 // 選曲ステージへ。
320                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
321                                                                 this.現在のステージ = this.選曲ステージ;
322                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
323                                                         }
324                                                         //---------------
325                                                         #endregion
326                                                         #region " キャンセル → タイトルステージへ。"
327                                                         //---------------
328                                                         else if( this.ログインステージ.現在のフェーズ == ステージ.ログイン.ログインステージ.フェーズ.キャンセル )
329                                                         {
330                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
331                                                                 this.現在のステージ = this.タイトルステージ;
332                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
333                                                         }
334                                                         //---------------
335                                                         #endregion
336                                                         break;
337
338                                                 case nameof( ステージ.選曲.選曲ステージ ):
339                                                         #region " 曲確定 → 曲読込ステージへ。"
340                                                         //---------------
341                                                         if( this.選曲ステージ.現在のフェーズ == ステージ.選曲.選曲ステージ.フェーズ.曲確定 )
342                                                         {
343                                                                 // 曲ノードが選択されていることを確認。
344                                                                 Trace.Assert( null != StrokeStyleT.曲ツリー管理.現在選択されているノード, "[バグあり] 選択曲が null です。" );
345                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
346                                                                 this.現在のステージ = this.曲読込ステージ;
347                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
348                                                         }
349                                                         //---------------
350                                                         #endregion
351                                                         #region " キャンセル → アプリを終了する。"
352                                                         //---------------
353                                                         else if( this.選曲ステージ.現在のフェーズ == ステージ.選曲.選曲ステージ.フェーズ.キャンセル )
354                                                         {
355                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
356                                                                 this.現在のステージ = null;
357                                                                 this.スレッド排他領域.アプリを終了せよ = true;
358                                                         }
359                                                         //---------------
360                                                         #endregion
361                                                         break;
362
363                                                 case nameof( ステージ.曲読込.曲読込ステージ ):
364                                                         #region " 終了 → 演奏ステージへ。"
365                                                         //--------------------
366                                                         if( this.曲読込ステージ.現在のフェーズ == ステージ.曲読込.曲読込ステージ.フェーズ.終了 )
367                                                         {
368                                                                 // 演奏スコアインスタンスが作成されていることを確認。
369                                                                 Debug.Assert( null != StrokeStyleT.演奏スコア );
370
371                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
372                                                                 this.現在のステージ = this.演奏ステージ;
373                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
374                                                         }
375                                                         //--------------------
376                                                         #endregion
377                                                         break;
378
379                                                 case nameof( ステージ.演奏.演奏ステージ ):
380                                                         #region " 演奏終了 → 結果ステージへ。"
381                                                         //--------------------
382                                                         if( this.演奏ステージ.現在のフェーズ == ステージ.演奏.演奏ステージ.フェーズ.クリアor失敗 )
383                                                         {
384                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
385                                                                 this.現在のステージ = this.結果ステージ;
386                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
387                                                         }
388                                                         //--------------------
389                                                         #endregion
390                                                         #region " キャンセル → 選曲ステージへ。"
391                                                         //--------------------
392                                                         if( this.演奏ステージ.現在のフェーズ == ステージ.演奏.演奏ステージ.フェーズ.キャンセル )
393                                                         {
394 #warning 結果ステージのデバッグのため、演奏キャンセル時も結果ステージへ遷移する。
395                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
396                                                                 this.現在のステージ = this.結果ステージ;
397                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
398                                                                 //this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
399                                                                 //this.現在のステージ = this.選曲ステージ;
400                                                                 //this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
401                                                         }
402                                                         //--------------------
403                                                         #endregion
404                                                         break;
405
406                                                 case nameof( ステージ.結果.結果ステージ ):
407                                                         #region " 終了 → 選曲ステージへ。"
408                                                         //--------------------
409                                                         if( this.結果ステージ.現在のフェーズ == ステージ.結果.結果ステージ.フェーズ.終了 )
410                                                         {
411                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
412                                                                 this.現在のステージ = this.選曲ステージ;
413                                                                 this.現在のステージ.活性化する( this.スレッド排他領域.デバイスリソース );
414                                                         }
415                                                         //--------------------
416                                                         #endregion
417                                                         break;
418
419                                                 case nameof( ステージ.演奏.ビュアーステージ ):
420                                                         #region " 終了 → アプリを終了する。"
421                                                         //---------------
422                                                         if( this.ビュアーステージ.現在のフェーズ == ステージ.演奏.ビュアーステージ.フェーズ.終了 )
423                                                         {
424                                                                 this.現在のステージ.非活性化する( this.スレッド排他領域.デバイスリソース );
425                                                                 this.現在のステージ = null;
426                                                                 this.スレッド排他領域.アプリを終了せよ = true;
427                                                         }
428                                                         //---------------
429                                                         #endregion
430                                                         break;
431                                         }
432                                 }
433
434                         } );
435                 }
436                 protected override void デバイス依存リソースを再構築する()
437                 {
438                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
439
440                         this.現在のステージ?.デバイス依存リソースを作成する( this.スレッド排他領域.デバイスリソース );
441
442                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
443                 }
444                 protected override void デバイス依存リソースを解放する()
445                 {
446                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
447
448                         this.現在のステージ?.デバイス依存リソースを解放する( this.スレッド排他領域.デバイスリソース );
449
450                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
451                 }
452                 /// <summary>
453                 /// アプリが二重起動されたときに発生するイベント。
454                 /// </summary>
455                 /// <remarks>
456                 /// 後続のインスタンスは起動せず、既存のインスタンスに対してこのイベントが発生する。
457                 /// eventArg.CommandLine で、後続のインスタンスのコマンドライン引数を確認することができる。
458                 /// </remarks>
459                 protected override void OnStartupNextInstance( StartupNextInstanceEventArgs eventArgs )
460                 {
461                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
462
463                         if( StrokeStyleT.ビュアーモード.ビュアーモードである )
464                         {
465                                 this.新しいビュアーモード = this.コマンドライン引数を解析する( eventArgs.CommandLine );
466                                 this.ビュアーモードアクションを実行せよ.状態 = FDK.同期.TriStateEvent.状態種別.ON;
467                         }
468                         else
469                         {
470                                 FDK.Log.ERROR( "現在、ビュアーモードではありません。" );
471                         }
472
473                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
474                 }
475
476                 // 各ステージの、唯一のインスタンス。最終的に null を代入するので、readonlyにはしない。
477                 protected SST.ステージ.ステージ 最初のダミーステージ = new ステージ.ステージ();
478                 protected SST.ステージ.起動.起動ステージ 起動ステージ = new ステージ.起動.起動ステージ();
479                 protected SST.ステージ.タイトル.タイトルステージ タイトルステージ = new ステージ.タイトル.タイトルステージ();
480                 protected SST.ステージ.ログイン.ログインステージ ログインステージ = new ステージ.ログイン.ログインステージ();
481                 protected SST.ステージ.選曲.選曲ステージ 選曲ステージ = new ステージ.選曲.選曲ステージ();
482                 protected SST.ステージ.曲読込.曲読込ステージ 曲読込ステージ = new ステージ.曲読込.曲読込ステージ();
483                 protected SST.ステージ.演奏.演奏ステージ 演奏ステージ = new ステージ.演奏.演奏ステージ();
484                 protected SST.ステージ.結果.結果ステージ 結果ステージ = new ステージ.結果.結果ステージ();
485                 protected SST.ステージ.演奏.ビュアーステージ ビュアーステージ = new ステージ.演奏.ビュアーステージ();
486
487                 private SST.ステージ.ステージ 現在のステージ = null;
488
489                 // ビュアーモード連携。
490                 private ViewerMode 新しいビュアーモード = null;
491                 private FDK.同期.TriStateEvent ビュアーモードアクションを実行せよ = new FDK.同期.TriStateEvent( FDK.同期.TriStateEvent.状態種別.OFF );
492
493                 #region " バックストア。"
494                 //----------------
495                 private static SST.フォルダ bs_フォルダ = null;
496                 private static FDK.入力.Keyboard bs_キーボード入力 = null;
497                 private static FDK.入力.MidiIn bs_MIDI入力 = null;
498                 private static FDK.メディア.サウンド.WASAPI排他.ExclusiveDevice bs_Wasapiデバイス = null;
499                 private static readonly Random bs_乱数 = new Random( DateTime.Now.Millisecond );
500                 private static SST.ユーザ.ユーザ管理 bs_ユーザ管理 = null;
501                 private static SST.曲.曲ツリー管理 bs_曲ツリー管理 = null;
502                 private static SST.設定.Config bs_Config = null;
503                 private static SST.StrokeStyleT.ViewerMode bs_ビュアーモード = new ViewerMode();
504                 //----------------
505                 #endregion
506
507                 /// <summary>
508                 /// コマンドライン引数を解析して、ビュアーモードの設定があればそれを返す。
509                 /// </summary>
510                 /// <param name="args">コマンドライン引数の列挙。exeファイル名は含まない。</param>
511                 /// <return>引数を反映したビュアーモード変数。</return>
512                 private ViewerMode コマンドライン引数を解析する( IEnumerable<string> args )
513                 {
514                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
515
516                         var vmode = new ViewerMode() {
517                                 曲ファイルパス = null,
518                                 演奏停止 = false,
519                                 演奏開始小節番号 = 0,
520                                 ドラム音を使う = false,
521                         };
522
523                         try
524                         {
525                                 if( 0 == args.Count() )
526                                 {
527                                         FDK.Log.Info( "引数は指定されていません。" );      // ビュアーモードではない。
528                                         return vmode;
529                                 }
530
531                                 try
532                                 {
533                                         // オプションを定義する。
534                                         var optionSet = new Mono.Options.OptionSet() {
535                                                 "Usage: StrokeStyleT [File] [OPTIONS]+",
536                                                 "  File\t\t\t\tビュアーモードで表示する曲ファイル名です。",
537                                                 { "p=|part=", "ビュアーモードで起動し、指定された小節番号から演奏を開始します。小節番号を省略すると、先頭から再生します。", (int v) => { vmode.演奏開始小節番号 = v; }  },
538                                                 { "s|stop", "ビュアーモードで演奏中であれば、演奏を停止します。", v => { vmode.演奏停止 = ( v != null ); } },
539                                                 { "d|drums", "ビュアーモードで、チップヒット時に内蔵のドラム音を再生します。", v => { vmode.ドラム音を使う = ( v != null ); } },
540                                         };
541
542                                         // オプションを解析する。
543                                         List<string> ファイルパスs = optionSet.Parse( args );
544
545                                         // 解析結果。
546                                         if( vmode.演奏停止 )
547                                         {
548                                                 // (A) 演奏停止(曲ファイルパスは省略可。)
549                                                 FDK.Log.Info( "演奏停止" );
550                                         }
551                                         else
552                                         {
553                                                 // (B) 演奏開始(曲ファイルパスは必須。)
554                                                 if( 0 < ファイルパスs.Count )
555                                                 {
556                                                         if( File.Exists( ファイルパスs[ 0 ] ) )
557                                                         {
558                                                                 vmode.曲ファイルパス = ファイルパスs[ 0 ];
559                                                         }
560                                                         else
561                                                         {
562                                                                 vmode.曲ファイルパス = null;    // ファイルが存在しなかったら null 。
563                                                                 throw new Mono.Options.OptionException( $"ファイルが存在しません。[{FDK.フォルダ.絶対パスをフォルダ変数付き絶対パスに変換して返す( ファイルパスs[ 0 ] )}]", "File" );
564                                                         }
565                                                 }
566                                                 else
567                                                 {
568                                                         throw new Mono.Options.OptionException( "ファイルの指定がありません。", "File" );
569                                                 }
570
571                                                 FDK.Log.Info( $"曲ファイルパス: {vmode.曲ファイルパス}" );
572                                                 FDK.Log.Info( $"開始小節番号: {vmode.演奏開始小節番号}" );
573                                                 FDK.Log.Info( $"ドラム音: {vmode.ドラム音を使う}" );
574                                         }
575                                 }
576                                 catch( Mono.Options.OptionException e )
577                                 {
578                                         FDK.Log.ERROR( $"{e.Message}" );
579                                 }
580
581                                 return vmode;
582                         }
583                         finally
584                         {
585                                 FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
586                         }
587                 }
588
589                 #region " Win32 API "
590                 //-----------------
591                 [ System.Runtime.InteropServices.DllImport( "winmm.dll", EntryPoint = "timeBeginPeriod" )]
592                 private static extern uint timeBeginPeriod( uint uMilliseconds );
593
594                 [System.Runtime.InteropServices.DllImport( "winmm.dll", EntryPoint = "timeEndPeriod" )]
595                 private static extern uint timeEndPeriod( uint uMilliseconds );
596                 //-----------------
597                 #endregion
598         }
599 }