/* * sound.cpp * * Copyright (C) 2001 Matt Ownby * * This file is part of DAPHNE, a laserdisc arcade game emulator * * DAPHNE is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * DAPHNE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // DAPHNE // The sound code in this file uses SDL #ifdef DEBUG #include #endif #include #include #include "SDL.h" #include "SDL_audio.h" #include "sound.h" //#include "sn_intf.h" //#include "pc_beeper.h" //#include "gisound.h" //#include "dac.h" //#include "tonegen.h" #include "samples.h" #include "mix.h" #include "../io/conout.h" #include "../io/mpo_mem.h" #include "../io/numstr.h" #include "../game/game.h" #include "../daphne.h" #include "../ldp-out/ldp-vldp.h" // added by JFA for -startsilent sample_s g_samples[MAX_NUM_SOUNDS] = { { 0 } }; sample_s g_sample_saveme; // the special saveme wav which is loaded independently of any game bool g_sound_enabled = true; // whether sound is enabled bool g_bSoundMuted = false; // whether sound is muted struct sounddef *g_soundchip_head = NULL; // pointer to the first sound chip in our linked list of chips's unsigned int g_uSoundChipNextID = 0; // the idea that the next soundchip to get added will get (also usually indicates how many sound chips have been added, but not if a soundchip gets deleted) // callback to actually do the mixing void (*g_soundmix_callback)(Uint8* stream, int length) = mixNone; // The # of samples in the sound buffer // Matt prefers 1024 but some people (cough Warren) can't handle it haha // but now it can be changed from the command line Uint16 g_u16SoundBufSamples = 2048; // # of bytes each individual sound chip should be allocated for its buffer unsigned int g_uSoundChipBufSize = g_u16SoundBufSamples * AUDIO_BYTES_PER_SAMPLE; // the volume (user adjustable) of the VLDP audio stream unsigned int g_uVolumeVLDP = AUDIO_MAX_VOLUME; // the volume (user adjustable) of all over sound streams besides VLDP unsigned int g_uVolumeNonVLDP = AUDIO_MAX_VOLUME; int cur_wave = 0; // the current wave being played (0 to NUM_DL_BEEPS-1) bool g_sound_initialized = false; // whether the sound will work if we try to play it // Macros to help automatically verify that our locks and unlocks are correct #ifdef DEBUG bool g_bAudioLocked = false; #define LOCK_AUDIO assert (!g_bAudioLocked); g_bAudioLocked = true; SDL_LockAudio #define UNLOCK_AUDIO assert (g_bAudioLocked); g_bAudioLocked = false; SDL_UnlockAudio #else #define LOCK_AUDIO SDL_LockAudio #define UNLOCK_AUDIO SDL_UnlockAudio #endif // lock audio macros // added by JFA for -startsilent void set_sound_mute(bool bMuted) { g_bSoundMuted = bMuted; // only proceed if sound has been initialized if (g_sound_initialized) { LOCK_AUDIO(); // this should set the mixing callback back to something that isn't muted update_soundchip_volumes(); UNLOCK_AUDIO(); } } // end edit void set_soundbuf_size(Uint16 newbufsize) { g_u16SoundBufSamples = newbufsize; g_uSoundChipBufSize = newbufsize * AUDIO_BYTES_PER_SAMPLE; // re-allocate all sound buffers since the size has changed struct sounddef *cur = g_soundchip_head; while (cur) { delete cur->buffer; cur->buffer = new Uint8 [g_uSoundChipBufSize]; memset(cur->buffer, 0, g_uSoundChipBufSize); cur->buffer_pointer = cur->buffer; cur->bytes_left = g_uSoundChipBufSize; cur = cur->next_soundchip; } } static SDL_AudioSpec specDesired, specObtained; bool sound_init() // returns a true on success, false on failure { bool result = false; int audio_rate = AUDIO_FREQ; // rate to mix audio at. This cannot be changed without resampling all .wav's and all .ogg's Uint16 audio_format = AUDIO_FORMAT; int audio_channels = AUDIO_CHANNELS; printline("Initializing sound system ... "); // if the user has not disabled sound from the command line if (is_sound_enabled()) { // if SDL audio initialization was successful if (SDL_InitSubSystem(SDL_INIT_AUDIO) >= 0) { specDesired.callback = audio_callback; specDesired.channels = audio_channels; specDesired.format = audio_format; specDesired.freq = audio_rate; specDesired.samples = g_u16SoundBufSamples; specDesired.userdata = NULL; // this stuff doesn't need to be filled in supposedly ... specDesired.padding = 0; specDesired.size = 0; // if we can open the audio device if (SDL_OpenAudio(&specDesired, &specObtained) >= 0) { // make sure we got what we asked for if ((specObtained.channels == audio_channels) && (specObtained.format == audio_format) && (specObtained.freq == audio_rate) && (specObtained.callback == audio_callback)) { // if we can load all our waves, we're set // If we are supposed to start without playing any sound, then set muted bool here. // It must come here because add_soundchip (which comes right afterwards) will set the sound mixing callback. if (get_startsilent()) { g_bSoundMuted = true; } // right before initialization, add the samples 'sound chip', which can (and should be) // only added once, so we need not track its ID (we call its functions directly) struct sounddef soundchip; soundchip.type = SOUNDCHIP_SAMPLES; add_soundchip(&soundchip); // initialize sound chips init_soundchip(); if (specObtained.samples != g_u16SoundBufSamples) { string strWarning = "WARNING : requested " + numstr::ToStr(g_u16SoundBufSamples) + " samples for sound buffer, but got " + numstr::ToStr(specObtained.samples) + " samples"; printline(strWarning.c_str()); // reset memory allocations set_soundbuf_size(specObtained.samples); } result = true; g_sound_initialized = true; // enable the audio callback (this should come last to be safe) SDL_PauseAudio(0); // start mixing! :) } // end if audio specs are correct else { printline("ERROR: unable to obtain desired audio configuration"); } } // end if audio device could be opened ... // if audio device could not be opened (ie no sound card) else { outstr("WARNING: Audio device could not be opened: "); printline(SDL_GetError()); g_sound_enabled = false; } } // end if sound initializtion worked } // end if sound is enabled // if sound isn't enabled, then we act is if sound initialization worked so daphne doesn't quit if (!is_sound_enabled()) { result = true; } return(result); } // shuts down the sound subsystem void sound_shutdown() { // shutdown sound only if we previously initialized it if (g_sound_initialized) { printline("Shutting down sound system..."); SDL_PauseAudio(1); SDL_CloseAudio(); free_waves(); shutdown_soundchip(); g_sound_initialized = 0; SDL_QuitSubSystem(SDL_INIT_AUDIO); } } // plays a sample, returns true on success bool sound_play(Uint32 whichone) { bool result = false; // only play a sound if sound has been initialized (and if whichone points to a valid wav) if (is_sound_enabled() && (whichone < MAX_NUM_SOUNDS)) { samples_play_sample(g_samples[whichone].pu8Buf, g_samples[whichone].uLength, g_samples[whichone].uChannels); result = true; } return(result); } // plays the 'saveme' sound bool sound_play_saveme() { bool result = false; if (is_sound_enabled()) { samples_play_sample(g_sample_saveme.pu8Buf, g_sample_saveme.uLength); result = true; } return result; } // loads the wave files into the wave structure // returns 0 if failure, or non-zero if success int load_waves() { Uint32 i = 0; int result = 1; string filename = ""; SDL_AudioSpec spec; for (; (i < g_game->get_num_sounds()) && result; i++) { filename = "sound/"; filename += g_game->get_sound_name(i); // initialize so that shutting down succeeds g_samples[i].pu8Buf = NULL; g_samples[i].uLength = 0; // if loading the .wav file succeeds if (SDL_LoadWAV(filename.c_str(), &spec, &g_samples[i].pu8Buf, &g_samples[i].uLength)) { // make sure audio specs are correct if (((spec.channels == AUDIO_CHANNELS) || (spec.channels == 1)) && (spec.freq == AUDIO_FREQ) && (spec.format == AUDIO_S16)) { g_samples[i].uChannels = spec.channels; } // else specs are not correct else { outstr("ERROR: Audio specs are not correct for "); printline(filename.c_str()); result = 0; } } // end if loading worked ... // TODO : add .OGG loading support in here else { outstr("ERROR: Could not open sample file "); printline(filename.c_str()); result = 0; } } // load "saveme" sound in if (!SDL_LoadWAV("sound/saveme.wav", &spec, &g_sample_saveme.pu8Buf, &g_sample_saveme.uLength)) { printline("Loading 'saveme.wav' failed..."); result = 0; } // if something went wrong, eject ... if (!result) { free_waves(); } return(result); } // free all allocated waves void free_waves() { unsigned int i = 0; for (; i < g_game->get_num_sounds(); i++) { // don't de-allocate a sound if it hasn't been allocated if (g_samples[i].pu8Buf) { SDL_FreeWAV(g_samples[i].pu8Buf); g_samples[i].pu8Buf = NULL; } } if (g_sample_saveme.pu8Buf) { SDL_FreeWAV(g_sample_saveme.pu8Buf); g_sample_saveme.pu8Buf = NULL; } } int get_sound_initialized() { return(g_sound_initialized); } void set_sound_enabled_status (bool value) { g_sound_enabled = value; } bool is_sound_enabled() { return g_sound_enabled; } // NOTE : this is called by the game driver, so it can be called even if sound is disabled unsigned int add_soundchip(struct sounddef *candidate) { LOCK_AUDIO(); // safety precaution, we don't want callback running during this function struct sounddef *cur = NULL; // if this is the first sound chip to be added to the list if (!g_soundchip_head) { g_soundchip_head = new struct sounddef; // allocate a new sound chip, assume allocation is successful cur = g_soundchip_head; // point to the new sound chip so we can populate it with info } // else we have to move to the end of the list else { cur = g_soundchip_head; // go to the last sound chip in the list while (cur->next_soundchip) { cur = cur->next_soundchip; } cur->next_soundchip = new struct sounddef; // allocate a new sound chip at the end of our list cur = cur->next_soundchip; // point to the new sound chip so we can populate it with info } // now we must copy over the relevant info memcpy(cur, candidate, sizeof(struct sounddef)); // copy entire thing over cur->id = g_uSoundChipNextID; cur->internal_id = 0; // sensible initial value // This function will be called before the user has a chance to modify the volumes, // so we will initialize them all to sensible defaults. cur->uDriverVolume[0] = cur->uDriverVolume[1] = cur->uBaseVolume[0] = cur->uBaseVolume[1] = cur->uVolume[0] = cur->uVolume[1] = AUDIO_MAX_VOLUME; ++g_uSoundChipNextID; cur->next_soundchip = NULL; cur->bNeedsConstantUpdates = false; // sensible default // create a buffer for each chip cur->buffer = new Uint8 [g_uSoundChipBufSize]; cur->buffer_pointer = cur->buffer; cur->bytes_left = g_uSoundChipBufSize; cur->init_callback = NULL; cur->shutdown_callback = NULL; cur->stream_callback = NULL; cur->writedata_callback = NULL; cur->write_ctrl_data_callback = NULL; memset(cur->buffer, 0, g_uSoundChipBufSize); // now we must assign the appropriate callbacks switch (cur->type) { case SOUNDCHIP_SAMPLES: cur->init_callback = samples_init; cur->shutdown_callback = samples_shutdown; cur->stream_callback = samples_get_stream; break; case SOUNDCHIP_VLDP: // we only need to define stream_callback, everything else is handled by ldp-vldp cur->stream_callback = ldp_vldp_audio_callback; break; default: printline("FATAL ERROR : unknown sound chip added"); set_quitflag(); // force user to deal with this problem break; } // calculate mixing callback, adjust volume, recalculate rshift // NOTE : this should come last in this function update_soundchip_volumes(); UNLOCK_AUDIO(); return cur->id; } bool delete_soundchip(unsigned int id) { bool bSuccess = false; struct sounddef *cur = g_soundchip_head; struct sounddef *prev = NULL; LOCK_AUDIO(); // if 1 or more sound chips exists ... while (cur) { struct sounddef *pNext = cur->next_soundchip; // if we found a match, then delete it... if (cur->id == id) { // if there is a shutdown callback defined, call it if (cur->shutdown_callback) { cur->shutdown_callback(cur->internal_id); } // if cur != g_soundchip_head in other words ... if (prev != NULL) { // restore the chain that we're about to break prev->next_soundchip = cur->next_soundchip; } delete [] cur->buffer; delete cur; // if we just deleted the head, then make the next soundchip be the head if (cur == g_soundchip_head) { g_soundchip_head = pNext; } bSuccess = true; break; } prev = cur; cur = cur->next_soundchip; } UNLOCK_AUDIO(); return bSuccess; } void init_soundchip() { #ifdef DEBUG assert(is_sound_enabled()); #endif LOCK_AUDIO(); // safety precaution, we don't want callback running during this function if (g_soundchip_head) { struct sounddef *cur = g_soundchip_head; while (cur) { // only initialize if the callback exists if (cur->init_callback) { cur->internal_id = cur->init_callback(cur->hz); if (cur->internal_id == -1) { printline("sound.cpp Error : sound chip failed to initialize"); set_quitflag(); // force dev to deal with this problem } // else everything initialized correctly } cur = cur->next_soundchip; } } UNLOCK_AUDIO(); } // Mixing callback // USED WHEN : all audio is muted void mixMute(Uint8 *stream, int length) { memset(stream, 0, length); } // Mixing callback // USED WHEN: there is only 1 sound chip, then there is no need to do any mixing void mixNone(Uint8 *stream, int length) { if (g_soundchip_head != NULL) { memcpy(stream, g_soundchip_head->buffer, length); } } // Mixing callback // USED WHEN: there are more than 1 sound chip, but all volumes are maximum void mixWithMaxVolume(Uint8 *stream, int length) { #ifdef DEBUG assert(g_soundchip_head); #endif // DEBUG // this is a dangerous trick (casting one struct to another) in order to get us extra speed g_pMixBufs = (struct mix_s *) g_soundchip_head; g_pSampleDst = stream; g_uBytesToMix = length; g_mix_func(); /* struct sounddef *cur; // mix all sound chip buffers together // NOTE : this algorithm is accurate but NOT OPTIMIZED and probably should be optimized, // because it is called constantly. for (int sample = 0; sample < (length >> 1); sample += 2) { Sint32 mixed_sample_1 = 0, mixed_sample_2 = 0; // left/right channels cur = g_soundchip_head; while (cur) { mixed_sample_1 += LOAD_LIL_SINT16(((Sint16 *) cur->buffer) + sample); mixed_sample_2 += LOAD_LIL_SINT16(((Sint16 *) cur->buffer) + sample + 1); cur = cur->next_soundchip; } DO_CLIP(mixed_sample_1); DO_CLIP(mixed_sample_2); // note: sample2 needs to be on top because this is little endian, hence LSB Uint32 val_to_store = (((Uint16) mixed_sample_2) << 16) | (Uint16) mixed_sample_1; STORE_LIL_UINT32(stream, val_to_store); stream += 4; } */ } // Mixing callback // USED WHEN: there are more than 1 sound chip, and volumes are variable // (this is the slowest callback) void mixWithMults(Uint8 *stream, int length) { struct sounddef *cur; // mix all sound chip buffers together // NOTE : this algorithm is accurate but NOT OPTIMIZED and probably should be optimized, // because it is called constantly. for (int sample = 0; sample < (length >> 1); sample += 2) { Sint32 mixed_sample_1 = 0, mixed_sample_2 = 0; // left/right channels, 32-bit to support adding many 16-bit samples cur = g_soundchip_head; while (cur) { // multiply by the volume and then dividing by the max volume (by shifting right, which is much faster) mixed_sample_1 += (Sint16) ((LOAD_LIL_SINT16(((Sint16 *) cur->buffer) + sample) * cur->uVolume[0]) >> AUDIO_MAX_VOL_POWER); mixed_sample_2 += (Sint16) ((LOAD_LIL_SINT16(((Sint16 *) cur->buffer) + sample + 1) * cur->uVolume[1]) >> AUDIO_MAX_VOL_POWER); cur = cur->next_soundchip; } DO_CLIP(mixed_sample_1); DO_CLIP(mixed_sample_2); Uint32 val_to_store = (((Uint16) mixed_sample_2) << 16) | (Uint16) mixed_sample_1; STORE_LIL_UINT32(stream, val_to_store); stream += 4; } } void audio_callback ( void *data, Uint8 *stream, int length ) { // now go through the sound chips and mix them in struct sounddef *cur = g_soundchip_head; // fill remaining buffer space for each sound chip while (cur) { #ifdef DEBUG assert(cur->stream_callback != NULL); // every sound chip will have to supply this #endif cur->stream_callback(cur->buffer_pointer, cur->bytes_left, cur->internal_id); cur->buffer_pointer = cur->buffer; cur->bytes_left = g_uSoundChipBufSize; cur = cur->next_soundchip; } // do the actual mixing now g_soundmix_callback(stream, length); } void audio_writedata(Uint8 id, Uint8 data) { // if sound isn't initialized, then the soundchips aren't initialized either if (g_sound_initialized) { LOCK_AUDIO(); // safety precaution, we don't want callback running during this function struct sounddef *cur = g_soundchip_head; while (cur) { if (cur->id == id) { cur->writedata_callback(data, cur->internal_id); } cur = cur->next_soundchip; } UNLOCK_AUDIO(); } } // in case audio_writedata doesn't cut it ... void audio_write_ctrl_data(unsigned int uCtrl, unsigned int uData, Uint8 id) { // if sound isn't initialized, then the soundchips aren't initialized either if (g_sound_initialized) { LOCK_AUDIO(); struct sounddef *cur = g_soundchip_head; while (cur) { if (cur->id == id) { cur->write_ctrl_data_callback(uCtrl, uData, cur->internal_id); } cur = cur->next_soundchip; } UNLOCK_AUDIO(); } } void set_soundchip_volume(Uint8 id, unsigned int uChannel, unsigned int uVolume) { struct sounddef *cur = g_soundchip_head; while (cur) { if (cur->id == id) { set_soundchip_volume(cur, uChannel, uVolume); break; } cur = cur->next_soundchip; } } void set_soundchip_volume(struct sounddef *cur, unsigned int uChannel, unsigned int uVolume) { // safety check if (uChannel < AUDIO_CHANNELS) { // safety check if (uVolume <= AUDIO_MAX_VOLUME) { cur->uDriverVolume[uChannel] = uVolume; LOCK_AUDIO(); update_soundchip_volumes(); UNLOCK_AUDIO(); } else { printline("sound.cpp, set_soundchip_volume() ERROR: volume is out of range, shutting down"); set_quitflag(); // force dev to deal with this :) } } // channel was out of range else { printline("sound.cpp, set_soundchip_volume() ERROR : channel is out of range"); set_quitflag(); // force dev to fix this } } void set_soundchip_vldp_volume(unsigned int uVolume) { if (uVolume <= AUDIO_MAX_VOLUME) { g_uVolumeVLDP = uVolume; LOCK_AUDIO(); update_soundchip_volumes(); UNLOCK_AUDIO(); } else { printline("WARNING : request VLDP volume is out of range"); } } void set_soundchip_nonvldp_volume(unsigned int uVolume) { if (uVolume <= AUDIO_MAX_VOLUME) { g_uVolumeNonVLDP = uVolume; LOCK_AUDIO(); update_soundchip_volumes(); UNLOCK_AUDIO(); } else { printline("WARNING : request non-VLDP volume is out of range"); } } unsigned int get_soundchip_nonvldp_volume() { return g_uVolumeNonVLDP; } // IMPORTANT : assumes LOCK_AUDIO has already been called!!!! void update_soundchip_volumes() { bool bNonMaxVolume = false; unsigned int uSoundchipCount = 0; #ifdef DEBUG assert (g_bAudioLocked == true); #endif // If sound is not muted then do some calculations if (!g_bSoundMuted) { struct sounddef *cur = g_soundchip_head; while (cur) { // if this isn't a VLDP chip ... if (cur->type != SOUNDCHIP_VLDP) { cur->uBaseVolume[0] = cur->uBaseVolume[1] = g_uVolumeNonVLDP; } // else this is the VLDP chip else { cur->uBaseVolume[0] = cur->uBaseVolume[1] = g_uVolumeVLDP; } for (unsigned int uChannel = 0; uChannel < 2; ++uChannel) { // The actual volume must take into account the user-defined BaseVolume, // in case the user requested that the volume be 50% of whatever it would normally be. // If the base volume is AUDIO_MAX_VOLUME, then the new volume becomes uVolume cur->uVolume[uChannel] = (cur->uDriverVolume[uChannel] * cur->uBaseVolume[uChannel]) / AUDIO_MAX_VOLUME; // if we've wound up with a volume that is less than the max if (cur->uVolume[uChannel] < AUDIO_MAX_VOLUME) { bNonMaxVolume = true; } } cur = cur->next_soundchip; ++uSoundchipCount; } // if we need to use the most expensive mixing callback... if (bNonMaxVolume) { g_soundmix_callback = mixWithMults; } else if (uSoundchipCount > 1) { g_soundmix_callback = mixWithMaxVolume; } // just 1 soundchip? we can mix super fast in that case else { g_soundmix_callback = mixNone; } } // end if sound is not muted // else sound is muted else { g_soundmix_callback = mixMute; } } void shutdown_soundchip() { #ifdef DEBUG assert(g_sound_initialized); #endif LOCK_AUDIO(); // safety precaution, we don't want callback running during this function struct sounddef *cur = g_soundchip_head; while (cur) { // if there is a shutdown callback defined, call it if (cur->shutdown_callback) { cur->shutdown_callback(cur->internal_id); } struct sounddef *temp = cur; cur = cur->next_soundchip; delete [] temp->buffer; delete temp; } UNLOCK_AUDIO(); } void update_soundbuffer() { // we don't want to update the sound buffer, if sound isn't initialized if (g_sound_initialized) { // to ensure that the audio callback doesn't get called while we're in this function LOCK_AUDIO(); struct sounddef *cur = g_soundchip_head; while (cur) { // only update if needed, to save CPU cycles if (cur->bNeedsConstantUpdates) { if (cur->bytes_left >= G_1MS_BUF_SIZE) { cur->stream_callback(cur->buffer_pointer, G_1MS_BUF_SIZE, cur->internal_id); cur->bytes_left -= G_1MS_BUF_SIZE; cur->buffer_pointer += G_1MS_BUF_SIZE; } // else we throw away the new data // should we handle this some other way? } // else doesn't need to be updated so often, so don't do it ... cur = cur->next_soundchip; } UNLOCK_AUDIO(); } }