summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/CMakeLists.txt2
-rw-r--r--src/audio_core/hle/dsp.cpp59
-rw-r--r--src/audio_core/hle/dsp.h2
-rw-r--r--src/audio_core/hle/mixers.cpp201
-rw-r--r--src/audio_core/hle/mixers.h63
5 files changed, 317 insertions, 10 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index eba0a5697..a72a907ef 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -3,6 +3,7 @@ set(SRCS
3 codec.cpp 3 codec.cpp
4 hle/dsp.cpp 4 hle/dsp.cpp
5 hle/filter.cpp 5 hle/filter.cpp
6 hle/mixers.cpp
6 hle/pipe.cpp 7 hle/pipe.cpp
7 hle/source.cpp 8 hle/source.cpp
8 interpolate.cpp 9 interpolate.cpp
@@ -16,6 +17,7 @@ set(HEADERS
16 hle/common.h 17 hle/common.h
17 hle/dsp.h 18 hle/dsp.h
18 hle/filter.h 19 hle/filter.h
20 hle/mixers.h
19 hle/pipe.h 21 hle/pipe.h
20 hle/source.h 22 hle/source.h
21 interpolate.h 23 interpolate.h
diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp
index 5113ad8ca..0640e1eff 100644
--- a/src/audio_core/hle/dsp.cpp
+++ b/src/audio_core/hle/dsp.cpp
@@ -6,6 +6,7 @@
6#include <memory> 6#include <memory>
7 7
8#include "audio_core/hle/dsp.h" 8#include "audio_core/hle/dsp.h"
9#include "audio_core/hle/mixers.h"
9#include "audio_core/hle/pipe.h" 10#include "audio_core/hle/pipe.h"
10#include "audio_core/hle/source.h" 11#include "audio_core/hle/source.h"
11#include "audio_core/sink.h" 12#include "audio_core/sink.h"
@@ -14,6 +15,8 @@
14namespace DSP { 15namespace DSP {
15namespace HLE { 16namespace HLE {
16 17
18// Region management
19
17std::array<SharedMemory, 2> g_regions; 20std::array<SharedMemory, 2> g_regions;
18 21
19static size_t CurrentRegionIndex() { 22static size_t CurrentRegionIndex() {
@@ -41,16 +44,57 @@ static SharedMemory& WriteRegion() {
41 return g_regions[1 - CurrentRegionIndex()]; 44 return g_regions[1 - CurrentRegionIndex()];
42} 45}
43 46
47// Audio processing and mixing
48
44static std::array<Source, num_sources> sources = { 49static std::array<Source, num_sources> sources = {
45 Source(0), Source(1), Source(2), Source(3), Source(4), Source(5), 50 Source(0), Source(1), Source(2), Source(3), Source(4), Source(5),
46 Source(6), Source(7), Source(8), Source(9), Source(10), Source(11), 51 Source(6), Source(7), Source(8), Source(9), Source(10), Source(11),
47 Source(12), Source(13), Source(14), Source(15), Source(16), Source(17), 52 Source(12), Source(13), Source(14), Source(15), Source(16), Source(17),
48 Source(18), Source(19), Source(20), Source(21), Source(22), Source(23) 53 Source(18), Source(19), Source(20), Source(21), Source(22), Source(23)
49}; 54};
55static Mixers mixers;
56
57static StereoFrame16 GenerateCurrentFrame() {
58 SharedMemory& read = ReadRegion();
59 SharedMemory& write = WriteRegion();
60
61 std::array<QuadFrame32, 3> intermediate_mixes = {};
62
63 // Generate intermediate mixes
64 for (size_t i = 0; i < num_sources; i++) {
65 write.source_statuses.status[i] = sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]);
66 for (size_t mix = 0; mix < 3; mix++) {
67 sources[i].MixInto(intermediate_mixes[mix], mix);
68 }
69 }
70
71 // Generate final mix
72 write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, write.intermediate_mix_samples, intermediate_mixes);
73
74 StereoFrame16 output_frame = mixers.GetOutput();
75
76 // Write current output frame to the shared memory region
77 for (size_t samplei = 0; samplei < output_frame.size(); samplei++) {
78 for (size_t channeli = 0; channeli < output_frame[0].size(); channeli++) {
79 write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]);
80 }
81 }
82
83 return output_frame;
84}
85
86// Audio output
50 87
51static std::unique_ptr<AudioCore::Sink> sink; 88static std::unique_ptr<AudioCore::Sink> sink;
52static AudioCore::TimeStretcher time_stretcher; 89static AudioCore::TimeStretcher time_stretcher;
53 90
91static void OutputCurrentFrame(const StereoFrame16& frame) {
92 time_stretcher.AddSamples(&frame[0][0], frame.size());
93 sink->EnqueueSamples(time_stretcher.Process(sink->SamplesInQueue()));
94}
95
96// Public Interface
97
54void Init() { 98void Init() {
55 DSP::HLE::ResetPipes(); 99 DSP::HLE::ResetPipes();
56 100
@@ -58,6 +102,8 @@ void Init() {
58 source.Reset(); 102 source.Reset();
59 } 103 }
60 104
105 mixers.Reset();
106
61 time_stretcher.Reset(); 107 time_stretcher.Reset();
62 if (sink) { 108 if (sink) {
63 time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); 109 time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate());
@@ -75,17 +121,12 @@ void Shutdown() {
75} 121}
76 122
77bool Tick() { 123bool Tick() {
78 SharedMemory& read = ReadRegion(); 124 StereoFrame16 current_frame = {};
79 SharedMemory& write = WriteRegion();
80 125
81 std::array<QuadFrame32, 3> intermediate_mixes = {}; 126 // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to shared memory region)
127 current_frame = GenerateCurrentFrame();
82 128
83 for (size_t i = 0; i < num_sources; i++) { 129 OutputCurrentFrame(current_frame);
84 write.source_statuses.status[i] = sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]);
85 for (size_t mix = 0; mix < 3; mix++) {
86 sources[i].MixInto(intermediate_mixes[mix], mix);
87 }
88 }
89 130
90 return true; 131 return true;
91} 132}
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h
index f6e53f68f..9275cd7de 100644
--- a/src/audio_core/hle/dsp.h
+++ b/src/audio_core/hle/dsp.h
@@ -428,7 +428,7 @@ ASSERT_DSP_STRUCT(DspStatus, 32);
428/// Final mixed output in PCM16 stereo format, what you hear out of the speakers. 428/// Final mixed output in PCM16 stereo format, what you hear out of the speakers.
429/// When the application writes to this region it has no effect. 429/// When the application writes to this region it has no effect.
430struct FinalMixSamples { 430struct FinalMixSamples {
431 s16_le pcm16[2 * samples_per_frame]; 431 s16_le pcm16[samples_per_frame][2];
432}; 432};
433ASSERT_DSP_STRUCT(FinalMixSamples, 640); 433ASSERT_DSP_STRUCT(FinalMixSamples, 640);
434 434
diff --git a/src/audio_core/hle/mixers.cpp b/src/audio_core/hle/mixers.cpp
new file mode 100644
index 000000000..18335f7f0
--- /dev/null
+++ b/src/audio_core/hle/mixers.cpp
@@ -0,0 +1,201 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cstddef>
6
7#include "audio_core/hle/common.h"
8#include "audio_core/hle/dsp.h"
9#include "audio_core/hle/mixers.h"
10
11#include "common/assert.h"
12#include "common/logging/log.h"
13#include "common/math_util.h"
14
15namespace DSP {
16namespace HLE {
17
18void Mixers::Reset() {
19 current_frame.fill({});
20 state = {};
21}
22
23DspStatus Mixers::Tick(DspConfiguration& config,
24 const IntermediateMixSamples& read_samples,
25 IntermediateMixSamples& write_samples,
26 const std::array<QuadFrame32, 3>& input)
27{
28 ParseConfig(config);
29
30 AuxReturn(read_samples);
31 AuxSend(write_samples, input);
32
33 MixCurrentFrame();
34
35 return GetCurrentStatus();
36}
37
38void Mixers::ParseConfig(DspConfiguration& config) {
39 if (!config.dirty_raw) {
40 return;
41 }
42
43 if (config.mixer1_enabled_dirty) {
44 config.mixer1_enabled_dirty.Assign(0);
45 state.mixer1_enabled = config.mixer1_enabled != 0;
46 LOG_TRACE(Audio_DSP, "mixers mixer1_enabled = %hu", config.mixer1_enabled);
47 }
48
49 if (config.mixer2_enabled_dirty) {
50 config.mixer2_enabled_dirty.Assign(0);
51 state.mixer2_enabled = config.mixer2_enabled != 0;
52 LOG_TRACE(Audio_DSP, "mixers mixer2_enabled = %hu", config.mixer2_enabled);
53 }
54
55 if (config.volume_0_dirty) {
56 config.volume_0_dirty.Assign(0);
57 state.intermediate_mixer_volume[0] = config.volume[0];
58 LOG_TRACE(Audio_DSP, "mixers volume[0] = %f", config.volume[0]);
59 }
60
61 if (config.volume_1_dirty) {
62 config.volume_1_dirty.Assign(0);
63 state.intermediate_mixer_volume[1] = config.volume[1];
64 LOG_TRACE(Audio_DSP, "mixers volume[1] = %f", config.volume[1]);
65 }
66
67 if (config.volume_2_dirty) {
68 config.volume_2_dirty.Assign(0);
69 state.intermediate_mixer_volume[2] = config.volume[2];
70 LOG_TRACE(Audio_DSP, "mixers volume[2] = %f", config.volume[2]);
71 }
72
73 if (config.output_format_dirty) {
74 config.output_format_dirty.Assign(0);
75 state.output_format = config.output_format;
76 LOG_TRACE(Audio_DSP, "mixers output_format = %zu", static_cast<size_t>(config.output_format));
77 }
78
79 if (config.headphones_connected_dirty) {
80 config.headphones_connected_dirty.Assign(0);
81 // Do nothing.
82 // (Note: Whether headphones are connected does affect coefficients used for surround sound.)
83 LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected);
84 }
85
86 if (config.dirty_raw) {
87 LOG_DEBUG(Audio_DSP, "mixers remaining_dirty=%x", config.dirty_raw);
88 }
89
90 config.dirty_raw = 0;
91}
92
93static s16 ClampToS16(s32 value) {
94 return static_cast<s16>(MathUtil::Clamp(value, -32768, 32767));
95}
96
97static std::array<s16, 2> AddAndClampToS16(const std::array<s16, 2>& a, const std::array<s16, 2>& b) {
98 return {
99 ClampToS16(static_cast<s32>(a[0]) + static_cast<s32>(b[0])),
100 ClampToS16(static_cast<s32>(a[1]) + static_cast<s32>(b[1]))
101 };
102}
103
104void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples) {
105 // TODO(merry): Limiter. (Currently we're performing final mixing assuming a disabled limiter.)
106
107 switch (state.output_format) {
108 case OutputFormat::Mono:
109 std::transform(current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
110 [gain](const std::array<s16, 2>& accumulator, const std::array<s32, 4>& sample) -> std::array<s16, 2> {
111 // Downmix to mono
112 s16 mono = ClampToS16(static_cast<s32>((gain * sample[0] + gain * sample[1] + gain * sample[2] + gain * sample[3]) / 2));
113 // Mix into current frame
114 return AddAndClampToS16(accumulator, { mono, mono });
115 });
116 return;
117
118 case OutputFormat::Surround:
119 // TODO(merry): Implement surround sound.
120 // fallthrough
121
122 case OutputFormat::Stereo:
123 std::transform(current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(),
124 [gain](const std::array<s16, 2>& accumulator, const std::array<s32, 4>& sample) -> std::array<s16, 2> {
125 // Downmix to stereo
126 s16 left = ClampToS16(static_cast<s32>(gain * sample[0] + gain * sample[2]));
127 s16 right = ClampToS16(static_cast<s32>(gain * sample[1] + gain * sample[3]));
128 // Mix into current frame
129 return AddAndClampToS16(accumulator, { left, right });
130 });
131 return;
132 }
133
134 UNREACHABLE_MSG("Invalid output_format %zu", static_cast<size_t>(state.output_format));
135}
136
137void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
138 // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
139
140 if (state.mixer1_enabled) {
141 for (size_t sample = 0; sample < samples_per_frame; sample++) {
142 for (size_t channel = 0; channel < 4; channel++) {
143 state.intermediate_mix_buffer[1][sample][channel] = read_samples.mix1.pcm32[channel][sample];
144 }
145 }
146 }
147
148 if (state.mixer2_enabled) {
149 for (size_t sample = 0; sample < samples_per_frame; sample++) {
150 for (size_t channel = 0; channel < 4; channel++) {
151 state.intermediate_mix_buffer[2][sample][channel] = read_samples.mix2.pcm32[channel][sample];
152 }
153 }
154 }
155}
156
157void Mixers::AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input) {
158 // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
159
160 state.intermediate_mix_buffer[0] = input[0];
161
162 if (state.mixer1_enabled) {
163 for (size_t sample = 0; sample < samples_per_frame; sample++) {
164 for (size_t channel = 0; channel < 4; channel++) {
165 write_samples.mix1.pcm32[channel][sample] = input[1][sample][channel];
166 }
167 }
168 } else {
169 state.intermediate_mix_buffer[1] = input[1];
170 }
171
172 if (state.mixer2_enabled) {
173 for (size_t sample = 0; sample < samples_per_frame; sample++) {
174 for (size_t channel = 0; channel < 4; channel++) {
175 write_samples.mix2.pcm32[channel][sample] = input[2][sample][channel];
176 }
177 }
178 } else {
179 state.intermediate_mix_buffer[2] = input[2];
180 }
181}
182
183void Mixers::MixCurrentFrame() {
184 current_frame.fill({});
185
186 for (size_t mix = 0; mix < 3; mix++) {
187 DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix], state.intermediate_mix_buffer[mix]);
188 }
189
190 // TODO(merry): Compressor. (We currently assume a disabled compressor.)
191}
192
193DspStatus Mixers::GetCurrentStatus() const {
194 DspStatus status;
195 status.unknown = 0;
196 status.dropped_frames = 0;
197 return status;
198}
199
200} // namespace HLE
201} // namespace DSP
diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h
new file mode 100644
index 000000000..b52952eb5
--- /dev/null
+++ b/src/audio_core/hle/mixers.h
@@ -0,0 +1,63 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8
9#include "audio_core/hle/common.h"
10#include "audio_core/hle/dsp.h"
11
12namespace DSP {
13namespace HLE {
14
15class Mixers final {
16public:
17 Mixers() {
18 Reset();
19 }
20
21 void Reset();
22
23 DspStatus Tick(DspConfiguration& config,
24 const IntermediateMixSamples& read_samples,
25 IntermediateMixSamples& write_samples,
26 const std::array<QuadFrame32, 3>& input);
27
28 StereoFrame16 GetOutput() const {
29 return current_frame;
30 }
31
32private:
33 StereoFrame16 current_frame = {};
34
35 using OutputFormat = DspConfiguration::OutputFormat;
36
37 struct {
38 std::array<float, 3> intermediate_mixer_volume = {};
39
40 bool mixer1_enabled = false;
41 bool mixer2_enabled = false;
42 std::array<QuadFrame32, 3> intermediate_mix_buffer = {};
43
44 OutputFormat output_format = OutputFormat::Stereo;
45
46 } state;
47
48 /// INTERNAL: Update our internal state based on the current config.
49 void ParseConfig(DspConfiguration& config);
50 /// INTERNAL: Read samples from shared memory that have been modified by the ARM11.
51 void AuxReturn(const IntermediateMixSamples& read_samples);
52 /// INTERNAL: Write samples to shared memory for the ARM11 to modify.
53 void AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input);
54 /// INTERNAL: Mix current_frame.
55 void MixCurrentFrame();
56 /// INTERNAL: Downmix from quadraphonic to stereo based on status.output_format and accumulate into current_frame.
57 void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples);
58 /// INTERNAL: Generate DspStatus based on internal state.
59 DspStatus GetCurrentStatus() const;
60};
61
62} // namespace HLE
63} // namespace DSP