2015年8月22日土曜日

C#でWaveファイルを作る

C#でWAVEファイルを作りたくなったのでメモ。

ヘッダ部分はCの構造体みたいな感じにスマートに作れるっぽいけど、とりあえず今回は1個1個ちまちま書き込んでる。
Visual Studio 2015のC#6からプロパティの初期化を行えるようになってちょっと便利になった(コンストラクタ作れって話だが)。

このプログラムはFormアプリケーションとして動作するが、起動時にデスクトップに"test.wav"というファイルを作ったらすぐに終了するようになっている。コンストラクタ内でCloseすると例外が発生するためやってはいけない。こういう「フォームなんて要らねぇよ」という場合はLoadイベントに匿名メソッドを追加し、その中でCloseするとヨサゲ。

作成するWAVEファイルは、44.1kHz16bit2chの標準的な感じの物。長さは10秒で、ch1/ch2共に1336Hzと941Hzを加算した正弦波を作る。今のところ、自動生成に対応しているのはヘッダのみ。実際の音声データは自前でバイナリレベルを扱う必要がある。でもまぁクラスに配列を渡すと最大4GiB近くを確保しようとしてOutOfMemoryExceptionが発生しちゃうから仕方ないね!!
BinaryWriterにちまちま書き込んでいけば4GB(4*10^9byte)の波形データでも扱える(書きだすのに非常に時間が掛かるが)。大容量のデータをR/Wする場合はすべてをバッファに読み出すことは不可能なので、その辺りをうまくゴニョゴニョする必要がある。

このプログラムで作ったWAVEをSoundEngine Freeで開いてFFTするとこんな感じ。





using System;
using System.IO;
using System.Text;
using System.Windows.Forms;

namespace SMSPtest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            string FileName = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + @"\test.wav";

            using (FileStream filStream = new FileStream(FileName, FileMode.Create, FileAccess.Write))
            using (BinaryWriter binWriter = new BinaryWriter(filStream))
            {
                WaveHdr Hdr = new WaveHdr();

                UInt32 DataLength = Hdr.SamplingRate * 10;

                Hdr.NumberOfBytesOfWaveData = Hdr.BlockSize * DataLength;

                binWriter.Write(Hdr.Bytes);

                for (UInt32 cnt = 0; cnt < DataLength; cnt++)
                {
                    double Radian = (double)cnt / Hdr.SamplingRate;
                    Radian *= 2 * Math.PI;

                    double Wave = 0;

                    Wave += Math.Sin(Radian * 1336);
                    Wave += Math.Sin(Radian * 941);

                    Wave /= 2;

                    Int16 Data = (Int16)(Wave * 30000);

                    binWriter.Write(BitConverter.GetBytes(Data));
                    binWriter.Write(BitConverter.GetBytes(Data));
                }
            }

            Load += delegate (object sender, EventArgs e) { Close(); }; /* すぐにプログラムを終了させる */
        }

        private class WaveHdr
        {
            public UInt16 NumberOfChannel { get; set; } = 2;
            public UInt32 SamplingRate { get; set; } = 44100;
            public UInt16 NumberOfBitPerSample { get; set; } = 16;
            public UInt32 NumberOfBytesOfWaveData { get; set; } = 0;

            public UInt16 NumberOfBytePerSample
            {
                get
                {
                    return ((UInt16)(Math.Ceiling((double)NumberOfBitPerSample / 8)));
                }
            }

            public UInt32 FileSize
            {
                get
                {
                    return (NumberOfBytesOfWaveData + 44);
                }
            }

            public UInt32 DataRate
            {
                get
                {
                    return (SamplingRate * NumberOfChannel * NumberOfBytePerSample);
                }
            }

            public UInt16 BlockSize
            {
                get
                {
                    return (UInt16)(NumberOfBytePerSample * NumberOfChannel);
                }
            }

            public byte[] Bytes
            {
                get
                {
                    byte[] Datas = new byte[44];

                    Array.Copy(Encoding.ASCII.GetBytes("RIFF"), 0, Datas, 0, 4);
                    Array.Copy(BitConverter.GetBytes((UInt32)(FileSize - 8)), 0, Datas, 4, 4);
                    Array.Copy(Encoding.ASCII.GetBytes("WAVE"), 0, Datas, 8, 4);
                    Array.Copy(Encoding.ASCII.GetBytes("fmt "), 0, Datas, 12, 4);
                    Array.Copy(BitConverter.GetBytes((UInt32)(16)), 0, Datas, 16, 4);
                    Array.Copy(BitConverter.GetBytes((UInt16)(1)), 0, Datas, 20, 2);
                    Array.Copy(BitConverter.GetBytes((UInt16)(NumberOfChannel)), 0, Datas, 22, 2);
                    Array.Copy(BitConverter.GetBytes((UInt32)(SamplingRate)), 0, Datas, 24, 4);
                    Array.Copy(BitConverter.GetBytes((UInt32)(DataRate)), 0, Datas, 28, 4);
                    Array.Copy(BitConverter.GetBytes((UInt16)(BlockSize)), 0, Datas, 32, 2);
                    Array.Copy(BitConverter.GetBytes((UInt16)(NumberOfBitPerSample)), 0, Datas, 34, 2);
                    Array.Copy(Encoding.ASCII.GetBytes("data"), 0, Datas, 36, 4);
                    Array.Copy(BitConverter.GetBytes((UInt32)(NumberOfBytesOfWaveData)), 0, Datas, 40, 4);

                    return (Datas);
                }
            }
        }
    }
}

0 件のコメント:

コメントを投稿