OSDN Git Service

#34825, #36261 電源プランをHighPerformanceにするかどうかと、WASAPIをevent drivenで動作させるかどうかを、CONFIGURA...
[dtxmania/dtxmania.git] / FDK17プロジェクト / コード / 03.サウンド / CSound.cs
1 using System;\r
2 using System.Collections.Generic;\r
3 using System.Text;\r
4 using System.Diagnostics;\r
5 using System.Runtime.InteropServices;\r
6 using System.IO;\r
7 using System.Runtime.CompilerServices;\r
8 using System.Threading;\r
9 using SlimDX;\r
10 using SlimDX.DirectSound;\r
11 using SlimDX.Multimedia;\r
12 using Un4seen.Bass;\r
13 using Un4seen.BassAsio;\r
14 using Un4seen.BassWasapi;\r
15 using Un4seen.Bass.AddOn.Mix;\r
16 using Un4seen.Bass.AddOn.Fx;\r
17 using DirectShowLib;\r
18 \r
19 namespace FDK\r
20 {\r
21         #region [ DTXMania用拡張 ]\r
22         public class CSound管理       // : CSound\r
23         {\r
24                 public static ISoundDevice SoundDevice\r
25                 {\r
26                         get; set;\r
27                 }\r
28                 public static ESoundDeviceType SoundDeviceType\r
29                 {\r
30                         get; set;\r
31                 }\r
32                 public static CSoundTimer rc演奏用タイマ = null;\r
33                 public static bool bUseOSTimer = false;         // OSのタイマーを使うか、CSoundTimerを使うか。DTXCではfalse, DTXManiaではtrue。\r
34                                                                                                         // DTXC(DirectSound)でCSoundTimerを使うと、内部で無音のループサウンドを再生するため\r
35                                                                                                         // サウンドデバイスを占有してしまい、Viewerとして呼び出されるDTXManiaで、ASIOが使えなくなる。\r
36 \r
37                                                                                                         // DTXMania単体でこれをtrueにすると、WASAPI/ASIO時に演奏タイマーとしてFDKタイマーではなく\r
38                                                                                                         // システムのタイマーを使うようになる。こうするとスクロールは滑らかになるが、音ズレが出るかもしれない。\r
39                 \r
40                 public static IntPtr WindowHandle;\r
41 \r
42                 public static bool bIsTimeStretch = false;\r
43 \r
44                 private static int _nMasterVolume;\r
45                 public int nMasterVolume\r
46                 {\r
47                         get\r
48                         {\r
49                                 return _nMasterVolume;\r
50                         }\r
51                         //get\r
52                         //{\r
53                         //    if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )\r
54                         //    {\r
55                         //        return Bass.BASS_GetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM ) / 100;\r
56                         //    }\r
57                         //    else\r
58                         //    {\r
59                         //        return 100;\r
60                         //    }\r
61                         //}\r
62                         //set\r
63                         //{\r
64                         //    if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI )\r
65                         //    {\r
66                         //                      // LINEARでなくWINDOWS(2)を使う必要があるが、exclusive時は使用不可、またデバイス側が対応してないと使用不可\r
67                         //        bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_LINEAR, value / 100.0f );\r
68                         //        if ( !b )\r
69                         //        {\r
70                         //            BASSError be = Bass.BASS_ErrorGetCode();\r
71                         //            Trace.TraceInformation( "WASAPI Master Volume Set Error: " + be.ToString() );\r
72                         //        }\r
73                         //    }\r
74                         //}\r
75                         //set\r
76                         //{\r
77                         //    if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )\r
78                         //    {\r
79                         //        bool b = Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, value * 100 );\r
80                         //        if ( !b )\r
81                         //        {\r
82                         //            BASSError be = Bass.BASS_ErrorGetCode();\r
83                         //            Trace.TraceInformation( "Master Volume Set Error: " + be.ToString() );\r
84                         //        }\r
85                         //    }\r
86                         //}\r
87                         //set\r
88                         //{\r
89                         //    if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )\r
90                         //    {\r
91                         //        var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };\r
92                         //        BassMix.BASS_Mixer_ChannelSetEnvelope( SoundDevice.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );\r
93                         //    }\r
94                         //}\r
95                         set\r
96                         {\r
97                                 SoundDevice.nMasterVolume = value;\r
98                                 _nMasterVolume = value;\r
99                         }\r
100                 }\r
101 \r
102                 ///// <summary>\r
103                 ///// BASS時、mp3をストリーミング再生せずに、デコードしたraw wavをオンメモリ再生する場合はtrueにする。\r
104                 ///// 特殊なmp3を使用時はシークが乱れるので、必要に応じてtrueにすること。(Config.iniのNoMP3Streamingで設定可能。)\r
105                 ///// ただし、trueにすると、その分再生開始までの時間が長くなる。\r
106                 ///// </summary>\r
107                 //public static bool bIsMP3DecodeByWindowsCodec = false;\r
108 \r
109                 public static int nMixing = 0;\r
110                 public int GetMixingStreams()\r
111                 {\r
112                         return nMixing;\r
113                 }\r
114                 public static int nStreams = 0;\r
115                 public int GetStreams()\r
116                 {\r
117                         return nStreams;\r
118                 }\r
119                 #region [ WASAPI/ASIO/DirectSound設定値 ]\r
120                 /// <summary>\r
121                 /// <para>WASAPI 排他モード出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>\r
122                 /// <para>0以下の値を指定すると、この数値はWASAPI初期化時に自動設定する。正数を指定すると、その値を設定しようと試みる。</para>\r
123                 /// </summary>\r
124                 public static int SoundDelayExclusiveWASAPI = 0;                // SSTでは、50ms\r
125                 public int GetSoundExclusiveWASAPI()\r
126                 {\r
127                         return SoundDelayExclusiveWASAPI;\r
128                 }\r
129                 public void SetSoundDelayExclusiveWASAPI( int value )\r
130                 {\r
131                         SoundDelayExclusiveWASAPI = value;\r
132                 }\r
133                 /// <summary>\r
134                 /// <para>WASAPI 共有モード出力における再生遅延[ms]。ユーザが決定する。</para>\r
135                 /// </summary>\r
136                 public static int SoundDelaySharedWASAPI = 100;\r
137                 /// <summary>\r
138                 /// <para>排他WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>\r
139                 /// <para>→ 自動設定されるのでSoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>\r
140                 /// </summary>\r
141                 public static int SoundUpdatePeriodExclusiveWASAPI = 6;\r
142                 /// <summary>\r
143                 /// <para>共有WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>\r
144                 /// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>\r
145                 /// </summary>\r
146                 public static int SoundUpdatePeriodSharedWASAPI = 6;\r
147                 /// <summary>\r
148                 /// WASAPI利用時に、サウンドバッファの更新をevent drivenにするか、pollingにするかの設定。\r
149                 /// デフォルト設定はpolling。event drivenにすることで、よりラグを小さくできるが、CPU負荷は若干上昇する。\r
150                 /// (更新頻度が上がるため)\r
151                 /// なおこれをtrueにすると、SoundUpdatePeriodExclusiveWASAPIの設定は無視される。\r
152                 /// </summary>\r
153                 public static bool bSoundUpdateByEventWASAPI = false;\r
154 \r
155                 ///// <summary>\r
156                 ///// <para>ASIO 出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>\r
157                 ///// </summary>\r
158                 //public static int SoundDelayASIO = 0;                                 // SSTでは50ms。0にすると、デバイスの設定値をそのまま使う。\r
159                 /// <summary>\r
160                 /// <para>ASIO 出力におけるバッファサイズ。</para>\r
161                 /// </summary>\r
162                 public static int SoundDelayASIO = 0;                                           // 0にすると、デバイスの設定値をそのまま使う。\r
163                 public int GetSoundDelayASIO()\r
164                 {\r
165                         return SoundDelayASIO;\r
166                 }\r
167                 public void SetSoundDelayASIO(int value)\r
168                 {\r
169                         SoundDelayASIO = value;\r
170                 }\r
171                 public static int ASIODevice = 0;\r
172                 public int GetASIODevice()\r
173                 {\r
174                         return ASIODevice;\r
175                 }\r
176                 public void SetASIODevice(int value)\r
177                 {\r
178                         ASIODevice = value;\r
179                 }\r
180                 /// <summary>\r
181                 /// <para>DirectSound 出力における再生遅延[ms]。ユーザが決定する。</para>\r
182                 /// </summary>\r
183                 public static int SoundDelayDirectSound = 100;\r
184 \r
185                 public long GetSoundDelay()\r
186                 {\r
187                         if ( SoundDevice != null )\r
188                         {\r
189                                 return SoundDevice.n実バッファサイズms;\r
190                         }\r
191                         else\r
192                         {\r
193                                 return -1;\r
194                         }\r
195                 }\r
196 \r
197                 #endregion\r
198 \r
199 \r
200                 /// <summary>\r
201                 /// DTXC用コンストラクタ\r
202                 /// </summary>\r
203                 /// <param name="handle"></param>\r
204                 public CSound管理( IntPtr handle )    // #30803 従来のコンストラクタ相当のI/Fを追加。(DTXC用)\r
205                 {\r
206                         WindowHandle = handle;\r
207                         SoundDevice = null;\r
208                         bUseOSTimer = true;\r
209                         t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );\r
210                 }\r
211                 /// <summary>\r
212                 /// DTXMania用コンストラクタ\r
213                 /// </summary>\r
214                 /// <param name="handle"></param>\r
215                 /// <param name="soundDeviceType"></param>\r
216                 /// <param name="nSoundDelayExclusiveWASAPI"></param>\r
217                 /// <param name="nSoundDelayASIO"></param>\r
218                 /// <param name="nASIODevice"></param>\r
219                 public CSound管理( IntPtr handle, ESoundDeviceType soundDeviceType,\r
220                         int nSoundDelayExclusiveWASAPI, bool _bSoundUpdateByEventWASAPI,\r
221                         int nSoundDelayASIO, int nASIODevice,\r
222                         bool _bUseOSTimer )\r
223                 {\r
224                         WindowHandle = handle;\r
225                         SoundDevice = null;\r
226                         //bUseOSTimer = false;\r
227                         t初期化( soundDeviceType, nSoundDelayExclusiveWASAPI, _bSoundUpdateByEventWASAPI, nSoundDelayASIO, nASIODevice, _bUseOSTimer );\r
228                 }\r
229                 public void Dispose()\r
230                 {\r
231                         t終了();\r
232                 }\r
233 \r
234                 //public static void t初期化()\r
235                 //{\r
236                 //    t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );\r
237                 //}\r
238 \r
239                 public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, IntPtr handle )\r
240                 {\r
241                         //if ( !bInitialized )\r
242                         {\r
243                                 WindowHandle = handle;\r
244                                 t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice );\r
245                                 //bInitialized = true;\r
246                         }\r
247                 }\r
248                 public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice )\r
249                 {\r
250                         t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, false, _nSoundDelayASIO, _nASIODevice, false );\r
251                 }\r
252 \r
253                 public void t初期化( ESoundDeviceType soundDeviceType,\r
254                         int _nSoundDelayExclusiveWASAPI, bool _bSoundUpdateByEventWASAPI,\r
255                         int _nSoundDelayASIO, int _nASIODevice,\r
256                         bool _bUseOSTimer )\r
257                 {\r
258                         //SoundDevice = null;                                           // 後で再初期化することがあるので、null初期化はコンストラクタに回す\r
259                         rc演奏用タイマ = null;                                            // Global.Bass 依存(つまりユーザ依存)\r
260                         nMixing = 0;\r
261 \r
262                         SoundDelayExclusiveWASAPI = _nSoundDelayExclusiveWASAPI;\r
263                         SoundDelayASIO = _nSoundDelayASIO;\r
264                         ASIODevice = _nASIODevice;\r
265                         bUseOSTimer = _bUseOSTimer;\r
266                         bSoundUpdateByEventWASAPI = _bSoundUpdateByEventWASAPI;\r
267 \r
268                         ESoundDeviceType[] ESoundDeviceTypes = new ESoundDeviceType[ 4 ]\r
269                         {\r
270                                 ESoundDeviceType.ExclusiveWASAPI,\r
271                                 ESoundDeviceType.ASIO,\r
272                                 ESoundDeviceType.DirectSound,\r
273                                 ESoundDeviceType.Unknown\r
274                         };\r
275 \r
276                         int n初期デバイス;\r
277                         switch ( soundDeviceType )\r
278                         {\r
279                                 case ESoundDeviceType.ExclusiveWASAPI:\r
280                                         n初期デバイス = 0;\r
281                                         break;\r
282                                 case ESoundDeviceType.ASIO:\r
283                                         n初期デバイス = 1;\r
284                                         break;\r
285                                 case ESoundDeviceType.DirectSound:\r
286                                         n初期デバイス = 2;\r
287                                         break;\r
288                                 default:\r
289                                         n初期デバイス = 3;\r
290                                         break;\r
291                         }\r
292                         for ( SoundDeviceType = ESoundDeviceTypes[ n初期デバイス ]; ; SoundDeviceType = ESoundDeviceTypes[ ++n初期デバイス ] )\r
293                         {\r
294                                 try\r
295                                 {\r
296                                         t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する();\r
297                                         break;\r
298                                 }\r
299                                 catch ( Exception e )\r
300                                 {\r
301                                         Trace.TraceInformation( e.Message );\r
302                                         if ( ESoundDeviceTypes[ n初期デバイス ] == ESoundDeviceType.Unknown )\r
303                                         {\r
304                                                 Trace.TraceError( string.Format( "サウンドデバイスの初期化に失敗しました。" ) );\r
305                                                 break;\r
306                                         }\r
307                                 }\r
308                         }\r
309                         if ( soundDeviceType == ESoundDeviceType.ExclusiveWASAPI || soundDeviceType == ESoundDeviceType.ASIO )\r
310                         {\r
311                                 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 4 );\r
312                                 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );\r
313 \r
314                                 Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );\r
315                                 Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );\r
316                         }\r
317                 }\r
318 \r
319                 public void tDisableUpdateBufferAutomatically()\r
320                 {\r
321                         //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 0 );\r
322                         //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );\r
323 \r
324                         //Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );\r
325                         //Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );\r
326                 }\r
327 \r
328 \r
329                 public static void t終了()\r
330                 {\r
331                         C共通.tDisposeする( SoundDevice ); SoundDevice = null;\r
332                         C共通.tDisposeする( ref rc演奏用タイマ );     // Global.Bass を解放した後に解放すること。(Global.Bass で参照されているため)\r
333                 }\r
334 \r
335 \r
336                 public static void t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する()\r
337                 {\r
338                         #region [ すでにサウンドデバイスと演奏タイマが構築されていれば解放する。]\r
339                         //-----------------\r
340                         if ( SoundDevice != null )\r
341                         {\r
342                                 // すでに生成済みのサウンドがあれば初期状態に戻す。\r
343 \r
344                                 CSound.tすべてのサウンドを初期状態に戻す();             // リソースは解放するが、CSoundのインスタンスは残す。\r
345 \r
346 \r
347                                 // サウンドデバイスと演奏タイマを解放する。\r
348 \r
349                                 C共通.tDisposeする( SoundDevice ); SoundDevice = null;\r
350                                 C共通.tDisposeする( ref rc演奏用タイマ );     // Global.SoundDevice を解放した後に解放すること。(Global.SoundDevice で参照されているため)\r
351                         }\r
352                         //-----------------\r
353                         #endregion\r
354 \r
355                         #region [ 新しいサウンドデバイスを構築する。]\r
356                         //-----------------\r
357                         switch ( SoundDeviceType )\r
358                         {\r
359                                 case ESoundDeviceType.ExclusiveWASAPI:\r
360                                         SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.排他, SoundDelayExclusiveWASAPI, SoundUpdatePeriodExclusiveWASAPI );\r
361                                         break;\r
362 \r
363                                 case ESoundDeviceType.SharedWASAPI:\r
364                                         SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.共有, SoundDelaySharedWASAPI, SoundUpdatePeriodSharedWASAPI );\r
365                                         break;\r
366 \r
367                                 case ESoundDeviceType.ASIO:\r
368                                         SoundDevice = new CSoundDeviceASIO( SoundDelayASIO, ASIODevice );\r
369                                         break;\r
370 \r
371                                 case ESoundDeviceType.DirectSound:\r
372                                         SoundDevice = new CSoundDeviceDirectSound( WindowHandle, SoundDelayDirectSound, bUseOSTimer );\r
373                                         break;\r
374 \r
375                                 default:\r
376                                         throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );\r
377                         }\r
378                         //-----------------\r
379                         #endregion\r
380                         #region [ 新しい演奏タイマを構築する。]\r
381                         //-----------------\r
382                         rc演奏用タイマ = new CSoundTimer( SoundDevice );\r
383                         //-----------------\r
384                         #endregion\r
385 \r
386                         SoundDevice.nMasterVolume = _nMasterVolume;                                     // サウンドデバイスに対して、マスターボリュームを再設定する\r
387 \r
388                         CSound.tすべてのサウンドを再構築する( SoundDevice );              // すでに生成済みのサウンドがあれば作り直す。\r
389                 }\r
390                 public CSound tサウンドを生成する( string filename )\r
391                 {\r
392                         if ( SoundDeviceType == ESoundDeviceType.Unknown )\r
393                         {\r
394                                 throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );\r
395                         }\r
396                         return SoundDevice.tサウンドを作成する( filename );\r
397                 }\r
398 \r
399                 private static DateTime lastUpdateTime = DateTime.MinValue;\r
400                 public void t再生中の処理をする( object o )                    // #26122 2011.9.1 yyagi; delegate経由の呼び出し用\r
401                 {\r
402                         t再生中の処理をする();\r
403                 }\r
404                 public void t再生中の処理をする()\r
405                 {\r
406 //★★★★★★★★★★★★★★★★★★★★★ダミー★★★★★★★★★★★★★★★★★★\r
407 //                      Debug.Write( "再生中の処理をする()" );\r
408                         //DateTime now = DateTime.Now;\r
409                         //TimeSpan ts = now - lastUpdateTime;\r
410                         //if ( ts.Milliseconds > 5 )\r
411                         //{\r
412                         //    bool b = Bass.BASS_Update( 100 * 2 );\r
413                         //    lastUpdateTime = DateTime.Now;\r
414                         //    if ( !b )\r
415                         //    {\r
416                         //        Trace.TraceInformation( "BASS_UPdate() failed: " + Bass.BASS_ErrorGetCode().ToString() );\r
417                         //    }\r
418                         //}\r
419                 }\r
420 \r
421                 public void tサウンドを破棄する( CSound csound )\r
422                 {\r
423                         csound.t解放する( true );                   // インスタンスは存続→破棄にする。\r
424                         csound = null;\r
425                 }\r
426 \r
427                 public float GetCPUusage()\r
428                 {\r
429                         float f;\r
430                         switch ( SoundDeviceType )\r
431                         {\r
432                                 case ESoundDeviceType.ExclusiveWASAPI:\r
433                                 case ESoundDeviceType.SharedWASAPI:\r
434                                         f = BassWasapi.BASS_WASAPI_GetCPU();\r
435                                         break;\r
436                                 case ESoundDeviceType.ASIO:\r
437                                         f = BassAsio.BASS_ASIO_GetCPU();\r
438                                         break;\r
439                                 case ESoundDeviceType.DirectSound:\r
440                                         f = 0.0f;\r
441                                         break;\r
442                                 default:\r
443                                         f = 0.0f;\r
444                                         break;\r
445                         }\r
446                         return f;\r
447                 }\r
448 \r
449                 public string GetCurrentSoundDeviceType()\r
450                 {\r
451                         switch ( SoundDeviceType )\r
452                         {\r
453                                 case ESoundDeviceType.ExclusiveWASAPI:\r
454                                 case ESoundDeviceType.SharedWASAPI:\r
455                                         return "WASAPI";\r
456                                 case ESoundDeviceType.ASIO:\r
457                                         return "ASIO";\r
458                                 case ESoundDeviceType.DirectSound:\r
459                                         return "DirectSound";\r
460                                 default:\r
461                                         return "Unknown";\r
462                         }\r
463                 }\r
464 \r
465                 public void AddMixer( CSound cs, double db再生速度, bool _b演奏終了後も再生が続くチップである )\r
466                 {\r
467                         cs.b演奏終了後も再生が続くチップである = _b演奏終了後も再生が続くチップである;\r
468                         cs.db再生速度 = db再生速度;\r
469                         cs.tBASSサウンドをミキサーに追加する();\r
470                 }\r
471                 public void AddMixer( CSound cs, double db再生速度 )\r
472                 {\r
473                         cs.db再生速度 = db再生速度;\r
474                         cs.tBASSサウンドをミキサーに追加する();\r
475                 }\r
476                 public void AddMixer( CSound cs )\r
477                 {\r
478                         cs.tBASSサウンドをミキサーに追加する();\r
479                 }\r
480                 public void RemoveMixer( CSound cs )\r
481                 {\r
482                         cs.tBASSサウンドをミキサーから削除する();\r
483                 }\r
484         }\r
485         #endregion\r
486 \r
487         // CSound は、サウンドデバイスが変更されたときも、インスタンスを再作成することなく、新しいデバイスで作り直せる必要がある。\r
488         // そのため、デバイスごとに別のクラスに分割するのではなく、1つのクラスに集約するものとする。\r
489 \r
490         public class CSound : IDisposable, ICloneable\r
491         {\r
492                 #region [ DTXMania用拡張 ]\r
493                 public int n総演奏時間ms\r
494                 {\r
495                         get;\r
496                         private set;\r
497                 }\r
498                 public int nサウンドバッファサイズ           // 取りあえず0固定★★★★★★★★★★★★★★★★★★★★\r
499                 {\r
500                         get { return 0; }\r
501                 }\r
502                 public bool bストリーム再生する                        // 取りあえずfalse固定★★★★★★★★★★★★★★★★★★★★\r
503                                                                                                 // trueにすると同一チップ音の多重再生で問題が出る(4POLY音源として動かない)\r
504                 {\r
505                         get { return false; }\r
506                 }\r
507                 public double db周波数倍率\r
508                 {\r
509                         get\r
510                         {\r
511                                 return _db周波数倍率;\r
512                         }\r
513                         set\r
514                         {\r
515                                 if ( _db周波数倍率 != value )\r
516                                 {\r
517                                         _db周波数倍率 = value;\r
518                                         if ( bBASSサウンドである )\r
519                                         {\r
520                                                 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );\r
521                                         }\r
522                                         else\r
523                                         {\r
524 //                                              if ( b再生中 )       // #30838 2012.2.24 yyagi (delete b再生中)\r
525 //                                              {\r
526                                                         this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );\r
527 //                                              }\r
528                                         }\r
529                                 }\r
530                         }\r
531                 }\r
532                 public double db再生速度\r
533                 {\r
534                         get\r
535                         {\r
536                                 return _db再生速度;\r
537                         }\r
538                         set\r
539                         {\r
540                                 if ( _db再生速度 != value )\r
541                                 {\r
542                                         _db再生速度 = value;\r
543                                         bIs1倍速再生 = ( _db再生速度 == 1.000f );\r
544                                         if ( bBASSサウンドである )\r
545                                         {\r
546                                                 if ( _hTempoStream != 0 && !this.bIs1倍速再生 )     // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する\r
547                                         {\r
548                                                         this.hBassStream = _hTempoStream;\r
549                                         }\r
550                                         else\r
551                                                 {\r
552                                                         this.hBassStream = _hBassStream;\r
553                                         }\r
554 \r
555                                                 if ( CSound管理.bIsTimeStretch )\r
556                                                 {\r
557                                                         Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_TEMPO, (float) ( db再生速度 * 100 - 100 ) );\r
558                                                         //double seconds = Bass.BASS_ChannelBytes2Seconds( this.hTempoStream, nBytes );\r
559                                                         //this.n総演奏時間ms = (int) ( seconds * 1000 );\r
560                                                 }\r
561                                                 else\r
562                                                 {\r
563                                                         Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );\r
564                                                 }\r
565                                         }\r
566                                         else\r
567                                         {\r
568 //                                              if ( b再生中 )       // #30838 2012.2.24 yyagi (delete b再生中)\r
569 //                                              {\r
570                                                         this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );\r
571 //                                              }\r
572                                         }\r
573                                 }\r
574                         }\r
575                 }\r
576                 #endregion\r
577 \r
578                 public bool b演奏終了後も再生が続くチップである = false;       // これがtrueなら、本サウンドの再生終了のコールバック時に自動でミキサーから削除する\r
579 \r
580                 //private STREAMPROC _cbStreamXA;               // make it global, so that the GC can not remove it\r
581                 private SYNCPROC _cbEndofStream;        // ストリームの終端まで再生されたときに呼び出されるコールバック\r
582 //              private WaitCallback _cbRemoveMixerChannel;\r
583 \r
584                 /// <summary>\r
585                 /// <para>0:最小~100:原音</para>\r
586                 /// </summary>\r
587                 public int n音量\r
588                 {\r
589                         get\r
590                         {\r
591                                 if( this.bBASSサウンドである )\r
592                                 {\r
593                                         float f音量 = 0.0f;\r
594                                         if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 ) )\r
595                                         //if ( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, ref f音量 ) == -1 )\r
596                                             return 100;\r
597                                         return (int) ( f音量 * 100 );\r
598                                 }\r
599                                 else if( this.bDirectSoundである )\r
600                                 {\r
601                                         return this._n音量;\r
602                                 }\r
603                                 return -1;\r
604                         }\r
605                         set\r
606                         {\r
607                                 if( this.bBASSサウンドである )\r
608                                 {\r
609                                         float f音量 = Math.Min( Math.Max( value, 0 ), 100 ) / 100.0f; // 0~100 → 0.0~1.0\r
610                                         //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, f音量 ) };\r
611                                         //BassMix.BASS_Mixer_ChannelSetEnvelope( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );\r
612                                         Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_VOL, f音量 );\r
613 \r
614                                 }\r
615                                 else if( this.bDirectSoundである )\r
616                                 {\r
617                                         this._n音量 = value;\r
618 \r
619                                         if( this._n音量 == 0 )\r
620                                         {\r
621                                                 this._n音量db = -10000;\r
622                                         }\r
623                                         else\r
624                                         {\r
625                                                 this._n音量db = (int) ( ( 20.0 * Math.Log10( ( (double) this._n音量 ) / 100.0 ) ) * 100.0 );\r
626                                         }\r
627 \r
628                                         this.Buffer.Volume = this._n音量db;\r
629                                 }\r
630                         }\r
631                 }\r
632 \r
633                 /// <summary>\r
634                 /// <para>左:-100~中央:0~100:右。set のみ。</para>\r
635                 /// </summary>\r
636                 public int n位置\r
637                 {\r
638                         get\r
639                         {\r
640                                 if( this.bBASSサウンドである )\r
641                                 {\r
642                                         float f位置 = 0.0f;\r
643                                         if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_PAN, ref f位置 ) )\r
644                                                 //if( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_PAN, ref f位置 ) == -1 )\r
645                                                 return 0;\r
646                                         return (int) ( f位置 * 100 );\r
647                                 }\r
648                                 else if( this.bDirectSoundである )\r
649                                 {\r
650                                         return this._n位置;\r
651                                 }\r
652                                 return -9999;\r
653                         }\r
654                         set\r
655                         {\r
656                                 if( this.bBASSサウンドである )\r
657                                 {\r
658                                         float f位置 = Math.Min( Math.Max( value, -100 ), 100 ) / 100.0f;      // -100~100 → -1.0~1.0\r
659                                         //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, f位置 ) };\r
660                                         //BassMix.BASS_Mixer_ChannelSetEnvelope( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_PAN, nodes );\r
661                                         Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_PAN, f位置 );\r
662                                 }\r
663                                 else if( this.bDirectSoundである )\r
664                                 {\r
665                                         this._n位置 = Math.Min( Math.Max( -100, value ), 100 );               // -100~100\r
666 \r
667                                         if( this._n位置 == 0 )\r
668                                         {\r
669                                                 this._n位置db = 0;\r
670                                         }\r
671                                         else if( this._n位置 == -100 )\r
672                                         {\r
673                                                 this._n位置db = -10000;\r
674                                         }\r
675                                         else if( this._n位置 == 100 )\r
676                                         {\r
677                                                 this._n位置db = 10000;\r
678                                         }\r
679                                         else if( this._n位置 < 0 )\r
680                                         {\r
681                                                 this._n位置db = (int) ( ( 20.0 * Math.Log10( ( (double) ( this._n位置 + 100 ) ) / 100.0 ) ) * 100.0 );\r
682                                         }\r
683                                         else\r
684                                         {\r
685                                                 this._n位置db = (int) ( ( -20.0 * Math.Log10( ( (double) ( 100 - this._n位置 ) ) / 100.0 ) ) * 100.0 );\r
686                                         }\r
687 \r
688                                         this.Buffer.Pan = this._n位置db;\r
689                                 }\r
690                         }\r
691                 }\r
692 \r
693                 /// <summary>\r
694                 /// <para>DirectSoundのセカンダリバッファ。</para>\r
695                 /// </summary>\r
696                 //public SecondarySoundBuffer DirectSoundBuffer\r
697                 public SoundBuffer DirectSoundBuffer\r
698                 {\r
699                         get { return this.Buffer; }\r
700                 }\r
701 \r
702                 /// <summary>\r
703                 /// <para>DirectSoundのセカンダリバッファ作成時のフラグ。</para>\r
704                 /// </summary>\r
705                 public BufferFlags DirectSoundBufferFlags\r
706                 {\r
707                         get;\r
708                         protected set;\r
709                 }\r
710 \r
711                 /// <summary>\r
712                 /// <para>全インスタンスリスト。</para>\r
713                 /// <para>~を作成する() で追加され、t解放する() or Dispose() で解放される。</para>\r
714                 /// </summary>\r
715                 public static List<CSound> listインスタンス = new List<CSound>();\r
716 \r
717                 public static void ShowAllCSoundFiles()\r
718                 {\r
719                         int i = 0;\r
720                         foreach ( CSound cs in listインスタンス )\r
721                         {\r
722                                 Debug.WriteLine( i++.ToString( "d3" ) + ": " + Path.GetFileName( cs.strファイル名 ) );\r
723                         }\r
724                 }\r
725 \r
726                 public CSound()\r
727                 {\r
728                         this.n音量 = 100;\r
729                         this.n位置 = 0;\r
730                         this._db周波数倍率 = 1.0;\r
731                         this._db再生速度 = 1.0;\r
732                         this.DirectSoundBufferFlags = CSoundDeviceDirectSound.DefaultFlags;\r
733 //                      this._cbRemoveMixerChannel = new WaitCallback( RemoveMixerChannelLater );\r
734                         this._hBassStream = -1;\r
735                         this._hTempoStream = 0;\r
736                 }\r
737 \r
738                 public object Clone()\r
739                 {\r
740                         if ( !bDirectSoundである )\r
741                         {\r
742                                 throw new NotImplementedException();\r
743                         }\r
744                         CSound clone = (CSound) MemberwiseClone();      // これだけだとCY連打が途切れる&タイトルに戻る際にNullRef例外発生\r
745                         this.DirectSound.DuplicateSoundBuffer( this.Buffer, out clone.Buffer );\r
746 \r
747                         // CSound.listインスタンス.Add( this );                   // インスタンスリストに登録。\r
748                         // 本来これを加えるべきだが、Add後Removeできなくなっている。Clone()の仕方の問題であろう。\r
749 \r
750                         return clone;\r
751                 }\r
752                 public void tASIOサウンドを作成する( string strファイル名, int hMixer )\r
753                 {\r
754                         this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE );\r
755                         this.eデバイス種別 = ESoundDeviceType.ASIO;               // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
756                 }\r
757                 public void tASIOサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer )\r
758                 {\r
759                         this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE );\r
760                         this.eデバイス種別 = ESoundDeviceType.ASIO;               // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
761                 }\r
762                 public void tWASAPIサウンドを作成する( string strファイル名, int hMixer, ESoundDeviceType eデバイス種別 )\r
763                 {\r
764                         this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );\r
765                         this.eデバイス種別 = eデバイス種別;         // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
766                 }\r
767                 public void tWASAPIサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, ESoundDeviceType eデバイス種別 )\r
768                 {\r
769                         this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );\r
770                         this.eデバイス種別 = eデバイス種別;         // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
771                 }\r
772                 public void tDirectSoundサウンドを作成する( string strファイル名, DirectSound DirectSound )\r
773                 {\r
774                         this.e作成方法 = E作成方法.ファイルから;\r
775                         this.strファイル名 = strファイル名;\r
776                         if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 ||\r
777                                  String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 ||\r
778                                  String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 ) // caselessで文字列比較\r
779                         {\r
780                                 tDirectSoundサウンドを作成するXaOggMp3( strファイル名, DirectSound );\r
781                                 return;\r
782                         }\r
783 \r
784                         // すべてのファイルを DirectShow でデコードすると時間がかかるので、ファイルが WAV かつ PCM フォーマットでない場合のみ DirectShow でデコードする。\r
785 \r
786                         byte[] byArrWAVファイルイメージ = null;\r
787                         bool bファイルがWAVかつPCMフォーマットである = true;\r
788 \r
789                         {\r
790                                 #region [ ファイルがWAVかつPCMフォーマットか否か調べる。]\r
791                                 //-----------------\r
792                                 try\r
793                                 {\r
794                                         using ( var ws = new WaveStream( strファイル名 ) )\r
795                                         {\r
796                                                 if ( ws.Format.FormatTag != WaveFormatTag.Pcm )\r
797                                                         bファイルがWAVかつPCMフォーマットである = false;\r
798                                         }\r
799                                 }\r
800                                 catch\r
801                                 {\r
802                                         bファイルがWAVかつPCMフォーマットである = false;\r
803                                 }\r
804                                 //-----------------\r
805                                 #endregion\r
806 \r
807                                 if ( bファイルがWAVかつPCMフォーマットである )\r
808                                 {\r
809                                         #region [ ファイルを読み込んで byArrWAVファイルイメージへ格納。]\r
810                                         //-----------------\r
811                                         var fs = File.Open( strファイル名, FileMode.Open, FileAccess.Read );\r
812                                         var br = new BinaryReader( fs );\r
813 \r
814                                         byArrWAVファイルイメージ = new byte[ fs.Length ];\r
815                                         br.Read( byArrWAVファイルイメージ, 0, (int) fs.Length );\r
816 \r
817                                         br.Close();\r
818                                         fs.Close();\r
819                                         //-----------------\r
820                                         #endregion\r
821                                 }\r
822                                 else\r
823                                 {\r
824                                         #region [ DirectShow でデコード変換し、 byArrWAVファイルイメージへ格納。]\r
825                                         //-----------------\r
826                                         CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );\r
827                                         //-----------------\r
828                                         #endregion\r
829                                 }\r
830                         }\r
831 \r
832                         // あとはあちらで。\r
833 \r
834                         this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound );\r
835                 }\r
836                 public void tDirectSoundサウンドを作成するXaOggMp3( string strファイル名, DirectSound DirectSound )\r
837                 {\r
838                         this.e作成方法 = E作成方法.ファイルから;\r
839                         this.strファイル名 = strファイル名;\r
840 \r
841 \r
842                         WaveFormat wfx = new WaveFormat();\r
843                         int nPCMデータの先頭インデックス = 0;\r
844 //                      int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 );     // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );\r
845 \r
846                         int nPCMサイズbyte;\r
847                         CWin32.WAVEFORMATEX cw32wfx;\r
848                         tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,\r
849                         out nPCMデータの先頭インデックス, out nPCMサイズbyte, out cw32wfx, false );\r
850 \r
851                         wfx.AverageBytesPerSecond = (int) cw32wfx.nAvgBytesPerSec;\r
852                         wfx.BitsPerSample = (short) cw32wfx.wBitsPerSample;\r
853                         wfx.BlockAlignment = (short) cw32wfx.nBlockAlign;\r
854                         wfx.Channels = (short) cw32wfx.nChannels;\r
855                         wfx.FormatTag = WaveFormatTag.Pcm;      // xa.waveformatex.wFormatTag;\r
856                         wfx.SamplesPerSecond = (int) cw32wfx.nSamplesPerSec;\r
857 \r
858                         // セカンダリバッファを作成し、PCMデータを書き込む。\r
859                         tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み\r
860                                 ( ref this.byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags, wfx,\r
861                                   nPCMサイズbyte, nPCMデータの先頭インデックス );\r
862                 }\r
863 \r
864                 public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound )\r
865                 {\r
866                         this.tDirectSoundサウンドを作成する(  byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags );\r
867                 }\r
868                 public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags )\r
869                 {\r
870                         if( this.e作成方法 == E作成方法.Unknown )\r
871                                 this.e作成方法 = E作成方法.WAVファイルイメージから;\r
872 \r
873                         WaveFormat wfx = null;\r
874                         int nPCMデータの先頭インデックス = -1;\r
875                         int nPCMサイズbyte = -1;\r
876         \r
877                         #region [ byArrWAVファイルイメージ[] から上記3つのデータを取得。]\r
878                         //-----------------\r
879                         var ms = new MemoryStream( byArrWAVファイルイメージ );\r
880                         var br = new BinaryReader( ms );\r
881 \r
882                         try\r
883                         {\r
884                                 // 'RIFF'+RIFFデータサイズ\r
885 \r
886                                 if( br.ReadUInt32() != 0x46464952 )\r
887                                         throw new InvalidDataException( "RIFFファイルではありません。" );\r
888                                 br.ReadInt32();\r
889 \r
890                                 // 'WAVE'\r
891                                 if( br.ReadUInt32() != 0x45564157 )\r
892                                         throw new InvalidDataException( "WAVEファイルではありません。" );\r
893 \r
894                                 // チャンク\r
895                                 while( ( ms.Position + 8 ) < ms.Length )        // +8 は、チャンク名+チャンクサイズ。残り8バイト未満ならループ終了。\r
896                                 {\r
897                                         uint chunkName = br.ReadUInt32();\r
898 \r
899                                         // 'fmt '\r
900                                         if( chunkName == 0x20746D66 )\r
901                                         {\r
902                                                 long chunkSize = (long) br.ReadUInt32();\r
903 \r
904                                                 var tag = (WaveFormatTag) br.ReadUInt16();\r
905 \r
906                                                 if( tag == WaveFormatTag.Pcm ) wfx = new WaveFormat();\r
907                                                 else if( tag == WaveFormatTag.Extensible ) wfx = new SlimDX.Multimedia.WaveFormatExtensible();  // このクラスは WaveFormat を継承している。\r
908                                                 else\r
909                                                         throw new InvalidDataException( string.Format( "未対応のWAVEフォーマットタグです。(Tag:{0})", tag.ToString() ) );\r
910 \r
911                                                 wfx.FormatTag = tag;\r
912                                                 wfx.Channels = br.ReadInt16();\r
913                                                 wfx.SamplesPerSecond = br.ReadInt32();\r
914                                                 wfx.AverageBytesPerSecond = br.ReadInt32();\r
915                                                 wfx.BlockAlignment = br.ReadInt16();\r
916                                                 wfx.BitsPerSample = br.ReadInt16();\r
917 \r
918                                                 long nフォーマットサイズbyte = 16;\r
919 \r
920                                                 if( wfx.FormatTag == WaveFormatTag.Extensible )\r
921                                                 {\r
922                                                         br.ReadUInt16();        // 拡張領域サイズbyte\r
923                                                         var wfxEx = (SlimDX.Multimedia.WaveFormatExtensible) wfx;\r
924                                                         wfxEx.ValidBitsPerSample = br.ReadInt16();\r
925                                                         wfxEx.ChannelMask = (Speakers) br.ReadInt32();\r
926                                                         wfxEx.SubFormat = new Guid( br.ReadBytes( 16 ) );       // GUID は 16byte (128bit)\r
927 \r
928                                                         nフォーマットサイズbyte += 24;\r
929                                                 }\r
930 \r
931                                                 ms.Seek( chunkSize - nフォーマットサイズbyte, SeekOrigin.Current );\r
932                                                 continue;\r
933                                         }\r
934 \r
935                                         // 'data'\r
936                                         else if( chunkName == 0x61746164 )\r
937                                         {\r
938                                                 nPCMサイズbyte = br.ReadInt32();\r
939                                                 nPCMデータの先頭インデックス = (int) ms.Position;\r
940 \r
941                                                 ms.Seek( nPCMサイズbyte, SeekOrigin.Current );\r
942                                                 continue;\r
943                                         }\r
944 \r
945                                         // その他\r
946                                         else\r
947                                         {\r
948                                                 long chunkSize = (long) br.ReadUInt32();\r
949                                                 ms.Seek( chunkSize, SeekOrigin.Current );\r
950                                                 continue;\r
951                                         }\r
952                                 }\r
953 \r
954                                 if( wfx == null )\r
955                                         throw new InvalidDataException( "fmt チャンクが存在しません。不正なサウンドデータです。" );\r
956                                 if( nPCMサイズbyte < 0 )\r
957                                         throw new InvalidDataException( "data チャンクが存在しません。不正なサウンドデータです。" );\r
958                         }\r
959                         finally\r
960                         {\r
961                                 ms.Close();\r
962                                 br.Close();\r
963                         }\r
964                         //-----------------\r
965                         #endregion\r
966 \r
967 \r
968                         // セカンダリバッファを作成し、PCMデータを書き込む。\r
969                         tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み(\r
970                                 ref byArrWAVファイルイメージ, DirectSound, flags, wfx, nPCMサイズbyte, nPCMデータの先頭インデックス );\r
971                 }\r
972 \r
973                 private void tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み\r
974                         ( ref byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags, WaveFormat wfx,\r
975                         int nPCMサイズbyte, int nPCMデータの先頭インデックス )\r
976                 {\r
977                         // セカンダリバッファを作成し、PCMデータを書き込む。\r
978 \r
979                         this.Buffer = new SecondarySoundBuffer( DirectSound, new SoundBufferDescription()\r
980                         {\r
981                                 Format = ( wfx.FormatTag == WaveFormatTag.Pcm ) ? wfx : (SlimDX.Multimedia.WaveFormatExtensible) wfx,\r
982                                 Flags = flags,\r
983                                 SizeInBytes = nPCMサイズbyte,\r
984                         } );\r
985                         this.Buffer.Write( byArrWAVファイルイメージ, nPCMデータの先頭インデックス, nPCMサイズbyte, 0, LockFlags.None );\r
986 \r
987                         // 作成完了。\r
988 \r
989                         this.eデバイス種別 = ESoundDeviceType.DirectSound;\r
990                         this.DirectSoundBufferFlags = flags;\r
991                         this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;\r
992                         this.DirectSound = DirectSound;\r
993 \r
994                         // DTXMania用に追加\r
995                         this.nオリジナルの周波数 = wfx.SamplesPerSecond;\r
996                         n総演奏時間ms = (int) ( ( (double) nPCMサイズbyte ) / ( this.Buffer.Format.AverageBytesPerSecond * 0.001 ) );\r
997 \r
998 \r
999                         // インスタンスリストに登録。\r
1000 \r
1001                         CSound.listインスタンス.Add( this );\r
1002                 }\r
1003 \r
1004                 #region [ DTXMania用の変換 ]\r
1005 \r
1006                 public void tサウンドを破棄する( CSound cs )\r
1007                 {\r
1008                         cs.t解放する();\r
1009                 }\r
1010                 public void t再生を開始する()\r
1011                 {\r
1012                         t再生位置を先頭に戻す();\r
1013                         tサウンドを再生する();\r
1014                 }\r
1015                 public void t再生を開始する( bool bループする )\r
1016                 {\r
1017                         if ( bBASSサウンドである )\r
1018                         {\r
1019                                 if ( bループする )\r
1020                                 {\r
1021                                         Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_SAMPLE_LOOP, BASSFlag.BASS_SAMPLE_LOOP );\r
1022                                 }\r
1023                                 else\r
1024                                 {\r
1025                                         Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_DEFAULT, BASSFlag.BASS_DEFAULT );\r
1026                                 }\r
1027                         }\r
1028                         t再生位置を先頭に戻す();\r
1029                         tサウンドを再生する( bループする );\r
1030                 }\r
1031                 public void t再生を停止する()\r
1032                 {\r
1033                         tサウンドを停止する();\r
1034                         t再生位置を先頭に戻す();\r
1035                 }\r
1036                 public void t再生を一時停止する()\r
1037                 {\r
1038                         tサウンドを停止する(true);\r
1039                         this.n一時停止回数++;\r
1040                 }\r
1041                 public void t再生を再開する( long t )    // ★★★★★★★★★★★★★★★★★★★★★★★★★★★★\r
1042                 {\r
1043                         Debug.WriteLine( "t再生を再開する(long " + t + ")" );\r
1044                         t再生位置を変更する( t );\r
1045                         tサウンドを再生する();\r
1046                         this.n一時停止回数--;\r
1047                 }\r
1048                 public bool b一時停止中\r
1049                 {\r
1050                         get\r
1051                         {\r
1052                                 if ( this.bBASSサウンドである )\r
1053                                 {\r
1054                                         bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PAUSED ) &\r
1055                                                                 ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) > 0 );\r
1056                                         return ret;\r
1057                                 }\r
1058                                 else\r
1059                                 {\r
1060                                         return ( this.n一時停止回数 > 0 );\r
1061                                 }\r
1062                         }\r
1063                 }\r
1064                 public bool b再生中\r
1065                 {\r
1066                         get\r
1067                         {\r
1068                                 if ( this.eデバイス種別 == ESoundDeviceType.DirectSound )\r
1069                                 {\r
1070                                         return ( ( this.Buffer.Status & BufferStatus.Playing ) != BufferStatus.None );\r
1071                                 }\r
1072                                 else\r
1073                                 {\r
1074                                         // 基本的にはBASS_ACTIVE_PLAYINGなら再生中だが、最後まで再生しきったchannelも\r
1075                                         // BASS_ACTIVE_PLAYINGのままになっているので、小細工が必要。\r
1076                                         bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PLAYING );\r
1077                                         if ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) >= nBytes )\r
1078                                         {\r
1079                                                 ret = false;\r
1080                                         }\r
1081                                         return ret;\r
1082                                 }\r
1083                         }\r
1084                 }\r
1085                 //public lint t時刻から位置を返す( long t )\r
1086                 //{\r
1087                 //    double num = ( n時刻 * this.db再生速度 ) * this.db周波数倍率;\r
1088                 //    return (int) ( ( num * 0.01 ) * this.nSamplesPerSecond );\r
1089                 //}\r
1090                 #endregion\r
1091 \r
1092 \r
1093                 public void t解放する()\r
1094                 {\r
1095                         t解放する( false );\r
1096                 }\r
1097 \r
1098                 public void t解放する( bool _bインスタンス削除 )\r
1099                 {\r
1100                         if ( this.bBASSサウンドである )          // stream数の削減用\r
1101                         {\r
1102                                 tBASSサウンドをミキサーから削除する();\r
1103                                 _cbEndofStream = null;\r
1104                                 //_cbStreamXA = null;\r
1105                                 CSound管理.nStreams--;\r
1106                         }\r
1107                         bool bManagedも解放する = true;\r
1108                         bool bインスタンス削除 = _bインスタンス削除;    // CSoundの再初期化時は、インスタンスは存続する。\r
1109                         this.Dispose( bManagedも解放する, bインスタンス削除 );\r
1110 //Debug.WriteLine( "Disposed: " + _bインスタンス削除 + " : " + Path.GetFileName( this.strファイル名 ) );\r
1111                 }\r
1112                 public void tサウンドを再生する()\r
1113                 {\r
1114                         tサウンドを再生する( false );\r
1115                 }\r
1116                 public void tサウンドを再生する( bool bループする )\r
1117                 {\r
1118                         if ( this.bBASSサウンドである )                  // BASSサウンド時のループ処理は、t再生を開始する()側に実装。ここでは「bループする」は未使用。\r
1119                         {\r
1120 //Debug.WriteLine( "再生中?: " +  System.IO.Path.GetFileName(this.strファイル名) + " status=" + BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) + " current=" + BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) + " nBytes=" + nBytes );\r
1121                                 bool b = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );\r
1122                                 if ( !b )\r
1123                                 {\r
1124 //Debug.WriteLine( "再生しようとしたが、Mixerに登録されていなかった: " + Path.GetFileName( this.strファイル名 ) + ", stream#=" + this.hBassStream + ", ErrCode=" + Bass.BASS_ErrorGetCode() );\r
1125 \r
1126                                         bool bb = tBASSサウンドをミキサーに追加する();\r
1127                                         if ( !bb )\r
1128                                         {\r
1129 Debug.WriteLine( "Mixerへの登録に失敗: " + Path.GetFileName( this.strファイル名 ) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );\r
1130                                         }\r
1131                                         else\r
1132                                         {\r
1133 //Debug.WriteLine( "Mixerへの登録に成功: " + Path.GetFileName( this.strファイル名 ) + ": " + Bass.BASS_ErrorGetCode() );\r
1134                                         }\r
1135                                         //this.t再生位置を先頭に戻す();\r
1136 \r
1137                                         bool bbb = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );\r
1138                                         if (!bbb)\r
1139                                         {\r
1140 Debug.WriteLine("更に再生に失敗: " + Path.GetFileName(this.strファイル名) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );\r
1141                                         }\r
1142                                         else\r
1143                                         {\r
1144 //                                              Debug.WriteLine("再生成功(ミキサー追加後)                       : " + Path.GetFileName(this.strファイル名));\r
1145                                         }\r
1146                                 }\r
1147                                 else\r
1148                                 {\r
1149 //Debug.WriteLine( "再生成功: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" );\r
1150                                 }\r
1151                         }\r
1152                         else if( this.bDirectSoundである )\r
1153                         {\r
1154                                 PlayFlags pf = ( bループする ) ? PlayFlags.Looping : PlayFlags.None;\r
1155                                 this.Buffer.Play( 0, pf );\r
1156                         }\r
1157                 }\r
1158                 public void tサウンドを先頭から再生する()\r
1159                 {\r
1160                         this.t再生位置を先頭に戻す();\r
1161                         this.tサウンドを再生する();\r
1162                 }\r
1163                 public void tサウンドを停止してMixerからも削除する()\r
1164                 {\r
1165                         tサウンドを停止する( false );\r
1166                         if ( bBASSサウンドである )\r
1167                         {\r
1168                                 tBASSサウンドをミキサーから削除する();\r
1169                         }\r
1170                 }\r
1171                 public void tサウンドを停止する()\r
1172                 {\r
1173                         tサウンドを停止する( false );\r
1174                 }\r
1175                 public void tサウンドを停止する( bool pause )\r
1176                 {\r
1177                         if( this.bBASSサウンドである )\r
1178                         {\r
1179 //Debug.WriteLine( "停止: " + System.IO.Path.GetFileName( this.strファイル名 ) + " status=" + BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) + " current=" + BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) + " nBytes=" + nBytes );\r
1180                                 BassMix.BASS_Mixer_ChannelPause( this.hBassStream );\r
1181                                 if ( !pause )\r
1182                                 {\r
1183                         //              tBASSサウンドをミキサーから削除する();           // PAUSEと再生停止を区別できるようにすること!!\r
1184                                 }\r
1185                         }\r
1186                         else if( this.bDirectSoundである )\r
1187                         {\r
1188                                 try\r
1189                                 {\r
1190                                         this.Buffer.Stop();\r
1191                                 }\r
1192                                 catch ( Exception )\r
1193                                 {\r
1194                                         // WASAPI/ASIOとDirectSoundを同時使用すると、Bufferがlostしてここで例外発生する。→ catchして無視する。\r
1195                                         // DTXCからDTXManiaを呼び出すと、DTXC終了時にこの現象が発生する。\r
1196                                 }\r
1197                         }\r
1198                         this.n一時停止回数 = 0;\r
1199                 }\r
1200                 \r
1201                 public void t再生位置を先頭に戻す()\r
1202                 {\r
1203                         if( this.bBASSサウンドである )\r
1204                         {\r
1205                                 BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, 0 );\r
1206                                 //pos = 0;\r
1207                         }\r
1208                         else if( this.bDirectSoundである )\r
1209                         {\r
1210                                 this.Buffer.CurrentPlayPosition = 0;\r
1211                         }\r
1212                 }\r
1213                 public void t再生位置を変更する( long n位置ms )\r
1214                 {\r
1215                         if( this.bBASSサウンドである )\r
1216                         {\r
1217                                 bool b = true;\r
1218                                 try\r
1219                                 {\r
1220                                         b = BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, Bass.BASS_ChannelSeconds2Bytes( this.hBassStream, n位置ms * this.db周波数倍率 * this.db再生速度 / 1000.0 ), BASSMode.BASS_POS_BYTES );\r
1221                                 }\r
1222                                 catch( Exception e )\r
1223                                 {\r
1224                                         Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + e.ToString() + ": " + n位置ms + "ms" );\r
1225                                 }\r
1226                                 finally\r
1227                                 {\r
1228                                         if ( !b )\r
1229                                         {\r
1230                                                 BASSError be = Bass.BASS_ErrorGetCode();\r
1231                                                 Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + be.ToString() + ": " + n位置ms + "MS" );\r
1232                                         }\r
1233                                 }\r
1234                                 //if ( this.n総演奏時間ms > 5000 )\r
1235                                 //{\r
1236                                 //    Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seeked to " + n位置ms + "ms = " + Bass.BASS_ChannelSeconds2Bytes( this.hBassStream, n位置ms * this.db周波数倍率 * this.db再生速度 / 1000.0 ) );\r
1237                                 //}\r
1238                         }\r
1239                         else if( this.bDirectSoundである )\r
1240                         {\r
1241                                 int n位置sample = (int) ( this.Buffer.Format.SamplesPerSecond * n位置ms * 0.001 * _db周波数倍率 * _db再生速度 );   // #30839 2013.2.24 yyagi; add _db周波数倍率 and _db再生速度\r
1242                                 try\r
1243                                 {\r
1244                                         this.Buffer.CurrentPlayPosition = n位置sample * this.Buffer.Format.BlockAlignment;\r
1245                                 }\r
1246                                 catch ( DirectSoundException e )\r
1247                                 {\r
1248                                         Trace.TraceError( "{0}: Seek error: {1}", Path.GetFileName( this.strファイル名 ), n位置ms, e.Message );\r
1249                                 }\r
1250                                 //if ( this.n総演奏時間ms > 5000 )\r
1251                                 //{\r
1252                                 //    Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seeked to " + n位置ms + "ms = " + n位置sample );\r
1253                                 //}\r
1254                         }\r
1255                 }\r
1256                 /// <summary>\r
1257                 /// デバッグ用\r
1258                 /// </summary>\r
1259                 /// <param name="n位置byte"></param>\r
1260                 /// <param name="db位置ms"></param>\r
1261                 public void t再生位置を取得する( out long n位置byte, out double db位置ms )\r
1262                 {\r
1263                         if ( this.bBASSサウンドである )\r
1264                         {\r
1265                                 n位置byte = BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream );\r
1266                                 db位置ms = Bass.BASS_ChannelBytes2Seconds( this.hBassStream, n位置byte );\r
1267                         }\r
1268                         else if ( this.bDirectSoundである )\r
1269                         {\r
1270                                 n位置byte = this.Buffer.CurrentPlayPosition;\r
1271                                 db位置ms = n位置byte / this.Buffer.Format.SamplesPerSecond / 0.001 / _db周波数倍率 / _db再生速度;\r
1272                         }\r
1273                         else\r
1274                         {\r
1275                                 n位置byte = 0;\r
1276                                 db位置ms = 0.0;\r
1277                         }\r
1278                 }\r
1279 \r
1280 \r
1281                 public static void tすべてのサウンドを初期状態に戻す()\r
1282                 {\r
1283                         foreach ( var sound in CSound.listインスタンス )\r
1284                         {\r
1285                                 sound.t解放する( false );\r
1286                         }\r
1287                 }\r
1288                 public static void tすべてのサウンドを再構築する( ISoundDevice device )\r
1289                 {\r
1290                         if( CSound.listインスタンス.Count == 0 )\r
1291                                 return;\r
1292 \r
1293 \r
1294                         // サウンドを再生する際にインスタンスリストも更新されるので、配列にコピーを取っておき、リストはクリアする。\r
1295 \r
1296                         var sounds = CSound.listインスタンス.ToArray();\r
1297                         CSound.listインスタンス.Clear();\r
1298                         \r
1299 \r
1300                         // 配列に基づいて個々のサウンドを作成する。\r
1301 \r
1302                         for( int i = 0; i < sounds.Length; i++ )\r
1303                         {\r
1304                                 switch( sounds[ i ].e作成方法 )\r
1305                                 {\r
1306                                         #region [ ファイルから ]\r
1307                                         case E作成方法.ファイルから:\r
1308                                                 string strファイル名 = sounds[ i ].strファイル名;\r
1309                                                 sounds[ i ].Dispose( true, false );\r
1310                                                 device.tサウンドを作成する( strファイル名, ref sounds[ i ] );\r
1311                                                 break;\r
1312                                         #endregion\r
1313                                         #region [ WAVファイルイメージから ]\r
1314                                         case E作成方法.WAVファイルイメージから:\r
1315                                                 if( sounds[ i ].bBASSサウンドである )\r
1316                                                 {\r
1317                                                         byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;\r
1318                                                         sounds[ i ].Dispose( true, false );\r
1319                                                         device.tサウンドを作成する( byArrWaveファイルイメージ, ref sounds[ i ] );\r
1320                                                 }\r
1321                                                 else if( sounds[ i ].bDirectSoundである )\r
1322                                                 {\r
1323                                                         byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;\r
1324                                                         var flags = sounds[ i ].DirectSoundBufferFlags;\r
1325                                                         sounds[ i ].Dispose( true, false );\r
1326                                                         ( (CSoundDeviceDirectSound) device ).tサウンドを作成する( byArrWaveファイルイメージ, flags, ref sounds[ i ] );\r
1327                                                 }\r
1328                                                 break;\r
1329                                         #endregion\r
1330                                 }\r
1331                         }\r
1332                 }\r
1333 \r
1334                 #region [ Dispose-Finalizeパターン実装 ]\r
1335                 //-----------------\r
1336                 public void Dispose()\r
1337                 {\r
1338                         this.Dispose( true, true );\r
1339                         GC.SuppressFinalize( this );\r
1340                 }\r
1341                 protected void Dispose( bool bManagedも解放する, bool bインスタンス削除 )\r
1342                 {\r
1343                         if( this.bBASSサウンドである )\r
1344                         {\r
1345                                 #region [ ASIO, WASAPI の解放 ]\r
1346                                 //-----------------\r
1347                                 if ( _hTempoStream != 0 )\r
1348                                 {\r
1349                                         BassMix.BASS_Mixer_ChannelRemove( this._hTempoStream );\r
1350                                         Bass.BASS_StreamFree( this._hTempoStream );\r
1351                                 }\r
1352                                 BassMix.BASS_Mixer_ChannelRemove( this._hBassStream );\r
1353                                 Bass.BASS_StreamFree( this._hBassStream );\r
1354                                 this.hBassStream = -1;\r
1355                                 this._hBassStream = -1;\r
1356                                 this._hTempoStream = 0;\r
1357                                 //-----------------\r
1358                                 #endregion\r
1359                         }\r
1360 \r
1361                         if( bManagedも解放する )\r
1362                         {\r
1363                                 //int freeIndex = -1;\r
1364 \r
1365                                 //if ( CSound.listインスタンス != null )\r
1366                                 //{\r
1367                                 //    freeIndex = CSound.listインスタンス.IndexOf( this );\r
1368                                 //    if ( freeIndex == -1 )\r
1369                                 //    {\r
1370                                 //        Debug.WriteLine( "ERR: freeIndex==-1 : Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );\r
1371                                 //    }\r
1372                                 //}\r
1373 \r
1374                                 if( this.eデバイス種別 == ESoundDeviceType.DirectSound )\r
1375                                 {\r
1376                                         #region [ DirectSound の解放 ]\r
1377                                         //-----------------\r
1378                                         if( this.Buffer != null )\r
1379                                         {\r
1380                                                 try\r
1381                                                 {\r
1382                                                         this.Buffer.Stop();\r
1383                                                 }\r
1384                                                 catch\r
1385                                                 {\r
1386                                                         // 演奏終了後、長時間解放しないでいると、たまに AccessViolationException が発生することがある。\r
1387                                                 }\r
1388                                                 C共通.tDisposeする( ref this.Buffer );\r
1389                                         }\r
1390                                         //-----------------\r
1391                                         #endregion\r
1392                                 }\r
1393 \r
1394                                 if( this.e作成方法 == E作成方法.WAVファイルイメージから &&\r
1395                                         this.eデバイス種別 != ESoundDeviceType.DirectSound )      // DirectSound は hGC 未使用。\r
1396                                 {\r
1397                                         if ( this.hGC != null && this.hGC.IsAllocated )\r
1398                                         {\r
1399                                                 this.hGC.Free();\r
1400                                                 this.hGC = default( GCHandle );\r
1401                                         }\r
1402                                 }\r
1403                                 if ( this.byArrWAVファイルイメージ != null )\r
1404                                 {\r
1405                                         this.byArrWAVファイルイメージ = null;\r
1406                                 }\r
1407 \r
1408                                 if ( bインスタンス削除 )\r
1409                                 {\r
1410                                         //try\r
1411                                         //{\r
1412                                         //    CSound.listインスタンス.RemoveAt( freeIndex );\r
1413                                         //}\r
1414                                         //catch\r
1415                                         //{\r
1416                                         //    Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );\r
1417                                         //}\r
1418                                         bool b = CSound.listインスタンス.Remove( this );  // これだと、Clone()したサウンドのremoveに失敗する\r
1419                                         if ( !b )\r
1420                                         {\r
1421                                                 Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );\r
1422                                         }\r
1423 \r
1424                                 }\r
1425                         }\r
1426                 }\r
1427                 ~CSound()\r
1428                 {\r
1429                         this.Dispose( false, true );\r
1430                 }\r
1431                 //-----------------\r
1432                 #endregion\r
1433 \r
1434                 #region [ protected ]\r
1435                 //-----------------\r
1436                 protected enum E作成方法 { ファイルから, WAVファイルイメージから, Unknown }\r
1437                 protected E作成方法 e作成方法 = E作成方法.Unknown;\r
1438                 protected ESoundDeviceType eデバイス種別 = ESoundDeviceType.Unknown;\r
1439                 public string strファイル名 = null;\r
1440                 protected byte[] byArrWAVファイルイメージ = null;       // WAVファイルイメージ、もしくはchunkのDATA部のみ\r
1441                 protected GCHandle hGC;\r
1442                 protected int _hTempoStream = 0;\r
1443                 protected int _hBassStream = -1;                                        // ASIO, WASAPI 用\r
1444                 protected int hBassStream = 0;                                          // #31076 2013.4.1 yyagi; プロパティとして実装すると動作が低速になったため、\r
1445                                                                                                                         // tBASSサウンドを作成する・ストリーム生成後の共通処理()のタイミングと、\r
1446                                                                                                                         // 再生速度を変更したタイミングでのみ、\r
1447                                                                                                                         // hBassStreamを更新するようにした。\r
1448                 //{\r
1449                 //    get\r
1450                 //    {\r
1451                 //        if ( _hTempoStream != 0 && !this.bIs1倍速再生 )   // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する\r
1452                 //        {\r
1453                 //            return _hTempoStream;\r
1454                 //        }\r
1455                 //        else\r
1456                 //        {\r
1457                 //            return _hBassStream;\r
1458                 //        }\r
1459                 //    }\r
1460                 //    set\r
1461                 //    {\r
1462                 //        _hBassStream = value;\r
1463                 //    }\r
1464                 //}\r
1465                 protected SoundBuffer Buffer = null;                    // DirectSound 用\r
1466                 protected DirectSound DirectSound;\r
1467                 protected int hMixer = -1;      // 設計壊してゴメン Mixerに後で登録するときに使う\r
1468                 //-----------------\r
1469                 #endregion\r
1470 \r
1471                 #region [ private ]\r
1472                 //-----------------\r
1473                 private bool bDirectSoundである\r
1474                 {\r
1475                         get { return ( this.eデバイス種別 == ESoundDeviceType.DirectSound ); }\r
1476                 }\r
1477                 private bool bBASSサウンドである\r
1478                 {\r
1479                         get\r
1480                         {\r
1481                                 return (\r
1482                                         this.eデバイス種別 == ESoundDeviceType.ASIO ||\r
1483                                         this.eデバイス種別 == ESoundDeviceType.ExclusiveWASAPI ||\r
1484                                         this.eデバイス種別 == ESoundDeviceType.SharedWASAPI );\r
1485                         }\r
1486                 }\r
1487                 private int _n位置 = 0;\r
1488                 private int _n位置db;\r
1489                 private int _n音量 = 100;\r
1490                 private int _n音量db;\r
1491                 private long nBytes = 0;\r
1492                 private int n一時停止回数 = 0;\r
1493                 private int nオリジナルの周波数 = 0;\r
1494                 private double _db周波数倍率 = 1.0;\r
1495                 private double _db再生速度 = 1.0;\r
1496                 private bool bIs1倍速再生 = true;\r
1497 \r
1498                 private void tBASSサウンドを作成する( string strファイル名, int hMixer, BASSFlag flags )\r
1499                 {\r
1500                         #region [ xaとwav(RIFF chunked vorbis)に対しては専用の処理をする ]\r
1501                         switch ( Path.GetExtension( strファイル名 ).ToLower() )\r
1502                         {\r
1503                                 case ".xa":\r
1504                                         tBASSサウンドを作成するXA( strファイル名, hMixer, flags );\r
1505                                         return;\r
1506 \r
1507                                 case ".wav":\r
1508                                         if ( tRIFFchunkedVorbisならDirectShowでDecodeする( strファイル名, ref byArrWAVファイルイメージ ) )\r
1509                                         {\r
1510                                                 tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, flags );\r
1511                                                 return;\r
1512                                         }\r
1513                                         break;\r
1514 \r
1515                                 default:\r
1516                                         break;\r
1517                         }\r
1518                         #endregion\r
1519 \r
1520                         this.e作成方法 = E作成方法.ファイルから;\r
1521                         this.strファイル名 = strファイル名;\r
1522 \r
1523 \r
1524                         // BASSファイルストリームを作成。\r
1525 \r
1526                         this._hBassStream = Bass.BASS_StreamCreateFile( strファイル名, 0, 0, flags );\r
1527                         if( this._hBassStream == 0 )\r
1528                                 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
1529                         \r
1530                         nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );\r
1531                         \r
1532                         tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );\r
1533                 }\r
1534                 private void tBASSサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, BASSFlag flags )\r
1535                 {\r
1536                         this.e作成方法 = E作成方法.WAVファイルイメージから;\r
1537                         this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;\r
1538                         this.hGC = GCHandle.Alloc( byArrWAVファイルイメージ, GCHandleType.Pinned );             // byte[] をピン留め\r
1539 \r
1540 \r
1541                         // BASSファイルストリームを作成。\r
1542 \r
1543                         this._hBassStream = Bass.BASS_StreamCreateFile( hGC.AddrOfPinnedObject(), 0, byArrWAVファイルイメージ.Length, flags );\r
1544                         if ( this._hBassStream == 0 )\r
1545                                 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
1546 \r
1547                         nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );\r
1548         \r
1549                         tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );\r
1550                 }\r
1551 \r
1552                 /// <summary>\r
1553                 /// Decode "RIFF chunked Vorbis" to "raw wave"\r
1554                 /// because BASE.DLL has two problems for RIFF chunked Vorbis;\r
1555                 /// 1. time seek is not fine  2. delay occurs (about 10ms)\r
1556                 /// </summary>\r
1557                 /// <param name="strファイル名">wave filename</param>\r
1558                 /// <param name="byArrWAVファイルイメージ">wav file image</param>\r
1559                 /// <returns></returns>\r
1560                 private bool tRIFFchunkedVorbisならDirectShowでDecodeする( string strファイル名, ref byte[] byArrWAVファイルイメージ )\r
1561                 {\r
1562                         bool bファイルにVorbisコンテナが含まれている = false;\r
1563 \r
1564                         #region [ ファイルがWAVかつ、Vorbisコンテナが含まれているかを調べ、それに該当するなら、DirectShowでデコードする。]\r
1565                         //-----------------\r
1566                         try\r
1567                         {\r
1568                                 using ( var ws = new WaveStream( strファイル名 ) )\r
1569                                 {\r
1570                                         if ( ws.Format.FormatTag == (WaveFormatTag) 0x6770 ||   // Ogg Vorbis Mode 2+\r
1571                                                  ws.Format.FormatTag == (WaveFormatTag) 0x6771 )        // Ogg Vorbis Mode 3+\r
1572                                         {\r
1573                                                 Trace.TraceInformation( Path.GetFileName( strファイル名 ) + ": RIFF chunked Vorbis. Decode to raw Wave first, to avoid BASS.DLL troubles" );\r
1574                                                 try\r
1575                                                 {\r
1576                                                         CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );\r
1577                                                         bファイルにVorbisコンテナが含まれている = true;\r
1578                                                 }\r
1579                                                 catch\r
1580                                                 {\r
1581                                                         Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : RIFF chunked Vorbisのデコードに失敗しました。" );\r
1582                                                 }\r
1583                                         }\r
1584                                 }\r
1585                         }\r
1586                         catch ( InvalidDataException )\r
1587                         {\r
1588                                 // DirectShowのデコードに失敗したら、次はACMでのデコードを試すことになるため、ここではエラーログを出さない。\r
1589                                 // Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : デコードに失敗しました。" );\r
1590                         }\r
1591                         catch ( Exception )\r
1592                         {\r
1593                                 Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : 読み込みに失敗しました。" );\r
1594                         }\r
1595                         #endregion\r
1596 \r
1597                         return bファイルにVorbisコンテナが含まれている;\r
1598                 }\r
1599 \r
1600                 private void tBASSサウンドを作成するXA( string strファイル名, int hMixer, BASSFlag flags )\r
1601                 {\r
1602                         int nPCMデータの先頭インデックス;\r
1603                         CWin32.WAVEFORMATEX wfx;\r
1604                         int totalPCMSize;\r
1605 \r
1606                         tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,\r
1607                                 out nPCMデータの先頭インデックス, out totalPCMSize, out wfx, true );\r
1608 \r
1609                         nBytes = totalPCMSize;\r
1610 \r
1611                         this.e作成方法 = E作成方法.WAVファイルイメージから;           //.ファイルから;  // 再構築時はデコード後のイメージを流用する&Dispose時にhGCを解放する\r
1612                         this.strファイル名 = strファイル名;\r
1613                         this.hGC = GCHandle.Alloc( this.byArrWAVファイルイメージ, GCHandleType.Pinned );                // byte[] をピン留め\r
1614 \r
1615                         //_cbStreamXA = new STREAMPROC( CallbackPlayingXA );\r
1616 \r
1617                         // BASSファイルストリームを作成。\r
1618 \r
1619                         //this.hBassStream = Bass.BASS_StreamCreate( xa.xaheader.nSamplesPerSec, xa.xaheader.nChannels, BASSFlag.BASS_STREAM_DECODE, _myStreamCreate, IntPtr.Zero );\r
1620                         //this._hBassStream = Bass.BASS_StreamCreate( (int) wfx.nSamplesPerSec, (int) wfx.nChannels, BASSFlag.BASS_STREAM_DECODE, _cbStreamXA, IntPtr.Zero );\r
1621 \r
1622                         // StreamCreate()で作成したstreamはseek不可のため、StreamCreateFile()を使う。\r
1623                         this._hBassStream = Bass.BASS_StreamCreateFile( this.hGC.AddrOfPinnedObject(), 0L, totalPCMSize, flags );\r
1624                         if ( this._hBassStream == 0 )\r
1625                         {\r
1626                                 hGC.Free();\r
1627                                 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_SampleCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
1628                         }\r
1629 \r
1630                         nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );\r
1631 \r
1632 \r
1633                         tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );\r
1634                 }\r
1635 \r
1636 \r
1637                 private void tBASSサウンドを作成する_ストリーム生成後の共通処理( int hMixer )\r
1638                 {\r
1639                         CSound管理.nStreams++;\r
1640 \r
1641                         // 個々のストリームの出力をテンポ変更のストリームに入力する。テンポ変更ストリームの出力を、Mixerに出力する。\r
1642 \r
1643 //                      if ( CSound管理.bIsTimeStretch )      // TimeStretchのON/OFFに関わりなく、テンポ変更のストリームを生成する。後からON/OFF切り替え可能とするため。\r
1644                         {\r
1645                                 this._hTempoStream = BassFx.BASS_FX_TempoCreate( this._hBassStream, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_FX_FREESOURCE );\r
1646                                 if ( this._hTempoStream == 0 )\r
1647                                 {\r
1648                                         hGC.Free();\r
1649                                         throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_FX_TempoCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
1650                                 }\r
1651                                 else\r
1652                                 {\r
1653                                         Bass.BASS_ChannelSetAttribute( this._hTempoStream, BASSAttribute.BASS_ATTRIB_TEMPO_OPTION_USE_QUICKALGO, 1f );  // 高速化(音の品質は少し落ちる)\r
1654                                 }\r
1655                         }\r
1656 \r
1657                         if ( _hTempoStream != 0 && !this.bIs1倍速再生 )     // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する\r
1658                         {\r
1659                                 this.hBassStream = _hTempoStream;\r
1660                         }\r
1661                         else\r
1662                         {\r
1663                                 this.hBassStream = _hBassStream;\r
1664                         }\r
1665 \r
1666                         // #32248 再生終了時に発火するcallbackを登録する (演奏終了後に再生終了するチップを非同期的にミキサーから削除するため。)\r
1667                         _cbEndofStream = new SYNCPROC( CallbackEndofStream );\r
1668                         Bass.BASS_ChannelSetSync( hBassStream, BASSSync.BASS_SYNC_END | BASSSync.BASS_SYNC_MIXTIME, 0, _cbEndofStream, IntPtr.Zero );\r
1669 \r
1670                         // インスタンスリストに登録。\r
1671 \r
1672                         CSound.listインスタンス.Add( this );\r
1673 \r
1674                         // n総演奏時間の取得; DTXMania用に追加。\r
1675                         double seconds = Bass.BASS_ChannelBytes2Seconds( this._hBassStream, nBytes );\r
1676                         this.n総演奏時間ms = (int) ( seconds * 1000 );\r
1677                         //this.pos = 0;\r
1678                         this.hMixer = hMixer;\r
1679                         float freq = 0.0f;\r
1680                         if ( !Bass.BASS_ChannelGetAttribute( this._hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ref freq ) )\r
1681                         {\r
1682                                 hGC.Free();\r
1683                                 throw new Exception( string.Format( "サウンドストリームの周波数取得に失敗しました。(BASS_ChannelGetAttribute)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
1684                         }\r
1685                         this.nオリジナルの周波数 = (int) freq;\r
1686                 }\r
1687                 //-----------------\r
1688 \r
1689                 //private int pos = 0;\r
1690                 //private int CallbackPlayingXA( int handle, IntPtr buffer, int length, IntPtr user )\r
1691                 //{\r
1692                 //    int bytesread = ( pos + length > Convert.ToInt32( nBytes ) ) ? Convert.ToInt32( nBytes ) - pos : length;\r
1693 \r
1694                 //    Marshal.Copy( byArrWAVファイルイメージ, pos, buffer, bytesread );\r
1695                 //    pos += bytesread;\r
1696                 //    if ( pos >= nBytes )\r
1697                 //    {\r
1698                 //        // set indicator flag\r
1699                 //        bytesread |= (int) BASSStreamProc.BASS_STREAMPROC_END;\r
1700                 //    }\r
1701                 //    return bytesread;\r
1702                 //}\r
1703                 /// <summary>\r
1704                 /// ストリームの終端まで再生したときに呼び出されるコールバック\r
1705                 /// </summary>\r
1706                 /// <param name="handle"></param>\r
1707                 /// <param name="channel"></param>\r
1708                 /// <param name="data"></param>\r
1709                 /// <param name="user"></param>\r
1710                 private void CallbackEndofStream( int handle, int channel, int data, IntPtr user )      // #32248 2013.10.14 yyagi\r
1711                 {\r
1712 // Trace.TraceInformation( "Callback!(remove): " + Path.GetFileName( this.strファイル名 ) );\r
1713                         if ( b演奏終了後も再生が続くチップである )                     // 演奏終了後に再生終了するチップ音のミキサー削除は、再生終了のコールバックに引っ掛けて、自前で行う。\r
1714                         {                                                                                                       // そうでないものは、ミキサー削除予定時刻に削除する。\r
1715                                 tBASSサウンドをミキサーから削除する( channel );\r
1716                         }\r
1717                 }\r
1718 \r
1719 // mixerからの削除\r
1720 \r
1721                 public bool tBASSサウンドをミキサーから削除する()\r
1722                 {\r
1723                         return tBASSサウンドをミキサーから削除する( this.hBassStream );\r
1724                 }\r
1725                 public bool tBASSサウンドをミキサーから削除する( int channel )\r
1726                 {\r
1727                         bool b = BassMix.BASS_Mixer_ChannelRemove( channel );\r
1728                         if ( b )\r
1729                         {\r
1730                                 Interlocked.Decrement( ref CSound管理.nMixing );\r
1731 //                              Debug.WriteLine( "Removed: " + Path.GetFileName( this.strファイル名 ) + " (" + channel + ")" + " MixedStreams=" + CSound管理.nMixing );\r
1732                         }\r
1733                         return b;\r
1734                 }\r
1735 \r
1736 \r
1737 // mixer への追加\r
1738                 \r
1739                 public bool tBASSサウンドをミキサーに追加する()\r
1740                 {\r
1741                         if ( BassMix.BASS_Mixer_ChannelGetMixer( hBassStream ) == 0 )\r
1742                         {\r
1743                                 BASSFlag bf = BASSFlag.BASS_SPEAKER_FRONT | BASSFlag.BASS_MIXER_NORAMPIN | BASSFlag.BASS_MIXER_PAUSE;\r
1744                                 Interlocked.Increment( ref CSound管理.nMixing );\r
1745 \r
1746                                 // preloadされることを期待して、敢えてflagからはBASS_MIXER_PAUSEを外してAddChannelした上で、すぐにPAUSEする\r
1747                                 // -> ChannelUpdateでprebufferできることが分かったため、BASS_MIXER_PAUSEを使用することにした\r
1748 \r
1749                                 bool b1 = BassMix.BASS_Mixer_StreamAddChannel( this.hMixer, this.hBassStream, bf );\r
1750                                 //bool b2 = BassMix.BASS_Mixer_ChannelPause( this.hBassStream );\r
1751                                 t再生位置を先頭に戻す();      // StreamAddChannelの後で再生位置を戻さないとダメ。逆だと再生位置が変わらない。\r
1752 //Trace.TraceInformation( "Add Mixer: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" + " MixedStreams=" + CSound管理.nMixing );\r
1753                                 Bass.BASS_ChannelUpdate( this.hBassStream, 0 ); // pre-buffer\r
1754                                 return b1;      // &b2;\r
1755                         }\r
1756                         return true;\r
1757                 }\r
1758 \r
1759                 #region [ tオンメモリ方式でデコードする() ]\r
1760                 public void tオンメモリ方式でデコードする( string strファイル名, out byte[] buffer,\r
1761                         out int nPCMデータの先頭インデックス, out int totalPCMSize, out CWin32.WAVEFORMATEX wfx,\r
1762                         bool bIntegrateWaveHeader )\r
1763                 {\r
1764                         nPCMデータの先頭インデックス = 0;\r
1765                         //int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 );   // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );\r
1766 \r
1767                         SoundDecoder sounddecoder;\r
1768 \r
1769                         if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 )\r
1770                         {\r
1771                                 sounddecoder = new Cxa();\r
1772                         }\r
1773                         else if ( String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 )\r
1774                         {\r
1775                                 sounddecoder = new Cogg();\r
1776                         }\r
1777                         else if ( String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 )\r
1778                         {\r
1779                                 sounddecoder = new Cmp3();\r
1780                         }\r
1781                         else\r
1782                         {\r
1783                                 throw new NotImplementedException();\r
1784                         }\r
1785 \r
1786                         if ( !File.Exists( strファイル名 ) )\r
1787                         {\r
1788                                 throw new Exception( string.Format( "ファイルが見つかりませんでした。({0})", strファイル名 ) );\r
1789                         }\r
1790                         int nHandle = sounddecoder.Open( strファイル名 );\r
1791                         if ( nHandle < 0 )\r
1792                         {\r
1793                                 throw new Exception( string.Format( "Open() に失敗しました。({0})({1})", nHandle, strファイル名 ) );\r
1794                         }\r
1795                         wfx = new CWin32.WAVEFORMATEX();\r
1796                         if ( sounddecoder.GetFormat( nHandle, ref wfx ) < 0 )\r
1797                         {\r
1798                                 sounddecoder.Close( nHandle );\r
1799                                 throw new Exception( string.Format( "GetFormat() に失敗しました。({0})", strファイル名 ) );\r
1800                         }\r
1801                         //totalPCMSize = (int) sounddecoder.nTotalPCMSize;              //  tデコード後のサイズを調べる()で既に取得済みの値を流用する。ms単位の高速化だが、チップ音がたくさんあると塵積で結構効果がある\r
1802                         totalPCMSize = (int) sounddecoder.GetTotalPCMSize( nHandle );\r
1803                         if ( totalPCMSize == 0 )\r
1804                         {\r
1805                                 sounddecoder.Close( nHandle );\r
1806                                 throw new Exception( string.Format( "GetTotalPCMSize() に失敗しました。({0})", strファイル名 ) );\r
1807                         }\r
1808                         totalPCMSize += ( ( totalPCMSize % 2 ) != 0 ) ? 1 : 0;\r
1809                         int wavheadersize = ( bIntegrateWaveHeader ) ? 44 : 0;\r
1810                         byte[] buffer_rawdata = new byte[ totalPCMSize ];\r
1811                         buffer = new byte[ wavheadersize + totalPCMSize ];\r
1812                         GCHandle handle = GCHandle.Alloc( buffer_rawdata, GCHandleType.Pinned );\r
1813                         try\r
1814                         {\r
1815                                 if ( sounddecoder.Decode( nHandle, handle.AddrOfPinnedObject(), (uint) totalPCMSize, 0 ) < 0 )\r
1816                                 {\r
1817                                         buffer = null;\r
1818                                         throw new Exception( string.Format( "デコードに失敗しました。({0})", strファイル名 ) );\r
1819                                 }\r
1820                                 if ( bIntegrateWaveHeader )\r
1821                                 {\r
1822                                         // wave headerを書き込む\r
1823 \r
1824                                         int wfx拡張領域_Length = 0;\r
1825                                         var ms = new MemoryStream();\r
1826                                         var bw = new BinaryWriter( ms );\r
1827                                         bw.Write( new byte[] { 0x52, 0x49, 0x46, 0x46 } );              // 'RIFF'\r
1828                                         bw.Write( (UInt32) totalPCMSize + 44 - 8 );                             // ファイルサイズ - 8 [byte];今は不明なので後で上書きする。\r
1829                                         bw.Write( new byte[] { 0x57, 0x41, 0x56, 0x45 } );              // 'WAVE'\r
1830                                         bw.Write( new byte[] { 0x66, 0x6D, 0x74, 0x20 } );              // 'fmt '\r
1831                                         bw.Write( (UInt32) ( 16 + ( ( wfx拡張領域_Length > 0 ) ? ( 2/*sizeof(WAVEFORMATEX.cbSize)*/ + wfx拡張領域_Length ) : 0 ) ) );   // fmtチャンクのサイズ[byte]\r
1832                                         bw.Write( (UInt16) wfx.wFormatTag );                                    // フォーマットID(リニアPCMなら1)\r
1833                                         bw.Write( (UInt16) wfx.nChannels );                                             // チャンネル数\r
1834                                         bw.Write( (UInt32) wfx.nSamplesPerSec );                                // サンプリングレート\r
1835                                         bw.Write( (UInt32) wfx.nAvgBytesPerSec );                               // データ速度\r
1836                                         bw.Write( (UInt16) wfx.nBlockAlign );                                   // ブロックサイズ\r
1837                                         bw.Write( (UInt16) wfx.wBitsPerSample );                                // サンプルあたりのビット数\r
1838                                         //if ( wfx拡張領域_Length > 0 )\r
1839                                         //{\r
1840                                         //    bw.Write( (UInt16) wfx拡張領域.Length );                      // 拡張領域のサイズ[byte]\r
1841                                         //    bw.Write( wfx拡張領域 );                                                      // 拡張データ\r
1842                                         //}\r
1843                                         bw.Write( new byte[] { 0x64, 0x61, 0x74, 0x61 } );              // 'data'\r
1844                                         //int nDATAチャンクサイズ位置 = (int) ms.Position;\r
1845                                         bw.Write( (UInt32) totalPCMSize );                                              // dataチャンクのサイズ[byte]\r
1846 \r
1847                                         byte[] bs = ms.ToArray();\r
1848 \r
1849                                         bw.Close();\r
1850                                         ms.Close();\r
1851 \r
1852                                         for ( int i = 0; i < bs.Length; i++ )\r
1853                                         {\r
1854                                                 buffer[ i ] = bs[ i ];\r
1855                                         }\r
1856                                 }\r
1857                                 int s = ( bIntegrateWaveHeader ) ? 44 : 0;\r
1858                                 for ( int i = 0; i < totalPCMSize; i++ )\r
1859                                 {\r
1860                                         buffer[ i + s ] = buffer_rawdata[ i ];\r
1861                                 }\r
1862                                 totalPCMSize += wavheadersize;\r
1863                                 nPCMデータの先頭インデックス = wavheadersize;\r
1864                         }\r
1865                         finally\r
1866                         {\r
1867                                 handle.Free();\r
1868                                 sounddecoder.Close( nHandle );\r
1869                                 sounddecoder = null;\r
1870                         }\r
1871                 }\r
1872                 #endregion\r
1873                 #endregion\r
1874         }\r
1875 }\r