OSDN Git Service

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