// Copyright (c) 2016-2018 Kiwano - Nomango // // 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 #include #include namespace kiwano { namespace audio { SoundPtr Sound::Preload(const String& file_path, std::initializer_list callbacks) { auto ptr = MakePtr(); for (auto& cb : callbacks) { ptr->AddCallback(cb); } size_t hash_code = std::hash{}(file_path); if (TranscoderPtr transcoder = TranscoderCache::GetInstance().Get(hash_code)) { if (ptr->Load(transcoder)) return ptr; return nullptr; } if (ptr && ptr->Load(file_path)) { TranscoderCache::GetInstance().Add(hash_code, ptr->coder_); } return ptr; } SoundPtr Sound::Preload(const Resource& res, std::initializer_list callbacks) { auto ptr = MakePtr(); for (auto& cb : callbacks) { ptr->AddCallback(cb); } size_t hash_code = res.GetId(); if (TranscoderPtr transcoder = TranscoderCache::GetInstance().Get(hash_code)) { if (ptr->Load(transcoder)) return ptr; return nullptr; } if (ptr && ptr->Load(res)) { TranscoderCache::GetInstance().Add(hash_code, ptr->coder_); } return ptr; } Sound::Sound(const String& file_path) : Sound() { Load(file_path); } Sound::Sound(const Resource& res) : Sound() { Load(res); } Sound::Sound() : opened_(false) , playing_(false) , volume_(1.f) { } Sound::~Sound() { Close(); } bool Sound::Load(const String& file_path) { if (opened_) { Close(); } TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(file_path); if (!transcoder) { return false; } return Load(transcoder); } bool Sound::Load(const Resource& res) { if (opened_) { Close(); } TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(res); if (!transcoder) { return false; } return Load(transcoder); } bool Sound::Load(TranscoderPtr transcoder) { if (opened_) { Close(); } if (!AudioModule::GetInstance().CreateSound(*this, transcoder)) { return false; } // reset volume ResetVolume(); coder_ = transcoder; opened_ = true; return true; } void Sound::Play(int loop_count) { if (!opened_) { KGE_ERRORF("Sound must be opened first!"); return; } auto voice = GetNativePtr(); KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); // if sound stream is not empty, stop() will clear it XAUDIO2_VOICE_STATE state; voice->GetState(&state); if (state.BuffersQueued) Stop(); // clamp loop count loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1); auto buffer = coder_->GetBuffer(); XAUDIO2_BUFFER xaudio2_buffer = { 0 }; xaudio2_buffer.pAudioData = buffer.data; xaudio2_buffer.Flags = XAUDIO2_END_OF_STREAM; xaudio2_buffer.AudioBytes = buffer.size; xaudio2_buffer.LoopCount = static_cast(loop_count); HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer); if (SUCCEEDED(hr)) { hr = voice->Start(); } if (FAILED(hr)) { KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr); } playing_ = SUCCEEDED(hr); } void Sound::Pause() { auto voice = GetNativePtr(); KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); HRESULT hr = voice->Stop(); if (SUCCEEDED(hr)) playing_ = false; if (FAILED(hr)) { KGE_ERRORF("Pause voice failed with HRESULT of %08X", hr); } } void Sound::Resume() { auto voice = GetNativePtr(); KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); HRESULT hr = voice->Start(); if (SUCCEEDED(hr)) playing_ = true; if (FAILED(hr)) { KGE_ERRORF("Start voice failed with HRESULT of %08X", hr); } } void Sound::Stop() { auto voice = GetNativePtr(); KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); HRESULT hr = voice->Stop(); if (SUCCEEDED(hr)) hr = voice->ExitLoop(); if (SUCCEEDED(hr)) hr = voice->FlushSourceBuffers(); if (SUCCEEDED(hr)) playing_ = false; if (FAILED(hr)) { KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr); } } void Sound::Close() { auto voice = GetNativePtr(); if (voice) { voice->Stop(); voice->FlushSourceBuffers(); voice->DestroyVoice(); } coder_ = nullptr; opened_ = false; playing_ = false; } bool Sound::IsPlaying() const { if (opened_) { if (!playing_) return false; auto voice = GetNativePtr(); if (!voice) return false; XAUDIO2_VOICE_STATE state; voice->GetState(&state); return !!state.BuffersQueued; } return false; } float Sound::GetVolume() const { return volume_; } void Sound::SetVolume(float volume) { if (volume_ == volume) { return; } volume_ = volume; float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_); auto voice = GetNativePtr(); KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL"); actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL); voice->SetVolume(actual_volume); } void Sound::ResetVolume() { const float old_volume = volume_; volume_ += 1.f; SetVolume(old_volume); } SoundCallbackPtr Sound::GetCallbackChain() { class SoundCallbackChain : public SoundCallback { public: Sound* sound; void OnStart(Sound*) override { for (auto& cb : sound->GetCallbacks()) { if (cb) { cb->OnStart(sound); } } RemoveUsedCallbacks(); } void OnLoopEnd(Sound*) override { for (auto& cb : sound->GetCallbacks()) { if (cb) { cb->OnLoopEnd(sound); } } RemoveUsedCallbacks(); } void OnEnd(Sound*) override { for (auto& cb : sound->GetCallbacks()) { if (cb) { cb->OnEnd(sound); } } RemoveUsedCallbacks(); } float OnVolumeChanged(Sound*, float volume) override { float actual_volume = volume; for (auto& cb : sound->GetCallbacks()) { if (cb) { actual_volume = cb->OnVolumeChanged(sound, volume); } } return actual_volume; } void RemoveUsedCallbacks() { auto& cbs = sound->GetCallbacks(); auto iter = cbs.begin(); while (iter != cbs.end()) { if (*iter == nullptr) { iter = cbs.erase(iter); } else { iter++; } } } }; if (!callback_chain_) { auto chain = MakePtr(); chain->sound = this; callback_chain_ = chain; } return callback_chain_; } SoundCallbackPtr SoundCallback::OnStart(const Function& cb) { class SoundCallbackFunc : public SoundCallback { public: Function cb; void OnStart(Sound* sound) override { if (cb) { cb(sound); } } }; auto ptr = MakePtr(); ptr->cb = cb; return ptr; } SoundCallbackPtr SoundCallback::OnLoopEnd(const Function& cb) { class SoundCallbackFunc : public SoundCallback { public: Function cb; void OnLoopEnd(Sound* sound) override { if (cb) { cb(sound); } } }; auto ptr = MakePtr(); ptr->cb = cb; return ptr; } SoundCallbackPtr SoundCallback::OnEnd(const Function& cb) { class SoundCallbackFunc : public SoundCallback { public: Function cb; void OnEnd(Sound* sound) override { if (cb) { cb(sound); } } }; auto ptr = MakePtr(); ptr->cb = cb; return ptr; } SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function& cb) { class SoundCallbackFunc : public SoundCallback { public: Function cb; float OnVolumeChanged(Sound* sound, float volume) override { if (cb) { return cb(sound, volume); } return volume; } }; auto ptr = MakePtr(); ptr->cb = cb; return ptr; } } // namespace audio } // namespace kiwano