// // Copyright (c) 2008-2017 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "../Precompiled.h" #include "../Audio/Audio.h" #include "../Audio/Sound.h" #include "../Audio/SoundListener.h" #include "../Audio/SoundSource3D.h" #include "../Core/Context.h" #include "../Core/CoreEvents.h" #include "../Core/ProcessUtils.h" #include "../Core/Profiler.h" #include "../IO/Log.h" // ATOMIC BEGIN #include // ATOMIC END #include "../DebugNew.h" #ifdef _MSC_VER #pragma warning(disable:6293) #endif namespace Atomic { const char* AUDIO_CATEGORY = "Audio"; static const int MIN_BUFFERLENGTH = 20; static const int MIN_MIXRATE = 11025; static const int MAX_MIXRATE = 48000; static const StringHash SOUND_MASTER_HASH("Master"); static void SDLAudioCallback(void* userdata, Uint8* stream, int len); Audio::Audio(Context* context) : Object(context), deviceID_(0), sampleSize_(0), playing_(false) { context_->RequireSDL(SDL_INIT_AUDIO); // Set the master to the default value masterGain_[SOUND_MASTER_HASH] = 1.0f; // Register Audio library object factories RegisterAudioLibrary(context_); SubscribeToEvent(E_RENDERUPDATE, ATOMIC_HANDLER(Audio, HandleRenderUpdate)); } Audio::~Audio() { Release(); context_->ReleaseSDL(); } bool Audio::SetMode(int bufferLengthMSec, int mixRate, bool stereo, bool interpolation) { Release(); bufferLengthMSec = Max(bufferLengthMSec, MIN_BUFFERLENGTH); mixRate = Clamp(mixRate, MIN_MIXRATE, MAX_MIXRATE); SDL_AudioSpec desired; SDL_AudioSpec obtained; desired.freq = mixRate; // The concept behind the emscripten audio port is to treat it as 16 bit until the final accumulation form the clip buffer #ifdef __EMSCRIPTEN__ desired.format = AUDIO_F32LSB; #else desired.format = AUDIO_S16; #endif desired.channels = (Uint8)(stereo ? 2 : 1); desired.callback = SDLAudioCallback; desired.userdata = this; // SDL uses power of two audio fragments. Determine the closest match int bufferSamples = mixRate * bufferLengthMSec / 1000; desired.samples = (Uint16)NextPowerOfTwo((unsigned)bufferSamples); if (Abs((int)desired.samples / 2 - bufferSamples) < Abs((int)desired.samples - bufferSamples)) desired.samples /= 2; deviceID_ = SDL_OpenAudioDevice(0, SDL_FALSE, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); if (!deviceID_) { ATOMIC_LOGERROR("Could not initialize audio output"); return false; } #ifdef __EMSCRIPTEN__ if (obtained.format != AUDIO_F32LSB && obtained.format != AUDIO_F32MSB && obtained.format != AUDIO_F32SYS) { ATOMIC_LOGERROR("Could not initialize audio output, 32-bit float buffer format not supported"); SDL_CloseAudioDevice(deviceID_); deviceID_ = 0; return false; } #else if (obtained.format != AUDIO_S16SYS && obtained.format != AUDIO_S16LSB && obtained.format != AUDIO_S16MSB) { ATOMIC_LOGERROR("Could not initialize audio output, 16-bit buffer format not supported"); SDL_CloseAudioDevice(deviceID_); deviceID_ = 0; return false; } #endif stereo_ = obtained.channels == 2; sampleSize_ = (unsigned)(stereo_ ? sizeof(int) : sizeof(short)); // Guarantee a fragment size that is low enough so that Vorbis decoding buffers do not wrap fragmentSize_ = Min(NextPowerOfTwo((unsigned)(mixRate >> 6)), (unsigned)obtained.samples); mixRate_ = obtained.freq; interpolation_ = interpolation; clipBuffer_ = new int[stereo ? fragmentSize_ << 1 : fragmentSize_]; ATOMIC_LOGINFO("Set audio mode " + String(mixRate_) + " Hz " + (stereo_ ? "stereo" : "mono") + " " + (interpolation_ ? "interpolated" : "")); return Play(); } void Audio::Update(float timeStep) { if (!playing_) return; UpdateInternal(timeStep); } bool Audio::Play() { if (playing_) return true; if (!deviceID_) { ATOMIC_LOGERROR("No audio mode set, can not start playback"); return false; } SDL_PauseAudioDevice(deviceID_, 0); // Update sound sources before resuming playback to make sure 3D positions are up to date UpdateInternal(0.0f); playing_ = true; return true; } void Audio::Stop() { playing_ = false; } void Audio::SetMasterGain(const String& type, float gain) { masterGain_[type] = Clamp(gain, 0.0f, 1.0f); for (PODVector::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i) (*i)->UpdateMasterGain(); } void Audio::PauseSoundType(const String& type) { pausedSoundTypes_.Insert(type); } void Audio::ResumeSoundType(const String& type) { MutexLock lock(audioMutex_); pausedSoundTypes_.Erase(type); // Update sound sources before resuming playback to make sure 3D positions are up to date // Done under mutex to ensure no mixing happens before we are ready UpdateInternal(0.0f); } void Audio::ResumeAll() { MutexLock lock(audioMutex_); pausedSoundTypes_.Clear(); UpdateInternal(0.0f); } void Audio::SetListener(SoundListener* listener) { listener_ = listener; } void Audio::StopSound(Sound* soundClip) { for (PODVector::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i) { if ((*i)->GetSound() == soundClip) (*i)->Stop(); } } float Audio::GetMasterGain(const String& type) const { // By definition previously unknown types return full volume HashMap::ConstIterator findIt = masterGain_.Find(type); if (findIt == masterGain_.End()) return 1.0f; return findIt->second_.GetFloat(); } bool Audio::IsSoundTypePaused(const String& type) const { return pausedSoundTypes_.Contains(type); } SoundListener* Audio::GetListener() const { return listener_; } void Audio::AddSoundSource(SoundSource* channel) { MutexLock lock(audioMutex_); soundSources_.Push(channel); } void Audio::RemoveSoundSource(SoundSource* channel) { PODVector::Iterator i = soundSources_.Find(channel); if (i != soundSources_.End()) { MutexLock lock(audioMutex_); soundSources_.Erase(i); } } float Audio::GetSoundSourceMasterGain(StringHash typeHash) const { HashMap::ConstIterator masterIt = masterGain_.Find(SOUND_MASTER_HASH); if (!typeHash) return masterIt->second_.GetFloat(); HashMap::ConstIterator typeIt = masterGain_.Find(typeHash); if (typeIt == masterGain_.End() || typeIt == masterIt) return masterIt->second_.GetFloat(); return masterIt->second_.GetFloat() * typeIt->second_.GetFloat(); } void SDLAudioCallback(void* userdata, Uint8* stream, int len) { Audio* audio = static_cast(userdata); { MutexLock Lock(audio->GetMutex()); audio->MixOutput(stream, len / audio->GetSampleSize() / Audio::SAMPLE_SIZE_MUL); } } void Audio::MixOutput(void* dest, unsigned samples) { if (!playing_ || !clipBuffer_) { memset(dest, 0, samples * sampleSize_ * SAMPLE_SIZE_MUL); return; } while (samples) { // If sample count exceeds the fragment (clip buffer) size, split the work unsigned workSamples = Min(samples, fragmentSize_); unsigned clipSamples = workSamples; if (stereo_) clipSamples <<= 1; // Clear clip buffer int* clipPtr = clipBuffer_.Get(); memset(clipPtr, 0, clipSamples * sizeof(int)); // Mix samples to clip buffer for (PODVector::Iterator i = soundSources_.Begin(); i != soundSources_.End(); ++i) { SoundSource* source = *i; // Check for pause if necessary if (!pausedSoundTypes_.Empty()) { if (pausedSoundTypes_.Contains(source->GetSoundType())) continue; } source->Mix(clipPtr, workSamples, mixRate_, stereo_, interpolation_); } // Copy output from clip buffer to destination #ifdef __EMSCRIPTEN__ float* destPtr = (float*)dest; while (clipSamples--) *destPtr++ = (float)Clamp(*clipPtr++, -32768, 32767) / 32768.0f; #else short* destPtr = (short*)dest; while (clipSamples--) *destPtr++ = (short)Clamp(*clipPtr++, -32768, 32767); #endif samples -= workSamples; ((unsigned char*&)dest) += sampleSize_ * SAMPLE_SIZE_MUL * workSamples; } } void Audio::HandleRenderUpdate(StringHash eventType, VariantMap& eventData) { using namespace RenderUpdate; Update(eventData[P_TIMESTEP].GetFloat()); } void Audio::Release() { Stop(); if (deviceID_) { SDL_CloseAudioDevice(deviceID_); deviceID_ = 0; clipBuffer_.Reset(); } } void Audio::UpdateInternal(float timeStep) { ATOMIC_PROFILE(UpdateAudio); // Update in reverse order, because sound sources might remove themselves for (unsigned i = soundSources_.Size() - 1; i < soundSources_.Size(); --i) { SoundSource* source = soundSources_[i]; // Check for pause if necessary; do not update paused sound sources if (!pausedSoundTypes_.Empty()) { if (pausedSoundTypes_.Contains(source->GetSoundType())) continue; } source->Update(timeStep); } } void RegisterAudioLibrary(Context* context) { Sound::RegisterObject(context); SoundSource::RegisterObject(context); SoundSource3D::RegisterObject(context); SoundListener::RegisterObject(context); } }