Skip to content

Commit

Permalink
Play bootSound.btsnd while shaders/pipelines are compiling (#1047)
Browse files Browse the repository at this point in the history
  • Loading branch information
goeiecool9999 authored Dec 18, 2024
1 parent b53b223 commit 3738ccd
Show file tree
Hide file tree
Showing 17 changed files with 310 additions and 128 deletions.
122 changes: 121 additions & 1 deletion src/Cafe/HW/Latte/Core/LatteShaderCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#include "util/helpers/Serializer.h"

#include <wx/msgdlg.h>
#include <audio/IAudioAPI.h>
#include <util/bootSound/BootSoundReader.h>
#include <thread>

#if BOOST_OS_WINDOWS
#include <psapi.h>
Expand Down Expand Up @@ -155,6 +158,118 @@ bool LoadTGAFile(const std::vector<uint8>& buffer, TGAFILE *tgaFile)
return true;
}

class BootSoundPlayer
{
public:
BootSoundPlayer() = default;
~BootSoundPlayer()
{
m_stopRequested = true;
}

void StartSound()
{
if (!m_bootSndPlayThread.joinable())
{
m_fadeOutRequested = false;
m_stopRequested = false;
m_bootSndPlayThread = std::thread{[this]() {
StreamBootSound();
}};
}
}

void FadeOutSound()
{
m_fadeOutRequested = true;
}

void ApplyFadeOutEffect(std::span<sint16> samples, uint64& fadeOutSample, uint64 fadeOutDuration)
{
for (size_t i = 0; i < samples.size(); i += 2)
{
const float decibel = (float)fadeOutSample / fadeOutDuration * -60.0f;
const float volumeFactor = pow(10, decibel / 20);
samples[i] *= volumeFactor;
samples[i + 1] *= volumeFactor;
fadeOutSample++;
}
}

void StreamBootSound()
{
SetThreadName("bootsnd");
constexpr sint32 sampleRate = 48'000;
constexpr sint32 bitsPerSample = 16;
constexpr sint32 samplesPerBlock = sampleRate / 10; // block is 1/10th of a second
constexpr sint32 nChannels = 2;
static_assert(bitsPerSample % 8 == 0, "bits per sample is not a multiple of 8");

AudioAPIPtr bootSndAudioDev;

try
{
bootSndAudioDev = IAudioAPI::CreateDeviceFromConfig(true, sampleRate, nChannels, samplesPerBlock, bitsPerSample);
if(!bootSndAudioDev)
return;
}
catch (const std::runtime_error& ex)
{
cemuLog_log(LogType::Force, "Failed to initialise audio device for bootup sound");
return;
}
bootSndAudioDev->SetAudioDelayOverride(4);
bootSndAudioDev->Play();

std::string sndPath = fmt::format("{}/meta/{}", CafeSystem::GetMlcStoragePath(CafeSystem::GetForegroundTitleId()), "bootSound.btsnd");
sint32 fscStatus = FSC_STATUS_UNDEFINED;

if(!fsc_doesFileExist(sndPath.c_str()))
return;

FSCVirtualFile* bootSndFileHandle = fsc_open(sndPath.c_str(), FSC_ACCESS_FLAG::OPEN_FILE | FSC_ACCESS_FLAG::READ_PERMISSION, &fscStatus);
if(!bootSndFileHandle)
{
cemuLog_log(LogType::Force, "failed to open bootSound.btsnd");
return;
}

constexpr sint32 audioBlockSize = samplesPerBlock * (bitsPerSample/8) * nChannels;
BootSoundReader bootSndFileReader(bootSndFileHandle, audioBlockSize);

uint64 fadeOutSample = 0; // track how far into the fadeout
constexpr uint64 fadeOutDuration = sampleRate * 2; // fadeout should last 2 seconds
while(fadeOutSample < fadeOutDuration && !m_stopRequested)
{
while (bootSndAudioDev->NeedAdditionalBlocks())
{
sint16* data = bootSndFileReader.getSamples();
if(data == nullptr)
{
// break outer loop
m_stopRequested = true;
break;
}
if(m_fadeOutRequested)
ApplyFadeOutEffect({data, samplesPerBlock * nChannels}, fadeOutSample, fadeOutDuration);

bootSndAudioDev->FeedBlock(data);
}
// sleep for the duration of a single block
std::this_thread::sleep_for(std::chrono::milliseconds(samplesPerBlock / (sampleRate/ 1'000)));
}

if(bootSndFileHandle)
fsc_close(bootSndFileHandle);
}

private:
std::thread m_bootSndPlayThread;
std::atomic_bool m_fadeOutRequested = false;
std::atomic_bool m_stopRequested = false;
};
static BootSoundPlayer g_bootSndPlayer;

void LatteShaderCache_finish()
{
if (g_renderer->GetType() == RendererAPI::Vulkan)
Expand Down Expand Up @@ -299,6 +414,9 @@ void LatteShaderCache_Load()
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);

if(GetConfig().play_boot_sound)
g_bootSndPlayer.StartSound();

sint32 numLoadedShaders = 0;
uint32 loadIndex = 0;

Expand Down Expand Up @@ -365,6 +483,8 @@ void LatteShaderCache_Load()
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureTVId);
if (g_shaderCacheLoaderState.textureDRCId)
g_renderer->DeleteTexture(g_shaderCacheLoaderState.textureDRCId);

g_bootSndPlayer.FadeOutSound();
}

void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
Expand Down Expand Up @@ -805,4 +925,4 @@ void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path
fs::remove(pathGenericPre1_25_0, ec);
}
}
}
}
79 changes: 12 additions & 67 deletions src/Cafe/OS/libs/snd_core/ax_out.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,90 +396,35 @@ namespace snd_core

void AXOut_init()
{
auto& config = GetConfig();
const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;

numQueuedFramesSndGeneric = 0;

std::unique_lock lock(g_audioMutex);
if (!g_tvAudio)
{
sint32 channels;
switch (config.tv_channels)
try
{
case 0:
channels = 1; // will mix mono sound on both output channels
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
g_tvAudio = IAudioAPI::CreateDeviceFromConfig(true, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
}

IAudioAPI::DeviceDescriptionPtr device_description;
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
{
auto devices = IAudioAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.tv_device; });
if (it != devices.end())
device_description = *it;
}

if (device_description)
catch (std::runtime_error& ex)
{
try
{
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_tvAudio->SetVolume(config.tv_volume);
}
catch (std::runtime_error& ex)
{
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
exit(0);
}
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
exit(0);
}
}

if (!g_padAudio)
{
sint32 channels;
switch (config.pad_channels)
try
{
case 0:
channels = 1; // will mix mono sound on both output channels
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
g_padAudio = IAudioAPI::CreateDeviceFromConfig(false, 48000, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
if(g_padAudio)
g_padVolume = g_padAudio->GetVolume();
}

IAudioAPI::DeviceDescriptionPtr device_description;
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
{
auto devices = IAudioAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&config](const auto& d) {return d->GetIdentifier() == config.pad_device; });
if (it != devices.end())
device_description = *it;
}

if (device_description)
catch (std::runtime_error& ex)
{
try
{
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, device_description, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
g_padAudio->SetVolume(config.pad_volume);
g_padVolume = config.pad_volume;
}
catch (std::runtime_error& ex)
{
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
exit(0);
}
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
exit(0);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/audio/CubebAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ CubebAPI::~CubebAPI()
bool CubebAPI::NeedAdditionalBlocks() const
{
std::shared_lock lock(m_mutex);
return m_buffer.size() < s_audioDelay * m_bytesPerBlock;
return m_buffer.size() < GetAudioDelay() * m_bytesPerBlock;
}

bool CubebAPI::FeedBlock(sint16* data)
Expand Down
2 changes: 1 addition & 1 deletion src/audio/DirectSoundAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ void DirectSoundAPI::SetVolume(sint32 volume)
bool DirectSoundAPI::NeedAdditionalBlocks() const
{
std::shared_lock lock(m_mutex);
return m_buffer.size() < s_audioDelay;
return m_buffer.size() < GetAudioDelay();
}

std::vector<DirectSoundAPI::DeviceDescriptionPtr> DirectSoundAPI::GetDevices()
Expand Down
42 changes: 42 additions & 0 deletions src/audio/IAudioAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,40 @@ bool IAudioAPI::IsAudioAPIAvailable(AudioAPI api)
return false;
}

AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample)
{
auto& config = GetConfig();
sint32 channels = CemuConfig::AudioChannelsToNChannels(TV ? config.tv_channels : config.pad_channels);
return CreateDeviceFromConfig(TV, rate, channels, samples_per_block, bits_per_sample);
}

AudioAPIPtr IAudioAPI::CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
{
AudioAPIPtr audioAPIDev;

auto& config = GetConfig();

const auto audio_api = (IAudioAPI::AudioAPI)config.audio_api;
auto& selectedDevice = TV ? config.tv_device : config.pad_device;

if(selectedDevice.empty())
return {};

IAudioAPI::DeviceDescriptionPtr device_description;
if (IAudioAPI::IsAudioAPIAvailable(audio_api))
{
auto devices = IAudioAPI::GetDevices(audio_api);
const auto it = std::find_if(devices.begin(), devices.end(), [&selectedDevice](const auto& d) {return d->GetIdentifier() == selectedDevice; });
if (it != devices.end())
device_description = *it;
}
if (!device_description)
throw std::runtime_error("failed to find selected device while trying to create audio device");

audioAPIDev = CreateDevice(audio_api, device_description, rate, channels, samples_per_block, bits_per_sample);
audioAPIDev->SetVolume(TV ? config.tv_volume : config.pad_volume);
return audioAPIDev;
}

AudioAPIPtr IAudioAPI::CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample)
{
Expand Down Expand Up @@ -167,3 +200,12 @@ std::vector<IAudioAPI::DeviceDescriptionPtr> IAudioAPI::GetDevices(AudioAPI api)
}
}

void IAudioAPI::SetAudioDelayOverride(uint32 delay)
{
m_audioDelayOverride = delay;
}

uint32 IAudioAPI::GetAudioDelay() const
{
return m_audioDelayOverride > 0 ? m_audioDelayOverride : s_audioDelay;
}
9 changes: 7 additions & 2 deletions src/audio/IAudioAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ class IAudioAPI
virtual bool FeedBlock(sint16* data) = 0;
virtual bool Play() = 0;
virtual bool Stop() = 0;
void SetAudioDelayOverride(uint32 delay);
uint32 GetAudioDelay() const;

static void PrintLogging();
static void InitializeStatic();
static bool IsAudioAPIAvailable(AudioAPI api);


static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 samples_per_block, sint32 bits_per_sample);
static std::unique_ptr<IAudioAPI> CreateDeviceFromConfig(bool TV, sint32 rate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
static std::unique_ptr<IAudioAPI> CreateDevice(AudioAPI api, const DeviceDescriptionPtr& device, sint32 samplerate, sint32 channels, sint32 samples_per_block, sint32 bits_per_sample);
static std::vector<DeviceDescriptionPtr> GetDevices(AudioAPI api);

Expand All @@ -75,9 +79,10 @@ class IAudioAPI
bool m_playing = false;

static std::array<bool, AudioAPIEnd> s_availableApis;
static uint32 s_audioDelay;
uint32 m_audioDelayOverride = 0;

private:
static uint32 s_audioDelay;
void InitWFX(sint32 samplerate, sint32 channels, sint32 bits_per_sample);

};
Expand Down
17 changes: 11 additions & 6 deletions src/audio/XAudio27API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ XAudio27API::XAudio27API(uint32 device_id, uint32 samplerate, uint32 channels, u
m_wfx.Format.nChannels = channels;
m_wfx.Format.nSamplesPerSec = samplerate;
m_wfx.Format.wBitsPerSample = bits_per_sample;
m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8
m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
m_wfx.Format.nBlockAlign = (m_wfx.Format.nChannels * m_wfx.Format.wBitsPerSample) / 8; // must equal (nChannels × wBitsPerSample) / 8
m_wfx.Format.nAvgBytesPerSec = m_wfx.Format.nSamplesPerSec * m_wfx.Format.nBlockAlign; // must equal nSamplesPerSec × nBlockAlign.
m_wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);

m_wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
Expand Down Expand Up @@ -199,9 +199,7 @@ bool XAudio27API::FeedBlock(sint16* data)
// check if we queued too many blocks
if(m_blocks_queued >= kBlockCount)
{
XAUDIO2_VOICE_STATE state{};
m_source_voice->GetState(&state);
m_blocks_queued = state.BuffersQueued;
m_blocks_queued = GetQueuedBuffers();

if (m_blocks_queued >= kBlockCount)
{
Expand All @@ -222,7 +220,14 @@ bool XAudio27API::FeedBlock(sint16* data)
return true;
}

uint32 XAudio27API::GetQueuedBuffers() const
{
XAUDIO2_VOICE_STATE state{};
m_source_voice->GetState(&state);
return state.BuffersQueued;
}

bool XAudio27API::NeedAdditionalBlocks() const
{
return m_blocks_queued < s_audioDelay;
return GetQueuedBuffers() < GetAudioDelay();
}
Loading

0 comments on commit 3738ccd

Please sign in to comment.