1 /**
2  * Read support for the RIFF file format (used in .wav files).
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.sound.riff.reader;
15 
16 import std.algorithm.searching;
17 import std.exception;
18 
19 import ae.utils.sound.riff.common;
20 
21 /// RIFF chunk.
22 struct Chunk
23 {
24 	/// Chunk header.
25 	struct Header
26 	{
27 		char[4] name;  ///
28 		uint length;   ///
29 		ubyte[0] data; /// Chunk data follows.
30 	}
31 
32 	Header* header; /// ditto
33 
34 	/// Parse and consume one chunk from `data`.
35 	this(ref ubyte[] data)
36 	{
37 		enforce(data.length >= Header.sizeof);
38 		header = cast(Header*)data;
39 		data = data[Header.sizeof..$];
40 
41 		enforce(data.length >= header.length);
42 		data = data[header.length..$];
43 	}
44 
45 	char[4] name() { return header.name; } /// Chunk name (from header).
46 	ubyte[] data() { return header.data.ptr[0..header.length]; } /// Chunk data.
47 }
48 
49 /// Reads chunks from an array of bytes.
50 struct Chunks
51 {
52 	ubyte[] data; /// The array of bytes.
53 	/// Range primitives.
54 	bool empty() { return data.length == 0; }
55 	Chunk front() { auto cData = data; return Chunk(cData); } /// ditto
56 	void popFront() { auto c = Chunk(data); } /// ditto
57 }
58 
59 /// Return a `Chunk` from `data`.
60 auto readRiff(ubyte[] data)
61 {
62 	return Chunk(data);
63 }
64 
65 /// Get samples from a parsed RIFF file.
66 /// The format is expected to match `T`.
67 auto getWave(T)(Chunk chunk)
68 {
69 	enforce(chunk.name == "RIFF", "Unknown file format");
70 	auto riffData = chunk.data;
71 	enforce(riffData.skipOver("WAVE"), "Unknown RIFF contents");
72 	WaveFmt fmt = (cast(WaveFmt[])Chunks(riffData).find!(c => c.name == "fmt ").front.data)[0];
73 	enforce(fmt.format == 1, "Unknown WAVE format");
74 	enforce(fmt.sampleRate * T.sizeof == fmt.byteRate, "Format mismatch");
75 	auto data = Chunks(riffData).find!(c => c.name == "data").front.data;
76 	return cast(T[])data;
77 }