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 }