Magic_Game/src/kiwano-audio/Sound.cpp

472 lines
10 KiB
C++
Raw Normal View History

2019-07-30 10:46:01 +08:00
// Copyright (c) 2016-2018 Kiwano - Nomango
2020-01-21 10:09:55 +08:00
//
2019-07-30 10:46:01 +08:00
// 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:
2020-01-21 10:09:55 +08:00
//
2019-07-30 10:46:01 +08:00
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
2020-01-21 10:09:55 +08:00
//
2019-07-30 10:46:01 +08:00
// 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.
2020-02-14 17:56:50 +08:00
#include <kiwano-audio/AudioModule.h>
2020-01-21 10:09:55 +08:00
#include <kiwano-audio/Sound.h>
2020-05-24 11:26:21 +08:00
#include <kiwano/utils/Logger.h>
2019-07-30 10:46:01 +08:00
namespace kiwano
{
2020-01-21 10:09:55 +08:00
namespace audio
{
2023-09-24 22:17:04 +08:00
2023-09-25 00:15:18 +08:00
SoundPtr Sound::Preload(const String& file_path, std::initializer_list<SoundCallbackPtr> callbacks)
2023-09-24 22:17:04 +08:00
{
auto ptr = MakePtr<Sound>();
2023-09-25 00:15:18 +08:00
for (auto& cb : callbacks)
{
ptr->AddCallback(cb);
}
2023-09-24 22:17:04 +08:00
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;
}
2023-09-25 00:15:18 +08:00
SoundPtr Sound::Preload(const Resource& res, std::initializer_list<SoundCallbackPtr> callbacks)
2023-09-24 22:17:04 +08:00
{
auto ptr = MakePtr<Sound>();
2023-09-25 00:15:18 +08:00
for (auto& cb : callbacks)
{
ptr->AddCallback(cb);
}
2023-09-24 22:17:04 +08:00
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;
}
2020-07-22 21:08:48 +08:00
Sound::Sound(const String& file_path)
: Sound()
2020-02-06 16:54:47 +08:00
{
2020-07-22 21:08:48 +08:00
Load(file_path);
2020-02-06 16:54:47 +08:00
}
2020-07-22 21:08:48 +08:00
Sound::Sound(const Resource& res)
: Sound()
2020-02-06 16:54:47 +08:00
{
2020-07-22 21:08:48 +08:00
Load(res);
2020-02-06 16:54:47 +08:00
}
2020-01-21 10:09:55 +08:00
Sound::Sound()
: opened_(false)
, playing_(false)
2023-09-25 00:15:18 +08:00
, volume_(1.f)
2020-01-21 10:09:55 +08:00
{
}
Sound::~Sound()
{
Close();
}
2020-02-19 12:09:50 +08:00
bool Sound::Load(const String& file_path)
2020-01-21 10:09:55 +08:00
{
if (opened_)
{
Close();
}
2023-09-24 22:17:04 +08:00
TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(file_path);
if (!transcoder)
2020-01-21 10:09:55 +08:00
{
return false;
}
2023-09-24 22:17:04 +08:00
return Load(transcoder);
2020-01-21 10:09:55 +08:00
}
2020-02-19 12:09:50 +08:00
bool Sound::Load(const Resource& res)
2020-01-21 10:09:55 +08:00
{
if (opened_)
{
Close();
}
2023-09-24 22:17:04 +08:00
TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(res);
if (!transcoder)
2020-01-21 10:09:55 +08:00
{
return false;
}
2023-09-24 22:17:04 +08:00
return Load(transcoder);
}
2020-01-21 10:09:55 +08:00
2023-09-24 22:17:04 +08:00
bool Sound::Load(TranscoderPtr transcoder)
{
if (opened_)
2020-01-21 10:09:55 +08:00
{
Close();
2023-09-24 22:17:04 +08:00
}
if (!AudioModule::GetInstance().CreateSound(*this, transcoder))
{
2020-01-21 10:09:55 +08:00
return false;
}
2023-09-25 00:15:18 +08:00
// reset volume
2023-09-25 00:59:27 +08:00
ResetVolume();
2023-09-25 00:15:18 +08:00
2023-09-24 22:17:04 +08:00
coder_ = transcoder;
2020-01-21 10:09:55 +08:00
opened_ = true;
return true;
}
void Sound::Play(int loop_count)
{
if (!opened_)
{
KGE_ERRORF("Sound must be opened first!");
2020-01-21 10:09:55 +08:00
return;
}
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
2020-01-21 10:09:55 +08:00
// if sound stream is not empty, stop() will clear it
XAUDIO2_VOICE_STATE state;
2023-09-24 22:17:04 +08:00
voice->GetState(&state);
2020-01-21 10:09:55 +08:00
if (state.BuffersQueued)
Stop();
// clamp loop count
loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1);
2023-09-24 22:17:04 +08:00
auto buffer = coder_->GetBuffer();
2020-01-21 10:09:55 +08:00
2023-09-24 22:17:04 +08:00
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);
2020-01-21 10:09:55 +08:00
2023-09-24 22:17:04 +08:00
HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer);
2020-01-21 10:09:55 +08:00
if (SUCCEEDED(hr))
{
2023-09-24 22:17:04 +08:00
hr = voice->Start();
2020-01-21 10:09:55 +08:00
}
if (FAILED(hr))
{
KGE_ERRORF("Submitting source buffer failed with HRESULT of %08X", hr);
2020-01-21 10:09:55 +08:00
}
playing_ = SUCCEEDED(hr);
}
void Sound::Pause()
{
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
2020-01-21 10:09:55 +08:00
2023-09-25 00:15:18 +08:00
HRESULT hr = voice->Stop();
if (SUCCEEDED(hr))
2020-01-21 10:09:55 +08:00
playing_ = false;
2023-09-25 00:15:18 +08:00
if (FAILED(hr))
{
KGE_ERRORF("Pause voice failed with HRESULT of %08X", hr);
}
2020-01-21 10:09:55 +08:00
}
void Sound::Resume()
{
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
2020-01-21 10:09:55 +08:00
2023-09-25 00:15:18 +08:00
HRESULT hr = voice->Start();
if (SUCCEEDED(hr))
2020-01-21 10:09:55 +08:00
playing_ = true;
2023-09-25 00:15:18 +08:00
if (FAILED(hr))
{
KGE_ERRORF("Start voice failed with HRESULT of %08X", hr);
}
2020-01-21 10:09:55 +08:00
}
void Sound::Stop()
{
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
2020-01-21 10:09:55 +08:00
2023-09-24 22:17:04 +08:00
HRESULT hr = voice->Stop();
2020-01-21 10:09:55 +08:00
if (SUCCEEDED(hr))
2023-09-24 22:17:04 +08:00
hr = voice->ExitLoop();
2020-01-21 10:09:55 +08:00
if (SUCCEEDED(hr))
2023-09-24 22:17:04 +08:00
hr = voice->FlushSourceBuffers();
2020-01-21 10:09:55 +08:00
if (SUCCEEDED(hr))
playing_ = false;
2023-09-25 00:15:18 +08:00
if (FAILED(hr))
{
KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr);
}
2020-01-21 10:09:55 +08:00
}
void Sound::Close()
{
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
if (voice)
2020-01-21 10:09:55 +08:00
{
2023-09-24 22:17:04 +08:00
voice->Stop();
voice->FlushSourceBuffers();
voice->DestroyVoice();
2020-01-21 10:09:55 +08:00
}
2023-09-24 22:17:04 +08:00
coder_ = nullptr;
2020-01-21 10:09:55 +08:00
opened_ = false;
playing_ = false;
}
bool Sound::IsPlaying() const
{
if (opened_)
{
2023-09-24 22:17:04 +08:00
if (!playing_)
2020-01-21 10:09:55 +08:00
return false;
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
if (!voice)
2023-09-24 21:43:06 +08:00
return false;
2020-01-21 10:09:55 +08:00
XAUDIO2_VOICE_STATE state;
2023-09-24 22:17:04 +08:00
voice->GetState(&state);
2023-09-24 21:43:06 +08:00
return !!state.BuffersQueued;
2020-01-21 10:09:55 +08:00
}
return false;
}
float Sound::GetVolume() const
{
2023-09-25 00:15:18 +08:00
return volume_;
2020-01-21 10:09:55 +08:00
}
void Sound::SetVolume(float volume)
{
2023-09-25 00:15:18 +08:00
if (volume_ == volume)
{
return;
}
volume_ = volume;
float actual_volume = GetCallbackChain()->OnVolumeChanged(this, volume_);
2023-09-24 22:17:04 +08:00
auto voice = GetNativePtr<IXAudio2SourceVoice>();
KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
2020-01-21 10:09:55 +08:00
2023-09-25 00:15:18 +08:00
actual_volume = std::min(std::max(actual_volume, -XAUDIO2_MAX_VOLUME_LEVEL), XAUDIO2_MAX_VOLUME_LEVEL);
voice->SetVolume(actual_volume);
}
2023-09-25 00:15:18 +08:00
2023-09-25 00:59:27 +08:00
void Sound::ResetVolume()
{
const float old_volume = volume_;
volume_ += 1.f;
SetVolume(old_volume);
}
2023-09-25 00:15:18 +08:00
SoundCallbackPtr Sound::GetCallbackChain()
{
class SoundCallbackChain : public SoundCallback
{
public:
Sound* sound;
void OnStart(Sound*) override
{
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
cb->OnStart(sound);
}
}
2023-09-25 00:59:27 +08:00
RemoveUsedCallbacks();
2023-09-25 00:15:18 +08:00
}
void OnLoopEnd(Sound*) override
{
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
cb->OnLoopEnd(sound);
}
}
2023-09-25 00:59:27 +08:00
RemoveUsedCallbacks();
2023-09-25 00:15:18 +08:00
}
void OnEnd(Sound*) override
{
for (auto& cb : sound->GetCallbacks())
{
if (cb)
{
cb->OnEnd(sound);
}
}
2023-09-25 00:59:27 +08:00
RemoveUsedCallbacks();
2023-09-25 00:15:18 +08:00
}
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;
}
2023-09-25 00:59:27 +08:00
void RemoveUsedCallbacks()
{
auto& cbs = sound->GetCallbacks();
auto iter = cbs.begin();
while (iter != cbs.end())
{
if (*iter == nullptr)
{
iter = cbs.erase(iter);
}
else
{
iter++;
}
}
}
2023-09-25 00:15:18 +08:00
};
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;
}
2020-01-21 10:09:55 +08:00
} // namespace audio
} // namespace kiwano