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 }