1 /* 2 Copyright (c) 2015-2021 Timur Gafarov 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 29 /** 30 * Uncompressed RIFF/WAV encoder and decoder 31 * 32 * Copyright: Timur Gafarov 2015-2021. 33 * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0). 34 * Authors: Timur Gafarov 35 */ 36 module dlib.audio.io.wav; 37 38 //version = WAVDebug; // Uncomment to see debug messages 39 40 version(WAVDebug) 41 { 42 import std.stdio; 43 } 44 import dlib.core.stream; 45 import dlib.filesystem.local; 46 import dlib.audio.sample; 47 import dlib.audio.sound; 48 49 /** 50 * Simple RIFF/WAV decoder. Decodes WAV from stream using provided sound factory 51 */ 52 GenericSound loadWAV(InputStream istrm, GenericSoundFactory gsf) 53 { 54 char[4] magic; 55 istrm.fillArray(magic); 56 assert(magic == "RIFF"); 57 58 int chunkSize; 59 istrm.readLE(&chunkSize); 60 version(WAVDebug) 61 { 62 writeln(chunkSize); 63 } 64 65 char[4] format; 66 istrm.fillArray(format); 67 version(WAVDebug) 68 { 69 writeln(format); 70 } 71 assert(format == "WAVE"); 72 73 char[4] fmtSubchunkID; // fmt 74 istrm.fillArray(fmtSubchunkID); 75 76 int fmtSubchunkSize; 77 istrm.readLE(&fmtSubchunkSize); 78 79 short audioFormat; 80 short numChannels; 81 int sampleRate; 82 int byteRate; 83 short blockAlign; 84 short bitsPerSample; 85 86 istrm.readLE(&audioFormat); 87 istrm.readLE(&numChannels); 88 istrm.readLE(&sampleRate); 89 istrm.readLE(&byteRate); 90 istrm.readLE(&blockAlign); 91 istrm.readLE(&bitsPerSample); 92 93 version(WAVDebug) 94 { 95 writefln("Format: %s", audioFormat); 96 writefln("Number of channels: %s", numChannels); 97 writefln("Sample rate: %s Hz", sampleRate); 98 writefln("Byte rate: %s", byteRate); 99 writefln("Block align: %s", blockAlign); 100 writefln("Bits per sample: %s", bitsPerSample); 101 } 102 103 char[4] dataSubchunkID; // data 104 istrm.fillArray(dataSubchunkID); 105 106 int dataSubchunkSize; 107 istrm.readLE(&dataSubchunkSize); 108 version(WAVDebug) 109 { 110 writeln(dataSubchunkSize); 111 } 112 int numSamples = dataSubchunkSize / (numChannels * bitsPerSample/8); 113 version(WAVDebug) 114 { 115 writefln("Number of samples: %s", numSamples); 116 writefln("Duration: %s", (cast(float)numSamples) / sampleRate); 117 } 118 119 SampleFormat f; 120 if (bitsPerSample == 8) 121 { 122 f = SampleFormat.U8; 123 } 124 else if (bitsPerSample == 16) 125 { 126 f = SampleFormat.S16; 127 } 128 129 GenericSound gs; 130 gs = gsf.createSound( 131 dataSubchunkSize, 132 numSamples, 133 (cast(double)numSamples) / sampleRate, 134 numChannels, 135 sampleRate, 136 bitsPerSample, 137 f 138 ); 139 istrm.fillArray(gs.data); 140 141 return gs; 142 } 143 144 /** 145 * Decodes WAV from stream using default sound factory 146 */ 147 GenericSound loadWAV(InputStream istrm) 148 { 149 return loadWAV(istrm, defaultGenericSoundFactory); 150 } 151 152 /** 153 * Decodes WAV from file 154 */ 155 GenericSound loadWAV(string filename) 156 { 157 auto istrm = openForInput(filename); 158 auto snd = loadWAV(istrm); 159 istrm.close(); 160 return snd; 161 } 162 163 /** 164 * Simple RIFF/WAV encoder. Encodes WAV to stream 165 */ 166 void saveWAV(Sound snd, OutputStream ostrm) 167 { 168 string magic = "RIFF"; 169 ostrm.writeBytes(magic.ptr, 4); 170 171 int chunkSize = 36 + cast(int)snd.data.length; // total file size - 8 172 ostrm.writeLE(chunkSize); 173 174 string format = "WAVE"; 175 ostrm.writeBytes(format.ptr, 4); 176 177 string fmt = "fmt "; 178 ostrm.writeBytes(fmt.ptr, 4); 179 180 int fmtSubchunkSize = 16; 181 ostrm.writeLE(fmtSubchunkSize); 182 183 short audioFormat = 1; 184 short numChannels = cast(short)snd.channels; 185 assert(numChannels == 1 || numChannels == 2); 186 int sampleRate = snd.sampleRate; // samples per second 187 int byteRate = snd.sampleRate * snd.sampleSize; // bytes per second 188 short blockAlign = cast(short)snd.sampleSize; // bytes per sample 189 short bitsPerSample; 190 if (snd.sampleFormat == SampleFormat.U8) 191 bitsPerSample = 8; 192 else if (snd.sampleFormat == SampleFormat.S16) 193 bitsPerSample = 16; 194 else 195 { 196 assert(0, "Illegal sample format to use with RIFF/WAV"); 197 } 198 199 ostrm.writeLE(audioFormat); 200 ostrm.writeLE(numChannels); 201 ostrm.writeLE(sampleRate); 202 ostrm.writeLE(byteRate); 203 ostrm.writeLE(blockAlign); 204 ostrm.writeLE(bitsPerSample); 205 206 string dataID = "data"; 207 ostrm.writeBytes(dataID.ptr, 4); 208 209 int dataSubchunkSize = cast(int)snd.data.length; 210 ostrm.writeLE(dataSubchunkSize); 211 ostrm.writeArray(snd.data); 212 } 213 214 /** 215 * Encodes WAV to file 216 */ 217 void saveWAV(Sound snd, string filename) 218 { 219 auto ostrm = openForOutput(filename); 220 saveWAV(snd, ostrm); 221 ostrm.close(); 222 }