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