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