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