aboutsummaryrefslogtreecommitdiff
path: root/src/audio_core
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core')
-rw-r--r--src/audio_core/CMakeLists.txt17
-rw-r--r--src/audio_core/audio_core.cpp46
-rw-r--r--src/audio_core/audio_core.h7
-rw-r--r--src/audio_core/codec.cpp122
-rw-r--r--src/audio_core/codec.h50
-rw-r--r--src/audio_core/hle/common.h36
-rw-r--r--src/audio_core/hle/dsp.cpp69
-rw-r--r--src/audio_core/hle/dsp.h55
-rw-r--r--src/audio_core/hle/filter.cpp115
-rw-r--r--src/audio_core/hle/filter.h113
-rw-r--r--src/audio_core/hle/pipe.cpp32
-rw-r--r--src/audio_core/hle/pipe.h4
-rw-r--r--src/audio_core/hle/source.cpp320
-rw-r--r--src/audio_core/hle/source.h144
-rw-r--r--src/audio_core/interpolate.cpp85
-rw-r--r--src/audio_core/interpolate.h41
-rw-r--r--src/audio_core/null_sink.h29
-rw-r--r--src/audio_core/sink_details.cpp18
-rw-r--r--src/audio_core/sink_details.h27
19 files changed, 1268 insertions, 62 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index b0d1c7eb6..4cd7aba67 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -1,16 +1,31 @@
set(SRCS
audio_core.cpp
+ codec.cpp
hle/dsp.cpp
+ hle/filter.cpp
hle/pipe.cpp
+ hle/source.cpp
+ interpolate.cpp
+ sink_details.cpp
)
set(HEADERS
audio_core.h
+ codec.h
+ hle/common.h
hle/dsp.h
+ hle/filter.h
hle/pipe.h
+ hle/source.h
+ interpolate.h
+ null_sink.h
sink.h
+ sink_details.h
)
+include_directories(../../externals/soundtouch/include)
+
create_directory_groups(${SRCS} ${HEADERS})
-add_library(audio_core STATIC ${SRCS} ${HEADERS}) \ No newline at end of file
+add_library(audio_core STATIC ${SRCS} ${HEADERS})
+target_link_libraries(audio_core SoundTouch)
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index 894f46990..d42249ebd 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -2,8 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <memory>
+#include <string>
+
#include "audio_core/audio_core.h"
#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/pipe.h"
+#include "audio_core/null_sink.h"
+#include "audio_core/sink.h"
+#include "audio_core/sink_details.h"
#include "core/core_timing.h"
#include "core/hle/kernel/vm_manager.h"
@@ -17,17 +24,16 @@ static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles
static void AudioTickCallback(u64 /*userdata*/, int cycles_late) {
if (DSP::HLE::Tick()) {
- // HACK: We're not signaling the interrups when they should be, but just firing them all off together.
- // It should be only (interrupt_id = 2, channel_id = 2) that's signalled here.
- // TODO(merry): Understand when the other interrupts are fired.
- DSP_DSP::SignalAllInterrupts();
+ // TODO(merry): Signal all the other interrupts as appropriate.
+ DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Audio);
+ // HACK(merry): Added to prevent regressions. Will remove soon.
+ DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Binary);
}
// Reschedule recurrent event
CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event);
}
-/// Initialise Audio
void Init() {
DSP::HLE::Init();
@@ -35,19 +41,39 @@ void Init() {
CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event);
}
-/// Add DSP address spaces to Process's address space.
void AddAddressSpace(Kernel::VMManager& address_space) {
- auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_region0), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
+ auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite);
- auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_region1), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
+ auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite);
}
-/// Shutdown Audio
+void SelectSink(std::string sink_id) {
+ if (sink_id == "auto") {
+ // Auto-select.
+ // g_sink_details is ordered in terms of desirability, with the best choice at the front.
+ const auto& sink_detail = g_sink_details.front();
+ DSP::HLE::SetSink(sink_detail.factory());
+ return;
+ }
+
+ auto iter = std::find_if(g_sink_details.begin(), g_sink_details.end(), [sink_id](const auto& sink_detail) {
+ return sink_detail.id == sink_id;
+ });
+
+ if (iter == g_sink_details.end()) {
+ LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id");
+ DSP::HLE::SetSink(std::make_unique<NullSink>());
+ return;
+ }
+
+ DSP::HLE::SetSink(iter->factory());
+}
+
void Shutdown() {
CoreTiming::UnscheduleEvent(tick_event, 0);
DSP::HLE::Shutdown();
}
-} //namespace
+} // namespace AudioCore
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h
index 64c330914..f618361f3 100644
--- a/src/audio_core/audio_core.h
+++ b/src/audio_core/audio_core.h
@@ -4,14 +4,14 @@
#pragma once
+#include <string>
+
namespace Kernel {
class VMManager;
}
namespace AudioCore {
-constexpr int num_sources = 24;
-constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate
constexpr int native_sample_rate = 32728; ///< 32kHz
/// Initialise Audio Core
@@ -20,6 +20,9 @@ void Init();
/// Add DSP address spaces to a Process.
void AddAddressSpace(Kernel::VMManager& vm_manager);
+/// Select the sink to use based on sink id.
+void SelectSink(std::string sink_id);
+
/// Shutdown Audio Core
void Shutdown();
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp
new file mode 100644
index 000000000..ab65514b7
--- /dev/null
+++ b/src/audio_core/codec.cpp
@@ -0,0 +1,122 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstddef>
+#include <cstring>
+#include <vector>
+
+#include "audio_core/codec.h"
+
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+
+namespace Codec {
+
+StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state) {
+ // GC-ADPCM with scale factor and variable coefficients.
+ // Frames are 8 bytes long containing 14 samples each.
+ // Samples are 4 bits (one nibble) long.
+
+ constexpr size_t FRAME_LEN = 8;
+ constexpr size_t SAMPLES_PER_FRAME = 14;
+ constexpr std::array<int, 16> SIGNED_NIBBLES {{ 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 }};
+
+ const size_t ret_size = sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two.
+ StereoBuffer16 ret(ret_size);
+
+ int yn1 = state.yn1,
+ yn2 = state.yn2;
+
+ const size_t NUM_FRAMES = (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
+ for (size_t framei = 0; framei < NUM_FRAMES; framei++) {
+ const int frame_header = data[framei * FRAME_LEN];
+ const int scale = 1 << (frame_header & 0xF);
+ const int idx = (frame_header >> 4) & 0x7;
+
+ // Coefficients are fixed point with 11 bits fractional part.
+ const int coef1 = adpcm_coeff[idx * 2 + 0];
+ const int coef2 = adpcm_coeff[idx * 2 + 1];
+
+ // Decodes an audio sample. One nibble produces one sample.
+ const auto decode_sample = [&](const int nibble) -> s16 {
+ const int xn = nibble * scale;
+ // We first transform everything into 11 bit fixed point, perform the second order digital filter, then transform back.
+ // 0x400 == 0.5 in 11 bit fixed point.
+ // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2]
+ int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11;
+ // Clamp to output range.
+ val = MathUtil::Clamp(val, -32768, 32767);
+ // Advance output feedback.
+ yn2 = yn1;
+ yn1 = val;
+ return (s16)val;
+ };
+
+ size_t outputi = framei * SAMPLES_PER_FRAME;
+ size_t datai = framei * FRAME_LEN + 1;
+ for (size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) {
+ const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]);
+ ret[outputi].fill(sample1);
+ outputi++;
+
+ const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]);
+ ret[outputi].fill(sample2);
+ outputi++;
+
+ datai++;
+ }
+ }
+
+ state.yn1 = yn1;
+ state.yn2 = yn2;
+
+ return ret;
+}
+
+static s16 SignExtendS8(u8 x) {
+ // The data is actually signed PCM8.
+ // We sign extend this to signed PCM16.
+ return static_cast<s16>(static_cast<s8>(x));
+}
+
+StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count) {
+ ASSERT(num_channels == 1 || num_channels == 2);
+
+ StereoBuffer16 ret(sample_count);
+
+ if (num_channels == 1) {
+ for (size_t i = 0; i < sample_count; i++) {
+ ret[i].fill(SignExtendS8(data[i]));
+ }
+ } else {
+ for (size_t i = 0; i < sample_count; i++) {
+ ret[i][0] = SignExtendS8(data[i * 2 + 0]);
+ ret[i][1] = SignExtendS8(data[i * 2 + 1]);
+ }
+ }
+
+ return ret;
+}
+
+StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count) {
+ ASSERT(num_channels == 1 || num_channels == 2);
+
+ StereoBuffer16 ret(sample_count);
+
+ if (num_channels == 1) {
+ for (size_t i = 0; i < sample_count; i++) {
+ s16 sample;
+ std::memcpy(&sample, data + i * sizeof(s16), sizeof(s16));
+ ret[i].fill(sample);
+ }
+ } else {
+ std::memcpy(ret.data(), data, sample_count * 2 * sizeof(u16));
+ }
+
+ return ret;
+}
+
+};
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h
new file mode 100644
index 000000000..e695f2edc
--- /dev/null
+++ b/src/audio_core/codec.h
@@ -0,0 +1,50 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Codec {
+
+/// A variable length buffer of signed PCM16 stereo samples.
+using StereoBuffer16 = std::vector<std::array<s16, 2>>;
+
+/// See: Codec::DecodeADPCM
+struct ADPCMState {
+ // Two historical samples from previous processed buffer,
+ // required for ADPCM decoding
+ s16 yn1; ///< y[n-1]
+ s16 yn2; ///< y[n-2]
+};
+
+/**
+ * @param data Pointer to buffer that contains ADPCM data to decode
+ * @param sample_count Length of buffer in terms of number of samples
+ * @param adpcm_coeff ADPCM coefficients
+ * @param state ADPCM state, this is updated with new state
+ * @return Decoded stereo signed PCM16 data, sample_count in length
+ */
+StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state);
+
+/**
+ * @param num_channels Number of channels
+ * @param data Pointer to buffer that contains PCM8 data to decode
+ * @param sample_count Length of buffer in terms of number of samples
+ * @return Decoded stereo signed PCM16 data, sample_count in length
+ */
+StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count);
+
+/**
+ * @param num_channels Number of channels
+ * @param data Pointer to buffer that contains PCM16 data to decode
+ * @param sample_count Length of buffer in terms of number of samples
+ * @return Decoded stereo signed PCM16 data, sample_count in length
+ */
+StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count);
+
+};
diff --git a/src/audio_core/hle/common.h b/src/audio_core/hle/common.h
new file mode 100644
index 000000000..596b67eaf
--- /dev/null
+++ b/src/audio_core/hle/common.h
@@ -0,0 +1,36 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+
+#include "common/common_types.h"
+
+namespace DSP {
+namespace HLE {
+
+constexpr int num_sources = 24;
+constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate
+
+/// The final output to the speakers is stereo. Preprocessing output in Source is also stereo.
+using StereoFrame16 = std::array<std::array<s16, 2>, samples_per_frame>;
+
+/// The DSP is quadraphonic internally.
+using QuadFrame32 = std::array<std::array<s32, 4>, samples_per_frame>;
+
+/**
+ * This performs the filter operation defined by FilterT::ProcessSample on the frame in-place.
+ * FilterT::ProcessSample is called sequentially on the samples.
+ */
+template<typename FrameT, typename FilterT>
+void FilterFrame(FrameT& frame, FilterT& filter) {
+ std::transform(frame.begin(), frame.end(), frame.begin(), [&filter](const auto& sample) {
+ return filter.ProcessSample(sample);
+ });
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/dsp.cpp b/src/audio_core/hle/dsp.cpp
index c89356edc..0cdbdb06a 100644
--- a/src/audio_core/hle/dsp.cpp
+++ b/src/audio_core/hle/dsp.cpp
@@ -2,40 +2,81 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <memory>
+
#include "audio_core/hle/dsp.h"
#include "audio_core/hle/pipe.h"
+#include "audio_core/hle/source.h"
+#include "audio_core/sink.h"
namespace DSP {
namespace HLE {
-SharedMemory g_region0;
-SharedMemory g_region1;
+std::array<SharedMemory, 2> g_regions;
+
+static size_t CurrentRegionIndex() {
+ // The region with the higher frame counter is chosen unless there is wraparound.
+ // This function only returns a 0 or 1.
+
+ if (g_regions[0].frame_counter == 0xFFFFu && g_regions[1].frame_counter != 0xFFFEu) {
+ // Wraparound has occured.
+ return 1;
+ }
+
+ if (g_regions[1].frame_counter == 0xFFFFu && g_regions[0].frame_counter != 0xFFFEu) {
+ // Wraparound has occured.
+ return 0;
+ }
+
+ return (g_regions[0].frame_counter > g_regions[1].frame_counter) ? 0 : 1;
+}
+
+static SharedMemory& ReadRegion() {
+ return g_regions[CurrentRegionIndex()];
+}
+
+static SharedMemory& WriteRegion() {
+ return g_regions[1 - CurrentRegionIndex()];
+}
+
+static std::array<Source, num_sources> sources = {
+ Source(0), Source(1), Source(2), Source(3), Source(4), Source(5),
+ Source(6), Source(7), Source(8), Source(9), Source(10), Source(11),
+ Source(12), Source(13), Source(14), Source(15), Source(16), Source(17),
+ Source(18), Source(19), Source(20), Source(21), Source(22), Source(23)
+};
+
+static std::unique_ptr<AudioCore::Sink> sink;
void Init() {
DSP::HLE::ResetPipes();
+ for (auto& source : sources) {
+ source.Reset();
+ }
}
void Shutdown() {
}
bool Tick() {
- return true;
-}
+ SharedMemory& read = ReadRegion();
+ SharedMemory& write = WriteRegion();
-SharedMemory& CurrentRegion() {
- // The region with the higher frame counter is chosen unless there is wraparound.
+ std::array<QuadFrame32, 3> intermediate_mixes = {};
- if (g_region0.frame_counter == 0xFFFFu && g_region1.frame_counter != 0xFFFEu) {
- // Wraparound has occured.
- return g_region1;
+ for (size_t i = 0; i < num_sources; i++) {
+ write.source_statuses.status[i] = sources[i].Tick(read.source_configurations.config[i], read.adpcm_coefficients.coeff[i]);
+ for (size_t mix = 0; mix < 3; mix++) {
+ sources[i].MixInto(intermediate_mixes[mix], mix);
+ }
}
- if (g_region1.frame_counter == 0xFFFFu && g_region0.frame_counter != 0xFFFEu) {
- // Wraparound has occured.
- return g_region0;
- }
+ return true;
+}
- return (g_region0.frame_counter > g_region1.frame_counter) ? g_region0 : g_region1;
+void SetSink(std::unique_ptr<AudioCore::Sink> sink_) {
+ sink = std::move(sink_);
}
} // namespace HLE
diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h
index 376436c29..4459a5668 100644
--- a/src/audio_core/hle/dsp.h
+++ b/src/audio_core/hle/dsp.h
@@ -4,16 +4,22 @@
#pragma once
+#include <array>
#include <cstddef>
+#include <memory>
#include <type_traits>
-#include "audio_core/audio_core.h"
+#include "audio_core/hle/common.h"
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
+namespace AudioCore {
+class Sink;
+}
+
namespace DSP {
namespace HLE {
@@ -30,10 +36,9 @@ namespace HLE {
struct SharedMemory;
constexpr VAddr region0_base = 0x1FF50000;
-extern SharedMemory g_region0;
-
constexpr VAddr region1_base = 0x1FF70000;
-extern SharedMemory g_region1;
+
+extern std::array<SharedMemory, 2> g_regions;
/**
* The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from
@@ -126,8 +131,11 @@ struct SourceConfiguration {
union {
u32_le dirty_raw;
+ BitField<0, 1, u32_le> format_dirty;
+ BitField<1, 1, u32_le> mono_or_stereo_dirty;
BitField<2, 1, u32_le> adpcm_coefficients_dirty;
BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
+ BitField<4, 1, u32_le> partial_reset_flag;
BitField<16, 1, u32_le> enable_dirty;
BitField<17, 1, u32_le> interpolation_dirty;
@@ -143,8 +151,7 @@ struct SourceConfiguration {
BitField<27, 1, u32_le> gain_2_dirty;
BitField<28, 1, u32_le> sync_dirty;
BitField<29, 1, u32_le> reset_flag;
-
- BitField<31, 1, u32_le> embedded_buffer_dirty;
+ BitField<30, 1, u32_le> embedded_buffer_dirty;
};
// Gain control
@@ -162,9 +169,9 @@ struct SourceConfiguration {
float_le rate_multiplier;
enum class InterpolationMode : u8 {
- None = 0,
+ Polyphase = 0,
Linear = 1,
- Polyphase = 2
+ None = 2
};
InterpolationMode interpolation_mode;
@@ -175,7 +182,8 @@ struct SourceConfiguration {
/**
* This is the simplest normalized first-order digital recursive filter.
* The transfer function of this filter is:
- * H(z) = b0 / (1 + a1 z^-1)
+ * H(z) = b0 / (1 - a1 z^-1)
+ * Note the feedbackward coefficient is negated.
* Values are signed fixed point with 15 fractional bits.
*/
struct SimpleFilter {
@@ -192,11 +200,11 @@ struct SourceConfiguration {
* Values are signed fixed point with 14 fractional bits.
*/
struct BiquadFilter {
- s16_le b0;
- s16_le b1;
- s16_le b2;
- s16_le a1;
s16_le a2;
+ s16_le a1;
+ s16_le b2;
+ s16_le b1;
+ s16_le b0;
};
union {
@@ -302,7 +310,7 @@ struct SourceConfiguration {
u16_le buffer_id;
};
- Configuration config[AudioCore::num_sources];
+ Configuration config[num_sources];
};
ASSERT_DSP_STRUCT(SourceConfiguration::Configuration, 192);
ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20);
@@ -310,14 +318,14 @@ ASSERT_DSP_STRUCT(SourceConfiguration::Configuration::Buffer, 20);
struct SourceStatus {
struct Status {
u8 is_enabled; ///< Is this channel enabled? (Doesn't have to be playing anything.)
- u8 previous_buffer_id_dirty; ///< Non-zero when previous_buffer_id changes
+ u8 current_buffer_id_dirty; ///< Non-zero when current_buffer_id changes
u16_le sync; ///< Is set by the DSP to the value of SourceConfiguration::sync
u32_dsp buffer_position; ///< Number of samples into the current buffer
- u16_le previous_buffer_id; ///< Updated when a buffer finishes playing
+ u16_le current_buffer_id; ///< Updated when a buffer finishes playing
INSERT_PADDING_DSPWORDS(1);
};
- Status status[AudioCore::num_sources];
+ Status status[num_sources];
};
ASSERT_DSP_STRUCT(SourceStatus::Status, 12);
@@ -410,7 +418,7 @@ ASSERT_DSP_STRUCT(DspConfiguration::ReverbEffect, 52);
struct AdpcmCoefficients {
/// Coefficients are signed fixed point with 11 fractional bits.
/// Each source has 16 coefficients associated with it.
- s16_le coeff[AudioCore::num_sources][16];
+ s16_le coeff[num_sources][16];
};
ASSERT_DSP_STRUCT(AdpcmCoefficients, 768);
@@ -424,7 +432,7 @@ ASSERT_DSP_STRUCT(DspStatus, 32);
/// Final mixed output in PCM16 stereo format, what you hear out of the speakers.
/// When the application writes to this region it has no effect.
struct FinalMixSamples {
- s16_le pcm16[2 * AudioCore::samples_per_frame];
+ s16_le pcm16[2 * samples_per_frame];
};
ASSERT_DSP_STRUCT(FinalMixSamples, 640);
@@ -434,7 +442,7 @@ ASSERT_DSP_STRUCT(FinalMixSamples, 640);
/// Values that exceed s16 range will be clipped by the DSP after further processing.
struct IntermediateMixSamples {
struct Samples {
- s32_le pcm32[4][AudioCore::samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian.
+ s32_le pcm32[4][samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian.
};
Samples mix1;
@@ -532,8 +540,11 @@ void Shutdown();
*/
bool Tick();
-/// Returns a mutable reference to the current region. Current region is selected based on the frame counter.
-SharedMemory& CurrentRegion();
+/**
+ * Set the output sink. This must be called before calling Tick().
+ * @param sink The sink to which audio will be output to.
+ */
+void SetSink(std::unique_ptr<AudioCore::Sink> sink);
} // namespace HLE
} // namespace DSP
diff --git a/src/audio_core/hle/filter.cpp b/src/audio_core/hle/filter.cpp
new file mode 100644
index 000000000..2c65ef026
--- /dev/null
+++ b/src/audio_core/hle/filter.cpp
@@ -0,0 +1,115 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstddef>
+
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/filter.h"
+
+#include "common/common_types.h"
+#include "common/math_util.h"
+
+namespace DSP {
+namespace HLE {
+
+void SourceFilters::Reset() {
+ Enable(false, false);
+}
+
+void SourceFilters::Enable(bool simple, bool biquad) {
+ simple_filter_enabled = simple;
+ biquad_filter_enabled = biquad;
+
+ if (!simple)
+ simple_filter.Reset();
+ if (!biquad)
+ biquad_filter.Reset();
+}
+
+void SourceFilters::Configure(SourceConfiguration::Configuration::SimpleFilter config) {
+ simple_filter.Configure(config);
+}
+
+void SourceFilters::Configure(SourceConfiguration::Configuration::BiquadFilter config) {
+ biquad_filter.Configure(config);
+}
+
+void SourceFilters::ProcessFrame(StereoFrame16& frame) {
+ if (!simple_filter_enabled && !biquad_filter_enabled)
+ return;
+
+ if (simple_filter_enabled) {
+ FilterFrame(frame, simple_filter);
+ }
+
+ if (biquad_filter_enabled) {
+ FilterFrame(frame, biquad_filter);
+ }
+}
+
+// SimpleFilter
+
+void SourceFilters::SimpleFilter::Reset() {
+ y1.fill(0);
+ // Configure as passthrough.
+ a1 = 0;
+ b0 = 1 << 15;
+}
+
+void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) {
+ a1 = config.a1;
+ b0 = config.b0;
+}
+
+std::array<s16, 2> SourceFilters::SimpleFilter::ProcessSample(const std::array<s16, 2>& x0) {
+ std::array<s16, 2> y0;
+ for (size_t i = 0; i < 2; i++) {
+ const s32 tmp = (b0 * x0[i] + a1 * y1[i]) >> 15;
+ y0[i] = MathUtil::Clamp(tmp, -32768, 32767);
+ }
+
+ y1 = y0;
+
+ return y0;
+}
+
+// BiquadFilter
+
+void SourceFilters::BiquadFilter::Reset() {
+ x1.fill(0);
+ x2.fill(0);
+ y1.fill(0);
+ y2.fill(0);
+ // Configure as passthrough.
+ a1 = a2 = b1 = b2 = 0;
+ b0 = 1 << 14;
+}
+
+void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) {
+ a1 = config.a1;
+ a2 = config.a2;
+ b0 = config.b0;
+ b1 = config.b1;
+ b2 = config.b2;
+}
+
+std::array<s16, 2> SourceFilters::BiquadFilter::ProcessSample(const std::array<s16, 2>& x0) {
+ std::array<s16, 2> y0;
+ for (size_t i = 0; i < 2; i++) {
+ const s32 tmp = (b0 * x0[i] + b1 * x1[i] + b2 * x2[i] + a1 * y1[i] + a2 * y2[i]) >> 14;
+ y0[i] = MathUtil::Clamp(tmp, -32768, 32767);
+ }
+
+ x2 = x1;
+ x1 = x0;
+ y2 = y1;
+ y1 = y0;
+
+ return y0;
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/filter.h b/src/audio_core/hle/filter.h
new file mode 100644
index 000000000..43d2035cd
--- /dev/null
+++ b/src/audio_core/hle/filter.h
@@ -0,0 +1,113 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+
+#include "common/common_types.h"
+
+namespace DSP {
+namespace HLE {
+
+/// Preprocessing filters. There is an independent set of filters for each Source.
+class SourceFilters final {
+public:
+ SourceFilters() { Reset(); }
+
+ /// Reset internal state.
+ void Reset();
+
+ /**
+ * Enable/Disable filters
+ * See also: SourceConfiguration::Configuration::simple_filter_enabled,
+ * SourceConfiguration::Configuration::biquad_filter_enabled.
+ * @param simple If true, enables the simple filter. If false, disables it.
+ * @param simple If true, enables the biquad filter. If false, disables it.
+ */
+ void Enable(bool simple, bool biquad);
+
+ /**
+ * Configure simple filter.
+ * @param config Configuration from DSP shared memory.
+ */
+ void Configure(SourceConfiguration::Configuration::SimpleFilter config);
+
+ /**
+ * Configure biquad filter.
+ * @param config Configuration from DSP shared memory.
+ */
+ void Configure(SourceConfiguration::Configuration::BiquadFilter config);
+
+ /**
+ * Processes a frame in-place.
+ * @param frame Audio samples to process. Modified in-place.
+ */
+ void ProcessFrame(StereoFrame16& frame);
+
+private:
+ bool simple_filter_enabled;
+ bool biquad_filter_enabled;
+
+ struct SimpleFilter {
+ SimpleFilter() { Reset(); }
+
+ /// Resets internal state.
+ void Reset();
+
+ /**
+ * Configures this filter with application settings.
+ * @param config Configuration from DSP shared memory.
+ */
+ void Configure(SourceConfiguration::Configuration::SimpleFilter config);
+
+ /**
+ * Processes a single stereo PCM16 sample.
+ * @param x0 Input sample
+ * @return Output sample
+ */
+ std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0);
+
+ private:
+ // Configuration
+ s32 a1, b0;
+ // Internal state
+ std::array<s16, 2> y1;
+ } simple_filter;
+
+ struct BiquadFilter {
+ BiquadFilter() { Reset(); }
+
+ /// Resets internal state.
+ void Reset();
+
+ /**
+ * Configures this filter with application settings.
+ * @param config Configuration from DSP shared memory.
+ */
+ void Configure(SourceConfiguration::Configuration::BiquadFilter config);
+
+ /**
+ * Processes a single stereo PCM16 sample.
+ * @param x0 Input sample
+ * @return Output sample
+ */
+ std::array<s16, 2> ProcessSample(const std::array<s16, 2>& x0);
+
+ private:
+ // Configuration
+ s32 a1, a2, b0, b1, b2;
+ // Internal state
+ std::array<s16, 2> x1;
+ std::array<s16, 2> x2;
+ std::array<s16, 2> y1;
+ std::array<s16, 2> y2;
+ } biquad_filter;
+};
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp
index 9381883b4..03280780f 100644
--- a/src/audio_core/hle/pipe.cpp
+++ b/src/audio_core/hle/pipe.cpp
@@ -12,12 +12,14 @@
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "core/hle/service/dsp_dsp.h"
+
namespace DSP {
namespace HLE {
static DspState dsp_state = DspState::Off;
-static std::array<std::vector<u8>, static_cast<size_t>(DspPipe::DspPipe_MAX)> pipe_data;
+static std::array<std::vector<u8>, NUM_DSP_PIPE> pipe_data;
void ResetPipes() {
for (auto& data : pipe_data) {
@@ -27,16 +29,18 @@ void ResetPipes() {
}
std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) {
- if (pipe_number >= DspPipe::DspPipe_MAX) {
- LOG_ERROR(Audio_DSP, "pipe_number = %u invalid", pipe_number);
+ const size_t pipe_index = static_cast<size_t>(pipe_number);
+
+ if (pipe_index >= NUM_DSP_PIPE) {
+ LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index);
return {};
}
- std::vector<u8>& data = pipe_data[static_cast<size_t>(pipe_number)];
+ std::vector<u8>& data = pipe_data[pipe_index];
if (length > data.size()) {
- LOG_WARNING(Audio_DSP, "pipe_number = %u is out of data, application requested read of %u but %zu remain",
- pipe_number, length, data.size());
+ LOG_WARNING(Audio_DSP, "pipe_number = %zu is out of data, application requested read of %u but %zu remain",
+ pipe_index, length, data.size());
length = data.size();
}
@@ -49,16 +53,20 @@ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) {
}
size_t GetPipeReadableSize(DspPipe pipe_number) {
- if (pipe_number >= DspPipe::DspPipe_MAX) {
- LOG_ERROR(Audio_DSP, "pipe_number = %u invalid", pipe_number);
+ const size_t pipe_index = static_cast<size_t>(pipe_number);
+
+ if (pipe_index >= NUM_DSP_PIPE) {
+ LOG_ERROR(Audio_DSP, "pipe_number = %zu invalid", pipe_index);
return 0;
}
- return pipe_data[static_cast<size_t>(pipe_number)].size();
+ return pipe_data[pipe_index].size();
}
static void WriteU16(DspPipe pipe_number, u16 value) {
- std::vector<u8>& data = pipe_data[static_cast<size_t>(pipe_number)];
+ const size_t pipe_index = static_cast<size_t>(pipe_number);
+
+ std::vector<u8>& data = pipe_data.at(pipe_index);
// Little endian
data.emplace_back(value & 0xFF);
data.emplace_back(value >> 8);
@@ -91,6 +99,8 @@ static void AudioPipeWriteStructAddresses() {
for (u16 addr : struct_addresses) {
WriteU16(DspPipe::Audio, addr);
}
+ // Signal that we have data on this pipe.
+ DSP_DSP::SignalPipeInterrupt(DspPipe::Audio);
}
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
@@ -145,7 +155,7 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
return;
}
default:
- LOG_CRITICAL(Audio_DSP, "pipe_number = %u unimplemented", pipe_number);
+ LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", static_cast<size_t>(pipe_number));
UNIMPLEMENTED();
return;
}
diff --git a/src/audio_core/hle/pipe.h b/src/audio_core/hle/pipe.h
index 382d35e87..64d97f8ba 100644
--- a/src/audio_core/hle/pipe.h
+++ b/src/audio_core/hle/pipe.h
@@ -19,9 +19,9 @@ enum class DspPipe {
Debug = 0,
Dma = 1,
Audio = 2,
- Binary = 3,
- DspPipe_MAX
+ Binary = 3
};
+constexpr size_t NUM_DSP_PIPE = 8;
/**
* Read a DSP pipe.
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp
new file mode 100644
index 000000000..daaf6e3f3
--- /dev/null
+++ b/src/audio_core/hle/source.cpp
@@ -0,0 +1,320 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+
+#include "audio_core/codec.h"
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/source.h"
+#include "audio_core/interpolate.h"
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+
+#include "core/memory.h"
+
+namespace DSP {
+namespace HLE {
+
+SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
+ ParseConfig(config, adpcm_coeffs);
+
+ if (state.enabled) {
+ GenerateFrame();
+ }
+
+ return GetCurrentStatus();
+}
+
+void Source::MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const {
+ if (!state.enabled)
+ return;
+
+ const std::array<float, 4>& gains = state.gain.at(intermediate_mix_id);
+ for (size_t samplei = 0; samplei < samples_per_frame; samplei++) {
+ // Conversion from stereo (current_frame) to quadraphonic (dest) occurs here.
+ dest[samplei][0] += static_cast<s32>(gains[0] * current_frame[samplei][0]);
+ dest[samplei][1] += static_cast<s32>(gains[1] * current_frame[samplei][1]);
+ dest[samplei][2] += static_cast<s32>(gains[2] * current_frame[samplei][0]);
+ dest[samplei][3] += static_cast<s32>(gains[3] * current_frame[samplei][1]);
+ }
+}
+
+void Source::Reset() {
+ current_frame.fill({});
+ state = {};
+}
+
+void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
+ if (!config.dirty_raw) {
+ return;
+ }
+
+ if (config.reset_flag) {
+ config.reset_flag.Assign(0);
+ Reset();
+ LOG_TRACE(Audio_DSP, "source_id=%zu reset", source_id);
+ }
+
+ if (config.partial_reset_flag) {
+ config.partial_reset_flag.Assign(0);
+ state.input_queue = std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder>{};
+ LOG_TRACE(Audio_DSP, "source_id=%zu partial_reset", source_id);
+ }
+
+ if (config.enable_dirty) {
+ config.enable_dirty.Assign(0);
+ state.enabled = config.enable != 0;
+ LOG_TRACE(Audio_DSP, "source_id=%zu enable=%d", source_id, state.enabled);
+ }
+
+ if (config.sync_dirty) {
+ config.sync_dirty.Assign(0);
+ state.sync = config.sync;
+ LOG_TRACE(Audio_DSP, "source_id=%zu sync=%u", source_id, state.sync);
+ }
+
+ if (config.rate_multiplier_dirty) {
+ config.rate_multiplier_dirty.Assign(0);
+ state.rate_multiplier = config.rate_multiplier;
+ LOG_TRACE(Audio_DSP, "source_id=%zu rate=%f", source_id, state.rate_multiplier);
+
+ if (state.rate_multiplier <= 0) {
+ LOG_ERROR(Audio_DSP, "Was given an invalid rate multiplier: source_id=%zu rate=%f", source_id, state.rate_multiplier);
+ state.rate_multiplier = 1.0f;
+ // Note: Actual firmware starts producing garbage if this occurs.
+ }
+ }
+
+ if (config.adpcm_coefficients_dirty) {
+ config.adpcm_coefficients_dirty.Assign(0);
+ std::transform(adpcm_coeffs, adpcm_coeffs + state.adpcm_coeffs.size(), state.adpcm_coeffs.begin(),
+ [](const auto& coeff) { return static_cast<s16>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu adpcm update", source_id);
+ }
+
+ if (config.gain_0_dirty) {
+ config.gain_0_dirty.Assign(0);
+ std::transform(config.gain[0], config.gain[0] + state.gain[0].size(), state.gain[0].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 0 update", source_id);
+ }
+
+ if (config.gain_1_dirty) {
+ config.gain_1_dirty.Assign(0);
+ std::transform(config.gain[1], config.gain[1] + state.gain[1].size(), state.gain[1].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 1 update", source_id);
+ }
+
+ if (config.gain_2_dirty) {
+ config.gain_2_dirty.Assign(0);
+ std::transform(config.gain[2], config.gain[2] + state.gain[2].size(), state.gain[2].begin(),
+ [](const auto& coeff) { return static_cast<float>(coeff); });
+ LOG_TRACE(Audio_DSP, "source_id=%zu gain 2 update", source_id);
+ }
+
+ if (config.filters_enabled_dirty) {
+ config.filters_enabled_dirty.Assign(0);
+ state.filters.Enable(config.simple_filter_enabled.ToBool(), config.biquad_filter_enabled.ToBool());
+ LOG_TRACE(Audio_DSP, "source_id=%zu enable_simple=%hu enable_biquad=%hu",
+ source_id, config.simple_filter_enabled.Value(), config.biquad_filter_enabled.Value());
+ }
+
+ if (config.simple_filter_dirty) {
+ config.simple_filter_dirty.Assign(0);
+ state.filters.Configure(config.simple_filter);
+ LOG_TRACE(Audio_DSP, "source_id=%zu simple filter update");
+ }
+
+ if (config.biquad_filter_dirty) {
+ config.biquad_filter_dirty.Assign(0);
+ state.filters.Configure(config.biquad_filter);
+ LOG_TRACE(Audio_DSP, "source_id=%zu biquad filter update");
+ }
+
+ if (config.interpolation_dirty) {
+ config.interpolation_dirty.Assign(0);
+ state.interpolation_mode = config.interpolation_mode;
+ LOG_TRACE(Audio_DSP, "source_id=%zu interpolation_mode=%zu", source_id, static_cast<size_t>(state.interpolation_mode));
+ }
+
+ if (config.format_dirty || config.embedded_buffer_dirty) {
+ config.format_dirty.Assign(0);
+ state.format = config.format;
+ LOG_TRACE(Audio_DSP, "source_id=%zu format=%zu", source_id, static_cast<size_t>(state.format));
+ }
+
+ if (config.mono_or_stereo_dirty || config.embedded_buffer_dirty) {
+ config.mono_or_stereo_dirty.Assign(0);
+ state.mono_or_stereo = config.mono_or_stereo;
+ LOG_TRACE(Audio_DSP, "source_id=%zu mono_or_stereo=%zu", source_id, static_cast<size_t>(state.mono_or_stereo));
+ }
+
+ if (config.embedded_buffer_dirty) {
+ config.embedded_buffer_dirty.Assign(0);
+ state.input_queue.emplace(Buffer{
+ config.physical_address,
+ config.length,
+ static_cast<u8>(config.adpcm_ps),
+ { config.adpcm_yn[0], config.adpcm_yn[1] },
+ config.adpcm_dirty.ToBool(),
+ config.is_looping.ToBool(),
+ config.buffer_id,
+ state.mono_or_stereo,
+ state.format,
+ false
+ });
+ LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", config.physical_address, config.length, config.buffer_id);
+ }
+
+ if (config.buffer_queue_dirty) {
+ config.buffer_queue_dirty.Assign(0);
+ for (size_t i = 0; i < 4; i++) {
+ if (config.buffers_dirty & (1 << i)) {
+ const auto& b = config.buffers[i];
+ state.input_queue.emplace(Buffer{
+ b.physical_address,
+ b.length,
+ static_cast<u8>(b.adpcm_ps),
+ { b.adpcm_yn[0], b.adpcm_yn[1] },
+ b.adpcm_dirty != 0,
+ b.is_looping != 0,
+ b.buffer_id,
+ state.mono_or_stereo,
+ state.format,
+ true
+ });
+ LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id);
+ }
+ }
+ config.buffers_dirty = 0;
+ }
+
+ if (config.dirty_raw) {
+ LOG_DEBUG(Audio_DSP, "source_id=%zu remaining_dirty=%x", source_id, config.dirty_raw);
+ }
+
+ config.dirty_raw = 0;
+}
+
+void Source::GenerateFrame() {
+ current_frame.fill({});
+
+ if (state.current_buffer.empty() && !DequeueBuffer()) {
+ state.enabled = false;
+ state.buffer_update = true;
+ state.current_buffer_id = 0;
+ return;
+ }
+
+ size_t frame_position = 0;
+
+ state.current_sample_number = state.next_sample_number;
+ while (frame_position < current_frame.size()) {
+ if (state.current_buffer.empty() && !DequeueBuffer()) {
+ break;
+ }
+
+ const size_t size_to_copy = std::min(state.current_buffer.size(), current_frame.size() - frame_position);
+
+ std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, current_frame.begin() + frame_position);
+ state.current_buffer.erase(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy);
+
+ frame_position += size_to_copy;
+ state.next_sample_number += static_cast<u32>(size_to_copy);
+ }
+
+ state.filters.ProcessFrame(current_frame);
+}
+
+
+bool Source::DequeueBuffer() {
+ ASSERT_MSG(state.current_buffer.empty(), "Shouldn't dequeue; we still have data in current_buffer");
+
+ if (state.input_queue.empty())
+ return false;
+
+ const Buffer buf = state.input_queue.top();
+ state.input_queue.pop();
+
+ if (buf.adpcm_dirty) {
+ state.adpcm_state.yn1 = buf.adpcm_yn[0];
+ state.adpcm_state.yn2 = buf.adpcm_yn[1];
+ }
+
+ if (buf.is_looping) {
+ LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment");
+ }
+
+ const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address);
+ if (memory) {
+ const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1;
+ switch (buf.format) {
+ case Format::PCM8:
+ state.current_buffer = Codec::DecodePCM8(num_channels, memory, buf.length);
+ break;
+ case Format::PCM16:
+ state.current_buffer = Codec::DecodePCM16(num_channels, memory, buf.length);
+ break;
+ case Format::ADPCM:
+ DEBUG_ASSERT(num_channels == 1);
+ state.current_buffer = Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state);
+ break;
+ default:
+ UNIMPLEMENTED();
+ break;
+ }
+ } else {
+ LOG_WARNING(Audio_DSP, "source_id=%zu buffer_id=%hu length=%u: Invalid physical address 0x%08X",
+ source_id, buf.buffer_id, buf.length, buf.physical_address);
+ state.current_buffer.clear();
+ return true;
+ }
+
+ switch (state.interpolation_mode) {
+ case InterpolationMode::None:
+ state.current_buffer = AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ case InterpolationMode::Linear:
+ state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ case InterpolationMode::Polyphase:
+ // TODO(merry): Implement polyphase interpolation
+ state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
+ break;
+ default:
+ UNIMPLEMENTED();
+ break;
+ }
+
+ state.current_sample_number = 0;
+ state.next_sample_number = 0;
+ state.current_buffer_id = buf.buffer_id;
+ state.buffer_update = buf.from_queue;
+
+ LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu",
+ source_id, buf.buffer_id, buf.from_queue ? "true" : "false", state.current_buffer.size());
+ return true;
+}
+
+SourceStatus::Status Source::GetCurrentStatus() {
+ SourceStatus::Status ret;
+
+ // Applications depend on the correct emulation of
+ // current_buffer_id_dirty and current_buffer_id to synchronise
+ // audio with video.
+ ret.is_enabled = state.enabled;
+ ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
+ state.buffer_update = false;
+ ret.current_buffer_id = state.current_buffer_id;
+ ret.buffer_position = state.current_sample_number;
+ ret.sync = state.sync;
+
+ return ret;
+}
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h
new file mode 100644
index 000000000..7ee08d424
--- /dev/null
+++ b/src/audio_core/hle/source.h
@@ -0,0 +1,144 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <queue>
+#include <vector>
+
+#include "audio_core/codec.h"
+#include "audio_core/hle/common.h"
+#include "audio_core/hle/dsp.h"
+#include "audio_core/hle/filter.h"
+#include "audio_core/interpolate.h"
+
+#include "common/common_types.h"
+
+namespace DSP {
+namespace HLE {
+
+/**
+ * This module performs:
+ * - Buffer management
+ * - Decoding of buffers
+ * - Buffer resampling and interpolation
+ * - Per-source filtering (SimpleFilter, BiquadFilter)
+ * - Per-source gain
+ * - Other per-source processing
+ */
+class Source final {
+public:
+ explicit Source(size_t source_id_) : source_id(source_id_) {
+ Reset();
+ }
+
+ /// Resets internal state.
+ void Reset();
+
+ /**
+ * This is called once every audio frame. This performs per-source processing every frame.
+ * @param config The new configuration we've got for this Source from the application.
+ * @param adpcm_coeffs ADPCM coefficients to use if config tells us to use them (may contain invalid values otherwise).
+ * @return The current status of this Source. This is given back to the emulated application via SharedMemory.
+ */
+ SourceStatus::Status Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
+
+ /**
+ * Mix this source's output into dest, using the gains for the `intermediate_mix_id`-th intermediate mixer.
+ * @param dest The QuadFrame32 to mix into.
+ * @param intermediate_mix_id The id of the intermediate mix whose gains we are using.
+ */
+ void MixInto(QuadFrame32& dest, size_t intermediate_mix_id) const;
+
+private:
+ const size_t source_id;
+ StereoFrame16 current_frame;
+
+ using Format = SourceConfiguration::Configuration::Format;
+ using InterpolationMode = SourceConfiguration::Configuration::InterpolationMode;
+ using MonoOrStereo = SourceConfiguration::Configuration::MonoOrStereo;
+
+ /// Internal representation of a buffer for our buffer queue
+ struct Buffer {
+ PAddr physical_address;
+ u32 length;
+ u8 adpcm_ps;
+ std::array<u16, 2> adpcm_yn;
+ bool adpcm_dirty;
+ bool is_looping;
+ u16 buffer_id;
+
+ MonoOrStereo mono_or_stereo;
+ Format format;
+
+ bool from_queue;
+ };
+
+ struct BufferOrder {
+ bool operator() (const Buffer& a, const Buffer& b) const {
+ // Lower buffer_id comes first.
+ return a.buffer_id > b.buffer_id;
+ }
+ };
+
+ struct {
+
+ // State variables
+
+ bool enabled = false;
+ u16 sync = 0;
+
+ // Mixing
+
+ std::array<std::array<float, 4>, 3> gain = {};
+
+ // Buffer queue
+
+ std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder> input_queue;
+ MonoOrStereo mono_or_stereo = MonoOrStereo::Mono;
+ Format format = Format::ADPCM;
+
+ // Current buffer
+
+ u32 current_sample_number = 0;
+ u32 next_sample_number = 0;
+ std::vector<std::array<s16, 2>> current_buffer;
+
+ // buffer_id state
+
+ bool buffer_update = false;
+ u32 current_buffer_id = 0;
+
+ // Decoding state
+
+ std::array<s16, 16> adpcm_coeffs = {};
+ Codec::ADPCMState adpcm_state = {};
+
+ // Resampling state
+
+ float rate_multiplier = 1.0;
+ InterpolationMode interpolation_mode = InterpolationMode::Polyphase;
+ AudioInterp::State interp_state = {};
+
+ // Filter state
+
+ SourceFilters filters;
+
+ } state;
+
+ // Internal functions
+
+ /// INTERNAL: Update our internal state based on the current config.
+ void ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]);
+ /// INTERNAL: Generate the current audio output for this frame based on our internal state.
+ void GenerateFrame();
+ /// INTERNAL: Dequeues a buffer and does preprocessing on it (decoding, resampling). Puts it into current_buffer.
+ bool DequeueBuffer();
+ /// INTERNAL: Generates a SourceStatus::Status based on our internal state.
+ SourceStatus::Status GetCurrentStatus();
+};
+
+} // namespace HLE
+} // namespace DSP
diff --git a/src/audio_core/interpolate.cpp b/src/audio_core/interpolate.cpp
new file mode 100644
index 000000000..fcd3aa066
--- /dev/null
+++ b/src/audio_core/interpolate.cpp
@@ -0,0 +1,85 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "audio_core/interpolate.h"
+
+#include "common/assert.h"
+#include "common/math_util.h"
+
+namespace AudioInterp {
+
+// Calculations are done in fixed point with 24 fractional bits.
+// (This is not verified. This was chosen for minimal error.)
+constexpr u64 scale_factor = 1 << 24;
+constexpr u64 scale_mask = scale_factor - 1;
+
+/// Here we step over the input in steps of rate_multiplier, until we consume all of the input.
+/// Three adjacent samples are passed to fn each step.
+template <typename Function>
+static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, float rate_multiplier, Function fn) {
+ ASSERT(rate_multiplier > 0);
+
+ if (input.size() < 2)
+ return {};
+
+ StereoBuffer16 output;
+ output.reserve(static_cast<size_t>(input.size() / rate_multiplier));
+
+ u64 step_size = static_cast<u64>(rate_multiplier * scale_factor);
+
+ u64 fposition = 0;
+ const u64 max_fposition = input.size() * scale_factor;
+
+ while (fposition < 1 * scale_factor) {
+ u64 fraction = fposition & scale_mask;
+
+ output.push_back(fn(fraction, state.xn2, state.xn1, input[0]));
+
+ fposition += step_size;
+ }
+
+ while (fposition < 2 * scale_factor) {
+ u64 fraction = fposition & scale_mask;
+
+ output.push_back(fn(fraction, state.xn1, input[0], input[1]));
+
+ fposition += step_size;
+ }
+
+ while (fposition < max_fposition) {
+ u64 fraction = fposition & scale_mask;
+
+ size_t index = static_cast<size_t>(fposition / scale_factor);
+ output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index]));
+
+ fposition += step_size;
+ }
+
+ state.xn2 = input[input.size() - 2];
+ state.xn1 = input[input.size() - 1];
+
+ return output;
+}
+
+StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) {
+ return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
+ return x0;
+ });
+}
+
+StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) {
+ // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
+ return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
+ // This is a saturated subtraction. (Verified by black-box fuzzing.)
+ s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
+ s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
+
+ return std::array<s16, 2> {
+ static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
+ static_cast<s16>(x0[1] + fraction * delta1 / scale_factor)
+ };
+ });
+}
+
+} // namespace AudioInterp
diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h
new file mode 100644
index 000000000..a4c0a453d
--- /dev/null
+++ b/src/audio_core/interpolate.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace AudioInterp {
+
+/// A variable length buffer of signed PCM16 stereo samples.
+using StereoBuffer16 = std::vector<std::array<s16, 2>>;
+
+struct State {
+ // Two historical samples.
+ std::array<s16, 2> xn1 = {}; ///< x[n-1]
+ std::array<s16, 2> xn2 = {}; ///< x[n-2]
+};
+
+/**
+ * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
+ * @param input Input buffer.
+ * @param rate_multiplier Stretch factor. Must be a positive non-zero value.
+ * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling.
+ * @return The resampled audio buffer.
+ */
+StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier);
+
+/**
+ * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
+ * @param input Input buffer.
+ * @param rate_multiplier Stretch factor. Must be a positive non-zero value.
+ * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling.
+ * @return The resampled audio buffer.
+ */
+StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier);
+
+} // namespace AudioInterp
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
new file mode 100644
index 000000000..faf0ee4e1
--- /dev/null
+++ b/src/audio_core/null_sink.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+
+#include "audio_core/audio_core.h"
+#include "audio_core/sink.h"
+
+namespace AudioCore {
+
+class NullSink final : public Sink {
+public:
+ ~NullSink() override = default;
+
+ unsigned int GetNativeSampleRate() const override {
+ return native_sample_rate;
+ }
+
+ void EnqueueSamples(const std::vector<s16>&) override {}
+
+ size_t SamplesInQueue() const override {
+ return 0;
+ }
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
new file mode 100644
index 000000000..d2cc74103
--- /dev/null
+++ b/src/audio_core/sink_details.cpp
@@ -0,0 +1,18 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <vector>
+
+#include "audio_core/null_sink.h"
+#include "audio_core/sink_details.h"
+
+namespace AudioCore {
+
+// g_sink_details is ordered in terms of desirability, with the best choice at the top.
+const std::vector<SinkDetails> g_sink_details = {
+ { "null", []() { return std::make_unique<NullSink>(); } },
+};
+
+} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
new file mode 100644
index 000000000..4b30cf835
--- /dev/null
+++ b/src/audio_core/sink_details.h
@@ -0,0 +1,27 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace AudioCore {
+
+class Sink;
+
+struct SinkDetails {
+ SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>()> factory_)
+ : id(id_), factory(factory_) {}
+
+ /// Name for this sink.
+ const char* id;
+ /// A method to call to construct an instance of this type of sink.
+ std::function<std::unique_ptr<Sink>()> factory;
+};
+
+extern const std::vector<SinkDetails> g_sink_details;
+
+} // namespace AudioCore