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