//////////////////////////////////////////////////////////////////////////////// // Scorched3D (c) 2000-2003 // // This file is part of Scorched3D. // // Scorched3D 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. // // Scorched3D 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 Scorched3D; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include Sound *Sound::instance_ = 0; Sound *Sound::instance() { if (!instance_) { instance_ = new Sound; } return instance_; } Sound::Sound() : init_(false), totalTime_(0.0f), GameStateI("Sound") { new GLConsoleRuleMethodIAdapter( this, &Sound::showSoundBuffers, "SoundBuffers"); new GLConsoleRuleMethodIAdapterEx( this, &Sound::soundPlay, "SoundPlay"); } Sound::~Sound() { if (init_) { { SourceList::iterator itor; for (itor = totalSources_.begin(); itor != totalSources_.end(); itor++) { SoundSource *source = (*itor); delete source; } } { BufferMap::iterator itor; for (itor = bufferMap_.begin(); itor != bufferMap_.end(); itor++) { SoundBuffer *buffer = (*itor).second; delete buffer; } } ALCcontext *context = alcGetCurrentContext(); ALCdevice *device = alcGetContextsDevice(context); alcDestroyContext(context); alcCloseDevice(device); } init_ = false; } void Sound::destroy() { delete this; instance_ = 0; } static char *checkString(char *x) { return (char *)(x?x:"null"); } bool Sound::init(int channels) { ALCdevice *soundDevice = alcOpenDevice(0); if (!soundDevice) { dialogExit("Scorched3D", "Failed to open sound device"); return false; } // Setting the frequency seems to cause screeching and // loss of stereo under linux!! /*int attrlist[] = { ALC_FREQUENCY, 11025, ALC_INVALID };*/ ALCcontext *soundContext = alcCreateContext(soundDevice, 0); if (!soundContext) { dialogExit("Scorched3D", "Failed to create sound context"); return false; } alcMakeContextCurrent(soundContext); alDistanceModel(AL_INVERSE_DISTANCE); Logger::log("AL_VENDOR:"); Logger::log( checkString((char *) alGetString(AL_VENDOR))); Logger::log("AL_VERSION:"); GLConsole::instance()->addLine(false, checkString((char *) alGetString(AL_VERSION))); Logger::log("AL_RENDERER:"); Logger::log( checkString((char *) alGetString(AL_RENDERER))); Logger::log("AL_EXTENSIONS:"); GLConsole::instance()->addLine(false, checkString((char *) alGetString(AL_EXTENSIONS))); Logger::log("ALC_DEVICE_SPECIFIER:"); GLConsole::instance()->addLine(false, checkString((char *) alcGetString(soundDevice, ALC_DEVICE_SPECIFIER))); // Create all sound channels for (int i=1; i<=OptionsDisplay::instance()->getSoundChannels(); i++) { SoundSource *source = new SoundSource; if (!source->create()) { dialogExit("Scorched3D", formatString("Failed to create sound channel number %i", i)); return false; } totalSources_.push_back(source); availableSources_.push_back(source); } init_ = true; return init_; } void Sound::soundPlay(std::list list) { list.pop_front(); if (!list.empty()) { SoundBuffer *buffer = fetchOrCreateBuffer( (char *) list.begin()->rule.c_str()); VirtualSoundSource *source = new VirtualSoundSource(10000, false, true); source->setRelative(); source->play(buffer); } } void Sound::showSoundBuffers() { // Show some debug of the current playing sounds int i = 1; Logger::log(formatString("%i sounds playing, %i sources free", getPlayingChannels(), getAvailableChannels())); PlayingSourceList::iterator itor; for (itor = playingSources_.begin(); itor != playingSources_.end(); itor++, i++) { PlayingSoundSource *source = (*itor); if (source->virtualSource) { Logger::log(formatString("%i - %u,%f - %s%s:%s", i, source->virtualSource->getPriority(), source->virtualSource->getDistance(), (source->stopped?"Finished":(source->virtualSource->getPlaying()?"Playing":"Stopped")), (source->virtualSource->getLooping()?"(Looped)":""), source->virtualSource->getBuffer()->getFileName())); } else { Logger::log(formatString("%i - Pending Removal")); } } } void Sound::simulate(const unsigned state, float frameTime) { // Simulate all the current sources // This is only applicable for streams PlayingSourceList::iterator playingitor; for (playingitor = playingSources_.begin(); playingitor != playingSources_.end(); playingitor++) { SoundSource *source = (*playingitor)->actualSource; if (source && source->getPlaying()) { source->simulate(); } } // Check the buffers every so often totalTime_ += frameTime; if (totalTime_ < 0.2f) return; totalTime_ = 0.0f; updateSources(); } static inline bool lt_virt(PlayingSoundSource *p2, PlayingSoundSource *p1) { float dist1 = 0.0f; float dist2 = 0.0f; unsigned int priority1 = 0; unsigned int priority2 = 0; VirtualSoundSource *v1 = p1->virtualSource; VirtualSoundSource *v2 = p2->virtualSource; if (v1 && !p1->stopped) priority1 = v1->getPriority(); if (v2 && !p2->stopped) priority2 = v2->getPriority(); if (v1) dist1 = v1->getDistance(); if (v2) dist2 = v2->getDistance(); return (priority1 < priority2 || (priority1 == priority2 && dist1 > dist2)); } void Sound::addPlaying(VirtualSoundSource *virt) { // Add the new source PlayingSoundSource *source = new PlayingSoundSource(virt); playingSources_.push_back(source); virt->setPlayingSource(source); // Need to do this before updateSources updateSources(); } void Sound::updateSources() { // Update all of the distances Vector listenerPosition = listener_.getPosition(); PlayingSourceList::iterator fitor; for (fitor = playingSources_.begin(); fitor != playingSources_.end(); fitor++) { PlayingSoundSource *source = (*fitor); if (source->virtualSource) { source->virtualSource->updateDistance(listenerPosition); } } // Sort the queue by priority and distance std::sort(playingSources_.begin(), playingSources_.end(), lt_virt); // Start and stop the relevant sources int totalSources = (int) totalSources_.size(); int totalPlaying = (int) playingSources_.size(); int count = 0; PlayingSourceList::reverse_iterator ritor; for (ritor = playingSources_.rbegin(); ritor != playingSources_.rend(); ritor++, count++) { PlayingSoundSource *source = (*ritor); bool stopSource = false; // Check if we have been stopped if (source->stopped) { stopSource = true; } // Check if we should be playing else if (totalPlaying - count <= totalSources) { if (source->actualSource) { if (source->actualSource->getPlaying()) { // It should be playing and is playing } else { // It should be playing, but its finished playing stopSource = true; } } else if (!source->actualSource) { // Its not playing and should be playing DIALOG_ASSERT(!availableSources_.empty()); source->actualSource = availableSources_.back(); availableSources_.pop_back(); source->virtualSource->actualPlay(); } } // We should not be playing this one else { stopSource = true; } // We should not be playing this sound if (stopSource) { // We are currently playing if (source->actualSource) { // Stop it source->actualSource->stop(); availableSources_.push_back(source->actualSource); source->actualSource = 0; } // If we are not looped so stop for good if (source->virtualSource) { if (!source->virtualSource->getLooping()) { source->stopped = true; } } } } // Remove any finished sources while (!playingSources_.empty()) { PlayingSoundSource *source = playingSources_.back(); if (source->stopped) { if (source->virtualSource) { source->virtualSource->setPlayingSource(0); } DIALOG_ASSERT(!(source->actualSource)); delete source; playingSources_.pop_back(); } else break; } // Tidy any managed sources that have stopped playing // Managed sources are virtualsources that are not kept by the user // and should be deleted if they stop playing bool repeat = true; while (repeat) { repeat = false; VirtualSourceList::iterator manitor; for (manitor = managedSources_.begin(); manitor != managedSources_.end(); manitor++) { VirtualSoundSource *source = (*manitor); if (!source->getPlaying()) { managedSources_.erase(manitor); delete source; repeat = true; break; } } } } void Sound::removePlaying(VirtualSoundSource *virt) { if (virt->getPlayingSource()) { if (virt->getPlayingSource()) { virt->getPlayingSource()->stopped = true; virt->getPlayingSource()->virtualSource = 0; } } updateSources(); } void Sound::addManaged(VirtualSoundSource *source) { managedSources_.push_back(source); } int Sound::getAvailableChannels() { return availableSources_.size(); } int Sound::getPlayingChannels() { return playingSources_.size(); } SoundListener *Sound::getDefaultListener() { return &listener_; } SoundBuffer *Sound::createBuffer(char *fileName) { // Return a buffer full of sound :) SoundBuffer *buffer = SoundBufferFactory::createBuffer( (const char *) fileName); if (!buffer) { dialogExit("Failed to load sound", formatString("\"%s\"", fileName)); delete buffer; return 0; } return buffer; } SoundBuffer *Sound::fetchOrCreateBuffer(char *fn) { std::string filename(fn); BufferMap::iterator itor = bufferMap_.find(filename); if (itor != bufferMap_.end()) { return (*itor).second; } SoundBuffer *buffer = createBuffer(fn); bufferMap_[filename] = buffer; return buffer; }