1 /** 2 3 Binary parsing and emitting from input ranges, or to 4 output ranges. 5 6 Copyright: Guillaume Piolat 2015-2024. 7 License: http://www.boost.org/LICENSE_1_0.txt 8 9 It is used internally by Dplug, and also by the user for 10 emitting and parsing plugin state chunks. 11 12 See_also: `futureBinState`. 13 14 */ 15 module dplug.core.binrange; 16 17 // FUTURE: Monomorphism. only allow reading from [] and 18 // writing on Vec!ubyte 19 import std.range.primitives; 20 21 import dplug.core.nogc; 22 import dplug.core.traits; 23 24 @nogc nothrow: 25 26 public: 27 28 /* Public API in this file here: 29 30 - popLE Read a little-endian integer (or FP number) 31 - popBE Read a big-endian integer (or FP number) 32 - writeLE Write a little-endian integer (or FP number) 33 - writeBE Write a big-endian integer (or FP number) 34 - skipBytes Skip a number of bytes in input stream. 35 - readRIFFChunkHeader 36 - writeRIFFChunkHeader 37 - RIFFChunkId FourCC constant helper. 38 39 Those functions operate on slices of `ubyte` for input, 40 and output ranges of `ubyte` for output only. 41 42 */ 43 44 /** 45 Skip bytes while parsing an input slice. 46 47 Params: 48 input = `ubyte` input range. 49 numBytes = Number of bytes to skip. 50 err = `true` if read error. 51 52 Returns: On success, `*err` is set to `false`. 53 On failure, `*err` is set to `true`, and the 54 input range cannot be used anymore. 55 */ 56 void skipBytes(ref const(ubyte)[] input, 57 int numBytes, 58 bool* err) 59 { 60 for (int i = 0; i < numBytes; ++i) 61 { 62 popUbyte(input, err); 63 if (*err) 64 return; 65 } 66 *err = false; 67 } 68 69 /** 70 Reads a base type from input slice. 71 `popLE` parses bytes in little-endian order. 72 `popBE` parses bytes in big-endian order. 73 74 Params: 75 input = `ubyte` input range. 76 err = `true` if read error. 77 78 Supported types: 79 `byte`, `ubyte`, `short`, `ushort`, 80 `int`, `uint`, `long`, `ulong`, 81 `float`, `double` 82 83 Returns: On failure, `*err` is set to `true`, return 0. 84 The input range cannot be used anymore. 85 */ 86 T popBE(T)(ref const(ubyte)[] input, bool* err) 87 { 88 return popFunction!(T, false)(input, err); 89 } 90 ///ditto 91 T popLE(T)(ref const(ubyte)[] input, bool* err) 92 { 93 return popFunction!(T, true)(input, err); 94 } 95 96 /** 97 Writes a big-endian/little-endian base type to output 98 range. 99 100 Params: 101 output = `ubyte` output range. 102 n = A base type to write. 103 104 Supported types: 105 `byte`, `ubyte`, `short`, `ushort`, 106 `int`, `uint`, `long`, `ulong`, 107 `float`, `double` 108 109 Warning: Doesn't report write errors. 110 */ 111 void writeBE(T, R)(ref R output, T n) 112 if (isOutputRange!(R, ubyte)) 113 { 114 writeFunction!(T, R, false)(output, n); 115 } 116 ///ditto 117 void writeLE(T, R)(ref R output, T n) 118 if (isOutputRange!(R, ubyte)) 119 { 120 writeFunction!(T, R, true)(output, n); 121 } 122 123 /** 124 Reads a [RIFF] chunk header from an input range. 125 126 Params: 127 input = `ubyte` input range. 128 chunkId = RIFF chunk id. 129 chunkSize = Chunk size. 130 err = `true` if input error. 131 132 On failure, `*err` is set to `true` 133 and `chunkId` and `chunkSize` are undefined. 134 The input range cannot be used anymore. 135 [RIFF]: http://www.daubnet.com/en/file-format-riff 136 */ 137 void readRIFFChunkHeader(ref const(ubyte)[] input, 138 out uint chunkId, 139 out uint chunkSize, 140 bool* err) 141 { 142 chunkId = popBE!uint(input, err); 143 if (*err) 144 return; 145 chunkSize = popLE!uint(input, err); 146 if (*err) 147 return; 148 *err = false; 149 } 150 151 /** 152 Reads a [RIFF] chunk header to an output range. 153 154 Params: 155 output = `ubyte` output range. 156 chunkId = RIFF chunk id. 157 chunkSize = Chunk size. 158 159 [RIFF]: http://www.daubnet.com/en/file-format-riff 160 */ 161 void writeRIFFChunkHeader(R)(ref R output, 162 uint chunkId, 163 uint chunkSize) 164 if (isOutputRange!(R, ubyte)) 165 { 166 writeBE!uint(output, chunkId); 167 writeLE!uint(output, chunkSize); 168 } 169 170 /** 171 A RIFF chunk id. Also called "FourCC". 172 173 [RIFF]: http://www.daubnet.com/en/file-format-riff 174 */ 175 template RIFFChunkId(string id) 176 { 177 static assert(id.length == 4); 178 enum uint RIFFChunkId = (cast(ubyte)(id[0]) << 24) 179 | (cast(ubyte)(id[1]) << 16) 180 | (cast(ubyte)(id[2]) << 8) 181 | (cast(ubyte)(id[3]) ); 182 } 183 184 private: 185 186 // read/write 64-bits float 187 union float_uint 188 { 189 float f; 190 uint i; 191 } 192 193 // read/write 64-bits float 194 union double_ulong 195 { 196 double f; 197 ulong i; 198 } 199 200 uint float2uint(float x) pure 201 { 202 float_uint fi; 203 fi.f = x; 204 return fi.i; 205 } 206 207 float uint2float(int x) pure 208 { 209 float_uint fi; 210 fi.i = x; 211 return fi.f; 212 } 213 214 ulong double2ulong(double x) pure 215 { 216 double_ulong fi; 217 fi.f = x; 218 return fi.i; 219 } 220 221 double ulong2double(ulong x) pure 222 { 223 double_ulong fi; 224 fi.i = x; 225 return fi.f; 226 } 227 228 private template IntegerLargerThan(int numBytes) 229 if (numBytes >= 1 && numBytes <= 8) 230 { 231 static if (numBytes == 1) 232 alias IntegerLargerThan = ubyte; 233 else static if (numBytes == 2) 234 alias IntegerLargerThan = ushort; 235 else static if (numBytes <= 4) 236 alias IntegerLargerThan = uint; 237 else 238 alias IntegerLargerThan = ulong; 239 } 240 241 ubyte popUbyte(ref const(ubyte)[] input, bool* err) 242 { 243 if (input.length == 0) 244 { 245 *err = true; 246 return 0; 247 } 248 ubyte b = input[0]; 249 input = input[1..$]; 250 return b; 251 } 252 253 auto popInteger(int NumBytes, 254 bool WantSigned, 255 bool LittleEndian) 256 (ref const(ubyte)[] input, bool* err) 257 { 258 alias T = IntegerLargerThan!NumBytes; 259 260 T result = 0; 261 262 static if (LittleEndian) 263 { 264 for (int i = 0; i < NumBytes; ++i) 265 { 266 ubyte b = popUbyte(input, err); 267 if (*err) 268 return 0; 269 result |= ( cast(T)(b) << (8 * i) ); 270 } 271 } 272 else 273 { 274 for (int i = 0; i < NumBytes; ++i) 275 { 276 ubyte b = popUbyte(input, err); 277 if (*err) 278 return 0; 279 result = cast(T)( (result << 8) | b ); 280 } 281 } 282 283 *err = false; 284 285 static if (WantSigned) 286 return cast(UnsignedToSigned!T)result; 287 else 288 return result; 289 } 290 291 void writeInteger(R, int NumBytes, bool LittleEndian) 292 (ref R output, IntegerLargerThan!NumBytes n) 293 if (isOutputRange!(R, ubyte)) 294 { 295 alias T = IntegerLargerThan!NumBytes; 296 297 static assert(isUnsignedIntegral!T); 298 auto u = cast(T)n; 299 300 static if (LittleEndian) 301 { 302 for (int i = 0; i < NumBytes; ++i) 303 { 304 ubyte b = (u >> (i * 8)) & 255; 305 output.put(b); 306 } 307 } 308 else 309 { 310 for (int i = 0; i < NumBytes; ++i) 311 { 312 ubyte b = (u >> ((NumBytes-1-i)*8)) & 255; 313 output.put(b); 314 } 315 } 316 } 317 318 void writeFunction(T, R, bool endian)(ref R o, T n) 319 if (isOutputRange!(R, ubyte)) 320 { 321 static if (isBuiltinIntegral!T) 322 writeInteger!(R, T.sizeof, endian)(o, n); 323 else static if (is(T : float)) 324 writeInteger!(R, 4, endian)(o, float2uint(n)); 325 else static if (is(T : double)) 326 writeInteger!(R, 8, endian)(o, double2ulong(n)); 327 else 328 static assert(false); 329 } 330 331 T popFunction(T, bool endian) 332 (ref const(ubyte)[] i, bool* err) 333 { 334 static if(isBuiltinIntegral!T) 335 { 336 enum Signed = isSignedIntegral!T; 337 alias F = popInteger!(T.sizeof, Signed, endian); 338 return cast(T) F(i, err); 339 } 340 else static if (is(T == float)) 341 { 342 alias F = popInteger!(float.sizeof, false, endian); 343 return uint2float(F(i, err)); 344 } 345 else static if (is(T == double)) 346 { 347 alias F = popInteger!(double.sizeof, false, endian); 348 return ulong2double(F(i, err)); 349 } 350 else 351 static assert(false); 352 } 353 354 unittest 355 { 356 static immutable ubyte[8] ARR = 357 [ 0x00, 0x01, 0x02, 0x03 , 358 0x00, 0x01, 0x02, 0x03 ]; 359 360 // test 32-bit integer parsing 361 { 362 const(ubyte)[] arr = ARR[]; 363 bool err; 364 assert(popLE!uint(arr, &err) == 0x03020100); 365 assert(!err); 366 367 assert(popBE!int(arr, &err) == 0x00010203); 368 assert(!err); 369 } 370 371 // test 64-bit integer parsing 372 { 373 bool err; 374 const(ubyte)[] arr = ARR[]; 375 assert(popLE!ulong(arr, &err)==0x03020100_03020100); 376 assert(!err); 377 } 378 { 379 bool err; 380 const(ubyte)[] arr = ARR[]; 381 assert(popBE!long(arr, &err)==0x00010203_00010203); 382 assert(!err); 383 } 384 385 import dplug.core.vec; 386 auto app = makeVec!ubyte(); 387 writeBE!float(app, 1.0f); 388 writeLE!double(app, 2.0); 389 } 390 391 392 unittest 393 { 394 static immutable ubyte[8] ARR1 = 395 [ 0, 0, 0, 0, 0, 0, 0xe0, 0x3f ]; 396 397 static immutable ubyte[8] ARR2 = 398 [ 0, 0, 0, 0, 0, 0, 0xe0, 0xbf ]; 399 400 bool err; 401 const(ubyte)[] arr = ARR1[]; 402 double r = popLE!double(arr, &err); 403 assert(!err); 404 assert(r == 0.5); 405 406 arr = ARR2[]; 407 r = popLE!double(arr, &err); 408 assert(!err); 409 assert(r == -0.5); 410 }