探してもあんまり見つからなかったので。
手段
NAudioのWaveOutはWaveFileReader
を経由してファイルを読み込みながら再生できる。
WaveFileReader
に偽装してやれば好きなデータをWaveOutに食わせられる。
ソース
WaveStream
を継承してテキトーにNotImplementedException
の部分を穴埋めしただけ。
class DummyWaveStream : WaveStream
{
private const int samplingRate = 44100;
private Int64 pos = 0;
public override Int64 Length
{
get
{
return (Int64.MaxValue);
}
}
public override Int64 Position
{
get
{
return (pos);
}
set
{
pos = value;
}
}
public override WaveFormat WaveFormat
{
get
{
return (new WaveFormat(samplingRate, 16, 2));
}
}
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
{
if (offset != 0 || buffer.Length != count) { throw new Exception(); }
const double freq = 500;
const int bytePerSample = 4;
for (int i = 0; i < count; i += bytePerSample)
{
double sin = Math.Sin(freq * (pos + i) / samplingRate * Math.PI * 2 / bytePerSample);
short wav = (short)(sin * 30000);
buffer[i + 1] = (byte)(wav >> 8);
buffer[i + 0] = (byte)wav;
}
pos += count;
return (count);
}
}
DummyWaveStream
をWaveOut
に与えると、500Hzの正弦波を再生する。
このサンプルでは左側しか音声を与えていないので、右のスピーカーは無音になる。
Read
の中でイベントを蹴るなりdelegateを呼ぶなりして外部で音声を作れば、任意の波形を再生できる。
Posを真面目に扱うと面倒なので、setはNotImplementedException
にしてしまうと楽かも。
その他
デバイス一覧
void updateAudioDeviceList()
{
audioDeviceComboBox.Items.Clear();
audioDeviceComboBox.Items.AddRange(
Enumerable.Range(0, WaveOut.DeviceCount)
.Select(i => i + " " + WaveOut.GetCapabilities(i).ProductName)
.ToArray());
if (audioDeviceComboBox.Items.Count > 0)
{
audioDeviceComboBox.SelectedIndex = 0;
}
}
みたいな感じでデバイス番号とデバイス名を列挙できる。
WaveOutでデバイスを指定する際にデバイス番号が必要になる。必ずしも必要ではないが、名前に併記したほうが楽。
デバイス名は変なところで切られてしまう。NAudio内部処理の問題な気がする。長い名前が全部必要なら別の手段を講じる必要がある。
Play/Stop
private WaveOut waveOut = null;
private DummyWaveStream waveSrc = null;
private void playCheckBox_Click(Object sender, EventArgs e)
{
if (waveOut == null)
{
string deviceIndexAndNameText = audioDeviceComboBox.SelectedItem as string;
if (!string.IsNullOrEmpty(deviceIndexAndNameText))
{
int devIndex = int.Parse(deviceIndexAndNameText.Split(' ')[0]);
waveSrc = new DummyWaveStream();
waveOut = new WaveOut();
waveOut.DeviceNumber = devIndex;
waveOut.Init(waveSrc);
waveOut.Play();
}
}
else
{
waveOut.Stop();
waveOut.Dispose();
waveOut = null;
waveSrc.Dispose();
waveSrc = null;
}
playCheckBox.Checked = waveOut != null;
audioDeviceComboBox.Enabled = waveOut == null;
}
CheckBoxのAutoCheck
はfalse
にしておく。
waveOutがnullならPlay、そうでなければStop、という動作。
Playの場合は、デバイス名の最初の文字列を取り出してint.Parse
に与えてデバイス番号を得る。
waveOut.Init()
にDummyWaveStream
のインスタンスを与える。
StopはStopしてDisposeしてnullを入れるだけ。
再生時間
データ数が$2^{63} - 1$バイト、1サンプルあたり4バイト、44.1kHzなので、160万年くらいの長さ。まぁ、たぶん、大抵の用途では足りるだろう。
特殊な用途だと1サンプル64バイト/2MHzみたいなデータもあるけど、それでも2000年程度は再生できるし、その用途だとNAudioを使ったりはしないだろうから問題ないはず。
追記
Length
, Position
はNAudio周りからはアクセスされないっぽい。NotImplementedException
のままでも動いた(WaveFormat
は実装する必要がある)。
また、Readの戻り値はチェックされないらしく、少ないサンプル数を返した場合は正常に動作しない。