OSDN Git Service

排他モードと共有モードの両方の実相を完了。
[strokestylet/CsWin10Desktop3.git] / 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 FDK.メディア;
7 using FDK;      // for SystemStringExtensions
8
9 namespace SST.ステージ.演奏
10 {
11         /// <remarks>
12         /// 入力:
13         ///  StrokeStyleT.演奏スコア(ビュアーモードなら null でも可)
14         ///  StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)
15         ///  StrokeStyleT.Wasapiデバイス.AudioClock
16         ///  StrokeStyleT.ビュアーモードである
17         ///  StrokeStyleT.最後に取得したビュアーメッセージ
18         ///  
19         /// 出力:
20         ///  (A) クリアまたは失敗した場合
21         ///    StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
22         ///    this.現在のフェーズ ← クリアor失敗
23         ///    
24         ///  (B) キャンセルされた場合
25         ///    StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
26         ///    this.現在のフェーズ ← キャンセル
27         /// </remarks>
28         class 演奏ステージ : ステージ
29         {
30                 public ConcurrentDictionary<ヒット判定種別, int> ヒットした回数 { get; } = new ConcurrentDictionary<ヒット判定種別, int>();
31                 public enum フェーズ
32                 {
33                         初期状態,
34                         演奏中,
35                         クリアor失敗,
36                         キャンセル,
37                         ビュアーメッセージ待機中,
38                 }
39                 public FDK.同期.RWLock<フェーズ> 現在のフェーズ { get; } = new FDK.同期.RWLock<フェーズ>( フェーズ.初期状態 );
40
41                 public 演奏ステージ()
42                 {
43                         this.子リスト.Add( this.コンボ = new コンボ() );
44                         this.子リスト.Add( this.レーンフレーム = new レーンフレーム() );
45                         this.子リスト.Add( this.スクロール譜面 = new スクロール譜面() );
46                         this.子リスト.Add( this.ヒット判定文字列 = new ヒット判定文字列() );
47                         this.子リスト.Add( this.回転羽 = new 回転羽( 最大同時発火数: 32 ) );
48                         this.子リスト.Add( this.ドラムサウンド = new ドラムサウンド() );
49                         this.子リスト.Add( this.ドラムセット = new ドラムセット() );
50                         this.子リスト.Add( this.判定バー = new 画像( @"$(Static)\images\判定バー.png" ) );
51                         this.子リスト.Add( this.ステージ台 = new 画像( @"$(Static)\images\ステージ台.png" ) );
52                         this.子リスト.Add( this.FPS画像 = new 文字列画像() );
53
54                         // 子Activity の外部依存 Action の実体を定義する。
55                         this.スクロール譜面.ヒット判定文字列開始 = ( chipType, hitType ) => {
56                                 this.ヒット判定文字列.表示開始( chipType, hitType );
57                         };
58                         this.スクロール譜面.コンボリセット = () => {
59                                 this.コンボ.COMBO値.Value = 0;
60                         };
61                         this.スクロール譜面.コンボ加算 = () => {
62                                 this.コンボ.COMBO値.Value++;
63                         };
64                         this.スクロール譜面.ヒット判定数加算 = ( hitType ) => {
65                                 this.ヒットした回数[ hitType ]++;
66                         };
67                         this.スクロール譜面.背景動画の長さsec = () => {
68                                 return ( null != this.BGM ) ? this.BGM.長さsec : 0.0;
69                         };
70                         this.スクロール譜面.背景動画再生開始 = ( 開始位置sec ) => {
71                                 this.背景動画.再生を開始する( 開始位置sec );
72                                 this.背景動画開始済み.Value = true;
73                                 if( null != this.BGM )
74                                 {
75                                         this.BGM.位置sec = 開始位置sec;
76                                         this.BGM?.Play();
77                                 }
78                                 this.BGM再生開始済み = true;
79                         };
80                         this.スクロール譜面.チップヒット = ( chip ) => {
81                                 this.回転羽.発火する( chip.チップ種別 );
82                                 if( this.Autoチップのドラム音を再生する )
83                                         this.ドラムサウンド.発声する( chip.チップ種別, chip.音量 * 0.25f );
84                         };
85                         this.スクロール譜面.ステージクリア = () => {
86                                 this.現在のフェーズ.Value = フェーズ.クリアor失敗;
87                         };
88                         this.スクロール譜面.リアルタイム演奏時刻sec = () => {
89                                 return this.現在の演奏時刻secを返す();
90                         };
91                         this.スクロール譜面.譜面スクロール速度の倍率 = () => {
92                                 return this.現在進行描画中の譜面スクロール速度の倍率.Value;
93                         };
94                         this.スクロール譜面.FPSをカウント = () => {
95                                 this.FPS.FPSをカウントする();
96                         };
97                 }
98                 protected override void On活性化( デバイスリソース dr )
99                 {
100                         FDK.Log.Info( "演奏ステージを開始します。" + ( StrokeStyleT.ビュアーモードである ? "(ViewerMode)" : "" ) );
101
102                         #region " 動画ファイルパスが有効なら、背景動画とBGMを初期化する。"
103                         //----------------
104                         if( ( null != StrokeStyleT.演奏スコア ) && ( StrokeStyleT.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
105                         {
106                                 // 動画を子リストに追加。
107                                 this.子リスト.Add( this.背景動画 = new 動画( StrokeStyleT.演奏スコア.背景動画ファイル名, StrokeStyleT.Config.動画デコーダのキューサイズ ) );
108
109                                 // 動画から BGM を作成。
110                                 this.BGM = StrokeStyleT.サウンドデバイス.CreateSound( StrokeStyleT.演奏スコア.背景動画ファイル名 );
111                         }
112                         else
113                         {
114                                 this.背景動画 = null;
115                                 this.BGM = null;
116                         }
117                         //----------------
118                         #endregion
119
120                         this.演奏開始時刻sec = 0.0;
121                         this.現在進行描画中の譜面スクロール速度の倍率.Value = StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率;
122                         this.BGM再生開始済み = false;
123                         this.背景動画開始済み.Value = false;
124                         this.レーンフレーム.左端位置dpx = 400f;
125                         this.レーンフレーム.高さdpx = dr.設計画面サイズdpx.Height;
126                         this.ヒットした回数.Clear();
127                         foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
128                                 this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
129
130                         // 最初のフェーズを設定する。
131                         if( StrokeStyleT.ビュアーモードである )
132                         {
133                                 // 演奏スコアが設定済みなら演奏開始。それ以外ならメッセージ待機へ。
134                                 this.現在のフェーズ.Value = ( null != StrokeStyleT.演奏スコア ) ? フェーズ.演奏中 : フェーズ.ビュアーメッセージ待機中;
135                         }
136                         else
137                         {
138                                 // 演奏開始。
139                                 this.現在のフェーズ.Value = フェーズ.演奏中;
140                         }
141
142                         this.活性化した直後である = true;
143                 }
144                 protected override void On非活性化( デバイスリソース dr )
145                 {
146                         FDK.Log.Info( "演奏ステージを終了します。" );
147
148                         //this.BGMを解放する(); → ここではまだ解放しない。結果ステージが終わるときに、外部から解放する。
149
150                         if( null != this.背景動画 )
151                                 this.子リスト.Remove( this.背景動画 );  // 子リストから削除
152                 }
153                 public override void 進行描画する( デバイスリソース dr )
154                 {
155                         // 進行描画。
156
157                         #region " 初めての進行描画。"
158                         //----------------
159                         if( this.活性化した直後である )
160                         {
161                                 this.活性化した直後である = false;
162                                 this.FPS = new FDK.カウンタ.FPS();
163
164                                 double 演奏開始位置の先頭からの時間sec = 0.0;
165                                 int 演奏開始小節番号 = 0;
166                                 var msg = StrokeStyleT.最後に取得したビュアーメッセージ;
167                                 if( null != msg )
168                                 {
169                                         演奏開始小節番号 = msg.演奏開始小節番号;
170                                         this.Autoチップのドラム音を再生する = msg.ドラムチップ発声;
171                                 }
172                                 演奏開始位置の先頭からの時間sec = this.スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( 演奏開始小節番号 );
173
174                                 long position, qpcPosition, frequency;
175                                 StrokeStyleT.サウンドデバイス.GetClock( out position, out qpcPosition, out frequency );
176
177                                 this.演奏開始時刻sec = 
178                                         this.サウンドタイマ.現在のデバイス位置secを取得する( position, qpcPosition, frequency )
179                                         - 演奏開始位置の先頭からの時間sec;                // 「+」じゃないので注意!
180                         }
181                         //----------------
182                         #endregion
183
184                         double 演奏時刻sec = this.現在の演奏時刻secを返す();
185
186                         #region " 譜面スクロール速度が変化している場合の追い付き進行。"
187                         //----------------
188                         double 倍率 = this.現在進行描画中の譜面スクロール速度の倍率.Value;
189                         if( 倍率 < StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
190                         {
191                                 // todo: 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているので、あとからVPSに依存しないよう修正すること。
192                                 this.現在進行描画中の譜面スクロール速度の倍率.Value =
193                                         Math.Min( 倍率 + 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
194                         }
195                         else if( 倍率 > StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
196                         {
197                                 // todo: 同上。
198                                 this.現在進行描画中の譜面スクロール速度の倍率.Value =
199                                         Math.Max( 倍率 - 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
200                         }
201                         //----------------
202                         #endregion
203                         #region " 背景動画とBGMの開始/進行描画を行う。"
204                         //----------------
205                         if( this.背景動画開始済み.Value )
206                         {
207                                 // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
208                                 this.背景動画?.進行描画する( dr, new SharpDX.RectangleF( 0f, 0f, dr.設計画面サイズdpx.Width, dr.設計画面サイズdpx.Height ) );
209
210                                 // 動画が重たいかもしれないので、演奏時刻をここで再度取得する。
211                                 演奏時刻sec = this.現在の演奏時刻secを返す();
212
213                                 // 背景動画が再生されているのにBGMがまだ再生されていないなら、再生を開始する。
214                                 if( false == this.BGM再生開始済み )
215                                 {
216                                         this.BGM?.Play();
217                                         this.BGM再生開始済み = true;
218                                 }
219                         }
220                         //----------------
221                         #endregion
222
223                         this.ステージ台.描画する( dr, 0.0f, 0.0f );
224                         this.レーンフレーム.進行描画する( dr );
225                         this.コンボ.進行描画する( dr );
226                         this.ヒット判定文字列.進行描画する( dr );
227                         this.スクロール譜面.小節線拍線を進行描画する( dr, 演奏時刻sec );
228                         this.判定バー.描画する( dr, 597f, 座標.判定バーの中央Y座標dpx - 43f );
229                         this.ドラムセット.進行描画する( dr );
230                         this.スクロール譜面.チップを進行描画する( dr, 演奏時刻sec );
231                         this.回転羽.進行描画する( dr );
232                         this.FPS.VPSをカウントする();
233                         this.FPS画像.表示文字列 = $"VPS: {this.FPS.現在のVPS.ToString()} / FPS: {this.FPS.現在のFPS.ToString()}";
234                         this.FPS画像.進行描画する( dr, 0f, 0f );
235
236                         // 入力。
237
238                         StrokeStyleT.すべての入力デバイスをポーリングする();
239
240                         // ESC 押下 → キャンセル
241                         if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Escape ) &&
242                                 StrokeStyleT.ビュアーモードではない )  // ビュアーモードでは無効。
243                         {
244                                 this.BGMを解放する();
245                                 this.現在のフェーズ.Value = フェーズ.キャンセル;
246                         }
247                         // 上矢印押下 → 譜面スクロール速度の倍率を +0.5
248                         else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Up ) )
249                         {
250                                 StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 =
251                                         Math.Min( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 + 0.5, 8.0 );    // 最大 8.0
252                         }
253                         // 下矢印押下 → 譜面スクロール速度の倍率を -0.5
254                         else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Down ) )
255                         {
256                                 StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 =
257                                         Math.Max( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 - 0.5, 0.5 );    // 最小 0.5
258                         }
259                 }
260                 public void 演奏を停止する()
261                 {
262                         this.スクロール譜面?.演奏を停止する();
263                         this.背景動画開始済み.Value = false;
264                         this.BGMを解放する();
265                 }
266                 public void BGMを解放する()
267                 {
268                         if( null != this.BGM )  // 背景動画がなければ BGM も null である
269                         {
270                                 this.BGM.Stop();
271                                 FDK.Utilities.解放する( ref this.BGM );
272                         }
273                 }
274
275                 protected bool 活性化した直後である = false;
276                 protected FDK.同期.RWLock<bool> 背景動画開始済み = new FDK.同期.RWLock<bool>( false );
277                 protected bool BGM再生開始済み = false;
278                 protected FDK.同期.RWLock<double> 現在進行描画中の譜面スクロール速度の倍率 = new FDK.同期.RWLock<double>( 0.0 );
279                 protected double 演奏開始時刻sec = 0.0;
280                 protected bool Autoチップのドラム音を再生する = true;
281                 protected readonly FDK.メディア.サウンド.WASAPI.SoundTimer サウンドタイマ = new FDK.メディア.サウンド.WASAPI.SoundTimer();
282                 protected readonly SST.ステージ.演奏.コンボ コンボ;
283                 protected readonly SST.ステージ.演奏.レーンフレーム レーンフレーム;
284                 protected readonly SST.ステージ.演奏.スクロール譜面 スクロール譜面;
285                 protected readonly SST.ステージ.演奏.ヒット判定文字列 ヒット判定文字列;
286                 protected readonly SST.ステージ.演奏.回転羽 回転羽 = new 回転羽( 32 );
287                 protected readonly SST.ステージ.演奏.ドラムサウンド ドラムサウンド;
288                 protected readonly SST.ステージ.ドラムセット ドラムセット;
289                 protected readonly FDK.メディア.画像 判定バー;
290                 protected readonly FDK.メディア.画像 ステージ台;
291                 protected readonly FDK.メディア.文字列画像 FPS画像;
292                 /// <remarks>
293                 /// 解放は、演奏ステージクラスの非活性化後に、外部から行われる。
294                 /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMを解放する"/>
295                 /// </remarks>
296                 protected FDK.メディア.サウンド.WASAPI.Sound BGM = null;
297                 protected FDK.カウンタ.FPS FPS = null;
298                 /// <summary>
299                 /// 動的子Activity。背景動画を再生しない場合は null のまま。
300                 /// </summary>
301                 protected FDK.メディア.動画 背景動画 = null;
302
303                 private double 現在の演奏時刻secを返す()
304                 {
305                         long position, qpcPosition, frequency;
306                         StrokeStyleT.サウンドデバイス.GetClock( out position, out qpcPosition, out frequency );
307
308                         return this.サウンドタイマ.現在のデバイス位置secを取得する( position, qpcPosition, frequency ) - this.演奏開始時刻sec;
309                 }
310         }
311 }