OSDN Git Service

少々リファクタ。
[strokestylet/CsWin10Desktop3.git] / StrokeStyleT / StrokeStyleT.cs
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.IO;
6 using System.IO.Pipes;
7 using System.Linq;
8 using System.Windows.Forms;
9 using FDK;      // for string 拡張
10
11 namespace SST
12 {
13         class StrokeStyleT
14         {
15                 // グローバルリソース (static) 
16                 public static SST.フォルダ フォルダ
17                         => ( StrokeStyleT.bs_フォルダ );
18
19                 public static FDK.入力.Keyboard キーボード入力
20                         => ( StrokeStyleT.bs_キーボード入力 );
21
22                 public static FDK.入力.MidiIn MIDI入力
23                         => ( StrokeStyleT.bs_MIDI入力 );
24
25                 public static FDK.メディア.サウンド.WASAPI排他.ExclusiveDevice Wasapiデバイス
26                         => ( StrokeStyleT.bs_Wasapiデバイス );
27
28                 public static Random 乱数
29                         => ( StrokeStyleT.bs_乱数 );
30
31                 public static SST.ユーザ.ユーザ管理 ユーザ管理
32                         => ( StrokeStyleT.bs_ユーザ管理 );
33
34                 public static SST.曲.曲ツリー管理 曲ツリー管理
35                         => ( StrokeStyleT.bs_曲ツリー管理 );
36
37                 public static SSTFormat.スコア 演奏スコア { get; set; } = null;
38
39                 public static SST.設定.Config Config
40                         => ( StrokeStyleT.bs_Config );
41
42                 public static bool ビュアーモードである
43                 {
44                         get;
45                         set;
46                 } = false;
47                 public static bool ビュアーモードではない
48                 {
49                         get { return !StrokeStyleT.ビュアーモードである; }
50                         set { StrokeStyleT.ビュアーモードである = !value; }
51                 }
52
53                 public static ConcurrentQueue<SST.ステージ.演奏.ビュアーメッセージ> ビュアーメッセージキュー
54                         => ( StrokeStyleT.bs_ビュアーメッセージキュー );
55
56                 public static void すべての入力デバイスをポーリングする()
57                 {
58                         // hack: 追加の入力デバイスができたら、ここにポーリングコードを追加すること。
59                         StrokeStyleT.キーボード入力?.ポーリングする();
60                         StrokeStyleT.MIDI入力?.ポーリングする();
61                 }
62
63                 static StrokeStyleT()
64                 {
65                         // フォルダ変数を真っ先に登録する。(ほかのメンバのコンストラクタでフォルダ変数を利用できるようにするため。)
66                         StrokeStyleT.bs_フォルダ = new SST.フォルダ();
67                         SST.フォルダ.フォルダ変数を追加する( "Static", StrokeStyleT.フォルダ.StaticFolder );
68                         SST.フォルダ.フォルダ変数を追加する( "AppData", StrokeStyleT.フォルダ.AppDataFolder );
69                         SST.フォルダ.フォルダ変数を追加する( "User", null );
70                 }
71                 public StrokeStyleT()
72                 {
73                         this.State = ApplicationState.起動;
74                         this.MainForm = new SharpDX.Windows.RenderForm();
75                         this.MainForm.AllowUserResizing = false;        // ユーザはフォームサイズを変更できない。
76                         this.MainForm.UserResized += フォームサイズが変更された;
77                 }
78                 public void Run()
79                 {
80                         Debug.Assert( null != this.MainForm );
81                         SharpDX.Windows.RenderLoop.Run( this.MainForm, () => {
82
83                                 // アプリケーションの状態に応じて処理分岐。
84                                 switch( this.State )
85                                 {
86                                         case ApplicationState.起動:
87                                                 this.初期化する();
88                                                 this.State = ApplicationState.進行描画;
89                                                 break;
90
91                                         case ApplicationState.進行描画:
92                                                 this.進行描画する();
93                                                 if( this.State == ApplicationState.終了 )
94                                                         this.終了する();
95                                                 break;
96
97                                         case ApplicationState.終了:
98                                                 // 何もしない
99                                                 break;
100                                 }
101
102                         } );
103                 }
104
105                 protected SharpDX.Windows.RenderForm MainForm = null;
106                 protected enum ApplicationState { 起動, 進行描画, 終了 }
107                 protected ApplicationState State;
108                 protected SharpDX.Size2F 設計画面サイズdpx = SharpDX.Size2F.Empty;
109                 protected FDK.メディア.デバイスリソース デバイスリソース = null;
110                 protected SST.ステージ.ステージ 最初のダミーステージ = null;
111                 protected SST.ステージ.起動.起動ステージ 起動ステージ = null;
112                 protected SST.ステージ.タイトル.タイトルステージ タイトルステージ = null;
113                 protected SST.ステージ.ログイン.ログインステージ ログインステージ = null;
114                 protected SST.ステージ.選曲.選曲ステージ 選曲ステージ = null;
115                 protected SST.ステージ.曲読込.曲読込ステージ 曲読込ステージ = null;
116                 protected SST.ステージ.演奏.演奏ステージ 演奏ステージ = null;
117                 protected SST.ステージ.結果.結果ステージ 結果ステージ = null;
118                 protected SST.ステージ.ステージ 現在のステージ = null;
119
120                 protected void 初期化する()
121                 {
122                         FDK.Log.現在のスレッドに名前をつける( "Render" );
123                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
124                         Debug.Assert( null == this.デバイスリソース, "デバイスリソースの作成前であること。" );
125
126                         StrokeStyleT.bs_ユーザ管理 = new ユーザ.ユーザ管理();
127                         StrokeStyleT.bs_曲ツリー管理 = new 曲.曲ツリー管理();
128
129                         #region " コマンドライン引数を解析する。"
130                         //----------------
131                         // 最初の要素は exe ファイル名なのでスキップする。
132                         this.コマンドライン引数を解析する( Environment.GetCommandLineArgs().Skip( 1 ) );
133                         //----------------
134                         #endregion
135
136                         #region " 高解像度タイマを使えないならエラー。"
137                         //-----------------
138                         if( false == System.Diagnostics.Stopwatch.IsHighResolution )
139                                 throw new SSTException( "このシステムは、高解像度タイマをサポートしていません。" );
140                         //-----------------
141                         #endregion
142                         #region " MediaFoundation を起動する。"
143                         //-----------------
144                         SharpDX.MediaFoundation.MediaManager.Startup();
145                         //-----------------
146                         #endregion
147                         #region " Sleep 精度を上げる。"
148                         //-----------------
149                         Win32.timeBeginPeriod( 1 );
150                         //-----------------
151                         #endregion
152
153                         #region " コンフィグ を初期化する。"
154                         //----------------
155                         FDK.Log.Info( "コンフィグを初期化します。" );
156                         StrokeStyleT.bs_Config = new 設定.Config();
157                         StrokeStyleT.bs_Config.ConfigXmlを読み込む();
158                         //----------------
159                         #endregion
160
161                         #region " コンフィグで指定されたウィンドウサイズに変更する。"
162                         //----------------
163                         this.MainForm.ClientSize = new System.Drawing.Size( StrokeStyleT.Config.物理画面サイズpx.Width, StrokeStyleT.Config.物理画面サイズpx.Height );
164                         //----------------
165                         #endregion
166                         #region " フォームタイトルと設計画面サイズを設定する。"
167                         //----------------
168                         this.MainForm.Text = $"StrokeStyle<T> {System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()}";
169                         if( StrokeStyleT.ビュアーモードである )
170                                 this.MainForm.Text += " (Viewer)";
171
172                         this.設計画面サイズdpx = new SharpDX.Size2F( 1920f, 1080f );
173                         //----------------
174                         #endregion
175
176                         #region " デバイスリソースを作成する。"
177                         //----------------
178                         FDK.Log.BeginInfo( "デバイスリソースを作成します。" );
179                         FDK.Log.Info( $"設計画面サイズ: {this.設計画面サイズdpx}" );
180                         FDK.Log.Info( $"物理画面サイズ: {this.MainForm.ClientSize}" );
181                         this.デバイスリソース = new FDK.メディア.デバイスリソース( this.設計画面サイズdpx );
182                         this.デバイスリソース.すべてのリソースを作成する( this.MainForm.ClientSize, this.MainForm.Handle );
183                         //----------------
184                         #endregion
185
186                         #region " ステージを生成する。"
187                         //----------------
188                         this.最初のダミーステージ = new ステージ.ステージ();
189                         this.起動ステージ = new ステージ.起動.起動ステージ();
190                         this.タイトルステージ = new ステージ.タイトル.タイトルステージ();
191                         this.ログインステージ = new ステージ.ログイン.ログインステージ();
192                         this.選曲ステージ = new ステージ.選曲.選曲ステージ();
193                         this.曲読込ステージ = new ステージ.曲読込.曲読込ステージ();
194                         this.演奏ステージ = new ステージ.演奏.演奏ステージ();
195                         this.結果ステージ = new ステージ.結果.結果ステージ();
196
197                         // 外部依存アクションを接続する。
198                         this.曲読込ステージ.読込曲のファイルパスを取得する = () => ( ( StrokeStyleT.曲ツリー管理.現在選択されているノード as SST.曲.MusicNode )?.sstfファイルパス );
199                         this.結果ステージ.演奏ステージインスタンスを取得する = () => ( this.演奏ステージ );
200                         this.結果ステージ.BGMを終了する = () => { this.演奏ステージ.BGMを解放する(); };
201                         //----------------
202                         #endregion
203                         #region " ユーザを初期化する。"
204                         //-----------------
205                         FDK.Log.Info( "ユーザ情報を初期化します。" );
206                         StrokeStyleT.ユーザ管理.UsersXmlを読み込む();
207
208                         // ユーザ別の初期化。
209                         foreach( var ユーザ in StrokeStyleT.ユーザ管理.ユーザリスト )
210                                 ユーザ.SourcesXmlを読み込む();
211                         //-----------------
212                         #endregion
213                         #region " WASAPI デバイスを初期化する。"
214                         //----------------
215                         StrokeStyleT.bs_Wasapiデバイス = new FDK.メディア.サウンド.WASAPI排他.ExclusiveDevice();
216                         StrokeStyleT.bs_Wasapiデバイス.初期化する( 15.0f );
217                         //----------------
218                         #endregion
219                         #region " キーボード入力 を初期化する。"
220                         //-----------------
221                         FDK.Log.Info( "キーボード入力デバイスを初期化します。" );
222                         StrokeStyleT.bs_キーボード入力 = new FDK.入力.Keyboard( this.MainForm.Handle );
223                         //-----------------
224                         #endregion
225                         #region " MIDI入力 を初期化する。"
226                         //-----------------
227                         FDK.Log.Info( "MIDI入力デバイスを初期化します。" );
228                         StrokeStyleT.bs_MIDI入力 = new FDK.入力.MidiIn();
229                         //-----------------
230                         #endregion
231
232                         FDK.Log.Info( "最初のダミーステージを開始します。" );
233                         this.現在のステージ = this.最初のダミーステージ;
234
235                         //#warning 全画面モード切替えを KeyDown で仮実装。
236                         this.MainForm.KeyDown += ( target, arg ) => {
237
238                                 // F11 → 画面モードの切り替え
239                                 if( arg.KeyCode == System.Windows.Forms.Keys.F11 )
240                                 {
241                                         this.全画面モードとウィンドウモードを切り替える();
242                                         arg.Handled = true;
243                                 }
244                         };
245
246                         Debug.Assert( SharpDX.Size2F.Empty != this.設計画面サイズdpx, "初期化メソッド内で設計画面サイズを設定してあること。" );
247                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
248                 }
249                 protected void 終了する()
250                 {
251                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
252                         Debug.Assert( null != this.デバイスリソース, "デバイスリソースが解放される前であること。" );
253
254                         #region " ステージを終了し、解放する。"
255                         //----------------
256                         if( ( null != this.現在のステージ ) && ( this.現在のステージ.活性化している ) )      // 念のため
257                                 this.現在のステージ.非活性化する( this.デバイスリソース );
258
259                         this.最初のダミーステージ = null;
260                         this.起動ステージ = null;
261                         this.タイトルステージ = null;
262                         this.ログインステージ = null;
263                         this.選曲ステージ = null;
264                         this.曲読込ステージ = null;
265                         this.演奏ステージ = null;
266                         this.結果ステージ = null;
267                         //----------------
268                         #endregion
269
270                         #region " デバイスリソースを解放する。"
271                         //----------------
272                         FDK.Utilities.解放する( ref this.デバイスリソース );
273                         //----------------
274                         #endregion
275
276                         #region " MIDI入力デバイスを解放する。"
277                         //-----------------
278                         FDK.Log.Info( "MIDI入力デバイスを解放します。" );
279                         FDK.Utilities.解放する( ref StrokeStyleT.bs_MIDI入力 );
280                         //-----------------
281                         #endregion
282                         #region " キーボード入力デバイスを解放する。"
283                         //-----------------
284                         FDK.Log.Info( "キーボード入力デバイスを解放します。" );
285                         FDK.Utilities.解放する( ref StrokeStyleT.bs_キーボード入力 );
286                         //-----------------
287                         #endregion
288                         #region " WASAPIデバイスを解放する。"
289                         //----------------
290                         FDK.Log.Info( "WASAPIデバイスを解放します。" );
291                         FDK.Utilities.解放する( ref StrokeStyleT.bs_Wasapiデバイス );
292                         //----------------
293                         #endregion
294                         
295                         #region " コンフィグを保存し、解放する。"
296                         //----------------
297                         FDK.Log.Info( "コンフィグを解放します。" );
298                         StrokeStyleT.bs_Config.ConfigXmlを保存する();
299                         StrokeStyleT.bs_Config = null;
300                         //----------------
301                         #endregion
302                         #region " フォームを閉じる。"
303                         //----------------
304                         FDK.Utilities.解放する( ref this.MainForm );
305                         //----------------
306                         #endregion
307
308                         #region " MediaFoundation を終了する。"
309                         //-----------------
310                         SharpDX.MediaFoundation.MediaManager.Shutdown();
311                         //-----------------
312                         #endregion
313
314                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
315                 }
316                 protected void 進行描画する()
317                 {
318                         #region " D3Dデバイスが消失していれば再構築する。"
319                         //----------------
320                         bool 異常発生 = false;
321                         this.デバイスリソース.D3Dデバイスが消失していれば再構築する( out 異常発生 );
322
323                         // 再構築不可能な異常の場合は、即終了する。
324                         if( 異常発生 )
325                         {
326                                 this.State = ApplicationState.終了;
327                                 return;
328                         }
329                         //----------------
330                         #endregion
331                         #region " 描画の前処理を行う。"
332                         //----------------
333                         {
334                                 var d3dDevice = (SharpDX.Direct3D11.Device) null;
335                                 using( var d3dLock = new FDK.同期.AutoD3DDeviceLock( this.デバイスリソース.DXGIDeviceManager, out d3dDevice ) )
336                                 using( d3dDevice )
337                                 using( var d3dContext = d3dDevice.ImmediateContext )
338                                 {
339                                         // 既定のD3Dレンダーターゲットビューを黒でクリアする。
340                                         d3dContext.ClearRenderTargetView( this.デバイスリソース.D3DRenderTargetView, SharpDX.Color4.Black );
341
342                                         // 深度バッファを 1.0f でクリアする。
343                                         d3dContext.ClearDepthStencilView(
344                                                 this.デバイスリソース.D3DDepthStencilView,
345                                                 SharpDX.Direct3D11.DepthStencilClearFlags.Depth,
346                                                 depth: 1.0f,
347                                                 stencil: 0 );
348                                 }
349                         }
350                         //----------------
351                         #endregion
352                         #region " 現在のステージを進行描画する。"
353                         //----------------
354                         this.現在のステージ?.進行描画する( this.デバイスリソース );
355                         //----------------
356                         #endregion
357                         #region " スワップチェーンを表示する。"
358                         //----------------
359                         if( StrokeStyleT.Config.垂直帰線待ちを行う )
360                         {
361                                 // We recommend that you use Flush when the CPU waits for an arbitrary amount of time
362                                 // ( such as when you call the Sleep function). 
363                                 // https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff476425(v=vs.85).aspx
364                                 var d3dDevice = (SharpDX.Direct3D11.Device) null;
365                                 using( var d3dLock = new FDK.同期.AutoD3DDeviceLock( this.デバイスリソース.DXGIDeviceManager, out d3dDevice ) )
366                                 using( d3dDevice )
367                                 using( var d3dContext = d3dDevice.ImmediateContext )
368                                 {
369                                         d3dContext.Flush();
370                                 }
371                         }
372
373                         this.デバイスリソース.SwapChain1.Present(
374                                 ( StrokeStyleT.Config.垂直帰線待ちを行う ) ? 1 : 0,
375                                 SharpDX.DXGI.PresentFlags.None );
376                         //----------------
377                         #endregion
378                         #region " ステージの状態をチェックし、必要あれば遷移またはアプリを終了する。"
379                         //----------------
380                         if( null != this.現在のステージ )
381                         {
382                                 switch( this.現在のステージ.GetType().Name )
383                                 {
384                                         case nameof( ステージ.ステージ ):
385                                                 #region " ビュアーモード → AutoPlayerでログインして曲読込ステージへ。"
386                                                 //----------------
387                                                 if( StrokeStyleT.ビュアーモードである )
388                                                 {
389                                                         FDK.Log.Info( "ビュアーモード: AutoPlayer ユーザでログインします。" );
390                                                         this.ログインする( Properties.Resources.AUTOPLAYER );
391
392                                                         this.曲読込ステージ.読込曲のファイルパスを取得する = () => ( null );  // 今は null 。あとでメッセージキューを見る。
393                                                         this.曲読込ステージ.活性化する( this.デバイスリソース );
394                                                         this.現在のステージ = this.曲読込ステージ;
395                                                 }
396                                                 //----------------
397                                                 #endregion
398                                                 #region " 通常モード → 起動ステージへ。"
399                                                 //----------------
400                                                 else
401                                                 {
402                                                         this.起動ステージ.活性化する( this.デバイスリソース );
403                                                         this.現在のステージ = this.起動ステージ;
404                                                 }
405                                                 //----------------
406                                                 #endregion
407                                                 break;
408
409                                         case nameof( ステージ.起動.起動ステージ ):
410                                                 #region " 終了 → タイトルステージへ。"
411                                                 //---------------
412                                                 if( this.起動ステージ.現在のフェーズ == ステージ.起動.起動ステージ.フェーズ.終了 )
413                                                 {
414                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
415                                                         this.現在のステージ = this.タイトルステージ;
416                                                         this.現在のステージ.活性化する( this.デバイスリソース );
417                                                 }
418                                                 //---------------
419                                                 #endregion
420                                                 break;
421
422                                         case nameof( ステージ.タイトル.タイトルステージ ):
423                                                 #region " 確定 → ログインステージへ。"
424                                                 //---------------
425                                                 if( this.タイトルステージ.現在のフェーズ == ステージ.タイトル.タイトルステージ.フェーズ.確定 )
426                                                 {
427                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
428                                                         this.現在のステージ = this.ログインステージ;
429                                                         this.現在のステージ.活性化する( this.デバイスリソース );
430                                                 }
431                                                 //---------------
432                                                 #endregion
433                                                 #region " キャンセル → アプリを終了する。"
434                                                 //---------------
435                                                 else if( this.タイトルステージ.現在のフェーズ == ステージ.タイトル.タイトルステージ.フェーズ.キャンセル )
436                                                 {
437                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
438                                                         this.現在のステージ = null;
439                                                         this.State = ApplicationState.終了;
440                                                 }
441                                                 //---------------
442                                                 #endregion
443                                                 break;
444
445                                         case nameof( ステージ.ログイン.ログインステージ ):
446                                                 #region " 確定 → ログイン処理を行って、選曲ステージへ。"
447                                                 //---------------
448                                                 if( this.ログインステージ.現在のフェーズ == ステージ.ログイン.ログインステージ.フェーズ.確定 )
449                                                 {
450                                                         var user = StrokeStyleT.ユーザ管理.現在選択されているユーザ;
451
452                                                         if( null != user )
453                                                         {
454                                                                 foreach( var path in user.曲の検索元フォルダパスのリスト )
455                                                                         SST.曲.曲ツリー管理.フォルダから曲を再帰的に検索して子ノードリストに追加する( user.曲ツリーのルートノード, path );
456
457                                                                 StrokeStyleT.曲ツリー管理.現在の管理対象ツリー = StrokeStyleT.ユーザ管理.現在選択されているユーザ.曲ツリーのルートノード;
458                                                         }
459
460                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
461                                                         this.現在のステージ = this.選曲ステージ;
462                                                         this.現在のステージ.活性化する( this.デバイスリソース );
463                                                 }
464                                                 //---------------
465                                                 #endregion
466                                                 #region " キャンセル → タイトルステージへ。"
467                                                 //---------------
468                                                 else if( this.ログインステージ.現在のフェーズ == ステージ.ログイン.ログインステージ.フェーズ.キャンセル )
469                                                 {
470                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
471                                                         this.現在のステージ = this.タイトルステージ;
472                                                         this.現在のステージ.活性化する( this.デバイスリソース );
473                                                 }
474                                                 //---------------
475                                                 #endregion
476                                                 break;
477
478                                         case nameof( ステージ.選曲.選曲ステージ ):
479                                                 #region " 曲確定 → 曲読込ステージへ。"
480                                                 //---------------
481                                                 if( this.選曲ステージ.現在のフェーズ == ステージ.選曲.選曲ステージ.フェーズ.曲確定 )
482                                                 {
483                                                         // 曲ノードが選択されていることを確認。
484                                                         Trace.Assert( null != StrokeStyleT.曲ツリー管理.現在選択されているノード, "[バグあり] 選択曲が null です。" );
485                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
486                                                         this.現在のステージ = this.曲読込ステージ;
487                                                         this.現在のステージ.活性化する( this.デバイスリソース );
488                                                 }
489                                                 //---------------
490                                                 #endregion
491                                                 #region " キャンセル → アプリを終了する。"
492                                                 //---------------
493                                                 else if( this.選曲ステージ.現在のフェーズ == ステージ.選曲.選曲ステージ.フェーズ.キャンセル )
494                                                 {
495                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
496                                                         this.現在のステージ = null;
497                                                         this.State = ApplicationState.終了;
498                                                 }
499                                                 //---------------
500                                                 #endregion
501                                                 break;
502
503                                         case nameof( ステージ.曲読込.曲読込ステージ ):
504                                                 #region " 終了 → 演奏ステージへ。"
505                                                 //--------------------
506                                                 if( this.曲読込ステージ.現在のフェーズ == ステージ.曲読込.曲読込ステージ.フェーズ.終了 )
507                                                 {
508                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
509
510                                                         if( StrokeStyleT.ビュアーモードである )
511                                                                 this.デバイス情報を出力する();
512
513                                                         this.現在のステージ = this.演奏ステージ;
514                                                         this.現在のステージ.活性化する( this.デバイスリソース );
515                                                 }
516                                                 //--------------------
517                                                 #endregion
518                                                 break;
519
520                                         case nameof( ステージ.演奏.演奏ステージ ):
521                                                 #region " 演奏終了 → 結果ステージへ。"
522                                                 //--------------------
523                                                 if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.クリアor失敗 )
524                                                 {
525                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
526                                                         this.現在のステージ = this.結果ステージ;
527                                                         this.現在のステージ.活性化する( this.デバイスリソース );
528                                                 }
529                                                 //--------------------
530                                                 #endregion
531                                                 #region " キャンセル → 選曲ステージへ。"
532                                                 //--------------------
533                                                 if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.キャンセル )
534                                                 {
535                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
536                                                         this.現在のステージ = this.選曲ステージ;
537                                                         this.現在のステージ.活性化する( this.デバイスリソース );
538                                                 }
539                                                 //--------------------
540                                                 #endregion
541                                                 #region " ビュアーメッセージ受信 → 曲読込ステージへ。"
542                                                 //--------------------
543                                                 if( this.演奏ステージ.現在のフェーズ.Value == ステージ.演奏.演奏ステージ.フェーズ.ビュアーメッセージ待機中 )
544                                                 {
545                                                         var msg = (SST.ステージ.演奏.ビュアーメッセージ) null;
546                                                         if( ( 0 < StrokeStyleT.ビュアーメッセージキュー.Count ) &&
547                                                                 ( StrokeStyleT.ビュアーメッセージキュー.TryDequeue( out msg ) ) )
548                                                         {
549                                                                 FDK.Log.Info( "ビュアーメッセージを受信しました。" );
550                                                                 this.曲読込ステージ.読込曲のファイルパスを取得する = () => { return msg.曲ファイルパス; };
551
552                                                                 this.現在のステージ.非活性化する( this.デバイスリソース );
553                                                                 this.演奏ステージ.BGMを解放する();
554                                                                 this.現在のステージ = this.曲読込ステージ;
555                                                                 this.現在のステージ.活性化する( this.デバイスリソース );
556                                                         }
557                                                 }
558                                                 //--------------------
559                                                 #endregion
560                                                 break;
561
562                                         case nameof( ステージ.結果.結果ステージ ):
563                                                 #region " 終了 → 選曲ステージへ。"
564                                                 //--------------------
565                                                 if( this.結果ステージ.現在のフェーズ == ステージ.結果.結果ステージ.フェーズ.終了 )
566                                                 {
567                                                         this.現在のステージ.非活性化する( this.デバイスリソース );
568                                                         this.現在のステージ = this.選曲ステージ;
569                                                         this.現在のステージ.活性化する( this.デバイスリソース );
570                                                 }
571                                                 //--------------------
572                                                 #endregion
573                                                 break;
574                                 }
575                         }
576                         //----------------
577                         #endregion
578                 }
579                 protected void デバイス依存リソースを解放する()
580                 {
581                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
582                         Debug.Assert( null != this.デバイスリソース );  // 解放前であること。
583
584                         this.現在のステージ?.デバイス依存リソースを解放する( this.デバイスリソース );
585
586                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
587                 }
588                 protected void デバイス依存リソースを再構築する()
589                 {
590                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
591                         Debug.Assert( null != this.デバイスリソース );  // 再生成済みであること。
592
593                         this.現在のステージ?.デバイス依存リソースを作成する( this.デバイスリソース );
594
595                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
596                 }
597
598                 private void フォームサイズが変更された( object sender, EventArgs e )
599                 {
600                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
601                         FDK.Log.Info( $"新しいクライアントサイズ = {this.MainForm.ClientSize}" );
602
603                         #region " 実行条件チェック。"
604                         //----------------
605                         if( null == this.デバイスリソース )
606                         {
607                                 FDK.Log.Info( " まだ初期化されてないので、何もしません。" );
608                                 FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
609                                 return;
610                         }
611                         if( this.MainForm.WindowState == FormWindowState.Minimized )
612                         {
613                                 FDK.Log.Info( "最小化されました。" );
614                                 FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
615                                 return; // 何もしない
616                         }
617                         if( this.State != ApplicationState.進行描画 )
618                         {
619                                 FDK.Log.Info( " アプリケーションの状態が進行描画じゃないので、何もしません。" );
620                                 FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
621                                 return;
622                         }
623                         //----------------
624                         #endregion
625
626                         Debug.Assert( null != this.デバイスリソース, "デバイスリソースが作成済みであること。" );
627
628                         // 現在の画面モードを取得しておく。(Alt+TABなど、勝手に全画面を解除されることもあるので。)
629                         SharpDX.Mathematics.Interop.RawBool fullscreen;
630                         SharpDX.DXGI.Output outputTarget;
631                         this.デバイスリソース.SwapChain1.GetFullscreenState( out fullscreen, out outputTarget );
632                         this.MainForm.IsFullscreen = fullscreen;
633                         outputTarget?.Dispose();
634                         FDK.Log.Info( $"現在、全画面モードである = {this.MainForm.IsFullscreen}" );
635
636                         // (1) リソースを解放して、
637                         this.デバイス依存リソースを解放する();
638                         this.デバイスリソース.サイズに依存するリソースを解放する();
639
640                         // (2) 物理画面サイズを変更して、
641                         this.デバイスリソース.物理画面サイズpx = new SharpDX.Size2F( this.MainForm.ClientSize.Width, this.MainForm.ClientSize.Height );
642
643                         // (3) リソースを再構築する。
644                         this.デバイスリソース.サイズに依存するリソースを作成する();
645                         this.デバイス依存リソースを再構築する();
646
647                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
648                 }
649                 private void 全画面モードとウィンドウモードを切り替える()
650                 {
651                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
652
653                         this.MainForm.IsFullscreen = !this.MainForm.IsFullscreen;
654                         this.デバイスリソース.SwapChain1.SetFullscreenState( this.MainForm.IsFullscreen, null );
655
656                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
657                 }
658                 private void ログインする( string ユーザ名 )
659                 {
660                         StrokeStyleT.ユーザ管理.ユーザを選択する( ユーザ名 );
661                         FDK.Log.Info( $"ユーザが選択されました。[{StrokeStyleT.ユーザ管理.現在選択されているユーザ.名前}]" );
662                 }
663                 private void デバイス情報を出力する()
664                 {
665                         using( var stream = new NamedPipeClientStream( "SSTFEditor Viewer Device Information" ) )
666                         {
667                                 try
668                                 {
669                                         stream.Connect( 1000 );
670
671                                         using( var writer = new StreamWriter( stream ) )
672                                         {
673                                                 writer.WriteLine( $"SoundDevice.Delay={StrokeStyleT.Wasapiデバイス.遅延ms.ToString()}" );
674                                                 FDK.Log.Info( "デバイス情報を出力しました。" );
675                                         }
676                                 }
677                                 catch( TimeoutException )
678                                 {
679                                         FDK.Log.WARNING( "SSTFEditor ビュアー用パイプへの接続がタイムアウトしました。SSTFEditor が起動していない可能性があります。" );
680                                 }
681                         }
682                 }
683                 private void 二重起動された( string[] args )
684                 {
685                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
686
687                         if( StrokeStyleT.ビュアーモードである )
688                         {
689                                 this.コマンドライン引数を解析する( args );
690                         }
691                         else
692                         {
693                                 FDK.Log.ERROR( "アプリが二重起動されましたが、先行アプリがビュアーモードではないので何もしません。" );
694                         }
695
696                         FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
697                 }
698                 /// <param name="args">コマンドライン引数の列挙。exeファイル名は含まない。</param>
699                 private void コマンドライン引数を解析する( IEnumerable<string> args )
700                 {
701                         FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
702
703                         try
704                         {
705                                 if( 0 == args.Count() )
706                                 {
707                                         FDK.Log.Info( "引数は指定されていません。" );    // ビュアーモードではない。
708                                         return;
709                                 }
710
711                                 StrokeStyleT.ビュアーモードである = true;
712
713                                 try
714                                 {
715                                         // 新しいメッセージを生成する。
716                                         var msg = new ステージ.演奏.ビュアーメッセージ() {
717                                                 種別 = ステージ.演奏.ビュアーメッセージ.E種別.演奏開始,        // 規定値は「演奏開始」
718                                                 曲ファイルパス = null,
719                                                 演奏開始小節番号 = 0,
720                                                 ドラム音を発声する = false,  // 規定値は false
721                                         };
722
723                                         // オプションを定義する。
724                                         var optionSet = new Mono.Options.OptionSet() {
725                                                 "Usage: StrokeStyleT [File] [OPTIONS]+",
726                                                 "  File\t\t\t\tビュアーモードで表示する曲ファイル名です。",
727                                                 { "p=|part=", "ビュアーモードで起動し、指定された小節番号から演奏を開始します。小節番号を省略すると、先頭から再生します。", (int v) => { msg.演奏開始小節番号 = v; }  },
728                                                 { "s|stop", "ビュアーモードで演奏中であれば、演奏を停止します。", v => { if ( v != null ) { msg.種別 = ステージ.演奏.ビュアーメッセージ.E種別.演奏停止; } } },
729                                                 { "d|drums", "ビュアーモードで、チップヒット時に内蔵のドラム音を再生します。", v => { if( v != null ) { msg.ドラム音を発声する = true; } } },
730                                         };
731
732                                         // オプションを解析する。
733                                         List<string> ファイルパスs = optionSet.Parse( args );
734
735                                         // 解析結果。
736                                         if( msg.種別 == ステージ.演奏.ビュアーメッセージ.E種別.演奏停止 )
737                                         {
738                                                 // (A) 演奏停止(曲ファイルパスは省略可。)
739                                                 FDK.Log.Info( "ビュアーメッセージ: 演奏停止" );
740                                         }
741                                         else
742                                         {
743                                                 // (B) 演奏開始(曲ファイルパスは必須。)
744                                                 if( 0 < ファイルパスs.Count )
745                                                 {
746                                                         if( File.Exists( ファイルパスs[ 0 ] ) )
747                                                         {
748                                                                 msg.曲ファイルパス = ファイルパスs[ 0 ];
749                                                         }
750                                                         else
751                                                         {
752                                                                 msg.曲ファイルパス = null;    // ファイルが存在しなかったら null 。
753                                                                 throw new Mono.Options.OptionException( $"ファイルが存在しません。[{FDK.フォルダ.絶対パスをフォルダ変数付き絶対パスに変換して返す( ファイルパスs[ 0 ] )}]", "File" );
754                                                         }
755                                                 }
756                                                 else
757                                                 {
758                                                         throw new Mono.Options.OptionException( "ファイルの指定がありません。", "File" );
759                                                 }
760
761                                                 FDK.Log.Info( "ビュアーメッセージ: 演奏開始" );
762                                                 FDK.Log.Info( $"曲ファイルパス: {msg.曲ファイルパス}" );
763                                                 FDK.Log.Info( $"開始小節番号: {msg.演奏開始小節番号}" );
764                                                 FDK.Log.Info( $"ドラム音: {msg.ドラム音を発声する}" );
765                                         }
766
767                                         // キューへ格納。
768                                         StrokeStyleT.ビュアーメッセージキュー.Enqueue( msg );
769                                         FDK.Log.Info( "ビュアーメッセージを送信しました。" );
770                                 }
771                                 catch( Mono.Options.OptionException e )
772                                 {
773                                         FDK.Log.ERROR( $"{e.Message}" );
774                                 }
775                         }
776                         finally
777                         {
778                                 FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
779                         }
780                 }
781
782                 #region " バックストア。"
783                 //----------------
784                 private static SST.フォルダ bs_フォルダ = null;
785                 private static FDK.入力.Keyboard bs_キーボード入力 = null;
786                 private static FDK.入力.MidiIn bs_MIDI入力 = null;
787                 private static FDK.メディア.サウンド.WASAPI排他.ExclusiveDevice bs_Wasapiデバイス = null;
788                 private static readonly Random bs_乱数 = new Random( DateTime.Now.Millisecond );
789                 private static SST.ユーザ.ユーザ管理 bs_ユーザ管理 = null;
790                 private static SST.曲.曲ツリー管理 bs_曲ツリー管理 = null;
791                 private static SST.設定.Config bs_Config = null;
792                 private static readonly ConcurrentQueue<SST.ステージ.演奏.ビュアーメッセージ> bs_ビュアーメッセージキュー = new ConcurrentQueue<ステージ.演奏.ビュアーメッセージ>();
793                 //----------------
794                 #endregion
795         }
796 }