/* * audio.c * * Sound stuff * * (c) 2008 Thomas White * * thrust3d - a silly game * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include "types.h" static void audio_mix(void *data, Uint8 *stream8, int len) { AudioContext *a = data; int i, j, clip_count; Sint16 *stream = (Sint16 *)stream8; len /= 2; /* Number of samples to write */ /* Zero the buffer */ for ( j=0; jsounds[i].playing ) continue; for ( j=0; jsounds[i].data[a->sounds[i].dpos++]; test = stream[j] + samp*a->sounds[i].volume; if ( test > 32767 ) { if ( stream[j] != 32767 ) { clip_count++; } stream[j] = 32767; } else { stream[j] += samp * a->sounds[i].volume; } if ( a->sounds[i].dpos == a->sounds[i].dlen ) { if ( a->sounds[i].repeat ) { a->sounds[i].dpos = 0; if ( a->debug ) printf("AU: Channel %i: Looping at offset %i/%i\n", i, j, len); } else { a->sounds[i].inuse = 0; a->sounds[i].playing = 0; free(a->sounds[i].data); if ( a->debug ) printf("AU: Channel %i: End of data\n", i); break; } } } } /* Fast ramp-up of volume when starting program, avoids 'click' */ if ( a->startup ) { for ( j=0; jstartup_volume; a->startup_volume += 0.001; if ( a->startup_volume > 1.0 ) { if ( a->debug && a->startup ) printf("AU: Finished startup ramp.\n"); a->startup = 0; a->startup_volume = 1.0; } } } if ( a->debug && (clip_count > 0) ) printf("AU: Clipped %i samples.\n", clip_count); } static void *audio_play_wav(void *add_void) { AudioDispatchData *add = add_void; AudioContext *a = add->audiocontext; char *filename = add->filename; int idx; SDL_AudioSpec wave; Uint8 *data; Uint32 dlen; SDL_AudioCVT cvt; /* Look for an empty sound slot */ pthread_mutex_lock(&a->sounds_mutex); for ( idx=0; idxsounds[idx].inuse ) break; } if ( idx == AUDIO_MAX_SOUNDS ) { fprintf(stderr, "Not enough audio channels to play '%s'\n", filename); return NULL; } a->sounds[idx].inuse = 1; pthread_mutex_unlock(&a->sounds_mutex); if ( a->debug ) printf("AU: Channel %i: Selected this channel for sound '%s'\n", idx, filename); /* Load the sound file and convert it to 16-bit stereo at 44.1 kHz */ if ( SDL_LoadWAV(filename, &wave, &data, &dlen) == NULL ) { fprintf(stderr, "Couldn't load %s: (channel %i) %s\n", filename, idx, SDL_GetError()); return NULL; } SDL_BuildAudioCVT(&cvt, wave.format, wave.channels, wave.freq, AUDIO_S16, 2, 44100); cvt.buf = malloc(dlen*cvt.len_mult); if ( cvt.buf == NULL ) { fprintf(stderr, "Not enough memory to convert audio (channel %i)\n", idx); } memcpy(cvt.buf, data, dlen); cvt.len = dlen; SDL_ConvertAudio(&cvt); SDL_FreeWAV(data); /* Put the sound data in the slot */ a->sounds[idx].data = (Sint16 *)cvt.buf; /* Not entirely sure whose fault it is that the last (stereo) sample is wrong */ a->sounds[idx].dlen = cvt.len_cvt / 2 - 2; /* Convert bytes to samples */ a->sounds[idx].dpos = 0; a->sounds[idx].repeat = add->repeat; a->sounds[idx].volume = add->volume; a->sounds[idx].playing = 1; /* Must be done last - tell the mixer thread it can use this */ free(add->filename); if ( a->debug ) printf("AU: Channel %i: WAV dispatch thread completed.\n", idx); a->dispatch_threads_inuse[add->idx] = 0; free(add); return NULL; } static void *audio_play_vorbis(void *add_void) { AudioDispatchData *add = add_void; AudioContext *a = add->audiocontext; char *filename = add->filename; int idx; char *data; int len; OggVorbis_File vf; vorbis_info *vi; int finished, err, current_section; size_t offs; SDL_AudioCVT cvt; /* Look for an empty sound slot */ pthread_mutex_lock(&a->sounds_mutex); for ( idx=0; idxsounds[idx].inuse ) break; } if ( idx == AUDIO_MAX_SOUNDS ) { fprintf(stderr, "Not enough audio channels to play '%s'\n", filename); return NULL; } a->sounds[idx].inuse = 1; pthread_mutex_unlock(&a->sounds_mutex); if ( a->debug ) printf("AU: Channel %i: Selected this channel for sound '%s'\n", idx, filename); err = ov_fopen(filename, &vf); if ( err != 0 ) { fprintf(stderr, "Couldn't open Vorbis file '%s' (channel %i, code %i,%i)\n", filename, idx, err, errno); return NULL; } len = ov_pcm_total(&vf, -1); /* Length in samples 'per channel' */ if ( a->debug ) printf("AU: Channel %i: Length is %i samples 'per channel'\n", idx, len); vi = ov_info(&vf,-1); if ( a->debug ) printf("AU: Channel %i: %i channels, %li Hz\n", idx, vi->channels, vi->rate); data = malloc(vi->channels*2*len); /* Two bytes per sample per channel */ if ( data == NULL ) { fprintf(stderr, "Not enough memory to decode Vorbis stream (channel %i)\n", idx); } offs = 0; finished = 0; if ( a->debug ) printf("AU: Channel %i: Decoding Vorbis stream...\n", idx); while ( finished == 0 ) { long rval; rval = ov_read(&vf, data+offs, 2*2*len, 0, 2, 1, ¤t_section); if ( rval == 0 ) { finished = 1; } else if ( rval < 0 ) { fprintf(stderr, "Vorbis stream error (channel %i)\n", idx); } else { offs += rval; } } /* Convert to 44.1 kHz */ SDL_BuildAudioCVT(&cvt, AUDIO_S16, vi->channels, vi->rate, AUDIO_S16, 2, 44100); cvt.buf = malloc(vi->channels*2*len*cvt.len_mult); if ( cvt.buf == NULL ) { fprintf(stderr, "Not enough memory to convert audio (channel %i)\n", idx); } memcpy(cvt.buf, data, vi->channels*2*len); cvt.len = vi->channels*2*len; SDL_ConvertAudio(&cvt); free(data); ov_clear(&vf); /* Put the sound data in the slot */ a->sounds[idx].data = (Sint16 *)cvt.buf; /* Assuming that the same dud last (stereo) sample occurs here. This may not be the case. */ a->sounds[idx].dlen = cvt.len_cvt / 2 - 2; a->sounds[idx].dpos = 0; a->sounds[idx].repeat = add->repeat; a->sounds[idx].volume = add->volume; a->sounds[idx].playing = 1; /* Must be done last - tell the mixer thread it can use this */ free(add->filename); if ( a->debug ) printf("AU: Channel %i: Vorbis dispatch thread completed.\n", idx); a->dispatch_threads_inuse[add->idx] = 0; free(add); return NULL; } void audio_play(AudioContext *a, char *name, float volume, int repeat) { char filename[128]; struct stat statbuf; /* Try to find an Ogg/Vorbis file */ snprintf(filename, 127, "%s%s.ogg", DATADIR"/sound/", name); if ( stat(filename, &statbuf) == 0 ) { int rval, idx; AudioDispatchData *add; pthread_attr_t attr; add = malloc(sizeof(AudioDispatchData)); add->audiocontext = a; add->filename = strdup(filename); add->volume = volume; add->repeat = repeat; /* Claim a dispatch thread slot */ for ( idx=0; idxdispatch_threads_inuse[idx] ) break; } if ( idx == AUDIO_MAX_SOUNDS ) { fprintf(stderr, "Not enough dispatch thread space to play '%s'\n", filename); free(add->filename); free(add); return; } a->dispatch_threads_inuse[idx] = 1; add->idx = idx; /* Start the dispatch thread */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); rval = pthread_create(&a->dispatch_threads[idx], &attr, audio_play_vorbis, add); pthread_attr_destroy(&attr); if ( rval != 0 ) { fprintf(stderr, "Couldn't create audio dispatch thread (%i)\n", rval); free(add->filename); free(add); a->dispatch_threads_inuse[idx] = 0; return; } goto done; } /* Try to find a WAV file */ snprintf(filename, 127, "%s%s.wav", DATADIR"/sound/", name); if ( stat(filename, &statbuf) == 0 ) { int rval, idx; AudioDispatchData *add; pthread_attr_t attr; add = malloc(sizeof(AudioDispatchData)); add->audiocontext = a; add->filename = strdup(filename); add->volume = volume; add->repeat = repeat; /* Claim a dispatch thread slot */ for ( idx=0; idxdispatch_threads_inuse[idx] ) break; } if ( idx == AUDIO_MAX_SOUNDS ) { fprintf(stderr, "Not enough dispatch thread space to play '%s'\n", filename); free(add->filename); free(add); return; } a->dispatch_threads_inuse[idx] = 1; add->idx = idx; /* Start the dispatch thread */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); rval = pthread_create(&a->dispatch_threads[idx], &attr, audio_play_wav, add); pthread_attr_destroy(&attr); if ( rval != 0 ) { fprintf(stderr, "Couldn't create audio dispatch thread (%i)\n", rval); free(add->filename); free(add); a->dispatch_threads_inuse[idx] = 0; return; } goto done; } /* Still not found */ fprintf(stderr, "Couldn't find file for sound '%s'\n", name); return; done: /* Success */ if ( a->paused == 1 ) { SDL_PauseAudio(0); a->paused = 0; } return; } /* SDL audio initial setup */ AudioContext *audio_setup(int debug, int no_music) { AudioContext *a; SDL_AudioSpec fmt; int i; /* Create audio context */ a = malloc(sizeof(AudioContext)); if ( a == NULL ) return NULL; /* Initialise audio context */ a->debug = debug; a->startup = 1; a->startup_volume = 0.0; a->paused = 1; pthread_mutex_init(&a->sounds_mutex, NULL); for ( i=0; isounds[i].inuse = 0; a->sounds[i].playing = 0; a->dispatch_threads[i] = 0; a->dispatch_threads_inuse[i] = 0; } /* 16-bit stereo audio at 44.1 kHz */ fmt.freq = 44100; fmt.format = AUDIO_S16; fmt.channels = 2; fmt.samples = 512; fmt.callback = audio_mix; fmt.userdata = a; fmt.silence = 0; if ( SDL_OpenAudio(&fmt, NULL) < 0 ) { fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError()); free(a); return NULL; } if ( !no_music ) { audio_play(a, "kraftwerk", 0.5, 1); } else { if ( a->debug ) printf("AU: Music disabled.\n"); } audio_play(a, "moan", 0.2, 1); return a; } void audio_shutdown(AudioContext *a) { int i; SDL_CloseAudio(); /* Wait for dispatch threads */ for ( i=0; idispatch_threads_inuse[i] ) { pthread_join(a->dispatch_threads[i], NULL); } } /* Now that all the dispatch threads are done, and the mixer callback is no longer getting called, the sound system is in a consistent state. i.e. 'inuse' means memory is allocated and can be freed */ for ( i=0; isounds[i].inuse ) { free(a->sounds[i].data); } } /* Now this can be freed */ free(a); }