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