OSDN Git Service

#39490 環境によっては同じ名前のWASAPIデバイスが複数定義されている場合に対応。 実際に利用可能なWASAPIデバイスのみ利用する。
[dtxmania/dtxmania.git] / FDK / コード / 03.サウンド / CSoundDeviceWASAPI.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Text;
4 using System.Diagnostics;
5 using Un4seen.Bass;
6 using Un4seen.BassWasapi;
7 using Un4seen.Bass.AddOn.Mix;
8 using Un4seen.Bass.Misc;
9
10 namespace FDK
11 {
12         public class CSoundDeviceWASAPI : ISoundDevice
13         {
14                 // プロパティ
15
16                 public ESoundDeviceType e出力デバイス
17                 {
18                         get;
19                         protected set;
20                 }
21                 public long n実出力遅延ms
22                 {
23                         get;
24                         protected set;
25                 }
26                 public long n実バッファサイズms
27                 {
28                         get;
29                         protected set;
30                 }
31
32                 public string strRecordFileType = null;
33
34                 // CSoundTimer 用に公開しているプロパティ
35
36                 public long n経過時間ms
37                 {
38                         get;
39                         protected set;
40                 }
41                 public long n経過時間を更新したシステム時刻ms
42                 {
43                         get;
44                         protected set;
45                 }
46                 public CTimer tmシステムタイマ
47                 {
48                         get;
49                         protected set;
50                 }
51
52                 public enum Eデバイスモード { 排他, 共有 }
53
54                 public int nMasterVolume
55                 {
56                         get
57                         {
58                                 float f音量 = 0.0f;
59                                 //if ( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, ref f音量 ) == -1 )
60                                 //    return 100;
61                                 //bool b = Bass.BASS_ChannelGetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 );
62                                 bool b = Bass.BASS_ChannelGetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 );
63                                 if ( !b )
64                                 {
65                                         BASSError be = Bass.BASS_ErrorGetCode();
66                                         Trace.TraceInformation( "WASAPI Master Volume Get Error: " + be.ToString() );
67                                 }
68                                 else
69                                 {
70                                         Trace.TraceInformation( "WASAPI Master Volume Get Success: " + (f音量 * 100) );
71
72                                 }
73                                 return (int) ( f音量 * 100 );
74                         }
75                         set
76                         {
77                                 // bool b = Bass.BASS_SetVolume( value / 100.0f );
78                                 // →Exclusiveモード時は無効
79
80 //                              bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_VOL_SESSION, (float) ( value / 100 ) );
81 //                              bool b = BassWasapi.BASS_WASAPI_SetVolume( BASSWASAPIVolume.BASS_WASAPI_CURVE_WINDOWS, (float) ( value / 100 ) );
82                                 bool b = Bass.BASS_ChannelSetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, (float) ( value / 100.0 ) );
83                                 // If you would like to have a volume control in exclusive mode too, and you're using the BASSmix add-on,
84                                 // you can adjust the source's BASS_ATTRIB_VOL setting via BASS_ChannelSetAttribute.
85                                 // しかし、hMixerに対するBASS_ChannelSetAttribute()でBASS_ATTRIB_VOLを変更: なぜか出力音量に反映されず
86
87                                 // Bass_SetVolume(): BASS_ERROR_NOTAVIL ("no sound" deviceには適用不可)
88
89                                 // Mixer_ChannelSetEnvelope():
90
91                                 //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, (float) value ) };
92                                 //bool b = BassMix.BASS_Mixer_ChannelSetEnvelope( this.hMixer, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );
93                                 //bool b = Bass.BASS_ChannelSetAttribute( this.hMixer, BASSAttribute.BASS_ATTRIB_VOL, value / 100.0f );
94                                 if ( !b )
95                                 {
96                                         BASSError be = Bass.BASS_ErrorGetCode();
97                                         Trace.TraceInformation( "WASAPI Master Volume Set Error: " + be.ToString() );
98                                 }
99                                 else
100                                 {
101                                         // int n = this.nMasterVolume;  
102                                         // Trace.TraceInformation( "WASAPI Master Volume Set Success: " + value );
103
104                                 }
105                         }
106                 }
107
108                 public string strDefaultSoundDeviceBusType {
109                         get;
110                         protected set;
111                 }
112
113                 
114                 // メソッド
115
116                 /// <summary>
117                 /// WASAPIの初期化
118                 /// </summary>
119                 /// <param name="mode"></param>
120                 /// <param name="n希望バッファサイズms">WASAPIのサウンドバッファサイズ</param>
121                 /// <param name="n更新間隔ms">サウンドバッファの更新間隔</param>
122                 public CSoundDeviceWASAPI( Eデバイスモード mode, long n希望バッファサイズms, long n更新間隔ms, string strRecordFileType, string strEncoderPath )
123                 {
124                         // 初期化。
125
126                         Trace.TraceInformation( "BASS (WASAPI{0}) の初期化を開始します。", mode.ToString() );
127
128                         this.e出力デバイス = ESoundDeviceType.Unknown;
129                         this.n実出力遅延ms = 0;
130                         this.n経過時間ms = 0;
131                         this.n経過時間を更新したシステム時刻ms = CTimer.n未使用;
132                         this.tmシステムタイマ = new CTimer( CTimer.E種別.MultiMedia );
133                         this.b最初の実出力遅延算出 = true;
134
135                         #region [ BASS registration ]
136                         // BASS.NET ユーザ登録(BASSスプラッシュが非表示になる)。
137
138                         BassNet.Registration( "dtx2013@gmail.com", "2X9181017152222" );
139                         #endregion
140
141                         #region [ BASS Version Check ]
142                         // BASS のバージョンチェック。
143                         int nBASSVersion = Utils.HighWord( Bass.BASS_GetVersion() );
144                         if( nBASSVersion != Bass.BASSVERSION )
145                                 throw new DllNotFoundException( string.Format( "bass.dll のバージョンが異なります({0})。このプログラムはバージョン{1}で動作します。", nBASSVersion, Bass.BASSVERSION ) );
146
147                         int nBASSMixVersion = Utils.HighWord( BassMix.BASS_Mixer_GetVersion() );
148                         if( nBASSMixVersion != BassMix.BASSMIXVERSION )
149                                 throw new DllNotFoundException( string.Format( "bassmix.dll のバージョンが異なります({0})。このプログラムはバージョン{1}で動作します。", nBASSMixVersion, BassMix.BASSMIXVERSION ) );
150
151                         int nBASSWASAPIVersion = Utils.HighWord( BassWasapi.BASS_WASAPI_GetVersion() );
152                         if( nBASSWASAPIVersion != BassWasapi.BASSWASAPIVERSION )
153                                 throw new DllNotFoundException( string.Format( "basswasapi.dll のバージョンが異なります({0})。このプログラムはバージョン{1}で動作します。", nBASSWASAPIVersion, BassWasapi.BASSWASAPIVERSION ) );
154                         #endregion
155
156                         // BASS の設定。
157
158                         this.bIsBASSFree = true;
159                         Debug.Assert( Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 ),            // 0:BASSストリームの自動更新を行わない。(BASSWASAPIから行うため)
160                                 string.Format( "BASS_SetConfig() に失敗しました。[{0}", Bass.BASS_ErrorGetCode() ) );
161
162                         #region [ デバッグ用: BASSデバイスのenumerateと、ログ出力 ]
163                         //Trace.TraceInformation( "BASSデバイス一覧:" );
164                         //int defDevice = -1;
165                         //BASS_DEVICEINFO bdi;
166                         //for ( int n = 0; ( bdi = Bass.BASS_GetDeviceInfo( n ) ) != null; n++ )
167                         //{
168                         //      Trace.TraceInformation( "BASS Device #{0}: {1}: IsDefault={2}, flags={3}, type={4}",
169                         //              n,
170                         //              bdi.name,
171                         //              bdi.IsDefault, bdi.flags.ToString(), bdi.type,ToString()
172                         //      );
173
174                         //      //if ( bdi.IsDefault )
175                         //      //{
176                         //      //      defDevice = n;
177                         //      //      break;
178                         //      //}
179                         //}
180                         #endregion
181
182                         // BASS の初期化。
183
184                         int n周波数 = 48000;   // 仮決め。lデバイス(≠ドライバ)がネイティブに対応している周波数であれば何でもいい?ようだ。BASSWASAPIでデバイスの周波数は変えられる。いずれにしろBASSMXで自動的にリサンプリングされる。
185                                                                 // BASS_Initは、WASAPI初期化の直前に行うよう変更。WASAPIのmix周波数を使って初期化することで、余計なリサンプリング処理を省き高速化するため。
186                                                                 //if( !Bass.BASS_Init( nデバイス, n周波数, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero ) )
187                                                                 //      throw new Exception( string.Format( "BASS (WASAPI) の初期化に失敗しました。(BASS_Init)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );
188
189
190                         #region [ デバッグ用: サウンドデバイスのenumerateと、ログ出力 ]
191                         //(デバッグ用)
192                         Trace.TraceInformation("サウンドデバイス一覧:");
193                         int a;
194                         string strDefaultSoundDeviceName = null;
195                         BASS_DEVICEINFO[] bassDevInfos = Bass.BASS_GetDeviceInfos();
196                         for (a = 0; a < bassDevInfos.GetLength(0); a++)
197                         {
198                                 {
199                                         Trace.TraceInformation("Sound Device #{0}: {1}: IsDefault={2}, isEnabled={3}, flags={4}, id={5}",
200                                                 a,
201                                                 bassDevInfos[a].name,
202                                                 bassDevInfos[a].IsDefault,
203                                                 bassDevInfos[a].IsEnabled,
204                                                 bassDevInfos[a].flags,
205                                                 bassDevInfos[a].id
206                                         );
207                                         if (bassDevInfos[a].IsDefault)
208                                         {
209                                                 // これはOS標準のdefault device。後でWASAPIのdefault deviceと比較する。
210                                                 strDefaultSoundDeviceName = bassDevInfos[a].name;
211                                                 
212                                                 // 以下はOS標準 default deviceのbus type (PNPIDの頭の文字列)。上位側で使用する。
213                                                 string[] s = bassDevInfos[a].id.ToString().ToUpper().Split(new char[] { '#' });
214                                                 if (s != null && s[0] != null)
215                                                 {
216                                                         strDefaultSoundDeviceBusType = s[0];
217                                                 }
218                                         }
219                                 }
220                         }
221                         #endregion
222
223                         // BASS WASAPI の初期化。
224
225                         n周波数 = 0;                 // デフォルトデバイスの周波数 (0="mix format" sample rate)
226                         int nチャンネル数 = 0;    // デフォルトデバイスのチャンネル数 (0="mix format" channels)
227                         this.tWasapiProc = new WASAPIPROC( this.tWASAPI処理 );                // アンマネージに渡す delegate は、フィールドとして保持しておかないとGCでアドレスが変わってしまう。
228
229                         // WASAPIの更新間隔(period)は、バッファサイズにも影響を与える。
230                         // 更新間隔を最小にするには、BassWasapi.BASS_WASAPI_GetDeviceInfo( ndevNo ).minperiod の値を使えばよい。
231                         // これをやらないと、更新間隔ms=6ms となり、バッファサイズを 6ms x 4 = 24msより小さくできない。
232                         #region [ 既定の出力デバイスと設定されているWASAPIデバイスを検索し、更新間隔msを設定できる最小値にする ]
233                         int nDevNo = -1;
234                         BASS_WASAPI_DEVICEINFO deviceInfo;
235                         for ( int n = 0; ( deviceInfo = BassWasapi.BASS_WASAPI_GetDeviceInfo( n ) ) != null; n++ )
236                         {
237                                 // #37940 2018.2.15: BASS_DEVICEINFOとBASS_WASAPI_DEVICEINFOで、IsDefaultとなっているデバイスが異なる場合がある。
238                                 // (WASAPIでIsDefaultとなっているデバイスが正しくない場合がある)
239                                 // そのため、BASS_DEVICEでIsDefaultとなっているものを探し、それと同じ名前のWASAPIデバイスを使用する。
240                                 // #39490 2019.8.19: 更に、環境によっては同じ名前のWASAPIデバイスが複数定義されている場合があるため、
241                                 // 実際に利用可能なWASAPIデバイスのみに対象を絞り込む。
242                                 // (具体的には、defperiod, minperiod, mixchans, mixfreqがすべて0のデバイスは使用不可のため
243                                 //  これらが0でないものを選択する)
244                                 //if ( deviceInfo.IsDefault )
245                                 if ( deviceInfo.name == strDefaultSoundDeviceName && deviceInfo.mixfreq > 0 )
246                                 {
247                                         nDevNo = n;
248 #region [ 既定の出力デバイスの情報を表示 ]
249 Trace.TraceInformation("WASAPI Device #{0}: {1}: IsDefault={2}, defPeriod={3}s, minperiod={4}s, mixchans={5}, mixfreq={6}",
250         n,
251         deviceInfo.name,
252         deviceInfo.IsDefault, deviceInfo.defperiod, deviceInfo.minperiod, deviceInfo.mixchans, deviceInfo.mixfreq);
253 #endregion
254                                         break;
255                                 }
256                         }
257                         if ( nDevNo != -1 )
258                         {
259                                 Trace.TraceInformation("Start Bass_Init(device=0(fixed value: no sound), deviceInfo.mixfreq=" + deviceInfo.mixfreq +", BASS_DEVICE_DEFAULT, Zero)");
260                                 if ( !Bass.BASS_Init( 0, deviceInfo.mixfreq, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero ) )  // device = 0:"no device": BASS からはデバイスへアクセスさせない。アクセスは BASSWASAPI アドオンから行う。
261                                         throw new Exception( string.Format( "BASS (WASAPI{0}) の初期化に失敗しました。(BASS_Init)[{1}]", mode.ToString(), Bass.BASS_ErrorGetCode().ToString() ) );
262
263                                 // Trace.TraceInformation( "Selected Default WASAPI Device: {0}", deviceInfo.name );
264                                 // Trace.TraceInformation( "MinPeriod={0}, DefaultPeriod={1}", deviceInfo.minperiod, deviceInfo.defperiod );
265
266                                 // n更新間隔ms = ( mode == Eデバイスモード.排他 )? Convert.ToInt64(Math.Ceiling(deviceInfo.minperiod * 1000.0f)) : Convert.ToInt64(Math.Ceiling(deviceInfo.defperiod * 1000.0f));
267                                 // 更新間隔として、WASAPI排他時はminperiodより大きい最小のms値を、WASAPI共有時はdefperiodより大きい最小のms値を用いる
268                                 // Win10では、更新間隔がminperiod以下だと、確実にBASS_ERROR_UNKNOWNとなる。
269
270                                 //if ( n希望バッファサイズms <= 0 || n希望バッファサイズms < n更新間隔ms + 1 )
271                                 //{
272                                 //      n希望バッファサイズms = n更新間隔ms + 1; // 2013.4.25 #31237 yyagi; バッファサイズ設定の完全自動化。更新間隔=バッファサイズにするとBASS_ERROR_UNKNOWNになるので+1する。
273                                 //}
274                         }
275                         else
276                         {
277                                 Trace.TraceError( "Error: Default WASAPI Device is not found." );
278                         }
279                         #endregion
280
281                         #region [ デバッグ用: WASAPIデバイスのenumerateと、ログ出力 ]
282                         //(デバッグ用)
283                         Trace.TraceInformation("WASAPIデバイス一覧:");
284                         //int a, count = 0;
285                         BASS_WASAPI_DEVICEINFO wasapiDevInfo;
286                         for (a = 0; (wasapiDevInfo = BassWasapi.BASS_WASAPI_GetDeviceInfo(a)) != null; a++)
287                         {
288                                 if ((wasapiDevInfo.flags & BASSWASAPIDeviceInfo.BASS_DEVICE_INPUT) == 0 // device is an output device (not input)
289                                                 && (wasapiDevInfo.flags & BASSWASAPIDeviceInfo.BASS_DEVICE_ENABLED) != 0) // and it is enabled
290                                 {
291                                         Trace.TraceInformation("WASAPI Device #{0}: {1}: IsDefault={2}, defPeriod={3}s, minperiod={4}s, mixchans={5}, mixfreq={6}",
292                                                 a,
293                                                 wasapiDevInfo.name,
294                                                 wasapiDevInfo.IsDefault, wasapiDevInfo.defperiod, wasapiDevInfo.minperiod, wasapiDevInfo.mixchans, wasapiDevInfo.mixfreq);
295                                 }
296                         }
297                 #endregion
298
299                 Retry:
300                         var flags = (mode == Eデバイスモード.排他) ? BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EXCLUSIVE : BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT;
301                         //var flags = ( mode == Eデバイスモード.排他 ) ? BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EVENT | BASSWASAPIInit.BASS_WASAPI_EXCLUSIVE : BASSWASAPIInit.BASS_WASAPI_AUTOFORMAT | BASSWASAPIInit.BASS_WASAPI_EVENT;
302                         if ( COS.bIsWin7OrLater() && CSound管理.bSoundUpdateByEventWASAPI )
303                         {
304                                 flags |= BASSWASAPIInit.BASS_WASAPI_EVENT;      // Win7以降の場合は、WASAPIをevent drivenで動作させてCPU負荷減、レイテインシ改善
305                         }
306                         n周波数 = deviceInfo.mixfreq;
307                         nチャンネル数 = deviceInfo.mixchans;
308
309                         // 更新間隔として、WASAPI排他時はminperiodより大きい最小のms値を、WASAPI共有時はdefperiodより大きい最小のms値を用いる
310                         // (Win10のlow latency modeではない前提でまずは設定値を決める)
311                         float fPeriod = (mode == Eデバイスモード.排他) ? deviceInfo.minperiod : deviceInfo.defperiod;
312
313                         Trace.TraceInformation("arg: n希望バッファサイズms=" + n希望バッファサイズms);
314                         Trace.TraceInformation("arg: n更新間隔ms=" + n更新間隔ms);
315                         Trace.TraceInformation("fPeriod = " + fPeriod + " (排他時: minperiod, 共有時: defperiod。Win10 low latency audio考慮前)");
316
317                         float f更新間隔sec = (n更新間隔ms > 0) ? (n更新間隔ms / 1000.0f) : fPeriod;
318                         if (f更新間隔sec < fPeriod)
319                         {
320                                 f更新間隔sec = fPeriod;     // Win10では、更新間隔がminperiod以下だと、確実にBASS_ERROR_UNKNOWNとなる。
321                         }
322                         Trace.TraceInformation("f更新間隔sec=" + f更新間隔sec);
323                         // バッファサイズは、更新間隔より大きくする必要あり。(イコールだと、WASAPI排他での初期化時にBASS_ERROR_UNKNOWNとなる)
324                         // そのため、最低でも、更新間隔より1ms大きく設定する。
325                         float f希望バッファサイズsec = (n希望バッファサイズms > 0) ? (n希望バッファサイズms / 1000.0f) : fPeriod + 0.001f;
326                         if (f希望バッファサイズsec < fPeriod)
327                         {
328                                 f希望バッファサイズsec = fPeriod + 0.001f;
329                         }
330                         // WASAPI排他時は、バッファサイズは更新間隔の4倍必要(event driven時は2倍)
331                         if (mode == Eデバイスモード.排他)
332                         {
333                                 if ( (flags & BASSWASAPIInit.BASS_WASAPI_EVENT) != BASSWASAPIInit.BASS_WASAPI_EVENT &&
334                                         f希望バッファサイズsec < f更新間隔sec * 4)
335                                 {
336                                         f希望バッファサイズsec = f更新間隔sec * 4;
337                                 }
338                                 else if ((flags & BASSWASAPIInit.BASS_WASAPI_EVENT) == BASSWASAPIInit.BASS_WASAPI_EVENT &&
339                                         f希望バッファサイズsec < f更新間隔sec * 2)
340                                 {
341                                         f希望バッファサイズsec = f更新間隔sec * 2;
342                                 }
343                         }
344                         else
345                         if (COS.bIsWin10OrLater() && (mode == Eデバイスモード.共有))           // Win10 low latency shared mode support
346                         {
347                                 // バッファ自動設定をユーザーが望む場合は、periodを最小値にする。さもなくば、バッファサイズとしてユーザーが指定した値を、periodとして用いる。
348                                 if (n希望バッファサイズms == 0)
349                                 {
350                                         f更新間隔sec = deviceInfo.minperiod;
351                                 }
352                                 else
353                                 {
354                                         f更新間隔sec = n希望バッファサイズms / 1000.0f;
355                                         if (f更新間隔sec < deviceInfo.minperiod)
356                                         {
357                                                 f更新間隔sec = deviceInfo.minperiod;
358                                         }
359                                 }
360                                 f希望バッファサイズsec = 0.0f;
361                         }
362
363                         Trace.TraceInformation("f希望バッファサイズsec=" + f希望バッファサイズsec + ", f更新間隔sec=" + f更新間隔sec + ": Win10 low latency audio 考慮後");
364
365                         Trace.TraceInformation("Start Bass_Wasapi_Init(device=" + nDevNo + ", freq=" + n周波数 + ", nchans=" + nチャンネル数 + ", flags=" + flags + "," +
366                                 " buffer=" + f希望バッファサイズsec + ", period=" + f更新間隔sec + ")" );
367                         if (BassWasapi.BASS_WASAPI_Init(nDevNo, n周波数, nチャンネル数, flags, f希望バッファサイズsec, f更新間隔sec, this.tWasapiProc, IntPtr.Zero))
368                         {
369                                         if ( mode == Eデバイスモード.排他 )
370                                 {
371                                         #region [ 排他モードで作成成功。]
372                                         //-----------------
373                                         this.e出力デバイス = ESoundDeviceType.ExclusiveWASAPI;
374
375                                         nDevNo = BassWasapi.BASS_WASAPI_GetDevice();
376                                         deviceInfo = BassWasapi.BASS_WASAPI_GetDeviceInfo( nDevNo );
377                                         var wasapiInfo = BassWasapi.BASS_WASAPI_GetInfo();
378                                         int n1サンプルのバイト数 = 2 * wasapiInfo.chans;       // default;
379                                         switch( wasapiInfo.format )             // BASS WASAPI で扱うサンプルはすべて 32bit float で固定されているが、デバイスはそうとは限らない。
380                                         {
381                                                 case BASSWASAPIFormat.BASS_WASAPI_FORMAT_8BIT: n1サンプルのバイト数 = 1 * wasapiInfo.chans; break;
382                                                 case BASSWASAPIFormat.BASS_WASAPI_FORMAT_16BIT: n1サンプルのバイト数 = 2 * wasapiInfo.chans; break;
383                                                 case BASSWASAPIFormat.BASS_WASAPI_FORMAT_24BIT: n1サンプルのバイト数 = 3 * wasapiInfo.chans; break;
384                                                 case BASSWASAPIFormat.BASS_WASAPI_FORMAT_32BIT: n1サンプルのバイト数 = 4 * wasapiInfo.chans; break;
385                                                 case BASSWASAPIFormat.BASS_WASAPI_FORMAT_FLOAT: n1サンプルのバイト数 = 4 * wasapiInfo.chans; break;
386                                         }
387                                         int n1秒のバイト数 = n1サンプルのバイト数 * wasapiInfo.freq;
388                                         this.n実バッファサイズms = (long) ( wasapiInfo.buflen * 1000.0f / n1秒のバイト数 );
389                                         this.n実出力遅延ms = 0;    // 初期値はゼロ
390                                         Trace.TraceInformation( "使用デバイス: #" + nDevNo + " : " + deviceInfo.name + ", flags=" + deviceInfo.flags );
391                                         Trace.TraceInformation("BASS を初期化しました。(WASAPI排他モード, {0}Hz, {1}ch, フォーマット:{2}, バッファ{3}bytes [{4}ms(希望{5}ms)], 更新間隔{6}ms)",
392                                                 wasapiInfo.freq,
393                                                 wasapiInfo.chans,
394                                                 wasapiInfo.format.ToString(),
395                                                 wasapiInfo.buflen,
396                                                 n実バッファサイズms.ToString(),
397                                                 (f希望バッファサイズsec * 1000).ToString(),  //n希望バッファサイズms.ToString(),
398                                                 (f更新間隔sec * 1000).ToString()            //n更新間隔ms.ToString()
399                                         );
400                                         Trace.TraceInformation( "デバイスの最小更新時間={0}ms, 既定の更新時間={1}ms", deviceInfo.minperiod * 1000, deviceInfo.defperiod * 1000 );
401                                         this.bIsBASSFree = false;
402                                         //-----------------
403                                         #endregion
404                                 }
405                                 else
406                                 {
407                                         #region [ 共有モードで作成成功。]
408                                         //-----------------
409                                         this.e出力デバイス = ESoundDeviceType.SharedWASAPI;
410
411                                         var wasapiInfo = BassWasapi.BASS_WASAPI_GetInfo();
412                                         int n1サンプルのバイト数 = 2 * wasapiInfo.chans; // default;
413                                         int n1秒のバイト数 = n1サンプルのバイト数 * wasapiInfo.freq;
414                                         this.n実バッファサイズms = (long)(wasapiInfo.buflen * 1000.0f / n1秒のバイト数);
415                                         this.n実出力遅延ms = 0;  // 初期値はゼロ
416                                         var devInfo = BassWasapi.BASS_WASAPI_GetDeviceInfo( BassWasapi.BASS_WASAPI_GetDevice() );       // 共有モードの場合、更新間隔はデバイスのデフォルト値に固定される。
417                                         //Trace.TraceInformation( "BASS を初期化しました。(WASAPI共有モード, 希望バッファサイズ={0}ms, 更新間隔{1}ms)", n希望バッファサイズms, devInfo.defperiod * 1000.0f );
418                                         Trace.TraceInformation("使用デバイス: #" + nDevNo + " : " + deviceInfo.name + ", flags=" + deviceInfo.flags);
419                                         Trace.TraceInformation("BASS を初期化しました。(WASAPI共有モード, {0}Hz, {1}ch, フォーマット:{2}, バッファ{3}bytes [{4}ms(希望{5}ms)], 更新間隔{6}ms)",
420                                                 wasapiInfo.freq,
421                                                 wasapiInfo.chans,
422                                                 wasapiInfo.format.ToString(),
423                                                 wasapiInfo.buflen,
424                                                 n実バッファサイズms.ToString(),
425                                                 (f希望バッファサイズsec * 1000).ToString(),  //n希望バッファサイズms.ToString(),
426                                                 (f更新間隔sec * 1000).ToString()            //n更新間隔ms.ToString()
427                                         );
428                                         Trace.TraceInformation("デバイスの最小更新時間={0}ms, 既定の更新時間={1}ms", deviceInfo.minperiod * 1000, deviceInfo.defperiod * 1000);
429                                         this.bIsBASSFree = false;
430                                         //-----------------
431                                         #endregion
432                                 }
433                         }
434                         #region [ #31737 WASAPI排他モードのみ利用可能とし、WASAPI共有モードは使用できないようにするために、WASAPI共有モードでの初期化フローを削除する。 ]
435                         else if (mode == Eデバイスモード.排他)
436                         {
437                                 BASSError errcode = Bass.BASS_ErrorGetCode();
438                                 Trace.TraceInformation("Failed to initialize setting BASS_WASAPI_Init (WASAPI{0}): [{1}]", mode.ToString(), errcode);
439                                 #region [ 排他モードに失敗したのなら共有モードでリトライ。]
440                                 //-----------------
441                                 //      mode = Eデバイスモード.共有;
442                                 //      goto Retry;
443                                 //-----------------
444                                 Bass.BASS_Free();
445                                 this.bIsBASSFree = true;
446                                 throw new Exception(string.Format("BASS (WASAPI{0}) の初期化に失敗しました。(BASS_WASAPI_Init)[{1}]", mode.ToString(), errcode));
447                                 #endregion
448                         }
449                         #endregion
450                         else
451                         {
452                                 #region [ それでも失敗したら例外発生。]
453                                 //-----------------
454                                 BASSError errcode = Bass.BASS_ErrorGetCode();
455                                 Bass.BASS_Free();
456                                 this.bIsBASSFree = true;
457                                 throw new Exception( string.Format( "BASS (WASAPI{0}) の初期化に失敗しました。(BASS_WASAPI_Init)[{1}]", mode.ToString(), errcode ) );
458                                 //-----------------
459                                 #endregion
460                         }
461 #if TEST_MultiThreadedMixer
462                         //LoadLibraryに失敗する・・・
463                         //BASSThreadedMixerLibraryWrapper.InitBASSThreadedMixerLibrary();
464 #endif
465
466
467                         // WASAPI出力と同じフォーマットを持つ BASS ミキサーを作成。
468                         // 1つのまとめとなるmixer (hMixer) と、そこにつなぐ複数の楽器別mixer (hMixer _forChips)を作成。
469
470                         //Debug.Assert( Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_MIXER_BUFFER, 5 ),          // バッファ量を最大量の5にする
471                         //      string.Format( "BASS_SetConfig(CONFIG_MIXER_BUFFER) に失敗しました。[{0}", Bass.BASS_ErrorGetCode() ) );
472
473                         var info = BassWasapi.BASS_WASAPI_GetInfo();
474 #if TEST_MultiThreadedMixer
475                         this.hMixer = BASSThreadedMixerLibraryWrapper.BASS_ThreadedMixer_Create(
476                                 info.freq,
477                                 info.chans,
478                                 (int)(BASSFlag.BASS_MIXER_NONSTOP | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_POSEX),
479                                 out hMixerThreaded
480                         );
481 #else
482                         this.hMixer = BassMix.BASS_Mixer_StreamCreate(
483                                 info.freq,
484                                 info.chans,
485                                 BASSFlag.BASS_MIXER_NONSTOP | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_POSEX);    // デコードのみ=発声しない。WASAPIに出力されるだけ。
486 #endif
487                         if (this.hMixer == 0 )
488                         {
489                                 BASSError errcode = Bass.BASS_ErrorGetCode();
490                                 BassWasapi.BASS_WASAPI_Free();
491                                 Bass.BASS_Free();
492                                 this.bIsBASSFree = true;
493                                 throw new Exception( string.Format( "BASSミキサ(mixing)の作成に失敗しました。[{0}]", errcode ) );
494                         }
495
496
497                         for (int i = 0; i <= (int)CSound.EInstType.Unknown; i++)
498                         {
499 #if TEST_MultiThreadedMixer
500                                 this.hMixer_Chips[i] = BASSThreadedMixerLibraryWrapper.BASS_ThreadedMixer_Create(
501                                         info.freq,
502                                         info.chans,
503                                         (int)(BASSFlag.BASS_MIXER_NONSTOP | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_POSEX),
504                                         out this.hMixerThreaded_Chips[i]
505                                 );    // デコードのみ=発声しない。WASAPIに出力されるだけ。
506 #else
507                                 this.hMixer_Chips[ i ] = BassMix.BASS_Mixer_StreamCreate(
508                                         info.freq,
509                                         info.chans,
510                                         BASSFlag.BASS_MIXER_NONSTOP | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_POSEX);    // デコードのみ=発声しない。WASAPIに出力されるだけ。
511 #endif
512                                 if (this.hMixer_Chips[ i ] == 0)
513                                 {
514                                         BASSError errcode = Bass.BASS_ErrorGetCode();
515                                         BassWasapi.BASS_WASAPI_Free();
516                                         Bass.BASS_Free();
517                                         this.bIsBASSFree = true;
518                                         throw new Exception(string.Format("BASSミキサ(楽器[{1}]ごとのmixing)の作成に失敗しました。[{0}]", errcode, i));
519                                 }
520
521                                 // Mixerのボリューム設定
522                                 Bass.BASS_ChannelSetAttribute(this.hMixer_Chips[i], BASSAttribute.BASS_ATTRIB_VOL, CSound管理.nMixerVolume[i] / 100.0f);
523                                 //Trace.TraceInformation("Vol{0}: {1}", i, CSound管理.nMixerVolume[i]);
524
525 #if TEST_MultiThreadedMixer
526                                 bool b1 = BASSThreadedMixerLibraryWrapper.BASS_ThreadedMixer_AddSource(this.hMixerThreaded, this.hMixer_Chips[i], IntPtr.Zero);
527 #else
528                                 bool b1 = BassMix.BASS_Mixer_StreamAddChannel(this.hMixer, this.hMixer_Chips[i], BASSFlag.BASS_DEFAULT);
529 #endif
530                                 if (!b1)
531                                 {
532                                         BASSError errcode = Bass.BASS_ErrorGetCode();
533                                         BassWasapi.BASS_WASAPI_Free();
534                                         Bass.BASS_Free();
535                                         this.bIsBASSFree = true;
536                                         throw new Exception(string.Format("個別BASSミキサ({1}}から(mixing)への接続に失敗しました。[{0}]", errcode, i));
537                                 };
538
539                         }
540
541                         // BASS ミキサーの1秒あたりのバイト数を算出。
542
543                         var mixerInfo = Bass.BASS_ChannelGetInfo( this.hMixer );
544                         long nミキサーの1サンプルあたりのバイト数 = mixerInfo.chans * 4;       // 4 = sizeof(FLOAT)
545                         this.nミキサーの1秒あたりのバイト数 = nミキサーの1サンプルあたりのバイト数 * mixerInfo.freq;
546
547
548
549                         // 単純に、hMixerの音量をMasterVolumeとして制御しても、
550                         // ChannelGetData()の内容には反映されない。
551                         // そのため、もう一段mixerを噛ませて、一段先のmixerからChannelGetData()することで、
552                         // hMixerの音量制御を反映させる。
553                         this.hMixer_DeviceOut = BassMix.BASS_Mixer_StreamCreate(
554                                 info.freq,
555                                 info.chans,
556                                 BASSFlag.BASS_MIXER_NONSTOP | BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_MIXER_POSEX );   // デコードのみ=発声しない。WASAPIに出力されるだけ。
557                         if ( this.hMixer_DeviceOut == 0 )
558                         {
559                                 BASSError errcode = Bass.BASS_ErrorGetCode();
560                                 BassWasapi.BASS_WASAPI_Free();
561                                 Bass.BASS_Free();
562                                 this.bIsBASSFree = true;
563                                 throw new Exception( string.Format( "BASSミキサ(最終段)の作成に失敗しました。[{0}]", errcode ) );
564                         }
565
566                         {
567                                 bool b1 = BassMix.BASS_Mixer_StreamAddChannel( this.hMixer_DeviceOut, this.hMixer, BASSFlag.BASS_DEFAULT );
568                                 if ( !b1 )
569                                 {
570                                         BASSError errcode = Bass.BASS_ErrorGetCode();
571                                         BassWasapi.BASS_WASAPI_Free();
572                                         Bass.BASS_Free();
573                                         this.bIsBASSFree = true;
574                                         throw new Exception( string.Format( "BASSミキサ(最終段とmixing)の接続に失敗しました。[{0}]", errcode ) );
575                                 };
576                         }
577
578
579                         // 録音設定(DTX2WAV)
580                         if (!string.IsNullOrEmpty(strRecordFileType))
581                         {
582                                 switch (strRecordFileType.ToUpper())
583                                 {
584                                         case "WAV":
585                                                 {
586                                                         var e = new EncoderWAV(this.hMixer_DeviceOut);
587                                                         //e.WAV_EncoderType = BASSChannelType.BASS_CTYPE_STREAM_WAV_PCM;
588                                                         encoder = e;
589                                                 }
590                                                 break;
591                                         case "OGG":
592                                                 {
593                                                         var e = new EncoderOGG(this.hMixer_DeviceOut);
594                                                         e.EncoderDirectory = strEncoderPath;
595                                                         e.OGG_UseQualityMode = true;
596                                                         e.OGG_Quality = (float)CSound管理.nBitrate;
597                                                         //e.OGG_Bitrate = 128;
598                                                         //e.OGG_MinBitrate = 0;
599                                                         //e.OGG_MaxBitrate = 0;
600
601                                                         encoder = e;
602                                                 }
603                                                 break;
604                                         case "MP3":
605                                                 {
606                                                         var e = new EncoderLAME(this.hMixer_DeviceOut);
607                                                         e.EncoderDirectory = strEncoderPath;
608                                                         e.LAME_UseVBR = false;
609                                                         e.LAME_Bitrate = CSound管理.nBitrate;
610                                                         encoder = e;
611                                                 }
612                                                 break;
613                                         default:
614                                                 encoder = new EncoderWAV(this.hMixer_DeviceOut);
615                                                 break;
616                                 }
617                                 encoder.InputFile = null;    //STDIN
618                                 encoder.OutputFile = CSound管理.strRecordOutFilename;
619                                 encoder.UseAsyncQueue = true;
620                                 encoder.Start(null, IntPtr.Zero, true);     // PAUSE状態で録音開始
621                         }
622                         //Bass.BASS_ChannelSetAttribute(this.hMixer_DeviceOut, BASSAttribute.BASS_ATTRIB_VOL, 0.10f);
623                         //Bass.BASS_SetConfig(BASSConfig.BASS_CONFIG_GVOL_SAMPLE, 1000);
624                         //Bass.BASS_SetVolume(0.1f);
625
626                         // 出力を開始。
627                         BassWasapi.BASS_WASAPI_Start();
628                 }
629
630 #region [録音開始]
631                 public bool tStartRecording()
632                 {
633                         return encoder.Pause(false);
634                 }
635 #endregion
636 #region [録音終了]
637                 public bool tStopRecording()
638                 {
639                         return encoder.Stop(true);
640                 }
641 #endregion
642
643 #region [ tサウンドを作成する() ]
644                 public CSound tサウンドを作成する(string strファイル名)
645                 {
646                         return tサウンドを作成する( strファイル名, CSound.EInstType.Unknown );
647                 }
648                 public CSound tサウンドを作成する( string strファイル名, CSound.EInstType eInstType )
649                 {
650                         var sound = new CSound();
651 #if TEST_MultiThreadedMixer
652                         int hmixer = (int)hMixerThreaded_Chips[ (int)eInstType ];
653 #else
654                         int hmixer = hMixer_Chips[(int)eInstType];
655 #endif
656                         sound.tWASAPIサウンドを作成する( strファイル名, hmixer, this.e出力デバイス, eInstType );
657                         return sound;
658                 }
659                 public CSound tサウンドを作成する( byte[] byArrWAVファイルイメージ )
660                 {
661                         return tサウンドを作成する( byArrWAVファイルイメージ, CSound.EInstType.Unknown);
662                 }
663                 public CSound tサウンドを作成する( byte[] byArrWAVファイルイメージ, CSound.EInstType eInstType )
664                 {
665                         var sound = new CSound();
666 #if TEST_MultiThreadedMixer
667                         int hmixer = (int)hMixerThreaded_Chips[(int)eInstType];
668 #else
669                         int hmixer = hMixer_Chips[(int)eInstType];
670 #endif
671                         sound.tWASAPIサウンドを作成する( byArrWAVファイルイメージ, hmixer, this.e出力デバイス, eInstType );
672                         return sound;
673                 }
674                 public void tサウンドを作成する( string strファイル名, ref CSound sound, CSound.EInstType eInstType )
675                 {
676 #if TEST_MultiThreadedMixer
677                         int hmixer = (int)hMixerThreaded_Chips[(int)eInstType];
678 #else
679                         int hmixer = hMixer_Chips[(int)eInstType];
680 #endif
681                         sound.tWASAPIサウンドを作成する( strファイル名, hmixer, this.e出力デバイス, eInstType );
682                 }
683                 public void tサウンドを作成する( byte[] byArrWAVファイルイメージ, ref CSound sound, CSound.EInstType eInstType)
684                 {
685 #if TEST_MultiThreadedMixer
686                         int hmixer = (int)hMixerThreaded_Chips[(int)eInstType];
687 #else
688                         int hmixer = hMixer_Chips[(int)eInstType];
689 #endif
690                         sound.tWASAPIサウンドを作成する( byArrWAVファイルイメージ, hmixer, this.e出力デバイス, eInstType );
691                 }
692 #endregion
693
694 #region [ Dispose-Finallizeパターン実装 ]
695                 //-----------------
696                 public void Dispose()
697                 {
698                         this.Dispose( true );
699                         GC.SuppressFinalize( this );
700                 }
701                 protected void Dispose( bool bManagedDispose )
702                 {
703                         if ( encoder != null )
704                         {
705                                 encoder.Stop();  // finish
706                                 encoder.Dispose();
707                                 encoder = null;
708                         }
709                         this.e出力デバイス = ESoundDeviceType.Unknown;        // まず出力停止する(Dispose中にクラス内にアクセスされることを防ぐ)
710
711                         if ( this.hMixer_DeviceOut != 0 )
712                         {
713                                 BassMix.BASS_Mixer_ChannelPause(this.hMixer_DeviceOut);
714                                 Bass.BASS_StreamFree(this.hMixer_DeviceOut);
715                                 this.hMixer_DeviceOut = 0;
716                         }
717                         if (this.hMixer_Record != 0)
718                         {
719                                 BassMix.BASS_Mixer_ChannelPause(this.hMixer_Record);
720                                 Bass.BASS_StreamFree(this.hMixer_Record);
721                                 this.hMixer_Record = 0;
722                         }
723
724                         if (hMixer != 0)
725                         {
726                                 BassMix.BASS_Mixer_ChannelPause(this.hMixer_Record);
727                                 Bass.BASS_StreamFree(this.hMixer);
728                         }
729                         if (this.hMixer_Chips != null)
730                         {
731                                 for (int i = 0; i <= (int)CSound.EInstType.Unknown; i++)
732                                 {
733                                         if (this.hMixer_Chips[i] != 0)
734                                         {
735                                                 // Mixerにinputされるchannelsがfreeされると、Mixerへのinputも自動でremoveされる。
736                                                 // 従い、ここでは、mixer本体をfreeするだけでよい
737                                                 BassMix.BASS_Mixer_ChannelPause(this.hMixer_Chips[i]);
738                                                 Bass.BASS_StreamFree(this.hMixer_Chips[i]);
739                                                 this.hMixer_Chips[i] = 0;
740                                         }
741                                 }
742                         }
743 #if TEST_MultiThreadedMixer
744                         //BASSThreadedMixerLibraryWrapper.FreeBASSThreadedMixerLibrary();               
745 #endif
746
747                         if ( !this.bIsBASSFree )
748                         {
749                                 BassWasapi.BASS_WASAPI_Free();  // システムタイマより先に呼び出すこと。(tWasapi処理() の中でシステムタイマを参照してるため)
750                                 Bass.BASS_Free();
751                         }
752                         if( bManagedDispose )
753                         {
754                                 C共通.tDisposeする( this.tmシステムタイマ );
755                                 this.tmシステムタイマ = null;
756                         }
757                 }
758                 ~CSoundDeviceWASAPI()
759                 {
760                         this.Dispose( false );
761                 }
762                 //-----------------
763 #endregion
764
765                 protected int hMixer = 0;
766                 protected int hMixer_DeviceOut = 0;
767                 protected int hMixer_Record = 0;
768                 protected int[] hMixer_Chips = new int[(int)CSound.EInstType.Unknown + 1];  //DTX2WAV対応 BGM, SE, Drums...を別々のmixerに入れて、個別に音量変更できるようにする
769
770 #if TEST_MultiThreadedMixer
771                 protected IntPtr hMixerThreaded = IntPtr.Zero;
772                 protected IntPtr[] hMixerThreaded_Chips = new IntPtr[(int)CSound.EInstType.Unknown + 1];
773 #endif
774                 protected BaseEncoder encoder;
775                 protected int stream;
776                 protected WASAPIPROC tWasapiProc = null;
777
778                 protected int tWASAPI処理( IntPtr buffer, int length, IntPtr user )
779                 {
780                         // BASSミキサからの出力データをそのまま WASAPI buffer へ丸投げ。
781
782                         int num = Bass.BASS_ChannelGetData( this.hMixer_DeviceOut, buffer, length );        // num = 実際に転送した長さ
783                         //int num = BassMix.BASS_Mixer_ChannelGetData(this.hMixer_DeviceOut, buffer, length);      // これだと動作がめちゃくちゃ重くなる
784                         if ( num == -1 ) num = 0;
785
786
787                         // 経過時間を更新。
788                         // データの転送差分ではなく累積転送バイト数から算出する。
789
790                         int n未再生バイト数 = BassWasapi.BASS_WASAPI_GetData( null, (int) BASSData.BASS_DATA_AVAILABLE );        // 誤差削減のため、必要となるギリギリ直前に取得する。
791                         this.n経過時間ms = ( this.n累積転送バイト数 - n未再生バイト数 ) * 1000 / this.nミキサーの1秒あたりのバイト数;
792                         this.n経過時間を更新したシステム時刻ms = this.tmシステムタイマ.nシステム時刻ms;
793
794                         // 実出力遅延を更新。
795                         // 未再生バイト数の平均値。
796
797                         long n今回の遅延ms = n未再生バイト数 * 1000 / this.nミキサーの1秒あたりのバイト数;
798                         this.n実出力遅延ms = ( this.b最初の実出力遅延算出 ) ? n今回の遅延ms : ( this.n実出力遅延ms + n今回の遅延ms ) / 2;
799                         this.b最初の実出力遅延算出 = false;
800
801                         
802                         // 経過時間を更新後に、今回分の累積転送バイト数を反映。
803                         
804                         this.n累積転送バイト数 += num;
805                         return num;
806                 }
807
808                 private long nミキサーの1秒あたりのバイト数 = 0;
809                 private long n累積転送バイト数 = 0;
810                 private bool b最初の実出力遅延算出 = true;
811                 private bool bIsBASSFree = true;
812         }
813 }