OSDN Git Service

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