同時に1chしか出せないこと、たぶん生成しながら再生することができないこと、たぶんリングバッファ状に延々と再生することができないこと、といった制限がある。そのかわり簡単に再生できる。
手段としては、SoundPlayerにStreamを渡し、そのStreamは自分で作ったWAVEファイルになっている。あとはBackgroundWorkerで再生する。
SoundPlayerで非同期に再生すると、再生終了を知ることができないらしい。ということでBackgroundWorkerで同期的に再生し、PlaySyncメソッドから戻った時点でSoundPlayerとStreamをDisposeしている。
BackgroundWorkerは同時にひとつのスレッドしか持てないが、SoundPlayerも同時再生はできないらしいので特に問題ないはず。
今回は16bit1chの波形のみを扱った。あとサンプリングレートは44.1kHzに固定している。
波形を生成するときに10000の係数をかけている。本来は32767をかけるべきで、実際Streamをファイルに書き出すならそれで良いのだが、なぜかSoundPlayerでは数倍に増幅されて再生される。そうすると正弦波の上下がクリップされて矩形波状になり、高調波がかなりでる。この係数は環境依存かもしれない。
正弦波1つを再生するならConsole.Beepで任意周波数を任意時間再生できるが、もうちょっと複雑な音を出そうとするとConsole.Beepでは出せないはず。
ゲームでBGMを鳴らしながらSEも鳴らして、という用途には向かないだろうが、まぁそこまでする予定はないのでこれでいいのだ。リングバッファ的なモノで再生しようとするとかなり面倒らしいんだよねぇ。
// RunWorkerAsyncの引数に再生するWAVEファイルのストリームを指定する。 // 再生が終了した時点でストリームをDisposeする。 private readonly BackgroundWorker SoundPlayerBackgroundWorker; public Form1() { InitializeComponent(); SoundPlayerBackgroundWorker = new BackgroundWorker(); SoundPlayerBackgroundWorker.DoWork += SoundPlayerBackgroundWorker_DoWork; } private void button1_Click(Object sender, EventArgs e) { playSinwave(500, 1); } private void button2_Click(Object sender, EventArgs e) { playSinwave(800, 0.5); } private void playSinwave(double waveFreq, double seconds) { Int32 amp = 10000; if (!SoundPlayerBackgroundWorker.IsBusy) { const UInt32 samplingRate = 44100; Int16[] wave = new Int16[(int)(samplingRate * seconds)]; for (int i = 0; i < wave.Length; i++) { double phase = (double)i / samplingRate * waveFreq * Math.PI * 2; double sin = Math.Sin(phase); wave[i] = (Int16)(sin * amp); } SoundPlayerBackgroundWorker.RunWorkerAsync(generateWaveData(wave, samplingRate)); } } private void SoundPlayerBackgroundWorker_DoWork(Object sender, DoWorkEventArgs e) { Stream ms = (Stream)e.Argument; using (SoundPlayer sp = new SoundPlayer(ms)) { sp.PlaySync(); } ms.Dispose(); } private MemoryStream generateWaveData(Int16[] data, UInt32 samplingRate) { MemoryStream ms = new MemoryStream(); ms.Write(Encoding.ASCII.GetBytes("RIFF"), 0, 4); // RIFF hdr ms.Write(BitConverter.GetBytes((UInt32)data.Length * 2 + 44 - 8), 0, 4); // file size - 8 ms.Write(Encoding.ASCII.GetBytes("WAVE"), 0, 4); // RIFF type is "WAVE" ms.Write(Encoding.ASCII.GetBytes("fmt "), 0, 4); // "fmt " chunk ms.Write(BitConverter.GetBytes((UInt32)16), 0, 4); // fmt chunk size ms.Write(BitConverter.GetBytes((UInt16)1), 0, 2); // format ID ms.Write(BitConverter.GetBytes((UInt16)1), 0, 2); // number of channels ms.Write(BitConverter.GetBytes((UInt32)samplingRate), 0, 4); // sampling rate ms.Write(BitConverter.GetBytes((UInt32)samplingRate * 2), 0, 4); // data speed: bytes per sec ms.Write(BitConverter.GetBytes((UInt16)2), 0, 2); // block size: bytes per 1 sample (channels * bit depth / 8) ms.Write(BitConverter.GetBytes((UInt16)16), 0, 2); // bit depth (bits) ms.Write(Encoding.ASCII.GetBytes("data"), 0, 4); // "data" chunk ms.Write(BitConverter.GetBytes((UInt32)data.Length * 2), 0, 4); // data chunk size foreach (Int16 hoge in data) { ms.Write(BitConverter.GetBytes(hoge), 0, 2); } ms.Position = 0; return (ms); }
0 件のコメント:
コメントを投稿