Change sound handling to use exceptions. No more empty.wav is needed, and all sound...
[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 #include "lisp/parser.hpp"
36 #include "lisp/lisp.hpp"
37 #include "file_system.hpp"
38
39 class SoundError : public std::exception
40 {
41 public:
42   SoundError(const std::string& message) throw();
43   virtual ~SoundError() throw();
44
45   const char* what() const throw();
46 private:
47   std::string message;
48 };
49
50 SoundError::SoundError(const std::string& message) throw()
51 {
52   this->message = message;
53 }
54
55 SoundError::~SoundError() throw()
56 {}
57
58 const char*
59 SoundError::what() const throw()
60 {
61   return message.c_str();
62 }
63
64 class WavSoundFile : public SoundFile
65 {
66 public:
67   WavSoundFile(PHYSFS_file* file);
68   ~WavSoundFile();
69
70   size_t read(void* buffer, size_t buffer_size);
71   void reset();
72
73 private:
74   PHYSFS_file* file;
75
76   PHYSFS_sint64 datastart;
77 };
78
79 static inline uint32_t read32LE(PHYSFS_file* file)
80 {
81   uint32_t result;
82   if(PHYSFS_readULE32(file, &result) == 0)
83     throw SoundError("file too short");
84
85   return result;
86 }
87
88 static inline uint16_t read16LE(PHYSFS_file* file)
89 {
90   uint16_t result;
91   if(PHYSFS_readULE16(file, &result) == 0)
92     throw SoundError("file too short");
93
94   return result;
95 }
96
97 WavSoundFile::WavSoundFile(PHYSFS_file* file)
98 {
99   this->file = file;
100
101   char magic[4];
102   if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1)
103     throw SoundError("Couldn't read file magic (not a wave file)");
104   if(strncmp(magic, "RIFF", 4) != 0) {
105     log_debug << "MAGIC: " << magic << std::endl;
106     throw SoundError("file is not a RIFF wav file");
107   }
108
109   uint32_t wavelen = read32LE(file);
110   (void) wavelen;
111
112   if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1)
113     throw SoundError("Couldn't read chunk header (not a wav file?)");
114   if(strncmp(magic, "WAVE", 4) != 0)
115     throw SoundError("file is not a valid RIFF/WAVE file");
116
117   char chunkmagic[4];
118   uint32_t chunklen;
119
120   // search audio data format chunk
121   do {
122     if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1)
123       throw SoundError("EOF while searching format chunk");
124     chunklen = read32LE(file);
125
126     if(strncmp(chunkmagic, "fmt ", 4) == 0)
127       break;
128
129     if(strncmp(chunkmagic, "fact", 4) == 0
130         || strncmp(chunkmagic, "LIST", 4) == 0) {
131       // skip chunk
132       if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0)
133         throw SoundError("EOF while searching fmt chunk");
134     } else {
135       throw SoundError("complex WAVE files not supported");
136     }
137   } while(true);
138
139   if(chunklen < 16)
140     throw SoundError("Format chunk too short");
141
142   // parse format
143   uint16_t encoding = read16LE(file);
144   if(encoding != 1)
145     throw SoundError("only PCM encoding supported");
146   channels = read16LE(file);
147   rate = read32LE(file);
148   uint32_t byterate = read32LE(file);
149   (void) byterate;
150   uint16_t blockalign = read16LE(file);
151   (void) blockalign;
152   bits_per_sample = read16LE(file);
153
154   if(chunklen > 16) {
155     if(PHYSFS_seek(file, PHYSFS_tell(file) + (chunklen-16)) == 0)
156       throw SoundError("EOF while reading rest of format chunk");
157   }
158
159   // set file offset to DATA chunk data
160   do {
161     if(PHYSFS_read(file, chunkmagic, sizeof(chunkmagic), 1) != 1)
162       throw SoundError("EOF while searching data chunk");
163     chunklen = read32LE(file);
164
165     if(strncmp(chunkmagic, "data", 4) == 0)
166       break;
167
168     // skip chunk
169     if(PHYSFS_seek(file, PHYSFS_tell(file) + chunklen) == 0)
170       throw SoundError("EOF while searching fmt chunk");
171   } while(true);
172
173   datastart = PHYSFS_tell(file);
174   size = static_cast<size_t> (chunklen);
175 }
176
177 WavSoundFile::~WavSoundFile()
178 {
179   PHYSFS_close(file);
180 }
181
182 void
183 WavSoundFile::reset()
184 {
185   if(PHYSFS_seek(file, datastart) == 0)
186     throw SoundError("Couldn't seek to data start");
187 }
188
189 size_t
190 WavSoundFile::read(void* buffer, size_t buffer_size)
191 {
192   PHYSFS_sint64 end = datastart + size;
193   PHYSFS_sint64 cur = PHYSFS_tell(file);
194   if(cur >= end)
195     return 0;
196
197   size_t readsize = std::min(static_cast<size_t> (end - cur), buffer_size);
198   if(PHYSFS_read(file, buffer, readsize, 1) != 1)
199     throw SoundError("read error while reading samples");
200
201 #ifdef WORDS_BIGENDIAN
202   if (bits_per_sample != 16)
203     return readsize;
204   char *tmp = (char*)buffer;
205
206   size_t i;
207   char c;
208   for (i = 0; i < readsize / 2; i++)
209   {
210     c          = tmp[2*i];
211     tmp[2*i]   = tmp[2*i+1];
212     tmp[2*i+1] = c;
213   }
214
215   buffer = tmp;
216 #endif
217
218   return readsize;
219 }
220
221 //---------------------------------------------------------------------------
222
223 class OggSoundFile : public SoundFile
224 {
225 public:
226   OggSoundFile(PHYSFS_file* file, double loop_begin, double loop_at);
227   ~OggSoundFile();
228
229   size_t read(void* buffer, size_t buffer_size);
230   void reset();
231
232 private:
233   static size_t cb_read(void* ptr, size_t size, size_t nmemb, void* source);
234   static int cb_seek(void* source, ogg_int64_t offset, int whence);
235   static int cb_close(void* source);
236   static long cb_tell(void* source);
237
238   PHYSFS_file*   file;
239   OggVorbis_File vorbis_file;
240   ogg_int64_t    loop_begin;
241   ogg_int64_t    loop_at;
242   size_t         normal_buffer_loop;
243 };
244
245 OggSoundFile::OggSoundFile(PHYSFS_file* file, double loop_begin, double loop_at)
246 {
247   this->file = file;
248
249   ov_callbacks callbacks = { cb_read, cb_seek, cb_close, cb_tell };
250   ov_open_callbacks(file, &vorbis_file, 0, 0, callbacks);
251
252   vorbis_info* vi = ov_info(&vorbis_file, -1);
253
254   channels        = vi->channels;
255   rate            = vi->rate;
256   bits_per_sample = 16;
257   size            = static_cast<size_t> (ov_pcm_total(&vorbis_file, -1) * 2);
258
259   double sample_len    = 1.0f / rate;
260   double samples_begin = loop_begin / sample_len;
261   double sample_loop   = loop_at / sample_len;
262
263   this->loop_begin     = (ogg_int64_t) samples_begin;
264   if(loop_begin < 0) {
265     this->loop_at = (ogg_int64_t) -1;
266   } else {
267     this->loop_at = (ogg_int64_t) sample_loop;
268   }
269 }
270
271 OggSoundFile::~OggSoundFile()
272 {
273   ov_clear(&vorbis_file);
274 }
275
276 size_t
277 OggSoundFile::read(void* _buffer, size_t buffer_size)
278 {
279   char*  buffer         = reinterpret_cast<char*> (_buffer);
280   int    section        = 0;
281   size_t totalBytesRead = 0;
282
283   while(buffer_size>0) {
284 #ifdef WORDS_BIGENDIAN
285     int bigendian = 1;
286 #else
287     int bigendian = 0;
288 #endif
289
290     size_t bytes_to_read    = buffer_size;
291     if(loop_at > 0) {
292       size_t      bytes_per_sample       = 2;
293       ogg_int64_t time                   = ov_pcm_tell(&vorbis_file);
294       ogg_int64_t samples_left_till_loop = loop_at - time;
295       ogg_int64_t bytes_left_till_loop
296         = samples_left_till_loop * bytes_per_sample;
297       if(bytes_left_till_loop <= 4)
298         break;
299
300       if(bytes_left_till_loop < (ogg_int64_t) bytes_to_read) {
301         bytes_to_read    = (size_t) bytes_left_till_loop;
302       }
303     }
304
305     long bytesRead
306       = ov_read(&vorbis_file, buffer, bytes_to_read, bigendian,
307           2, 1, &section);
308     if(bytesRead == 0) {
309       break;
310     }
311     buffer_size    -= bytesRead;
312     buffer         += bytesRead;
313     totalBytesRead += bytesRead;
314   }
315
316   return totalBytesRead;
317 }
318
319 void
320 OggSoundFile::reset()
321 {
322   ov_pcm_seek(&vorbis_file, loop_begin);
323 }
324
325 size_t
326 OggSoundFile::cb_read(void* ptr, size_t size, size_t nmemb, void* source)
327 {
328   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
329
330   PHYSFS_sint64 res
331     = PHYSFS_read(file, ptr, static_cast<PHYSFS_uint32> (size),
332         static_cast<PHYSFS_uint32> (nmemb));
333   if(res <= 0)
334     return 0;
335
336   return static_cast<size_t> (res);
337 }
338
339 int
340 OggSoundFile::cb_seek(void* source, ogg_int64_t offset, int whence)
341 {
342   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
343
344   switch(whence) {
345     case SEEK_SET:
346       if(PHYSFS_seek(file, static_cast<PHYSFS_uint64> (offset)) == 0)
347         return -1;
348       break;
349     case SEEK_CUR:
350       if(PHYSFS_seek(file, PHYSFS_tell(file) + offset) == 0)
351         return -1;
352       break;
353     case SEEK_END:
354       if(PHYSFS_seek(file, PHYSFS_fileLength(file) + offset) == 0)
355         return -1;
356       break;
357     default:
358 #ifdef DEBUG
359       assert(false);
360 #else
361       return -1;
362 #endif
363   }
364   return 0;
365 }
366
367 int
368 OggSoundFile::cb_close(void* source)
369 {
370   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
371   PHYSFS_close(file);
372   return 0;
373 }
374
375 long
376 OggSoundFile::cb_tell(void* source)
377 {
378   PHYSFS_file* file = reinterpret_cast<PHYSFS_file*> (source);
379   return static_cast<long> (PHYSFS_tell(file));
380 }
381
382 //---------------------------------------------------------------------------
383
384 SoundFile* load_music_file(const std::string& filename)
385 {
386   lisp::Parser parser(false);
387   const lisp::Lisp* root = parser.parse(filename);
388   const lisp::Lisp* music = root->get_lisp("supertux-music");
389   if(music == NULL)
390     throw SoundError("file is not a supertux-music file.");
391
392   std::string raw_music_file;
393   float loop_begin = 0;
394   float loop_at    = -1;
395
396   music->get("file", raw_music_file);
397   music->get("loop-begin", loop_begin);
398   music->get("loop-at", loop_at);
399   
400   if(loop_begin < 0) {
401     throw SoundError("can't loop from negative value");
402   }
403
404   std::string basedir = FileSystem::dirname(filename);
405   raw_music_file = FileSystem::normalize(basedir + raw_music_file);
406
407   PHYSFS_file* file = PHYSFS_openRead(raw_music_file.c_str());
408   if(!file) {
409     std::stringstream msg;
410     msg << "Couldn't open '" << raw_music_file << "': " << PHYSFS_getLastError();
411     throw SoundError(msg.str());
412   }
413
414   return new OggSoundFile(file, loop_begin, loop_at);
415 }
416
417 SoundFile* load_sound_file(const std::string& filename)
418 {
419   if(filename.length() > 6
420       && filename.compare(filename.length()-6, 6, ".music") == 0) {
421     return load_music_file(filename);
422   }
423
424   PHYSFS_file* file = PHYSFS_openRead(filename.c_str());
425   if(!file) {
426     std::stringstream msg;
427     msg << "Couldn't open '" << filename << "': " << PHYSFS_getLastError() << ", using dummy sound file.";
428     throw SoundError(msg.str());
429   }
430
431   try {
432     char magic[4];
433     if(PHYSFS_read(file, magic, sizeof(magic), 1) != 1)
434       throw SoundError("Couldn't read magic, file too short");
435     PHYSFS_seek(file, 0);
436     if(strncmp(magic, "RIFF", 4) == 0)
437       return new WavSoundFile(file);
438     else if(strncmp(magic, "OggS", 4) == 0)
439       return new OggSoundFile(file, 0, -1);
440     else
441       throw SoundError("Unknown file format");
442   } catch(std::exception& e) {
443     std::stringstream msg;
444     msg << "Couldn't read '" << filename << "': " << e.what();
445     throw SoundError(msg.str());
446   }
447 }