diff --git a/.gitignore b/.gitignore
index b0f0dad1..f2fe03d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ Release/
.vs
.idea
._Kiwano.sln
+.vscode
# vs2010
ipch/
diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json
deleted file mode 100644
index 1eb88ef5..00000000
--- a/.vscode/c_cpp_properties.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "configurations": [
- {
- "name": "Mac",
- "includePath": [
- "${workspaceFolder}/src",
- "${workspaceFolder}/src/3rd-party"
- ],
- "defines": [
- "TARGET_OS_MAC"
- ],
- "macFrameworkPath": [
- "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"
- ],
- "compilerPath": "/usr/bin/clang",
- "cStandard": "c11",
- "cppStandard": "c++14",
- "intelliSenseMode": "clang-x64"
- },
- {
- "name": "Linux",
- "includePath": [
- "${workspaceFolder}/src",
- "${workspaceFolder}/src/3rd-party"
- ],
- "defines": [],
- "compilerPath": "/usr/bin/gcc",
- "cStandard": "c11",
- "cppStandard": "c++14",
- "intelliSenseMode": "clang-x64"
- }
- ],
- "version": 4
-}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index cfc8e1f0..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "files.autoGuessEncoding": true,
- "files.encoding": "gb2312"
-}
\ No newline at end of file
diff --git a/projects/kiwano/kiwano.vcxproj b/projects/kiwano/kiwano.vcxproj
index a3910d00..15c310dc 100644
--- a/projects/kiwano/kiwano.vcxproj
+++ b/projects/kiwano/kiwano.vcxproj
@@ -82,6 +82,7 @@
+
@@ -96,12 +97,10 @@
-
-
@@ -193,7 +192,6 @@
-
diff --git a/projects/kiwano/kiwano.vcxproj.filters b/projects/kiwano/kiwano.vcxproj.filters
index 8853f034..8171b4f8 100644
--- a/projects/kiwano/kiwano.vcxproj.filters
+++ b/projects/kiwano/kiwano.vcxproj.filters
@@ -222,12 +222,6 @@
core
-
- render
-
-
- render\DirectX
-
render
@@ -411,6 +405,9 @@
render\DirectX
+
+ platform
+
@@ -608,9 +605,6 @@
render
-
- render
-
core
diff --git a/src/kiwano-audio/AudioModule.cpp b/src/kiwano-audio/AudioModule.cpp
index 4717c6b0..cfc3c961 100644
--- a/src/kiwano-audio/AudioModule.cpp
+++ b/src/kiwano-audio/AudioModule.cpp
@@ -22,11 +22,52 @@
#include
#include
#include
+#include
namespace kiwano
{
namespace audio
{
+
+class VoiceCallback : public IXAudio2VoiceCallback
+{
+public:
+ SoundCallback* cb;
+
+ VoiceCallback(SoundCallback* cb)
+ : cb(cb)
+ {
+ }
+
+ ~VoiceCallback() {}
+
+ STDMETHOD_(void, OnBufferStart(void* pBufferContext))
+ {
+ cb->OnStart(nullptr);
+ }
+
+ STDMETHOD_(void, OnLoopEnd(void* pBufferContext))
+ {
+ cb->OnLoopEnd(nullptr);
+ }
+
+ STDMETHOD_(void, OnBufferEnd(void* pBufferContext))
+ {
+ cb->OnEnd(nullptr);
+ }
+
+ STDMETHOD_(void, OnStreamEnd()) {}
+
+ STDMETHOD_(void, OnVoiceProcessingPassEnd()) {}
+
+ STDMETHOD_(void, OnVoiceProcessingPassStart(UINT32 SamplesRequired)) {}
+
+ STDMETHOD_(void, OnVoiceError(void* pBufferContext, HRESULT Error))
+ {
+ KGE_ERRORF("Voice error with HRESULT of %08X", Error);
+ }
+};
+
AudioModule::AudioModule()
: x_audio2_(nullptr)
, mastering_voice_(nullptr)
@@ -58,6 +99,8 @@ void AudioModule::DestroyModule()
{
KGE_DEBUG_LOGF("Destroying audio resources");
+ TranscoderCache::GetInstance().Clear();
+
if (mastering_voice_)
{
mastering_voice_->DestroyVoice();
@@ -73,31 +116,60 @@ void AudioModule::DestroyModule()
dlls::MediaFoundation::Get().MFShutdown();
}
-bool AudioModule::CreateSound(Sound& sound, const Transcoder::Buffer& buffer)
+TranscoderPtr AudioModule::CreateTranscoder(const String& file_path)
+{
+ if (!FileSystem::GetInstance().IsFileExists(file_path))
+ {
+ KGE_WARNF("Media file '%s' not found", file_path.c_str());
+ return nullptr;
+ }
+
+ String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path);
+
+ auto ptr = MakePtr();
+ HRESULT hr = ptr->LoadMediaFile(full_path);
+ if (FAILED(hr))
+ {
+ KGE_ERRORF("Load media file failed with HRESULT of %08X", hr);
+ return nullptr;
+ }
+ return ptr;
+}
+
+TranscoderPtr AudioModule::CreateTranscoder(const Resource& res)
+{
+ auto ptr = MakePtr();
+ HRESULT hr = ptr->LoadMediaResource(res);
+ if (FAILED(hr))
+ {
+ KGE_ERRORF("Load media resource failed with HRESULT of %08X", hr);
+ return nullptr;
+ }
+ return ptr;
+}
+
+bool AudioModule::CreateSound(Sound& sound, TranscoderPtr transcoder)
{
KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
HRESULT hr = S_OK;
+ auto buffer = transcoder->GetBuffer();
if (buffer.format == nullptr)
hr = E_INVALIDARG;
if (SUCCEEDED(hr))
{
+ auto chain = sound.GetCallbackChain();
+ chain->SetNative(VoiceCallback{ chain.Get() });
+ auto callback = const_cast(chain->GetNative().CastPtr());
+
IXAudio2SourceVoice* voice = nullptr;
-
- hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
-
+ hr = x_audio2_->CreateSourceVoice(&voice, buffer.format, 0, XAUDIO2_DEFAULT_FREQ_RATIO, callback);
if (SUCCEEDED(hr))
{
- IXAudio2SourceVoice* old = sound.GetXAudio2Voice();
- if (old)
- {
- old->DestroyVoice();
- old = nullptr;
- }
-
- sound.SetXAudio2Voice(voice);
+ sound.Close();
+ sound.SetNative(voice);
}
}
@@ -112,7 +184,6 @@ bool AudioModule::CreateSound(Sound& sound, const Transcoder::Buffer& buffer)
void AudioModule::Open()
{
KGE_ASSERT(x_audio2_ && "AudioModule hasn't been initialized!");
-
if (x_audio2_)
x_audio2_->StartEngine();
}
diff --git a/src/kiwano-audio/AudioModule.h b/src/kiwano-audio/AudioModule.h
index 0e51eb72..05045137 100644
--- a/src/kiwano-audio/AudioModule.h
+++ b/src/kiwano-audio/AudioModule.h
@@ -60,8 +60,16 @@ public:
void Close();
/// \~chinese
- /// @brief 从解码器数据缓冲中创建音频对象
- bool CreateSound(Sound& sound, const Transcoder::Buffer& buffer);
+ /// @brief 创建音频解码器
+ TranscoderPtr CreateTranscoder(const String& file_path);
+
+ /// \~chinese
+ /// @brief 创建音频解码器
+ TranscoderPtr CreateTranscoder(const Resource& res);
+
+ /// \~chinese
+ /// @brief 创建音频
+ bool CreateSound(Sound& sound, TranscoderPtr transcoder);
public:
void SetupModule() override;
diff --git a/src/kiwano-audio/Sound.cpp b/src/kiwano-audio/Sound.cpp
index f04421c4..ba9f2275 100644
--- a/src/kiwano-audio/Sound.cpp
+++ b/src/kiwano-audio/Sound.cpp
@@ -21,12 +21,58 @@
#include
#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()
{
@@ -42,7 +88,7 @@ Sound::Sound(const Resource& res)
Sound::Sound()
: opened_(false)
, playing_(false)
- , voice_(nullptr)
+ , volume_(1.f)
{
}
@@ -53,34 +99,17 @@ Sound::~Sound()
bool Sound::Load(const String& file_path)
{
- if (!FileSystem::GetInstance().IsFileExists(file_path))
- {
- KGE_WARNF("Media file '%s' not found", file_path.c_str());
- return false;
- }
-
if (opened_)
{
Close();
}
- String full_path = FileSystem::GetInstance().GetFullPathForFile(file_path);
-
- HRESULT hr = transcoder_.LoadMediaFile(full_path);
- if (FAILED(hr))
+ TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(file_path);
+ if (!transcoder)
{
- KGE_ERRORF("Load media file failed with HRESULT of %08X", hr);
return false;
}
-
- if (!AudioModule::GetInstance().CreateSound(*this, transcoder_.GetBuffer()))
- {
- Close();
- return false;
- }
-
- opened_ = true;
- return true;
+ return Load(transcoder);
}
bool Sound::Load(const Resource& res)
@@ -90,26 +119,31 @@ bool Sound::Load(const Resource& res)
Close();
}
- HRESULT hr = transcoder_.LoadMediaResource(res);
- if (FAILED(hr))
+ TranscoderPtr transcoder = AudioModule::GetInstance().CreateTranscoder(res);
+ if (!transcoder)
{
- KGE_ERRORF("Load media resource failed with HRESULT of %08X", hr);
return false;
}
-
- if (!AudioModule::GetInstance().CreateSound(*this, transcoder_.GetBuffer()))
- {
- Close();
- return false;
- }
-
- opened_ = true;
- return true;
+ return Load(transcoder);
}
-bool Sound::IsValid() const
+bool Sound::Load(TranscoderPtr transcoder)
{
- return voice_ != nullptr;
+ 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)
@@ -120,29 +154,30 @@ void Sound::Play(int loop_count)
return;
}
- KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
+ 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);
+ 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 wave_buffer = transcoder_.GetBuffer();
+ auto buffer = coder_->GetBuffer();
- XAUDIO2_BUFFER buffer = { 0 };
- buffer.pAudioData = wave_buffer.data;
- buffer.Flags = XAUDIO2_END_OF_STREAM;
- buffer.AudioBytes = wave_buffer.size;
- buffer.LoopCount = static_cast(loop_count);
+ 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(&buffer);
+ HRESULT hr = voice->SubmitSourceBuffer(&xaudio2_buffer);
if (SUCCEEDED(hr))
{
- hr = voice_->Start();
+ hr = voice->Start();
}
if (FAILED(hr))
@@ -155,48 +190,67 @@ void Sound::Play(int loop_count)
void Sound::Pause()
{
- KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
+ auto voice = GetNativePtr();
+ KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
- if (SUCCEEDED(voice_->Stop()))
+ 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()
{
- KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
+ auto voice = GetNativePtr();
+ KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
- if (SUCCEEDED(voice_->Start()))
+ 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()
{
- KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
+ auto voice = GetNativePtr();
+ KGE_ASSERT(voice != nullptr && "IXAudio2SourceVoice* is NULL");
- HRESULT hr = voice_->Stop();
+ HRESULT hr = voice->Stop();
if (SUCCEEDED(hr))
- hr = voice_->ExitLoop();
+ hr = voice->ExitLoop();
if (SUCCEEDED(hr))
- hr = voice_->FlushSourceBuffers();
+ hr = voice->FlushSourceBuffers();
if (SUCCEEDED(hr))
playing_ = false;
+
+ if (FAILED(hr))
+ {
+ KGE_ERRORF("Stop voice failed with HRESULT of %08X", hr);
+ }
}
void Sound::Close()
{
- if (voice_)
+ auto voice = GetNativePtr();
+ if (voice)
{
- voice_->Stop();
- voice_->FlushSourceBuffers();
- voice_->DestroyVoice();
- voice_ = nullptr;
+ voice->Stop();
+ voice->FlushSourceBuffers();
+ voice->DestroyVoice();
}
- transcoder_.ClearBuffer();
-
+ coder_ = nullptr;
opened_ = false;
playing_ = false;
}
@@ -205,34 +259,213 @@ bool Sound::IsPlaying() const
{
if (opened_)
{
- if (!voice_)
+ if (!playing_)
+ return false;
+
+ auto voice = GetNativePtr();
+ if (!voice)
return false;
XAUDIO2_VOICE_STATE state;
- voice_->GetState(&state);
- uint32_t buffers_queued = state.BuffersQueued;
-
- if (buffers_queued && playing_)
- return true;
+ voice->GetState(&state);
+ return !!state.BuffersQueued;
}
return false;
}
float Sound::GetVolume() const
{
- KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
-
- float volume = 0.0f;
- voice_->GetVolume(&volume);
- return volume;
+ return volume_;
}
void Sound::SetVolume(float volume)
{
- KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
+ if (volume_ == volume)
+ {
+ return;
+ }
+ volume_ = volume;
- volume = std::min(std::max(volume, -224.f), 224.f);
- voice_->SetVolume(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
diff --git a/src/kiwano-audio/Sound.h b/src/kiwano-audio/Sound.h
index ebd2ce84..d8580d26 100644
--- a/src/kiwano-audio/Sound.h
+++ b/src/kiwano-audio/Sound.h
@@ -21,8 +21,7 @@
#pragma once
#include
#include
-#include
-#include
+#include
#include
namespace kiwano
@@ -30,8 +29,10 @@ namespace kiwano
namespace audio
{
class AudioModule;
+class SoundPlayer;
KGE_DECLARE_SMART_PTR(Sound);
+KGE_DECLARE_SMART_PTR(SoundCallback);
/**
* \addtogroup Audio
@@ -40,13 +41,65 @@ KGE_DECLARE_SMART_PTR(Sound);
/**
* \~chinese
- * @brief 音频对象
+ * @brief 音频回调
*/
-class KGE_API Sound : public ObjectBase
+class KGE_API SoundCallback : public NativeObject
+{
+public:
+ /// \~chinese
+ /// @brief 创建一个回调,在音频开始播放时执行
+ static SoundCallbackPtr OnStart(const Function& cb);
+
+ /// \~chinese
+ /// @brief 创建一个回调,在音频循环结束时执行
+ static SoundCallbackPtr OnLoopEnd(const Function& cb);
+
+ /// \~chinese
+ /// @brief 创建一个回调,在音频结束时执行
+ static SoundCallbackPtr OnEnd(const Function& cb);
+
+ /// \~chinese
+ /// @brief 创建一个回调,在音频修改音量时执行
+ static SoundCallbackPtr OnVolumeChanged(const Function& cb);
+
+ /// \~chinese
+ /// @brief 在音频开始播放时执行
+ virtual inline void OnStart(Sound* sound) {}
+
+ /// \~chinese
+ /// @brief 在音频循环结束时执行
+ virtual inline void OnLoopEnd(Sound* sound) {}
+
+ /// \~chinese
+ /// @brief 在音频结束时执行
+ virtual inline void OnEnd(Sound* sound) {}
+
+ /// \~chinese
+ /// @brief 在音频修改音量时执行
+ virtual inline float OnVolumeChanged(Sound* sound, float volume)
+ {
+ return volume;
+ }
+};
+
+/**
+ * \~chinese
+ * @brief 音频
+ */
+class KGE_API Sound : public NativeObject
{
friend class AudioModule;
+ friend class SoundPlayer;
public:
+ /// \~chinese
+ /// @brief 预加载音频
+ static SoundPtr Preload(const String& file_path, std::initializer_list callbacks = {});
+
+ /// \~chinese
+ /// @brief 预加载音频资源
+ static SoundPtr Preload(const Resource& res, std::initializer_list callbacks = {});
+
/// \~chinese
/// @brief 创建音频对象
/// @param res 本地音频文件路径
@@ -61,20 +114,6 @@ public:
virtual ~Sound();
- /// \~chinese
- /// @brief 打开本地音频文件
- /// @param res 本地音频文件路径
- bool Load(const String& file_path);
-
- /// \~chinese
- /// @brief 打开音频资源
- /// @param res 音频资源
- bool Load(const Resource& res);
-
- /// \~chinese
- /// @brief 是否有效
- bool IsValid() const;
-
/// \~chinese
/// @brief 播放
/// @param loop_count 播放循环次数,设置 -1 为循环播放
@@ -109,28 +148,64 @@ public:
/// @param volume 音量大小,1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量
void SetVolume(float volume);
-private:
- IXAudio2SourceVoice* GetXAudio2Voice() const;
+ /// \~chinese
+ /// @brief 添加回调
+ void AddCallback(SoundCallbackPtr callback);
- void SetXAudio2Voice(IXAudio2SourceVoice* voice);
+ /// \~chinese
+ /// @brief 获取所有回调
+ List& GetCallbacks();
+
+ /// \~chinese
+ /// @brief 获取所有回调
+ const List& GetCallbacks() const;
+
+protected:
+ /// \~chinese
+ /// @brief 打开本地音频文件
+ /// @param res 本地音频文件路径
+ bool Load(const String& file_path);
+
+ /// \~chinese
+ /// @brief 打开音频资源
+ /// @param res 音频资源
+ bool Load(const Resource& res);
+
+ /// \~chinese
+ /// @brief 打开音频资源
+ /// @param res 音频资源
+ bool Load(TranscoderPtr transcoder);
+
+ SoundCallbackPtr GetCallbackChain();
+
+ void ResetVolume();
private:
- bool opened_;
- bool playing_;
- Transcoder transcoder_;
- IXAudio2SourceVoice* voice_;
+ bool opened_;
+ bool playing_;
+ float volume_;
+ TranscoderPtr coder_;
+
+ SoundCallbackPtr callback_chain_;
+ List callbacks_;
};
/** @} */
-inline IXAudio2SourceVoice* Sound::GetXAudio2Voice() const
+inline List& kiwano::audio::Sound::GetCallbacks()
{
- return voice_;
+ return callbacks_;
}
-inline void Sound::SetXAudio2Voice(IXAudio2SourceVoice* voice)
+inline const List& kiwano::audio::Sound::GetCallbacks() const
{
- voice_ = voice;
+ return callbacks_;
}
+
+inline void kiwano::audio::Sound::AddCallback(SoundCallbackPtr callback)
+{
+ callbacks_.push_back(callback);
+}
+
} // namespace audio
} // namespace kiwano
diff --git a/src/kiwano-audio/SoundPlayer.cpp b/src/kiwano-audio/SoundPlayer.cpp
index db48a810..737dbaeb 100644
--- a/src/kiwano-audio/SoundPlayer.cpp
+++ b/src/kiwano-audio/SoundPlayer.cpp
@@ -19,6 +19,7 @@
// THE SOFTWARE.
#include
+#include
namespace kiwano
{
@@ -28,85 +29,55 @@ namespace audio
SoundPlayer::SoundPlayer()
: volume_(1.f)
{
+ class SoundCallbackFunc : public SoundCallback
+ {
+ public:
+ Function cb_on_end;
+ Function cb_on_volume_changed;
+
+ void OnEnd(Sound* sound) override
+ {
+ cb_on_end(sound);
+ }
+
+ float OnVolumeChanged(Sound* sound, float volume) override
+ {
+ return cb_on_volume_changed(sound, volume);
+ }
+ };
+ auto cb = MakePtr();
+ cb->cb_on_end = std::bind(&SoundPlayer::OnEnd, this, std::placeholders::_1);
+ cb->cb_on_volume_changed =
+ std::bind(&SoundPlayer::OnVolumeChanged, this, std::placeholders::_1, std::placeholders::_2);
+ callback_ = cb;
}
SoundPlayer::~SoundPlayer()
{
- ClearCache();
}
-size_t SoundPlayer::GetId(const String& file_path) const
+void SoundPlayer::Play(SoundPtr sound, int loop_count)
{
- return std::hash()(file_path);
-}
-
-size_t SoundPlayer::GetId(const Resource& res) const
-{
- return static_cast(res.GetId());
-}
-
-size_t SoundPlayer::Load(const String& file_path)
-{
- size_t id = GetId(file_path);
- if (sound_cache_.end() != sound_cache_.find(id))
- return id;
-
- SoundPtr sound = MakePtr();
- if (sound && sound->Load(file_path))
+ if (sound)
{
- sound->SetVolume(volume_);
- sound_cache_.insert(std::make_pair(id, sound));
- return id;
- }
- return 0;
-}
-
-size_t SoundPlayer::Load(const Resource& res)
-{
- size_t id = GetId(res);
- if (sound_cache_.end() != sound_cache_.find(id))
- return id;
-
- SoundPtr sound = MakePtr();
-
- if (sound && sound->Load(res))
- {
- sound->SetVolume(volume_);
- sound_cache_.insert(std::make_pair(id, sound));
- return id;
- }
- return 0;
-}
-
-void SoundPlayer::Play(size_t id, int loop_count)
-{
- if (auto sound = GetSound(id))
+ SetCallback(sound.Get());
sound->Play(loop_count);
+ sound_list_.push_back(sound);
+ }
}
-void SoundPlayer::Pause(size_t id)
+SoundPtr SoundPlayer::Play(const String& file_path, int loop_count, std::initializer_list callbacks)
{
- if (auto sound = GetSound(id))
- sound->Pause();
+ SoundPtr sound = Sound::Preload(file_path, callbacks);
+ Play(sound, loop_count);
+ return sound;
}
-void SoundPlayer::Resume(size_t id)
+SoundPtr SoundPlayer::Play(const Resource& res, int loop_count, std::initializer_list callbacks)
{
- if (auto sound = GetSound(id))
- sound->Resume();
-}
-
-void SoundPlayer::Stop(size_t id)
-{
- if (auto sound = GetSound(id))
- sound->Stop();
-}
-
-bool SoundPlayer::IsPlaying(size_t id)
-{
- if (auto sound = GetSound(id))
- return sound->IsPlaying();
- return false;
+ SoundPtr sound = Sound::Preload(res, callbacks);
+ Play(sound, loop_count);
+ return sound;
}
float SoundPlayer::GetVolume() const
@@ -116,48 +87,81 @@ float SoundPlayer::GetVolume() const
void SoundPlayer::SetVolume(float volume)
{
- volume_ = std::min(std::max(volume, -224.f), 224.f);
- for (auto& pair : sound_cache_)
- {
- pair.second->SetVolume(volume_);
- }
-}
-
-SoundPtr SoundPlayer::GetSound(size_t id) const
-{
- auto iter = sound_cache_.find(id);
- if (iter != sound_cache_.end())
- return iter->second;
- return SoundPtr();
+ volume_ = volume;
}
void SoundPlayer::PauseAll()
{
- for (auto& pair : sound_cache_)
+ for (auto& sound : sound_list_)
{
- pair.second->Pause();
+ sound->Pause();
}
}
void SoundPlayer::ResumeAll()
{
- for (auto& pair : sound_cache_)
+ for (auto& sound : sound_list_)
{
- pair.second->Resume();
+ sound->Resume();
}
}
void SoundPlayer::StopAll()
{
- for (auto& pair : sound_cache_)
+ for (auto& sound : sound_list_)
{
- pair.second->Stop();
+ sound->Stop();
}
}
-void SoundPlayer::ClearCache()
+void SoundPlayer::OnEnd(Sound* sound)
{
- sound_cache_.clear();
+ // remove callback
+ RemoveCallback(sound);
+
+ // remove sound after stopped
+ auto iter = std::find(sound_list_.begin(), sound_list_.end(), sound);
+ if (iter != sound_list_.end())
+ {
+ trash_.push_back(*iter);
+ sound_list_.erase(iter);
+ }
+
+ // clear trash in main thread
+ Application::GetInstance().PerformInMainThread(std::bind(&SoundPlayer::ClearTrash, this));
}
+
+float SoundPlayer::OnVolumeChanged(Sound* sound, float volume)
+{
+ return volume * volume_;
+}
+
+void SoundPlayer::SetCallback(Sound* sound)
+{
+ // add callback if not exists
+ auto& cbs = sound->GetCallbacks();
+ auto iter = std::find(cbs.begin(), cbs.end(), callback_);
+ if (iter == cbs.end())
+ {
+ sound->AddCallback(callback_);
+ }
+ sound->ResetVolume();
+}
+
+void SoundPlayer::RemoveCallback(Sound* sound)
+{
+ auto& cbs = sound->GetCallbacks();
+ auto iter = std::find(cbs.begin(), cbs.end(), callback_);
+ if (iter != cbs.end())
+ {
+ *iter = nullptr; // will be removed by sound
+ }
+}
+
+void SoundPlayer::ClearTrash()
+{
+ trash_.clear();
+}
+
} // namespace audio
} // namespace kiwano
diff --git a/src/kiwano-audio/SoundPlayer.h b/src/kiwano-audio/SoundPlayer.h
index 6ed8d8f6..baca0034 100644
--- a/src/kiwano-audio/SoundPlayer.h
+++ b/src/kiwano-audio/SoundPlayer.h
@@ -20,7 +20,6 @@
#pragma once
#include
-#include
namespace kiwano
{
@@ -40,73 +39,31 @@ KGE_DECLARE_SMART_PTR(SoundPlayer);
class KGE_API SoundPlayer : public ObjectBase
{
public:
+ using SoundList = List;
+
SoundPlayer();
~SoundPlayer();
/// \~chinese
- /// @brief 加载本地音频文件
- /// @param file_path 本地音频文件路径
- /// @return 音频标识符
- size_t Load(const String& file_path);
-
- /// \~chinese
- /// @brief 加载音频资源
- /// @param res 音频资源
- /// @return 音频标识符
- size_t Load(const Resource& res);
+ /// @brief 播放音频
+ /// @param sound 音频
+ /// @param loop_count 播放循环次数,设置 -1 为循环播放
+ void Play(SoundPtr sound, int loop_count = 0);
/// \~chinese
/// @brief 播放音频
- /// @param id 音频标识符
- /// @param loop_count 播放循环次数,设置 -1 为循环播放
- void Play(size_t id, int loop_count = 0);
-
- /// \~chinese
- /// @brief 暂停音频
- /// @param id 音频标识符
- void Pause(size_t id);
-
- /// \~chinese
- /// @brief 继续播放音频
- /// @param id 音频标识符
- void Resume(size_t id);
-
- /// \~chinese
- /// @brief 停止音频
- /// @param id 音频标识符
- void Stop(size_t id);
-
- /// \~chinese
- /// @brief 获取音频播放状态
- /// @param id 音频标识符
- bool IsPlaying(size_t id);
-
- /// \~chinese
- /// @brief 获取音量
- float GetVolume() const;
-
- /// \~chinese
- /// @brief 设置音量
- /// @param volume 音量大小,1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量
- void SetVolume(float volume);
-
- /// \~chinese
- /// @brief 获取本地音频文件id
/// @param file_path 本地音频文件路径
- /// @return 音频标识符
- size_t GetId(const String& file_path) const;
+ /// @param loop_count 播放循环次数,设置 -1 为循环播放
+ /// @param callbacks 注册回调
+ SoundPtr Play(const String& file_path, int loop_count = 0, std::initializer_list callbacks = {});
/// \~chinese
- /// @brief 获取音频资源id
+ /// @brief 播放音频
/// @param res 音频资源
- /// @return 音频标识符
- size_t GetId(const Resource& res) const;
-
- /// \~chinese
- /// @brief 获取音乐对象
- /// @param id 音频标识符
- SoundPtr GetSound(size_t id) const;
+ /// @param loop_count 播放循环次数,设置 -1 为循环播放
+ /// @param callbacks 注册回调
+ SoundPtr Play(const Resource& res, int loop_count = 0, std::initializer_list callbacks = {});
/// \~chinese
/// @brief 暂停所有音频
@@ -121,16 +78,42 @@ public:
void StopAll();
/// \~chinese
- /// @brief 清除缓存
- void ClearCache();
+ /// @brief 获取正在播放的音频列表
+ const SoundList& GetPlayingList() const;
-private:
- float volume_;
+ /// \~chinese
+ /// @brief 获取音量
+ float GetVolume() const;
- using SoundMap = Map;
- SoundMap sound_cache_;
+ /// \~chinese
+ /// @brief 设置音量
+ /// @param volume 音量大小,1.0 为原始音量, 大于 1 为放大音量, 0 为最小音量
+ void SetVolume(float volume);
+
+protected:
+ void OnEnd(Sound* sound);
+
+ float OnVolumeChanged(Sound* sound, float volume);
+
+ void SetCallback(Sound* sound);
+
+ void RemoveCallback(Sound* sound);
+
+ void ClearTrash();
+
+protected:
+ float volume_;
+ SoundList sound_list_;
+ SoundList trash_;
+ SoundCallbackPtr callback_;
};
/** @} */
+
+inline const SoundPlayer::SoundList& SoundPlayer::GetPlayingList() const
+{
+ return sound_list_;
+}
+
} // namespace audio
} // namespace kiwano
diff --git a/src/kiwano-audio/Transcoder.h b/src/kiwano-audio/Transcoder.h
index bd49b3ba..6bc6bc2c 100644
--- a/src/kiwano-audio/Transcoder.h
+++ b/src/kiwano-audio/Transcoder.h
@@ -20,6 +20,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -28,7 +29,9 @@ namespace kiwano
{
namespace audio
{
-class Sound;
+class AudioModule;
+
+KGE_DECLARE_SMART_PTR(Transcoder);
/**
* \addtogroup Audio
@@ -39,9 +42,9 @@ class Sound;
* \~chinese
* @brief 音频解码器
*/
-class KGE_API Transcoder
+class KGE_API Transcoder : public ObjectBase
{
- friend class Sound;
+ friend class AudioModule;
public:
/**
@@ -86,6 +89,56 @@ private:
WAVEFORMATEX* wave_format_;
};
+
+class KGE_API TranscoderCache final : public Singleton
+{
+ friend Singleton;
+
+public:
+ /// \~chinese
+ /// @brief 添加缓存
+ inline void Add(size_t key, TranscoderPtr v)
+ {
+ cache_.insert(std::make_pair(key, v));
+ }
+
+ /// \~chinese
+ /// @brief 获取缓存
+ inline TranscoderPtr Get(size_t key) const
+ {
+ if (cache_.count(key))
+ {
+ return cache_.at(key);
+ }
+ return nullptr;
+ }
+
+ /// \~chinese
+ /// @brief 移除缓存
+ inline void Remove(size_t key)
+ {
+ cache_.erase(key);
+ }
+
+ /// \~chinese
+ /// @brief 清空缓存
+ inline void Clear()
+ {
+ cache_.clear();
+ }
+
+ ~TranscoderCache()
+ {
+ Clear();
+ }
+
+private:
+ TranscoderCache() = default;
+
+private:
+ UnorderedMap cache_;
+};
+
/** @} */
} // namespace audio
} // namespace kiwano
diff --git a/src/kiwano-network/HttpModule.cpp b/src/kiwano-network/HttpModule.cpp
index 83f16e69..29a8c0cc 100644
--- a/src/kiwano-network/HttpModule.cpp
+++ b/src/kiwano-network/HttpModule.cpp
@@ -260,7 +260,7 @@ void HttpModule::NetworkThread()
response_queue_.push(response);
response_mutex_.unlock();
- Application::GetInstance().PreformInMainThread(Closure(this, &HttpModule::DispatchResponseCallback));
+ Application::GetInstance().PerformInMainThread(Closure(this, &HttpModule::DispatchResponseCallback));
}
}
diff --git a/src/kiwano-physics/PhysicWorld.cpp b/src/kiwano-physics/PhysicWorld.cpp
index fc459269..5b6b4cca 100644
--- a/src/kiwano-physics/PhysicWorld.cpp
+++ b/src/kiwano-physics/PhysicWorld.cpp
@@ -31,7 +31,7 @@ class PhysicWorld::DebugDrawer : public b2Draw
public:
DebugDrawer(const Size& size)
{
- canvas_ = MakePtr