1 module dplug.client.binrange; 2 3 import std.range.primitives; 4 5 import dplug.core.nogc; 6 import dplug.core.traits; 7 8 // Note: the exceptions thrown here are allocated with mallocEmplace, 9 // and should be released with destroyFree. 10 11 public @nogc 12 { 13 void skipBytes(R)(ref R input, int numBytes) if (isInputRange!R) 14 { 15 for (int i = 0; i < numBytes; ++i) 16 popUbyte(input); 17 } 18 19 // Reads a big endian integer from input. 20 T popBE(T, R)(ref R input) if (isInputRange!R) 21 { 22 return popFunction!(T, R, false)(input); 23 } 24 25 // Reads a little endian integer from input. 26 T popLE(T, R)(ref R input) if (isInputRange!R) 27 { 28 return popFunction!(T, R, true)(input); 29 } 30 31 /// Writes a big endian integer/float to output. 32 void writeBE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte)) 33 { 34 writeFunction!(T, R, false)(output, n); 35 } 36 37 /// Writes a little endian integer/float to output. 38 void writeLE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte)) 39 { 40 writeFunction!(T, R, true)(output, n); 41 } 42 43 /// Returns: A RIFF chunk header parsed from an input range. 44 void readRIFFChunkHeader(R)(ref R input, out uint chunkId, out uint chunkSize) if (isInputRange!R) 45 { 46 chunkId = popBE!uint(input); 47 chunkSize = popLE!uint(input); 48 } 49 50 /// Writes a RIFF chunk header to an output range. 51 void writeRIFFChunkHeader(R)(ref R output, uint chunkId, uint chunkSize) if (isOutputRange!(R, ubyte)) 52 { 53 writeBE!uint(output, chunkId); 54 writeLE!uint(output, chunkSize); 55 } 56 57 /// Returns: A RIFF chunk id. 58 template RIFFChunkId(string id) 59 { 60 static assert(id.length == 4); 61 uint RIFFChunkId = (cast(ubyte)(id[0]) << 24) 62 | (cast(ubyte)(id[1]) << 16) 63 | (cast(ubyte)(id[2]) << 8) 64 | (cast(ubyte)(id[3])); 65 } 66 67 } 68 69 private 70 { 71 // read/write 64-bits float 72 union float_uint 73 { 74 float f; 75 uint i; 76 } 77 78 // read/write 64-bits float 79 union double_ulong 80 { 81 double f; 82 ulong i; 83 } 84 85 uint float2uint(float x) pure nothrow @nogc 86 { 87 float_uint fi; 88 fi.f = x; 89 return fi.i; 90 } 91 92 float uint2float(int x) pure nothrow @nogc 93 { 94 float_uint fi; 95 fi.i = x; 96 return fi.f; 97 } 98 99 ulong double2ulong(double x) pure nothrow @nogc 100 { 101 double_ulong fi; 102 fi.f = x; 103 return fi.i; 104 } 105 106 double ulong2double(ulong x) pure nothrow @nogc 107 { 108 double_ulong fi; 109 fi.i = x; 110 return fi.f; 111 } 112 113 private template IntegerLargerThan(int numBytes) if (numBytes >= 1 && numBytes <= 8) 114 { 115 static if (numBytes == 1) 116 alias IntegerLargerThan = ubyte; 117 else static if (numBytes == 2) 118 alias IntegerLargerThan = ushort; 119 else static if (numBytes <= 4) 120 alias IntegerLargerThan = uint; 121 else 122 alias IntegerLargerThan = ulong; 123 } 124 125 ubyte popUbyte(R)(ref R input) @nogc if (isInputRange!R) 126 { 127 if (input.empty) 128 throw mallocEmplace!Exception("Expected a byte, but found end of input"); 129 130 ubyte b = input.front; 131 input.popFront(); 132 return b; 133 } 134 135 // Generic integer parsing 136 auto popInteger(R, int NumBytes, bool WantSigned, bool LittleEndian)(ref R input) @nogc if (isInputRange!R) 137 { 138 alias T = IntegerLargerThan!NumBytes; 139 140 T result = 0; 141 142 static if (LittleEndian) 143 { 144 for (int i = 0; i < NumBytes; ++i) 145 result |= ( cast(T)(popUbyte(input)) << (8 * i) ); 146 } 147 else 148 { 149 for (int i = 0; i < NumBytes; ++i) 150 result = (result << 8) | popUbyte(input); 151 } 152 153 static if (WantSigned) 154 return cast(UnsignedToSigned!T)result; 155 else 156 return result; 157 } 158 159 // Generic integer writing 160 void writeInteger(R, int NumBytes, bool LittleEndian)(ref R output, IntegerLargerThan!NumBytes n) if (isOutputRange!(R, ubyte)) 161 { 162 alias T = IntegerLargerThan!NumBytes; 163 164 static assert(isUnsignedIntegral!T); 165 auto u = cast(T)n; 166 167 static if (LittleEndian) 168 { 169 for (int i = 0; i < NumBytes; ++i) 170 { 171 ubyte b = (u >> (i * 8)) & 255; 172 output.put(b); 173 } 174 } 175 else 176 { 177 for (int i = 0; i < NumBytes; ++i) 178 { 179 ubyte b = (u >> ( (NumBytes - 1 - i) * 8) ) & 255; 180 output.put(b); 181 } 182 } 183 } 184 185 void writeFunction(T, R, bool endian)(ref R output, T n) @nogc if (isOutputRange!(R, ubyte)) 186 { 187 static if (isBuiltinIntegral!T) 188 writeInteger!(R, T.sizeof, endian)(output, n); 189 else static if (is(T : float)) 190 writeInteger!(R, 4, endian)(output, float2uint(n)); 191 else static if (is(T : double)) 192 writeInteger!(R, 8, endian)(output, double2ulong(n)); 193 else 194 static assert(false, "Unsupported type " ~ T.stringof); 195 } 196 197 T popFunction(T, R, bool endian)(ref R input) @nogc if (isInputRange!R) 198 { 199 static if(isBuiltinIntegral!T) 200 return popInteger!(R, T.sizeof, isSignedIntegral!T, endian)(input); 201 else static if (is(T == float)) 202 return uint2float(popInteger!(R, 4, false, endian)(input)); 203 else static if (is(T == double)) 204 return ulong2double(popInteger!(R, 8, false, endian)(input)); 205 else 206 static assert(false, "Unsupported type " ~ T.stringof); 207 } 208 } 209 210 unittest 211 { 212 ubyte[] arr = [ 0x00, 0x01, 0x02, 0x03 , 213 0x00, 0x01, 0x02, 0x03 ]; 214 215 assert(popLE!uint(arr) == 0x03020100); 216 assert(popBE!int(arr) == 0x00010203); 217 218 import dplug.core.alignedbuffer; 219 auto app = makeAlignedBuffer!ubyte(); 220 writeBE!float(app, 1.0f); 221 writeLE!double(app, 2.0); 222 } 223 224 225 unittest 226 { 227 ubyte[] arr = [0, 0, 0, 0, 0, 0, 0xe0, 0x3f]; 228 assert(popLE!double(arr) == 0.5); 229 arr = [0, 0, 0, 0, 0, 0, 0xe0, 0xbf]; 230 assert(popLE!double(arr) == -0.5); 231 }