diff --git a/rpcs3/Emu/Audio/AudioDumper.cpp b/rpcs3/Emu/Audio/AudioDumper.cpp index 8c9e838053..cceb2d2e0e 100644 --- a/rpcs3/Emu/Audio/AudioDumper.cpp +++ b/rpcs3/Emu/Audio/AudioDumper.cpp @@ -1,10 +1,6 @@ -#include "stdafx.h" +#include "stdafx.h" #include "AudioDumper.h" -#include "AudioThread.h" -AudioThread::~AudioThread() -{ -} AudioDumper::AudioDumper(u16 ch) : m_header(ch) diff --git a/rpcs3/Emu/Audio/AudioThread.h b/rpcs3/Emu/Audio/AudioThread.h index ea061b3a66..805ca86eba 100644 --- a/rpcs3/Emu/Audio/AudioThread.h +++ b/rpcs3/Emu/Audio/AudioThread.h @@ -1,13 +1,51 @@ -#pragma once +#pragma once + +#include "Utilities/types.h" +#include "Emu/System.h" + +enum : u32 +{ + DEFAULT_AUDIO_SAMPLING_RATE = 48000, + MAX_AUDIO_BUFFERS = 64, + AUDIO_BUFFER_SAMPLES = 256 +}; class AudioThread { public: - virtual ~AudioThread(); + virtual ~AudioThread() = default; + + // Callbacks + virtual void Open() = 0; + virtual void Close() = 0; virtual void Play() = 0; - virtual void Open(const void* src, int size) = 0; - virtual void Close() = 0; - virtual void Stop() = 0; - virtual void AddData(const void* src, int size) = 0; + virtual void Pause() = 0; + virtual bool IsPlaying() = 0; + + virtual bool AddData(const void* src, int size) = 0; + virtual void Flush() = 0; + + // Helper methods + static u32 get_sampling_rate() + { + const u32 sampling_period_multiplier_u32 = g_cfg.audio.sampling_period_multiplier; + + if (sampling_period_multiplier_u32 == 100) + return DEFAULT_AUDIO_SAMPLING_RATE; + + const f32 sampling_period_multiplier = sampling_period_multiplier_u32 / 100.0f; + const f32 sampling_rate_multiplier = 1.0f / sampling_period_multiplier; + return static_cast(DEFAULT_AUDIO_SAMPLING_RATE * sampling_rate_multiplier); + } + + static u32 get_sample_size() + { + return g_cfg.audio.convert_to_u16 ? sizeof(u16) : sizeof(float); + } + + static u32 get_channels() + { + return g_cfg.audio.downmix_to_2ch ? 2 : 8; + } }; diff --git a/rpcs3/Emu/Audio/Null/NullAudioThread.h b/rpcs3/Emu/Audio/Null/NullAudioThread.h index 46de961af2..a1ccf99d84 100644 --- a/rpcs3/Emu/Audio/Null/NullAudioThread.h +++ b/rpcs3/Emu/Audio/Null/NullAudioThread.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Emu/Audio/AudioThread.h" @@ -8,11 +8,13 @@ public: NullAudioThread() {} virtual ~NullAudioThread() {} - virtual void Init() {} - virtual void Quit() {} - virtual void Play() {} - virtual void Open(const void* src, int size) {} - virtual void Close() {} - virtual void Stop() {} - virtual void AddData(const void* src, int size) {} + virtual void Open() {}; + virtual void Close() {}; + + virtual void Play() {}; + virtual void Pause() {}; + virtual bool IsPlaying() { return true; }; + + virtual bool AddData(const void* src, int size) { return true; }; + virtual void Flush() {}; }; diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp index 36acee53bb..62a20245ba 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio27Thread.cpp @@ -1,4 +1,4 @@ -#ifdef _WIN32 +#ifdef _WIN32 #include "Utilities/Log.h" #include "Utilities/StrFmt.h" @@ -97,18 +97,27 @@ void XAudio2Thread::xa27_stop() } } +bool XAudio2Thread::xa27_is_playing() +{ + XAUDIO2_VOICE_STATE state; + s_tls_source_voice->GetState(&state); + + return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr; +} + void XAudio2Thread::xa27_open() { HRESULT hr; - WORD sample_size = g_cfg.audio.convert_to_u16 ? sizeof(u16) : sizeof(float); - WORD channels = g_cfg.audio.downmix_to_2ch ? 2 : 8; + const u32 sample_size = get_sample_size(); + const u32 channels = get_channels(); + const u32 sampling_rate = get_sampling_rate(); WAVEFORMATEX waveformatex; waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT; waveformatex.nChannels = channels; - waveformatex.nSamplesPerSec = 48000; - waveformatex.nAvgBytesPerSec = 48000 * (DWORD)channels * (DWORD)sample_size; + waveformatex.nSamplesPerSec = sampling_rate; + waveformatex.nAvgBytesPerSec = static_cast(sampling_rate * channels * sample_size); waveformatex.nBlockAlign = channels * sample_size; waveformatex.wBitsPerSample = sample_size * 8; waveformatex.cbSize = 0; @@ -121,25 +130,24 @@ void XAudio2Thread::xa27_open() return; } - s_tls_source_voice->SetVolume(g_cfg.audio.downmix_to_2ch ? 1.0f : 4.0f); + s_tls_source_voice->SetVolume(channels == 2 ? 1.0f : 4.0f); } -void XAudio2Thread::xa27_add(const void* src, int size) +bool XAudio2Thread::xa27_add(const void* src, int size) { XAUDIO2_VOICE_STATE state; s_tls_source_voice->GetState(&state); // XAudio 2.7 bug workaround, when it says "SimpList: non-growable list ran out of room for new elements" and hits int 3 - if (state.BuffersQueued > 32) + if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) { LOG_WARNING(GENERAL, "XAudio2Thread : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed); - - return xa27_flush(); + return false; } XAUDIO2_BUFFER buffer; - buffer.AudioBytes = size; + buffer.AudioBytes = size * get_sample_size(); buffer.Flags = 0; buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION; buffer.LoopCount = 0; @@ -147,7 +155,7 @@ void XAudio2Thread::xa27_add(const void* src, int size) buffer.pAudioData = (const BYTE*)src; buffer.pContext = 0; buffer.PlayBegin = 0; - buffer.PlayLength = 256; + buffer.PlayLength = AUDIO_BUFFER_SAMPLES; HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer); if (FAILED(hr)) @@ -155,6 +163,8 @@ void XAudio2Thread::xa27_add(const void* src, int size) LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr); Emu.Pause(); } + + return true; } #endif diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp index 36eb120c00..d6953fc818 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio28Thread.cpp @@ -1,4 +1,4 @@ -#ifdef _WIN32 +#ifdef _WIN32 #include "Utilities/Log.h" #include "Utilities/StrFmt.h" @@ -71,6 +71,8 @@ void XAudio2Thread::xa28_destroy() void XAudio2Thread::xa28_play() { + AUDIT(s_tls_source_voice != nullptr); + HRESULT hr = s_tls_source_voice->Start(); if (FAILED(hr)) { @@ -81,6 +83,8 @@ void XAudio2Thread::xa28_play() void XAudio2Thread::xa28_flush() { + AUDIT(s_tls_source_voice != nullptr); + HRESULT hr = s_tls_source_voice->FlushSourceBuffers(); if (FAILED(hr)) { @@ -91,6 +95,8 @@ void XAudio2Thread::xa28_flush() void XAudio2Thread::xa28_stop() { + AUDIT(s_tls_source_voice != nullptr); + HRESULT hr = s_tls_source_voice->Stop(); if (FAILED(hr)) { @@ -99,18 +105,29 @@ void XAudio2Thread::xa28_stop() } } +bool XAudio2Thread::xa28_is_playing() +{ + AUDIT(s_tls_source_voice != nullptr); + + XAUDIO2_VOICE_STATE state; + s_tls_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED); + + return state.BuffersQueued > 0 || state.pCurrentBufferContext != nullptr; +} + void XAudio2Thread::xa28_open() { HRESULT hr; - WORD sample_size = g_cfg.audio.convert_to_u16 ? sizeof(u16) : sizeof(float); - WORD channels = g_cfg.audio.downmix_to_2ch ? 2 : 8; + const u32 sample_size = get_sample_size(); + const u32 channels = get_channels(); + const u32 sampling_rate = get_sampling_rate(); WAVEFORMATEX waveformatex; waveformatex.wFormatTag = g_cfg.audio.convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT; waveformatex.nChannels = channels; - waveformatex.nSamplesPerSec = 48000; - waveformatex.nAvgBytesPerSec = 48000 * (DWORD)channels * (DWORD)sample_size; + waveformatex.nSamplesPerSec = sampling_rate; + waveformatex.nAvgBytesPerSec = static_cast(sampling_rate * channels * sample_size); waveformatex.nBlockAlign = channels * sample_size; waveformatex.wBitsPerSample = sample_size * 8; waveformatex.cbSize = 0; @@ -123,24 +140,26 @@ void XAudio2Thread::xa28_open() return; } - s_tls_source_voice->SetVolume(g_cfg.audio.downmix_to_2ch ? 1.0 : 4.0); + AUDIT(s_tls_source_voice != nullptr); + s_tls_source_voice->SetVolume(channels == 2 ? 1.0 : 4.0); } -void XAudio2Thread::xa28_add(const void* src, int size) +bool XAudio2Thread::xa28_add(const void* src, int size) { - XAUDIO2_VOICE_STATE state; - s_tls_source_voice->GetState(&state); + AUDIT(s_tls_source_voice != nullptr); - if (state.BuffersQueued > 32) + XAUDIO2_VOICE_STATE state; + s_tls_source_voice->GetState(&state, XAUDIO2_VOICE_NOSAMPLESPLAYED); + + if (state.BuffersQueued >= MAX_AUDIO_BUFFERS) { LOG_WARNING(GENERAL, "XAudio2Thread : too many buffers enqueued (%d, pos=%u)", state.BuffersQueued, state.SamplesPlayed); - - return xa28_flush(); + return false; } XAUDIO2_BUFFER buffer; - buffer.AudioBytes = size; + buffer.AudioBytes = size * get_sample_size(); buffer.Flags = 0; buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION; buffer.LoopCount = 0; @@ -148,7 +167,7 @@ void XAudio2Thread::xa28_add(const void* src, int size) buffer.pAudioData = (const BYTE*)src; buffer.pContext = 0; buffer.PlayBegin = 0; - buffer.PlayLength = 256; + buffer.PlayLength = AUDIO_BUFFER_SAMPLES; HRESULT hr = s_tls_source_voice->SubmitSourceBuffer(&buffer); if (FAILED(hr)) @@ -156,6 +175,8 @@ void XAudio2Thread::xa28_add(const void* src, int size) LOG_ERROR(GENERAL, "XAudio2Thread : AddData() failed(0x%08x)", (u32)hr); Emu.Pause(); } + + return true; } #endif diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp index 9663387a99..5bd13d4d3c 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.cpp @@ -1,4 +1,4 @@ -#ifdef _WIN32 +#ifdef _WIN32 #include "Utilities/Log.h" #include "Utilities/StrFmt.h" @@ -18,12 +18,13 @@ XAudio2Thread::XAudio2Thread() // xa28* implementation is fully compatible with library 2.9 xa28_init(lib2_9); - m_funcs.destroy = &xa28_destroy; - m_funcs.play = &xa28_play; - m_funcs.flush = &xa28_flush; - m_funcs.stop = &xa28_stop; - m_funcs.open = &xa28_open; - m_funcs.add = &xa28_add; + m_funcs.destroy = &xa28_destroy; + m_funcs.play = &xa28_play; + m_funcs.flush = &xa28_flush; + m_funcs.stop = &xa28_stop; + m_funcs.open = &xa28_open; + m_funcs.is_playing = &xa28_is_playing; + m_funcs.add = &xa28_add; LOG_SUCCESS(GENERAL, "XAudio 2.9 initialized"); return; @@ -33,12 +34,13 @@ XAudio2Thread::XAudio2Thread() { xa27_init(lib2_7); - m_funcs.destroy = &xa27_destroy; - m_funcs.play = &xa27_play; - m_funcs.flush = &xa27_flush; - m_funcs.stop = &xa27_stop; - m_funcs.open = &xa27_open; - m_funcs.add = &xa27_add; + m_funcs.destroy = &xa27_destroy; + m_funcs.play = &xa27_play; + m_funcs.flush = &xa27_flush; + m_funcs.stop = &xa27_stop; + m_funcs.open = &xa27_open; + m_funcs.is_playing = &xa27_is_playing; + m_funcs.add = &xa27_add; LOG_SUCCESS(GENERAL, "XAudio 2.7 initialized"); return; @@ -48,12 +50,13 @@ XAudio2Thread::XAudio2Thread() { xa28_init(lib2_8); - m_funcs.destroy = &xa28_destroy; - m_funcs.play = &xa28_play; - m_funcs.flush = &xa28_flush; - m_funcs.stop = &xa28_stop; - m_funcs.open = &xa28_open; - m_funcs.add = &xa28_add; + m_funcs.destroy = &xa28_destroy; + m_funcs.play = &xa28_play; + m_funcs.flush = &xa28_flush; + m_funcs.stop = &xa28_stop; + m_funcs.open = &xa28_open; + m_funcs.is_playing = &xa28_is_playing; + m_funcs.add = &xa28_add; LOG_SUCCESS(GENERAL, "XAudio 2.8 initialized"); return; @@ -78,21 +81,29 @@ void XAudio2Thread::Close() m_funcs.flush(); } -void XAudio2Thread::Stop() +void XAudio2Thread::Pause() { m_funcs.stop(); } -void XAudio2Thread::Open(const void* src, int size) +void XAudio2Thread::Open() { m_funcs.open(); - m_funcs.add(src, size); - m_funcs.play(); } -void XAudio2Thread::AddData(const void* src, int size) +bool XAudio2Thread::IsPlaying() { - m_funcs.add(src, size); + return m_funcs.is_playing(); +} + +bool XAudio2Thread::AddData(const void* src, int size) +{ + return m_funcs.add(src, size); +} + +void XAudio2Thread::Flush() +{ + m_funcs.flush(); } #endif diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h b/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h index 99f80b98c8..9a77d442f2 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Thread.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #ifdef _WIN32 @@ -13,7 +13,8 @@ class XAudio2Thread : public AudioThread void(*flush)(); void(*stop)(); void(*open)(); - void(*add)(const void*, int); + bool(*is_playing)(); + bool(*add)(const void*, int); }; vtable m_funcs; @@ -24,7 +25,8 @@ class XAudio2Thread : public AudioThread static void xa27_flush(); static void xa27_stop(); static void xa27_open(); - static void xa27_add(const void*, int); + static bool xa27_is_playing(); + static bool xa27_add(const void*, int); static void xa28_init(void*); static void xa28_destroy(); @@ -32,17 +34,22 @@ class XAudio2Thread : public AudioThread static void xa28_flush(); static void xa28_stop(); static void xa28_open(); - static void xa28_add(const void*, int); + static bool xa28_is_playing(); + static bool xa28_add(const void*, int); public: XAudio2Thread(); virtual ~XAudio2Thread() override; - virtual void Play() override; - virtual void Open(const void* src, int size) override; + virtual void Open() override; virtual void Close() override; - virtual void Stop() override; - virtual void AddData(const void* src, int size) override; + + virtual void Play() override; + virtual void Pause() override; + virtual bool IsPlaying() override; + + virtual bool AddData(const void* src, int size) override; + virtual void Flush() override; }; #endif diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index 4796f871c4..21ad34278b 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -1,12 +1,11 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Cell/lv2/sys_event.h" -#include "Emu/Audio/AudioDumper.h" -#include "Emu/Audio/AudioThread.h" #include "cellAudio.h" +#include LOG_CHANNEL(cellAudio); @@ -38,287 +37,754 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -void audio_thread::operator()() +audio_ringbuffer::audio_ringbuffer(u32 num_buffers, u32 audio_sampling_rate, u32 channels) + : num_allocated_buffers(num_buffers) + , audio_sampling_rate(audio_sampling_rate) + , backend(Emu.GetCallbacks().get_audio()) + , channels(channels) + , buf_sz(AUDIO_BUFFER_SAMPLES * channels) + , emu_paused(Emu.IsPaused()) +{ + // Initialize buffers + if (num_allocated_buffers >= MAX_AUDIO_BUFFERS) + { + fmt::throw_exception("MAX_AUDIO_BUFFERS is too small"); + } + + for (u32 i = 0; i < num_allocated_buffers; i++) + { + buffer[i].reset(new float[buf_sz]{}); + } + + // Init audio dumper if enabled + if (g_cfg.audio.dump_to_file) + { + m_dump.reset(new AudioDumper(channels)); + } + + // Initialize backend + backend->Open(); + backend_open = true; + ASSERT(!backend->IsPlaying()); +} + +audio_ringbuffer::~audio_ringbuffer() +{ + if (!backend_open) + return; + + if (backend->IsPlaying()) + backend->Pause(); + + backend->Close(); +} + +void audio_ringbuffer::enqueue(const float* in_buffer) +{ + AUDIT(next_buf < num_allocated_buffers); + + // Prepare buffer + const void* buf = in_buffer; + + if (buf == nullptr) + { + buf = buffer[next_buf].get(); + next_buf = (next_buf + 1) % num_allocated_buffers; + } + + // Dump audio if enabled + if (m_dump) + { + m_dump->WriteData(buf, buf_sz); + } + + // Enqueue audio + bool success = backend->AddData(buf, buf_sz); + if (!success) + { + cellAudio.error("Could not enqueue buffer onto audio backend. Attempting to recover..."); + flush(); + return; + } + + enqueued_samples += AUDIO_BUFFER_SAMPLES; + + // Start playing audio + play(); +} + +void audio_ringbuffer::enqueue_silence(u32 buf_count) +{ + for (u32 i = 0; i < buf_count; i++) + { + enqueue(silence_buffer); + } +} + +void audio_ringbuffer::play() +{ + if (playing) + return; + + playing = true; + + ASSERT(enqueued_samples > 0); + + play_timestamp = get_timestamp(); + backend->Play(); +} + +void audio_ringbuffer::flush() +{ + cellAudio.trace("Flushing an estimated %llu enqueued samples", enqueued_samples); + + backend->Pause(); + playing = false; + + backend->Flush(); + enqueued_samples = 0; +} + +u64 audio_ringbuffer::update() +{ + // Check emulator pause state + if (Emu.IsPaused()) + { + // Emulator paused + if (playing) + { + backend->Pause(); + } + emu_paused = true; + } + else if (emu_paused) + { + // Emulator unpaused + play(); + emu_paused = false; + } + + const u64 timestamp = get_timestamp(); + const bool new_playing = !emu_paused && backend->IsPlaying(); + + // Calculate how many audio samples have played since last time + // TODO: Natively query backend for remaining samples + if (playing || new_playing) + { + const u64 play_delta = timestamp - (play_timestamp > update_timestamp ? play_timestamp : update_timestamp); + + // NOTE: Only works with a fixed sampling rate + const u64 delta_samples_tmp = (play_delta * audio_sampling_rate) + last_remainder; + last_remainder = delta_samples_tmp % 1'000'000; + const u64 delta_samples = delta_samples_tmp / 1'000'000; + + //cellAudio.error("play_delta=%llu delta_samples=%llu", play_delta, delta_samples); + if (delta_samples > 0) + { + + if (enqueued_samples < delta_samples) + { + enqueued_samples = 0; + } + else + { + enqueued_samples -= delta_samples; + } + + if (enqueued_samples == 0) + { + cellAudio.warning("Audio buffer about to underrun!"); + } + } + } + + // Update playing state + if (playing != new_playing) + { + if (!new_playing) + { + cellAudio.error("Audio backend stopped unexpectedly, likely due to a buffer underrun"); + + flush(); + playing = false; + } + else + { + playing = true; + } + } + + // Store and return timestamp + update_timestamp = timestamp; + return timestamp; +} + +constexpr bool audio_port::is_tag(float val) +{ + return val == 0.0f && std::signbit(val); +} + +void audio_port::tag(s32 offset) +{ + auto port_pos = position(offset); + auto port_buf = get_vm_ptr(offset); + + // This tag will be used to make sure that the game has finished writing the audio for the next audio period + // We use -0.0f in case games check if the buffer is empty. -0.0f == 0.0f evaluates to true, but std::signbit can be used to distinguish them + const f32 tag = -0.0f; + + const u32 tag_first_pos = num_channels == 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH; + const u32 tag_delta = num_channels == 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH; + + for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++) + { + port_buf[tag_pos] = tag; + tag_backup[port_pos][tag_nr] = 0.0f; + } + + prev_touched_tag_nr = UINT32_MAX; +} + +void audio_port::apply_tag_backups(s32 offset) +{ + auto port_pos = position(offset); + auto port_buf = get_vm_ptr(offset); + + const u32 tag_first_pos = num_channels == 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH; + const u32 tag_delta = num_channels == 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH; + + for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++) + { + port_buf[tag_pos] += tag_backup[port_pos][tag_nr]; + } +} + +std::tuple cell_audio_thread::count_port_buffer_tags() +{ + AUDIT(buffering_enabled); + + u32 active = 0; + u32 in_progress = 0; + u32 untouched = 0; + u32 incomplete = 0; + + for (auto& port : ports) + { + if (port.state != audio_port_state::started) continue; + active++; + + auto port_buf = port.get_vm_ptr(); + u32 port_pos = port.position(); + + // Find the last tag that has been touched + const u32 tag_first_pos = port.num_channels == 2 ? PORT_BUFFER_TAG_FIRST_2CH : PORT_BUFFER_TAG_FIRST_8CH; + const u32 tag_delta = port.num_channels == 2 ? PORT_BUFFER_TAG_DELTA_2CH : PORT_BUFFER_TAG_DELTA_8CH; + + const f32 tag = -0.0f; + + u32 last_touched_tag_nr = port.prev_touched_tag_nr; + bool retouched = false; + for (u32 tag_pos = tag_first_pos, tag_nr = 0; tag_nr < PORT_BUFFER_TAG_COUNT; tag_pos += tag_delta, tag_nr++) + { + // grab current value and re-tag atomically + f32 val = atomic_storage, 4>::exchange(port_buf[tag_pos], tag); + + if (!audio_port::is_tag(val)) + { + port.tag_backup[port_pos][tag_nr] += val; + + retouched |= tag_nr < port.prev_touched_tag_nr && port.prev_touched_tag_nr != UINT32_MAX; + last_touched_tag_nr = tag_nr; + } + } + + // Decide whether the buffer is untouched, in progress, incomplete, or complete + if (last_touched_tag_nr == UINT32_MAX) + { + // no tag has been touched yet + untouched++; + } + else if (last_touched_tag_nr == PORT_BUFFER_TAG_COUNT - 1) + { + if (retouched) + { + // we retouched, so wait at least once more to make sure no more tags get touched + in_progress++; + } + // buffer has been completely filled + port.prev_touched_tag_nr = last_touched_tag_nr; + } + else if (last_touched_tag_nr == port.prev_touched_tag_nr) + { + // hasn't been touched since the last call + incomplete++; + } + else + { + // the touched tag changed since the last call + in_progress++; + port.prev_touched_tag_nr = last_touched_tag_nr; + } + } + + return std::make_tuple(active, in_progress, untouched, incomplete); +} + +void cell_audio_thread::reset_ports(s32 offset) +{ + // Memset previous buffer to 0 + for (auto& port : ports) + { + if (port.state != audio_port_state::started) continue; + + memset(port.get_vm_ptr(offset), 0, port.block_size() * sizeof(float)); + + if (buffering_enabled) + { + //port.reset_tag_backups(offset); + port.tag(offset); + } + } +} + +void cell_audio_thread::advance(u64 timestamp, bool reset) +{ + auto _locked = g_idm->lock>(0); + + // update ports + if (reset) + { + reset_ports(0); + } + + for (auto& port : ports) + { + if (port.state != audio_port_state::started) continue; + + port.global_counter = m_counter; + port.active_counter++; + port.timestamp = timestamp; + + port.cur_pos = port.position(1); + m_indexes[port.number] = port.cur_pos; + } + + m_counter++; + m_last_period_end = timestamp; + m_dynamic_period = 0; + + // send aftermix event (normal audio event) + std::array, MAX_AUDIO_EVENT_QUEUES> queues; + u32 queue_count = 0; + + for (u64 key : keys) + { + if (auto queue = lv2_event_queue::find(key)) + { + queues[queue_count++] = queue; + } + } + + _locked.unlock(); + + for (u32 i = 0; i < queue_count; i++) + { + queues[i]->send(0, 0, 0, 0); // TODO: check arguments + } +} + +void cell_audio_thread::operator()() { thread_ctrl::set_native_priority(1); - AudioDumper m_dump(g_cfg.audio.dump_to_file ? 2 : 0); // Init AudioDumper for 2 channels if enabled + // Allocate ringbuffer + ringbuffer.reset(new audio_ringbuffer(num_allocated_buffers, audio_sampling_rate, audio_channels)); - float buf2ch[2 * BUFFER_SIZE]{}; // intermediate buffer for 2 channels - float buf8ch[8 * BUFFER_SIZE]{}; // intermediate buffer for 8 channels + // Initialize loop variables + m_counter = 0; + m_start_time = ringbuffer->get_timestamp(); + m_last_period_end = m_start_time; + m_dynamic_period = 0; - const u32 buf_sz = BUFFER_SIZE * (g_cfg.audio.convert_to_u16 ? 2 : 4) * (g_cfg.audio.downmix_to_2ch ? 2 : 8); - - std::unique_ptr out_buffer[BUFFER_NUM]; - - for (u32 i = 0; i < BUFFER_NUM; i++) - { - out_buffer[i].reset(new float[8 * BUFFER_SIZE] {}); - } - - const auto audio = Emu.GetCallbacks().get_audio(); - audio->Open(buf8ch, buf_sz); + u32 untouched_expected = 0; + u32 in_progress_expected = 0; + // Main cellAudio loop while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped()) { + const u64 timestamp = ringbuffer->update(); + if (Emu.IsPaused()) { - thread_ctrl::wait_for(1000); // hack + thread_ctrl::wait_for(10000); continue; } - const u64 stamp0 = get_system_time(); - - const u64 time_pos = stamp0 - start_time - Emu.GetPauseTime(); - // TODO: send beforemix event (in ~2,6 ms before mixing) - // precise time of sleeping: 5,(3) ms (or 256/48000 sec) - const u64 expected_time = m_counter * AUDIO_SAMPLES * 1000000 / 48000; - if (expected_time >= time_pos) + const u64 time_since_last_period = timestamp - m_last_period_end; + const bool playing = !ringbuffer->is_playing(); + + if (!buffering_enabled) { - thread_ctrl::wait_for(1000); // hack - continue; + const u64 period_end = (m_counter * audio_block_period) + m_start_time; + const s64 time_left = period_end - timestamp; + + if (time_left > period_comparison_margin) + { + thread_ctrl::wait_for(get_thread_wait_delay(time_left)); + continue; + } } - - m_counter++; - - const u32 out_pos = m_counter % BUFFER_NUM; - - bool first_mix = true; - - // mixing: - for (auto& port : ports) + else { - if (port.state != audio_port_state::started) continue; + const u64 enqueued_playtime = ringbuffer->get_enqueued_samples() * 1'000'000 / audio_sampling_rate; + const u64 enqueued_buffers = (enqueued_playtime) / audio_block_period; - const u32 block_size = port.channel * AUDIO_SAMPLES; - const u32 position = port.tag % port.block; // old value - const u32 buf_addr = port.addr.addr() + position * block_size * sizeof(float); + const bool playing = ringbuffer->is_playing(); - auto buf = vm::_ptr(buf_addr); + const auto tag_info = count_port_buffer_tags(); + const u32 active_ports = std::get<0>(tag_info); + const u32 in_progress = std::get<1>(tag_info); + const u32 untouched = std::get<2>(tag_info); + const u32 incomplete = std::get<3>(tag_info); - static const float k = 1.0f; // may be 1.0f - const float& m = port.level; - - auto step_volume = [](audio_port& port) // part of cellAudioSetPortLevel functionality + // Wait for a dynamic period - try to maintain an average as close as possible to 5.(3)ms + if (m_dynamic_period == 0) { - const auto param = port.level_set.load(); - - if (param.inc != 0.0f) + if (!playing) { - port.level += param.inc; - const bool dec = param.inc < 0.0f; - - if ((!dec && param.value - port.level <= 0.0f) || (dec && param.value - port.level >= 0.0f)) - { - port.level = param.value; - port.level_set.compare_and_swap(param, { param.value, 0.0f }); - } - } - }; - - if (port.channel == 2) - { - if (first_mix) - { - for (u32 i = 0; i < std::size(buf2ch); i += 2) - { - step_volume(port); - - // reverse byte order - const float left = buf[i + 0] * m; - const float right = buf[i + 1] * m; - - buf2ch[i + 0] = left; - buf2ch[i + 1] = right; - - buf8ch[i * 4 + 0] = left; - buf8ch[i * 4 + 1] = right; - buf8ch[i * 4 + 2] = 0.0f; - buf8ch[i * 4 + 3] = 0.0f; - buf8ch[i * 4 + 4] = 0.0f; - buf8ch[i * 4 + 5] = 0.0f; - buf8ch[i * 4 + 6] = 0.0f; - buf8ch[i * 4 + 7] = 0.0f; - } - first_mix = false; + // When the buffer is empty, always use the correct block period + m_dynamic_period = audio_block_period; } else { - for (u32 i = 0; i < std::size(buf2ch); i += 2) + // 1.0 means exactly as desired + // <1.0 means not as full as desired + // >1.0 means more full than desired + const f32 desired_duration_rate = (enqueued_playtime) / static_cast(desired_buffer_duration); + + if (desired_duration_rate >= 1.0f) { - step_volume(port); - - const float left = buf[i + 0] * m; - const float right = buf[i + 1] * m; - - buf2ch[i + 0] += left; - buf2ch[i + 1] += right; - - buf8ch[i * 4 + 0] += left; - buf8ch[i * 4 + 1] += right; + // more full than desired + const f32 multiplier = 1.0f / desired_duration_rate; + m_dynamic_period = maximum_block_period - static_cast((maximum_block_period - audio_block_period) * multiplier); + } + else + { + // not as full as desired + const f32 multiplier = desired_duration_rate; + m_dynamic_period = minimum_block_period + static_cast((audio_block_period - minimum_block_period) * multiplier); } } } - else if (port.channel == 8) + + s64 time_left = m_dynamic_period - time_since_last_period; + if (time_left > period_comparison_margin) { - if (first_mix) + thread_ctrl::wait_for(get_thread_wait_delay(time_left)); + continue; + } + + // Fast path for 0 ports active + if (active_ports == 0) + { + // no need to mix, just enqueue silence and advance time + cellAudio.error("advancing time: no active ports, enqueued_buffers=%llu", enqueued_buffers); + if (playing) { - for (u32 i = 0; i < std::size(buf2ch); i += 2) - { - step_volume(port); + ringbuffer->enqueue_silence(1); + } + untouched_expected = 0; + advance(timestamp); + continue; + } - const float left = buf[i * 4 + 0] * m; - const float right = buf[i * 4 + 1] * m; - const float center = buf[i * 4 + 2] * m; - const float low_freq = buf[i * 4 + 3] * m; - const float rear_left = buf[i * 4 + 4] * m; - const float rear_right = buf[i * 4 + 5] * m; - const float side_left = buf[i * 4 + 6] * m; - const float side_right = buf[i * 4 + 7] * m; - - const float mid = (center + low_freq) * 0.708f; - buf2ch[i + 0] = (left + rear_left + side_left + mid) * k; - buf2ch[i + 1] = (right + rear_right + side_right + mid) * k; - - buf8ch[i * 4 + 0] = left; - buf8ch[i * 4 + 1] = right; - buf8ch[i * 4 + 2] = center; - buf8ch[i * 4 + 3] = low_freq; - buf8ch[i * 4 + 4] = rear_left; - buf8ch[i * 4 + 5] = rear_right; - buf8ch[i * 4 + 6] = side_left; - buf8ch[i * 4 + 7] = side_right; - } - first_mix = false; + // Wait until buffers have been touched + //cellAudio.error("active=%u, in_progress=%u, untouched=%u, incomplete=%u", active_ports, in_progress, untouched, incomplete); + if ( + (untouched == active_ports) || // wait for at least one touched buffer + (untouched > (untouched_expected > 0 ? active_ports - untouched_expected : 0)) // with multiple audio ports, wait for the expected number of buffers to be touched + ) + { + if (!playing) + { + // We ran out of buffer, probably because we waited too long + // Don't enqueue anything, just advance time + cellAudio.error("advancing time: untouched=%u/%u (expected=%u), enqueued_buffers=0", untouched, active_ports, untouched_expected); + untouched_expected = untouched; + advance(timestamp); } else { - for (u32 i = 0; i < std::size(buf2ch); i += 2) + cellAudio.error("waiting: untouched=%u/%u (expected=%u), enqueued_buffers=%llu", untouched, active_ports, untouched_expected, enqueued_buffers); + thread_ctrl::wait_for(1000); + } + + continue; + } + + // Wait for buffer(s) to be completely filled + if (in_progress > 0) + { + cellAudio.error("waiting: in_progress=%u/%u, enqueued_buffers=%u", in_progress, active_ports, enqueued_buffers); + thread_ctrl::wait_for(500); + continue; + } + + //cellAudio.error("time_since_last=%llu, dynamic_period=%llu => %3.2f%%", time_since_last_period, m_dynamic_period, (((f32)m_dynamic_period) / audio_block_period) * 100); + //cellAudio.error("active=%u, untouched=%u, in_progress=%d, incomplete=%d, enqueued_buffers=%u", active_ports, untouched, in_progress, incomplete, enqueued_buffers); + + // Store number of untouched buffers for future reference + untouched_expected = untouched; + + // Warn if we enqueued untouched/incomplete buffers + if (untouched > 0 || incomplete > 0) + { + cellAudio.error("enqueueing: untouched=%u/%u (expected=%u), incomplete=%u/%u enqueued_buffers=%llu", untouched, active_ports, untouched_expected, incomplete, active_ports, enqueued_buffers); + } + + // Handle audio restart + if (!playing) + { + // We are not playing (likely buffer underrun) + // align to 5.(3)ms on global clock + const s64 audio_period_alignment_delta = (timestamp - m_start_time) % audio_block_period; + if (audio_period_alignment_delta > period_comparison_margin) + { + thread_ctrl::wait_for(audio_period_alignment_delta - period_comparison_margin); + } + + // Flush, add silence, restart algorithm + cellAudio.error("play/resume audio: received first audio buffer"); + ringbuffer->flush(); + ringbuffer->enqueue_silence(desired_full_buffers); + finish_port_volume_stepping(); + } + } + + // Mix + float *buf = ringbuffer->get_current_buffer(); + if (audio_channels == 2) + { + mix(buf); + } + else if (audio_channels == 8) + { + mix(buf); + } + else + { + fmt::throw_exception("Unsupported number of audio channels: %u", audio_channels); + } + + // Enqueue + ringbuffer->enqueue(); + + // Advance time + advance(timestamp); + } + + // Destroy ringbuffer + ringbuffer.reset(); +} + +template +void cell_audio_thread::mix(float *out_buffer, s32 offset) +{ + AUDIT(out_buffer != nullptr); + + constexpr u32 channels = downmix_to_2ch ? 2 : 8; + constexpr u32 out_buffer_sz = channels * AUDIO_BUFFER_SAMPLES; + + bool first_mix = true; + + // mixing + for (auto& port : ports) + { + if (port.state != audio_port_state::started) continue; + + if (buffering_enabled) + { + port.apply_tag_backups(offset); + } + + auto buf = port.get_vm_ptr(offset); + static const float k = 1.0f; + float& m = port.level; + + // part of cellAudioSetPortLevel functionality + // spread port volume changes over 13ms + auto step_volume = [](audio_port& port) + { + const auto param = port.level_set.load(); + + if (param.inc != 0.0f) + { + port.level += param.inc; + const bool dec = param.inc < 0.0f; + + if ((!dec && param.value - port.level <= 0.0f) || (dec && param.value - port.level >= 0.0f)) + { + port.level = param.value; + port.level_set.compare_and_swap(param, { param.value, 0.0f }); + } + } + }; + + if (port.num_channels == 2) + { + if (first_mix) + { + for (u32 out = 0, in = 0; out < out_buffer_sz; out += channels, in += 2) + { + step_volume(port); + + const float left = buf[in + 0] * m; + const float right = buf[in + 1] * m; + + out_buffer[out + 0] = left; + out_buffer[out + 1] = right; + + if (!downmix_to_2ch) { - step_volume(port); + out_buffer[out + 2] = 0.0f; + out_buffer[out + 3] = 0.0f; + out_buffer[out + 4] = 0.0f; + out_buffer[out + 5] = 0.0f; + out_buffer[out + 6] = 0.0f; + out_buffer[out + 7] = 0.0f; + } + } + first_mix = false; + } + else + { + for (u32 out = 0, in = 0; out < out_buffer_sz; out += channels, in += 2) + { + step_volume(port); - const float left = buf[i * 4 + 0] * m; - const float right = buf[i * 4 + 1] * m; - const float center = buf[i * 4 + 2] * m; - const float low_freq = buf[i * 4 + 3] * m; - const float rear_left = buf[i * 4 + 4] * m; - const float rear_right = buf[i * 4 + 5] * m; - const float side_left = buf[i * 4 + 6] * m; - const float side_right = buf[i * 4 + 7] * m; + const float left = buf[in + 0] * m; + const float right = buf[in + 1] * m; + out_buffer[out + 0] += left; + out_buffer[out + 1] += right; + } + } + } + else if (port.num_channels == 8) + { + if (first_mix) + { + for (u32 out = 0, in = 0; out < out_buffer_sz; out += channels, in += 8) + { + step_volume(port); + + const float left = buf[in + 0] * m; + const float right = buf[in + 1] * m; + const float center = buf[in + 2] * m; + const float low_freq = buf[in + 3] * m; + const float rear_left = buf[in + 4] * m; + const float rear_right = buf[in + 5] * m; + const float side_left = buf[in + 6] * m; + const float side_right = buf[in + 7] * m; + + if (downmix_to_2ch) + { const float mid = (center + low_freq) * 0.708f; - buf2ch[i + 0] += (left + rear_left + side_left + mid) * k; - buf2ch[i + 1] += (right + rear_right + side_right + mid) * k; + out_buffer[out + 0] = (left + rear_left + side_left + mid) * k; + out_buffer[out + 1] = (right + rear_right + side_right + mid) * k; + } + else + { + out_buffer[out + 0] = left; + out_buffer[out + 1] = right; + out_buffer[out + 2] = center; + out_buffer[out + 3] = low_freq; + out_buffer[out + 4] = rear_left; + out_buffer[out + 5] = rear_right; + out_buffer[out + 6] = side_left; + out_buffer[out + 7] = side_right; + } + } + first_mix = false; + } + else + { + for (u32 out = 0, in = 0; out < out_buffer_sz; out += channels, in += 8) + { + step_volume(port); - buf8ch[i * 4 + 0] += left; - buf8ch[i * 4 + 1] += right; - buf8ch[i * 4 + 2] += center; - buf8ch[i * 4 + 3] += low_freq; - buf8ch[i * 4 + 4] += rear_left; - buf8ch[i * 4 + 5] += rear_right; - buf8ch[i * 4 + 6] += side_left; - buf8ch[i * 4 + 7] += side_right; + const float left = buf[in + 0] * m; + const float right = buf[in + 1] * m; + const float center = buf[in + 2] * m; + const float low_freq = buf[in + 3] * m; + const float rear_left = buf[in + 4] * m; + const float rear_right = buf[in + 5] * m; + const float side_left = buf[in + 6] * m; + const float side_right = buf[in + 7] * m; + + if (downmix_to_2ch) + { + const float mid = (center + low_freq) * 0.708f; + out_buffer[out + 0] += (left + rear_left + side_left + mid) * k; + out_buffer[out + 1] += (right + rear_right + side_right + mid) * k; + } + else + { + out_buffer[out + 0] += left; + out_buffer[out + 1] += right; + out_buffer[out + 2] += center; + out_buffer[out + 3] += low_freq; + out_buffer[out + 4] += rear_left; + out_buffer[out + 5] += rear_right; + out_buffer[out + 6] += side_left; + out_buffer[out + 7] += side_right; } } } - else - { - fmt::throw_exception("Unknown channel count (port=%u, channel=%d)" HERE, port.number, port.channel); - } - - memset(buf, 0, block_size * sizeof(float)); } - - - if (!first_mix) + else { - // Copy output data (2ch or 8ch) - if (g_cfg.audio.downmix_to_2ch) - { - for (u32 i = 0; i < std::size(buf2ch); i++) - { - out_buffer[out_pos][i] = buf2ch[i]; - } - } - else - { - for (u32 i = 0; i < std::size(buf8ch); i++) - { - out_buffer[out_pos][i] = buf8ch[i]; - } - } + fmt::throw_exception("Unknown channel count (port=%u, channel=%d)" HERE, port.number, port.num_channels); } + } - const u64 stamp1 = get_system_time(); + // Nothing was mixed, memset out_buffer to 0 + if (first_mix) + { + std::memset(out_buffer, 0, out_buffer_sz * sizeof(float)); + } + else if (g_cfg.audio.convert_to_u16) + { + // convert the data from float to u16 with clipping: + // 2x MULPS + // 2x MAXPS (optional) + // 2x MINPS (optional) + // 2x CVTPS2DQ (converts float to s32) + // PACKSSDW (converts s32 to s16 with signed saturation) - if (first_mix) + for (size_t i = 0; i < out_buffer_sz; i += 8) { - std::memset(out_buffer[out_pos].get(), 0, 8 * BUFFER_SIZE * sizeof(float)); + // TODO ruipin: Revisit this + const auto scale = _mm_set1_ps(0x8000); + _mm_store_ps(out_buffer + i / 2, _mm_castsi128_ps(_mm_packs_epi32( + _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i), scale)), + _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer + i + 4), scale))))); } + } +} - if (g_cfg.audio.convert_to_u16) - { - // convert the data from float to u16 with clipping: - // 2x MULPS - // 2x MAXPS (optional) - // 2x MINPS (optional) - // 2x CVTPS2DQ (converts float to s32) - // PACKSSDW (converts s32 to s16 with signed saturation) +void cell_audio_thread::finish_port_volume_stepping() +{ + // part of cellAudioSetPortLevel functionality + for (auto& port : ports) + { + if (port.state != audio_port_state::started) continue; - for (size_t i = 0; i < 8 * BUFFER_SIZE; i += 8) - { - const auto scale = _mm_set1_ps(0x8000); - _mm_store_ps(out_buffer[out_pos].get() + i / 2, _mm_castsi128_ps(_mm_packs_epi32( - _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer[out_pos].get() + i), scale)), - _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(out_buffer[out_pos].get() + i + 4), scale))))); - } - } - - audio->AddData(out_buffer[out_pos].get(), buf_sz); - - const u64 stamp2 = get_system_time(); - - { - // update indices: - - for (u32 i = 0; i < AUDIO_PORT_COUNT; i++) - { - audio_port& port = ports[i]; - - if (port.state != audio_port_state::started) continue; - - u32 position = port.tag % port.block; // old value - port.counter = m_counter; - port.tag++; // absolute index of block that will be read - m_indexes[i] = (position + 1) % port.block; // write new value - } - - // send aftermix event (normal audio event) - - auto _locked = g_idm->lock>(0); - - for (u64 key : keys) - { - // TODO: move out of the lock scope - if (auto queue = lv2_event_queue::find(key)) - { - queue->send(0, 0, 0, 0); // TODO: check arguments - } - } - } - - const u64 stamp3 = get_system_time(); - - switch (m_dump.GetCh()) - { - case 2: m_dump.WriteData(&buf2ch, sizeof(buf2ch)); break; // write file data (2 ch) - case 8: m_dump.WriteData(&buf8ch, sizeof(buf8ch)); break; // write file data (8 ch) - } - - cellAudio.trace("Audio perf: (access=%d, AddData=%d, events=%d, dump=%d)", - stamp1 - stamp0, stamp2 - stamp1, stamp3 - stamp2, get_system_time() - stamp3); + const auto param = port.level_set.load(); + port.level = param.value; + port.level_set.compare_and_swap(param, { param.value, 0.0f }); } } @@ -330,7 +796,7 @@ error_code cellAudioInit() const auto ind = vm::cast(vm::alloc(sizeof(u64) * AUDIO_PORT_COUNT, vm::main)); // Start audio thread - auto g_audio = g_idm->lock>(id_new); + auto g_audio = g_idm->lock>(id_new); if (!g_audio) { @@ -339,7 +805,7 @@ error_code cellAudioInit() return CELL_AUDIO_ERROR_ALREADY_INIT; } - g_audio.create("Audio Thread", buf, ind); + g_audio.create("cellAudio Thread", buf, ind); return CELL_OK; } @@ -348,7 +814,7 @@ error_code cellAudioQuit(ppu_thread& ppu) cellAudio.warning("cellAudioQuit()"); // Stop audio thread - auto g_audio = g_idm->lock>(0); + auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -368,7 +834,7 @@ error_code cellAudioQuit(ppu_thread& ppu) thread_ctrl::wait_for(1000); - auto g_audio = g_idm->lock>(0); + auto g_audio = g_idm->lock>(0); if (*g_audio.get() == thread_state::finished) { @@ -389,7 +855,7 @@ error_code cellAudioPortOpen(vm::ptr audioParam, vm::ptrlock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -401,23 +867,23 @@ error_code cellAudioPortOpen(vm::ptr audioParam, vm::ptrnChannel; - const u64 block = audioParam->nBlock; + const u64 num_channels = audioParam->nChannel; + const u64 num_blocks = audioParam->nBlock; const u64 attr = audioParam->attr; // check attributes - if (channel != CELL_AUDIO_PORT_2CH && - channel != CELL_AUDIO_PORT_8CH && - channel) + if (num_channels != CELL_AUDIO_PORT_2CH && + num_channels != CELL_AUDIO_PORT_8CH && + num_channels) { return CELL_AUDIO_ERROR_PARAM; } - if (block != CELL_AUDIO_BLOCK_8 && - block != CELL_AUDIO_BLOCK_16 && - block != 2 && - block != 4 && - block != 32) + if (num_blocks != CELL_AUDIO_BLOCK_8 && + num_blocks != CELL_AUDIO_BLOCK_16 && + num_blocks != 2 && + num_blocks != 4 && + num_blocks != 32) { return CELL_AUDIO_ERROR_PARAM; } @@ -464,11 +930,14 @@ error_code cellAudioPortOpen(vm::ptr audioParam, vm::ptrchannel = ::narrow(channel); - port->block = ::narrow(block); - port->attr = attr; - port->size = ::narrow(channel * block * AUDIO_SAMPLES * sizeof(f32)); - port->tag = 0; + port->num_channels = ::narrow(num_channels); + port->num_blocks = ::narrow(num_blocks); + port->attr = attr; + port->size = ::narrow(num_channels * num_blocks * port->block_size()); + port->cur_pos = 0; + port->global_counter = g_audio->m_counter; + port->active_counter = 0; + port->timestamp = g_audio->m_last_period_end; if (attr & CELL_AUDIO_PORTATTR_INITLEVEL) { @@ -487,9 +956,9 @@ error_code cellAudioPortOpen(vm::ptr audioParam, vm::ptr portConfig) { - cellAudio.warning("cellAudioGetPortConfig(portNum=%d, portConfig=*0x%x)", portNum, portConfig); + cellAudio.trace("cellAudioGetPortConfig(portNum=%d, portConfig=*0x%x)", portNum, portConfig); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -513,8 +982,8 @@ error_code cellAudioGetPortConfig(u32 portNum, vm::ptr port default: fmt::throw_exception("Invalid port state (%d: %d)", portNum, (u32)state); } - portConfig->nChannel = port.channel; - portConfig->nBlock = port.block; + portConfig->nChannel = port.num_channels; + portConfig->nBlock = port.num_blocks; portConfig->portSize = port.size; portConfig->portAddr = port.addr.addr(); return CELL_OK; @@ -524,7 +993,7 @@ error_code cellAudioPortStart(u32 portNum) { cellAudio.warning("cellAudioPortStart(portNum=%d)", portNum); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -549,7 +1018,7 @@ error_code cellAudioPortClose(u32 portNum) { cellAudio.warning("cellAudioPortClose(portNum=%d)", portNum); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -574,7 +1043,7 @@ error_code cellAudioPortStop(u32 portNum) { cellAudio.warning("cellAudioPortStop(portNum=%d)", portNum); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -599,7 +1068,7 @@ error_code cellAudioGetPortTimestamp(u32 portNum, u64 tag, vm::ptr stamp) { cellAudio.trace("cellAudioGetPortTimestamp(portNum=%d, tag=0x%llx, stamp=*0x%x)", portNum, tag, stamp); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -618,9 +1087,15 @@ error_code cellAudioGetPortTimestamp(u32 portNum, u64 tag, vm::ptr stamp) return CELL_AUDIO_ERROR_PORT_NOT_OPEN; } - // TODO: check tag (CELL_AUDIO_ERROR_TAG_NOT_FOUND error) + if (port.global_counter < tag) + { + cellAudio.error("cellAudioGetPortTimestamp(portNum=%d, tag=0x%llx, stamp=*0x%x) -> CELL_AUDIO_ERROR_TAG_NOT_FOUND", portNum, tag, stamp); + return CELL_AUDIO_ERROR_TAG_NOT_FOUND; + } - *stamp = g_audio->start_time + Emu.GetPauseTime() + (port.counter + (tag - port.tag)) * 256000000 / 48000; + u64 delta_tag = port.global_counter - tag; + u64 delta_tag_stamp = delta_tag * g_audio->audio_block_period; + *stamp = port.timestamp - delta_tag_stamp; return CELL_OK; } @@ -629,7 +1104,7 @@ error_code cellAudioGetPortBlockTag(u32 portNum, u64 blockNo, vm::ptr tag) { cellAudio.trace("cellAudioGetPortBlockTag(portNum=%d, blockNo=0x%llx, tag=*0x%x)", portNum, blockNo, tag); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -648,22 +1123,12 @@ error_code cellAudioGetPortBlockTag(u32 portNum, u64 blockNo, vm::ptr tag) return CELL_AUDIO_ERROR_PORT_NOT_OPEN; } - if (blockNo >= port.block) + if (blockNo >= port.num_blocks) { return CELL_AUDIO_ERROR_PARAM; } - u64 tag_base = port.tag; - if (tag_base % port.block > blockNo) - { - tag_base &= ~(port.block - 1); - tag_base += port.block; - } - else - { - tag_base &= ~(port.block - 1); - } - *tag = tag_base + blockNo; + *tag = port.global_counter + blockNo - port.cur_pos; return CELL_OK; } @@ -672,7 +1137,7 @@ error_code cellAudioSetPortLevel(u32 portNum, float level) { cellAudio.trace("cellAudioSetPortLevel(portNum=%d, level=%f)", portNum, level); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -714,7 +1179,7 @@ error_code cellAudioCreateNotifyEventQueue(vm::ptr id, vm::ptr key) attr->type = SYS_PPU_QUEUE; attr->name_u64 = 0; - for (u64 i = 0; i < 100; i++) + for (u64 i = 0; i < MAX_AUDIO_EVENT_QUEUES; i++) { // Create an event queue "bruteforcing" an available key const u64 key_value = 0x80004d494f323221ull + i; @@ -754,7 +1219,7 @@ error_code cellAudioSetNotifyEventQueue(u64 key) { cellAudio.warning("cellAudioSetNotifyEventQueue(key=0x%llx)", key); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -787,7 +1252,7 @@ error_code cellAudioRemoveNotifyEventQueue(u64 key) { cellAudio.warning("cellAudioRemoveNotifyEventQueue(key=0x%llx)", key); - const auto g_audio = g_idm->lock>(0); + const auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -820,7 +1285,7 @@ error_code cellAudioAddData(u32 portNum, vm::ptr src, u32 samples, float { cellAudio.trace("cellAudioAddData(portNum=%d, src=*0x%x, samples=%d, volume=%f)", portNum, src, samples, volume); - auto g_audio = g_idm->lock>(0); + auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -841,11 +1306,11 @@ error_code cellAudioAddData(u32 portNum, vm::ptr src, u32 samples, float const audio_port& port = g_audio->ports[portNum]; - const auto dst = vm::ptr::make(port.addr.addr() + u32(port.tag % port.block) * port.channel * 256 * u32{sizeof(float)}); + const auto dst = port.get_vm_ptr(); g_audio.unlock(); - for (u32 i = 0; i < samples * port.channel; i++) + for (u32 i = 0; i < samples * port.num_channels; i++) { dst[i] += src[i] * volume; // mix all channels } @@ -857,7 +1322,7 @@ error_code cellAudioAdd2chData(u32 portNum, vm::ptr src, u32 samples, flo { cellAudio.trace("cellAudioAdd2chData(portNum=%d, src=*0x%x, samples=%d, volume=%f)", portNum, src, samples, volume); - auto g_audio = g_idm->lock>(0); + auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -878,11 +1343,11 @@ error_code cellAudioAdd2chData(u32 portNum, vm::ptr src, u32 samples, flo const audio_port& port = g_audio->ports[portNum]; - const auto dst = vm::ptr::make(port.addr.addr() + s32(port.tag % port.block) * port.channel * 256 * u32{sizeof(float)}); + const auto dst = port.get_vm_ptr(); g_audio.unlock(); - if (port.channel == 2) + if (port.num_channels == 2) { for (u32 i = 0; i < samples; i++) { @@ -890,7 +1355,7 @@ error_code cellAudioAdd2chData(u32 portNum, vm::ptr src, u32 samples, flo dst[i * 2 + 1] += src[i * 2 + 1] * volume; // mix R ch } } - else if (port.channel == 6) + else if (port.num_channels == 6) { for (u32 i = 0; i < samples; i++) { @@ -902,7 +1367,7 @@ error_code cellAudioAdd2chData(u32 portNum, vm::ptr src, u32 samples, flo //dst[i * 6 + 5] += 0.0f; // rear R } } - else if (port.channel == 8) + else if (port.num_channels == 8) { for (u32 i = 0; i < samples; i++) { @@ -918,7 +1383,7 @@ error_code cellAudioAdd2chData(u32 portNum, vm::ptr src, u32 samples, flo } else { - cellAudio.error("cellAudioAdd2chData(portNum=%d): invalid port.channel value (%d)", portNum, port.channel); + cellAudio.error("cellAudioAdd2chData(portNum=%d): invalid port.channel value (%d)", portNum, port.num_channels); } return CELL_OK; @@ -928,7 +1393,7 @@ error_code cellAudioAdd6chData(u32 portNum, vm::ptr src, float volume) { cellAudio.trace("cellAudioAdd6chData(portNum=%d, src=*0x%x, volume=%f)", portNum, src, volume); - auto g_audio = g_idm->lock>(0); + auto g_audio = g_idm->lock>(0); if (!g_audio) { @@ -942,11 +1407,11 @@ error_code cellAudioAdd6chData(u32 portNum, vm::ptr src, float volume) const audio_port& port = g_audio->ports[portNum]; - const auto dst = vm::ptr::make(port.addr.addr() + s32(port.tag % port.block) * port.channel * 256 * u32{sizeof(float)}); + const auto dst = port.get_vm_ptr(); g_audio.unlock(); - if (port.channel == 6) + if (port.num_channels == 6) { for (u32 i = 0; i < 256; i++) { @@ -958,7 +1423,7 @@ error_code cellAudioAdd6chData(u32 portNum, vm::ptr src, float volume) dst[i * 6 + 5] += src[i * 6 + 5] * volume; // mix rear R } } - else if (port.channel == 8) + else if (port.num_channels == 8) { for (u32 i = 0; i < 256; i++) { @@ -974,7 +1439,7 @@ error_code cellAudioAdd6chData(u32 portNum, vm::ptr src, float volume) } else { - cellAudio.error("cellAudioAdd6chData(portNum=%d): invalid port.channel value (%d)", portNum, port.channel); + cellAudio.error("cellAudioAdd6chData(portNum=%d): invalid port.channel value (%d)", portNum, port.num_channels); } return CELL_OK; diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index ffe9367f34..25f14eb982 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -1,6 +1,9 @@ -#pragma once +#pragma once #include "Utilities/Thread.h" +#include "Emu/Memory/vm.h" +#include "Emu/Audio/AudioThread.h" +#include "Emu/Audio/AudioDumper.h" // Error codes enum CellAudioError : u32 @@ -76,11 +79,26 @@ struct CellAudioPortConfig enum : u32 { - BUFFER_NUM = 32, - BUFFER_SIZE = 256, AUDIO_PORT_COUNT = 8, - AUDIO_PORT_OFFSET = 256 * 1024, - AUDIO_SAMPLES = CELL_AUDIO_BLOCK_SAMPLES, + AUDIO_MAX_BLOCK_COUNT = 32, + AUDIO_MAX_CHANNELS_COUNT = 8, + + AUDIO_PORT_OFFSET = AUDIO_BUFFER_SAMPLES * AUDIO_MAX_BLOCK_COUNT * AUDIO_MAX_CHANNELS_COUNT * sizeof(f32), + EXTRA_AUDIO_BUFFERS = 8, + MAX_AUDIO_EVENT_QUEUES = 64, + + AUDIO_BLOCK_SIZE_2CH = 2 * AUDIO_BUFFER_SAMPLES, + AUDIO_BLOCK_SIZE_8CH = 8 * AUDIO_BUFFER_SAMPLES, + + PORT_BUFFER_TAG_COUNT = 4, + + PORT_BUFFER_TAG_LAST_2CH = AUDIO_BLOCK_SIZE_2CH - 1, + PORT_BUFFER_TAG_DELTA_2CH = PORT_BUFFER_TAG_LAST_2CH / (PORT_BUFFER_TAG_COUNT - 1), + PORT_BUFFER_TAG_FIRST_2CH = PORT_BUFFER_TAG_LAST_2CH % (PORT_BUFFER_TAG_COUNT - 1), + + PORT_BUFFER_TAG_LAST_8CH = AUDIO_BLOCK_SIZE_8CH - 1, + PORT_BUFFER_TAG_DELTA_8CH = PORT_BUFFER_TAG_LAST_8CH / (PORT_BUFFER_TAG_COUNT - 1), + PORT_BUFFER_TAG_FIRST_8CH = PORT_BUFFER_TAG_LAST_8CH % (PORT_BUFFER_TAG_COUNT - 1), }; enum class audio_port_state : u32 @@ -98,12 +116,14 @@ struct audio_port vm::ptr addr{}; vm::ptr index{}; - u32 channel; - u32 block; + u32 num_channels; + u32 num_blocks; u64 attr; - u64 tag; - u64 counter; // copy of global counter + u64 cur_pos; + u64 global_counter; // copy of global counter + u64 active_counter; u32 size; + u64 timestamp; // copy of global timestamp struct alignas(8) level_set_t { @@ -113,25 +133,158 @@ struct audio_port float level; atomic_t level_set; + + u32 block_size() const + { + return num_channels * AUDIO_BUFFER_SAMPLES; + } + + u32 buf_size() const + { + return block_size() * sizeof(float); + } + + u32 position(s32 offset = 0) const + { + s32 ofs = (offset % num_blocks) + num_blocks; + return (cur_pos + ofs) % num_blocks; + } + + u32 buf_addr(s32 offset = 0) const + { + return addr.addr() + position(offset) * buf_size(); + } + + to_be_t* get_vm_ptr(s32 offset = 0) const + { + return vm::_ptr(buf_addr(offset)); + } + + + // Tags + u32 prev_touched_tag_nr; + f32 tag_backup[AUDIO_MAX_BLOCK_COUNT][PORT_BUFFER_TAG_COUNT] = { 0 }; + + constexpr static bool is_tag(float val); + void tag(s32 offset = 0); + void apply_tag_backups(s32 offset = 0); }; -class audio_thread +class audio_ringbuffer +{ +private: + const std::shared_ptr backend; + + const u32 num_allocated_buffers; + const u32 buf_sz; + const u32 audio_sampling_rate; + const u32 channels; + + std::unique_ptr m_dump; + + std::unique_ptr buffer[MAX_AUDIO_BUFFERS]; + const float silence_buffer[8 * AUDIO_BUFFER_SAMPLES] = { 0 }; + + bool backend_open = false; + bool playing = false; + bool emu_paused = false; + + u64 update_timestamp = 0; + u64 play_timestamp = 0; + + u64 last_remainder = 0; + u64 enqueued_samples = 0; + + u32 next_buf = 0; + +public: + audio_ringbuffer(u32 num_buffers, u32 audio_sampling_rate, u32 channels); + ~audio_ringbuffer(); + + void play(); + void enqueue(const float* in_buffer = nullptr); + void flush(); + u64 update(); + void enqueue_silence(u32 buf_count = 1); + + float* get_buffer(u32 num) const + { + AUDIT(num < num_allocated_buffers); + AUDIT(buffer[num].get() != nullptr); + return buffer[num].get(); + } + + u32 get_buf_sz() const + { + return buf_sz; + } + + u64 get_timestamp() const + { + return get_system_time() - Emu.GetPauseTime(); + } + + float* get_current_buffer() const + { + return get_buffer(next_buf); + } + + u64 get_enqueued_samples() const + { + return enqueued_samples; + } + + bool is_playing() const + { + return playing; + } +}; + + +class cell_audio_thread { vm::ptr m_buffer; vm::ptr m_indexes; - u64 m_counter{}; + std::unique_ptr ringbuffer; + + void reset_ports(s32 offset = 0); + void advance(u64 timestamp, bool reset = true); + std::tuple count_port_buffer_tags(); + template void mix(float *out_buffer, s32 offset = 0); + void finish_port_volume_stepping(); + + constexpr static u64 get_thread_wait_delay(u64 time_left) + { + return (time_left > 1000) ? time_left - 750 : 100; + } public: - const u64 start_time = get_system_time(); + const s64 period_comparison_margin = 100; // When comparing the current period time with the desired period, if it is below this number of usecs we do not wait any longer - std::array ports; + const u32 audio_channels = AudioThread::get_channels(); + const u32 audio_sampling_rate = AudioThread::get_sampling_rate(); + const u64 audio_block_period = AUDIO_BUFFER_SAMPLES * 1000000 / audio_sampling_rate; + const u64 desired_buffer_duration = g_cfg.audio.enable_buffering ? g_cfg.audio.desired_buffer_duration : 0; + const bool buffering_enabled = g_cfg.audio.enable_buffering && (desired_buffer_duration >= audio_block_period); + + const u64 minimum_block_period = audio_block_period / 2; // the block period will not be dynamically lowered below this value (usecs) + const u64 maximum_block_period = audio_block_period + (audio_block_period - minimum_block_period); // the block period will not be dynamically increased above this value (usecs) + + const u32 desired_full_buffers = buffering_enabled ? static_cast(desired_buffer_duration / audio_block_period) + 1 : 1; + const u32 num_allocated_buffers = desired_full_buffers + EXTRA_AUDIO_BUFFERS; // number of ringbuffer buffers std::vector keys; + std::array ports; + + u64 m_last_period_end = 0; + u64 m_counter = 0; + u64 m_start_time = 0; + u64 m_dynamic_period = 0; void operator()(); - audio_thread(vm::ptr buf, vm::ptr ind) + cell_audio_thread(vm::ptr buf, vm::ptr ind) : m_buffer(buf) , m_indexes(ind) { @@ -157,4 +310,4 @@ public: } }; -using audio_config = named_thread; +using cell_audio = named_thread; diff --git a/rpcs3/Emu/Cell/Modules/libmixer.cpp b/rpcs3/Emu/Cell/Modules/libmixer.cpp index f503c59a85..f07053ffe5 100644 --- a/rpcs3/Emu/Cell/Modules/libmixer.cpp +++ b/rpcs3/Emu/Cell/Modules/libmixer.cpp @@ -1,4 +1,4 @@ -#include "stdafx.h" +#include "stdafx.h" #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" @@ -328,13 +328,13 @@ struct surmixer_thread : ppu_thread void non_task() { - const auto g_audio = fxm::get(); + const auto g_audio = fxm::get(); audio_port& port = g_audio->ports[g_surmx.audio_port]; while (port.state != audio_port_state::closed) { - if (g_surmx.mixcount > (port.tag + 0)) // adding positive value (1-15): preemptive buffer filling (hack) + if (g_surmx.mixcount > (port.active_counter + 0)) // adding positive value (1-15): preemptive buffer filling (hack) { thread_ctrl::wait_for(1000); // hack continue; @@ -432,7 +432,7 @@ struct surmixer_thread : ppu_thread //u64 stamp2 = get_system_time(); - auto buf = vm::_ptr(port.addr.addr() + (g_surmx.mixcount % port.block) * port.channel * AUDIO_SAMPLES * sizeof(float)); + auto buf = vm::_ptr(port.addr.addr() + (g_surmx.mixcount % port.num_blocks) * port.num_channels * AUDIO_BUFFER_SAMPLES * sizeof(float)); for (auto& mixdata : g_surmx.mixdata) { @@ -456,7 +456,7 @@ s32 cellSurMixerCreate(vm::cptr config) { libmixer.warning("cellSurMixerCreate(config=*0x%x)", config); - const auto g_audio = fxm::get(); + const auto g_audio = fxm::get(); const auto port = g_audio->open_port(); @@ -472,11 +472,10 @@ s32 cellSurMixerCreate(vm::cptr config) g_surmx.ch_strips_6 = config->chStrips6; g_surmx.ch_strips_8 = config->chStrips8; - port->channel = 8; - port->block = 16; + port->num_channels = 8; + port->num_blocks = 16; port->attr = 0; - port->size = port->channel * port->block * AUDIO_SAMPLES * sizeof(float); - port->tag = 0; + port->size = port->num_channels * port->num_blocks * AUDIO_BUFFER_SAMPLES * sizeof(float); port->level = 1.0f; port->level_set.store({ 1.0f, 0.0f }); @@ -541,7 +540,7 @@ s32 cellSurMixerStart() { libmixer.warning("cellSurMixerStart()"); - const auto g_audio = fxm::get(); + const auto g_audio = fxm::get(); if (g_surmx.audio_port >= AUDIO_PORT_COUNT) { @@ -563,7 +562,7 @@ s32 cellSurMixerFinalize() { libmixer.warning("cellSurMixerFinalize()"); - const auto g_audio = fxm::get(); + const auto g_audio = fxm::get(); if (g_surmx.audio_port >= AUDIO_PORT_COUNT) { @@ -608,7 +607,7 @@ s32 cellSurMixerPause(u32 type) { libmixer.warning("cellSurMixerPause(type=%d)", type); - const auto g_audio = fxm::get(); + const auto g_audio = fxm::get(); if (g_surmx.audio_port >= AUDIO_PORT_COUNT) { @@ -630,10 +629,12 @@ s32 cellSurMixerGetCurrentBlockTag(vm::ptr tag) s32 cellSurMixerGetTimestamp(u64 tag, vm::ptr stamp) { - libmixer.trace("cellSurMixerGetTimestamp(tag=0x%llx, stamp=*0x%x)", tag, stamp); + libmixer.error("cellSurMixerGetTimestamp(tag=0x%llx, stamp=*0x%x)", tag, stamp); + + const auto g_audio = fxm::get(); + + *stamp = g_audio->m_start_time + tag * AUDIO_BUFFER_SAMPLES * 1'000'000 / g_audio->audio_sampling_rate; - const auto g_audio = fxm::get(); - *stamp = g_audio->start_time + (tag) * 256000000 / 48000; // ??? return CELL_OK; } diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index c285ded8e4..b59767772d 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -526,9 +526,11 @@ struct cfg_root : cfg::node cfg::_bool dump_to_file{this, "Dump to file"}; cfg::_bool convert_to_u16{this, "Convert to 16 bit"}; cfg::_bool downmix_to_2ch{this, "Downmix to Stereo", true}; - cfg::_int<2, 128> frames{this, "Buffer Count", 32}; cfg::_int<1, 128> startt{this, "Start Threshold", 1}; cfg::_int<0, 200> volume{this, "Master Volume", 100}; + cfg::_bool enable_buffering{this, "Enable Buffering", true}; + cfg::_int <0, 250'000> desired_buffer_duration{this, "Desired Audio Buffer Duration", 100'000}; + cfg::_int<1, 1000> sampling_period_multiplier{this, "Sampling Period Multiplier", 100}; } audio{this}; diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 918cd635c6..3a06f8ffb5 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -928,9 +928,6 @@ Emu\Audio\Null - - Emu\Audio - Utilities @@ -1443,7 +1440,7 @@ Utilities - + Utilities @@ -1452,12 +1449,15 @@ Emu\GPU\RSX\Common - + - Emu\GPU\RSX + Emu\GPU\RSX Emu\GPU\RSX\Common + + Emu\Audio + \ No newline at end of file diff --git a/rpcs3/rpcs3_app.cpp b/rpcs3/rpcs3_app.cpp index 3e425e8554..6076bc089b 100644 --- a/rpcs3/rpcs3_app.cpp +++ b/rpcs3/rpcs3_app.cpp @@ -37,7 +37,7 @@ #include "Emu/RSX/Null/NullGSRender.h" #include "Emu/RSX/GL/GLGSRender.h" #include "Emu/Audio/Null/NullAudioThread.h" -#include "Emu/Audio/AL/OpenALThread.h" +//#include "Emu/Audio/AL/OpenALThread.h" #ifdef _MSC_VER #include "Emu/RSX/D3D12/D3D12GSRender.h" #endif @@ -269,7 +269,7 @@ void rpcs3_app::InitializeCallbacks() case audio_renderer::pulse: return std::make_shared(); #endif - case audio_renderer::openal: return std::make_shared(); + //case audio_renderer::openal: return std::make_shared(); default: fmt::throw_exception("Invalid audio renderer: %s" HERE, type); } };