/* * 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 #include "types.h" int ppp = 0; 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].decode_pos == 0) || (a->sounds[i].dpos < a->sounds[i].decode_pos) ) { samp = a->sounds[i].data[a->sounds[i].dpos++]; } else { if ( a->debug ) printf("AU: Channel %i: Decoding thread underran\n", i); samp = 0; } test = stream[j] + samp*a->sounds[i].volume; if ( test > 32767 ) { if ( stream[j] != 32767 ) { clip_count++; } stream[j] = 32767; } else 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); pthread_mutex_unlock(&a->sounds_mutex); goto out; } 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()); a->sounds[idx].inuse = 0; goto out; } 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); a->sounds[idx].inuse = 0; SDL_FreeWAV(data); free(cvt.buf); goto out; } 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].decode_pos = 0; a->sounds[idx].playing = 1; /* Must be done last - tell the mixer thread it can use this */ out: 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, decode_done, decode_samples_done; 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); pthread_mutex_unlock(&a->sounds_mutex); goto out; } 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); a->sounds[idx].inuse = 0; goto out; } 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); /* Decode ten seconds of audio every 8 seconds until the job's done */ if ( a->debug ) printf("AU: Channel %i: Started decoding Vorbis stream\n", idx); /* Do this now because cvt.len_mult is needed to work out the size of 'data' */ SDL_BuildAudioCVT(&cvt, AUDIO_S16, vi->channels, vi->rate, AUDIO_S16, 2, 44100); data = malloc(10*vi->rate*2*vi->channels*cvt.len_mult); if ( data == NULL ) { fprintf(stderr, "Not enough memory to decode Vorbis stream (channel %i)\n", idx); a->sounds[idx].inuse = 0; ov_clear(&vf); goto out; } decode_done = 0; decode_samples_done = 0; /* Number of samples from the Vorbis file (i.e. BEFORE CONVERSION) */ a->sounds[idx].decode_pos = 0; /* Position (in sounds[idx].data) at which to write the next block */ while ( !decode_done ) { long decode_block_samples; /* Number of samples BEFORE CONVERSION */ long decode_block_length; int w; int i; Sint16 *cvtbuf16; /* Decide how much data to shovel this time */ decode_block_samples = 10*vi->rate; /* 10 seconds in samples */ if ( decode_samples_done + decode_block_samples > len ) { /* This is the last block. Yay! */ decode_block_samples = len - decode_samples_done; decode_done = 1; } decode_block_length = vi->channels*2*decode_block_samples; decode_samples_done += decode_block_samples; /* Read the chosen amount of data */ offs = 0; finished = 0; while ( finished == 0 ) { long rval; rval = ov_read(&vf, data+offs, decode_block_length-offs, 0, 2, 1, ¤t_section); if ( rval < 0 ) { fprintf(stderr, "Vorbis stream error (channel %i)\n", idx); } else { offs += rval; } if ( offs == decode_block_length ) finished = 1; } /* Convert to 44.1 kHz */ cvt.buf = (Uint8 *)data; cvt.len = decode_block_length; SDL_ConvertAudio(&cvt); /* Paste this seamlessly into the playing thread's buffer */ if ( !a->sounds[idx].playing ) { a->sounds[idx].data = malloc(len*vi->channels*2); } cvtbuf16 = (Sint16 *)cvt.buf; for ( i=0; isounds[idx].data[i+a->sounds[idx].decode_pos] = cvtbuf16[i]; } a->sounds[idx].decode_pos += decode_block_samples*2; /* It's safe to start playing at this point */ if ( !a->sounds[idx].playing ) { a->sounds[idx].dlen = 2*(len-1); 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 */ } if ( a->debug ) printf("AU: Channel %i: decode_pos=%i, dpos=%li. " "I am now %.1f seconds ahead of the playing position.\n", idx, a->sounds[idx].decode_pos, a->sounds[idx].dpos, ((double)a->sounds[idx].decode_pos-a->sounds[idx].dpos)/(44100*2)); /* Sleep for eight seconds while periodically checking for shutdown */ for ( w=0; w<80; w++ ) { if ( a->shutdown ) { ov_clear(&vf); free(data); if ( a->debug ) printf("AU: Channel %i: Dispatch thread aborting.\n", idx); goto out; } usleep(100000); /* 0.1 seconds */ } } if ( a->debug ) printf("AU: Channel %i: Finished decoding the Vorbis stream.\n", idx); free(data); /* Needed until now */ ov_clear(&vf); out: 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; if ( a == NULL ) return; /* 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; a->shutdown = 0; 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, "music", 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; if ( a == NULL ) return; a->shutdown = 1; 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); }