1 module waved.utils; 2 3 import std.file, 4 std.range, 5 std.traits, 6 std.string, 7 std.format; 8 9 10 /// The simple structure currently used in wave-d. Expect changes about this. 11 struct Sound 12 { 13 int sampleRate; /// Sample rate. 14 15 deprecated("Use channels instead") alias numChannels = channels; 16 int channels; /// Number of interleaved channels in data. 17 18 deprecated("Use samples instead") alias data = samples; 19 float[] samples; /// data layout: machine endianness, interleaved channels. Contains numChannels * lengthInFrames() samples. 20 21 /// Build with interleaved data. 22 this(int sampleRate, int channels, float[] samples) 23 { 24 this.sampleRate = sampleRate; 25 this.channels = channels; 26 this.samples = samples; 27 } 28 29 /// Build with channel data. Interleave them. 30 /// channels must have the same length. 31 this(int sampleRate, float[][] planarSamples) 32 { 33 assert(planarSamples.length > 0); 34 int N = cast(int)planarSamples[0].length; 35 this.sampleRate = sampleRate; 36 this.channels = cast(int)(planarSamples.length); 37 this.samples = new float[N * channels]; 38 foreach (chan; 0..channels) 39 { 40 assert(planarSamples[chan].length == N); 41 foreach (frame; 0..N) 42 { 43 sample(chan, frame) = planarSamples[chan][frame]; 44 } 45 } 46 } 47 48 /// Returns: Length in number of frames. 49 int lengthInFrames() pure const nothrow 50 { 51 return cast(int)(samples.length) / channels; 52 } 53 54 /// Returns: Length in seconds. 55 double lengthInSeconds() pure const nothrow 56 { 57 return lengthInFrames() / cast(double)sampleRate; 58 } 59 60 /// Direct sample access. 61 ref inout(float) sample(int chan, int frame) pure inout nothrow @nogc 62 { 63 assert(cast(uint)chan < channels); 64 return samples[frame * channels + chan]; 65 } 66 67 /// Allocates a new array and put deinterleaved channel samples inside. 68 float[] channel(int chan) pure const nothrow 69 { 70 int N = lengthInFrames(); 71 float[] c = new float[N]; 72 foreach(frame; 0..N) 73 c[frame] = this.sample(chan, frame); 74 return c; 75 } 76 77 /// Returns: Another Sound with one channel (left). 78 Sound makeMono() pure const nothrow 79 { 80 assert(channels > 0); 81 Sound output; 82 output.sampleRate = this.sampleRate; 83 output.samples = new float[this.lengthInFrames()]; 84 output.channels = 1; 85 for (int i = 0; i < this.lengthInFrames(); ++i) 86 { 87 output.sample(0, i) = this.sample(0, i); // take left only 88 } 89 return output; 90 } 91 } 92 93 /// The one type of Exception thrown in this library 94 final class WavedException : Exception 95 { 96 @safe pure nothrow this(string message, string file =__FILE__, size_t line = __LINE__, Throwable next = null) 97 { 98 super(message, file, line, next); 99 } 100 } 101 102 package: 103 104 template IntegerLargerThan(int numBytes) if (numBytes >= 1 && numBytes <= 8) 105 { 106 static if (numBytes == 1) 107 alias IntegerLargerThan = ubyte; 108 else static if (numBytes == 2) 109 alias IntegerLargerThan = ushort; 110 else static if (numBytes <= 4) 111 alias IntegerLargerThan = uint; 112 else 113 alias IntegerLargerThan = ulong; 114 } 115 116 ubyte popUbyte(R)(ref R input) if (isInputRange!R) 117 { 118 if (input.empty) 119 throw new WavedException("Expected a byte, but end-of-input found."); 120 121 ubyte b = input.front; 122 input.popFront(); 123 return b; 124 } 125 126 void skipBytes(R)(ref R input, int numBytes) if (isInputRange!R) 127 { 128 for (int i = 0; i < numBytes; ++i) 129 popUbyte(input); 130 } 131 132 // Generic integer parsing 133 auto popInteger(R, int NumBytes, bool WantSigned, bool LittleEndian)(ref R input) if (isInputRange!R) 134 { 135 alias T = IntegerLargerThan!NumBytes; 136 137 T result = 0; 138 139 static if (LittleEndian) 140 { 141 for (int i = 0; i < NumBytes; ++i) 142 result |= ( cast(T)(popUbyte(input)) << (8 * i) ); 143 } 144 else 145 { 146 for (int i = 0; i < NumBytes; ++i) 147 result = (result << 8) | popUbyte(input); 148 } 149 150 static if (WantSigned) 151 { 152 // make sure the sign bit is extended to the top in case of a larger result value 153 Signed!T signedResult = cast(Signed!T)result; 154 enum bits = 8 * (T.sizeof - NumBytes); 155 static if (bits > 0) 156 { 157 signedResult = signedResult << bits; 158 signedResult = signedResult >> bits; // signed right shift, replicates sign bit 159 } 160 return signedResult; 161 } 162 else 163 return result; 164 } 165 166 // Generic integer writing 167 void writeInteger(R, int NumBytes, bool LittleEndian)(ref R output, IntegerLargerThan!NumBytes n) if (isOutputRange!(R, ubyte)) 168 { 169 alias T = IntegerLargerThan!NumBytes; 170 171 auto u = cast(Unsigned!T)n; 172 173 static if (LittleEndian) 174 { 175 for (int i = 0; i < NumBytes; ++i) 176 { 177 ubyte b = (u >> (i * 8)) & 255; 178 output.put(b); 179 } 180 } 181 else 182 { 183 for (int i = 0; i < NumBytes; ++i) 184 { 185 ubyte b = (u >> ( (NumBytes - 1 - i) * 8) ) & 255; 186 output.put(b); 187 } 188 } 189 } 190 191 // Reads a big endian integer from input. 192 T popBE(T, R)(ref R input) if (isInputRange!R) 193 { 194 return popInteger!(R, T.sizeof, isSigned!T, false)(input); 195 } 196 197 // Reads a little endian integer from input. 198 T popLE(T, R)(ref R input) if (isInputRange!R) 199 { 200 return popInteger!(R, T.sizeof, isSigned!T, true)(input); 201 } 202 203 // Writes a big endian integer to output. 204 void writeBE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte)) 205 { 206 writeInteger!(R, T.sizeof, false)(output, n); 207 } 208 209 // Writes a little endian integer to output. 210 void writeLE(T, R)(ref R output, T n) if (isOutputRange!(R, ubyte)) 211 { 212 writeInteger!(R, T.sizeof, true)(output, n); 213 } 214 215 216 alias pop24bitsLE(R) = popInteger!(R, 3, true, true); 217 218 219 // read/write 32-bits float 220 221 union float_uint 222 { 223 float f; 224 uint i; 225 } 226 227 float popFloatLE(R)(ref R input) if (isInputRange!R) 228 { 229 float_uint fi; 230 fi.i = popLE!uint(input); 231 return fi.f; 232 } 233 234 void writeFloatLE(R)(ref R output, float x) if (isOutputRange!(R, ubyte)) 235 { 236 float_uint fi; 237 fi.f = x; 238 writeLE!uint(output, fi.i); 239 } 240 241 242 // read/write 64-bits float 243 244 union double_ulong 245 { 246 double d; 247 ulong i; 248 } 249 250 float popDoubleLE(R)(ref R input) if (isInputRange!R) 251 { 252 double_ulong du; 253 du.i = popLE!ulong(input); 254 return du.d; 255 } 256 257 void writeDoubleLE(R)(ref R output, double x) if (isOutputRange!(R, ubyte)) 258 { 259 double_ulong du; 260 du.d = x; 261 writeLE!ulong(output, du.i); 262 } 263 264 // Reads RIFF chunk header. 265 void getRIFFChunkHeader(R)(ref R input, out uint chunkId, out uint chunkSize) if (isInputRange!R) 266 { 267 chunkId = popBE!uint(input); 268 chunkSize = popLE!uint(input); 269 } 270 271 // Writes RIFF chunk header (you have to count size manually for now...). 272 void writeRIFFChunkHeader(R)(ref R output, uint chunkId, size_t chunkSize) if (isOutputRange!(R, ubyte)) 273 { 274 writeBE!uint(output, cast(uint)(chunkId)); 275 writeLE!uint(output, cast(uint)(chunkSize)); 276 } 277 278 template RIFFChunkId(string id) 279 { 280 static assert(id.length == 4); 281 uint RIFFChunkId = (cast(ubyte)(id[0]) << 24) 282 | (cast(ubyte)(id[1]) << 16) 283 | (cast(ubyte)(id[2]) << 8) 284 | (cast(ubyte)(id[3])); 285 }