仕様
シンプルなWAVEファイル(任意のチャンネル数、任意のサンプリングレート、16bit)を書き出す。
データはIEnumerable<short>
で与える(配列を渡すとか、LINQを渡すとか)。
データが2^32バイト未満の場合はデータチャンクに大きさを記録する。
データが2^32バイト以上の場合はデータチャンクの大きさをゼロにする。これにより4GiB制限を超えた音声データを記録することができる。ただし正常なファイルではないので、例えばWindows Media Playerでは開くことができない。一方でEdgeのようなChromium系のブラウザでは開くことができ、もちろん、自作のプログラム等での取り扱いにも支障はない。(本来この用途にはRF64のような64bitファイルフォーマットを使うべきだが、今回は簡易的な用途を想定しているので「そこそこ動く」程度で妥協する)
処理速度は気にしない。
使用例
1ch
シングルトーン
var sps = 44.1e3;
var f = 0.8e3;
WriteWave16("log.wav",
Enumerable.Range(0, (int)sps)
.Select(i => (short)(Math.Sin(f * Math.PI * 2 / sps * i) * short.MaxValue)),
1, (int)sps);
デュアルトーン
var sps = 44.1e3;
var f1 = 697.0;
var f2 = 1209.0;
WriteWave16("log.wav",
Enumerable.Range(0, (int)sps)
.Select(i => (short)((
Math.Sin(f1 * Math.PI * 2 / sps * i) +
Math.Sin(f2 * Math.PI * 2 / sps * i)) * 0.5 * short.MaxValue)),
1, (int)sps);
チャープ信号
var sps = 44.1e3;
var f0 = 1e3;
var f1 = 2e3;
var T = 10.0;
var k = (f1 - f0) / T;
WriteWave16("log.wav",
Enumerable.Range(0, (int)(sps * T))
.Select(i => i / sps)
.Select(t => (short)(Math.Sin(2 * Math.PI * (f0 * t + k / 2 * t * t)) * short.MaxValue)),
1, (int)sps);
2ch
シングルトーン
var sps = 44.1e3;
var f1 = 697.0;
var f2 = 1209.0;
WriteWave16("log.wav",
Enumerable.Range(0, (int)sps * 2)
.Select(i => (short)((Math.Sin((i % 2 == 0 ? f1 : f2) * Math.PI * 2 / sps * (i / 2))) * short.MaxValue)),
2, (int)sps);
シングルトーン
var sps = 44.1e3;
var f1 = 697.0;
var f2 = 1209.0;
var tmp = new short[(int)sps][];
for (int i = 0; i < tmp.Length; i++)
{
tmp[i] = new[]
{
(short)(Math.Sin(f1 * Math.PI * 2 / sps * i) * short.MaxValue),
(short)(Math.Sin(f2 * Math.PI * 2 / sps * i) * short.MaxValue),
};
}
WriteWave16("log.wav", tmp.SelectMany(a => a), 2, (int)sps);
ソース
static void WriteWave16(string path, IEnumerable<short> src, int channel, int samplingrate)
=> WriteWave16(new FileStream(path, FileMode.Create, FileAccess.Write), src, channel, samplingrate);
static void WriteWave16(Stream stream, IEnumerable<short> src, int channel, int samplingrate, bool leaveOpen = false)
{
try
{
stream.Write(GenerateWaveHeader(channel, samplingrate, 16, channel * 2));
var start = stream.Position;
foreach (var a in src) { stream.Write(BitConverter.GetBytes(a)); }
var pos = stream.Position;
var size = pos - start;
if (size <= uint.MaxValue)
{
stream.Position = start - 4;
stream.Write(BitConverter.GetBytes((uint)size));
stream.Position = pos;
}
}
finally
{
if (!leaveOpen) { stream.Dispose(); }
}
}
static byte[] GenerateWaveHeader(int channels, int samplingrate, int bits, int blocksize = 4)
{
var tmp = new byte[44];
Encoding.ASCII.GetBytes("RIFF\0\0\0\0WAVEfmt ").CopyTo(tmp, 0);
Encoding.ASCII.GetBytes("data").CopyTo(tmp, 36);
BitConverter.GetBytes((uint)16).CopyTo(tmp, 16);
BitConverter.GetBytes((ushort)1).CopyTo(tmp, 20);
BitConverter.GetBytes((ushort)channels).CopyTo(tmp, 22);
BitConverter.GetBytes((uint)samplingrate).CopyTo(tmp, 24);
BitConverter.GetBytes((uint)(samplingrate * blocksize)).CopyTo(tmp, 28);
BitConverter.GetBytes((ushort)blocksize).CopyTo(tmp, 32);
BitConverter.GetBytes((ushort)bits).CopyTo(tmp, 34);
return tmp;
}