2 using System.Collections.Generic;
\r
4 using System.Diagnostics;
\r
5 using System.Runtime.InteropServices;
\r
7 using System.Runtime.CompilerServices;
\r
8 using System.Threading;
\r
10 using SlimDX.DirectSound;
\r
11 using SlimDX.Multimedia;
\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
21 #region [ DTXMania用拡張 ]
\r
22 public class CSound管理 // : CSound
\r
24 public static ISoundDevice SoundDevice
\r
28 public static ESoundDeviceType SoundDeviceType
\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
37 // DTXMania単体でこれをtrueにすると、WASAPI/ASIO時に演奏タイマーとしてFDKタイマーではなく
\r
38 // システムのタイマーを使うようになる。こうするとスクロールは滑らかになるが、音ズレが出るかもしれない。
\r
40 public static IntPtr WindowHandle;
\r
42 public static bool bIsTimeStretch = false;
\r
44 private static int _nMasterVolume;
\r
45 public int nMasterVolume
\r
49 return _nMasterVolume;
\r
53 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
\r
55 // return Bass.BASS_GetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM ) / 100;
\r
64 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI )
\r
66 // // LINEARでなくWINDOWS(2)を使う必要があるが、exclusive時は使用不可、またデバイス側が対応してないと使用不可
\r
67 // bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_LINEAR, value / 100.0f );
\r
70 // BASSError be = Bass.BASS_ErrorGetCode();
\r
71 // Trace.TraceInformation( "WASAPI Master Volume Set Error: " + be.ToString() );
\r
77 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
\r
79 // bool b = Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, value * 100 );
\r
82 // BASSError be = Bass.BASS_ErrorGetCode();
\r
83 // Trace.TraceInformation( "Master Volume Set Error: " + be.ToString() );
\r
89 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
\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
97 SoundDevice.nMasterVolume = value;
\r
98 _nMasterVolume = value;
\r
103 ///// BASS時、mp3をストリーミング再生せずに、デコードしたraw wavをオンメモリ再生する場合はtrueにする。
\r
104 ///// 特殊なmp3を使用時はシークが乱れるので、必要に応じてtrueにすること。(Config.iniのNoMP3Streamingで設定可能。)
\r
105 ///// ただし、trueにすると、その分再生開始までの時間が長くなる。
\r
107 //public static bool bIsMP3DecodeByWindowsCodec = false;
\r
109 public static int nMixing = 0;
\r
110 public int GetMixingStreams()
\r
114 public static int nStreams = 0;
\r
115 public int GetStreams()
\r
119 #region [ WASAPI/ASIO/DirectSound設定値 ]
\r
121 /// <para>WASAPI 排他モード出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
\r
122 /// <para>0以下の値を指定すると、この数値はWASAPI初期化時に自動設定する。正数を指定すると、その値を設定しようと試みる。</para>
\r
124 public static int SoundDelayExclusiveWASAPI = 0; // SSTでは、50ms
\r
125 public int GetSoundExclusiveWASAPI()
\r
127 return SoundDelayExclusiveWASAPI;
\r
129 public void SetSoundDelayExclusiveWASAPI( int value )
\r
131 SoundDelayExclusiveWASAPI = value;
\r
134 /// <para>WASAPI 共有モード出力における再生遅延[ms]。ユーザが決定する。</para>
\r
136 public static int SoundDelaySharedWASAPI = 100;
\r
138 /// <para>排他WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
\r
139 /// <para>→ 自動設定されるのでSoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
\r
141 public static int SoundUpdatePeriodExclusiveWASAPI = 6;
\r
143 /// <para>共有WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
\r
144 /// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
\r
146 public static int SoundUpdatePeriodSharedWASAPI = 6;
\r
148 /// WASAPI利用時に、サウンドバッファの更新をevent drivenにするか、pollingにするかの設定。
\r
149 /// デフォルト設定はpolling。event drivenにすることで、よりラグを小さくできるが、CPU負荷は若干上昇する。
\r
151 /// なおこれをtrueにすると、SoundUpdatePeriodExclusiveWASAPIの設定は無視される。
\r
153 public static bool bSoundUpdateByEventWASAPI = false;
\r
156 ///// <para>ASIO 出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
\r
158 //public static int SoundDelayASIO = 0; // SSTでは50ms。0にすると、デバイスの設定値をそのまま使う。
\r
160 /// <para>ASIO 出力におけるバッファサイズ。</para>
\r
162 public static int SoundDelayASIO = 0; // 0にすると、デバイスの設定値をそのまま使う。
\r
163 public int GetSoundDelayASIO()
\r
165 return SoundDelayASIO;
\r
167 public void SetSoundDelayASIO(int value)
\r
169 SoundDelayASIO = value;
\r
171 public static int ASIODevice = 0;
\r
172 public int GetASIODevice()
\r
176 public void SetASIODevice(int value)
\r
178 ASIODevice = value;
\r
181 /// <para>DirectSound 出力における再生遅延[ms]。ユーザが決定する。</para>
\r
183 public static int SoundDelayDirectSound = 100;
\r
185 public long GetSoundDelay()
\r
187 if ( SoundDevice != null )
\r
189 return SoundDevice.n実バッファサイズms;
\r
203 /// <param name="handle"></param>
\r
204 public CSound管理( IntPtr handle ) // #30803 従来のコンストラクタ相当のI/Fを追加。(DTXC用)
\r
206 WindowHandle = handle;
\r
207 SoundDevice = null;
\r
208 bUseOSTimer = true;
\r
209 t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );
\r
212 /// DTXMania用コンストラクタ
\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
224 WindowHandle = handle;
\r
225 SoundDevice = null;
\r
226 //bUseOSTimer = false;
\r
227 t初期化( soundDeviceType, nSoundDelayExclusiveWASAPI, _bSoundUpdateByEventWASAPI, nSoundDelayASIO, nASIODevice, _bUseOSTimer );
\r
229 public void Dispose()
\r
234 //public static void t初期化()
\r
236 // t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );
\r
239 public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, IntPtr handle )
\r
241 //if ( !bInitialized )
\r
243 WindowHandle = handle;
\r
244 t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice );
\r
245 //bInitialized = true;
\r
248 public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice )
\r
250 t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, false, _nSoundDelayASIO, _nASIODevice, false );
\r
253 public void t初期化( ESoundDeviceType soundDeviceType,
\r
254 int _nSoundDelayExclusiveWASAPI, bool _bSoundUpdateByEventWASAPI,
\r
255 int _nSoundDelayASIO, int _nASIODevice,
\r
256 bool _bUseOSTimer )
\r
258 //SoundDevice = null; // 後で再初期化することがあるので、null初期化はコンストラクタに回す
\r
259 rc演奏用タイマ = null; // Global.Bass 依存(つまりユーザ依存)
\r
262 SoundDelayExclusiveWASAPI = _nSoundDelayExclusiveWASAPI;
\r
263 SoundDelayASIO = _nSoundDelayASIO;
\r
264 ASIODevice = _nASIODevice;
\r
265 bUseOSTimer = _bUseOSTimer;
\r
266 bSoundUpdateByEventWASAPI = _bSoundUpdateByEventWASAPI;
\r
268 ESoundDeviceType[] ESoundDeviceTypes = new ESoundDeviceType[ 4 ]
\r
270 ESoundDeviceType.ExclusiveWASAPI,
\r
271 ESoundDeviceType.ASIO,
\r
272 ESoundDeviceType.DirectSound,
\r
273 ESoundDeviceType.Unknown
\r
277 switch ( soundDeviceType )
\r
279 case ESoundDeviceType.ExclusiveWASAPI:
\r
282 case ESoundDeviceType.ASIO:
\r
285 case ESoundDeviceType.DirectSound:
\r
292 for ( SoundDeviceType = ESoundDeviceTypes[ n初期デバイス ]; ; SoundDeviceType = ESoundDeviceTypes[ ++n初期デバイス ] )
\r
296 t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する();
\r
299 catch ( Exception e )
\r
301 Trace.TraceInformation( e.Message );
\r
302 if ( ESoundDeviceTypes[ n初期デバイス ] == ESoundDeviceType.Unknown )
\r
304 Trace.TraceError( string.Format( "サウンドデバイスの初期化に失敗しました。" ) );
\r
309 if ( soundDeviceType == ESoundDeviceType.ExclusiveWASAPI || soundDeviceType == ESoundDeviceType.ASIO )
\r
311 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 4 );
\r
312 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
\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
319 public void tDisableUpdateBufferAutomatically()
\r
321 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 0 );
\r
322 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
\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
329 public static void t終了()
\r
331 C共通.tDisposeする( SoundDevice ); SoundDevice = null;
\r
332 C共通.tDisposeする( ref rc演奏用タイマ ); // Global.Bass を解放した後に解放すること。(Global.Bass で参照されているため)
\r
336 public static void t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する()
\r
338 #region [ すでにサウンドデバイスと演奏タイマが構築されていれば解放する。]
\r
339 //-----------------
\r
340 if ( SoundDevice != null )
\r
342 // すでに生成済みのサウンドがあれば初期状態に戻す。
\r
344 CSound.tすべてのサウンドを初期状態に戻す(); // リソースは解放するが、CSoundのインスタンスは残す。
\r
347 // サウンドデバイスと演奏タイマを解放する。
\r
349 C共通.tDisposeする( SoundDevice ); SoundDevice = null;
\r
350 C共通.tDisposeする( ref rc演奏用タイマ ); // Global.SoundDevice を解放した後に解放すること。(Global.SoundDevice で参照されているため)
\r
352 //-----------------
\r
355 #region [ 新しいサウンドデバイスを構築する。]
\r
356 //-----------------
\r
357 switch ( SoundDeviceType )
\r
359 case ESoundDeviceType.ExclusiveWASAPI:
\r
360 SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.排他, SoundDelayExclusiveWASAPI, SoundUpdatePeriodExclusiveWASAPI );
\r
363 case ESoundDeviceType.SharedWASAPI:
\r
364 SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.共有, SoundDelaySharedWASAPI, SoundUpdatePeriodSharedWASAPI );
\r
367 case ESoundDeviceType.ASIO:
\r
368 SoundDevice = new CSoundDeviceASIO( SoundDelayASIO, ASIODevice );
\r
371 case ESoundDeviceType.DirectSound:
\r
372 SoundDevice = new CSoundDeviceDirectSound( WindowHandle, SoundDelayDirectSound, bUseOSTimer );
\r
376 throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );
\r
378 //-----------------
\r
380 #region [ 新しい演奏タイマを構築する。]
\r
381 //-----------------
\r
382 rc演奏用タイマ = new CSoundTimer( SoundDevice );
\r
383 //-----------------
\r
386 SoundDevice.nMasterVolume = _nMasterVolume; // サウンドデバイスに対して、マスターボリュームを再設定する
\r
388 CSound.tすべてのサウンドを再構築する( SoundDevice ); // すでに生成済みのサウンドがあれば作り直す。
\r
390 public CSound tサウンドを生成する( string filename )
\r
392 if ( SoundDeviceType == ESoundDeviceType.Unknown )
\r
394 throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );
\r
396 return SoundDevice.tサウンドを作成する( filename );
\r
399 private static DateTime lastUpdateTime = DateTime.MinValue;
\r
400 public void t再生中の処理をする( object o ) // #26122 2011.9.1 yyagi; delegate経由の呼び出し用
\r
404 public void t再生中の処理をする()
\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
412 // bool b = Bass.BASS_Update( 100 * 2 );
\r
413 // lastUpdateTime = DateTime.Now;
\r
416 // Trace.TraceInformation( "BASS_UPdate() failed: " + Bass.BASS_ErrorGetCode().ToString() );
\r
421 public void tサウンドを破棄する( CSound csound )
\r
423 csound.t解放する( true ); // インスタンスは存続→破棄にする。
\r
427 public float GetCPUusage()
\r
430 switch ( SoundDeviceType )
\r
432 case ESoundDeviceType.ExclusiveWASAPI:
\r
433 case ESoundDeviceType.SharedWASAPI:
\r
434 f = BassWasapi.BASS_WASAPI_GetCPU();
\r
436 case ESoundDeviceType.ASIO:
\r
437 f = BassAsio.BASS_ASIO_GetCPU();
\r
439 case ESoundDeviceType.DirectSound:
\r
449 public string GetCurrentSoundDeviceType()
\r
451 switch ( SoundDeviceType )
\r
453 case ESoundDeviceType.ExclusiveWASAPI:
\r
454 case ESoundDeviceType.SharedWASAPI:
\r
456 case ESoundDeviceType.ASIO:
\r
458 case ESoundDeviceType.DirectSound:
\r
459 return "DirectSound";
\r
465 public void AddMixer( CSound cs, double db再生速度, bool _b演奏終了後も再生が続くチップである )
\r
467 cs.b演奏終了後も再生が続くチップである = _b演奏終了後も再生が続くチップである;
\r
468 cs.db再生速度 = db再生速度;
\r
469 cs.tBASSサウンドをミキサーに追加する();
\r
471 public void AddMixer( CSound cs, double db再生速度 )
\r
473 cs.db再生速度 = db再生速度;
\r
474 cs.tBASSサウンドをミキサーに追加する();
\r
476 public void AddMixer( CSound cs )
\r
478 cs.tBASSサウンドをミキサーに追加する();
\r
480 public void RemoveMixer( CSound cs )
\r
482 cs.tBASSサウンドをミキサーから削除する();
\r
487 // CSound は、サウンドデバイスが変更されたときも、インスタンスを再作成することなく、新しいデバイスで作り直せる必要がある。
\r
488 // そのため、デバイスごとに別のクラスに分割するのではなく、1つのクラスに集約するものとする。
\r
490 public class CSound : IDisposable, ICloneable
\r
492 #region [ DTXMania用拡張 ]
\r
493 public int n総演奏時間ms
\r
498 public int nサウンドバッファサイズ // 取りあえず0固定★★★★★★★★★★★★★★★★★★★★
\r
502 public bool bストリーム再生する // 取りあえずfalse固定★★★★★★★★★★★★★★★★★★★★
\r
503 // trueにすると同一チップ音の多重再生で問題が出る(4POLY音源として動かない)
\r
505 get { return false; }
\r
507 public double db周波数倍率
\r
515 if ( _db周波数倍率 != value )
\r
518 if ( bBASSサウンドである )
\r
520 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );
\r
524 // if ( b再生中 ) // #30838 2012.2.24 yyagi (delete b再生中)
\r
526 this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );
\r
532 public double db再生速度
\r
540 if ( _db再生速度 != value )
\r
543 bIs1倍速再生 = ( _db再生速度 == 1.000f );
\r
544 if ( bBASSサウンドである )
\r
546 if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する
\r
548 this.hBassStream = _hTempoStream;
\r
552 this.hBassStream = _hBassStream;
\r
555 if ( CSound管理.bIsTimeStretch )
\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
563 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );
\r
568 // if ( b再生中 ) // #30838 2012.2.24 yyagi (delete b再生中)
\r
570 this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );
\r
578 public bool b演奏終了後も再生が続くチップである = false; // これがtrueなら、本サウンドの再生終了のコールバック時に自動でミキサーから削除する
\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
585 /// <para>0:最小~100:原音</para>
\r
591 if( this.bBASSサウンドである )
\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
597 return (int) ( f音量 * 100 );
\r
599 else if( this.bDirectSoundである )
\r
607 if( this.bBASSサウンドである )
\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
615 else if( this.bDirectSoundである )
\r
619 if( this._n音量 == 0 )
\r
621 this._n音量db = -10000;
\r
625 this._n音量db = (int) ( ( 20.0 * Math.Log10( ( (double) this._n音量 ) / 100.0 ) ) * 100.0 );
\r
628 this.Buffer.Volume = this._n音量db;
\r
634 /// <para>左:-100~中央:0~100:右。set のみ。</para>
\r
640 if( this.bBASSサウンドである )
\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
646 return (int) ( f位置 * 100 );
\r
648 else if( this.bDirectSoundである )
\r
656 if( this.bBASSサウンドである )
\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
663 else if( this.bDirectSoundである )
\r
665 this._n位置 = Math.Min( Math.Max( -100, value ), 100 ); // -100~100
\r
667 if( this._n位置 == 0 )
\r
671 else if( this._n位置 == -100 )
\r
673 this._n位置db = -10000;
\r
675 else if( this._n位置 == 100 )
\r
677 this._n位置db = 10000;
\r
679 else if( this._n位置 < 0 )
\r
681 this._n位置db = (int) ( ( 20.0 * Math.Log10( ( (double) ( this._n位置 + 100 ) ) / 100.0 ) ) * 100.0 );
\r
685 this._n位置db = (int) ( ( -20.0 * Math.Log10( ( (double) ( 100 - this._n位置 ) ) / 100.0 ) ) * 100.0 );
\r
688 this.Buffer.Pan = this._n位置db;
\r
694 /// <para>DirectSoundのセカンダリバッファ。</para>
\r
696 //public SecondarySoundBuffer DirectSoundBuffer
\r
697 public SoundBuffer DirectSoundBuffer
\r
699 get { return this.Buffer; }
\r
703 /// <para>DirectSoundのセカンダリバッファ作成時のフラグ。</para>
\r
705 public BufferFlags DirectSoundBufferFlags
\r
712 /// <para>全インスタンスリスト。</para>
\r
713 /// <para>~を作成する() で追加され、t解放する() or Dispose() で解放される。</para>
\r
715 public static List<CSound> listインスタンス = new List<CSound>();
\r
717 public static void ShowAllCSoundFiles()
\r
720 foreach ( CSound cs in listインスタンス )
\r
722 Debug.WriteLine( i++.ToString( "d3" ) + ": " + Path.GetFileName( cs.strファイル名 ) );
\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
738 public object Clone()
\r
740 if ( !bDirectSoundである )
\r
742 throw new NotImplementedException();
\r
744 CSound clone = (CSound) MemberwiseClone(); // これだけだとCY連打が途切れる&タイトルに戻る際にNullRef例外発生
\r
745 this.DirectSound.DuplicateSoundBuffer( this.Buffer, out clone.Buffer );
\r
747 // CSound.listインスタンス.Add( this ); // インスタンスリストに登録。
\r
748 // 本来これを加えるべきだが、Add後Removeできなくなっている。Clone()の仕方の問題であろう。
\r
752 public void tASIOサウンドを作成する( string strファイル名, int hMixer )
\r
754 this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE );
\r
755 this.eデバイス種別 = ESoundDeviceType.ASIO; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
\r
757 public void tASIOサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer )
\r
759 this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE );
\r
760 this.eデバイス種別 = ESoundDeviceType.ASIO; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
\r
762 public void tWASAPIサウンドを作成する( string strファイル名, int hMixer, ESoundDeviceType eデバイス種別 )
\r
764 this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );
\r
765 this.eデバイス種別 = eデバイス種別; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
\r
767 public void tWASAPIサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, ESoundDeviceType eデバイス種別 )
\r
769 this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );
\r
770 this.eデバイス種別 = eデバイス種別; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
\r
772 public void tDirectSoundサウンドを作成する( string strファイル名, DirectSound DirectSound )
\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
780 tDirectSoundサウンドを作成するXaOggMp3( strファイル名, DirectSound );
\r
784 // すべてのファイルを DirectShow でデコードすると時間がかかるので、ファイルが WAV かつ PCM フォーマットでない場合のみ DirectShow でデコードする。
\r
786 byte[] byArrWAVファイルイメージ = null;
\r
787 bool bファイルがWAVかつPCMフォーマットである = true;
\r
790 #region [ ファイルがWAVかつPCMフォーマットか否か調べる。]
\r
791 //-----------------
\r
794 using ( var ws = new WaveStream( strファイル名 ) )
\r
796 if ( ws.Format.FormatTag != WaveFormatTag.Pcm )
\r
797 bファイルがWAVかつPCMフォーマットである = false;
\r
802 bファイルがWAVかつPCMフォーマットである = false;
\r
804 //-----------------
\r
807 if ( bファイルがWAVかつPCMフォーマットである )
\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
814 byArrWAVファイルイメージ = new byte[ fs.Length ];
\r
815 br.Read( byArrWAVファイルイメージ, 0, (int) fs.Length );
\r
819 //-----------------
\r
824 #region [ DirectShow でデコード変換し、 byArrWAVファイルイメージへ格納。]
\r
825 //-----------------
\r
826 CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );
\r
827 //-----------------
\r
834 this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound );
\r
836 public void tDirectSoundサウンドを作成するXaOggMp3( string strファイル名, DirectSound DirectSound )
\r
838 this.e作成方法 = E作成方法.ファイルから;
\r
839 this.strファイル名 = strファイル名;
\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
847 CWin32.WAVEFORMATEX cw32wfx;
\r
848 tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,
\r
849 out nPCMデータの先頭インデックス, out nPCMサイズbyte, out cw32wfx, false );
\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
858 // セカンダリバッファを作成し、PCMデータを書き込む。
\r
859 tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み
\r
860 ( ref this.byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags, wfx,
\r
861 nPCMサイズbyte, nPCMデータの先頭インデックス );
\r
864 public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound )
\r
866 this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags );
\r
868 public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags )
\r
870 if( this.e作成方法 == E作成方法.Unknown )
\r
871 this.e作成方法 = E作成方法.WAVファイルイメージから;
\r
873 WaveFormat wfx = null;
\r
874 int nPCMデータの先頭インデックス = -1;
\r
875 int nPCMサイズbyte = -1;
\r
877 #region [ byArrWAVファイルイメージ[] から上記3つのデータを取得。]
\r
878 //-----------------
\r
879 var ms = new MemoryStream( byArrWAVファイルイメージ );
\r
880 var br = new BinaryReader( ms );
\r
884 // 'RIFF'+RIFFデータサイズ
\r
886 if( br.ReadUInt32() != 0x46464952 )
\r
887 throw new InvalidDataException( "RIFFファイルではありません。" );
\r
891 if( br.ReadUInt32() != 0x45564157 )
\r
892 throw new InvalidDataException( "WAVEファイルではありません。" );
\r
895 while( ( ms.Position + 8 ) < ms.Length ) // +8 は、チャンク名+チャンクサイズ。残り8バイト未満ならループ終了。
\r
897 uint chunkName = br.ReadUInt32();
\r
900 if( chunkName == 0x20746D66 )
\r
902 long chunkSize = (long) br.ReadUInt32();
\r
904 var tag = (WaveFormatTag) br.ReadUInt16();
\r
906 if( tag == WaveFormatTag.Pcm ) wfx = new WaveFormat();
\r
907 else if( tag == WaveFormatTag.Extensible ) wfx = new SlimDX.Multimedia.WaveFormatExtensible(); // このクラスは WaveFormat を継承している。
\r
909 throw new InvalidDataException( string.Format( "未対応のWAVEフォーマットタグです。(Tag:{0})", tag.ToString() ) );
\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
918 long nフォーマットサイズbyte = 16;
\r
920 if( wfx.FormatTag == WaveFormatTag.Extensible )
\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
928 nフォーマットサイズbyte += 24;
\r
931 ms.Seek( chunkSize - nフォーマットサイズbyte, SeekOrigin.Current );
\r
936 else if( chunkName == 0x61746164 )
\r
938 nPCMサイズbyte = br.ReadInt32();
\r
939 nPCMデータの先頭インデックス = (int) ms.Position;
\r
941 ms.Seek( nPCMサイズbyte, SeekOrigin.Current );
\r
948 long chunkSize = (long) br.ReadUInt32();
\r
949 ms.Seek( chunkSize, SeekOrigin.Current );
\r
955 throw new InvalidDataException( "fmt チャンクが存在しません。不正なサウンドデータです。" );
\r
956 if( nPCMサイズbyte < 0 )
\r
957 throw new InvalidDataException( "data チャンクが存在しません。不正なサウンドデータです。" );
\r
964 //-----------------
\r
968 // セカンダリバッファを作成し、PCMデータを書き込む。
\r
969 tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み(
\r
970 ref byArrWAVファイルイメージ, DirectSound, flags, wfx, nPCMサイズbyte, nPCMデータの先頭インデックス );
\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
977 // セカンダリバッファを作成し、PCMデータを書き込む。
\r
979 this.Buffer = new SecondarySoundBuffer( DirectSound, new SoundBufferDescription()
\r
981 Format = ( wfx.FormatTag == WaveFormatTag.Pcm ) ? wfx : (SlimDX.Multimedia.WaveFormatExtensible) wfx,
\r
983 SizeInBytes = nPCMサイズbyte,
\r
985 this.Buffer.Write( byArrWAVファイルイメージ, nPCMデータの先頭インデックス, nPCMサイズbyte, 0, LockFlags.None );
\r
989 this.eデバイス種別 = ESoundDeviceType.DirectSound;
\r
990 this.DirectSoundBufferFlags = flags;
\r
991 this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;
\r
992 this.DirectSound = DirectSound;
\r
995 this.nオリジナルの周波数 = wfx.SamplesPerSecond;
\r
996 n総演奏時間ms = (int) ( ( (double) nPCMサイズbyte ) / ( this.Buffer.Format.AverageBytesPerSecond * 0.001 ) );
\r
1001 CSound.listインスタンス.Add( this );
\r
1004 #region [ DTXMania用の変換 ]
\r
1006 public void tサウンドを破棄する( CSound cs )
\r
1010 public void t再生を開始する()
\r
1015 public void t再生を開始する( bool bループする )
\r
1017 if ( bBASSサウンドである )
\r
1021 Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_SAMPLE_LOOP, BASSFlag.BASS_SAMPLE_LOOP );
\r
1025 Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_DEFAULT, BASSFlag.BASS_DEFAULT );
\r
1029 tサウンドを再生する( bループする );
\r
1031 public void t再生を停止する()
\r
1036 public void t再生を一時停止する()
\r
1041 public void t再生を再開する( long t ) // ★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
1043 Debug.WriteLine( "t再生を再開する(long " + t + ")" );
\r
1048 public bool b一時停止中
\r
1052 if ( this.bBASSサウンドである )
\r
1054 bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PAUSED ) &
\r
1055 ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) > 0 );
\r
1060 return ( this.n一時停止回数 > 0 );
\r
1068 if ( this.eデバイス種別 == ESoundDeviceType.DirectSound )
\r
1070 return ( ( this.Buffer.Status & BufferStatus.Playing ) != BufferStatus.None );
\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
1085 //public lint t時刻から位置を返す( long t )
\r
1087 // double num = ( n時刻 * this.db再生速度 ) * this.db周波数倍率;
\r
1088 // return (int) ( ( num * 0.01 ) * this.nSamplesPerSecond );
\r
1093 public void t解放する()
\r
1098 public void t解放する( bool _bインスタンス削除 )
\r
1100 if ( this.bBASSサウンドである ) // stream数の削減用
\r
1102 tBASSサウンドをミキサーから削除する();
\r
1103 _cbEndofStream = null;
\r
1104 //_cbStreamXA = null;
\r
1105 CSound管理.nStreams--;
\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
1112 public void tサウンドを再生する()
\r
1114 tサウンドを再生する( false );
\r
1116 public void tサウンドを再生する( bool bループする )
\r
1118 if ( this.bBASSサウンドである ) // BASSサウンド時のループ処理は、t再生を開始する()側に実装。ここでは「bループする」は未使用。
\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
1124 //Debug.WriteLine( "再生しようとしたが、Mixerに登録されていなかった: " + Path.GetFileName( this.strファイル名 ) + ", stream#=" + this.hBassStream + ", ErrCode=" + Bass.BASS_ErrorGetCode() );
\r
1126 bool bb = tBASSサウンドをミキサーに追加する();
\r
1129 Debug.WriteLine( "Mixerへの登録に失敗: " + Path.GetFileName( this.strファイル名 ) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );
\r
1133 //Debug.WriteLine( "Mixerへの登録に成功: " + Path.GetFileName( this.strファイル名 ) + ": " + Bass.BASS_ErrorGetCode() );
\r
1135 //this.t再生位置を先頭に戻す();
\r
1137 bool bbb = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );
\r
1140 Debug.WriteLine("更に再生に失敗: " + Path.GetFileName(this.strファイル名) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );
\r
1144 // Debug.WriteLine("再生成功(ミキサー追加後) : " + Path.GetFileName(this.strファイル名));
\r
1149 //Debug.WriteLine( "再生成功: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" );
\r
1152 else if( this.bDirectSoundである )
\r
1154 PlayFlags pf = ( bループする ) ? PlayFlags.Looping : PlayFlags.None;
\r
1155 this.Buffer.Play( 0, pf );
\r
1158 public void tサウンドを先頭から再生する()
\r
1160 this.t再生位置を先頭に戻す();
\r
1161 this.tサウンドを再生する();
\r
1163 public void tサウンドを停止してMixerからも削除する()
\r
1165 tサウンドを停止する( false );
\r
1166 if ( bBASSサウンドである )
\r
1168 tBASSサウンドをミキサーから削除する();
\r
1171 public void tサウンドを停止する()
\r
1173 tサウンドを停止する( false );
\r
1175 public void tサウンドを停止する( bool pause )
\r
1177 if( this.bBASSサウンドである )
\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
1183 // tBASSサウンドをミキサーから削除する(); // PAUSEと再生停止を区別できるようにすること!!
\r
1186 else if( this.bDirectSoundである )
\r
1190 this.Buffer.Stop();
\r
1192 catch ( Exception )
\r
1194 // WASAPI/ASIOとDirectSoundを同時使用すると、Bufferがlostしてここで例外発生する。→ catchして無視する。
\r
1195 // DTXCからDTXManiaを呼び出すと、DTXC終了時にこの現象が発生する。
\r
1201 public void t再生位置を先頭に戻す()
\r
1203 if( this.bBASSサウンドである )
\r
1205 BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, 0 );
\r
1208 else if( this.bDirectSoundである )
\r
1210 this.Buffer.CurrentPlayPosition = 0;
\r
1213 public void t再生位置を変更する( long n位置ms )
\r
1215 if( this.bBASSサウンドである )
\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
1222 catch( Exception e )
\r
1224 Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + e.ToString() + ": " + n位置ms + "ms" );
\r
1230 BASSError be = Bass.BASS_ErrorGetCode();
\r
1231 Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + be.ToString() + ": " + n位置ms + "MS" );
\r
1234 //if ( this.n総演奏時間ms > 5000 )
\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
1239 else if( this.bDirectSoundである )
\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
1244 this.Buffer.CurrentPlayPosition = n位置sample * this.Buffer.Format.BlockAlignment;
\r
1246 catch ( DirectSoundException e )
\r
1248 Trace.TraceError( "{0}: Seek error: {1}", Path.GetFileName( this.strファイル名 ), n位置ms, e.Message );
\r
1250 //if ( this.n総演奏時間ms > 5000 )
\r
1252 // Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seeked to " + n位置ms + "ms = " + n位置sample );
\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
1263 if ( this.bBASSサウンドである )
\r
1265 n位置byte = BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream );
\r
1266 db位置ms = Bass.BASS_ChannelBytes2Seconds( this.hBassStream, n位置byte );
\r
1268 else if ( this.bDirectSoundである )
\r
1270 n位置byte = this.Buffer.CurrentPlayPosition;
\r
1271 db位置ms = n位置byte / this.Buffer.Format.SamplesPerSecond / 0.001 / _db周波数倍率 / _db再生速度;
\r
1281 public static void tすべてのサウンドを初期状態に戻す()
\r
1283 foreach ( var sound in CSound.listインスタンス )
\r
1285 sound.t解放する( false );
\r
1288 public static void tすべてのサウンドを再構築する( ISoundDevice device )
\r
1290 if( CSound.listインスタンス.Count == 0 )
\r
1294 // サウンドを再生する際にインスタンスリストも更新されるので、配列にコピーを取っておき、リストはクリアする。
\r
1296 var sounds = CSound.listインスタンス.ToArray();
\r
1297 CSound.listインスタンス.Clear();
\r
1300 // 配列に基づいて個々のサウンドを作成する。
\r
1302 for( int i = 0; i < sounds.Length; i++ )
\r
1304 switch( sounds[ i ].e作成方法 )
\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
1313 #region [ WAVファイルイメージから ]
\r
1314 case E作成方法.WAVファイルイメージから:
\r
1315 if( sounds[ i ].bBASSサウンドである )
\r
1317 byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;
\r
1318 sounds[ i ].Dispose( true, false );
\r
1319 device.tサウンドを作成する( byArrWaveファイルイメージ, ref sounds[ i ] );
\r
1321 else if( sounds[ i ].bDirectSoundである )
\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
1334 #region [ Dispose-Finalizeパターン実装 ]
\r
1335 //-----------------
\r
1336 public void Dispose()
\r
1338 this.Dispose( true, true );
\r
1339 GC.SuppressFinalize( this );
\r
1341 protected void Dispose( bool bManagedも解放する, bool bインスタンス削除 )
\r
1343 if( this.bBASSサウンドである )
\r
1345 #region [ ASIO, WASAPI の解放 ]
\r
1346 //-----------------
\r
1347 if ( _hTempoStream != 0 )
\r
1349 BassMix.BASS_Mixer_ChannelRemove( this._hTempoStream );
\r
1350 Bass.BASS_StreamFree( this._hTempoStream );
\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
1361 if( bManagedも解放する )
\r
1363 //int freeIndex = -1;
\r
1365 //if ( CSound.listインスタンス != null )
\r
1367 // freeIndex = CSound.listインスタンス.IndexOf( this );
\r
1368 // if ( freeIndex == -1 )
\r
1370 // Debug.WriteLine( "ERR: freeIndex==-1 : Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );
\r
1374 if( this.eデバイス種別 == ESoundDeviceType.DirectSound )
\r
1376 #region [ DirectSound の解放 ]
\r
1377 //-----------------
\r
1378 if( this.Buffer != null )
\r
1382 this.Buffer.Stop();
\r
1386 // 演奏終了後、長時間解放しないでいると、たまに AccessViolationException が発生することがある。
\r
1388 C共通.tDisposeする( ref this.Buffer );
\r
1390 //-----------------
\r
1394 if( this.e作成方法 == E作成方法.WAVファイルイメージから &&
\r
1395 this.eデバイス種別 != ESoundDeviceType.DirectSound ) // DirectSound は hGC 未使用。
\r
1397 if ( this.hGC != null && this.hGC.IsAllocated )
\r
1400 this.hGC = default( GCHandle );
\r
1403 if ( this.byArrWAVファイルイメージ != null )
\r
1405 this.byArrWAVファイルイメージ = null;
\r
1412 // CSound.listインスタンス.RemoveAt( freeIndex );
\r
1416 // Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );
\r
1418 bool b = CSound.listインスタンス.Remove( this ); // これだと、Clone()したサウンドのremoveに失敗する
\r
1421 Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );
\r
1429 this.Dispose( false, true );
\r
1431 //-----------------
\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
1451 // if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する
\r
1453 // return _hTempoStream;
\r
1457 // return _hBassStream;
\r
1462 // _hBassStream = value;
\r
1465 protected SoundBuffer Buffer = null; // DirectSound 用
\r
1466 protected DirectSound DirectSound;
\r
1467 protected int hMixer = -1; // 設計壊してゴメン Mixerに後で登録するときに使う
\r
1468 //-----------------
\r
1471 #region [ private ]
\r
1472 //-----------------
\r
1473 private bool bDirectSoundである
\r
1475 get { return ( this.eデバイス種別 == ESoundDeviceType.DirectSound ); }
\r
1477 private bool bBASSサウンドである
\r
1482 this.eデバイス種別 == ESoundDeviceType.ASIO ||
\r
1483 this.eデバイス種別 == ESoundDeviceType.ExclusiveWASAPI ||
\r
1484 this.eデバイス種別 == ESoundDeviceType.SharedWASAPI );
\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
1498 private void tBASSサウンドを作成する( string strファイル名, int hMixer, BASSFlag flags )
\r
1500 #region [ xaとwav(RIFF chunked vorbis)に対しては専用の処理をする ]
\r
1501 switch ( Path.GetExtension( strファイル名 ).ToLower() )
\r
1504 tBASSサウンドを作成するXA( strファイル名, hMixer, flags );
\r
1508 if ( tRIFFchunkedVorbisならDirectShowでDecodeする( strファイル名, ref byArrWAVファイルイメージ ) )
\r
1510 tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, flags );
\r
1520 this.e作成方法 = E作成方法.ファイルから;
\r
1521 this.strファイル名 = strファイル名;
\r
1524 // BASSファイルストリームを作成。
\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
1530 nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );
\r
1532 tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );
\r
1534 private void tBASSサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, BASSFlag flags )
\r
1536 this.e作成方法 = E作成方法.WAVファイルイメージから;
\r
1537 this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;
\r
1538 this.hGC = GCHandle.Alloc( byArrWAVファイルイメージ, GCHandleType.Pinned ); // byte[] をピン留め
\r
1541 // BASSファイルストリームを作成。
\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
1547 nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );
\r
1549 tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );
\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
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
1562 bool bファイルにVorbisコンテナが含まれている = false;
\r
1564 #region [ ファイルがWAVかつ、Vorbisコンテナが含まれているかを調べ、それに該当するなら、DirectShowでデコードする。]
\r
1565 //-----------------
\r
1568 using ( var ws = new WaveStream( strファイル名 ) )
\r
1570 if ( ws.Format.FormatTag == (WaveFormatTag) 0x6770 || // Ogg Vorbis Mode 2+
\r
1571 ws.Format.FormatTag == (WaveFormatTag) 0x6771 ) // Ogg Vorbis Mode 3+
\r
1573 Trace.TraceInformation( Path.GetFileName( strファイル名 ) + ": RIFF chunked Vorbis. Decode to raw Wave first, to avoid BASS.DLL troubles" );
\r
1576 CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );
\r
1577 bファイルにVorbisコンテナが含まれている = true;
\r
1581 Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : RIFF chunked Vorbisのデコードに失敗しました。" );
\r
1586 catch ( InvalidDataException )
\r
1588 // DirectShowのデコードに失敗したら、次はACMでのデコードを試すことになるため、ここではエラーログを出さない。
\r
1589 // Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : デコードに失敗しました。" );
\r
1591 catch ( Exception )
\r
1593 Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : 読み込みに失敗しました。" );
\r
1597 return bファイルにVorbisコンテナが含まれている;
\r
1600 private void tBASSサウンドを作成するXA( string strファイル名, int hMixer, BASSFlag flags )
\r
1602 int nPCMデータの先頭インデックス;
\r
1603 CWin32.WAVEFORMATEX wfx;
\r
1606 tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,
\r
1607 out nPCMデータの先頭インデックス, out totalPCMSize, out wfx, true );
\r
1609 nBytes = totalPCMSize;
\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
1615 //_cbStreamXA = new STREAMPROC( CallbackPlayingXA );
\r
1617 // BASSファイルストリームを作成。
\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
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
1627 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_SampleCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
\r
1630 nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );
\r
1633 tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );
\r
1637 private void tBASSサウンドを作成する_ストリーム生成後の共通処理( int hMixer )
\r
1639 CSound管理.nStreams++;
\r
1641 // 個々のストリームの出力をテンポ変更のストリームに入力する。テンポ変更ストリームの出力を、Mixerに出力する。
\r
1643 // if ( CSound管理.bIsTimeStretch ) // TimeStretchのON/OFFに関わりなく、テンポ変更のストリームを生成する。後からON/OFF切り替え可能とするため。
\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
1649 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_FX_TempoCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
\r
1653 Bass.BASS_ChannelSetAttribute( this._hTempoStream, BASSAttribute.BASS_ATTRIB_TEMPO_OPTION_USE_QUICKALGO, 1f ); // 高速化(音の品質は少し落ちる)
\r
1657 if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する
\r
1659 this.hBassStream = _hTempoStream;
\r
1663 this.hBassStream = _hBassStream;
\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
1672 CSound.listインスタンス.Add( this );
\r
1674 // n総演奏時間の取得; DTXMania用に追加。
\r
1675 double seconds = Bass.BASS_ChannelBytes2Seconds( this._hBassStream, nBytes );
\r
1676 this.n総演奏時間ms = (int) ( seconds * 1000 );
\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
1683 throw new Exception( string.Format( "サウンドストリームの周波数取得に失敗しました。(BASS_ChannelGetAttribute)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
\r
1685 this.nオリジナルの周波数 = (int) freq;
\r
1687 //-----------------
\r
1689 //private int pos = 0;
\r
1690 //private int CallbackPlayingXA( int handle, IntPtr buffer, int length, IntPtr user )
\r
1692 // int bytesread = ( pos + length > Convert.ToInt32( nBytes ) ) ? Convert.ToInt32( nBytes ) - pos : length;
\r
1694 // Marshal.Copy( byArrWAVファイルイメージ, pos, buffer, bytesread );
\r
1695 // pos += bytesread;
\r
1696 // if ( pos >= nBytes )
\r
1698 // // set indicator flag
\r
1699 // bytesread |= (int) BASSStreamProc.BASS_STREAMPROC_END;
\r
1701 // return bytesread;
\r
1704 /// ストリームの終端まで再生したときに呼び出されるコールバック
\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
1712 // Trace.TraceInformation( "Callback!(remove): " + Path.GetFileName( this.strファイル名 ) );
\r
1713 if ( b演奏終了後も再生が続くチップである ) // 演奏終了後に再生終了するチップ音のミキサー削除は、再生終了のコールバックに引っ掛けて、自前で行う。
\r
1714 { // そうでないものは、ミキサー削除予定時刻に削除する。
\r
1715 tBASSサウンドをミキサーから削除する( channel );
\r
1721 public bool tBASSサウンドをミキサーから削除する()
\r
1723 return tBASSサウンドをミキサーから削除する( this.hBassStream );
\r
1725 public bool tBASSサウンドをミキサーから削除する( int channel )
\r
1727 bool b = BassMix.BASS_Mixer_ChannelRemove( channel );
\r
1730 Interlocked.Decrement( ref CSound管理.nMixing );
\r
1731 // Debug.WriteLine( "Removed: " + Path.GetFileName( this.strファイル名 ) + " (" + channel + ")" + " MixedStreams=" + CSound管理.nMixing );
\r
1739 public bool tBASSサウンドをミキサーに追加する()
\r
1741 if ( BassMix.BASS_Mixer_ChannelGetMixer( hBassStream ) == 0 )
\r
1743 BASSFlag bf = BASSFlag.BASS_SPEAKER_FRONT | BASSFlag.BASS_MIXER_NORAMPIN | BASSFlag.BASS_MIXER_PAUSE;
\r
1744 Interlocked.Increment( ref CSound管理.nMixing );
\r
1746 // preloadされることを期待して、敢えてflagからはBASS_MIXER_PAUSEを外してAddChannelした上で、すぐにPAUSEする
\r
1747 // -> ChannelUpdateでprebufferできることが分かったため、BASS_MIXER_PAUSEを使用することにした
\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
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
1764 nPCMデータの先頭インデックス = 0;
\r
1765 //int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 ); // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );
\r
1767 SoundDecoder sounddecoder;
\r
1769 if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 )
\r
1771 sounddecoder = new Cxa();
\r
1773 else if ( String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 )
\r
1775 sounddecoder = new Cogg();
\r
1777 else if ( String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 )
\r
1779 sounddecoder = new Cmp3();
\r
1783 throw new NotImplementedException();
\r
1786 if ( !File.Exists( strファイル名 ) )
\r
1788 throw new Exception( string.Format( "ファイルが見つかりませんでした。({0})", strファイル名 ) );
\r
1790 int nHandle = sounddecoder.Open( strファイル名 );
\r
1791 if ( nHandle < 0 )
\r
1793 throw new Exception( string.Format( "Open() に失敗しました。({0})({1})", nHandle, strファイル名 ) );
\r
1795 wfx = new CWin32.WAVEFORMATEX();
\r
1796 if ( sounddecoder.GetFormat( nHandle, ref wfx ) < 0 )
\r
1798 sounddecoder.Close( nHandle );
\r
1799 throw new Exception( string.Format( "GetFormat() に失敗しました。({0})", strファイル名 ) );
\r
1801 //totalPCMSize = (int) sounddecoder.nTotalPCMSize; // tデコード後のサイズを調べる()で既に取得済みの値を流用する。ms単位の高速化だが、チップ音がたくさんあると塵積で結構効果がある
\r
1802 totalPCMSize = (int) sounddecoder.GetTotalPCMSize( nHandle );
\r
1803 if ( totalPCMSize == 0 )
\r
1805 sounddecoder.Close( nHandle );
\r
1806 throw new Exception( string.Format( "GetTotalPCMSize() に失敗しました。({0})", strファイル名 ) );
\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
1815 if ( sounddecoder.Decode( nHandle, handle.AddrOfPinnedObject(), (uint) totalPCMSize, 0 ) < 0 )
\r
1818 throw new Exception( string.Format( "デコードに失敗しました。({0})", strファイル名 ) );
\r
1820 if ( bIntegrateWaveHeader )
\r
1822 // wave headerを書き込む
\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
1840 // bw.Write( (UInt16) wfx拡張領域.Length ); // 拡張領域のサイズ[byte]
\r
1841 // bw.Write( wfx拡張領域 ); // 拡張データ
\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
1847 byte[] bs = ms.ToArray();
\r
1852 for ( int i = 0; i < bs.Length; i++ )
\r
1854 buffer[ i ] = bs[ i ];
\r
1857 int s = ( bIntegrateWaveHeader ) ? 44 : 0;
\r
1858 for ( int i = 0; i < totalPCMSize; i++ )
\r
1860 buffer[ i + s ] = buffer_rawdata[ i ];
\r
1862 totalPCMSize += wavheadersize;
\r
1863 nPCMデータの先頭インデックス = wavheadersize;
\r
1868 sounddecoder.Close( nHandle );
\r
1869 sounddecoder = null;
\r