OSDN Git Service

#39490 環境によっては同じ名前のWASAPIデバイスが複数定義されている場合に対応。 実際に利用可能なWASAPIデバイスのみ利用する。
[dtxmania/dtxmania.git] / FDK / コード / 03.サウンド / CSoundTimer.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Threading;
5 using System.Diagnostics;
6
7 namespace FDK
8 {
9         public class CSoundTimer : CTimerBase
10         {
11                 public override long nシステム時刻ms
12                 {
13                         get
14                         {
15                                 if( this.Device.e出力デバイス == ESoundDeviceType.ExclusiveWASAPI || 
16                                         this.Device.e出力デバイス == ESoundDeviceType.SharedWASAPI ||
17                                         this.Device.e出力デバイス == ESoundDeviceType.ASIO )
18                                 {
19                                         // BASS 系の ISoundDevice.n経過時間ms はオーディオバッファの更新間隔ずつでしか更新されないため、単にこれを返すだけではとびとびの値になる。
20                                         // そこで、更新間隔の最中に呼ばれた場合は、システムタイマを使って補間する。
21                                         // この場合の経過時間との誤差は更新間隔以内に収まるので問題ないと判断する。
22                                         // ただし、ASIOの場合は、転送byte数から時間算出しているため、ASIOの音声合成処理の負荷が大きすぎる場合(処理時間が実時間を超えている場合)は
23                                         // 動作がおかしくなる。(具体的には、ここで返すタイマー値の逆行が発生し、スクロールが巻き戻る)
24                                         // この場合の対策は、ASIOのバッファ量を増やして、ASIOの音声合成処理の負荷を下げること。
25
26                                         if ( this.Device.n経過時間を更新したシステム時刻ms == CTimer.n未使用 )        // #33890 2014.5.27 yyagi
27                                         {
28                                                 // 環境によっては、ASIOベースの演奏タイマーが動作する前(つまりASIOのサウンド転送が始まる前)に
29                                                 // DTXデータの演奏が始まる場合がある。
30                                                 // その場合、"this.Device.n経過時間を更新したシステム時刻" が正しい値でないため、
31                                                 // 演奏タイマの値が正しいものとはならない。そして、演奏タイマーの動作が始まると同時に、
32                                                 // 演奏タイマの値がすっ飛ぶ(極端な負の値になる)ため、演奏のみならず画面表示もされない状態となる。
33                                                 // (画面表示はタイマの値に連動して行われるが、0以上のタイマ値に合わせて動作するため、
34                                                 //  不の値が来ると画面に何も表示されなくなる)
35
36                                                 // そこで、演奏タイマが動作を始める前(this.Device.n経過時間を更新したシステム時刻ms == CTimer.n未使用)は、
37                                                 // 補正部分をゼロにして、n経過時間msだけを返すようにする。
38                                                 // こうすることで、演奏タイマが動作を始めても、破綻しなくなる。
39                                                 return this.Device.n経過時間ms;
40                                         }
41                                         else
42                                         {
43                                                 if ( FDK.CSound管理.bUseOSTimer )
44                                                 //if ( true )
45                                                 {
46                                                         return ctDInputTimer.nシステム時刻ms;                             // 仮にCSoundTimerをCTimer相当の動作にしてみた
47                                                 }
48                                                 else
49                                                 {
50                                                         return this.Device.n経過時間ms
51                                                                 + ( this.Device.tmシステムタイマ.nシステム時刻ms - this.Device.n経過時間を更新したシステム時刻ms );
52                                                 }
53                                         }
54                                 }
55                                 else if( this.Device.e出力デバイス == ESoundDeviceType.DirectSound )
56                                 {
57                                         //return this.Device.n経過時間ms;           // #24820 2013.2.3 yyagi TESTCODE DirectSoundでスクロールが滑らかにならないため、
58                                         return ct.nシステム時刻ms;                                // 仮にCSoundTimerをCTimer相当の動作にしてみた
59                                 }
60                                 return CTimerBase.n未使用;
61                         }
62                 }
63
64                 public CSoundTimer( ISoundDevice device )
65                 {
66                         this.Device = device;
67
68                         if ( this.Device.e出力デバイス != ESoundDeviceType.DirectSound )
69                         {
70                                 TimerCallback timerDelegate = new TimerCallback( SnapTimers );  // CSoundTimerをシステム時刻に変換するために、
71                                 timer = new Timer( timerDelegate, null, 0, 1000 );                              // CSoundTimerとCTimerを両方とも走らせておき、
72                                 ctDInputTimer = new CTimer( CTimer.E種別.MultiMedia );                        // 1秒に1回時差を測定するようにしておく
73                         }
74                         else                                                                                                                            // TESTCODE DirectSound時のみ、CSoundTimerでなくCTimerを使う
75                         {
76                             ct = new CTimer( CTimer.E種別.MultiMedia );
77                         }
78                 }
79         
80                 private void SnapTimers(object o)       // 1秒に1回呼び出され、2つのタイマー間の現在値をそれぞれ保持する。
81                 {
82                         if ( this.Device.e出力デバイス != ESoundDeviceType.DirectSound )
83                         {
84                                 try
85                                 {
86                                         this.nDInputTimerCounter = this.ctDInputTimer.nシステム時刻ms;
87                                         this.nSoundTimerCounter = this.nシステム時刻ms;
88                                         //Debug.WriteLine( "BaseCounter: " + nDInputTimerCounter + ", " + nSoundTimerCounter );
89                                 }
90                                 catch ( Exception e )
91                                 // サウンド設定変更時に、timer.Dispose()した後、timerが実際に停止する前にここに来てしまう場合があり
92                                 // その際にNullReferenceExceptionが発生する
93                                 // timerが実際に停止したことを検出してから次の設定をすべきだが、実装が難しいため、
94                                 // ここで単に例外破棄することで代替する
95                                 {
96                                         Trace.TraceInformation("FDK: CSoundTimer.SnapTimers(): 例外発生しましたが、継続します。" + e.Message );
97                                 }
98                         }
99                 }
100                 public long nサウンドタイマーのシステム時刻msへの変換( long nDInputのタイムスタンプ )
101                 {
102                         return nDInputのタイムスタンプ - this.nDInputTimerCounter + this.nSoundTimerCounter;    // Timer違いによる時差を補正する
103                 }
104
105 #if false
106                 // キーボードイベント(keybd_eventの引数と同様のデータ)
107                 [StructLayout( LayoutKind.Sequential )]
108                 private struct KEYBDINPUT
109                 {
110                         public ushort wVk;
111                         public ushort wScan;
112                         public uint dwFlags;
113                         public uint time;
114                         public IntPtr dwExtraInfo;
115                 };
116                 // 各種イベント(SendInputの引数データ)
117                 [StructLayout( LayoutKind.Sequential )]
118                 private struct INPUT
119                 {
120                         public int type;
121                         public KEYBDINPUT ki;
122                 };
123                 // キー操作、マウス操作をシミュレート(擬似的に操作する)
124                 [DllImport( "user32.dll" )]
125                 private extern static void SendInput(
126                         int nInputs, ref INPUT pInputs, int cbsize );
127
128                 // 仮想キーコードをスキャンコードに変換
129                 [DllImport( "user32.dll", EntryPoint = "MapVirtualKeyA" )]
130                 private extern static int MapVirtualKey(
131                         int wCode, int wMapType );
132                 
133                 [DllImport( "user32.dll" )]
134                 static extern IntPtr GetMessageExtraInfo();
135
136                 private const int INPUT_MOUSE = 0;                  // マウスイベント
137                 private const int INPUT_KEYBOARD = 1;               // キーボードイベント
138                 private const int INPUT_HARDWARE = 2;               // ハードウェアイベント
139                 private const int KEYEVENTF_KEYDOWN = 0x0;          // キーを押す
140                 private const int KEYEVENTF_KEYUP = 0x2;            // キーを離す
141                 private const int KEYEVENTF_EXTENDEDKEY = 0x1;      // 拡張コード
142                 private const int KEYEVENTF_SCANCODE = 0x8;
143                 private const int KEYEVENTF_UNIOCODE = 0x4;
144                 private const int VK_SHIFT = 0x10;                  // SHIFTキー
145
146                 private void pollingSendInput()
147                 {
148 //                      INPUT[] inp = new INPUT[ 2 ];
149                         INPUT inp = new INPUT();
150                         while ( true )
151                         {
152                                 // (2)キーボード(A)を押す
153                                 //inp[0].type = INPUT_KEYBOARD;
154                                 //inp[ 0 ].ki.wVk = ( ushort ) Key.B;
155                                 //inp[ 0 ].ki.wScan = ( ushort ) MapVirtualKey( inp[ 0 ].ki.wVk, 0 );
156                                 //inp[ 0 ].ki.dwFlags = KEYEVENTF_KEYDOWN;
157                                 //inp[ 0 ].ki.dwExtraInfo = IntPtr.Zero;
158                                 //inp[ 0 ].ki.time = 0;
159                                 inp.type = INPUT_KEYBOARD;
160                                 inp.ki.wVk = ( ushort ) Key.B;
161                                 inp.ki.wScan = ( ushort ) MapVirtualKey( inp.ki.wVk, 0 );
162                                 inp.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYDOWN;
163                                 inp.ki.dwExtraInfo = GetMessageExtraInfo();
164                                 inp.ki.time = 0;
165
166                                 //// (3)キーボード(A)を離す
167                                 //inp[ 1 ].type = INPUT_KEYBOARD;
168                                 //inp[ 1 ].ki.wVk = ( short ) Key.B;
169                                 //inp[ 1 ].ki.wScan = ( short ) MapVirtualKey( inp[ 1 ].ki.wVk, 0 );
170                                 //inp[ 1 ].ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP;
171                                 //inp[ 1 ].ki.dwExtraInfo = 0;
172                                 //inp[ 1 ].ki.time = 0;
173
174                                 // キーボード操作実行
175                                 SendInput( 1, ref inp, Marshal.SizeOf( inp ) );
176 Debug.WriteLine( "B" );
177                                 Thread.Sleep( 1000 );
178                         }
179                 }
180 #endif
181                 public override void Dispose()
182                 {
183                         // 特になし; ISoundDevice の解放は呼び出し元で行うこと。
184
185                         //sendinputスレッド削除
186                         if ( timer != null )
187                         {
188                                 timer.Change( System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite );
189                                 // ここで、実際にtimerが停止したことを確認するコードを追加すべきだが、やり方わからず。
190                                 // 代替策として、SnapTimers()中で、例外発生を破棄している。
191                                 timer.Dispose();
192                                 timer = null;
193                         }
194                         if ( ct != null )
195                         {
196                                 ct.t一時停止();
197                                 ct.Dispose();
198                                 ct = null;
199                         }
200                 }
201
202                 public ISoundDevice Device = null;      // debugのため、一時的にprotectedをpublicにする。後で元に戻しておくこと。
203                 //protected Thread thSendInput = null;
204                 //protected Thread thSnapTimers = null;
205                 private CTimer ctDInputTimer = null;
206                 private long nDInputTimerCounter = 0;
207                 private long nSoundTimerCounter = 0;
208                 Timer timer = null;
209
210                 private CTimer ct = null;                                                               // TESTCODE
211         }
212 }