OSDN Git Service

Merge branch 'feature/進行タスクを高速進行に限定する' into develop
[strokestylet/CsWin10Desktop3.git] / StrokeStyleT / ステージ / 演奏 / 演奏ステージ.cs
1 using System;
2 using System.Collections.Concurrent;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Linq;
6 using SharpDX;
7 using SharpDX.DirectInput;
8 using FDK;
9 using FDK.メディア;
10 using FDK.メディア.サウンド.WASAPI;
11 using FDK.カウンタ;
12 using SSTFormat.v2;
13 using SST.入力;
14 using SST.設定;
15
16 using FDKUtilities = FDK.FDKUtilities;
17
18 namespace SST.ステージ.演奏
19 {
20         class 演奏ステージ : ステージ
21         {
22                 public enum フェーズ
23                 {
24                         演奏中,
25                         Failed,
26                         クリア時フェードアウト,
27                         クリア,
28                         キャンセル,
29                         ビュアーメッセージ待機,
30                 }
31
32                 public フェーズ 現在のフェーズ
33                 {
34                         get;
35                         set;
36                 }
37
38                 public const float ヒット判定バーの中央Y座標 = 869f + 43f;
39
40                 public const float レーンフレームの左端位置 = 619f;
41
42                 public ConcurrentDictionary<ヒットランク種別, int> ヒットランク別ヒット回数
43                 {
44                         get;
45                 } = new ConcurrentDictionary<ヒットランク種別, int>();
46
47
48                 public 演奏ステージ()
49                 {
50                         this.子リスト.Add( this._ステージ台 = new 画像( @"$(System)images\ステージ台.png" ) );
51                         this.子リスト.Add( this._レーンフレーム = new レーンフレーム() );
52                         this.子リスト.Add( this._ドラムセット = new ドラムセット() );
53                         this.子リスト.Add( this._ヒット判定バー = new 画像( @"$(System)images\判定バー.png" ) );
54                         this.子リスト.Add( this._チップ画像 = new 画像( @"$(System)images\Chips.png" ) );
55                         this.子リスト.Add( this._回転羽 = new 回転羽( 最大同時発火数: 32 ) );
56                         this.子リスト.Add( this._コンソールフォント = new コンソールフォント() );
57                         this.子リスト.Add( this._ドラムサウンド = new ドラムサウンド() );
58                         this.子リスト.Add( this._コンボ = new コンボ() );
59                         this.子リスト.Add( this._ヒットランク = new ヒットランク() );
60                         this.子リスト.Add( this._クリア時フェードアウト = new フェードアウト( 1.0f ) );
61                         this.子リスト.Add( this._白パネル = new 画像( @"$(System)images\パネル白64x64.png" ) );
62                         this.子リスト.Add( this._FPS = new FPS() );
63                 }
64
65                 protected override void On活性化( グラフィックデバイス gd )
66                 {
67                         using( Log.Block( FDKUtilities.現在のメソッド名 ) )
68                         {
69                                 this._活性化した直後である = true;
70                                 this._演奏開始時刻sec = 0.0;
71                                 this._背景動画開始済み = false;
72                                 this._現在進行描画中の譜面スクロール速度の倍率 = App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率;
73                                 this._描画開始チップ番号 = -1;
74                                 this._背景動画 = null;
75                                 this._BGM = null;
76                                 //this._デコード済みWaveSource = null;    --> キャッシュなので消さない。
77                                 this._背景動画開始済み = false;
78                                 this._BGM再生開始済み = false;
79                                 this._チップ画像の矩形リスト = new 矩形リスト( @"$(System)images\Chips Rectangle List.xml" );      // デバイスリソースは持たないので、子Activityではない。
80
81                                 this.ヒットランク別ヒット回数.Clear();
82                                 foreach( ヒットランク種別 hitRankType in Enum.GetValues( typeof( ヒットランク種別 ) ) )
83                                         this.ヒットランク別ヒット回数[ hitRankType ] = 0;
84
85                                 #region " 背景動画とBGMを生成する。"
86                                 //----------------
87                                 if( ( null != App.演奏スコア ) && ( App.演奏スコア.背景動画ファイル名.Nullでも空でもない() ) )
88                                 {
89                                         Log.Info( "背景動画とBGMを読み込みます。" );
90
91                                         // 動画を子リストに追加。
92                                         this.子リスト.Add( this._背景動画 = new 動画( App.演奏スコア.背景動画ファイル名, App.システム設定.動画デコーダのキューサイズ ) );
93
94                                         // 動画から音声パートを抽出して BGM を作成。
95                                         if( ( null != this._デコード済みWaveSource ) && this._デコード済みWaveSource.Path.Equals( App.演奏スコア.背景動画ファイル名 ) )
96                                         {
97                                                 // (A) 前回生成したBGMとパスが同じなので、前回のデコード済み WaveSource をキャッシュとして再利用する。
98                                                 Log.Info( "前回生成したサウンドデータを再利用します。" );
99                                         }
100                                         else
101                                         {
102                                                 // (B) 初めての生成か、または前回生成したBGMとパスが違うので、新しくデコード済み WaveSource を生成する。
103                                                 this._デコード済みWaveSource?.Dispose();
104                                                 this._デコード済みWaveSource = new DecodedWaveSource( App.演奏スコア.背景動画ファイル名, App.サウンドデバイス.WaveFormat );
105                                         }
106
107                                         this._BGM?.Dispose();
108                                         this._BGM = App.サウンドデバイス.サウンドを生成する( this._デコード済みWaveSource );
109                                 }
110                                 else
111                                 {
112                                         Log.Info( "背景動画とBGMはありません。" );
113                                 }
114                                 //----------------
115                                 #endregion
116
117                                 #region " 最初のフェーズを設定する。"
118                                 //----------------
119                                 if( ( App.ビュアーモードではない ) ||
120                                         ( ( null != App.最後に取得したビュアーメッセージ ) &&
121                                           ( App.最後に取得したビュアーメッセージ.種別 == Viewer.ViewerMessage.指示種別.演奏開始 ) ) )
122                                 {
123                                         this.現在のフェーズ = フェーズ.演奏中;
124                                 }
125                                 else
126                                 {
127                                         this.現在のフェーズ = フェーズ.ビュアーメッセージ待機;
128                                 }
129                                 //----------------
130                                 #endregion
131                         }
132                 }
133
134                 protected override void On非活性化( グラフィックデバイス gd )
135                 {
136                         using( Log.Block( FDKUtilities.現在のメソッド名 ) )
137                         {
138                                 // 背景動画を生成した場合は子リストから削除。
139                                 if( null != this._背景動画 )
140                                         this.子リスト.Remove( this._背景動画 );
141
142                                 App.ユーザ管理.選択されているユーザ.保存する();
143
144                                 this._活性化した直後である = false;
145                         }
146                 }
147
148                 public override void 高速進行する()
149                 {
150                         Debug.Assert( this.活性化している );
151
152                         switch( this.現在のフェーズ )
153                         {
154                                 case フェーズ.演奏中:
155                                         #region " *** "
156                                         //----------------
157                                         {
158                                                 // 進行。
159
160                                                 #region " 初めての進行描画。"
161                                                 //----------------
162                                                 if( this._活性化した直後である )
163                                                 {
164                                                         this._活性化した直後である = false;
165                                                         this._描画開始チップ番号 = 0; // -1 → 0; 演奏開始。
166                                                         this._チップアニメ.開始する( 最初の値: 0, 最後の値: 48, 値をひとつ増加させるのにかける時間ms: 10 );
167
168                                                         #region " 演奏開始時刻を初期化し、動画とBGMの再生を開始する。"
169                                                         //----------------
170                                                         double 演奏開始位置の先頭からの時間sec = 0.0;
171
172                                                         // 演奏開始時刻の設定(1)
173                                                         this._演奏開始時刻sec = App.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
174                                                         Log.Info( $"演奏開始時刻(背景動画再生チェック前): {this._演奏開始時刻sec} sec" );
175
176                                                         // 演奏開始時刻の設定(2) ビュアーメッセージがある場合、開始時刻を修正する。
177                                                         var msg = App.最後に取得したビュアーメッセージ;
178                                                         if( null != msg )
179                                                         {
180                                                                 // 演奏開始位置を設定。
181                                                                 演奏開始位置の先頭からの時間sec = this._演奏開始小節番号を設定しその時刻secを返す( msg.演奏を開始する小節番号 );
182                                                                 Log.Info( $"演奏開始の先頭からの時間: {演奏開始位置の先頭からの時間sec} sec" );
183
184                                                                 // その他のオプション。
185                                                                 App.システム設定.Autoチップのドラム音を再生する = msg.ドラムチップのヒット時に発声する;
186                                                         }
187
188                                                         // BGMと動画を(必要あれば)再生開始。
189                                                         this._再生中の時刻なら動画とBGMを再生開始する( 演奏開始位置の先頭からの時間sec );
190
191                                                         // 演奏開始時刻の設定(3) 動画とBGMが再生された場合、ここに到達するまでに多少の遅延が生じているので、ここで演奏開始時刻を再取得しておく。
192                                                         this._演奏開始時刻sec = App.サウンドデバイス.GetDevicePosition() - 演奏開始位置の先頭からの時間sec;
193                                                         Log.Info( $"演奏開始時刻(背景動画再生チェック後): {this._演奏開始時刻sec} sec" );
194                                                         //----------------
195                                                         #endregion
196                                                 }
197                                                 //----------------
198                                                 #endregion
199
200                                                 this._FPS.FPSをカウントしプロパティを更新する();
201
202                                                 #region " 背景動画が再生されているのにBGMがまだ再生されていないなら、すぐに再生を開始する。"
203                                                 //----------------
204                                                 if( this._背景動画開始済み && !( this._BGM再生開始済み ) )
205                                                 {
206                                                         this._BGM?.Play();
207                                                         this._BGM再生開始済み = true;
208                                                 }
209                                                 //----------------
210                                                 #endregion
211
212                                                 double 現在の演奏時刻sec = this._演奏開始からの経過時間secを返す();
213
214                                                 #region " 自動ヒット処理。"
215                                                 //----------------
216                                                 this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離 ) => {
217
218                                                         var オプション設定 = App.ユーザ管理.選択されているユーザ.オプション設定;
219                                                         var 対応表 = オプション設定.ドラムとチップと入力の対応表[ chip.チップ種別 ];
220                                                         var AutoPlay = オプション設定.AutoPlay[ 対応表.AutoPlay種別 ];
221
222                                                         bool チップはヒット済みである = chip.ヒット済みである;
223                                                         bool チップはMISSエリアに達している = ( ヒット判定バーとの時間sec > オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] );
224                                                         bool チップはヒット判定バーを通過した = ( 0 <= ヒット判定バーとの距離 );
225
226                                                         if( チップはヒット済みである )
227                                                         {
228                                                                 // 何もしない。
229                                                                 return;
230                                                         }
231
232                                                         if( チップはMISSエリアに達している )
233                                                         {
234                                                                 // MISS判定。
235                                                                 if( AutoPlay && 対応表.AutoPlayON.MISS判定 )
236                                                                 {
237                                                                         this._チップのヒット処理を行う( chip, ヒットランク種別.MISS, 対応表.AutoPlayON.自動ヒット時処理 );
238                                                                         return;
239                                                                 }
240                                                                 else if( !AutoPlay && 対応表.AutoPlayOFF.MISS判定 )
241                                                                 {
242                                                                         this._チップのヒット処理を行う( chip, ヒットランク種別.MISS, 対応表.AutoPlayOFF.ユーザヒット時処理 );
243                                                                         return;
244                                                                 }
245                                                                 else
246                                                                 {
247                                                                         // 通過。
248                                                                 }
249                                                         }
250
251                                                         if( チップはヒット判定バーを通過した )
252                                                         {
253                                                                 // 自動ヒット判定。
254                                                                 if( AutoPlay && 対応表.AutoPlayON.自動ヒット )
255                                                                 {
256                                                                         this._チップのヒット処理を行う( chip, ヒットランク種別.AUTO, 対応表.AutoPlayON.自動ヒット時処理 );
257                                                                         return;
258                                                                 }
259                                                                 else if( !AutoPlay && 対応表.AutoPlayOFF.自動ヒット )
260                                                                 {
261                                                                         this._チップのヒット処理を行う( chip, ヒットランク種別.AUTO, 対応表.AutoPlayOFF.自動ヒット時処理 );
262                                                                         return;
263                                                                 }
264                                                                 else
265                                                                 {
266                                                                         // 通過。
267                                                                 }
268                                                         }
269
270                                                 } );
271                                                 //----------------
272                                                 #endregion
273
274                                                 // 入力。
275
276                                                 App.入力管理.すべての入力デバイスをポーリングする( 入力履歴を記録する: false );
277
278                                                 if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Escape ) )
279                                                 {
280                                                         #region " ESC → ステージキャンセル "
281                                                         //----------------
282                                                         if( App.ビュアーモードではない )
283                                                         {
284                                                                 this.BGMを停止する();
285                                                                 this.現在のフェーズ = フェーズ.キャンセル;
286                                                                 break;  // このタスクを終了。
287                                                         }
288                                                         else
289                                                         {
290                                                                 // ビュアーモード時のキャンセルは無効。
291                                                         }
292                                                         //----------------
293                                                         #endregion
294                                                 }
295                                                 if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Up ) )
296                                                 {
297                                                         #region " 上 → 譜面スクロールを加速 "
298                                                         //----------------
299                                                         const double 最大倍率 = 8.0;
300                                                         App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 =
301                                                                 Math.Min( App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 + 0.5, 最大倍率 );
302                                                         //----------------
303                                                         #endregion
304                                                 }
305                                                 if( App.入力管理.キーボードデバイス.キーが押された( 0, Key.Down ) )
306                                                 {
307                                                         #region " 下 → 譜面スクロールを減速 "
308                                                         //----------------
309                                                         const double 最小倍率 = 0.5;
310                                                         App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 =
311                                                                 Math.Max( App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 - 0.5, 最小倍率 );
312                                                         //----------------
313                                                         #endregion
314                                                 }
315                                                 foreach( var ev in App.入力管理.MIDI入力デバイス.入力イベントリスト.Where( ( ie ) => ( 255 == ie.Key ) ) )
316                                                 {
317                                                         #region " ハイハットの開閉 "
318                                                         //----------------
319                                                         this._ドラムセット.ハイハットのベロシティ = ev.Velocity;
320                                                         //----------------
321                                                         #endregion
322                                                 }
323
324                                                 #region " ユーザヒット処理。"
325                                                 //----------------
326                                                 foreach( var 入力 in App.入力管理.ポーリング結果 )
327                                                 {
328                                                         if( 入力.InputEvent.離された )
329                                                                 continue;   // 押下イベントじゃないなら無視。
330
331                                                         this._ドラムセット.ヒットアニメ開始( 入力.Type, App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右 );
332                                                 }
333
334                                                 var 処理済み入力 = new List<ドラム入力イベント>(); // ヒット処理が終わった入力は、二重処理しないよう、この中に追加しておく。
335
336                                                 this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離 ) => {
337
338                                                         var オプション設定 = App.ユーザ管理.選択されているユーザ.オプション設定;
339                                                         var 対応表 = オプション設定.ドラムとチップと入力の対応表[ chip.チップ種別 ];
340                                                         bool AutoPlay = オプション設定.AutoPlay[ 対応表.AutoPlay種別 ];
341                                                         double ヒット判定バーとの時間の絶対値sec = Math.Abs( ヒット判定バーとの時間sec );
342                                                         bool チップはヒット済みである = chip.ヒット済みである;
343                                                         bool チップはMISSエリアに達している = ( ヒット判定バーとの時間sec > オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] );
344                                                         bool チップはヒット可能エリアにある = ( ヒット判定バーとの時間sec >= -( オプション設定.最大ヒット距離sec[ ヒットランク種別.POOR ] ) && !チップはMISSエリアに達している );
345
346                                                         if( AutoPlay || チップはヒット済みである || !( 対応表.AutoPlayOFF.ユーザヒット ) || !( チップはヒット可能エリアにある ) )
347                                                                 return;
348
349                                                         // チップにヒットする入力を探す。
350                                                         var ヒット入力 = App.入力管理.ポーリング結果.FirstOrDefault( ( 入力 ) => {
351
352                                                                 if( !( 入力.InputEvent.押された ) )
353                                                                         return false;   // 押下入力じゃない。
354
355                                                                 if( 処理済み入力.Contains( 入力 ) )
356                                                                         return false;   // すでに今回のターンで処理済み(=処理済み入力リストに追加済み)。
357
358                                                                 if( 対応表.シンバルフリーの対象 && オプション設定.シンバルフリー )
359                                                                 {
360                                                                         // (A) シンバルフリーの対象チップであり、かつシンバルフリーが ON である場合
361                                                                         var 入力の対応表 = オプション設定.ドラムとチップと入力の対応表.対応表.FirstOrDefault( ( kvp ) => ( kvp.Value.ドラム入力種別 == 入力.Type ) ).Value;        // 見つからなきゃバグだ
362                                                                         if( !( 入力の対応表.シンバルフリーの対象 ) )
363                                                                                 return false;   // この入力はシンバルフリーの対象ではない。
364                                                                 }
365                                                                 else
366                                                                 {
367                                                                         // (B) シンバルフリーの対象ではないチップ、またはシンバルフリーが OFF である場合
368                                                                         if( 対応表.ドラム入力種別 != 入力.Type )
369                                                                                 return false;   // チップに対応している入力じゃない。
370                                                                 }
371
372                                                                 return true;    // この 入力 はこの chip にヒットしている。
373                                                         } );
374
375                                                         if( null == ヒット入力 )
376                                                                 return;
377
378                                                         処理済み入力.Add( ヒット入力 );
379
380                                                         // ヒットランクを判定する。
381
382                                                         var ヒットランク = ヒットランク種別.POOR;
383
384                                                         if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.PERFECT ] )
385                                                         {
386                                                                 ヒットランク = ヒットランク種別.PERFECT;
387                                                         }
388                                                         else if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.GREAT ] )
389                                                         {
390                                                                 ヒットランク = ヒットランク種別.GREAT;
391                                                         }
392                                                         else if( ヒット判定バーとの時間の絶対値sec <= オプション設定.最大ヒット距離sec[ ヒットランク種別.GOOD ] )
393                                                         {
394                                                                 ヒットランク = ヒットランク種別.GOOD;
395                                                         }
396
397                                                         // ヒット処理。
398
399                                                         this._チップのヒット処理を行う( chip, ヒットランク, 対応表.AutoPlayOFF.ユーザヒット時処理 );
400
401                                                 } );
402                                                 //----------------
403                                                 #endregion
404                                         }
405                                         //----------------
406                                         #endregion
407                                         break;
408                         }
409                 }
410
411                 public override void 進行描画する( グラフィックデバイス gd )
412                 {
413                         Debug.Assert( this.活性化している );
414                         Debug.Assert( null != gd );
415
416                         switch( this.現在のフェーズ )
417                         {
418                                 case フェーズ.演奏中:
419                                         if( this._活性化した直後である )
420                                                 break;  // 進行処理がまだ行われていない。
421
422                                         #region " 譜面スクロール速度が変化している → 追い付き進行 "
423                                         //----------------
424                                         {
425                                                 double 倍率 = this._現在進行描画中の譜面スクロール速度の倍率;
426
427                                                 if( 倍率 < App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 )
428                                                 {
429                                                         if( 0 > this._スクロール倍率追い付き用_最後の値 )
430                                                         {
431                                                                 this._スクロール倍率追い付き用カウンタ = new LoopCounter( 0, 1000, 10 );    // 0→100; 全部で10×1000 = 10000ms = 10sec あれば十分だろう
432                                                                 this._スクロール倍率追い付き用_最後の値 = 0;
433                                                         }
434                                                         else
435                                                         {
436                                                                 while( this._スクロール倍率追い付き用_最後の値 < this._スクロール倍率追い付き用カウンタ.現在値 )
437                                                                 {
438                                                                         倍率 += 0.025;
439                                                                         this._スクロール倍率追い付き用_最後の値++;
440                                                                 }
441
442                                                                 this._現在進行描画中の譜面スクロール速度の倍率 =
443                                                                         Math.Min( 倍率, App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 );
444                                                         }
445                                                 }
446                                                 else if( 倍率 > App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 )
447                                                 {
448                                                         if( 0 > this._スクロール倍率追い付き用_最後の値 )
449                                                         {
450                                                                 this._スクロール倍率追い付き用カウンタ = new LoopCounter( 0, 1000, 10 );    // 0→100; 全部で10×1000 = 10000ms = 10sec あれば十分だろう
451                                                                 this._スクロール倍率追い付き用_最後の値 = 0;
452                                                         }
453                                                         else
454                                                         {
455                                                                 while( this._スクロール倍率追い付き用_最後の値 < this._スクロール倍率追い付き用カウンタ.現在値 )
456                                                                 {
457                                                                         倍率 -= 0.025;
458                                                                         this._スクロール倍率追い付き用_最後の値++;
459                                                                 }
460
461                                                                 this._現在進行描画中の譜面スクロール速度の倍率 =
462                                                                         Math.Max( 倍率, App.ユーザ管理.選択されているユーザ.オプション設定.譜面スクロール速度の倍率 );
463                                                         }
464                                                 }
465                                                 else
466                                                 {
467                                                         this._スクロール倍率追い付き用_最後の値 = -1;
468                                                         this._スクロール倍率追い付き用カウンタ = null;
469                                                 }
470                                         }
471                                         //----------------
472                                         #endregion
473
474                                         this._コンボ.進行描画する( gd );
475                                         this._回転羽.進行描画する( gd );
476
477                                         double 演奏時刻sec = this._演奏開始からの経過時間secを返す();
478
479                                         #region " 背景動画とBGMの進行描画。"
480                                         //----------------
481                                         if( this._背景動画開始済み )
482                                         {
483                                                 // 背景動画チップがヒット済みなら、背景動画の進行描画を行う。
484                                                 switch( App.ユーザ管理.選択されているユーザ.オプション設定.動画表示パターン種別 )
485                                                 {
486                                                         case 動画表示パターン種別.中央表示:
487                                                                 #region " *** "
488                                                                 //----------------
489                                                                 {
490                                                                         float w = gd.設計画面サイズ.Width;
491                                                                         float h = gd.設計画面サイズ.Height;
492
493                                                                         this._背景動画?.描画する( gd, new RectangleF( 0f, 0f, w, h ), 0.2f );  // 全体
494
495                                                                         float 拡大縮小率 = 0.75f;
496                                                                         float 上移動 = 100.0f;
497
498                                                                         // 進行描画せず、直前に取得したフレームをそのまま描画する。
499                                                                         this._背景動画?.前のフレームを描画する( gd, new RectangleF(
500                                                                                 w * ( 1f - 拡大縮小率 ) / 2f,
501                                                                                 h * ( 1f - 拡大縮小率 ) / 2f - 上移動,
502                                                                                 w * 拡大縮小率,
503                                                                                 h * 拡大縮小率 ) );
504                                                                 }
505                                                                 //----------------
506                                                                 #endregion
507                                                                 break;
508
509                                                         case 動画表示パターン種別.最大表示:
510                                                                 #region " *** "
511                                                                 //----------------
512                                                                 this._背景動画?.描画する( gd, new RectangleF( 0f, 0f, gd.設計画面サイズ.Width, gd.設計画面サイズ.Height ), 1.0f );
513                                                                 //----------------
514                                                                 #endregion
515                                                                 break;
516                                                 }
517
518                                                 // 動画が重たいかもしれないので、演奏時刻をここで更新しておく。   ---> 重たくても更新禁止!(譜面スクロールがガタつく原因になる)
519                                                 //演奏時刻sec = this._演奏開始からの経過時間secを返す();
520                                         }
521                                         //----------------
522                                         #endregion
523
524                                         this._ステージ台.描画する( gd, 0f, 0f );
525                                         this._レーンフレーム.進行描画する( gd, レーンフレームの左端位置 );
526                                         this._コンボ.進行描画する( gd );
527                                         this._ヒットランク.進行描画する( gd, レーンフレームの左端位置 );
528                                         this._小節線拍線を描画する( gd, 演奏時刻sec );
529                                         this._ヒット判定バー.描画する( gd, 597f, ヒット判定バーの中央Y座標 - 43f );
530                                         this._ドラムセット.進行描画する( gd );
531                                         this._チップを描画する( gd, 演奏時刻sec );
532                                         this._回転羽.進行描画する( gd );
533                                         this._FPS.VPSをカウントする();
534                                         this._FPS.描画する( gd, 0f, 0f );
535                                         break;
536
537                                 case フェーズ.クリア時フェードアウト:
538                                         if( this._クリア時フェードアウト.開始されていない )
539                                         {
540                                                 this._クリア時フェードアウト.開始する();
541                                         }
542                                         else
543                                         {
544                                                 if( this._クリア時フェードアウト.完了した )
545                                                 {
546                                                         this.現在のフェーズ = フェーズ.クリア;
547                                                 }
548                                         }
549                                         this._ステージ台.描画する( gd, 0f, 0f );
550                                         this._レーンフレーム.進行描画する( gd, レーンフレームの左端位置 );
551                                         this._ヒット判定バー.描画する( gd, 597f, ヒット判定バーの中央Y座標 - 43f );
552                                         this._ドラムセット.進行描画する( gd );
553                                         this._回転羽.進行描画する( gd );
554                                         this._クリア時フェードアウト.進行描画する( gd, this._白パネル );
555                                         this._FPS.VPSをカウントする();
556                                         this._FPS.描画する( gd, 0f, 0f );
557                                         break;
558
559                                 case フェーズ.Failed:
560                                 case フェーズ.クリア:
561                                 case フェーズ.キャンセル:
562                                 case フェーズ.ビュアーメッセージ待機:
563                                         break;
564                         }
565                 }
566
567                 public void 演奏を停止する()
568                 {
569                         using( Log.Block( FDKUtilities.現在のメソッド名 ) )
570                         {
571                                 this._描画開始チップ番号 = -1;   // 演奏停止
572
573                                 this.BGMを停止する();
574                                 this._背景動画開始済み = false;
575
576                                 this._コンボ.COMBO値 = 0;
577                         }
578                 }
579
580                 /// <remarks>
581                 ///             演奏クリア時には、次の結果ステージに入ってもBGMが鳴り続ける。
582                 ///             そのため、後からBGMだけを別個に停止するためのメソッドが必要になる。
583                 /// </remarks>
584                 public void BGMを停止する()
585                 {
586                         using( Log.Block( FDKUtilities.現在のメソッド名 ) )
587                         {
588                                 this._BGM?.Stop();
589                                 this._BGM?.Dispose();
590                                 this._BGM = null;
591
592                                 //this._デコード済みWaveSource?.Dispose();        --> ここではまだ解放しない。
593                                 //this._デコード済みWaveSource = null;
594                         }
595                 }
596
597                 public void BGMのキャッシュを解放する()
598                 {
599                         using( Log.Block( FDKUtilities.現在のメソッド名 ) )
600                         {
601                                 this.BGMを停止する();
602                                 FDKUtilities.解放する( ref this._デコード済みWaveSource );
603                         }
604                 }
605
606
607                 private double _演奏開始時刻sec = 0.0;
608
609                 private double _現在進行描画中の譜面スクロール速度の倍率 = 1.0;
610
611                 /// <summary>
612                 ///             <see cref="スコア.チップリスト"/> のうち、描画を始めるチップのインデックス番号。
613                 ///             未演奏時・演奏終了時は -1 。
614                 /// </summary>
615                 /// <remarks>
616                 ///             演奏開始直後は 0 で始まり、対象番号のチップが描画範囲を流れ去るたびに +1 される。
617                 ///             このメンバの更新は、高頻度進行タスクではなく、進行描画メソッドで行う。(低精度で構わないので)
618                 /// </remarks>
619                 private int _描画開始チップ番号 = -1;
620
621                 private 動画 _背景動画 = null;
622
623                 /// <remarks>
624                 ///             停止と解放は、演奏ステージクラスの非活性化後に、外部から行われる。
625                 ///             <see cref="SST.ステージ.演奏.演奏ステージ.BGMを停止する"/>
626                 ///             <see cref="SST.ステージ.演奏.演奏ステージ.BGMのキャッシュを解放する"/>
627                 /// </remarks>
628                 private Sound _BGM = null;
629
630                 /// <summary>
631                 ///             BGM の生成もとになるデコード済みサウンドデータ。
632                 ///     </summary>
633                 ///     <remarks>
634                 ///             活性化と非活性化に関係なく、常に最後にデコードしたデータを持つ。(キャッシュ)
635                 ///             演奏ステージインスタンスを破棄する際に、このインスタンスもDisposeすること。
636                 /// </remarks>
637                 private DecodedWaveSource _デコード済みWaveSource = null;
638
639                 private bool _背景動画開始済み = false;
640
641                 private bool _BGM再生開始済み = false;
642
643                 private 画像 _ステージ台 = null;
644
645                 private レーンフレーム _レーンフレーム = null;
646
647                 private ドラムセット _ドラムセット = null;
648
649                 private 画像 _ヒット判定バー = null;
650
651                 private 画像 _チップ画像 = null;
652
653                 private 矩形リスト _チップ画像の矩形リスト = null;
654
655                 private LoopCounter _チップアニメ = new LoopCounter();
656
657                 private 回転羽 _回転羽 = null;
658
659                 private コンソールフォント _コンソールフォント = null;
660
661                 private ドラムサウンド _ドラムサウンド = null;
662
663                 private コンボ _コンボ = null;
664
665                 private ヒットランク _ヒットランク = null;
666
667                 private FPS _FPS = null;
668
669                 private LoopCounter _スクロール倍率追い付き用カウンタ = null;
670
671                 private int _スクロール倍率追い付き用_最後の値 = -1;
672
673                 private フェードアウト _クリア時フェードアウト = null;
674
675                 private 画像 _白パネル = null;
676
677                 private bool _活性化した直後である;
678
679                 // 譜面描画。
680
681                 /// <summary>
682                 ///             指定した小節の先頭を演奏開始位置として設定し、その位置(時刻)を返す。
683                 /// </summary>
684                 /// <returns>演奏開始位置sec。(0~)</returns>
685                 private double _演奏開始小節番号を設定しその時刻secを返す( int 演奏開始小節番号 )
686                 {
687                         var score = App.演奏スコア;
688
689                         if( null == score )
690                                 return 0.0;
691
692                         double 演奏開始時刻sec = 0.0;
693
694                         for( int i = 0; i < score.チップリスト.Count; i++ )
695                         {
696                                 if( score.チップリスト[ i ].小節番号 < 演奏開始小節番号 )
697                                 {
698                                         // 開始チップ以前のチップはヒット済みとする。
699                                         score.チップリスト[ i ].ヒット済みの状態にする();
700                                 }
701                                 else
702                                 {
703                                         // 開始チップを設定。
704                                         this._描画開始チップ番号 = i;
705
706                                         // 演奏開始時刻は、開始チップの発声時刻から少し早めの値に。
707                                         演奏開始時刻sec = score.チップリスト[ i ].発声時刻sec;
708                                         演奏開始時刻sec -= 0.5;
709
710                                         // 開始チップ以降のすべてのチップをヒット前の状態にする。
711                                         for( int j = i; j >= 0; j-- )
712                                         {
713                                                 if( score.チップリスト[ j ].ヒット済みである )
714                                                         score.チップリスト[ j ].ヒット前の状態にする();
715                                         }
716
717                                         break;
718                                 }
719                         }
720
721                         return 演奏開始時刻sec;
722                 }
723
724                 private void _再生中の時刻なら動画とBGMを再生開始する( double 時刻sec )
725                 {
726                         using( Log.Block( FDKUtilities.現在のメソッド名 ) )
727                         {
728                                 Log.Info( $"指定された時刻: {時刻sec} sec" );
729
730                                 var 背景動画チップ = App.演奏スコア.チップリスト.FirstOrDefault( ( chip ) => ( chip.チップ種別 == チップ種別.背景動画 ) );
731
732                                 if( null == 背景動画チップ )
733                                 {
734                                         Log.Info( "背景動画チップは存在しません。" );
735                                         return;
736                                 }
737
738                                 double 背景動画の長さsec = this._BGM?.長さsec ?? 0.0;        // 動画のサイズは、映像ではなく音声を優先する。
739
740                                 Log.Info( $"背景動画の発生時刻: {背景動画チップ.発声時刻sec} sec" );
741                                 Log.Info( $"背景動画の長さ: {背景動画の長さsec} sec" );
742
743                                 // 指定された時刻secは再生期間内?
744                                 if( ( 背景動画チップ.発声時刻sec <= 時刻sec ) && ( 時刻sec < ( 背景動画チップ.発声時刻sec + 背景動画の長さsec ) ) )
745                                 {
746                                         // 背景動画の再生を開始する。
747                                         double 再生開始時刻sec = ( 時刻sec - 背景動画チップ.発声時刻sec );
748                                         this._背景動画?.再生を開始する( 再生開始時刻sec );
749                                         this._背景動画開始済み = true;
750                                         this._BGM?.Play( 再生開始時刻sec );
751                                         this._BGM再生開始済み = true;
752                                         Log.Info( $"背景動画の再生を開始しました。(再生開始時刻: {再生開始時刻sec} sec)" );
753                                 }
754                                 else
755                                 {
756                                         Log.Info( $"指定された時刻は背景動画の再生期間内ではないので、何もしません。" );
757                                 }
758                         }
759                 }
760
761                 private double _演奏開始からの経過時間secを返す()
762                 {
763                         return App.サウンドデバイス.GetDevicePosition() - this._演奏開始時刻sec;
764                 }
765
766                 // 小節線・拍線 と チップ は描画階層(奥行き)が異なるので、別々のメソッドに分ける。
767
768                 private void _小節線拍線を描画する( グラフィックデバイス gd, double 現在の演奏時刻sec )
769                 {
770                         if( null == this._チップ画像 )
771                                 return;
772
773                         this._チップ画像.加算合成 = false;
774
775                         this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離 ) => {
776
777                                 if( chip.チップ種別 == チップ種別.小節線 )
778                                 {
779                                         #region " 小節線 "
780                                         //----------------
781                                         float 左位置 = レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ 表示レーン種別.LeftCrash ] - 1f;                // -1f はレーン線の幅の半分。
782                                         float 上位置 = (float)( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 - 1f );   // -1f は小節線の厚みの半分。
783                                         if( this._チップ画像の矩形リスト[ nameof( チップ種別.小節線 ) ] is RectangleF 画像範囲 )
784                                         {
785                                                 this._チップ画像.描画する( gd, 左位置, 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );     // false にすると、
786                                                 this._チップ画像.描画する( gd, 左位置 + 画像範囲.Width, 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );        // チカチカする。
787                                         }
788                                         //----------------
789                                         #endregion
790                                 }
791                                 else if( chip.チップ種別 == チップ種別.拍線 )
792                                 {
793                                         #region " 拍線 "
794                                         //----------------
795                                         float 左位置 = レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置 [ 表示レーン種別.LeftCrash ] - 1f;        // -1f はレーン線の幅の半分。
796                                         float 上位置 = (float)( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 - 1f );   // -1f は拍線の厚みの半分。
797                                         if( this._チップ画像の矩形リスト[ nameof( チップ種別.拍線 ) ] is RectangleF 画像範囲 )
798                                         {
799                                                 this._チップ画像.描画する( gd, 左位置: 左位置, 上位置: 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );       // false にすると、
800                                                 this._チップ画像.描画する( gd, 左位置: 左位置 + 画像範囲.Width, 上位置: 上位置, 転送元矩形: 画像範囲, 描画先矩形を整数境界に合わせる: true );  // チカチカする。
801                                         }
802                                         //----------------
803                                         #endregion
804                                 }
805
806                         } );
807                 }
808
809                 private void _チップを描画する( グラフィックデバイス gd, double 現在の演奏時刻sec )
810                 {
811                         if( null == this._チップ画像 )
812                                 return;
813
814                         this._チップ画像.加算合成 = false;
815
816                         this._描画範囲のチップに処理を適用する( 現在の演奏時刻sec, ( chip, index, ヒット判定バーとの時間sec, ヒット判定バーとの距離 ) => {
817
818                                 float 縦中央位置 = (float)( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 );
819
820                                 #region " チップが描画開始チップであり、かつ、そのY座標が画面下端を超えたなら、描画開始チップ番号を更新する。"
821                                 //----------------
822                                 if( ( index == this._描画開始チップ番号 ) &&
823                                         ( gd.設計画面サイズ.Height + 40.0 < 縦中央位置 ) )   // +40 はチップが隠れるであろう適当なマージン。
824                                 {
825                                         this._描画開始チップ番号++;
826
827                                         if( App.演奏スコア.チップリスト.Count <= this._描画開始チップ番号 )
828                                         {
829                                                 this.現在のフェーズ = フェーズ.クリア時フェードアウト;
830                                                 this._描画開始チップ番号 = -1;    // 演奏完了。
831                                                 return;
832                                         }
833                                 }
834                                 //----------------
835                                 #endregion
836
837                                 if( chip.不可視 )
838                                         return;
839
840                                 #region " チップを個別に描画する。"
841                                 //----------------
842                                 float 音量0to1 = chip.音量 / (float) チップ.最大音量;
843
844                                 switch( chip.チップ種別 )
845                                 {
846                                         case チップ種別.LeftCrash:
847                                                 _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ nameof( チップ種別.LeftCrash ) ], 縦中央位置, 音量0to1 );
848                                                 break;
849
850                                         case チップ種別.HiHat_Close:
851                                                 _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置, 音量0to1 );
852                                                 break;
853
854                                         case チップ種別.HiHat_HalfOpen:
855                                                 _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置, 音量0to1 );
856                                                 _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_HalfOpen ) ], 縦中央位置, 1.0f );
857                                                 break;
858
859                                         case チップ種別.HiHat_Open:
860                                                 _アニメチップを1つ描画する( 表示レーン種別.HiHat, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Close ) ], 縦中央位置, 音量0to1 );
861                                                 _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Open ) ], 縦中央位置, 1.0f );
862                                                 break;
863
864                                         case チップ種別.HiHat_Foot:
865                                                 _単画チップを1つ描画する( 表示レーン種別.Foot, this._チップ画像の矩形リスト[ nameof( チップ種別.HiHat_Foot ) ], 縦中央位置, 1.0f );
866                                                 break;
867
868                                         case チップ種別.Snare:
869                                                 _アニメチップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare ) ], 縦中央位置, 音量0to1 );
870                                                 break;
871
872                                         case チップ種別.Snare_ClosedRim:
873                                                 _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_ClosedRim ) ], 縦中央位置, 1.0f );
874                                                 break;
875
876                                         case チップ種別.Snare_OpenRim:
877                                                 _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_OpenRim ) ], 縦中央位置, 音量0to1 );
878                                                 // ↓ないほうがいいかも。
879                                                 //_単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare ) ], 縦中央位置, 音量0to1 );
880                                                 break;
881
882                                         case チップ種別.Snare_Ghost:
883                                                 _単画チップを1つ描画する( 表示レーン種別.Snare, this._チップ画像の矩形リスト[ nameof( チップ種別.Snare_Ghost ) ], 縦中央位置, 1.0f );
884                                                 break;
885
886                                         case チップ種別.Bass:
887                                                 _アニメチップを1つ描画する( 表示レーン種別.Bass, this._チップ画像の矩形リスト[ nameof( チップ種別.Bass ) ], 縦中央位置, 音量0to1 );
888                                                 break;
889
890                                         case チップ種別.Tom1:
891                                                 _アニメチップを1つ描画する( 表示レーン種別.Tom1, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom1 ) ], 縦中央位置, 音量0to1 );
892                                                 break;
893
894                                         case チップ種別.Tom1_Rim:
895                                                 _単画チップを1つ描画する( 表示レーン種別.Tom1, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom1_Rim ) ], 縦中央位置, 1.0f );
896                                                 break;
897
898                                         case チップ種別.Tom2:
899                                                 _アニメチップを1つ描画する( 表示レーン種別.Tom2, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom2 ) ], 縦中央位置, 音量0to1 );
900                                                 break;
901
902                                         case チップ種別.Tom2_Rim:
903                                                 _単画チップを1つ描画する( 表示レーン種別.Tom2, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom2_Rim ) ], 縦中央位置, 1.0f );
904                                                 break;
905
906                                         case チップ種別.Tom3:
907                                                 _アニメチップを1つ描画する( 表示レーン種別.Tom3, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom3 ) ], 縦中央位置, 音量0to1 );
908                                                 break;
909
910                                         case チップ種別.Tom3_Rim:
911                                                 _単画チップを1つ描画する( 表示レーン種別.Tom3, this._チップ画像の矩形リスト[ nameof( チップ種別.Tom3_Rim ) ], 縦中央位置, 1.0f );
912                                                 break;
913
914                                         case チップ種別.RightCrash:
915                                                 _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ nameof( チップ種別.RightCrash ) ], 縦中央位置, 音量0to1 );
916                                                 break;
917
918                                         case チップ種別.China:
919                                                 if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Chinaは左 )
920                                                 {
921                                                         _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftChina" ], 縦中央位置, 音量0to1 );
922                                                 }
923                                                 else
924                                                 {
925                                                         _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightChina" ], 縦中央位置, 音量0to1 );
926                                                 }
927                                                 break;
928
929                                         case チップ種別.Ride:
930                                                 if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Rideは左 )
931                                                 {
932                                                         _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftRide" ], 縦中央位置, 音量0to1 );
933                                                 }
934                                                 else
935                                                 {
936                                                         _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightRide" ], 縦中央位置, 音量0to1 );
937                                                 }
938                                                 break;
939
940                                         case チップ種別.Ride_Cup:
941                                                 if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Rideは左 )
942                                                 {
943                                                         _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftRide_Cup" ], 縦中央位置, 音量0to1 );
944                                                 }
945                                                 else
946                                                 {
947                                                         _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightRide_Cup" ], 縦中央位置, 音量0to1 );
948                                                 }
949                                                 break;
950
951                                         case チップ種別.Splash:
952                                                 if( App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右.Splashは左 )
953                                                 {
954                                                         _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftSplash" ], 縦中央位置, 音量0to1 );
955                                                 }
956                                                 else
957                                                 {
958                                                         _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightSplash" ], 縦中央位置, 音量0to1 );
959                                                 }
960                                                 break;
961
962                                         case チップ種別.LeftCymbal_Mute:
963                                                 _単画チップを1つ描画する( 表示レーン種別.LeftCrash, this._チップ画像の矩形リスト[ "LeftCymbal_Mute" ], 縦中央位置, 1.0f );
964                                                 break;
965
966                                         case チップ種別.RightCymbal_Mute:
967                                                 _単画チップを1つ描画する( 表示レーン種別.RightCrash, this._チップ画像の矩形リスト[ "RightCymbal_Mute" ], 縦中央位置, 1.0f );
968                                                 break;
969                                 }
970                                 //----------------
971                                 #endregion
972
973                         } );
974
975                         #region " ローカル関数 "
976                         //----------------
977                         void _単画チップを1つ描画する( 表示レーン種別 lane, RectangleF? 元矩形, float 上位置, float 音量0to1 )
978                         {
979                                 if( null == 元矩形 )
980                                         return;
981
982                                 var 画像範囲 = (RectangleF) 元矩形;
983
984                                 this._チップ画像?.描画する(
985                                         gd,
986                                         左位置: レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ lane ] - ( 画像範囲.Width / 2f ),
987                                         上位置: 上位置 - ( ( 画像範囲.Height / 2f ) * 音量0to1 ),
988                                         転送元矩形: 元矩形,
989                                         Y方向拡大率: 音量0to1 );
990                         }
991                         void _アニメチップを1つ描画する( 表示レーン種別 lane, RectangleF? 画像範囲orNull, float Y, float 音量0to1 )
992                         {
993                                 if( null == 画像範囲orNull )
994                                         return;
995
996                                 var 画像範囲 = (RectangleF) 画像範囲orNull;
997
998                                 float チップ1枚の高さ = 18f;
999                                 画像範囲.Offset( 0f, this._チップアニメ.現在値 * 15f );   // 下端3pxは下のチップと共有する前提のデザインなので、18f-3f = 15f。
1000                                 画像範囲.Height = チップ1枚の高さ;
1001                                 float 左位置 = レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ lane ] - ( 画像範囲.Width / 2f );
1002                                 float 上位置 = Y - ( チップ1枚の高さ / 2f ) * 音量0to1;
1003
1004                                 this._チップ画像?.描画する( gd, 左位置, 上位置, 転送元矩形: 画像範囲, Y方向拡大率: 音量0to1 );
1005                         }
1006                         //----------------
1007                         #endregion
1008                 }
1009
1010                 /// <summary>
1011                 ///             <see cref="_描画開始チップ番号"/> から画面上端にはみ出すまでの間の各チップに対して、指定された処理を適用する。
1012                 /// </summary>
1013                 /// <param name="適用する処理">引数は、順に、対象のチップ、チップ番号、ヒット判定バーとの時間sec、ヒット判定バーとの距離</param>
1014                 private void _描画範囲のチップに処理を適用する( double 現在の演奏時刻sec, Action<チップ, int, double, double> 適用する処理 )
1015                 {
1016                         var スコア = App.演奏スコア;
1017                         if( null == スコア )
1018                                 return;
1019
1020                         for( int i = this._描画開始チップ番号; ( 0 <= i ) && ( i < スコア.チップリスト.Count ); i++ )
1021                         {
1022                                 var チップ = スコア.チップリスト[ i ];
1023
1024                                 // ヒット判定バーとチップの間の、時間 と 距離 を算出。→ いずれも、負数ならバー未達、0でバー直上、正数でバー通過。
1025                                 double ヒット判定バーとの時間sec = 現在の演奏時刻sec - チップ.描画時刻sec;
1026                                 double ヒット判定バーとの距離 = スコア.指定された時間secに対応する符号付きピクセル数を返す( this._現在進行描画中の譜面スクロール速度の倍率, ヒット判定バーとの時間sec );
1027
1028                                 // 終了判定。
1029                                 bool チップは画面上端より上に出ている = ( ( ヒット判定バーの中央Y座標 + ヒット判定バーとの距離 ) < -40.0 );   // -40 はチップが隠れるであろう適当なマージン。
1030                                 if( チップは画面上端より上に出ている )
1031                                         break;
1032
1033                                 // 処理実行。開始判定(描画開始チップ番号の更新)もこの中で。
1034                                 適用する処理( チップ, i, ヒット判定バーとの時間sec, ヒット判定バーとの距離 );
1035                         }
1036                 }
1037
1038                 private void _チップのヒット処理を行う( チップ chip, ヒットランク種別 hitRankType, ドラムとチップと入力の対応表.Column.Columnヒット処理 ヒット処理表 )
1039                 {
1040                         chip.ヒット済みである = true;
1041
1042                         if( ヒット処理表.再生 )
1043                         {
1044                                 #region " チップを再生する。"
1045                                 //----------------
1046                                 if( chip.チップ種別 == チップ種別.背景動画 )
1047                                 {
1048                                         // 背景動画の再生を開始する。
1049                                         this._背景動画?.再生を開始する();
1050                                         this._背景動画開始済み = true;
1051
1052                                         // BGMの再生を開始する。
1053                                         this._BGM?.Play();
1054                                         this._BGM再生開始済み = true;
1055                                 }
1056                                 else
1057                                 {
1058                                         if( App.システム設定.Autoチップのドラム音を再生する )
1059                                                 this._ドラムサウンド.発声する( chip.チップ種別, ( chip.音量 / (float) チップ.最大音量 ) );
1060                                 }
1061                                 //----------------
1062                                 #endregion
1063                         }
1064                         if( ヒット処理表.判定 )
1065                         {
1066                                 #region " チップの判定処理を行う。"
1067                                 //----------------
1068                                 if( hitRankType != ヒットランク種別.MISS )
1069                                 {
1070                                         // (A) PERFECT~POOR
1071
1072                                         var 対応表 = App.ユーザ管理.選択されているユーザ.オプション設定.ドラムとチップと入力の対応表[ chip.チップ種別 ];
1073
1074                                         this._コンボ.COMBO値++;
1075
1076                                         this._回転羽.発火する(
1077                                                 new Vector2(
1078                                                         レーンフレームの左端位置 + レーンフレーム.レーンto横中央相対位置[ 対応表.表示レーン種別 ],
1079                                                         ヒット判定バーの中央Y座標 ) );
1080
1081                                         this._ドラムセット.ヒットアニメ開始( 対応表.ドラム入力種別, App.ユーザ管理.選択されているユーザ.オプション設定.表示レーンの左右 );
1082                                         this._レーンフレーム.フラッシュ開始( 対応表.表示レーン種別 );
1083
1084                                         this._ヒットランク.表示開始( chip.チップ種別, hitRankType );
1085                                         this.ヒットランク別ヒット回数[ hitRankType ]++;
1086                                 }
1087                                 else
1088                                 {
1089                                         // (B) MISS
1090
1091                                         this._コンボ.COMBO値 = 0;
1092
1093                                         this._ヒットランク.表示開始( chip.チップ種別, ヒットランク種別.MISS );
1094                                         this.ヒットランク別ヒット回数[ hitRankType ]++;
1095                                 }
1096                                 //----------------
1097                                 #endregion
1098                         }
1099                         if( ヒット処理表.非表示 )
1100                         {
1101                                 #region " チップを非表示にする。"
1102                                 //----------------
1103                                 if( hitRankType != ヒットランク種別.MISS )
1104                                 {
1105                                         chip.可視 = false;        // PERFECT~POOR チップは非表示。
1106                                 }
1107                                 else
1108                                 {
1109                                         if( chip.可視 )
1110                                                 chip.可視 = false;    // MISSチップは最後まで表示し続ける。
1111                                 }
1112                                 //----------------
1113                                 #endregion
1114                         }
1115                 }
1116         }
1117 }