OSDN Git Service

曲ツリーの構築機能を ユーザ クラスから分離。
[strokestylet/CsWin10Desktop3.git] / StrokeStyleT / ステージ / 演奏 / スクロール譜面.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.Linq;
5 using FDK.メディア;
6
7 namespace SST.ステージ.演奏
8 {
9         class スクロール譜面 : FDK.Activity
10         {
11                 // 外部依存 Action。
12                 public Action<SSTFormat.チップ種別, ヒット判定種別> ヒット判定文字列開始 = null;
13                 public Action コンボリセット = null;
14                 public Action コンボ加算 = null;
15                 public Action<ヒット判定種別> ヒット判定数加算 = null;
16                 public Action 背景動画再生開始 = null;
17                 public Action<SSTFormat.チップ> チップヒット = null;
18                 public Action ステージクリア = null;
19                 public Func<double> リアルタイム演奏時刻sec = null;
20                 public Func<double> 譜面スクロール速度の倍率 = null;
21                 public Action FPSをカウント = null;
22
23                 public スクロール譜面()
24                 {
25                         this.子リスト.Add( this.チップ画像 = new 画像( @"$(Static)\images\Chips.png" ) );
26                 }
27                 protected override void On活性化( デバイスリソース dr )
28                 {
29                         this.描画開始チップ番号 = -1;
30                         this.チップ画像の矩形リスト = new FDK.メディア.矩形リスト( @"$(Static)\images\Chips Rectangle List.xml" );   // ここで読み込む → 変更があったら反映される(デバッグ用途)。
31                         var ユーザ = StrokeStyleT.ユーザ管理.現在選択されているユーザ;
32                         this.ヒット判定を行うチップと対応するレーン = new Dictionary<SSTFormat.チップ種別, ヒットレーン種別>() {
33                                 { SSTFormat.チップ種別.LeftCrash, ヒットレーン種別.LeftCrash },
34                                 { SSTFormat.チップ種別.Ride, ( ユーザ.Rideは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
35                                 { SSTFormat.チップ種別.Ride_Cup, ( ユーザ.Rideは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
36                                 { SSTFormat.チップ種別.China, ( ユーザ.Chinaは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
37                                 { SSTFormat.チップ種別.Splash, ( ユーザ.Splashは左 ) ? ヒットレーン種別.LeftCrash : ヒットレーン種別.RightCrash },
38                                 { SSTFormat.チップ種別.HiHat_Open, ヒットレーン種別.HiHat },
39                                 { SSTFormat.チップ種別.HiHat_HalfOpen, ヒットレーン種別.HiHat },
40                                 { SSTFormat.チップ種別.HiHat_Close, ヒットレーン種別.HiHat },
41                                 //{ SSTFormat.チップ種別.HiHat_Foot, ヒット判定レーン種別.HiHat },
42                                 { SSTFormat.チップ種別.Snare, ヒットレーン種別.Snare },
43                                 { SSTFormat.チップ種別.Snare_OpenRim, ヒットレーン種別.Snare },
44                                 { SSTFormat.チップ種別.Snare_ClosedRim, ヒットレーン種別.Snare },
45                                 //{ SSTFormat.チップ種別.Snare_Ghost, ヒット判定レーン種別.Snare },
46                                 { SSTFormat.チップ種別.Bass, ヒットレーン種別.Bass },
47                                 { SSTFormat.チップ種別.Tom1, ヒットレーン種別.Tom1 },
48                                 { SSTFormat.チップ種別.Tom1_Rim, ヒットレーン種別.Tom1 },
49                                 { SSTFormat.チップ種別.Tom2, ヒットレーン種別.Tom2 },
50                                 { SSTFormat.チップ種別.Tom2_Rim, ヒットレーン種別.Tom2 },
51                                 { SSTFormat.チップ種別.Tom3, ヒットレーン種別.Tom2 },
52                                 { SSTFormat.チップ種別.Tom3_Rim, ヒットレーン種別.Tom2 },
53                                 { SSTFormat.チップ種別.RightCrash, ヒットレーン種別.RightCrash },
54                         };
55                         this.活性化した直後である = true;
56                 }
57                 protected override void On非活性化( デバイスリソース dr )
58                 {
59                         // 高頻度進行スレッドへ、終了を指示。
60                         this.高頻度進行.状態 = FDK.同期.TriStateEvent.状態種別.OFF;
61                 }
62                 protected override void Onデバイス依存リソースの作成( デバイスリソース dr )
63                 {
64                         this.画面の高さdpx = dr.設計画面サイズdpx.Height;
65                 }
66                 public void 小節線拍線を進行描画する( デバイスリソース dr, double 現在の演奏時刻sec )
67                 {
68                         this.画面内に収まるチップをすべて進行描画する(
69                                 dr,
70                                 this.小節線拍線を1つ描画する,
71                                 現在の演奏時刻sec );
72                 }
73                 public void チップを進行描画する( デバイスリソース dr, double 現在の演奏時刻sec )
74                 {
75                         this.画面内に収まるチップをすべて進行描画する(
76                                 dr,
77                                 this.チップを1つ描画する,
78                                 現在の演奏時刻sec );
79                 }
80
81                 /// <summary>
82                 /// 音声の再生と演奏用入力は、描画処理とは並行して、高頻度に行う。
83                 /// </summary>
84                 private void 高頻度進行処理タスクエントリ()
85                 {
86                         FDK.Log.Info( $"{FDK.Utilities.現在のメソッド名} --> 開始" );
87
88                         while( this.高頻度進行.状態 == FDK.同期.TriStateEvent.状態種別.ON )
89                         {
90                                 lock( this.スレッド排他 )
91                                 {
92                                         this.FPSをカウント();
93
94                                         var 演奏スコア = StrokeStyleT.演奏スコア;
95
96                                         for( int i = this.描画開始チップ番号; ( 0 <= i ) && ( 演奏スコア.チップリスト.Count > i ); i++ )
97                                         {
98                                                 var チップ = 演奏スコア.チップリスト[ i ];
99
100                                                 // 時間差を算出する。
101                                                 double 描画時間差sec = this.リアルタイム演奏時刻sec() - チップ.描画時刻sec;
102
103                                                 // 判定バーとチップの距離[dpx;符号付き;負数ならバー未達、0でバー直上、正数でバー通過]を算出する。
104                                                 double 描画距離dpx = 演奏スコア.指定された時間secに対応する符号付きピクセル数を返す( this.譜面スクロール速度の倍率(), 描画時間差sec );
105
106                                                 // チップのY座標を算出し、ループの終了判定を行う。
107                                                 double y = 座標.判定バーの中央Y座標dpx + 描画距離dpx;
108                                                 if( y < -40.0 )   // y が画面上端より上に出ていればそこでチップのループ描画は終了。-40dpx はチップが隠れるであろう適当なマージン。
109                                                         break;
110
111                                                 #region " チップが描画開始チップであり、かつ、そのY座標が画面下端を超えたなら、描画開始チップ番号を更新する。"
112                                                 //----------------
113                                                 if( ( this.描画開始チップ番号 == i ) && ( this.画面の高さdpx + 40.0 < y ) )   // +40 dpx はチップが(以下同上
114                                                 {
115                                                         this.描画開始チップ番号++;
116                                                         if( 演奏スコア.チップリスト.Count <= this.描画開始チップ番号 )
117                                                         {
118                                                                 this.ステージクリア();
119                                                                 this.描画開始チップ番号 = -1;    // 演奏終了。
120                                                         }
121                                                 }
122                                                 //----------------
123                                                 #endregion
124                                                 #region " AutoPlay チップなら、自動ヒット判定を行う。"
125                                                 //----------------
126                                                 if( ( チップ.ヒットされていない ) &&    // チップが未ヒット、かつ、
127                                                         ( StrokeStyleT.ユーザ管理.現在選択されているユーザ.チップが自動演奏である( チップ.チップ種別 ) ) &&    // チップの AutoPlay が ON、かつ、
128                                                         ( 0 <= 描画距離dpx ) )  // バーを通過した。
129                                                 {
130                                                         チップ.ヒット済みである = true;
131                                                         チップ.不可視 = true;
132                                                         if( チップ.チップ種別 == SSTFormat.チップ種別.背景動画 )
133                                                         {
134                                                                 // (A) 背景動画の場合。
135                                                                 this.背景動画再生開始?.Invoke();
136                                                         }
137                                                         else
138                                                         {
139                                                                 // (B) その他のチップの場合。
140                                                                 this.チップヒット?.Invoke( チップ );
141                                                                 this.コンボ加算?.Invoke();
142                                                                 this.ヒット判定数加算?.Invoke( ヒット判定種別.AUTO );
143                                                                 this.ヒット判定文字列開始?.Invoke( チップ.チップ種別, ヒット判定種別.AUTO );
144                                                         }
145                                                 }
146                                                 //----------------
147                                                 #endregion
148                                                 #region " Miss 判定を行う。"
149                                                 //----------------
150                                                 if( ( チップ.ヒットされていない ) &&    // チップが未ヒット、かつ、
151                                                         ( this.ヒット判定を行うチップと対応するレーン.ContainsKey( チップ.チップ種別 ) ) && // チップがヒット判定対象である、かつ、
152                                                         ( 描画時間差sec > StrokeStyleT.ユーザ管理.現在選択されているユーザ.ヒット範囲sec.Poor ) )  // 描画時間差が Poor 範囲を超えている。
153                                                 {
154                                                         チップ.ヒット済みである = true;
155                                                         チップ.不可視 = true;
156                                                         this.コンボリセット?.Invoke();
157                                                         this.ヒット判定文字列開始?.Invoke( チップ.チップ種別, ヒット判定種別.MISS );
158                                                 }
159                                                 //----------------
160                                                 #endregion
161                                         }
162                                 }
163
164                                 // ウェイト
165                                 System.Threading.Thread.Sleep( 1 );
166                         }
167
168                         FDK.Log.Info( $"{FDK.Utilities.現在のメソッド名} <-- 終了" );
169                 }
170                 protected void 画面内に収まるチップをすべて進行描画する( デバイスリソース dr, Action<デバイスリソース, SSTFormat.チップ, float> 描画アクション, double 現在の演奏時刻sec )
171                 {
172                         lock( this.スレッド排他 )
173                         {
174                                 var 演奏スコア = StrokeStyleT.演奏スコア;
175                                 Trace.Assert( null != 演奏スコア, "[バグあり] 演奏スコアが null です。" );
176
177                                 #region " 最初の進行描画。"
178                                 //----------------
179                                 if( this.活性化した直後である )
180                                 {
181                                         this.活性化した直後である = false;
182                                         this.描画開始チップ番号 = 0; // -1 → 0; 演奏開始。
183                                         this.チップアニメ.開始する( 最初の値: 0, 最後の値: 48, 値をひとつ増加させるのにかける時間ms: 10 );
184
185                                         // 高頻度進行タスクを生成。
186                                         this.高頻度進行 = new FDK.同期.TriStateEvent( FDK.同期.TriStateEvent.状態種別.ON );
187                                         System.Threading.Tasks.Task.Run( () => {
188                                                 高頻度進行処理タスクエントリ();
189                                         } );
190                                 }
191                                 //----------------
192                                 #endregion
193
194                                 for( int i = this.描画開始チップ番号; ( 0 <= i ) && ( 演奏スコア.チップリスト.Count > i ); i++ )
195                                 {
196                                         var チップ = 演奏スコア.チップリスト[ i ];
197
198                                         // 時間差を算出する。
199                                         double 描画時間差sec = 現在の演奏時刻sec - チップ.描画時刻sec;
200
201                                         // 判定バーとチップの距離[dpx;符号付き;負数ならバー未達、0でバー直上、正数でバー通過]を算出する。
202                                         double 描画距離dpx = 演奏スコア.指定された時間secに対応する符号付きピクセル数を返す( this.譜面スクロール速度の倍率(), 描画時間差sec );
203
204                                         // チップのY座標を算出し、ループの終了判定を行う。
205                                         double y = 座標.判定バーの中央Y座標dpx + 描画距離dpx;
206                                         if( y < -40.0 )   // y が画面上端より上に出ていればそこでチップのループ描画は終了。-40dpx はチップが隠れるであろう適当なマージン。
207                                                 break;
208
209                                         // チップを1つ描画する。
210                                         if( チップ.可視 )
211                                                 描画アクション( dr, チップ, (float) y );
212                                 }
213                         }
214                 }
215                 private void 小節線拍線を1つ描画する( デバイスリソース dr, SSTFormat.チップ chip, float Ydpx )
216                 {
217                         if( null == this.チップ画像 )
218                                 return;
219
220                         this.チップ画像.加算合成 = false;
221
222                         switch( chip.チップ種別 )
223                         {
224                                 case SSTFormat.チップ種別.小節線:
225                                         #region " 小節線を1本表示する。"
226                                         //----------------
227                                         {
228                                                 float 左位置dpx = 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ SSTFormat.レーン種別.LeftCrash ] - 1f;
229                                                 float 上位置dpx = Ydpx - 1f;
230                                                 var 画像範囲orNull = this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.小節線 ) ];
231                                                 if( null != 画像範囲orNull )
232                                                 {
233                                                         var 画像範囲 = (SharpDX.RectangleF) 画像範囲orNull;
234                                                         this.チップ画像.描画する( dr, 左位置dpx, 上位置dpx, 転送元矩形dpx: 画像範囲 );
235                                                         this.チップ画像.描画する( dr, 左位置dpx + 画像範囲.Width, 上位置dpx, 転送元矩形dpx: 画像範囲 );
236                                                 }
237                                         }
238                                         //----------------
239                                         #endregion
240                                         break;
241
242                                 case SSTFormat.チップ種別.拍線:
243                                         #region " 拍線を1本表示する。"
244                                         //----------------
245                                         {
246                                                 float 左位置dpx = 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ SSTFormat.レーン種別.LeftCrash ] - 1f;
247                                                 float 上位置dpx = Ydpx;
248                                                 var 画像範囲orNull = this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.拍線 ) ];
249                                                 if( null != 画像範囲orNull )
250                                                 {
251                                                         var 画像範囲 = (SharpDX.RectangleF) 画像範囲orNull;
252                                                         this.チップ画像.描画する( dr, 左位置dpx: 左位置dpx, 上位置dpx: 上位置dpx, 転送元矩形dpx: 画像範囲 );
253                                                         this.チップ画像.描画する( dr, 左位置dpx: 左位置dpx + 画像範囲.Width, 上位置dpx: 上位置dpx, 転送元矩形dpx: 画像範囲 );
254                                                 }
255                                         }
256                                         //----------------
257                                         #endregion
258                                         break;
259                         }
260                 }
261                 private void チップを1つ描画する( デバイスリソース dr, SSTFormat.チップ chip, float Ydpx )
262                 {
263                         if( null == this.チップ画像 )
264                                 return;
265
266                         this.チップ画像.加算合成 = false;
267                         float 音量0to1 = chip.音量 * 0.25f; // 1~4 → 0.25~1.00
268
269                         switch( chip.チップ種別 )
270                         {
271                                 case SSTFormat.チップ種別.LeftCrash:
272                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.LeftCrash ) ], Ydpx, 音量0to1 );
273                                         break;
274
275                                 case SSTFormat.チップ種別.HiHat_Close:
276                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Close ) ], SSTFormat.レーン種別.HiHat, Ydpx, 音量0to1 );
277                                         break;
278
279                                 case SSTFormat.チップ種別.HiHat_HalfOpen:
280                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Close ) ], SSTFormat.レーン種別.HiHat, Ydpx, 音量0to1 );
281                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Foot, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_HalfOpen ) ], Ydpx, 1.0f );    // 音量は反映しない
282                                         break;
283
284                                 case SSTFormat.チップ種別.HiHat_Open:
285                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Close ) ], SSTFormat.レーン種別.HiHat, Ydpx, 音量0to1 );
286                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Foot, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Open ) ], Ydpx, 1.0f );    // 音量は反映しない
287                                         break;
288
289                                 case SSTFormat.チップ種別.HiHat_Foot:
290                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Foot, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.HiHat_Foot ) ], Ydpx, 1.0f );    // 音量は反映しない
291                                         break;
292
293                                 case SSTFormat.チップ種別.Snare:
294                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare ) ], SSTFormat.レーン種別.Snare, Ydpx, 音量0to1 );
295                                         break;
296
297                                 case SSTFormat.チップ種別.Snare_ClosedRim:
298                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare_ClosedRim ) ], Ydpx, 1.0f );  // 音量は反映しない
299                                         break;
300
301                                 case SSTFormat.チップ種別.Snare_OpenRim:
302                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare_OpenRim ) ], Ydpx, 音量0to1 );
303                                         //this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare ) ], y, 音量0to1 ); → ないほうが見た目がいいかも。
304                                         break;
305
306                                 case SSTFormat.チップ種別.Snare_Ghost:
307                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Snare, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Snare_Ghost ) ], Ydpx, 1.0f );  // 音量は反映しない
308                                         break;
309
310                                 case SSTFormat.チップ種別.Bass:
311                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Bass ) ], SSTFormat.レーン種別.Bass, Ydpx, 音量0to1 );
312                                         break;
313
314                                 case SSTFormat.チップ種別.Tom1:
315                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom1 ) ], SSTFormat.レーン種別.Tom1, Ydpx, 音量0to1 );
316                                         break;
317
318                                 case SSTFormat.チップ種別.Tom1_Rim:
319                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Tom1, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom1_Rim ) ], Ydpx, 1.0f );  // 音量は反映しない
320                                         break;
321
322                                 case SSTFormat.チップ種別.Tom2:
323                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom2 ) ], SSTFormat.レーン種別.Tom2, Ydpx, 音量0to1 );
324                                         break;
325
326                                 case SSTFormat.チップ種別.Tom2_Rim:
327                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Tom2, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom2_Rim ) ], Ydpx, 1.0f );  // 音量は反映しない
328                                         break;
329
330                                 case SSTFormat.チップ種別.Tom3:
331                                         this.アニメチップを1つ描画する( dr, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom3 ) ], SSTFormat.レーン種別.Tom3, Ydpx, 音量0to1 );
332                                         break;
333
334                                 case SSTFormat.チップ種別.Tom3_Rim:
335                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.Tom3, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.Tom3_Rim ) ], Ydpx, 1.0f );  // 音量は反映しない
336                                         break;
337
338                                 case SSTFormat.チップ種別.RightCrash:
339                                         this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ nameof( SSTFormat.チップ種別.RightCrash ) ], Ydpx, 音量0to1 );
340                                         break;
341
342                                 case SSTFormat.チップ種別.China:
343                                         if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Chinaは左 )
344                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftChina" ], Ydpx, 音量0to1 );
345                                         else
346                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightChina" ], Ydpx, 音量0to1 );
347                                         break;
348
349                                 case SSTFormat.チップ種別.Ride:
350                                         if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Rideは左 )
351                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftRide" ], Ydpx, 音量0to1 );
352                                         else
353                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightRide" ], Ydpx, 音量0to1 );
354                                         break;
355
356                                 case SSTFormat.チップ種別.Ride_Cup:
357                                         if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Rideは左 )
358                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftRide_Cup" ], Ydpx, 音量0to1 );
359                                         else
360                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightRide_Cup" ], Ydpx, 音量0to1 );
361                                         break;
362
363                                 case SSTFormat.チップ種別.Splash:
364                                         if( StrokeStyleT.ユーザ管理.現在選択されているユーザ.Splashは左 )
365                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.LeftCrash, this.チップ画像の矩形リスト[ "LeftSplash" ], Ydpx, 音量0to1 );
366                                         else
367                                                 this.単画チップを1つ描画する( dr, SSTFormat.レーン種別.RightCrash, this.チップ画像の矩形リスト[ "RightSplash" ], Ydpx, 音量0to1 );
368                                         break;
369                         }
370                 }
371                 private void アニメチップを1つ描画する( デバイスリソース dr, SharpDX.RectangleF? 画像範囲orNull, SSTFormat.レーン種別 elane, float Ydpx, float 音量0to1 )
372                 {
373                         if( null == 画像範囲orNull )
374                                 return;
375                         var 画像範囲 = (SharpDX.RectangleF) 画像範囲orNull;
376
377                         float チップ1枚の高さdpx = 18f;
378                         画像範囲.Offset( 0f, this.チップアニメ.現在値 * 15f );   // 下端3dpxは下のチップと共有する前提のデザインなので、18f-3f = 15f。
379                         画像範囲.Height = チップ1枚の高さdpx;
380                         float 左位置dpx = 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ elane ] - 画像範囲.Width / 2f;
381                         float 上位置dpx = Ydpx - ( チップ1枚の高さdpx / 2f ) * 音量0to1;
382
383                         this.チップ画像.描画する( dr, 左位置dpx, 上位置dpx, 転送元矩形dpx: 画像範囲, Y方向拡大率: 音量0to1 );
384                 }
385                 private void 単画チップを1つ描画する( デバイスリソース dr, SSTFormat.レーン種別 eLane, SharpDX.RectangleF? 元矩形dpx, float 上位置dpx, float 音量0to1 )
386                 {
387                         if( null == 元矩形dpx )
388                                 return;
389
390                         var 画像範囲dpx = (SharpDX.RectangleF) 元矩形dpx;
391                         this.チップ画像.描画する(
392                                 dr,
393                                 左位置dpx: 座標.レーンフレーム左端のX座標dpx + this.レーン種別toレーンフレーム左端からの相対X位置dpx[ eLane ] - ( 画像範囲dpx.Width / 2f ),
394                                 上位置dpx: 上位置dpx - ( ( 画像範囲dpx.Height / 2f ) * 音量0to1 ),
395                                 転送元矩形dpx: 元矩形dpx,
396                                 Y方向拡大率: 音量0to1 );
397                 }
398
399                 protected bool 活性化した直後である = false;
400                 /// <summary>
401                 /// 演奏スコア.listチップ[] のうち、描画を始めるチップのインデックス番号を示す。
402                 /// 演奏開始直後は 0 で始まり、対象番号のチップが描画範囲を流れ去るたびに +1 される。
403                 /// 未演奏時・演奏終了時は -1 。
404                 /// </summary>
405                 protected int 描画開始チップ番号 = -1;
406                 protected readonly FDK.メディア.画像 チップ画像;
407                 protected FDK.メディア.矩形リスト チップ画像の矩形リスト = null;
408                 protected Dictionary<SSTFormat.チップ種別, ヒットレーン種別> ヒット判定を行うチップと対応するレーン = null;
409                 protected readonly FDK.カウンタ.単純増加後反復カウンタ チップアニメ = new FDK.カウンタ.単純増加後反復カウンタ();
410                 protected readonly Dictionary<SSTFormat.レーン種別, float> レーン種別toレーンフレーム左端からの相対X位置dpx = new Dictionary<SSTFormat.レーン種別, float>() {
411                         { SSTFormat.レーン種別.LeftCrash, +36f },
412                         { SSTFormat.レーン種別.HiHat, +105f },
413                         { SSTFormat.レーン種別.Foot, +145f },
414                         { SSTFormat.レーン種別.Snare, +214f },
415                         { SSTFormat.レーン種別.Tom1, +310f },
416                         { SSTFormat.レーン種別.Bass, +381f },
417                         { SSTFormat.レーン種別.Tom2, +448f },
418                         { SSTFormat.レーン種別.Tom3, +544f },
419                         { SSTFormat.レーン種別.RightCrash, +632f },
420                 };
421
422                 private float 画面の高さdpx = 0f;    // 進行スレッドからはデバイスリソースを参照しないので、ここにキャッシュしておく。
423                 private readonly object スレッド排他 = new object();
424                 private FDK.同期.TriStateEvent 高頻度進行 = null;
425         }
426 }