-using System;\r
+using System;\r
using System.Collections.Generic;\r
using System.Text;\r
+using System.Diagnostics;\r
using System.Runtime.InteropServices;\r
using System.IO;\r
-using System.Diagnostics;\r
+using System.Runtime.CompilerServices;\r
+using System.Threading;\r
using SlimDX;\r
using SlimDX.DirectSound;\r
using SlimDX.Multimedia;\r
+using Un4seen.Bass;\r
+using Un4seen.BassAsio;\r
+using Un4seen.BassWasapi;\r
+using Un4seen.Bass.AddOn.Mix;\r
+using Un4seen.Bass.AddOn.Fx;\r
+using DirectShowLib;\r
\r
namespace FDK\r
{\r
- public class CSound : IDisposable\r
+ #region [ DTXMania用拡張 ]\r
+ public class CSound管理 // : CSound\r
{\r
- // (1) 以下は全子クラスに共通\r
+ public static ISoundDevice SoundDevice\r
+ {\r
+ get; set;\r
+ }\r
+ public static ESoundDeviceType SoundDeviceType\r
+ {\r
+ get; set;\r
+ }\r
+ public static CSoundTimer rc演奏用タイマ = null;\r
+ public static bool bUseOSTimer = false; // OSのタイマーを使うか、CSoundTimerを使うか。DTXCではfalse, DTXManiaではtrue。\r
+ // DTXC(DirectSound)でCSoundTimerを使うと、内部で無音のループサウンドを再生するため\r
+ // サウンドデバイスを占有してしまい、Viewerとして呼び出されるDTXManiaで、ASIOが使えなくなる。\r
\r
+ // DTXMania単体でこれをtrueにすると、WASAPI/ASIO時に演奏タイマーとしてFDKタイマーではなく\r
+ // システムのタイマーを使うようになる。こうするとスクロールは滑らかになるが、音ズレが出るかもしれない。\r
+ \r
+ public static IntPtr WindowHandle;\r
\r
- // プロパティ\r
+ public static bool bIsTimeStretch = false;\r
\r
- public SecondarySoundBuffer Buffer\r
- {\r
- get;\r
- private set;\r
- }\r
- public bool bストリーム再生する\r
+ private static int _nMasterVolume;\r
+ public int nMasterVolume\r
{\r
get\r
{\r
- if( this.nHandle < 0 )\r
- {\r
- return false;\r
- }\r
- return true;\r
+ return _nMasterVolume;\r
+ }\r
+ set\r
+ {\r
+ SoundDevice.nMasterVolume = value;\r
+ _nMasterVolume = value;\r
}\r
}\r
- public bool bループする\r
+\r
+ ///// <summary>\r
+ ///// BASS時、mp3をストリーミング再生せずに、デコードしたraw wavをオンメモリ再生する場合はtrueにする。\r
+ ///// 特殊なmp3を使用時はシークが乱れるので、必要に応じてtrueにすること。(Config.iniのNoMP3Streamingで設定可能。)\r
+ ///// ただし、trueにすると、その分再生開始までの時間が長くなる。\r
+ ///// </summary>\r
+ //public static bool bIsMP3DecodeByWindowsCodec = false;\r
+\r
+ public static int nMixing = 0;\r
+ public int GetMixingStreams()\r
{\r
- get;\r
- private set;\r
+ return nMixing;\r
}\r
- public bool b一時停止中\r
+ public static int nStreams = 0;\r
+ public int GetStreams()\r
{\r
- get\r
- {\r
- return ( this.n一時停止回数 > 0 );\r
- }\r
+ return nStreams;\r
}\r
- public bool b再生中\r
+ #region [ WASAPI/ASIO/DirectSound設定値 ]\r
+ /// <summary>\r
+ /// <para>WASAPI 排他モード出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>\r
+ /// <para>0以下の値を指定すると、この数値はWASAPI初期化時に自動設定する。正数を指定すると、その値を設定しようと試みる。</para>\r
+ /// </summary>\r
+ public static int SoundDelayExclusiveWASAPI = 0; // SSTでは、50ms\r
+ public int GetSoundExclusiveWASAPI()\r
{\r
- get\r
- {\r
- return ( ( this.Buffer.Status & BufferStatus.Playing ) != BufferStatus.None );\r
- }\r
+ return SoundDelayExclusiveWASAPI;\r
}\r
- public double db再生速度\r
+ public void SetSoundDelayExclusiveWASAPI( int value )\r
{\r
- get\r
- {\r
- return this._db再生速度;\r
- }\r
- set\r
- {\r
- if( this._db再生速度 != value )\r
- {\r
- this._db再生速度 = value;\r
- if( this.b再生中 )\r
- {\r
- this.Buffer.Frequency = (int) ( ( this.nオリジナルの周波数 * this._db再生速度 ) * this.db周波数倍率 );\r
- }\r
- }\r
- }\r
+ SoundDelayExclusiveWASAPI = value;\r
}\r
- public double db周波数倍率\r
+ /// <summary>\r
+ /// <para>WASAPI 共有モード出力における再生遅延[ms]。ユーザが決定する。</para>\r
+ /// </summary>\r
+ public static int SoundDelaySharedWASAPI = 100;\r
+ /// <summary>\r
+ /// <para>排他WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>\r
+ /// <para>→ 自動設定されるのでSoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>\r
+ /// </summary>\r
+ public static int SoundUpdatePeriodExclusiveWASAPI = 6;\r
+ /// <summary>\r
+ /// <para>共有WASAPIバッファの更新間隔。出力間隔ではないので注意。</para>\r
+ /// <para>SoundDelay よりも小さい値であること。(小さすぎる場合はBASSによって自動修正される。)</para>\r
+ /// </summary>\r
+ public static int SoundUpdatePeriodSharedWASAPI = 6;\r
+ ///// <summary>\r
+ ///// <para>ASIO 出力における再生遅延[ms](の希望値)。最終的にはこの数値を基にドライバが決定する)。</para>\r
+ ///// </summary>\r
+ //public static int SoundDelayASIO = 0; // SSTでは50ms。0にすると、デバイスの設定値をそのまま使う。\r
+ /// <summary>\r
+ /// <para>ASIO 出力におけるバッファサイズ。</para>\r
+ /// </summary>\r
+ public static int SoundDelayASIO = 0; // 0にすると、デバイスの設定値をそのまま使う。\r
+ public int GetSoundDelayASIO()\r
{\r
- get\r
+ return SoundDelayASIO;\r
+ }\r
+ public void SetSoundDelayASIO(int value)\r
+ {\r
+ SoundDelayASIO = value;\r
+ }\r
+ public static int ASIODevice = 0;\r
+ public int GetASIODevice()\r
+ {\r
+ return ASIODevice;\r
+ }\r
+ public void SetASIODevice(int value)\r
+ {\r
+ ASIODevice = value;\r
+ }\r
+ /// <summary>\r
+ /// <para>DirectSound 出力における再生遅延[ms]。ユーザが決定する。</para>\r
+ /// </summary>\r
+ public static int SoundDelayDirectSound = 100;\r
+\r
+ public long GetSoundDelay()\r
+ {\r
+ if ( SoundDevice != null )\r
{\r
- return this._db周波数倍率;\r
+ return SoundDevice.n実バッファサイズms;\r
}\r
- set\r
+ else\r
{\r
- if( this._db周波数倍率 != value )\r
- {\r
- this._db周波数倍率 = value;\r
- if( this.b再生中 )\r
- {\r
- this.Buffer.Frequency = (int) ( ( this.nオリジナルの周波数 * this.db再生速度 ) * this.db周波数倍率 );\r
- }\r
- }\r
+ return -1;\r
}\r
}\r
- public DateTime dt最終更新時刻\r
+\r
+ #endregion\r
+\r
+\r
+ /// <summary>\r
+ /// DTXC用コンストラクタ\r
+ /// </summary>\r
+ /// <param name="handle"></param>\r
+ public CSound管理( IntPtr handle ) // #30803 従来のコンストラクタ相当のI/Fを追加。(DTXC用)\r
{\r
- get; \r
- private set;\r
+ WindowHandle = handle;\r
+ SoundDevice = null;\r
+ bUseOSTimer = true;\r
+ t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );\r
}\r
- public WaveFormat Format\r
- { \r
- get;\r
- private set;\r
+ /// <summary>\r
+ /// DTXMania用コンストラクタ\r
+ /// </summary>\r
+ /// <param name="handle"></param>\r
+ /// <param name="soundDeviceType"></param>\r
+ /// <param name="nSoundDelayExclusiveWASAPI"></param>\r
+ /// <param name="nSoundDelayASIO"></param>\r
+ /// <param name="nASIODevice"></param>\r
+ public CSound管理( IntPtr handle, ESoundDeviceType soundDeviceType, int nSoundDelayExclusiveWASAPI, int nSoundDelayASIO, int nASIODevice, bool _bUseOSTimer )\r
+ {\r
+ WindowHandle = handle;\r
+ SoundDevice = null;\r
+ //bUseOSTimer = false;\r
+ t初期化( soundDeviceType, nSoundDelayExclusiveWASAPI, nSoundDelayASIO, nASIODevice, _bUseOSTimer );\r
}\r
- public int nオリジナルの周波数 \r
- { \r
- get; \r
- private set;\r
+ public void Dispose()\r
+ {\r
+ t終了();\r
}\r
- public int nサウンドバッファサイズ\r
+\r
+ //public static void t初期化()\r
+ //{\r
+ // t初期化( ESoundDeviceType.DirectSound, 0, 0, 0 );\r
+ //}\r
+\r
+ public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, IntPtr handle )\r
{\r
- get\r
+ //if ( !bInitialized )\r
{\r
- return this.Buffer.Capabilities.BufferSize;\r
+ WindowHandle = handle;\r
+ t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice );\r
+ //bInitialized = true;\r
}\r
}\r
- public int n位置\r
+ public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice )\r
{\r
- get\r
- {\r
- return this._n位置;\r
- }\r
- set\r
- {\r
- this._n位置 = value;\r
- if( this._n位置 < -100 )\r
- {\r
- this._n位置 = -100;\r
- }\r
- else if( this._n位置 > 100 )\r
- {\r
- this._n位置 = 100;\r
- }\r
- if( this._n位置 == 0 )\r
- {\r
- this._n位置db = 0;\r
- }\r
- else if( this._n位置 == -100 )\r
- {\r
- this._n位置db = -10000;\r
- }\r
- else if( this._n位置 == 100 )\r
- {\r
- this._n位置db = 0x2710;\r
- }\r
- else if( this._n位置 < 0 )\r
- {\r
- this._n位置db = (int) ( ( 20.0 * Math.Log10( ( (double) ( this._n位置 + 100 ) ) / 100.0 ) ) * 100.0 );\r
- }\r
- else\r
- {\r
- this._n位置db = (int) ( ( -20.0 * Math.Log10( ( (double) ( 100 - this._n位置 ) ) / 100.0 ) ) * 100.0 );\r
- }\r
- if( this.b再生中 )\r
- {\r
- this.Buffer.Pan = this._n位置db;\r
- }\r
- }\r
+ t初期化( soundDeviceType, _nSoundDelayExclusiveWASAPI, _nSoundDelayASIO, _nASIODevice, false );\r
}\r
- public int n音量\r
+\r
+ public void t初期化( ESoundDeviceType soundDeviceType, int _nSoundDelayExclusiveWASAPI, int _nSoundDelayASIO, int _nASIODevice, bool _bUseOSTimer )\r
{\r
- get\r
+ //SoundDevice = null; // 後で再初期化することがあるので、null初期化はコンストラクタに回す\r
+ rc演奏用タイマ = null; // Global.Bass 依存(つまりユーザ依存)\r
+ nMixing = 0;\r
+\r
+ SoundDelayExclusiveWASAPI = _nSoundDelayExclusiveWASAPI;\r
+ SoundDelayASIO = _nSoundDelayASIO;\r
+ ASIODevice = _nASIODevice;\r
+ bUseOSTimer = _bUseOSTimer;\r
+\r
+ ESoundDeviceType[] ESoundDeviceTypes = new ESoundDeviceType[ 4 ]\r
+ {\r
+ ESoundDeviceType.ExclusiveWASAPI,\r
+ ESoundDeviceType.ASIO,\r
+ ESoundDeviceType.DirectSound,\r
+ ESoundDeviceType.Unknown\r
+ };\r
+\r
+ int n初期デバイス;\r
+ switch ( soundDeviceType )\r
{\r
- return this._n音量;\r
+ case ESoundDeviceType.ExclusiveWASAPI:\r
+ n初期デバイス = 0;\r
+ break;\r
+ case ESoundDeviceType.ASIO:\r
+ n初期デバイス = 1;\r
+ break;\r
+ case ESoundDeviceType.DirectSound:\r
+ n初期デバイス = 2;\r
+ break;\r
+ default:\r
+ n初期デバイス = 3;\r
+ break;\r
}\r
- set\r
+ for ( SoundDeviceType = ESoundDeviceTypes[ n初期デバイス ]; ; SoundDeviceType = ESoundDeviceTypes[ ++n初期デバイス ] )\r
{\r
- this._n音量 = value;\r
- if( this._n音量 == 0 )\r
- {\r
- this._n音量db = -10000;\r
- }\r
- else\r
+ try\r
{\r
- this._n音量db = (int) ( ( 20.0 * Math.Log10( ( (double) this._n音量 ) / 100.0 ) ) * 100.0 );\r
+ t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する();\r
+ break;\r
}\r
- if( this.b再生中 )\r
+ catch ( Exception e )\r
{\r
- this.Buffer.Volume = this._n音量db;\r
+ Trace.TraceInformation( e.Message );\r
+ if ( ESoundDeviceTypes[ n初期デバイス ] == ESoundDeviceType.Unknown )\r
+ {\r
+ Trace.TraceError( string.Format( "サウンドデバイスの初期化に失敗しました。" ) );\r
+ break;\r
+ }\r
}\r
}\r
- }\r
- public int n周波数\r
- {\r
- get\r
- {\r
- return this.Buffer.Frequency;\r
- }\r
- set\r
- {\r
- this.Buffer.Frequency = value;\r
- }\r
- }\r
- public int n総演奏時間ms\r
- {\r
- get\r
+ if ( soundDeviceType == ESoundDeviceType.ExclusiveWASAPI || soundDeviceType == ESoundDeviceType.ASIO )\r
{\r
- return (int) ( ( (double) this.GetTotalPCMSize( this.nHandle ) ) / ( this.Buffer.Format.AverageBytesPerSecond * 0.001 ) );\r
+ //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 4 );\r
+ //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );\r
+\r
+ Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );\r
+ Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );\r
}\r
}\r
- public string strファイル名\r
+\r
+ public void tDisableUpdateBufferAutomatically()\r
{\r
- get; \r
- private set; \r
- }\r
+ //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS, 0 );\r
+ //Bass.BASS_SetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD, 0 );\r
\r
+ //Trace.TraceInformation( "BASS_CONFIG_UpdatePeriod=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATEPERIOD ) );\r
+ //Trace.TraceInformation( "BASS_CONFIG_UpdateThreads=" + Bass.BASS_GetConfig( BASSConfig.BASS_CONFIG_UPDATETHREADS ) );\r
+ }\r
\r
- // コンストラクタ\r
\r
- public CSound()\r
+ public static void t終了()\r
{\r
- this.Buffer = null;\r
+ C共通.tDisposeする( SoundDevice ); SoundDevice = null;\r
+ C共通.tDisposeする( ref rc演奏用タイマ ); // Global.Bass を解放した後に解放すること。(Global.Bass で参照されているため)\r
}\r
\r
\r
- // メソッド\r
-\r
- public int tデコード後のサイズを調べる( string strファイル名 )\r
+ public static void t現在のユーザConfigに従ってサウンドデバイスとすべての既存サウンドを再構築する()\r
{\r
- int nHandle = this.Open( strファイル名 );\r
- if( nHandle < 0 )\r
- {\r
- return -1;\r
- }\r
- CWin32.WAVEFORMATEX wfx = new CWin32.WAVEFORMATEX();\r
- if( this.GetFormat( nHandle, ref wfx ) < 0 )\r
+ #region [ すでにサウンドデバイスと演奏タイマが構築されていれば解放する。]\r
+ //-----------------\r
+ if ( SoundDevice != null )\r
{\r
- this.Close( nHandle );\r
- return -2;\r
+ // すでに生成済みのサウンドがあれば初期状態に戻す。\r
+\r
+ CSound.tすべてのサウンドを初期状態に戻す(); // リソースは解放するが、CSoundのインスタンスは残す。\r
+\r
+\r
+ // サウンドデバイスと演奏タイマを解放する。\r
+\r
+ C共通.tDisposeする( SoundDevice ); SoundDevice = null;\r
+ C共通.tDisposeする( ref rc演奏用タイマ ); // Global.SoundDevice を解放した後に解放すること。(Global.SoundDevice で参照されているため)\r
}\r
- uint totalPCMSize = this.GetTotalPCMSize( nHandle );\r
- if( totalPCMSize == 0 )\r
+ //-----------------\r
+ #endregion\r
+\r
+ #region [ 新しいサウンドデバイスを構築する。]\r
+ //-----------------\r
+ switch ( SoundDeviceType )\r
{\r
- this.Close( nHandle );\r
- return -3;\r
+ case ESoundDeviceType.ExclusiveWASAPI:\r
+ SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.排他, SoundDelayExclusiveWASAPI, SoundUpdatePeriodExclusiveWASAPI );\r
+ break;\r
+\r
+ case ESoundDeviceType.SharedWASAPI:\r
+ SoundDevice = new CSoundDeviceWASAPI( CSoundDeviceWASAPI.Eデバイスモード.共有, SoundDelaySharedWASAPI, SoundUpdatePeriodSharedWASAPI );\r
+ break;\r
+\r
+ case ESoundDeviceType.ASIO:\r
+ SoundDevice = new CSoundDeviceASIO( SoundDelayASIO, ASIODevice );\r
+ break;\r
+\r
+ case ESoundDeviceType.DirectSound:\r
+ SoundDevice = new CSoundDeviceDirectSound( WindowHandle, SoundDelayDirectSound, bUseOSTimer );\r
+ break;\r
+\r
+ default:\r
+ throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );\r
}\r
- this.Close( nHandle );\r
- return (int) totalPCMSize;\r
+ //-----------------\r
+ #endregion\r
+ #region [ 新しい演奏タイマを構築する。]\r
+ //-----------------\r
+ rc演奏用タイマ = new CSoundTimer( SoundDevice );\r
+ //-----------------\r
+ #endregion\r
+\r
+ SoundDevice.nMasterVolume = _nMasterVolume; // サウンドデバイスに対して、マスターボリュームを再設定する\r
+\r
+ CSound.tすべてのサウンドを再構築する( SoundDevice ); // すでに生成済みのサウンドがあれば作り直す。\r
}\r
- public void tオンメモリ方式で作成する( SlimDX.DirectSound.DirectSound Device, string strファイル名 )\r
+ public CSound tサウンドを生成する( string filename )\r
{\r
- byte[] buffer = null;\r
- int num = this.Open( strファイル名 );\r
- if( num < 0 )\r
- {\r
- throw new Exception( string.Format( "Open() に失敗しました。({0})({1})", num, strファイル名 ) );\r
- }\r
- CWin32.WAVEFORMATEX wfx = new CWin32.WAVEFORMATEX();\r
- if( this.GetFormat( num, ref wfx ) < 0 )\r
- {\r
- this.Close( num );\r
- throw new Exception( string.Format( "GetFormat() に失敗しました。({0})", strファイル名 ) );\r
- }\r
- int totalPCMSize = (int) this.GetTotalPCMSize( num );\r
- if( totalPCMSize == 0 )\r
- {\r
- this.Close( num );\r
- throw new Exception( string.Format( "GetTotalPCMSize() に失敗しました。({0})", strファイル名 ) );\r
- }\r
- totalPCMSize += ( ( totalPCMSize % 2 ) != 0 ) ? 1 : 0;\r
- buffer = new byte[ totalPCMSize ];\r
- GCHandle handle = GCHandle.Alloc( buffer, GCHandleType.Pinned );\r
- try\r
+ if ( SoundDeviceType == ESoundDeviceType.Unknown )\r
{\r
- if( this.Decode( num, Marshal.UnsafeAddrOfPinnedArrayElement( buffer, 0 ), (uint) totalPCMSize, 0 ) < 0 )\r
- {\r
- buffer = null;\r
- throw new Exception( string.Format( "デコードに失敗しました。({0})", strファイル名 ) );\r
- }\r
+ throw new Exception( string.Format( "未対応の SoundDeviceType です。[{0}]", SoundDeviceType.ToString() ) );\r
}\r
- finally\r
- {\r
- handle.Free();\r
- this.Close( num );\r
- }\r
- WaveFormat format = new WaveFormat();\r
- format.FormatTag = WaveFormatTag.Pcm;\r
- format.Channels = (short) wfx.nChannels;\r
- format.SamplesPerSecond = (int) wfx.nSamplesPerSec;\r
- format.AverageBytesPerSecond = (int) wfx.nAvgBytesPerSec;\r
- format.BlockAlignment = (short) wfx.nBlockAlign;\r
- format.BitsPerSample = (short) wfx.wBitsPerSample;\r
- this.Format = format;\r
- SoundBufferDescription description = new SoundBufferDescription();\r
- description.Format = this.Format;\r
- description.Flags = BufferFlags.Defer | BufferFlags.GetCurrentPosition2 | BufferFlags.GlobalFocus | BufferFlags.ControlVolume | BufferFlags.ControlPan | BufferFlags.ControlFrequency;\r
- description.SizeInBytes = totalPCMSize;\r
- this.Buffer = new SecondarySoundBuffer( Device, description );\r
- this.Buffer.Write( buffer, 0, LockFlags.None );\r
- this.strファイル名 = strファイル名;\r
- this.dt最終更新時刻 = File.GetLastWriteTime( strファイル名 );\r
- this.nオリジナルの周波数 = this.Buffer.Frequency;\r
+ return SoundDevice.tサウンドを作成する( filename );\r
}\r
- public void tストリーム方式で作成する( SlimDX.DirectSound.DirectSound Device, string strファイル名 )\r
+\r
+ private static DateTime lastUpdateTime = DateTime.MinValue;\r
+ public void t再生中の処理をする( object o ) // #26122 2011.9.1 yyagi; delegate経由の呼び出し用\r
{\r
- this.nHandle = this.Open( strファイル名 );\r
- if( this.nHandle < 0 )\r
- {\r
- throw new Exception( string.Format( "Open() に失敗しました。({0})", strファイル名 ) );\r
- }\r
- CWin32.WAVEFORMATEX wfx = new CWin32.WAVEFORMATEX();\r
- if( this.GetFormat( this.nHandle, ref wfx ) < 0 )\r
- {\r
- this.Close( this.nHandle );\r
- this.nHandle = -1;\r
- throw new Exception( string.Format( "GetFormat() に失敗しました。({0})", strファイル名 ) );\r
- }\r
- WaveFormat format = new WaveFormat();\r
- format.FormatTag = WaveFormatTag.Pcm;\r
- format.AverageBytesPerSecond = (int) wfx.nAvgBytesPerSec;\r
- format.BitsPerSample = (short) wfx.wBitsPerSample;\r
- format.BlockAlignment = (short) wfx.nBlockAlign;\r
- format.Channels = (short) wfx.nChannels;\r
- format.SamplesPerSecond = (int) wfx.nSamplesPerSec;\r
- this.Format = format;\r
- this.by中継バッファ = new byte[ CSound管理.nバッファサイズ ];\r
- this.gch中継バッファ = GCHandle.Alloc( this.by中継バッファ, GCHandleType.Pinned );\r
- SoundBufferDescription description = new SoundBufferDescription();\r
- description.Format = this.Format;\r
- description.Flags = BufferFlags.Defer | BufferFlags.GetCurrentPosition2 | BufferFlags.GlobalFocus | BufferFlags.ControlVolume | BufferFlags.ControlPan | BufferFlags.ControlFrequency;\r
- description.SizeInBytes = this.by中継バッファ.Length * CSound管理.nバッファの数;\r
- this.Buffer = new SecondarySoundBuffer( Device, description );\r
- this.tストリーム再生位置リセット();\r
- for( int i = 0; i < CSound管理.nバッファの数; i++ )\r
- {\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, i, this.bループする );\r
- }\r
- this.n現在書き込み許可待ちのバッファ番号 = 0;\r
- this.strファイル名 = strファイル名;\r
- this.dt最終更新時刻 = File.GetLastWriteTime( strファイル名 );\r
- this.nオリジナルの周波数 = this.Buffer.Frequency;\r
+ t再生中の処理をする();\r
}\r
- public void t再生を一時停止する()\r
+ public void t再生中の処理をする()\r
{\r
- if( this.b再生中 )\r
- {\r
- this.n一時停止位置byte = this.Buffer.CurrentPlayPosition;\r
- this.Buffer.Stop();\r
- this.n一時停止回数++;\r
- }\r
+//★★★★★★★★★★★★★★★★★★★★★ダミー★★★★★★★★★★★★★★★★★★\r
+// Debug.Write( "再生中の処理をする()" );\r
+ //DateTime now = DateTime.Now;\r
+ //TimeSpan ts = now - lastUpdateTime;\r
+ //if ( ts.Milliseconds > 5 )\r
+ //{\r
+ // bool b = Bass.BASS_Update( 100 * 2 );\r
+ // lastUpdateTime = DateTime.Now;\r
+ // if ( !b )\r
+ // {\r
+ // Trace.TraceInformation( "BASS_UPdate() failed: " + Bass.BASS_ErrorGetCode().ToString() );\r
+ // }\r
+ //}\r
}\r
- public void t再生を開始する()\r
+\r
+ public void tサウンドを破棄する( CSound csound )\r
{\r
- this.t再生を開始する( false );\r
+ csound.t解放する( true ); // インスタンスは存続→破棄にする。\r
+ csound = null;\r
}\r
- public void t再生を開始する( bool bループする )\r
+\r
+ public float GetCPUusage()\r
{\r
- this.Buffer.Stop();\r
- this.Buffer.CurrentPlayPosition = 0;\r
- this.Buffer.Frequency = ( ( this.db再生速度 != 1.0 ) || ( this.db周波数倍率 != 1.0 ) ) ? ( (int) ( ( this.nオリジナルの周波数 * this.db再生速度 ) * this.db周波数倍率 ) ) : this.nオリジナルの周波数;\r
- this.Buffer.Volume = this._n音量db;\r
- this.Buffer.Pan = this._n位置db;\r
- try\r
- {\r
- this.Buffer.Play( 0, ( this.bストリーム再生する || bループする ) ? PlayFlags.Looping : PlayFlags.None );\r
- }\r
- catch( OutOfMemoryException )\r
+ float f;\r
+ switch ( SoundDeviceType )\r
{\r
- Trace.TraceError( "サウンドを再生するために必要なメモリ領域が不足しています。" );\r
+ case ESoundDeviceType.ExclusiveWASAPI:\r
+ case ESoundDeviceType.SharedWASAPI:\r
+ f = BassWasapi.BASS_WASAPI_GetCPU();\r
+ break;\r
+ case ESoundDeviceType.ASIO:\r
+ f = BassAsio.BASS_ASIO_GetCPU();\r
+ break;\r
+ case ESoundDeviceType.DirectSound:\r
+ f = 0.0f;\r
+ break;\r
+ default:\r
+ f = 0.0f;\r
+ break;\r
}\r
- this.n一時停止回数 = 0;\r
+ return f;\r
}\r
- public void t再生を再開する()\r
+\r
+ public string GetCurrentSoundDeviceType()\r
{\r
- if( this.n一時停止回数 > 0 )\r
+ switch ( SoundDeviceType )\r
{\r
- this.n一時停止回数--;\r
- if( this.n一時停止回数 <= 0 )\r
- {\r
- this.Buffer.CurrentPlayPosition = this.n一時停止位置byte;\r
- this.Buffer.Frequency = ( ( this.db再生速度 != 1.0 ) || ( this.db周波数倍率 != 1.0 ) ) ? ( (int) ( ( this.nオリジナルの周波数 * this.db再生速度 ) * this.db周波数倍率 ) ) : this.nオリジナルの周波数;\r
- this.Buffer.Volume = this._n音量db;\r
- this.Buffer.Pan = this._n位置db;\r
- try\r
- {\r
- this.Buffer.Play( 0, ( this.bストリーム再生する || this.bループする ) ? PlayFlags.Looping : PlayFlags.None );\r
- }\r
- catch( OutOfMemoryException )\r
- {\r
- Trace.TraceError( "サウンドを再生するために必要なメモリ領域が不足しています。" );\r
- }\r
- }\r
+ case ESoundDeviceType.ExclusiveWASAPI:\r
+ case ESoundDeviceType.SharedWASAPI:\r
+ return "WASAPI";\r
+ case ESoundDeviceType.ASIO:\r
+ return "ASIO";\r
+ case ESoundDeviceType.DirectSound:\r
+ return "DirectSound";\r
+ default:\r
+ return "Unknown";\r
}\r
}\r
- public void t再生を再開する( long n再生開始位置ms )\r
+\r
+ public void AddMixer( CSound cs, double db再生速度, bool _b演奏終了後も再生が続くチップである )\r
{\r
- this.t再生位置を変更する( this.t時刻から位置を返す( n再生開始位置ms ) );\r
- this.t再生を再開する();\r
+ cs.b演奏終了後も再生が続くチップである = _b演奏終了後も再生が続くチップである;\r
+ cs.db再生速度 = db再生速度;\r
+ cs.tBASSサウンドをミキサーに追加する();\r
}\r
- public void t再生を停止する()\r
+ public void AddMixer( CSound cs, double db再生速度 )\r
{\r
- this.Buffer.Stop();\r
- this.Buffer.CurrentPlayPosition = 0;\r
- this.n一時停止回数 = 0;\r
- if( this.bストリーム再生する )\r
- {\r
- this.tストリーム再生位置リセット();\r
- for( int i = 0; i < CSound管理.nバッファの数; i++ )\r
- {\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, i, this.bループする );\r
- }\r
- this.n現在書き込み許可待ちのバッファ番号 = 0;\r
- }\r
+ cs.db再生速度 = db再生速度;\r
+ cs.tBASSサウンドをミキサーに追加する();\r
+ }\r
+ public void AddMixer( CSound cs )\r
+ {\r
+ cs.tBASSサウンドをミキサーに追加する();\r
+ }\r
+ public void RemoveMixer( CSound cs )\r
+ {\r
+ cs.tBASSサウンドをミキサーから削除する();\r
+ }\r
+ }\r
+ #endregion\r
+\r
+ // CSound は、サウンドデバイスが変更されたときも、インスタンスを再作成することなく、新しいデバイスで作り直せる必要がある。\r
+ // そのため、デバイスごとに別のクラスに分割するのではなく、1つのクラスに集約するものとする。\r
+\r
+ public class CSound : IDisposable, ICloneable\r
+ {\r
+ #region [ DTXMania用拡張 ]\r
+ public int n総演奏時間ms\r
+ {\r
+ get;\r
+ private set;\r
+ }\r
+ public int nサウンドバッファサイズ // 取りあえず0固定★★★★★★★★★★★★★★★★★★★★\r
+ {\r
+ get { return 0; }\r
}\r
- public void t再生位置を変更する( int n新しい再生位置sample )\r
+ public bool bストリーム再生する // 取りあえずfalse固定★★★★★★★★★★★★★★★★★★★★\r
+ // trueにすると同一チップ音の多重再生で問題が出る(4POLY音源として動かない)\r
{\r
- if( this.nHandle >= 0 )\r
+ get { return false; }\r
+ }\r
+ public double db周波数倍率\r
+ {\r
+ get\r
{\r
- int num = n新しい再生位置sample * this.Buffer.Format.BlockAlignment;\r
- if( this.bストリーム再生する )\r
+ return _db周波数倍率;\r
+ }\r
+ set\r
+ {\r
+ if ( _db周波数倍率 != value )\r
{\r
- this.t再生中の処理をする();\r
- int num2 = this.n現在のPCM側の位置byte - ( CSound管理.nバッファサイズ * CSound管理.nバッファの数 );\r
- if( num2 < 0 )\r
- {\r
- num2 = 0;\r
- }\r
- int num3 = this.n現在のPCM側の位置byte - 1;\r
- if( num3 < 0 )\r
- {\r
- num3 = 0;\r
- }\r
- if( ( num >= num2 ) && ( num <= num3 ) )\r
+ _db周波数倍率 = value;\r
+ if ( bBASSサウンドである )\r
{\r
- this.Buffer.CurrentPlayPosition = num % ( CSound管理.nバッファサイズ * CSound管理.nバッファの数 );\r
+ Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );\r
}\r
else\r
{\r
- this.Seek( this.nHandle, (uint) num );\r
- for( int i = 0; i < CSound管理.nバッファの数; i++ )\r
- {\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, i, this.bループする );\r
- }\r
- this.n現在書き込み許可待ちのバッファ番号 = 0;\r
- this.Buffer.CurrentPlayPosition = 0;\r
+// if ( b再生中 ) // #30838 2012.2.24 yyagi (delete b再生中)\r
+// {\r
+ this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );\r
+// }\r
}\r
}\r
- else if( ( num >= 0 ) || ( num < this.nサウンドバッファサイズ ) )\r
- {\r
- this.Buffer.CurrentPlayPosition = num;\r
- }\r
}\r
}\r
- public void t再生中の処理をする()\r
+ public double db再生速度\r
{\r
- if( ( this.bストリーム再生する && ( this.Buffer != null ) ) && ( this.b再生中 && ( this.n一時停止回数 <= 0 ) ) )\r
+ get\r
{\r
- int num2 = this.Buffer.CurrentPlayPosition / this.by中継バッファ.Length;\r
- int num3 = num2 - CSound管理.nキープする再生済みバッファの数;\r
- if( this.n現在書き込み許可待ちのバッファ番号 < num2 )\r
- {\r
- while( this.n現在書き込み許可待ちのバッファ番号 < num3 )\r
- {\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, this.n現在書き込み許可待ちのバッファ番号, this.bループする );\r
- this.n現在書き込み許可待ちのバッファ番号++;\r
- }\r
- }\r
- else if( this.n現在書き込み許可待ちのバッファ番号 > num2 )\r
+ return _db再生速度;\r
+ }\r
+ set\r
+ {\r
+ if ( _db再生速度 != value )\r
{\r
- num3 = ( num3 + CSound管理.nバッファの数 ) % CSound管理.nバッファの数;\r
- if( this.n現在書き込み許可待ちのバッファ番号 < num3 )\r
+ _db再生速度 = value;\r
+ bIs1倍速再生 = ( _db再生速度 == 1.000f );\r
+ if ( bBASSサウンドである )\r
{\r
- while( this.n現在書き込み許可待ちのバッファ番号 < num3 )\r
+ if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する\r
+ {\r
+ this.hBassStream = _hTempoStream;\r
+ }\r
+ else\r
{\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, this.n現在書き込み許可待ちのバッファ番号, this.bループする );\r
- this.n現在書き込み許可待ちのバッファ番号++;\r
- }\r
- }\r
- else if( this.n現在書き込み許可待ちのバッファ番号 > num3 )\r
- {\r
- while( this.n現在書き込み許可待ちのバッファ番号 < CSound管理.nバッファの数 )\r
+ this.hBassStream = _hBassStream;\r
+ }\r
+\r
+ if ( CSound管理.bIsTimeStretch )\r
{\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, this.n現在書き込み許可待ちのバッファ番号, this.bループする );\r
- this.n現在書き込み許可待ちのバッファ番号++;\r
+ Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_TEMPO, (float) ( db再生速度 * 100 - 100 ) );\r
+ //double seconds = Bass.BASS_ChannelBytes2Seconds( this.hTempoStream, nBytes );\r
+ //this.n総演奏時間ms = (int) ( seconds * 1000 );\r
}\r
- this.n現在書き込み許可待ちのバッファ番号 = 0;\r
- while( this.n現在書き込み許可待ちのバッファ番号 < num3 )\r
+ else\r
{\r
- this.tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( this.Buffer, this.n現在書き込み許可待ちのバッファ番号, this.bループする );\r
- this.n現在書き込み許可待ちのバッファ番号++;\r
+ Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ( float ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 ) );\r
}\r
}\r
+ else\r
+ {\r
+// if ( b再生中 ) // #30838 2012.2.24 yyagi (delete b再生中)\r
+// {\r
+ this.Buffer.Frequency = ( int ) ( _db周波数倍率 * _db再生速度 * nオリジナルの周波数 );\r
+// }\r
+ }\r
}\r
}\r
}\r
- public int t時刻から位置を返す( long n時刻 )\r
- {\r
- double num = ( n時刻 * this.db再生速度 ) * this.db周波数倍率;\r
- return (int) ( ( num * 0.001 ) * this.Buffer.Format.SamplesPerSecond );\r
- }\r
+ #endregion\r
\r
- #region [ IDisposable 実装 ]\r
- //-----------------\r
- public void Dispose()\r
+ public bool b演奏終了後も再生が続くチップである = false; // これがtrueなら、本サウンドの再生終了のコールバック時に自動でミキサーから削除する\r
+\r
+ //private STREAMPROC _cbStreamXA; // make it global, so that the GC can not remove it\r
+ private SYNCPROC _cbEndofStream; // ストリームの終端まで再生されたときに呼び出されるコールバック\r
+// private WaitCallback _cbRemoveMixerChannel;\r
+\r
+ /// <summary>\r
+ /// <para>0:最小~100:原音</para>\r
+ /// </summary>\r
+ public int n音量\r
{\r
- if( !this.bDispose完了済み )\r
+ get\r
{\r
- if( this.Buffer != null )\r
+ if( this.bBASSサウンドである )\r
{\r
- this.Buffer.Stop();\r
- this.Buffer.Dispose();\r
- this.Buffer = null;\r
+ float f音量 = 0.0f;\r
+ if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_VOL, ref f音量 ) )\r
+ //if ( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, ref f音量 ) == -1 )\r
+ return 100;\r
+ return (int) ( f音量 * 100 );\r
}\r
- if( this.gch中継バッファ.IsAllocated )\r
+ else if( this.bDirectSoundである )\r
{\r
- this.gch中継バッファ.Free();\r
+ return this._n音量;\r
}\r
- if( this.by中継バッファ != null )\r
+ return -1;\r
+ }\r
+ set\r
+ {\r
+ if( this.bBASSサウンドである )\r
{\r
- this.by中継バッファ = null;\r
+ float f音量 = Math.Min( Math.Max( value, 0 ), 100 ) / 100.0f; // 0~100 → 0.0~1.0\r
+ //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, f音量 ) };\r
+ //BassMix.BASS_Mixer_ChannelSetEnvelope( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_VOL, nodes );\r
+ Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_VOL, f音量 );\r
+\r
}\r
- if( this.nHandle >= 0 )\r
+ else if( this.bDirectSoundである )\r
{\r
- this.Close( this.nHandle );\r
- }\r
- this.bDispose完了済み = true;\r
+ this._n音量 = value;\r
+\r
+ if( this._n音量 == 0 )\r
+ {\r
+ this._n音量db = -10000;\r
+ }\r
+ else\r
+ {\r
+ this._n音量db = (int) ( ( 20.0 * Math.Log10( ( (double) this._n音量 ) / 100.0 ) ) * 100.0 );\r
+ }\r
+\r
+ this.Buffer.Volume = this._n音量db;\r
+ }\r
}\r
}\r
- //-----------------\r
+\r
+ /// <summary>\r
+ /// <para>左:-100~中央:0~100:右。set のみ。</para>\r
+ /// </summary>\r
+ public int n位置\r
+ {\r
+ get\r
+ {\r
+ if( this.bBASSサウンドである )\r
+ {\r
+ float f位置 = 0.0f;\r
+ if ( !Bass.BASS_ChannelGetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_PAN, ref f位置 ) )\r
+ //if( BassMix.BASS_Mixer_ChannelGetEnvelopePos( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_PAN, ref f位置 ) == -1 )\r
+ return 0;\r
+ return (int) ( f位置 * 100 );\r
+ }\r
+ else if( this.bDirectSoundである )\r
+ {\r
+ return this._n位置;\r
+ }\r
+ return -9999;\r
+ }\r
+ set\r
+ {\r
+ if( this.bBASSサウンドである )\r
+ {\r
+ float f位置 = Math.Min( Math.Max( value, -100 ), 100 ) / 100.0f; // -100~100 → -1.0~1.0\r
+ //var nodes = new BASS_MIXER_NODE[ 1 ] { new BASS_MIXER_NODE( 0, f位置 ) };\r
+ //BassMix.BASS_Mixer_ChannelSetEnvelope( this.hBassStream, BASSMIXEnvelope.BASS_MIXER_ENV_PAN, nodes );\r
+ Bass.BASS_ChannelSetAttribute( this.hBassStream, BASSAttribute.BASS_ATTRIB_PAN, f位置 );\r
+ }\r
+ else if( this.bDirectSoundである )\r
+ {\r
+ this._n位置 = Math.Min( Math.Max( -100, value ), 100 ); // -100~100\r
+\r
+ if( this._n位置 == 0 )\r
+ {\r
+ this._n位置db = 0;\r
+ }\r
+ else if( this._n位置 == -100 )\r
+ {\r
+ this._n位置db = -10000;\r
+ }\r
+ else if( this._n位置 == 100 )\r
+ {\r
+ this._n位置db = 10000;\r
+ }\r
+ else if( this._n位置 < 0 )\r
+ {\r
+ this._n位置db = (int) ( ( 20.0 * Math.Log10( ( (double) ( this._n位置 + 100 ) ) / 100.0 ) ) * 100.0 );\r
+ }\r
+ else\r
+ {\r
+ this._n位置db = (int) ( ( -20.0 * Math.Log10( ( (double) ( 100 - this._n位置 ) ) / 100.0 ) ) * 100.0 );\r
+ }\r
+\r
+ this.Buffer.Pan = this._n位置db;\r
+ }\r
+ }\r
+ }\r
+\r
+ /// <summary>\r
+ /// <para>DirectSoundのセカンダリバッファ。</para>\r
+ /// </summary>\r
+ //public SecondarySoundBuffer DirectSoundBuffer\r
+ public SoundBuffer DirectSoundBuffer\r
+ {\r
+ get { return this.Buffer; }\r
+ }\r
+\r
+ /// <summary>\r
+ /// <para>DirectSoundのセカンダリバッファ作成時のフラグ。</para>\r
+ /// </summary>\r
+ public BufferFlags DirectSoundBufferFlags\r
+ {\r
+ get;\r
+ protected set;\r
+ }\r
+\r
+ /// <summary>\r
+ /// <para>全インスタンスリスト。</para>\r
+ /// <para>~を作成する() で追加され、t解放する() or Dispose() で解放される。</para>\r
+ /// </summary>\r
+ public static List<CSound> listインスタンス = new List<CSound>();\r
+\r
+ public static void ShowAllCSoundFiles()\r
+ {\r
+ int i = 0;\r
+ foreach ( CSound cs in listインスタンス )\r
+ {\r
+ Debug.WriteLine( i++.ToString( "d3" ) + ": " + Path.GetFileName( cs.strファイル名 ) );\r
+ }\r
+ }\r
+\r
+ public CSound()\r
+ {\r
+ this.n音量 = 100;\r
+ this.n位置 = 0;\r
+ this._db周波数倍率 = 1.0;\r
+ this._db再生速度 = 1.0;\r
+ this.DirectSoundBufferFlags = CSoundDeviceDirectSound.DefaultFlags;\r
+// this._cbRemoveMixerChannel = new WaitCallback( RemoveMixerChannelLater );\r
+ this._hBassStream = -1;\r
+ this._hTempoStream = 0;\r
+ }\r
+\r
+ public object Clone()\r
+ {\r
+ if ( !bDirectSoundである )\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+ CSound clone = (CSound) MemberwiseClone(); // これだけだとCY連打が途切れる&タイトルに戻る際にNullRef例外発生\r
+ this.DirectSound.DuplicateSoundBuffer( this.Buffer, out clone.Buffer );\r
+\r
+ CSound.listインスタンス.Add( clone ); // インスタンスリストに登録。\r
+\r
+ return clone;\r
+ }\r
+ public void tASIOサウンドを作成する( string strファイル名, int hMixer )\r
+ {\r
+ this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE );\r
+ this.eデバイス種別 = ESoundDeviceType.ASIO; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
+ }\r
+ public void tASIOサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer )\r
+ {\r
+ this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE );\r
+ this.eデバイス種別 = ESoundDeviceType.ASIO; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
+ }\r
+ public void tWASAPIサウンドを作成する( string strファイル名, int hMixer, ESoundDeviceType eデバイス種別 )\r
+ {\r
+ this.tBASSサウンドを作成する( strファイル名, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );\r
+ this.eデバイス種別 = eデバイス種別; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
+ }\r
+ public void tWASAPIサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, ESoundDeviceType eデバイス種別 )\r
+ {\r
+ this.tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_SAMPLE_FLOAT );\r
+ this.eデバイス種別 = eデバイス種別; // 作成後に設定する。(作成に失敗してると例外発出されてここは実行されない)\r
+ }\r
+ public void tDirectSoundサウンドを作成する( string strファイル名, DirectSound DirectSound )\r
+ {\r
+ this.e作成方法 = E作成方法.ファイルから;\r
+ this.strファイル名 = strファイル名;\r
+\r
+ if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 ||\r
+ String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 ||\r
+ String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 ) // caselessで文字列比較\r
+ {\r
+ tDirectSoundサウンドを作成するXaOggMp3( strファイル名, DirectSound );\r
+ return;\r
+ }\r
+\r
+ // すべてのファイルを DirectShow でデコードすると時間がかかるので、ファイルが WAV かつ PCM フォーマットでない場合のみ DirectShow でデコードする。\r
+\r
+ byte[] byArrWAVファイルイメージ = null;\r
+ bool bファイルがWAVかつPCMフォーマットである = true;\r
+\r
+ {\r
+ #region [ ファイルがWAVかつPCMフォーマットか否か調べる。]\r
+ //-----------------\r
+ try\r
+ {\r
+ using ( var ws = new WaveStream( strファイル名 ) )\r
+ {\r
+ if ( ws.Format.FormatTag != WaveFormatTag.Pcm )\r
+ bファイルがWAVかつPCMフォーマットである = false;\r
+ }\r
+ }\r
+ catch\r
+ {\r
+ bファイルがWAVかつPCMフォーマットである = false;\r
+ }\r
+ //-----------------\r
+ #endregion\r
+\r
+ if ( bファイルがWAVかつPCMフォーマットである )\r
+ {\r
+ #region [ ファイルを読み込んで byArrWAVファイルイメージへ格納。]\r
+ //-----------------\r
+ var fs = File.Open( strファイル名, FileMode.Open, FileAccess.Read );\r
+ var br = new BinaryReader( fs );\r
+\r
+ byArrWAVファイルイメージ = new byte[ fs.Length ];\r
+ br.Read( byArrWAVファイルイメージ, 0, (int) fs.Length );\r
+\r
+ br.Close();\r
+ fs.Close();\r
+ //-----------------\r
+ #endregion\r
+ }\r
+ else\r
+ {\r
+ #region [ DirectShow でデコード変換し、 byArrWAVファイルイメージへ格納。]\r
+ //-----------------\r
+ CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );\r
+ //-----------------\r
+ #endregion\r
+ }\r
+ }\r
+\r
+ // あとはあちらで。\r
+\r
+ this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound );\r
+ }\r
+ public void tDirectSoundサウンドを作成するXaOggMp3( string strファイル名, DirectSound DirectSound )\r
+ {\r
+ this.e作成方法 = E作成方法.ファイルから;\r
+ this.strファイル名 = strファイル名;\r
+\r
+\r
+ WaveFormat wfx = new WaveFormat();\r
+ int nPCMデータの先頭インデックス = 0;\r
+// int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 ); // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );\r
+\r
+ int nPCMサイズbyte;\r
+ CWin32.WAVEFORMATEX cw32wfx;\r
+ tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,\r
+ out nPCMデータの先頭インデックス, out nPCMサイズbyte, out cw32wfx, false );\r
+\r
+ wfx.AverageBytesPerSecond = (int) cw32wfx.nAvgBytesPerSec;\r
+ wfx.BitsPerSample = (short) cw32wfx.wBitsPerSample;\r
+ wfx.BlockAlignment = (short) cw32wfx.nBlockAlign;\r
+ wfx.Channels = (short) cw32wfx.nChannels;\r
+ wfx.FormatTag = WaveFormatTag.Pcm; // xa.waveformatex.wFormatTag;\r
+ wfx.SamplesPerSecond = (int) cw32wfx.nSamplesPerSec;\r
+\r
+ // セカンダリバッファを作成し、PCMデータを書き込む。\r
+ tDirectSoundサウンドを作成する・セカンダリバッファの作成とWAVデータ書き込み\r
+ ( ref this.byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags, wfx,\r
+ nPCMサイズbyte, nPCMデータの先頭インデックス );\r
+ }\r
+\r
+ public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound )\r
+ {\r
+ this.tDirectSoundサウンドを作成する( byArrWAVファイルイメージ, DirectSound, CSoundDeviceDirectSound.DefaultFlags );\r
+ }\r
+ public void tDirectSoundサウンドを作成する( byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags )\r
+ {\r
+ if( this.e作成方法 == E作成方法.Unknown )\r
+ this.e作成方法 = E作成方法.WAVファイルイメージから;\r
+\r
+ WaveFormat wfx = null;\r
+ int nPCMデータの先頭インデックス = -1;\r
+ int nPCMサイズbyte = -1;\r
+ \r
+ #region [ byArrWAVファイルイメージ[] から上記3つのデータを取得。]\r
+ //-----------------\r
+ var ms = new MemoryStream( byArrWAVファイルイメージ );\r
+ var br = new BinaryReader( ms );\r
+\r
+ try\r
+ {\r
+ // 'RIFF'+RIFFデータサイズ\r
+\r
+ if( br.ReadUInt32() != 0x46464952 )\r
+ throw new InvalidDataException( "RIFFファイルではありません。" );\r
+ br.ReadInt32();\r
+\r
+ // 'WAVE'\r
+ if( br.ReadUInt32() != 0x45564157 )\r
+ throw new InvalidDataException( "WAVEファイルではありません。" );\r
+\r
+ // チャンク\r
+ while( ( ms.Position + 8 ) < ms.Length ) // +8 は、チャンク名+チャンクサイズ。残り8バイト未満ならループ終了。\r
+ {\r
+ uint chunkName = br.ReadUInt32();\r
+\r
+ // 'fmt '\r
+ if( chunkName == 0x20746D66 )\r
+ {\r
+ long chunkSize = (long) br.ReadUInt32();\r
+\r
+ var tag = (WaveFormatTag) br.ReadUInt16();\r
+\r
+ if( tag == WaveFormatTag.Pcm ) wfx = new WaveFormat();\r
+ else if( tag == WaveFormatTag.Extensible ) wfx = new SlimDX.Multimedia.WaveFormatExtensible(); // このクラスは WaveFormat を継承している。\r
+ else\r
+ throw new InvalidDataException( string.Format( "未対応のWAVEフォーマットタグです。(Tag:{0})", tag.ToString() ) );\r
+\r
+ wfx.FormatTag = tag;\r
+ wfx.Channels = br.ReadInt16();\r
+ wfx.SamplesPerSecond = br.ReadInt32();\r
+ wfx.AverageBytesPerSecond = br.ReadInt32();\r
+ wfx.BlockAlignment = br.ReadInt16();\r
+ wfx.BitsPerSample = br.ReadInt16();\r
+\r
+ long nフォーマットサイズbyte = 16;\r
+\r
+ if( wfx.FormatTag == WaveFormatTag.Extensible )\r
+ {\r
+ br.ReadUInt16(); // 拡張領域サイズbyte\r
+ var wfxEx = (SlimDX.Multimedia.WaveFormatExtensible) wfx;\r
+ wfxEx.ValidBitsPerSample = br.ReadInt16();\r
+ wfxEx.ChannelMask = (Speakers) br.ReadInt32();\r
+ wfxEx.SubFormat = new Guid( br.ReadBytes( 16 ) ); // GUID は 16byte (128bit)\r
+\r
+ nフォーマットサイズbyte += 24;\r
+ }\r
+\r
+ ms.Seek( chunkSize - nフォーマットサイズbyte, SeekOrigin.Current );\r
+ continue;\r
+ }\r
+\r
+ // 'data'\r
+ else if( chunkName == 0x61746164 )\r
+ {\r
+ nPCMサイズbyte = br.ReadInt32();\r
+ nPCMデータの先頭インデックス = (int) ms.Position;\r
+\r
+ ms.Seek( nPCMサイズbyte, SeekOrigin.Current );\r
+ continue;\r
+ }\r
+\r
+ // その他\r
+ else\r
+ {\r
+ long chunkSize = (long) br.ReadUInt32();\r
+ ms.Seek( chunkSize, SeekOrigin.Current );\r
+ continue;\r
+ }\r
+ }\r
+\r
+ if( wfx == null )\r
+ throw new InvalidDataException( "fmt チャンクが存在しません。不正なサウンドデータです。" );\r
+ if( nPCMサイズbyte < 0 )\r
+ throw new InvalidDataException( "data チャンクが存在しません。不正なサウンドデータです。" );\r
+ }\r
+ finally\r
+ {\r
+ ms.Close();\r
+ br.Close();\r
+ }\r
+ //-----------------\r
+ #endregion\r
+\r
+\r
+ // セカンダリバッファを作成し、PCMデータを書き込む。\r
+ tDirectSoundサウンドを作成する・セカンダリバッファの作成とWAVデータ書き込み(\r
+ ref byArrWAVファイルイメージ, DirectSound, flags, wfx, nPCMサイズbyte, nPCMデータの先頭インデックス );\r
+ }\r
+\r
+ private void tDirectSoundサウンドを作成する・セカンダリバッファの作成とWAVデータ書き込み\r
+ ( ref byte[] byArrWAVファイルイメージ, DirectSound DirectSound, BufferFlags flags, WaveFormat wfx,\r
+ int nPCMサイズbyte, int nPCMデータの先頭インデックス )\r
+ {\r
+ // セカンダリバッファを作成し、PCMデータを書き込む。\r
+\r
+ this.Buffer = new SecondarySoundBuffer( DirectSound, new SoundBufferDescription()\r
+ {\r
+ Format = ( wfx.FormatTag == WaveFormatTag.Pcm ) ? wfx : (SlimDX.Multimedia.WaveFormatExtensible) wfx,\r
+ Flags = flags,\r
+ SizeInBytes = nPCMサイズbyte,\r
+ } );\r
+ this.Buffer.Write( byArrWAVファイルイメージ, nPCMデータの先頭インデックス, nPCMサイズbyte, 0, LockFlags.None );\r
+\r
+ // 作成完了。\r
+\r
+ this.eデバイス種別 = ESoundDeviceType.DirectSound;\r
+ this.DirectSoundBufferFlags = flags;\r
+ this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;\r
+ this.DirectSound = DirectSound;\r
+\r
+ // DTXMania用に追加\r
+ this.nオリジナルの周波数 = wfx.SamplesPerSecond;\r
+ n総演奏時間ms = (int) ( ( (double) nPCMサイズbyte ) / ( this.Buffer.Format.AverageBytesPerSecond * 0.001 ) );\r
+\r
+\r
+ // インスタンスリストに登録。\r
+\r
+ CSound.listインスタンス.Add( this );\r
+ }\r
+\r
+ #region [ DTXMania用の変換 ]\r
+\r
+ public void tサウンドを破棄する( CSound cs )\r
+ {\r
+ cs.t解放する();\r
+ }\r
+ public void t再生を開始する()\r
+ {\r
+ t再生位置を先頭に戻す();\r
+ tサウンドを再生する();\r
+ }\r
+ public void t再生を開始する( bool bループする )\r
+ {\r
+ if ( bBASSサウンドである )\r
+ {\r
+ if ( bループする )\r
+ {\r
+ Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_SAMPLE_LOOP, BASSFlag.BASS_SAMPLE_LOOP );\r
+ }\r
+ else\r
+ {\r
+ Bass.BASS_ChannelFlags( this.hBassStream, BASSFlag.BASS_DEFAULT, BASSFlag.BASS_DEFAULT );\r
+ }\r
+ }\r
+ t再生位置を先頭に戻す();\r
+ tサウンドを再生する( bループする );\r
+ }\r
+ public void t再生を停止する()\r
+ {\r
+ tサウンドを停止する();\r
+ t再生位置を先頭に戻す();\r
+ }\r
+ public void t再生を一時停止する()\r
+ {\r
+ tサウンドを停止する(true);\r
+ this.n一時停止回数++;\r
+ }\r
+ public void t再生を再開する( long t ) // ★★★★★★★★★★★★★★★★★★★★★★★★★★★★\r
+ {\r
+ Debug.WriteLine( "t再生を再開する(long " + t + ")" );\r
+ t再生位置を変更する( t );\r
+ tサウンドを再生する();\r
+ this.n一時停止回数--;\r
+ }\r
+ public bool b一時停止中\r
+ {\r
+ get\r
+ {\r
+ if ( this.bBASSサウンドである )\r
+ {\r
+ bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PAUSED ) &\r
+ ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) > 0 );\r
+ return ret;\r
+ }\r
+ else\r
+ {\r
+ return ( this.n一時停止回数 > 0 );\r
+ }\r
+ }\r
+ }\r
+ public bool b再生中\r
+ {\r
+ get\r
+ {\r
+ if ( this.eデバイス種別 == ESoundDeviceType.DirectSound )\r
+ {\r
+ return ( ( this.Buffer.Status & BufferStatus.Playing ) != BufferStatus.None );\r
+ }\r
+ else\r
+ {\r
+ // 基本的にはBASS_ACTIVE_PLAYINGなら再生中だが、最後まで再生しきったchannelも\r
+ // BASS_ACTIVE_PLAYINGのままになっているので、小細工が必要。\r
+ bool ret = ( BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) == BASSActive.BASS_ACTIVE_PLAYING );\r
+ if ( BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) >= nBytes )\r
+ {\r
+ ret = false;\r
+ }\r
+ return ret;\r
+ }\r
+ }\r
+ }\r
+ //public lint t時刻から位置を返す( long t )\r
+ //{\r
+ // double num = ( n時刻 * this.db再生速度 ) * this.db周波数倍率;\r
+ // return (int) ( ( num * 0.01 ) * this.nSamplesPerSecond );\r
+ //}\r
#endregion\r
\r
\r
+ public void t解放する()\r
+ {\r
+ t解放する( false );\r
+ }\r
+\r
+ public void t解放する( bool _bインスタンス削除 )\r
+ {\r
+ if ( this.bBASSサウンドである ) // stream数の削減用\r
+ {\r
+ tBASSサウンドをミキサーから削除する();\r
+ _cbEndofStream = null;\r
+ //_cbStreamXA = null;\r
+ CSound管理.nStreams--;\r
+ }\r
+ bool bManagedも解放する = true;\r
+ bool bインスタンス削除 = _bインスタンス削除; // CSoundの再初期化時は、インスタンスは存続する。\r
+ this.Dispose( bManagedも解放する, bインスタンス削除 );\r
+//Debug.WriteLine( "Disposed: " + _bインスタンス削除 + " : " + Path.GetFileName( this.strファイル名 ) );\r
+ }\r
+ public void tサウンドを再生する()\r
+ {\r
+ tサウンドを再生する( false );\r
+ }\r
+ public void tサウンドを再生する( bool bループする )\r
+ {\r
+ if ( this.bBASSサウンドである ) // BASSサウンド時のループ処理は、t再生を開始する()側に実装。ここでは「bループする」は未使用。\r
+ {\r
+//Debug.WriteLine( "再生中?: " + System.IO.Path.GetFileName(this.strファイル名) + " status=" + BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) + " current=" + BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) + " nBytes=" + nBytes );\r
+ bool b = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );\r
+ if ( !b )\r
+ {\r
+//Debug.WriteLine( "再生しようとしたが、Mixerに登録されていなかった: " + Path.GetFileName( this.strファイル名 ) + ", stream#=" + this.hBassStream + ", ErrCode=" + Bass.BASS_ErrorGetCode() );\r
\r
- // (2) 以下は各子クラスごとに実装する\r
+ bool bb = tBASSサウンドをミキサーに追加する();\r
+ if ( !bb )\r
+ {\r
+Debug.WriteLine( "Mixerへの登録に失敗: " + Path.GetFileName( this.strファイル名 ) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );\r
+ }\r
+ else\r
+ {\r
+//Debug.WriteLine( "Mixerへの登録に成功: " + Path.GetFileName( this.strファイル名 ) + ": " + Bass.BASS_ErrorGetCode() );\r
+ }\r
+ //this.t再生位置を先頭に戻す();\r
\r
- protected virtual int Open( string filename )\r
+ bool bbb = BassMix.BASS_Mixer_ChannelPlay( this.hBassStream );\r
+ if (!bbb)\r
+ {\r
+Debug.WriteLine("更に再生に失敗: " + Path.GetFileName(this.strファイル名) + ", ErrCode=" + Bass.BASS_ErrorGetCode() );\r
+ }\r
+ else\r
+ {\r
+// Debug.WriteLine("再生成功(ミキサー追加後) : " + Path.GetFileName(this.strファイル名));\r
+ }\r
+ }\r
+ else\r
+ {\r
+//Debug.WriteLine( "再生成功: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" );\r
+ }\r
+ }\r
+ else if( this.bDirectSoundである )\r
+ {\r
+ PlayFlags pf = ( bループする ) ? PlayFlags.Looping : PlayFlags.None;\r
+ this.Buffer.Play( 0, pf );\r
+ }\r
+ }\r
+ public void tサウンドを先頭から再生する()\r
{\r
- throw new NotImplementedException();\r
+ this.t再生位置を先頭に戻す();\r
+ this.tサウンドを再生する();\r
}\r
- protected virtual int GetFormat( int nHandle, ref CWin32.WAVEFORMATEX wfx )\r
+ public void tサウンドを停止してMixerからも削除する()\r
{\r
- throw new NotImplementedException();\r
+ tサウンドを停止する( false );\r
+ if ( bBASSサウンドである )\r
+ {\r
+ tBASSサウンドをミキサーから削除する();\r
+ }\r
}\r
- protected virtual uint GetTotalPCMSize( int nHandle )\r
+ public void tサウンドを停止する()\r
{\r
- throw new NotImplementedException();\r
+ tサウンドを停止する( false );\r
}\r
- protected virtual int Seek( int nHandle, uint dwPosition )\r
+ public void tサウンドを停止する( bool pause )\r
{\r
- throw new NotImplementedException();\r
+ if( this.bBASSサウンドである )\r
+ {\r
+//Debug.WriteLine( "停止: " + System.IO.Path.GetFileName( this.strファイル名 ) + " status=" + BassMix.BASS_Mixer_ChannelIsActive( this.hBassStream ) + " current=" + BassMix.BASS_Mixer_ChannelGetPosition( this.hBassStream ) + " nBytes=" + nBytes );\r
+ BassMix.BASS_Mixer_ChannelPause( this.hBassStream );\r
+ if ( !pause )\r
+ {\r
+ // tBASSサウンドをミキサーから削除する(); // PAUSEと再生停止を区別できるようにすること!!\r
+ }\r
+ }\r
+ else if( this.bDirectSoundである )\r
+ {\r
+ try\r
+ {\r
+ this.Buffer.Stop();\r
+ }\r
+ catch ( Exception )\r
+ {\r
+ // WASAPI/ASIOとDirectSoundを同時使用すると、Bufferがlostしてここで例外発生する。→ catchして無視する。\r
+ // DTXCからDTXManiaを呼び出すと、DTXC終了時にこの現象が発生する。\r
+ }\r
+ }\r
+ this.n一時停止回数 = 0;\r
}\r
- protected virtual int Decode( int nHandle, IntPtr pDest, uint szDestSize, int bLoop )\r
+ \r
+ public void t再生位置を先頭に戻す()\r
{\r
- throw new NotImplementedException();\r
+ if( this.bBASSサウンドである )\r
+ {\r
+ BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, 0 );\r
+ //pos = 0;\r
+ }\r
+ else if( this.bDirectSoundである )\r
+ {\r
+ this.Buffer.CurrentPlayPosition = 0;\r
+ }\r
}\r
- protected virtual void Close( int nHandle )\r
+ public void t再生位置を変更する( long n位置ms )\r
{\r
- throw new NotImplementedException();\r
+ if ( this.bBASSサウンドである )\r
+ {\r
+ bool b = true;\r
+ try\r
+ {\r
+ b = BassMix.BASS_Mixer_ChannelSetPosition( this.hBassStream, Bass.BASS_ChannelSeconds2Bytes( this.hBassStream, n位置ms * this.db周波数倍率 * this.db再生速度 / 1000.0 ), BASSMode.BASS_POS_BYTES );\r
+ }\r
+ catch ( Exception e )\r
+ {\r
+ Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + e.ToString() );\r
+ }\r
+ finally\r
+ {\r
+ if ( !b )\r
+ {\r
+ BASSError be = Bass.BASS_ErrorGetCode();\r
+ Trace.TraceInformation( Path.GetFileName( this.strファイル名 ) + ": Seek error: " + be.ToString() );\r
+ }\r
+ }\r
+ }\r
+ else if( this.bDirectSoundである )\r
+ {\r
+ int n位置sample = (int) ( this.Buffer.Format.SamplesPerSecond * n位置ms * 0.001 * _db周波数倍率 * _db再生速度 ); // #30839 2013.2.24 yyagi; add _db周波数倍率 and _db再生速度\r
+ try\r
+ {\r
+ this.Buffer.CurrentPlayPosition = n位置sample * this.Buffer.Format.BlockAlignment;\r
+ }\r
+ catch ( DirectSoundException e )\r
+ {\r
+ Trace.TraceError( "{0}: Seek error: {1}", Path.GetFileName( this.strファイル名 ), n位置ms, e.Message );\r
+ }\r
+ }\r
+ }\r
+\r
+ public static void tすべてのサウンドを初期状態に戻す()\r
+ {\r
+ foreach ( var sound in CSound.listインスタンス )\r
+ {\r
+ sound.t解放する( false );\r
+ }\r
}\r
+ public static void tすべてのサウンドを再構築する( ISoundDevice device )\r
+ {\r
+ if( CSound.listインスタンス.Count == 0 )\r
+ return;\r
+\r
\r
+ // サウンドを再生する際にインスタンスリストも更新されるので、配列にコピーを取っておき、リストはクリアする。\r
+\r
+ var sounds = CSound.listインスタンス.ToArray();\r
+ CSound.listインスタンス.Clear();\r
+ \r
+\r
+ // 配列に基づいて個々のサウンドを作成する。\r
+\r
+ for( int i = 0; i < sounds.Length; i++ )\r
+ {\r
+ switch( sounds[ i ].e作成方法 )\r
+ {\r
+ #region [ ファイルから ]\r
+ case E作成方法.ファイルから:\r
+ string strファイル名 = sounds[ i ].strファイル名;\r
+ sounds[ i ].Dispose( true, false );\r
+ device.tサウンドを作成する( strファイル名, ref sounds[ i ] );\r
+ break;\r
+ #endregion\r
+ #region [ WAVファイルイメージから ]\r
+ case E作成方法.WAVファイルイメージから:\r
+ if( sounds[ i ].bBASSサウンドである )\r
+ {\r
+ byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;\r
+ sounds[ i ].Dispose( true, false );\r
+ device.tサウンドを作成する( byArrWaveファイルイメージ, ref sounds[ i ] );\r
+ }\r
+ else if( sounds[ i ].bDirectSoundである )\r
+ {\r
+ byte[] byArrWaveファイルイメージ = sounds[ i ].byArrWAVファイルイメージ;\r
+ var flags = sounds[ i ].DirectSoundBufferFlags;\r
+ sounds[ i ].Dispose( true, false );\r
+ ( (CSoundDeviceDirectSound) device ).tサウンドを作成する( byArrWaveファイルイメージ, flags, ref sounds[ i ] );\r
+ }\r
+ break;\r
+ #endregion\r
+ }\r
+ }\r
+ }\r
\r
- // その他\r
+ #region [ Dispose-Finalizeパターン実装 ]\r
+ //-----------------\r
+ public void Dispose()\r
+ {\r
+ this.Dispose( true, true );\r
+ GC.SuppressFinalize( this );\r
+ }\r
+ protected void Dispose( bool bManagedも解放する, bool bインスタンス削除 )\r
+ {\r
+ if( this.bBASSサウンドである )\r
+ {\r
+ #region [ ASIO, WASAPI の解放 ]\r
+ //-----------------\r
+ if ( _hTempoStream != 0 )\r
+ {\r
+ BassMix.BASS_Mixer_ChannelRemove( this._hTempoStream );\r
+ Bass.BASS_StreamFree( this._hTempoStream );\r
+ }\r
+ BassMix.BASS_Mixer_ChannelRemove( this._hBassStream );\r
+ Bass.BASS_StreamFree( this._hBassStream );\r
+ this.hBassStream = -1;\r
+ this._hBassStream = -1;\r
+ this._hTempoStream = 0;\r
+ //-----------------\r
+ #endregion\r
+ }\r
+\r
+ if( bManagedも解放する )\r
+ {\r
+ //int freeIndex = -1;\r
+\r
+ //if ( CSound.listインスタンス != null )\r
+ //{\r
+ // freeIndex = CSound.listインスタンス.IndexOf( this );\r
+ // if ( freeIndex == -1 )\r
+ // {\r
+ // Debug.WriteLine( "ERR: freeIndex==-1 : Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );\r
+ // }\r
+ //}\r
+\r
+ if( this.eデバイス種別 == ESoundDeviceType.DirectSound )\r
+ {\r
+ #region [ DirectSound の解放 ]\r
+ //-----------------\r
+ if( this.Buffer != null )\r
+ {\r
+ try\r
+ {\r
+ this.Buffer.Stop();\r
+ }\r
+ catch\r
+ {\r
+ // 演奏終了後、長時間解放しないでいると、たまに AccessViolationException が発生することがある。\r
+ }\r
+ C共通.tDisposeする( ref this.Buffer );\r
+ }\r
+ //-----------------\r
+ #endregion\r
+ }\r
+\r
+ if( this.e作成方法 == E作成方法.WAVファイルイメージから &&\r
+ this.eデバイス種別 != ESoundDeviceType.DirectSound ) // DirectSound は hGC 未使用。\r
+ {\r
+ if ( this.hGC != null && this.hGC.IsAllocated )\r
+ {\r
+ this.hGC.Free();\r
+ this.hGC = default( GCHandle );\r
+ }\r
+ }\r
+ if ( this.byArrWAVファイルイメージ != null )\r
+ {\r
+ this.byArrWAVファイルイメージ = null;\r
+ }\r
+\r
+ if ( bインスタンス削除 )\r
+ {\r
+ //try\r
+ //{\r
+ // CSound.listインスタンス.RemoveAt( freeIndex );\r
+ //}\r
+ //catch\r
+ //{\r
+ // Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );\r
+ //}\r
+ bool b = CSound.listインスタンス.Remove( this ); // これだと、Clone()したサウンドのremoveに失敗する\r
+ if ( !b )\r
+ {\r
+ Debug.WriteLine( "FAILED to remove CSound.listインスタンス: Count=" + CSound.listインスタンス.Count + ", filename=" + Path.GetFileName( this.strファイル名 ) );\r
+ }\r
+\r
+ }\r
+ }\r
+ }\r
+ ~CSound()\r
+ {\r
+ this.Dispose( false, true );\r
+ }\r
+ //-----------------\r
+ #endregion\r
+\r
+ #region [ protected ]\r
+ //-----------------\r
+ protected enum E作成方法 { ファイルから, WAVファイルイメージから, Unknown }\r
+ protected E作成方法 e作成方法 = E作成方法.Unknown;\r
+ protected ESoundDeviceType eデバイス種別 = ESoundDeviceType.Unknown;\r
+ public string strファイル名 = null;\r
+ protected byte[] byArrWAVファイルイメージ = null; // WAVファイルイメージ、もしくはchunkのDATA部のみ\r
+ protected GCHandle hGC;\r
+ protected int _hTempoStream = 0;\r
+ protected int _hBassStream = -1; // ASIO, WASAPI 用\r
+ protected int hBassStream = 0; // #31076 2013.4.1 yyagi; プロパティとして実装すると動作が低速になったため、\r
+ // tBASSサウンドを作成する・ストリーム生成後の共通処理()のタイミングと、\r
+ // 再生速度を変更したタイミングでのみ、\r
+ // hBassStreamを更新するようにした。\r
+ //{\r
+ // get\r
+ // {\r
+ // if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する\r
+ // {\r
+ // return _hTempoStream;\r
+ // }\r
+ // else\r
+ // {\r
+ // return _hBassStream;\r
+ // }\r
+ // }\r
+ // set\r
+ // {\r
+ // _hBassStream = value;\r
+ // }\r
+ //}\r
+ protected SoundBuffer Buffer = null; // DirectSound 用\r
+ protected DirectSound DirectSound;\r
+ protected int hMixer = -1; // 設計壊してゴメン Mixerに後で登録するときに使う\r
+ //-----------------\r
+ #endregion\r
\r
#region [ private ]\r
//-----------------\r
- private double _db再生速度 = 1.0;\r
- private double _db周波数倍率 = 1.0;\r
- private int _n位置;\r
+ private bool bDirectSoundである\r
+ {\r
+ get { return ( this.eデバイス種別 == ESoundDeviceType.DirectSound ); }\r
+ }\r
+ private bool bBASSサウンドである\r
+ {\r
+ get\r
+ {\r
+ return (\r
+ this.eデバイス種別 == ESoundDeviceType.ASIO ||\r
+ this.eデバイス種別 == ESoundDeviceType.ExclusiveWASAPI ||\r
+ this.eデバイス種別 == ESoundDeviceType.SharedWASAPI );\r
+ }\r
+ }\r
+ private int _n位置 = 0;\r
private int _n位置db;\r
private int _n音量 = 100;\r
private int _n音量db;\r
- private bool bDispose完了済み;\r
- private byte[] by中継バッファ;\r
- private GCHandle gch中継バッファ;\r
- private int nHandle = -1;\r
- private int n一時停止位置byte;\r
- private int n一時停止回数;\r
- private int n現在のPCM側の位置byte;\r
- private int n現在書き込み許可待ちのバッファ番号;\r
+ private long nBytes = 0;\r
+ private int n一時停止回数 = 0;\r
+ private int nオリジナルの周波数 = 0;\r
+ private double _db周波数倍率 = 1.0;\r
+ private double _db再生速度 = 1.0;\r
+ private bool bIs1倍速再生 = true;\r
\r
- private void tストリーム再生位置リセット()\r
+ private void tBASSサウンドを作成する( string strファイル名, int hMixer, BASSFlag flags )\r
{\r
- if( this.nHandle != -1 )\r
+ #region [ xaとwav(RIFF chunked vorbis)に対しては専用の処理をする ]\r
+ switch ( Path.GetExtension( strファイル名 ).ToLower() )\r
{\r
- this.Seek( this.nHandle, 0 );\r
- this.n現在のPCM側の位置byte = 0;\r
+ case ".xa":\r
+ tBASSサウンドを作成するXA( strファイル名, hMixer, flags );\r
+ return;\r
+\r
+ case ".wav":\r
+ if ( tRIFFchunkedVorbisならDirectShowでDecodeする( strファイル名, ref byArrWAVファイルイメージ ) )\r
+ {\r
+ tBASSサウンドを作成する( byArrWAVファイルイメージ, hMixer, flags );\r
+ return;\r
+ }\r
+ break;\r
+\r
+ default:\r
+ break;\r
+ }\r
+ #endregion\r
+\r
+ this.e作成方法 = E作成方法.ファイルから;\r
+ this.strファイル名 = strファイル名;\r
+\r
+\r
+ // BASSファイルストリームを作成。\r
+\r
+ this._hBassStream = Bass.BASS_StreamCreateFile( strファイル名, 0, 0, flags );\r
+ if( this._hBassStream == 0 )\r
+ throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
+ \r
+ nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );\r
+ \r
+ tBASSサウンドを作成する・ストリーム生成後の共通処理( hMixer );\r
+ }\r
+ private void tBASSサウンドを作成する( byte[] byArrWAVファイルイメージ, int hMixer, BASSFlag flags )\r
+ {\r
+ this.e作成方法 = E作成方法.WAVファイルイメージから;\r
+ this.byArrWAVファイルイメージ = byArrWAVファイルイメージ;\r
+ this.hGC = GCHandle.Alloc( byArrWAVファイルイメージ, GCHandleType.Pinned ); // byte[] をピン留め\r
+\r
+\r
+ // BASSファイルストリームを作成。\r
+\r
+ this._hBassStream = Bass.BASS_StreamCreateFile( hGC.AddrOfPinnedObject(), 0, byArrWAVファイルイメージ.Length, flags );\r
+ if ( this._hBassStream == 0 )\r
+ throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_StreamCreateFile)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
+\r
+ nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );\r
+ \r
+ tBASSサウンドを作成する・ストリーム生成後の共通処理( hMixer );\r
+ }\r
+ /// <summary>\r
+ /// Decode "RIFF chunked Vorbis" to "raw wave"\r
+ /// because BASE.DLL has two problems for RIFF chunked Vorbis;\r
+ /// 1. time seek is not fine 2. delay occurs (about 10ms)\r
+ /// </summary>\r
+ /// <param name="strファイル名">wave filename</param>\r
+ /// <param name="byArrWAVファイルイメージ">wav file image</param>\r
+ /// <returns></returns>\r
+ private bool tRIFFchunkedVorbisならDirectShowでDecodeする( string strファイル名, ref byte[] byArrWAVファイルイメージ )\r
+ {\r
+ bool bファイルにVorbisコンテナが含まれている = false;\r
+\r
+ #region [ ファイルがWAVかつ、Vorbisコンテナが含まれているかを調べ、それに該当するなら、DirectShowでデコードする。]\r
+ //-----------------\r
+ try\r
+ {\r
+ using ( var ws = new WaveStream( strファイル名 ) )\r
+ {\r
+ if ( ws.Format.FormatTag == ( WaveFormatTag ) 0x6770 || // Ogg Vorbis Mode 2+\r
+ ws.Format.FormatTag == ( WaveFormatTag ) 0x6771 ) // Ogg Vorbis Mode 3+\r
+ {\r
+ Trace.TraceInformation( Path.GetFileName( strファイル名 ) + ": RIFF chunked Vorbis. Decode to raw Wave first, to avoid BASS.DLL troubles" );\r
+ try\r
+ {\r
+ CDStoWAVFileImage.t変換( strファイル名, out byArrWAVファイルイメージ );\r
+ bファイルにVorbisコンテナが含まれている = true;\r
+ }\r
+ catch\r
+ {\r
+ Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : RIFF chunked Vorbisのデコードに失敗しました。" );\r
+ }\r
+ }\r
+ }\r
+ }\r
+ catch ( InvalidDataException )\r
+ {\r
+ // DirectShowのデコードに失敗したら、次はACMでのデコードを試すことになるため、ここではエラーログを出さない。\r
+ // Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : デコードに失敗しました。" );\r
+ }\r
+ catch ( Exception e )\r
+ {\r
+ Trace.TraceWarning( "Warning: " + Path.GetFileName( strファイル名 ) + " : 読み込みに失敗しました。" );\r
+ }\r
+ #endregion\r
+\r
+ return bファイルにVorbisコンテナが含まれている;\r
+ }\r
+ private void tBASSサウンドを作成するXA( string strファイル名, int hMixer, BASSFlag flags )\r
+ {\r
+ int nPCMデータの先頭インデックス;\r
+ CWin32.WAVEFORMATEX wfx;\r
+ int totalPCMSize;\r
+\r
+ tオンメモリ方式でデコードする( strファイル名, out this.byArrWAVファイルイメージ,\r
+ out nPCMデータの先頭インデックス, out totalPCMSize, out wfx, true );\r
+\r
+ nBytes = totalPCMSize;\r
+\r
+ this.e作成方法 = E作成方法.WAVファイルイメージから; //.ファイルから; // 再構築時はデコード後のイメージを流用する&Dispose時にhGCを解放する\r
+ this.strファイル名 = strファイル名;\r
+ this.hGC = GCHandle.Alloc( this.byArrWAVファイルイメージ, GCHandleType.Pinned ); // byte[] をピン留め\r
+\r
+ //_cbStreamXA = new STREAMPROC( CallbackPlayingXA );\r
+\r
+ // BASSファイルストリームを作成。\r
+\r
+ //this.hBassStream = Bass.BASS_StreamCreate( xa.xaheader.nSamplesPerSec, xa.xaheader.nChannels, BASSFlag.BASS_STREAM_DECODE, _myStreamCreate, IntPtr.Zero );\r
+ //this._hBassStream = Bass.BASS_StreamCreate( (int) wfx.nSamplesPerSec, (int) wfx.nChannels, BASSFlag.BASS_STREAM_DECODE, _cbStreamXA, IntPtr.Zero );\r
+\r
+ // StreamCreate()で作成したstreamはseek不可のため、StreamCreateFile()を使う。\r
+ this._hBassStream = Bass.BASS_StreamCreateFile( this.hGC.AddrOfPinnedObject(), 0L, totalPCMSize, flags );\r
+ if ( this._hBassStream == 0 )\r
+ {\r
+ hGC.Free();\r
+ throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_SampleCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
}\r
+\r
+ nBytes = Bass.BASS_ChannelGetLength( this._hBassStream );\r
+\r
+\r
+ tBASSサウンドを作成する・ストリーム生成後の共通処理( hMixer );\r
}\r
- private void tデコーダの現在の読み出し位置から1バッファ分のPCMを指定されたバッファに書き込む( SecondarySoundBuffer buffer, int n書込先バッファ番号, bool bPCMの末尾に達したら先頭に戻る )\r
+\r
+\r
+ private void tBASSサウンドを作成する・ストリーム生成後の共通処理( int hMixer )\r
{\r
- if( !this.bDispose完了済み && ( this.nHandle >= 0 ) )\r
+ CSound管理.nStreams++;\r
+\r
+ // 個々のストリームの出力をテンポ変更のストリームに入力する。テンポ変更ストリームの出力を、Mixerに出力する。\r
+\r
+ _hTempoStream = 0;\r
+ if ( CSound管理.bIsTimeStretch ) // TimeStretchのON/OFFに関わりなく、テンポ変更のストリームを生成する。後からON/OFF切り替え可能とするため。\r
{\r
- this.Decode( this.nHandle, Marshal.UnsafeAddrOfPinnedArrayElement( this.by中継バッファ, 0 ), (uint) this.by中継バッファ.Length, bPCMの末尾に達したら先頭に戻る ? 1 : 0 );\r
- this.n現在のPCM側の位置byte += this.by中継バッファ.Length;\r
- if( bPCMの末尾に達したら先頭に戻る )\r
+ this._hTempoStream = BassFx.BASS_FX_TempoCreate( this._hBassStream, BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_FX_FREESOURCE );\r
+ if ( this._hTempoStream == 0 )\r
{\r
- this.n現在のPCM側の位置byte = (int) ( this.n現在のPCM側の位置byte % this.GetTotalPCMSize( this.nHandle ) );\r
+ hGC.Free();\r
+ throw new Exception( string.Format( "サウンドストリームの生成に失敗しました。(BASS_FX_TempoCreate)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
}\r
- this.Buffer.Write( this.by中継バッファ, n書込先バッファ番号 * this.by中継バッファ.Length, LockFlags.None );\r
+ else\r
+ {\r
+ Bass.BASS_ChannelSetAttribute( this._hTempoStream, BASSAttribute.BASS_ATTRIB_TEMPO_OPTION_USE_QUICKALGO, 1f ); // 高速化(音の品質は少し落ちる)\r
+ }\r
+ }\r
+\r
+ if ( _hTempoStream != 0 && !this.bIs1倍速再生 ) // 再生速度がx1.000のときは、TempoStreamを用いないようにして高速化する\r
+ {\r
+ this.hBassStream = _hTempoStream;\r
+ }\r
+ else\r
+ {\r
+ this.hBassStream = _hBassStream;\r
+ }\r
+\r
+ // #32248 再生終了時に発火するcallbackを登録する (演奏終了後に再生終了するチップを非同期的にミキサーから削除するため。)\r
+ _cbEndofStream = new SYNCPROC( CallbackEndofStream );\r
+ Bass.BASS_ChannelSetSync( hBassStream, BASSSync.BASS_SYNC_END | BASSSync.BASS_SYNC_MIXTIME, 0, _cbEndofStream, IntPtr.Zero );\r
+\r
+ // インスタンスリストに登録。\r
+\r
+ CSound.listインスタンス.Add( this );\r
+\r
+ // n総演奏時間の取得; DTXMania用に追加。\r
+ double seconds = Bass.BASS_ChannelBytes2Seconds( this._hBassStream, nBytes );\r
+ this.n総演奏時間ms = (int) ( seconds * 1000 );\r
+ //this.pos = 0;\r
+ this.hMixer = hMixer;\r
+ float freq = 0.0f;\r
+ if ( !Bass.BASS_ChannelGetAttribute( this._hBassStream, BASSAttribute.BASS_ATTRIB_FREQ, ref freq ) )\r
+ {\r
+ hGC.Free();\r
+ throw new Exception( string.Format( "サウンドストリームの周波数取得に失敗しました。(BASS_ChannelGetAttribute)[{0}]", Bass.BASS_ErrorGetCode().ToString() ) );\r
}\r
+ this.nオリジナルの周波数 = (int) freq;\r
}\r
//-----------------\r
+\r
+ //private int pos = 0;\r
+ //private int CallbackPlayingXA( int handle, IntPtr buffer, int length, IntPtr user )\r
+ //{\r
+ // int bytesread = ( pos + length > Convert.ToInt32( nBytes ) ) ? Convert.ToInt32( nBytes ) - pos : length;\r
+\r
+ // Marshal.Copy( byArrWAVファイルイメージ, pos, buffer, bytesread );\r
+ // pos += bytesread;\r
+ // if ( pos >= nBytes )\r
+ // {\r
+ // // set indicator flag\r
+ // bytesread |= (int) BASSStreamProc.BASS_STREAMPROC_END;\r
+ // }\r
+ // return bytesread;\r
+ //}\r
+ /// <summary>\r
+ /// ストリームの終端まで再生したときに呼び出されるコールバック\r
+ /// </summary>\r
+ /// <param name="handle"></param>\r
+ /// <param name="channel"></param>\r
+ /// <param name="data"></param>\r
+ /// <param name="user"></param>\r
+ private void CallbackEndofStream( int handle, int channel, int data, IntPtr user ) // #32248 2013.10.14 yyagi\r
+ {\r
+// Trace.TraceInformation( "Callback!(remove): " + Path.GetFileName( this.strファイル名 ) );\r
+ if ( b演奏終了後も再生が続くチップである ) // 演奏終了後に再生終了するチップ音のミキサー削除は、再生終了のコールバックに引っ掛けて、自前で行う。\r
+ { // そうでないものは、ミキサー削除予定時刻に削除する。\r
+ tBASSサウンドをミキサーから削除する( channel );\r
+ }\r
+ }\r
+\r
+// mixerからの削除\r
+\r
+ public bool tBASSサウンドをミキサーから削除する()\r
+ {\r
+ return tBASSサウンドをミキサーから削除する( this.hBassStream );\r
+ }\r
+ public bool tBASSサウンドをミキサーから削除する( int channel )\r
+ {\r
+ bool b = BassMix.BASS_Mixer_ChannelRemove( channel );\r
+ if ( b )\r
+ {\r
+ Interlocked.Decrement( ref CSound管理.nMixing );\r
+// Debug.WriteLine( "Removed: " + Path.GetFileName( this.strファイル名 ) + " (" + channel + ")" + " MixedStreams=" + CSound管理.nMixing );\r
+ }\r
+ return b;\r
+ }\r
+\r
+\r
+// mixer への追加\r
+ \r
+ public bool tBASSサウンドをミキサーに追加する()\r
+ {\r
+ if ( BassMix.BASS_Mixer_ChannelGetMixer( hBassStream ) == 0 )\r
+ {\r
+ BASSFlag bf = BASSFlag.BASS_SPEAKER_FRONT | BASSFlag.BASS_MIXER_NORAMPIN | BASSFlag.BASS_MIXER_PAUSE;\r
+ Interlocked.Increment( ref CSound管理.nMixing );\r
+\r
+ // preloadされることを期待して、敢えてflagからはBASS_MIXER_PAUSEを外してAddChannelした上で、すぐにPAUSEする\r
+ // -> ChannelUpdateでprebufferできることが分かったため、BASS_MIXER_PAUSEを使用することにした\r
+\r
+ bool b1 = BassMix.BASS_Mixer_StreamAddChannel( this.hMixer, this.hBassStream, bf );\r
+ //bool b2 = BassMix.BASS_Mixer_ChannelPause( this.hBassStream );\r
+ t再生位置を先頭に戻す(); // StreamAddChannelの後で再生位置を戻さないとダメ。逆だと再生位置が変わらない。\r
+//Trace.TraceInformation( "Add Mixer: " + Path.GetFileName( this.strファイル名 ) + " (" + hBassStream + ")" + " MixedStreams=" + CSound管理.nMixing );\r
+ Bass.BASS_ChannelUpdate( this.hBassStream, 0 ); // pre-buffer\r
+ return b1; // &b2;\r
+ }\r
+ return true;\r
+ }\r
+\r
+ #region [ tオンメモリ方式でデコードする() ]\r
+ public void tオンメモリ方式でデコードする( string strファイル名, out byte[] buffer,\r
+ out int nPCMデータの先頭インデックス, out int totalPCMSize, out CWin32.WAVEFORMATEX wfx,\r
+ bool bIntegrateWaveHeader )\r
+ {\r
+ nPCMデータの先頭インデックス = 0;\r
+ //int nPCMサイズbyte = (int) ( xa.xaheader.nSamples * xa.xaheader.nChannels * 2 ); // nBytes = Bass.BASS_ChannelGetLength( this.hBassStream );\r
+\r
+ SoundDecoder sounddecoder;\r
+\r
+ if ( String.Compare( Path.GetExtension( strファイル名 ), ".xa", true ) == 0 )\r
+ {\r
+ sounddecoder = new Cxa();\r
+ }\r
+ else if ( String.Compare( Path.GetExtension( strファイル名 ), ".ogg", true ) == 0 )\r
+ {\r
+ sounddecoder = new Cogg();\r
+ }\r
+ else if ( String.Compare( Path.GetExtension( strファイル名 ), ".mp3", true ) == 0 )\r
+ {\r
+ sounddecoder = new Cmp3();\r
+ }\r
+ else\r
+ {\r
+ throw new NotImplementedException();\r
+ }\r
+\r
+ if ( !File.Exists( strファイル名 ) )\r
+ {\r
+ throw new Exception( string.Format( "ファイルが見つかりませんでした。({0})", strファイル名 ) );\r
+ }\r
+ int nHandle = sounddecoder.Open( strファイル名 );\r
+ if ( nHandle < 0 )\r
+ {\r
+ throw new Exception( string.Format( "Open() に失敗しました。({0})({1})", nHandle, strファイル名 ) );\r
+ }\r
+ wfx = new CWin32.WAVEFORMATEX();\r
+ if ( sounddecoder.GetFormat( nHandle, ref wfx ) < 0 )\r
+ {\r
+ sounddecoder.Close( nHandle );\r
+ throw new Exception( string.Format( "GetFormat() に失敗しました。({0})", strファイル名 ) );\r
+ }\r
+ //totalPCMSize = (int) sounddecoder.nTotalPCMSize; // tデコード後のサイズを調べる()で既に取得済みの値を流用する。ms単位の高速化だが、チップ音がたくさんあると塵積で結構効果がある\r
+ totalPCMSize = (int) sounddecoder.GetTotalPCMSize( nHandle );\r
+ if ( totalPCMSize == 0 )\r
+ {\r
+ sounddecoder.Close( nHandle );\r
+ throw new Exception( string.Format( "GetTotalPCMSize() に失敗しました。({0})", strファイル名 ) );\r
+ }\r
+ totalPCMSize += ( ( totalPCMSize % 2 ) != 0 ) ? 1 : 0;\r
+ int wavheadersize = ( bIntegrateWaveHeader ) ? 44 : 0;\r
+ byte[] buffer_rawdata = new byte[ totalPCMSize ];\r
+ buffer = new byte[ wavheadersize + totalPCMSize ];\r
+ GCHandle handle = GCHandle.Alloc( buffer_rawdata, GCHandleType.Pinned );\r
+ try\r
+ {\r
+ if ( sounddecoder.Decode( nHandle, handle.AddrOfPinnedObject(), (uint) totalPCMSize, 0 ) < 0 )\r
+ {\r
+ buffer = null;\r
+ throw new Exception( string.Format( "デコードに失敗しました。({0})", strファイル名 ) );\r
+ }\r
+ if ( bIntegrateWaveHeader )\r
+ {\r
+ // wave headerを書き込む\r
+\r
+ int wfx拡張領域_Length = 0;\r
+ var ms = new MemoryStream();\r
+ var bw = new BinaryWriter( ms );\r
+ bw.Write( new byte[] { 0x52, 0x49, 0x46, 0x46 } ); // 'RIFF'\r
+ bw.Write( (UInt32) totalPCMSize + 44 - 8 ); // ファイルサイズ - 8 [byte];今は不明なので後で上書きする。\r
+ bw.Write( new byte[] { 0x57, 0x41, 0x56, 0x45 } ); // 'WAVE'\r
+ bw.Write( new byte[] { 0x66, 0x6D, 0x74, 0x20 } ); // 'fmt '\r
+ bw.Write( (UInt32) ( 16 + ( ( wfx拡張領域_Length > 0 ) ? ( 2/*sizeof(WAVEFORMATEX.cbSize)*/ + wfx拡張領域_Length ) : 0 ) ) ); // fmtチャンクのサイズ[byte]\r
+ bw.Write( (UInt16) wfx.wFormatTag ); // フォーマットID(リニアPCMなら1)\r
+ bw.Write( (UInt16) wfx.nChannels ); // チャンネル数\r
+ bw.Write( (UInt32) wfx.nSamplesPerSec ); // サンプリングレート\r
+ bw.Write( (UInt32) wfx.nAvgBytesPerSec ); // データ速度\r
+ bw.Write( (UInt16) wfx.nBlockAlign ); // ブロックサイズ\r
+ bw.Write( (UInt16) wfx.wBitsPerSample ); // サンプルあたりのビット数\r
+ //if ( wfx拡張領域_Length > 0 )\r
+ //{\r
+ // bw.Write( (UInt16) wfx拡張領域.Length ); // 拡張領域のサイズ[byte]\r
+ // bw.Write( wfx拡張領域 ); // 拡張データ\r
+ //}\r
+ bw.Write( new byte[] { 0x64, 0x61, 0x74, 0x61 } ); // 'data'\r
+ //int nDATAチャンクサイズ位置 = (int) ms.Position;\r
+ bw.Write( (UInt32) totalPCMSize ); // dataチャンクのサイズ[byte]\r
+\r
+ byte[] bs = ms.ToArray();\r
+\r
+ bw.Close();\r
+ ms.Close();\r
+\r
+ for ( int i = 0; i < bs.Length; i++ )\r
+ {\r
+ buffer[ i ] = bs[ i ];\r
+ }\r
+ }\r
+ int s = ( bIntegrateWaveHeader ) ? 44 : 0;\r
+ for ( int i = 0; i < totalPCMSize; i++ )\r
+ {\r
+ buffer[ i + s ] = buffer_rawdata[ i ];\r
+ }\r
+ totalPCMSize += wavheadersize;\r
+ nPCMデータの先頭インデックス = wavheadersize;\r
+ }\r
+ finally\r
+ {\r
+ handle.Free();\r
+ sounddecoder.Close( nHandle );\r
+ sounddecoder = null;\r
+ }\r
+ }\r
+ #endregion\r
#endregion\r
}\r
-}
\ No newline at end of file
+}\r