2 using System.Collections.Generic;
4 using System.Threading;
5 using System.Diagnostics;
9 public class CSoundTimer : CTimerBase
11 public override long nシステム時刻ms
15 if( this.Device.e出力デバイス == ESoundDeviceType.ExclusiveWASAPI ||
16 this.Device.e出力デバイス == ESoundDeviceType.SharedWASAPI ||
17 this.Device.e出力デバイス == ESoundDeviceType.ASIO )
19 // BASS 系の ISoundDevice.n経過時間ms はオーディオバッファの更新間隔ずつでしか更新されないため、単にこれを返すだけではとびとびの値になる。
20 // そこで、更新間隔の最中に呼ばれた場合は、システムタイマを使って補間する。
21 // この場合の経過時間との誤差は更新間隔以内に収まるので問題ないと判断する。
22 // ただし、ASIOの場合は、転送byte数から時間算出しているため、ASIOの音声合成処理の負荷が大きすぎる場合(処理時間が実時間を超えている場合)は
23 // 動作がおかしくなる。(具体的には、ここで返すタイマー値の逆行が発生し、スクロールが巻き戻る)
24 // この場合の対策は、ASIOのバッファ量を増やして、ASIOの音声合成処理の負荷を下げること。
26 if ( this.Device.n経過時間を更新したシステム時刻ms == CTimer.n未使用 ) // #33890 2014.5.27 yyagi
28 // 環境によっては、ASIOベースの演奏タイマーが動作する前(つまりASIOのサウンド転送が始まる前)に
29 // DTXデータの演奏が始まる場合がある。
30 // その場合、"this.Device.n経過時間を更新したシステム時刻" が正しい値でないため、
31 // 演奏タイマの値が正しいものとはならない。そして、演奏タイマーの動作が始まると同時に、
32 // 演奏タイマの値がすっ飛ぶ(極端な負の値になる)ため、演奏のみならず画面表示もされない状態となる。
33 // (画面表示はタイマの値に連動して行われるが、0以上のタイマ値に合わせて動作するため、
34 // 不の値が来ると画面に何も表示されなくなる)
36 // そこで、演奏タイマが動作を始める前(this.Device.n経過時間を更新したシステム時刻ms == CTimer.n未使用)は、
37 // 補正部分をゼロにして、n経過時間msだけを返すようにする。
38 // こうすることで、演奏タイマが動作を始めても、破綻しなくなる。
39 return this.Device.n経過時間ms;
43 if ( FDK.CSound管理.bUseOSTimer )
46 return ctDInputTimer.nシステム時刻ms; // 仮にCSoundTimerをCTimer相当の動作にしてみた
50 return this.Device.n経過時間ms
51 + ( this.Device.tmシステムタイマ.nシステム時刻ms - this.Device.n経過時間を更新したシステム時刻ms );
55 else if( this.Device.e出力デバイス == ESoundDeviceType.DirectSound )
57 //return this.Device.n経過時間ms; // #24820 2013.2.3 yyagi TESTCODE DirectSoundでスクロールが滑らかにならないため、
58 return ct.nシステム時刻ms; // 仮にCSoundTimerをCTimer相当の動作にしてみた
60 return CTimerBase.n未使用;
64 public CSoundTimer( ISoundDevice device )
68 if ( this.Device.e出力デバイス != ESoundDeviceType.DirectSound )
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回時差を測定するようにしておく
74 else // TESTCODE DirectSound時のみ、CSoundTimerでなくCTimerを使う
76 ct = new CTimer( CTimer.E種別.MultiMedia );
80 private void SnapTimers(object o) // 1秒に1回呼び出され、2つのタイマー間の現在値をそれぞれ保持する。
82 if ( this.Device.e出力デバイス != ESoundDeviceType.DirectSound )
86 this.nDInputTimerCounter = this.ctDInputTimer.nシステム時刻ms;
87 this.nSoundTimerCounter = this.nシステム時刻ms;
88 //Debug.WriteLine( "BaseCounter: " + nDInputTimerCounter + ", " + nSoundTimerCounter );
91 // サウンド設定変更時に、timer.Dispose()した後、timerが実際に停止する前にここに来てしまう場合があり
92 // その際にNullReferenceExceptionが発生する
93 // timerが実際に停止したことを検出してから次の設定をすべきだが、実装が難しいため、
96 Trace.TraceInformation("FDK: CSoundTimer.SnapTimers(): 例外発生しましたが、継続します。" + e.Message );
100 public long nサウンドタイマーのシステム時刻msへの変換( long nDInputのタイムスタンプ )
102 return nDInputのタイムスタンプ - this.nDInputTimerCounter + this.nSoundTimerCounter; // Timer違いによる時差を補正する
106 // キーボードイベント(keybd_eventの引数と同様のデータ)
107 [StructLayout( LayoutKind.Sequential )]
108 private struct KEYBDINPUT
114 public IntPtr dwExtraInfo;
116 // 各種イベント(SendInputの引数データ)
117 [StructLayout( LayoutKind.Sequential )]
121 public KEYBDINPUT ki;
123 // キー操作、マウス操作をシミュレート(擬似的に操作する)
124 [DllImport( "user32.dll" )]
125 private extern static void SendInput(
126 int nInputs, ref INPUT pInputs, int cbsize );
128 // 仮想キーコードをスキャンコードに変換
129 [DllImport( "user32.dll", EntryPoint = "MapVirtualKeyA" )]
130 private extern static int MapVirtualKey(
131 int wCode, int wMapType );
133 [DllImport( "user32.dll" )]
134 static extern IntPtr GetMessageExtraInfo();
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キー
146 private void pollingSendInput()
148 // INPUT[] inp = new INPUT[ 2 ];
149 INPUT inp = new INPUT();
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();
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;
175 SendInput( 1, ref inp, Marshal.SizeOf( inp ) );
176 Debug.WriteLine( "B" );
177 Thread.Sleep( 1000 );
181 public override void Dispose()
183 // 特になし; ISoundDevice の解放は呼び出し元で行うこと。
188 timer.Change( System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite );
189 // ここで、実際にtimerが停止したことを確認するコードを追加すべきだが、やり方わからず。
190 // 代替策として、SnapTimers()中で、例外発生を破棄している。
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;
210 private CTimer ct = null; // TESTCODE