Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Play bootSound.btsnd while shaders/pipelines are compiling #1047

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a7a116a
proof of concept
goeiecool9999 Dec 17, 2023
9354db6
fix bugs and clean up
goeiecool9999 Dec 17, 2023
312ecbc
attempt to implement looping
goeiecool9999 Dec 17, 2023
04ed1c4
simplify deinit
goeiecool9999 Dec 17, 2023
363fe59
add some more error handling, whitespace in cmake file
goeiecool9999 Dec 17, 2023
dc127e5
fix mistake while refactoring Settings code
goeiecool9999 Dec 17, 2023
cab8a1f
add config option to enable/disable boot sound, don't play during OSS…
goeiecool9999 Dec 17, 2023
5033318
move audio playback code to a more reasonable place
goeiecool9999 Dec 17, 2023
57ee241
fix indentation mistake
goeiecool9999 Dec 17, 2023
c8b4e48
fix loop
goeiecool9999 Dec 17, 2023
49b28c1
adjust assert
goeiecool9999 Dec 17, 2023
9c2b8a0
fix sm3dw loop point
goeiecool9999 Dec 17, 2023
4ac7cb9
make globals static
goeiecool9999 Dec 17, 2023
91c5a01
stuttering observed, move to jthread
goeiecool9999 Dec 17, 2023
adc988e
fix macOS build (?)
goeiecool9999 Dec 17, 2023
4468e7a
substitute jthread for thread
goeiecool9999 Dec 17, 2023
da9c3c8
Fix no audio on zero latency bug. make Xaudio consistent with other APIs
goeiecool9999 Dec 23, 2023
2223860
fix encoding error present since the repo was opened
goeiecool9999 Dec 23, 2023
2cd6dbd
Merge branch 'main' into loadaudio
goeiecool9999 Jan 15, 2024
a5aba28
disable by default
goeiecool9999 Jan 15, 2024
a143556
name the magic values
goeiecool9999 Jan 15, 2024
e0f5a9f
Merge branch 'main' into loadaudio
goeiecool9999 Jan 22, 2024
f1d4c39
more explicit and sensible error handling
goeiecool9999 Feb 12, 2024
f928eee
add log message to indicate file open failed
goeiecool9999 Feb 12, 2024
f633113
Merge branch 'main' into loadaudio
goeiecool9999 Mar 18, 2024
925e853
Merge branch 'main' into loadaudio
goeiecool9999 Mar 28, 2024
09f32d0
Merge branch 'main' into loadaudio
goeiecool9999 Apr 3, 2024
da3144a
Merge branch 'refs/heads/main' into loadaudio
goeiecool9999 May 14, 2024
c796527
don't print error when file doesn't exist
goeiecool9999 May 14, 2024
ac5c44d
Merge branch 'refs/heads/main' into loadaudio
goeiecool9999 Sep 15, 2024
da8c035
fix General Settings layout
goeiecool9999 Sep 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions 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 @@ -66,6 +69,9 @@ void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId);
bool LatteShaderCache_updatePipelineLoadingProgress();
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines);

void LatteShaderCache_InitBootSound();
void LatteShaderCache_ShutdownBootSound();

void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path pathGenericPre1_25_0, fs::path pathGenericPre1_16_0);

struct
Expand Down Expand Up @@ -299,6 +305,10 @@ void LatteShaderCache_Load()
loadBackgroundTexture(true, g_shaderCacheLoaderState.textureTVId);
loadBackgroundTexture(false, g_shaderCacheLoaderState.textureDRCId);

// initialise resources for playing bootup sound
if(GetConfig().play_boot_sound)
LatteShaderCache_InitBootSound();

sint32 numLoadedShaders = 0;
uint32 loadIndex = 0;

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

// free resources for playing boot sound
LatteShaderCache_ShutdownBootSound();
}

void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines)
Expand Down Expand Up @@ -806,3 +819,82 @@ void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path
}
}
}

static std::atomic<bool> audiothread_keeprunning = true;

void LatteShaderCache_StreamBootSound()
{
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;
std::unique_ptr<BootSoundReader> bootSndFileReader;
FSCVirtualFile* bootSndFileHandle = 0;

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;
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;
bootSndFileReader = std::make_unique<BootSoundReader>(bootSndFileHandle, audioBlockSize);

if(bootSndAudioDev && bootSndFileHandle && bootSndFileReader)
{
while(audiothread_keeprunning)
{
while (bootSndAudioDev->NeedAdditionalBlocks())
{
sint16* data = bootSndFileReader->getSamples();
if(data == nullptr)
{
audiothread_keeprunning = false;
break;
}
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);
}

static std::thread g_bootSndPlayThread;
void LatteShaderCache_InitBootSound()
{
audiothread_keeprunning = true;
if(!g_bootSndPlayThread.joinable())
g_bootSndPlayThread = std::thread{LatteShaderCache_StreamBootSound};
}

void LatteShaderCache_ShutdownBootSound()
{
audiothread_keeprunning = false;
if(g_bootSndPlayThread.joinable())
g_bootSndPlayThread.join();
g_bootSndPlayThread = {};
}
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();
}
2 changes: 2 additions & 0 deletions src/audio/XAudio27API.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class XAudio27API : public IAudioAPI
static std::vector<DeviceDescriptionPtr> GetDevices();

private:
uint32 GetQueuedBuffers() const;

struct XAudioDeleter
{
void operator()(IXAudio2* ptr) const;
Expand Down
Loading
Loading