この記事はゲームエンジン・ライブラリ・ツールの開発 Advent Calendar 2016 13日目の記事です
<<16日目 | Tonyu System 2: キャラクターの「動き」に注目した、ブラウザゲーム開発環境 || 18日目 | エフェクト制作ツール「Effekseer」について + 他色々 >>
記事の投稿が大幅に遅れ申し訳ありません。特に、@Reputeless 氏には数回に渡るTwitterでの催促を頂き、申し訳ありません。
一応言い訳しておくと、DxLibの実装が想像をはるかに上回る汚さで調査がはかどらなかったのと、
説明に関数呼び出しツリーがほしかったのですが、Visual Studioのそれは子Nodeまでコピーできない代物で
LLVM IRをoptコマンドでdotファイルに変換してそれを更にパースしてjstreeで読めるjsonにするプログラムを書いていたのですが、
これまたC++の構文の難しさに時間を取られました。
うわああ、24日目と私の13日目が並んどる!遅れてごめんなさい(n回目)!@Reputeless
— yumetodo-C++erだけど化学科 (@yumetodo) 2016年12月24日
そしてこの記事を読んだ方、最後のループ+gotoのあわせ技は何をやっているのか教えてください! pic.twitter.com/VgwNMAWoTq
私は現在DxLibExという、DxLibを現代C++的に操作できるようにラップするライブラリを制作しています。
Nagarei/DxLibEx: DXライブラリC++化プロジェクト
DxLibは日本では有名なゲームライブラリで、C/C++の入門教材としてもよく用いられます。
DXライブラリ置き場 HOME
これ自体はDirectX 9/11やogg、opus、zlib、Win32APIをラップしたものになっています。
一応C++だけどほとんどCという感じのライブラリです。
いろいろ小回りは効くのですが、実装がちょっと人間を卒業していることで知られています(要出典)
さて、DxLibにはLoadSoundMem と LoadBGMという似た関数があります。これはどう違うのか。
DxLibExでラップする作業中に気になったので、調べてみました。
公式サイトから落とした3.16f
LOADSOUND_GPARAMまず引数経由でフラグを渡すための構造体定義が存在します。
// ファイルからサウンドハンドルを作成する処理に必要なグローバルデータを纏めたもの
struct LOADSOUND_GPARAM
{
int NotInitSoundMemDelete ; // InitSoundMem で削除しないかどうかのフラグ( TRUE:InitSoundMemでは削除しない FALSE:InitSoundMemで削除する )
int Create3DSoundFlag ; // 3Dサウンドを作成するかどうかのフラグ( TRUE:3Dサウンドを作成する FALSE:3Dサウンドを作成しない )
int CreateSoundDataType ; // 作成するサウンドデータのデータタイプ
int CreateSoundPitchRateEnable ; // 作成するサウンドデータのピッチレートが有効かどうか
float CreateSoundPitchRate ; // 作成するサウンドデータのピッチレート
int CreateSoundTimeStretchRateEnable ; // 作成するサウンドデータのタイムストレッチレートが有効かどうか
float CreateSoundTimeStretchRate ; // 作成するサウンドデータのタイムストレッチレート
int CreateSoundLoopStartTimePosition ; // 作成するサウンドデータのループ範囲の先端( ミリ秒 )
int CreateSoundLoopStartSamplePosition ; // 作成するサウンドデータのループ範囲の先端( サンプル )
int CreateSoundLoopEndTimePosition ; // 作成するサウンドデータのループ範囲の終端( ミリ秒 )
int CreateSoundLoopEndSamplePosition ; // 作成するサウンドデータのループ範囲の終端( サンプル )
int DisableReadSoundFunctionMask ; // 使用しない読み込み処理のマスク
#ifndef DX_NON_OGGVORBIS
int OggVorbisBitDepth ; // OggVorbis使用時のビット深度(1:8bit 2:16bit)
int OggVorbisFromTheoraFile ; // Ogg Theora ファイル中の Vorbis データを参照するかどうかのフラグ( TRUE:Theora ファイル中の Vorbis データを参照する )
#endif
} ;
SOUNDSYSTEMDATADxLib内部でのフラフの保管につかうグローバル変数として
// サウンドシステムデータ SOUNDSYSTEMDATA SoundSysData ;
があります。SOUNDSYSTEMDATAは
// サウンドシステム用データ構造体
struct SOUNDSYSTEMDATA
{
int InitializeFlag ; // 初期化フラグ
DWORD OutputChannels ; // 出力チャンネル数
DWORD OutputSmaplesPerSec ; // 出力サンプリングレート
int _3DSoundOneMetreEnable ; // _3DSoundOneMetre が有効かどうか( TRUE:有効 FALSE:無効 )
float _3DSoundOneMetre ; // 3Dサウンド処理用の1メートル
_3DSOUNDINFO ListenerInfo ; // 3Dサウンドのリスナーの情報
VECTOR ListenerSideDirection ; // 3Dサウンドのリスナーの横方向
HANDLELIST _3DSoundListFirst ; // 3Dサウンドハンドルリストの先頭
HANDLELIST _3DSoundListLast ; // 3Dサウンドハンドルリストの終端
DX_CRITICAL_SECTION _3DSoundListCriticalSection ; // 3Dサウンドリストアクセス時用クリティカルセクション
HANDLELIST StreamSoundListFirst ; // ストリームサウンドハンドルリストの先頭
HANDLELIST StreamSoundListLast ; // ストリームサウンドハンドルリストの終端
DX_CRITICAL_SECTION StreamSoundListCriticalSection ;// ストリームサウンドハンドルリストアクセス時用クリティカルセクション
HANDLELIST SoftSoundPlayerListFirst ; // ソフトウエアで扱う波形データのプレイヤーのハンドルリストの先頭
HANDLELIST SoftSoundPlayerListLast ; // ソフトウエアで扱う波形データのプレイヤーのハンドルリストの終端
HANDLELIST PlayFinishDeleteSoundListFirst ; // 再生終了時に削除するサウンドハンドルリストの先頭
HANDLELIST PlayFinishDeleteSoundListLast ; // 再生終了時に削除するサウンドハンドルリストの終端
HANDLELIST Play3DSoundListFirst ; // 再生している3Dサウンドリストの先頭
HANDLELIST Play3DSoundListLast ; // 再生している3Dサウンドリストの終端
DX_CRITICAL_SECTION Play3DSoundListCriticalSection ;// 再生している3Dサウンドリストアクセス時用クリティカルセクション
int PlayWavSoundHandle ; // PlayWav関数で鳴らされているWAVEデータのハンドル
int Create3DSoundFlag ; // 3Dサウンドを作成するかどうかのフラグ( TRUE:3Dサウンドを作成する FALSE:3Dサウンドを作成しない )
int OldVolumeTypeFlag ; // Ver3.10c以前の音量計算式を使用するかどうかのフラグ( TRUE:古い計算式を使用する FALSE:新しい計算式を使用する )
int SoundMode ; // 再生形式
int MaxVolume ; // 最大音量
int EnableSoundCaptureFlag ; // サウンドキャプチャを前提とした動作をする
#ifndef DX_NON_SAVEFUNCTION
int SoundCaptureFlag ; // サウンドキャプチャを実行している最中かどうかのフラグ(TRUE:最中 FASLE:違う)
int SoundCaptureSample ; // キャプチャしたサンプルの数(44.1KHz換算)
DWORD_PTR SoundCaptureFileHandle ; // キャプチャしたサウンドを保存しているファイル
#endif // DX_NON_SAVEFUNCTION
int CreateSoundDataType ; // 作成するサウンドデータのデータタイプ
int CreateSoundPitchRateEnable ; // 作成するサウンドデータのピッチレートが有効かどうか
float CreateSoundPitchRate ; // 作成するサウンドデータのピッチレート
float CreateSoundPitchRate_Cents ; // 作成するサウンドデータのピッチレート( セント単位 )
int CreateSoundTimeStretchRateEnable ; // 作成するサウンドデータのタイムストレッチレートが有効かどうか
float CreateSoundTimeStretchRate ; // 作成するサウンドデータのタイムストレッチレート
int CreateSoundLoopStartTimePosition ; // 作成するサウンドデータのループ範囲の先端( ミリ秒 )
int CreateSoundLoopStartSamplePosition ; // 作成するサウンドデータのループ範囲の先端( サンプル )
int CreateSoundLoopEndTimePosition ; // 作成するサウンドデータのループ範囲の終端( ミリ秒 )
int CreateSoundLoopEndSamplePosition ; // 作成するサウンドデータのループ範囲の終端( サンプル )
int DisableReadSoundFunctionMask ; // 使用しない読み込み処理のマスク
#ifndef DX_NON_OGGVORBIS
int OggVorbisBitDepth ; // OggVorbis使用時のビット深度(1:8bit 2:16bit)
int OggVorbisFromTheoraFile ; // Ogg Theora ファイル中の Vorbis データを参照するかどうかのフラグ( TRUE:Theora ファイル中の Vorbis データを参照する )
#endif
#ifndef DX_NON_BEEP
int BeepFrequency ; // 再生するBEEP音の周波数
int BeepPlay ; // BEEPを再生中かどうか
int BeepSoundBufferUseIndex ; // 使用するBEEP音再生用のサウンドバッファ
SOUND_BEEP_BUFFERDATA BeepSoundBuffer[ SOUND_BEEPSOUNDBUFFER_NUM ] ; // BEEP音用のサウンドバッファ関連情報
#endif
short SinTable[ SOUND_SINTABLE_DIV ] ; // サインテーブル
SOUNDSYSTEMDATA_PF PF ; // 環境依存情報
} ;
のように定義されています。
で大事なのはCreateSoundDataTypeです。これが取りうる値は
の3つです。まあDX_SOUNDDATATYPE_MEMNOPRESS_PLUSというのもあるんですが、
後述するNS_SetCreateSoundDataType関数内にDX_SOUNDDATATYPE_MEMNOPRESSに強制置換するコードと現在使えないというコメントがありまして・・・
DX_SOUNDDATATYPE_MEMPRESSはストリーム形式と呼んでいるようです。
SOUNDこれとは別に、サウンドハンドルと紐付けられた情報を格納するための構造体が存在します。
// サウンドデータ
struct SOUND
{
HANDLEINFO HandleInfo ; // ハンドル共通情報
HANDLELIST _3DSoundList ; // 3Dサウンドリスト処理用構造体
int Is3DSound ; // 3Dサウンドかどうか( TRUE:3Dサウンド FALSE:非3Dサウンド )
int AddPlay3DSoundList ; // Play3DSoundList がリストに追加されているかどうか( TRUE:追加されている FALSE:追加されていない )
HANDLELIST Play3DSoundList ; // 再生中の3Dサウンドのリスト処理用構造体
int PlayFinishDeleteFlag ; // サウンドの再生が終了したら削除するかどうか( TRUE:削除する FALSE:削除しない )
HANDLELIST PlayFinishDeleteSoundList ; // サウンドの再生が終了したら削除するサウンドのリスト処理用構造体
int NotInitSoundMemDelete ; // InitSoundMem で削除しないかどうかのフラグ( TRUE:InitSoundMemでは削除しない FALSE:InitSoundMemで削除する )
// char FilePath[ 256 ] ; // ディレクトリ
// char SoundName[ 256 ] ; // サウンドファイルネーム
int ValidBufferNum ; // 有効なバッファの数
int BufferPlayStateBackupFlagValid[ MAX_SOUNDBUFFER_NUM ] ; // BufferPlayStateBackupFlag が有効かどうかのフラグ( TRUE:有効 FALSE:無効 )
int BufferPlayStateBackupFlag[ MAX_SOUNDBUFFER_NUM ] ; // サウンドバッファを一時停止するときの再生状態フラグ
SOUNDBUFFER Buffer[ MAX_SOUNDBUFFER_NUM ] ; // サウンドバッファ
WAVEFORMATEX BufferFormat ; // サウンドバッファのフォーマット
int Type ; // サウンドのタイプ
int PlayType ; // 再生タイプ
STREAMPLAYDATA Stream ; // ストリーム風サウンドプレイ用データ
NORMALPLAYDATA Normal ; // ノーマルサウンドプレイ用データ
int PresetReverbParam ; // 3Dサウンド時に設定するプリセットリバーブパラメータ番号( DX_REVERB_PRESET_DEFAULT 等 )
SOUND3D_REVERB_PARAM ReverbParam ; // 3Dサウンド時に設定するリバーブパラメータ( PresetReverbParam が -1 の際に使用 )
int BaseVolume[ SOUNDBUFFER_MAX_CHANNEL_NUM ] ; // 基本ボリューム( -1:デフォルト )
int BasePan ; // 基本パン( -1:デフォルト )
int BaseFrequency ; // 基本再生周波数( -1:デフォルト )
VECTOR Base3DPosition ; // 基本再生位置
float Base3DRadius ; // 基本聞こえる距離
VECTOR Base3DVelocity ; // 基本音の速度
int PitchRateEnable ; // ピッチレートが有効化どうか( TRUE:有効 FALSE:無効 )
float PitchRate ; // ピッチレート
BYTE ValidNextPlayVolume[ SOUNDBUFFER_MAX_CHANNEL_NUM ] ; // NextPlayVolume が有効かどうか( 1:有効 0:無効 )
BYTE ValidNextPlayPan ; // NextPlayPan が有効かどうか( 1:有効 0:無効 )
BYTE ValidNextPlayFrequency ; // NextPlayFrequency が有効かどうか( 1:有効 0:無効 )
BYTE ValidNextPlay3DPosition ; // NextPlay3DPosition が有効かどうか( 1:有効 0:無効 )
BYTE ValidNextPlay3DRadius ; // NextPlay3DRadius が有効かどうか( 1:有効 0:無効 )
BYTE ValidNextPlay3DVelocity ; // NextPlay3DVelocity が有効かどうか( 1:有効 0:無効 )
int NextPlayVolume[ SOUNDBUFFER_MAX_CHANNEL_NUM ] ; // 次に鳴らす音のボリューム
int NextPlayPan ; // 次に鳴らす音のパン
int NextPlayFrequency ; // 次に鳴らす音の周波数
VECTOR NextPlay3DPosition ; // 次に鳴らす音の位置
float NextPlay3DRadius ; // 次に鳴らす音の聞こえる距離
VECTOR NextPlay3DVelocity ; // 次に鳴らす音の移動速度
} ;
SOUNDTYPEで、大事なのがTypeです。これに入る値は、
DX_SOUNDTYPE_NORMALDX_SOUNDTYPE_STREAMSTYLEとなります。
DxLib_Init()InitializeSoundSystem()皆さんDxLibを使うときはこのDxLib_Init関数を呼んでいることと思いますが、まずはここから行きましょう。
DxLib_Initの定義は./Windows/DxSystemWin.cppにあります
// ライブラリ初期化関数
extern int NS_DxLib_Init( void )
{
// 既に初期化済みの場合は何もせず終了
if( DxSysData.DxLib_InitializeFlag == TRUE ) return 0 ;
DXST_ERRORLOGFMT_ADDUTF16LE(( "\x24\xff\x38\xff\xe9\x30\xa4\x30\xd6\x30\xe9\x30\xea\x30\x6e\x30\x1d\x52\x1f\x67\x16\x53\xe6\x51\x06\x74\x8b\x95\xcb\x59\x00"/*@ L"DXライブラリの初期化処理開始" @*/ )) ;
DXST_ERRORLOG_TABADD ;
// 初期化中フラグを立てる
DxSysData.DxLib_RunInitializeFlag = TRUE ;
でこの230行もある関数の途中に
if( DxSysData.NotSoundFlag == FALSE )
{
#ifndef DX_NON_SOUND
InitializeSoundConvert() ; // サウンド変換処理の初期化
InitializeSoundSystem() ; // サウンドシステムのの初期化
#endif // DX_NON_SOUND
}
InitializeSoundSystemという関数があると思います。この関数の定義はDxSound.cppにあります。
// サウンドシステムを初期化する
extern int InitializeSoundSystem( void )
{
if( SoundSysData.InitializeFlag )
{
return -1 ;
}
// 出力レートをセット
if( SoundSysData.OutputSmaplesPerSec == 0 )
{
SoundSysData.OutputSmaplesPerSec = 44100 ;
}
// 3Dサウンドの1メートルを設定
if( SoundSysData._3DSoundOneMetreEnable == FALSE )
{
SoundSysData._3DSoundOneMetre = 1.0f ;
}
// サウンドハンドル管理情報初期化
InitializeHandleManage( DX_HANDLETYPE_SOUND, sizeof( SOUND ), MAX_SOUND_NUM, InitializeSoundHandle, TerminateSoundHandle, L"Sound" ) ;
// ソフトウエアで扱う波形データハンドル管理情報初期化
InitializeHandleManage( DX_HANDLETYPE_SOFTSOUND, sizeof( SOFTSOUND ), MAX_SOFTSOUND_NUM, InitializeSoftSoundHandle, TerminateSoftSoundHandle, L"SoftSound" ) ;
// MIDIハンドル管理情報初期化
InitializeHandleManage( DX_HANDLETYPE_MUSIC, sizeof( MIDIHANDLEDATA ), MAX_MUSIC_NUM, InitializeMidiHandle, TerminateMidiHandle, L"Music" ) ;
// クリティカルセクションの初期化
CriticalSection_Initialize( &SoundSysData._3DSoundListCriticalSection ) ;
CriticalSection_Initialize( &SoundSysData.Play3DSoundListCriticalSection ) ;
CriticalSection_Initialize( &SoundSysData.StreamSoundListCriticalSection ) ;
// ハンドルリストを初期化
InitializeHandleList( &SoundSysData._3DSoundListFirst, &SoundSysData._3DSoundListLast ) ;
InitializeHandleList( &SoundSysData.StreamSoundListFirst, &SoundSysData.StreamSoundListLast ) ;
InitializeHandleList( &SoundSysData.SoftSoundPlayerListFirst, &SoundSysData.SoftSoundPlayerListLast ) ;
InitializeHandleList( &SoundSysData.PlayFinishDeleteSoundListFirst, &SoundSysData.PlayFinishDeleteSoundListLast ) ;
InitializeHandleList( &SoundSysData.Play3DSoundListFirst, &SoundSysData.Play3DSoundListLast ) ;
// サインテーブルの初期化
{
int i ;
float Sin ;
float Cos ;
for( i = 0 ; i < SOUND_SINTABLE_DIV ; i ++ )
{
_SINCOS( i * DX_PI_F * 2 / SOUND_SINTABLE_DIV, &Sin, &Cos ) ;
SoundSysData.SinTable[ i ] = ( short )_FTOL( Sin * 16384.0f ) ;
}
}
// 環境依存処理
if( InitializeSoundSystem_PF_Timing0() < 0 )
{
return -1 ;
}
// 作成する音のデータタイプをセット
SoundSysData.CreateSoundDataType = DX_SOUNDDATATYPE_MEMNOPRESS ;
// 作成する音のピッチレートをセット
SoundSysData.CreateSoundPitchRateEnable = FALSE ;
SoundSysData.CreateSoundPitchRate = 1.0f ;
// 作成する音のタイムストレッチレートをセット
SoundSysData.CreateSoundTimeStretchRateEnable = FALSE ;
SoundSysData.CreateSoundTimeStretchRate = 1.0f ;
#ifndef DX_NON_OGGVORBIS
// OggVorbisのPCMデコード時の、ビット深度を16bitにセット
SoundSysData.OggVorbisBitDepth = 2 ;
#endif // DX_NON_OGGVORBIS
// 3Dサウンド処理用のリスナー情報を初期化
SoundSysData.ListenerInfo.Position = VGet( 0.0f, 0.0f, 0.0f ) ;
SoundSysData.ListenerInfo.FrontDirection = VGet( 0.0f, 0.0f, 1.0f ) ;
SoundSysData.ListenerInfo.Velocity = VGet( 0.0f, 0.0f, 0.0f ) ;
SoundSysData.ListenerInfo.InnerAngle = DX_PI_F * 5.0f / 6.0f ;
SoundSysData.ListenerInfo.OuterAngle = DX_PI_F * 11.0f / 6.0f ;
SoundSysData.ListenerInfo.InnerVolume = 1.0f ;
SoundSysData.ListenerInfo.OuterVolume = 0.75f ;
SoundSysData.ListenerSideDirection = VGet( 1.0f, 0.0f, 0.0f ) ;
// 初期化フラグを立てる
SoundSysData.InitializeFlag = TRUE ;
NS_InitSoundMem() ;
NS_InitSoftSound() ;
NS_InitSoftSoundPlayer() ;
// 環境依存処理
if( InitializeSoundSystem_PF_Timing1() < 0 )
{
return -1 ;
}
#ifndef DX_NON_BEEP
// BEEP音の初期化
if( BeepSound_Initialize() < 0 )
{
return -1 ;
}
#endif //DX_NON_BEEP
// 終了
return 0 ;
}
つまりデフォルトではSoundSysData.CreateSoundDataTypeはDX_SOUNDDATATYPE_MEMNOPRESSだということがわかりました。
NS_SetCreateSoundDataType()このあとの解説に必要なので見ておきます。
// 作成するサウンドのデータ形式を設定する
extern int NS_SetCreateSoundDataType( int SoundDataType )
{
// 値が範囲外のデータ形式かどうか調べる
if( SoundDataType >= DX_SOUNDDATATYPE_MEMNOPRESS && SoundDataType <= DX_SOUNDDATATYPE_FILE )
{
// 現在 DX_SOUNDDATATYPE_MEMNOPRESS_PLUS は非対応
if( SoundDataType == DX_SOUNDDATATYPE_MEMNOPRESS_PLUS )
{
SoundSysData.CreateSoundDataType = DX_SOUNDDATATYPE_MEMNOPRESS ;
}
else
{
SoundSysData.CreateSoundDataType = SoundDataType ;
}
}
else
{
return -1 ;
}
// 終了
return 0 ;
}
基本的には先程見たSoundSysData.CreateSoundDataTypeにフラグをセットする関数です。
DX_SOUNDDATATYPE_MEMNOPRESS_PLUSは、コメントによれば、メモリーに読み込んだ後再生中に展開して、一度展開し終わるとメモリーに読み込んだ圧縮データを破棄するモードのようですが、ご覧のように無効化されています。
LoadBGM()LoadBGM_WCHAR_T()// 主にBGMを読み込むのに適した関数
extern int LoadBGM_WCHAR_T( const wchar_t *WaveName )
{
int Type = SoundSysData.CreateSoundDataType, SoundHandle ;
if( _WCSICMP( WaveName + _WCSLEN( WaveName ) - 3, L"wav" ) == 0 )
{
NS_SetCreateSoundDataType( DX_SOUNDDATATYPE_FILE ) ;
}
else
{
NS_SetCreateSoundDataType( DX_SOUNDDATATYPE_MEMPRESS ) ;
}
SoundHandle = LoadSoundMem_WCHAR_T( WaveName, 1 ) ;
NS_SetCreateSoundDataType( Type ) ;
return SoundHandle ;
}
WAVファイルの場合はDX_SOUNDDATATYPE_FILEに、そうではない場合はDX_SOUNDDATATYPE_MEMPRESSをセットしています。
AddStreamSoundMem_UseGParam()では音楽データの読み込み部分を見てみましょう。すべての読み込み関数は内部でAddStreamSoundMem_UseGParam()関数を呼び出します。
// AddStreamSoundMem のグローバル変数にアクセスしないバージョン
extern int AddStreamSoundMem_UseGParam(
LOADSOUND_GPARAM *GParam,
STREAMDATA *Stream,
int LoopNum,
int SoundHandle,
int StreamDataType,
int *CanStreamCloseFlag,
int UnionHandle,
int ASyncThread
)
{
第5引数に先程まで見てきたフラグが渡されます。フラグの意味については先に述べましたが、そのとおりになっていることを確認しましょう。
// 新しいWAVEファイルのロード、データのタイプによって処理を分岐
switch( StreamDataType )
{
まずはswitch文に渡されます。
DX_SOUNDDATATYPE_MEMNOPRESSの場合// 丸々メモリに読み込む if( StreamFullRead( Stream, &SrcBuffer, &SrcSize ) < 0 )
まずはファイルからメモリー上に読み込みます
res = SoundConvertFast( &ConvData, &Format, &PlayData->FileImage, &PlayData->FileImageSize ) ;
次にこれをWAVE形式に変換・展開していきます。ただし無圧縮PCMの場合はSoundConvertFast関数内部で純粋なコピーが行われます。
// WAVEファイルをでっち上げる if( CreateWaveFileImage( &WaveImage, &WaveSize, &Format, sizeof( WAVEFORMATEX ), PlayData->FileImage, PlayData->FileImageSize ) < 0 ) goto ERR ; // 展開されたデータをストリームとして再度開く PlayData->MemStream.DataPoint = MemStreamOpen( WaveImage, ( size_t )WaveSize ) ; PlayData->MemStream.ReadShred = *GetMemStreamDataShredStruct() ; SetupSoundConvert( &PlayData->ConvData, &PlayData->MemStream, GParam->DisableReadSoundFunctionMask #ifndef DX_NON_OGGVORBIS ,GParam->OggVorbisBitDepth, GParam->OggVorbisFromTheoraFile #endif ) ; // メモリアドレスの入れ替え DXFREE( PlayData->FileImage ) ; PlayData->FileImage = WaveImage ; PlayData->FileImageSize = WaveSize ;
最後に変換したデータをサウンドハンドルに紐付けられた領域に登録します。
つまり、
圧縮された全データは再生が始まる前にサウンドメモリにすべて展開され、格納される。
のとおり実装されていることが確認できました。
DX_SOUNDDATATYPE_MEMPRESSの場合// 丸々メモリに読み込む if( StreamFullRead( Stream, &PlayData->FileImage, &PlayData->FileImageSize ) < 0 )
まずはファイルからメモリー上に読み込みます
// 展開されたデータをストリームとして再度開く PlayData->MemStream.DataPoint = MemStreamOpen( PlayData->FileImage, ( size_t )PlayData->FileImageSize ) ; PlayData->MemStream.ReadShred = *GetMemStreamDataShredStruct() ; if( SetupSoundConvert( &PlayData->ConvData, &PlayData->MemStream, GParam->DisableReadSoundFunctionMask #ifndef DX_NON_OGGVORBIS ,GParam->OggVorbisBitDepth, GParam->OggVorbisFromTheoraFile #endif ) < 0 )
読み込んだデータをサウンドハンドルに紐付けられた領域に登録します。
この段階では
圧縮された全データはシステムメモリに格納され、再生する部分だけ逐次展開しながらサウンドメモリに格納する(鳴らし終わると展開したデータは破棄されるので何度も展開処理が行われる)
は確かめられていません。
DX_SOUNDDATATYPE_FILEの場合if( SetupSoundConvert( &PlayData->ConvData, Stream, GParam->DisableReadSoundFunctionMask #ifndef DX_NON_OGGVORBIS ,GParam->OggVorbisBitDepth, GParam->OggVorbisFromTheoraFile #endif ) < 0 )
ファイルストリームをサウンドハンドルに紐付けられた領域に登録します。
この段階では
圧縮されたデータの再生する部分だけファイルから逐次読み込み展開され、サウンドメモリに格納される(鳴らし終わると展開したデータは破棄されるので何度も展開処理が行われる)
は確かめられていません。
このswitch文のあと、サウンドバッファの作成処理があります。
// サウンドバッファを共有するかどうかで処理を分岐
if( UniSound == NULL )
{
DWORD BufferSec ;
// サウンドバッファを作成
BufferSec = StreamDataType == DX_SOUNDDATATYPE_FILE ? STS_BUFSEC_FILE : STS_BUFSEC ;
CreateSoundBuffer(
&Sound->BufferFormat,
SOUNDSIZE( BufferSec * Sound->BufferFormat.nAvgBytesPerSec / STS_DIVNUM, Sound->BufferFormat.nBlockAlign ),
DX_SOUNDTYPE_STREAMSTYLE, 1, SoundHandle, -1, ASyncThread
) ;
Sound->Stream.SoundBufferSize = SOUNDSIZE( BufferSec * Sound->BufferFormat.nAvgBytesPerSec / STS_DIVNUM, Sound->BufferFormat.nBlockAlign ) ;
Sound->BaseFrequency = ( int )Sound->BufferFormat.nSamplesPerSec ;
// Sound->PitchRateEnable = GParam->CreateSoundPitchRateEnable ;
// Sound->PitchRate = GParam->CreateSoundPitchRate ;
// 共有情報をセット
Sound->Stream.BufferBorrowSoundHandle = -1 ;
}
PlaySoundMem()SoundBuffer_Play_PF()サウンド再生に使うPlaySoundMem()は環境によって定義場所が違うSoundBuffer_Play_PF()に処理が飛びます。
extern int SoundBuffer_Play_PF( SOUNDBUFFER *Buffer, int Loop )
{
if( SoundSysData.PF.EnableXAudioFlag )
{
Buffer->State = TRUE ;
if( SoundSysData.PF.XAudio2_8DLL != NULL )
{
Buffer->PF.XA2_8SourceVoice->Start( 0 ) ;
if( Buffer->PF.XA2_8SubmixVoice )
{
Buffer->PF.XA2_8SubmixVoice->EnableEffect( 0 ) ;
}
}
else
{
Buffer->PF.XA2SourceVoice->Start( 0 ) ;
if( Buffer->PF.XA2SubmixVoice )
{
Buffer->PF.XA2SubmixVoice->EnableEffect( 0 ) ;
}
}
Buffer->StopTimeState = 1 ;
Buffer->StopTime = 0 ;
}
else
{
if( Buffer->PF.DSBuffer->Play( 0, 0, ( DWORD )( Loop ? D_DSBPLAY_LOOPING : 0 ) ) != D_DS_OK )
{
return -1 ;
}
}
return 0 ;
}
2512,2520行目でXAudio、2531でDirectSoundの関数をラップしているクラスのメンバ関数を呼び出しているのがわかると思います。
ここで2509行目を見ると、
Buffer->State = TRUE ;
という文が見つかります。このBuffer->Stateは一体何に使われるのでしょうか。
SoundBuffer_CycleProcess_PF()extern int SoundBuffer_CycleProcess_PF( SOUNDBUFFER *Buffer )
{
if( SoundSysData.PF.EnableXAudioFlag )
{
int NowCount ;
int Time ;
if( Buffer->PF.XA2SubmixVoice == NULL )
{
return -1 ;
}
if( Buffer->StopTimeState == 0 )
{
return -1 ;
}
if( Buffer->State == TRUE )
{
return 0 ;
}
SoundBuffer_CycleProcess_PFという関数内で見られています。trueのとき、つまり再生中のとき0を返すんですね。
ProcessPlay3DSoundMemAll()SoundBuffer_CycleProcess_PF関数はSoundBuffer_CycleProcess関数を介して呼ばれていますが、ProcessPlay3DSoundMemAll関数から呼ばれます。
// 3Dサウンドを再生しているサウンドハンドルに対する処理を行う
extern int ProcessPlay3DSoundMemAll( void )
{
HANDLELIST *List ;
SOUND *Sound ;
int i ;
int Valid ;
if( CheckSoundSystem_Initialize_PF() == FALSE )
{
return -1 ;
}
// クリティカルセクションの取得
CRITICALSECTION_LOCK( &SoundSysData.Play3DSoundListCriticalSection ) ;
LOOPSTART:
for( List = SoundSysData.Play3DSoundListFirst.Next ; List->Next != NULL ; List = List->Next )
{
Sound = ( SOUND * )List->Data ;
Valid = FALSE ;
for( i = 0 ; i < Sound->ValidBufferNum ; i ++ )
{
if( Sound->Buffer[ i ].Valid == 0 )
{
continue ;
}
if( SoundBuffer_CycleProcess( &Sound->Buffer[ i ] ) == 0 )
{
Valid = TRUE ;
}
}
if( Valid == FALSE )
{
Sound->AddPlay3DSoundList = FALSE ;
SubHandleList( &Sound->Play3DSoundList ) ;
goto LOOPSTART ;
}
}
// クリティカルセクションの解放
CriticalSection_Unlock( &SoundSysData.Play3DSoundListCriticalSection ) ;
// 終了
return 0 ;
}
SoundBuffer_CycleProcessが0を返すとき、つまり再生中のとき、Validはtrueになり、650行目のif文の処理が実行されます。
SubHandleList関数が呼ばれ、Listの要素が削除され、その後、なんとまさかのgoto文でループをやり直します。
ところでSoundSysData.Play3DSoundListFirst.Nextというリストの要素のメンバーであるDataの中にあるPlay3DSoundListを操作しているわけですが、これは一体何でしょうか?その2つのリストは同じものなのでしょうか?
また、なんでループをやり直すのでしょうか?
このあと実装を追っていたらさらなる闇に突っ込んでしまったので、そのうちまた記事を書きます。
とりあえず、ワケガワカラナイヨ!な実装でした。
最後のループ+goto+謎の2つのリスト問題、なにをやっているのかわかる方、@yumetodoまでお願いします!
友人の@173210とコードを追ったのですが追いきれませんでしたorz
AddhandleList()関数やSubHandleList()関数、その他関係するフラグの初期化が関係するとは思うのですが・・・
この記事はゲームエンジン・ライブラリ・ツールの開発 Advent Calendar 2016 13日目の記事です
<<16日目 | Tonyu System 2: キャラクターの「動き」に注目した、ブラウザゲーム開発環境 || 18日目 | エフェクト制作ツール「Effekseer」について + 他色々 >>