2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics;
7 using SharpDX.DirectInput;
10 using FDK.メディア.サウンド.WASAPI;
19 /// StrokeStyleT.演奏スコア(ビュアーモードなら null でも可)
20 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)
21 /// StrokeStyleT.Wasapiデバイス.AudioClock
22 /// StrokeStyleT.ビュアーモードである
23 /// StrokeStyleT.最後に取得したビュアーメッセージ
27 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
28 /// this.現在のフェーズ ← クリアor失敗
31 /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
32 /// this.現在のフェーズ ← キャンセル
36 public ConcurrentDictionary<ヒット判定種別, int> ヒットした回数 { get; } = new ConcurrentDictionary<ヒット判定種別, int>();
47 public RWLock<フェーズ> 現在のフェーズ { get; } = new RWLock<フェーズ>( フェーズ.初期状態 );
51 this.子リスト.Add( this._コンボ = new コンボ() );
52 this.子リスト.Add( this._レーンフレーム = new レーンフレーム() );
53 this.子リスト.Add( this._スクロール譜面 = new スクロール譜面() );
54 this.子リスト.Add( this._ヒット判定文字列 = new ヒット判定文字列() );
55 this.子リスト.Add( this._回転羽 = new 回転羽( 最大同時発火数: 32 ) );
56 this.子リスト.Add( this._ドラムサウンド = new ドラムサウンド() );
57 this.子リスト.Add( this._ドラムセット = new ドラムセット() );
58 this.子リスト.Add( this._判定バー = new 画像( @"$(Static)\images\判定バー.png" ) );
59 this.子リスト.Add( this._ステージ台 = new 画像( @"$(Static)\images\ステージ台.png" ) );
60 this.子リスト.Add( this._FPS画像 = new 文字列画像() );
62 // 子Activity の外部依存 Action の実体を定義する。
63 this._スクロール譜面.ヒット判定文字列を開始する = ( chipType, hitType ) => {
64 this._ヒット判定文字列.表示開始( chipType, hitType );
66 this._スクロール譜面.コンボをリセットする = () => {
69 this._スクロール譜面.コンボを1つ加算する = () => {
72 this._スクロール譜面.ヒット判定数を1つ加算する = ( hitType ) => {
73 this.ヒットした回数[ hitType ]++;
75 this._スクロール譜面.背景動画の長さsecを取得する = () => {
76 return ( null != this._BGM ) ? this._BGM.長さsec : 0.0;
78 this._スクロール譜面.背景動画の再生を開始する = ( 開始位置sec ) => {
79 this._背景動画?.再生を開始する( 開始位置sec );
80 this._背景動画開始済み.Value = true;
81 if( null != this._BGM )
82 this._BGM?.Play( 開始位置sec );
83 this._BGM再生開始済み = true;
85 this._スクロール譜面.チップをヒットする = ( chip ) => {
86 this._回転羽.発火する( chip.チップ種別 );
87 if( this._Autoチップのドラム音を再生する )
88 this._ドラムサウンド.発声する( chip.チップ種別, ( chip.音量 / (float) チップ.最大音量 ) );
90 this._スクロール譜面.ステージをクリアする = () => {
91 this.現在のフェーズ.Value = フェーズ.クリアor失敗;
93 this._スクロール譜面.ステージをキャンセルする = () => {
94 if( StrokeStyleT.ビュアーモードではない ) // ビュアーモード時のキャンセルは無効。
97 this.現在のフェーズ.Value = フェーズ.キャンセル;
100 this._スクロール譜面.譜面スクロール速度の倍率を増やす = () => {
101 StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 =
102 Math.Min( StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 + 0.5, 8.0 ); // 最大 8.0
104 this._スクロール譜面.譜面スクロール速度の倍率を減らす = () => {
105 StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 =
106 Math.Max( StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 - 0.5, 0.5 ); // 最小 0.5
108 this._スクロール譜面.リアルタイム演奏時刻secを取得する = () => {
109 return this._現在の演奏時刻secを返す();
111 this._スクロール譜面.譜面スクロール速度の倍率を取得する = () => {
112 return this._現在進行描画中の譜面スクロール速度の倍率.Value;
114 this._スクロール譜面.FPSをカウントアップする = () => {
115 this._FPS.FPSをカウントしプロパティを更新する();
119 protected override void On活性化( デバイスリソース dr )
121 Log.Info( "演奏ステージを開始します。" + ( StrokeStyleT.ビュアーモードである ? "(ViewerMode)" : "" ) );
123 #region " 動画ファイルパスが有効なら、背景動画とBGMを初期化する。"
125 if( ( null != StrokeStyleT.演奏スコア ) && ( StrokeStyleT.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
128 this.子リスト.Add( this._背景動画 = new 動画( StrokeStyleT.演奏スコア.背景動画ファイル名, StrokeStyleT.Config.動画デコーダのキューサイズ ) );
130 // 動画から音声パートを抽出して BGM を作成。
131 if( ( null != this._デコード済みWaveSource ) && this._デコード済みWaveSource.Path.Equals( StrokeStyleT.演奏スコア.背景動画ファイル名 ) )
133 // (A) 前回生成したBGMとパスが同じなので、前回のデコード済み WaveSource をキャッシュとして再利用する。
134 Log.Info( "前回生成したサウンドデータを再利用します。" );
138 // (B) 初めての生成か、または前回生成したBGMとパスが違うので、新しくデコード済み WaveSource を生成する。
139 this._デコード済みWaveSource?.Dispose();
140 this._デコード済みWaveSource = new FDK.メディア.サウンド.WASAPI.DecodedWaveSource(
141 StrokeStyleT.演奏スコア.背景動画ファイル名,
142 StrokeStyleT.サウンドデバイス.WaveFormat );
144 this._BGM?.Dispose();
145 this._BGM = StrokeStyleT.サウンドデバイス.CreateSound( this._デコード済みWaveSource );
151 //this._デコード済みWaveSource = null; キャッシュは消さない。
156 this._演奏開始時刻sec = 0.0;
157 this._現在進行描画中の譜面スクロール速度の倍率.Value = StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率;
158 this._BGM再生開始済み = false;
159 this._背景動画開始済み.Value = false;
160 this._レーンフレーム.左端位置dpx = 400f;
161 this._レーンフレーム.高さdpx = dr.設計画面サイズdpx.Height;
163 this.ヒットした回数.Clear();
164 foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
165 this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
167 #region " 最初のフェーズを設定する。"
169 if( StrokeStyleT.ビュアーモードである )
171 // 演奏スコアが設定済みなら演奏開始。それ以外ならメッセージ待機へ。
172 this.現在のフェーズ.Value = ( null != StrokeStyleT.演奏スコア ) ? フェーズ.演奏中 : フェーズ.ビュアーメッセージ待機中;
177 this.現在のフェーズ.Value = フェーズ.演奏中;
182 this._活性化した直後である = true;
185 protected override void On非活性化( デバイスリソース dr )
187 Log.Info( "演奏ステージを終了します。" );
189 //this.BGMを解放する(); → ここではまだ解放しない。結果ステージに非活性化時に、外部から解放する。
190 //FDK.Utilities.解放する( ref this._DecodedWaveSource ); → 演奏ステージインスタンスの破棄時に、外部から解放する。
192 if( null != this._背景動画 )
193 this.子リスト.Remove( this._背景動画 ); // 子リストから削除
196 public override void 進行描画する( デバイスリソース dr )
202 if( this._活性化した直後である )
204 this._活性化した直後である = false;
205 this._FPS = new FPS();
207 double 演奏開始位置の先頭からの時間sec = 0.0;
210 var msg = StrokeStyleT.最後に取得したビュアーメッセージ;
213 Log.Info( msg.ToString() );
215 演奏開始位置の先頭からの時間sec = this._スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( msg.演奏を開始する小節番号 );
216 Log.Info( $"演奏開始の先頭からの時間sec: {演奏開始位置の先頭からの時間sec}" );
218 this._Autoチップのドラム音を再生する = msg.ドラムチップのヒット時に発声する;
222 this._演奏開始時刻sec = StrokeStyleT.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
223 Log.Info( $"演奏開始時刻sec(背景動画再生チェック前): {this._演奏開始時刻sec}" );
225 this._スクロール譜面.再生中の時刻なら動画とBGMを再生開始する( 演奏開始位置の先頭からの時間sec );
227 // 演奏開始時刻sec の設定(2) 動画とBGMが再生された場合の誤差を修正する。
228 this._演奏開始時刻sec = StrokeStyleT.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
229 Log.Info( $"演奏開始時刻sec(背景動画再生チェック後): {this._演奏開始時刻sec}" );
234 double 演奏時刻sec = this._現在の演奏時刻secを返す();
236 #region " 譜面スクロール速度が変化している場合の追い付き進行。"
238 double 倍率 = this._現在進行描画中の譜面スクロール速度の倍率.Value;
240 if( 倍率 < StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 )
242 // todo: 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているので、あとからVPSに依存しないよう修正すること。
243 this._現在進行描画中の譜面スクロール速度の倍率.Value =
244 Math.Min( 倍率 + 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 );
246 else if( 倍率 > StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 )
249 this._現在進行描画中の譜面スクロール速度の倍率.Value =
250 Math.Max( 倍率 - 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.譜面スクロール速度の倍率 );
255 #region " 背景動画とBGMの開始と進行描画。"
257 if( this._背景動画開始済み.Value )
259 float width = dr.設計画面サイズdpx.Width;
260 float height = dr.設計画面サイズdpx.Height;
262 // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
263 switch( StrokeStyleT.ユーザ管理.現在選択されているユーザ.オプション.演奏動画の表示パターン )
269 this._背景動画?.進行描画する( dr, new RectangleF( 0f, 0f, width, height ), 0.2f ); // 全体
272 float 上移動dpx = 100.0f;
274 this._背景動画?.進行描画する( dr, new RectangleF(
275 width * ( 1f - 拡大縮小率 ) / 2f,
276 height * ( 1f - 拡大縮小率 ) / 2f - 上移動dpx,
287 this._背景動画?.進行描画する( dr, new RectangleF( 0f, 0f, width, height ), 1.0f );
293 // 動画が重たいかもしれないので、演奏時刻をここで再度取得する。
294 演奏時刻sec = this._現在の演奏時刻secを返す();
296 // 背景動画が再生されているのにBGMがまだ再生されていないなら、再生を開始する。
297 if( false == this._BGM再生開始済み )
300 this._BGM再生開始済み = true;
306 this._ステージ台.描画する( dr, 0.0f, 0.0f );
307 this._レーンフレーム.進行描画する( dr );
308 this._コンボ.進行描画する( dr );
309 this._ヒット判定文字列.進行描画する( dr );
310 this._スクロール譜面.小節線拍線を進行描画する( dr, 演奏時刻sec );
311 this._判定バー.描画する( dr, 597f, 座標.判定バーの中央Y座標dpx - 43f );
312 this._ドラムセット.進行描画する( dr );
313 this._スクロール譜面.チップを進行描画する( dr, 演奏時刻sec );
314 this._回転羽.進行描画する( dr );
315 this._FPS.VPSをカウントする();
316 this._FPS画像.表示文字列 = $"VPS: {this._FPS.現在のVPS.ToString()} / FPS: {this._FPS.現在のFPS.ToString()}";
317 this._FPS画像.進行描画する( dr, 0f, 0f );
319 // --> 入力処理は、スクロール譜面.高頻度処理タスク で行う。
322 public void 演奏を停止する()
324 this._スクロール譜面?.演奏を停止する();
326 this._背景動画開始済み.Value = false;
330 this._コンボ.COMBO値 = 0;
333 public void BGMを停止する()
335 if( null != this._BGM ) // 背景動画がなければ BGM も null であり、それはエラーではない。
338 FDK.Utilities.解放する( ref this._BGM );
339 //FDK.Utilities.解放する( ref this._デコード済みWaveSource ); → ここでは解放しない。
343 public void BGMのキャッシュを解放する()
347 FDK.Utilities.解放する( ref this._デコード済みWaveSource );
350 private bool _活性化した直後である = false;
352 private double _演奏開始時刻sec = 0.0;
354 private bool _Autoチップのドラム音を再生する = true;
356 private readonly コンボ _コンボ;
358 private readonly レーンフレーム _レーンフレーム;
360 private readonly スクロール譜面 _スクロール譜面;
362 private readonly ヒット判定文字列 _ヒット判定文字列;
364 private readonly 回転羽 _回転羽 = new 回転羽( 32 );
366 private readonly ドラムサウンド _ドラムサウンド;
368 private readonly ドラムセット _ドラムセット;
370 private readonly 画像 _判定バー;
372 private readonly 画像 _ステージ台;
374 private readonly 文字列画像 _FPS画像;
376 private RWLock<bool> _背景動画開始済み = new RWLock<bool>( false );
378 private bool _BGM再生開始済み = false;
380 private RWLock<double> _現在進行描画中の譜面スクロール速度の倍率 = new RWLock<double>( 0.0 );
383 /// 停止と解放は、演奏ステージクラスの非活性化後に、外部から行われる。
384 /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMを停止する"/>
385 /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMのキャッシュを解放する"/>
387 private FDK.メディア.サウンド.WASAPI.Sound _BGM = null;
390 /// BGM の生成もとになるデコード済みサウンドデータ。
393 /// 活性化と非活性化に関係なく、常に最後にデコードしたデータを持つ。(キャッシュ)
394 /// 演奏ステージインスタンスを破棄する際に、このインスタンスもDisposeすること。
396 private DecodedWaveSource _デコード済みWaveSource = null;
398 private 動画 _背景動画 = null;
400 private FPS _FPS = null;
402 private double _現在の演奏時刻secを返す()
404 return StrokeStyleT.サウンドデバイス.GetDevicePosition() - this._演奏開始時刻sec;