using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace SST.ステージ.演奏
{
+ /// <remarks>
+ /// 入力:
+ /// StrokeStyleT.演奏スコア(ビュアーモードなら null でも可)
+ /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)
+ /// StrokeStyleT.Wasapiデバイス.AudioClock
+ /// StrokeStyleT.ビュアーモードである
+ /// StrokeStyleT.最後に取得したビュアーメッセージ
+ ///
+ /// 出力:
+ /// (A) クリアまたは失敗した場合
+ /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
+ /// this.現在のフェーズ ← クリアor失敗
+ ///
+ /// (B) キャンセルされた場合
+ /// StrokeStyleT.ユーザ管理.現在選択されているユーザ(譜面スクロール速度の倍率)(変更されていた場合)
+ /// this.現在のフェーズ ← キャンセル
+ /// </remarks>
class 演奏ステージ : ステージ
{
- public Dictionary<ヒット判定種別, int> ヒットした回数 { get; } = new Dictionary<ヒット判定種別, int>();
+ public ConcurrentDictionary<ヒット判定種別, int> ヒットした回数 { get; } = new ConcurrentDictionary<ヒット判定種別, int>();
public enum フェーズ
{
初期状態,
演奏中,
クリアor失敗,
キャンセル,
+ ビュアーメッセージ待機中,
}
- public フェーズ 現在のフェーズ { get; protected set; } = フェーズ.初期状態;
+ public FDK.同期.RWLock<フェーズ> 現在のフェーズ { get; } = new FDK.同期.RWLock<フェーズ>( フェーズ.初期状態 );
public 演奏ステージ()
{
this.子リスト.Add( this.ヒット判定文字列 = new ヒット判定文字列() );
this.子リスト.Add( this.回転羽 = new 回転羽( 最大同時発火数: 32 ) );
this.子リスト.Add( this.ドラムサウンド = new ドラムサウンド() );
- this.子リスト.Add( this.ドラムセット = new 汎用.ドラムセット() );
+ this.子リスト.Add( this.ドラムセット = new ドラムセット() );
this.子リスト.Add( this.判定バー = new 画像( @"$(Static)\images\判定バー.png" ) );
this.子リスト.Add( this.ステージ台 = new 画像( @"$(Static)\images\ステージ台.png" ) );
this.子リスト.Add( this.FPS画像 = new 文字列画像() );
- // 子Activity の外部依存 Action を実装する。
- this.スクロール譜面.ヒット判定文字列開始 = ( chipType, hitType ) => { this.ヒット判定文字列.表示開始( chipType, hitType ); };
- this.スクロール譜面.コンボリセット = () => { this.コンボ.COMBO値 = 0; };
- this.スクロール譜面.コンボ加算 = () => { this.コンボ.COMBO値++; };
- this.スクロール譜面.ヒット判定数加算 = ( hitType ) => { this.ヒットした回数[ hitType ]++; };
- this.スクロール譜面.背景動画再生開始 = () => { this.背景動画開始済み = true; };
+ // 子Activity の外部依存 Action の実体を定義する。
+ this.スクロール譜面.ヒット判定文字列開始 = ( chipType, hitType ) => {
+ this.ヒット判定文字列.表示開始( chipType, hitType );
+ };
+ this.スクロール譜面.コンボリセット = () => {
+ this.コンボ.COMBO値.Value = 0;
+ };
+ this.スクロール譜面.コンボ加算 = () => {
+ this.コンボ.COMBO値.Value++;
+ };
+ this.スクロール譜面.ヒット判定数加算 = ( hitType ) => {
+ this.ヒットした回数[ hitType ]++;
+ };
+ this.スクロール譜面.背景動画の長さsec = () => {
+ return ( null != this.BGM ) ? this.BGM.長さsec : 0.0;
+ };
+ this.スクロール譜面.背景動画再生開始 = ( 開始位置sec ) => {
+ this.背景動画.再生を開始する( 開始位置sec );
+ this.背景動画開始済み.Value = true;
+ if( null != this.BGM )
+ {
+ this.BGM.位置sec = 開始位置sec;
+ this.BGM?.Play();
+ }
+ this.BGM再生開始済み = true;
+ };
this.スクロール譜面.チップヒット = ( chip ) => {
this.回転羽.発火する( chip.チップ種別 );
- this.ドラムサウンド.発声する( chip.チップ種別, chip.音量 * 0.25f );
+ if( this.Autoチップのドラム音を再生する )
+ this.ドラムサウンド.発声する( chip.チップ種別, chip.音量 * 0.25f );
+ };
+ this.スクロール譜面.ステージクリア = () => {
+ this.現在のフェーズ.Value = フェーズ.クリアor失敗;
+ };
+ this.スクロール譜面.リアルタイム演奏時刻sec = () => {
+ return this.現在の演奏時刻secを返す();
+ };
+ this.スクロール譜面.譜面スクロール速度の倍率 = () => {
+ return this.現在進行描画中の譜面スクロール速度の倍率.Value;
+ };
+ this.スクロール譜面.FPSをカウント = () => {
+ this.FPS.FPSをカウントする();
};
- this.スクロール譜面.ステージクリア = () => { this.現在のフェーズ = フェーズ.クリアor失敗; };
}
protected override void On活性化( デバイスリソース dr )
{
- FDK.Log.Info( "演奏ステージを開始します。" );
- Debug.Assert( null != StrokeStyleT.演奏スコア, "[バグあり] 演奏スコアが null です。" );
+ FDK.Log.Info( "演奏ステージを開始します。" + ( StrokeStyleT.ビュアーモードである ? "(ViewerMode)" : "" ) );
- #region " ヒットした回数をすべてクリア。"
+ #region " 動画ファイルパスが有効なら、背景動画とBGMを初期化する。"
//----------------
- this.ヒットした回数.Clear();
- foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
- this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
- //----------------
- #endregion
- #region " 選択ノードの持つ動画ファイルパスが有効なら背景動画とBGMを初期化する。"
- //----------------
- var 動画ファイルパス = ( (SST.曲.MusicNode) StrokeStyleT.曲ツリー管理.現在選択されているノード ).動画ファイルパス;
- if( 動画ファイルパス.Nullでも空でもない() )
+ if( ( null != StrokeStyleT.演奏スコア ) && ( StrokeStyleT.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
+ {
+ // 動画を子リストに追加。
+ this.子リスト.Add( this.背景動画 = new 動画( StrokeStyleT.演奏スコア.背景動画ファイル名, StrokeStyleT.Config.動画デコーダのキューサイズ ) );
+
+ // 動画から BGM を作成。
+ this.BGM = StrokeStyleT.サウンドデバイス.CreateSound( StrokeStyleT.演奏スコア.背景動画ファイル名 );
+ }
+ else
{
- this.子リスト.Add( this.背景動画 = new 動画( 動画ファイルパス ) ); // 子リストに追加
- this.BGM = new FDK.メディア.サウンド.WASAPI排他.Sound();
- this.BGM.ファイルから作成する( 動画ファイルパス );
- StrokeStyleT.Wasapiデバイス.サウンドをミキサーに追加する( this.BGM ); // 作成に失敗した Sound を追加しても鳴らないだけなので、ノーチェックで大丈夫。
+ this.背景動画 = null;
+ this.BGM = null;
}
//----------------
#endregion
this.演奏開始時刻sec = 0.0;
- this.現在進行描画中の譜面スクロール速度の倍率 = StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率;
+ this.現在進行描画中の譜面スクロール速度の倍率.Value = StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率;
this.BGM再生開始済み = false;
- this.背景動画開始済み = false;
+ this.背景動画開始済み.Value = false;
this.レーンフレーム.左端位置dpx = 400f;
this.レーンフレーム.高さdpx = dr.設計画面サイズdpx.Height;
- this.現在のフェーズ = フェーズ.演奏中;
+ this.ヒットした回数.Clear();
+ foreach( var 判定 in typeof( ヒット判定種別 ).GetEnumValues() )
+ this.ヒットした回数[ (ヒット判定種別) 判定 ] = 0;
+
+ // 最初のフェーズを設定する。
+ if( StrokeStyleT.ビュアーモードである )
+ {
+ // 演奏スコアが設定済みなら演奏開始。それ以外ならメッセージ待機へ。
+ this.現在のフェーズ.Value = ( null != StrokeStyleT.演奏スコア ) ? フェーズ.演奏中 : フェーズ.ビュアーメッセージ待機中;
+ }
+ else
+ {
+ // 演奏開始。
+ this.現在のフェーズ.Value = フェーズ.演奏中;
+ }
+
this.活性化した直後である = true;
}
protected override void On非活性化( デバイスリソース dr )
{
FDK.Log.Info( "演奏ステージを終了します。" );
- //this.BGMã\82\92解æ\94¾ã\81\99ã\82\8b(); â\86\92 ã\81\93ã\81\93ã\81§ã\81¯ã\81¾ã\81 解æ\94¾ã\81\97ã\81ªã\81\84ã\80\82çµ\90æ\9e\9cã\82¹ã\83\86ã\83¼ã\82¸ã\81\8cçµ\82ã\82\8fã\81£ã\81\9fã\81¨ã\81\8dã\81«解放する。
+ //this.BGMã\82\92解æ\94¾ã\81\99ã\82\8b(); â\86\92 ã\81\93ã\81\93ã\81§ã\81¯ã\81¾ã\81 解æ\94¾ã\81\97ã\81ªã\81\84ã\80\82çµ\90æ\9e\9cã\82¹ã\83\86ã\83¼ã\82¸ã\81\8cçµ\82ã\82\8fã\82\8bã\81¨ã\81\8dã\81«ã\80\81å¤\96é\83¨ã\81\8bã\82\89解放する。
if( null != this.背景動画 )
this.子リスト.Remove( this.背景動画 ); // 子リストから削除
//----------------
if( this.活性化した直後である )
{
- this.演奏開始時刻sec = this.サウンドタイマ.現在のデバイス位置secを取得する( StrokeStyleT.Wasapiデバイス.AudioClock );
- this.FPS = new FDK.カウンタ.FPS();
this.活性化した直後である = false;
+ this.FPS = new FDK.カウンタ.FPS();
+
+ double 演奏開始位置の先頭からの時間sec = 0.0;
+ int 演奏開始小節番号 = 0;
+ var msg = StrokeStyleT.最後に取得したビュアーメッセージ;
+ if( null != msg )
+ {
+ 演奏開始小節番号 = msg.演奏開始小節番号;
+ this.Autoチップのドラム音を再生する = msg.ドラムチップ発声;
+ }
+ 演奏開始位置の先頭からの時間sec = this.スクロール譜面.演奏開始小節番号を設定しその時刻secを返す( 演奏開始小節番号 );
+
+ long position, qpcPosition, frequency;
+ StrokeStyleT.サウンドデバイス.GetClock( out position, out qpcPosition, out frequency );
+
+ this.演奏開始時刻sec =
+ this.サウンドタイマ.現在のデバイス位置secを取得する( position, qpcPosition, frequency )
+ - 演奏開始位置の先頭からの時間sec; // 「+」じゃないので注意!
}
//----------------
#endregion
- double 演奏時刻sec = this.サウンドタイマ.現在のデバイス位置secを取得する( StrokeStyleT.Wasapiデバイス.AudioClock ) - this.演奏開始時刻sec;
+ double 演奏時刻sec = this.現在の演奏時刻secを返す();
- this.FPS.FPSをカウントする();
- this.FPS.VPSをカウントする();
- this.FPS画像.表示文字列 = $"VPS: {this.FPS.現在のVPS.ToString()}";
-
- #region " 譜面スクロール速度の追い付き進行。"
+ #region " 譜面スクロール速度が変化している場合の追い付き進行。"
//----------------
- if( this.現在進行描画中の譜面スクロール速度の倍率 < StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
+ double 倍率 = this.現在進行描画中の譜面スクロール速度の倍率.Value;
+ if( 倍率 < StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
{
- // 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているが、まあVPSなど60~120くらいやろうからよし。
- this.現在進行描画中の譜面スクロール速度の倍率 =
- Math.Min( this.現在進行描画中の譜面スクロール速度の倍率 + 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
+ // todo: 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているので、あとからVPSに依存しないよう修正すること。
+ this.現在進行描画中の譜面スクロール速度の倍率.Value =
+ Math.Min( 倍率 + 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
}
- else if( this.現在進行描画中の譜面スクロール速度の倍率 > StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
+ else if( 倍率 > StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 )
{
- // 同上。
- this.現在進行描画中の譜面スクロール速度の倍率 =
- Math.Max( this.現在進行描画中の譜面スクロール速度の倍率 - 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
+ // todo: 同上。
+ this.現在進行描画中の譜面スクロール速度の倍率.Value =
+ Math.Max( 倍率 - 0.05, StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 );
}
//----------------
#endregion
-
- if( this.背景動画開始済み )
+ #region " 背景動画とBGMの開始/進行描画を行う。"
+ //----------------
+ if( this.背景動画開始済み.Value )
{
// 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
this.背景動画?.進行描画する( dr, new SharpDX.RectangleF( 0f, 0f, dr.設計画面サイズdpx.Width, dr.設計画面サイズdpx.Height ) );
- // å\8b\95ç\94»ã\81\8cé\87\8dã\81\9fã\81\84ã\81\8bã\82\82ã\81\97ã\82\8cã\81ªã\81\84ã\81®ã\81§ã\80\81æ\99\82å\88»ã\82\92ã\81\93ã\81\93ã\81§å\86\8d取得する。
- 演奏時刻sec = this.サウンドタイマ.現在のデバイス位置secを取得する( StrokeStyleT.Wasapiデバイス.AudioClock ) - this.演奏開始時刻sec;
+ // å\8b\95ç\94»ã\81\8cé\87\8dã\81\9fã\81\84ã\81\8bã\82\82ã\81\97ã\82\8cã\81ªã\81\84ã\81®ã\81§ã\80\81æ¼\94å¥\8fæ\99\82å\88»ã\82\92ã\81\93ã\81\93ã\81§å\86\8d度取得する。
+ 演奏時刻sec = this.現在の演奏時刻secを返す();
// 背景動画が再生されているのにBGMがまだ再生されていないなら、再生を開始する。
if( false == this.BGM再生開始済み )
{
- this.BGM?.再生を開始する();
+ this.BGM?.Play();
this.BGM再生開始済み = true;
}
}
+ //----------------
+ #endregion
- this.ステージ台.進行描画する( dr, 0.0f, 0.0f );
+ this.ステージ台.描画する( dr, 0.0f, 0.0f );
this.レーンフレーム.進行描画する( dr );
this.コンボ.進行描画する( dr );
this.ヒット判定文字列.進行描画する( dr );
- this.スクロール譜面.小節線拍線を進行描画する( dr, 演奏時刻sec, this.現在進行描画中の譜面スクロール速度の倍率 );
- this.判定バー.進行描画する( dr, 597f, 座標.判定バーの中央Y座標dpx - 43f );
+ this.スクロール譜面.小節線拍線を進行描画する( dr, 演奏時刻sec );
+ this.判定バー.描画する( dr, 597f, 座標.判定バーの中央Y座標dpx - 43f );
this.ドラムセット.進行描画する( dr );
- this.スクロール譜面.チップを進行描画する( dr, 演奏時刻sec, this.現在進行描画中の譜面スクロール速度の倍率 );
+ this.スクロール譜面.チップを進行描画する( dr, 演奏時刻sec );
this.回転羽.進行描画する( dr );
+ this.FPS.VPSをカウントする();
+ this.FPS画像.表示文字列 = $"VPS: {this.FPS.現在のVPS.ToString()} / FPS: {this.FPS.現在のFPS.ToString()}";
this.FPS画像.進行描画する( dr, 0f, 0f );
// 入力。
StrokeStyleT.すべての入力デバイスをポーリングする();
// ESC 押下 → キャンセル
- if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Escape ) )
+ if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Escape ) &&
+ StrokeStyleT.ビュアーモードではない ) // ビュアーモードでは無効。
{
this.BGMを解放する();
- this.現在のフェーズ = フェーズ.キャンセル;
+ this.現在のフェーズ.Value = フェーズ.キャンセル;
}
// 上矢印押下 → 譜面スクロール速度の倍率を +0.5
else if( StrokeStyleT.キーボード入力.キーが押された( SharpDX.DirectInput.Key.Up ) )
Math.Max( StrokeStyleT.ユーザ管理.現在選択されているユーザ.譜面スクロール速度の倍率 - 0.5, 0.5 ); // 最小 0.5
}
}
+ public void 演奏を停止する()
+ {
+ this.スクロール譜面?.演奏を停止する();
+ this.背景動画開始済み.Value = false;
+ this.BGMを解放する();
+ }
public void BGMを解放する()
{
- if( null != this.BGM ) // 背景動画がなければ null である
+ if( null != this.BGM ) // 背景動画がなければ BGM も null である
{
- StrokeStyleT.Wasapiデバイス.サウンドをミキサーから削除する( this.BGM );
+ this.BGM.Stop();
FDK.Utilities.解放する( ref this.BGM );
}
}
protected bool 活性化した直後である = false;
- protected bool 背景動画開始済み = false;
+ protected FDK.同期.RWLock<bool> 背景動画開始済み = new FDK.同期.RWLock<bool>( false );
protected bool BGM再生開始済み = false;
- protected double 現在進行描画中の譜面スクロール速度の倍率 = 0.0;
+ protected FDK.同期.RWLock<double> 現在進行描画中の譜面スクロール速度の倍率 = new FDK.同期.RWLock<double>( 0.0 );
protected double 演奏開始時刻sec = 0.0;
- protected readonly FDK.メディア.サウンド.WASAPI排他.SoundTimer サウンドタイマ = new FDK.メディア.サウンド.WASAPI排他.SoundTimer();
+ protected bool Autoチップのドラム音を再生する = true;
+ protected readonly FDK.メディア.サウンド.WASAPI.SoundTimer サウンドタイマ = new FDK.メディア.サウンド.WASAPI.SoundTimer();
protected readonly SST.ステージ.演奏.コンボ コンボ;
protected readonly SST.ステージ.演奏.レーンフレーム レーンフレーム;
protected readonly SST.ステージ.演奏.スクロール譜面 スクロール譜面;
protected readonly SST.ステージ.演奏.ヒット判定文字列 ヒット判定文字列;
protected readonly SST.ステージ.演奏.回転羽 回転羽 = new 回転羽( 32 );
protected readonly SST.ステージ.演奏.ドラムサウンド ドラムサウンド;
- protected readonly SST.ステージ.汎用.ドラムセット ドラムセット;
+ protected readonly SST.ステージ.ドラムセット ドラムセット;
protected readonly FDK.メディア.画像 判定バー;
protected readonly FDK.メディア.画像 ステージ台;
protected readonly FDK.メディア.文字列画像 FPS画像;
/// <remarks>
- /// 解放は、このクラスの非活性化後に、外から行われる。
+ /// 解放は、演奏ステージクラスの非活性化後に、外部から行われる。
/// <see cref="SST.ステージ.演奏.演奏ステージ.BGMを解放する"/>
/// </remarks>
- protected FDK.メディア.サウンド.WASAPI排他.Sound BGM = null;
+ protected FDK.メディア.サウンド.WASAPI.Sound BGM = null;
protected FDK.カウンタ.FPS FPS = null;
/// <summary>
/// 動的子Activity。背景動画を再生しない場合は null のまま。
/// </summary>
protected FDK.メディア.動画 背景動画 = null;
+
+ private double 現在の演奏時刻secを返す()
+ {
+ long position, qpcPosition, frequency;
+ StrokeStyleT.サウンドデバイス.GetClock( out position, out qpcPosition, out frequency );
+
+ return this.サウンドタイマ.現在のデバイス位置secを取得する( position, qpcPosition, frequency ) - this.演奏開始時刻sec;
+ }
}
}