OSDN Git Service

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