2 using System.Collections.Generic;
3 using System.Diagnostics;
6 using System.ServiceModel;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using System.Windows.Forms;
31 [ServiceBehavior( InstanceContextMode = InstanceContextMode.Single )] // サービスインターフェースをシングルスレッドで呼び出す。
32 class App : ApplicationForm, IStrokeStyleTService, IDisposable
34 public static bool ビュアーモードである
39 public static bool ビュアーモードではない
42 => !( App.ビュアーモードである );
45 => App.ビュアーモードである = !( value );
48 #region //////// グローバルリソース(static) ////////
51 /// SharpDX.Mathematics パッケージを参照し、かつ SharpDX 名前空間を using しておくと、
52 /// SharpDX で定義する追加の拡張メソッド(NextFloatなど)を使えるようになる。
54 public static Random 乱数
60 public static システム設定 システム設定
66 public static 入力管理 入力管理
72 public static FDK.メディア.サウンド.WASAPI.Device サウンドデバイス
78 public static FDK.メディア.サウンド.WASAPI.SoundTimer サウンドタイマ
87 public static ユーザ管理 ユーザ管理
96 public static ステージ管理 ステージ管理
102 public static スコア 演奏スコア
108 public static ViewerMessageQueue ビュアーメッセージキュー
114 public static ViewerMessage 最後に取得したビュアーメッセージ
120 public static MusicNode ビュアー用ノード
131 /// インスタンスのコンストラクタに先駆けて必要となる処理を行う。
135 SST.IO.Folder.初期化する();
141 public App( string[] args )
142 : base( 設計画面サイズ: new SizeF( 1920f, 1080f ), 物理画面サイズ: new SizeF( 960f, 540f ) )
144 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
146 Log.現在のスレッドに名前をつける( "描画" );
148 // 高解像度タイマを使えないならエラー。
149 if( !( Stopwatch.IsHighResolution ) )
150 throw new Exception( "このシステムは、高解像度タイマをサポートしていません。" );
153 timeBeginPeriod( 1 );
155 #region " ビュアーモードかどうかを確認する。"
157 foreach( var arg in args )
159 if( ( "-v" == arg.ToLower() ) || ( "-viewer" == arg.ToLower() ) )
161 App.ビュアーモードである = true;
168 // グローバルリソースを初期化する (1)フォーム初期化前
170 App.乱数 = new Random( DateTime.Now.Millisecond );
172 App.システム設定 = システム設定.復元する();
175 App.システム設定.全画面モードである = false; // ビュアーモードでは常にウィンドウモード。
177 App.ビュアーメッセージキュー = new ViewerMessageQueue();
179 App.最後に取得したビュアーメッセージ = null;
183 #region " メインフォームを初期化する。"
185 this.Text = $"{Application.ProductName} {Application.ProductVersion}";
187 this.Text += " (Viewer)";
189 // ユーザはフォームサイズを変更できない。
190 //this.AllowUserResizing = false;
194 // グローバルリソースを初期化する (2)フォーム初期化後
196 App.入力管理 = new 入力管理( this.Handle, 32 );
198 App.サウンドデバイス = new FDK.メディア.サウンド.WASAPI.Device( CSCore.CoreAudioAPI.AudioClientShareMode.Shared );
199 App.サウンドタイマ = new FDK.メディア.サウンド.WASAPI.SoundTimer( App.サウンドデバイス );
201 App.ユーザ管理 = ユーザ管理.復元する( App.グラフィックデバイス ); // この中で活性化も行われる。
202 App.ユーザ管理.最初のユーザを選択する();
204 App.ステージ管理 = new ステージ管理();
211 #region " 最初のステージへ遷移する。"
213 if( App.ビュアーモードではない )
216 App.ステージ管理.ステージを遷移する( App.グラフィックデバイス, App.ステージ管理.最初のステージ名 );
221 App.ステージ管理.ステージを遷移する( App.グラフィックデバイス, nameof( ユーザ選択ステージ ) );
231 public new void Dispose()
233 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
238 App.最後に取得したビュアーメッセージ = null;
239 App.ビュアーメッセージキュー = null;
241 App.演奏スコア?.Dispose();
244 App.ステージ管理.Dispose( App.グラフィックデバイス );
247 //App.ユーザ管理.保存する(); --> 必要時に更新する。
250 //App.システム設定.保存する(); --> 必要時に更新する。
251 App.システム設定?.Dispose();
254 App.サウンドタイマ?.Dispose();
257 App.サウンドデバイス?.Dispose();
260 App.入力管理?.キーバインディングを保存する(); // 同上。
271 public sealed override void Run()
273 Debug.Assert( ( null != App.ステージ管理.現在のステージ ), "最初のステージが設定されていません。" );
275 SharpDX.Windows.RenderLoop.Run( this, () => {
277 switch( this._AppStatus )
282 this._高速進行ステータス = new TriStateEvent( TriStateEvent.状態種別.OFF );
283 Task.Factory.StartNew( this._高速進行タスクエントリ );
286 this._AppStatus = AppStatus.実行中;
294 Thread.Sleep( 500 ); // 終了待機中。
304 protected override void OnLoad( EventArgs e )
306 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
308 lock( this._高速進行と描画の同期 )
310 if( App.システム設定.全画面モードである )
319 protected override void OnClosing( System.ComponentModel.CancelEventArgs e )
321 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
323 lock( this._高速進行と描画の同期 )
325 // 通常は進行タスクから終了するが、Alt+F4でここに来た場合はそれが行われてないので、行う。
326 if( this._AppStatus != AppStatus.終了 )
336 protected override void スワップチェーンに依存するグラフィックリソースを作成する( SharpDX.Direct3D11.Device d3dDevice )
338 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
340 Debug.Assert( null != d3dDevice );
344 protected override void スワップチェーンに依存するグラフィックリソースを解放する()
346 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
351 #region " IStrokeStyleT の実装 "
353 // このアセンブリ(exe)は、WCF で IStrokeStyleTService を公開する。(基本的に、SSTFViewer 向け。)
354 // ・このサービスインターフェースは、シングルスレッド(GUIスレッド)で同期実行される。(Appクラスの ServiceBehavior属性を参照。)
355 // ・このサービスホストはシングルトンであり、すべてのクライアントセッションは同一(単一)のサービスインスタンスへ接続される。(Program.Main() を参照。)
361 /// <param name="path">曲ファイルパス</param>
362 /// <param name="startPart">演奏開始小節番号(0~)</param>
363 /// <param name="drumsSound">ドラムチップ音を発声させるなら true。</param>
364 public void ViewerPlay( string path, int startPart = 0, bool drumsSound = true )
366 if( App.ビュアーモードではない )
369 App.ビュアーメッセージキュー.格納する( new ViewerMessage() {
370 種別 = ViewerMessage.指示種別.演奏開始,
372 演奏を開始する小節番号 = startPart,
373 ドラムチップのヒット時に発声する = drumsSound,
381 public void ViewerStop()
383 if( App.ビュアーモードではない )
386 App.ビュアーメッセージキュー.格納する( new ViewerMessage() {
387 種別 = ViewerMessage.指示種別.演奏停止,
392 /// サウンドデバイスの発声遅延[ms]を返す。
394 /// <returns>遅延量[ms]</returns>
395 public float GetSoundDelay()
397 if( App.ビュアーモードではない )
400 return (float) ( App.サウンドデバイス?.遅延sec ?? 0.0 ) * 1000.0f;
406 private enum AppStatus { 開始, 実行中, 終了 };
411 private AppStatus _AppStatus = AppStatus.開始;
414 // ※ Form イベントの override メソッドは描画スレッドで実行されるため、処理中に進行タスクが呼び出されると困る場合には、進行タスクとの lock を忘れないこと。
415 private readonly object _高速進行と描画の同期 = new object();
419 /// OFF:タスク起動前、ON:タスク実行中、OFF:タスク終了済み
421 private TriStateEvent _高速進行ステータス;
427 private void _高速進行タスクエントリ()
429 Log.現在のスレッドに名前をつける( "高速進行" );
430 Log.Info( "高速進行タスクを開始します。" );
432 this._高速進行ステータス.現在の状態 = TriStateEvent.状態種別.ON;
436 lock( this._高速進行と描画の同期 )
438 if( this._高速進行ステータス.現在の状態 != TriStateEvent.状態種別.ON ) // lock してる間に状態が変わることがあるので注意。
441 //App.入力管理.すべての入力デバイスをポーリングする();
442 // --> 入力ポーリングの挙動はステージごとに異なるので、それぞれのステージ内で行う。
444 App.ステージ管理.現在のステージ.高速進行する();
447 Thread.Sleep( 1 ); // ウェイト。
450 this._高速進行ステータス.現在の状態 = TriStateEvent.状態種別.無効;
452 Log.Info( "高速進行タスクを終了しました。" );
458 private void _進行と描画を行う()
460 var gd = App.グラフィックデバイス;
463 lock( this._高速進行と描画の同期 )
465 if( this._AppStatus != AppStatus.実行中 ) // 上記lock中に終了されている場合があればそれをはじく。
468 gd.D3DDeviceを取得する( ( d3dDevice ) => {
470 #region " (1) D3Dレンダリングの前処理を行う。"
472 // 既定のD3Dレンダーターゲットビューを黒でクリアする。
473 d3dDevice.ImmediateContext.ClearRenderTargetView( gd.D3DRenderTargetView, Color4.Black );
475 // 深度バッファを 1.0f でクリアする。
476 d3dDevice.ImmediateContext.ClearDepthStencilView(
477 gd.D3DDepthStencilView,
478 SharpDX.Direct3D11.DepthStencilClearFlags.Depth,
484 #region " (2) 現在のステージの進行描画を行う。"
486 App.ステージ管理.現在のステージ.進行描画する( gd );
490 #region " (3) ステージの状態をチェックし、遷移処理を行う。(必要に応じてビュアーメッセージキューの処理も行う。)"
492 switch( App.ステージ管理.現在のステージ )
495 #region " 完了 → タイトルステージへ "
497 if( stage.現在のフェーズ == 起動ステージ.フェーズ.完了 )
499 App.ステージ管理.ステージを遷移する( gd, nameof( タイトルステージ ) );
506 #region " 確定 → ユーザステージへ "
508 if( stage.現在のフェーズ == タイトルステージ.フェーズ.確定 )
510 App.ステージ管理.ステージを遷移する( gd, nameof( ユーザ選択ステージ ) );
514 #region " キャンセル → アプリを終了する。"
516 else if( stage.現在のフェーズ == タイトルステージ.フェーズ.キャンセル )
518 App.ステージ管理.ステージを遷移する( gd, null );
525 case ユーザ選択ステージ stage:
526 #region " キャンセル → タイトルステージへ "
528 if( stage.現在のフェーズ == ユーザ選択ステージ.フェーズ.キャンセル )
530 App.ステージ管理.ステージを遷移する( gd, nameof( タイトルステージ ) );
534 #region " 完了 → 通常モード時は選曲ステージ、ビュアーモード時は演奏ステージへ "
536 else if( stage.現在のフェーズ == ユーザ選択ステージ.フェーズ.完了 )
538 if( App.ビュアーモードではない )
540 App.ステージ管理.ステージを遷移する( gd, nameof( 選曲ステージ ) );
544 App.ステージ管理.ステージを遷移する( gd, nameof( 演奏ステージ ) );
552 #region " キャンセル → ユーザステージへ "
554 if( stage.現在のフェーズ == 選曲ステージ.フェーズ.キャンセル )
556 App.ステージ管理.ステージを遷移する( gd, nameof( ユーザ選択ステージ ) );
560 #region " 曲決定 → 曲読込ステージへ "
562 else if( stage.現在のフェーズ == 選曲ステージ.フェーズ.曲決定 )
564 App.ステージ管理.ステージを遷移する( gd, nameof( 曲読込ステージ ) );
571 #region " 完了 → 演奏ステージへ "
573 if( stage.現在のフェーズ == 曲読込ステージ.フェーズ.完了 )
575 App.ステージ管理.ステージを遷移する( gd, nameof( 演奏ステージ ) );
582 if( App.ビュアーモードではない )
585 #region " Failed, キャンセル → 選曲ステージへ。"
586 //--------------------
587 if( ( stage.現在のフェーズ == 演奏ステージ.フェーズ.キャンセル ) ||
588 ( stage.現在のフェーズ == 演奏ステージ.フェーズ.Failed ) )
590 App.ステージ管理.ステージを遷移する( gd, nameof( 選曲ステージ ) );
592 //--------------------
594 #region " クリア → クリアステージへ。"
595 //--------------------
596 else if( stage.現在のフェーズ == 演奏ステージ.フェーズ.クリア )
598 App.ステージ管理.ステージを遷移する( gd, nameof( クリアステージ ) );
600 //--------------------
606 #region " ビュアーメッセージが届いていれば、処理する。"
609 = App.最後に取得したビュアーメッセージ
610 = App.ビュアーメッセージキュー.取得する();
614 Log.Info( $"{msg.ToString()}" );
618 case ViewerMessage.指示種別.指示なし:
621 case ViewerMessage.指示種別.演奏開始:
622 if( stage.現在のフェーズ == 演奏ステージ.フェーズ.演奏中 ||
623 stage.現在のフェーズ == 演奏ステージ.フェーズ.ビュアーメッセージ待機 )
629 if( ( null != App.ビュアー用ノード ) && App.ビュアー用ノード.活性化している )
630 App.ビュアー用ノード.非活性化する( gd );
632 App.ビュアー用ノード = new MusicNode( msg.演奏対象曲のファイルパス, null );
635 App.ステージ管理.ステージを遷移する( gd, nameof( 曲読込ステージ ) );
639 case ViewerMessage.指示種別.演奏停止:
640 if( stage.現在のフェーズ == 演奏ステージ.フェーズ.演奏中 ||
641 stage.現在のフェーズ == 演奏ステージ.フェーズ.ビュアーメッセージ待機 ) // 待機中もまだBGMが鳴ってるかもしれない
644 stage.現在のフェーズ = 演奏ステージ.フェーズ.ビュアーメッセージ待機;
649 Log.ERROR( "未対応のViewerMessageです。" );
655 #region " クリア, Failed, キャンセル → メッセージ待機へ。"
657 if( stage.現在のフェーズ == 演奏ステージ.フェーズ.クリア ||
658 stage.現在のフェーズ == 演奏ステージ.フェーズ.Failed ||
659 stage.現在のフェーズ == 演奏ステージ.フェーズ.キャンセル )
661 // フェーズのみ変更し、BGM は止めない。
662 stage.現在のフェーズ = 演奏ステージ.フェーズ.ビュアーメッセージ待機;
670 #region " 完了 → 結果ステージへ "
672 if( stage.現在のフェーズ == クリアステージ.フェーズ.完了 )
674 App.ステージ管理.ステージを遷移する( gd, nameof( 結果ステージ ) );
681 #region " 完了 → 選曲ステージへ。"
682 //--------------------
683 if( stage.現在のフェーズ == 結果ステージ.フェーズ.完了 )
685 App.ステージ管理.ステージを遷移する( gd, nameof( 選曲ステージ ) );
687 //--------------------
694 #region " (4) D3Dコマンドをフラッシュする。 "
696 if( App.システム設定.垂直帰線待ちを行う )
698 // ID3D11DeviceContext::Flush
699 // <https://msdn.microsoft.com/ja-jp/library/windows/desktop/ff476425(v=vs.85).aspx>
700 // > We recommend that you use Flush when the CPU waits for an arbitrary amount of time
701 // > (such as when you call the Sleep function).
702 // > CPUが(Sleep関数の呼び出しなどで)任意の時間 wait するのであれば、Flush の使用を推奨します。
703 d3dDevice.ImmediateContext.Flush();
709 vsync = App.システム設定.垂直帰線待ちを行う;
712 #region " (5) スワップチェーンを表示する。"
714 gd.SwapChain.Present( ( vsync ) ? 1 : 0, SharpDX.DXGI.PresentFlags.None );
720 /// グローバルリソースのうち、グラフィックリソースを持つものについて、活性化がまだなら活性化する。
722 private void _活性化する()
724 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
726 if( App.ユーザ管理.活性化していない )
727 App.ユーザ管理.活性化する( App.グラフィックデバイス );
729 if( App.ステージ管理.現在のステージ?.活性化していない ?? false )
730 App.ステージ管理.現在のステージ?.活性化する( App.グラフィックデバイス );
735 /// グローバルリソースのうち、グラフィックリソースを持つものについて、活性化中なら非活性化する。
737 private void _非活性化する()
739 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
741 if( App.ステージ管理.現在のステージ?.活性化している ?? false )
742 App.ステージ管理.現在のステージ?.非活性化する( App.グラフィックデバイス );
744 if( App.ユーザ管理.活性化している )
745 App.ユーザ管理.非活性化する( App.グラフィックデバイス );
750 /// 進行タスクを終了し、ウィンドウを閉じ、アプリを終了する。
752 private void _アプリを終了する()
754 using( Log.Block( FDKUtilities.現在のメソッド名 ) )
756 if( this._AppStatus != AppStatus.終了 )
758 this._高速進行ステータス.現在の状態 = TriStateEvent.状態種別.OFF;
759 this._AppStatus = AppStatus.終了;
761 // _AppStatus を変更したあとに、GUI スレッドで非同期実行を指示する。
762 this.BeginInvoke( new Action( () => { this.Close(); } ) );
767 protected override void OnKeyDown( KeyEventArgs e )
769 if( e.KeyCode == Keys.F11 )
770 this.全画面モード = !( this.全画面モード );
775 [System.Runtime.InteropServices.DllImport( "winmm.dll", EntryPoint = "timeBeginPeriod" )]
776 public static extern uint timeBeginPeriod( uint uMilliseconds );
778 [System.Runtime.InteropServices.DllImport( "winmm.dll", EntryPoint = "timeEndPeriod" )]
779 public static extern uint timeEndPeriod( uint uMilliseconds );