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