OSDN Git Service

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