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 }