1 /**
2  * 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 <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.sound.riff;
15 
16 import std.algorithm;
17 import std.conv;
18 import std.range;
19 
20 struct ValueReprRange(T)
21 {
22 	ubyte[T.sizeof] bytes;
23 	size_t p;
24 
25 	this(ref T t)
26 	{
27 		bytes[] = (cast(ubyte[])((&t)[0..1]))[];
28 	}
29 
30 	@property ubyte front() { return bytes[p]; }
31 	void popFront() { p++; }
32 	@property bool empty() { return p == T.sizeof; }
33 	@property size_t length() { return T.sizeof - p; }
34 }
35 
36 auto valueReprRange(T)(auto ref T t) { return ValueReprRange!T(t); }
37 
38 auto fourCC(char[4] name)
39 {
40 	return valueReprRange(name);
41 }
42 
43 auto riffChunk(R)(char[4] name, R data)
44 {
45 	return chain(
46 		fourCC(name),
47 		valueReprRange(data.length.to!uint),
48 		data
49 	);
50 }
51 
52 struct WaveFmt
53 {
54 	ushort format;
55 	ushort numChannels;
56 	uint sampleRate;
57 	uint byteRate;
58 	ushort blockAlign;
59 	ushort bitsPerSample;
60 }
61 
62 auto makeRiff(R)(R r, uint sampleRate = 44100)
63 {
64 	static if (!is(typeof(r.front) == struct))
65 	{
66 		struct Mono { typeof(r.front) sample; }
67 		return makeRiff(r.map!(s => Mono(s)), sampleRate);
68 	}
69 	else
70 	{
71 		enum numChannels = r.front.tupleof.length;
72 		auto bytesPerSample = r.front.tupleof[0].sizeof;
73 		auto bitsPerSample = bytesPerSample * 8;
74 
75 		return riffChunk("RIFF",
76 			chain(
77 				fourCC("WAVE"),
78 				riffChunk("fmt ",
79 					valueReprRange(WaveFmt(
80 						1, // PCM
81 						to!ushort(numChannels),
82 						sampleRate,
83 						to!uint  (sampleRate * bytesPerSample * numChannels),
84 						to!ushort(bytesPerSample * numChannels),
85 						to!ushort(bitsPerSample),
86 					)),
87 				),
88 				riffChunk("data",
89 					r.map!(s => valueReprRange(s)).joiner.takeExactly(r.length * r.front.sizeof),
90 				),
91 			),
92 		);
93 	}
94 }