OSDN Git Service

AUTOヒット時にレーンフラッシュするようにした。
[strokestylet/CsWin10Desktop3.git] / StrokeStyleT / ステージ / 演奏 / 演奏ステージ.cs
index ace4323..2ec8baa 100644 (file)
@@ -3,20 +3,17 @@ using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
 using SharpDX;
 using SharpDX.DirectInput;
 using FDK;
 using FDK.メディア;
 using FDK.メディア.サウンド.WASAPI;
 using FDK.カウンタ;
-using FDK.同期;
 using SSTFormat.v2;
 using SST.入力;
 using SST.設定;
 
-using Utilities = FDK.Utilities;
+using FDKUtilities = FDK.FDKUtilities;
 
 namespace SST.ステージ.演奏
 {
@@ -26,6 +23,7 @@ namespace SST.ステージ.演奏
                {
                        演奏中,
                        Failed,
+                       クリア時フェードアウト,
                        クリア,
                        キャンセル,
                        ビュアーメッセージ待機,
@@ -37,6 +35,10 @@ namespace SST.ステージ.演奏
                        set;
                }
 
+               public const float ヒット判定バーの中央Y座標 = 869f + 43f;
+
+               public const float レーンフレームの左端位置 = 619f;
+
                public ConcurrentDictionary<ヒットランク種別, int> ヒットランク別ヒット回数
                {
                        get;
@@ -45,128 +47,115 @@ namespace SST.ステージ.演奏
 
                public 演奏ステージ()
                {
-                       this.子リスト.Add( this._ステージ台 = new 画像( @"$(System)\images\ステージ台.png" ) );
+                       this.子リスト.Add( this._ステージ台 = new 画像( @"$(System)images\ステージ台.png" ) );
                        this.子リスト.Add( this._レーンフレーム = new レーンフレーム() );
                        this.子リスト.Add( this._ドラムセット = new ドラムセット() );
-                       this.子リスト.Add( this._ヒット判定バー = new 画像( @"$(System)\images\判定バー.png" ) );
-                       this.子リスト.Add( this._チップ画像 = new 画像( @"$(System)\images\Chips.png" ) );
+                       this.子リスト.Add( this._ヒット判定バー = new 画像( @"$(System)images\判定バー.png" ) );
+                       this.子リスト.Add( this._チップ画像 = new 画像( @"$(System)images\Chips.png" ) );
                        this.子リスト.Add( this._回転羽 = new 回転羽( 最大同時発火数: 32 ) );
                        this.子リスト.Add( this._コンソールフォント = new コンソールフォント() );
                        this.子リスト.Add( this._ドラムサウンド = new ドラムサウンド() );
                        this.子リスト.Add( this._コンボ = new コンボ() );
                        this.子リスト.Add( this._ヒットランク = new ヒットランク() );
-                       this.子リスト.Add( this._FPSパラメータ = new 文字列画像() );
+                       this.子リスト.Add( this._クリア時フェードアウト = new フェードアウト( 1.0f ) );
+                       this.子リスト.Add( this._白パネル = new 画像( @"$(System)images\パネル白64x64.png" ) );
+                       this.子リスト.Add( this._FPS = new FPS() );
                }
 
-               protected override void Onæ´»æ\80§å\8c\96( ã\83\87ã\83\90ã\82¤ã\82¹ã\83ªã\82½ã\83¼ã\82¹ dr )
+               protected override void Onæ´»æ\80§å\8c\96( ã\82°ã\83©ã\83\95ã\82£ã\83\83ã\82¯ã\83\87ã\83\90ã\82¤ã\82¹ gd )
                {
-                       using( Log.Block( Utilities.現在のメソッド名 ) )
+                       using( Log.Block( FDKUtilities.現在のメソッド名 ) )
                        {
-                               lock( this._スレッド間同期 )
+                               this._活性化した直後である = true;
+                               this._背景動画開始済み = false;
+                               this._現在進行描画中の譜面スクロール速度の倍率 = App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率;
+                               this._描画開始チップ番号 = -1;
+                               this._背景動画 = null;
+                               this._BGM = null;
+                               //this._デコード済みWaveSource = null;    --> キャッシュなので消さない。
+                               this._背景動画開始済み = false;
+                               this._BGM再生開始済み = false;
+                               this._チップ画像の矩形リスト = new 矩形リスト( @"$(System)images\Chips Rectangle List.xml" );      // デバイスリソースは持たないので、子Activityではない。
+
+                               this.ヒットランク別ヒット回数.Clear();
+                               foreach( ヒットランク種別 hitRankType in Enum.GetValues( typeof( ヒットランク種別 ) ) )
+                                       this.ヒットランク別ヒット回数[ hitRankType ] = 0;
+
+                               #region " 背景動画とBGMを生成する。"
+                               //----------------
+                               if( ( null != App.演奏スコア ) && ( App.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
                                {
-                                       this._活性化した直後である = true;
-                                       this._演奏開始時刻sec = 0.0;
-                                       this._背景動画開始済み = false;
-                                       this._現在進行描画中の譜面スクロール速度の倍率 = App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率;
-                                       this._描画開始チップ番号 = -1;
-                                       this._背景動画 = null;
-                                       this._BGM = null;
-                                       //this._デコード済みWaveSource = null;    --> キャッシュなので消さない。
-                                       this._背景動画開始済み = false;
-                                       this._BGM再生開始済み = false;
-                                       this._高頻度進行タスクへのイベント = new TriStateEvent( TriStateEvent.状態種別.OFF );
-                                       this._チップ画像の矩形リスト = new 矩形リスト( @"$(System)\images\Chips Rectangle List.xml" );      // デバイスリソースは持たないので、子Activityではない。
-
-                                       this.ヒットランク別ヒット回数.Clear();
-                                       foreach( var hitRankType in typeof( ヒットランク種別 ).GetEnumValues() )
-                                               this.ヒットランク別ヒット回数[ ( ヒットランク種別 ) hitRankType ] = 0;
-
-                                       #region " 背景動画とBGMを生成する。"
-                                       //----------------
-                                       if( ( null != App.演奏スコア ) && ( App.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
-                                       {
-                                               Log.Info( "背景動画とBGMを読み込みます。" );
+                                       Log.Info( "背景動画とBGMを読み込みます。" );
 
-                                               // 動画を子リストに追加。
-                                               this.子リスト.Add( this._背景動画 = new 動画( App.演奏スコア.背景動画ファイル名, App.システム設定.動画デコーダのキューサイズ ) );
+                                       // 動画を子リストに追加。
+                                       this.子リスト.Add( this._背景動画 = new 動画( App.演奏スコア.背景動画ファイル名, App.システム設定.動画デコーダのキューサイズ ) );
 
-                                               // 動画から音声パートを抽出して BGM を作成。
-                                               if( ( null != this._デコード済みWaveSource ) && this._デコード済みWaveSource.Path.Equals( App.演奏スコア.背景動画ファイル名 ) )
-                                               {
-                                                       // (A) 前回生成したBGMとパスが同じなので、前回のデコード済み WaveSource をキャッシュとして再利用する。
-                                                       Log.Info( "前回生成したサウンドデータを再利用します。" );
-                                               }
-                                               else
-                                               {
-                                                       // (B) 初めての生成か、または前回生成したBGMとパスが違うので、新しくデコード済み WaveSource を生成する。
-                                                       this._デコード済みWaveSource?.Dispose();
-                                                       this._デコード済みWaveSource = new DecodedWaveSource( App.演奏スコア.背景動画ファイル名, App.サウンドデバイス.WaveFormat );
-                                               }
-
-                                               this._BGM?.Dispose();
-                                               this._BGM = App.サウンドデバイス.サウンドを生成する( this._デコード済みWaveSource );
-                                       }
-                                       else
+                                       // 動画から音声パートを抽出して BGM を作成。
+                                       if( ( null != this._デコード済みWaveSource ) && this._デコード済みWaveSource.Path.Equals( App.演奏スコア.背景動画ファイル名 ) )
                                        {
-                                               Log.Info( "背景動画とBGMはありません。" );
-                                       }
-                                       //----------------
-                                       #endregion
-
-                                       #region " 最初のフェーズを設定する。"
-                                       //----------------
-                                       if( App.ビュアーモードではない )
-                                       {
-                                               this.現在のフェーズ = フェーズ.演奏中;
+                                               // (A) 前回生成したBGMとパスが同じなので、前回のデコード済み WaveSource をキャッシュとして再利用する。
+                                               Log.Info( "前回生成したサウンドデータを再利用します。" );
                                        }
                                        else
                                        {
-                                               this.現在のフェーズ = フェーズ.ビュアーメッセージ待機;
+                                               // (B) 初めての生成か、または前回生成したBGMとパスが違うので、新しくデコード済み WaveSource を生成する。
+                                               this._デコード済みWaveSource?.Dispose();
+                                               this._デコード済みWaveSource = new DecodedWaveSource( App.演奏スコア.背景動画ファイル名, App.サウンドデバイス.WaveFormat );
                                        }
-                                       //----------------
-                                       #endregion
+
+                                       this._BGM?.Dispose();
+                                       this._BGM = App.サウンドデバイス.サウンドを生成する( this._デコード済みWaveSource );
+                               }
+                               else
+                               {
+                                       Log.Info( "背景動画とBGMはありません。" );
                                }
+                               //----------------
+                               #endregion
+
+                               #region " 最初のフェーズを設定する。"
+                               //----------------
+                               if( ( App.ビュアーモードではない ) ||
+                                       ( ( null != App.最後に取得したビュアーメッセージ ) &&
+                                         ( App.最後に取得したビュアーメッセージ.種別 == Viewer.ViewerMessage.指示種別.演奏開始 ) ) )
+                               {
+                                       this.現在のフェーズ = フェーズ.演奏中;
+                               }
+                               else
+                               {
+                                       this.現在のフェーズ = フェーズ.ビュアーメッセージ待機;
+                               }
+                               //----------------
+                               #endregion
                        }
                }
 
-               protected override void Oné\9d\9eæ´»æ\80§å\8c\96( ã\83\87ã\83\90ã\82¤ã\82¹ã\83ªã\82½ã\83¼ã\82¹ dr )
+               protected override void Oné\9d\9eæ´»æ\80§å\8c\96( ã\82°ã\83©ã\83\95ã\82£ã\83\83ã\82¯ã\83\87ã\83\90ã\82¤ã\82¹ gd )
                {
-                       using( Log.Block( Utilities.現在のメソッド名 ) )
+                       using( Log.Block( FDKUtilities.現在のメソッド名 ) )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       #region " 高頻度進行タスクがまだ実行されていれば、終了する。 "
-                                       //----------------
-                                       if( this._高頻度進行タスクへのイベント.現在の状態 == TriStateEvent.状態種別.ON )
-                                       {
-                                               // タスクへ終了を指示。
-                                               this._高頻度進行タスクへのイベント.現在の状態 = TriStateEvent.状態種別.OFF;
-                                               //this._高頻度進行タスクへのイベント.無効になるまでブロックする();   --> タスクが終了するまで待つ必要はない。
-                                       }
-                                       //----------------
-                                       #endregion
+                               // 背景動画を生成した場合は子リストから削除。
+                               if( null != this._背景動画 )
+                                       this.子リスト.Remove( this._背景動画 );
 
-                                       // 背景動画を生成した場合は子リストから削除。
-                                       if( null != this._背景動画 )
-                                               this.子リスト.Remove( this._背景動画 );
+                               App.ユーザ管理.選択されているユーザ.保存する();
 
-                                       this._活性化した直後である = false;
-                               }
+                               this._活性化した直後である = false;
                        }
                }
 
-               public override void é\80²è¡\8cæ\8f\8fç\94»ã\81\99ã\82\8b( ã\83\87ã\83\90ã\82¤ã\82¹ã\83ªã\82½ã\83¼ã\82¹ dr )
+               public override void é«\98é\80\9fé\80²è¡\8cã\81\99ã\82\8b()
                {
                        Debug.Assert( this.活性化している );
-                       Debug.Assert( null != dr );
 
-                       lock( this._スレッド間同期 )
+                       switch( this.現在のフェーズ )
                        {
-                               double 演奏時刻sec = this._演奏開始からの経過時間secを返す();
+                               case フェーズ.演奏中:
+                                       #region " *** "
+                                       //----------------
+                                       {
+                                               // 進行。
 
-                               switch( this.現在のフェーズ )
-                               {
-                                       case フェーズ.演奏中:
                                                #region " 初めての進行描画。"
                                                //----------------
                                                if( this._活性化した直後である )
@@ -180,8 +169,8 @@ namespace SST.ステージ.演奏
                                                        double 演奏開始位置の先頭からの時間sec = 0.0;
 
                                                        // 演奏開始時刻の設定(1)
-                                                       this._演奏開始時刻sec = App.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
-                                                       Log.Info( $"演奏開始時刻(背景動画再生チェック前): {this._演奏開始時刻sec} sec" );
+                                                       App.サウンドタイマ.リセットする( 演奏開始位置の先頭からの時間sec );
+                                                       Log.Info( $"演奏開始時刻(背景動画再生チェック前): {App.サウンドタイマ.現在時刻sec} sec" );
 
                                                        // 演奏開始時刻の設定(2) ビュアーメッセージがある場合、開始時刻を修正する。
                                                        var msg = App.最後に取得したビュアーメッセージ;
@@ -199,130 +188,432 @@ namespace SST.ステージ.演奏
                                                        this._再生中の時刻なら動画とBGMを再生開始する( 演奏開始位置の先頭からの時間sec );
 
                                                        // 演奏開始時刻の設定(3) 動画とBGMが再生された場合、ここに到達するまでに多少の遅延が生じているので、ここで演奏開始時刻を再取得しておく。
-                                                       this._演奏開始時刻sec = App.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
-                                                       Log.Info( $"演奏開始時刻(背景動画再生チェック後): {this._演奏開始時刻sec} sec" );
+                                                       App.サウンドタイマ.リセットする( 演奏開始位置の先頭からの時間sec );
+                                                       Log.Info( $"演奏開始時刻(背景動画再生チェック後): {App.サウンドタイマ.現在時刻sec} sec" );
                                                        //----------------
                                                        #endregion
+                                               }
+                                               //----------------
+                                               #endregion
 
-                                                       this._FPS = new FPS();
+                                               this._FPS.FPSをカウントしプロパティを更新する();
 
-                                                       // 高頻度進行タスクを生成、実行開始。
-                                                       Task.Factory.StartNew( this._高頻度進行処理タスクエントリ );
+                                               #region " 背景動画が再生されているのにBGMがまだ再生されていないなら、すぐに再生を開始する。"
+                                               //----------------
+                                               if( this._背景動画開始済み && !( this._BGM再生開始済み ) )
+                                               {
+                                                       this._BGM?.Play();
+                                                       this._BGM再生開始済み = true;
                                                }
                                                //----------------
                                                #endregion
-                                               #region " 譜面スクロール速度が変化している → 追い付き進行 "
+
+                                               double 現在の演奏時刻sec = this._演奏開始からの経過時間secを返す();
+
+                                               // 自動演奏
+
+                                               #region " 自動ヒット処理。"
                                                //----------------
-                                               {
-                                                       double 倍率 = this._現在進行描画中の譜面スクロール速度の倍率;
+                                               this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーと描画との時間sec, ヒット判定バーと発声との時間sec, ヒット判定バーとの距離 ) => {
+
+                                                       var オプション設定 = App.ユーザ管理.選択されているユーザ.オプション設定;
+                                                       var 対応表 = オプション設定.ドラムとチップと入力の対応表[ chip.チップ種別 ];
+                                                       var AutoPlay = オプション設定.AutoPlay[ 対応表.AutoPlay種別 ];
+
+                                                       bool チップはヒット済みである = chip.ヒット済みである;
+                                                       bool チップはMISSエリアに達している = ( ヒット判定バーと描画との時間sec > オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] );
+                                                       bool チップは描画についてヒット判定バーを通過した = ( 0 <= ヒット判定バーと描画との時間sec );
+                                                       bool チップは発声についてヒット判定バーを通過した = ( 0 <= ヒット判定バーと発声との時間sec );
 
-                                                       if( 倍率 < App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 )
+                                                       if( チップはヒット済みである )
                                                        {
-                                                               // todo: 時間間隔に関係なく進行描画ごとに倍率を変えてしまっているので、あとからVPSに依存しないよう修正すること。
-                                                               this._現在進行描画中の譜面スクロール速度の倍率 =
-                                                                       Math.Min( 倍率 + 0.05, App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 );
+                                                               // 何もしない。
+                                                               return;
                                                        }
-                                                       else if( 倍率 > App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 )
+
+                                                       if( チップはMISSエリアに達している )
                                                        {
-                                                               // todo: 同上。
-                                                               this._現在進行描画中の譜面スクロール速度の倍率 =
-                                                                       Math.Max( 倍率 - 0.05, App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 );
+                                                               // MISS判定。
+                                                               if( AutoPlay && 対応表.AutoPlayON.MISS判定 )
+                                                               {
+                                                                       this._チップのヒット処理を行う( chip, ヒットランク種別.MISS, 対応表.AutoPlayON.自動ヒット時処理, ヒット判定バーと発声との時間sec );
+                                                                       return;
+                                                               }
+                                                               else if( !AutoPlay && 対応表.AutoPlayOFF.MISS判定 )
+                                                               {
+                                                                       this._チップのヒット処理を行う( chip, ヒットランク種別.MISS, 対応表.AutoPlayOFF.ユーザヒット時処理, ヒット判定バーと発声との時間sec );
+                                                                       return;
+                                                               }
+                                                               else
+                                                               {
+                                                                       // 通過。
+                                                               }
                                                        }
-                                               }
+
+                                                       if( チップは発声についてヒット判定バーを通過した )
+                                                       {
+                                                               // 自動ヒット判定。
+                                                               if( ( AutoPlay && 対応表.AutoPlayON.自動ヒット && 対応表.AutoPlayON.自動ヒット時処理.再生 ) ||
+                                                                       ( !AutoPlay && 対応表.AutoPlayOFF.自動ヒット && 対応表.AutoPlayOFF.自動ヒット時処理.再生 ) )
+                                                               {
+                                                                       this._チップの発声を行う( chip, ヒット判定バーと発声との時間sec );
+                                                               }
+                                                       }
+
+                                                       if( チップは描画についてヒット判定バーを通過した )
+                                                       {
+                                                               // 自動ヒット判定。
+                                                               if( AutoPlay && 対応表.AutoPlayON.自動ヒット )
+                                                               {
+                                                                       this._チップのヒット処理を行う( chip, ヒットランク種別.AUTO, 対応表.AutoPlayON.自動ヒット時処理, ヒット判定バーと発声との時間sec );
+                                                                       return;
+                                                               }
+                                                               else if( !AutoPlay && 対応表.AutoPlayOFF.自動ヒット )
+                                                               {
+                                                                       this._チップのヒット処理を行う( chip, ヒットランク種別.AUTO, 対応表.AutoPlayOFF.自動ヒット時処理, ヒット判定バーと発声との時間sec );
+                                                                       return;
+                                                               }
+                                                               else
+                                                               {
+                                                                       // 通過。
+                                                               }
+                                                       }
+
+                                               } );
                                                //----------------
                                                #endregion
-                                               #region " 背景動画とBGMの進行描画。"
+
+                                               // 入力(1) 手動演奏
+
+                                               App.入力管理.すべての入力デバイスをポーリングする( 入力履歴を記録する: false );
+
+                                               #region " ユーザヒット処理。"
                                                //----------------
-                                               if( this._背景動画開始済み )
                                                {
-                                                       // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
-                                                       switch( App.ユーザ管理.選択されているユーザ.オプション設定.動画表示パターン種別 )
+                                                       var オプション設定 = App.ユーザ管理.選択されているユーザ.オプション設定;
+
+                                                       #region " ヒットしてようがしてまいが起こすアクション(空打ち処理)。 "
+                                                       //----------------
+                                                       foreach( var 入力 in App.入力管理.ポーリング結果 )
                                                        {
-                                                               case 動画表示パターン種別.中央表示:
-                                                                       #region " *** "
-                                                                       //----------------
-                                                                       {
-                                                                               float w = dr.設計画面サイズdpx.Width;
-                                                                               float h = dr.設計画面サイズdpx.Height;
+                                                               if( 入力.InputEvent.離された )
+                                                                       continue;   // 押下イベントじゃないなら無視。
 
-                                                                               this._背景動画?.進行描画する( dr, new RectangleF( 0f, 0f, w, h ), 0.2f );  // 全体
+                                                               this._ドラムセット.ヒットアニメ開始( 入力.Type, オプション設定.表示レーンの左右 );
 
-                                                                               float 拡大縮小率 = 0.75f;
-                                                                               float 上移動dpx = 100.0f;
+                                                               var カラム = オプション設定.ドラムとチップと入力の対応表.対応表.Where( ( kvp ) => ( kvp.Value.ドラム入力種別 == 入力.Type ) );  // カラムは struct なので FirstOrDefault() は使わない。
+                                                               if( 0 < カラム.Count() )
+                                                                       this._レーンフレーム.フラッシュ開始( カラム.First().Value.表示レーン種別 );
+                                                       }
+                                                       //----------------
+                                                       #endregion
 
-                                                                               this._背景動画?.進行描画する( dr, new RectangleF(
-                                                                                       w * ( 1f - 拡大縮小率 ) / 2f,
-                                                                                       h * ( 1f - 拡大縮小率 ) / 2f - 上移動dpx,
-                                                                                       w * 拡大縮小率,
-                                                                                       h * 拡大縮小率 ) );
-                                                                       }
-                                                                       //----------------
-                                                                       #endregion
-                                                                       break;
+                                                       var 処理済み入力 = new List<ドラム入力イベント>(); // ヒット処理が終わった入力は、二重処理しないよう、この中に追加しておく。
+
+                                                       this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーと描画との時間sec, ヒット判定バーと発声との時間sec, ヒット判定バーとの距離 ) => {
+
+                                                               #region " ヒット判定 "
+                                                               //----------------
+                                                               if( chip.ヒット済みである )
+                                                                       return;
+
+                                                               var チップの対応表 = オプション設定.ドラムとチップと入力の対応表[ chip.チップ種別 ];
+
+                                                               if( オプション設定.AutoPlay[ チップの対応表.AutoPlay種別 ] )
+                                                                       return; // チップが AutoPlay ON である。
+
+                                                               if( !( チップの対応表.AutoPlayOFF.ユーザヒット ) )
+                                                                       return; // このチップは、AutoPlay OFF 時でもユーザヒットの対象ではない。
 
-                                                               case 動画表示パターン種別.最大表示:
-                                                                       #region " *** "
+                                                               double ヒット判定バーとの時間の絶対値sec = Math.Abs( ヒット判定バーと描画との時間sec );
+
+                                                               bool チップはMISSエリアに達している = ( ヒット判定バーと描画との時間sec > オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] );
+                                                               if( !( ヒット判定バーと描画との時間sec >= -( オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] ) && !チップはMISSエリアに達している ) )
+                                                                       return; // チップはヒット可能エリアにある。
+
+                                                               var ヒット入力 = App.入力管理.ポーリング結果.FirstOrDefault( ( 入力 ) => {
+                                                                       #region " chip にヒットする入力があれば true を返す。"
                                                                        //----------------
-                                                                       this._背景動画?.進行描画する( dr, new RectangleF( 0f, 0f, dr.設計画面サイズdpx.Width, dr.設計画面サイズdpx.Height ), 1.0f );
+                                                                       if( !( 入力.InputEvent.押された ) )
+                                                                               return false;   // 押下入力じゃない。
+
+                                                                       if( 処理済み入力.Contains( 入力 ) )
+                                                                               return false;   // すでに今回のターンで処理済み(=処理済み入力リストに追加済み)。
+
+                                                                       // chip がシンバルフリーの対象なら、chip に直接対応する入力の他にも、ヒット判定すべき入力がある。
+                                                                       if( チップの対応表.シンバルフリーの対象 && オプション設定.シンバルフリー )
+                                                                       {
+                                                                               // この入力に対応するカラムのうち、
+                                                                               var カラムs = オプション設定.ドラムとチップと入力の対応表.対応表.Where( ( kvp ) => ( kvp.Value.ドラム入力種別 == 入力.Type ) );
+                                                                               foreach( var カラム in カラムs )
+                                                                               {
+                                                                                       // シンバルフリーなチップがあるなら true。
+                                                                                       if( カラム.Value.シンバルフリーの対象 )
+                                                                                               return true;
+                                                                               }
+                                                                               // まったくなかったら false。
+                                                                               return false;
+                                                                       }
+
+                                                                       // chip に対応する入力なら true。
+                                                                       return ( チップの対応表.ドラム入力種別 == 入力.Type );
                                                                        //----------------
                                                                        #endregion
-                                                                       break;
+                                                               } );
+
+                                                               if( null == ヒット入力 )
+                                                                       return;
+                                                               //----------------
+                                                               #endregion
+
+                                                               処理済み入力.Add( ヒット入力 );
+
+                                                               #region " ヒットランクを判定する。"
+                                                               //----------------
+                                                               var ヒットランク = ヒットランク種別.POOR;
+
+                                                               if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.PERFECT ] )
+                                                               {
+                                                                       ヒットランク = ヒットランク種別.PERFECT;
+                                                               }
+                                                               else if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.GREAT ] )
+                                                               {
+                                                                       ヒットランク = ヒットランク種別.GREAT;
+                                                               }
+                                                               else if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.GOOD ] )
+                                                               {
+                                                                       ヒットランク = ヒットランク種別.GOOD;
+                                                               }
+                                                               //----------------
+                                                               #endregion
+
+                                                               this._チップのヒット処理を行う( chip, ヒットランク, チップの対応表.AutoPlayOFF.ユーザヒット時処理, ヒット判定バーと発声との時間sec );
+
+                                                       } );
+                                               }
+                                               //----------------
+                                               #endregion
+
+                                               // 入力(2) アプリ操作
+
+                                               if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Escape ) )
+                                               {
+                                                       #region " ESC → ステージキャンセル "
+                                                       //----------------
+                                                       if( App.ビュアーモードではない )
+                                                       {
+                                                               this.BGMを停止する();
+                                                               this.現在のフェーズ = フェーズ.キャンセル;
+                                                               break;  // このタスクを終了。
                                                        }
+                                                       else
+                                                       {
+                                                               // ビュアーモード時のキャンセルは無効。
+                                                       }
+                                                       //----------------
+                                                       #endregion
+                                               }
+                                               if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Up ) )
+                                               {
+                                                       #region " 上 → 譜面スクロールを加速 "
+                                                       //----------------
+                                                       const double 最大倍率 = 8.0;
+                                                       App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 =
+                                                               Math.Min( App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 + 0.5, 最大倍率 );
+                                                       //----------------
+                                                       #endregion
+                                               }
+                                               if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Down ) )
+                                               {
+                                                       #region " 下 → 譜面スクロールを減速 "
+                                                       //----------------
+                                                       const double 最小倍率 = 0.5;
+                                                       App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 =
+                                                               Math.Max( App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 - 0.5, 最小倍率 );
+                                                       //----------------
+                                                       #endregion
+                                               }
+                                               foreach( var ev in App.入力管理.MIDI入力デバイス.入力イベントリスト.Where( ( ie ) => ( 255 == ie.Key ) ) )
+                                               {
+                                                       #region " ハイハットの開閉 "
+                                                       //----------------
+                                                       this._ドラムセット.ハイハットのベロシティ = ev.Velocity;
+                                                       //----------------
+                                                       #endregion
+                                               }
+                                       }
+                                       //----------------
+                                       #endregion
+                                       break;
+                       }
+               }
+
+               public override void 進行描画する( グラフィックデバイス gd )
+               {
+                       Debug.Assert( this.活性化している );
+                       Debug.Assert( null != gd );
+
+                       switch( this.現在のフェーズ )
+                       {
+                               case フェーズ.演奏中:
+                                       if( this._活性化した直後である )
+                                               break;  // 進行処理がまだ行われていない。
 
-                                                       // 動画が重たいかもしれないので、演奏時刻をここで更新しておく。
-                                                       演奏時刻sec = this._演奏開始からの経過時間secを返す();
+                                       #region " 譜面スクロール速度が変化している → 追い付き進行 "
+                                       //----------------
+                                       {
+                                               double 倍率 = this._現在進行描画中の譜面スクロール速度の倍率;
 
-                                                       // 背景動画が再生されているのにBGMがまだ再生されていないなら、すぐに再生を開始する。
-                                                       if( false == this._BGM再生開始済み )
+                                               if( 倍率 < App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 )
+                                               {
+                                                       if( 0 > this._スクロール倍率追い付き用_最後の値 )
                                                        {
-                                                               this._BGM?.Play();
-                                                               this._BGM再生開始済み = true;
+                                                               this._スクロール倍率追い付き用カウンタ = new LoopCounter( 0, 1000, 10 );    // 0→100; 全部で10×1000 = 10000ms = 10sec あれば十分だろう
+                                                               this._スクロール倍率追い付き用_最後の値 = 0;
+                                                       }
+                                                       else
+                                                       {
+                                                               while( this._スクロール倍率追い付き用_最後の値 < this._スクロール倍率追い付き用カウンタ.現在値 )
+                                                               {
+                                                                       倍率 += 0.025;
+                                                                       this._スクロール倍率追い付き用_最後の値++;
+                                                               }
+
+                                                               this._現在進行描画中の譜面スクロール速度の倍率 =
+                                                                       Math.Min( 倍率, App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 );
                                                        }
                                                }
-                                               //----------------
-                                               #endregion
-                                               this._ステージ台.描画する( dr, 0f, 0f );
-                                               this._レーンフレーム.進行描画する( dr, レーンフレームの左端位置dpx );
-                                               this._コンボ.進行描画する( dr );
-                                               this._ヒットランク.進行描画する( dr, レーンフレームの左端位置dpx );
-                                               this._小節線拍線を描画する( dr, 演奏時刻sec );
-                                               this._ヒット判定バー.描画する( dr, 597f, ヒット判定バーの中央Y座標dpx - 43f );
-                                               this._ドラムセット.進行描画する( dr );
-                                               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 );
-                                               break;
+                                               else if( 倍率 > App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 )
+                                               {
+                                                       if( 0 > this._スクロール倍率追い付き用_最後の値 )
+                                                       {
+                                                               this._スクロール倍率追い付き用カウンタ = new LoopCounter( 0, 1000, 10 );    // 0→100; 全部で10×1000 = 10000ms = 10sec あれば十分だろう
+                                                               this._スクロール倍率追い付き用_最後の値 = 0;
+                                                       }
+                                                       else
+                                                       {
+                                                               while( this._スクロール倍率追い付き用_最後の値 < this._スクロール倍率追い付き用カウンタ.現在値 )
+                                                               {
+                                                                       倍率 -= 0.025;
+                                                                       this._スクロール倍率追い付き用_最後の値++;
+                                                               }
 
-                                       case フェーズ.Failed:
-                                               break;
+                                                               this._現在進行描画中の譜面スクロール速度の倍率 =
+                                                                       Math.Max( 倍率, App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 );
+                                                       }
+                                               }
+                                               else
+                                               {
+                                                       this._スクロール倍率追い付き用_最後の値 = -1;
+                                                       this._スクロール倍率追い付き用カウンタ = null;
+                                               }
+                                       }
+                                       //----------------
+                                       #endregion
 
-                                       case フェーズ.クリア:
-                                               break;
+                                       this._コンボ.進行描画する( gd );
+                                       this._回転羽.進行描画する( gd );
 
-                                       case フェーズ.キャンセル:
-                                               break;
+                                       double 演奏時刻sec = this._演奏開始からの経過時間secを返す();
 
-                                       case フェーズ.ビュアーメッセージ待機:
-                                               break;
-                               }
+                                       #region " 背景動画とBGMの進行描画。"
+                                       //----------------
+                                       if( this._背景動画開始済み )
+                                       {
+                                               // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
+                                               switch( App.ユーザ管理.選択されているユーザ.オプション設定.動画表示パターン種別 )
+                                               {
+                                                       case 動画表示パターン種別.中央表示:
+                                                               #region " *** "
+                                                               //----------------
+                                                               {
+                                                                       float w = gd.設計画面サイズ.Width;
+                                                                       float h = gd.設計画面サイズ.Height;
+
+                                                                       this._背景動画?.描画する( gd, new RectangleF( 0f, 0f, w, h ), 0.2f );  // 全体
+
+                                                                       float 拡大縮小率 = 0.75f;
+                                                                       float 上移動 = 100.0f;
+
+                                                                       // 進行描画せず、直前に取得したフレームをそのまま描画する。
+                                                                       this._背景動画?.前のフレームを描画する( gd, new RectangleF(
+                                                                               w * ( 1f - 拡大縮小率 ) / 2f,
+                                                                               h * ( 1f - 拡大縮小率 ) / 2f - 上移動,
+                                                                               w * 拡大縮小率,
+                                                                               h * 拡大縮小率 ) );
+                                                               }
+                                                               //----------------
+                                                               #endregion
+                                                               break;
+
+                                                       case 動画表示パターン種別.最大表示:
+                                                               #region " *** "
+                                                               //----------------
+                                                               this._背景動画?.描画する( gd, new RectangleF( 0f, 0f, gd.設計画面サイズ.Width, gd.設計画面サイズ.Height ), 1.0f );
+                                                               //----------------
+                                                               #endregion
+                                                               break;
+                                               }
+
+                                               // 動画が重たいかもしれないので、演奏時刻をここで更新しておく。   ---> 重たくても更新禁止!(譜面スクロールがガタつく原因になる)
+                                               //演奏時刻sec = this._演奏開始からの経過時間secを返す();
+                                       }
+                                       //----------------
+                                       #endregion
+
+                                       this._ステージ台.描画する( gd, 0f, 0f );
+                                       this._レーンフレーム.進行描画する( gd, レーンフレームの左端位置 );
+                                       this._コンボ.進行描画する( gd );
+                                       this._ヒットランク.進行描画する( gd, レーンフレームの左端位置 );
+                                       this._小節線拍線を描画する( gd, 演奏時刻sec );
+                                       this._ヒット判定バー.描画する( gd, 597f, ヒット判定バーの中央Y座標 - 43f );
+                                       this._ドラムセット.進行描画する( gd );
+                                       this._チップを描画する( gd, 演奏時刻sec );
+                                       this._回転羽.進行描画する( gd );
+                                       this._FPS.VPSをカウントする();
+                                       this._FPS.描画する( gd, 0f, 0f );
+                                       break;
+
+                               case フェーズ.クリア時フェードアウト:
+                                       if( this._クリア時フェードアウト.開始されていない )
+                                       {
+                                               this._クリア時フェードアウト.開始する();
+                                       }
+                                       else
+                                       {
+                                               if( this._クリア時フェードアウト.完了した )
+                                               {
+                                                       this.現在のフェーズ = フェーズ.クリア;
+                                               }
+                                       }
+                                       this._ステージ台.描画する( gd, 0f, 0f );
+                                       this._レーンフレーム.進行描画する( gd, レーンフレームの左端位置 );
+                                       this._ヒット判定バー.描画する( gd, 597f, ヒット判定バーの中央Y座標 - 43f );
+                                       this._ドラムセット.進行描画する( gd );
+                                       this._回転羽.進行描画する( gd );
+                                       this._クリア時フェードアウト.進行描画する( gd, this._白パネル );
+                                       this._FPS.VPSをカウントする();
+                                       this._FPS.描画する( gd, 0f, 0f );
+                                       break;
+
+                               case フェーズ.Failed:
+                               case フェーズ.クリア:
+                               case フェーズ.キャンセル:
+                               case フェーズ.ビュアーメッセージ待機:
+                                       break;
                        }
                }
 
                public void 演奏を停止する()
                {
-                       using( Log.Block( Utilities.現在のメソッド名 ) )
+                       using( Log.Block( FDKUtilities.現在のメソッド名 ) )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       this._描画開始チップ番号 = -1;   // 演奏停止
+                               this._描画開始チップ番号 = -1;   // 演奏停止
 
-                                       this.BGMを停止する();
-                                       this._背景動画開始済み = false;
+                               this.BGMを停止する();
+                               this._背景動画開始済み = false;
 
-                                       this._コンボ.COMBO値 = 0;
-                               }
+                               this._コンボ.COMBO値 = 0;
                        }
                }
 
@@ -332,41 +623,31 @@ namespace SST.ステージ.演奏
                /// </remarks>
                public void BGMを停止する()
                {
-                       using( Log.Block( Utilities.現在のメソッド名 ) )
+                       using( Log.Block( FDKUtilities.現在のメソッド名 ) )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       this._BGM?.Stop();
-                                       this._BGM?.Dispose();
-                                       this._BGM = null;
+                               this._BGM?.Stop();
+                               this._BGM?.Dispose();
+                               this._BGM = null;
 
-                                       //this._デコード済みWaveSource?.Dispose();        --> ここではまだ解放しない。
-                                       //this._デコード済みWaveSource = null;
-                               }
+                               //this._デコード済みWaveSource?.Dispose();        --> ここではまだ解放しない。
+                               //this._デコード済みWaveSource = null;
                        }
                }
 
                public void BGMのキャッシュを解放する()
                {
-                       using( Log.Block( Utilities.現在のメソッド名 ) )
+                       using( Log.Block( FDKUtilities.現在のメソッド名 ) )
                        {
                                this.BGMを停止する();
-                               Utilities.解放する( ref this._デコード済みWaveSource );
+                               FDKUtilities.解放する( ref this._デコード済みWaveSource );
                        }
                }
 
 
-               private const float ヒット判定バーの中央Y座標dpx = 869f + 43f;
-
-               private const float レーンフレームの左端位置dpx = 619f;
-
-
-               private double _演奏開始時刻sec = 0.0;
-
                private double _現在進行描画中の譜面スクロール速度の倍率 = 1.0;
 
                /// <summary>
-               ///             演奏スコア.チップリスト[] のうち、描画を始めるチップのインデックス番号。
+               ///             <see cref="スコア.チップリスト"/> のうち、描画を始めるチップのインデックス番号。
                ///             未演奏時・演奏終了時は -1 。
                /// </summary>
                /// <remarks>
@@ -409,7 +690,7 @@ namespace SST.ステージ.演奏
 
                private 矩形リスト _チップ画像の矩形リスト = null;
 
-               private 単純増加後反復カウンタ _チップアニメ = new 単純増加後反復カウンタ();
+               private LoopCounter _チップアニメ = new LoopCounter();
 
                private 回転羽 _回転羽 = null;
 
@@ -423,16 +704,15 @@ namespace SST.ステージ.演奏
 
                private FPS _FPS = null;
 
-               private 文字列画像 _FPSパラメータ = null;
+               private LoopCounter _スクロール倍率追い付き用カウンタ = null;
 
-               private bool _活性化した直後である;
+               private int _スクロール倍率追い付き用_最後の値 = -1;
 
-               /// <summary>
-               ///             OFF:タスク未生成、ON:タスク稼働中、無効:タスク終了済み
-               /// </summary>
-               private TriStateEvent _高頻度進行タスクへのイベント = null;
+               private フェードアウト _クリア時フェードアウト = null;
+
+               private 画像 _白パネル = null;
 
-               private readonly object _スレッド間同期 = new object();
+               private bool _活性化した直後である;
 
 
                // 譜面描画。
@@ -443,115 +723,106 @@ namespace SST.ステージ.演奏
                /// <returns>演奏開始位置sec。(0~)</returns>
                private double _演奏開始小節番号を設定しその時刻secを返す( int 演奏開始小節番号 )
                {
-                       lock( this._スレッド間同期 )
-                       {
-                               var score = App.演奏スコア;
+                       var score = App.演奏スコア;
 
-                               if( null == score )
-                                       return 0.0;
+                       if( null == score )
+                               return 0.0;
 
-                               double 演奏開始時刻sec = 0.0;
+                       double 演奏開始時刻sec = 0.0;
 
-                               for( int i = 0; i < score.チップリスト.Count; i++ )
+                       for( int i = 0; i < score.チップリスト.Count; i++ )
+                       {
+                               if( score.チップリスト[ i ].小節番号 < 演奏開始小節番号 )
                                {
-                                       if( score.チップリスト[ i ].小節番号 < 演奏開始小節番号 )
-                                       {
-                                               // 開始チップ以前のチップはヒット済みとする。
-                                               score.チップリスト[ i ].ヒット済みの状態にする();
-                                       }
-                                       else
-                                       {
-                                               // 開始チップを設定。
-                                               this._描画開始チップ番号 = i;
+                                       // 開始チップ以前のチップはヒット済みとする。
+                                       score.チップリスト[ i ].ヒット済みの状態にする();
+                               }
+                               else
+                               {
+                                       // 開始チップを設定。
+                                       this._描画開始チップ番号 = i;
 
-                                               // 演奏開始時刻は、開始チップの発声時刻から少し早めの値に。
-                                               演奏開始時刻sec = score.チップリスト[ i ].発声時刻sec;
-                                               演奏開始時刻sec -= 0.5;
+                                       // 演奏開始時刻は、開始チップの発声時刻から少し早めの値に。
+                                       演奏開始時刻sec = score.チップリスト[ i ].発声時刻sec;
+                                       演奏開始時刻sec -= 0.5;
 
-                                               // 開始チップ以降のすべてのチップをヒット前の状態にする。
-                                               for( int j = i; j >= 0; j-- )
-                                               {
-                                                       if( score.チップリスト[ j ].ヒット済みである )
-                                                               score.チップリスト[ j ].ヒット前の状態にする();
-                                               }
-
-                                               break;
+                                       // 開始チップ以降のすべてのチップをヒット前の状態にする。
+                                       for( int j = i; j >= 0; j-- )
+                                       {
+                                               if( score.チップリスト[ j ].ヒット済みである )
+                                                       score.チップリスト[ j ].ヒット前の状態にする();
                                        }
-                               }
 
-                               return 演奏開始時刻sec;
+                                       break;
+                               }
                        }
+
+                       return 演奏開始時刻sec;
                }
 
                private void _再生中の時刻なら動画とBGMを再生開始する( double 時刻sec )
                {
-                       using( Log.Block( Utilities.現在のメソッド名 ) )
+                       using( Log.Block( FDKUtilities.現在のメソッド名 ) )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       Log.Info( $"指定された時刻: {時刻sec} sec" );
+                               Log.Info( $"指定された時刻: {時刻sec} sec" );
 
-                                       var 背景動画チップ = App.演奏スコア.チップリスト.FirstOrDefault( (chip) => ( chip.チップ種別 == チップ種別.背景動画 ) );
+                               var 背景動画チップ = App.演奏スコア.チップリスト.FirstOrDefault( ( chip ) => ( chip.チップ種別 == チップ種別.背景動画 ) );
 
-                                       if( null == 背景動画チップ )
-                                       {
-                                               Log.Info( "背景動画チップは存在しません。" );
-                                               return;
-                                       }
+                               if( null == 背景動画チップ )
+                               {
+                                       Log.Info( "背景動画チップは存在しません。" );
+                                       return;
+                               }
 
-                                       double 背景動画の長さsec = this._BGM?.長さsec ?? 0.0;        // 動画のサイズは、映像ではなく音声を優先する。
+                               double 背景動画の長さsec = this._BGM?.長さsec ?? 0.0;        // 動画のサイズは、映像ではなく音声を優先する。
 
-                                       Log.Info( $"背景動画の発生時刻: {背景動画チップ.発声時刻sec} sec" );
-                                       Log.Info( $"背景動画の長さ: {背景動画の長さsec} sec" );
+                               Log.Info( $"背景動画の発生時刻: {背景動画チップ.発声時刻sec} sec" );
+                               Log.Info( $"背景動画の長さ: {背景動画の長さsec} sec" );
 
-                                       // 指定された時刻secは再生期間内?
-                                       if( ( 背景動画チップ.発声時刻sec <= 時刻sec ) && ( 時刻sec < ( 背景動画チップ.発声時刻sec + 背景動画の長さsec ) ) )
-                                       {
-                                               // 背景動画の再生を開始する。
-                                               double 再生開始時刻sec = ( 時刻sec - 背景動画チップ.発声時刻sec );
-                                               this._背景動画?.再生を開始する( 再生開始時刻sec );
-                                               this._背景動画開始済み = true;
-                                               this._BGM?.Play( 再生開始時刻sec );
-                                               this._BGM再生開始済み = true;
-                                               Log.Info( $"背景動画の再生を開始しました。(再生開始時刻: {再生開始時刻sec} sec)" );
-                                       }
-                                       else
-                                       {
-                                               Log.Info( $"指定された時刻は背景動画の再生期間内ではないので、何もしません。" );
-                                       }
+                               // 指定された時刻secは再生期間内?
+                               if( ( 背景動画チップ.発声時刻sec <= 時刻sec ) && ( 時刻sec < ( 背景動画チップ.発声時刻sec + 背景動画の長さsec ) ) )
+                               {
+                                       // 背景動画の再生を開始する。
+                                       double 再生開始時刻sec = ( 時刻sec - 背景動画チップ.発声時刻sec );
+                                       this._背景動画?.再生を開始する( 再生開始時刻sec );
+                                       this._背景動画開始済み = true;
+                                       this._BGM?.Play( 再生開始時刻sec - App.サウンドデバイス.遅延sec );
+                                       this._BGM再生開始済み = true;
+                                       Log.Info( $"背景動画の再生を開始しました。(再生開始時刻: {再生開始時刻sec} sec)" );
+                               }
+                               else
+                               {
+                                       Log.Info( $"指定された時刻は背景動画の再生期間内ではないので、何もしません。" );
                                }
                        }
                }
 
                private double _演奏開始からの経過時間secを返す()
                {
-                       lock( this._スレッド間同期 )
-                       {
-                               return App.サウンドデバイス.GetDevicePosition() - this._演奏開始時刻sec;
-                       }
+                       return App.サウンドタイマ.現在時刻sec;
                }
 
                // 小節線・拍線 と チップ は描画階層(奥行き)が異なるので、別々のメソッドに分ける。
 
-               private void _å°\8fç¯\80ç·\9aæ\8b\8dç·\9aã\82\92æ\8f\8fç\94»ã\81\99ã\82\8b( ã\83\87ã\83\90ã\82¤ã\82¹ã\83ªã\82½ã\83¼ã\82¹ dr, double 現在の演奏時刻sec )
+               private void _å°\8fç¯\80ç·\9aæ\8b\8dç·\9aã\82\92æ\8f\8fç\94»ã\81\99ã\82\8b( ã\82°ã\83©ã\83\95ã\82£ã\83\83ã\82¯ã\83\87ã\83\90ã\82¤ã\82¹ gd, double 現在の演奏時刻sec )
                {
                        if( null == this._チップ画像 )
                                return;
 
                        this._チップ画像.加算合成 = false;
 
-                       this._描画範囲のチップに処理を適用する( ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離dpx ) => {
+                       this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーと描画との時間sec, ヒット判定バーと発声との時間sec, ヒット判定バーとの距離 ) => {
 
                                if( chip.チップ種別 == チップ種別.小節線 )
                                {
                                        #region " 小節線 "
                                        //----------------
-                                       float 左位置dpx = レーンフレームの左端位置dpx + レーンフレーム.レーンto横中央相対位置dpx[ 表示レーン種別.LeftCrash ] - 1f;               // -1f はレーン線の幅の半分。
-                                       float 上位置dpx = (float)( ヒット判定バーの中央Y座標dpx + ヒット判定バーとの距離dpx - 1f );   // -1f は小節線の厚みの半分。
+                                       float 左位置 = レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ 表示レーン種別.LeftCrash ] - 1f;                // -1f はレーン線の幅の半分。
+                                       float 上位置 = (float)( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 - 1f );   // -1f は小節線の厚みの半分。
                                        if( this._チップ画像の矩形リスト[ nameof( チップ種別.小節線 ) ] is RectangleF 画像範囲 )
                                        {
-                                               this._チップ画像.描画する( dr, 左位置dpx, 上位置dpx, 転送元矩形dpx: 画像範囲 );
-                                               this._チップ画像.描画する( dr, 左位置dpx + 画像範囲.Width, 上位置dpx, 転送元矩形dpx: 画像範囲 );
+                                               this._チップ画像.描画する( gd, 左位置, 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );     // false にすると、
+                                               this._チップ画像.描画する( gd, 左位置 + 画像範囲.Width, 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );        // チカチカする。
                                        }
                                        //----------------
                                        #endregion
@@ -560,12 +831,12 @@ namespace SST.ステージ.演奏
                                {
                                        #region " 拍線 "
                                        //----------------
-                                       float 左位置dpx = レーンフレームの左端位置dpx + レーンフレーム.レーンto横中央相対位置dpx [ 表示レーン種別.LeftCrash ] - 1f;       // -1f はレーン線の幅の半分。
-                                       float 上位置dpx = (float)( ヒット判定バーの中央Y座標dpx + ヒット判定バーとの距離dpx - 1f );   // -1f は拍線の厚みの半分。
+                                       float 左位置 = レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置 [ 表示レーン種別.LeftCrash ] - 1f;        // -1f はレーン線の幅の半分。
+                                       float 上位置 = (float)( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 - 1f );   // -1f は拍線の厚みの半分。
                                        if( this._チップ画像の矩形リスト[ nameof( チップ種別.拍線 ) ] is RectangleF 画像範囲 )
                                        {
-                                               this._チップ画像.描画する( dr, 左位置dpx: 左位置dpx, 上位置dpx: 上位置dpx, 転送元矩形dpx: 画像範囲 );
-                                               this._チップ画像.描画する( dr, 左位置dpx: 左位置dpx + 画像範囲.Width, 上位置dpx: 上位置dpx, 転送元矩形dpx: 画像範囲 );
+                                               this._チップ画像.描画する( gd, 左位置: 左位置, 上位置: 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );       // false にすると、
+                                               this._チップ画像.描画する( gd, 左位置: 左位置 + 画像範囲.Width, 上位置: 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );  // チカチカする。
                                        }
                                        //----------------
                                        #endregion
@@ -574,27 +845,27 @@ namespace SST.ステージ.演奏
                        } );
                }
 
-               private void _ã\83\81ã\83\83ã\83\97ã\82\92æ\8f\8fç\94»ã\81\99ã\82\8b( ã\83\87ã\83\90ã\82¤ã\82¹ã\83ªã\82½ã\83¼ã\82¹ dr, double 現在の演奏時刻sec )
+               private void _ã\83\81ã\83\83ã\83\97ã\82\92æ\8f\8fç\94»ã\81\99ã\82\8b( ã\82°ã\83©ã\83\95ã\82£ã\83\83ã\82¯ã\83\87ã\83\90ã\82¤ã\82¹ gd, double 現在の演奏時刻sec )
                {
                        if( null == this._チップ画像 )
                                return;
 
                        this._チップ画像.加算合成 = false;
 
-                       this._描画範囲のチップに処理を適用する( ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離dpx ) => {
+                       this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーと描画との時間sec, ヒット判定バーと発声との時間sec, ヒット判定バーとの距離 ) => {
 
-                               float 縦中央位置dpx = (float)( ヒット判定バーの中央Y座標dpx + ヒット判定バーとの距離dpx );
+                               float 縦中央位置 = (float)( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 );
 
                                #region " チップが描画開始チップであり、かつ、そのY座標が画面下端を超えたなら、描画開始チップ番号を更新する。"
                                //----------------
                                if( ( index == this._描画開始チップ番号 ) &&
-                                       ( dr.設計画面サイズdpx.Height + 40.0 < 縦中央位置dpx ) )   // +40 はチップが隠れるであろう適当なマージン。
+                                       ( gd.設計画面サイズ.Height + 40.0 < 縦中央位置 ) )   // +40 はチップが隠れるであろう適当なマージン。
                                {
                                        this._描画開始チップ番号++;
 
                                        if( App.演奏スコア.チップリスト.Count <= this._描画開始チップ番号 )
                                        {
-                                               this.現在のフェーズ = フェーズ.クリア;
+                                               this.現在のフェーズ = フェーズ.クリア時フェードアウト;
                                                this._描画開始チップ番号 = -1;    // 演奏完了。
                                                return;
                                        }
@@ -612,127 +883,127 @@ namespace SST.ステージ.演奏
                                switch( chip.チップ種別 )
                                {
                                        case チップ種別.LeftCrash:
-                                               _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ nameof( チップ種別.LeftCrash ) ], 縦中央位置dpx, 音量0to1 );
+                                               _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ nameof( チップ種別.LeftCrash ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.HiHat_Close:
-                                               _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置dpx, 音量0to1 );
+                                               _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.HiHat_HalfOpen:
-                                               _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置dpx, 音量0to1 );
-                                               _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_HalfOpen ) ], 縦中央位置dpx, 1.0f );
+                                               _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置, 音量0to1 );
+                                               _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_HalfOpen ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.HiHat_Open:
-                                               _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置dpx, 音量0to1 );
-                                               _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Open ) ], 縦中央位置dpx, 1.0f );
+                                               _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置, 音量0to1 );
+                                               _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Open ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.HiHat_Foot:
-                                               _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Foot ) ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Foot ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.Snare:
-                                               _アニメチップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare ) ], 縦中央位置dpx, 音量0to1 );
+                                               _アニメチップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.Snare_ClosedRim:
-                                               _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_ClosedRim ) ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_ClosedRim ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.Snare_OpenRim:
-                                               _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_OpenRim ) ], 縦中央位置dpx, 音量0to1 );
+                                               _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_OpenRim ) ], 縦中央位置, 音量0to1 );
                                                // ↓ないほうがいいかも。
-                                               //_単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare ) ], 縦中央位置dpx, 音量0to1 );
+                                               //_単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.Snare_Ghost:
-                                               _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_Ghost ) ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_Ghost ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.Bass:
-                                               _アニメチップを1つ描画する( 表示レーン種別.Bass, this._チップ画像の矩形リスト[ nameof( チップ種別.Bass ) ], 縦中央位置dpx, 音量0to1 );
+                                               _アニメチップを1つ描画する( 表示レーン種別.Bass, this._チップ画像の矩形リスト[ nameof( チップ種別.Bass ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.Tom1:
-                                               _アニメチップを1つ描画する( 表示レーン種別.Tom1, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom1 ) ], 縦中央位置dpx, 音量0to1 );
+                                               _アニメチップを1つ描画する( 表示レーン種別.Tom1, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom1 ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.Tom1_Rim:
-                                               _単画チップを1つ描画する( 表示レーン種別.Tom1, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom1_Rim ) ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.Tom1, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom1_Rim ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.Tom2:
-                                               _アニメチップを1つ描画する( 表示レーン種別.Tom2, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom2 ) ], 縦中央位置dpx, 音量0to1 );
+                                               _アニメチップを1つ描画する( 表示レーン種別.Tom2, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom2 ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.Tom2_Rim:
-                                               _単画チップを1つ描画する( 表示レーン種別.Tom2, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom2_Rim ) ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.Tom2, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom2_Rim ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.Tom3:
-                                               _アニメチップを1つ描画する( 表示レーン種別.Tom3, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom3 ) ], 縦中央位置dpx, 音量0to1 );
+                                               _アニメチップを1つ描画する( 表示レーン種別.Tom3, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom3 ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.Tom3_Rim:
-                                               _単画チップを1つ描画する( 表示レーン種別.Tom3, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom3_Rim ) ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.Tom3, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom3_Rim ) ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.RightCrash:
-                                               _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ nameof( チップ種別.RightCrash ) ], 縦中央位置dpx, 音量0to1 );
+                                               _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ nameof( チップ種別.RightCrash ) ], 縦中央位置, 音量0to1 );
                                                break;
 
                                        case チップ種別.China:
                                                if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Chinaは左 )
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftChina" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftChina" ], 縦中央位置, 音量0to1 );
                                                }
                                                else
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightChina" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightChina" ], 縦中央位置, 音量0to1 );
                                                }
                                                break;
 
                                        case チップ種別.Ride:
                                                if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Rideは左 )
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftRide" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftRide" ], 縦中央位置, 音量0to1 );
                                                }
                                                else
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightRide" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightRide" ], 縦中央位置, 音量0to1 );
                                                }
                                                break;
 
                                        case チップ種別.Ride_Cup:
                                                if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Rideは左 )
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftRide_Cup" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftRide_Cup" ], 縦中央位置, 音量0to1 );
                                                }
                                                else
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightRide_Cup" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightRide_Cup" ], 縦中央位置, 音量0to1 );
                                                }
                                                break;
 
                                        case チップ種別.Splash:
                                                if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Splashは左 )
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftSplash" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftSplash" ], 縦中央位置, 音量0to1 );
                                                }
                                                else
                                                {
-                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightSplash" ], 縦中央位置dpx, 音量0to1 );
+                                                       _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightSplash" ], 縦中央位置, 音量0to1 );
                                                }
                                                break;
 
                                        case チップ種別.LeftCymbal_Mute:
-                                               _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftCymbal_Mute" ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftCymbal_Mute" ], 縦中央位置, 1.0f );
                                                break;
 
                                        case チップ種別.RightCymbal_Mute:
-                                               _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightCymbal_Mute" ], 縦中央位置dpx, 1.0f );
+                                               _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightCymbal_Mute" ], 縦中央位置, 1.0f );
                                                break;
                                }
                                //----------------
@@ -742,348 +1013,162 @@ namespace SST.ステージ.演奏
 
                        #region " ローカル関数 "
                        //----------------
-                       void _単画チップを1つ描画する( 表示レーン種別 lane, RectangleF? 元矩形dpx, float 上位置dpx, float 音量0to1 )
+                       void _単画チップを1つ描画する( 表示レーン種別 lane, RectangleF? 元矩形, float 上位置, float 音量0to1 )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       if( null == 元矩形dpx )
-                                               return;
+                               if( null == 元矩形 )
+                                       return;
 
-                                       var 画像範囲dpx = (RectangleF) 元矩形dpx;
+                               var 画像範囲 = (RectangleF) 元矩形;
 
-                                       this._チップ画像?.描画する(
-                                               dr,
-                                               左位置dpx: レーンフレームの左端位置dpx + レーンフレーム.レーンto横中央相対位置dpx[ lane ] - ( 画像範囲dpx.Width / 2f ),
-                                               上位置dpx: 上位置dpx - ( ( 画像範囲dpx.Height / 2f ) * 音量0to1 ),
-                                               転送元矩形dpx: 元矩形dpx,
-                                               Y方向拡大率: 音量0to1 );
-                               }
+                               this._チップ画像?.描画する(
+                                       gd,
+                                       左位置: レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ lane ] - ( 画像範囲.Width / 2f ),
+                                       上位置: 上位置 - ( ( 画像範囲.Height / 2f ) * 音量0to1 ),
+                                       転送元矩形: 元矩形,
+                                       Y方向拡大率: 音量0to1 );
                        }
-                       void _アニメチップを1つ描画する( 表示レーン種別 lane, RectangleF? 画像範囲orNull, float Ydpx, float 音量0to1 )
+                       void _アニメチップを1つ描画する( 表示レーン種別 lane, RectangleF? 画像範囲orNull, float Y, float 音量0to1 )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       if( null == 画像範囲orNull )
-                                               return;
+                               if( null == 画像範囲orNull )
+                                       return;
 
-                                       var 画像範囲 = (RectangleF) 画像範囲orNull;
+                               var 画像範囲 = (RectangleF) 画像範囲orNull;
 
-                                       float チップ1枚の高さdpx = 18f;
-                                       画像範囲.Offset( 0f, this._チップアニメ.現在値 * 15f );   // 下端3dpxは下のチップと共有する前提のデザインなので、18f-3f = 15f。
-                                       画像範囲.Height = チップ1枚の高さdpx;
-                                       float 左位置dpx = レーンフレームの左端位置dpx + レーンフレーム.レーンto横中央相対位置dpx[ lane ] - ( 画像範囲.Width / 2f );
-                                       float 上位置dpx = Ydpx - ( チップ1枚の高さdpx / 2f ) * 音量0to1;
+                               float チップ1枚の高さ = 18f;
+                               画像範囲.Offset( 0f, this._チップアニメ.現在値 * 15f );   // 下端3pxは下のチップと共有する前提のデザインなので、18f-3f = 15f。
+                               画像範囲.Height = チップ1枚の高さ;
+                               float 左位置 = レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ lane ] - ( 画像範囲.Width / 2f );
+                               float 上位置 = Y - ( チップ1枚の高さ / 2f ) * 音量0to1;
 
-                                       this._チップ画像?.描画する( dr, 左位置dpx, 上位置dpx, 転送元矩形dpx: 画像範囲, Y方向拡大率: 音量0to1 );
-                               }
+                               this._チップ画像?.描画する( gd, 左位置, 上位置, 転送元矩形: 画像範囲, Y方向拡大率: 音量0to1 );
                        }
                        //----------------
                        #endregion
                }
 
-               // 高頻度進行処理関連。
-
                /// <summary>
-               ///             音声の再生と演奏用入力を行う
+               ///             <see cref="_描画開始チップ番号"/> から画面上端にはみ出すまでの間の各チップに対して、指定された処理を適用する
                /// </summary>
-               /// <remarks>
-               ///             描画処理とは独立したタスクを使い、より高頻度にループさせる。
-               /// </remarks>
-               private void _高頻度進行処理タスクエントリ()
+               /// <param name="適用する処理">引数は、順に、対象のチップ、チップ番号、ヒット判定バーとの時間sec、ヒット判定バーとの距離</param>
+               private void _描画範囲のチップに処理を適用する( double 現在の演奏時刻sec, Action<チップ, int, double, double, double> 適用する処理 )
                {
-                       Log.Info( "高頻度進行処理タスクを開始します。" );
-
-                       this._高頻度進行タスクへのイベント.現在の状態 = TriStateEvent.状態種別.ON;
+                       var スコア = App.演奏スコア;
+                       if( null == スコア )
+                               return;
 
-                       while( this._高頻度進行タスクへのイベント.現在の状態 == TriStateEvent.状態種別.ON )
+                       for( int i = this._描画開始チップ番号; ( 0 <= i ) && ( i < スコア.チップリスト.Count ); i++ )
                        {
-                               lock( this._スレッド間同期 )
-                               {
-                                       // 進行。
-
-                                       this._FPS.FPSをカウントしプロパティを更新する();
-
-                                       #region " 自動ヒット処理。"
-                                       //----------------
-                                       this._描画範囲のチップに処理を適用する( ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離dpx ) => {
-
-                                               var オプション設定 = App.ユーザ管理.選択されているユーザ.オプション設定;
-                                               var 対応表 = オプション設定.ドラムとチップと入力の対応表.対応表[ chip.チップ種別 ];
-                                               var AutoPlay = オプション設定.AutoPlay[ 対応表.AutoPlay種別 ];
-
-                                               bool チップはヒット済みである = chip.ヒット済みである;
-                                               bool チップはMISSエリアに達している = ( ヒット判定バーとの時間sec > オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] );
-                                               bool チップはヒット判定バーを通過した = ( 0 <= ヒット判定バーとの距離dpx );
-
-                                               if( チップはヒット済みである )
-                                               {
-                                                       // 何もしない。
-                                                       return;
-                                               }
-
-                                               if( チップはMISSエリアに達している )
-                                               {
-                                                       // MISS判定。
-                                                       if( AutoPlay && 対応表.AutoPlayON.MISS判定 )
-                                                       {
-                                                               this._チップのヒット処理を行う( chip, ヒットランク種別.MISS, 対応表.AutoPlayON.自動ヒット時処理 );
-                                                               return;
-                                                       }
-                                                       else if( !AutoPlay && 対応表.AutoPlayOFF.MISS判定 )
-                                                       {
-                                                               this._チップのヒット処理を行う( chip, ヒットランク種別.MISS, 対応表.AutoPlayOFF.ユーザヒット時処理 );
-                                                               return;
-                                                       }
-                                                       else
-                                                       {
-                                                               // 通過。
-                                                       }
-                                               }
-
-                                               if( チップはヒット判定バーを通過した )
-                                               {
-                                                       // 自動ヒット判定。
-                                                       if( AutoPlay && 対応表.AutoPlayON.自動ヒット )
-                                                       {
-                                                               this._チップのヒット処理を行う( chip, ヒットランク種別.AUTO, 対応表.AutoPlayON.自動ヒット時処理 );
-                                                               return;
-                                                       }
-                                                       else if( !AutoPlay && 対応表.AutoPlayOFF.自動ヒット )
-                                                       {
-                                                               this._チップのヒット処理を行う( chip, ヒットランク種別.AUTO, 対応表.AutoPlayOFF.自動ヒット時処理 );
-                                                               return;
-                                                       }
-                                                       else
-                                                       {
-                                                               // 通過。
-                                                       }
-                                               }
-
-                                       } );
-                                       //----------------
-                                       #endregion
-
-                                       // 入力。
-
-                                       App.入力管理.すべての入力デバイスをポーリングする( 入力履歴を記録する: false );
-
-                                       if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Escape ) )
-                                       {
-                                               #region " ESC → ステージキャンセル "
-                                               //----------------
-                                               if( App.ビュアーモードではない )
-                                               {
-                                                       this.BGMを停止する();
-                                                       this.現在のフェーズ = フェーズ.キャンセル;
-                                                       break;  // このタスクを終了。
-                                               }
-                                               else
-                                               {
-                                                       // ビュアーモード時のキャンセルは無効。
-                                               }
-                                               //----------------
-                                               #endregion
-                                       }
-                                       if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Up ) )
-                                       {
-                                               #region " 上 → 譜面スクロールを加速 "
-                                               //----------------
-                                               const double 最大倍率 = 8.0;
-                                               App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 =
-                                                       Math.Min( App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 + 0.5, 最大倍率 );
-                                               //----------------
-                                               #endregion
-                                       }
-                                       if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Down ) )
-                                       {
-                                               #region " 下 → 譜面スクロールを減速 "
-                                               //----------------
-                                               const double 最小倍率 = 0.5;
-                                               App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 =
-                                                       Math.Max( App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 - 0.5, 最小倍率 );
-                                               //----------------
-                                               #endregion
-                                       }
+                               var チップ = スコア.チップリスト[ i ];
 
-                                       #region " ユーザヒット処理。"
-                                       //----------------
-                                       foreach( var 入力 in App.入力管理.ポーリング結果 )
-                                       {
-                                               if( 入力.InputEvent.離された )
-                                                       continue;   // 押下イベントじゃないなら無視。
+                               // ヒット判定バーとチップの間の、時間 と 距離 を算出。→ いずれも、負数ならバー未達、0でバー直上、正数でバー通過。
+                               double ヒット判定バーと描画との時間sec = 現在の演奏時刻sec - チップ.描画時刻sec;
+                               double ヒット判定バーと発声との時間sec = 現在の演奏時刻sec - チップ.発声時刻sec;
+                               double ヒット判定バーとの距離 = スコア.指定された時間secに対応する符号付きピクセル数を返す( this._現在進行描画中の譜面スクロール速度の倍率, ヒット判定バーと描画との時間sec );
 
-                                               // todo: チップに対応するパッドのアニメを開始。
-                                       }
+                               // 終了判定。
+                               bool チップは画面上端より上に出ている = ( ( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 ) < -40.0 );   // -40 はチップが隠れるであろう適当なマージン。
+                               if( チップは画面上端より上に出ている )
+                                       break;
 
-                                       var 処理済み入力 = new List<ドラム入力イベント>(); // ヒット処理が終わった入力は、二重処理しないよう、この中に追加しておく。
+                               // 処理実行。開始判定(描画開始チップ番号の更新)もこの中で。
+                               適用する処理( チップ, i, ヒット判定バーと描画との時間sec, ヒット判定バーと発声との時間sec, ヒット判定バーとの距離 );
+                       }
+               }
 
-                                       this._描画範囲のチップに処理を適用する( ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離dpx ) => {
+               private void _チップのヒット処理を行う( チップ chip, ヒットランク種別 hitRankType, ドラムとチップと入力の対応表.Column.Columnヒット処理 ヒット処理表, double ヒット判定バーと発声との時間sec )
+               {
+                       chip.ヒット済みである = true;
 
-                                               var オプション設定 = App.ユーザ管理.選択されているユーザ.オプション設定;
-                                               var 対応表 = オプション設定.ドラムとチップと入力の対応表.対応表[ chip.チップ種別 ];
-                                               var AutoPlay = オプション設定.AutoPlay[ 対応表.AutoPlay種別 ];
-                                               var ヒット判定バーとの時間の絶対値sec = Math.Abs( ヒット判定バーとの時間sec );
+                       if( ヒット処理表.再生 )
+                       {
+                               #region " チップの発声を行う。"
+                               //----------------
+                               if( chip.発声されていない )
+                                       this._チップの発声を行う( chip, ヒット判定バーと発声との時間sec );
+                               //----------------
+                               #endregion
+                       }
+                       if( ヒット処理表.判定 )
+                       {
+                               #region " チップの判定処理を行う。"
+                               //----------------
+                               if( hitRankType != ヒットランク種別.MISS )
+                               {
+                                       // (A) PERFECT~POOR
 
-                                               bool チップはヒット済みである = chip.ヒット済みである;
-                                               bool チップはMISSエリアに達している = ( ヒット判定バーとの時間sec > オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] );
-                                               bool チップはヒット可能エリアにある = ( ヒット判定バーとの時間sec >= -( オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] ) && !チップはMISSエリアに達している );
+                                       var 対応表 = App.ユーザ管理.選択されているユーザ.オプション設定.ドラムとチップと入力の対応表[ chip.チップ種別 ];
 
-                                               if( AutoPlay || チップはヒット済みである || !( 対応表.AutoPlayOFF.ユーザヒット ) || !( チップはヒット可能エリアにある ) )
-                                                       return;
+                                       this._コンボ.COMBO値++;
 
-                                               // チップにヒットする入力を探す。
+                                       this._回転羽.発火する(
+                                               new Vector2(
+                                                       レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ 対応表.表示レーン種別 ],
+                                                       ヒット判定バーの中央Y座標 ) );
 
-                                               // todo: シンバルフリーを実装する。
-                                               var ヒット入力 = App.入力管理.ポーリング結果.FirstOrDefault( ( 入力 ) =>
-                                                       ( 入力.InputEvent.押された ) &&
-                                                       ( 対応表.ドラム入力種別 == 入力.Type ) &&
-                                                       !( 処理済み入力.Contains( 入力 ) )
-                                               );
+                                       this._ドラムセット.ヒットアニメ開始( 対応表.ドラム入力種別, App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右 );
 
-                                               if( null == ヒット入力 )
-                                               {
-                                                       return;
-                                               }
-                                               else
-                                               {
-                                                       処理済み入力.Add( ヒット入力 );
-                                               }
+                                       if( hitRankType == ヒットランク種別.AUTO )
+                                               this._レーンフレーム.フラッシュ開始( 対応表.表示レーン種別 );   // レーンフラッシュは Auto 時のみ。
 
-                                               // ヒットランクを判定する。
-
-                                               var ヒットランク = ヒットランク種別.POOR;
-
-                                               if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.PERFECT ] )
-                                               {
-                                                       ヒットランク = ヒットランク種別.PERFECT;
-                                               }
-                                               else if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.GREAT ] )
-                                               {
-                                                       ヒットランク = ヒットランク種別.GREAT;
-                                               }
-                                               else if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.GOOD ] )
-                                               {
-                                                       ヒットランク = ヒットランク種別.GOOD;
-                                               }
-
-                                               // ヒット処理。
+                                       this._ヒットランク.表示開始( chip.チップ種別, hitRankType );
+                                       this.ヒットランク別ヒット回数[ hitRankType ]++;
+                               }
+                               else
+                               {
+                                       // (B) MISS
 
-                                               this._チップのヒット処理を行う( chip, ヒットランク, 対応表.AutoPlayOFF.ユーザヒット時処理 );
+                                       this._コンボ.COMBO値 = 0;
 
-                                       } );
-                                       //----------------
-                                       #endregion
+                                       this._ヒットランク.表示開始( chip.チップ種別, ヒットランク種別.MISS );
+                                       this.ヒットランク別ヒット回数[ hitRankType ]++;
                                }
-
-                               // ウェイト
-                               Thread.Sleep( 1 );
+                               //----------------
+                               #endregion
                        }
-
-                       this._高頻度進行タスクへのイベント.現在の状態 = TriStateEvent.状態種別.無効;
-
-                       Log.Info( "高頻度進行処理タスクを終了しました。" );
-               }
-
-               /// <summary>
-               ///             <see cref="_描画開始チップ番号"/> から画面上端にはみ出すまでの間の各チップに対して、指定された処理を適用する。
-               /// </summary>
-               /// <param name="適用する処理">引数は、順に、対象のチップ、チップ番号、ヒット判定バーとの時間sec、ヒット判定バーとの距離dpx</param>
-               private void _描画範囲のチップに処理を適用する( Action<チップ, int, double, double> 適用する処理 )
-               {
-                       lock( this._スレッド間同期 )
+                       if( ヒット処理表.非表示 )
                        {
-                               var スコア = App.演奏スコア;
-                               if( null == スコア )
-                                       return;
-
-                               double リアルタイム演奏時刻sec = this._演奏開始からの経過時間secを返す();
-
-                               for( int i = this._描画開始チップ番号; ( 0 <= i ) && ( i < スコア.チップリスト.Count ); i++ )
+                               #region " チップを非表示にする。"
+                               //----------------
+                               if( hitRankType != ヒットランク種別.MISS )
                                {
-                                       var チップ = スコア.チップリスト[ i ];
-
-                                       // ヒット判定バーとチップの間の、時間 と 距離 を算出。→ いずれも、負数ならバー未達、0でバー直上、正数でバー通過。
-                                       double ヒット判定バーとの時間sec = リアルタイム演奏時刻sec - チップ.描画時刻sec;
-                                       double ヒット判定バーとの距離dpx = スコア.指定された時間secに対応する符号付きピクセル数を返す( this._現在進行描画中の譜面スクロール速度の倍率, ヒット判定バーとの時間sec );
-
-                                       // 終了判定。
-                                       bool チップは画面上端より上に出ている = ( ( ヒット判定バーの中央Y座標dpx + ヒット判定バーとの距離dpx ) < -40.0 );   // -40dpx はチップが隠れるであろう適当なマージン。
-                                       if( チップは画面上端より上に出ている )
-                                               break;
-
-                                       // 処理実行。開始判定(描画開始チップ番号の更新)もこの中で。
-                                       適用する処理( チップ, i, ヒット判定バーとの時間sec, ヒット判定バーとの距離dpx );
+                                       chip.可視 = false;        // PERFECT~POOR チップは非表示。
+                               }
+                               else
+                               {
+                                       if( chip.可視 )
+                                               chip.可視 = false;    // MISSチップは最後まで表示し続ける。
                                }
+                               //----------------
+                               #endregion
                        }
                }
 
-               private void _チップのヒット処理を行う( チップ chip, ヒットランク種別 hitRankType, ドラムとチップと入力の対応表.Column.Columnヒット処理 ヒット処理表 )
+               private void _チップの発声を行う( チップ chip, double 再生開始位置sec )
                {
-                       lock( this._スレッド間同期 )
+                       if( chip.発声済みである )
+                               return;
+
+                       chip.発声済みである = true;
+
+                       if( chip.チップ種別 == チップ種別.背景動画 )
                        {
-                               chip.ヒット済みである = true;
+                               App.サウンドタイマ.一時停止する();
 
-                               if( ヒット処理表.再生 )
-                               {
-                                       #region " チップを再生する。"
-                                       //----------------
-                                       if( chip.チップ種別 == チップ種別.背景動画 )
-                                       {
-                                               // 背景動画の再生を開始する。
-                                               this._背景動画?.再生を開始する();
-                                               this._背景動画開始済み = true;
+                               // 背景動画の再生を開始する。
+                               this._背景動画?.再生を開始する();
+                               this._背景動画開始済み = true;
 
-                                               // BGMの再生を開始する。
-                                               this._BGM?.Play();
-                                               this._BGM再生開始済み = true;
-                                       }
-                                       else
-                                       {
-                                               if( App.システム設定.Autoチップのドラム音を再生する )
-                                                       this._ドラムサウンド.発声する( chip.チップ種別, ( chip.音量 / ( float ) チップ.最大音量 ) );
-                                       }
-                                       //----------------
-                                       #endregion
-                               }
-                               if( ヒット処理表.判定 )
-                               {
-                                       #region " チップの判定処理を行う。"
-                                       //----------------
-                                       if( hitRankType != ヒットランク種別.MISS )
-                                       {
-                                               this._コンボ.COMBO値++;
-                                               this._回転羽.発火する( 
-                                                       new Vector2(
-                                                               レーンフレームの左端位置dpx + レーンフレーム.レーンto横中央相対位置dpx[ App.ユーザ管理.選択されているユーザ.オプション設定.ドラムとチップと入力の対応表.対応表[ chip.チップ種別 ].表示レーン種別 ],
-                                                               ヒット判定バーの中央Y座標dpx ) );
-                                               this._ヒットランク.表示開始( chip.チップ種別, hitRankType );
-                                               this.ヒットランク別ヒット回数[ hitRankType ]++;
-                                       }
-                                       else
-                                       {
-                                               this._コンボ.COMBO値 = 0;
-                                               this._ヒットランク.表示開始( chip.チップ種別, ヒットランク種別.MISS );
-                                               this.ヒットランク別ヒット回数[ hitRankType ]++;
-                                       }
-                                       //----------------
-                                       #endregion
-                               }
-                               if( ヒット処理表.非表示 )
-                               {
-                                       #region " チップを非表示にする。"
-                                       //----------------
-                                       if( hitRankType != ヒットランク種別.MISS )
-                                       {
-                                               chip.可視 = false;        // PERFECT~POOR チップは非表示。
-                                       }
-                                       else
-                                       {
-                                               if( chip.可視 )
-                                                       chip.可視 = false;    // MISSチップは最後まで表示し続ける。
-                                       }
-                                       //----------------
-                                       #endregion
-                               }
+                               // BGMの再生を開始する。
+                               this._BGM?.Play( 再生開始位置sec );
+                               this._BGM再生開始済み = true;
+
+                               App.サウンドタイマ.再開する();
+                       }
+                       else
+                       {
+                               // BGM以外のサウンドについては、再生開始位置sec は反映せず、常に最初から再生する。
+                               if( App.システム設定.Autoチップのドラム音を再生する )
+                                       this._ドラムサウンド.発声する( chip.チップ種別, ( chip.音量 / (float) チップ.最大音量 ) );
                        }
                }
        }