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 		alias ChannelSample = typeof(r.front[0]);
66 		auto bytesPerSample = ChannelSample.sizeof;
67 		auto bitsPerSample = bytesPerSample * 8;
68 		static if (is(ChannelSample : long))
69 			enum format = 1; // Integer PCM
70 		else
71 		static if (is(ChannelSample : real))
72 			enum format = 3; // Floating-point PCM
73 		else
74 			static assert(false, "Unknown sample format: " ~ Sample.stringof);
75 
76 		return riffChunk("RIFF",
77 			chain(
78 				fourCC("WAVE"),
79 				riffChunk("fmt ",
80 					valueReprRange(WaveFmt(
81 						format,
82 						to!ushort(numChannels),
83 						sampleRate,
84 						to!uint  (sampleRate * bytesPerSample * numChannels),
85 						to!ushort(bytesPerSample * numChannels),
86 						to!ushort(bitsPerSample),
87 					)),
88 				),
89 				riffChunk("data",
90 					r.map!(s => valueReprRange(s)).joiner.takeExactly(r.length * r.front.sizeof),
91 				),
92 			),
93 		);
94 	}
95 }