ゲームに効果音、BGMは欠かせませんよね。Unityだと確かsoundオブジェクトか何かで再生するわけですが、ファイルの読み込みもしなければならず結構めんどい処理なのです。
C#標準でも音を鳴らす奴はあったと思うのですが、せっかくなのでXaudioを使いましょう。低レベルなのでいいらしいですよ。
ちなみに原理は全く抑えていません。こうやったら使える程度に思ってください。
エフェクトをかけれるという噂もありますが、私は全く活用していません。
準備
Vortice.XAudio2をnugetでインストールしましょう
Vorticeのgithub
仕組み
Xaudio2はエンジンにあたるIXaudio2というオブジェクトを起動させ、IXaudio2専属の再生するためのIXAudio2MasteringVoiceに音をぶち込むことで音を再生します。IXAudio2MasteringVoiceにぶち込むための音はIXaudio2SourceVoiceというクラスです。つまりファイルから音を再生するためには.wavのファイルなどからIXaudio2SourceVoiceに変換する必要があります。
エンジンのセット
IXaudio2、IXAudio2MasteringVoiceを作りましょう。なんてことはありません。
static IXAudio2 audio;
static IXAudio2MasteringVoice MV;
static public void reset()
{
audio = Vortice.XAudio2.XAudio2.XAudio2Create();
MV = audio.CreateMasteringVoice();
}
static public float glovol { get { return _glovol; } set { _glovol = value; if (_glovol < 0) _glovol = 0; if (_glovol > 1) _glovol = 1; MV.SetVolume(glovol); } }
//これで全体の音量を設定できます。別で変数を用意すれば効果音とBGMの音量の設定を変えるとかそういう奴もできます。
これからいろいろ作るわけですがこのようにaudioから作ることが多々あります。
wavファイルの再生
かなりめんどくさい手続きが必要です。これもどこから持ってきたのかわからないぐらいのキメラなので正しい書き方はわかりません。とりあえず動きます。
static void loadoto(string file)
{
if (File.Exists(@".\oto\" + file + @".wav") == false) return;
var reader = new BinaryReader(File.OpenRead(@".\oto\" + file + @".wav"));
// waveファイルに該当するかどうかファイルの先頭を読んで確かめるらしい
var chunkId = new string(reader.ReadChars(4));
var chunkSize = reader.ReadInt32();
var format = new string(reader.ReadChars(4));
var subChunkId = new string(reader.ReadChars(4));
var subChunkSize = reader.ReadInt32();
var audioFormat = (WaveFormatEncoding)reader.ReadInt16();
var numChannels = reader.ReadInt16();
var sampleRate = reader.ReadInt32();
var bytesPerSecond = reader.ReadInt32();
var blockAlign = reader.ReadInt16();
var bitsPerSample = reader.ReadInt16();
var dataChunkId = new string(reader.ReadChars(4));
var dataSize = reader.ReadInt32();
if (chunkId != "RIFF" || format != "WAVE" || subChunkId.Trim() != "fmt" || audioFormat != WaveFormatEncoding.Pcm || bitsPerSample != 16 || dataChunkId != "data")
{
Console.WriteLine(chunkId + format + subChunkId.Trim() + (audioFormat != WaveFormatEncoding.Pcm) + bitsPerSample + dataChunkId + " otoloadsippai");
return;
}
//フォーマットを作る
var formattt = new WaveFormat(sampleRate, 16, numChannels);
//ソースヴォイスを作る
IXAudio2SourceVoice SV = audio.CreateSourceVoice(formattt);
//データをファイルから読み取る
var waveData = reader.ReadBytes(dataSize);
//ソースヴォイスのためのAudiobufferを作る
int size = Marshal.SizeOf(waveData[0]) * waveData.Length;
IntPtr WDintPtr = Marshal.AllocHGlobal(size);
Marshal.Copy(waveData, 0, WDintPtr, size);
var buffer = new AudioBuffer();
buffer.Flags = BufferFlags.EndOfStream;
buffer.AudioBytes = dataSize;
buffer.AudioDataPointer = WDintPtr;
buffer.LoopCount = 1;//255にしたらずっと繰り返すらしい
//バッファーを入れ込んで完成!
SV.SubmitSourceBuffer(buffer);
SV.SetVolume(1);
SV.Start();
reader.Close();//ファイルリーダーは閉じとく
reader.Dispose();
}
これで再生はできます。
しかし一つ問題があります。これだと音を鳴らすたびにロードをしなくてはならず、SourceVoiceはDispose()しなくてはならないのでなんだかメモリを食ってしまいそうな気がします。
使いまわす!
音を重ねて再生できるようにし、使いまわせるようにするために次のクラスを作りました
public class otoman
{
public IXAudio2SourceVoice sorce;
public WaveFormat wvf;
public AudioBuffer buf;
//この三つさえあれば音を使いまわせるみたい
public otoman(IXAudio2SourceVoice s,WaveFormat wf,AudioBuffer ab)
{
sorce = s;
wvf = wf;
buf = ab;
}
public void dispo()
{
if (!sorce.IsDisposed)
{
buf.Dispose();
sorce.Dispose();
}
}
}
これを使ってこんな風にすれば同じ音を再生できます
//Dictionary<string,otoman>か何かにコピー元が保存されてるんだよ
var SV = audio.CreateSourceVoice(otoman.wvf);
SV.SubmitSourceBuffer(otoman.buf);
oton.Add(new otoman(SV, otoman.wvf, otoman.buf));//後で追跡して解放するためにList<otoman>かなにかに保存しとく
SV.SetVolume(1);
SV.start();
再生を止めてメモリを開放したい場合は
otoman.sorce.Stop();
otoman.dispo();
これでロードは最初の一回だけで同じ音をいくつでも重ねて再生できますね。メモリの開放基準ですが一定時間経過後とかはめんどくさいのでListの長さが一定以上になったら数を減らすとすればまあまあいいと思います。
ちなみにWavファイルの編集、作成にはAudacityでいいと思います。BGMのmp3をwavに変換するときにオンライン変換みたいなのを使ったら再生できなかったので。