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 <ae@cy.md>
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 private 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 private auto valueReprRange(T)(auto ref T t) { return ValueReprRange!T(t); }
40 
41 private auto fourCC(char[4] name)
42 {
43 	return valueReprRange(name);
44 }
45 
46 /// Serialize a chunk and data as a range of bytes.
47 auto riffChunk(R)(char[4] name, R data)
48 {
49 	return chain(
50 		fourCC(name),
51 		valueReprRange(data.length.to!uint),
52 		data
53 	);
54 }
55 
56 /// Serialize a range of samples into a range of bytes representing a RIFF file.
57 auto makeRiff(R)(R r, uint sampleRate = 44100)
58 {
59 	alias Sample = typeof(r.front);
60 	static if (!is(Sample C : C[channels_], size_t channels_))
61 		return makeRiff(r.map!(s => [s].staticArray), sampleRate);
62 	else
63 	{
64 		enum numChannels = r.front.length;
65 		auto bytesPerSample = r.front[0].sizeof;
66 		auto bitsPerSample = bytesPerSample * 8;
67 
68 		return riffChunk("RIFF",
69 			chain(
70 				fourCC("WAVE"),
71 				riffChunk("fmt ",
72 					valueReprRange(WaveFmt(
73 						1, // PCM
74 						to!ushort(numChannels),
75 						sampleRate,
76 						to!uint  (sampleRate * bytesPerSample * numChannels),
77 						to!ushort(bytesPerSample * numChannels),
78 						to!ushort(bitsPerSample),
79 					)),
80 				),
81 				riffChunk("data",
82 					r.map!(s => valueReprRange(s)).joiner.takeExactly(r.length * r.front.sizeof),
83 				),
84 			),
85 		);
86 	}
87 }