2 (C) Copyright 2007,2008, Stephen M. Cameron.
4 This file is part of wordwarvi.
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.
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.
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
22 #ifndef WWVIAUDIO_STUBS_ONLY
27 #include <sys/types.h>
33 #define WWVIAUDIO_DEFINE_GLOBALS
34 #include "wwviaudio.h"
35 #undef WWVIAUDIO_DEFINE_GLOBALS
37 #include <portaudio.h>
38 #include <ccan/ogg_to_pcm/ogg_to_pcm.h>
40 #define FRAMES_PER_BUFFER (1024)
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;
52 /* Pause all audio output, output silence. */
53 void wwviaudio_pause_audio(void)
58 /* Resume playing audio previously paused. */
59 void wwviaudio_resume_audio(void)
64 /* Silence the music channel */
65 void wwviaudio_silence_music(void)
70 /* Resume volume on the music channel. */
71 void wwviaudio_resume_music(void)
76 void wwviaudio_toggle_music(void)
78 music_playing = !music_playing;
81 /* Silence the sound effects. */
82 void wwviaudio_silence_sound_effects(void)
87 /* Resume volume on the sound effects. */
88 void wwviaudio_resume_sound_effects(void)
93 void wwviaudio_toggle_sound_effects(void)
95 sound_effects_on = !sound_effects_on;
98 void wwviaudio_set_nomusic(void)
103 static struct sound_clip {
110 static struct sound_clip *audio_queue = NULL;
112 int wwviaudio_read_ogg_clip(int clipnum, char *filename)
115 char filebuf[PATH_MAX];
117 int samplesize, sample_rate;
121 if (clipnum >= max_sound_clips || clipnum < 0)
124 strncpy(filebuf, filename, PATH_MAX);
125 rc = stat(filebuf, &statbuf);
127 snprintf(filebuf, PATH_MAX, "%s", filename);
128 rc = stat(filebuf, &statbuf);
130 fprintf(stderr, "stat('%s') failed.\n", filebuf);
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);
143 if (clip[clipnum].sample != NULL)
144 /* overwriting a previously read clip... */
145 free(clip[clipnum].sample);
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",
156 fprintf(stderr, "Error: ogg_to_pcm('%s') failed.\n",
161 clip[clipnum].nsamples = (int) nframes;
162 if (clip[clipnum].nsamples < 0)
163 clip[clipnum].nsamples = 0;
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().
174 static int patestCallback(const void *inputBuffer, void *outputBuffer,
175 unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo,
176 PaStreamCallbackFlags statusFlags, __attribute__ ((unused)) void *userData )
178 int i, j, sample, count;
181 out = (float*) outputBuffer;
186 /* output silence when paused and
187 * don't advance any sound slot pointers
189 for (i = 0; i < framesPerBuffer; i++)
194 for (i = 0; i < framesPerBuffer; i++) {
197 for (j = 0; j < max_concurrent_sounds; j++) {
198 if (!audio_queue[j].active ||
199 audio_queue[j].sample == NULL)
201 sample = i + audio_queue[j].pos;
203 if (sample >= audio_queue[j].nsamples) {
204 audio_queue[j].active = 0;
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);
212 *out++ = (float) output / 2.0;
214 for (i = 0; i < max_concurrent_sounds; i++) {
215 if (!audio_queue[i].active)
217 audio_queue[i].pos += framesPerBuffer;
218 if (audio_queue[i].pos >= audio_queue[i].nsamples)
219 audio_queue[i].active = 0;
221 return 0; /* we're never finished */
225 static void decode_paerror(PaError rc)
229 fprintf(stderr, "An error occurred while using the portaudio stream\n");
230 fprintf(stderr, "Error number: %d\n", rc);
231 fprintf(stderr, "Error message: %s\n", Pa_GetErrorText(rc));
234 static void wwviaudio_terminate_portaudio(PaError rc)
240 int wwviaudio_initialize_portaudio(int maximum_concurrent_sounds, int maximum_sound_clips)
242 PaStreamParameters outparams;
244 PaDeviceIndex device_count;
246 max_concurrent_sounds = maximum_concurrent_sounds;
247 max_sound_clips = maximum_sound_clips;
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)
254 memset(audio_queue, 0, sizeof(audio_queue[0]) * max_concurrent_sounds);
255 memset(clip, 0, sizeof(clip[0]) * max_sound_clips);
257 rc = Pa_Initialize();
261 device_count = Pa_GetDeviceCount();
262 printf("Portaudio reports %d sound devices.\n", device_count);
264 if (device_count == 0) {
265 printf("There will be no audio.\n");
271 outparams.device = Pa_GetDefaultOutputDevice(); /* default output device */
273 printf("Portaudio says the default device is: %d\n", outparams.device);
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);
280 outparams.device = sound_device;
281 printf("Using sound device %d\n", outparams.device);
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");
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;
300 rc = Pa_OpenStream(&stream,
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 */);
307 if ((rc = Pa_StartStream(stream)) != paNoError)
311 wwviaudio_terminate_portaudio(rc);
316 void wwviaudio_stop_portaudio(void)
322 if ((rc = Pa_StopStream(stream)) != paNoError)
324 rc = Pa_CloseStream(stream);
326 wwviaudio_terminate_portaudio(rc);
330 max_concurrent_sounds = 0;
333 for (i = 0; i < max_sound_clips; i++) {
335 free(clip[i].sample);
344 static int wwviaudio_add_sound_to_slot(int which_sound, int which_slot)
351 if (nomusic && which_slot == WWVIAUDIO_MUSIC_SLOT)
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;
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;
375 return (i >= max_concurrent_sounds) ? -1 : i;
378 int wwviaudio_add_sound(int which_sound)
380 return wwviaudio_add_sound_to_slot(which_sound, WWVIAUDIO_ANY_SLOT);
383 int wwviaudio_play_music(int which_sound)
385 return wwviaudio_add_sound_to_slot(which_sound, WWVIAUDIO_MUSIC_SLOT);
389 void wwviaudio_add_sound_low_priority(int which_sound)
392 /* adds a sound if there are at least 5 empty sound slots. */
400 for (i = 1; i < max_concurrent_sounds; i++)
401 if (audio_queue[i].active == 0) {
404 if (empty_slots >= 5)
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;
423 void wwviaudio_cancel_sound(int queue_entry)
427 audio_queue[queue_entry].active = 0;
430 void wwviaudio_cancel_music(void)
432 wwviaudio_cancel_sound(WWVIAUDIO_MUSIC_SLOT);
435 void wwviaudio_cancel_all_sounds(void)
440 for (i = 0; i < max_concurrent_sounds; i++)
441 audio_queue[i].active = 0;
444 int wwviaudio_set_sound_device(int device)
446 sound_device = device;
450 #else /* stubs only... */
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; }
457 void wwviaudio_pause_audio() { return; }
458 void wwviaudio_resume_audio() { return; }
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; }
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; }