update Sound

This commit is contained in:
Nomango 2019-04-11 16:00:57 +08:00 committed by Nomango
parent b49d583fd5
commit 2ab6a7bd29
13 changed files with 138 additions and 359 deletions

View File

@ -9,10 +9,9 @@
<ClInclude Include="kiwano-network.h" />
<ClInclude Include="audio\audio-modules.h" />
<ClInclude Include="audio\audio.h" />
<ClInclude Include="audio\Music.h" />
<ClInclude Include="audio\Sound.h" />
<ClInclude Include="audio\Player.h" />
<ClInclude Include="audio\Transcoder.h" />
<ClInclude Include="audio\Voice.h" />
<ClInclude Include="imgui\ImGuiLayer.h" />
<ClInclude Include="imgui\ImGuiView.h" />
<ClInclude Include="imgui\imgui_impl_dx11.h" />
@ -120,10 +119,9 @@
<ClCompile Include="2d\Transition.cpp" />
<ClCompile Include="audio\audio-modules.cpp" />
<ClCompile Include="audio\audio.cpp" />
<ClCompile Include="audio\Music.cpp" />
<ClCompile Include="audio\Sound.cpp" />
<ClCompile Include="audio\Player.cpp" />
<ClCompile Include="audio\Transcoder.cpp" />
<ClCompile Include="audio\Voice.cpp" />
<ClCompile Include="base\AsyncTask.cpp" />
<ClCompile Include="base\EventDispatcher.cpp" />
<ClCompile Include="base\EventListener.cpp" />

View File

@ -267,18 +267,12 @@
<ClInclude Include="audio\audio-modules.h">
<Filter>audio</Filter>
</ClInclude>
<ClInclude Include="audio\Music.h">
<Filter>audio</Filter>
</ClInclude>
<ClInclude Include="audio\Player.h">
<Filter>audio</Filter>
</ClInclude>
<ClInclude Include="audio\Transcoder.h">
<Filter>audio</Filter>
</ClInclude>
<ClInclude Include="audio\Voice.h">
<Filter>audio</Filter>
</ClInclude>
<ClInclude Include="network\helper.h">
<Filter>network</Filter>
</ClInclude>
@ -321,6 +315,9 @@
<ClInclude Include="third-party\ImGui\imstb_truetype.h">
<Filter>third-party\ImGui</Filter>
</ClInclude>
<ClInclude Include="audio\Sound.h">
<Filter>audio</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="ui\Button.cpp">
@ -455,18 +452,12 @@
<ClCompile Include="audio\audio-modules.cpp">
<Filter>audio</Filter>
</ClCompile>
<ClCompile Include="audio\Music.cpp">
<Filter>audio</Filter>
</ClCompile>
<ClCompile Include="audio\Player.cpp">
<Filter>audio</Filter>
</ClCompile>
<ClCompile Include="audio\Transcoder.cpp">
<Filter>audio</Filter>
</ClCompile>
<ClCompile Include="audio\Voice.cpp">
<Filter>audio</Filter>
</ClCompile>
<ClCompile Include="network\HttpClient.cpp">
<Filter>network</Filter>
</ClCompile>
@ -491,5 +482,8 @@
<ClCompile Include="third-party\ImGui\imgui_widgets.cpp">
<Filter>third-party\ImGui</Filter>
</ClCompile>
<ClCompile Include="audio\Sound.cpp">
<Filter>audio</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -36,63 +36,59 @@ namespace kiwano
bool Player::Load(Resource const& res)
{
size_t hash_code = res.GetHashCode();
if (musics_cache_.end() != musics_cache_.find(hash_code))
if (sound_cache_.end() != sound_cache_.find(hash_code))
return true;
MusicPtr music = new (std::nothrow) Music();
SoundPtr sound = new (std::nothrow) Sound();
if (music)
if (sound)
{
if (music->Load(res))
if (sound->Load(res))
{
music->SetVolume(volume_);
musics_cache_.insert(std::make_pair(hash_code, music));
sound->SetVolume(volume_);
sound_cache_.insert(std::make_pair(hash_code, sound));
return true;
}
}
return false;
}
bool Player::Play(Resource const& res, int loop_count)
void Player::Play(Resource const& res, int loop_count)
{
if (Load(res))
{
size_t hash_code = res.GetHashCode();
auto music = musics_cache_[hash_code];
if (music->Play(loop_count))
{
return true;
}
if (sound_cache_.end() != sound_cache_.find(hash_code))
sound_cache_[hash_code]->Play(loop_count);
}
return false;
}
void Player::Pause(Resource const& res)
{
size_t hash_code = res.GetHashCode();
if (musics_cache_.end() != musics_cache_.find(hash_code))
musics_cache_[hash_code]->Pause();
if (sound_cache_.end() != sound_cache_.find(hash_code))
sound_cache_[hash_code]->Pause();
}
void Player::Resume(Resource const& res)
{
size_t hash_code = res.GetHashCode();
if (musics_cache_.end() != musics_cache_.find(hash_code))
musics_cache_[hash_code]->Resume();
if (sound_cache_.end() != sound_cache_.find(hash_code))
sound_cache_[hash_code]->Resume();
}
void Player::Stop(Resource const& res)
{
size_t hash_code = res.GetHashCode();
if (musics_cache_.end() != musics_cache_.find(hash_code))
musics_cache_[hash_code]->Stop();
if (sound_cache_.end() != sound_cache_.find(hash_code))
sound_cache_[hash_code]->Stop();
}
bool Player::IsPlaying(Resource const& res)
{
size_t hash_code = res.GetHashCode();
if (musics_cache_.end() != musics_cache_.find(hash_code))
return musics_cache_[hash_code]->IsPlaying();
if (sound_cache_.end() != sound_cache_.find(hash_code))
return sound_cache_[hash_code]->IsPlaying();
return false;
}
@ -104,7 +100,7 @@ namespace kiwano
void Player::SetVolume(float volume)
{
volume_ = std::min(std::max(volume, -224.f), 224.f);
for (const auto& pair : musics_cache_)
for (const auto& pair : sound_cache_)
{
pair.second->SetVolume(volume_);
}
@ -112,7 +108,7 @@ namespace kiwano
void Player::PauseAll()
{
for (const auto& pair : musics_cache_)
for (const auto& pair : sound_cache_)
{
pair.second->Pause();
}
@ -120,7 +116,7 @@ namespace kiwano
void Player::ResumeAll()
{
for (const auto& pair : musics_cache_)
for (const auto& pair : sound_cache_)
{
pair.second->Resume();
}
@ -128,7 +124,7 @@ namespace kiwano
void Player::StopAll()
{
for (const auto& pair : musics_cache_)
for (const auto& pair : sound_cache_)
{
pair.second->Stop();
}
@ -136,6 +132,6 @@ namespace kiwano
void Player::ClearCache()
{
musics_cache_.clear();
sound_cache_.clear();
}
}

View File

@ -28,7 +28,7 @@ namespace kiwano
class KGE_API Player
: protected Object
{
using MusicMap = Map<size_t, MusicPtr>;
using MusicMap = Map<size_t, SoundPtr>;
public:
Player();
@ -41,7 +41,7 @@ namespace kiwano
);
// 播放音乐
bool Play(
void Play(
Resource const& res, /* 音乐资源 */
int loop_count = 0 /* 播放循环次数 (-1 为循环播放) */
);
@ -88,6 +88,6 @@ namespace kiwano
protected:
float volume_;
MusicMap musics_cache_;
MusicMap sound_cache_;
};
}

View File

@ -19,32 +19,32 @@
// THE SOFTWARE.
#include "../kiwano-audio.h"
#include "Music.h"
#include "Sound.h"
#include "Transcoder.h"
namespace kiwano
{
Music::Music()
Sound::Sound()
: opened_(false)
, playing_(false)
, wave_data_(nullptr)
, size_(0)
, wave_data_(nullptr)
, voice_(nullptr)
{
}
Music::Music(Resource const& res)
: Music()
Sound::Sound(Resource const& res)
: Sound()
{
Load(res);
}
Music::~Music()
Sound::~Sound()
{
Close();
}
bool Music::Load(Resource const& res)
bool Sound::Load(Resource const& res)
{
if (opened_)
{
@ -74,7 +74,7 @@ namespace kiwano
return false;
}
hr = Audio::Instance().CreateVoice(voice_, transcoder.GetWaveFormatEx());
hr = Audio::Instance().CreateVoice(&voice_, transcoder.GetWaveFormatEx());
if (FAILED(hr))
{
if (wave_data_)
@ -90,56 +90,86 @@ namespace kiwano
return true;
}
bool Music::Play(int loop_count)
void Sound::Play(int loop_count)
{
if (!opened_)
{
KGE_ERROR_LOG(L"Music must be opened first!");
return false;
KGE_ERROR_LOG(L"Sound must be opened first!");
return;
}
UINT32 buffers_queued = 0;
voice_.GetBuffersQueued(&buffers_queued);
if (buffers_queued)
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();
if (loop_count < 0)
loop_count = XAUDIO2_LOOP_INFINITE;
else
loop_count = std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1);
// clamp loop count
loop_count = (loop_count < 0) ? XAUDIO2_LOOP_INFINITE : std::min(loop_count, XAUDIO2_LOOP_INFINITE - 1);
XAUDIO2_BUFFER buffer = { 0 };
buffer.pAudioData = wave_data_;
buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.AudioBytes = size_;
buffer.LoopCount = static_cast<UINT32>(loop_count);
HRESULT hr = voice_->SubmitSourceBuffer(&buffer);
if (SUCCEEDED(hr))
{
hr = voice_->Start();
}
HRESULT hr = voice_.Play(wave_data_, size_, static_cast<UINT32>(loop_count));
if (FAILED(hr))
{
KGE_ERROR_LOG(L"Submitting source buffer failed with HRESULT of %08X", hr);
}
playing_ = SUCCEEDED(hr);
return playing_;
}
void Music::Pause()
void Sound::Pause()
{
if (SUCCEEDED(voice_.Pause()))
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
if (SUCCEEDED(voice_->Stop()))
playing_ = false;
}
void Music::Resume()
void Sound::Resume()
{
if (SUCCEEDED(voice_.Resume()))
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
if (SUCCEEDED(voice_->Start()))
playing_ = true;
}
void Music::Stop()
void Sound::Stop()
{
if (SUCCEEDED(voice_.Stop()))
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;
}
void Music::Close()
void Sound::Close()
{
voice_.Destroy();
if (voice_)
{
voice_->Stop();
voice_->FlushSourceBuffers();
voice_->DestroyVoice();
voice_ = nullptr;
}
if (wave_data_)
{
@ -151,27 +181,37 @@ namespace kiwano
playing_ = false;
}
bool Music::IsPlaying() const
bool Sound::IsPlaying() const
{
if (opened_)
{
UINT32 buffers_queued = 0;
voice_.GetBuffersQueued(&buffers_queued);
if (!voice_)
return false;
XAUDIO2_VOICE_STATE state;
voice_->GetState(&state);
UINT32 buffers_queued = state.BuffersQueued;
if (buffers_queued && playing_)
return true;
}
return false;
}
float Music::GetVolume() const
float Sound::GetVolume() const
{
float volume = 0.f;
voice_.GetVolume(&volume);
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
float volume = 0.0f;
voice_->GetVolume(&volume);
return volume;
}
bool Music::SetVolume(float volume)
void Sound::SetVolume(float volume)
{
return SUCCEEDED(voice_.SetVolume(volume));
KGE_ASSERT(voice_ != nullptr && "IXAudio2SourceVoice* is NULL");
volume = std::min(std::max(volume, -224.f), 224.f);
voice_->SetVolume(volume);
}
}

View File

@ -19,24 +19,24 @@
// THE SOFTWARE.
#pragma once
#include "Voice.h"
#include <xaudio2.h>
namespace kiwano
{
KGE_DECLARE_SMART_PTR(Music);
KGE_DECLARE_SMART_PTR(Sound);
// 音乐对象
class KGE_API Music
class KGE_API Sound
: public virtual Object
{
public:
Music();
Sound();
Music(
Sound(
Resource const& res /* 音乐资源 */
);
virtual ~Music();
virtual ~Sound();
// 打开音乐资源
bool Load(
@ -44,7 +44,7 @@ namespace kiwano
);
// 播放
bool Play(
void Play(
int loop_count = 0 /* 播放循环次数 (-1 为循环播放) */
);
@ -67,7 +67,7 @@ namespace kiwano
float GetVolume() const;
// 设置音量
bool SetVolume(
void SetVolume(
float volume /* 1 为原始音量, 大于 1 为放大音量, 0 为最小音量 */
);
@ -76,6 +76,6 @@ namespace kiwano
bool playing_;
UINT32 size_;
BYTE* wave_data_;
Voice voice_;
IXAudio2SourceVoice* voice_;
};
}

View File

@ -1,148 +0,0 @@
// 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.h"
#include "Voice.h"
namespace kiwano
{
Voice::Voice()
: source_voice_(nullptr)
{
}
Voice::Voice(IXAudio2SourceVoice * source_voice)
: source_voice_(source_voice)
{
}
Voice::~Voice()
{
Destroy();
Audio::Instance().DeleteVoice(this);
}
HRESULT Voice::Play(const BYTE * wave_data, UINT32 data_size, UINT32 loop_count)
{
if (!source_voice_)
return E_UNEXPECTED;
XAUDIO2_BUFFER buffer = { 0 };
buffer.pAudioData = wave_data;
buffer.Flags = XAUDIO2_END_OF_STREAM;
buffer.AudioBytes = data_size;
buffer.LoopCount = loop_count;
HRESULT hr = source_voice_->SubmitSourceBuffer(&buffer);
if (SUCCEEDED(hr))
{
hr = source_voice_->Start();
}
return hr;
}
HRESULT Voice::Pause()
{
if (!source_voice_)
return E_UNEXPECTED;
return source_voice_->Stop();
}
HRESULT Voice::Resume()
{
if (!source_voice_)
return E_UNEXPECTED;
return source_voice_->Start();
}
HRESULT Voice::Stop()
{
if (!source_voice_)
return E_UNEXPECTED;
HRESULT hr = source_voice_->Stop();
if (SUCCEEDED(hr))
{
hr = source_voice_->ExitLoop();
}
if (SUCCEEDED(hr))
{
hr = source_voice_->FlushSourceBuffers();
}
return hr;
}
HRESULT Voice::GetVolume(float * volume) const
{
if (!source_voice_)
return E_UNEXPECTED;
if (volume == nullptr)
return E_POINTER;
source_voice_->GetVolume(volume);
return S_OK;
}
HRESULT Voice::SetVolume(float volume)
{
if (!source_voice_)
return E_UNEXPECTED;
volume = std::min(std::max(volume, -224.f), 224.f);
return source_voice_->SetVolume(volume);
}
HRESULT Voice::GetBuffersQueued(UINT32 * queued) const
{
if (!source_voice_)
return E_UNEXPECTED;
if (queued == nullptr)
return E_POINTER;
XAUDIO2_VOICE_STATE state;
source_voice_->GetState(&state);
*queued = state.BuffersQueued;
return S_OK;
}
void Voice::Destroy()
{
if (source_voice_)
{
source_voice_->Stop();
source_voice_->FlushSourceBuffers();
source_voice_->DestroyVoice();
source_voice_ = nullptr;
}
}
void Voice::SetSourceVoice(IXAudio2SourceVoice * source_voice)
{
Destroy();
source_voice_ = source_voice;
}
}

View File

@ -1,72 +0,0 @@
// 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.
#pragma once
#include <xaudio2.h>
namespace kiwano
{
class KGE_API Voice
: protected Noncopyable
{
public:
Voice();
Voice(
IXAudio2SourceVoice* source_voice
);
~Voice();
HRESULT Play(
const BYTE* wave_data,
UINT32 data_size,
UINT32 loop_count
);
HRESULT Pause();
HRESULT Resume();
HRESULT Stop();
HRESULT GetVolume(
float* volume
) const;
HRESULT SetVolume(
float volume
);
HRESULT GetBuffersQueued(
UINT32* queued
) const;
void Destroy();
void SetSourceVoice(
IXAudio2SourceVoice* source_voice
);
protected:
IXAudio2SourceVoice* source_voice_;
};
}

View File

@ -57,8 +57,6 @@ namespace kiwano
{
KGE_LOG(L"Destroying audio resources");
ClearVoiceCache();
if (mastering_voice_)
{
mastering_voice_->DestroyVoice();
@ -74,34 +72,17 @@ namespace kiwano
modules::MediaFoundation::Get().MFShutdown();
}
HRESULT Audio::CreateVoice(Voice& voice, const WAVEFORMATEX* wfx)
HRESULT Audio::CreateVoice(IXAudio2SourceVoice** voice, const WAVEFORMATEX* wfx)
{
HRESULT hr;
IXAudio2SourceVoice* source_voice;
hr = x_audio2_->CreateSourceVoice(&source_voice, wfx, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
if (SUCCEEDED(hr))
if (voice == nullptr)
{
voice.SetSourceVoice(source_voice);
voice_cache_.insert(&voice);
return E_UNEXPECTED;
}
HRESULT hr = x_audio2_->CreateSourceVoice(voice, wfx, 0, XAUDIO2_DEFAULT_FREQ_RATIO);
return hr;
}
void Audio::DeleteVoice(Voice* voice)
{
voice_cache_.erase(voice);
}
void Audio::ClearVoiceCache()
{
for (auto voice : voice_cache_)
{
voice->Destroy();
}
voice_cache_.clear();
}
void Audio::Open()
{
x_audio2_->StartEngine();

View File

@ -19,7 +19,6 @@
// THE SOFTWARE.
#pragma once
#include "Voice.h"
namespace kiwano
{
@ -29,8 +28,6 @@ namespace kiwano
{
KGE_DECLARE_SINGLETON(Audio);
using VoiceMap = UnorderedSet<Voice*>;
public:
void SetupComponent(Application*) override;
@ -43,23 +40,16 @@ namespace kiwano
void Close();
HRESULT CreateVoice(
Voice& voice,
IXAudio2SourceVoice** voice,
const WAVEFORMATEX* wfx
);
void DeleteVoice(
Voice* voice
);
void ClearVoiceCache();
protected:
Audio();
~Audio();
protected:
VoiceMap voice_cache_;
IXAudio2* x_audio2_;
IXAudio2MasteringVoice* mastering_voice_;
};

View File

@ -22,5 +22,5 @@
#include "kiwano.h"
#include "audio/audio.h"
#include "audio/Music.h"
#include "audio/Sound.h"
#include "audio/Player.h"

View File

@ -6,7 +6,7 @@
class Demo3
: public Scene
{
MusicPtr music; // ÒôÀÖ¶ÔÏó
SoundPtr bgmusic; // ÒôÀÖ¶ÔÏó
TextPtr volume_text; // 音量文字
TextPtr state_text; // 播放状态文字
@ -19,17 +19,17 @@ public:
Demo3()
{
// 加载音乐
music = new Music;
if (!music->Load(L"res/splash.mp3"))
bgmusic = new Sound;
if (!bgmusic->Load(L"res/splash.mp3"))
{
music = nullptr;
bgmusic = nullptr;
TextPtr err = new Text(L"音频文件加载失败");
this->AddChild(err);
}
// 播放音乐(参数用来设置播放循环次数,-1 表示循环播放)
music->Play(-1);
bgmusic->Play(-1);
// 创建说明文字
TextPtr intro_text = new Text(L"按上下键调整音量\n按空格键暂停或继续");
@ -51,12 +51,12 @@ public:
void OnUpdate(Duration dt) override
{
if (music == nullptr)
if (bgmusic == nullptr)
return;
// 获取音量和播放状态
float volume = music->GetVolume();
bool playing = music->IsPlaying();
float volume = bgmusic->GetVolume();
bool playing = bgmusic->IsPlaying();
// 修改文本
volume_text->SetText(L"当前音量:" + std::to_wstring(volume));
@ -68,17 +68,17 @@ public:
// 按空格键暂停或继续
if (input.WasPressed(KeyCode::Space))
{
music->IsPlaying() ? music->Pause() : music->Resume();
bgmusic->IsPlaying() ? bgmusic->Pause() : bgmusic->Resume();
}
// 按上下键调整音量
if (input.WasPressed(KeyCode::Up))
{
music->SetVolume(volume + 0.1f);
bgmusic->SetVolume(volume + 0.1f);
}
else if (input.WasPressed(KeyCode::Down))
{
music->SetVolume(volume - 0.1f);
bgmusic->SetVolume(volume - 0.1f);
}
}
};

View File

@ -53,7 +53,7 @@ public:
s_CurrIndex = index;
String title = s_Demos[index].title;
GetWindow()->SetTitle(title);
GetWindow()->SetTitle(L"KiwanoʾÀý³ÌÐò - " + title);
ScenePtr scene = s_Demos[index].Create();
EnterScene(scene);