忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

C#で.WAVファイルを作る

今回は音を保存する.WAVファイルをC#で作ります。このブログではこれまで音を鳴らすプログラムは書いてきましたが、それをファイルに書き込んだりはしていませんでした。今回は音を鳴らしはしませんが、出来たファイルをダブルクリックすれば他のいろんな音楽ソフトで再生できますし、メールで送ったりサイトに載せたりして他の人に聞かせることも出来るわけです(これまでは音を鳴らすプログラムを書き、それをわざわざ別のソフトで録音してブログにのせていました)。


ソースコード

WaveFile.cs

using System.Text;
using System.IO;
using System.Runtime.InteropServices;

public enum WaveFormatType : ushort
{
    Pcm = 1,
}

abstract class Chunk
{
    public int ID { get; private set; }
    public abstract int DataSizeInBytes { get; }
    public int SizeInBytes => DataSizeInBytes + 8;

    public Chunk(string idText)
    {
        this.ID = ToInt32ID(idText);
    }

    protected int ToInt32ID(string idText)
    {
        return System.BitConverter.ToInt32(Encoding.ASCII.GetBytes(idText), 0);
    }

    public void Write(BinaryWriter writer)
    {
        writer.Write(ID);
        writer.Write(DataSizeInBytes);
        WriteData(writer);
    }

    protected abstract void WriteData(BinaryWriter writer);
}

class WaveFileChunk<SampleType> : Chunk
    where SampleType : struct
{
    public int Format => ToInt32ID("WAVE");
    public WaveFormatChunk<SampleType> WaveFormat = new WaveFormatChunk<SampleType>();
    public WaveDataChunk<SampleType> WaveData = new WaveDataChunk<SampleType>();

    public override int DataSizeInBytes => WaveFormat.SizeInBytes + WaveData.SizeInBytes + 4;

    public WaveFileChunk() : base("RIFF")
    {
    }

    protected override void WriteData(BinaryWriter writer)
    {
        writer.Write(Format);
        WaveFormat.Write(writer);
        WaveData.Write(writer);
    }
}

class WaveFormatChunk<SampleType> : Chunk
    where SampleType : struct
{
    public override int DataSizeInBytes => 16;

    public WaveFormatType WaveFormatType;
    public short ChannelCount;
    public int SampleFrequency;

    public int BytesPerSecond
        => SampleFrequency * DataBlockSizeInBytes;

    public short DataBlockSizeInBytes
        => (short)(SampleSizeInBits / 8 * ChannelCount);

    public short SampleSizeInBits
    {
        get
        {
            return (short)(Marshal.SizeOf(typeof(SampleType)) * 8);
        }
    }

    public WaveFormatChunk() : base("fmt ")
    {
    }

    protected override void WriteData(BinaryWriter writer)
    {
        writer.Write((ushort)WaveFormatType);
        writer.Write(ChannelCount);
        writer.Write(SampleFrequency);
        writer.Write(BytesPerSecond);
        writer.Write(DataBlockSizeInBytes);
        writer.Write(SampleSizeInBits);
    }
}

class WaveDataChunk<SampleType> : Chunk
     where SampleType : struct
{
    public override int DataSizeInBytes => Data.Length * sampleSizeInBytes;
    public SampleType[] Data;

    public WaveDataChunk() : base("data")
    {
    }

    private int sampleSizeInBytes => Marshal.SizeOf(typeof(SampleType));

    protected override void WriteData(BinaryWriter writer)
    {
        var sampleSizeInBytes = this.sampleSizeInBytes;

        foreach (var value in Data)
        {
            for (var i = 0; i < sampleSizeInBytes; i++)
            {
                writer.Write(Marshal.ReadByte(value, i));
            }
        }
    }
}


Program.cs

using System;
using System.Collections.Generic;
using System.IO;

class Program
{
    static void Main()
    {
        var waveFileData = createWaveFileData();

        using (var stream = new FileStream("myWave.wav", FileMode.Create))
        {
            var writer = new BinaryWriter(stream);
            waveFileData.Write(writer);
        }
    }

    private static WaveFileChunk<byte> createWaveFileData()
    {
        uint sampleFrequency = 44100;
        var result = new WaveFileChunk<byte>();
        result.WaveFormat.WaveFormatType = WaveFormatType.Pcm;
        result.WaveFormat.ChannelCount = 1;
        result.WaveFormat.SampleFrequency = 44100;
        result.WaveData.Data = createStereoWaveData(sampleFrequency);
        return result;
    }

    private static byte[] createStereoWaveData(uint sampleFrequency)
    {
        double waveFrequency = 261.6256;
        double totalSeconds = 2;

        var resultBuffer = new List<byte>();
        int totalSampleCount = (int)(sampleFrequency * totalSeconds);

        for (int i = 0; i < totalSampleCount; i++)
        {
            double time = (double)i / sampleFrequency;
            double value = Math.Sin(2 * Math.PI * waveFrequency * time);
            resultBuffer.Add((byte)(255 * (value * 0.5 + 0.5)));
        }

        return resultBuffer.ToArray();
    }
}

結果

再生ボタンを押してみてください(音量に注意!)。ドの音(1秒間に約262回振動するsin波)が2秒再生されます。これはこのプログラムで作成した.wavファイルです。残念ながらC#には.wavファイルを作るための専用のクラスは用意されていないようなので、こうして手作業で作るしかないのです。

拍手[1回]

PR