]> git.mxchange.org Git - simgear.git/blob - simgear/sound/readwav.cxx
Reset: allow re-init of Nasal Ghosts.
[simgear.git] / simgear / sound / readwav.cxx
1 // Copyright (C) 2012  James Turner - zakalawe@mac.com
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 //
17
18 // adapted from the freealut sources, especially alutBufferData.c, alutLoader.c
19 // and alutCodec.c (freealut is also LGPL licensed)
20
21 #include "readwav.hxx"
22
23 #include <cassert>
24 #include <cstdlib>
25
26 #include <zlib.h> // for gzXXX functions
27
28 #include <simgear/misc/sg_path.hxx>
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/misc/stdint.hxx>
31 #include <simgear/structure/exception.hxx>
32
33 namespace 
34 {
35   class Buffer {
36   public:
37     ALvoid* data;
38     ALenum format;
39     ALsizei length;
40     ALfloat frequency;
41     SGPath path;
42       
43     Buffer() : data(NULL), format(AL_NONE), length(0), frequency(0.0f) {}
44     
45     ~Buffer()
46     {
47       if (data) {
48         free(data);
49       }
50     }
51   };
52   
53   ALenum formatConstruct(ALint numChannels, ALint bitsPerSample)
54   {
55     switch (numChannels)
56       {
57       case 1:
58         switch (bitsPerSample) {
59           case 8: return  AL_FORMAT_MONO8;
60           case 16: return AL_FORMAT_MONO16;
61         }
62         break;
63       case 2:
64         switch (bitsPerSample) {
65           case 8: return AL_FORMAT_STEREO8;
66           case 16: return AL_FORMAT_STEREO16;
67           }
68         break;
69       }
70     return AL_NONE;
71   }
72   
73 // function prototype for decoding audio data
74   typedef void Codec(Buffer* buf);
75   
76   void codecLinear(Buffer* /*buf*/)
77   {
78   }
79   
80   void codecPCM16 (Buffer* buf)
81   {
82     // always byte-swaps here; is this a good idea?
83     uint16_t *d = (uint16_t *) buf->data;
84     size_t i, l = buf->length / 2;
85     for (i = 0; i < l; i++) {
86       *d = sg_bswap_16(*d);
87     }
88   }
89   
90  /*
91   * From: http://www.multimedia.cx/simpleaudio.html#tth_sEc6.1
92   */
93 int16_t mulaw2linear (uint8_t mulawbyte)
94  {
95    static const int16_t exp_lut[8] = {
96      0, 132, 396, 924, 1980, 4092, 8316, 16764
97    };
98    int16_t sign, exponent, mantissa, sample;
99    mulawbyte = ~mulawbyte;
100    sign = (mulawbyte & 0x80);
101    exponent = (mulawbyte >> 4) & 0x07;
102    mantissa = mulawbyte & 0x0F;
103    sample = exp_lut[exponent] + (mantissa << (exponent + 3));
104    return sign ? -sample : sample;
105  }
106
107  void codecULaw (Buffer* b)
108  {
109    uint8_t *d = (uint8_t *) b->data;
110    size_t newLength = b->length * 2;
111    int16_t *buf = (int16_t *) malloc(newLength);
112    if (buf == NULL)
113      throw sg_exception("malloc failed decoing ULaw WAV file");
114    
115    for (ALsizei i = 0; i < b->length; i++) {
116        buf[i] = mulaw2linear(d[i]);
117     }
118     
119    free(b->data);
120    b->data = buf;
121    b->length = newLength;
122  }
123  
124   bool gzSkip(gzFile fd, int skipCount)
125   {
126       int r = gzseek(fd, skipCount, SEEK_CUR);
127       return (r >= 0);
128   }
129   
130   const int32_t WAV_RIFF_4CC = 0x52494646; // 'RIFF'
131   const int32_t WAV_WAVE_4CC = 0x57415645; // 'WAVE'
132   const int32_t WAV_DATA_4CC = 0x64617461; // 'data'
133   const int32_t WAV_FORMAT_4CC = 0x666d7420; // 'fmt '
134   
135   template<class T>
136   bool wavReadBE(gzFile fd, T& value)
137   {
138     if (gzread(fd, &value, sizeof(T)) != sizeof(T))
139       return false;
140     
141     if (sgIsLittleEndian()) 
142       sgEndianSwap(&value);
143     
144     return true;
145   }
146
147   template<class T>
148   bool wavReadLE(gzFile fd, T& value)
149   {
150     if (gzread(fd, &value, sizeof(T)) != sizeof(T))
151       return false;
152     
153     if (sgIsBigEndian()) 
154       sgEndianSwap(&value);
155     
156     return true;
157   }
158   
159   void loadWavFile(gzFile fd, Buffer* b)
160   {
161     assert(b->data == NULL);
162     
163     bool found_header = false;
164     uint32_t chunkLength;
165     int32_t magic;
166     uint16_t audioFormat;
167     uint16_t numChannels;
168     uint32_t samplesPerSecond;
169     uint32_t byteRate;
170     uint16_t blockAlign;
171     uint16_t bitsPerSample;
172     Codec *codec = codecLinear;
173
174     if (!wavReadBE(fd, magic))
175       throw sg_io_exception("corrupt or truncated WAV data", b->path);
176     
177     if (magic != WAV_RIFF_4CC) {
178       throw sg_io_exception("not a .wav file", b->path);
179     }
180
181     if (!wavReadLE(fd, chunkLength) || !wavReadBE(fd, magic))
182       throw sg_io_exception("corrupt or truncated WAV data", b->path);
183
184     if (magic != WAV_WAVE_4CC)      /* "WAVE" */
185     {
186         throw sg_io_exception("unrecognized WAV magic", b->path);
187     }
188
189     while (1) {
190         if (!wavReadBE(fd, magic) || !wavReadLE(fd, chunkLength))
191             throw sg_io_exception("corrupt or truncated WAV data", b->path);
192
193         if (magic == WAV_FORMAT_4CC)  /* "fmt " */
194         {
195             found_header = true;
196             if (chunkLength < 16) {
197               throw sg_io_exception("corrupt or truncated WAV data", b->path);
198             }
199
200             if (!wavReadLE (fd, audioFormat) ||
201                 !wavReadLE (fd, numChannels) ||
202                 !wavReadLE (fd, samplesPerSecond) ||
203                 !wavReadLE (fd, byteRate) ||
204                 !wavReadLE (fd, blockAlign) ||
205                 !wavReadLE (fd, bitsPerSample))
206               {
207                 throw sg_io_exception("corrupt or truncated WAV data", b->path);
208               }
209
210             if (!gzSkip(fd, chunkLength - 16))
211                 throw sg_io_exception("corrupt or truncated WAV data", b->path);
212
213             switch (audioFormat)
214               {
215               case 1:            /* PCM */
216                 codec = (bitsPerSample == 8 || sgIsLittleEndian()) ? codecLinear : codecPCM16;
217                 break;
218               case 7:            /* uLaw */
219                 bitsPerSample *= 2;       /* ToDo: ??? */
220                 codec = codecULaw;
221                 break;
222               default:
223                 throw sg_io_exception("unsupported WAV encoding", b->path);
224               }
225               
226               b->frequency = samplesPerSecond;
227               b->format = formatConstruct(numChannels, bitsPerSample);
228         } else if (magic == WAV_DATA_4CC) {
229             if (!found_header) {
230                 /* ToDo: A bit wrong to check here, fmt chunk could come later... */
231                 throw sg_io_exception("corrupt or truncated WAV data", b->path);
232             }
233             
234             b->data = malloc(chunkLength);
235             b->length = chunkLength;
236             size_t read = gzread(fd, b->data, chunkLength);
237             if (read != chunkLength) {
238                 throw sg_io_exception("insufficent data reading WAV file", b->path);
239             }
240             
241             break;
242         } else {
243             if (!gzSkip(fd, chunkLength))
244               throw sg_io_exception("corrupt or truncated WAV data", b->path);
245         }
246
247         if ((chunkLength & 1) && !gzeof(fd) && !gzSkip(fd, 1))
248           throw sg_io_exception("corrupt or truncated WAV data", b->path);
249       } // of file chunk parser loop
250       
251       codec(b); // might throw if something really bad occurs
252   } // of loadWav function
253   
254 } // of anonymous namespace
255
256 namespace simgear
257 {
258
259 ALvoid* loadWAVFromFile(const SGPath& path, ALenum& format, ALsizei& size, ALfloat& freqf)
260 {
261   if (!path.exists()) {
262     throw sg_io_exception("loadWAVFromFile: file not found", path);
263   }
264   
265   Buffer b;
266     b.path = path;
267     
268   gzFile fd;
269   fd = gzopen(path.c_str(), "rb");
270   if (!fd) {
271     throw sg_io_exception("loadWAVFromFile: unable to open file", path);
272   }
273   
274     loadWavFile(fd, &b);
275   ALvoid* data = b.data;
276     b.data = NULL; // don't free when Buffer does out of scope
277     format = b.format;
278     size = b.length;
279     freqf = b.frequency;
280   
281   gzclose(fd);
282   return data;
283 }
284
285 ALuint createBufferFromFile(const SGPath& path)
286 {
287   ALuint buffer = -1;
288 #ifdef ENABLE_SOUND
289   ALenum format;
290   ALsizei size;
291   ALfloat sampleFrequency;
292   ALvoid* data = loadWAVFromFile(path, format, size, sampleFrequency);
293   assert(data);
294   
295   alGenBuffers(1, &buffer);
296   if (alGetError() != AL_NO_ERROR) {
297     free(data);
298     throw sg_io_exception("OpenAL buffer allocation failed", sg_location(path.str()));
299   }
300     
301   alBufferData (buffer, format, data, size, (ALsizei) sampleFrequency);
302   if (alGetError() != AL_NO_ERROR) {
303     alDeleteBuffers(1, &buffer);
304     free(data);
305     throw sg_io_exception("OpenAL setting buffer data failed", sg_location(path.str()));
306   }
307 #endif 
308   return buffer;
309 }
310
311 } // of namespace simgear