Magic_Game/src/kiwano-audio/Sound.cpp

472 lines
10 KiB
C++

// 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 <kiwano-audio/AudioModule.h>
#include <kiwano-audio/Sound.h>
#include <kiwano/utils/Logger.h>
namespace kiwano
{
namespace audio
{
SoundPtr Sound::Preload(const String& file_path, std::initializer_list<SoundCallbackPtr> callbacks)
{
auto ptr = MakePtr<Sound>();
for (auto& cb : callbacks)
{
ptr->AddCallback(cb);
}
size_t hash_code = std::hash<String>{}(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<SoundCallbackPtr> callbacks)
{
auto ptr = MakePtr<Sound>();
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<IXAudio2SourceVoice>();
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<uint32_t>(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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<IXAudio2SourceVoice>();
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<SoundCallbackChain>();
chain->sound = this;
callback_chain_ = chain;
}
return callback_chain_;
}
SoundCallbackPtr SoundCallback::OnStart(const Function<void(Sound* sound)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<void(Sound* sound)> cb;
void OnStart(Sound* sound) override
{
if (cb)
{
cb(sound);
}
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
SoundCallbackPtr SoundCallback::OnLoopEnd(const Function<void(Sound* sound)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<void(Sound* sound)> cb;
void OnLoopEnd(Sound* sound) override
{
if (cb)
{
cb(sound);
}
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
SoundCallbackPtr SoundCallback::OnEnd(const Function<void(Sound* sound)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<void(Sound* sound)> cb;
void OnEnd(Sound* sound) override
{
if (cb)
{
cb(sound);
}
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
SoundCallbackPtr SoundCallback::OnVolumeChanged(const Function<float(Sound* sound, float volume)>& cb)
{
class SoundCallbackFunc : public SoundCallback
{
public:
Function<float(Sound* sound, float volume)> cb;
float OnVolumeChanged(Sound* sound, float volume) override
{
if (cb)
{
return cb(sound, volume);
}
return volume;
}
};
auto ptr = MakePtr<SoundCallbackFunc>();
ptr->cb = cb;
return ptr;
}
} // namespace audio
} // namespace kiwano