summaryrefslogtreecommitdiff
path: root/src/audio_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core')
-rw-r--r--src/audio_core/CMakeLists.txt1
-rw-r--r--src/audio_core/audio_core.cpp18
-rw-r--r--src/audio_core/audio_core.h26
-rw-r--r--src/audio_core/audio_event.h4
-rw-r--r--src/audio_core/audio_in_manager.cpp2
-rw-r--r--src/audio_core/audio_in_manager.h5
-rw-r--r--src/audio_core/audio_manager.h2
-rw-r--r--src/audio_core/audio_out_manager.cpp2
-rw-r--r--src/audio_core/audio_render_manager.cpp6
-rw-r--r--src/audio_core/audio_render_manager.h12
-rw-r--r--src/audio_core/device/audio_buffer.h4
-rw-r--r--src/audio_core/device/audio_buffers.h21
-rw-r--r--src/audio_core/device/device_session.cpp64
-rw-r--r--src/audio_core/device/device_session.h34
-rw-r--r--src/audio_core/in/audio_in.cpp8
-rw-r--r--src/audio_core/in/audio_in.h8
-rw-r--r--src/audio_core/in/audio_in_system.cpp22
-rw-r--r--src/audio_core/in/audio_in_system.h12
-rw-r--r--src/audio_core/out/audio_out.cpp8
-rw-r--r--src/audio_core/out/audio_out.h8
-rw-r--r--src/audio_core/out/audio_out_system.cpp21
-rw-r--r--src/audio_core/out/audio_out_system.h10
-rw-r--r--src/audio_core/renderer/adsp/adsp.cpp2
-rw-r--r--src/audio_core/renderer/adsp/adsp.h4
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.cpp11
-rw-r--r--src/audio_core/renderer/adsp/audio_renderer.h8
-rw-r--r--src/audio_core/renderer/adsp/command_list_processor.h13
-rw-r--r--src/audio_core/renderer/audio_device.cpp34
-rw-r--r--src/audio_core/renderer/audio_device.h22
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.cpp16
-rw-r--r--src/audio_core/renderer/behavior/behavior_info.h4
-rw-r--r--src/audio_core/renderer/behavior/info_updater.cpp2
-rw-r--r--src/audio_core/renderer/behavior/info_updater.h2
-rw-r--r--src/audio_core/renderer/command/command_buffer.h12
-rw-r--r--src/audio_core/renderer/command/command_generator.h46
-rw-r--r--src/audio_core/renderer/command/effect/compressor.cpp11
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.cpp18
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp.h8
-rw-r--r--src/audio_core/renderer/command/mix/mix_ramp_grouped.h4
-rw-r--r--src/audio_core/renderer/command/sink/device.cpp4
-rw-r--r--src/audio_core/renderer/effect/effect_context.h14
-rw-r--r--src/audio_core/renderer/effect/effect_info_base.h4
-rw-r--r--src/audio_core/renderer/effect/i3dl2.h4
-rw-r--r--src/audio_core/renderer/effect/reverb.h4
-rw-r--r--src/audio_core/renderer/memory/address_info.h5
-rw-r--r--src/audio_core/renderer/nodes/node_states.h8
-rw-r--r--src/audio_core/renderer/performance/performance_manager.h8
-rw-r--r--src/audio_core/renderer/system_manager.cpp46
-rw-r--r--src/audio_core/renderer/system_manager.h9
-rw-r--r--src/audio_core/renderer/upsampler/upsampler_manager.h2
-rw-r--r--src/audio_core/renderer/voice/voice_info.h26
-rw-r--r--src/audio_core/sink/cubeb_sink.cpp383
-rw-r--r--src/audio_core/sink/cubeb_sink.h17
-rw-r--r--src/audio_core/sink/null_sink.h49
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp377
-rw-r--r--src/audio_core/sink/sdl2_sink.h17
-rw-r--r--src/audio_core/sink/sink.h15
-rw-r--r--src/audio_core/sink/sink_details.cpp6
-rw-r--r--src/audio_core/sink/sink_stream.cpp279
-rw-r--r--src/audio_core/sink/sink_stream.h175
60 files changed, 810 insertions, 1157 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 5fe1d5fa5..144f1bab2 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -194,6 +194,7 @@ add_library(audio_core STATIC
194 sink/sink.h 194 sink/sink.h
195 sink/sink_details.cpp 195 sink/sink_details.cpp
196 sink/sink_details.h 196 sink/sink_details.h
197 sink/sink_stream.cpp
197 sink/sink_stream.h 198 sink/sink_stream.h
198) 199)
199 200
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 78e615a10..c845330cd 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -47,22 +47,12 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() {
47 return *adsp; 47 return *adsp;
48} 48}
49 49
50void AudioCore::PauseSinks(const bool pausing) const { 50void AudioCore::SetNVDECActive(bool active) {
51 if (pausing) { 51 nvdec_active = active;
52 output_sink->PauseStreams();
53 input_sink->PauseStreams();
54 } else {
55 output_sink->UnpauseStreams();
56 input_sink->UnpauseStreams();
57 }
58} 52}
59 53
60u32 AudioCore::GetStreamQueue() const { 54bool AudioCore::IsNVDECActive() const {
61 return estimated_queue.load(); 55 return nvdec_active;
62}
63
64void AudioCore::SetStreamQueue(u32 size) {
65 estimated_queue.store(size);
66} 56}
67 57
68} // namespace AudioCore 58} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index 0f7d61ee4..e33e00a3e 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -17,7 +17,7 @@ namespace AudioCore {
17 17
18class AudioManager; 18class AudioManager;
19/** 19/**
20 * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. 20 * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP.
21 */ 21 */
22class AudioCore { 22class AudioCore {
23public: 23public:
@@ -58,26 +58,16 @@ public:
58 AudioRenderer::ADSP::ADSP& GetADSP(); 58 AudioRenderer::ADSP::ADSP& GetADSP();
59 59
60 /** 60 /**
61 * Pause the sink. Called from the core. 61 * Toggle NVDEC state, used to avoid stall in playback.
62 * 62 *
63 * @param pausing - Is this pause due to an actual pause, or shutdown? 63 * @param active - Set true if nvdec is active, otherwise false.
64 * Unfortunately, shutdown also pauses streams, which can cause issues.
65 */ 64 */
66 void PauseSinks(bool pausing) const; 65 void SetNVDECActive(bool active);
67 66
68 /** 67 /**
69 * Get the size of the current stream queue. 68 * Get NVDEC state.
70 *
71 * @return Current stream queue size.
72 */
73 u32 GetStreamQueue() const;
74
75 /**
76 * Get the size of the current stream queue.
77 *
78 * @param size - New stream size.
79 */ 69 */
80 void SetStreamQueue(u32 size); 70 bool IsNVDECActive() const;
81 71
82private: 72private:
83 /** 73 /**
@@ -93,8 +83,8 @@ private:
93 std::unique_ptr<Sink::Sink> input_sink; 83 std::unique_ptr<Sink::Sink> input_sink;
94 /// The ADSP in the sysmodule 84 /// The ADSP in the sysmodule
95 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; 85 std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp;
96 /// Current size of the stream queue 86 /// Is NVDec currently active?
97 std::atomic<u32> estimated_queue{0}; 87 bool nvdec_active{false};
98}; 88};
99 89
100} // namespace AudioCore 90} // namespace AudioCore
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h
index 82dd32dca..012d2ed70 100644
--- a/src/audio_core/audio_event.h
+++ b/src/audio_core/audio_event.h
@@ -14,7 +14,7 @@ namespace AudioCore {
14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and 14 * Responsible for the input/output events, set by the stream backend when buffers are consumed, and
15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer 15 * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer
16 * recycling going. 16 * recycling going.
17 * In a real Switch this is not a seprate class, and exists entirely within the audio manager. 17 * In a real Switch this is not a separate class, and exists entirely within the audio manager.
18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can 18 * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can
19 * wait on multiple events at once, and the events are not needed by the backend. 19 * wait on multiple events at once, and the events are not needed by the backend.
20 */ 20 */
@@ -81,7 +81,7 @@ public:
81 void ClearEvents(); 81 void ClearEvents();
82 82
83private: 83private:
84 /// Lock, used bythe audio manager 84 /// Lock, used by the audio manager
85 std::mutex event_lock; 85 std::mutex event_lock;
86 /// Array of events, one per system type (see Type), last event is used to terminate 86 /// Array of events, one per system type (see Type), last event is used to terminate
87 std::array<std::atomic<bool>, 4> events_signalled; 87 std::array<std::atomic<bool>, 4> events_signalled;
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp
index 4aadb7fd6..f39fb4002 100644
--- a/src/audio_core/audio_in_manager.cpp
+++ b/src/audio_core/audio_in_manager.cpp
@@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN
82 82
83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; 83 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
84 if (input_devices.size() > 1) { 84 if (input_devices.size() > 1) {
85 names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); 85 names.emplace_back("Uac");
86 return 1; 86 return 1;
87 } 87 }
88 return 0; 88 return 0;
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h
index 75b73a0b6..8a519df99 100644
--- a/src/audio_core/audio_in_manager.h
+++ b/src/audio_core/audio_in_manager.h
@@ -59,9 +59,10 @@ public:
59 /** 59 /**
60 * Get a list of audio in device names. 60 * Get a list of audio in device names.
61 * 61 *
62 * @oaram names - Output container to write names to. 62 * @param names - Output container to write names to.
63 * @param max_count - Maximum numebr of deivce names to write. Unused 63 * @param max_count - Maximum number of device names to write. Unused
64 * @param filter - Should the list be filtered? Unused. 64 * @param filter - Should the list be filtered? Unused.
65 *
65 * @return Number of names written. 66 * @return Number of names written.
66 */ 67 */
67 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, 68 u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names,
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h
index 70316e9cb..8cbd95e22 100644
--- a/src/audio_core/audio_manager.h
+++ b/src/audio_core/audio_manager.h
@@ -76,7 +76,7 @@ public:
76 76
77private: 77private:
78 /** 78 /**
79 * Main thread, waiting on a manager signal and calling the registered fucntion. 79 * Main thread, waiting on a manager signal and calling the registered function.
80 */ 80 */
81 void ThreadFunc(); 81 void ThreadFunc();
82 82
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp
index 71d67de64..1766efde1 100644
--- a/src/audio_core/audio_out_manager.cpp
+++ b/src/audio_core/audio_out_manager.cpp
@@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() {
74 74
75u32 Manager::GetAudioOutDeviceNames( 75u32 Manager::GetAudioOutDeviceNames(
76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { 76 std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const {
77 names.push_back({"DeviceOut"}); 77 names.emplace_back("DeviceOut");
78 return 1; 78 return 1;
79} 79}
80 80
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp
index 7a846835b..7aba2b423 100644
--- a/src/audio_core/audio_render_manager.cpp
+++ b/src/audio_core/audio_render_manager.cpp
@@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() {
25 return *system_manager; 25 return *system_manager;
26} 26}
27 27
28auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) 28Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params,
29 -> Result { 29 u64& out_count) const {
30 if (!CheckValidRevision(params.revision)) { 30 if (!CheckValidRevision(params.revision)) {
31 return Service::Audio::ERR_INVALID_REVISION; 31 return Service::Audio::ERR_INVALID_REVISION;
32 } 32 }
@@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) {
54 session_ids[--session_count] = session_id; 54 session_ids[--session_count] = session_id;
55} 55}
56 56
57u32 Manager::GetSessionCount() { 57u32 Manager::GetSessionCount() const {
58 std::scoped_lock l{session_lock}; 58 std::scoped_lock l{session_lock};
59 return session_count; 59 return session_count;
60} 60}
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h
index 6a508ec56..bf4837190 100644
--- a/src/audio_core/audio_render_manager.h
+++ b/src/audio_core/audio_render_manager.h
@@ -46,7 +46,7 @@ public:
46 * @param out_count - Output size of the required workbuffer. 46 * @param out_count - Output size of the required workbuffer.
47 * @return Result code. 47 * @return Result code.
48 */ 48 */
49 Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); 49 Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const;
50 50
51 /** 51 /**
52 * Get a new session id. 52 * Get a new session id.
@@ -60,14 +60,14 @@ public:
60 * 60 *
61 * @return The number of active sessions. 61 * @return The number of active sessions.
62 */ 62 */
63 u32 GetSessionCount(); 63 u32 GetSessionCount() const;
64 64
65 /** 65 /**
66 * Add a renderer system to the manager. 66 * Add a renderer system to the manager.
67 * The system will be reguarly called to generate commands for the AudioRenderer. 67 * The system will be regularly called to generate commands for the AudioRenderer.
68 * 68 *
69 * @param system - The system to add. 69 * @param system - The system to add.
70 * @return True if the system was sucessfully added, otherwise false. 70 * @return True if the system was successfully added, otherwise false.
71 */ 71 */
72 bool AddSystem(System& system); 72 bool AddSystem(System& system);
73 73
@@ -75,7 +75,7 @@ public:
75 * Remove a renderer system from the manager. 75 * Remove a renderer system from the manager.
76 * 76 *
77 * @param system - The system to remove. 77 * @param system - The system to remove.
78 * @return True if the system was sucessfully removed, otherwise false. 78 * @return True if the system was successfully removed, otherwise false.
79 */ 79 */
80 bool RemoveSystem(System& system); 80 bool RemoveSystem(System& system);
81 81
@@ -94,7 +94,7 @@ private:
94 /// Number of active renderers 94 /// Number of active renderers
95 u32 session_count{}; 95 u32 session_count{};
96 /// Lock for interacting with the sessions 96 /// Lock for interacting with the sessions
97 std::mutex session_lock{}; 97 mutable std::mutex session_lock{};
98 /// Regularly generates commands from the registered systems for the AudioRenderer 98 /// Regularly generates commands from the registered systems for the AudioRenderer
99 std::unique_ptr<SystemManager> system_manager{}; 99 std::unique_ptr<SystemManager> system_manager{};
100}; 100};
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h
index cae7fa970..7128ef72a 100644
--- a/src/audio_core/device/audio_buffer.h
+++ b/src/audio_core/device/audio_buffer.h
@@ -8,6 +8,10 @@
8namespace AudioCore { 8namespace AudioCore {
9 9
10struct AudioBuffer { 10struct AudioBuffer {
11 /// Timestamp this buffer started playing.
12 u64 start_timestamp;
13 /// Timestamp this buffer should finish playing.
14 u64 end_timestamp;
11 /// Timestamp this buffer completed playing. 15 /// Timestamp this buffer completed playing.
12 s64 played_timestamp; 16 s64 played_timestamp;
13 /// Game memory address for these samples. 17 /// Game memory address for these samples.
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
index 5d1979ea0..3dae1a3b7 100644
--- a/src/audio_core/device/audio_buffers.h
+++ b/src/audio_core/device/audio_buffers.h
@@ -36,7 +36,7 @@ public:
36 * 36 *
37 * @param buffer - The new buffer. 37 * @param buffer - The new buffer.
38 */ 38 */
39 void AppendBuffer(AudioBuffer& buffer) { 39 void AppendBuffer(const AudioBuffer& buffer) {
40 std::scoped_lock l{lock}; 40 std::scoped_lock l{lock};
41 buffers[appended_index] = buffer; 41 buffers[appended_index] = buffer;
42 appended_count++; 42 appended_count++;
@@ -58,6 +58,7 @@ public:
58 if (index < 0) { 58 if (index < 0) {
59 index += N; 59 index += N;
60 } 60 }
61
61 out_buffers.push_back(buffers[index]); 62 out_buffers.push_back(buffers[index]);
62 registered_count++; 63 registered_count++;
63 registered_index = (registered_index + 1) % append_limit; 64 registered_index = (registered_index + 1) % append_limit;
@@ -87,10 +88,12 @@ public:
87 /** 88 /**
88 * Release all registered buffers. 89 * Release all registered buffers.
89 * 90 *
90 * @param timestamp - The released timestamp for this buffer. 91 * @param core_timing - The CoreTiming instance
92 * @param session - The device session
93 *
91 * @return Is the buffer was released. 94 * @return Is the buffer was released.
92 */ 95 */
93 bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { 96 bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
94 std::scoped_lock l{lock}; 97 std::scoped_lock l{lock};
95 bool buffer_released{false}; 98 bool buffer_released{false};
96 while (registered_count > 0) { 99 while (registered_count > 0) {
@@ -100,7 +103,7 @@ public:
100 } 103 }
101 104
102 // Check with the backend if this buffer can be released yet. 105 // Check with the backend if this buffer can be released yet.
103 if (!session.IsBufferConsumed(buffers[index].tag)) { 106 if (!session.IsBufferConsumed(buffers[index])) {
104 break; 107 break;
105 } 108 }
106 109
@@ -280,6 +283,16 @@ public:
280 return true; 283 return true;
281 } 284 }
282 285
286 u64 GetNextTimestamp() const {
287 // Iterate backwards through the buffer queue, and take the most recent buffer's end
288 std::scoped_lock l{lock};
289 auto index{appended_index - 1};
290 if (index < 0) {
291 index += append_limit;
292 }
293 return buffers[index].end_timestamp;
294 }
295
283private: 296private:
284 /// Buffer lock 297 /// Buffer lock
285 mutable std::recursive_mutex lock{}; 298 mutable std::recursive_mutex lock{};
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
index 095fc96ce..995060414 100644
--- a/src/audio_core/device/device_session.cpp
+++ b/src/audio_core/device/device_session.cpp
@@ -7,11 +7,20 @@
7#include "audio_core/device/device_session.h" 7#include "audio_core/device/device_session.h"
8#include "audio_core/sink/sink_stream.h" 8#include "audio_core/sink/sink_stream.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/core_timing.h"
10#include "core/memory.h" 11#include "core/memory.h"
11 12
12namespace AudioCore { 13namespace AudioCore {
13 14
14DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} 15using namespace std::literals;
16constexpr auto INCREMENT_TIME{5ms};
17
18DeviceSession::DeviceSession(Core::System& system_)
19 : system{system_}, thread_event{Core::Timing::CreateEvent(
20 "AudioOutSampleTick",
21 [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
22 return ThreadFunc();
23 })} {}
15 24
16DeviceSession::~DeviceSession() { 25DeviceSession::~DeviceSession() {
17 Finalize(); 26 Finalize();
@@ -50,25 +59,26 @@ void DeviceSession::Finalize() {
50} 59}
51 60
52void DeviceSession::Start() { 61void DeviceSession::Start() {
53 stream->SetPlayedSampleCount(played_sample_count); 62 if (stream) {
54 stream->Start(); 63 stream->Start();
64 system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME,
65 thread_event);
66 }
55} 67}
56 68
57void DeviceSession::Stop() { 69void DeviceSession::Stop() {
58 if (stream) { 70 if (stream) {
59 played_sample_count = stream->GetPlayedSampleCount();
60 stream->Stop(); 71 stream->Stop();
72 system.CoreTiming().UnscheduleEvent(thread_event, {});
61 } 73 }
62} 74}
63 75
64void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { 76void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
65 auto& memory{system.Memory()}; 77 for (const auto& buffer : buffers) {
66
67 for (size_t i = 0; i < buffers.size(); i++) {
68 Sink::SinkBuffer new_buffer{ 78 Sink::SinkBuffer new_buffer{
69 .frames = buffers[i].size / (channel_count * sizeof(s16)), 79 .frames = buffer.size / (channel_count * sizeof(s16)),
70 .frames_played = 0, 80 .frames_played = 0,
71 .tag = buffers[i].tag, 81 .tag = buffer.tag,
72 .consumed = false, 82 .consumed = false,
73 }; 83 };
74 84
@@ -76,26 +86,22 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const {
76 std::vector<s16> samples{}; 86 std::vector<s16> samples{};
77 stream->AppendBuffer(new_buffer, samples); 87 stream->AppendBuffer(new_buffer, samples);
78 } else { 88 } else {
79 std::vector<s16> samples(buffers[i].size / sizeof(s16)); 89 std::vector<s16> samples(buffer.size / sizeof(s16));
80 memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); 90 system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size);
81 stream->AppendBuffer(new_buffer, samples); 91 stream->AppendBuffer(new_buffer, samples);
82 } 92 }
83 } 93 }
84} 94}
85 95
86void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { 96void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const {
87 if (type == Sink::StreamType::In) { 97 if (type == Sink::StreamType::In) {
88 auto& memory{system.Memory()};
89 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; 98 auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))};
90 memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); 99 system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size);
91 } 100 }
92} 101}
93 102
94bool DeviceSession::IsBufferConsumed(u64 tag) const { 103bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const {
95 if (stream) { 104 return played_sample_count >= buffer.end_timestamp;
96 return stream->IsBufferConsumed(tag);
97 }
98 return true;
99} 105}
100 106
101void DeviceSession::SetVolume(f32 volume) const { 107void DeviceSession::SetVolume(f32 volume) const {
@@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const {
105} 111}
106 112
107u64 DeviceSession::GetPlayedSampleCount() const { 113u64 DeviceSession::GetPlayedSampleCount() const {
108 if (stream) { 114 return played_sample_count;
109 return stream->GetPlayedSampleCount(); 115}
116
117std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() {
118 // Add 5ms of samples at a 48K sample rate.
119 played_sample_count += 48'000 * INCREMENT_TIME / 1s;
120 if (type == Sink::StreamType::Out) {
121 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true);
122 } else {
123 system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true);
110 } 124 }
111 return 0; 125 return std::nullopt;
126}
127
128void DeviceSession::SetRingSize(u32 ring_size) {
129 stream->SetRingSize(ring_size);
112} 130}
113 131
114} // namespace AudioCore 132} // namespace AudioCore
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
index 4a031b765..74f4dc085 100644
--- a/src/audio_core/device/device_session.h
+++ b/src/audio_core/device/device_session.h
@@ -3,6 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <chrono>
7#include <memory>
8#include <optional>
6#include <span> 9#include <span>
7 10
8#include "audio_core/common/common.h" 11#include "audio_core/common/common.h"
@@ -11,9 +14,13 @@
11 14
12namespace Core { 15namespace Core {
13class System; 16class System;
14} 17namespace Timing {
18struct EventType;
19} // namespace Timing
20} // namespace Core
15 21
16namespace AudioCore { 22namespace AudioCore {
23
17namespace Sink { 24namespace Sink {
18class SinkStream; 25class SinkStream;
19struct SinkBuffer; 26struct SinkBuffer;
@@ -55,22 +62,23 @@ public:
55 * 62 *
56 * @param buffers - The buffers to play. 63 * @param buffers - The buffers to play.
57 */ 64 */
58 void AppendBuffers(std::span<AudioBuffer> buffers) const; 65 void AppendBuffers(std::span<const AudioBuffer> buffers) const;
59 66
60 /** 67 /**
61 * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. 68 * (Audio In only) Pop samples from the backend, and write them back to this buffer's address.
62 * 69 *
63 * @param buffer - The buffer to write to. 70 * @param buffer - The buffer to write to.
64 */ 71 */
65 void ReleaseBuffer(AudioBuffer& buffer) const; 72 void ReleaseBuffer(const AudioBuffer& buffer) const;
66 73
67 /** 74 /**
68 * Check if the buffer for the given tag has been consumed by the backend. 75 * Check if the buffer for the given tag has been consumed by the backend.
69 * 76 *
70 * @param tag - Unqiue tag of the buffer to check. 77 * @param buffer - the buffer to check.
78 *
71 * @return true if the buffer has been consumed, otherwise false. 79 * @return true if the buffer has been consumed, otherwise false.
72 */ 80 */
73 bool IsBufferConsumed(u64 tag) const; 81 bool IsBufferConsumed(const AudioBuffer& buffer) const;
74 82
75 /** 83 /**
76 * Start this device session, starting the backend stream. 84 * Start this device session, starting the backend stream.
@@ -96,6 +104,16 @@ public:
96 */ 104 */
97 u64 GetPlayedSampleCount() const; 105 u64 GetPlayedSampleCount() const;
98 106
107 /*
108 * CoreTiming callback to increment played_sample_count over time.
109 */
110 std::optional<std::chrono::nanoseconds> ThreadFunc();
111
112 /*
113 * Set the size of the ring buffer.
114 */
115 void SetRingSize(u32 ring_size);
116
99private: 117private:
100 /// System 118 /// System
101 Core::System& system; 119 Core::System& system;
@@ -118,9 +136,13 @@ private:
118 /// Applet resource user id of this device session 136 /// Applet resource user id of this device session
119 u64 applet_resource_user_id{}; 137 u64 applet_resource_user_id{};
120 /// Total number of samples played by this device session 138 /// Total number of samples played by this device session
121 u64 played_sample_count{}; 139 std::atomic<u64> played_sample_count{};
140 /// Event increasing the played sample count every 5ms
141 std::shared_ptr<Core::Timing::EventType> thread_event;
122 /// Is this session initialised? 142 /// Is this session initialised?
123 bool initialized{}; 143 bool initialized{};
144 /// Buffer queue
145 std::vector<AudioBuffer> buffer_queue{};
124}; 146};
125 147
126} // namespace AudioCore 148} // namespace AudioCore
diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp
index c946895d6..91ccd5ad7 100644
--- a/src/audio_core/in/audio_in.cpp
+++ b/src/audio_core/in/audio_in.cpp
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
72 return event->GetReadableEvent(); 72 return event->GetReadableEvent();
73} 73}
74 74
75f32 In::GetVolume() { 75f32 In::GetVolume() const {
76 std::scoped_lock l{parent_mutex}; 76 std::scoped_lock l{parent_mutex};
77 return system.GetVolume(); 77 return system.GetVolume();
78} 78}
@@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) {
82 system.SetVolume(volume); 82 system.SetVolume(volume);
83} 83}
84 84
85bool In::ContainsAudioBuffer(u64 tag) { 85bool In::ContainsAudioBuffer(u64 tag) const {
86 std::scoped_lock l{parent_mutex}; 86 std::scoped_lock l{parent_mutex};
87 return system.ContainsAudioBuffer(tag); 87 return system.ContainsAudioBuffer(tag);
88} 88}
89 89
90u32 In::GetBufferCount() { 90u32 In::GetBufferCount() const {
91 std::scoped_lock l{parent_mutex}; 91 std::scoped_lock l{parent_mutex};
92 return system.GetBufferCount(); 92 return system.GetBufferCount();
93} 93}
94 94
95u64 In::GetPlayedSampleCount() { 95u64 In::GetPlayedSampleCount() const {
96 std::scoped_lock l{parent_mutex}; 96 std::scoped_lock l{parent_mutex};
97 return system.GetPlayedSampleCount(); 97 return system.GetPlayedSampleCount();
98} 98}
diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h
index 6253891d5..092ab7236 100644
--- a/src/audio_core/in/audio_in.h
+++ b/src/audio_core/in/audio_in.h
@@ -102,7 +102,7 @@ public:
102 * 102 *
103 * @return The current volume. 103 * @return The current volume.
104 */ 104 */
105 f32 GetVolume(); 105 f32 GetVolume() const;
106 106
107 /** 107 /**
108 * Set the system volume. 108 * Set the system volume.
@@ -117,21 +117,21 @@ public:
117 * @param tag - The tag to search for. 117 * @param tag - The tag to search for.
118 * @return True if the buffer is in the system, otherwise false. 118 * @return True if the buffer is in the system, otherwise false.
119 */ 119 */
120 bool ContainsAudioBuffer(u64 tag); 120 bool ContainsAudioBuffer(u64 tag) const;
121 121
122 /** 122 /**
123 * Get the maximum number of buffers. 123 * Get the maximum number of buffers.
124 * 124 *
125 * @return The maximum number of buffers. 125 * @return The maximum number of buffers.
126 */ 126 */
127 u32 GetBufferCount(); 127 u32 GetBufferCount() const;
128 128
129 /** 129 /**
130 * Get the total played sample count for this audio in. 130 * Get the total played sample count for this audio in.
131 * 131 *
132 * @return The played sample count. 132 * @return The played sample count.
133 */ 133 */
134 u64 GetPlayedSampleCount(); 134 u64 GetPlayedSampleCount() const;
135 135
136private: 136private:
137 /// The AudioIn::Manager this audio in is registered with 137 /// The AudioIn::Manager this audio in is registered with
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index ec5d37ed4..e7f918a47 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -34,16 +34,16 @@ size_t System::GetSessionId() const {
34 return session_id; 34 return session_id;
35} 35}
36 36
37std::string_view System::GetDefaultDeviceName() { 37std::string_view System::GetDefaultDeviceName() const {
38 return "BuiltInHeadset"; 38 return "BuiltInHeadset";
39} 39}
40 40
41std::string_view System::GetDefaultUacDeviceName() { 41std::string_view System::GetDefaultUacDeviceName() const {
42 return "Uac"; 42 return "Uac";
43} 43}
44 44
45Result System::IsConfigValid(const std::string_view device_name, 45Result System::IsConfigValid(const std::string_view device_name,
46 const AudioInParameter& in_params) { 46 const AudioInParameter& in_params) const {
47 if ((device_name.size() > 0) && 47 if ((device_name.size() > 0) &&
48 (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { 48 (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) {
49 return Service::Audio::ERR_INVALID_DEVICE_NAME; 49 return Service::Audio::ERR_INVALID_DEVICE_NAME;
@@ -93,6 +93,7 @@ Result System::Start() {
93 std::vector<AudioBuffer> buffers_to_flush{}; 93 std::vector<AudioBuffer> buffers_to_flush{};
94 buffers.RegisterBuffers(buffers_to_flush); 94 buffers.RegisterBuffers(buffers_to_flush);
95 session->AppendBuffers(buffers_to_flush); 95 session->AppendBuffers(buffers_to_flush);
96 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
96 97
97 return ResultSuccess; 98 return ResultSuccess;
98} 99}
@@ -112,8 +113,15 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) {
112 return false; 113 return false;
113 } 114 }
114 115
115 AudioBuffer new_buffer{ 116 const auto timestamp{buffers.GetNextTimestamp()};
116 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 117 const AudioBuffer new_buffer{
118 .start_timestamp = timestamp,
119 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
120 .played_timestamp = 0,
121 .samples = buffer.samples,
122 .tag = tag,
123 .size = buffer.size,
124 };
117 125
118 buffers.AppendBuffer(new_buffer); 126 buffers.AppendBuffer(new_buffer);
119 RegisterBuffers(); 127 RegisterBuffers();
@@ -194,11 +202,11 @@ void System::SetVolume(const f32 volume_) {
194 session->SetVolume(volume_); 202 session->SetVolume(volume_);
195} 203}
196 204
197bool System::ContainsAudioBuffer(const u64 tag) { 205bool System::ContainsAudioBuffer(const u64 tag) const {
198 return buffers.ContainsBuffer(tag); 206 return buffers.ContainsBuffer(tag);
199} 207}
200 208
201u32 System::GetBufferCount() { 209u32 System::GetBufferCount() const {
202 return buffers.GetAppendedRegisteredCount(); 210 return buffers.GetAppendedRegisteredCount();
203} 211}
204 212
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h
index 165e35d83..b9dc0e60f 100644
--- a/src/audio_core/in/audio_in_system.h
+++ b/src/audio_core/in/audio_in_system.h
@@ -68,7 +68,7 @@ public:
68 * 68 *
69 * @return The default audio input device name. 69 * @return The default audio input device name.
70 */ 70 */
71 std::string_view GetDefaultDeviceName(); 71 std::string_view GetDefaultDeviceName() const;
72 72
73 /** 73 /**
74 * Get the default USB audio input device name. 74 * Get the default USB audio input device name.
@@ -77,7 +77,7 @@ public:
77 * 77 *
78 * @return The default USB audio input device name. 78 * @return The default USB audio input device name.
79 */ 79 */
80 std::string_view GetDefaultUacDeviceName(); 80 std::string_view GetDefaultUacDeviceName() const;
81 81
82 /** 82 /**
83 * Is the given initialize config valid? 83 * Is the given initialize config valid?
@@ -86,7 +86,7 @@ public:
86 * @param in_params - Input parameters, see AudioInParameter. 86 * @param in_params - Input parameters, see AudioInParameter.
87 * @return Result code. 87 * @return Result code.
88 */ 88 */
89 Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); 89 Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const;
90 90
91 /** 91 /**
92 * Initialize this system. 92 * Initialize this system.
@@ -208,7 +208,7 @@ public:
208 /** 208 /**
209 * Set this system's current volume. 209 * Set this system's current volume.
210 * 210 *
211 * @param The new volume. 211 * @param volume The new volume.
212 */ 212 */
213 void SetVolume(f32 volume); 213 void SetVolume(f32 volume);
214 214
@@ -218,14 +218,14 @@ public:
218 * @param tag - Unique tag to search for. 218 * @param tag - Unique tag to search for.
219 * @return True if the buffer is in the system, otherwise false. 219 * @return True if the buffer is in the system, otherwise false.
220 */ 220 */
221 bool ContainsAudioBuffer(u64 tag); 221 bool ContainsAudioBuffer(u64 tag) const;
222 222
223 /** 223 /**
224 * Get the maximum number of usable buffers (default 32). 224 * Get the maximum number of usable buffers (default 32).
225 * 225 *
226 * @return The number of buffers. 226 * @return The number of buffers.
227 */ 227 */
228 u32 GetBufferCount(); 228 u32 GetBufferCount() const;
229 229
230 /** 230 /**
231 * Get the total number of samples played by this system. 231 * Get the total number of samples played by this system.
diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp
index 9a8d8a742..d3ee4f0eb 100644
--- a/src/audio_core/out/audio_out.cpp
+++ b/src/audio_core/out/audio_out.cpp
@@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
72 return event->GetReadableEvent(); 72 return event->GetReadableEvent();
73} 73}
74 74
75f32 Out::GetVolume() { 75f32 Out::GetVolume() const {
76 std::scoped_lock l{parent_mutex}; 76 std::scoped_lock l{parent_mutex};
77 return system.GetVolume(); 77 return system.GetVolume();
78} 78}
@@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) {
82 system.SetVolume(volume); 82 system.SetVolume(volume);
83} 83}
84 84
85bool Out::ContainsAudioBuffer(const u64 tag) { 85bool Out::ContainsAudioBuffer(const u64 tag) const {
86 std::scoped_lock l{parent_mutex}; 86 std::scoped_lock l{parent_mutex};
87 return system.ContainsAudioBuffer(tag); 87 return system.ContainsAudioBuffer(tag);
88} 88}
89 89
90u32 Out::GetBufferCount() { 90u32 Out::GetBufferCount() const {
91 std::scoped_lock l{parent_mutex}; 91 std::scoped_lock l{parent_mutex};
92 return system.GetBufferCount(); 92 return system.GetBufferCount();
93} 93}
94 94
95u64 Out::GetPlayedSampleCount() { 95u64 Out::GetPlayedSampleCount() const {
96 std::scoped_lock l{parent_mutex}; 96 std::scoped_lock l{parent_mutex};
97 return system.GetPlayedSampleCount(); 97 return system.GetPlayedSampleCount();
98} 98}
diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h
index f6b921645..946f345c6 100644
--- a/src/audio_core/out/audio_out.h
+++ b/src/audio_core/out/audio_out.h
@@ -102,7 +102,7 @@ public:
102 * 102 *
103 * @return The current volume. 103 * @return The current volume.
104 */ 104 */
105 f32 GetVolume(); 105 f32 GetVolume() const;
106 106
107 /** 107 /**
108 * Set the system volume. 108 * Set the system volume.
@@ -117,21 +117,21 @@ public:
117 * @param tag - The tag to search for. 117 * @param tag - The tag to search for.
118 * @return True if the buffer is in the system, otherwise false. 118 * @return True if the buffer is in the system, otherwise false.
119 */ 119 */
120 bool ContainsAudioBuffer(u64 tag); 120 bool ContainsAudioBuffer(u64 tag) const;
121 121
122 /** 122 /**
123 * Get the maximum number of buffers. 123 * Get the maximum number of buffers.
124 * 124 *
125 * @return The maximum number of buffers. 125 * @return The maximum number of buffers.
126 */ 126 */
127 u32 GetBufferCount(); 127 u32 GetBufferCount() const;
128 128
129 /** 129 /**
130 * Get the total played sample count for this audio out. 130 * Get the total played sample count for this audio out.
131 * 131 *
132 * @return The played sample count. 132 * @return The played sample count.
133 */ 133 */
134 u64 GetPlayedSampleCount(); 134 u64 GetPlayedSampleCount() const;
135 135
136private: 136private:
137 /// The AudioOut::Manager this audio out is registered with 137 /// The AudioOut::Manager this audio out is registered with
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index 35afddf06..8b907590a 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -27,11 +27,12 @@ void System::Finalize() {
27 buffer_event->GetWritableEvent().Signal(); 27 buffer_event->GetWritableEvent().Signal();
28} 28}
29 29
30std::string_view System::GetDefaultOutputDeviceName() { 30std::string_view System::GetDefaultOutputDeviceName() const {
31 return "DeviceOut"; 31 return "DeviceOut";
32} 32}
33 33
34Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { 34Result System::IsConfigValid(std::string_view device_name,
35 const AudioOutParameter& in_params) const {
35 if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { 36 if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) {
36 return Service::Audio::ERR_INVALID_DEVICE_NAME; 37 return Service::Audio::ERR_INVALID_DEVICE_NAME;
37 } 38 }
@@ -92,6 +93,7 @@ Result System::Start() {
92 std::vector<AudioBuffer> buffers_to_flush{}; 93 std::vector<AudioBuffer> buffers_to_flush{};
93 buffers.RegisterBuffers(buffers_to_flush); 94 buffers.RegisterBuffers(buffers_to_flush);
94 session->AppendBuffers(buffers_to_flush); 95 session->AppendBuffers(buffers_to_flush);
96 session->SetRingSize(static_cast<u32>(buffers_to_flush.size()));
95 97
96 return ResultSuccess; 98 return ResultSuccess;
97} 99}
@@ -111,8 +113,15 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) {
111 return false; 113 return false;
112 } 114 }
113 115
114 AudioBuffer new_buffer{ 116 const auto timestamp{buffers.GetNextTimestamp()};
115 .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; 117 const AudioBuffer new_buffer{
118 .start_timestamp = timestamp,
119 .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)),
120 .played_timestamp = 0,
121 .samples = buffer.samples,
122 .tag = tag,
123 .size = buffer.size,
124 };
116 125
117 buffers.AppendBuffer(new_buffer); 126 buffers.AppendBuffer(new_buffer);
118 RegisterBuffers(); 127 RegisterBuffers();
@@ -192,11 +201,11 @@ void System::SetVolume(const f32 volume_) {
192 session->SetVolume(volume_); 201 session->SetVolume(volume_);
193} 202}
194 203
195bool System::ContainsAudioBuffer(const u64 tag) { 204bool System::ContainsAudioBuffer(const u64 tag) const {
196 return buffers.ContainsBuffer(tag); 205 return buffers.ContainsBuffer(tag);
197} 206}
198 207
199u32 System::GetBufferCount() { 208u32 System::GetBufferCount() const {
200 return buffers.GetAppendedRegisteredCount(); 209 return buffers.GetAppendedRegisteredCount();
201} 210}
202 211
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h
index 4ca2f3417..0817b2f37 100644
--- a/src/audio_core/out/audio_out_system.h
+++ b/src/audio_core/out/audio_out_system.h
@@ -68,7 +68,7 @@ public:
68 * 68 *
69 * @return The default audio output device name. 69 * @return The default audio output device name.
70 */ 70 */
71 std::string_view GetDefaultOutputDeviceName(); 71 std::string_view GetDefaultOutputDeviceName() const;
72 72
73 /** 73 /**
74 * Is the given initialize config valid? 74 * Is the given initialize config valid?
@@ -77,7 +77,7 @@ public:
77 * @param in_params - Input parameters, see AudioOutParameter. 77 * @param in_params - Input parameters, see AudioOutParameter.
78 * @return Result code. 78 * @return Result code.
79 */ 79 */
80 Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); 80 Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const;
81 81
82 /** 82 /**
83 * Initialize this system. 83 * Initialize this system.
@@ -199,7 +199,7 @@ public:
199 /** 199 /**
200 * Set this system's current volume. 200 * Set this system's current volume.
201 * 201 *
202 * @param The new volume. 202 * @param volume The new volume.
203 */ 203 */
204 void SetVolume(f32 volume); 204 void SetVolume(f32 volume);
205 205
@@ -209,14 +209,14 @@ public:
209 * @param tag - Unique tag to search for. 209 * @param tag - Unique tag to search for.
210 * @return True if the buffer is in the system, otherwise false. 210 * @return True if the buffer is in the system, otherwise false.
211 */ 211 */
212 bool ContainsAudioBuffer(u64 tag); 212 bool ContainsAudioBuffer(u64 tag) const;
213 213
214 /** 214 /**
215 * Get the maximum number of usable buffers (default 32). 215 * Get the maximum number of usable buffers (default 32).
216 * 216 *
217 * @return The number of buffers. 217 * @return The number of buffers.
218 */ 218 */
219 u32 GetBufferCount(); 219 u32 GetBufferCount() const;
220 220
221 /** 221 /**
222 * Get the total number of samples played by this system. 222 * Get the total number of samples played by this system.
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp
index e05a22d86..a28395663 100644
--- a/src/audio_core/renderer/adsp/adsp.cpp
+++ b/src/audio_core/renderer/adsp/adsp.cpp
@@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const {
50 return render_mailbox.GetRemainCommandCount(session_id); 50 return render_mailbox.GetRemainCommandCount(session_id);
51} 51}
52 52
53void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { 53void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) {
54 render_mailbox.SetCommandBuffer(session_id, command_buffer); 54 render_mailbox.SetCommandBuffer(session_id, command_buffer);
55} 55}
56 56
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h
index 4dfcef4a5..f7a2f25e4 100644
--- a/src/audio_core/renderer/adsp/adsp.h
+++ b/src/audio_core/renderer/adsp/adsp.h
@@ -63,8 +63,6 @@ public:
63 63
64 /** 64 /**
65 * Stop the ADSP. 65 * Stop the ADSP.
66 *
67 * @return True if started or already running, otherwise false.
68 */ 66 */
69 void Stop(); 67 void Stop();
70 68
@@ -133,7 +131,7 @@ public:
133 * @param session_id - The session id to check (0 or 1). 131 * @param session_id - The session id to check (0 or 1).
134 * @param command_buffer - The command buffer to process. 132 * @param command_buffer - The command buffer to process.
135 */ 133 */
136 void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); 134 void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer);
137 135
138 /** 136 /**
139 * Clear the command buffers (does not clear the time taken or the remaining command count) 137 * Clear the command buffers (does not clear the time taken or the remaining command count)
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp
index 3967ccfe6..bafe4822a 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.cpp
+++ b/src/audio_core/renderer/adsp/audio_renderer.cpp
@@ -51,7 +51,7 @@ CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) {
51 return command_buffers[session_id]; 51 return command_buffers[session_id];
52} 52}
53 53
54void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { 54void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) {
55 command_buffers[session_id] = buffer; 55 command_buffers[session_id] = buffer;
56} 56}
57 57
@@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) {
106 106
107 mailbox = mailbox_; 107 mailbox = mailbox_;
108 thread = std::thread(&AudioRenderer::ThreadFunc, this); 108 thread = std::thread(&AudioRenderer::ThreadFunc, this);
109 for (auto& stream : streams) {
110 stream->Start();
111 }
112 running = true; 109 running = true;
113} 110}
114 111
@@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() {
130 std::string name{fmt::format("ADSP_RenderStream-{}", i)}; 127 std::string name{fmt::format("ADSP_RenderStream-{}", i)};
131 streams[i] = 128 streams[i] =
132 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); 129 sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render);
130 streams[i]->SetRingSize(4);
133 } 131 }
134} 132}
135 133
@@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() {
198 command_list_processor.Process(index) - start_time; 196 command_list_processor.Process(index) - start_time;
199 } 197 }
200 198
201 if (index == 0) {
202 auto stream{command_list_processor.GetOutputSinkStream()};
203 system.AudioCore().SetStreamQueue(stream->GetQueueSize());
204 }
205
206 const auto end_time{system.CoreTiming().GetClockTicks()}; 199 const auto end_time{system.CoreTiming().GetClockTicks()};
207 200
208 command_buffer.remaining_command_count = 201 command_buffer.remaining_command_count =
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h
index b6ced9d2b..02e923c84 100644
--- a/src/audio_core/renderer/adsp/audio_renderer.h
+++ b/src/audio_core/renderer/adsp/audio_renderer.h
@@ -52,7 +52,7 @@ public:
52 /** 52 /**
53 * Send a message from the host to the AudioRenderer. 53 * Send a message from the host to the AudioRenderer.
54 * 54 *
55 * @param message_ - The message to send to the AudioRenderer. 55 * @param message - The message to send to the AudioRenderer.
56 */ 56 */
57 void HostSendMessage(RenderMessage message); 57 void HostSendMessage(RenderMessage message);
58 58
@@ -66,7 +66,7 @@ public:
66 /** 66 /**
67 * Send a message from the AudioRenderer to the host. 67 * Send a message from the AudioRenderer to the host.
68 * 68 *
69 * @param message_ - The message to send to the host. 69 * @param message - The message to send to the host.
70 */ 70 */
71 void ADSPSendMessage(RenderMessage message); 71 void ADSPSendMessage(RenderMessage message);
72 72
@@ -91,7 +91,7 @@ public:
91 * @param session_id - The session id to get (0 or 1). 91 * @param session_id - The session id to get (0 or 1).
92 * @param buffer - The command buffer to set. 92 * @param buffer - The command buffer to set.
93 */ 93 */
94 void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); 94 void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer);
95 95
96 /** 96 /**
97 * Get the total render time taken for the last command lists sent. 97 * Get the total render time taken for the last command lists sent.
@@ -163,7 +163,7 @@ public:
163 /** 163 /**
164 * Start the AudioRenderer. 164 * Start the AudioRenderer.
165 * 165 *
166 * @param The mailbox to use for this session. 166 * @param mailbox The mailbox to use for this session.
167 */ 167 */
168 void Start(AudioRenderer_Mailbox* mailbox); 168 void Start(AudioRenderer_Mailbox* mailbox);
169 169
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h
index 3f99173e3..d78269e1d 100644
--- a/src/audio_core/renderer/adsp/command_list_processor.h
+++ b/src/audio_core/renderer/adsp/command_list_processor.h
@@ -33,10 +33,10 @@ public:
33 /** 33 /**
34 * Initialize the processor. 34 * Initialize the processor.
35 * 35 *
36 * @param system_ - The core system. 36 * @param system - The core system.
37 * @param buffer - The command buffer to process. 37 * @param buffer - The command buffer to process.
38 * @param size - The size of the buffer. 38 * @param size - The size of the buffer.
39 * @param stream_ - The stream to be used for sending the samples. 39 * @param stream - The stream to be used for sending the samples.
40 */ 40 */
41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); 41 void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream);
42 42
@@ -72,7 +72,8 @@ public:
72 /** 72 /**
73 * Process the command list. 73 * Process the command list.
74 * 74 *
75 * @param index - Index of the current command list. 75 * @param session_id - Session ID for the commands being processed.
76 *
76 * @return The time taken to process. 77 * @return The time taken to process.
77 */ 78 */
78 u64 Process(u32 session_id); 79 u64 Process(u32 session_id);
@@ -89,7 +90,7 @@ public:
89 u8* commands{}; 90 u8* commands{};
90 /// The command buffer size 91 /// The command buffer size
91 u64 commands_buffer_size{}; 92 u64 commands_buffer_size{};
92 /// The maximum processing time alloted 93 /// The maximum processing time allotted
93 u64 max_process_time{}; 94 u64 max_process_time{};
94 /// The number of commands in the buffer 95 /// The number of commands in the buffer
95 u32 command_count{}; 96 u32 command_count{};
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp
index d5886e55e..0d9d8f6ce 100644
--- a/src/audio_core/renderer/audio_device.cpp
+++ b/src/audio_core/renderer/audio_device.cpp
@@ -1,6 +1,9 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <array>
5#include <span>
6
4#include "audio_core/audio_core.h" 7#include "audio_core/audio_core.h"
5#include "audio_core/common/feature_support.h" 8#include "audio_core/common/feature_support.h"
6#include "audio_core/renderer/audio_device.h" 9#include "audio_core/renderer/audio_device.h"
@@ -9,14 +12,33 @@
9 12
10namespace AudioCore::AudioRenderer { 13namespace AudioCore::AudioRenderer {
11 14
15constexpr std::array usb_device_names{
16 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
17 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
18 AudioDevice::AudioDeviceName{"AudioTvOutput"},
19 AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"},
20};
21
22constexpr std::array device_names{
23 AudioDevice::AudioDeviceName{"AudioStereoJackOutput"},
24 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
25 AudioDevice::AudioDeviceName{"AudioTvOutput"},
26};
27
28constexpr std::array output_device_names{
29 AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"},
30 AudioDevice::AudioDeviceName{"AudioTvOutput"},
31 AudioDevice::AudioDeviceName{"AudioExternalOutput"},
32};
33
12AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, 34AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_,
13 const u32 revision) 35 const u32 revision)
14 : output_sink{system.AudioCore().GetOutputSink()}, 36 : output_sink{system.AudioCore().GetOutputSink()},
15 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} 37 applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {}
16 38
17u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, 39u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
18 const size_t max_count) { 40 const size_t max_count) const {
19 std::span<AudioDeviceName> names{}; 41 std::span<const AudioDeviceName> names{};
20 42
21 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { 43 if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) {
22 names = usb_device_names; 44 names = usb_device_names;
@@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
24 names = device_names; 46 names = device_names;
25 } 47 }
26 48
27 u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; 49 const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))};
28 for (u32 i = 0; i < out_count; i++) { 50 for (u32 i = 0; i < out_count; i++) {
29 out_buffer.push_back(names[i]); 51 out_buffer.push_back(names[i]);
30 } 52 }
@@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer,
32} 54}
33 55
34u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, 56u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer,
35 const size_t max_count) { 57 const size_t max_count) const {
36 u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; 58 const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))};
37 59
38 for (u32 i = 0; i < out_count; i++) { 60 for (u32 i = 0; i < out_count; i++) {
39 out_buffer.push_back(output_device_names[i]); 61 out_buffer.push_back(output_device_names[i]);
@@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) {
45 output_sink.SetDeviceVolume(volume); 67 output_sink.SetDeviceVolume(volume);
46} 68}
47 69
48f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { 70f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const {
49 return output_sink.GetDeviceVolume(); 71 return output_sink.GetDeviceVolume();
50} 72}
51 73
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h
index 1f449f261..dd6be70ee 100644
--- a/src/audio_core/renderer/audio_device.h
+++ b/src/audio_core/renderer/audio_device.h
@@ -3,7 +3,7 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <span> 6#include <string_view>
7 7
8#include "audio_core/audio_render_manager.h" 8#include "audio_core/audio_render_manager.h"
9 9
@@ -23,21 +23,13 @@ namespace AudioRenderer {
23class AudioDevice { 23class AudioDevice {
24public: 24public:
25 struct AudioDeviceName { 25 struct AudioDeviceName {
26 std::array<char, 0x100> name; 26 std::array<char, 0x100> name{};
27 27
28 AudioDeviceName(const char* name_) { 28 constexpr AudioDeviceName(std::string_view name_) {
29 std::strncpy(name.data(), name_, name.size()); 29 name_.copy(name.data(), name.size() - 1);
30 } 30 }
31 }; 31 };
32 32
33 std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput",
34 "AudioBuiltInSpeakerOutput", "AudioTvOutput",
35 "AudioUsbDeviceOutput"};
36 std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput",
37 "AudioBuiltInSpeakerOutput", "AudioTvOutput"};
38 std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput",
39 "AudioExternalOutput"};
40
41 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); 33 explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision);
42 34
43 /** 35 /**
@@ -47,7 +39,7 @@ public:
47 * @param max_count - Maximum number of devices to write (count of out_buffer). 39 * @param max_count - Maximum number of devices to write (count of out_buffer).
48 * @return Number of device names written. 40 * @return Number of device names written.
49 */ 41 */
50 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); 42 u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
51 43
52 /** 44 /**
53 * Get a list of the available output devices. 45 * Get a list of the available output devices.
@@ -57,7 +49,7 @@ public:
57 * @param max_count - Maximum number of devices to write (count of out_buffer). 49 * @param max_count - Maximum number of devices to write (count of out_buffer).
58 * @return Number of device names written. 50 * @return Number of device names written.
59 */ 51 */
60 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); 52 u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const;
61 53
62 /** 54 /**
63 * Set the volume of all streams in the backend sink. 55 * Set the volume of all streams in the backend sink.
@@ -73,7 +65,7 @@ public:
73 * @param name - Name of the device to check. Unused. 65 * @param name - Name of the device to check. Unused.
74 * @return Volume of the device. 66 * @return Volume of the device.
75 */ 67 */
76 f32 GetDeviceVolume(std::string_view name); 68 f32 GetDeviceVolume(std::string_view name) const;
77 69
78private: 70private:
79 /// Backend output sink for the device 71 /// Backend output sink for the device
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp
index c5d4d66d8..3d2a91312 100644
--- a/src/audio_core/renderer/behavior/behavior_info.cpp
+++ b/src/audio_core/renderer/behavior/behavior_info.cpp
@@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() {
34 error_count = 0; 34 error_count = 0;
35} 35}
36 36
37void BehaviorInfo::AppendError(ErrorInfo& error) { 37void BehaviorInfo::AppendError(const ErrorInfo& error) {
38 LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", 38 LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}",
39 error.error_code.raw, error.address); 39 error.error_code.raw, error.address);
40 if (error_count < MaxErrors) { 40 if (error_count < MaxErrors) {
@@ -42,14 +42,16 @@ void BehaviorInfo::AppendError(ErrorInfo& error) {
42 } 42 }
43} 43}
44 44
45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { 45void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const {
46 auto error_count_{std::min(error_count, MaxErrors)}; 46 out_count = std::min(error_count, MaxErrors);
47 std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo));
48 47
49 for (size_t i = 0; i < error_count_; i++) { 48 for (size_t i = 0; i < MaxErrors; i++) {
50 out_errors[i] = errors[i]; 49 if (i < out_count) {
50 out_errors[i] = errors[i];
51 } else {
52 out_errors[i] = {};
53 }
51 } 54 }
52 out_count = error_count_;
53} 55}
54 56
55void BehaviorInfo::UpdateFlags(const Flags flags_) { 57void BehaviorInfo::UpdateFlags(const Flags flags_) {
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h
index 7333c297f..15c948344 100644
--- a/src/audio_core/renderer/behavior/behavior_info.h
+++ b/src/audio_core/renderer/behavior/behavior_info.h
@@ -94,7 +94,7 @@ public:
94 * 94 *
95 * @param error - The new error. 95 * @param error - The new error.
96 */ 96 */
97 void AppendError(ErrorInfo& error); 97 void AppendError(const ErrorInfo& error);
98 98
99 /** 99 /**
100 * Copy errors to the given output container. 100 * Copy errors to the given output container.
@@ -102,7 +102,7 @@ public:
102 * @param out_errors - Output container to receive the errors. 102 * @param out_errors - Output container to receive the errors.
103 * @param out_count - The number of errors written. 103 * @param out_count - The number of errors written.
104 */ 104 */
105 void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); 105 void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const;
106 106
107 /** 107 /**
108 * Update the behaviour flags. 108 * Update the behaviour flags.
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp
index 06a37e1a6..c0a307b89 100644
--- a/src/audio_core/renderer/behavior/info_updater.cpp
+++ b/src/audio_core/renderer/behavior/info_updater.cpp
@@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) {
485 return ResultSuccess; 485 return ResultSuccess;
486} 486}
487 487
488Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { 488Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) {
489 auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; 489 auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)};
490 behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); 490 behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count);
491 491
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h
index f0b445d9c..c817d8d8d 100644
--- a/src/audio_core/renderer/behavior/info_updater.h
+++ b/src/audio_core/renderer/behavior/info_updater.h
@@ -130,7 +130,7 @@ public:
130 * @param behaviour - Behaviour to update. 130 * @param behaviour - Behaviour to update.
131 * @return Result code. 131 * @return Result code.
132 */ 132 */
133 Result UpdateErrorInfo(BehaviorInfo& behaviour); 133 Result UpdateErrorInfo(const BehaviorInfo& behaviour);
134 134
135 /** 135 /**
136 * Update splitter. 136 * Update splitter.
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h
index 496b0e50a..162170846 100644
--- a/src/audio_core/renderer/command/command_buffer.h
+++ b/src/audio_core/renderer/command/command_buffer.h
@@ -191,6 +191,7 @@ public:
191 * @param volume - Current mix volume used for calculating the ramp. 191 * @param volume - Current mix volume used for calculating the ramp.
192 * @param prev_volume - Previous mix volume, used for calculating the ramp, 192 * @param prev_volume - Previous mix volume, used for calculating the ramp,
193 * also applied to the input. 193 * also applied to the input.
194 * @param prev_samples - Previous sample buffer. Used for depopping.
194 * @param precision - Number of decimal bits for fixed point operations. 195 * @param precision - Number of decimal bits for fixed point operations.
195 */ 196 */
196 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, 197 void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
@@ -208,6 +209,7 @@ public:
208 * @param volumes - Current mix volumes used for calculating the ramp. 209 * @param volumes - Current mix volumes used for calculating the ramp.
209 * @param prev_volumes - Previous mix volumes, used for calculating the ramp, 210 * @param prev_volumes - Previous mix volumes, used for calculating the ramp,
210 * also applied to the input. 211 * also applied to the input.
212 * @param prev_samples - Previous sample buffer. Used for depopping.
211 * @param precision - Number of decimal bits for fixed point operations. 213 * @param precision - Number of decimal bits for fixed point operations.
212 */ 214 */
213 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, 215 void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
@@ -297,11 +299,11 @@ public:
297 /** 299 /**
298 * Generate a device sink command, adding it to the command list. 300 * Generate a device sink command, adding it to the command list.
299 * 301 *
300 * @param node_id - Node id of the voice this command is generated for. 302 * @param node_id - Node id of the voice this command is generated for.
301 * @param buffer_offset - Base mix buffer offset to use. 303 * @param buffer_offset - Base mix buffer offset to use.
302 * @param sink_info - The sink_info to generate this command from. 304 * @param sink_info - The sink_info to generate this command from.
303 * @session_id - System session id this command is generated from. 305 * @param session_id - System session id this command is generated from.
304 * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. 306 * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used.
305 */ 307 */
306 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, 308 void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
307 u32 session_id, std::span<s32> samples_buffer); 309 u32 session_id, std::span<s32> samples_buffer);
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h
index d80d9b0d8..b3cd7b408 100644
--- a/src/audio_core/renderer/command/command_generator.h
+++ b/src/audio_core/renderer/command/command_generator.h
@@ -197,9 +197,9 @@ public:
197 /** 197 /**
198 * Generate an I3DL2 reverb effect command. 198 * Generate an I3DL2 reverb effect command.
199 * 199 *
200 * @param buffer_offset - Base mix buffer offset to use. 200 * @param buffer_offset - Base mix buffer offset to use.
201 * @param effect_info_base - I3DL2Reverb effect info. 201 * @param effect_info - I3DL2Reverb effect info.
202 * @param node_id - Node id of the mix this command is generated for. 202 * @param node_id - Node id of the mix this command is generated for.
203 */ 203 */
204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 204 void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
205 s32 node_id); 205 s32 node_id);
@@ -207,18 +207,18 @@ public:
207 /** 207 /**
208 * Generate an aux effect command. 208 * Generate an aux effect command.
209 * 209 *
210 * @param buffer_offset - Base mix buffer offset to use. 210 * @param buffer_offset - Base mix buffer offset to use.
211 * @param effect_info_base - Aux effect info. 211 * @param effect_info - Aux effect info.
212 * @param node_id - Node id of the mix this command is generated for. 212 * @param node_id - Node id of the mix this command is generated for.
213 */ 213 */
214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); 214 void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
215 215
216 /** 216 /**
217 * Generate a biquad filter effect command. 217 * Generate a biquad filter effect command.
218 * 218 *
219 * @param buffer_offset - Base mix buffer offset to use. 219 * @param buffer_offset - Base mix buffer offset to use.
220 * @param effect_info_base - Aux effect info. 220 * @param effect_info - Aux effect info.
221 * @param node_id - Node id of the mix this command is generated for. 221 * @param node_id - Node id of the mix this command is generated for.
222 */ 222 */
223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 223 void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
224 s32 node_id); 224 s32 node_id);
@@ -226,10 +226,10 @@ public:
226 /** 226 /**
227 * Generate a light limiter effect command. 227 * Generate a light limiter effect command.
228 * 228 *
229 * @param buffer_offset - Base mix buffer offset to use. 229 * @param buffer_offset - Base mix buffer offset to use.
230 * @param effect_info_base - Limiter effect info. 230 * @param effect_info - Limiter effect info.
231 * @param node_id - Node id of the mix this command is generated for. 231 * @param node_id - Node id of the mix this command is generated for.
232 * @param effect_index - Index for the statistics state. 232 * @param effect_index - Index for the statistics state.
233 */ 233 */
234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, 234 void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
235 s32 node_id, u32 effect_index); 235 s32 node_id, u32 effect_index);
@@ -238,21 +238,20 @@ public:
238 * Generate a capture effect command. 238 * Generate a capture effect command.
239 * Writes a mix buffer back to game memory. 239 * Writes a mix buffer back to game memory.
240 * 240 *
241 * @param buffer_offset - Base mix buffer offset to use. 241 * @param buffer_offset - Base mix buffer offset to use.
242 * @param effect_info_base - Capture effect info. 242 * @param effect_info - Capture effect info.
243 * @param node_id - Node id of the mix this command is generated for. 243 * @param node_id - Node id of the mix this command is generated for.
244 */ 244 */
245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); 245 void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
246 246
247 /** 247 /**
248 * Generate a compressor effect command. 248 * Generate a compressor effect command.
249 * 249 *
250 * @param buffer_offset - Base mix buffer offset to use. 250 * @param buffer_offset - Base mix buffer offset to use.
251 * @param effect_info_base - Compressor effect info. 251 * @param effect_info - Compressor effect info.
252 * @param node_id - Node id of the mix this command is generated for. 252 * @param node_id - Node id of the mix this command is generated for.
253 */ 253 */
254 void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, 254 void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
255 const s32 node_id);
256 255
257 /** 256 /**
258 * Generate all effect commands for a mix. 257 * Generate all effect commands for a mix.
@@ -318,8 +317,9 @@ public:
318 * Generate a performance command. 317 * Generate a performance command.
319 * Used to report performance metrics of the AudioRenderer back to the game. 318 * Used to report performance metrics of the AudioRenderer back to the game.
320 * 319 *
321 * @param buffer_offset - Base mix buffer offset to use. 320 * @param node_id - Node ID of the mix this command is generated for
322 * @param sink_info - Sink info to generate the commands from. 321 * @param state - Output state of the generated performance command
322 * @param entry_addresses - Addresses to be written
323 */ 323 */
324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state, 324 void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
325 const PerformanceEntryAddresses& entry_addresses); 325 const PerformanceEntryAddresses& entry_addresses);
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp
index 2ebc140f1..7229618e8 100644
--- a/src/audio_core/renderer/command/effect/compressor.cpp
+++ b/src/audio_core/renderer/command/effect/compressor.cpp
@@ -11,7 +11,7 @@
11 11
12namespace AudioCore::AudioRenderer { 12namespace AudioCore::AudioRenderer {
13 13
14static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, 14static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params,
15 CompressorInfo::State& state) { 15 CompressorInfo::State& state) {
16 const auto ratio{1.0f / params.compressor_ratio}; 16 const auto ratio{1.0f / params.compressor_ratio};
17 auto makeup_gain{0.0f}; 17 auto makeup_gain{0.0f};
@@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para
31 state.unk_20 = c; 31 state.unk_20 = c;
32} 32}
33 33
34static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, 34static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params,
35 CompressorInfo::State& state) { 35 CompressorInfo::State& state) {
36 std::memset(&state, 0, sizeof(CompressorInfo::State)); 36 state = {};
37 37
38 state.unk_00 = 0; 38 state.unk_00 = 0;
39 state.unk_04 = 1.0f; 39 state.unk_04 = 1.0f;
@@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params
42 SetCompressorEffectParameter(params, state); 42 SetCompressorEffectParameter(params, state);
43} 43}
44 44
45static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, 45static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params,
46 CompressorInfo::State& state, bool enabled, 46 CompressorInfo::State& state, bool enabled,
47 std::vector<std::span<const s32>> input_buffers, 47 std::vector<std::span<const s32>> input_buffers,
48 std::vector<std::span<s32>> output_buffers, u32 sample_count) { 48 std::vector<std::span<s32>> output_buffers, u32 sample_count) {
@@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
103 } else { 103 } else {
104 for (s16 channel = 0; channel < params.channel_count; channel++) { 104 for (s16 channel = 0; channel < params.channel_count; channel++) {
105 if (params.inputs[channel] != params.outputs[channel]) { 105 if (params.inputs[channel] != params.outputs[channel]) {
106 std::memcpy((char*)output_buffers[channel].data(), 106 std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(),
107 (char*)input_buffers[channel].data(),
108 output_buffers[channel].size_bytes()); 107 output_buffers[channel].size_bytes());
109 } 108 }
110 } 109 }
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp
index ffdafa1c8..d67123cd8 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.cpp
+++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp
@@ -7,17 +7,7 @@
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8 8
9namespace AudioCore::AudioRenderer { 9namespace AudioCore::AudioRenderer {
10/** 10
11 * Mix input mix buffer into output mix buffer, with volume applied to the input.
12 *
13 * @tparam Q - Number of bits for fixed point operations.
14 * @param output - Output mix buffer.
15 * @param input - Input mix buffer.
16 * @param volume - Volume applied to the input.
17 * @param ramp - Ramp applied to volume every sample.
18 * @param sample_count - Number of samples to process.
19 * @return The final gained input sample, used for depopping.
20 */
21template <size_t Q> 11template <size_t Q>
22s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 12s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
23 const f32 ramp_, const u32 sample_count) { 13 const f32 ramp_, const u32 sample_count) {
@@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo
40 return sample.to_int(); 30 return sample.to_int();
41} 31}
42 32
43template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, 33template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32);
44 const u32); 34template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32);
45template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
46 const u32);
47 35
48void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 36void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
49 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; 37 const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h
index 770f57e80..52f74a273 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp.h
@@ -61,13 +61,13 @@ struct MixRampCommand : ICommand {
61 * @tparam Q - Number of bits for fixed point operations. 61 * @tparam Q - Number of bits for fixed point operations.
62 * @param output - Output mix buffer. 62 * @param output - Output mix buffer.
63 * @param input - Input mix buffer. 63 * @param input - Input mix buffer.
64 * @param volume - Volume applied to the input. 64 * @param volume_ - Volume applied to the input.
65 * @param ramp - Ramp applied to volume every sample. 65 * @param ramp_ - Ramp applied to volume every sample.
66 * @param sample_count - Number of samples to process. 66 * @param sample_count - Number of samples to process.
67 * @return The final gained input sample, used for depopping. 67 * @return The final gained input sample, used for depopping.
68 */ 68 */
69template <size_t Q> 69template <size_t Q>
70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 70s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_,
71 const f32 ramp_, const u32 sample_count); 71 u32 sample_count);
72 72
73} // namespace AudioCore::AudioRenderer 73} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
index 027276e5a..3b0ce67ef 100644
--- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
+++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h
@@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand {
50 std::array<s16, MaxMixBuffers> inputs; 50 std::array<s16, MaxMixBuffers> inputs;
51 /// Output mix buffer indexes for each mix buffer 51 /// Output mix buffer indexes for each mix buffer
52 std::array<s16, MaxMixBuffers> outputs; 52 std::array<s16, MaxMixBuffers> outputs;
53 /// Previous mix vloumes for each mix buffer 53 /// Previous mix volumes for each mix buffer
54 std::array<f32, MaxMixBuffers> prev_volumes; 54 std::array<f32, MaxMixBuffers> prev_volumes;
55 /// Current mix vloumes for each mix buffer 55 /// Current mix volumes for each mix buffer
56 std::array<f32, MaxMixBuffers> volumes; 56 std::array<f32, MaxMixBuffers> volumes;
57 /// Pointer to the previous sample buffer, used for depop 57 /// Pointer to the previous sample buffer, used for depop
58 CpuAddr previous_samples; 58 CpuAddr previous_samples;
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp
index 47e0c6722..e88372a75 100644
--- a/src/audio_core/renderer/command/sink/device.cpp
+++ b/src/audio_core/renderer/command/sink/device.cpp
@@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
46 46
47 out_buffer.tag = reinterpret_cast<u64>(samples.data()); 47 out_buffer.tag = reinterpret_cast<u64>(samples.data());
48 stream->AppendBuffer(out_buffer, samples); 48 stream->AppendBuffer(out_buffer, samples);
49
50 if (stream->IsPaused()) {
51 stream->Start();
52 }
49} 53}
50 54
51bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { 55bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h
index 85955bd9c..8f6d6e7d8 100644
--- a/src/audio_core/renderer/effect/effect_context.h
+++ b/src/audio_core/renderer/effect/effect_context.h
@@ -15,15 +15,15 @@ class EffectContext {
15public: 15public:
16 /** 16 /**
17 * Initialize the effect context 17 * Initialize the effect context
18 * @param effect_infos List of effect infos for this context 18 * @param effect_infos_ - List of effect infos for this context
19 * @param effect_count The number of effects in the list 19 * @param effect_count_ - The number of effects in the list
20 * @param result_states_cpu The workbuffer of result states for the CPU for this context 20 * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context
21 * @param result_states_dsp The workbuffer of result states for the DSP for this context 21 * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context
22 * @param state_count The number of result states 22 * @param dsp_state_count - The number of result states
23 */ 23 */
24 void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, 24 void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_,
25 std::span<EffectResultState> result_states_cpu_, 25 std::span<EffectResultState> result_states_cpu_,
26 std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); 26 std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count);
27 27
28 /** 28 /**
29 * Get the EffectInfo for a given index 29 * Get the EffectInfo for a given index
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h
index 8c9583878..8525fde05 100644
--- a/src/audio_core/renderer/effect/effect_info_base.h
+++ b/src/audio_core/renderer/effect/effect_info_base.h
@@ -291,7 +291,7 @@ public:
291 * Update the info with new parameters, version 1. 291 * Update the info with new parameters, version 1.
292 * 292 *
293 * @param error_info - Used to write call result code. 293 * @param error_info - Used to write call result code.
294 * @param in_params - New parameters to update the info with. 294 * @param params - New parameters to update the info with.
295 * @param pool_mapper - Pool for mapping buffers. 295 * @param pool_mapper - Pool for mapping buffers.
296 */ 296 */
297 virtual void Update(BehaviorInfo::ErrorInfo& error_info, 297 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
@@ -305,7 +305,7 @@ public:
305 * Update the info with new parameters, version 2. 305 * Update the info with new parameters, version 2.
306 * 306 *
307 * @param error_info - Used to write call result code. 307 * @param error_info - Used to write call result code.
308 * @param in_params - New parameters to update the info with. 308 * @param params - New parameters to update the info with.
309 * @param pool_mapper - Pool for mapping buffers. 309 * @param pool_mapper - Pool for mapping buffers.
310 */ 310 */
311 virtual void Update(BehaviorInfo::ErrorInfo& error_info, 311 virtual void Update(BehaviorInfo::ErrorInfo& error_info,
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h
index 7a088a627..1ebbc5c4c 100644
--- a/src/audio_core/renderer/effect/i3dl2.h
+++ b/src/audio_core/renderer/effect/i3dl2.h
@@ -99,7 +99,7 @@ public:
99 return out_sample; 99 return out_sample;
100 } 100 }
101 101
102 Common::FixedPoint<50, 14> Read() { 102 Common::FixedPoint<50, 14> Read() const {
103 return *output; 103 return *output;
104 } 104 }
105 105
@@ -110,7 +110,7 @@ public:
110 } 110 }
111 } 111 }
112 112
113 Common::FixedPoint<50, 14> TapOut(const s32 index) { 113 Common::FixedPoint<50, 14> TapOut(const s32 index) const {
114 auto out{input - (index + 1)}; 114 auto out{input - (index + 1)};
115 if (out < buffer.data()) { 115 if (out < buffer.data()) {
116 out += max_delay + 1; 116 out += max_delay + 1;
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h
index b4df9f6ef..a72475c3c 100644
--- a/src/audio_core/renderer/effect/reverb.h
+++ b/src/audio_core/renderer/effect/reverb.h
@@ -95,7 +95,7 @@ public:
95 return out_sample; 95 return out_sample;
96 } 96 }
97 97
98 Common::FixedPoint<50, 14> Read() { 98 Common::FixedPoint<50, 14> Read() const {
99 return *output; 99 return *output;
100 } 100 }
101 101
@@ -106,7 +106,7 @@ public:
106 } 106 }
107 } 107 }
108 108
109 Common::FixedPoint<50, 14> TapOut(const s32 index) { 109 Common::FixedPoint<50, 14> TapOut(const s32 index) const {
110 auto out{input - (index + 1)}; 110 auto out{input - (index + 1)};
111 if (out < buffer.data()) { 111 if (out < buffer.data()) {
112 out += sample_count; 112 out += sample_count;
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h
index 4cfefea8e..bb5c930e1 100644
--- a/src/audio_core/renderer/memory/address_info.h
+++ b/src/audio_core/renderer/memory/address_info.h
@@ -19,8 +19,8 @@ public:
19 /** 19 /**
20 * Setup a new AddressInfo. 20 * Setup a new AddressInfo.
21 * 21 *
22 * @param cpu_address - The CPU address of this region. 22 * @param cpu_address_ - The CPU address of this region.
23 * @param size - The size of this region. 23 * @param size_ - The size of this region.
24 */ 24 */
25 void Setup(CpuAddr cpu_address_, u64 size_) { 25 void Setup(CpuAddr cpu_address_, u64 size_) {
26 cpu_address = cpu_address_; 26 cpu_address = cpu_address_;
@@ -42,7 +42,6 @@ public:
42 * Assign this region to a memory pool. 42 * Assign this region to a memory pool.
43 * 43 *
44 * @param memory_pool_ - Memory pool to assign. 44 * @param memory_pool_ - Memory pool to assign.
45 * @return The CpuAddr address of this region.
46 */ 45 */
47 void SetPool(MemoryPoolInfo* memory_pool_) { 46 void SetPool(MemoryPoolInfo* memory_pool_) {
48 memory_pool = memory_pool_; 47 memory_pool = memory_pool_;
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h
index a1e0958a2..94b1d1254 100644
--- a/src/audio_core/renderer/nodes/node_states.h
+++ b/src/audio_core/renderer/nodes/node_states.h
@@ -56,7 +56,7 @@ class NodeStates {
56 * 56 *
57 * @return The current stack position. 57 * @return The current stack position.
58 */ 58 */
59 u32 Count() { 59 u32 Count() const {
60 return pos; 60 return pos;
61 } 61 }
62 62
@@ -83,7 +83,7 @@ class NodeStates {
83 * 83 *
84 * @return The node on the top of the stack. 84 * @return The node on the top of the stack.
85 */ 85 */
86 u32 top() { 86 u32 top() const {
87 return stack[pos - 1]; 87 return stack[pos - 1];
88 } 88 }
89 89
@@ -112,11 +112,11 @@ public:
112 /** 112 /**
113 * Initialize the node states. 113 * Initialize the node states.
114 * 114 *
115 * @param buffer - The workbuffer to use. Unused. 115 * @param buffer_ - The workbuffer to use. Unused.
116 * @param node_buffer_size - The size of the workbuffer. Unused. 116 * @param node_buffer_size - The size of the workbuffer. Unused.
117 * @param count - The number of nodes in the graph. 117 * @param count - The number of nodes in the graph.
118 */ 118 */
119 void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); 119 void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count);
120 120
121 /** 121 /**
122 * Sort the graph. Only calls DepthFirstSearch. 122 * Sort the graph. Only calls DepthFirstSearch.
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h
index b82176bef..b65caa9b6 100644
--- a/src/audio_core/renderer/performance/performance_manager.h
+++ b/src/audio_core/renderer/performance/performance_manager.h
@@ -73,7 +73,8 @@ public:
73 * Calculate the required size for the performance workbuffer. 73 * Calculate the required size for the performance workbuffer.
74 * 74 *
75 * @param behavior - Check which version is supported. 75 * @param behavior - Check which version is supported.
76 * @param params - Input parameters. 76 * @param params - Input parameters.
77 *
77 * @return Required workbuffer size. 78 * @return Required workbuffer size.
78 */ 79 */
79 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( 80 static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame(
@@ -104,7 +105,7 @@ public:
104 * @param workbuffer - Workbuffer to use for performance frames. 105 * @param workbuffer - Workbuffer to use for performance frames.
105 * @param workbuffer_size - Size of the workbuffer. 106 * @param workbuffer_size - Size of the workbuffer.
106 * @param params - Input parameters. 107 * @param params - Input parameters.
107 * @param behavior - Behaviour to check version and data format. 108 * @param behavior - Behaviour to check version and data format.
108 * @param memory_pool - Used to translate the workbuffer address for the DSP. 109 * @param memory_pool - Used to translate the workbuffer address for the DSP.
109 */ 110 */
110 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, 111 virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size,
@@ -160,7 +161,8 @@ public:
160 * workbuffer, to be written by the AudioRenderer. 161 * workbuffer, to be written by the AudioRenderer.
161 * 162 *
162 * @param addresses - Filled with pointers to the new detail, which should be passed 163 * @param addresses - Filled with pointers to the new detail, which should be passed
163 * to the AudioRenderer with Performance commands to be written. 164 * to the AudioRenderer with Performance commands to be written.
165 * @param detail_type - Performance detail type.
164 * @param entry_type - The type of this detail. See PerformanceEntryType 166 * @param entry_type - The type of this detail. See PerformanceEntryType
165 * @param node_id - Node id for this detail. 167 * @param node_id - Node id for this detail.
166 * @return True if a new detail was created and the offsets are valid, otherwise false. 168 * @return True if a new detail was created and the offsets are valid, otherwise false.
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp
index b326819ed..9c1331e19 100644
--- a/src/audio_core/renderer/system_manager.cpp
+++ b/src/audio_core/renderer/system_manager.cpp
@@ -15,17 +15,14 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager",
15 MP_RGB(60, 19, 97)); 15 MP_RGB(60, 19, 97));
16 16
17namespace AudioCore::AudioRenderer { 17namespace AudioCore::AudioRenderer {
18constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; 18constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL};
19constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL};
20 19
21SystemManager::SystemManager(Core::System& core_) 20SystemManager::SystemManager(Core::System& core_)
22 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, 21 : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()},
23 thread_event{Core::Timing::CreateEvent( 22 thread_event{Core::Timing::CreateEvent(
24 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { 23 "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) {
25 return ThreadFunc2(time); 24 return ThreadFunc2(time);
26 })} { 25 })} {}
27 core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); });
28}
29 26
30SystemManager::~SystemManager() { 27SystemManager::~SystemManager() {
31 Stop(); 28 Stop();
@@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() {
36 if (adsp.Start()) { 33 if (adsp.Start()) {
37 active = true; 34 active = true;
38 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); 35 thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); });
39 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), 36 core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME,
40 BaseRenderTime - RenderTimeOffset, thread_event); 37 thread_event);
41 } 38 }
42 } 39 }
43 40
@@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() {
121} 118}
122 119
123std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { 120std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) {
124 std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt};
125 const auto queue_size{core.AudioCore().GetStreamQueue()};
126 switch (state) {
127 case StreamState::Filling:
128 if (queue_size >= 5) {
129 new_schedule_time = BaseRenderTime;
130 state = StreamState::Steady;
131 }
132 break;
133 case StreamState::Steady:
134 if (queue_size <= 2) {
135 new_schedule_time = BaseRenderTime - RenderTimeOffset;
136 state = StreamState::Filling;
137 } else if (queue_size > 5) {
138 new_schedule_time = BaseRenderTime + RenderTimeOffset;
139 state = StreamState::Draining;
140 }
141 break;
142 case StreamState::Draining:
143 if (queue_size <= 5) {
144 new_schedule_time = BaseRenderTime;
145 state = StreamState::Steady;
146 }
147 break;
148 }
149
150 update.store(true); 121 update.store(true);
151 update.notify_all(); 122 update.notify_all();
152 return new_schedule_time; 123 return std::nullopt;
153}
154
155void SystemManager::PauseCallback(bool paused) {
156 if (paused && core.IsPoweredOn() && core.IsShuttingDown()) {
157 update.store(true);
158 update.notify_all();
159 }
160} 124}
161 125
162} // namespace AudioCore::AudioRenderer 126} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h
index 1291e9e0e..81457a3a1 100644
--- a/src/audio_core/renderer/system_manager.h
+++ b/src/audio_core/renderer/system_manager.h
@@ -73,13 +73,6 @@ private:
73 */ 73 */
74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); 74 std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time);
75 75
76 /**
77 * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc.
78 *
79 * @param paused - Are we pausing or resuming?
80 */
81 void PauseCallback(bool paused);
82
83 enum class StreamState { 76 enum class StreamState {
84 Filling, 77 Filling,
85 Steady, 78 Steady,
@@ -106,8 +99,6 @@ private:
106 std::shared_ptr<Core::Timing::EventType> thread_event; 99 std::shared_ptr<Core::Timing::EventType> thread_event;
107 /// Atomic for main thread to wait on 100 /// Atomic for main thread to wait on
108 std::atomic<bool> update{}; 101 std::atomic<bool> update{};
109 /// Current state of the streams
110 StreamState state{StreamState::Filling};
111}; 102};
112 103
113} // namespace AudioCore::AudioRenderer 104} // namespace AudioCore::AudioRenderer
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h
index 70cd42b08..83c697c0c 100644
--- a/src/audio_core/renderer/upsampler/upsampler_manager.h
+++ b/src/audio_core/renderer/upsampler/upsampler_manager.h
@@ -27,7 +27,7 @@ public:
27 /** 27 /**
28 * Free the given upsampler. 28 * Free the given upsampler.
29 * 29 *
30 * @param The upsampler to be freed. 30 * @param info The upsampler to be freed.
31 */ 31 */
32 void Free(UpsamplerInfo* info); 32 void Free(UpsamplerInfo* info);
33 33
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h
index 896723e0c..930180895 100644
--- a/src/audio_core/renderer/voice/voice_info.h
+++ b/src/audio_core/renderer/voice/voice_info.h
@@ -185,7 +185,8 @@ public:
185 /** 185 /**
186 * Does this voice ned an update? 186 * Does this voice ned an update?
187 * 187 *
188 * @param params - Input parametetrs to check matching. 188 * @param params - Input parameters to check matching.
189 *
189 * @return True if this voice needs an update, otherwise false. 190 * @return True if this voice needs an update, otherwise false.
190 */ 191 */
191 bool ShouldUpdateParameters(const InParameter& params) const; 192 bool ShouldUpdateParameters(const InParameter& params) const;
@@ -194,9 +195,9 @@ public:
194 * Update the parameters of this voice. 195 * Update the parameters of this voice.
195 * 196 *
196 * @param error_info - Output error code. 197 * @param error_info - Output error code.
197 * @param params - Input parametters to udpate from. 198 * @param params - Input parameters to update from.
198 * @param pool_mapper - Used to map buffers. 199 * @param pool_mapper - Used to map buffers.
199 * @param behavior - behavior to check supported features. 200 * @param behavior - behavior to check supported features.
200 */ 201 */
201 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, 202 void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params,
202 const PoolMapper& pool_mapper, const BehaviorInfo& behavior); 203 const PoolMapper& pool_mapper, const BehaviorInfo& behavior);
@@ -218,12 +219,12 @@ public:
218 /** 219 /**
219 * Update all wavebuffers. 220 * Update all wavebuffers.
220 * 221 *
221 * @param error_infos - Output 2D array of errors, 2 per wavebuffer. 222 * @param error_infos - Output 2D array of errors, 2 per wavebuffer.
222 * @param error_count - Number of errors provided. Unused. 223 * @param error_count - Number of errors provided. Unused.
223 * @param params - Input parametters to be used for the update. 224 * @param params - Input parameters to be used for the update.
224 * @param voice_states - The voice states for each channel in this voice to be updated. 225 * @param voice_states - The voice states for each channel in this voice to be updated.
225 * @param pool_mapper - Used to map the wavebuffers. 226 * @param pool_mapper - Used to map the wavebuffers.
226 * @param behavior - Used to check for supported features. 227 * @param behavior - Used to check for supported features.
227 */ 228 */
228 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, 229 void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos,
229 u32 error_count, const InParameter& params, 230 u32 error_count, const InParameter& params,
@@ -233,13 +234,13 @@ public:
233 /** 234 /**
234 * Update a wavebuffer. 235 * Update a wavebuffer.
235 * 236 *
236 * @param error_infos - Output array of errors. 237 * @param error_info - Output array of errors.
237 * @param wave_buffer - The wavebuffer to be updated. 238 * @param wave_buffer - The wavebuffer to be updated.
238 * @param wave_buffer_internal - Input parametters to be used for the update. 239 * @param wave_buffer_internal - Input parametters to be used for the update.
239 * @param sample_format - Sample format of the wavebuffer. 240 * @param sample_format - Sample format of the wavebuffer.
240 * @param valid - Is this wavebuffer valid? 241 * @param valid - Is this wavebuffer valid?
241 * @param pool_mapper - Used to map the wavebuffers. 242 * @param pool_mapper - Used to map the wavebuffers.
242 * @param behavior - Used to check for supported features. 243 * @param behavior - Used to check for supported features.
243 */ 244 */
244 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, 245 void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer,
245 const WaveBufferInternal& wave_buffer_internal, 246 const WaveBufferInternal& wave_buffer_internal,
@@ -276,7 +277,7 @@ public:
276 /** 277 /**
277 * Check if this voice has any mixing connections. 278 * Check if this voice has any mixing connections.
278 * 279 *
279 * @return True if this voice participes in mixing, otherwise false. 280 * @return True if this voice participates in mixing, otherwise false.
280 */ 281 */
281 bool HasAnyConnection() const; 282 bool HasAnyConnection() const;
282 283
@@ -301,7 +302,8 @@ public:
301 /** 302 /**
302 * Update this voice on command generation. 303 * Update this voice on command generation.
303 * 304 *
304 * @param voice_states - Voice states for these wavebuffers. 305 * @param voice_context - Voice context for these wavebuffers.
306 *
305 * @return True if this voice should be generated, otherwise false. 307 * @return True if this voice should be generated, otherwise false.
306 */ 308 */
307 bool UpdateForCommandGeneration(VoiceContext& voice_context); 309 bool UpdateForCommandGeneration(VoiceContext& voice_context);
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp
index 90d049e8e..36b115ad6 100644
--- a/src/audio_core/sink/cubeb_sink.cpp
+++ b/src/audio_core/sink/cubeb_sink.cpp
@@ -1,21 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm>
5#include <atomic>
6#include <span> 4#include <span>
5#include <vector>
7 6
8#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
9#include "audio_core/audio_event.h"
10#include "audio_core/audio_manager.h"
11#include "audio_core/sink/cubeb_sink.h" 8#include "audio_core/sink/cubeb_sink.h"
12#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
13#include "common/assert.h"
14#include "common/fixed_point.h"
15#include "common/logging/log.h" 10#include "common/logging/log.h"
16#include "common/reader_writer_queue.h"
17#include "common/ring_buffer.h"
18#include "common/settings.h"
19#include "core/core.h" 11#include "core/core.h"
20 12
21#ifdef _WIN32 13#ifdef _WIN32
@@ -42,10 +34,10 @@ public:
42 * @param system_ - Core system. 34 * @param system_ - Core system.
43 * @param event - Event used only for audio renderer, signalled on buffer consume. 35 * @param event - Event used only for audio renderer, signalled on buffer consume.
44 */ 36 */
45 CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, 37 CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
46 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, 38 cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
47 const StreamType type_, Core::System& system_) 39 StreamType type_, Core::System& system_)
48 : ctx{ctx_}, type{type_}, system{system_} { 40 : SinkStream(system_, type_), ctx{ctx_} {
49#ifdef _WIN32 41#ifdef _WIN32
50 CoInitializeEx(nullptr, COINIT_MULTITHREADED); 42 CoInitializeEx(nullptr, COINIT_MULTITHREADED);
51#endif 43#endif
@@ -79,12 +71,10 @@ public:
79 71
80 minimum_latency = std::max(minimum_latency, 256u); 72 minimum_latency = std::max(minimum_latency, 256u);
81 73
82 playing_buffer.consumed = true; 74 LOG_INFO(Service_Audio,
83 75 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
84 LOG_DEBUG(Service_Audio, 76 "latency {}",
85 "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " 77 name, type, params.rate, params.channels, system_channels, minimum_latency);
86 "latency {}",
87 name, type, params.rate, params.channels, system_channels, minimum_latency);
88 78
89 auto init_error{0}; 79 auto init_error{0};
90 if (type == StreamType::In) { 80 if (type == StreamType::In) {
@@ -111,6 +101,8 @@ public:
111 ~CubebSinkStream() override { 101 ~CubebSinkStream() override {
112 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); 102 LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
113 103
104 Unstall();
105
114 if (!ctx) { 106 if (!ctx) {
115 return; 107 return;
116 } 108 }
@@ -136,21 +128,14 @@ public:
136 * @param resume - Set to true if this is resuming the stream a previously-active stream. 128 * @param resume - Set to true if this is resuming the stream a previously-active stream.
137 * Default false. 129 * Default false.
138 */ 130 */
139 void Start(const bool resume = false) override { 131 void Start(bool resume = false) override {
140 if (!ctx) { 132 if (!ctx || !paused) {
141 return; 133 return;
142 } 134 }
143 135
144 if (resume && was_playing) { 136 paused = false;
145 if (cubeb_stream_start(stream_backend) != CUBEB_OK) { 137 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
146 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); 138 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
147 }
148 paused = false;
149 } else if (!resume) {
150 if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
151 LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
152 }
153 paused = false;
154 } 139 }
155 } 140 }
156 141
@@ -158,207 +143,20 @@ public:
158 * Stop the sink stream. 143 * Stop the sink stream.
159 */ 144 */
160 void Stop() override { 145 void Stop() override {
161 if (!ctx) { 146 Unstall();
147
148 if (!ctx || paused) {
162 return; 149 return;
163 } 150 }
164 151
152 paused = true;
165 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { 153 if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
166 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); 154 LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
167 } 155 }
168
169 was_playing.store(!paused);
170 paused = true;
171 }
172
173 /**
174 * Append a new buffer and its samples to a waiting queue to play.
175 *
176 * @param buffer - Audio buffer information to be queued.
177 * @param samples - The s16 samples to be queue for playback.
178 */
179 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
180 if (type == StreamType::In) {
181 queue.enqueue(buffer);
182 queued_buffers++;
183 } else {
184 constexpr s32 min{std::numeric_limits<s16>::min()};
185 constexpr s32 max{std::numeric_limits<s16>::max()};
186
187 auto yuzu_volume{Settings::Volume()};
188 if (yuzu_volume > 1.0f) {
189 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
190 }
191 auto volume{system_volume * device_volume * yuzu_volume};
192
193 if (system_channels == 6 && device_channels == 2) {
194 // We're given 6 channels, but our device only outputs 2, so downmix.
195 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
196
197 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
198 read_index += system_channels, write_index += device_channels) {
199 const auto left_sample{
200 ((Common::FixedPoint<49, 15>(
201 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
202 down_mix_coeff[0] +
203 samples[read_index + static_cast<u32>(Channels::Center)] *
204 down_mix_coeff[1] +
205 samples[read_index + static_cast<u32>(Channels::LFE)] *
206 down_mix_coeff[2] +
207 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
208 down_mix_coeff[3]) *
209 volume)
210 .to_int()};
211
212 const auto right_sample{
213 ((Common::FixedPoint<49, 15>(
214 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
215 down_mix_coeff[0] +
216 samples[read_index + static_cast<u32>(Channels::Center)] *
217 down_mix_coeff[1] +
218 samples[read_index + static_cast<u32>(Channels::LFE)] *
219 down_mix_coeff[2] +
220 samples[read_index + static_cast<u32>(Channels::BackRight)] *
221 down_mix_coeff[3]) *
222 volume)
223 .to_int()};
224
225 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
226 static_cast<s16>(std::clamp(left_sample, min, max));
227 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 static_cast<s16>(std::clamp(right_sample, min, max));
229 }
230
231 samples.resize(samples.size() / system_channels * device_channels);
232
233 } else if (system_channels == 2 && device_channels == 6) {
234 // We need moar samples! Not all games will provide 6 channel audio.
235 // TODO: Implement some upmixing here. Currently just passthrough, with other
236 // channels left as silence.
237 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
238
239 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
240 read_index += system_channels, write_index += device_channels) {
241 const auto left_sample{static_cast<s16>(std::clamp(
242 static_cast<s32>(
243 static_cast<f32>(
244 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
245 volume),
246 min, max))};
247
248 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
249
250 const auto right_sample{static_cast<s16>(std::clamp(
251 static_cast<s32>(
252 static_cast<f32>(
253 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
254 volume),
255 min, max))};
256
257 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
258 right_sample;
259 }
260 samples = std::move(new_samples);
261
262 } else if (volume != 1.0f) {
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(std::clamp(
265 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267 }
268
269 samples_buffer.Push(samples);
270 queue.enqueue(buffer);
271 queued_buffers++;
272 }
273 }
274
275 /**
276 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
277 *
278 * @param num_samples - Maximum number of samples to receive.
279 * @return Vector of recorded samples. May have fewer than num_samples.
280 */
281 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
282 static constexpr s32 min = std::numeric_limits<s16>::min();
283 static constexpr s32 max = std::numeric_limits<s16>::max();
284
285 auto samples{samples_buffer.Pop(num_samples)};
286
287 // TODO: Up-mix to 6 channels if the game expects it.
288 // For audio input this is unlikely to ever be the case though.
289
290 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
291 // TODO: Play with this and find something that works better.
292 auto volume{system_volume * device_volume * 8};
293 for (u32 i = 0; i < samples.size(); i++) {
294 samples[i] = static_cast<s16>(
295 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
296 }
297
298 if (samples.size() < num_samples) {
299 samples.resize(num_samples, 0);
300 }
301 return samples;
302 }
303
304 /**
305 * Check if a certain buffer has been consumed (fully played).
306 *
307 * @param tag - Unique tag of a buffer to check for.
308 * @return True if the buffer has been played, otherwise false.
309 */
310 bool IsBufferConsumed(const u64 tag) override {
311 if (released_buffer.tag == 0) {
312 if (!released_buffers.try_dequeue(released_buffer)) {
313 return false;
314 }
315 }
316
317 if (released_buffer.tag == tag) {
318 released_buffer.tag = 0;
319 return true;
320 }
321 return false;
322 }
323
324 /**
325 * Empty out the buffer queue.
326 */
327 void ClearQueue() override {
328 samples_buffer.Pop();
329 while (queue.pop()) {
330 }
331 while (released_buffers.pop()) {
332 }
333 queued_buffers = 0;
334 released_buffer = {};
335 playing_buffer = {};
336 playing_buffer.consumed = true;
337 } 156 }
338 157
339private: 158private:
340 /** 159 /**
341 * Signal events back to the audio system that a buffer was played/can be filled.
342 *
343 * @param buffer - Consumed audio buffer to be released.
344 */
345 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
346 auto& manager{system.AudioCore().GetAudioManager()};
347 switch (type) {
348 case StreamType::Out:
349 released_buffers.enqueue(buffer);
350 manager.SetEvent(Event::Type::AudioOutManager, true);
351 break;
352 case StreamType::In:
353 released_buffers.enqueue(buffer);
354 manager.SetEvent(Event::Type::AudioInManager, true);
355 break;
356 case StreamType::Render:
357 break;
358 }
359 }
360
361 /**
362 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will 160 * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
363 * provide samples to be copied (audio in). 161 * provide samples to be copied (audio in).
364 * 162 *
@@ -378,106 +176,15 @@ private:
378 176
379 const std::size_t num_channels = impl->GetDeviceChannels(); 177 const std::size_t num_channels = impl->GetDeviceChannels();
380 const std::size_t frame_size = num_channels; 178 const std::size_t frame_size = num_channels;
381 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
382 const std::size_t num_frames{static_cast<size_t>(num_frames_)}; 179 const std::size_t num_frames{static_cast<size_t>(num_frames_)};
383 size_t frames_written{0};
384 [[maybe_unused]] bool underrun{false};
385 180
386 if (impl->type == StreamType::In) { 181 if (impl->type == StreamType::In) {
387 // INPUT
388 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), 182 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
389 num_frames * frame_size}; 183 num_frames * frame_size};
390 184 impl->ProcessAudioIn(input_buffer, num_frames);
391 while (frames_written < num_frames) {
392 auto& playing_buffer{impl->playing_buffer};
393
394 // If the playing buffer has been consumed or has no frames, we need a new one
395 if (playing_buffer.consumed || playing_buffer.frames == 0) {
396 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
397 // If no buffer was available we've underrun, just push the samples and
398 // continue.
399 underrun = true;
400 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
401 (num_frames - frames_written) * frame_size);
402 frames_written = num_frames;
403 continue;
404 } else {
405 // Successfully got a new buffer, mark the old one as consumed and signal.
406 impl->queued_buffers--;
407 impl->SignalEvent(impl->playing_buffer);
408 }
409 }
410
411 // Get the minimum frames available between the currently playing buffer, and the
412 // amount we have left to fill
413 size_t frames_available{
414 std::min(playing_buffer.frames - playing_buffer.frames_played,
415 num_frames - frames_written)};
416
417 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
418 frames_available * frame_size);
419
420 frames_written += frames_available;
421 playing_buffer.frames_played += frames_available;
422
423 // If that's all the frames in the current buffer, add its samples and mark it as
424 // consumed
425 if (playing_buffer.frames_played >= playing_buffer.frames) {
426 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
427 impl->playing_buffer.consumed = true;
428 }
429 }
430
431 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
432 frame_size_bytes);
433 } else { 185 } else {
434 // OUTPUT
435 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; 186 std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
436 187 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
437 while (frames_written < num_frames) {
438 auto& playing_buffer{impl->playing_buffer};
439
440 // If the playing buffer has been consumed or has no frames, we need a new one
441 if (playing_buffer.consumed || playing_buffer.frames == 0) {
442 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
443 // If no buffer was available we've underrun, fill the remaining buffer with
444 // the last written frame and continue.
445 underrun = true;
446 for (size_t i = frames_written; i < num_frames; i++) {
447 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
448 frame_size_bytes);
449 }
450 frames_written = num_frames;
451 continue;
452 } else {
453 // Successfully got a new buffer, mark the old one as consumed and signal.
454 impl->queued_buffers--;
455 impl->SignalEvent(impl->playing_buffer);
456 }
457 }
458
459 // Get the minimum frames available between the currently playing buffer, and the
460 // amount we have left to fill
461 size_t frames_available{
462 std::min(playing_buffer.frames - playing_buffer.frames_played,
463 num_frames - frames_written)};
464
465 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
466 frames_available * frame_size);
467
468 frames_written += frames_available;
469 playing_buffer.frames_played += frames_available;
470
471 // If that's all the frames in the current buffer, add its samples and mark it as
472 // consumed
473 if (playing_buffer.frames_played >= playing_buffer.frames) {
474 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
475 impl->playing_buffer.consumed = true;
476 }
477 }
478
479 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
480 frame_size_bytes);
481 } 188 }
482 189
483 return num_frames_; 190 return num_frames_;
@@ -490,32 +197,12 @@ private:
490 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. 197 * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
491 * @param state - New state of the device. 198 * @param state - New state of the device.
492 */ 199 */
493 static void StateCallback([[maybe_unused]] cubeb_stream* stream, 200 static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
494 [[maybe_unused]] void* user_data,
495 [[maybe_unused]] cubeb_state state) {}
496 201
497 /// Main Cubeb context 202 /// Main Cubeb context
498 cubeb* ctx{}; 203 cubeb* ctx{};
499 /// Cubeb stream backend 204 /// Cubeb stream backend
500 cubeb_stream* stream_backend{}; 205 cubeb_stream* stream_backend{};
501 /// Name of this stream
502 std::string name{};
503 /// Type of this stream
504 StreamType type;
505 /// Core system
506 Core::System& system;
507 /// Ring buffer of the samples waiting to be played or consumed
508 Common::RingBuffer<s16, 0x10000> samples_buffer;
509 /// Audio buffers queued and waiting to play
510 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
511 /// The currently-playing audio buffer
512 ::AudioCore::Sink::SinkBuffer playing_buffer{};
513 /// Audio buffers which have been played and are in queue to be released by the audio system
514 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
515 /// Currently released buffer waiting to be taken by the audio system
516 ::AudioCore::Sink::SinkBuffer released_buffer{};
517 /// The last played (or received) frame of audio, used when the callback underruns
518 std::array<s16, MaxChannels> last_frame{};
519}; 206};
520 207
521CubebSink::CubebSink(std::string_view target_device_name) { 208CubebSink::CubebSink(std::string_view target_device_name) {
@@ -569,15 +256,15 @@ CubebSink::~CubebSink() {
569#endif 256#endif
570} 257}
571 258
572SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 259SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
573 const std::string& name, const StreamType type) { 260 const std::string& name, StreamType type) {
574 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( 261 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
575 ctx, device_channels, system_channels, output_device, input_device, name, type, system)); 262 ctx, device_channels, system_channels, output_device, input_device, name, type, system));
576 263
577 return stream.get(); 264 return stream.get();
578} 265}
579 266
580void CubebSink::CloseStream(const SinkStream* stream) { 267void CubebSink::CloseStream(SinkStream* stream) {
581 for (size_t i = 0; i < sink_streams.size(); i++) { 268 for (size_t i = 0; i < sink_streams.size(); i++) {
582 if (sink_streams[i].get() == stream) { 269 if (sink_streams[i].get() == stream) {
583 sink_streams[i].reset(); 270 sink_streams[i].reset();
@@ -591,18 +278,6 @@ void CubebSink::CloseStreams() {
591 sink_streams.clear(); 278 sink_streams.clear();
592} 279}
593 280
594void CubebSink::PauseStreams() {
595 for (auto& stream : sink_streams) {
596 stream->Stop();
597 }
598}
599
600void CubebSink::UnpauseStreams() {
601 for (auto& stream : sink_streams) {
602 stream->Start(true);
603 }
604}
605
606f32 CubebSink::GetDeviceVolume() const { 281f32 CubebSink::GetDeviceVolume() const {
607 if (sink_streams.empty()) { 282 if (sink_streams.empty()) {
608 return 1.0f; 283 return 1.0f;
@@ -611,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const {
611 return sink_streams[0]->GetDeviceVolume(); 286 return sink_streams[0]->GetDeviceVolume();
612} 287}
613 288
614void CubebSink::SetDeviceVolume(const f32 volume) { 289void CubebSink::SetDeviceVolume(f32 volume) {
615 for (auto& stream : sink_streams) { 290 for (auto& stream : sink_streams) {
616 stream->SetDeviceVolume(volume); 291 stream->SetDeviceVolume(volume);
617 } 292 }
618} 293}
619 294
620void CubebSink::SetSystemVolume(const f32 volume) { 295void CubebSink::SetSystemVolume(f32 volume) {
621 for (auto& stream : sink_streams) { 296 for (auto& stream : sink_streams) {
622 stream->SetSystemVolume(volume); 297 stream->SetSystemVolume(volume);
623 } 298 }
624} 299}
625 300
626std::vector<std::string> ListCubebSinkDevices(const bool capture) { 301std::vector<std::string> ListCubebSinkDevices(bool capture) {
627 std::vector<std::string> device_list; 302 std::vector<std::string> device_list;
628 cubeb* ctx; 303 cubeb* ctx;
629 304
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h
index f0f43dfa1..4b0cb160d 100644
--- a/src/audio_core/sink/cubeb_sink.h
+++ b/src/audio_core/sink/cubeb_sink.h
@@ -34,8 +34,7 @@ public:
34 * May differ from the device's channel count. 34 * May differ from the device's channel count.
35 * @param name - Name of this stream. 35 * @param name - Name of this stream.
36 * @param type - Type of this stream, render/in/out. 36 * @param type - Type of this stream, render/in/out.
37 * @param event - Audio render only, a signal used to prevent the renderer running too 37 *
38 * fast.
39 * @return A pointer to the created SinkStream 38 * @return A pointer to the created SinkStream
40 */ 39 */
41 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 40 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@@ -46,7 +45,7 @@ public:
46 * 45 *
47 * @param stream - The stream to close. 46 * @param stream - The stream to close.
48 */ 47 */
49 void CloseStream(const SinkStream* stream) override; 48 void CloseStream(SinkStream* stream) override;
50 49
51 /** 50 /**
52 * Close all streams. 51 * Close all streams.
@@ -54,16 +53,6 @@ public:
54 void CloseStreams() override; 53 void CloseStreams() override;
55 54
56 /** 55 /**
57 * Pause all streams.
58 */
59 void PauseStreams() override;
60
61 /**
62 * Unpause all streams.
63 */
64 void UnpauseStreams() override;
65
66 /**
67 * Get the device volume. Set from calls to the IAudioDevice service. 56 * Get the device volume. Set from calls to the IAudioDevice service.
68 * 57 *
69 * @return Volume of the device. 58 * @return Volume of the device.
@@ -101,7 +90,7 @@ private:
101}; 90};
102 91
103/** 92/**
104 * Get a list of conencted devices from Cubeb. 93 * Get a list of connected devices from Cubeb.
105 * 94 *
106 * @param capture - Return input (capture) devices if true, otherwise output devices. 95 * @param capture - Return input (capture) devices if true, otherwise output devices.
107 */ 96 */
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h
index 47a342171..1215d3cd2 100644
--- a/src/audio_core/sink/null_sink.h
+++ b/src/audio_core/sink/null_sink.h
@@ -3,10 +3,29 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <string>
7#include <string_view>
8#include <vector>
9
6#include "audio_core/sink/sink.h" 10#include "audio_core/sink/sink.h"
7#include "audio_core/sink/sink_stream.h" 11#include "audio_core/sink/sink_stream.h"
8 12
13namespace Core {
14class System;
15} // namespace Core
16
9namespace AudioCore::Sink { 17namespace AudioCore::Sink {
18class NullSinkStreamImpl final : public SinkStream {
19public:
20 explicit NullSinkStreamImpl(Core::System& system_, StreamType type_)
21 : SinkStream{system_, type_} {}
22 ~NullSinkStreamImpl() override {}
23 void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {}
24 std::vector<s16> ReleaseBuffer(u64) override {
25 return {};
26 }
27};
28
10/** 29/**
11 * A no-op sink for when no audio out is wanted. 30 * A no-op sink for when no audio out is wanted.
12 */ 31 */
@@ -15,17 +34,16 @@ public:
15 explicit NullSink(std::string_view) {} 34 explicit NullSink(std::string_view) {}
16 ~NullSink() override = default; 35 ~NullSink() override = default;
17 36
18 SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, 37 SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&,
19 [[maybe_unused]] u32 system_channels, 38 StreamType type) override {
20 [[maybe_unused]] const std::string& name, 39 if (null_sink == nullptr) {
21 [[maybe_unused]] StreamType type) override { 40 null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
22 return &null_sink_stream; 41 }
42 return null_sink.get();
23 } 43 }
24 44
25 void CloseStream([[maybe_unused]] const SinkStream* stream) override {} 45 void CloseStream(SinkStream*) override {}
26 void CloseStreams() override {} 46 void CloseStreams() override {}
27 void PauseStreams() override {}
28 void UnpauseStreams() override {}
29 f32 GetDeviceVolume() const override { 47 f32 GetDeviceVolume() const override {
30 return 1.0f; 48 return 1.0f;
31 } 49 }
@@ -33,20 +51,7 @@ public:
33 void SetSystemVolume(f32 volume) override {} 51 void SetSystemVolume(f32 volume) override {}
34 52
35private: 53private:
36 struct NullSinkStreamImpl final : SinkStream { 54 SinkStreamPtr null_sink{};
37 void Finalize() override {}
38 void Start(bool resume = false) override {}
39 void Stop() override {}
40 void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer,
41 [[maybe_unused]] std::vector<s16>& samples) override {}
42 std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override {
43 return {};
44 }
45 bool IsBufferConsumed([[maybe_unused]] const u64 tag) {
46 return true;
47 }
48 void ClearQueue() override {}
49 } null_sink_stream;
50}; 55};
51 56
52} // namespace AudioCore::Sink 57} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index d6c9ec90d..1bd001b94 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -1,20 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <algorithm> 4#include <span>
5#include <atomic> 5#include <vector>
6 6
7#include "audio_core/audio_core.h" 7#include "audio_core/common/common.h"
8#include "audio_core/audio_event.h"
9#include "audio_core/audio_manager.h"
10#include "audio_core/sink/sdl2_sink.h" 8#include "audio_core/sink/sdl2_sink.h"
11#include "audio_core/sink/sink_stream.h" 9#include "audio_core/sink/sink_stream.h"
12#include "common/assert.h"
13#include "common/fixed_point.h"
14#include "common/logging/log.h" 10#include "common/logging/log.h"
15#include "common/reader_writer_queue.h"
16#include "common/ring_buffer.h"
17#include "common/settings.h"
18#include "core/core.h" 11#include "core/core.h"
19 12
20// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 13// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
@@ -44,10 +37,9 @@ public:
44 * @param system_ - Core system. 37 * @param system_ - Core system.
45 * @param event - Event used only for audio renderer, signalled on buffer consume. 38 * @param event - Event used only for audio renderer, signalled on buffer consume.
46 */ 39 */
47 SDLSinkStream(u32 device_channels_, const u32 system_channels_, 40 SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device,
48 const std::string& output_device, const std::string& input_device, 41 const std::string& input_device, StreamType type_, Core::System& system_)
49 const StreamType type_, Core::System& system_) 42 : SinkStream{system_, type_} {
50 : type{type_}, system{system_} {
51 system_channels = system_channels_; 43 system_channels = system_channels_;
52 device_channels = device_channels_; 44 device_channels = device_channels_;
53 45
@@ -63,8 +55,6 @@ public:
63 spec.callback = &SDLSinkStream::DataCallback; 55 spec.callback = &SDLSinkStream::DataCallback;
64 spec.userdata = this; 56 spec.userdata = this;
65 57
66 playing_buffer.consumed = true;
67
68 std::string device_name{output_device}; 58 std::string device_name{output_device};
69 bool capture{false}; 59 bool capture{false};
70 if (type == StreamType::In) { 60 if (type == StreamType::In) {
@@ -84,31 +74,30 @@ public:
84 return; 74 return;
85 } 75 }
86 76
87 LOG_DEBUG(Service_Audio, 77 LOG_INFO(Service_Audio,
88 "Opening sdl stream {} with: rate {} channels {} (system channels {}) " 78 "Opening SDL stream {} with: rate {} channels {} (system channels {}) "
89 " samples {}", 79 " samples {}",
90 device, obtained.freq, obtained.channels, system_channels, obtained.samples); 80 device, obtained.freq, obtained.channels, system_channels, obtained.samples);
91 } 81 }
92 82
93 /** 83 /**
94 * Destroy the sink stream. 84 * Destroy the sink stream.
95 */ 85 */
96 ~SDLSinkStream() override { 86 ~SDLSinkStream() override {
97 if (device == 0) { 87 LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name);
98 return; 88 Finalize();
99 }
100
101 SDL_CloseAudioDevice(device);
102 } 89 }
103 90
104 /** 91 /**
105 * Finalize the sink stream. 92 * Finalize the sink stream.
106 */ 93 */
107 void Finalize() override { 94 void Finalize() override {
95 Unstall();
108 if (device == 0) { 96 if (device == 0) {
109 return; 97 return;
110 } 98 }
111 99
100 Stop();
112 SDL_CloseAudioDevice(device); 101 SDL_CloseAudioDevice(device);
113 } 102 }
114 103
@@ -118,217 +107,29 @@ public:
118 * @param resume - Set to true if this is resuming the stream a previously-active stream. 107 * @param resume - Set to true if this is resuming the stream a previously-active stream.
119 * Default false. 108 * Default false.
120 */ 109 */
121 void Start(const bool resume = false) override { 110 void Start(bool resume = false) override {
122 if (device == 0) { 111 if (device == 0 || !paused) {
123 return; 112 return;
124 } 113 }
125 114
126 if (resume && was_playing) { 115 paused = false;
127 SDL_PauseAudioDevice(device, 0); 116 SDL_PauseAudioDevice(device, 0);
128 paused = false;
129 } else if (!resume) {
130 SDL_PauseAudioDevice(device, 0);
131 paused = false;
132 }
133 } 117 }
134 118
135 /** 119 /**
136 * Stop the sink stream. 120 * Stop the sink stream.
137 */ 121 */
138 void Stop() { 122 void Stop() override {
139 if (device == 0) { 123 Unstall();
124 if (device == 0 || paused) {
140 return; 125 return;
141 } 126 }
142 SDL_PauseAudioDevice(device, 1);
143 paused = true; 127 paused = true;
144 } 128 SDL_PauseAudioDevice(device, 1);
145
146 /**
147 * Append a new buffer and its samples to a waiting queue to play.
148 *
149 * @param buffer - Audio buffer information to be queued.
150 * @param samples - The s16 samples to be queue for playback.
151 */
152 void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override {
153 if (type == StreamType::In) {
154 queue.enqueue(buffer);
155 queued_buffers++;
156 } else {
157 constexpr s32 min = std::numeric_limits<s16>::min();
158 constexpr s32 max = std::numeric_limits<s16>::max();
159
160 auto yuzu_volume{Settings::Volume()};
161 auto volume{system_volume * device_volume * yuzu_volume};
162
163 if (system_channels == 6 && device_channels == 2) {
164 // We're given 6 channels, but our device only outputs 2, so downmix.
165 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
166
167 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
168 read_index += system_channels, write_index += device_channels) {
169 const auto left_sample{
170 ((Common::FixedPoint<49, 15>(
171 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
172 down_mix_coeff[0] +
173 samples[read_index + static_cast<u32>(Channels::Center)] *
174 down_mix_coeff[1] +
175 samples[read_index + static_cast<u32>(Channels::LFE)] *
176 down_mix_coeff[2] +
177 samples[read_index + static_cast<u32>(Channels::BackLeft)] *
178 down_mix_coeff[3]) *
179 volume)
180 .to_int()};
181
182 const auto right_sample{
183 ((Common::FixedPoint<49, 15>(
184 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
185 down_mix_coeff[0] +
186 samples[read_index + static_cast<u32>(Channels::Center)] *
187 down_mix_coeff[1] +
188 samples[read_index + static_cast<u32>(Channels::LFE)] *
189 down_mix_coeff[2] +
190 samples[read_index + static_cast<u32>(Channels::BackRight)] *
191 down_mix_coeff[3]) *
192 volume)
193 .to_int()};
194
195 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
196 static_cast<s16>(std::clamp(left_sample, min, max));
197 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
198 static_cast<s16>(std::clamp(right_sample, min, max));
199 }
200
201 samples.resize(samples.size() / system_channels * device_channels);
202
203 } else if (system_channels == 2 && device_channels == 6) {
204 // We need moar samples! Not all games will provide 6 channel audio.
205 // TODO: Implement some upmixing here. Currently just passthrough, with other
206 // channels left as silence.
207 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
208
209 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
210 read_index += system_channels, write_index += device_channels) {
211 const auto left_sample{static_cast<s16>(std::clamp(
212 static_cast<s32>(
213 static_cast<f32>(
214 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
215 volume),
216 min, max))};
217
218 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
219
220 const auto right_sample{static_cast<s16>(std::clamp(
221 static_cast<s32>(
222 static_cast<f32>(
223 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
224 volume),
225 min, max))};
226
227 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] =
228 right_sample;
229 }
230 samples = std::move(new_samples);
231
232 } else if (volume != 1.0f) {
233 for (u32 i = 0; i < samples.size(); i++) {
234 samples[i] = static_cast<s16>(std::clamp(
235 static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
236 }
237 }
238
239 samples_buffer.Push(samples);
240 queue.enqueue(buffer);
241 queued_buffers++;
242 }
243 }
244
245 /**
246 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
247 *
248 * @param num_samples - Maximum number of samples to receive.
249 * @return Vector of recorded samples. May have fewer than num_samples.
250 */
251 std::vector<s16> ReleaseBuffer(const u64 num_samples) override {
252 static constexpr s32 min = std::numeric_limits<s16>::min();
253 static constexpr s32 max = std::numeric_limits<s16>::max();
254
255 auto samples{samples_buffer.Pop(num_samples)};
256
257 // TODO: Up-mix to 6 channels if the game expects it.
258 // For audio input this is unlikely to ever be the case though.
259
260 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
261 // TODO: Play with this and find something that works better.
262 auto volume{system_volume * device_volume * 8};
263 for (u32 i = 0; i < samples.size(); i++) {
264 samples[i] = static_cast<s16>(
265 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
266 }
267
268 if (samples.size() < num_samples) {
269 samples.resize(num_samples, 0);
270 }
271 return samples;
272 }
273
274 /**
275 * Check if a certain buffer has been consumed (fully played).
276 *
277 * @param tag - Unique tag of a buffer to check for.
278 * @return True if the buffer has been played, otherwise false.
279 */
280 bool IsBufferConsumed(const u64 tag) override {
281 if (released_buffer.tag == 0) {
282 if (!released_buffers.try_dequeue(released_buffer)) {
283 return false;
284 }
285 }
286
287 if (released_buffer.tag == tag) {
288 released_buffer.tag = 0;
289 return true;
290 }
291 return false;
292 }
293
294 /**
295 * Empty out the buffer queue.
296 */
297 void ClearQueue() override {
298 samples_buffer.Pop();
299 while (queue.pop()) {
300 }
301 while (released_buffers.pop()) {
302 }
303 released_buffer = {};
304 playing_buffer = {};
305 playing_buffer.consumed = true;
306 queued_buffers = 0;
307 } 129 }
308 130
309private: 131private:
310 /** 132 /**
311 * Signal events back to the audio system that a buffer was played/can be filled.
312 *
313 * @param buffer - Consumed audio buffer to be released.
314 */
315 void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) {
316 auto& manager{system.AudioCore().GetAudioManager()};
317 switch (type) {
318 case StreamType::Out:
319 released_buffers.enqueue(buffer);
320 manager.SetEvent(Event::Type::AudioOutManager, true);
321 break;
322 case StreamType::In:
323 released_buffers.enqueue(buffer);
324 manager.SetEvent(Event::Type::AudioInManager, true);
325 break;
326 case StreamType::Render:
327 break;
328 }
329 }
330
331 /**
332 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will 133 * Main callback from SDL. Either expects samples from us (audio render/audio out), or will
333 * provide samples to be copied (audio in). 134 * provide samples to be copied (audio in).
334 * 135 *
@@ -345,122 +146,20 @@ private:
345 146
346 const std::size_t num_channels = impl->GetDeviceChannels(); 147 const std::size_t num_channels = impl->GetDeviceChannels();
347 const std::size_t frame_size = num_channels; 148 const std::size_t frame_size = num_channels;
348 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
349 const std::size_t num_frames{len / num_channels / sizeof(s16)}; 149 const std::size_t num_frames{len / num_channels / sizeof(s16)};
350 size_t frames_written{0};
351 [[maybe_unused]] bool underrun{false};
352 150
353 if (impl->type == StreamType::In) { 151 if (impl->type == StreamType::In) {
354 std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 152 std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream),
355 153 num_frames * frame_size};
356 while (frames_written < num_frames) { 154 impl->ProcessAudioIn(input_buffer, num_frames);
357 auto& playing_buffer{impl->playing_buffer};
358
359 // If the playing buffer has been consumed or has no frames, we need a new one
360 if (playing_buffer.consumed || playing_buffer.frames == 0) {
361 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
362 // If no buffer was available we've underrun, just push the samples and
363 // continue.
364 underrun = true;
365 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
366 (num_frames - frames_written) * frame_size);
367 frames_written = num_frames;
368 continue;
369 } else {
370 impl->queued_buffers--;
371 impl->SignalEvent(impl->playing_buffer);
372 }
373 }
374
375 // Get the minimum frames available between the currently playing buffer, and the
376 // amount we have left to fill
377 size_t frames_available{
378 std::min(playing_buffer.frames - playing_buffer.frames_played,
379 num_frames - frames_written)};
380
381 impl->samples_buffer.Push(&input_buffer[frames_written * frame_size],
382 frames_available * frame_size);
383
384 frames_written += frames_available;
385 playing_buffer.frames_played += frames_available;
386
387 // If that's all the frames in the current buffer, add its samples and mark it as
388 // consumed
389 if (playing_buffer.frames_played >= playing_buffer.frames) {
390 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
391 impl->playing_buffer.consumed = true;
392 }
393 }
394
395 std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size],
396 frame_size_bytes);
397 } else { 155 } else {
398 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; 156 std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size};
399 157 impl->ProcessAudioOutAndRender(output_buffer, num_frames);
400 while (frames_written < num_frames) {
401 auto& playing_buffer{impl->playing_buffer};
402
403 // If the playing buffer has been consumed or has no frames, we need a new one
404 if (playing_buffer.consumed || playing_buffer.frames == 0) {
405 if (!impl->queue.try_dequeue(impl->playing_buffer)) {
406 // If no buffer was available we've underrun, fill the remaining buffer with
407 // the last written frame and continue.
408 underrun = true;
409 for (size_t i = frames_written; i < num_frames; i++) {
410 std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0],
411 frame_size_bytes);
412 }
413 frames_written = num_frames;
414 continue;
415 } else {
416 impl->queued_buffers--;
417 impl->SignalEvent(impl->playing_buffer);
418 }
419 }
420
421 // Get the minimum frames available between the currently playing buffer, and the
422 // amount we have left to fill
423 size_t frames_available{
424 std::min(playing_buffer.frames - playing_buffer.frames_played,
425 num_frames - frames_written)};
426
427 impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size],
428 frames_available * frame_size);
429
430 frames_written += frames_available;
431 playing_buffer.frames_played += frames_available;
432
433 // If that's all the frames in the current buffer, add its samples and mark it as
434 // consumed
435 if (playing_buffer.frames_played >= playing_buffer.frames) {
436 impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels);
437 impl->playing_buffer.consumed = true;
438 }
439 }
440
441 std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
442 frame_size_bytes);
443 } 158 }
444 } 159 }
445 160
446 /// SDL device id of the opened input/output device 161 /// SDL device id of the opened input/output device
447 SDL_AudioDeviceID device{}; 162 SDL_AudioDeviceID device{};
448 /// Type of this stream
449 StreamType type;
450 /// Core system
451 Core::System& system;
452 /// Ring buffer of the samples waiting to be played or consumed
453 Common::RingBuffer<s16, 0x10000> samples_buffer;
454 /// Audio buffers queued and waiting to play
455 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue;
456 /// The currently-playing audio buffer
457 ::AudioCore::Sink::SinkBuffer playing_buffer{};
458 /// Audio buffers which have been played and are in queue to be released by the audio system
459 Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{};
460 /// Currently released buffer waiting to be taken by the audio system
461 ::AudioCore::Sink::SinkBuffer released_buffer{};
462 /// The last played (or received) frame of audio, used when the callback underruns
463 std::array<s16, MaxChannels> last_frame{};
464}; 163};
465 164
466SDLSink::SDLSink(std::string_view target_device_name) { 165SDLSink::SDLSink(std::string_view target_device_name) {
@@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) {
482 181
483SDLSink::~SDLSink() = default; 182SDLSink::~SDLSink() = default;
484 183
485SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, 184SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels,
486 const std::string&, const StreamType type) { 185 const std::string&, StreamType type) {
487 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( 186 SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
488 device_channels, system_channels, output_device, input_device, type, system)); 187 device_channels, system_channels, output_device, input_device, type, system));
489 return stream.get(); 188 return stream.get();
490} 189}
491 190
492void SDLSink::CloseStream(const SinkStream* stream) { 191void SDLSink::CloseStream(SinkStream* stream) {
493 for (size_t i = 0; i < sink_streams.size(); i++) { 192 for (size_t i = 0; i < sink_streams.size(); i++) {
494 if (sink_streams[i].get() == stream) { 193 if (sink_streams[i].get() == stream) {
495 sink_streams[i].reset(); 194 sink_streams[i].reset();
@@ -503,18 +202,6 @@ void SDLSink::CloseStreams() {
503 sink_streams.clear(); 202 sink_streams.clear();
504} 203}
505 204
506void SDLSink::PauseStreams() {
507 for (auto& stream : sink_streams) {
508 stream->Stop();
509 }
510}
511
512void SDLSink::UnpauseStreams() {
513 for (auto& stream : sink_streams) {
514 stream->Start();
515 }
516}
517
518f32 SDLSink::GetDeviceVolume() const { 205f32 SDLSink::GetDeviceVolume() const {
519 if (sink_streams.empty()) { 206 if (sink_streams.empty()) {
520 return 1.0f; 207 return 1.0f;
@@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const {
523 return sink_streams[0]->GetDeviceVolume(); 210 return sink_streams[0]->GetDeviceVolume();
524} 211}
525 212
526void SDLSink::SetDeviceVolume(const f32 volume) { 213void SDLSink::SetDeviceVolume(f32 volume) {
527 for (auto& stream : sink_streams) { 214 for (auto& stream : sink_streams) {
528 stream->SetDeviceVolume(volume); 215 stream->SetDeviceVolume(volume);
529 } 216 }
530} 217}
531 218
532void SDLSink::SetSystemVolume(const f32 volume) { 219void SDLSink::SetSystemVolume(f32 volume) {
533 for (auto& stream : sink_streams) { 220 for (auto& stream : sink_streams) {
534 stream->SetSystemVolume(volume); 221 stream->SetSystemVolume(volume);
535 } 222 }
536} 223}
537 224
538std::vector<std::string> ListSDLSinkDevices(const bool capture) { 225std::vector<std::string> ListSDLSinkDevices(bool capture) {
539 std::vector<std::string> device_list; 226 std::vector<std::string> device_list;
540 227
541 if (!SDL_WasInit(SDL_INIT_AUDIO)) { 228 if (!SDL_WasInit(SDL_INIT_AUDIO)) {
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h
index 186bc2fa3..f01eddc1b 100644
--- a/src/audio_core/sink/sdl2_sink.h
+++ b/src/audio_core/sink/sdl2_sink.h
@@ -32,8 +32,7 @@ public:
32 * May differ from the device's channel count. 32 * May differ from the device's channel count.
33 * @param name - Name of this stream. 33 * @param name - Name of this stream.
34 * @param type - Type of this stream, render/in/out. 34 * @param type - Type of this stream, render/in/out.
35 * @param event - Audio render only, a signal used to prevent the renderer running too 35 *
36 * fast.
37 * @return A pointer to the created SinkStream 36 * @return A pointer to the created SinkStream
38 */ 37 */
39 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 38 SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
@@ -44,7 +43,7 @@ public:
44 * 43 *
45 * @param stream - The stream to close. 44 * @param stream - The stream to close.
46 */ 45 */
47 void CloseStream(const SinkStream* stream) override; 46 void CloseStream(SinkStream* stream) override;
48 47
49 /** 48 /**
50 * Close all streams. 49 * Close all streams.
@@ -52,16 +51,6 @@ public:
52 void CloseStreams() override; 51 void CloseStreams() override;
53 52
54 /** 53 /**
55 * Pause all streams.
56 */
57 void PauseStreams() override;
58
59 /**
60 * Unpause all streams.
61 */
62 void UnpauseStreams() override;
63
64 /**
65 * Get the device volume. Set from calls to the IAudioDevice service. 54 * Get the device volume. Set from calls to the IAudioDevice service.
66 * 55 *
67 * @return Volume of the device. 56 * @return Volume of the device.
@@ -92,7 +81,7 @@ private:
92}; 81};
93 82
94/** 83/**
95 * Get a list of conencted devices from Cubeb. 84 * Get a list of connected devices from SDL.
96 * 85 *
97 * @param capture - Return input (capture) devices if true, otherwise output devices. 86 * @param capture - Return input (capture) devices if true, otherwise output devices.
98 */ 87 */
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h
index 91fe455e4..f28c6d126 100644
--- a/src/audio_core/sink/sink.h
+++ b/src/audio_core/sink/sink.h
@@ -32,7 +32,7 @@ public:
32 * 32 *
33 * @param stream - The stream to close. 33 * @param stream - The stream to close.
34 */ 34 */
35 virtual void CloseStream(const SinkStream* stream) = 0; 35 virtual void CloseStream(SinkStream* stream) = 0;
36 36
37 /** 37 /**
38 * Close all streams. 38 * Close all streams.
@@ -40,16 +40,6 @@ public:
40 virtual void CloseStreams() = 0; 40 virtual void CloseStreams() = 0;
41 41
42 /** 42 /**
43 * Pause all streams.
44 */
45 virtual void PauseStreams() = 0;
46
47 /**
48 * Unpause all streams.
49 */
50 virtual void UnpauseStreams() = 0;
51
52 /**
53 * Create a new sink stream, kept within this sink, with a pointer returned for use. 43 * Create a new sink stream, kept within this sink, with a pointer returned for use.
54 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. 44 * Do not free the returned pointer. When done with the stream, call CloseStream on the sink.
55 * 45 *
@@ -58,8 +48,7 @@ public:
58 * May differ from the device's channel count. 48 * May differ from the device's channel count.
59 * @param name - Name of this stream. 49 * @param name - Name of this stream.
60 * @param type - Type of this stream, render/in/out. 50 * @param type - Type of this stream, render/in/out.
61 * @param event - Audio render only, a signal used to prevent the renderer running too 51 *
62 * fast.
63 * @return A pointer to the created SinkStream 52 * @return A pointer to the created SinkStream
64 */ 53 */
65 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, 54 virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels,
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp
index 253c0fd1e..67bdab779 100644
--- a/src/audio_core/sink/sink_details.cpp
+++ b/src/audio_core/sink/sink_details.cpp
@@ -5,7 +5,7 @@
5#include <memory> 5#include <memory>
6#include <string> 6#include <string>
7#include <vector> 7#include <vector>
8#include "audio_core/sink/null_sink.h" 8
9#include "audio_core/sink/sink_details.h" 9#include "audio_core/sink/sink_details.h"
10#ifdef HAVE_CUBEB 10#ifdef HAVE_CUBEB
11#include "audio_core/sink/cubeb_sink.h" 11#include "audio_core/sink/cubeb_sink.h"
@@ -13,6 +13,7 @@
13#ifdef HAVE_SDL2 13#ifdef HAVE_SDL2
14#include "audio_core/sink/sdl2_sink.h" 14#include "audio_core/sink/sdl2_sink.h"
15#endif 15#endif
16#include "audio_core/sink/null_sink.h"
16#include "common/logging/log.h" 17#include "common/logging/log.h"
17 18
18namespace AudioCore::Sink { 19namespace AudioCore::Sink {
@@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) {
59 60
60 if (sink_id == "auto" || iter == std::end(sink_details)) { 61 if (sink_id == "auto" || iter == std::end(sink_details)) {
61 if (sink_id != "auto") { 62 if (sink_id != "auto") {
62 LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", 63 LOG_ERROR(Audio, "Invalid sink_id {}", sink_id);
63 sink_id);
64 } 64 }
65 // Auto-select. 65 // Auto-select.
66 // sink_details is ordered in terms of desirability, with the best choice at the front. 66 // sink_details is ordered in terms of desirability, with the best choice at the front.
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp
new file mode 100644
index 000000000..37fe725e4
--- /dev/null
+++ b/src/audio_core/sink/sink_stream.cpp
@@ -0,0 +1,279 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <atomic>
6#include <memory>
7#include <span>
8#include <vector>
9
10#include "audio_core/audio_core.h"
11#include "audio_core/common/common.h"
12#include "audio_core/sink/sink_stream.h"
13#include "common/common_types.h"
14#include "common/fixed_point.h"
15#include "common/settings.h"
16#include "core/core.h"
17
18namespace AudioCore::Sink {
19
20void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) {
21 if (type == StreamType::In) {
22 queue.enqueue(buffer);
23 queued_buffers++;
24 return;
25 }
26
27 constexpr s32 min{std::numeric_limits<s16>::min()};
28 constexpr s32 max{std::numeric_limits<s16>::max()};
29
30 auto yuzu_volume{Settings::Volume()};
31 if (yuzu_volume > 1.0f) {
32 yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume);
33 }
34 auto volume{system_volume * device_volume * yuzu_volume};
35
36 if (system_channels == 6 && device_channels == 2) {
37 // We're given 6 channels, but our device only outputs 2, so downmix.
38 constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f};
39
40 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
41 read_index += system_channels, write_index += device_channels) {
42 const auto left_sample{
43 ((Common::FixedPoint<49, 15>(
44 samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
45 down_mix_coeff[0] +
46 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
47 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
48 samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) *
49 volume)
50 .to_int()};
51
52 const auto right_sample{
53 ((Common::FixedPoint<49, 15>(
54 samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
55 down_mix_coeff[0] +
56 samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] +
57 samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] +
58 samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) *
59 volume)
60 .to_int()};
61
62 samples[write_index + static_cast<u32>(Channels::FrontLeft)] =
63 static_cast<s16>(std::clamp(left_sample, min, max));
64 samples[write_index + static_cast<u32>(Channels::FrontRight)] =
65 static_cast<s16>(std::clamp(right_sample, min, max));
66 }
67
68 samples.resize(samples.size() / system_channels * device_channels);
69
70 } else if (system_channels == 2 && device_channels == 6) {
71 // We need moar samples! Not all games will provide 6 channel audio.
72 // TODO: Implement some upmixing here. Currently just passthrough, with other
73 // channels left as silence.
74 std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0);
75
76 for (u32 read_index = 0, write_index = 0; read_index < samples.size();
77 read_index += system_channels, write_index += device_channels) {
78 const auto left_sample{static_cast<s16>(std::clamp(
79 static_cast<s32>(
80 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) *
81 volume),
82 min, max))};
83
84 new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample;
85
86 const auto right_sample{static_cast<s16>(std::clamp(
87 static_cast<s32>(
88 static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) *
89 volume),
90 min, max))};
91
92 new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample;
93 }
94 samples = std::move(new_samples);
95
96 } else if (volume != 1.0f) {
97 for (u32 i = 0; i < samples.size(); i++) {
98 samples[i] = static_cast<s16>(
99 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
100 }
101 }
102
103 samples_buffer.Push(samples);
104 queue.enqueue(buffer);
105 queued_buffers++;
106}
107
108std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) {
109 constexpr s32 min = std::numeric_limits<s16>::min();
110 constexpr s32 max = std::numeric_limits<s16>::max();
111
112 auto samples{samples_buffer.Pop(num_samples)};
113
114 // TODO: Up-mix to 6 channels if the game expects it.
115 // For audio input this is unlikely to ever be the case though.
116
117 // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here.
118 // TODO: Play with this and find something that works better.
119 auto volume{system_volume * device_volume * 8};
120 for (u32 i = 0; i < samples.size(); i++) {
121 samples[i] = static_cast<s16>(
122 std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max));
123 }
124
125 if (samples.size() < num_samples) {
126 samples.resize(num_samples, 0);
127 }
128 return samples;
129}
130
131void SinkStream::ClearQueue() {
132 samples_buffer.Pop();
133 while (queue.pop()) {
134 }
135 queued_buffers = 0;
136 playing_buffer = {};
137 playing_buffer.consumed = true;
138}
139
140void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) {
141 const std::size_t num_channels = GetDeviceChannels();
142 const std::size_t frame_size = num_channels;
143 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
144 size_t frames_written{0};
145
146 // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
147 // paused and we'll desync, so just return.
148 if (system.IsPaused() || system.IsShuttingDown()) {
149 return;
150 }
151
152 if (queued_buffers > max_queue_size) {
153 Stall();
154 }
155
156 while (frames_written < num_frames) {
157 // If the playing buffer has been consumed or has no frames, we need a new one
158 if (playing_buffer.consumed || playing_buffer.frames == 0) {
159 if (!queue.try_dequeue(playing_buffer)) {
160 // If no buffer was available we've underrun, just push the samples and
161 // continue.
162 samples_buffer.Push(&input_buffer[frames_written * frame_size],
163 (num_frames - frames_written) * frame_size);
164 frames_written = num_frames;
165 continue;
166 }
167 // Successfully dequeued a new buffer.
168 queued_buffers--;
169 }
170
171 // Get the minimum frames available between the currently playing buffer, and the
172 // amount we have left to fill
173 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
174 num_frames - frames_written)};
175
176 samples_buffer.Push(&input_buffer[frames_written * frame_size],
177 frames_available * frame_size);
178
179 frames_written += frames_available;
180 playing_buffer.frames_played += frames_available;
181
182 // If that's all the frames in the current buffer, add its samples and mark it as
183 // consumed
184 if (playing_buffer.frames_played >= playing_buffer.frames) {
185 playing_buffer.consumed = true;
186 }
187 }
188
189 std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes);
190
191 if (queued_buffers <= max_queue_size) {
192 Unstall();
193 }
194}
195
196void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) {
197 const std::size_t num_channels = GetDeviceChannels();
198 const std::size_t frame_size = num_channels;
199 const std::size_t frame_size_bytes = frame_size * sizeof(s16);
200 size_t frames_written{0};
201
202 // If we're paused or going to shut down, we don't want to consume buffers as coretiming is
203 // paused and we'll desync, so just play silence.
204 if (system.IsPaused() || system.IsShuttingDown()) {
205 constexpr std::array<s16, 6> silence{};
206 for (size_t i = frames_written; i < num_frames; i++) {
207 std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes);
208 }
209 return;
210 }
211
212 // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get
213 // queued up (30+) but not all at once, which causes constant stalling here, so just let the
214 // video play out without attempting to stall.
215 // Can hopefully remove this later with a more complete NVDEC implementation.
216 const auto nvdec_active{system.AudioCore().IsNVDECActive()};
217 if (!nvdec_active && queued_buffers > max_queue_size) {
218 Stall();
219 }
220
221 while (frames_written < num_frames) {
222 // If the playing buffer has been consumed or has no frames, we need a new one
223 if (playing_buffer.consumed || playing_buffer.frames == 0) {
224 if (!queue.try_dequeue(playing_buffer)) {
225 // If no buffer was available we've underrun, fill the remaining buffer with
226 // the last written frame and continue.
227 for (size_t i = frames_written; i < num_frames; i++) {
228 std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes);
229 }
230 frames_written = num_frames;
231 continue;
232 }
233 // Successfully dequeued a new buffer.
234 queued_buffers--;
235 }
236
237 // Get the minimum frames available between the currently playing buffer, and the
238 // amount we have left to fill
239 size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played,
240 num_frames - frames_written)};
241
242 samples_buffer.Pop(&output_buffer[frames_written * frame_size],
243 frames_available * frame_size);
244
245 frames_written += frames_available;
246 playing_buffer.frames_played += frames_available;
247
248 // If that's all the frames in the current buffer, add its samples and mark it as
249 // consumed
250 if (playing_buffer.frames_played >= playing_buffer.frames) {
251 playing_buffer.consumed = true;
252 }
253 }
254
255 std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size],
256 frame_size_bytes);
257
258 if (stalled && queued_buffers <= max_queue_size) {
259 Unstall();
260 }
261}
262
263void SinkStream::Stall() {
264 if (stalled) {
265 return;
266 }
267 stalled = true;
268 system.StallProcesses();
269}
270
271void SinkStream::Unstall() {
272 if (!stalled) {
273 return;
274 }
275 system.UnstallProcesses();
276 stalled = false;
277}
278
279} // namespace AudioCore::Sink
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h
index 17ed6593f..38a4b2f51 100644
--- a/src/audio_core/sink/sink_stream.h
+++ b/src/audio_core/sink/sink_stream.h
@@ -3,12 +3,20 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <array>
6#include <atomic> 7#include <atomic>
7#include <memory> 8#include <memory>
9#include <span>
8#include <vector> 10#include <vector>
9 11
10#include "audio_core/common/common.h" 12#include "audio_core/common/common.h"
11#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/reader_writer_queue.h"
15#include "common/ring_buffer.h"
16
17namespace Core {
18class System;
19} // namespace Core
12 20
13namespace AudioCore::Sink { 21namespace AudioCore::Sink {
14 22
@@ -34,20 +42,24 @@ struct SinkBuffer {
34 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer 42 * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer
35 * has been consumed. 43 * has been consumed.
36 * 44 *
37 * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the 45 * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were
38 * buffers, skipping a buffer will result in all following buffers to never release. 46 * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to
47 * never release.
39 * 48 *
40 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this 49 * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this
41 * is what games do), or call ClearQueue to flush all of the buffers without a full restart. 50 * is what games do), or call ClearQueue to flush all of the buffers without a full restart.
42 */ 51 */
43class SinkStream { 52class SinkStream {
44public: 53public:
45 virtual ~SinkStream() = default; 54 explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {}
55 virtual ~SinkStream() {
56 Unstall();
57 }
46 58
47 /** 59 /**
48 * Finalize the sink stream. 60 * Finalize the sink stream.
49 */ 61 */
50 virtual void Finalize() = 0; 62 virtual void Finalize() {}
51 63
52 /** 64 /**
53 * Start the sink stream. 65 * Start the sink stream.
@@ -55,48 +67,19 @@ public:
55 * @param resume - Set to true if this is resuming the stream a previously-active stream. 67 * @param resume - Set to true if this is resuming the stream a previously-active stream.
56 * Default false. 68 * Default false.
57 */ 69 */
58 virtual void Start(bool resume = false) = 0; 70 virtual void Start(bool resume = false) {}
59 71
60 /** 72 /**
61 * Stop the sink stream. 73 * Stop the sink stream.
62 */ 74 */
63 virtual void Stop() = 0; 75 virtual void Stop() {}
64
65 /**
66 * Append a new buffer and its samples to a waiting queue to play.
67 *
68 * @param buffer - Audio buffer information to be queued.
69 * @param samples - The s16 samples to be queue for playback.
70 */
71 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0;
72
73 /**
74 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
75 *
76 * @param num_samples - Maximum number of samples to receive.
77 * @return Vector of recorded samples. May have fewer than num_samples.
78 */
79 virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0;
80
81 /**
82 * Check if a certain buffer has been consumed (fully played).
83 *
84 * @param tag - Unique tag of a buffer to check for.
85 * @return True if the buffer has been played, otherwise false.
86 */
87 virtual bool IsBufferConsumed(u64 tag) = 0;
88
89 /**
90 * Empty out the buffer queue.
91 */
92 virtual void ClearQueue() = 0;
93 76
94 /** 77 /**
95 * Check if the stream is paused. 78 * Check if the stream is paused.
96 * 79 *
97 * @return True if paused, otherwise false. 80 * @return True if paused, otherwise false.
98 */ 81 */
99 bool IsPaused() { 82 bool IsPaused() const {
100 return paused; 83 return paused;
101 } 84 }
102 85
@@ -128,34 +111,6 @@ public:
128 } 111 }
129 112
130 /** 113 /**
131 * Get the total number of samples played by this stream.
132 *
133 * @return Number of samples played.
134 */
135 u64 GetPlayedSampleCount() const {
136 return played_sample_count;
137 }
138
139 /**
140 * Set the number of samples played.
141 * This is started and stopped on system start/stop.
142 *
143 * @param played_sample_count_ - Number of samples to set.
144 */
145 void SetPlayedSampleCount(u64 played_sample_count_) {
146 played_sample_count = played_sample_count_;
147 }
148
149 /**
150 * Add to the played sample count.
151 *
152 * @param num_samples - Number of samples to add.
153 */
154 void AddPlayedSampleCount(u64 num_samples) {
155 played_sample_count += num_samples;
156 }
157
158 /**
159 * Get the system volume. 114 * Get the system volume.
160 * 115 *
161 * @return The current system volume. 116 * @return The current system volume.
@@ -196,27 +151,97 @@ public:
196 * 151 *
197 * @return The number of queued buffers. 152 * @return The number of queued buffers.
198 */ 153 */
199 u32 GetQueueSize() { 154 u32 GetQueueSize() const {
200 return queued_buffers.load(); 155 return queued_buffers.load();
201 } 156 }
202 157
158 /**
159 * Set the maximum buffer queue size.
160 */
161 void SetRingSize(u32 ring_size) {
162 max_queue_size = ring_size;
163 }
164
165 /**
166 * Append a new buffer and its samples to a waiting queue to play.
167 *
168 * @param buffer - Audio buffer information to be queued.
169 * @param samples - The s16 samples to be queue for playback.
170 */
171 virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples);
172
173 /**
174 * Release a buffer. Audio In only, will fill a buffer with recorded samples.
175 *
176 * @param num_samples - Maximum number of samples to receive.
177 * @return Vector of recorded samples. May have fewer than num_samples.
178 */
179 virtual std::vector<s16> ReleaseBuffer(u64 num_samples);
180
181 /**
182 * Empty out the buffer queue.
183 */
184 void ClearQueue();
185
186 /**
187 * Callback for AudioIn.
188 *
189 * @param input_buffer - Input buffer to be filled with samples.
190 * @param num_frames - Number of frames to be filled.
191 */
192 void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames);
193
194 /**
195 * Callback for AudioOut and AudioRenderer.
196 *
197 * @param output_buffer - Output buffer to be filled with samples.
198 * @param num_frames - Number of frames to be filled.
199 */
200 void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames);
201
202 /**
203 * Stall core processes if the audio thread falls too far behind.
204 */
205 void Stall();
206
207 /**
208 * Unstall core processes.
209 */
210 void Unstall();
211
203protected: 212protected:
204 /// Number of buffers waiting to be played 213 /// Core system
205 std::atomic<u32> queued_buffers{}; 214 Core::System& system;
206 /// Total samples played by this stream 215 /// Type of this stream
207 std::atomic<u64> played_sample_count{}; 216 StreamType type;
208 /// Set by the audio render/in/out system which uses this stream 217 /// Set by the audio render/in/out system which uses this stream
209 f32 system_volume{1.0f};
210 /// Set via IAudioDevice service calls
211 f32 device_volume{1.0f};
212 /// Set by the audio render/in/out systen which uses this stream
213 u32 system_channels{2}; 218 u32 system_channels{2};
214 /// Channels supported by hardware 219 /// Channels supported by hardware
215 u32 device_channels{2}; 220 u32 device_channels{2};
216 /// Is this stream currently paused? 221 /// Is this stream currently paused?
217 std::atomic<bool> paused{true}; 222 std::atomic<bool> paused{true};
218 /// Was this stream previously playing? 223 /// Name of this stream
219 std::atomic<bool> was_playing{false}; 224 std::string name{};
225
226private:
227 /// Ring buffer of the samples waiting to be played or consumed
228 Common::RingBuffer<s16, 0x10000> samples_buffer;
229 /// Audio buffers queued and waiting to play
230 Common::ReaderWriterQueue<SinkBuffer> queue;
231 /// The currently-playing audio buffer
232 SinkBuffer playing_buffer{};
233 /// The last played (or received) frame of audio, used when the callback underruns
234 std::array<s16, MaxChannels> last_frame{};
235 /// Number of buffers waiting to be played
236 std::atomic<u32> queued_buffers{};
237 /// The ring size for audio out buffers (usually 4, rarely 2 or 8)
238 u32 max_queue_size{};
239 /// Set by the audio render/in/out system which uses this stream
240 f32 system_volume{1.0f};
241 /// Set via IAudioDevice service calls
242 f32 device_volume{1.0f};
243 /// True if coretiming has been stalled
244 bool stalled{false};
220}; 245};
221 246
222using SinkStreamPtr = std::unique_ptr<SinkStream>; 247using SinkStreamPtr = std::unique_ptr<SinkStream>;