LoginSignup
2
0

More than 5 years have passed since last update.

C#でWaveの波形を読み込む

Posted at

機能

  • WAVE(16bit2chだけ実装済み)を読み込む
  • IEnumerableでChunkやDataの読み出しを実装(foreachで回せる)

Dataチャンクの読み込みは、内部で位置を記録してSeekしたりしてるので、Parallel.ForEachで並列処理できる。FFTとか時間かかるような処理をやる場合は並列化できる。
時間がかからない処理なら、並列化のボトルネックがあるので素直にforeach使うべき。

サンプル

こんな感じで使える。

using (WaveReader wr = new WaveReader(path))
{
    WaveReader.ChunkFormat fmt = null;

    foreach (WaveReader.ChunkBase chunk in wr.ReadChunks())
    {
        Console.WriteLine("* Chunk \"" + chunk.Type + "\" " + chunk.Size + " bytes");

        if (false) { }
        else if (chunk is WaveReader.ChunkFormat)
        {
            fmt = (WaveReader.ChunkFormat)chunk;

            Console.WriteLine("    " + fmt.Channels + " ch");
            Console.WriteLine("    " + (fmt.SamplingRate / 1e3) + " kSPS");
            Console.WriteLine("    " + fmt.BitPerSample + " bit");
        }
        else if (chunk is WaveReader.ChunkData)
        {
            foreach (Int16[,] wave in ((WaveReader.ChunkData)chunk).ReadDataS16x2((int)fmt.SamplingRate))
            {
                // wave: 波形データ
                // 何らかの処理
            }
        }
    }
}

制限

  • 一部のfmtしか対応してない
  • fmtの一部しか読み込んでない
  • S16/2chしか対応してない
  • やっつけ
  • エラー処理とかやってない
  • 素人なので変なコード書いてたらごめんね

非対応のChunkとかデータ形式とかを読み込みたい場合は各自でどうぞ。

ソース

/*
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
*/

class WaveReader : IDisposable
{
    private readonly FileStream fs;
    private readonly UInt32 fileSize;

    private long chunkPos;
    private long chunkSize;

    public WaveReader(string path)
    {
        fs = new FileStream(path, FileMode.Open, FileAccess.Read);

        if ("RIFF" != readStr(4, Encoding.ASCII))
        {
            throw (new Exception());
        }

        fileSize = read32u();

        if ("WAVE" != readStr(4, Encoding.ASCII))
        {
            throw (new Exception());
        }

        chunkPos = fs.Position;
        chunkSize = 0;
    }

    public IEnumerable<ChunkBase> ReadChunks()
    {
        while (true)
        {
            byte[] tmp = new byte[8];

            fs.Position = chunkPos + chunkSize;

            if (fs.Read(tmp, 0, tmp.Length) != tmp.Length)
            {
                yield break;
            }

            string type = Encoding.ASCII.GetString(tmp, 0, 4);
            UInt32 size = BitConverter.ToUInt32(tmp, 4);

            long chunkStart = fs.Position;
            chunkPos = chunkStart + size;

            switch (type)
            {
            case "fmt ":
                yield return (new ChunkFormat(type, size, fs, chunkStart));
                break;

            case "data":
                yield return (new ChunkData(type, size, fs, chunkStart));
                break;

            default:
                yield return (new ChunkBase(type, size, fs, chunkStart));
                break;
            }
        }
    }

    protected UInt32 read32u()
    {
        byte[] buff = new byte[4];
        fs.Read(buff, 0, buff.Length);
        return (BitConverter.ToUInt32(buff, 0));
    }

    protected string readStr(int bytes, Encoding encoding)
    {
        byte[] buff = new byte[bytes];
        fs.Read(buff, 0, buff.Length);
        return (encoding.GetString(buff));
    }

    #region IDisposable Support
    private bool disposedValue = false; // 重複する呼び出しを検出するには

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                fs.Dispose();

                // TODO: マネージ状態を破棄します (マネージ オブジェクト)。
            }

            // TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。
            // TODO: 大きなフィールドを null に設定します。

            disposedValue = true;
        }
    }

    // TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。
    // ~WaveReader() {
    //   // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
    //   Dispose(false);
    // }

    // このコードは、破棄可能なパターンを正しく実装できるように追加されました。
    public void Dispose()
    {
        // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。
        Dispose(true);
        // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。
        // GC.SuppressFinalize(this);
    }
    #endregion

    public class ChunkBase
    {
        private readonly string type;
        public string Type { get { return (type); } }

        private readonly UInt32 size;
        public UInt32 Size { get { return (size); } }

        protected readonly FileStream fs;
        protected readonly long chunkStart; // chunk head + 8 (type + size)

        public ChunkBase(string Type, UInt32 Size, FileStream fs, long chunkStart)
        {
            type = Type;
            size = Size;
            this.fs = fs;
            this.chunkStart = chunkStart;
        }

        protected void seek(long a)
        {
            fs.Position = chunkStart + a;
        }

        protected bool tryRead(out byte[] data, int length)
        {
            data = new byte[length];
            return (fs.Read(data, 0, length) == length);
        }
    }

    public class ChunkFormat : ChunkBase
    {
        private readonly UInt16 channels;
        public UInt16 Channels { get { return (channels); } }

        private readonly UInt32 samplingRate;
        public UInt32 SamplingRate { get { return (samplingRate); } }

        private readonly UInt16 bitPerSample;
        public UInt16 BitPerSample { get { return (bitPerSample); } }

        public ChunkFormat(string Type, UInt32 Size, FileStream fs, long ChunkStart)
            : base(Type, Size, fs, ChunkStart)
        {
            seek(0);

            bool success = false;

            byte[] data;
            if (false) { }
            else if (Size == 16 && tryRead(out data, (int)Size))
            {
                UInt16 formatId = BitConverter.ToUInt16(data, 0);

                if (formatId == 1)
                {
                    channels = BitConverter.ToUInt16(data, 2);
                    samplingRate = BitConverter.ToUInt32(data, 4);
                    bitPerSample = BitConverter.ToUInt16(data, 14);

                    success = true;
                }
            }

            if (!success)
            {
                throw new Exception();
            }
        }
    }

    public class ChunkData : ChunkBase
    {
        public ChunkData(string Type, UInt32 Size, FileStream fs, long ChunkStart)
            : base(Type, Size, fs, ChunkStart)
        {

        }

        public IEnumerable<Int16[,]> ReadDataS16x2(int blockSize)
        {
            for (long dataPos = 0; dataPos < Size; dataPos += 2 * 2 * blockSize)
            {
                byte[] rawData;

                seek(dataPos);
                if (!tryRead(out rawData, 2 * 2 * blockSize))
                {
                    yield break;
                }

                Int16[,] waveData = new Int16[blockSize, 2];

                for (int i = 0; i < blockSize; i++)
                {
                    waveData[i, 0] = BitConverter.ToInt16(rawData, i * 2 * 2 + 0);
                    waveData[i, 1] = BitConverter.ToInt16(rawData, i * 2 * 2 + 2);
                }

                yield return (waveData);
            }
        }
    }
}
2
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
2
0