01a4032c38c31381fd3a7fa8aada3e2c0ba7da7f
[supertux.git] / src / audio / sound_file.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 /** Used SDL_mixer and glest source as reference */
21 #include <config.h>
22
23 #include "sound_file.hpp"
24
25 #include <stdio.h>
26 #include <stdint.h>
27 #include <algorithm>
28 #include <stdexcept>
29 #include <sstream>
30 #include <assert.h>
31 #include <physfs.h>
32 #include <vorbis/codec.h>
33 #include <vorbis/vorbisfile.h>
34 #include "log.hpp"
35
36 class WavSoundFile : public SoundFile
37 {
38 public:
39   WavSoundFile(PHYSFS_file* file);
40   ~WavSoundFile();
41
42   size_t read(void* buffer, size_t buffer_size);
43   void reset();
44
45 private:
46   PHYSFS_file* file;
47   
48   PHYSFS_sint64 datastart;
49 };
50
51 static inline uint32_t read32LE(PHYSFS_file* file)
52 {
53   uint32_t result;
54   if(PHYSFS_readULE32(file, &result) == 0)
55     throw std::runtime_error("file too short");
56
57   return result;
58 }
59
60 static inline uint16_t read16LE(PHYSFS_file* file)
61 {
62   uint16_t result;
63   if(PHYSFS_readULE16(file, &result) == 0)
64     throw std::runtime_error("file too short");
65
66   return result;
67 }
68
69 WavSoundFile::WavSoundFile(PHYSFS_file* file)
70 {
71   this->file = file;
72
73   char magic[4];
74   if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1)
75     throw std::runtime_error("Couldn't read file magic (not a wave file)");
76   if(strncmp(magic, "RIFF", 4) != 0) {
77     log_debug << "MAGIC: " << magic << std::endl;
78     throw std::runtime_error("file is not a RIFF wav file");
79   }
80
81   uint32_t wavelen = read32LE(file);
82   (void) wavelen;
83   
84   if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1)
85     throw std::runtime_error("Couldn't read chunk header (not a wav file?)");
86   if(strncmp(magic, "WAVE", 4) != 0)
87     throw std::runtime_error("file is not a valid RIFF/WAVE file");
88
89   char chunkmagic[4];
90   uint32_t chunklen;
91
92   // search audio data format chunk
93   do {
94     if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1)
95       throw std::runtime_error("EOF while searching format chunk");    
96     chunklen = read32LE(file);
97     
98     if(strncmp(chunkmagic, "fmt ", 4) == 0)
99       break;
100
101     if(strncmp(chunkmagic, "fact", 4) == 0
102         || strncmp(chunkmagic, "LIST", 4) == 0) {
103       // skip chunk
104       if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0)
105         throw std::runtime_error("EOF while searching fmt chunk");
106     } else {
107       throw std::runtime_error("complex WAVE files not supported");
108     }
109   } while(true); 
110
111   if(chunklen < 16)
112     throw std::runtime_error("Format chunk too short");
113  
114   // parse format
115   uint16_t encoding = read16LE(file);
116   if(encoding != 1)
117     throw std::runtime_error("only PCM encoding supported");
118   channels = read16LE(file);
119   rate = read32LE(file);
120   uint32_t byterate = read32LE(file);
121   (void) byterate;
122   uint16_t blockalign = read16LE(file);
123   (void) blockalign;
124   bits_per_sample = read16LE(file);
125
126   if(chunklen > 16) {
127     if(PHYSFS_seek(file, PHYSFS_tell(file) + (chunklen-16)) == 0)
128       throw std::runtime_error("EOF while reading reast of format chunk");
129   }
130
131   // set file offset to DATA chunk data
132   do {
133     if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1)
134       throw std::runtime_error("EOF while searching data chunk");    
135     chunklen = read32LE(file);
136
137     if(strncmp(chunkmagic, "data", 4) == 0)
138       break;
139
140     // skip chunk
141     if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0)
142       throw std::runtime_error("EOF while searching fmt chunk");
143   } while(true);
144
145   datastart = PHYSFS_tell(file);
146   size = static_cast<size_t> (chunklen);
147 }
148
149 WavSoundFile::~WavSoundFile()
150 {
151   PHYSFS_close(file);
152 }
153
154 void
155 WavSoundFile::reset()
156 {
157   if(PHYSFS_seek(file, datastart) == 0)
158     throw std::runtime_error("Couldn't seek to data start");
159 }
160
161 size_t
162 WavSoundFile::read(void* buffer, size_t buffer_size)
163 {
164   PHYSFS_sint64 end = datastart + size;
165   PHYSFS_sint64 cur = PHYSFS_tell(file);
166   if(cur >= end)
167     return 0;
168   
169   size_t readsize = std::min(static_cast<size_t> (end - cur), buffer_size);
170   if(PHYSFS_read(file, buffer, readsize, 1) != 1)
171     throw std::runtime_error("read error while reading samples");
172
173 #ifdef WORDS_BIGENDIAN
174   if (bits_per_sample != 16)
175     return readsize;
176   char *tmp = (char*)buffer;
177
178   size_t i;
179   char c;
180   for (i = 0; i < readsize / 2; i++)
181   {
182     c          = tmp[2*i];
183     tmp[2*i]   = tmp[2*i+1];
184     tmp[2*i+1] = c;
185   }
186
187   buffer = tmp;
188 #endif
189
190   return readsize;
191 }
192
193 //---------------------------------------------------------------------------
194
195 class OggSoundFile : public SoundFile
196 {
197 public:
198   OggSoundFile(PHYSFS_file* file);
199   ~OggSoundFile();
200
201   size_t read(void* buffer, size_t buffer_size);
202   void reset();
203
204 private:
205   static size_t cb_read(void* ptr, size_t size, size_t nmemb, void* source);
206   static int cb_seek(void* source, ogg_int64_t offset, int whence);
207   static int cb_close(void* source);
208   static long cb_tell(void* source);
209   
210   PHYSFS_file* file;
211   OggVorbis_File vorbis_file;
212 };
213
214 OggSoundFile::OggSoundFile(PHYSFS_file* file)
215 {
216   this->file = file;
217
218   ov_callbacks callbacks = { cb_read, cb_seek, cb_close, cb_tell };
219   ov_open_callbacks(file, &vorbis_file, 0, 0, callbacks);
220
221   vorbis_info* vi = ov_info(&vorbis_file, -1);
222   channels = vi->channels;
223   rate = vi->rate;
224   bits_per_sample = 16;
225   size = static_cast<size_t> (ov_pcm_total(&vorbis_file, -1) * 2);
226 }
227
228 OggSoundFile::~OggSoundFile()
229 {
230   ov_clear(&vorbis_file);
231 }
232
233 size_t
234 OggSoundFile::read(void* _buffer, size_t buffer_size)
235 {
236   char* buffer = reinterpret_cast<char*> (_buffer);
237   int section = 0;
238   size_t totalBytesRead= 0;
239
240   while(buffer_size>0){
241     long bytesRead 
242       = ov_read(&vorbis_file, buffer, static_cast<int> (buffer_size),
243 #ifdef WORDS_BIGENDIAN
244 1,
245 #else
246 0,
247 #endif
248           2, 1, &section);
249     if(bytesRead==0){
250       break;
251     }
252     buffer_size -= bytesRead;
253     buffer += bytesRead;
254     totalBytesRead += bytesRead;
255   }
256   
257   return totalBytesRead;
258 }
259
260 void
261 OggSoundFile::reset()
262 {
263   ov_raw_seek(&vorbis_file, 0);
264 }
265
266 size_t
267 OggSoundFile::cb_read(void* ptr, size_t size, size_t nmemb, void* source)
268 {
269   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
270   
271   PHYSFS_sint64 res 
272     = PHYSFS_read(file, ptr, static_cast<PHYSFS_uint32> (size),
273         static_cast<PHYSFS_uint32> (nmemb));
274   if(res <= 0)
275     return 0;
276
277   return static_cast<size_t> (res);
278 }
279
280 int
281 OggSoundFile::cb_seek(void* source, ogg_int64_t offset, int whence)
282 {
283   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
284
285   switch(whence) {
286     case SEEK_SET:
287       if(PHYSFS_seek(file, static_cast<PHYSFS_uint64> (offset)) == 0)
288         return -1;
289       break;
290     case SEEK_CUR:
291       if(PHYSFS_seek(file, PHYSFS_tell(file) + offset) == 0)
292         return -1;
293       break;
294     case SEEK_END:
295       if(PHYSFS_seek(file, PHYSFS_fileLength(file) + offset) == 0)
296         return -1;
297       break;
298     default:
299 #ifdef DEBUG
300       assert(false);
301 #else
302       return -1;
303 #endif
304   }
305   return 0;
306 }
307   
308 int
309 OggSoundFile::cb_close(void* source)
310 {
311   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
312   PHYSFS_close(file);
313   return 0;
314 }
315
316 long
317 OggSoundFile::cb_tell(void* source)
318 {
319   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
320   return static_cast<long> (PHYSFS_tell(file));
321 }
322
323 //---------------------------------------------------------------------------
324
325 #include <fstream>
326 SoundFile* load_sound_file(const std::string& filename)
327 {
328   PHYSFS_file* file = PHYSFS_openRead(filename.c_str());
329   if(!file) {
330     std::stringstream msg;
331     msg << "Couldn't open '" << filename << "': " << PHYSFS_getLastError();
332     throw std::runtime_error(msg.str());
333   }
334     
335   try {
336     char magic[4];
337     if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1)
338       throw std::runtime_error("Couldn't read magic, file too short");
339     PHYSFS_seek(file, 0);
340     if(strncmp(magic, "RIFF", 4) == 0)
341       return new WavSoundFile(file);
342     else if(strncmp(magic, "OggS", 4) == 0)
343       return new OggSoundFile(file);
344     else
345       throw std::runtime_error("Unknown file format");
346   } catch(std::exception& e) {
347     std::stringstream msg;
348     msg << "Couldn't read '" << filename << "': " << e.what();
349     throw std::runtime_error(msg.str());
350   }
351 }
352