2 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Runtime.InteropServices;
7 using System.Runtime.CompilerServices;
8 using System.Threading;
10 using SharpDX.DirectSound;
11 using SharpDX.Multimedia;
13 using Un4seen.BassAsio;
14 using Un4seen.BassWasapi;
15 using Un4seen.Bass.AddOn.Mix;
16 using Un4seen.Bass.AddOn.Fx;
20 #region [ DTXMania用拡張 ]
21 public class CSound管理 // : CSound
23 public static ISoundDevice SoundDevice
27 public static ESoundDeviceType SoundDeviceType
31 public static CSoundTimer rc演奏用タイマ = null;
32 public static bool bUseOSTimer = false; // OSのタイマーを使うか、CSoundTimerを使うか。DTXCではfalse, DTXManiaではtrue。
33 // DTXC(DirectSound)でCSoundTimerを使うと、内部で無音のループサウンドを再生するため
34 // サウンドデバイスを占有してしまい、Viewerとして呼び出されるDTXManiaで、ASIOが使えなくなる。
36 // DTXMania単体でこれをtrueにすると、WASAPI/ASIO時に演奏タイマーとしてFDKタイマーではなく
37 // システムのタイマーを使うようになる。こうするとスクロールは滑らかになるが、音ズレが出るかもしれない。
39 public static IntPtr WindowHandle;
41 public static bool bIsTimeStretch = false;
43 public static string strDefaultDeviceBusType
47 return SoundDevice.strDefaultSoundDeviceBusType;
51 private static int _nMasterVolume;
52 public int nMasterVolume
56 return _nMasterVolume;
60 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
62 // return Bass.BASS_GetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM ) / 100;
71 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI )
73 // // LINEARでなくWINDOWS(2)を使う必要があるが、exclusive時は使用不可、またデバイス側が対応してないと使用不可
74 // bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_LINEAR, value / 100.0f );
77 // BASSError be = Bass.BASS_ErrorGetCode();
78 // Trace.TraceInformation( "WASAPI Master Volume Set Error: " + be.ToString() );
84 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
86 // bool b = Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_STREAM, value * 100 );
89 // BASSError be = Bass.BASS_ErrorGetCode();
90 // Trace.TraceInformation( "Master Volume Set Error: " + be.ToString() );
96 // if ( SoundDeviceType == ESoundDeviceType.ExclusiveWASAPI || SoundDeviceType == ESoundDeviceType.ASIO )
98 // var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };
99 // BassMix.BASS_Mixer_ChannelSetEnvelope( SoundDevice.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
104 SoundDevice.nMasterVolume = value;
105 _nMasterVolume = value;
110 ///// BASS時、mp3をストリーミング再生せずに、デコードしたraw wavをオンメモリ再生する場合はtrueにする。
111 ///// 特殊なmp3を使用時はシークが乱れるので、必要に応じてtrueにすること。(Config.iniのNoMP3Streamingで設定可能。)
112 ///// ただし、trueにすると、その分再生開始までの時間が長くなる。
114 //public static bool bIsMP3DecodeByWindowsCodec = false;
116 public static int nMixing = 0;
117 public int GetMixingStreams()
121 public static int nStreams = 0;
122 public int GetStreams()
126 #region [ WASAPI/ASIO/DirectSound設定値 ]
128 /// <para>WASAPI 排他モード出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
129 /// <para>0以下の値を指定すると、この数値はWASAPI初期化時に自動設定する。正数を指定すると、その値を設定しようと試みる。</para>
131 public static int SoundDelayExclusiveWASAPI = 0; // SSTでは、50ms
132 public int GetSoundExclusiveWASAPI()
134 return SoundDelayExclusiveWASAPI;
136 public void SetSoundDelayExclusiveWASAPI( int value )
138 SoundDelayExclusiveWASAPI = value;
141 /// <para>WASAPI 共有モード出力における再生遅延[ms]。ユーザが決定する。</para>
143 public static int SoundDelaySharedWASAPI = 10;
145 /// <para>排他WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
146 /// <para>→ 自動設定されるのでSoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
148 public static int SoundUpdatePeriodExclusiveWASAPI = 1;
150 /// <para>共有WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>
151 /// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>
153 public static int SoundUpdatePeriodSharedWASAPI = 10;
155 /// WASAPI利用時に、サウンドバッファの更新をevent drivenにするか、pollingにするかの設定。
156 /// デフォルト設定はpolling。event drivenにすることで、よりラグを小さくできるが、CPU負荷は若干上昇する。
158 /// なおこれをtrueにすると、SoundUpdatePeriodExclusiveWASAPIの設定は無視される。
160 public static bool bSoundUpdateByEventWASAPI = false;
163 ///// <para>ASIO 出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>
165 //public static int SoundDelayASIO = 0; // SSTでは50ms。0にすると、デバイスの設定値をそのまま使う。
167 /// <para>ASIO 出力におけるバッファサイズ。</para>
169 public static int SoundDelayASIO = 0; // 0にすると、デバイスの設定値をそのまま使う。
170 public int GetSoundDelayASIO()
172 return SoundDelayASIO;
174 public void SetSoundDelayASIO(int value)
176 SoundDelayASIO = value;
178 public static int ASIODevice = 0;
179 public int GetASIODevice()
183 public void SetASIODevice(int value)
188 /// <para>DirectSound 出力における再生遅延[ms]。ユーザが決定する。</para>
190 public static int SoundDelayDirectSound = 100;
192 public long GetSoundDelay()
194 if ( SoundDevice != null )
196 return SoundDevice.n実バッファサイズms;
207 public static string strRecordInputDTXfilename;
208 public static string strRecordOutFilename;
213 public static string strRecordFileType;
219 public static int[] nMixerVolume = { 100, 100, 100, 100, 100, 100 }; // BGM,SE,Drums,Guitar,Bass, Unknown(Unknownだけは外部から更新されないので事実上100で固定)
222 /// 録音機能で使う、エンコーダーパス (DLLフォルダ)
224 public static string strEncoderPath;
227 /// 録音機能で使う、サンプリング周波数
229 public static int nFreq;
232 /// 録音機能で使う、ビットレート(OGG時は、Q値(-1~10)
234 public static int nBitrate;
243 /// <param name="handle"></param>
244 public CSound管理( IntPtr handle ) // #30803 従来のコンストラクタ相当のI/Fを追加。(DTXC用)
246 WindowHandle = handle;
249 t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );
254 /// <param name="handle"></param>
255 /// <param name="soundDeviceType"></param>
256 /// <param name="nSoundDelayExclusiveWASAPI"></param>
257 /// <param name="nSoundDelayASIO"></param>
258 /// <param name="nASIODevice"></param>
259 public CSound管理( IntPtr handle, ESoundDeviceType soundDeviceType,
260 int nSoundDelayExclusiveWASAPI, bool _bSoundUpdateByEventWASAPI,
261 int nSoundDelayASIO, int nASIODevice,
264 WindowHandle = handle;
266 //bUseOSTimer = false;
267 t初期化( soundDeviceType, nSoundDelayExclusiveWASAPI, _bSoundUpdateByEventWASAPI, nSoundDelayASIO, nASIODevice, _bUseOSTimer );
269 public void Dispose()
274 //public static void t初期化()
276 // t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );
279 public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, IntPtr handle )
281 //if ( !bInitialized )
283 WindowHandle = handle;
284 t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice );
285 //bInitialized = true;
288 public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice )
290 t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, false, _nSoundDelayASIO, _nASIODevice, false );
293 public void t初期化( ESoundDeviceType soundDeviceType,
294 int _nSoundDelayExclusiveWASAPI, bool _bSoundUpdateByEventWASAPI,
295 int _nSoundDelayASIO, int _nASIODevice,
298 //SoundDevice = null; // 後で再初期化することがあるので、null初期化はコンストラクタに回す
299 rc演奏用タイマ = null; // Global.Bass 依存(つまりユーザ依存)
301 SoundDelayExclusiveWASAPI = _nSoundDelayExclusiveWASAPI;
302 SoundDelaySharedWASAPI = _nSoundDelayExclusiveWASAPI;
303 SoundDelayASIO = _nSoundDelayASIO;
304 ASIODevice = _nASIODevice;
305 bUseOSTimer = _bUseOSTimer;
306 bSoundUpdateByEventWASAPI = _bSoundUpdateByEventWASAPI;
308 ESoundDeviceType[] ESoundDeviceTypes = new ESoundDeviceType[ 5 ]
310 ESoundDeviceType.ASIO,
311 ESoundDeviceType.ExclusiveWASAPI,
312 ESoundDeviceType.SharedWASAPI,
313 ESoundDeviceType.DirectSound,
314 ESoundDeviceType.Unknown
317 int n初期デバイス; // = (int) soundDeviceType;
318 switch (soundDeviceType)
320 case ESoundDeviceType.ExclusiveWASAPI:
323 case ESoundDeviceType.ASIO:
326 case ESoundDeviceType.SharedWASAPI:
329 case ESoundDeviceType.DirectSound:
336 for (SoundDeviceType = ESoundDeviceTypes[n初期デバイス]; ; SoundDeviceType = ESoundDeviceTypes[++n初期デバイス])
340 t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する();
343 catch ( Exception e )
345 Trace.TraceInformation( e.Message );
346 if ( ESoundDeviceTypes[ n初期デバイス ] == ESoundDeviceType.Unknown )
348 Trace.TraceError( string.Format( "サウンドデバイスの初期化に失敗しました。" ) );
353 //if ( n初期デバイス > ESoundDeviceTypes.Length )
355 // Trace.TraceError(string.Format("サウンドデバイスの初期化に失敗しました。"));
356 // throw new Exception("サウンドデバイスの初期化に失敗しました。");
358 if ( soundDeviceType == ESoundDeviceType.ExclusiveWASAPI || soundDeviceType == ESoundDeviceType.ASIO || soundDeviceType == ESoundDeviceType.SharedWASAPI )
360 #region [ CPU論理コア数の取得 (HT含む) ]
361 CWin32.SYSTEM_INFO sysInfo = new CWin32.SYSTEM_INFO();
362 CWin32.GetSystemInfo(ref sysInfo);
363 int nCPUCores = (int)sysInfo.dwNumberOfProcessors;
366 Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, nCPUCores );
367 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
369 //Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );
370 Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );
374 public void tDisableUpdateBufferAutomatically()
376 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 0 );
377 //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );
379 //Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );
380 //Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );
384 public static void t終了()
386 C共通.tDisposeする( SoundDevice ); SoundDevice = null;
387 C共通.tDisposeする( ref rc演奏用タイマ ); // Global.Bass を解放した後に解放すること。(Global.Bass で参照されているため)
391 public static void t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する()
393 #region [ すでにサウンドデバイスと演奏タイマが構築されていれば解放する。]
395 if ( SoundDevice != null )
397 // すでに生成済みのサウンドがあれば初期状態に戻す。
399 CSound.tすべてのサウンドを初期状態に戻す(); // リソースは解放するが、CSoundのインスタンスは残す。
402 // サウンドデバイスと演奏タイマを解放する。
404 C共通.tDisposeする( SoundDevice ); SoundDevice = null;
405 C共通.tDisposeする( ref rc演奏用タイマ ); // Global.SoundDevice を解放した後に解放すること。(Global.SoundDevice で参照されているため)
410 #region [ 新しいサウンドデバイスを構築する。]
412 switch ( SoundDeviceType )
414 case ESoundDeviceType.ExclusiveWASAPI:
415 SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.排他, SoundDelayExclusiveWASAPI, SoundUpdatePeriodExclusiveWASAPI, strRecordFileType, strEncoderPath );
418 case ESoundDeviceType.SharedWASAPI:
419 SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.共有, SoundDelaySharedWASAPI, SoundUpdatePeriodSharedWASAPI, strRecordFileType, strEncoderPath );
422 case ESoundDeviceType.ASIO:
423 SoundDevice = new CSoundDeviceASIO( SoundDelayASIO, ASIODevice );
426 case ESoundDeviceType.DirectSound:
427 SoundDevice = new CSoundDeviceDirectSound( WindowHandle, SoundDelayDirectSound, bUseOSTimer );
431 throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );
435 #region [ 新しい演奏タイマを構築する。]
437 rc演奏用タイマ = new CSoundTimer( SoundDevice );
441 SoundDevice.nMasterVolume = _nMasterVolume; // サウンドデバイスに対して、マスターボリュームを再設定する
443 CSound.tすべてのサウンドを再構築する( SoundDevice ); // すでに生成済みのサウンドがあれば作り直す。
446 public CSound tサウンドを生成する( string filename )
448 return tサウンドを生成する( filename, CSound.EInstType.Unknown );
450 public CSound tサウンドを生成する( string filename, CSound.EInstType eInstType )
452 if ( SoundDeviceType == ESoundDeviceType.Unknown )
454 throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );
456 return SoundDevice.tサウンドを作成する( filename, eInstType);
459 private static DateTime lastUpdateTime = DateTime.MinValue;
460 public void t再生中の処理をする( object o ) // #26122 2011.9.1 yyagi; delegate経由の呼び出し用
464 public void t再生中の処理をする()
466 //★★★★★★★★★★★★★★★★★★★★★ダミー★★★★★★★★★★★★★★★★★★
467 // Debug.Write( "再生中の処理をする()" );
468 //DateTime now = DateTime.Now;
469 //TimeSpan ts = now - lastUpdateTime;
470 //if ( ts.Milliseconds > 5 )
472 // bool b = Bass.BASS_Update( 100 * 2 );
473 // lastUpdateTime = DateTime.Now;
476 // Trace.TraceInformation( "BASS_UPdate() failed: " + Bass.BASS_ErrorGetCode().ToString() );
481 public void tサウンドを破棄する( CSound csound )
483 csound.t解放する( true ); // インスタンスは存続→破棄にする。
487 public float GetCPUusage()
490 switch ( SoundDeviceType )
492 case ESoundDeviceType.ExclusiveWASAPI:
493 case ESoundDeviceType.SharedWASAPI:
494 f = BassWasapi.BASS_WASAPI_GetCPU();
496 case ESoundDeviceType.ASIO:
497 f = BassAsio.BASS_ASIO_GetCPU();
499 case ESoundDeviceType.DirectSound:
509 public string GetCurrentSoundDeviceType()
511 switch ( SoundDeviceType )
513 case ESoundDeviceType.ExclusiveWASAPI:
514 return "WASAPI(Exclusive)";
515 case ESoundDeviceType.SharedWASAPI:
516 return "WASAPI(Shared)";
517 case ESoundDeviceType.ASIO:
519 case ESoundDeviceType.DirectSound:
520 return "DirectSound";
525 public ESoundDeviceType CurrentSoundDeviceType
529 return SoundDeviceType;
533 public void AddMixer( CSound cs, double db再生速度, bool _b演奏終了後も再生が続くチップである )
535 cs.b演奏終了後も再生が続くチップである = _b演奏終了後も再生が続くチップである;
537 cs.tBASSサウンドをミキサーに追加する();
539 public void AddMixer( CSound cs, double db再生速度 )
542 cs.tBASSサウンドをミキサーに追加する();
544 public void AddMixer( CSound cs )
546 cs.tBASSサウンドをミキサーに追加する();
548 public void RemoveMixer( CSound cs )
550 cs.tBASSサウンドをミキサーから削除する();
554 /// 録音のPAUSEを解除し、録音を開始する。
556 public static bool t録音開始()
558 return SoundDevice.tStartRecording();
561 public static bool t録音終了()
563 return SoundDevice.tStartRecording();
568 // CSound は、サウンドデバイスが変更されたときも、インスタンスを再作成することなく、新しいデバイスで作り直せる必要がある。
569 // そのため、デバイスごとに別のクラスに分割するのではなく、1つのクラスに集約するものとする。
571 public class CSound : IDisposable, ICloneable
573 #region [ DTXMania用拡張 ]
579 public int nサウンドバッファサイズ // 取りあえず0固定★★★★★★★★★★★★★★★★★★★★
583 public bool bストリーム再生する // 取りあえずfalse固定★★★★★★★★★★★★★★★★★★★★
584 // trueにすると同一チップ音の多重再生で問題が出る(4POLY音源として動かない)
586 get { return false; }
588 public double db周波数倍率
596 if ( _db周波数倍率 != value )
601 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );
605 // if ( b再生中 ) // #30838 2012.2.24 yyagi (delete b再生中)
607 this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );
621 if ( _db再生速度 != value )
624 bIs1倍速再生 = ( _db再生速度 == 1.000f );
627 if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する
629 this.hBassStream = _hTempoStream;
633 this.hBassStream = _hBassStream;
636 if ( CSound管理.bIsTimeStretch )
638 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_TEMPO, (float) ( db再生速度 * 100 - 100 ) );
639 //double seconds = Bass.BASS_ChannelBytes2Seconds( this.hTempoStream, nBytes );
640 //this.n総演奏時間ms = (int) ( seconds * 1000 );
644 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );
649 // if ( b再生中 ) // #30838 2012.2.24 yyagi (delete b再生中)
651 this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );
657 public enum EInstType
666 public EInstType eInstType;
669 public bool b演奏終了後も再生が続くチップである = false; // これがtrueなら、本サウンドの再生終了のコールバック時に自動でミキサーから削除する
671 //private STREAMPROC _cbStreamXA; // make it global, so that the GC can not remove it
672 private SYNCPROC _cbEndofStream; // ストリームの終端まで再生されたときに呼び出されるコールバック
673 // private WaitCallback _cbRemoveMixerChannel;
676 /// <para>0:最小~100:原音</para>
682 if( this.bBASSサウンドである )
685 if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 ) )
686 //if ( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, ref f音量 ) == -1 )
688 return (int) ( f音量 * 100 );
690 else if( this.bDirectSoundである )
698 if( this.bBASSサウンドである )
700 float f音量 = Math.Min( Math.Max( value, 0 ), 100 ) / 100.0f; // 0~100 → 0.0~1.0
701 //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, f音量 ) };
702 //BassMix.BASS_Mixer_ChannelSetEnvelope( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
703 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_VOL, f音量 );
706 else if( this.bDirectSoundである )
712 this._n音量db = -10000;
716 this._n音量db = (int) ( ( 20.0 * Math.Log10( ( (double) this._n音量 ) / 100.0 ) ) * 100.0 );
719 this.Buffer.Volume = this._n音量db;
725 /// <para>左:-100~中央:0~100:右。set のみ。</para>
731 if( this.bBASSサウンドである )
734 if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_PAN, ref f位置 ) )
735 //if( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_PAN, ref f位置 ) == -1 )
737 return (int) ( f位置 * 100 );
739 else if( this.bDirectSoundである )
747 if( this.bBASSサウンドである )
749 float f位置 = Math.Min( Math.Max( value, -100 ), 100 ) / 100.0f; // -100~100 → -1.0~1.0
750 //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, f位置 ) };
751 //BassMix.BASS_Mixer_ChannelSetEnvelope( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_PAN, nodes );
752 Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_PAN, f位置 );
754 else if( this.bDirectSoundである )
756 this._n位置 = Math.Min( Math.Max( -100, value ), 100 ); // -100~100
762 else if( this._n位置 == -100 )
764 this._n位置db = -10000;
766 else if( this._n位置 == 100 )
770 else if( this._n位置 < 0 )
772 this._n位置db = (int) ( ( 20.0 * Math.Log10( ( (double) ( this._n位置 + 100 ) ) / 100.0 ) ) * 100.0 );
776 this._n位置db = (int) ( ( -20.0 * Math.Log10( ( (double) ( 100 - this._n位置 ) ) / 100.0 ) ) * 100.0 );
779 this.Buffer.Pan = this._n位置db;
785 /// <para>DirectSoundのセカンダリバッファ。</para>
787 //public SecondarySoundBuffer DirectSoundBuffer
788 public SoundBuffer DirectSoundBuffer
790 get { return this.Buffer; }
794 /// <para>DirectSoundのセカンダリバッファ作成時のフラグ。</para>
796 public BufferFlags DirectSoundBufferFlags
803 /// <para>全インスタンスリスト。</para>
804 /// <para>~を作成する() で追加され、t解放する() or Dispose() で解放される。</para>
806 public static List<CSound> listインスタンス = new List<CSound>();
808 public static void ShowAllCSoundFiles()
811 foreach ( CSound cs in listインスタンス )
813 Debug.WriteLine( i++.ToString( "d3" ) + ": " + Path.GetFileName( cs.strファイル名 ) );
823 this.DirectSoundBufferFlags = CSoundDeviceDirectSound.DefaultFlags;
824 // this._cbRemoveMixerChannel = new WaitCallback( RemoveMixerChannelLater );
825 this._hBassStream = -1;
826 this._hTempoStream = 0;
827 this.eInstType = EInstType.Unknown;
830 public object Clone()
832 if ( !bDirectSoundである )
834 throw new NotImplementedException();
836 CSound clone = (CSound) MemberwiseClone(); // これだけだとCY連打が途切れる&タイトルに戻る際にNullRef例外発生
837 clone.Buffer = this.DirectSound.DuplicateSoundBuffer( this.Buffer );
839 CSound.listインスタンス.Add( clone ); // インスタンスリストに登録。
843 public void tASIOサウンドを作成する( string strファイル名, int hMixer, CSound.EInstType _eInstType )
845 this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE );
846 this.eデバイス種別 = ESoundDeviceType.ASIO; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
847 this.eInstType = _eInstType;
849 public void tASIOサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, CSound.EInstType _eInstType )
851 this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE );
852 this.eデバイス種別 = ESoundDeviceType.ASIO; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
853 this.eInstType = _eInstType;
855 public void tWASAPIサウンドを作成する( string strファイル名, int hMixer, ESoundDeviceType eデバイス種別, CSound.EInstType _eInstType )
857 this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );
858 this.eデバイス種別 = eデバイス種別; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
859 this.eInstType = _eInstType;
861 public void tWASAPIサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, ESoundDeviceType eデバイス種別, CSound.EInstType _eInstType )
863 this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );
864 this.eデバイス種別 = eデバイス種別; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)
865 this.eInstType = _eInstType;
867 public void tDirectSoundサウンドを作成する( string strファイル名, DirectSound DirectSound, CSound.EInstType _eInstType )
869 this.e作成方法 = E作成方法.ファイルから;
870 this.strファイル名 = strファイル名;
871 if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 ||
872 String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 ||
873 String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 ) // caselessで文字列比較
877 tDirectSoundサウンドを作成するXaOggMp3(strファイル名, DirectSound);
878 this.eInstType = _eInstType;
883 string s = Path.GetFileName(strファイル名);
884 Trace.TraceWarning($"directsoundサウンドの作成に失敗しました。({s}: {e.Message})");
885 Trace.TraceWarning("続けて、他のデコーダでの作成を試みます。");
889 // すべてのファイルを DirectShow でデコードすると時間がかかるので、ファイルが WAV かつ PCM フォーマットでない場合のみ DirectShow でデコードする。
891 byte[] byArrWAVファイルイメージ = null;
892 bool bファイルがWAVかつPCMフォーマットである = true;
895 #region [ ファイルがWAVかつPCMフォーマットか否か調べる。]
897 SoundStream ws = null;
900 using( ws = new SoundStream( new FileStream( strファイル名, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ) )
902 if( ws.Format.Encoding != WaveFormatEncoding.Pcm )
903 bファイルがWAVかつPCMフォーマットである = false;
908 bファイルがWAVかつPCMフォーマットである = false;
921 if ( bファイルがWAVかつPCMフォーマットである )
923 #region [ ファイルを読み込んで byArrWAVファイルイメージへ格納。]
925 var fs = File.Open( strファイル名, FileMode.Open, FileAccess.Read, FileShare.ReadWrite );
926 var br = new BinaryReader( fs );
928 byArrWAVファイルイメージ = new byte[ fs.Length ];
929 br.Read( byArrWAVファイルイメージ, 0, (int) fs.Length );
938 #region [ DirectShow でデコード変換し、 byArrWAVファイルイメージへ格納。]
940 CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );
948 this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound, _eInstType );
950 public void tDirectSoundサウンドを作成するXaOggMp3( string strファイル名, DirectSound DirectSound )
952 this.e作成方法 = E作成方法.ファイルから;
953 this.strファイル名 = strファイル名;
956 int nPCMデータの先頭インデックス = 0;
957 // int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 ); // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );
960 CWin32.WAVEFORMATEX cw32wfx;
961 tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,
962 out nPCMデータの先頭インデックス, out nPCMサイズbyte, out cw32wfx, false );
963 WaveFormat wfx = WaveFormat.CreateCustomFormat( WaveFormatEncoding.Pcm, (int) cw32wfx.nSamplesPerSec, (int) cw32wfx.nChannels, (int) cw32wfx.nAvgBytesPerSec, (int) cw32wfx.nBlockAlign, (int) cw32wfx.wBitsPerSample );
965 // セカンダリバッファを作成し、PCMデータを書き込む。
966 tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み
967 ( ref this.byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags, wfx,
968 nPCMサイズbyte, nPCMデータの先頭インデックス );
971 public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound, CSound.EInstType _eInstType )
973 this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags, _eInstType);
975 public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags, CSound.EInstType _eInstType)
977 if( this.e作成方法 == E作成方法.Unknown )
978 this.e作成方法 = E作成方法.WAVファイルイメージから;
980 WaveFormat wfx = null;
981 int nPCMデータの先頭インデックス = -1;
982 int nPCMサイズbyte = -1;
984 #region [ byArrWAVファイルイメージ[] から上記3つのデータを取得。]
986 var ms = new MemoryStream( byArrWAVファイルイメージ );
987 var br = new BinaryReader( ms );
993 if( br.ReadUInt32() != 0x46464952 )
994 throw new InvalidDataException( "RIFFファイルではありません。" );
998 if( br.ReadUInt32() != 0x45564157 )
999 throw new InvalidDataException( "WAVEファイルではありません。" );
1002 while( ( ms.Position + 8 ) < ms.Length ) // +8 は、チャンク名+チャンクサイズ。残り8バイト未満ならループ終了。
1004 uint chunkName = br.ReadUInt32();
1007 if( chunkName == 0x20746D66 )
1009 long chunkSize = (long) br.ReadUInt32();
1011 var tag = (WaveFormatEncoding) br.ReadUInt16();
1012 var channels = br.ReadInt16();
1013 var samplesPerSecond = br.ReadInt32();
1014 var averageBytesPerSecond = br.ReadInt32();
1015 var blockAlignment = br.ReadInt16();
1016 var bitsPerSample = br.ReadInt16();
1018 if( tag == WaveFormatEncoding.Pcm )
1020 wfx = WaveFormat.CreateCustomFormat( tag, samplesPerSecond, channels, averageBytesPerSecond, blockAlignment, bitsPerSample );
1022 else if( tag == WaveFormatEncoding.Extensible )
1024 wfx = SharpDX.Multimedia.WaveFormatExtensible.CreateCustomFormat( // このクラスは WaveFormat を継承している。
1025 tag, samplesPerSecond, channels, averageBytesPerSecond, blockAlignment, bitsPerSample );
1028 throw new InvalidDataException( string.Format( "未対応のWAVEフォーマットタグです。(Tag:{0})", tag.ToString() ) );
1030 long nフォーマットサイズbyte = 16;
1032 if( wfx.Encoding == WaveFormatEncoding.Extensible )
1034 br.ReadUInt16(); // 拡張領域サイズbyte
1035 var wfxEx = (SharpDX.Multimedia.WaveFormatExtensible) wfx;
1036 /*wfxEx.ValidBitsPerSample = */br.ReadInt16(); // 対応するメンバがない?
1037 wfxEx.ChannelMask = (Speakers) br.ReadInt32();
1038 wfxEx.GuidSubFormat = new Guid( br.ReadBytes( 16 ) ); // GUID は 16byte (128bit)
1040 nフォーマットサイズbyte += 24;
1043 ms.Seek( chunkSize - nフォーマットサイズbyte, SeekOrigin.Current );
1048 else if( chunkName == 0x61746164 )
1050 nPCMサイズbyte = br.ReadInt32();
1051 nPCMデータの先頭インデックス = (int) ms.Position;
1053 ms.Seek( nPCMサイズbyte, SeekOrigin.Current );
1060 long chunkSize = (long) br.ReadUInt32();
1061 ms.Seek( chunkSize, SeekOrigin.Current );
1067 throw new InvalidDataException( "fmt チャンクが存在しません。不正なサウンドデータです。" );
1068 if( nPCMサイズbyte < 0 )
1069 throw new InvalidDataException( "data チャンクが存在しません。不正なサウンドデータです。" );
1080 // セカンダリバッファを作成し、PCMデータを書き込む。
1081 tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み(
1082 ref byArrWAVファイルイメージ, DirectSound, flags, wfx, nPCMサイズbyte, nPCMデータの先頭インデックス );
1083 this.eInstType = _eInstType;
1085 //var st = new FileStream("temp3.wav", FileMode.Create);
1086 //st.Write(byArrWAVファイルイメージ, 0, byArrWAVファイルイメージ.Length);
1088 //Trace.TraceInformation("wrote " + byArrWAVファイルイメージ.Length);
1092 private void tDirectSoundサウンドを作成する_セカンダリバッファの作成とWAVデータ書き込み
1093 ( ref byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags, WaveFormat wfx,
1094 int nPCMサイズbyte, int nPCMデータの先頭インデックス )
1096 // セカンダリバッファを作成し、PCMデータを書き込む。
1100 this.Buffer = new SecondarySoundBuffer( DirectSound, new SoundBufferDescription()
1102 Format = this._Format,
1104 BufferBytes = nPCMサイズbyte,
1106 this.Buffer.Write( byArrWAVファイルイメージ, nPCMデータの先頭インデックス, nPCMサイズbyte, 0, LockFlags.None );
1110 this.eデバイス種別 = ESoundDeviceType.DirectSound;
1111 this.DirectSoundBufferFlags = flags;
1112 this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;
1113 this.DirectSound = DirectSound;
1116 this.nオリジナルの周波数 = wfx.SampleRate;
1117 n総演奏時間ms = (int) ( ( (double) nPCMサイズbyte ) / ( this._Format.AverageBytesPerSecond * 0.001 ) );
1120 CSound.listインスタンス.Add( this );
1123 #region [ DTXMania用の変換 ]
1125 public void tサウンドを破棄する( CSound cs )
1129 public void t再生を開始する()
1134 public void t再生を開始する( bool bループする )
1140 Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_SAMPLE_LOOP, BASSFlag.BASS_SAMPLE_LOOP );
1144 Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_DEFAULT, BASSFlag.BASS_DEFAULT );
1148 tサウンドを再生する( bループする );
1150 public void t再生を停止する()
1155 public void t再生を一時停止する()
1160 public void t再生を再開する( long t ) // ★★★★★★★★★★★★★★★★★★★★★★★★★★★★
1162 Debug.WriteLine( "t再生を再開する(long " + t + ")" );
1171 if ( this.bBASSサウンドである )
1173 bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PAUSED ) &
1174 ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) > 0 );
1179 return ( this.n一時停止回数 > 0 );
1187 if ( this.eデバイス種別 == ESoundDeviceType.DirectSound )
1189 return ( ( this.Buffer.Status & (int) BufferStatus.Playing ) != (int) BufferStatus.None );
1193 // 基本的にはBASS_ACTIVE_PLAYINGなら再生中だが、最後まで再生しきったchannelも
1194 // BASS_ACTIVE_PLAYINGのままになっているので、小細工が必要。
1195 bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PLAYING );
1196 if ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) >= nBytes )
1204 //public lint t時刻から位置を返す( long t )
1206 // double num = ( n時刻 * this.db再生速度 ) * this.db周波数倍率;
1207 // return (int) ( ( num * 0.01 ) * this.nSamplesPerSecond );
1217 public void t解放する( bool _bインスタンス削除 )
1219 if ( this.bBASSサウンドである ) // stream数の削減用
1221 tBASSサウンドをミキサーから削除する();
1222 _cbEndofStream = null;
1223 //_cbStreamXA = null;
1224 CSound管理.nStreams--;
1226 bool bManagedも解放する = true;
1227 bool bインスタンス削除 = _bインスタンス削除; // CSoundの再初期化時は、インスタンスは存続する。
1228 this.Dispose( bManagedも解放する, bインスタンス削除 );
1229 //Debug.WriteLine( "Disposed: " + _bインスタンス削除 + " : " + Path.GetFileName( this.strファイル名 ) );
1231 public void tサウンドを再生する()
1233 tサウンドを再生する( false );
1235 public void tサウンドを再生する( bool bループする )
1237 if ( this.bBASSサウンドである ) // BASSサウンド時のループ処理は、t再生を開始する()側に実装。ここでは「bループする」は未使用。
1239 //Debug.WriteLine( "再生中?: " + System.IO.Path.GetFileName(this.strファイル名) + " status=" + BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) + " current=" + BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) + " nBytes=" + nBytes );
1240 bool b = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );
1243 //Debug.WriteLine( "再生しようとしたが、Mixerに登録されていなかった: " + Path.GetFileName( this.strファイル名 ) + ", stream#=" + this.hBassStream + ", ErrCode=" + Bass.BASS_ErrorGetCode() );
1245 bool bb = tBASSサウンドをミキサーに追加する();
1248 Debug.WriteLine( "Mixerへの登録に失敗: " + Path.GetFileName( this.strファイル名 ) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );
1252 //Debug.WriteLine( "Mixerへの登録に成功: " + Path.GetFileName( this.strファイル名 ) + ": " + Bass.BASS_ErrorGetCode() );
1254 //this.t再生位置を先頭に戻す();
1256 bool bbb = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );
1259 Debug.WriteLine("更に再生に失敗: " + Path.GetFileName(this.strファイル名) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );
1263 // Debug.WriteLine("再生成功(ミキサー追加後) : " + Path.GetFileName(this.strファイル名));
1268 //Debug.WriteLine( "再生成功: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" );
1271 else if( this.bDirectSoundである )
1273 PlayFlags pf = ( bループする ) ? PlayFlags.Looping : PlayFlags.None;
1274 this.Buffer.Play( 0, pf );
1277 public void tサウンドを先頭から再生する()
1282 public void tサウンドを停止してMixerからも削除する()
1284 tサウンドを停止する( false );
1287 tBASSサウンドをミキサーから削除する();
1290 public void tサウンドを停止する()
1292 tサウンドを停止する( false );
1294 public void tサウンドを停止する( bool pause )
1296 if( this.bBASSサウンドである )
1298 //Debug.WriteLine( "停止: " + System.IO.Path.GetFileName( this.strファイル名 ) + " status=" + BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) + " current=" + BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) + " nBytes=" + nBytes );
1299 BassMix.BASS_Mixer_ChannelPause( this.hBassStream );
1302 // tBASSサウンドをミキサーから削除する(); // PAUSEと再生停止を区別できるようにすること!!
1305 else if( this.bDirectSoundである )
1313 // WASAPI/ASIOとDirectSoundを同時使用すると、Bufferがlostしてここで例外発生する。→ catchして無視する。
1314 // DTXCからDTXManiaを呼び出すと、DTXC終了時にこの現象が発生する。
1320 public void t再生位置を先頭に戻す()
1322 if( this.bBASSサウンドである )
1324 BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, 0 );
1327 else if( this.bDirectSoundである )
1329 this.Buffer.CurrentPosition = 0;
1332 public void t再生位置を変更する( long n位置ms )
1334 if( this.bBASSサウンドである )
1339 b = BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, Bass.BASS_ChannelSeconds2Bytes( this.hBassStream, n位置ms * this.db周波数倍率 * this.db再生速度 / 1000.0 ), BASSMode.BASS_POS_BYTES );
1341 catch( Exception e )
1343 Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + e.ToString() + ": " + n位置ms + "ms" );
1349 BASSError be = Bass.BASS_ErrorGetCode();
1350 Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + be.ToString() + ": " + n位置ms + "MS" );
1353 //if ( this.n総演奏時間ms > 5000 )
1355 // Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seeked to " + n位置ms + "ms = " + Bass.BASS_ChannelSeconds2Bytes( this.hBassStream, n位置ms * this.db周波数倍率 * this.db再生速度 / 1000.0 ) );
1358 else if( this.bDirectSoundである )
1360 int n位置sample = (int) ( this._Format.SampleRate * n位置ms * 0.001 * _db周波数倍率 * _db再生速度 ); // #30839 2013.2.24 yyagi; add _db周波数倍率 and _db再生速度
1363 this.Buffer.CurrentPosition = n位置sample * this._Format.BlockAlign;
1365 catch ( Exception e )
1367 Trace.TraceError( "{0}: Seek error: {1}", Path.GetFileName( this.strファイル名 ), n位置ms, e.Message );
1369 //if ( this.n総演奏時間ms > 5000 )
1371 // Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seeked to " + n位置ms + "ms = " + n位置sample );
1378 /// <param name="n位置byte"></param>
1379 /// <param name="db位置ms"></param>
1380 public void t再生位置を取得する( out long n位置byte, out double db位置ms )
1382 if ( this.bBASSサウンドである )
1384 n位置byte = BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream );
1385 db位置ms = Bass.BASS_ChannelBytes2Seconds( this.hBassStream, n位置byte );
1387 else if ( this.bDirectSoundである )
1389 this.Buffer.GetCurrentPosition( out int pos, out _ );
1390 n位置byte = (long) pos;
1391 db位置ms = n位置byte / this._Format.SampleRate / 0.001 / _db周波数倍率 / _db再生速度;
1401 public static void tすべてのサウンドを初期状態に戻す()
1403 foreach ( var sound in CSound.listインスタンス )
1405 sound.t解放する( false );
1408 public static void tすべてのサウンドを再構築する( ISoundDevice device )
1410 if( CSound.listインスタンス.Count == 0 )
1414 // サウンドを再生する際にインスタンスリストも更新されるので、配列にコピーを取っておき、リストはクリアする。
1416 var sounds = CSound.listインスタンス.ToArray();
1417 CSound.listインスタンス.Clear();
1420 // 配列に基づいて個々のサウンドを作成する。
1422 for( int i = 0; i < sounds.Length; i++ )
1424 switch( sounds[ i ].e作成方法 )
1429 string strファイル名 = sounds[i].strファイル名;
1430 CSound.EInstType eInstType = sounds[i].eInstType;
1431 sounds[i].Dispose(true, false);
1432 device.tサウンドを作成する(strファイル名, ref sounds[i], eInstType);
1436 #region [ WAVファイルイメージから ]
1437 case E作成方法.WAVファイルイメージから:
1438 if( sounds[ i ].bBASSサウンドである )
1440 byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;
1441 CSound.EInstType eInstType = sounds[ i ].eInstType;
1442 sounds[ i ].Dispose( true, false );
1443 device.tサウンドを作成する( byArrWaveファイルイメージ, ref sounds[ i ], eInstType );
1445 else if( sounds[ i ].bDirectSoundである )
1447 byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;
1448 var flags = sounds[ i ].DirectSoundBufferFlags;
1449 CSound.EInstType eInstType = sounds[ i ].eInstType;
1450 sounds[ i ].Dispose( true, false );
1451 ( (CSoundDeviceDirectSound) device ).tサウンドを作成する( byArrWaveファイルイメージ, flags, ref sounds[ i ], eInstType );
1459 #region [ Dispose-Finalizeパターン実装 ]
1461 public void Dispose()
1463 this.Dispose( true, true );
1464 GC.SuppressFinalize( this );
1466 protected void Dispose( bool bManagedも解放する, bool bインスタンス削除 )
1468 if( this.bBASSサウンドである )
1470 #region [ ASIO, WASAPI の解放 ]
1472 if ( _hTempoStream != 0 )
1474 BassMix.BASS_Mixer_ChannelRemove( this._hTempoStream );
1475 Bass.BASS_StreamFree( this._hTempoStream );
1477 BassMix.BASS_Mixer_ChannelRemove( this._hBassStream );
1478 Bass.BASS_StreamFree( this._hBassStream );
1479 this.hBassStream = -1;
1480 this._hBassStream = -1;
1481 this._hTempoStream = 0;
1488 //int freeIndex = -1;
1490 //if ( CSound.listインスタンス != null )
1492 // freeIndex = CSound.listインスタンス.IndexOf( this );
1493 // if ( freeIndex == -1 )
1495 // Debug.WriteLine( "ERR: freeIndex==-1 : Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );
1499 if( this.eデバイス種別 == ESoundDeviceType.DirectSound )
1501 #region [ DirectSound の解放 ]
1503 if( this.Buffer != null )
1511 // 演奏終了後、長時間解放しないでいると、たまに AccessViolationException が発生することがある。
1513 C共通.tDisposeする( ref this.Buffer );
1519 if( this.e作成方法 == E作成方法.WAVファイルイメージから &&
1520 this.eデバイス種別 != ESoundDeviceType.DirectSound ) // DirectSound は hGC 未使用。
1522 if ( this.hGC != null && this.hGC.IsAllocated )
1525 this.hGC = default( GCHandle );
1528 if ( this.byArrWAVファイルイメージ != null )
1530 this.byArrWAVファイルイメージ = null;
1537 // CSound.listインスタンス.RemoveAt( freeIndex );
1541 // Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );
1543 bool b = CSound.listインスタンス.Remove( this ); // これだと、Clone()したサウンドのremoveに失敗する
1546 Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );
1554 this.Dispose( false, true );
1559 #region [ protected ]
1561 protected enum E作成方法 { ファイルから, WAVファイルイメージから, Unknown }
1562 protected E作成方法 e作成方法 = E作成方法.Unknown;
1563 protected ESoundDeviceType eデバイス種別 = ESoundDeviceType.Unknown;
1564 public string strファイル名 = null;
1565 protected byte[] byArrWAVファイルイメージ = null; // WAVファイルイメージ、もしくはchunkのDATA部のみ
1566 protected GCHandle hGC;
1567 protected int _hTempoStream = 0;
1568 protected int _hBassStream = -1; // ASIO, WASAPI 用
1569 protected int hBassStream = 0; // #31076 2013.4.1 yyagi; プロパティとして実装すると動作が低速になったため、
1570 // tBASSサウンドを作成する・ストリーム生成後の共通処理()のタイミングと、
1571 // 再生速度を変更したタイミングでのみ、
1572 // hBassStreamを更新するようにした。
1576 // if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する
1578 // return _hTempoStream;
1582 // return _hBassStream;
1587 // _hBassStream = value;
1590 protected SoundBuffer Buffer = null; // DirectSound 用
1591 protected DirectSound DirectSound;
1592 protected int hMixer = -1; // 設計壊してゴメン Mixerに後で登録するときに使う
1598 private bool bDirectSoundである
1600 get { return ( this.eデバイス種別 == ESoundDeviceType.DirectSound ); }
1602 private bool bBASSサウンドである
1607 this.eデバイス種別 == ESoundDeviceType.ASIO ||
1608 this.eデバイス種別 == ESoundDeviceType.ExclusiveWASAPI ||
1609 this.eデバイス種別 == ESoundDeviceType.SharedWASAPI );
1612 private int _n位置 = 0;
1614 private int _n音量 = 100;
1616 private long nBytes = 0;
1617 private int n一時停止回数 = 0;
1618 private int nオリジナルの周波数 = 0;
1619 private double _db周波数倍率 = 1.0;
1620 private double _db再生速度 = 1.0;
1621 private bool bIs1倍速再生 = true;
1622 private WaveFormat _Format;
1624 private void tBASSサウンドを作成する( string strファイル名, int hMixer, BASSFlag flags )
1626 #region [ xaとwav(RIFF chunked vorbis)に対しては専用の処理をする ]
1627 switch ( Path.GetExtension( strファイル名 ).ToLower() )
1632 tBASSサウンドを作成するXA(strファイル名, hMixer, flags);
1637 Trace.TraceWarning("xaファイルの作成に失敗しました。({0})", e.Message);
1638 Trace.TraceWarning("続けて、他のデコーダでの作成を試みます。");
1641 // XAのデコードに失敗した場合は、続けて中身がoggなwavとしてDirectShowデコーダでのデコードを試みる。
1642 // oggのでコードが成功した場合は、そのままこのブロックの中でBASSサウンドを生成。
1643 // さもなければ、switchの外でbassに任せてデコード。
1644 if (tRIFFchunkedVorbisならDirectShowでDecodeする(strファイル名, ref byArrWAVファイルイメージ))
1646 tBASSサウンドを作成する(byArrWAVファイルイメージ, hMixer, flags);
1652 if ( tRIFFchunkedVorbisならDirectShowでDecodeする( strファイル名, ref byArrWAVファイルイメージ ) )
1654 //tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, flags );
1655 tBASSサウンドを作成する(strファイル名, hMixer, flags);
1665 this.e作成方法 = E作成方法.ファイルから;
1666 this.strファイル名 = strファイル名;
1669 // BASSファイルストリームを作成。
1671 this._hBassStream = Bass.BASS_StreamCreateFile( strファイル名, 0, 0, flags );
1672 if( this._hBassStream == 0 )
1673 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
1675 nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );
1677 tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );
1679 private void tBASSサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, BASSFlag flags )
1681 this.e作成方法 = E作成方法.WAVファイルイメージから;
1682 this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;
1683 this.hGC = GCHandle.Alloc( byArrWAVファイルイメージ, GCHandleType.Pinned ); // byte[] をピン留め
1686 // BASSファイルストリームを作成。
1688 this._hBassStream = Bass.BASS_StreamCreateFile( hGC.AddrOfPinnedObject(), 0, byArrWAVファイルイメージ.Length, flags );
1689 if ( this._hBassStream == 0 )
1690 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
1692 nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );
1694 tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );
1698 /// Decode "RIFF chunked Vorbis" to "raw wave"
1699 /// because BASE.DLL has two problems for RIFF chunked Vorbis;
1700 /// 1. time seek is not fine 2. delay occurs (about 10ms)
1702 /// <param name="strファイル名">wave filename</param>
1703 /// <param name="byArrWAVファイルイメージ">wav file image</param>
1704 /// <returns></returns>
1705 private bool tRIFFchunkedVorbisならDirectShowでDecodeする( string strファイル名, ref byte[] byArrWAVファイルイメージ )
1707 bool bファイルにVorbisコンテナが含まれている = false;
1709 #region [ ファイルがWAVかつ、Vorbisコンテナが含まれているかを調べ、それに該当するなら、DirectShowでデコードする。]
1713 using( var ws = new SoundStream( new FileStream( strファイル名, FileMode.Open ) ) )
1715 if( ws.Format.Encoding == WaveFormatEncoding.OggVorbisMode2Plus ||
1716 ws.Format.Encoding == WaveFormatEncoding.OggVorbisMode3Plus )
1718 Trace.TraceInformation( Path.GetFileName( strファイル名 ) + ": RIFF chunked Vorbis. Decode to raw Wave first, to avoid BASS.DLL troubles" );
1721 CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );
1722 bファイルにVorbisコンテナが含まれている = true;
1726 Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : RIFF chunked Vorbisのデコードに失敗しました。" );
1731 // 以下、SharpDX.Multimedia.SoundStreamの生成に失敗した場合の処置
1732 catch ( InvalidDataException e)
1734 Trace.TraceWarning( "Warning: {0}: デコードに失敗しました。別の方法でデコードします。({1})", Path.GetFileName(strファイル名), e.Message );
1736 catch ( InvalidOperationException e) // RIFF chunked mp3の場合は、ここに来る
1738 // DirectShowでのデコードに失敗したら、次はACMでのデコードを試すことになるため、ここではエラーログを出さない。
1739 Trace.TraceWarning("Warning: {0}: RIFF Chunked MP3はSharpDXで扱えないため、別の方法でデコードします。({1})", Path.GetFileName(strファイル名), e.Message );
1742 catch ( Exception e)
1744 Trace.TraceWarning( "Warning: {0}: 読み込みに失敗しました。別の方法でデコードします。({1})", Path.GetFileName( strファイル名 ), e.Message );
1748 return bファイルにVorbisコンテナが含まれている;
1751 private void tBASSサウンドを作成するXA( string strファイル名, int hMixer, BASSFlag flags )
1753 int nPCMデータの先頭インデックス;
1754 CWin32.WAVEFORMATEX wfx;
1757 tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,
1758 out nPCMデータの先頭インデックス, out totalPCMSize, out wfx, true );
1760 nBytes = totalPCMSize;
1762 this.e作成方法 = E作成方法.WAVファイルイメージから; //.ファイルから; // 再構築時はデコード後のイメージを流用する&Dispose時にhGCを解放する
1763 this.strファイル名 = strファイル名;
1764 this.hGC = GCHandle.Alloc( this.byArrWAVファイルイメージ, GCHandleType.Pinned ); // byte[] をピン留め
1766 //_cbStreamXA = new STREAMPROC( CallbackPlayingXA );
1768 // BASSファイルストリームを作成。
1770 //this.hBassStream = Bass.BASS_StreamCreate( xa.xaheader.nSamplesPerSec, xa.xaheader.nChannels, BASSFlag.BASS_STREAM_DECODE, _myStreamCreate, IntPtr.Zero );
1771 //this._hBassStream = Bass.BASS_StreamCreate( (int) wfx.nSamplesPerSec, (int) wfx.nChannels, BASSFlag.BASS_STREAM_DECODE, _cbStreamXA, IntPtr.Zero );
1773 // StreamCreate()で作成したstreamはseek不可のため、StreamCreateFile()を使う。
1774 this._hBassStream = Bass.BASS_StreamCreateFile( this.hGC.AddrOfPinnedObject(), 0L, totalPCMSize, flags );
1775 if ( this._hBassStream == 0 )
1778 throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_SampleCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
1781 nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );
1784 tBASSサウンドを作成する_ストリーム生成後の共通処理( hMixer );
1788 private void tBASSサウンドを作成する_ストリーム生成後の共通処理( int hMixer )
1790 CSound管理.nStreams++;
1792 // 個々のストリームの出力をテンポ変更のストリームに入力する。テンポ変更ストリームの出力を、Mixerに出力する。
1795 if ( CSound管理.bIsTimeStretch ) // TimeStretchのON/OFFに関わりなく、テンポ変更のストリームを生成する。後からON/OFF切り替え可能とするため。
1796 // ... と思ったが、1サウンド辺り1つのテンポ変更ストリームが存在することになり、
1797 // ミキシング負荷が非常に高くなるため、結局TimeStretch=ONの時のみテンポ変更ストリームを提供することにした。
1799 this._hTempoStream = BassFx.BASS_FX_TempoCreate(this._hBassStream, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_FX_FREESOURCE);
1800 if (this._hTempoStream == 0)
1803 throw new Exception(string.Format("サウンドストリームの生成に失敗しました。(BASS_FX_TempoCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString()));
1807 Bass.BASS_ChannelSetAttribute(this._hTempoStream, BASSAttribute.BASS_ATTRIB_TEMPO_OPTION_USE_QUICKALGO, 1f); // 高速化(音の品質は少し落ちる)
1811 if (_hTempoStream != 0 && !this.bIs1倍速再生) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する
1813 this.hBassStream = _hTempoStream;
1817 this.hBassStream = _hBassStream;
1819 // #32248 再生終了時に発火するcallbackを登録する (演奏終了後に再生終了するチップを非同期的にミキサーから削除するため。)
1820 _cbEndofStream = new SYNCPROC( CallbackEndofStream );
1821 Bass.BASS_ChannelSetSync( hBassStream, BASSSync.BASS_SYNC_END | BASSSync.BASS_SYNC_MIXTIME, 0, _cbEndofStream, IntPtr.Zero );
1825 CSound.listインスタンス.Add( this );
1827 // n総演奏時間の取得; DTXMania用に追加。
1828 double seconds = Bass.BASS_ChannelBytes2Seconds( this._hBassStream, nBytes );
1829 this.n総演奏時間ms = (int) ( seconds * 1000 );
1831 this.hMixer = hMixer;
1833 if ( !Bass.BASS_ChannelGetAttribute( this._hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ref freq ) )
1836 throw new Exception( string.Format( "サウンドストリームの周波数取得に失敗しました。(BASS_ChannelGetAttribute)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
1838 this.nオリジナルの周波数 = (int) freq;
1842 //private int pos = 0;
1843 //private int CallbackPlayingXA( int handle, IntPtr buffer, int length, IntPtr user )
1845 // int bytesread = ( pos + length > Convert.ToInt32( nBytes ) ) ? Convert.ToInt32( nBytes ) - pos : length;
1847 // Marshal.Copy( byArrWAVファイルイメージ, pos, buffer, bytesread );
1848 // pos += bytesread;
1849 // if ( pos >= nBytes )
1851 // // set indicator flag
1852 // bytesread |= (int) BASSStreamProc.BASS_STREAMPROC_END;
1854 // return bytesread;
1857 /// ストリームの終端まで再生したときに呼び出されるコールバック
1859 /// <param name="handle"></param>
1860 /// <param name="channel"></param>
1861 /// <param name="data"></param>
1862 /// <param name="user"></param>
1863 private void CallbackEndofStream( int handle, int channel, int data, IntPtr user ) // #32248 2013.10.14 yyagi
1865 // Trace.TraceInformation( "Callback!(remove): " + Path.GetFileName( this.strファイル名 ) );
1866 if ( b演奏終了後も再生が続くチップである ) // 演奏終了後に再生終了するチップ音のミキサー削除は、再生終了のコールバックに引っ掛けて、自前で行う。
1867 { // そうでないものは、ミキサー削除予定時刻に削除する。
1868 tBASSサウンドをミキサーから削除する( channel );
1874 public bool tBASSサウンドをミキサーから削除する()
1876 return tBASSサウンドをミキサーから削除する( this.hBassStream );
1878 public bool tBASSサウンドをミキサーから削除する( int channel )
1880 #if TEST_MultiThreadedMixer
1881 bool b = BASSThreadedMixerLibraryWrapper.BASS_ThreadedMixer_RemoveSource((IntPtr)this.hMixer, channel );
1882 mixingChannel.Remove((IntPtr)this.hMixer);
1884 bool b = BassMix.BASS_Mixer_ChannelRemove(channel);
1888 Interlocked.Decrement( ref CSound管理.nMixing );
1889 // Debug.WriteLine( "Removed: " + Path.GetFileName( this.strファイル名 ) + " (" + channel + ")" + " MixedStreams=" + CSound管理.nMixing );
1896 #if TEST_MultiThreadedMixer
1897 private List<IntPtr> mixingChannel = new List<IntPtr>();
1900 public bool tBASSサウンドをミキサーに追加する()
1902 #if TEST_MultiThreadedMixer
1903 if (!mixingChannel.Contains((IntPtr)this.hMixer))
1905 if ( BassMix.BASS_Mixer_ChannelGetMixer( hBassStream ) == 0 )
1908 BASSFlag bf = BASSFlag.BASS_SPEAKER_FRONT | BASSFlag.BASS_MIXER_NORAMPIN | BASSFlag.BASS_MIXER_PAUSE;
1909 Interlocked.Increment( ref CSound管理.nMixing );
1911 // preloadされることを期待して、敢えてflagからはBASS_MIXER_PAUSEを外してAddChannelした上で、すぐにPAUSEする
1912 // -> ChannelUpdateでprebufferできることが分かったため、BASS_MIXER_PAUSEを使用することにした
1914 #if TEST_MultiThreadedMixer
1915 bool b1 = BASSThreadedMixerLibraryWrapper.BASS_ThreadedMixer_AddSource( (IntPtr)this.hMixer, this.hBassStream, IntPtr.Zero );
1916 mixingChannel.Add((IntPtr)this.hMixer);
1918 bool b1 = BassMix.BASS_Mixer_StreamAddChannel(this.hMixer, this.hBassStream, bf);
1920 //bool b2 = BassMix.BASS_Mixer_ChannelPause( this.hBassStream );
1921 t再生位置を先頭に戻す(); // StreamAddChannelの後で再生位置を戻さないとダメ。逆だと再生位置が変わらない。
1922 //Trace.TraceInformation( "Add Mixer: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" + " MixedStreams=" + CSound管理.nMixing );
1923 Bass.BASS_ChannelUpdate( this.hBassStream, 0 ); // pre-buffer
1929 #region [ tオンメモリ方式でデコードする() ]
1930 public void tオンメモリ方式でデコードする( string strファイル名, out byte[] buffer,
1931 out int nPCMデータの先頭インデックス, out int totalPCMSize, out CWin32.WAVEFORMATEX wfx,
1932 bool bIntegrateWaveHeader )
1934 nPCMデータの先頭インデックス = 0;
1935 //int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 ); // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );
1937 SoundDecoder sounddecoder;
1939 if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 )
1941 sounddecoder = new Cxa();
1943 else if ( String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 )
1945 sounddecoder = new Cmp3ogg();
1947 else if ( String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 )
1949 sounddecoder = new Cmp3ogg();
1953 throw new NotImplementedException();
1956 if ( !File.Exists( strファイル名 ) )
1958 throw new Exception( string.Format( "ファイルが見つかりませんでした。({0})", strファイル名 ) );
1960 int ret = sounddecoder.Open( strファイル名 );
1963 throw new Exception( string.Format( "Open() に失敗しました。({0})({1})", ret, strファイル名 ) );
1965 wfx = sounddecoder.wfx;
1966 if ( wfx.wFormatTag == 0 ) // 正しく初期化されていない場合
1968 sounddecoder.Close();
1969 throw new Exception( string.Format( "GetFormat() に失敗しました。({0})", strファイル名 ) );
1971 totalPCMSize = (int) sounddecoder.nTotalPCMSize; // tデコード後のサイズを調べる()で既に取得済みの値を流用する。ms単位の高速化だが、チップ音がたくさんあると塵積で結構効果がある
1972 if ( totalPCMSize == 0 )
1974 sounddecoder.Close();
1975 throw new Exception( string.Format( "GetTotalPCMSize() に失敗しました。({0})", strファイル名 ) );
1977 totalPCMSize += ( ( totalPCMSize % 2 ) != 0 ) ? 1 : 0;
1978 int wavheadersize = ( bIntegrateWaveHeader ) ? 44 : 0;
1979 buffer = new byte[ wavheadersize + totalPCMSize ];
1982 if (sounddecoder.Decode(ref buffer, wavheadersize) < 0)
1985 throw new Exception( string.Format( "デコードに失敗しました。({0})", strファイル名 ) );
1987 if ( bIntegrateWaveHeader )
1991 int wfx拡張領域_Length = 0;
1992 var ms = new MemoryStream();
1993 var bw = new BinaryWriter( ms );
1994 bw.Write( new byte[] { 0x52, 0x49, 0x46, 0x46 } ); // 'RIFF'
1995 bw.Write( (UInt32) totalPCMSize + 44 - 8 ); // ファイルサイズ - 8 [byte];
1996 bw.Write( new byte[] { 0x57, 0x41, 0x56, 0x45 } ); // 'WAVE'
1997 bw.Write( new byte[] { 0x66, 0x6D, 0x74, 0x20 } ); // 'fmt '
1998 bw.Write( (UInt32) ( 16 + ( ( wfx拡張領域_Length > 0 ) ? ( 2/*sizeof(WAVEFORMATEX.cbSize)*/ + wfx拡張領域_Length ) : 0 ) ) ); // fmtチャンクのサイズ[byte]
1999 bw.Write( (UInt16) wfx.wFormatTag ); // フォーマットID(リニアPCMなら1)
2000 bw.Write( (UInt16) wfx.nChannels ); // チャンネル数
2001 bw.Write( (UInt32) wfx.nSamplesPerSec ); // サンプリングレート
2002 bw.Write( (UInt32) wfx.nAvgBytesPerSec ); // データ速度
2003 bw.Write( (UInt16) wfx.nBlockAlign ); // ブロックサイズ
2004 bw.Write( (UInt16) wfx.wBitsPerSample ); // サンプルあたりのビット数
2005 //if ( wfx拡張領域_Length > 0 )
2007 // bw.Write( (UInt16) wfx拡張領域.Length ); // 拡張領域のサイズ[byte]
2008 // bw.Write( wfx拡張領域 ); // 拡張データ
2010 bw.Write( new byte[] { 0x64, 0x61, 0x74, 0x61 } ); // 'data'
2011 //int nDATAチャンクサイズ位置 = (int) ms.Position;
2012 bw.Write( (UInt32) totalPCMSize ); // dataチャンクのサイズ[byte]
2014 byte[] bs = ms.ToArray();
2019 for ( int i = 0; i < bs.Length; i++ )
2021 buffer[ i ] = bs[ i ];
2025 totalPCMSize += wavheadersize;
2026 nPCMデータの先頭インデックス = wavheadersize;
2028 //string filename = Path.GetFileName(this.strファイル名);
2029 //var st = new FileStream(filename+".wav", FileMode.Create);
2030 //st.Write(buffer, 0, buffer.Length);
2032 //Trace.TraceInformation($"wrote ({filename}.wav)" + buffer.Length);
2038 sounddecoder.Close();
2039 sounddecoder = null;