1 module waved.utils;
2 
3 import std.file,
4        std.range,
5        std.traits,
6        std.string,
7        std.format;
8 
9 
10 /// The simple structure currently used in wave-d. Expect changes about this.
11 struct Sound
12 {
13     int sampleRate;  /// Sample rate.
14 
15     deprecated("Use channels instead") alias numChannels = channels;
16     int channels; /// Number of interleaved channels in data.
17 
18     deprecated("Use samples instead") alias data = samples;
19     float[] samples;    /// data layout: machine endianness, interleaved channels. Contains numChannels * lengthInFrames() samples.
20 
21     /// Build with interleaved data.
22     this(int sampleRate, int channels, float[] samples)
23     {
24         this.sampleRate = sampleRate;
25         this.channels = channels;
26         this.samples = samples;
27     }
28 
29     /// Build with channel data. Interleave them.
30     /// channels must have the same length.
31     this(int sampleRate, float[][] planarSamples)
32     {
33         assert(planarSamples.length > 0);
34         int N = cast(int)planarSamples[0].length;
35         this.sampleRate = sampleRate;
36         this.channels = cast(int)(planarSamples.length);        
37         this.samples = new float[N * channels];
38         foreach (chan; 0..channels)
39         {
40             assert(planarSamples[chan].length == N);
41             foreach (frame; 0..N)
42             {
43                 sample(chan, frame) = planarSamples[chan][frame];
44             }
45         }
46     }
47 
48     /// Returns: Length in number of frames.
49     int lengthInFrames() pure const nothrow
50     {
51         return cast(int)(samples.length) / channels;
52     }
53 
54     /// Returns: Length in seconds.
55     double lengthInSeconds() pure const nothrow
56     {
57         return lengthInFrames() / cast(double)sampleRate;
58     }
59 
60     /// Direct sample access.
61     ref inout(float) sample(int chan, int frame) pure inout nothrow @nogc
62     {
63         assert(cast(uint)chan < channels);
64         return samples[frame * channels + chan];
65     }
66 
67     /// Allocates a new array and put deinterleaved channel samples inside.
68     float[] channel(int chan) pure const nothrow
69     {
70         int N = lengthInFrames();
71         float[] c = new float[N];
72         foreach(frame; 0..N)
73             c[frame] = this.sample(chan, frame);
74         return c;
75     }
76 
77     /// Returns: Another Sound with one channel (left).
78     Sound makeMono() pure const nothrow
79     {
80         assert(channels > 0);
81         Sound output;
82         output.sampleRate = this.sampleRate;
83         output.samples = new float[this.lengthInFrames()];
84         output.channels = 1;
85         for (int i = 0; i < this.lengthInFrames(); ++i)
86         {
87             output.sample(0, i) = this.sample(0, i); // take left only
88         }
89         return output;
90     }
91 }
92 
93 /// The one type of Exception thrown in this library
94 final class WavedException : Exception
95 {
96     @safe pure nothrow this(string message, string file =__FILE__, size_t line = __LINE__, Throwable next = null)
97     {
98         super(message, file, line, next);
99     }
100 }
101 
102 package:
103 
104 template IntegerLargerThan(int numBytes) if (numBytes >= 1 && numBytes <= 8)
105 {
106     static if (numBytes == 1)
107         alias IntegerLargerThan = ubyte;
108     else static if (numBytes == 2)
109         alias IntegerLargerThan = ushort;
110     else static if (numBytes <= 4)
111         alias IntegerLargerThan = uint;
112     else
113         alias IntegerLargerThan = ulong;
114 }
115 
116 ubyte popUbyte(R)(ref R input) if (isInputRange!R)
117 {
118     if (input.empty)
119         throw new WavedException("Expected a byte, but end-of-input found.");
120 
121     ubyte b = input.front;
122     input.popFront();
123     return b;
124 }
125 
126 void skipBytes(R)(ref R input, int numBytes) if (isInputRange!R)
127 {
128     for (int i = 0; i < numBytes; ++i)
129         popUbyte(input);
130 }
131 
132 // Generic integer parsing
133 auto popInteger(R, int NumBytes, bool WantSigned, bool LittleEndian)(ref R input) if (isInputRange!R)
134 {
135     alias T = IntegerLargerThan!NumBytes;
136 
137     T result = 0;
138 
139     static if (LittleEndian)
140     {
141         for (int i = 0; i < NumBytes; ++i)
142             result |= ( cast(T)(popUbyte(input)) << (8 * i) );
143     }
144     else
145     {
146         for (int i = 0; i < NumBytes; ++i)
147             result = (result << 8) | popUbyte(input);
148     }
149 
150     static if (WantSigned)
151     {
152         // make sure the sign bit is extended to the top in case of a larger result value
153         Signed!T signedResult = cast(Signed!T)result;
154         enum bits = 8 * (T.sizeof - NumBytes);
155         static if (bits > 0)
156         {
157             signedResult = signedResult << bits;
158             signedResult = signedResult >> bits; // signed right shift, replicates sign bit
159         }
160         return signedResult;
161     }
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     auto u = cast(Unsigned!T)n;
172 
173     static if (LittleEndian)
174     {
175         for (int i = 0; i < NumBytes; ++i)
176         {
177             ubyte b = (u >> (i * 8)) & 255;
178             output.put(b);
179         }
180     }
181     else
182     {
183         for (int i = 0; i < NumBytes; ++i)
184         {
185             ubyte b = (u >> ( (NumBytes - 1 - i) * 8) ) & 255;
186             output.put(b);
187         }
188     }
189 }
190 
191 // Reads a big endian integer from input.
192 T popBE(T, R)(ref R input) if (isInputRange!R)
193 {
194     return popInteger!(R, T.sizeof, isSigned!T, false)(input);
195 }
196 
197 // Reads a little endian integer from input.
198 T popLE(T, R)(ref R input) if (isInputRange!R)
199 {
200     return popInteger!(R, T.sizeof, isSigned!T, true)(input);
201 }
202 
203 // Writes a big endian integer to output.
204 void writeBE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte))
205 {
206     writeInteger!(R, T.sizeof, false)(output, n);
207 }
208 
209 // Writes a little endian integer to output.
210 void writeLE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte))
211 {
212     writeInteger!(R, T.sizeof, true)(output, n);
213 }
214 
215 
216 alias pop24bitsLE(R) = popInteger!(R, 3, true, true);
217 
218 
219 // read/write 32-bits float
220 
221 union float_uint
222 {
223     float f;
224     uint i;
225 }
226 
227 float popFloatLE(R)(ref R input) if (isInputRange!R)
228 {
229     float_uint fi;
230     fi.i = popLE!uint(input);
231     return fi.f;
232 }
233 
234 void writeFloatLE(R)(ref R output, float x) if (isOutputRange!(R, ubyte))
235 {
236     float_uint fi;
237     fi.f = x;
238     writeLE!uint(output, fi.i);
239 }
240 
241 
242 // read/write 64-bits float
243 
244 union double_ulong
245 {
246     double d;
247     ulong i;
248 }
249 
250 float popDoubleLE(R)(ref R input) if (isInputRange!R)
251 {
252     double_ulong du;
253     du.i = popLE!ulong(input);
254     return du.d;
255 }
256 
257 void writeDoubleLE(R)(ref R output, double x) if (isOutputRange!(R, ubyte))
258 {
259     double_ulong du;
260     du.d = x;
261     writeLE!ulong(output, du.i);
262 }
263 
264 // Reads RIFF chunk header.
265 void getRIFFChunkHeader(R)(ref R input, out uint chunkId, out uint chunkSize) if (isInputRange!R)
266 {
267     chunkId = popBE!uint(input);
268     chunkSize = popLE!uint(input);
269 }
270 
271 // Writes RIFF chunk header (you have to count size manually for now...).
272 void writeRIFFChunkHeader(R)(ref R output, uint chunkId, size_t chunkSize) if (isOutputRange!(R, ubyte))
273 {
274     writeBE!uint(output, cast(uint)(chunkId));
275     writeLE!uint(output, cast(uint)(chunkSize));
276 }
277 
278 template RIFFChunkId(string id)
279 {
280     static assert(id.length == 4);
281     uint RIFFChunkId = (cast(ubyte)(id[0]) << 24) 
282                      | (cast(ubyte)(id[1]) << 16)
283                      | (cast(ubyte)(id[2]) << 8)
284                      | (cast(ubyte)(id[3]));
285 }