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 }