2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics;
7 using FDK; // for SystemStringExtensions
13 /// StrokeStyleT.演奏スコア(ビュアーモードなら null でも可)
14 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)
15 /// StrokeStyleT.Wasapiデバイス.AudioClock
16 /// StrokeStyleT.ビュアーモードである
17 /// StrokeStyleT.最後に取得したビュアーメッセージ
21 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
22 /// this.現在のフェーズ ← クリアor失敗
25 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
26 /// this.現在のフェーズ ← キャンセル
30 public ConcurrentDictionary<ヒット判定種別, int> ヒットした回数 { get; } = new ConcurrentDictionary<ヒット判定種別, int>();
41 public FDK.同期.RWLock<フェーズ> 現在のフェーズ { get; } = new FDK.同期.RWLock<フェーズ>( フェーズ.初期状態 );
45 this.子リスト.Add( this._コンボ = new コンボ() );
46 this.子リスト.Add( this._レーンフレーム = new レーンフレーム() );
47 this.子リスト.Add( this._スクロール譜面 = new スクロール譜面() );
48 this.子リスト.Add( this._ヒット判定文字列 = new ヒット判定文字列() );
49 this.子リスト.Add( this._回転羽 = new 回転羽( 最大同時発火数: 32 ) );
50 this.子リスト.Add( this._ドラムサウンド = new ドラムサウンド() );
51 this.子リスト.Add( this._ドラムセット = new ドラムセット() );
52 this.子リスト.Add( this._判定バー = new 画像( @"$(Static)\images\判定バー.png" ) );
53 this.子リスト.Add( this._ステージ台 = new 画像( @"$(Static)\images\ステージ台.png" ) );
54 this.子リスト.Add( this._FPS画像 = new 文字列画像() );
56 // 子Activity の外部依存 Action の実体を定義する。
57 this._スクロール譜面.ヒット判定文字列開始 = ( chipType, hitType ) => {
58 this._ヒット判定文字列.表示開始( chipType, hitType );
60 this._スクロール譜面.コンボリセット = () => {
61 this._コンボ.COMBO値.Value = 0;
63 this._スクロール譜面.コンボ加算 = () => {
64 this._コンボ.COMBO値.Value++;
66 this._スクロール譜面.ヒット判定数加算 = ( hitType ) => {
67 this.ヒットした回数[ hitType ]++;
69 this._スクロール譜面.背景動画の長さsec = () => {
70 return ( null != this._BGM ) ? this._BGM.長さsec : 0.0;
72 this._スクロール譜面.背景動画再生開始 = ( 開始位置sec ) => {
73 this._背景動画?.再生を開始する( 開始位置sec );
74 this._背景動画開始済み.Value = true;
75 if( null != this._BGM )
76 this._BGM?.Play( 開始位置sec );
77 this._BGM再生開始済み = true;
79 this._スクロール譜面.チップヒット = ( chip ) => {
80 this._回転羽.発火する( chip.チップ種別 );
81 if( this._Autoチップのドラム音を再生する )
82 this._ドラムサウンド.発声する( chip.チップ種別, chip.音量 * 0.25f );
84 this._スクロール譜面.ステージクリア = () => {
85 this.現在のフェーズ.Value = フェーズ.クリアor失敗;
87 this._スクロール譜面.リアルタイム演奏時刻sec = () => {
88 return this._現在の演奏時刻secを返す();
90 this._スクロール譜面.譜面スクロール速度の倍率 = () => {
91 return this._現在進行描画中の譜面スクロール速度の倍率.Value;
93 this._スクロール譜面.FPSをカウント = () => {
94 this._FPS.FPSをカウントしプロパティを更新する();
98 protected override void On活性化( デバイスリソース dr )
100 FDK.Log.Info( "演奏ステージを開始します。" + ( StrokeStyleT.ビュアーモードである ? "(ViewerMode)" : "" ) );
102 #region " 動画ファイルパスが有効なら、背景動画とBGMを初期化する。"
104 if( ( null != StrokeStyleT.演奏スコア ) && ( StrokeStyleT.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
107 this.子リスト.Add( this._背景動画 = new 動画( StrokeStyleT.演奏スコア.背景動画ファイル名, StrokeStyleT.Config.動画デコーダのキューサイズ ) );
109 // 動画から音声パートを抽出して BGM を作成。
110 if( ( null != this._デコード済みWaveSource ) && this._デコード済みWaveSource.Path.Equals( StrokeStyleT.演奏スコア.背景動画ファイル名 ) )
112 // (A) 前回生成したBGMとパスが同じなので、前回のデコード済み WaveSource をキャッシュとして再利用する。
113 FDK.Log.Info( "前回生成したサウンドデータを再利用します。" );
117 // (B) 初めての生成か、または前回生成したBGMとパスが違うので、新しくデコード済み WaveSource を生成する。
118 this._デコード済みWaveSource?.Dispose();
119 this._デコード済みWaveSource = new FDK.メディア.サウンド.WASAPI.DecodedWaveSource(
120 StrokeStyleT.演奏スコア.背景動画ファイル名,
121 StrokeStyleT.サウンドデバイス.WaveFormat );
123 this._BGM?.Dispose();
124 this._BGM = StrokeStyleT.サウンドデバイス.CreateSound( this._デコード済みWaveSource );
130 //this._デコード済みWaveSource = null; キャッシュは消さない。
135 this._演奏開始時刻sec = 0.0;
136 this._現在進行描画中の譜面スクロール速度の倍率.Value = StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率;
137 this._BGM再生開始済み = false;
138 this._背景動画開始済み.Value = false;
139 this._レーンフレーム.左端位置dpx = 400f;
140 this._レーンフレーム.高さdpx = dr.設計画面サイズdpx.Height;
142 this.ヒットした回数.Clear();
143 foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
144 this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
146 #region " 最初のフェーズを設定する。"
148 if( StrokeStyleT.ビュアーモードである )
150 // 演奏スコアが設定済みなら演奏開始。それ以外ならメッセージ待機へ。
151 this.現在のフェーズ.Value = ( null != StrokeStyleT.演奏スコア ) ? フェーズ.演奏中 : フェーズ.ビュアーメッセージ待機中;
156 this.現在のフェーズ.Value = フェーズ.演奏中;
161 this._活性化した直後である = true;
164 protected override void On非活性化( デバイスリソース dr )
166 FDK.Log.Info( "演奏ステージを終了します。" );
168 //this.BGMを解放する(); → ここではまだ解放しない。結果ステージに非活性化時に、外部から解放する。
169 //FDK.Utilities.解放する( ref this._DecodedWaveSource ); → 演奏ステージインスタンスの破棄時に、外部から解放する。
171 if( null != this._背景動画 )
172 this.子リスト.Remove( this._背景動画 ); // 子リストから削除
175 public override void 進行描画する( デバイスリソース dr )
181 if( this._活性化した直後である )
183 this._活性化した直後である = false;
184 this._FPS = new FDK.カウンタ.FPS();
186 double 演奏開始位置の先頭からの時間sec = 0.0;
189 var msg = StrokeStyleT.最後に取得したビュアーメッセージ;
192 FDK.Log.Info( msg.ToString() );
194 演奏開始位置の先頭からの時間sec = this._スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( msg.演奏開始小節番号 );
195 FDK.Log.Info( $"演奏開始の先頭からの時間sec: {演奏開始位置の先頭からの時間sec}" );
197 this._Autoチップのドラム音を再生する = msg.ドラムチップ発声;
201 this._演奏開始時刻sec = StrokeStyleT.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
202 FDK.Log.Info( $"演奏開始時刻sec(背景動画再生チェック前): {this._演奏開始時刻sec}" );
204 this._スクロール譜面.再生中の時刻なら動画とBGMを再生開始する( 演奏開始位置の先頭からの時間sec );
206 // 演奏開始時刻sec の設定(2) 動画とBGMが再生された場合の誤差を修正する。
207 this._演奏開始時刻sec = StrokeStyleT.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
208 FDK.Log.Info( $"演奏開始時刻sec(背景動画再生チェック後): {this._演奏開始時刻sec}" );
213 double 演奏時刻sec = this._現在の演奏時刻secを返す();
215 #region " 譜面スクロール速度が変化している場合の追い付き進行。"
217 double 倍率 = this._現在進行描画中の譜面スクロール速度の倍率.Value;
218 if( 倍率 < StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
220 // todo: 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているので、あとからVPSに依存しないよう修正すること。
221 this._現在進行描画中の譜面スクロール速度の倍率.Value =
222 Math.Min( 倍率 + 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
224 else if( 倍率 > StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
227 this._現在進行描画中の譜面スクロール速度の倍率.Value =
228 Math.Max( 倍率 - 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
233 #region " 背景動画とBGMの開始と進行描画。"
235 if( this._背景動画開始済み.Value )
237 float width = dr.設計画面サイズdpx.Width;
238 float height = dr.設計画面サイズdpx.Height;
240 // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
241 if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.動画の縮小表示 )
243 this._背景動画?.進行描画する( dr, new SharpDX.RectangleF( 0f, 0f, width, height ), 0.2f ); // 全体
246 float 上移動dpx = 100.0f;
248 this._背景動画?.進行描画する( dr, new SharpDX.RectangleF(
249 width * ( 1f - 拡大縮小率 ) / 2f,
250 height * ( 1f - 拡大縮小率 ) / 2f - 上移動dpx,
256 this._背景動画?.進行描画する( dr, new SharpDX.RectangleF( 0f, 0f, width, height ), 1.0f );
259 // 動画が重たいかもしれないので、演奏時刻をここで再度取得する。
260 演奏時刻sec = this._現在の演奏時刻secを返す();
262 // 背景動画が再生されているのにBGMがまだ再生されていないなら、再生を開始する。
263 if( false == this._BGM再生開始済み )
266 this._BGM再生開始済み = true;
272 this._ステージ台.描画する( dr, 0.0f, 0.0f );
273 this._レーンフレーム.進行描画する( dr );
274 this._コンボ.進行描画する( dr );
275 this._ヒット判定文字列.進行描画する( dr );
276 this._スクロール譜面.小節線拍線を進行描画する( dr, 演奏時刻sec );
277 this._判定バー.描画する( dr, 597f, 座標.判定バーの中央Y座標dpx - 43f );
278 this._ドラムセット.進行描画する( dr );
279 this._スクロール譜面.チップを進行描画する( dr, 演奏時刻sec );
280 this._回転羽.進行描画する( dr );
281 this._FPS.VPSをカウントする();
282 this._FPS画像.表示文字列 = $"VPS: {this._FPS.現在のVPS.ToString()} / FPS: {this._FPS.現在のFPS.ToString()}";
283 this._FPS画像.進行描画する( dr, 0f, 0f );
287 StrokeStyleT.すべての入力デバイスをポーリングする();
290 if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Escape ) &&
291 StrokeStyleT.ビュアーモードではない ) // ビュアーモードでは無効。
294 this.現在のフェーズ.Value = フェーズ.キャンセル;
296 // 上矢印押下 → 譜面スクロール速度の倍率を +0.5
297 else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Up ) )
299 StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 =
300 Math.Min( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 + 0.5, 8.0 ); // 最大 8.0
302 // 下矢印押下 → 譜面スクロール速度の倍率を -0.5
303 else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Down ) )
305 StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 =
306 Math.Max( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 - 0.5, 0.5 ); // 最小 0.5
310 public void 演奏を停止する()
312 this._スクロール譜面?.演奏を停止する();
314 this._背景動画開始済み.Value = false;
318 this._コンボ.COMBO値.Value = 0;
321 public void BGMを停止する()
323 if( null != this._BGM ) // 背景動画がなければ BGM も null であり、それはエラーではない。
326 FDK.Utilities.解放する( ref this._BGM );
327 //FDK.Utilities.解放する( ref this._デコード済みWaveSource ); → ここでは解放しない。
331 public void BGMのキャッシュを解放する()
335 FDK.Utilities.解放する( ref this._デコード済みWaveSource );
338 private bool _活性化した直後である = false;
340 private double _演奏開始時刻sec = 0.0;
342 private bool _Autoチップのドラム音を再生する = true;
344 private readonly SST.ステージ.演奏.コンボ _コンボ;
346 private readonly SST.ステージ.演奏.レーンフレーム _レーンフレーム;
348 private readonly SST.ステージ.演奏.スクロール譜面 _スクロール譜面;
350 private readonly SST.ステージ.演奏.ヒット判定文字列 _ヒット判定文字列;
352 private readonly SST.ステージ.演奏.回転羽 _回転羽 = new 回転羽( 32 );
354 private readonly SST.ステージ.演奏.ドラムサウンド _ドラムサウンド;
356 private readonly SST.ステージ.ドラムセット _ドラムセット;
358 private readonly FDK.メディア.画像 _判定バー;
360 private readonly FDK.メディア.画像 _ステージ台;
362 private readonly FDK.メディア.文字列画像 _FPS画像;
364 private FDK.同期.RWLock<bool> _背景動画開始済み = new FDK.同期.RWLock<bool>( false );
366 private bool _BGM再生開始済み = false;
368 private FDK.同期.RWLock<double> _現在進行描画中の譜面スクロール速度の倍率 = new FDK.同期.RWLock<double>( 0.0 );
371 /// 停止と解放は、演奏ステージクラスの非活性化後に、外部から行われる。
372 /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMを停止する"/>
373 /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMのキャッシュを解放する"/>
375 private FDK.メディア.サウンド.WASAPI.Sound _BGM = null;
378 /// BGM の生成もとになるデコード済みサウンドデータ。
381 /// 活性化と非活性化に関係なく、常に最後にデコードしたデータを持つ。(キャッシュ)
382 /// 演奏ステージインスタンスを破棄する際に、このインスタンスもDisposeすること。
384 private FDK.メディア.サウンド.WASAPI.DecodedWaveSource _デコード済みWaveSource = null;
386 private FDK.メディア.動画 _背景動画 = null;
388 private FDK.カウンタ.FPS _FPS = null;
390 private double _現在の演奏時刻secを返す()
392 return StrokeStyleT.サウンドデバイス.GetDevicePosition() - this._演奏開始時刻sec;