1 /** 2 * Utilities for parsing and emitting binary data from input ranges, or to output ranges. 3 * It is unwise to depend on this outside of Dplug internals. 4 * 5 * Copyright: Copyright Auburn Sounds 2015-2023. 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Authors: Guillaume Piolat 8 */ 9 module dplug.core.binrange; 10 11 // FUTURE: Monomorphism. only allow reading from [] and writing on Vec!ubyte 12 import std.range.primitives; 13 14 import dplug.core.nogc; 15 import dplug.core.traits; 16 17 // Note: the exceptions thrown here are allocated with mallocEmplace, 18 // and should be released with destroyFree. 19 20 public @nogc 21 { 22 /// Skip bytes in input range. 23 /// Returns: On success, `*err` is set to `false` and return integer. 24 /// On failure, `*err` is set to `true` and return 0. The input range cannot be used anymore. 25 void skipBytes(ref const(ubyte)[] input, int numBytes, bool* err) nothrow 26 { 27 for (int i = 0; i < numBytes; ++i) 28 { 29 popUbyte(input, err); 30 if (*err) 31 return; 32 } 33 *err = false; 34 } 35 36 /// Reads a big endian integer from input. 37 /// Returns: On success, `*err` is set to `false` and return integer. 38 /// On failure, `*err` is set to `true` and return 0. The input range cannot be used anymore. 39 T popBE(T)(ref const(ubyte)[] input, bool* err) nothrow 40 { 41 return popFunction!(T, false)(input, err); 42 } 43 44 /// Reads a little endian integer from input. 45 /// Returns: On success, `*err` is set to `false` and return integer. 46 /// On failure, `*err` is set to `true` and return 0. The input range cannot be used anymore. 47 T popLE(T)(ref const(ubyte)[] input, bool* err) nothrow 48 { 49 return popFunction!(T, true)(input, err); 50 } 51 52 /// Writes a big endian integer/float to output. 53 void writeBE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte)) 54 { 55 writeFunction!(T, R, false)(output, n); 56 } 57 58 /// Writes a little endian integer/float to output. 59 void writeLE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte)) 60 { 61 writeFunction!(T, R, true)(output, n); 62 } 63 64 /// Returns: A RIFF chunk header parsed from an input range. 65 /// Returns: On success, `*err` is set to `false` and return integer. 66 /// On failure, `*err` is set to `true` and undefined values for chunkId and chunkSize. The input range cannot be used anymore. 67 void readRIFFChunkHeader(ref const(ubyte)[] input, out uint chunkId, out uint chunkSize, bool* err) nothrow 68 { 69 chunkId = popBE!uint(input, err); 70 if (*err) 71 return; 72 chunkSize = popLE!uint(input, err); 73 if (*err) 74 return; 75 *err = false; 76 } 77 78 /// Writes a RIFF chunk header to an output range. 79 void writeRIFFChunkHeader(R)(ref R output, uint chunkId, uint chunkSize) if (isOutputRange!(R, ubyte)) 80 { 81 writeBE!uint(output, chunkId); 82 writeLE!uint(output, chunkSize); 83 } 84 85 /// Returns: A RIFF chunk id. 86 template RIFFChunkId(string id) 87 { 88 static assert(id.length == 4); 89 uint RIFFChunkId = (cast(ubyte)(id[0]) << 24) 90 | (cast(ubyte)(id[1]) << 16) 91 | (cast(ubyte)(id[2]) << 8) 92 | (cast(ubyte)(id[3])); 93 } 94 95 } 96 97 private 98 { 99 // read/write 64-bits float 100 union float_uint 101 { 102 float f; 103 uint i; 104 } 105 106 // read/write 64-bits float 107 union double_ulong 108 { 109 double f; 110 ulong i; 111 } 112 113 uint float2uint(float x) pure nothrow @nogc 114 { 115 float_uint fi; 116 fi.f = x; 117 return fi.i; 118 } 119 120 float uint2float(int x) pure nothrow @nogc 121 { 122 float_uint fi; 123 fi.i = x; 124 return fi.f; 125 } 126 127 ulong double2ulong(double x) pure nothrow @nogc 128 { 129 double_ulong fi; 130 fi.f = x; 131 return fi.i; 132 } 133 134 double ulong2double(ulong x) pure nothrow @nogc 135 { 136 double_ulong fi; 137 fi.i = x; 138 return fi.f; 139 } 140 141 private template IntegerLargerThan(int numBytes) if (numBytes >= 1 && numBytes <= 8) 142 { 143 static if (numBytes == 1) 144 alias IntegerLargerThan = ubyte; 145 else static if (numBytes == 2) 146 alias IntegerLargerThan = ushort; 147 else static if (numBytes <= 4) 148 alias IntegerLargerThan = uint; 149 else 150 alias IntegerLargerThan = ulong; 151 } 152 153 // Read one ubyte in stream. 154 // On success, sets `*err` to `false` and return the byte value. 155 // On error, sets `err` to `true` and return 0. 156 ubyte popUbyte(ref const(ubyte)[] input, bool* err) nothrow @nogc 157 { 158 if (input.length == 0) 159 { 160 *err = true; 161 return 0; 162 } 163 ubyte b = input[0]; 164 input = input[1..$]; 165 return b; 166 } 167 168 // Generic integer parsing 169 // On success, sets `*err` to `false` and return the byte value. 170 // On error, sets `err` to `true` and return 0. 171 auto popInteger(int NumBytes, bool WantSigned, bool LittleEndian)(ref const(ubyte)[] input, bool* err) nothrow @nogc 172 { 173 alias T = IntegerLargerThan!NumBytes; 174 175 T result = 0; 176 177 static if (LittleEndian) 178 { 179 for (int i = 0; i < NumBytes; ++i) 180 { 181 ubyte b = popUbyte(input, err); 182 if (*err) 183 return 0; 184 result |= ( cast(T)(b) << (8 * i) ); 185 } 186 } 187 else 188 { 189 for (int i = 0; i < NumBytes; ++i) 190 { 191 ubyte b = popUbyte(input, err); 192 if (*err) 193 return 0; 194 result = cast(T)( (result << 8) | b ); 195 } 196 } 197 198 *err = false; 199 200 static if (WantSigned) 201 return cast(UnsignedToSigned!T)result; 202 else 203 return result; 204 } 205 206 // Generic integer writing 207 void writeInteger(R, int NumBytes, bool LittleEndian)(ref R output, IntegerLargerThan!NumBytes n) if (isOutputRange!(R, ubyte)) 208 { 209 alias T = IntegerLargerThan!NumBytes; 210 211 static assert(isUnsignedIntegral!T); 212 auto u = cast(T)n; 213 214 static if (LittleEndian) 215 { 216 for (int i = 0; i < NumBytes; ++i) 217 { 218 ubyte b = (u >> (i * 8)) & 255; 219 output.put(b); 220 } 221 } 222 else 223 { 224 for (int i = 0; i < NumBytes; ++i) 225 { 226 ubyte b = (u >> ( (NumBytes - 1 - i) * 8) ) & 255; 227 output.put(b); 228 } 229 } 230 } 231 232 void writeFunction(T, R, bool endian)(ref R output, T n) @nogc nothrow if (isOutputRange!(R, ubyte)) 233 { 234 static if (isBuiltinIntegral!T) 235 writeInteger!(R, T.sizeof, endian)(output, n); 236 else static if (is(T : float)) 237 writeInteger!(R, 4, endian)(output, float2uint(n)); 238 else static if (is(T : double)) 239 writeInteger!(R, 8, endian)(output, double2ulong(n)); 240 else 241 static assert(false, "Unsupported type " ~ T.stringof); 242 } 243 244 T popFunction(T, bool endian)(ref const(ubyte)[] input, bool* err) @nogc nothrow 245 { 246 static if(isBuiltinIntegral!T) 247 return cast(T) popInteger!(T.sizeof, isSignedIntegral!T, endian)(input, err); 248 else static if (is(T == float)) 249 return uint2float(popInteger!(4, false, endian)(input, err)); 250 else static if (is(T == double)) 251 return ulong2double(popInteger!(8, false, endian)(input, err)); 252 else 253 static assert(false, "Unsupported type " ~ T.stringof); 254 } 255 } 256 257 unittest 258 { 259 // test 32-bit integer parsing 260 { 261 const(ubyte)[] arr = [ 0x00, 0x01, 0x02, 0x03 , 262 0x00, 0x01, 0x02, 0x03 ]; 263 bool err; 264 assert(popLE!uint(arr, &err) == 0x03020100); 265 assert(!err); 266 267 assert(popBE!int(arr, &err) == 0x00010203); 268 assert(!err); 269 } 270 271 // test 64-bit integer parsing 272 { 273 bool err; 274 const(ubyte)[] arr = [ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03 ]; 275 assert(popLE!ulong(arr, &err) == 0x03020100_03020100); 276 assert(!err); 277 } 278 { 279 bool err; 280 const(ubyte)[] arr = [ 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03 ]; 281 assert(popBE!long(arr, &err) == 0x00010203_00010203); 282 assert(!err); 283 } 284 285 // read out of range 286 //assert(popLE!uint(arr[1..$], &err) == 0); 287 // assert(err); 288 289 290 import dplug.core.vec; 291 auto app = makeVec!ubyte(); 292 writeBE!float(app, 1.0f); 293 writeLE!double(app, 2.0); 294 } 295 296 297 unittest 298 { 299 bool err; 300 const(ubyte)[] arr = [0, 0, 0, 0, 0, 0, 0xe0, 0x3f]; 301 double r = popLE!double(arr, &err); 302 assert(!err); 303 assert(r == 0.5); 304 305 arr = [0, 0, 0, 0, 0, 0, 0xe0, 0xbf]; 306 r = popLE!double(arr, &err); 307 assert(!err); 308 assert(r == -0.5); 309 }