LoginSignup
0
0

More than 1 year has passed since last update.

C#から簡易的にWaveファイルを書き出す

Posted at

仕様

 シンプルな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;
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0