OSDN Git Service

Merge branch 'feature/37178_プロジェクトとソリューションファイルの英語化' into develop
[dtxmania/dtxmania.git] / FDK / コード / 03.サウンド / CSoundDeviceDirectSound.cs
diff --git a/FDK/コード/03.サウンド/CSoundDeviceDirectSound.cs b/FDK/コード/03.サウンド/CSoundDeviceDirectSound.cs
new file mode 100644 (file)
index 0000000..38f535e
--- /dev/null
@@ -0,0 +1,290 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using SharpDX;
+using SharpDX.DirectSound;
+
+namespace FDK
+{
+       public class CSoundDeviceDirectSound : ISoundDevice
+       {
+               // プロパティ
+
+               public ESoundDeviceType e出力デバイス
+               {
+                       get;
+                       protected set;
+               }
+               public long n実出力遅延ms
+               {
+                       get;
+                       protected set;
+               }
+               public long n実バッファサイズms
+               {
+                       get;
+                       protected set;
+               }
+
+               public static readonly BufferFlags DefaultFlags = BufferFlags.Defer | BufferFlags.GetCurrentPosition2 | BufferFlags.GlobalFocus | BufferFlags.ControlVolume | BufferFlags.ControlPan | BufferFlags.ControlFrequency;
+
+               // CSoundTimer 用に公開しているプロパティ
+
+               public long n経過時間ms
+               {
+                       get
+                       {
+                               if ( ctimer != null )
+                               {
+                                       this.sd経過時間計測用サウンドバッファ.DirectSoundBuffer.GetCurrentPosition( out int n現在位置, out _ );
+                                       long n現在のシステム時刻ms = this.tmシステムタイマ.nシステム時刻ms;
+
+
+                                       // ループ回数を調整。
+
+                                       long nシステム時刻での間隔ms = n現在のシステム時刻ms - this.n前に経過時間を測定したシステム時刻ms;
+
+                                       while ( nシステム時刻での間隔ms >= n単位繰り上げ間隔ms )              // 前回から単位繰り上げ間隔以上経過してるなら確実にループしている。誤差は大きくないだろうから無視。
+                                       {
+                                               this.nループ回数++;
+                                               nシステム時刻での間隔ms -= n単位繰り上げ間隔ms;
+                                       }
+
+                                       if ( n現在位置 < this.n前回の位置 )                                                    // 単位繰り上げ間隔以内であっても、現在位置が前回より手前にあるなら1回ループしている。
+                                               this.nループ回数++;
+
+
+                                       // 経過時間を算出。
+
+                                       long n経過時間ms = (long) ( ( this.nループ回数 * n単位繰り上げ間隔ms ) + ( n現在位置 * 1000.0 / ( 44100.0 * 2 * 2 ) ) );
+
+
+                                       // 今回の値を次回に向けて保存。
+
+                                       this.n前に経過時間を測定したシステム時刻ms = n現在のシステム時刻ms;
+                                       this.n前回の位置 = n現在位置;
+
+                                       return n経過時間ms;
+                               }
+                               else
+                               {
+                                       long nRet = ctimer.nシステム時刻ms - this.n前に経過時間を測定したシステム時刻ms;
+                                       if ( nRet < 0 ) // カウンタがループしたときは
+                                       {
+                                               nRet = ( ctimer.nシステム時刻 - long.MinValue ) + ( long.MaxValue - this.n前に経過時間を測定したシステム時刻ms ) + 1;
+                                       }
+                                       this.n前に経過時間を測定したシステム時刻ms = ctimer.nシステム時刻ms;
+       
+                                       return nRet;
+                               }
+                       }
+               }
+               public long n経過時間を更新したシステム時刻ms
+               {
+                       get { throw new NotImplementedException(); }
+               }
+               public CTimer tmシステムタイマ
+               {
+                       get;
+                       protected set;
+               }
+
+               public int nMasterVolume
+               {
+                       get
+                       {
+                               return (int) 100;
+                       }
+                       set
+                       {
+                               // 特に何もしない
+                       }
+               }
+
+
+               // メソッド
+
+               public CSoundDeviceDirectSound( IntPtr hWnd, long n遅延時間ms )
+               {
+                       t初期化( hWnd, n遅延時間ms, false );
+               }
+               public CSoundDeviceDirectSound( IntPtr hWnd, long n遅延時間ms, bool bUseOSTimer )
+               {
+                       t初期化( hWnd, n遅延時間ms, bUseOSTimer );
+               }
+               private void t初期化( IntPtr hWnd, long n遅延時間ms, bool bUseOSTimer )
+               {
+                       Trace.TraceInformation( "DirectSound の初期化を開始します。" );
+
+                       this.e出力デバイス = ESoundDeviceType.Unknown;
+                       this.n実バッファサイズms = this.n実出力遅延ms = n遅延時間ms;
+                       this.tmシステムタイマ = new CTimer( CTimer.E種別.MultiMedia );
+
+                       #region [ DirectSound デバイスを作成する。]
+                       //-----------------
+                       this.DirectSound = new DirectSound();   // 失敗したら例外をそのまま発出。
+
+                       // デバイスの協調レベルを設定する。
+
+                       bool priority = true;
+                       try
+                       {
+                               this.DirectSound.SetCooperativeLevel( hWnd, CooperativeLevel.Priority );
+                       }
+                       catch
+                       {
+                               this.DirectSound.SetCooperativeLevel( hWnd, CooperativeLevel.Normal );  // これでも失敗したら例外をそのまま発出。
+                               priority = false;
+                       }
+
+                       // デバイス作成完了。
+
+                       this.e出力デバイス = ESoundDeviceType.DirectSound;
+                       //-----------------
+                       #endregion
+
+                       if ( !bUseOSTimer )
+                       {
+                               #region [ 経過時間計測用サウンドバッファを作成し、ループ再生を開始する。]
+                               //-----------------
+
+                               // 単位繰り上げ間隔[秒]の長さを持つ無音のサウンドを作成。
+
+                               uint nデータサイズbyte = n単位繰り上げ間隔sec * 44100 * 2 * 2;
+                               var ms = new MemoryStream();
+                               var bw = new BinaryWriter( ms );
+                               bw.Write( (uint) 0x46464952 );                                          // 'RIFF'
+                               bw.Write( (uint) ( 44 + nデータサイズbyte - 8 ) );        // ファイルサイズ - 8
+                               bw.Write( (uint) 0x45564157 );                                          // 'WAVE'
+                               bw.Write( (uint) 0x20746d66 );                                          // 'fmt '
+                               bw.Write( (uint) 16 );                                                          // バイト数
+                               bw.Write( (ushort) 1 );                                                         // フォーマットID(リニアPCM)
+                               bw.Write( (ushort) 2 );                                                         // チャンネル数
+                               bw.Write( (uint) 44100 );                                                       // サンプリング周波数
+                               bw.Write( (uint) ( 44100 * 2 * 2 ) );                           // bytes/sec
+                               bw.Write( (ushort) ( 2 * 2 ) );                                         // blockサイズ
+                               bw.Write( (ushort) 16 );                                                        // bit/sample
+                               bw.Write( (uint) 0x61746164 );                                          // 'data'
+                               bw.Write( (uint) nデータサイズbyte );                             // データ長
+                               for ( int i = 0; i < nデータサイズbyte / sizeof( long ); i++ )    // PCMデータ
+                                       bw.Write( (long) 0 );
+                               var byArrWaveFleImage = ms.ToArray();
+                               bw.Close();
+                               ms = null;
+                               bw = null;
+                               this.sd経過時間計測用サウンドバッファ = this.tサウンドを作成する( byArrWaveFleImage );
+                               CSound.listインスタンス.Remove( this.sd経過時間計測用サウンドバッファ );   // 特殊用途なのでインスタンスリストからは除外する。
+
+                               // サウンドのループ再生開始。
+
+                               this.nループ回数 = 0;
+                               this.n前回の位置 = 0;
+                               this.sd経過時間計測用サウンドバッファ.DirectSoundBuffer.Play( 0, PlayFlags.Looping );
+                               this.n前に経過時間を測定したシステム時刻ms = this.tmシステムタイマ.nシステム時刻ms;
+                               //-----------------
+                               #endregion
+                       }
+                       else
+                       {
+                               ctimer = new CTimer( CTimer.E種別.MultiMedia );
+                       }
+                       Trace.TraceInformation( "DirectSound を初期化しました。({0})({1})", ( priority ) ? "Priority" : "Normal", bUseOSTimer? "OStimer" : "FDKtimer" );
+               }
+
+               public CSound tサウンドを作成する( string strファイル名 )
+               {
+                       var sound = new CSound();
+                       sound.tDirectSoundサウンドを作成する( strファイル名, this.DirectSound );
+                       return sound;
+               }
+               public CSound tサウンドを作成する( byte[] byArrWAVファイルイメージ )
+               {
+                       var sound = new CSound();
+                       sound.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, this.DirectSound );
+                       return sound;
+               }
+               public CSound tサウンドを作成する( byte[] byArrWAVファイルイメージ, BufferFlags flags )
+               {
+                       var sound = new CSound();
+                       sound.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, this.DirectSound, flags );
+                       return sound;
+               }
+
+               // 既存のインスタンス(生成直後 or Dispose済み)に対してサウンドを生成する。
+               public void tサウンドを作成する( string strファイル名, ref CSound sound )
+               {
+                       sound.tDirectSoundサウンドを作成する( strファイル名, this.DirectSound );
+               }
+               public void tサウンドを作成する( byte[] byArrWAVファイルイメージ, ref CSound sound )
+               {
+                       sound.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, this.DirectSound );
+               }
+               public void tサウンドを作成する( byte[] byArrWAVファイルイメージ, BufferFlags flags, ref CSound sound )
+               {
+                       sound.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, this.DirectSound, flags );
+               }
+
+               #region [ Dispose-Finallizeパターン実装 ]
+               //-----------------
+               public void Dispose()
+               {
+                       this.Dispose( true );
+                       GC.SuppressFinalize( this );
+               }
+               protected void Dispose( bool bManagedDispose )
+               {
+                       this.e出力デバイス = ESoundDeviceType.Unknown;            // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
+                       if ( bManagedDispose )
+                       {
+                               #region [ 経緯時間計測用サウンドバッファを解放。]
+                               //-----------------
+                               if ( this.sd経過時間計測用サウンドバッファ != null )
+                               {
+                                       this.sd経過時間計測用サウンドバッファ.tサウンドを停止する();
+                                       C共通.tDisposeする( ref this.sd経過時間計測用サウンドバッファ );
+                               }
+                               //-----------------
+                               #endregion
+                               #region [ 単位繰り上げ用スレッド停止。]
+                               //-----------------
+                               if( this.th経過時間測定用スレッド != null )
+                               {
+                                       this.th経過時間測定用スレッド.Abort();
+                                       this.th経過時間測定用スレッド = null;
+                               
+                               }
+                               //-----------------
+                               #endregion
+
+                               C共通.tDisposeする( ref this.DirectSound );
+                               C共通.tDisposeする( this.tmシステムタイマ );
+                       }
+                       if ( ctimer != null )
+                       {
+                               C共通.tDisposeする( ref this.ctimer );
+                       }
+               }
+               ~CSoundDeviceDirectSound()
+               {
+                       this.Dispose( false );
+               }
+               //-----------------
+               #endregion
+
+               protected DirectSound DirectSound = null;
+               protected CSound sd経過時間計測用サウンドバッファ = null;
+               protected Thread th経過時間測定用スレッド = null;
+//             protected AutoResetEvent autoResetEvent = new AutoResetEvent( false );
+               protected const uint n単位繰り上げ間隔sec = 1;  // [秒]
+               protected const uint n単位繰り上げ間隔ms = n単位繰り上げ間隔sec * 1000; // [ミリ秒]
+               protected int nループ回数 = 0;
+
+               private long n前に経過時間を測定したシステム時刻ms = CTimer.n未使用;
+               private int n前回の位置 = 0;
+
+               private CTimer ctimer = null;
+       }
+}