New junkcode.
[ccan] / ccan / wwviaudio / wwviaudio.c
1 /*
2     (C) Copyright 2007,2008, Stephen M. Cameron.
3
4     This file is part of wordwarvi.
5
6     wordwarvi is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     wordwarvi is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with wordwarvi; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
20  */
21
22 #ifndef WWVIAUDIO_STUBS_ONLY
23
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <limits.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <stdlib.h>
32
33 #define WWVIAUDIO_DEFINE_GLOBALS
34 #include "wwviaudio.h"
35 #undef WWVIAUDIO_DEFINE_GLOBALS
36
37 #include "portaudio.h"
38 #include "ccan/ogg_to_pcm/ogg_to_pcm.h"
39
40 #define FRAMES_PER_BUFFER  (1024)
41
42 static PaStream *stream = NULL;
43 static int audio_paused = 0;
44 static int music_playing = 1;
45 static int sound_working = 0;
46 static int nomusic = 0;
47 static int sound_effects_on = 1;
48 static int sound_device = -1; /* default sound device for port audio. */
49 static int max_concurrent_sounds = 0;
50 static int max_sound_clips = 0;
51
52 /* Pause all audio output, output silence. */
53 void wwviaudio_pause_audio(void)
54 {
55         audio_paused = 1;
56 }
57
58 /* Resume playing audio previously paused. */
59 void wwviaudio_resume_audio(void)
60 {
61         audio_paused = 0;
62 }
63
64 /* Silence the music channel */
65 void wwviaudio_silence_music(void)
66 {
67         music_playing = 0;
68 }
69
70 /* Resume volume on the music channel. */
71 void wwviaudio_resume_music(void)
72 {
73         music_playing = 1;
74 }
75
76 void wwviaudio_toggle_music(void)
77 {
78         music_playing = !music_playing;
79 }
80
81 /* Silence the sound effects. */
82 void wwviaudio_silence_sound_effects(void)
83 {
84         sound_effects_on = 0;
85 }
86
87 /* Resume volume on the sound effects. */
88 void wwviaudio_resume_sound_effects(void)
89 {
90         sound_effects_on = 1;
91 }
92
93 void wwviaudio_toggle_sound_effects(void)
94 {
95         sound_effects_on = !sound_effects_on;
96 }
97
98 void wwviaudio_set_nomusic(void)
99 {
100         nomusic = 1;
101 }
102
103 static struct sound_clip {
104         int active;
105         int nsamples;
106         int pos;
107         int16_t *sample;
108 } *clip = NULL;
109
110 static struct sound_clip *audio_queue = NULL;
111
112 int wwviaudio_read_ogg_clip(int clipnum, char *filename)
113 {
114         uint64_t nframes;
115         char filebuf[PATH_MAX];
116         struct stat statbuf;
117         int samplesize, sample_rate;
118         int nchannels;
119         int rc;
120
121         if (clipnum >= max_sound_clips || clipnum < 0)
122                 return -1;
123
124         strncpy(filebuf, filename, PATH_MAX);
125         rc = stat(filebuf, &statbuf);
126         if (rc != 0) {
127                 snprintf(filebuf, PATH_MAX, "%s", filename);
128                 rc = stat(filebuf, &statbuf);
129                 if (rc != 0) {
130                         fprintf(stderr, "stat('%s') failed.\n", filebuf);
131                         return -1;
132                 }
133         }
134 /*
135         printf("Reading sound file: '%s'\n", filebuf);
136         printf("frames = %lld\n", sfinfo.frames);
137         printf("samplerate = %d\n", sfinfo.samplerate);
138         printf("channels = %d\n", sfinfo.channels);
139         printf("format = %d\n", sfinfo.format);
140         printf("sections = %d\n", sfinfo.sections);
141         printf("seekable = %d\n", sfinfo.seekable);
142 */
143         if (clip[clipnum].sample != NULL)
144                 /* overwriting a previously read clip... */
145                 free(clip[clipnum].sample);
146
147         rc = ogg_to_pcm(filebuf, &clip[clipnum].sample, &samplesize,
148                 &sample_rate, &nchannels, &nframes);
149         if (clip[clipnum].sample == NULL) {
150                 printf("Can't get memory for sound data for %llu frames in %s\n",
151                         nframes, filebuf);
152                 goto error;
153         }
154
155         if (rc != 0) {
156                 fprintf(stderr, "Error: ogg_to_pcm('%s') failed.\n",
157                         filebuf);
158                 goto error;
159         }
160
161         clip[clipnum].nsamples = (int) nframes;
162         if (clip[clipnum].nsamples < 0)
163                 clip[clipnum].nsamples = 0;
164
165         return 0;
166 error:
167         return -1;
168 }
169
170 /* This routine will be called by the PortAudio engine when audio is needed.
171 ** It may called at interrupt level on some machines so don't do anything
172 ** that could mess up the system like calling malloc() or free().
173 */
174 static int patestCallback(const void *inputBuffer, void *outputBuffer,
175         unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
176         PaStreamCallbackFlags statusFlags, __attribute__ ((unused)) void *userData )
177 {
178         int i, j, sample, count;
179         float *out = NULL;
180         float output;
181         out = (float*) outputBuffer;
182         output = 0.0;
183         count = 0;
184
185         if (audio_paused) {
186                 /* output silence when paused and
187                  * don't advance any sound slot pointers
188                  */
189                 for (i = 0; i < framesPerBuffer; i++)
190                         *out++ = (float) 0;
191                 return 0;
192         }
193
194         for (i = 0; i < framesPerBuffer; i++) {
195                 output = 0.0;
196                 count = 0;
197                 for (j = 0; j < max_concurrent_sounds; j++) {
198                         if (!audio_queue[j].active ||
199                                 audio_queue[j].sample == NULL)
200                                 continue;
201                         sample = i + audio_queue[j].pos;
202                         count++;
203                         if (sample >= audio_queue[j].nsamples) {
204                                 audio_queue[j].active = 0;
205                                 continue;
206                         }
207                         if (j != WWVIAUDIO_MUSIC_SLOT && sound_effects_on)
208                                 output += (float) audio_queue[j].sample[sample] * 0.5 / (float) (INT16_MAX);
209                         else if (j == WWVIAUDIO_MUSIC_SLOT && music_playing)
210                                 output += (float) audio_queue[j].sample[sample] / (float) (INT16_MAX);
211                 }
212                 *out++ = (float) output / 2.0;
213         }
214         for (i = 0; i < max_concurrent_sounds; i++) {
215                 if (!audio_queue[i].active)
216                         continue;
217                 audio_queue[i].pos += framesPerBuffer;
218                 if (audio_queue[i].pos >= audio_queue[i].nsamples)
219                         audio_queue[i].active = 0;
220         }
221         return 0; /* we're never finished */
222 }
223
224
225 static void decode_paerror(PaError rc)
226 {
227         if (rc == paNoError)
228                 return;
229         fprintf(stderr, "An error occured while using the portaudio stream\n");
230         fprintf(stderr, "Error number: %d\n", rc);
231         fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(rc));
232 }
233
234 static void wwviaudio_terminate_portaudio(PaError rc)
235 {
236         Pa_Terminate();
237         decode_paerror(rc);
238 }
239
240 int wwviaudio_initialize_portaudio(int maximum_concurrent_sounds, int maximum_sound_clips)
241 {
242         PaStreamParameters outparams;
243         PaError rc;
244         PaDeviceIndex device_count;
245
246         max_concurrent_sounds = maximum_concurrent_sounds;
247         max_sound_clips = maximum_sound_clips;
248
249         audio_queue = malloc(max_concurrent_sounds * sizeof(audio_queue[0]));
250         clip = malloc(max_sound_clips * sizeof(clip[0]));
251         if (audio_queue == NULL || clip == NULL)
252                 return -1;
253
254         memset(audio_queue, 0, sizeof(audio_queue[0]) * max_concurrent_sounds);
255         memset(clip, 0, sizeof(clip[0]) * max_sound_clips);
256
257         rc = Pa_Initialize();
258         if (rc != paNoError)
259                 goto error;
260
261         device_count = Pa_GetDeviceCount();
262         printf("Portaudio reports %d sound devices.\n", device_count);
263
264         if (device_count == 0) {
265                 printf("There will be no audio.\n");
266                 goto error;
267                 rc = 0;
268         }
269         sound_working = 1;
270
271         outparams.device = Pa_GetDefaultOutputDevice();  /* default output device */
272
273         printf("Portaudio says the default device is: %d\n", outparams.device);
274
275         if (sound_device != -1) {
276                 if (sound_device >= device_count)
277                         fprintf(stderr, "wordwarvi:  Invalid sound device "
278                                 "%d specified, ignoring.\n", sound_device);
279                 else
280                         outparams.device = sound_device;
281                 printf("Using sound device %d\n", outparams.device);
282         }
283
284         if (outparams.device < 0 && device_count > 0) {
285                 printf("Hmm, that's strange, portaudio says the default device is %d,\n"
286                         " but there are %d devices\n",
287                         outparams.device, device_count);
288                 printf("I think we'll just skip sound for now.\n");
289                 printf("You might try the '--sounddevice' option and see if that helps.\n");
290                 sound_working = 0;
291                 return -1;
292         }
293
294         outparams.channelCount = 1;                      /* mono output */
295         outparams.sampleFormat = paFloat32;              /* 32 bit floating point output */
296         outparams.suggestedLatency =
297                 Pa_GetDeviceInfo(outparams.device)->defaultLowOutputLatency;
298         outparams.hostApiSpecificStreamInfo = NULL;
299
300         rc = Pa_OpenStream(&stream,
301                 NULL,         /* no input */
302                 &outparams, WWVIAUDIO_SAMPLE_RATE, FRAMES_PER_BUFFER,
303                 paNoFlag, /* paClipOff, */   /* we won't output out of range samples so don't bother clipping them */
304                 patestCallback, NULL /* cookie */);
305         if (rc != paNoError)
306                 goto error;
307         if ((rc = Pa_StartStream(stream)) != paNoError)
308                 goto error;
309         return rc;
310 error:
311         wwviaudio_terminate_portaudio(rc);
312         return rc;
313 }
314
315
316 void wwviaudio_stop_portaudio(void)
317 {
318         int i, rc;
319         
320         if (!sound_working)
321                 return;
322         if ((rc = Pa_StopStream(stream)) != paNoError)
323                 goto error;
324         rc = Pa_CloseStream(stream);
325 error:
326         wwviaudio_terminate_portaudio(rc);
327         if (audio_queue) {
328                 free(audio_queue);
329                 audio_queue = NULL;
330                 max_concurrent_sounds = 0;
331         }
332         if (clip) {
333                 for (i = 0; i < max_sound_clips; i++) {
334                         if (clip[i].sample)
335                                 free(clip[i].sample);
336                 }
337                 free(clip);
338                 clip = NULL;
339                 max_sound_clips = 0;
340         }
341         return;
342 }
343
344 static int wwviaudio_add_sound_to_slot(int which_sound, int which_slot)
345 {
346         int i;
347
348         if (!sound_working)
349                 return 0;
350
351         if (nomusic && which_slot == WWVIAUDIO_MUSIC_SLOT)
352                 return 0;
353
354         if (which_slot != WWVIAUDIO_ANY_SLOT) {
355                 if (audio_queue[which_slot].active)
356                         audio_queue[which_slot].active = 0;
357                 audio_queue[which_slot].pos = 0;
358                 audio_queue[which_slot].nsamples = 0;
359                 /* would like to put a memory barrier here. */
360                 audio_queue[which_slot].sample = clip[which_sound].sample;
361                 audio_queue[which_slot].nsamples = clip[which_sound].nsamples;
362                 /* would like to put a memory barrier here. */
363                 audio_queue[which_slot].active = 1;
364                 return which_slot;
365         }
366         for (i=1;i<max_concurrent_sounds;i++) {
367                 if (audio_queue[i].active == 0) {
368                         audio_queue[i].nsamples = clip[which_sound].nsamples;
369                         audio_queue[i].pos = 0;
370                         audio_queue[i].sample = clip[which_sound].sample;
371                         audio_queue[i].active = 1;
372                         break;
373                 }
374         }
375         return (i >= max_concurrent_sounds) ? -1 : i;
376 }
377
378 int wwviaudio_add_sound(int which_sound)
379 {
380         return wwviaudio_add_sound_to_slot(which_sound, WWVIAUDIO_ANY_SLOT);
381 }
382
383 int wwviaudio_play_music(int which_sound)
384 {
385         return wwviaudio_add_sound_to_slot(which_sound, WWVIAUDIO_MUSIC_SLOT);
386 }
387
388
389 void wwviaudio_add_sound_low_priority(int which_sound)
390 {
391
392         /* adds a sound if there are at least 5 empty sound slots. */
393         int i;
394         int empty_slots = 0;
395         int last_slot;
396
397         if (!sound_working)
398                 return;
399         last_slot = -1;
400         for (i = 1; i < max_concurrent_sounds; i++)
401                 if (audio_queue[i].active == 0) {
402                         last_slot = i;
403                         empty_slots++;
404                         if (empty_slots >= 5)
405                                 break;
406         }
407
408         if (empty_slots < 5)
409                 return;
410         
411         i = last_slot;
412
413         if (audio_queue[i].active == 0) {
414                 audio_queue[i].nsamples = clip[which_sound].nsamples;
415                 audio_queue[i].pos = 0;
416                 audio_queue[i].sample = clip[which_sound].sample;
417                 audio_queue[i].active = 1;
418         }
419         return;
420 }
421
422
423 void wwviaudio_cancel_sound(int queue_entry)
424 {
425         if (!sound_working)
426                 return;
427         audio_queue[queue_entry].active = 0;
428 }
429
430 void wwviaudio_cancel_music(void)
431 {
432         wwviaudio_cancel_sound(WWVIAUDIO_MUSIC_SLOT);
433 }
434
435 void wwviaudio_cancel_all_sounds(void)
436 {
437         int i;
438         if (!sound_working)
439                 return;
440         for (i = 0; i < max_concurrent_sounds; i++)
441                 audio_queue[i].active = 0;
442 }
443
444 int wwviaudio_set_sound_device(int device)
445 {
446         sound_device = device;
447         return 0;
448 }
449
450 #else /* stubs only... */
451
452 int wwviaudio_initialize_portaudio() { return 0; }
453 void wwviaudio_stop_portaudio() { return; }
454 void wwviaudio_set_nomusic() { return; }
455 int wwviaudio_read_ogg_clip(int clipnum, char *filename) { return 0; }
456
457 void wwviaudio_pause_audio() { return; }
458 void wwviaudio_resume_audio() { return; }
459
460 void wwviaudio_silence_music() { return; }
461 void wwviaudio_resume_music() { return; }
462 void wwviaudio_silence_sound_effects() { return; }
463 void wwviaudio_resume_sound_effects() { return; }
464 void wwviaudio_toggle_sound_effects() { return; }
465
466 int wwviaudio_play_music(int which_sound) { return 0; }
467 void wwviaudio_cancel_music() { return; }
468 void wwviaudio_toggle_music() { return; }
469 int wwviaudio_add_sound(int which_sound) { return 0; }
470 void wwviaudio_add_sound_low_priority(int which_sound) { return; }
471 void wwviaudio_cancel_sound(int queue_entry) { return; }
472 void wwviaudio_cancel_all_sounds() { return; }
473 int wwviaudio_set_sound_device(int device) { return 0; }
474
475 #endif