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.core.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 }