59b2d49b52c492360d021feac5e82f6167439d0e
[xonotic/darkplaces.git] / snd_wav.c
1 /*
2         Copyright (C) 1996-1997 Id Software, Inc.
3
4         This program is free software; you can redistribute it and/or
5         modify it under the terms of the GNU General Public License
6         as published by the Free Software Foundation; either version 2
7         of the License, or (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13         See the GNU General Public License for more details.
14
15         You should have received a copy of the GNU General Public License
16         along with this program; if not, write to:
17
18                 Free Software Foundation, Inc.
19                 59 Temple Place - Suite 330
20                 Boston, MA  02111-1307, USA
21
22 */
23
24
25 #include "quakedef.h"
26 #include "snd_main.h"
27 #include "snd_wav.h"
28
29
30 typedef struct wavinfo_s
31 {
32         int             rate;
33         int             width;
34         int             channels;
35         int             loopstart;
36         int             samples;
37         int             dataofs;                // chunk starts this many bytes from file start
38 } wavinfo_t;
39
40
41 static qbyte *data_p;
42 static qbyte *iff_end;
43 static qbyte *last_chunk;
44 static qbyte *iff_data;
45 static int iff_chunk_len;
46
47
48 static short GetLittleShort(void)
49 {
50         short val;
51
52         val = BuffLittleShort (data_p);
53         data_p += 2;
54
55         return val;
56 }
57
58 static int GetLittleLong(void)
59 {
60         int val = 0;
61
62         val = BuffLittleLong (data_p);
63         data_p += 4;
64
65         return val;
66 }
67
68 static void FindNextChunk(char *name)
69 {
70         while (1)
71         {
72                 data_p=last_chunk;
73
74                 if (data_p >= iff_end)
75                 {       // didn't find the chunk
76                         data_p = NULL;
77                         return;
78                 }
79
80                 data_p += 4;
81                 iff_chunk_len = GetLittleLong();
82                 if (iff_chunk_len < 0)
83                 {
84                         data_p = NULL;
85                         return;
86                 }
87                 data_p -= 8;
88                 last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
89                 if (!strncmp((const char *)data_p, name, 4))
90                         return;
91         }
92 }
93
94 static void FindChunk(char *name)
95 {
96         last_chunk = iff_data;
97         FindNextChunk (name);
98 }
99
100
101 /*
102 static void DumpChunks(void)
103 {
104         char str[5];
105
106         str[4] = 0;
107         data_p=iff_data;
108         do
109         {
110                 memcpy (str, data_p, 4);
111                 data_p += 4;
112                 iff_chunk_len = GetLittleLong();
113                 Con_Printf("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
114                 data_p += (iff_chunk_len + 1) & ~1;
115         } while (data_p < iff_end);
116 }
117 */
118
119
120 /*
121 ============
122 GetWavinfo
123 ============
124 */
125 static wavinfo_t GetWavinfo (char *name, qbyte *wav, int wavlength)
126 {
127         wavinfo_t info;
128         int i;
129         int format;
130         int samples;
131
132         memset (&info, 0, sizeof(info));
133
134         if (!wav)
135                 return info;
136
137         iff_data = wav;
138         iff_end = wav + wavlength;
139
140         // find "RIFF" chunk
141         FindChunk("RIFF");
142         if (!(data_p && !strncmp((const char *)data_p+8, "WAVE", 4)))
143         {
144                 Con_Print("Missing RIFF/WAVE chunks\n");
145                 return info;
146         }
147
148         // get "fmt " chunk
149         iff_data = data_p + 12;
150         //DumpChunks ();
151
152         FindChunk("fmt ");
153         if (!data_p)
154         {
155                 Con_Print("Missing fmt chunk\n");
156                 return info;
157         }
158         data_p += 8;
159         format = GetLittleShort();
160         if (format != 1)
161         {
162                 Con_Print("Microsoft PCM format only\n");
163                 return info;
164         }
165
166         info.channels = GetLittleShort();
167         info.rate = GetLittleLong();
168         data_p += 4+2;
169         info.width = GetLittleShort() / 8;
170
171         // get cue chunk
172         FindChunk("cue ");
173         if (data_p)
174         {
175                 data_p += 32;
176                 info.loopstart = GetLittleLong();
177
178                 // if the next chunk is a LIST chunk, look for a cue length marker
179                 FindNextChunk ("LIST");
180                 if (data_p)
181                 {
182                         if (!strncmp ((const char *)data_p + 28, "mark", 4))
183                         {       // this is not a proper parse, but it works with cooledit...
184                                 data_p += 24;
185                                 i = GetLittleLong ();   // samples in loop
186                                 info.samples = info.loopstart + i;
187                         }
188                 }
189         }
190         else
191                 info.loopstart = -1;
192
193         // find data chunk
194         FindChunk("data");
195         if (!data_p)
196         {
197                 Con_Print("Missing data chunk\n");
198                 return info;
199         }
200
201         data_p += 4;
202         samples = GetLittleLong () / info.width / info.channels;
203
204         if (info.samples)
205         {
206                 if (samples < info.samples)
207                 {
208                         Con_Printf ("Sound %s has a bad loop length", name);
209                         info.samples = samples;
210                 }
211         }
212         else
213                 info.samples = samples;
214
215         info.dataofs = data_p - wav;
216
217         return info;
218 }
219
220
221 /*
222 ====================
223 WAV_FetchSound
224 ====================
225 */
226 static const sfxbuffer_t* WAV_FetchSound (channel_t* ch, unsigned int start, unsigned int nbsamples)
227 {
228         return (sfxbuffer_t *)ch->sfx->fetcher_data;
229 }
230
231 /*
232 ====================
233 WAV_FreeSfx
234 ====================
235 */
236 static void WAV_FreeSfx (sfx_t* sfx)
237 {
238         sfxbuffer_t* sb = (sfxbuffer_t *)sfx->fetcher_data;
239
240         // Free the sound buffer
241         sfx->memsize -= (sb->length * sfx->format.channels * sfx->format.width) + sizeof (*sb) - sizeof (sb->data);
242         Mem_Free(sb);
243
244         sfx->fetcher_data = NULL;
245         sfx->fetcher = NULL;
246 }
247
248 const snd_fetcher_t wav_fetcher = { WAV_FetchSound, NULL, WAV_FreeSfx };
249
250
251 /*
252 ==============
253 S_LoadWavFile
254 ==============
255 */
256 qboolean S_LoadWavFile (const char *filename, sfx_t *s)
257 {
258         qbyte *data;
259         wavinfo_t info;
260         int len;
261         size_t memsize;
262         sfxbuffer_t* sb;
263
264         // Already loaded?
265         if (s->fetcher != NULL)
266                 return true;
267
268         // Load the file
269         data = FS_LoadFile(filename, snd_mempool, false);
270         if (!data)
271                 return false;
272
273         // Don't try to load it if it's not a WAV file
274         if (memcmp (data, "RIFF", 4) || memcmp (data + 8, "WAVE", 4))
275         {
276                 Mem_Free(data);
277                 return false;
278         }
279
280         Con_DPrintf ("Loading WAV file \"%s\"\n", filename);
281
282         info = GetWavinfo (s->name, data, (int)fs_filesize);
283         // Stereo sounds are allowed (intended for music)
284         if (info.channels < 1 || info.channels > 2)
285         {
286                 Con_Printf("%s has an unsupported number of channels (%i)\n",s->name, info.channels);
287                 Mem_Free(data);
288                 return false;
289         }
290         //if (info.channels == 2)
291         //      Log_Printf("stereosounds.log", "%s\n", s->name);
292
293         // calculate resampled length
294         len = (int) ((double) info.samples * (double) shm->format.speed / (double) info.rate);
295         len = len * info.width * info.channels;
296
297         memsize = len + sizeof (*sb) - sizeof (sb->data);
298         sb = (sfxbuffer_t *)Mem_Alloc (snd_mempool, memsize);
299         if (sb == NULL)
300         {
301                 Con_Printf("failed to allocate memory for sound \"%s\"\n", s->name);
302                 Mem_Free(data);
303                 return false;
304         }
305         s->memsize += memsize;
306
307         s->fetcher = &wav_fetcher;
308         s->fetcher_data = sb;
309         s->format.speed = info.rate;
310         s->format.width = info.width;
311         s->format.channels = info.channels;
312         if (info.loopstart < 0)
313                 s->loopstart = -1;
314         else
315                 s->loopstart = (double)info.loopstart * (double)shm->format.speed / (double)s->format.speed;
316         s->flags &= ~SFXFLAG_STREAMED;
317
318 #if BYTE_ORDER != LITTLE_ENDIAN
319         // We must convert the WAV data from little endian
320         // to the machine endianess before resampling it
321         if (info.width == 2)
322         {
323                 int i;
324                 short* ptr;
325
326                 len = info.samples * info.channels;
327                 ptr = (short*)(data + info.dataofs);
328                 for (i = 0; i < len; i++)
329                         ptr[i] = LittleShort (ptr[i]);
330         }
331 #endif
332
333         sb->length = (int)ResampleSfx (data + info.dataofs, info.samples, &s->format, sb->data, s->name);
334         s->format.speed = shm->format.speed;
335         s->total_length = sb->length;
336         sb->offset = 0;
337
338         Mem_Free (data);
339         return true;
340 }