"soundlist" now tells you if a sound is streamed and whether it is mono or stereo
[divverent/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_wav.h"
27
28
29 typedef struct
30 {
31         int             rate;
32         int             width;
33         int             channels;
34         int             loopstart;
35         int             samples;
36         int             dataofs;                // chunk starts this many bytes from file start
37 } wavinfo_t;
38
39
40 static qbyte *data_p;
41 static qbyte *iff_end;
42 static qbyte *last_chunk;
43 static qbyte *iff_data;
44 static int iff_chunk_len;
45
46
47 static short GetLittleShort(void)
48 {
49         short val;
50
51         val = BuffLittleShort (data_p);
52         data_p += 2;
53
54         return val;
55 }
56
57 static int GetLittleLong(void)
58 {
59         int val = 0;
60
61         val = BuffLittleLong (data_p);
62         data_p += 4;
63
64         return val;
65 }
66
67 static void FindNextChunk(char *name)
68 {
69         while (1)
70         {
71                 data_p=last_chunk;
72
73                 if (data_p >= iff_end)
74                 {       // didn't find the chunk
75                         data_p = NULL;
76                         return;
77                 }
78
79                 data_p += 4;
80                 iff_chunk_len = GetLittleLong();
81                 if (iff_chunk_len < 0)
82                 {
83                         data_p = NULL;
84                         return;
85                 }
86                 data_p -= 8;
87                 last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
88                 if (!strncmp(data_p, name, 4))
89                         return;
90         }
91 }
92
93 static void FindChunk(char *name)
94 {
95         last_chunk = iff_data;
96         FindNextChunk (name);
97 }
98
99
100 /*
101 static void DumpChunks(void)
102 {
103         char str[5];
104
105         str[4] = 0;
106         data_p=iff_data;
107         do
108         {
109                 memcpy (str, data_p, 4);
110                 data_p += 4;
111                 iff_chunk_len = GetLittleLong();
112                 Con_Printf("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len);
113                 data_p += (iff_chunk_len + 1) & ~1;
114         } while (data_p < iff_end);
115 }
116 */
117
118
119 /*
120 ============
121 GetWavinfo
122 ============
123 */
124 static wavinfo_t GetWavinfo (char *name, qbyte *wav, int wavlength)
125 {
126         wavinfo_t info;
127         int i;
128         int format;
129         int samples;
130
131         memset (&info, 0, sizeof(info));
132
133         if (!wav)
134                 return info;
135
136         iff_data = wav;
137         iff_end = wav + wavlength;
138
139         // find "RIFF" chunk
140         FindChunk("RIFF");
141         if (!(data_p && !strncmp(data_p+8, "WAVE", 4)))
142         {
143                 Con_Print("Missing RIFF/WAVE chunks\n");
144                 return info;
145         }
146
147         // get "fmt " chunk
148         iff_data = data_p + 12;
149         //DumpChunks ();
150
151         FindChunk("fmt ");
152         if (!data_p)
153         {
154                 Con_Print("Missing fmt chunk\n");
155                 return info;
156         }
157         data_p += 8;
158         format = GetLittleShort();
159         if (format != 1)
160         {
161                 Con_Print("Microsoft PCM format only\n");
162                 return info;
163         }
164
165         info.channels = GetLittleShort();
166         info.rate = GetLittleLong();
167         data_p += 4+2;
168         info.width = GetLittleShort() / 8;
169
170         // get cue chunk
171         FindChunk("cue ");
172         if (data_p)
173         {
174                 data_p += 32;
175                 info.loopstart = GetLittleLong();
176
177                 // if the next chunk is a LIST chunk, look for a cue length marker
178                 FindNextChunk ("LIST");
179                 if (data_p)
180                 {
181                         if (!strncmp (data_p + 28, "mark", 4))
182                         {       // this is not a proper parse, but it works with cooledit...
183                                 data_p += 24;
184                                 i = GetLittleLong ();   // samples in loop
185                                 info.samples = info.loopstart + i;
186                         }
187                 }
188         }
189         else
190                 info.loopstart = -1;
191
192         // find data chunk
193         FindChunk("data");
194         if (!data_p)
195         {
196                 Con_Print("Missing data chunk\n");
197                 return info;
198         }
199
200         data_p += 4;
201         samples = GetLittleLong () / info.width / info.channels;
202
203         if (info.samples)
204         {
205                 if (samples < info.samples)
206                         Host_Error ("Sound %s has a bad loop length", name);
207         }
208         else
209                 info.samples = samples;
210
211         info.dataofs = data_p - wav;
212
213         return info;
214 }
215
216
217 /*
218 ====================
219 WAV_FetchSound
220 ====================
221 */
222 static const sfxbuffer_t* WAV_FetchSound (channel_t* ch, unsigned int start, unsigned int nbsamples)
223 {
224         return ch->sfx->fetcher_data;
225 }
226
227
228 snd_fetcher_t wav_fetcher = { WAV_FetchSound, NULL };
229
230
231 /*
232 ==============
233 S_LoadWavFile
234 ==============
235 */
236 qboolean S_LoadWavFile (const char *filename, sfx_t *s)
237 {
238         qbyte *data;
239         wavinfo_t info;
240         int len;
241         sfxbuffer_t* sb;
242
243         Mem_FreePool (&s->mempool);
244         s->mempool = Mem_AllocPool(s->name);
245
246         // Load the file
247         data = FS_LoadFile(filename, s->mempool, false);
248         if (!data)
249         {
250                 Mem_FreePool (&s->mempool);
251                 return false;
252         }
253
254         // Don't try to load it if it's not a WAV file
255         if (memcmp (data, "RIFF", 4) || memcmp (data + 8, "WAVE", 4))
256         {
257                 Mem_FreePool (&s->mempool);
258                 return false;
259         }
260
261         Con_DPrintf ("Loading WAV file \"%s\"\n", filename);
262
263         info = GetWavinfo (s->name, data, fs_filesize);
264         // Stereo sounds are allowed (intended for music)
265         if (info.channels < 1 || info.channels > 2)
266         {
267                 Con_Printf("%s has an unsupported number of channels (%i)\n",s->name, info.channels);
268                 Mem_FreePool (&s->mempool);
269                 return false;
270         }
271
272         // calculate resampled length
273         len = (int) ((double) info.samples * (double) shm->format.speed / (double) info.rate);
274         len = len * info.width * info.channels;
275
276         sb = Mem_Alloc (s->mempool, len + sizeof (*sb) - sizeof (sb->data));
277         if (sb == NULL)
278         {
279                 Con_Printf("failed to allocate memory for sound \"%s\"\n", s->name);
280                 Mem_FreePool(&s->mempool);
281                 return false;
282         }
283
284         s->fetcher = &wav_fetcher;
285         s->fetcher_data = sb;
286         s->format.speed = info.rate;
287         s->format.width = info.width;
288         s->format.channels = info.channels;
289         if (info.loopstart < 0)
290                 s->loopstart = -1;
291         else
292                 s->loopstart = (double)info.loopstart * (double)shm->format.speed / (double)s->format.speed;
293         s->flags &= ~SFXFLAG_STREAMED;
294
295 #if BYTE_ORDER != LITTLE_ENDIAN
296         // We must convert the WAV data from little endian
297         // to the machine endianess before resampling it
298         if (info.width == 2)
299         {
300                 int i;
301                 short* ptr;
302
303                 len = info.samples * info.channels;
304                 ptr = (short*)(data + info.dataofs);
305                 for (i = 0; i < len; i++)
306                         ptr[i] = LittleShort (ptr[i]);
307         }
308 #endif
309
310         sb->length = ResampleSfx (data + info.dataofs, info.samples, &s->format, sb->data, s->name);
311         s->format.speed = shm->format.speed;
312         s->total_length = sb->length;
313         sb->offset = 0;
314
315         Mem_Free (data);
316         return true;
317 }