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