機能
- 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);
}
}
}
}