I saw a script about CircularBuffer.cs,and i can not understand a case.
This is the whole program:
The case that i can not underatand is " readOffset & mCapacityMask" in function Peek and Write.
I know the result of " readOffset & mCapacityMask ",but i do not know that why the operate can get that reault.
internal class CircularBuffer
{
private byte[] mBuffer = null;
private int mCapacity = 0;
private int mCapacityMask = 0;
private int mHead = 0;
private int mTail = 0;
private int PowerOfTwo(int e)
{
if (e == 0)
return 1;
return 2 * (PowerOfTwo(e - 1));
}
public CircularBuffer(int capacityPower)
{
mCapacity = PowerOfTwo(capacityPower);
mCapacityMask = mCapacity - 1;
mBuffer = new byte[mCapacity];
}
public WeakReference GetBuffer()
{
return new WeakReference(mBuffer, false);
}
public int GetStoredSize()
{
if (mHead > mTail)
{
return mHead - mTail;
}
else if (mHead < mTail)
{
return (mCapacity - mTail) + mHead;
}
return 0;
}
public bool Peek(byte[] destBuf, int bytes)
{
if (bytes > GetStoredSize())
return false;
int readOffset = mTail + bytes;
int afterReadBytes = readOffset > mCapacity ? readOffset & mCapacityMask : 0;
int readBytes = bytes - afterReadBytes;
Buffer.BlockCopy(mBuffer, mTail, destBuf, 0, readBytes);
if (afterReadBytes > 0)
{
Buffer.BlockCopy(mBuffer, 0, destBuf, readBytes, afterReadBytes);
}
return true;
}
public bool Read(byte[] destBuf, int bytes)
{
if (bytes > GetStoredSize())
return false;
int readOffset = mTail + bytes;
int afterReadBytes = readOffset > mCapacity ? readOffset & mCapacityMask : 0;
int readBytes = bytes - afterReadBytes;
Buffer.BlockCopy(mBuffer, mTail, destBuf, 0, readBytes);
if (afterReadBytes > 0)
{
Buffer.BlockCopy(mBuffer, 0, destBuf, readBytes, afterReadBytes);
}
mTail = readOffset & mCapacityMask;
return true;
}
public bool Write(byte[] data, int offset, int bytes)
{
if (mCapacity < GetStoredSize() + bytes)
{
return false;
}
int writeOffset = mHead + bytes;
int afterWriteBytes = writeOffset > mCapacity ? writeOffset & mCapacityMask : 0;
int writeBytes = bytes - afterWriteBytes;
Buffer.BlockCopy(data, offset, mBuffer, mHead, writeBytes);
if (afterWriteBytes > 0)
{
Buffer.BlockCopy(data, offset + writeBytes, mBuffer, 0, afterWriteBytes);
}
mHead = writeOffset & mCapacityMask;
return true;
}
public bool Remove(int bytes)
{
if (bytes > GetStoredSize())
return false;
mTail = (mTail + bytes) & mCapacityMask;
return true;
}
public void Clear()
{
mHead = 0;
mTail = 0;
}
}
I am trying to use Naudio to input audio and then output it again after it has been processed by a plugin. To do the output step I have created a custom SampleProvider but the buffer is not behaving as I expect and I can't hear any sound. The code that reads the audio and attempts to play it again is as follows
var audioFile = new AudioFileReader(#"C:\Users\alex.clayton\Downloads\Rhythm guitar.mp3");
PluginContext.PluginCommandStub.Commands.MainsChanged(true);
PluginContext.PluginCommandStub.Commands.StartProcess();
var vstSampleProvider = new VstSampleProvider(44100, 2);
var devices = DirectSoundOut.Devices.Last();
var output = new DirectSoundOut(devices.Guid);
output.Init(vstSampleProvider);
int chunckStep = 0;
while (chunckStep < audioFile.Length)
{
var nAudiobuffer = new float[blockSize * 2];
audioFile.Read(nAudiobuffer, 0, blockSize * 2);
var leftSpan = inputMgr.Buffers.ToArray()[0].AsSpan();
for (int i = 0; i < blockSize; i++)
{
leftSpan[i] = nAudiobuffer[i*2] / int.MaxValue;
}
var rightSpan = inputMgr.Buffers.ToArray()[0].AsSpan();
for (int i = 1; i < blockSize; i++)
{
rightSpan[i] = nAudiobuffer[i*2 + 1] / int.MaxValue;
}
PluginContext.PluginCommandStub.Commands.ProcessReplacing(inputBuffers, outputBuffers);
vstSampleProvider.LoadBuffer(outputBuffers);
chunckStep += blockSize;
}
PluginContext.PluginCommandStub.Commands.StopProcess();
PluginContext.PluginCommandStub.Commands.MainsChanged(false);
output.Play();
The Sample provider code is this
public class VstSampleProvider : ISampleProvider
{
private readonly int _sampleRate;
private readonly int _channels;
private readonly Queue<float> _buffer;
public VstSampleProvider(int sampleRate, int channels)
{
_sampleRate = sampleRate;
_channels = channels;
_buffer = new Queue<float>();
}
public WaveFormat WaveFormat => WaveFormat.CreateIeeeFloatWaveFormat(_sampleRate, _channels);
public void LoadBuffer(VstAudioBuffer[] outputBuffers)
{
var totalSampleCount = outputBuffers[0].SampleCount * _channels;
try
{
if (_channels == 1)
for (int i = 0; i < totalSampleCount; i++)
{
_buffer.Enqueue(outputBuffers[0][i]);
}
else
{
for (int i = 0; i < totalSampleCount; i++)
{
if (i % 2 == 0)
{
var value = outputBuffers[0][i / 2];
_buffer.Enqueue(value);
}
else
_buffer.Enqueue(outputBuffers[1][(i - 1) / 2]);
}
}
}
catch (Exception ex)
{
// Probably should log or something
}
}
public int Read(float[] buffer, int offset, int count)
{
if (_buffer.Count < count)
return 0;
if (offset > 0)
throw new NotImplementedException();
for (int i = 0; i < count; i++)
{
var value = _buffer.Dequeue();
buffer[i] = value;
}
if (buffer.Any(f => f > 1))
{
return count;
}
return count;
}
}
When I look at the values being dequeued they are all between -1 and 1, as expected but when I put a break point on the line after if (buffer.Any(f => f > 1)) I can see that the buffer values are integers larger than 1 or 0 and bear no resemblance to the dequeued values that, I thought, were added to the buffer.
I expect I have not understood something about how the SampleProvider is supposed to work byt looing at ones already in Naudio I cant see what I'm doing wrong.
Any help would be much appreciated. Thank you
So it turns out that the main issue was reading the input file and turning the volume down, so I was playing but very quietly.
leftSpan[i] = nAudiobuffer[i*2] / int.MaxValue
There was no need for the / int.MaxValue
So, I have a piece of code that reads out an ISampleSource in to a float[][], the first array layer being for the number of channels and the second being for the sample data within the channel. I am going to take this data and attempt to apply signal processing to it, however for debugging purposes I might want to manipulate the sample array and then play it back so that I can "hear" what the code is doing. is there an easy way to take the data returned by ISampleSource.Read and stick it back in to a new ISampleSource so it can then be converted to an IWaveSource and played using WasapiOut?
Here is the class I tried to make so far, you pass it the float[][] and basically all the data in a WaveFormat for it to make one from.. but it doesn't actually do anything. doesn't error, doesn't play.. just does nothing. What am I doing wrong?
private class SampleSource : ISampleSource
{
public long Position { get; set; }
public WaveFormat WaveFormat { get; private set; }
public bool CanSeek => true;
public long Length => _data.Length;
private float[] _data;
private long readPoint = 0;
public SampleSource(float[][] samples, int sampleRate, int bits, int channels)
{
WaveFormat = new WaveFormat(sampleRate, bits, channels);
if (samples.Length <= 0) return;
_data = new float[samples[0].Length * samples.Length];
int cchannels = samples.Length;
int sampleLength = samples[0].Length;
for (var i = 0; i < sampleLength; i += cchannels)
for (var n = 0; n < cchannels; n++)
_data[i + n] = samples[n][i / cchannels];
}
public int Read(float[] buffer, int offset, int count)
{
if (_data.Length < Position + count)
count = (int) (_data.Length - Position);
float[] outFloats = new float[count];
for (var i = 0; i < count; i++)
outFloats[i] = _data[i + Position + offset];
buffer = outFloats;
Position += count;
return count;
}
public void Dispose() =>_data = null;
}
Rather than trying to set buffer to a new array (which makes no sense) I needed to directly write to the buffer array elements, so that they can be used outside of the function call. I don't really like doing it this way, maybe it's to fix an issue I don't see, but clearly that's how the library I'm using does it.
private class SampleSource : ISampleSource
{
public long Position { get; set; }
public WaveFormat WaveFormat { get; private set; }
public bool CanSeek => true;
public long Length => _data.Length;
private float[] _data;
private long readPoint = 0;
public SampleSource(float[][] samples, int sampleRate, int bits, int channels)
{
WaveFormat = new WaveFormat(sampleRate, bits, channels);
if (samples.Length <= 0) return;
_data = new float[samples[0].Length * samples.Length];
int cchannels = samples.Length;
int sampleLength = samples[0].Length;
for (var i = 0; i < sampleLength; i += cchannels)
for (var n = 0; n < cchannels; n++)
_data[i + n] = samples[n][i / cchannels];
}
public int Read(float[] buffer, int offset, int count)
{
/*THIS IS THE CHANGED FUNCTION*/
if (_data.Length < Position + count)
count = (int) (_data.Length - Position);
for (var i = 0; i < count; i++)
buffer[i] = _data[i + Position + offset];
Position += count;
return count;
}
public void Dispose() =>_data = null;
}
I want to know a fastest way to count all byte in file ? I need to work on large binary file
I want to know the quantity of all byte in file (Quantity of 0x00, 0x01, .. 0xff)
It's for add a graph with file representation in my WPF Hexeditor usercontrol https://github.com/abbaye/WPFHexEditorControl like in HxD hexeditor.
This code work fine but it's to slow for large file.
public Dictionary<int, long> GetByteCount()
{
if (IsOpen)
{
Position = 0;
int currentByte = 0;
// Build dictionary
Dictionary<int, long> cd = new Dictionary<int, long>();
for (int i = 0; i <= 255; i++) cd.Add(i, 0);
//
for (int i = 0; i <= Length; i++)
{
//if (EOF) break;
currentByte = ReadByte();
if (currentByte != -1) cd[currentByte]++;
Position++;
}
return cd;
}
return new Dictionary<int, long>();
}
/// <summary>
/// Get an array of long computing the total of each byte in the file.
/// The position of the array makes it possible to obtain the sum of the desired byte
/// </summary>
public long[] GetByteCount()
{
if (IsOpen)
{
const int bufferLenght = 1048576; //1mb
var storedCnt = new long[256];
Position = 0;
while (!Eof)
{
var testLenght = Length - Position;
var buffer = testLenght <= bufferLenght ? new byte[testLenght] : new byte[bufferLenght];
Read(buffer, 0, buffer.Length);
foreach (var b in buffer)
storedCnt[b]++;
Position += bufferLenght;
}
return storedCnt;
}
return null;
}
I have optimized David's solution a bit. The "Position" - calls are not necessary. I've found that the buffer length and the unbuffered read mode are not very important, but the "for"- instead of "foreach" - construct in the calculation made a big difference.
Results with
foreach (var b in buffer.Take(count))
{
storedCnt[b]++;
}
file length is 4110217216
duration 00:00:51.1686821
Results with
for(var i = 0; i < count; i++)
{
storedCnt[buffer[i]]++;
}
file length 4110217216
duration 00:00:05.9695418
Here the program
private static void Main(
{
const string fileForCheck = #"D:\Data\System\en_visual_studio_enterprise_2015_x86_x64_dvd_6850497.iso";
Debug.Assert(File.Exists(fileForCheck));
var watch = new Stopwatch();
var counter = new FileBytesCounter(fileForCheck);
watch.Start();
var results = counter.GetByteCount();
watch.Stop();
counter.Dispose();
Console.WriteLine("results:");
Console.WriteLine(string.Join(", ", results.Select((c, b) => $"{b} -> {c}")));
var sumBytes = results.Sum(c => c);
Debug.Assert((new FileInfo(fileForCheck)).Length == sumBytes); // here's the proof
Console.WriteLine();
Console.WriteLine($"file length {sumBytes}");
Console.WriteLine($"duration {watch.Elapsed}");
}
and here the class
internal class FileBytesCounter
: FileStream
{
private const FileOptions FileFlagNoBuffering = (FileOptions)0x20000000;
private const int CopyBufferSize = 1024 * 1024;
//private const int CopyBufferSize = 4 * 1024 * 16;
public FileBytesCounter(string path, FileShare share = FileShare.Read)
: base(path, FileMode.Open, FileAccess.Read, share, CopyBufferSize/*, FileFlagNoBuffering*/)
{
}
public long[] GetByteCount()
{
var buffer = new byte[CopyBufferSize];
var storedCnt = new long[256];
int count;
Position = 0;
while ((count = Read(buffer, 0, CopyBufferSize)) > 0)
{
for(var i = 0; i < count; i++)
{
storedCnt[buffer[i]]++;
}
}
return storedCnt;
}
}
See also https://www.codeproject.com/Articles/172613/Fast-File-Copy-With-Managed-Code-UBCopy-update for FileFlagNoBuffering
It seems like you want something like this:
public Dictionary<char, long> GetCharCount(string filePath)
{
var result = new Dictionary<char, long>();
var content = File.ReadAllText(filePath);
foreach(var c in content)
{
if (result.ContainsKey(c))
{
result[c] = result[c] + 1;
}
else
{
result.Add(c, 1);
}
}
return result;
}
I'm working on a bit-based B/W/Greyscale Pre-Compiled font format, and was having issues with either reading or writing the format, (I've not been able to determine where the issue was. (I do have a B/W bit-based version working, but an Aliased font doesn't look too good, as you can imagine, especially when working with a 320x200 pixel screen) ) but decided that just using a BinaryWriter would be much easier than writing to a bool[] when I pulled the image data.
The basic format of a pixel in the file looks like this:
1 - White Pixel (Shortest, as this would be most of the pixels)
00 - Black Pixel (No reason to write 10-bits for a pure black pixel, which there are a reasonable number of)
01 - Greyscale Pixel, and is followed by 1 byte describing the shade of the pixel
Now, everything is fine and dandy with writing the required info, as that's all full bytes, but the default .Net 4.0 BinaryWriter writes a Boolean value as a full byte, and as you can imagine, that negates the use of a bit-based format. So I was wondering, is there a BinaryWriter, (and BinaryReader) implementation out there that's bit-based
Edit:
I ended up creating my own. (See the answer for the code.)
I ended up writing my own, so here they are.
The BinaryWriter (I've only overridden the ones that I needed)
private class BinaryWriter : System.IO.BinaryWriter
{
private bool[] curByte = new bool[8];
private byte curBitIndx = 0;
private System.Collections.BitArray ba;
public BinaryWriter(Stream s) : base(s) { }
public override void Flush()
{
base.Write(ConvertToByte(curByte));
base.Flush();
}
public override void Write(bool value)
{
curByte[curBitIndx] = value;
curBitIndx++;
if (curBitIndx == 8)
{
base.Write(ConvertToByte(curByte));
this.curBitIndx = 0;
this.curByte = new bool[8];
}
}
public override void Write(byte value)
{
ba = new BitArray(new byte[] { value });
for (byte i = 0; i < 8; i++)
{
this.Write(ba[i]);
}
ba = null;
}
public override void Write(byte[] buffer)
{
for (int i = 0; i < buffer.Length; i++)
{
this.Write((byte)buffer[i]);
}
}
public override void Write(uint value)
{
ba = new BitArray(BitConverter.GetBytes(value));
for (byte i = 0; i < 32; i++)
{
this.Write(ba[i]);
}
ba = null;
}
public override void Write(ulong value)
{
ba = new BitArray(BitConverter.GetBytes(value));
for (byte i = 0; i < 64; i++)
{
this.Write(ba[i]);
}
ba = null;
}
public override void Write(ushort value)
{
ba = new BitArray(BitConverter.GetBytes(value));
for (byte i = 0; i < 16; i++)
{
this.Write(ba[i]);
}
ba = null;
}
private static byte ConvertToByte(bool[] bools)
{
byte b = 0;
byte bitIndex = 0;
for (int i = 0; i < 8; i++)
{
if (bools[i])
{
b |= (byte)(((byte)1) << bitIndex);
}
bitIndex++;
}
return b;
}
}
And, the BinaryReader, once again, I've only overridden the methods that I needed.
private class BinaryReader : System.IO.BinaryReader
{
private bool[] curByte = new bool[8];
private byte curBitIndx = 0;
private BitArray ba;
public BinaryReader(Stream s) : base(s)
{
ba = new BitArray(new byte[] { base.ReadByte() });
ba.CopyTo(curByte, 0);
ba = null;
}
public override bool ReadBoolean()
{
if (curBitIndx == 8)
{
ba = new BitArray(new byte[] { base.ReadByte() });
ba.CopyTo(curByte, 0);
ba = null;
this.curBitIndx = 0;
}
bool b = curByte[curBitIndx];
curBitIndx++;
return b;
}
public override byte ReadByte()
{
bool[] bar = new bool[8];
byte i;
for (i = 0; i < 8; i++)
{
bar[i] = this.ReadBoolean();
}
byte b = 0;
byte bitIndex = 0;
for (i = 0; i < 8; i++)
{
if (bar[i])
{
b |= (byte)(((byte)1) << bitIndex);
}
bitIndex++;
}
return b;
}
public override byte[] ReadBytes(int count)
{
byte[] bytes = new byte[count];
for (int i = 0; i < count; i++)
{
bytes[i] = this.ReadByte();
}
return bytes;
}
public override ushort ReadUInt16()
{
byte[] bytes = ReadBytes(2);
return BitConverter.ToUInt16(bytes, 0);
}
public override uint ReadUInt32()
{
byte[] bytes = ReadBytes(4);
return BitConverter.ToUInt32(bytes, 0);
}
public override ulong ReadUInt64()
{
byte[] bytes = ReadBytes(8);
return BitConverter.ToUInt64(bytes, 0);
}
}
I don't believe there's anything in the framework for this, no. Basically you'd need to write a class to wrap a BinaryWriter (or just a stream) and "the byte written so far" and the number of bits written. When the number of bits gets to 8, write the byte to the underlying stream and clear.
EDIT: the OP posted a possible and working implementation of the above suggestion below.
If you keep your data in a byte array (bools are of no use and take too much space, if you use them for bits, they take up a byte in memory) or in an array of a particular struct that fits your dataformat.
Once you have an internal memory representation, you don't need a bit-based binary writer anymore. You can simply write the data to a BinaryWriter and you're done with it.
...but the default .Net 4.0 BinaryWriter writes a Boolean value as a full
byte, and as you can imagine, that negates the use of a bit-based
format....
The reason for this is: the bool is, by definition, of 1 byte size in C#. The BinaryWriter simply writes what you give it.
I found myself in need of this as well, so I built upon OP and filled in all the read/writes (except char & string since those are a bit special).
I also made a quick unit test try it out. For streams containing only boolean (or other custom sub-byte value types) it's obviously 87.5% cheaper, and for a random mixed stream containing 75% boolean values, it was about 33% cheaper. So could be useful for some scenarios.
Here are the both classes in case anyone else needs them, use at your own risk:
/// <summary>
/// A binary writer that packs data into bits, to preserve space when using many bit/boolean values. Up to about 87.5% cheaper for streams that only contains boolean values.
/// By: jsmars#gmail.com, based on posters classes in this post: https://stackoverflow.com/questions/7051939/bit-based-binarywriter-in-c-sharp
/// </summary>
public class BinaryBitWriter : BinaryWriter
{
public byte BitPosition { get; private set; } = 0;
private bool[] curByte = new bool[8];
private System.Collections.BitArray ba;
public BinaryBitWriter(Stream s) : base(s) { }
public override void Flush()
{
flushBitBuffer();
base.Flush();
}
public override void Write(byte[] buffer, int index, int count)
{
for (int i = index; i < index + count; i++)
Write((byte)buffer[i]);
}
public override void Write(byte value)
{
ba = new BitArray(new byte[] { value });
for (byte i = 0; i < 8; i++)
Write(ba[i]);
}
public override void Write(bool value)
{
curByte[BitPosition] = value;
BitPosition++;
if (BitPosition == 8)
flushBitBuffer();
}
public override void Write(char[] chars, int index, int count)
{
for (int i = index; i < index + count; i++)
Write(chars[i]);
}
public override void Write(string value)
{
// write strings as normal for now, so flush the bits first
flushBitBuffer();
base.Write(value);
}
public override void Write(decimal value)
{
var ints = decimal.GetBits(value);
for (int i = 0; i < ints.Length; i++)
Write(ints[i]);
}
public override void Write(float value) => Write(BitConverter.GetBytes(value));
public override void Write(ulong value) => Write(BitConverter.GetBytes(value));
public override void Write(long value) => Write(BitConverter.GetBytes(value));
public override void Write(uint value) => Write(BitConverter.GetBytes(value));
public override void Write(int value) => Write(BitConverter.GetBytes(value));
public override void Write(ushort value) => Write(BitConverter.GetBytes(value));
public override void Write(short value) => Write(BitConverter.GetBytes(value));
public override void Write(double value) => Write(BitConverter.GetBytes(value));
public override void Write(char[] value) => Write(value, 0, value.Length);
public override void Write(char value)
{
// write strings as normal for now, so flush the bits first
flushBitBuffer();
base.Write(value);
//var b = BitConverter.GetBytes(value);
//Write(b);
}
public override void Write(byte[] buffer) => Write(buffer, 0, buffer.Length);
public override void Write(sbyte value) => Write((byte)value);
void flushBitBuffer()
{
if (BitPosition == 0) // Nothing to flush
return;
base.Write(ConvertToByte(curByte));
BitPosition = 0;
curByte = new bool[8];
}
private static byte ConvertToByte(bool[] bools)
{
byte b = 0;
byte bitIndex = 0;
for (int i = 0; i < 8; i++)
{
if (bools[i])
b |= (byte)(((byte)1) << bitIndex);
bitIndex++;
}
return b;
}
}
public class BinaryBitReader : BinaryReader
{
public byte BitPosition { get; private set; } = 8;
private bool[] curByte = new bool[8];
public BinaryBitReader(Stream s) : base(s)
{
}
public override bool ReadBoolean()
{
if (BitPosition == 8)
{
var ba = new BitArray(new byte[] { base.ReadByte() });
ba.CopyTo(curByte, 0);
BitPosition = 0;
}
bool b = curByte[BitPosition];
BitPosition++;
return b;
}
public override byte ReadByte()
{
bool[] bar = new bool[8];
byte i;
for (i = 0; i < 8; i++)
{
bar[i] = this.ReadBoolean();
}
byte b = 0;
byte bitIndex = 0;
for (i = 0; i < 8; i++)
{
if (bar[i])
{
b |= (byte)(((byte)1) << bitIndex);
}
bitIndex++;
}
return b;
}
public override byte[] ReadBytes(int count)
{
byte[] bytes = new byte[count];
for (int i = 0; i < count; i++)
{
bytes[i] = this.ReadByte();
}
return bytes;
}
//public override int Read() => BitConverter.ToUInt64(ReadBytes(8), 0);
public override int Read(byte[] buffer, int index, int count)
{
for (int i = index; i < index + count; i++)
buffer[i] = ReadByte();
return count; // we can return this here, it will die at the above row if anything is off
}
public override int Read(char[] buffer, int index, int count)
{
for (int i = index; i < index + count; i++)
buffer[i] = ReadChar();
return count; // we can return this here, it will die at the above row if anything is off
}
public override char ReadChar()
{
BitPosition = 8;
return base.ReadChar();
//BitConverter.ToChar(ReadBytes(2), 0);
}
public override char[] ReadChars(int count)
{
var chars = new char[count];
Read(chars, 0, count);
return chars;
}
public override decimal ReadDecimal()
{
int[] ints = new int[4];
for (int i = 0; i < ints.Length; i++)
ints[i] = ReadInt32();
return new decimal(ints);
}
public override double ReadDouble() => BitConverter.ToDouble(ReadBytes(8), 0);
public override short ReadInt16() => BitConverter.ToInt16(ReadBytes(2), 0);
public override int ReadInt32() => BitConverter.ToInt32(ReadBytes(4), 0);
public override long ReadInt64() => BitConverter.ToInt64(ReadBytes(8), 0);
public override sbyte ReadSByte() => (sbyte)ReadByte();
public override float ReadSingle() => BitConverter.ToSingle(ReadBytes(4), 0);
public override string ReadString()
{
BitPosition = 8; // Make sure we read a new byte when we start reading the string
return base.ReadString();
}
public override ushort ReadUInt16() => BitConverter.ToUInt16(ReadBytes(2), 0);
public override uint ReadUInt32() => BitConverter.ToUInt32(ReadBytes(4), 0);
public override ulong ReadUInt64() => BitConverter.ToUInt64(ReadBytes(8), 0);
}
And the unit tests:
public static bool UnitTest()
{
const int testPairs = 512;
var bitstream = new MemoryStream();
var bitwriter = new BinaryBitWriter(bitstream);
var bitreader = new BinaryBitReader(bitstream);
byte[] bytes = new byte[] { 1, 2, 3, 4, 255 };
byte Byte = 128;
bool Bool = true;
char[] chars = new char[] { 'a', 'b', 'c' };
string str = "hello";
var Float = 2.5f;
ulong Ulong = 12345678901234567890;
long Long = 1122334455667788;
uint Uint = 1234567890;
int Int = 999998888;
ushort UShort = 12345;
short Short = 4321;
double Double = 9.9;
char Char = 'A';
sbyte Sbyte = -128;
decimal Decimal = 10000.00001m;
List<BBTest> pairs = new List<BBTest>();
// Make pairs of write and read tests
pairs.Add(new BBTest(Bool, (w) => w.Write(Bool), (r) => { if (r.ReadBoolean() != Bool) throw new Exception(); }));
pairs.Add(new BBTest(bytes, (w) => w.Write(bytes, 0, 5), (r) => { if (arrayCompare(r.ReadBytes(5), bytes)) throw new Exception(); }));
pairs.Add(new BBTest(Byte, (w) => w.Write(Byte), (r) => { if (r.ReadByte() != Byte) throw new Exception(); }));
pairs.Add(new BBTest(chars, (w) => w.Write(chars, 0, 3), (r) => { if (arrayCompare(r.ReadChars(3), chars)) throw new Exception(); })); /////////////
pairs.Add(new BBTest(str, (w) => w.Write(str), (r) => { string s; if ((s = r.ReadString()) != str) throw new Exception(); }));
pairs.Add(new BBTest(Decimal, (w) => w.Write(Decimal), (r) => { if (r.ReadDecimal() != Decimal) throw new Exception(); }));
pairs.Add(new BBTest(Float, (w) => w.Write(Float), (r) => { if (r.ReadSingle() != Float) throw new Exception(); }));
pairs.Add(new BBTest(Ulong, (w) => w.Write(Ulong), (r) => { if (r.ReadUInt64() != Ulong) throw new Exception(); }));
pairs.Add(new BBTest(Long, (w) => w.Write(Long), (r) => { if (r.ReadInt64() != Long) throw new Exception(); }));
pairs.Add(new BBTest(Uint, (w) => w.Write(Uint), (r) => { if (r.ReadUInt32() != Uint) throw new Exception(); }));
pairs.Add(new BBTest(Int, (w) => w.Write(Int), (r) => { if (r.ReadInt32() != Int) throw new Exception(); }));
pairs.Add(new BBTest(UShort, (w) => w.Write(UShort), (r) => { if (r.ReadUInt16() != UShort) throw new Exception(); }));
pairs.Add(new BBTest(Short, (w) => w.Write(Short), (r) => { if (r.ReadInt16() != Short) throw new Exception(); }));
pairs.Add(new BBTest(Double, (w) => w.Write(Double), (r) => { if (r.ReadDouble() != Double) throw new Exception(); }));
pairs.Add(new BBTest(Char, (w) => w.Write(Char), (r) => { if (r.ReadChar() != Char) throw new Exception(); })); ///////////////
pairs.Add(new BBTest(bytes, (w) => w.Write(bytes), (r) => { if (arrayCompare(r.ReadBytes(5), bytes)) throw new Exception(); }));
pairs.Add(new BBTest(Sbyte, (w) => w.Write(Sbyte), (r) => { if (r.ReadSByte() != Sbyte) throw new Exception(); }));
// Now add all tests, and then a bunch of randomized tests, to make sure we test lots of combinations incase there is some offsetting error
List<BBTest> test = new List<BBTest>();
test.AddRange(pairs);
var rnd = new Random();
for (int i = 0; i < testPairs - test.Count; i++)
{
if (rnd.NextDouble() < 0.75)
test.Add(pairs[0]);
else
test.Add(pairs[rnd.Next(pairs.Count)]);
}
// now write all the tests
for (int i = 0; i < test.Count; i++)
test[i].Writer(bitwriter);
bitwriter.Flush();
// now reset the stream and test to see that they are the same
bitstream.Position = 0;
for (int i = 0; i < test.Count; i++)
test[i].ReadTest(bitreader);
// As comparison, lets write the same stuff to a normal binarywriter and compare sized
var binstream = new MemoryStream();
var binwriter = new BinaryWriter(binstream);
for (int i = 0; i < test.Count; i++)
test[i].Writer(binwriter);
binwriter.Flush();
var saved = 1 - bitstream.Length / (float)binstream.Length;
var result = $"BinaryBitWriter was {(saved * 100).ToString("0.00")}% cheaper than a normal BinaryWriter with random data";
bool arrayCompare(IEnumerable a, IEnumerable b)
{
var B = b.GetEnumerator();
B.MoveNext();
foreach (var item in a)
{
if (item != B.Current)
return false;
B.MoveNext();
}
return true;
}
return true;
}
delegate void writer(BinaryWriter w);
delegate void reader(BinaryReader r);
class BBTest
{
public object Object;
public writer Writer;
public reader ReadTest;
public BBTest(object obj, writer w, reader r) { Object = obj; Writer = w; ReadTest = r; }
public override string ToString() => Object.ToString();
}