OSDN Git Service

WASAPI 名前空間を WASAPIold に改名し、新しい WASAPI 名前空間に新しい実装を追加。
authorくまかみ工房 <kumakamikoubou@gmail.com>
Wed, 16 Nov 2016 07:31:33 +0000 (16:31 +0900)
committerくまかみ工房 <kumakamikoubou@gmail.com>
Wed, 16 Nov 2016 07:31:33 +0000 (16:31 +0900)
15 files changed:
FDK24/Extensions.cs [new file with mode: 0644]
FDK24/FDK24.csproj
FDK24/メディア/サウンド/WASAPI/Decoder.cs [new file with mode: 0644]
FDK24/メディア/サウンド/WASAPI/Device.cs
FDK24/メディア/サウンド/WASAPI/Mixer.cs
FDK24/メディア/サウンド/WASAPI/Sound.cs
FDK24/メディア/サウンド/WASAPI/SoundTimer.cs
FDK24/メディア/サウンド/WASAPIold/Device.cs [new file with mode: 0644]
FDK24/メディア/サウンド/WASAPIold/MFAsyncCallback.cs [moved from FDK24/メディア/サウンド/WASAPI/MFAsyncCallback.cs with 95% similarity]
FDK24/メディア/サウンド/WASAPIold/Mixer.cs [new file with mode: 0644]
FDK24/メディア/サウンド/WASAPIold/Sound.cs [new file with mode: 0644]
FDK24/メディア/サウンド/WASAPIold/SoundTimer.cs [new file with mode: 0644]
StrokeStyleT/StrokeStyleT.cs
StrokeStyleT/ステージ/演奏/ドラムサウンド.cs
StrokeStyleT/ステージ/演奏/演奏ステージ.cs

diff --git a/FDK24/Extensions.cs b/FDK24/Extensions.cs
new file mode 100644 (file)
index 0000000..5743118
--- /dev/null
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace FDK
+{
+       public static class Extensions
+       {
+               /// <summary>
+               /// COM オブジェクトの参照カウントを取得して返す。
+               /// </summary>
+               /// <param name="unknownObject">COMオブジェクト。</param>
+               /// <returns>現在の参照カウントの値。</returns>
+               public static int GetRefferenceCount( this SharpDX.IUnknown unknownObject )
+               {
+                       try
+                       {
+                               unknownObject.AddReference();
+                       }
+                       catch( InvalidOperationException )
+                       {
+                               // すでに Dispose されている。
+                               return 0;
+                       }
+
+                       return unknownObject.Release();
+               }
+
+               /// <summary>
+               /// 文字列が Null でも空でもないなら true を返す。
+               /// </summary>
+               public static bool Nullでも空でもない( this string 検査対象 )
+               {
+                       return !string.IsNullOrEmpty( 検査対象 );
+               }
+
+               /// <summary>
+               /// 文字列が Null または空なら true を返す。
+               /// </summary>
+               public static bool Nullまたは空である( this string 検査対象 )
+               {
+                       return string.IsNullOrEmpty( 検査対象 );
+               }
+       }
+}
index e17d277..ada5180 100644 (file)
     <Compile Include="カウンタ\単純増加後反復カウンタ.cs" />
     <Compile Include="カウンタ\定間隔進行.cs" />
     <Compile Include="フォルダ.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\Decoder.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\Device.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\Mixer.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\Sound.cs" />
+    <Compile Include="メディア\サウンド\WASAPI\SoundTimer.cs" />
     <Compile Include="メディア\動画.cs" />
     <Compile Include="メディア\XML.cs" />
     <Compile Include="メディア\テクスチャフォント.cs" />
     <Compile Include="メディア\画像.cs" />
     <Compile Include="メディア\画像フォント.cs" />
     <Compile Include="メディア\矩形リスト.cs" />
-    <Compile Include="メディア\サウンド\WASAPI\Device.cs" />
-    <Compile Include="メディア\サウンド\WASAPI\MFAsyncCallback.cs" />
-    <Compile Include="メディア\サウンド\WASAPI\Mixer.cs" />
-    <Compile Include="メディア\サウンド\WASAPI\Sound.cs" />
-    <Compile Include="メディア\サウンド\WASAPI\SoundTimer.cs" />
+    <Compile Include="メディア\サウンド\WASAPIold\Device.cs" />
+    <Compile Include="メディア\サウンド\WASAPIold\MFAsyncCallback.cs" />
+    <Compile Include="メディア\サウンド\WASAPIold\Mixer.cs" />
+    <Compile Include="メディア\サウンド\WASAPIold\Sound.cs" />
+    <Compile Include="メディア\サウンド\WASAPIold\SoundTimer.cs" />
     <Compile Include="メディア\テクスチャ.cs" />
     <Compile Include="メディア\デバイスリソース.cs" />
     <Compile Include="入力\IInputDevice.cs" />
diff --git a/FDK24/メディア/サウンド/WASAPI/Decoder.cs b/FDK24/メディア/サウンド/WASAPI/Decoder.cs
new file mode 100644 (file)
index 0000000..8db8b27
--- /dev/null
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using CSCore;
+
+namespace FDK.メディア.サウンド.WASAPI
+{
+       /// <summary>
+       ///             指定されたメディアファイル(動画, 音楽)をデコードして、CSCore.IWaveStream オブジェクトを生成する。
+       /// </summary>
+       internal class Decoder : CSCore.IWaveSource
+       {
+               /// <summary>
+               ///             シークは常にサポートする。
+               /// </summary>
+               public bool CanSeek => ( true );
+
+               /// <summary>
+               ///             デコード後のオーディオデータの長さ[byte]。
+               /// </summary>
+               public long Length
+               {
+                       get { return this._EncodedWaveData.Length; }
+               }
+
+               /// <summary>
+               ///             現在の位置。
+               ///             先頭からのオフセット[byte]で表す。
+               /// </summary>
+               public long Position
+               {
+                       get { return this._Position; }
+                       set
+                       {
+                               if( ( 0 > value ) || ( this.Length <= value ) )
+                                       throw new ArgumentOutOfRangeException();
+
+                               this._Position = value;
+                       }
+               }
+
+               /// <summary>
+               ///             デコード後のオーディオデータのフォーマット。
+               /// </summary>
+               public CSCore.WaveFormat WaveFormat
+               {
+                       get;
+                       protected set;
+               }
+
+               /// <summary>
+               ///             メディアファイル(動画、音楽)をデコードする。
+               /// </summary>
+               /// <param name="path">メディアファイル(MediaFoundation でデコードできるもの)</param>
+               /// <param name="waveFormat">デコード先のフォーマット。</param>
+               public Decoder( string path, CSCore.WaveFormat waveFormat )
+               {
+                       // デコード先フォーマットの形式は、IEEE FLOAT しかサポートしない。
+                       if( ( waveFormat.WaveFormatTag != AudioEncoding.IeeeFloat ) &&
+                               ( !CSCore.WaveFormatExtensible.SubTypeFromWaveFormat( waveFormat ).Equals( SharpDX.MediaFoundation.AudioFormatGuids.Float ) ) )
+                       {
+                               throw new NotSupportedException( "IEEE Float 以外の形式のフォーマットはサポートしません。" );
+                       }
+                       this.WaveFormat = waveFormat;
+
+                       this._初期化する( path );
+               }
+
+               public void Dispose()
+               {
+                       this._解放する();
+               }
+
+               /// <summary>
+               ///             連続した要素を読み込み、this.Position を読み込んだ要素の数だけ進める。
+               /// </summary>
+               /// <param name="buffer">
+               ///             読み込んだ要素を格納するための配列。
+               ///             このメソッドから戻ると、buffer には offset ~ (offset + count - 1) の数の要素が格納されている。
+               /// </param>
+               /// <param name="offset">
+               ///             buffer に格納を始める位置。
+               /// </param>
+               /// <param name="count">
+               ///             読み込む最大の要素数。
+               /// </param>
+               /// <returns>
+               ///             buffer に読み込んだ要素の総数。
+               /// </returns>
+               public int Read( byte[] buffer, int offset, int count )
+               {
+                       // 音がめちゃくちゃになるとうざいので、このメソッド内では例外を出さないこと。
+
+                       if( ( null == this._EncodedWaveData ) && ( null == buffer ) )
+                               return 0;
+
+                       // offset は、0~buffer.Length-1 に収める。
+                       offset = Math.Max( 0, Math.Min( buffer.Length - 1, offset ) );
+
+                       // count は、_EncodeWaveData.Length, buffer.Length-offset, count のうちの最小値とする。
+                       count = Math.Min( Math.Min( this._EncodedWaveData.Length, count ), buffer.Length - offset );
+
+                       if( 0 < count )
+                       {
+                               Array.Copy(
+                                       sourceArray: this._EncodedWaveData,
+                                       sourceIndex: this._Position,
+                                       destinationArray: buffer,
+                                       destinationIndex: offset,
+                                       length: count );
+
+                               this._Position += count;
+                       }
+
+                       return count;
+               }
+
+               private SharpDX.MediaFoundation.MediaType _MediaType = null;
+               private byte[] _EncodedWaveData = null;
+               private long _Position = 0;
+
+               private void _初期化する( string path )
+               {
+                       try
+                       {
+                               using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( path ) )
+                               using( var waveStream = new System.IO.MemoryStream() )
+                               {
+                                       #region " 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。"
+                                       //----------------
+                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
+                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
+                                       //----------------
+                                       #endregion
+                                       #region " デコードフォーマットを持つ MediaType を作成し、SourceReader に登録する。"
+                                       //----------------
+                                       using( var partialMediaType = new SharpDX.MediaFoundation.MediaType() )
+                                       {
+                                               // 部分メディアタイプを作成し、オーディオフォーマットを設定する。 (オーディオフォーマットは IEEE FLOAT で固定。)
+                                               partialMediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio );
+                                               partialMediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Float );
+                                               partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WaveFormat.Channels );
+                                               partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WaveFormat.SampleRate );
+                                               partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WaveFormat.BlockAlign );
+                                               partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WaveFormat.BytesPerSecond );
+                                               partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, this.WaveFormat.BitsPerSample );
+                                               partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE
+
+                                               if( this.WaveFormat.WaveFormatTag == AudioEncoding.Extensible )
+                                               {
+                                                       var wfmEx = this.WaveFormat as CSCore.WaveFormatExtensible;
+                                                       partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioChannelMask, (int) wfmEx.ChannelMask );
+                                                       partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerBlock, wfmEx.SamplesPerBlock );
+                                                       partialMediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioValidBitsPerSample, wfmEx.ValidBitsPerSample );
+                                               }
+
+                                               // 作成したメディアタイプを sourceReader にセットする。必要なデコーダが見つからなかったら、ここで例外が発生する。
+                                               sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, partialMediaType );
+
+                                               // 完成されたメディアタイプを取得する。
+                                               this._MediaType = sourceReader.GetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream );
+
+                                               // 最初のオーディオストリームが選択されていることを保証する。
+                                               sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
+                                       }
+                                       //----------------
+                                       #endregion
+                                       #region " sourceReader からサンプルを取得してデコードし、waveStream へ書き込んだのち、byte[] _EncodedWaveData へ出力する。"
+                                       //-----------------
+                                       using( var pcmWriter = new System.IO.BinaryWriter( waveStream ) )
+                                       {
+                                               while( true )
+                                               {
+                                                       // 次のサンプルを読み込む。
+                                                       int dwActualStreamIndexRef = 0;
+                                                       var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None;
+                                                       Int64 llTimestampRef = 0;
+                                                       using( var sample = sourceReader.ReadSample(
+                                                               SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream,
+                                                               SharpDX.MediaFoundation.SourceReaderControlFlags.None,
+                                                               out dwActualStreamIndexRef,
+                                                               out dwStreamFlagsRef,
+                                                               out llTimestampRef ) )
+                                                       {
+                                                               if( null == sample )
+                                                                       break;      // EndOfStream やエラーのときも null になる。
+
+                                                               // sample をロックし、オーディオデータへのポインタを取得する。
+                                                               int cbMaxLengthRef = 0;
+                                                               int cbCurrentLengthRef = 0;
+                                                               using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
+                                                               {
+                                                                       // オーディオデータをメモリストリームに書き込む。
+                                                                       var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef );
+                                                                       try
+                                                                       {
+                                                                               byte[] dstData = new byte[ cbCurrentLengthRef ];
+                                                                               System.Runtime.InteropServices.Marshal.Copy( audioData, dstData, 0, cbCurrentLengthRef );
+                                                                               pcmWriter.Write( dstData, 0, cbCurrentLengthRef );
+                                                                       }
+                                                                       finally
+                                                                       {
+                                                                               mediaBuffer.Unlock();
+                                                                       }
+                                                               }
+                                                       }
+                                               }
+
+                                               // ストリームの内容を byte 配列に出力。
+                                               this._EncodedWaveData = waveStream.ToArray();
+                                       }
+                                       //-----------------
+                                       #endregion
+                               }
+                       }
+                       catch
+                       {
+                               this._EncodedWaveData = new byte[] { };
+                       }
+
+                       this._Position = 0;
+               }
+
+               private void _解放する()
+               {
+                       FDK.Utilities.解放する( ref this._MediaType );
+               }
+
+               #region " Win32 "
+               //----------------
+               private const int MF_E_INVALIDMEDIATYPE = unchecked((int) 0xC00D36B4);
+               //----------------
+               #endregion
+       }
+}
index 984923c..0495c97 100644 (file)
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using CSCore;
 
 namespace FDK.メディア.サウンド.WASAPI
 {
-       public unsafe class Device : IDisposable
+       public class Device : IDisposable
        {
+               public CSCore.SoundOut.PlaybackState レンダリング状態
+               {
+                       get { return this._レンダリング状態; }
+               }
+
                public double 遅延sec
                {
-                       get { return ( this.更新間隔sec * 1000.0 ); }
+                       get { return FDK.Utilities.変換_100ns単位からsec単位へ( this._遅延100ns ); }
+                       protected set { this._遅延100ns = FDK.Utilities.変換_sec単位から100ns単位へ( value ); }
                }
-               public CSCore.CoreAudioAPI.AudioClock AudioClock
+
+               public long 遅延100ns
                {
-                       get { return this.bs_AudioClock; }
+                       get { return this._遅延100ns; }
+                       protected set { this._遅延100ns = value; }
                }
-               public bool Dispose済み
+
+               public CSCore.WaveFormat フォーマット
                {
-                       get;
-                       protected set;
-               } = true;
+                       get { return this._WaveFormat; }
+               }
 
-               public void 初期化する( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double 希望更新間隔sec = 0.015 )
+               /// <summary>
+               ///             レンダリングボリューム。
+               ///             0.0 (0%) ~ 1.0 (100%) 。
+               /// </summary>
+               public float 音量
                {
-                       FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
+                       get
+                       {
+                               return ( null != this._レンダリング先 ) ? this._レンダリング先.Volume : 1.0f;
+                       }
+                       set
+                       {
+                               if( ( 0.0f > value ) || ( 1.0f < value ) )
+                                       throw new ArgumentOutOfRangeException();
 
-                       int hr = 0;
+                               this._レンダリング先.Volume = value;
+                       }
+               }
 
-                       lock( this.スレッド間同期 )
-                       {
-                               Trace.Assert( this.Dispose済み );
-                               this.Dispose済み = false;
+               public CSCore.CoreAudioAPI.AudioClock AudioClock
+               {
+                       get { return this._AudioClock; }
+               }
 
-                               this.共有モード = 共有モード;
+               public Device( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double 遅延sec = 0.010, CSCore.WaveFormat 希望フォーマット = null )
+               {
+                       this._共有モード = 共有モード;
+                       this.遅延sec = 遅延sec;
+                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+
+                       this._初期化する( 希望フォーマット );
+               }
+
+               /// <summary>
+               ///             メディアファイル(動画、音声)からサウンドインスタンスを生成して返す。
+               /// </summary>
+               public Sound CreateSound( string path )
+               {
+                       return new Sound( path, this._Mixer );
+               }
 
-                               #region " AudioClientをアクティベートする。"
-                               //-----------------
-                               using( var devices = new CSCore.CoreAudioAPI.MMDeviceEnumerator() )
-                               using( var 既定のデバイス = devices.GetDefaultAudioEndpoint( CSCore.CoreAudioAPI.DataFlow.Render, CSCore.CoreAudioAPI.Role.Console ) )
+               /// <summary>
+               ///             ミキサーの出力を開始する。
+               ///             以降、ミキサーに Sound を追加すれば、自動的に再生される。
+               /// </summary>
+               public void PlayRendering()
+               {
+                       lock( this._スレッド間同期 )
+                       {
+                               if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Paused )
                                {
-                                       this.AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( 既定のデバイス );
+                                       // Pause 中なら Resume する。
+                                       this.ResumeRendering();
                                }
-                               //-----------------
-                               #endregion
-                               #region " 指定された希望更新間隔とデバイス能力をもとに、更新間隔を決定する。"
-                               //-----------------
-                               long 共有モードでの間隔100ns = 0;
-                               long 排他モードでの最小間隔100ns = 0;
-
-                               // デバイスから間隔値を取得する。
-                               hr = this.AudioClient.GetDevicePeriodNative( out 共有モードでの間隔100ns, out 排他モードでの最小間隔100ns );
-                               if( 0 > hr )
-                                       System.Runtime.InteropServices.Marshal.ThrowExceptionForHR( hr );
-
-                               if( 共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
+                               else if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Stopped )
                                {
-                                       this.更新間隔100ns = 共有モードでの間隔100ns;
+                                       using( var 起動完了通知 = new System.Threading.AutoResetEvent( false ) )
+                                       {
+                                               // スレッドがすでに終了していることを確認する。
+                                               this._レンダリングスレッド?.Join();
+
+                                               // レンダリングスレッドを起動する。
+                                               this._レンダリングスレッド = new System.Threading.Thread( this._レンダリングスレッドエントリ ) {
+                                                       Name = "WASAPI Playback",
+                                                       Priority = System.Threading.ThreadPriority.AboveNormal, // 標準よりやや上
+                                               };
+                                               this._レンダリングスレッド.Start( 起動完了通知 );
+
+                                               // スレッドからの起動完了通知を待つ。
+                                               起動完了通知.WaitOne();
+                                       }
+                               }
+                       }
+               }
+
+               /// <summary>
+               ///             ミキサーの出力を停止する。
+               ///             ミキサーに登録されているすべての Sound の再生が停止する。
+               /// </summary>
+               public void StopRendering()
+               {
+                       lock( this._スレッド間同期 )
+                       {
+                               if( ( this._レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped ) && ( null != this._レンダリングスレッド ) )
+                               {
+                                       // レンダリングスレッドに終了を通知し、その終了を待つ。
+                                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+                                       this._レンダリングスレッド.Join();
+                                       this._レンダリングスレッド = null;
+                                       Debug.WriteLine( "WASAPIのレンダリングを停止しました。" );
                                }
                                else
                                {
-                                       this.更新間隔100ns = Math.Max( FDK.Utilities.変換_sec単位から100ns単位へ( 希望更新間隔sec ), 排他モードでの最小間隔100ns );
+                                       Debug.WriteLine( "WASAPIのレンダリングを停止しようとしましたが、すでに停止しています。" );
                                }
-                               //-----------------
-                               #endregion
-                               #region " デバイスフォーマットを決定する。"
-                               //----------------
-                               if( this.共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
+                       }
+               }
+
+               /// <summary>
+               ///             ミキサーの出力を一時停止する。
+               ///             ミキサーに登録されているすべての Sound の再生が一時停止する。
+               ///             ResumeRendering()で出力を再開できる。
+               /// </summary>
+               public void PauseRendering()
+               {
+                       lock( this._スレッド間同期 )
+                       {
+                               if( this.レンダリング状態 == CSCore.SoundOut.PlaybackState.Playing )
                                {
-                                       this.WaveFormat = this.AudioClient.GetMixFormat();
+                                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Paused;
+                                       Debug.WriteLine( "WASAPIのレンダリングを一時停止しました。" );
                                }
                                else
                                {
-                                       this.WaveFormat = new CSCore.WaveFormat( 44100, 16, 2, CSCore.AudioEncoding.Pcm );
+                                       Debug.WriteLine( "WASAPIのレンダリングを一時停止しようとしましたが、すでに一時停止しています。" );
                                }
-                               //----------------
-                               #endregion
-                               #region " AudioClient を初期化する。"
-                               //-----------------
-                               try
+                       }
+               }
+
+               /// <summary>
+               ///             ミキサーの出力を再開する。
+               ///             PauseRendering() で一時停止状態にあるときのみ有効。
+               /// </summary>
+               public void ResumeRendering()
+               {
+                       lock( this._スレッド間同期 )
+                       {
+                               if( this._レンダリング状態 == CSCore.SoundOut.PlaybackState.Paused )
                                {
-                                       this.AudioClient.Initialize(
-                                               this.共有モード,
-                                               CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,
-                                               // | CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsNoPersist,   // 音量とミュートを記憶しない → 無効。してください
-                                               this.更新間隔100ns,
-                                               this.更新間隔100ns,
-                                               this.WaveFormat,
-                                               Guid.Empty );   // この AudioClient (= AudioStrem) が所属する AudioSession。null ならデフォルトのAudioSessionに登録される。
+                                       this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Playing;
+                                       Debug.WriteLine( "WASAPIのレンダリングを再開しました。" );
                                }
-                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               else
                                {
-                                       if( AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED == e.ErrorCode )
-                                       {
-                                               #region " 排他&イベント駆動モードの場合、バッファサイズアライメントエラーが返される場合がある。この場合、サイズを調整してオーディオストリームを作成し直す。"
-                                               //----------------
-                                               this.更新間隔100ns = FDK.Utilities.変換_sec単位から100ns単位へ(
-                                                       (double) this.AudioClient.GetBufferSize() / (double) this.WaveFormat.SampleRate );   // GetBufferSize は、更新間隔に一番近い、アライメントされたバッファサイズ(sample単位)を返す。
-
-                                               // AudioClient を一度解放し、もう一度アクティベートし直す。
-                                               this.AudioClient.Dispose();
-                                               using( var devices = new CSCore.CoreAudioAPI.MMDeviceEnumerator() )
-                                               using( var 既定のデバイス = devices.GetDefaultAudioEndpoint( CSCore.CoreAudioAPI.DataFlow.Render, CSCore.CoreAudioAPI.Role.Console ) )
-                                               {
-                                                       this.AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( 既定のデバイス );
-                                               }
-
-                                               // アライメントされたサイズを使って、AudioClient を再初期化する。それでもエラーなら例外発出。
-                                               this.AudioClient.Initialize(
-                                                       this.共有モード,
-                                                       CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,
-                                                       // | CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsNoPersist,   // 音量とミュートを記憶しない → 無効。してください
-                                                       this.更新間隔100ns,
-                                                       this.更新間隔100ns,
-                                                       this.WaveFormat,
-                                                       Guid.Empty );
-                                               //----------------
-                                               #endregion
-                                       }
+                                       Debug.WriteLine( "WASAPIのレンダリングを再開しようとしましたが、すでに再開されています。" );
                                }
-
-                               this.更新間隔sample = this.AudioClient.GetBufferSize();
-                               //-----------------
-                               #endregion
-                               #region " AudioRenderClient を取得する。"
-                               //-----------------
-                               this.AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this.AudioClient );
-                               //-----------------
-                               #endregion
-                               #region " AudioClock を取得する。"
-                               //-----------------
-                               this.bs_AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this.AudioClient );
-                               //-----------------
-                               #endregion
-                               #region " エンドポイントバッファを無音で埋めておく。"
-                               //-----------------
-                               this.AudioRenderClient.GetBuffer( this.更新間隔sample );
-                               
-                               // 無音を書き込んだことにして、バッファをコミット。(GetBuffer の戻り値は使わない。)
-                               this.AudioRenderClient.ReleaseBuffer( this.更新間隔sample, CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent );
-                               //-----------------
-                               #endregion
-
-                               #region " ミキサーを生成し初期化する。"
-                               //-----------------
-                               this.Mixer = new Mixer( this.更新間隔sample );
-                               //-----------------
-                               #endregion
-
-                               #region " 情報表示。"
-                               //-----------------
-                               FDK.Log.Info( $"WASAPIクライアントを初期化しました。" );
-                               FDK.Log.Info( ( 共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared ) ?
-                                       " モード: 共有 & イベント駆動" :
-                                       " モード: 排他 & イベント駆動" );
-                               FDK.Log.Info( $" フォーマット: {this.WaveFormat.BitsPerSample} bits, {this.WaveFormat.SampleRate} Hz" );
-                               FDK.Log.Info( $" エンドポイントバッファ: {( (float) this.更新間隔sample / (double) this.WaveFormat.SampleRate ) * 1000.0f} ミリ秒 ({this.更新間隔sample} samples) × 2枚" );
-                               FDK.Log.Info( $" 希望更新間隔: {希望更新間隔sec * 1000.0} ミリ秒" );
-                               FDK.Log.Info( $" 更新間隔: {this.更新間隔sec * 1000.0} ミリ秒 ({this.更新間隔sample} samples)" );
-                               if( 共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive )
-                                       FDK.Log.Info( $" 最小間隔: {FDK.Utilities.変換_100ns単位からsec単位へ( 排他モードでの最小間隔100ns )} ミリ秒" );
-                               //-----------------
-                               #endregion
-
-                               #region " ワークキューとイベントを作成し、作業項目を登録する。"
-                               //-----------------
-                               // MediaFoundation が管理する、プロセス&MMCSSタスクごとに1つずつ作ることができる特別な共有ワークキューを取得(または生成して取得)する。
-                               int dwTaskId = 0;
-                               SharpDX.MediaFoundation.MediaFactory.LockSharedWorkQueue( ( 0.011 > this.更新間隔sec ) ? "Pro Audio" : "Games", 0, ref dwTaskId, out this.QueueID );
-
-                               // エンドポイントバッファからの出力要請イベントを作成し、AudioClient に登録する。
-                               this.出力要請イベント = CreateEvent( IntPtr.Zero, false, false, "WASAPI出力要請イベント" );
-                               this.AudioClient.SetEventHandle( this.出力要請イベント );
-
-                               // コールバックを作成し、ワークキューに最初の作業項目を登録する。
-                               this.出力要請イベントのコールバック = new MFAsyncCallback( this.QueueID, this.出力要請イベントへ対応する );
-                               this.作業項目をキューに格納する();
-                               //-----------------
-                               #endregion
-                               #region " WASAPI レンダリングを開始。"
-                               //-----------------
-                               this.AudioClient.Start();
-                               //-----------------
-                               #endregion
                        }
+               }
 
-                       FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
+               #region " 解放; Dispose-Finallize パターン "
+               //----------------
+               ~Device()
+               {
+                       this.Dispose( false );
                }
+
                public void Dispose()
                {
-                       FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
-
-                       Trace.Assert( false == this.Dispose済み );
+                       this.Dispose( true );
+                       GC.SuppressFinalize( this );
+               }
 
-                       #region " WASAPI作業項目を終了させる。オーディオのレンダリングを止める前に行うこと。"
-                       //-----------------
+               protected virtual void Dispose( bool bDisposeManaged )
+               {
+                       if( !this._dispose済み )
                        {
-                               //SharpDX.MediaFoundation.MediaFactory.CancelWorkItem( this.出力要請イベントキャンセル用キー ); --> コールバックの実行中にキャンセルしてしまうと NullReference例外
-                               this.出力終了通知.状態 = 同期.TriStateEvent.状態種別.ON;
-                               this.出力終了通知.OFFになるまでブロックする();
-                               FDK.Log.Info( "WASAPI出力処理を終了しました。" );
+                               lock( this._スレッド間同期 )
+                               {
+                                       if( bDisposeManaged )
+                                       {
+                                               // (A) ここでマネージリソースを解放する。
+                                               this.StopRendering();
+                                               this._解放する();
+                                       }
+
+                                       // (B) ここでネイティブリソースを解放する。
+
+                                       // ...特にない。
+
+                               }
+
+                               this._dispose済み = true;
                        }
-                       //-----------------
-                       #endregion
+               }
+               //----------------
+               #endregion
+
+
+               private volatile CSCore.SoundOut.PlaybackState _レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+
+               private CSCore.CoreAudioAPI.AudioClientShareMode _共有モード;
+
+               private long _遅延100ns = 0;
+
+               private CSCore.WaveFormat _WaveFormat = null;
+
+               private CSCore.CoreAudioAPI.AudioClock _AudioClock = null;
+
+               private CSCore.CoreAudioAPI.AudioRenderClient _AudioRenderClient = null;
+
+               private CSCore.CoreAudioAPI.AudioClient _AudioClient = null;
+
+               private CSCore.CoreAudioAPI.MMDevice _MMDevice = null;
+
+               private System.Threading.Thread _レンダリングスレッド = null;
 
-                       lock( this.スレッド間同期 )
+               private System.Threading.EventWaitHandle _レンダリングイベント = null;
+
+               private CSCore.Streams.VolumeSource _レンダリング先 = null;
+
+               private CSCore.IWaveSource _生レンダリング先 = null;
+
+               private Mixer _Mixer = null;
+
+               private readonly object _スレッド間同期 = new object();
+
+               private bool _dispose済み = false;
+
+
+               private void _初期化する( CSCore.WaveFormat 希望フォーマット = null )
+               {
+                       lock( this._スレッド間同期 )
                        {
-                               #region " オーディオのレンダリングを停止する。"
-                               //-----------------
-                               this.AudioClient?.Stop();
-                               //-----------------
-                               #endregion
-                               #region " ミキサー(とサウンドリスト)は現状を維持する。"
-                               //-----------------
-
-                               // 何もしない。
-
-                               //-----------------
-                               #endregion
-                               #region " WASAPIオブジェクトを解放する。"
-                               //-----------------
-                               FDK.Utilities.解放する( ref this.bs_AudioClock );
-                               FDK.Utilities.解放する( ref this.AudioRenderClient );
-                               FDK.Utilities.解放する( ref this.AudioClient );
-                               //-----------------
-                               #endregion
-                               #region " 共有ワークキューをこのプロセスから解放する。"
-                               //-----------------
-                               if( int.MaxValue != this.QueueID )
+                               if( this._レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped )
+                                       throw new InvalidOperationException( "WASAPI のレンダリングを停止しないまま初期化することはできません。" );
+
+                               this._レンダリングスレッド?.Join();
+
+                               this._解放する();
+
+                               // MMDevice を取得する。
+                               this._MMDevice = CSCore.CoreAudioAPI.MMDeviceEnumerator.DefaultAudioEndpoint(
+                                       CSCore.CoreAudioAPI.DataFlow.Render,    // 方向:再生
+                                       CSCore.CoreAudioAPI.Role.Console );     // 用途:ゲーム、システム通知音、音声命令
+
+                               // AudioClient を取得する。
+                               this._AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( this._MMDevice );
+
+                               // フォーマットを決定する。
+                               var defaultFormat = ( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared ) ?
+                                       this._AudioClient.GetMixFormat() :
+                                       new CSCore.WaveFormat( 48000, 32, 2, AudioEncoding.IeeeFloat );
+
+                               if( null == ( this._WaveFormat = this._適切なフォーマットを調べて返す( 希望フォーマット ?? defaultFormat ) ) )
                                {
-                                       SharpDX.MediaFoundation.MediaFactory.UnlockWorkQueue( this.QueueID );
-                                       this.QueueID = int.MaxValue;
+                                       throw new NotSupportedException( "サポート可能な WaveFormat が見つかりませんでした。" );
                                }
-                               //-----------------
-                               #endregion
-                               #region " WASAPIイベント駆動用のコールバックとイベントを解放する。"
-                               //-----------------
-                               FDK.Utilities.解放する( ref this.出力要請イベントのコールバック );
-
-                               if( IntPtr.Zero != this.出力要請イベント )
-                                       Device.CloseHandle( this.出力要請イベント );
-                               //-----------------
-                               #endregion
-
-                               this.Dispose済み = true;
-                       }
 
-                       FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
-               }
-               public void サウンドをミキサーに追加する( Sound sound )
-               {
-                       this.Mixer.サウンドを追加する( sound );
+                               // 遅延を既定値にする(共有モードの場合のみ)。
+                               if( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
+                                       this._遅延100ns = this._AudioClient.DefaultDevicePeriod;
+
+                               // AudioClient を初期化する。
+                               Action AudioClientを初期化する = () => {
+                                       this._AudioClient.Initialize(
+                                               this._共有モード,
+                                               CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,    // イベント駆動で固定。
+                                               this._遅延100ns,
+                                               this._遅延100ns,
+                                               this._WaveFormat,
+                                               Guid.Empty );
+                               };
+                               try
+                               {
+                                       AudioClientを初期化する();
+                               }
+                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               {
+                                       // 排他モードかつイベント駆動 の場合、この例外が返されることがある。
+                                       // この場合、バッファサイズを調整して再度初期化する。
+                                       if( e.ErrorCode == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED )
+                                       {
+                                               int サイズframe = this._AudioClient.GetBufferSize();
+                                               this._遅延100ns = (long) ( 10.0 * 1000.0 * 1000.0 * サイズframe / this._WaveFormat.SampleRate + 0.5 );   // +0.5 は四捨五入
+
+                                               AudioClientを初期化する();    // それでも例外なら知らん。
+                                       }
+                               }
+
+                               // イベント駆動用に使うイベントを生成し、AudioClient へ登録する。
+                               this._レンダリングイベント = new System.Threading.EventWaitHandle( false, System.Threading.EventResetMode.AutoReset );
+                               this._AudioClient.SetEventHandle( this._レンダリングイベント.SafeWaitHandle.DangerousGetHandle() );
+
+                               // その他の WASAPI インターフェースを取得する。
+                               this._AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this._AudioClient );
+                               this._AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this._AudioClient );
+
+                               // ミキサーを生成し、デバイスのソース(DirectSound でいうところのプライマリバッファ)として登録する。
+                               this._Mixer = new Mixer( this._WaveFormat ) {
+                                       DivideResult = false,
+                               };
+                               this._SetSource( this._Mixer );
+                       }
                }
-               public void サウンドをミキサーから削除する( Sound sound )
+
+               private void _解放する()
                {
-                       this.Mixer.サウンドを削除する( sound );
-               }
+                       FDK.Utilities.解放する( ref this._Mixer );
+                       FDK.Utilities.解放する( ref this._AudioClock );
+                       FDK.Utilities.解放する( ref this._AudioRenderClient );
 
-               protected CSCore.CoreAudioAPI.AudioClientShareMode 共有モード;
-               protected CSCore.CoreAudioAPI.AudioClient AudioClient = null;
-               protected CSCore.CoreAudioAPI.AudioRenderClient AudioRenderClient = null;
-               protected CSCore.WaveFormat WaveFormat = null;
-               protected long 更新間隔100ns = 0;
-               protected int 更新間隔sample = 0;
-               protected int 更新間隔byte => ( this.更新間隔sample * this.WaveFormat.Channels * this.WaveFormat.BytesPerSample );
-               protected double 更新間隔sec => ( FDK.Utilities.変換_100ns単位からsec単位へ( this.更新間隔100ns ) );
+                       if( ( null != this._AudioClient ) && ( this._AudioClient.BasePtr != IntPtr.Zero ) )
+                       {
+                               try
+                               {
+                                       this._AudioClient.StopNative();
+                                       this._AudioClient.Reset();
+                               }
+                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               {
+                                       if( e.ErrorCode != AUDCLNT_E_NOT_INITIALIZED )
+                                               throw;
+                               }
+                       }
 
-               // ミキサー。サウンドリストもここ。
-               protected Mixer Mixer = null;
+                       FDK.Utilities.解放する( ref this._AudioClient );
+                       FDK.Utilities.解放する( ref this._レンダリングイベント );
+                       FDK.Utilities.解放する( ref this._MMDevice );
+               }
 
-               // WASAPIバッファ出力用
-               private int QueueID = int.MaxValue;
-               private IntPtr 出力要請イベント = IntPtr.Zero;
-               private MFAsyncCallback 出力要請イベントのコールバック = null;
-               private long 出力要請イベントキャンセル用キー = 0;
-               private FDK.同期.TriStateEvent 出力終了通知 = new 同期.TriStateEvent();
+               private void _SetSource( CSCore.ISampleSource targetSource )
+               {
+                       if( null != this._レンダリング先 )
+                               throw new InvalidOperationException( "レンダリングターゲットはすでに設定済みです。" );
 
-               private readonly object スレッド間同期 = new object();
+                       this._レンダリング先 = new CSCore.Streams.VolumeSource( targetSource );  // サンプル(float)単位のレンダリング先と、
+                       this._生レンダリング先 = targetSource.ToWaveSource();                                           // データ(byte)単位のレンダリング先とを持っておく。
+               }
 
-               private void 作業項目をキューに格納する()
+               /// <summary>
+               ///             希望したフォーマットをもとに、適切なフォーマットを調べて返す。
+               /// </summary>
+               /// <param name="waveFormat">希望するフォーマット</param>
+               /// <param name="audioClient">AudioClient インスタンス。Initialize 前でも可。</param>
+               /// <returns>適切なフォーマット。見つからなかったら null。</returns>
+               private CSCore.WaveFormat _適切なフォーマットを調べて返す( CSCore.WaveFormat waveFormat )
                {
-                       var asyncResult = (SharpDX.MediaFoundation.AsyncResult) null;
-                       try
+                       Trace.Assert( null != this._AudioClient );
+
+                       var 最も近いフォーマット = (CSCore.WaveFormat) null;
+                       var 最終的に決定されたフォーマット = (CSCore.WaveFormat) null;
+
+                       if( this._AudioClient.IsFormatSupported( this._共有モード, waveFormat, out 最も近いフォーマット ) )
                        {
-                               // IAsyncCallback を内包した AsyncResult を作成する。
-                               SharpDX.MediaFoundation.MediaFactory.CreateAsyncResult(
-                                       null,
-                                       SharpDX.ComObject.ToCallbackPtr<SharpDX.MediaFoundation.IAsyncCallback>( this.出力要請イベントのコールバック ),
-                                       null,
-                                       out asyncResult );
-
-                               // 作成した AsyncResult を、ワークキュー投入イベントの待機状態にする。
-                               SharpDX.MediaFoundation.MediaFactory.PutWaitingWorkItem(
-                                       hEvent: this.出力要請イベント,
-                                       priority: 0,
-                                       resultRef: asyncResult,
-                                       keyRef: out this.出力要請イベントキャンセル用キー );
+                               // (A) そのまま使える。
+                               最終的に決定されたフォーマット = waveFormat;
                        }
-                       finally
+                       else if( null != 最も近いフォーマット )
                        {
-                               // out 引数に使う変数は using 変数にはできないので、代わりに try-finally を使う
-                               asyncResult?.Dispose();
+                               // (B) AudioClient が推奨フォーマットを返してきたなら、それを採択する
+                               最終的に決定されたフォーマット = 最も近いフォーマット;
                        }
+                       else
+                       {
+                               // (C) AudioClient からの提案がなかった場合は、共有モードのフォーマットを採択する。
+
+                               var 共有モードのフォーマット = this._AudioClient.GetMixFormat();
+
+                               if( ( null != 共有モードのフォーマット ) && this._AudioClient.IsFormatSupported( this._共有モード, 共有モードのフォーマット ) )
+                               {
+                                       最終的に決定されたフォーマット = 共有モードのフォーマット;
+                               }
+                               else
+                               {
+                                       // (D) AudioClient が共有モードのフォーマットすらNGと言ってきた場合は、以下から探す。
+
+                                       最終的に決定されたフォーマット = new[]
+                                       {
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate, 32, waveFormat.Channels, CSCore.AudioSubTypes.IeeeFloat ),
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate, 24, waveFormat.Channels, CSCore.AudioSubTypes.Pcm ),
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate, 16, waveFormat.Channels, CSCore.AudioSubTypes.Pcm ),
+                                                       new CSCore.WaveFormatExtensible( waveFormat.SampleRate,  8, waveFormat.Channels, CSCore.AudioSubTypes.Pcm ),
+                                               }
+                                       .FirstOrDefault( ( format ) => ( this._AudioClient.IsFormatSupported( this._共有モード, format ) ) );
+
+                                       // (E) それでも見つからなかったら null 。
+                               }
+                       }
+
+                       return 最終的に決定されたフォーマット;
                }
 
                /// <summary>
-               /// このメソッドは、WASAPIイベント発生時にワークキューに投入され作業項目から呼び出される
+               ///             WASAPIイベント駆動スレッドのエントリ
                /// </summary>
-               private void 出力要請イベントへ対応する( SharpDX.MediaFoundation.AsyncResult asyncResult )
+               /// <param name="起動完了通知">無事に起動できたら、これを Set して(スレッドの生成元に)知らせる。</param>
+               private void _レンダリングスレッドエントリ( object 起動完了通知 )
                {
+                       var 例外 = (Exception) null;
+                       var avrtHandle = IntPtr.Zero;
+
                        try
                        {
-                               // 出力終了通知が来ていれば、応答してすぐに終了する。
-                               if( this.出力終了通知.状態 == 同期.TriStateEvent.状態種別.ON )
-                               {
-                                       this.出力終了通知.状態 = 同期.TriStateEvent.状態種別.無効;
-                                       return;
+                               int バッファサイズframe = this._AudioClient.BufferSize;
+                               int フレームサイズbyte = this._WaveFormat.Channels * this._WaveFormat.BytesPerSample;
+                               var バッファ = new byte[ バッファサイズframe * フレームサイズbyte ];
+
+                               // このスレッドの MMCSS 型を登録する。
+                               int taskIndex;
+                               string mmcssType = new[] {
+                                       new { 最大遅延 = 0.010, 型名 = "Pro Audio" },             // 優先度の高いものから。
+                                       new { 最大遅延 = 0.015, 型名 = "Games" },
                                }
+                               .FirstOrDefault( ( i ) => ( i.最大遅延 > this.遅延sec ) )?.型名 ?? "Audio";
+                               avrtHandle = Device.AvSetMmThreadCharacteristics( mmcssType, out taskIndex );
 
-                               lock( this.スレッド間同期 )
-                               {
-                                       // エンドポインタの空きバッファへのポインタを取得する。
-                                       // このポインタが差すのはネイティブで確保されたメモリなので、GCの対象外である。はず。
-                                       var bufferPtr = this.AudioRenderClient.GetBuffer( this.更新間隔sample );    // イベント駆動なのでサイズ固定。
+                               // AudioClient を開始する。
+                               this._AudioClient.Start();
+                               this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Playing;
+
+                               // 起動完了を通知する。
+                               ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set();
+                               起動完了通知 = null;
 
-                                       // ミキサーを使って、エンドポインタへサウンドデータを出力する。
-                                       var flags = this.Mixer.エンドポイントへ出力する( (void*) bufferPtr, this.更新間隔sample );
+                               // 以下、メインループ。
+
+                               var イベントs = new System.Threading.WaitHandle[] { this._レンダリングイベント };
+                               while( this.レンダリング状態 != CSCore.SoundOut.PlaybackState.Stopped )
+                               {
+                                       int イベント番号 = System.Threading.WaitHandle.WaitAny(
+                                               waitHandles: イベントs,
+                                               millisecondsTimeout: (int) ( 3000.0 * this.遅延sec ), // 適正値は レイテンシ×3 [ms] (MSDN)
+                                               exitContext: false );
 
-                                       // エンドポインタのバッファを解放する。
-                                       this.AudioRenderClient.ReleaseBuffer( this.更新間隔sample, flags );
+                                       if( イベント番号 == System.Threading.WaitHandle.WaitTimeout )
+                                               continue;
 
-                                       // 後続のイベント待ち作業項目をキューに格納する。
-                                       this.作業項目をキューに格納する();
+                                       if( this.レンダリング状態 == CSCore.SoundOut.PlaybackState.Playing )
+                                       {
+                                               int 未再生数frame = ( this._共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive ) ? 0 : this._AudioClient.GetCurrentPadding();
+                                               int 空きframe = バッファサイズframe - 未再生数frame;
 
-                                       // 以降、WASAPIからイベントが発火されるたび、作業項目を通じて本メソッドが呼び出される。
+                                               if( 空きframe > 5 )   // あまりに空きが小さいならスキップする。
+                                               {
+                                                       if( !this._バッファを埋める( バッファ, 空きframe, フレームサイズbyte ) )
+                                                               this._レンダリング状態 = CSCore.SoundOut.PlaybackState.Stopped;
+                                               }
+                                       }
                                }
+
+                               // 以下、終了処理。
+
+                               // このスレッドの MMCSS 特性を元に戻す。
+                               Device.AvRevertMmThreadCharacteristics( avrtHandle );
+                               avrtHandle = IntPtr.Zero;
+
+                               // ハードウェアの再生が終わるくらいまで、少し待つ。
+                               System.Threading.Thread.Sleep( (int) ( this.遅延sec * 1000 / 2 ) );
+
+                               // AudioClient を停止する。
+                               this._AudioClient.Stop();
+                               this._AudioClient.Reset();
+                       }
+                       catch( Exception e )
+                       {
+                               例外 = e;
                        }
-                       catch
+                       finally
                        {
-                               // 例外は無視。
+                               if( avrtHandle != IntPtr.Zero )
+                                       Device.AvRevertMmThreadCharacteristics( avrtHandle );
+
+                               ( 起動完了通知 as System.Threading.EventWaitHandle )?.Set();      // 失敗時を想定して。
                        }
                }
 
-               #region " バックストア。"
-               //----------------
-               private CSCore.CoreAudioAPI.AudioClock bs_AudioClock = null;
-               //----------------
-               #endregion
+               private bool _バッファを埋める( byte[] バッファ, int フレーム数, int フレームサイズbyte )
+               {
+                       int 読み込むサイズbyte = フレーム数 * フレームサイズbyte;
+                       読み込むサイズbyte -= ( 読み込むサイズbyte % this._生レンダリング先.WaveFormat.BlockAlign );  // BlockAlign の倍数にする。
+
+                       if( 読み込むサイズbyte <= 0 )
+                               return true;
 
-               #region " Win32 API "
-               //-----------------
-               private static int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
+                       int 読み込んだサイズbyte = this._生レンダリング先.Read( バッファ, 0, 読み込むサイズbyte );
 
-               [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
-               private static extern IntPtr CreateEvent( IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName );
+                       IntPtr ptr = this._AudioRenderClient.GetBuffer( フレーム数 );
+                       Marshal.Copy( バッファ, 0, ptr, 読み込んだサイズbyte );
+                       this._AudioRenderClient.ReleaseBuffer( 読み込んだサイズbyte / フレームサイズbyte, CSCore.CoreAudioAPI.AudioClientBufferFlags.None );
 
-               [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
-               private static extern bool CloseHandle( IntPtr hObject );
-               //-----------------
+                       return ( 0 < 読み込んだサイズbyte );
+               }
+
+               #region " Win32 "
+               //----------------
+               private const int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
+               private const int AUDCLNT_E_INVALID_DEVICE_PERIOD = unchecked((int) 0x88890020);
+               private const int AUDCLNT_E_NOT_INITIALIZED = unchecked((int) 0x88890001);
+
+               [DllImport( "Avrt.dll", CharSet = CharSet.Unicode )]
+               private static extern IntPtr AvSetMmThreadCharacteristics( [MarshalAs( UnmanagedType.LPWStr )] string proAudio, out int taskIndex );
+
+               [DllImport( "Avrt.dll" )]
+               private static extern bool AvRevertMmThreadCharacteristics( IntPtr avrtHandle );
+               //----------------
                #endregion
        }
 }
index c9c9966..d0e6331 100644 (file)
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using CSCore;
 
 namespace FDK.メディア.サウンド.WASAPI
 {
        /// <summary>
-       /// Sound のリストを持ち、そのサウンドデータを合成してWASAPIデバイスへ出力するミキサー。
+       ///             オーディオミキサー。
+       ///             自身が ISampleSource であり、そのまま AudioClient のレンダリングターゲットに指定する。
        /// </summary>
-       public unsafe class Mixer : IDisposable
+       internal class Mixer : CSCore.ISampleSource
        {
-               public Mixer()
+               /// <summary>
+               /// true なら、サンプルの値を、合成したソースの数で割る。
+               /// (例えば、ソースを3つ合成した場合、合成した結果のサンプル値を3で割る。各サウンドはそれぞれ小さく聞こえる。)
+               /// </summary>
+               public bool DivideResult
                {
+                       get;
+                       set;
+               } = false;
+
+               /// <summary>
+               /// 音量。0.0(無音)~1.0(原音)。
+               /// </summary>
+               public float Volume
+               {
+                       get { return this._Volume; }
+                       set
+                       {
+                               if( ( 0.0f > value ) || ( 1.0f < value ) )
+                                       throw new ArgumentOutOfRangeException();
+
+                               this._Volume = value;
+                       }
                }
-               public Mixer( int エンドポイントバッファサイズsample ) : this()
+
+               /// <summary>
+               ///             ミキサーのフォーマット。
+               /// </summary>
+               public CSCore.WaveFormat WaveFormat
                {
-                       this.初期化する( エンドポイントバッファサイズsample );
+                       get { return _WaveFormat; }
                }
-               public void 初期化する( int エンドポイントバッファサイズsample )
+
+               /// <summary>
+               ///             ミキサーはループするので、Position には 非対応。
+               /// </summary>
+               public long Position
                {
-                       lock( this.スレッド間同期 )
-                       {
-                               if( エンドポイントバッファサイズsample == this.エンドポイントバッファサイズsample )
-                                       return;     // サイズに変更があったときのみ初期化する。
+                       get { return 0; }
+                       set { throw new NotSupportedException(); }
+               }
 
-                               this.エンドポイントバッファサイズsample = エンドポイントバッファサイズsample;
-                               this.サウンドリスト.Clear();
+               /// <summary>
+               ///             ミキサーはシークできない。
+               /// </summary>
+               public bool CanSeek => ( false );
 
-                               if( null != this.合成用バッファ )
-                                       FDK.Memory.Free( this.合成用バッファ );
-                               this.合成用バッファ = FDK.Memory.Alloc( this.エンドポイントバッファサイズsample * ( 4 * 2 ) );       // 1sample = 32bit×2ch
-                       }
+               /// <summary>
+               ///             ミキサーはループするので、長さの概念はない。
+               /// </summary>
+               public long Length => ( 0 );
+
+               /// <summary>
+               ///             指定したフォーマットを持つミキサーを生成する。
+               /// </summary>
+               public Mixer( CSCore.WaveFormat waveFormat )
+               {
+                       this._WaveFormat = waveFormat;
                }
+
                public void Dispose()
                {
-                       lock( this.スレッド間同期 )
+                       lock( this._スレッド間同期 )
                        {
-                               this.サウンドリスト.Clear();
-                               if( null != this.合成用バッファ )
-                               {
-                                       FDK.Memory.Free( this.合成用バッファ );
-                                       this.合成用バッファ = null;
-                               }
-                               this.エンドポイントバッファサイズsample = -1;
+                               // すべての Sound を解放する。
+                               foreach( var sampleSource in this._Sounds )
+                                       sampleSource.Dispose();
+
+                               // Sound リストをクリアする。
+                               this._Sounds.Clear();
                        }
                }
-               public void サウンドリストをクリアする()
+
+               /// <summary>
+               ///             Sound をミキサーに追加する。
+               ///             追加されると同時に、Sound の再生が開始される。
+               /// </summary>
+               public void AddSound( Sound sound )
                {
-                       lock( this.スレッド間同期 )
+                       if( null == sound )
+                               throw new ArgumentNullException();
+
+                       if( ( sound.WaveFormat.Channels != this._WaveFormat.Channels ) ||
+                               ( sound.WaveFormat.SampleRate != this._WaveFormat.SampleRate ) )
                        {
-                               this.サウンドリスト.Clear();
+                               throw new ArgumentException();
                        }
-               }
-               public void サウンドを追加する( Sound sound )
-               {
-                       lock( this.スレッド間同期 )
+
+                       lock( this._スレッド間同期 )
                        {
-                               this.サウンドリスト.Add( sound );
+                               if( !this.Contains( sound ) )
+                                       this._Sounds.Add( sound );
                        }
                }
-               public void サウンドを削除する( Sound sound )
+
+               /// <summary>
+               ///             Sound をミキサーから除外する。
+               ///             除外されると同時に、Sound の再生は終了する。
+               /// </summary>
+               public void RemoveSound( Sound sound )
                {
-                       lock( this.スレッド間同期 )
+                       lock( this._スレッド間同期 )
                        {
-                               this.サウンドリスト.Remove( sound );
+                               if( this.Contains( sound ) )
+                                       this._Sounds.Remove( sound );
                        }
                }
-               public CSCore.CoreAudioAPI.AudioClientBufferFlags エンドポイントへ出力する( void* エンドポイントの出力先, int 出力数sample )
+
+               /// <summary>
+               ///             Sound がミキサーに登録されているかを調べる。
+               /// </summary>
+               /// <returns>Sound がミキサーに追加済みなら true 。</returns>
+               public bool Contains( Sound sound )
                {
-                       lock( this.スレッド間同期 )
-                       {
-                               if( null == this.合成用バッファ )
-                                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
+                       if( null == sound )
+                               return false;
 
-                               #region " すべてのサウンドについて、合成バッファへ出力する。"
-                               //-----------------
-                               bool 最初の出力である = true;
+                       return this._Sounds.Contains( sound );
+               }
 
-                               foreach( var sound in this.サウンドリスト )
+               /// <summary>
+               ///             バッファにサウンドデータを出力する。
+               /// </summary>
+               /// <returns>出力したサンプル数。</returns>
+               public int Read( float[] バッファ, int バッファの出力開始位置, int 出力サンプル数 )
+               {
+                       if( 0 < 出力サンプル数 )
+                       {
+                               lock( this._スレッド間同期 )
                                {
-                                       var flag = sound.次のサウンドデータを出力する( this.合成用バッファ, 出力数sample, 最初の出力である );
+                                       // 中間バッファが十分あることを確認する。足りなければ新しく確保して戻ってくる。
+                                       this._中間バッファ = this._中間バッファ.CheckBuffer( 出力サンプル数 );
 
-                                       if( false == flag.HasFlag( CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent ) )
-                                               最初の出力である = false;   // sound が何らかのデータを出力した(戻り値がSILENTじゃなかった)
-                               }
+                                       // 無音を出力する。
+                                       Array.Clear( バッファ, 0, 出力サンプル数 );
 
-                               // 全サウンドが SILENT だったなら、エンドポイントには何も書き込まずに SILENT フラグを返す。
-                               if( 最初の出力である )
-                                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
-                               //-----------------
-                               #endregion
-                               #region " 合成バッファのデータ値(32bit;オーバーサンプル)を16bitに丸めてエンドポイントに出力する。"
-                               //-----------------
-                               Int32* 出力元 = (Int32*) ( this.合成用バッファ );
-                               Int16* 出力先 = (Int16*) エンドポイントの出力先;
-                               for( int i = 0; i < 出力数sample; i++ )
-                               {
-                                       Int32 src;
-
-                                       // 音量やミュートの処理は不要。(WASAPI が自動でマスタ音量・ミュート状態に合わせてくれる)
-
-                                       // 左ch
-                                       src = *出力元++;
-                                       if( -32768 > src )
-                                               src = -32768;
-                                       else if( 32767 < src )
-                                               src = 32767;
-                                       *出力先++ = (Int16) src;
-
-                                       // 右ch
-                                       src = *出力元++;
-                                       if( -32768 > src )
-                                               src = -32768;
-                                       else if( 32767 < src )
-                                               src = 32767;
-                                       *出力先++ = (Int16) src;
+                                       // 登録されたサンプルソースを出力する。
+                                       if( 0 < this._Sounds.Count )
+                                       {
+                                               // DiviveResult 用。
+                                               var サウンド別読み出し数 = new List<int>( this._Sounds.Count );
+
+                                               // ミキサに登録されているすべての Sound について……(逆順)
+                                               for( int m = this._Sounds.Count - 1; m >= 0; m-- )
+                                               {
+                                                       var sound = this._Sounds[ m ];
+
+                                                       // 中間バッファにサウンドデータを受け取る。
+                                                       int 受け取ったサンプル数 = sound.SampleSource.Read( this._中間バッファ, 0, 出力サンプル数 );
+
+                                                       // 中間バッファから出力バッファへ転送する。
+                                                       for( int i = バッファの出力開始位置, n = 0; n < 受け取ったサンプル数; i++, n++ )
+                                                       {
+                                                               float data = this._中間バッファ[ n ] // 原音
+                                                                       * sound.Volume                  // 個別音量
+                                                                       * this._Volume;                 // ミキサ音量
+
+                                                               バッファ[ i ] += data;  // ベースに無音を出力済みなので、上書きじゃなく常に加算。
+                                                       }
+
+                                                       if( 0 < 受け取ったサンプル数 )
+                                                       {
+                                                               // DiviveResult 用。
+                                                               サウンド別読み出し数.Add( 受け取ったサンプル数 );
+                                                       }
+                                                       else
+                                                       {
+                                                               // 再生終了。リストから削除。
+                                                               this.RemoveSound( sound );
+                                                       }
+                                               }
+
+                                               if( DivideResult )
+                                               {
+                                                       サウンド別読み出し数.Sort();
+
+                                                       int 出力位置 = バッファの出力開始位置;
+                                                       int 残りのSound数 = サウンド別読み出し数.Count;
+
+                                                       foreach( var 読み出し数 in サウンド別読み出し数 )
+                                                       {
+                                                               if( 0 == 残りのSound数 )
+                                                                       break;
+
+                                                               while( 出力位置 < ( バッファの出力開始位置 + 読み出し数 ) )
+                                                               {
+                                                                       バッファ[ 出力位置 ] /= 残りのSound数;
+                                                                       バッファ[ 出力位置 ] = Math.Max( -1, Math.Min( 1, バッファ[ 出力位置 ] ) );
+                                                                       出力位置++;
+                                                               }
+
+                                                               残りのSound数--;
+                                                       }
+                                               }
+                                       }
                                }
-                               //-----------------
-                               #endregion
                        }
-                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
+
+                       return 出力サンプル数;
                }
 
-               private int エンドポイントバッファサイズsample = -1;
-               private readonly List<Sound> サウンドリスト = new List<Sound>();
-               private void* 合成用バッファ = null;
-               private readonly object スレッド間同期 = new object();
+               private float _Volume = 1.0f;
+               private CSCore.WaveFormat _WaveFormat = null;
+               private readonly List<Sound> _Sounds = new List<Sound>();
+               private float[] _中間バッファ = null;
+               private readonly object _スレッド間同期 = new object();
        }
 }
index d2e9723..189a44e 100644 (file)
 using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using CSCore;
 
 namespace FDK.メディア.サウンド.WASAPI
 {
-       public unsafe class Sound : IDisposable
+       public class Sound
        {
-               public enum E再生状態
+               public long Length
                {
-                       停止中,    // 初期状態
-                       再生中,
-                       一時停止中,
-                       再生終了,
-               };
-               public E再生状態 再生状態 = E再生状態.停止中;
-
-               /// <summary>
-               /// 0.0(最小)~1.0(原音) の範囲で指定する。再生中でも反映される。
-               /// </summary>
-               public float 音量
-               {
-                       set
-                       {
-                               float 設定値 = Math.Min( Math.Max( value, 0.0f ), 1.0f );  // 0.0未満は0.0へ、1.0超は1.0へ。
-                               lock( this.排他利用 )
-                               {
-                                       this.bs_音量 = 設定値;
-                               }
-                       }
-                       get
-                       {
-                               lock( this.排他利用 )
-                               {
-                                       return this.bs_音量;
-                               }
-                       }
-               }
-               public double 長さsec
-               {
-                       get
-                       {
-                               lock( this.排他利用 )
-                               {
-                                       return ( this.サウンドデータサイズsample / this.WAVEフォーマット.SampleRate );
-                               }
-                       }
+                       get { return this._SampleSource.Length; }
                }
 
-               public Sound()
-               {
-               }
-               public Sound( string サウンドファイルuri ) : this()
+               public long Position
                {
-                       this.ファイルから作成する( サウンドファイルuri );
+                       get { return this._SampleSource.Position; }
+                       set { this._SampleSource.Position = value; }
                }
-               public void ファイルから作成する( string サウンドファイルuri )
-               {
-                       lock( this.排他利用 )
-                       {
-                               #region " 作成済みなら先にDisposeする。"
-                               //-----------------
-                               if( this.作成済み )
-                                       this.Dispose();
-
-                               this.作成済み = false;
-                               //-----------------
-                               #endregion
-
-                               byte[] encodedPcm = null;
 
-                               using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( サウンドファイルuri ) )
-                               using( var pcmStream = new System.IO.MemoryStream() )
-                               {
-                                       #region " サウンドファイル名から SourceReader を作成する。"
-                                       //-----------------
-
-                                       // 先述の using で作成済み。
-
-                                       // 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
-                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
-                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
-
-                                       // メディアタイプを作成し、オーディオフォーマットを設定する。(固定フォーマットとする。)
-                                       using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
-                                       {
-                                               mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio );
-                                               mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Pcm );
-                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WAVEフォーマット.Channels );
-                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WAVEフォーマット.SampleRate );
-                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WAVEフォーマット.BlockAlign );
-                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WAVEフォーマット.AverageBytesPerSecond );
-                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, this.WAVEフォーマット.BitsPerSample );
-                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE
-
-                                               // 作成したメディアタイプを sourceReader にセットする。sourceReader は、必要なデコーダをロードするだろう。
-                                               sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, mediaType );
-                                       }
-
-                                       // 最初のオーディオストリームが選択されていることを保証する。
-                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
-                                       //-----------------
-                                       #endregion
-                                       #region " sourceReader からサンプルを取得してデコードし、メモリストリーム pcmStream へ書き込んだのち、encodedPcm へ変換する。"
-                                       //-----------------
-                                       using( var pcmWriter = new System.IO.BinaryWriter( pcmStream ) )
-                                       {
-                                               while( true )
-                                               {
-                                                       // 次のサンプルを読み込む。
-                                                       int dwActualStreamIndexRef = 0;
-                                                       var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None;
-                                                       Int64 llTimestampRef = 0;
-
-                                                       using( var sample = sourceReader.ReadSample(
-                                                               SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream,
-                                                               SharpDX.MediaFoundation.SourceReaderControlFlags.None,
-                                                               out dwActualStreamIndexRef,
-                                                               out dwStreamFlagsRef,
-                                                               out llTimestampRef ) )
-                                                       {
-                                                               if( null == sample )
-                                                                       break;      // EndOfStream やエラーも含まれる。
-
-                                                               // サンプルをロックし、オーディオデータへのポインタを取得する。
-                                                               int cbMaxLengthRef = 0;
-                                                               int cbCurrentLengthRef = 0;
-                                                               using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
-                                                               {
-                                                                       // オーディオデータをメモリストリームに書き込む。
-                                                                       var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef );
-
-                                                                       byte[] dstData = new byte[ cbCurrentLengthRef ];
-                                                                       byte* psrcData = (byte*) audioData.ToPointer(); // fixed
-                                                                       fixed ( byte* pdstData = dstData )
-                                                                       {
-                                                                               CopyMemory( pdstData, psrcData, cbCurrentLengthRef );
-                                                                       }
-                                                                       pcmWriter.Write( dstData, 0, cbCurrentLengthRef );
-
-                                                                       // サンプルのロックを解除する。
-                                                                       mediaBuffer.Unlock();
-                                                               }
-                                                       }
-                                               }
-
-                                               // ストリームの内容を byte 配列に出力。(Position に関係なく全部出力される。)
-                                               encodedPcm = pcmStream.ToArray();
-                                       }
-                                       //-----------------
-                                       #endregion
-                               }
-                               #region " オーバーサンプリングサウンドデータバッファを確保し、encodedPcm からサンプルを転送する。"
-                               //-----------------
-                               using( var pcmReader = new System.IO.BinaryReader( new System.IO.MemoryStream( encodedPcm ) ) )
-                               {
-                                       // PCMサイズを計算する。(16bit → 32bit でオーバーサンプリングする。)
-                                       this.サウンドデータサイズbyte = encodedPcm.Length * 2;       // 32bit は 16bit の2倍。
-                                       this.サウンドデータサイズsample = this.サウンドデータサイズbyte / 8;    // 1sample = 32bit×2h = 64bit = 8bytes
-
-                                       // オーバーサンプリングサウンドデータ用メモリを確保する。
-                                       this.サウンドデータ = (byte*) FDK.Memory.Alloc( this.サウンドデータサイズbyte );
-
-                                       // ストリームからオーバーサンプリングサウンドデータへ転送する。
-                                       var p = (Int32*) this.サウンドデータ;
-                                       for( int i = 0; i < this.サウンドデータサイズsample; i++ )
-                                       {
-                                               // 1サンプル = 2ch×INT16 を 2ch×INT32 に変換しながら格納。
-                                               *p++ = (Int32) pcmReader.ReadInt16();   // 左ch
-                                               *p++ = (Int32) pcmReader.ReadInt16();   // 右ch
-                                       }
-                               }
-                               //-----------------
-                               #endregion
-
-                               this.再生位置sample = 0;
-                               this.作成済み = true;
-                       }
-               }
-               public void 再生を開始する( double 再生開始位置sec = 0.0 )
+               public WaveFormat WaveFormat
                {
-                       lock( this.排他利用 )
-                       {
-                               if( false == this.作成済み )
-                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
-
-                               int 開始位置sample = (int) ( 再生開始位置sec * this.WAVEフォーマット.SampleRate );
-                               if( 開始位置sample < this.サウンドデータサイズsample )
-                               {
-                                       this.再生状態 = E再生状態.再生中;
-                                       this.再生位置sample = 開始位置sample;
-                               }
-                       }
+                       get { return this._SampleSource.WaveFormat; }
                }
-               public void 再生を一時停止する()
-               {
-                       lock( this.排他利用 )
-                       {
-                               if( false == this.作成済み )
-                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
 
-                               this.再生状態 = E再生状態.一時停止中;
-                       }
-               }
-               public void 再生を再開する()
+               public CSCore.ISampleSource SampleSource
                {
-                       lock( this.排他利用 )
-                       {
-                               if( false == this.作成済み )
-                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
-
-                               if( E再生状態.一時停止中 != this.再生状態 )
-                                       this.再生位置sample = 0;
-
-                               this.再生状態 = E再生状態.再生中;
-                       }
+                       get { return this._SampleSource; }
                }
-               public void 再生を停止する()
-               {
-                       lock( this.排他利用 )
-                       {
-                               if( false == this.作成済み )
-                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
 
-                               this.再生状態 = E再生状態.停止中;
-                               this.再生位置sample = 0;
-                       }
-               }
-               public CSCore.CoreAudioAPI.AudioClientBufferFlags 次のサウンドデータを出力する( void* 出力先, int 出力サンプル数, bool 最初の出力である )
+               /// <summary>
+               /// 音量。0.0(無音)~1.0(原音)。
+               /// </summary>
+               public float Volume
                {
-                       lock( this.排他利用 )
+                       get { return this._Volume; }
+                       set
                        {
-                               #region " 未作成、または再生中でないなら無音フラグをもって帰還。"
-                               //-----------------
-                               if( ( false == this.作成済み ) || ( E再生状態.再生中 != this.再生状態 ) )
-                                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
-                               //-----------------
-                               #endregion
-
-                               int オーバーサンプルサイズbyte = 4 * 2;    // 32bit×2ch
-                               Int32* 出力元 = (Int32*) ( this.サウンドデータ + ( this.再生位置sample * オーバーサンプルサイズbyte ) );
-                               Int32* _出力先 = (Int32*) 出力先;     // この実装ではサンプルは Int32 単位
-                               int 出力できるサンプル数 = System.Math.Min( 出力サンプル数, ( this.サウンドデータサイズsample - this.再生位置sample ) );
-                               int 出力できないサンプル数 = 出力サンプル数 - 出力できるサンプル数;
-
-                               if( 出力できるサンプル数 <= 0 )
-                                       this.再生状態 = E再生状態.再生終了; // 念のため
-
-                               if( 最初の出力である )
-                               {
-                                       #region " (A) 上書き。余った部分にもデータ(無音またはループ)を出力する。"
-                                       //-----------------
-                                       if( 1.0f == this.bs_音量 )
-                                       {
-                                               // 原音(最大音量)。
-                                               CopyMemory( _出力先, 出力元, ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) );
-                                       }
-                                       else
-                                       {
-                                               // 音量を反映。
-                                               for( int i = 0; i < 出力できるサンプル数; i++ )
-                                               {
-                                                       // 1サンプル = 2ch×INT32
-                                                       *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
-                                                       *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
-                                               }
-                                       }
+                               if( ( 0.0f > value ) || ( 1.0f < value ) )
+                                       throw new ArgumentOutOfRangeException();
 
-                                       if( 0 < 出力できないサンプル数 ) // サウンドデータの末尾に達した
-                                       {
-                                               // 残りの部分は、とりあえず今は無音。(ループ再生未対応)
-                                               ZeroMemory(
-                                                       (void*) ( ( (byte*) _出力先 ) + ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ),
-                                                       出力できないサンプル数 * オーバーサンプルサイズbyte );
-                                       }
-                                       //-----------------
-                                       #endregion
-                               }
-                               else
-                               {
-                                       #region " (B) 加算合成。余った部分は放置してもいいし、ループしてデータ加算を続けてもいい。"
-                                       //-----------------
-                                       for( int i = 0; i < 出力できるサンプル数; i++ )
-                                       {
-                                               // 1サンプル = 2ch×INT32
-                                               *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
-                                               *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
-                                       }
-
-                                       if( 0 < 出力できないサンプル数 )
-                                       {
-                                               // 残りの部分は、今回の実装では無視。(ループ再生未対応。)
-                                       }
-                                       //-----------------
-                                       #endregion
-                               }
-
-                               #region " 再生位置を移動。"
-                               //---------------------------------------------------
-                               this.再生位置sample += 出力できるサンプル数;
-
-                               if( this.サウンドデータサイズsample <= this.再生位置sample )  // サウンドデータの末尾に達した
-                               {
-                                       this.再生位置sample = this.サウンドデータサイズsample;
-                                       this.再生状態 = E再生状態.再生終了;   // 再生終了に伴う自動終了なので、"停止中" ではない。
-                               }
-                               //---------------------------------------------------
-                               #endregion
+                               this._Volume = value;
                        }
-
-                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
                }
 
-               #region " Dispose-Finalizeパターン "
-               //----------------
-               ~Sound()
+               /// <summary>
+               /// Sound の生成は、コンストラクタではなく Device.CreateSound() で行うこと。
+               /// (Device 内部で持っている Mixer への参照が必要なため。)
+               /// </summary>
+               /// <param name="path">サウンドファイルパス</param>
+               /// <param name="mixer">使用する Mixer。</param>
+               internal Sound( string path, Mixer mixer )
                {
-                       this.Dispose( false );
+                       this._MixerRef = new WeakReference<Mixer>( mixer );
+                       this._WaveSource = new Decoder( path, mixer.WaveFormat );
+                       this._SampleSource = this._WaveSource.ToSampleSource();
                }
+
                public void Dispose()
                {
-                       this.Dispose( true );
-                       GC.SuppressFinalize( this );
+                       FDK.Utilities.解放する( ref this._SampleSource );
+                       FDK.Utilities.解放する( ref this._WaveSource );
+                       this._MixerRef = null;
                }
-               protected void Dispose( bool Managedも解放する )
-               {
-                       Action サウンドデータを解放する = () => {
-                               if( null != this.サウンドデータ )
-                               {
-                                       FDK.Memory.Free( (void*) this.サウンドデータ );
-                                       this.サウンドデータ = null;
-                               }
-                       };
-
-                       if( Managedも解放する )
-                       {
-                               // C#オブジェクトの解放があればここで。
 
-                               // this.排他利用を使った Unmanaged の解放。
-                               lock( this.排他利用 )
-                               {
-                                       サウンドデータを解放する();
-                               }
-                       }
-                       else
-                       {
-                               // (使える保証がないので)this.排他利用 を使わないUnmanaged の解放。
-                               サウンドデータを解放する();
-                       }
+               public void Play()
+               {
+                       Mixer mixer;
+                       if( this._MixerRef.TryGetTarget( out mixer ) )
+                               mixer.AddSound( this );
                }
-               //----------------
-               #endregion
 
-               private bool 作成済み = false;
-               private byte* サウンドデータ = null;
-               private int サウンドデータサイズbyte = 0;
-               private int サウンドデータサイズsample = 0;
-               private int 再生位置sample = 0;
-               private readonly SharpDX.Multimedia.WaveFormat WAVEフォーマット = new SharpDX.Multimedia.WaveFormat( 44100, 16, 2 );      // 固定
-               private readonly object 排他利用 = new object();
-
-               #region " バックストア "
-               //----------------
-               private float bs_音量 = 1.0f;
-               //----------------
-               #endregion
-
-               #region " Win32 API "
-               //-----------------
-               [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
-               private static extern unsafe void CopyMemory( void* dst, void* src, int size );
+               public void Stop()
+               {
+                       Mixer mixer;
+                       if( this._MixerRef.TryGetTarget( out mixer ) )
+                               mixer.RemoveSound( this );
+               }
 
-               [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
-               private static extern unsafe void ZeroMemory( void* dst, int length );
-               //-----------------
-               #endregion
+               private CSCore.IWaveSource _WaveSource = null;
+               private CSCore.ISampleSource _SampleSource = null;
+               private System.WeakReference<Mixer> _MixerRef = null;
+               private float _Volume = 1.0f;
        }
 }
index bc62ad4..24c2230 100644 (file)
@@ -1,25 +1,30 @@
 using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using CSCore;
 
 namespace FDK.メディア.サウンド.WASAPI
 {
        /// <summary>
-       /// WASAPIデバイス(のIAudioClock)をソースとするタイマー。
+       ///             WASAPIデバイス(のIAudioClock)をソースとするタイマー。
        /// </summary>
        /// <remarks>
-       /// WASAPIデバイスと厳密に同期をとる場合には、QPCTimer ではなく、このクラスを使用する。
+       ///             WASAPIデバイスと厳密に同期をとる場合には、QPCTimer ではなく、このクラスを使用する。
        /// </remarks>
        public class SoundTimer
        {
-               /// <summary>
-               /// エラー時は double.NaN を返す。
-               /// </summary>
+               /// <returns>エラー時は double.NaN を返す。</returns>
                public double 現在のデバイス位置secを取得する( CSCore.CoreAudioAPI.AudioClock audioClock )
                {
-                       lock( this.スレッド間同期 )
+                       lock( this._スレッド間同期 )
                        {
                                int hr = 0;
-                               long デバイス周波数 = 0;// audioClock.Pu64Frequency;
+
+                               long デバイス周波数 = 0;
+                               //デバイス周波数 = audioClock.Pu64Frequency;     --> たまにおかしくなるので、使わない。
                                audioClock.GetFrequencyNative( out デバイス周波数 );
+
                                long QPC周波数 = FDK.カウンタ.QPCTimer.周波数;
                                long デバイス位置 = 0;
                                long デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 = 0;
@@ -57,12 +62,12 @@ namespace FDK.メディア.サウンド.WASAPI
                                // そこで、この時点で最新のパフォーマンスカウンタを取得し、時間の増加分をデバイス位置に加えて精度を上げる。(MSDNより)
                                double QPCから調べた差分sec =
                                        ( (double) FDK.カウンタ.QPCTimer.生カウント / QPC周波数 ) -
-                                       ( (double) デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 ) / 10000000.0;
+                                       FDK.Utilities.変換_100ns単位からsec単位へ( デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 );
 
-                               return ( (double) デバイス位置 ) / デバイス周波数 + QPCから調べた差分sec;
+                               return ( ( (double) デバイス位置 ) / デバイス周波数 ) + QPCから調べた差分sec;
                        }
                }
 
-               private readonly object スレッド間同期 = new object();
+               private readonly object _スレッド間同期 = new object();
        }
 }
diff --git a/FDK24/メディア/サウンド/WASAPIold/Device.cs b/FDK24/メディア/サウンド/WASAPIold/Device.cs
new file mode 100644 (file)
index 0000000..88f8823
--- /dev/null
@@ -0,0 +1,360 @@
+using System;
+using System.Diagnostics;
+
+namespace FDK.メディア.サウンド.WASAPIold
+{
+       public unsafe class Device : IDisposable
+       {
+               public double 遅延sec
+               {
+                       get { return ( this.更新間隔sec * 1000.0 ); }
+               }
+               public CSCore.CoreAudioAPI.AudioClock AudioClock
+               {
+                       get { return this.bs_AudioClock; }
+               }
+               public bool Dispose済み
+               {
+                       get;
+                       protected set;
+               } = true;
+
+               public void 初期化する( CSCore.CoreAudioAPI.AudioClientShareMode 共有モード, double 希望更新間隔sec = 0.015 )
+               {
+                       FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
+
+                       int hr = 0;
+
+                       lock( this.スレッド間同期 )
+                       {
+                               Trace.Assert( this.Dispose済み );
+                               this.Dispose済み = false;
+
+                               this.共有モード = 共有モード;
+
+                               #region " AudioClientをアクティベートする。"
+                               //-----------------
+                               using( var devices = new CSCore.CoreAudioAPI.MMDeviceEnumerator() )
+                               using( var 既定のデバイス = devices.GetDefaultAudioEndpoint( CSCore.CoreAudioAPI.DataFlow.Render, CSCore.CoreAudioAPI.Role.Console ) )
+                               {
+                                       this.AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( 既定のデバイス );
+                               }
+                               //-----------------
+                               #endregion
+                               #region " 指定された希望更新間隔とデバイス能力をもとに、更新間隔を決定する。"
+                               //-----------------
+                               long 共有モードでの間隔100ns = 0;
+                               long 排他モードでの最小間隔100ns = 0;
+
+                               // デバイスから間隔値を取得する。
+                               hr = this.AudioClient.GetDevicePeriodNative( out 共有モードでの間隔100ns, out 排他モードでの最小間隔100ns );
+                               if( 0 > hr )
+                                       System.Runtime.InteropServices.Marshal.ThrowExceptionForHR( hr );
+
+                               if( 共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
+                               {
+                                       this.更新間隔100ns = 共有モードでの間隔100ns;
+                               }
+                               else
+                               {
+                                       this.更新間隔100ns = Math.Max( FDK.Utilities.変換_sec単位から100ns単位へ( 希望更新間隔sec ), 排他モードでの最小間隔100ns );
+                               }
+                               //-----------------
+                               #endregion
+                               #region " デバイスフォーマットを決定する。"
+                               //----------------
+                               if( this.共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared )
+                               {
+                                       this.WaveFormat = this.AudioClient.GetMixFormat();
+                               }
+                               else
+                               {
+                                       this.WaveFormat = new CSCore.WaveFormat( 44100, 16, 2, CSCore.AudioEncoding.Pcm );
+                               }
+                               //----------------
+                               #endregion
+                               #region " AudioClient を初期化する。"
+                               //-----------------
+                               try
+                               {
+                                       this.AudioClient.Initialize(
+                                               this.共有モード,
+                                               CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,
+                                               // | CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsNoPersist,   // 音量とミュートを記憶しない → 無効。してください
+                                               this.更新間隔100ns,
+                                               this.更新間隔100ns,
+                                               this.WaveFormat,
+                                               Guid.Empty );   // この AudioClient (= AudioStrem) が所属する AudioSession。null ならデフォルトのAudioSessionに登録される。
+                               }
+                               catch( CSCore.CoreAudioAPI.CoreAudioAPIException e )
+                               {
+                                       if( AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED == e.ErrorCode )
+                                       {
+                                               #region " 排他&イベント駆動モードの場合、バッファサイズアライメントエラーが返される場合がある。この場合、サイズを調整してオーディオストリームを作成し直す。"
+                                               //----------------
+                                               this.更新間隔100ns = FDK.Utilities.変換_sec単位から100ns単位へ(
+                                                       (double) this.AudioClient.GetBufferSize() / (double) this.WaveFormat.SampleRate );   // GetBufferSize は、更新間隔に一番近い、アライメントされたバッファサイズ(sample単位)を返す。
+
+                                               // AudioClient を一度解放し、もう一度アクティベートし直す。
+                                               this.AudioClient.Dispose();
+                                               using( var devices = new CSCore.CoreAudioAPI.MMDeviceEnumerator() )
+                                               using( var 既定のデバイス = devices.GetDefaultAudioEndpoint( CSCore.CoreAudioAPI.DataFlow.Render, CSCore.CoreAudioAPI.Role.Console ) )
+                                               {
+                                                       this.AudioClient = CSCore.CoreAudioAPI.AudioClient.FromMMDevice( 既定のデバイス );
+                                               }
+
+                                               // アライメントされたサイズを使って、AudioClient を再初期化する。それでもエラーなら例外発出。
+                                               this.AudioClient.Initialize(
+                                                       this.共有モード,
+                                                       CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsEventCallback,
+                                                       // | CSCore.CoreAudioAPI.AudioClientStreamFlags.StreamFlagsNoPersist,   // 音量とミュートを記憶しない → 無効。してください
+                                                       this.更新間隔100ns,
+                                                       this.更新間隔100ns,
+                                                       this.WaveFormat,
+                                                       Guid.Empty );
+                                               //----------------
+                                               #endregion
+                                       }
+                               }
+
+                               this.更新間隔sample = this.AudioClient.GetBufferSize();
+                               //-----------------
+                               #endregion
+                               #region " AudioRenderClient を取得する。"
+                               //-----------------
+                               this.AudioRenderClient = CSCore.CoreAudioAPI.AudioRenderClient.FromAudioClient( this.AudioClient );
+                               //-----------------
+                               #endregion
+                               #region " AudioClock を取得する。"
+                               //-----------------
+                               this.bs_AudioClock = CSCore.CoreAudioAPI.AudioClock.FromAudioClient( this.AudioClient );
+                               //-----------------
+                               #endregion
+                               #region " エンドポイントバッファを無音で埋めておく。"
+                               //-----------------
+                               this.AudioRenderClient.GetBuffer( this.更新間隔sample );
+                               
+                               // 無音を書き込んだことにして、バッファをコミット。(GetBuffer の戻り値は使わない。)
+                               this.AudioRenderClient.ReleaseBuffer( this.更新間隔sample, CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent );
+                               //-----------------
+                               #endregion
+
+                               #region " ミキサーを生成し初期化する。"
+                               //-----------------
+                               this.Mixer = new Mixer( this.更新間隔sample );
+                               //-----------------
+                               #endregion
+
+                               #region " 情報表示。"
+                               //-----------------
+                               FDK.Log.Info( $"WASAPIクライアントを初期化しました。" );
+                               FDK.Log.Info( ( 共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Shared ) ?
+                                       " モード: 共有 & イベント駆動" :
+                                       " モード: 排他 & イベント駆動" );
+                               FDK.Log.Info( $" フォーマット: {this.WaveFormat.BitsPerSample} bits, {this.WaveFormat.SampleRate} Hz" );
+                               FDK.Log.Info( $" エンドポイントバッファ: {( (float) this.更新間隔sample / (double) this.WaveFormat.SampleRate ) * 1000.0f} ミリ秒 ({this.更新間隔sample} samples) × 2枚" );
+                               FDK.Log.Info( $" 希望更新間隔: {希望更新間隔sec * 1000.0} ミリ秒" );
+                               FDK.Log.Info( $" 更新間隔: {this.更新間隔sec * 1000.0} ミリ秒 ({this.更新間隔sample} samples)" );
+                               if( 共有モード == CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive )
+                                       FDK.Log.Info( $" 最小間隔: {FDK.Utilities.変換_100ns単位からsec単位へ( 排他モードでの最小間隔100ns )} ミリ秒" );
+                               //-----------------
+                               #endregion
+
+                               #region " ワークキューとイベントを作成し、作業項目を登録する。"
+                               //-----------------
+                               // MediaFoundation が管理する、プロセス&MMCSSタスクごとに1つずつ作ることができる特別な共有ワークキューを取得(または生成して取得)する。
+                               int dwTaskId = 0;
+                               SharpDX.MediaFoundation.MediaFactory.LockSharedWorkQueue( ( 0.011 > this.更新間隔sec ) ? "Pro Audio" : "Games", 0, ref dwTaskId, out this.QueueID );
+
+                               // エンドポイントバッファからの出力要請イベントを作成し、AudioClient に登録する。
+                               this.出力要請イベント = CreateEvent( IntPtr.Zero, false, false, "WASAPI出力要請イベント" );
+                               this.AudioClient.SetEventHandle( this.出力要請イベント );
+
+                               // コールバックを作成し、ワークキューに最初の作業項目を登録する。
+                               this.出力要請イベントのコールバック = new MFAsyncCallback( this.QueueID, this.出力要請イベントへ対応する );
+                               this.作業項目をキューに格納する();
+                               //-----------------
+                               #endregion
+                               #region " WASAPI レンダリングを開始。"
+                               //-----------------
+                               this.AudioClient.Start();
+                               //-----------------
+                               #endregion
+                       }
+
+                       FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
+               }
+               public void Dispose()
+               {
+                       FDK.Log.BeginInfo( $"{FDK.Utilities.現在のメソッド名}" );
+
+                       Trace.Assert( false == this.Dispose済み );
+
+                       #region " WASAPI作業項目を終了させる。オーディオのレンダリングを止める前に行うこと。"
+                       //-----------------
+                       {
+                               //SharpDX.MediaFoundation.MediaFactory.CancelWorkItem( this.出力要請イベントキャンセル用キー ); --> コールバックの実行中にキャンセルしてしまうと NullReference例外
+                               this.出力終了通知.状態 = 同期.TriStateEvent.状態種別.ON;
+                               this.出力終了通知.OFFになるまでブロックする();
+                               FDK.Log.Info( "WASAPI出力処理を終了しました。" );
+                       }
+                       //-----------------
+                       #endregion
+
+                       lock( this.スレッド間同期 )
+                       {
+                               #region " オーディオのレンダリングを停止する。"
+                               //-----------------
+                               this.AudioClient?.Stop();
+                               //-----------------
+                               #endregion
+                               #region " ミキサー(とサウンドリスト)は現状を維持する。"
+                               //-----------------
+
+                               // 何もしない。
+
+                               //-----------------
+                               #endregion
+                               #region " WASAPIオブジェクトを解放する。"
+                               //-----------------
+                               FDK.Utilities.解放する( ref this.bs_AudioClock );
+                               FDK.Utilities.解放する( ref this.AudioRenderClient );
+                               FDK.Utilities.解放する( ref this.AudioClient );
+                               //-----------------
+                               #endregion
+                               #region " 共有ワークキューをこのプロセスから解放する。"
+                               //-----------------
+                               if( int.MaxValue != this.QueueID )
+                               {
+                                       SharpDX.MediaFoundation.MediaFactory.UnlockWorkQueue( this.QueueID );
+                                       this.QueueID = int.MaxValue;
+                               }
+                               //-----------------
+                               #endregion
+                               #region " WASAPIイベント駆動用のコールバックとイベントを解放する。"
+                               //-----------------
+                               FDK.Utilities.解放する( ref this.出力要請イベントのコールバック );
+
+                               if( IntPtr.Zero != this.出力要請イベント )
+                                       Device.CloseHandle( this.出力要請イベント );
+                               //-----------------
+                               #endregion
+
+                               this.Dispose済み = true;
+                       }
+
+                       FDK.Log.EndInfo( $"{FDK.Utilities.現在のメソッド名}" );
+               }
+               public void サウンドをミキサーに追加する( Sound sound )
+               {
+                       this.Mixer.サウンドを追加する( sound );
+               }
+               public void サウンドをミキサーから削除する( Sound sound )
+               {
+                       this.Mixer.サウンドを削除する( sound );
+               }
+
+               protected CSCore.CoreAudioAPI.AudioClientShareMode 共有モード;
+               protected CSCore.CoreAudioAPI.AudioClient AudioClient = null;
+               protected CSCore.CoreAudioAPI.AudioRenderClient AudioRenderClient = null;
+               protected CSCore.WaveFormat WaveFormat = null;
+               protected long 更新間隔100ns = 0;
+               protected int 更新間隔sample = 0;
+               protected int 更新間隔byte => ( this.更新間隔sample * this.WaveFormat.Channels * this.WaveFormat.BytesPerSample );
+               protected double 更新間隔sec => ( FDK.Utilities.変換_100ns単位からsec単位へ( this.更新間隔100ns ) );
+
+               // ミキサー。サウンドリストもここ。
+               protected Mixer Mixer = null;
+
+               // WASAPIバッファ出力用
+               private int QueueID = int.MaxValue;
+               private IntPtr 出力要請イベント = IntPtr.Zero;
+               private MFAsyncCallback 出力要請イベントのコールバック = null;
+               private long 出力要請イベントキャンセル用キー = 0;
+               private FDK.同期.TriStateEvent 出力終了通知 = new 同期.TriStateEvent();
+
+               private readonly object スレッド間同期 = new object();
+
+               private void 作業項目をキューに格納する()
+               {
+                       var asyncResult = (SharpDX.MediaFoundation.AsyncResult) null;
+                       try
+                       {
+                               // IAsyncCallback を内包した AsyncResult を作成する。
+                               SharpDX.MediaFoundation.MediaFactory.CreateAsyncResult(
+                                       null,
+                                       SharpDX.ComObject.ToCallbackPtr<SharpDX.MediaFoundation.IAsyncCallback>( this.出力要請イベントのコールバック ),
+                                       null,
+                                       out asyncResult );
+
+                               // 作成した AsyncResult を、ワークキュー投入イベントの待機状態にする。
+                               SharpDX.MediaFoundation.MediaFactory.PutWaitingWorkItem(
+                                       hEvent: this.出力要請イベント,
+                                       priority: 0,
+                                       resultRef: asyncResult,
+                                       keyRef: out this.出力要請イベントキャンセル用キー );
+                       }
+                       finally
+                       {
+                               // out 引数に使う変数は using 変数にはできないので、代わりに try-finally を使う。
+                               asyncResult?.Dispose();
+                       }
+               }
+
+               /// <summary>
+               /// このメソッドは、WASAPIイベント発生時にワークキューに投入され作業項目から呼び出される。
+               /// </summary>
+               private void 出力要請イベントへ対応する( SharpDX.MediaFoundation.AsyncResult asyncResult )
+               {
+                       try
+                       {
+                               // 出力終了通知が来ていれば、応答してすぐに終了する。
+                               if( this.出力終了通知.状態 == 同期.TriStateEvent.状態種別.ON )
+                               {
+                                       this.出力終了通知.状態 = 同期.TriStateEvent.状態種別.無効;
+                                       return;
+                               }
+
+                               lock( this.スレッド間同期 )
+                               {
+                                       // エンドポインタの空きバッファへのポインタを取得する。
+                                       // このポインタが差すのはネイティブで確保されたメモリなので、GCの対象外である。はず。
+                                       var bufferPtr = this.AudioRenderClient.GetBuffer( this.更新間隔sample );    // イベント駆動なのでサイズ固定。
+
+                                       // ミキサーを使って、エンドポインタへサウンドデータを出力する。
+                                       var flags = this.Mixer.エンドポイントへ出力する( (void*) bufferPtr, this.更新間隔sample );
+
+                                       // エンドポインタのバッファを解放する。
+                                       this.AudioRenderClient.ReleaseBuffer( this.更新間隔sample, flags );
+
+                                       // 後続のイベント待ち作業項目をキューに格納する。
+                                       this.作業項目をキューに格納する();
+
+                                       // 以降、WASAPIからイベントが発火されるたび、作業項目を通じて本メソッドが呼び出される。
+                               }
+                       }
+                       catch
+                       {
+                               // 例外は無視。
+                       }
+               }
+
+               #region " バックストア。"
+               //----------------
+               private CSCore.CoreAudioAPI.AudioClock bs_AudioClock = null;
+               //----------------
+               #endregion
+
+               #region " Win32 API "
+               //-----------------
+               private static int AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED = unchecked((int) 0x88890019);
+
+               [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
+               private static extern IntPtr CreateEvent( IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName );
+
+               [System.Runtime.InteropServices.DllImport( "kernel32.dll" )]
+               private static extern bool CloseHandle( IntPtr hObject );
+               //-----------------
+               #endregion
+       }
+}
@@ -1,6 +1,6 @@
 using System;
 
-namespace FDK.メディア.サウンド.WASAPI
+namespace FDK.メディア.サウンド.WASAPIold
 {
        /// <summary>
        /// IMFAsyncCallback の汎用的な実装。
diff --git a/FDK24/メディア/サウンド/WASAPIold/Mixer.cs b/FDK24/メディア/サウンド/WASAPIold/Mixer.cs
new file mode 100644 (file)
index 0000000..4e2dba7
--- /dev/null
@@ -0,0 +1,128 @@
+using System;
+using System.Collections.Generic;
+
+namespace FDK.メディア.サウンド.WASAPIold
+{
+       /// <summary>
+       /// Sound のリストを持ち、そのサウンドデータを合成してWASAPIデバイスへ出力するミキサー。
+       /// </summary>
+       public unsafe class Mixer : IDisposable
+       {
+               public Mixer()
+               {
+               }
+               public Mixer( int エンドポイントバッファサイズsample ) : this()
+               {
+                       this.初期化する( エンドポイントバッファサイズsample );
+               }
+               public void 初期化する( int エンドポイントバッファサイズsample )
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               if( エンドポイントバッファサイズsample == this.エンドポイントバッファサイズsample )
+                                       return;     // サイズに変更があったときのみ初期化する。
+
+                               this.エンドポイントバッファサイズsample = エンドポイントバッファサイズsample;
+                               this.サウンドリスト.Clear();
+
+                               if( null != this.合成用バッファ )
+                                       FDK.Memory.Free( this.合成用バッファ );
+                               this.合成用バッファ = FDK.Memory.Alloc( this.エンドポイントバッファサイズsample * ( 4 * 2 ) );       // 1sample = 32bit×2ch
+                       }
+               }
+               public void Dispose()
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               this.サウンドリスト.Clear();
+                               if( null != this.合成用バッファ )
+                               {
+                                       FDK.Memory.Free( this.合成用バッファ );
+                                       this.合成用バッファ = null;
+                               }
+                               this.エンドポイントバッファサイズsample = -1;
+                       }
+               }
+               public void サウンドリストをクリアする()
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               this.サウンドリスト.Clear();
+                       }
+               }
+               public void サウンドを追加する( Sound sound )
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               this.サウンドリスト.Add( sound );
+                       }
+               }
+               public void サウンドを削除する( Sound sound )
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               this.サウンドリスト.Remove( sound );
+                       }
+               }
+               public CSCore.CoreAudioAPI.AudioClientBufferFlags エンドポイントへ出力する( void* エンドポイントの出力先, int 出力数sample )
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               if( null == this.合成用バッファ )
+                                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
+
+                               #region " すべてのサウンドについて、合成バッファへ出力する。"
+                               //-----------------
+                               bool 最初の出力である = true;
+
+                               foreach( var sound in this.サウンドリスト )
+                               {
+                                       var flag = sound.次のサウンドデータを出力する( this.合成用バッファ, 出力数sample, 最初の出力である );
+
+                                       if( false == flag.HasFlag( CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent ) )
+                                               最初の出力である = false;   // sound が何らかのデータを出力した(戻り値がSILENTじゃなかった)
+                               }
+
+                               // 全サウンドが SILENT だったなら、エンドポイントには何も書き込まずに SILENT フラグを返す。
+                               if( 最初の出力である )
+                                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
+                               //-----------------
+                               #endregion
+                               #region " 合成バッファのデータ値(32bit;オーバーサンプル)を16bitに丸めてエンドポイントに出力する。"
+                               //-----------------
+                               Int32* 出力元 = (Int32*) ( this.合成用バッファ );
+                               Int16* 出力先 = (Int16*) エンドポイントの出力先;
+                               for( int i = 0; i < 出力数sample; i++ )
+                               {
+                                       Int32 src;
+
+                                       // 音量やミュートの処理は不要。(WASAPI が自動でマスタ音量・ミュート状態に合わせてくれる)
+
+                                       // 左ch
+                                       src = *出力元++;
+                                       if( -32768 > src )
+                                               src = -32768;
+                                       else if( 32767 < src )
+                                               src = 32767;
+                                       *出力先++ = (Int16) src;
+
+                                       // 右ch
+                                       src = *出力元++;
+                                       if( -32768 > src )
+                                               src = -32768;
+                                       else if( 32767 < src )
+                                               src = 32767;
+                                       *出力先++ = (Int16) src;
+                               }
+                               //-----------------
+                               #endregion
+                       }
+                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
+               }
+
+               private int エンドポイントバッファサイズsample = -1;
+               private readonly List<Sound> サウンドリスト = new List<Sound>();
+               private void* 合成用バッファ = null;
+               private readonly object スレッド間同期 = new object();
+       }
+}
diff --git a/FDK24/メディア/サウンド/WASAPIold/Sound.cs b/FDK24/メディア/サウンド/WASAPIold/Sound.cs
new file mode 100644 (file)
index 0000000..d7afcbe
--- /dev/null
@@ -0,0 +1,376 @@
+using System;
+
+namespace FDK.メディア.サウンド.WASAPIold
+{
+       public unsafe class Sound : IDisposable
+       {
+               public enum E再生状態
+               {
+                       停止中,    // 初期状態
+                       再生中,
+                       一時停止中,
+                       再生終了,
+               };
+               public E再生状態 再生状態 = E再生状態.停止中;
+
+               /// <summary>
+               /// 0.0(最小)~1.0(原音) の範囲で指定する。再生中でも反映される。
+               /// </summary>
+               public float 音量
+               {
+                       set
+                       {
+                               float 設定値 = Math.Min( Math.Max( value, 0.0f ), 1.0f );  // 0.0未満は0.0へ、1.0超は1.0へ。
+                               lock( this.排他利用 )
+                               {
+                                       this.bs_音量 = 設定値;
+                               }
+                       }
+                       get
+                       {
+                               lock( this.排他利用 )
+                               {
+                                       return this.bs_音量;
+                               }
+                       }
+               }
+               public double 長さsec
+               {
+                       get
+                       {
+                               lock( this.排他利用 )
+                               {
+                                       return ( this.サウンドデータサイズsample / this.WAVEフォーマット.SampleRate );
+                               }
+                       }
+               }
+
+               public Sound()
+               {
+               }
+               public Sound( string サウンドファイルuri ) : this()
+               {
+                       this.ファイルから作成する( サウンドファイルuri );
+               }
+               public void ファイルから作成する( string サウンドファイルuri )
+               {
+                       lock( this.排他利用 )
+                       {
+                               #region " 作成済みなら先にDisposeする。"
+                               //-----------------
+                               if( this.作成済み )
+                                       this.Dispose();
+
+                               this.作成済み = false;
+                               //-----------------
+                               #endregion
+
+                               byte[] encodedPcm = null;
+
+                               using( var sourceReader = new SharpDX.MediaFoundation.SourceReader( サウンドファイルuri ) )
+                               using( var pcmStream = new System.IO.MemoryStream() )
+                               {
+                                       #region " サウンドファイル名から SourceReader を作成する。"
+                                       //-----------------
+
+                                       // 先述の using で作成済み。
+
+                                       // 最初のオーディオストリームを選択し、その他のすべてのストリームを非選択にする。
+                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.AllStreams, false );
+                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
+
+                                       // メディアタイプを作成し、オーディオフォーマットを設定する。(固定フォーマットとする。)
+                                       using( var mediaType = new SharpDX.MediaFoundation.MediaType() )
+                                       {
+                                               mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.MajorType, SharpDX.MediaFoundation.MediaTypeGuids.Audio );
+                                               mediaType.Set<Guid>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.Subtype, SharpDX.MediaFoundation.AudioFormatGuids.Pcm );
+                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioNumChannels, this.WAVEフォーマット.Channels );
+                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioSamplesPerSecond, this.WAVEフォーマット.SampleRate );
+                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBlockAlignment, this.WAVEフォーマット.BlockAlign );
+                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioAvgBytesPerSecond, this.WAVEフォーマット.AverageBytesPerSecond );
+                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AudioBitsPerSample, this.WAVEフォーマット.BitsPerSample );
+                                               mediaType.Set<int>( SharpDX.MediaFoundation.MediaTypeAttributeKeys.AllSamplesIndependent, 1 ); // TRUE
+
+                                               // 作成したメディアタイプを sourceReader にセットする。sourceReader は、必要なデコーダをロードするだろう。
+                                               sourceReader.SetCurrentMediaType( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, mediaType );
+                                       }
+
+                                       // 最初のオーディオストリームが選択されていることを保証する。
+                                       sourceReader.SetStreamSelection( SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream, true );
+                                       //-----------------
+                                       #endregion
+                                       #region " sourceReader からサンプルを取得してデコードし、メモリストリーム pcmStream へ書き込んだのち、encodedPcm へ変換する。"
+                                       //-----------------
+                                       using( var pcmWriter = new System.IO.BinaryWriter( pcmStream ) )
+                                       {
+                                               while( true )
+                                               {
+                                                       // 次のサンプルを読み込む。
+                                                       int dwActualStreamIndexRef = 0;
+                                                       var dwStreamFlagsRef = SharpDX.MediaFoundation.SourceReaderFlags.None;
+                                                       Int64 llTimestampRef = 0;
+
+                                                       using( var sample = sourceReader.ReadSample(
+                                                               SharpDX.MediaFoundation.SourceReaderIndex.FirstAudioStream,
+                                                               SharpDX.MediaFoundation.SourceReaderControlFlags.None,
+                                                               out dwActualStreamIndexRef,
+                                                               out dwStreamFlagsRef,
+                                                               out llTimestampRef ) )
+                                                       {
+                                                               if( null == sample )
+                                                                       break;      // EndOfStream やエラーも含まれる。
+
+                                                               // サンプルをロックし、オーディオデータへのポインタを取得する。
+                                                               int cbMaxLengthRef = 0;
+                                                               int cbCurrentLengthRef = 0;
+                                                               using( var mediaBuffer = sample.ConvertToContiguousBuffer() )
+                                                               {
+                                                                       // オーディオデータをメモリストリームに書き込む。
+                                                                       var audioData = mediaBuffer.Lock( out cbMaxLengthRef, out cbCurrentLengthRef );
+
+                                                                       byte[] dstData = new byte[ cbCurrentLengthRef ];
+                                                                       byte* psrcData = (byte*) audioData.ToPointer(); // fixed
+                                                                       fixed ( byte* pdstData = dstData )
+                                                                       {
+                                                                               CopyMemory( pdstData, psrcData, cbCurrentLengthRef );
+                                                                       }
+                                                                       pcmWriter.Write( dstData, 0, cbCurrentLengthRef );
+
+                                                                       // サンプルのロックを解除する。
+                                                                       mediaBuffer.Unlock();
+                                                               }
+                                                       }
+                                               }
+
+                                               // ストリームの内容を byte 配列に出力。(Position に関係なく全部出力される。)
+                                               encodedPcm = pcmStream.ToArray();
+                                       }
+                                       //-----------------
+                                       #endregion
+                               }
+                               #region " オーバーサンプリングサウンドデータバッファを確保し、encodedPcm からサンプルを転送する。"
+                               //-----------------
+                               using( var pcmReader = new System.IO.BinaryReader( new System.IO.MemoryStream( encodedPcm ) ) )
+                               {
+                                       // PCMサイズを計算する。(16bit → 32bit でオーバーサンプリングする。)
+                                       this.サウンドデータサイズbyte = encodedPcm.Length * 2;       // 32bit は 16bit の2倍。
+                                       this.サウンドデータサイズsample = this.サウンドデータサイズbyte / 8;    // 1sample = 32bit×2h = 64bit = 8bytes
+
+                                       // オーバーサンプリングサウンドデータ用メモリを確保する。
+                                       this.サウンドデータ = (byte*) FDK.Memory.Alloc( this.サウンドデータサイズbyte );
+
+                                       // ストリームからオーバーサンプリングサウンドデータへ転送する。
+                                       var p = (Int32*) this.サウンドデータ;
+                                       for( int i = 0; i < this.サウンドデータサイズsample; i++ )
+                                       {
+                                               // 1サンプル = 2ch×INT16 を 2ch×INT32 に変換しながら格納。
+                                               *p++ = (Int32) pcmReader.ReadInt16();   // 左ch
+                                               *p++ = (Int32) pcmReader.ReadInt16();   // 右ch
+                                       }
+                               }
+                               //-----------------
+                               #endregion
+
+                               this.再生位置sample = 0;
+                               this.作成済み = true;
+                       }
+               }
+               public void 再生を開始する( double 再生開始位置sec = 0.0 )
+               {
+                       lock( this.排他利用 )
+                       {
+                               if( false == this.作成済み )
+                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
+
+                               int 開始位置sample = (int) ( 再生開始位置sec * this.WAVEフォーマット.SampleRate );
+                               if( 開始位置sample < this.サウンドデータサイズsample )
+                               {
+                                       this.再生状態 = E再生状態.再生中;
+                                       this.再生位置sample = 開始位置sample;
+                               }
+                       }
+               }
+               public void 再生を一時停止する()
+               {
+                       lock( this.排他利用 )
+                       {
+                               if( false == this.作成済み )
+                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
+
+                               this.再生状態 = E再生状態.一時停止中;
+                       }
+               }
+               public void 再生を再開する()
+               {
+                       lock( this.排他利用 )
+                       {
+                               if( false == this.作成済み )
+                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
+
+                               if( E再生状態.一時停止中 != this.再生状態 )
+                                       this.再生位置sample = 0;
+
+                               this.再生状態 = E再生状態.再生中;
+                       }
+               }
+               public void 再生を停止する()
+               {
+                       lock( this.排他利用 )
+                       {
+                               if( false == this.作成済み )
+                                       return;     // エラーにはしない。サウンド作成失敗時には、何も再生しないようにするだけ。
+
+                               this.再生状態 = E再生状態.停止中;
+                               this.再生位置sample = 0;
+                       }
+               }
+               public CSCore.CoreAudioAPI.AudioClientBufferFlags 次のサウンドデータを出力する( void* 出力先, int 出力サンプル数, bool 最初の出力である )
+               {
+                       lock( this.排他利用 )
+                       {
+                               #region " 未作成、または再生中でないなら無音フラグをもって帰還。"
+                               //-----------------
+                               if( ( false == this.作成済み ) || ( E再生状態.再生中 != this.再生状態 ) )
+                                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.Silent;
+                               //-----------------
+                               #endregion
+
+                               int オーバーサンプルサイズbyte = 4 * 2;    // 32bit×2ch
+                               Int32* 出力元 = (Int32*) ( this.サウンドデータ + ( this.再生位置sample * オーバーサンプルサイズbyte ) );
+                               Int32* _出力先 = (Int32*) 出力先;     // この実装ではサンプルは Int32 単位
+                               int 出力できるサンプル数 = System.Math.Min( 出力サンプル数, ( this.サウンドデータサイズsample - this.再生位置sample ) );
+                               int 出力できないサンプル数 = 出力サンプル数 - 出力できるサンプル数;
+
+                               if( 出力できるサンプル数 <= 0 )
+                                       this.再生状態 = E再生状態.再生終了; // 念のため
+
+                               if( 最初の出力である )
+                               {
+                                       #region " (A) 上書き。余った部分にもデータ(無音またはループ)を出力する。"
+                                       //-----------------
+                                       if( 1.0f == this.bs_音量 )
+                                       {
+                                               // 原音(最大音量)。
+                                               CopyMemory( _出力先, 出力元, ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) );
+                                       }
+                                       else
+                                       {
+                                               // 音量を反映。
+                                               for( int i = 0; i < 出力できるサンプル数; i++ )
+                                               {
+                                                       // 1サンプル = 2ch×INT32
+                                                       *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
+                                                       *_出力先++ = (Int32) ( ( *出力元++ ) * this.bs_音量 );
+                                               }
+                                       }
+
+                                       if( 0 < 出力できないサンプル数 ) // サウンドデータの末尾に達した
+                                       {
+                                               // 残りの部分は、とりあえず今は無音。(ループ再生未対応)
+                                               ZeroMemory(
+                                                       (void*) ( ( (byte*) _出力先 ) + ( 出力できるサンプル数 * オーバーサンプルサイズbyte ) ),
+                                                       出力できないサンプル数 * オーバーサンプルサイズbyte );
+                                       }
+                                       //-----------------
+                                       #endregion
+                               }
+                               else
+                               {
+                                       #region " (B) 加算合成。余った部分は放置してもいいし、ループしてデータ加算を続けてもいい。"
+                                       //-----------------
+                                       for( int i = 0; i < 出力できるサンプル数; i++ )
+                                       {
+                                               // 1サンプル = 2ch×INT32
+                                               *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
+                                               *_出力先++ += (Int32) ( ( *出力元++ ) * this.bs_音量 );
+                                       }
+
+                                       if( 0 < 出力できないサンプル数 )
+                                       {
+                                               // 残りの部分は、今回の実装では無視。(ループ再生未対応。)
+                                       }
+                                       //-----------------
+                                       #endregion
+                               }
+
+                               #region " 再生位置を移動。"
+                               //---------------------------------------------------
+                               this.再生位置sample += 出力できるサンプル数;
+
+                               if( this.サウンドデータサイズsample <= this.再生位置sample )  // サウンドデータの末尾に達した
+                               {
+                                       this.再生位置sample = this.サウンドデータサイズsample;
+                                       this.再生状態 = E再生状態.再生終了;   // 再生終了に伴う自動終了なので、"停止中" ではない。
+                               }
+                               //---------------------------------------------------
+                               #endregion
+                       }
+
+                       return CSCore.CoreAudioAPI.AudioClientBufferFlags.None;
+               }
+
+               #region " Dispose-Finalizeパターン "
+               //----------------
+               ~Sound()
+               {
+                       this.Dispose( false );
+               }
+               public void Dispose()
+               {
+                       this.Dispose( true );
+                       GC.SuppressFinalize( this );
+               }
+               protected void Dispose( bool Managedも解放する )
+               {
+                       Action サウンドデータを解放する = () => {
+                               if( null != this.サウンドデータ )
+                               {
+                                       FDK.Memory.Free( (void*) this.サウンドデータ );
+                                       this.サウンドデータ = null;
+                               }
+                       };
+
+                       if( Managedも解放する )
+                       {
+                               // C#オブジェクトの解放があればここで。
+
+                               // this.排他利用を使った Unmanaged の解放。
+                               lock( this.排他利用 )
+                               {
+                                       サウンドデータを解放する();
+                               }
+                       }
+                       else
+                       {
+                               // (使える保証がないので)this.排他利用 を使わないUnmanaged の解放。
+                               サウンドデータを解放する();
+                       }
+               }
+               //----------------
+               #endregion
+
+               private bool 作成済み = false;
+               private byte* サウンドデータ = null;
+               private int サウンドデータサイズbyte = 0;
+               private int サウンドデータサイズsample = 0;
+               private int 再生位置sample = 0;
+               private readonly SharpDX.Multimedia.WaveFormat WAVEフォーマット = new SharpDX.Multimedia.WaveFormat( 44100, 16, 2 );      // 固定
+               private readonly object 排他利用 = new object();
+
+               #region " バックストア "
+               //----------------
+               private float bs_音量 = 1.0f;
+               //----------------
+               #endregion
+
+               #region " Win32 API "
+               //-----------------
+               [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
+               private static extern unsafe void CopyMemory( void* dst, void* src, int size );
+
+               [System.Runtime.InteropServices.DllImport( "kernel32.dll", SetLastError = true )]
+               private static extern unsafe void ZeroMemory( void* dst, int length );
+               //-----------------
+               #endregion
+       }
+}
diff --git a/FDK24/メディア/サウンド/WASAPIold/SoundTimer.cs b/FDK24/メディア/サウンド/WASAPIold/SoundTimer.cs
new file mode 100644 (file)
index 0000000..309407b
--- /dev/null
@@ -0,0 +1,68 @@
+using System;
+
+namespace FDK.メディア.サウンド.WASAPIold
+{
+       /// <summary>
+       /// WASAPIデバイス(のIAudioClock)をソースとするタイマー。
+       /// </summary>
+       /// <remarks>
+       /// WASAPIデバイスと厳密に同期をとる場合には、QPCTimer ではなく、このクラスを使用する。
+       /// </remarks>
+       public class SoundTimer
+       {
+               /// <summary>
+               /// エラー時は double.NaN を返す。
+               /// </summary>
+               public double 現在のデバイス位置secを取得する( CSCore.CoreAudioAPI.AudioClock audioClock )
+               {
+                       lock( this.スレッド間同期 )
+                       {
+                               int hr = 0;
+                               long デバイス周波数 = 0;// audioClock.Pu64Frequency;
+                               audioClock.GetFrequencyNative( out デバイス周波数 );
+                               long QPC周波数 = FDK.カウンタ.QPCTimer.周波数;
+                               long デバイス位置 = 0;
+                               long デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 = 0;
+
+                               if( 0.0 >= デバイス周波数 )
+                                       return double.NaN;
+
+                               // IAudioClock::GetPosition() は、S_FALSE を返すことがある。
+                               // これは、WASAPI排他モードにおいて、GetPosition 時に優先度の高いイベントが発生しており
+                               // 既定時間内にデバイス位置を取得できなかった場合に返される。(MSDNより)
+                               for( int リトライ回数 = 0; リトライ回数 < 10; リトライ回数++ )    // 最大10回までリトライ。
+                               {
+                                       hr = audioClock.GetPositionNative( out デバイス位置, out デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 );
+
+                                       if( ( (int) CSCore.Win32.HResult.S_OK ) == hr )
+                                       {
+                                               break;      // OK
+                                       }
+                                       else if( ( (int) CSCore.Win32.HResult.S_FALSE ) == hr )
+                                       {
+                                               continue;   // リトライ
+                                       }
+                                       else
+                                       {
+                                               throw new CSCore.Win32.Win32ComException( hr, "IAudioClock", "GetPosition" );
+                                       }
+                               }
+                               if( ( (int) CSCore.Win32.HResult.S_FALSE ) == hr )
+                               {
+                                       // 全部リトライしてもまだダメならエラー。
+                                       return double.NaN;
+                               }
+
+                               // デバイス位置は、GetPosition() で取得した瞬間からここに至るまでに、既にいくらか進んでいる。
+                               // そこで、この時点で最新のパフォーマンスカウンタを取得し、時間の増加分をデバイス位置に加えて精度を上げる。(MSDNより)
+                               double QPCから調べた差分sec =
+                                       ( (double) FDK.カウンタ.QPCTimer.生カウント / QPC周波数 ) -
+                                       ( (double) デバイス位置取得時のパフォーマンスカウンタを100ns単位に変換した時間 ) / 10000000.0;
+
+                               return ( (double) デバイス位置 ) / デバイス周波数 + QPCから調べた差分sec;
+                       }
+               }
+
+               private readonly object スレッド間同期 = new object();
+       }
+}
index e047a70..bf463eb 100644 (file)
@@ -18,7 +18,7 @@ namespace SST
                {
                        get { return StrokeStyleT.bs_フォルダ; }
                }
-               public static FDK.メディア.サウンド.WASAPI.Device Wasapiデバイス
+               public static FDK.メディア.サウンド.WASAPIold.Device Wasapiデバイス
                {
                        get { return StrokeStyleT.bs_Wasapiデバイス; }
                }
@@ -308,7 +308,7 @@ namespace SST
                        #endregion
                        #region " WASAPI デバイスを初期化する。"
                        //----------------
-                       StrokeStyleT.bs_Wasapiデバイス = new FDK.メディア.サウンド.WASAPI.Device();
+                       StrokeStyleT.bs_Wasapiデバイス = new FDK.メディア.サウンド.WASAPIold.Device();
                        StrokeStyleT.bs_Wasapiデバイス.初期化する( CSCore.CoreAudioAPI.AudioClientShareMode.Exclusive, 0.015 );
                        //----------------
                        #endregion
@@ -845,7 +845,7 @@ namespace SST
                private static SST.フォルダ bs_フォルダ = null;
                private static FDK.入力.Keyboard bs_キーボード入力 = null;
                private static FDK.入力.MidiIn bs_MIDI入力 = null;
-               private static FDK.メディア.サウンド.WASAPI.Device bs_Wasapiデバイス = null;
+               private static FDK.メディア.サウンド.WASAPIold.Device bs_Wasapiデバイス = null;
                private static readonly System.Random bs_乱数 = new Random( DateTime.Now.Millisecond );
                private static SST.ユーザ.ユーザ管理 bs_ユーザ管理 = null;
                private static SST.曲.曲ツリー管理 bs_曲ツリー管理 = null;
index 636ecbc..18ed76a 100644 (file)
@@ -62,7 +62,7 @@ namespace SST.ステージ.演奏
                protected readonly string KitXmlファイルパス = @"$(Static)\sounds\Kit.xml";
                protected class Cコンテキスト : IDisposable
                {
-                       public FDK.メディア.サウンド.WASAPI.Sound[] Sounds = new FDK.メディア.サウンド.WASAPI.Sound[ ドラムサウンド.多重度 ];
+                       public FDK.メディア.サウンド.WASAPIold.Sound[] Sounds = new FDK.メディア.サウンド.WASAPIold.Sound[ ドラムサウンド.多重度 ];
                        public int 次に再生するSound番号 = 0;
 
                        public void Dispose()
@@ -141,13 +141,13 @@ namespace SST.ステージ.演奏
 
                                                                                        // コンテキストを作成する。
                                                                                        var context = new Cコンテキスト() {
-                                                                                               Sounds = new FDK.メディア.サウンド.WASAPI.Sound[ ドラムサウンド.多重度 ],
+                                                                                               Sounds = new FDK.メディア.サウンド.WASAPIold.Sound[ ドラムサウンド.多重度 ],
                                                                                                次に再生するSound番号 = 0,
                                                                                        };
                                                                                        for( int i = 0; i < context.Sounds.Length; i++ )
                                                                                        {
                                                                                                // 多重度分のサウンドを生成しつつ、ミキサーにも登録。
-                                                                                               context.Sounds[ i ] = new FDK.メディア.サウンド.WASAPI.Sound( サウンドファイルパス );
+                                                                                               context.Sounds[ i ] = new FDK.メディア.サウンド.WASAPIold.Sound( サウンドファイルパス );
                                                                                                StrokeStyleT.Wasapiデバイス.サウンドをミキサーに追加する( context.Sounds[ i ] );
                                                                                        }
 
index 15a6ff5..5563b26 100644 (file)
@@ -103,7 +103,7 @@ namespace SST.ステージ.演奏
                                this.子リスト.Add( this.背景動画 = new 動画( StrokeStyleT.演奏スコア.背景動画ファイル名, StrokeStyleT.Config.動画デコーダのキューサイズ ) );
 
                                // 動画から BGM を作成してミキサーに追加。
-                               this.BGM = new FDK.メディア.サウンド.WASAPI.Sound();
+                               this.BGM = new FDK.メディア.サウンド.WASAPIold.Sound();
                                this.BGM.ファイルから作成する( StrokeStyleT.演奏スコア.背景動画ファイル名 );
                                StrokeStyleT.Wasapiデバイス.サウンドをミキサーに追加する( this.BGM ); // 作成に失敗した Sound を追加しても鳴らないだけなので、ノーチェックで大丈夫。
                        }
@@ -270,7 +270,7 @@ namespace SST.ステージ.演奏
                protected FDK.同期.RWLock<double> 現在進行描画中の譜面スクロール速度の倍率 = new FDK.同期.RWLock<double>( 0.0 );
                protected double 演奏開始時刻sec = 0.0;
                protected bool Autoチップのドラム音を再生する = true;
-               protected readonly FDK.メディア.サウンド.WASAPI.SoundTimer サウンドタイマ = new FDK.メディア.サウンド.WASAPI.SoundTimer();
+               protected readonly FDK.メディア.サウンド.WASAPIold.SoundTimer サウンドタイマ = new FDK.メディア.サウンド.WASAPIold.SoundTimer();
                protected readonly SST.ステージ.演奏.コンボ コンボ;
                protected readonly SST.ステージ.演奏.レーンフレーム レーンフレーム;
                protected readonly SST.ステージ.演奏.スクロール譜面 スクロール譜面;
@@ -285,7 +285,7 @@ namespace SST.ステージ.演奏
                /// 解放は、演奏ステージクラスの非活性化後に、外部から行われる。
                /// <see cref="SST.ステージ.演奏.演奏ステージ.BGMを解放する"/>
                /// </remarks>
-               protected FDK.メディア.サウンド.WASAPI.Sound BGM = null;
+               protected FDK.メディア.サウンド.WASAPIold.Sound BGM = null;
                protected FDK.カウンタ.FPS FPS = null;
                /// <summary>
                /// 動的子Activity。背景動画を再生しない場合は null のまま。