/* * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file) * * This file is part of Arx Libertatis. * * Arx Libertatis 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 3 of the License, or * (at your option) any later version. * * Arx Libertatis 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 Arx Libertatis. If not, see . */ /* Based on: =========================================================================== ARX FATALIS GPL Source Code Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company. This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code'). Arx Fatalis Source Code 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 3 of the License, or (at your option) any later version. Arx Fatalis Source Code 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 Arx Fatalis Source Code. If not, see . In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "audio/Audio.h" #include "Configure.h" #include "audio/AudioResource.h" #include "audio/Mixer.h" #include "audio/Sample.h" #include "audio/Ambiance.h" #include "audio/AudioGlobal.h" #include "audio/AudioBackend.h" #include "audio/AudioSource.h" #include "audio/AudioEnvironment.h" #ifdef ARX_HAVE_DSOUND #include "audio/dsound/DSoundBackend.h" #endif #ifdef ARX_HAVE_OPENAL #include "audio/openal/OpenALBackend.h" #endif #include "io/log/Logger.h" #include "platform/Lock.h" #include "platform/Time.h" using std::string; namespace audio { namespace { static Lock * mutex = NULL; } aalError init(const string & backendName, bool enableEAX) { // Clean any initialized data clean(); LogDebug("Init"); stream_limit_bytes = DEFAULT_STREAMLIMIT; bool autoBackend = (backendName == "auto"); aalError error = AAL_ERROR_INIT; for(int i = 0; i < 2 && !backend; i++) { bool first = (i == 0); bool matched = false; #ifdef ARX_HAVE_OPENAL if(!backend && first == (autoBackend || backendName == "OpenAL")) { matched = true; LogDebug("initializing OpenAL backend"); OpenALBackend * _backend = new OpenALBackend(); error = _backend->init(enableEAX); if(!error) { backend = _backend; } else { delete _backend; } } #endif #ifdef ARX_HAVE_DSOUND if(!backend && first == (autoBackend || backendName == "DirectSound")) { matched = true; LogDebug("initializing DirectSound backend"); DSoundBackend * _backend = new DSoundBackend(); error = _backend->init(enableEAX); if(!error) { backend = _backend; } else { delete _backend; } } #endif if(first && !matched) { LogError << "Unknown backend: " << backendName; } } #if !defined(ARX_HAVE_OPENAL) && !defined(ARX_HAVE_DSOUND) ARX_UNUSED(autoBackend), ARX_UNUSED(enableEAX); #endif if(!backend) { LogError << "No working backend available"; return error; } mutex = new Lock(); session_time = Time::getMs(); return AAL_OK; } aalError clean() { if(!backend) { return AAL_OK; } LogDebug("Clean"); _amb.clear(); _sample.clear(); _mixer.clear(); _env.clear(); delete backend, backend = NULL; sample_path.clear(); ambiance_path.clear(); environment_path.clear(); delete mutex, mutex = NULL; return AAL_OK; } #define AAL_ENTRY \ if(!backend) { \ return AAL_ERROR_INIT; \ } \ Autolock lock(mutex); #define AAL_ENTRY_V(value) \ if(!backend) { \ return (value); \ } \ Autolock lock(mutex); aalError setStreamLimit(size_t limit) { AAL_ENTRY stream_limit_bytes = limit; return AAL_OK; } aalError setSamplePath(const res::path & path) { AAL_ENTRY sample_path = path; return AAL_OK; } aalError setAmbiancePath(const res::path & path) { AAL_ENTRY ambiance_path = path; return AAL_OK; } aalError setEnvironmentPath(const res::path & path) { AAL_ENTRY environment_path = path; return AAL_OK; } aalError setReverbEnabled(bool enable) { AAL_ENTRY return backend->setReverbEnabled(enable); } aalError update() { AAL_ENTRY session_time = Time::getMs(); // Update sources for(Backend::source_iterator p = backend->sourcesBegin(); p != backend->sourcesEnd();) { Source * source = *p; if(source && (source->update(), source->isIdle())) { p = backend->deleteSource(p); } else { ++p; } } // Update ambiances for(size_t i = 0; i < _amb.size(); i++) { Ambiance * ambiance = _amb[i]; if(ambiance) { ambiance->update(); if(ambiance->getChannel().flags & FLAG_AUTOFREE && ambiance->isIdle()) { _amb.remove(i); } } } // Update samples for(size_t i = 0; i < _sample.size(); i++) { Sample * sample = _sample[i]; if(sample && sample->isReferenced() < 1) { _sample.remove(i); } } return backend->updateDeferred(); } // Resource creation MixerId createMixer() { AAL_ENTRY_V(INVALID_ID) Mixer * mixer = new Mixer(); MixerId id = _mixer.add(mixer); if(id == INVALID_ID) { delete mixer; } return id; } SampleId createSample(const res::path & name) { AAL_ENTRY_V(INVALID_ID) Sample * sample = new Sample(name); SampleId s_id = INVALID_ID; if(sample->load() || (s_id = _sample.add(sample)) == INVALID_ID) { delete sample; } else { sample->reference(); } return Backend::clearSource(s_id); } AmbianceId createAmbiance(const res::path & name) { AAL_ENTRY_V(INVALID_ID) Ambiance * ambiance = new Ambiance(name); AmbianceId a_id = INVALID_ID; if(ambiance->load() || (a_id = _amb.add(ambiance)) == INVALID_ID) { delete ambiance; LogError << "Ambiance " << name << " not found"; } return a_id; } EnvId createEnvironment(const res::path & name) { AAL_ENTRY_V(INVALID_ID) Environment * env = new Environment(name); EnvId e_id = INVALID_ID; if(env->load() || (e_id = _env.add(env)) == INVALID_ID) { delete env; LogError << "Environment " << name << " not found"; } return e_id; } // Resource destruction aalError deleteSample(SampleId sample_id) { AAL_ENTRY SampleId s_id = Backend::getSampleId(sample_id); if(!_sample.isValid(s_id)) { return AAL_ERROR_HANDLE; } _sample.remove(s_id); return AAL_OK; } aalError deleteAmbiance(AmbianceId a_id) { AAL_ENTRY _amb.remove(a_id); return AAL_OK; } AmbianceId getAmbiance(const res::path & name) { AAL_ENTRY_V(INVALID_ID) for(size_t i = 0; i < _amb.size(); i++) { if(_amb[i] && name == _amb[i]->getName()) { return i; } } return INVALID_ID; } EnvId getEnvironment(const res::path & name) { AAL_ENTRY_V(INVALID_ID) for(size_t i = 0; i < _env.size(); i++) { if(_env[i] && name == _env[i]->name) { return i; } } return INVALID_ID; } // Retrieve next resource by ID AmbianceId getNextAmbiance(AmbianceId ambiance_id) { AAL_ENTRY_V(INVALID_ID) size_t i = _amb.isValid(ambiance_id) ? ambiance_id + 1 : 0; for(; i < _amb.size(); i++) { if(_amb[i]) { return i; } } return INVALID_ID; } // Environment setup aalError setRoomRolloffFactor(float factor) { AAL_ENTRY LogDebug("SetRoomRolloffFactor " << factor); return backend->setRoomRolloffFactor(factor); } // Listener settings aalError setUnitFactor(float factor) { AAL_ENTRY LogDebug("SetUnitFactor " << factor); return backend->setUnitFactor(factor); } aalError setRolloffFactor(float factor) { AAL_ENTRY LogDebug("SetRolloffFactor " << factor); return backend->setRolloffFactor(factor); } aalError setListenerPosition(const Vec3f & position) { AAL_ENTRY return backend->setListenerPosition(position); } aalError setListenerDirection(const Vec3f & front, const Vec3f & up) { AAL_ENTRY return backend->setListenerOrientation(front, up); } aalError setListenerEnvironment(EnvId e_id) { AAL_ENTRY if(!_env.isValid(e_id)) { return AAL_ERROR_HANDLE; } LogDebug("SetListenerEnvironment " << _env[e_id]->name); return backend->setListenerEnvironment(*_env[e_id]); } // Mixer setup aalError setMixerVolume(MixerId m_id, float volume) { AAL_ENTRY if(!_mixer.isValid(m_id)) { return AAL_ERROR_HANDLE; } LogDebug("SetMixerVolume " << m_id << " volume=" << volume); return _mixer[m_id]->setVolume(volume); } aalError setMixerParent(MixerId m_id, MixerId pm_id) { AAL_ENTRY if(m_id == pm_id || !_mixer.isValid(m_id) || !_mixer.isValid(pm_id)) { return AAL_ERROR_HANDLE; } LogDebug("SetMixerParent " << m_id << " parent=" << pm_id); return _mixer[m_id]->setParent(_mixer[pm_id]); } // Mixer status aalError getMixerVolume(MixerId m_id, float * volume) { *volume = DEFAULT_VOLUME; AAL_ENTRY if(!_mixer.isValid(m_id)) { return AAL_ERROR_HANDLE; } *volume = _mixer[m_id]->getVolume(); return AAL_OK; } // Mixer control aalError mixerStop(MixerId m_id) { AAL_ENTRY if(!_mixer.isValid(m_id)) { return AAL_ERROR_HANDLE; } LogDebug("MixerStop " << m_id); return _mixer[m_id]->stop(); } aalError mixerPause(MixerId m_id) { AAL_ENTRY; if(!_mixer.isValid(m_id)) { return AAL_ERROR_HANDLE; } LogDebug("MixerPause " << m_id); return _mixer[m_id]->pause(); } aalError mixerResume(MixerId m_id) { AAL_ENTRY if(!_mixer.isValid(m_id)) { return AAL_ERROR_HANDLE; } LogDebug("MixerResume " << m_id); return _mixer[m_id]->resume(); } // Sample setup aalError setSampleVolume(SourceId sample_id, float volume) { AAL_ENTRY Source * source = backend->getSource(sample_id); if(!source) { return AAL_ERROR_HANDLE; } return source->setVolume(volume); } aalError setSamplePitch(SourceId sample_id, float pitch) { AAL_ENTRY Source * source = backend->getSource(sample_id); if(!source) { return AAL_ERROR_HANDLE; } return source->setPitch(pitch); } aalError setSamplePosition(SourceId sample_id, const Vec3f & position) { AAL_ENTRY Source * source = backend->getSource(sample_id); if(!source) { return AAL_ERROR_HANDLE; } return source->setPosition(position); } // Sample status aalError getSampleName(SampleId sample_id, res::path & name) { name.clear(); AAL_ENTRY SampleId s_id = Backend::getSampleId(sample_id); if(!_sample.isValid(s_id)) { return AAL_ERROR_HANDLE; } name = _sample[s_id]->getName(); return AAL_OK; } aalError getSampleLength(SampleId sample_id, size_t & length, TimeUnit unit) { length = 0; AAL_ENTRY SampleId s_id = Backend::getSampleId(sample_id); if(!_sample.isValid(s_id)) { return AAL_ERROR_HANDLE; } Sample * sample = _sample[s_id]; length = bytesToUnits(sample->getLength(), sample->getFormat(), unit); return AAL_OK; } bool isSamplePlaying(SourceId sample_id) { AAL_ENTRY_V(false) Source * source = backend->getSource(sample_id); if(!source) { return false; } bool ret = source->isPlaying(); return ret; } // Sample control aalError samplePlay(SampleId & sample_id, const Channel & channel, unsigned play_count) { AAL_ENTRY SampleId s_id = Backend::getSampleId(sample_id); sample_id = Backend::clearSource(sample_id); if(!_sample.isValid(s_id) || !_mixer.isValid(channel.mixer)) { return AAL_ERROR_HANDLE; } LogDebug("SamplePlay " << _sample[s_id]->getName() << " play_count=" << play_count); Source * source = backend->getSource(sample_id); if(source) { if(channel.flags == source->getChannel().flags) { source = NULL; } else if(channel.flags & FLAG_RESTART) { source->stop(); } else if(channel.flags & FLAG_ENQUEUE) { source->play(play_count); } else if(source->isIdle()) { source->setMixer(channel.mixer); source->setVolume(channel.volume); source->setPitch(channel.pitch); source->setPan(channel.pan); source->setPosition(channel.position); source->setVelocity(channel.velocity); source->setDirection(channel.direction); source->setCone(channel.cone); source->setFalloff(channel.falloff); } else { source = NULL; } } if(!source) { source = backend->createSource(s_id, channel); if(!source) { return AAL_ERROR_SYSTEM; } } backend->updateDeferred(); if(aalError error = source->play(play_count)) { return error; } sample_id = source->getId(); if(channel.flags & FLAG_AUTOFREE) { _sample[s_id]->dereference(); } return AAL_OK; } aalError sampleStop(SourceId & sample_id) { AAL_ENTRY Source * source = backend->getSource(sample_id); if(!source) { return AAL_ERROR_HANDLE; } LogDebug("SampleStop " << source->getSample()->getName()); sample_id = Backend::clearSource(sample_id); return source->stop(); } // Track setup aalError muteAmbianceTrack(AmbianceId a_id, const string & track, bool mute) { AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } LogDebug("MuteAmbianceTrack " << _amb[a_id]->getName() << " " << track << " " << mute); return _amb[a_id]->muteTrack(track, mute); } // Ambiance setup aalError setAmbianceUserData(AmbianceId a_id, void * data) { AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } LogDebug("SetAmbianceUserData " << _amb[a_id]->getName() << " " << data); _amb[a_id]->setUserData(data); return AAL_OK; } aalError setAmbianceVolume(AmbianceId a_id, float volume) { AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } LogDebug("SetAmbianceVolume " << _amb[a_id]->getName() << " " << volume); return _amb[a_id]->setVolume(volume); } // Ambiance status aalError getAmbianceName(AmbianceId a_id, res::path & name) { name.clear(); AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } name = _amb[a_id]->getName(); return AAL_OK; } aalError getAmbianceUserData(AmbianceId a_id, void ** data) { AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } *data = _amb[a_id]->getUserData(); return AAL_OK; } aalError getAmbianceVolume(AmbianceId a_id, float & _volume) { _volume = DEFAULT_VOLUME; AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } if(!(_amb[a_id]->getChannel().flags & FLAG_VOLUME)) { return AAL_ERROR_INIT; } _volume = _amb[a_id]->getChannel().volume; return AAL_OK; } bool isAmbianceLooped(AmbianceId a_id) { AAL_ENTRY_V(false) if(!_amb.isValid(a_id)) { return false; } return _amb[a_id]->isLooped(); } // Ambiance control aalError ambiancePlay(AmbianceId a_id, const Channel & channel, bool loop, size_t fade_interval) { AAL_ENTRY if(!_amb.isValid(a_id) || !_mixer.isValid(channel.mixer)) { return AAL_ERROR_HANDLE; } LogDebug("AmbiancePlay " << _amb[a_id]->getName() << " loop=" << loop << " fade=" << fade_interval); return _amb[a_id]->play(channel, loop, fade_interval); } aalError ambianceStop(AmbianceId a_id, size_t fade_interval) { AAL_ENTRY if(!_amb.isValid(a_id)) { return AAL_ERROR_HANDLE; } LogDebug("AmbianceStop " << _amb[a_id]->getName() << " " << fade_interval); _amb[a_id]->stop(fade_interval); return AAL_OK; } } // namespace audio