diff options
author | James Rowe <jroweboy@gmail.com> | 2018-01-12 03:21:20 +0100 |
---|---|---|
committer | James Rowe <jroweboy@gmail.com> | 2018-01-13 03:11:03 +0100 |
commit | ebf9a784a9f7f4148a669dbb39e7cd50df779a14 (patch) | |
tree | d585685a1c0a34b903af1d086d62560bf56bb29f /src/audio_core | |
parent | config: Default CPU core to Unicorn. (diff) | |
download | yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.gz yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.bz2 yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.lz yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.xz yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.tar.zst yuzu-ebf9a784a9f7f4148a669dbb39e7cd50df779a14.zip |
Diffstat (limited to '')
26 files changed, 0 insertions, 3017 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt deleted file mode 100644 index 0ad86bb7a..000000000 --- a/src/audio_core/CMakeLists.txt +++ /dev/null @@ -1,44 +0,0 @@ -set(SRCS - audio_core.cpp - codec.cpp - hle/dsp.cpp - hle/filter.cpp - hle/mixers.cpp - hle/pipe.cpp - hle/source.cpp - interpolate.cpp - sink_details.cpp - time_stretch.cpp - ) - -set(HEADERS - audio_core.h - codec.h - hle/common.h - hle/dsp.h - hle/filter.h - hle/mixers.h - hle/pipe.h - hle/source.h - interpolate.h - null_sink.h - sink.h - sink_details.h - time_stretch.h - ) - -if(SDL2_FOUND) - set(SRCS ${SRCS} sdl2_sink.cpp) - set(HEADERS ${HEADERS} sdl2_sink.h) -endif() - -create_directory_groups(${SRCS} ${HEADERS}) - -add_library(audio_core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(audio_core PUBLIC common core) -target_link_libraries(audio_core PRIVATE SoundTouch) - -if(SDL2_FOUND) - target_link_libraries(audio_core PRIVATE SDL2) - target_compile_definitions(audio_core PRIVATE HAVE_SDL2) -endif() diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp deleted file mode 100644 index ae2b68f9c..000000000 --- a/src/audio_core/audio_core.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#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 "common/common_types.h" -#include "core/core_timing.h" -#include "core/hle/service/dsp_dsp.h" - -namespace AudioCore { - -// Audio Ticks occur about every 5 miliseconds. -static CoreTiming::EventType* tick_event; ///< CoreTiming event -static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles - -static void AudioTickCallback(u64 /*userdata*/, int cycles_late) { - if (DSP::HLE::Tick()) { - // TODO(merry): Signal all the other interrupts as appropriate. - Service::DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Audio); - // HACK(merry): Added to prevent regressions. Will remove soon. - Service::DSP_DSP::SignalPipeInterrupt(DSP::HLE::DspPipe::Binary); - } - - // Reschedule recurrent event - CoreTiming::ScheduleEvent(audio_frame_ticks - cycles_late, tick_event); -} - -void Init() { - DSP::HLE::Init(); - - tick_event = CoreTiming::RegisterEvent("AudioCore::tick_event", AudioTickCallback); - CoreTiming::ScheduleEvent(audio_frame_ticks, tick_event); -} - -std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory() { - return DSP::HLE::g_dsp_memory.raw_memory; -} - -void SelectSink(std::string sink_id) { - const SinkDetails& sink_details = GetSinkDetails(sink_id); - DSP::HLE::SetSink(sink_details.factory()); -} - -void EnableStretching(bool enable) { - DSP::HLE::EnableStretching(enable); -} - -void Shutdown() { - CoreTiming::UnscheduleEvent(tick_event, 0); - DSP::HLE::Shutdown(); -} - -} // namespace AudioCore diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h deleted file mode 100644 index ab323ce1f..000000000 --- a/src/audio_core/audio_core.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <string> -#include "common/common_types.h" -#include "core/memory.h" - -namespace AudioCore { - -constexpr int native_sample_rate = 32728; ///< 32kHz - -/// Initialise Audio Core -void Init(); - -/// Returns a reference to the array backing DSP memory -std::array<u8, Memory::DSP_RAM_SIZE>& GetDspMemory(); - -/// Select the sink to use based on sink id. -void SelectSink(std::string sink_id); - -/// Enable/Disable stretching. -void EnableStretching(bool enable); - -/// Shutdown Audio Core -void Shutdown(); - -} // namespace AudioCore diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp deleted file mode 100644 index 6fba9fdae..000000000 --- a/src/audio_core/codec.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// 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] >> 4]); - ret[outputi].fill(sample1); - outputi++; - - const s16 sample2 = decode_sample(SIGNED_NIBBLES[data[datai] & 0xF]); - 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 { - for (size_t i = 0; i < sample_count; ++i) { - std::memcpy(&ret[i], data + i * sizeof(s16) * 2, 2 * sizeof(s16)); - } - } - - return ret; -} -}; diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h deleted file mode 100644 index 877b2202d..000000000 --- a/src/audio_core/codec.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <deque> -#include "common/common_types.h" - -namespace Codec { - -/// A variable length buffer of signed PCM16 stereo samples. -using StereoBuffer16 = std::deque<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 deleted file mode 100644 index 7fbc3ad9a..000000000 --- a/src/audio_core/hle/common.h +++ /dev/null @@ -1,34 +0,0 @@ -// 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 deleted file mode 100644 index 260b182ed..000000000 --- a/src/audio_core/hle/dsp.cpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// 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/mixers.h" -#include "audio_core/hle/pipe.h" -#include "audio_core/hle/source.h" -#include "audio_core/sink.h" -#include "audio_core/time_stretch.h" - -namespace DSP { -namespace HLE { - -// Region management - -DspMemory g_dsp_memory; - -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. - u16 frame_counter_0 = g_dsp_memory.region_0.frame_counter; - u16 frame_counter_1 = g_dsp_memory.region_1.frame_counter; - - if (frame_counter_0 == 0xFFFFu && frame_counter_1 != 0xFFFEu) { - // Wraparound has occurred. - return 1; - } - - if (frame_counter_1 == 0xFFFFu && frame_counter_0 != 0xFFFEu) { - // Wraparound has occurred. - return 0; - } - - return (frame_counter_0 > frame_counter_1) ? 0 : 1; -} - -static SharedMemory& ReadRegion() { - return CurrentRegionIndex() == 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1; -} - -static SharedMemory& WriteRegion() { - return CurrentRegionIndex() != 0 ? g_dsp_memory.region_0 : g_dsp_memory.region_1; -} - -// Audio processing and mixing - -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 Mixers mixers; - -static StereoFrame16 GenerateCurrentFrame() { - SharedMemory& read = ReadRegion(); - SharedMemory& write = WriteRegion(); - - std::array<QuadFrame32, 3> intermediate_mixes = {}; - - // Generate intermediate mixes - 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); - } - } - - // Generate final mix - write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples, - write.intermediate_mix_samples, intermediate_mixes); - - StereoFrame16 output_frame = mixers.GetOutput(); - - // Write current output frame to the shared memory region - for (size_t samplei = 0; samplei < output_frame.size(); samplei++) { - for (size_t channeli = 0; channeli < output_frame[0].size(); channeli++) { - write.final_samples.pcm16[samplei][channeli] = s16_le(output_frame[samplei][channeli]); - } - } - - return output_frame; -} - -// Audio output - -static bool perform_time_stretching = true; -static std::unique_ptr<AudioCore::Sink> sink; -static AudioCore::TimeStretcher time_stretcher; - -static void FlushResidualStretcherAudio() { - time_stretcher.Flush(); - while (true) { - std::vector<s16> residual_audio = time_stretcher.Process(sink->SamplesInQueue()); - if (residual_audio.empty()) - break; - sink->EnqueueSamples(residual_audio.data(), residual_audio.size() / 2); - } -} - -static void OutputCurrentFrame(const StereoFrame16& frame) { - if (perform_time_stretching) { - time_stretcher.AddSamples(&frame[0][0], frame.size()); - std::vector<s16> stretched_samples = time_stretcher.Process(sink->SamplesInQueue()); - sink->EnqueueSamples(stretched_samples.data(), stretched_samples.size() / 2); - } else { - constexpr size_t maximum_sample_latency = 2048; // about 64 miliseconds - if (sink->SamplesInQueue() > maximum_sample_latency) { - // This can occur if we're running too fast and samples are starting to back up. - // Just drop the samples. - return; - } - - sink->EnqueueSamples(&frame[0][0], frame.size()); - } -} - -void EnableStretching(bool enable) { - if (perform_time_stretching == enable) - return; - - if (!enable) { - FlushResidualStretcherAudio(); - } - perform_time_stretching = enable; -} - -// Public Interface - -void Init() { - DSP::HLE::ResetPipes(); - - for (auto& source : sources) { - source.Reset(); - } - - mixers.Reset(); - - time_stretcher.Reset(); - if (sink) { - time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); - } -} - -void Shutdown() { - if (perform_time_stretching) { - FlushResidualStretcherAudio(); - } -} - -bool Tick() { - StereoFrame16 current_frame = {}; - - // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to - // shared memory region) - current_frame = GenerateCurrentFrame(); - - OutputCurrentFrame(current_frame); - - return true; -} - -void SetSink(std::unique_ptr<AudioCore::Sink> sink_) { - sink = std::move(sink_); - time_stretcher.SetOutputSampleRate(sink->GetNativeSampleRate()); -} - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/dsp.h b/src/audio_core/hle/dsp.h deleted file mode 100644 index 94ce48863..000000000 --- a/src/audio_core/hle/dsp.h +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <cstddef> -#include <memory> -#include <type_traits> -#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 { - -// The application-accessible region of DSP memory consists of two parts. Both are marked as IO and -// have Read/Write permissions. -// -// First Region: 0x1FF50000 (Size: 0x8000) -// Second Region: 0x1FF70000 (Size: 0x8000) -// -// The DSP reads from each region alternately based on the frame counter for each region much like a -// double-buffer. The frame counter is located as the very last u16 of each region and is -// incremented each audio tick. - -constexpr u32 region0_offset = 0x50000; -constexpr u32 region1_offset = 0x70000; - -/** - * The DSP is native 16-bit. The DSP also appears to be big-endian. When reading 32-bit numbers from - * its memory regions, the higher and lower 16-bit halves are swapped compared to the little-endian - * layout of the ARM11. Hence from the ARM11's point of view the memory space appears to be - * middle-endian. - * - * Unusually this does not appear to be an issue for floating point numbers. The DSP makes the more - * sensible choice of keeping that little-endian. There are also some exceptions such as the - * IntermediateMixSamples structure, which is little-endian. - * - * This struct implements the conversion to and from this middle-endianness. - */ -struct u32_dsp { - u32_dsp() = default; - operator u32() const { - return Convert(storage); - } - void operator=(u32 new_value) { - storage = Convert(new_value); - } - -private: - static constexpr u32 Convert(u32 value) { - return (value << 16) | (value >> 16); - } - u32_le storage; -}; -#if (__GNUC__ >= 5) || defined(__clang__) || defined(_MSC_VER) -static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivially copyable"); -#endif - -// There are 15 structures in each memory region. A table of them in the order they appear in memory -// is presented below: -// -// # First Region DSP Address Purpose Control -// 5 0x8400 DSP Status DSP -// 9 0x8410 DSP Debug Info DSP -// 6 0x8540 Final Mix Samples DSP -// 2 0x8680 Source Status [24] DSP -// 8 0x8710 Compressor Table Application -// 4 0x9430 DSP Configuration Application -// 7 0x9492 Intermediate Mix Samples DSP + App -// 1 0x9E92 Source Configuration [24] Application -// 3 0xA792 Source ADPCM Coefficients [24] Application -// 10 0xA912 Surround Sound Related -// 11 0xAA12 Surround Sound Related -// 12 0xAAD2 Surround Sound Related -// 13 0xAC52 Surround Sound Related -// 14 0xAC5C Surround Sound Related -// 0 0xBFFF Frame Counter Application -// -// #: This refers to the order in which they appear in the DspPipe::Audio DSP pipe. -// See also: DSP::HLE::PipeRead. -// -// Note that the above addresses do vary slightly between audio firmwares observed; the addresses -// are not fixed in stone. The addresses above are only an examplar; they're what this -// implementation does and provides to applications. -// -// Application requests the DSP service to convert DSP addresses into ARM11 virtual addresses using -// the ConvertProcessAddressFromDspDram service call. Applications seem to derive the addresses for -// the second region via: -// second_region_dsp_addr = first_region_dsp_addr | 0x10000 -// -// Applications maintain most of its own audio state, the memory region is used mainly for -// communication and not storage of state. -// -// In the documentation below, filter and effect transfer functions are specified in the z domain. -// (If you are more familiar with the Laplace transform, z = exp(sT). The z domain is the digital -// frequency domain, just like how the s domain is the analog frequency domain.) - -#define INSERT_PADDING_DSPWORDS(num_words) INSERT_PADDING_BYTES(2 * (num_words)) - -// GCC versions < 5.0 do not implement std::is_trivially_copyable. -// Excluding MSVC because it has weird behaviour for std::is_trivially_copyable. -#if (__GNUC__ >= 5) || defined(__clang__) -#define ASSERT_DSP_STRUCT(name, size) \ - static_assert(std::is_standard_layout<name>::value, \ - "DSP structure " #name " doesn't use standard layout"); \ - static_assert(std::is_trivially_copyable<name>::value, \ - "DSP structure " #name " isn't trivially copyable"); \ - static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name) -#else -#define ASSERT_DSP_STRUCT(name, size) \ - static_assert(std::is_standard_layout<name>::value, \ - "DSP structure " #name " doesn't use standard layout"); \ - static_assert(sizeof(name) == (size), "Unexpected struct size for DSP structure " #name) -#endif - -struct SourceConfiguration { - struct Configuration { - /// These dirty flags are set by the application when it updates the fields in this struct. - /// The DSP clears these each audio frame. - 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; - /// Tends to be set when a looped buffer is queued. - BitField<3, 1, u32_le> partial_embedded_buffer_dirty; - BitField<4, 1, u32_le> partial_reset_flag; - - BitField<16, 1, u32_le> enable_dirty; - BitField<17, 1, u32_le> interpolation_dirty; - BitField<18, 1, u32_le> rate_multiplier_dirty; - BitField<19, 1, u32_le> buffer_queue_dirty; - BitField<20, 1, u32_le> loop_related_dirty; - /// Tends to also be set when embedded buffer is updated. - BitField<21, 1, u32_le> play_position_dirty; - BitField<22, 1, u32_le> filters_enabled_dirty; - BitField<23, 1, u32_le> simple_filter_dirty; - BitField<24, 1, u32_le> biquad_filter_dirty; - BitField<25, 1, u32_le> gain_0_dirty; - BitField<26, 1, u32_le> gain_1_dirty; - BitField<27, 1, u32_le> gain_2_dirty; - BitField<28, 1, u32_le> sync_dirty; - BitField<29, 1, u32_le> reset_flag; - BitField<30, 1, u32_le> embedded_buffer_dirty; - }; - - // Gain control - - /** - * Gain is between 0.0-1.0. This determines how much will this source appear on each of the - * 12 channels that feed into the intermediate mixers. Each of the three intermediate mixers - * is fed two left and two right channels. - */ - float_le gain[3][4]; - - // Interpolation - - /// Multiplier for sample rate. Resampling occurs with the selected interpolation method. - float_le rate_multiplier; - - enum class InterpolationMode : u8 { - Polyphase = 0, - Linear = 1, - None = 2, - }; - - InterpolationMode interpolation_mode; - INSERT_PADDING_BYTES(1); ///< Interpolation related - - // Filters - - /** - * This is the simplest normalized first-order digital recursive filter. - * The transfer function of this filter is: - * H(z) = b0 / (1 - a1 z^-1) - * Note the feedbackward coefficient is negated. - * Values are signed fixed point with 15 fractional bits. - */ - struct SimpleFilter { - s16_le b0; - s16_le a1; - }; - - /** - * This is a normalised biquad filter (second-order). - * The transfer function of this filter is: - * H(z) = (b0 + b1 z^-1 + b2 z^-2) / (1 - a1 z^-1 - a2 z^-2) - * Nintendo chose to negate the feedbackward coefficients. This differs from standard - * notation as in: https://ccrma.stanford.edu/~jos/filters/Direct_Form_I.html - * Values are signed fixed point with 14 fractional bits. - */ - struct BiquadFilter { - s16_le a2; - s16_le a1; - s16_le b2; - s16_le b1; - s16_le b0; - }; - - union { - u16_le filters_enabled; - BitField<0, 1, u16_le> simple_filter_enabled; - BitField<1, 1, u16_le> biquad_filter_enabled; - }; - - SimpleFilter simple_filter; - BiquadFilter biquad_filter; - - // Buffer Queue - - /// A buffer of audio data from the application, along with metadata about it. - struct Buffer { - /// Physical memory address of the start of the buffer - u32_dsp physical_address; - - /// This is length in terms of samples. - /// Note that in different buffer formats a sample takes up different number of bytes. - u32_dsp length; - - /// ADPCM Predictor (4 bits) and Scale (4 bits) - union { - u16_le adpcm_ps; - BitField<0, 4, u16_le> adpcm_scale; - BitField<4, 4, u16_le> adpcm_predictor; - }; - - /// ADPCM Historical Samples (y[n-1] and y[n-2]) - u16_le adpcm_yn[2]; - - /// This is non-zero when the ADPCM values above are to be updated. - u8 adpcm_dirty; - - /// Is a looping buffer. - u8 is_looping; - - /// This value is shown in SourceStatus::previous_buffer_id when this buffer has - /// finished. This allows the emulated application to tell what buffer is currently - /// playing. - u16_le buffer_id; - - INSERT_PADDING_DSPWORDS(1); - }; - - u16_le buffers_dirty; ///< Bitmap indicating which buffers are dirty (bit i -> buffers[i]) - Buffer buffers[4]; ///< Queued Buffers - - // Playback controls - - u32_dsp loop_related; - u8 enable; - INSERT_PADDING_BYTES(1); - u16_le sync; ///< Application-side sync (See also: SourceStatus::sync) - u32_dsp play_position; ///< Position. (Units: number of samples) - INSERT_PADDING_DSPWORDS(2); - - // Embedded Buffer - // This buffer is often the first buffer to be used when initiating audio playback, - // after which the buffer queue is used. - - u32_dsp physical_address; - - /// This is length in terms of samples. - /// Note a sample takes up different number of bytes in different buffer formats. - u32_dsp length; - - enum class MonoOrStereo : u16_le { - Mono = 1, - Stereo = 2, - }; - - enum class Format : u16_le { - PCM8 = 0, - PCM16 = 1, - ADPCM = 2, - }; - - union { - u16_le flags1_raw; - BitField<0, 2, MonoOrStereo> mono_or_stereo; - BitField<2, 2, Format> format; - BitField<5, 1, u16_le> fade_in; - }; - - /// ADPCM Predictor (4 bit) and Scale (4 bit) - union { - u16_le adpcm_ps; - BitField<0, 4, u16_le> adpcm_scale; - BitField<4, 4, u16_le> adpcm_predictor; - }; - - /// ADPCM Historical Samples (y[n-1] and y[n-2]) - u16_le adpcm_yn[2]; - - union { - u16_le flags2_raw; - BitField<0, 1, u16_le> adpcm_dirty; ///< Has the ADPCM info above been changed? - BitField<1, 1, u16_le> is_looping; ///< Is this a looping buffer? - }; - - /// Buffer id of embedded buffer (used as a buffer id in SourceStatus to reference this - /// buffer). - u16_le buffer_id; - }; - - Configuration config[num_sources]; -}; -ASSERT_DSP_STRUCT(SourceConfiguration::Configuration, 192); -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 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 current_buffer_id; ///< Updated when a buffer finishes playing - INSERT_PADDING_DSPWORDS(1); - }; - - Status status[num_sources]; -}; -ASSERT_DSP_STRUCT(SourceStatus::Status, 12); - -struct DspConfiguration { - /// These dirty flags are set by the application when it updates the fields in this struct. - /// The DSP clears these each audio frame. - union { - u32_le dirty_raw; - - BitField<8, 1, u32_le> mixer1_enabled_dirty; - BitField<9, 1, u32_le> mixer2_enabled_dirty; - BitField<10, 1, u32_le> delay_effect_0_dirty; - BitField<11, 1, u32_le> delay_effect_1_dirty; - BitField<12, 1, u32_le> reverb_effect_0_dirty; - BitField<13, 1, u32_le> reverb_effect_1_dirty; - - BitField<16, 1, u32_le> volume_0_dirty; - - BitField<24, 1, u32_le> volume_1_dirty; - BitField<25, 1, u32_le> volume_2_dirty; - BitField<26, 1, u32_le> output_format_dirty; - BitField<27, 1, u32_le> limiter_enabled_dirty; - BitField<28, 1, u32_le> headphones_connected_dirty; - }; - - /// The DSP has three intermediate audio mixers. This controls the volume level (0.0-1.0) for - /// each at the final mixer. - float_le volume[3]; - - INSERT_PADDING_DSPWORDS(3); - - enum class OutputFormat : u16_le { - Mono = 0, - Stereo = 1, - Surround = 2, - }; - - OutputFormat output_format; - - u16_le limiter_enabled; ///< Not sure of the exact gain equation for the limiter. - u16_le headphones_connected; ///< Application updates the DSP on headphone status. - INSERT_PADDING_DSPWORDS(4); ///< TODO: Surround sound related - INSERT_PADDING_DSPWORDS(2); ///< TODO: Intermediate mixer 1/2 related - u16_le mixer1_enabled; - u16_le mixer2_enabled; - - /** - * This is delay with feedback. - * Transfer function: - * H(z) = a z^-N / (1 - b z^-1 + a g z^-N) - * where - * N = frame_count * samples_per_frame - * g, a and b are fixed point with 7 fractional bits - */ - struct DelayEffect { - /// These dirty flags are set by the application when it updates the fields in this struct. - /// The DSP clears these each audio frame. - union { - u16_le dirty_raw; - BitField<0, 1, u16_le> enable_dirty; - BitField<1, 1, u16_le> work_buffer_address_dirty; - BitField<2, 1, u16_le> other_dirty; ///< Set when anything else has been changed - }; - - u16_le enable; - INSERT_PADDING_DSPWORDS(1); - u16_le outputs; - /// The application allocates a block of memory for the DSP to use as a work buffer. - u32_dsp work_buffer_address; - /// Frames to delay by - u16_le frame_count; - - // Coefficients - s16_le g; ///< Fixed point with 7 fractional bits - s16_le a; ///< Fixed point with 7 fractional bits - s16_le b; ///< Fixed point with 7 fractional bits - }; - - DelayEffect delay_effect[2]; - - struct ReverbEffect { - INSERT_PADDING_DSPWORDS(26); ///< TODO - }; - - ReverbEffect reverb_effect[2]; - - INSERT_PADDING_DSPWORDS(4); -}; -ASSERT_DSP_STRUCT(DspConfiguration, 196); -ASSERT_DSP_STRUCT(DspConfiguration::DelayEffect, 20); -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[num_sources][16]; -}; -ASSERT_DSP_STRUCT(AdpcmCoefficients, 768); - -struct DspStatus { - u16_le unknown; - u16_le dropped_frames; - INSERT_PADDING_DSPWORDS(0xE); -}; -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[samples_per_frame][2]; -}; -ASSERT_DSP_STRUCT(FinalMixSamples, 640); - -/// DSP writes output of intermediate mixers 1 and 2 here. -/// Writes to this region by the application edits the output of the intermediate mixers. -/// This seems to be intended to allow the application to do custom effects on the ARM11. -/// Values that exceed s16 range will be clipped by the DSP after further processing. -struct IntermediateMixSamples { - struct Samples { - s32_le pcm32[4][samples_per_frame]; ///< Little-endian as opposed to DSP middle-endian. - }; - - Samples mix1; - Samples mix2; -}; -ASSERT_DSP_STRUCT(IntermediateMixSamples, 5120); - -/// Compressor table -struct Compressor { - INSERT_PADDING_DSPWORDS(0xD20); ///< TODO -}; - -/// There is no easy way to implement this in a HLE implementation. -struct DspDebug { - INSERT_PADDING_DSPWORDS(0x130); -}; -ASSERT_DSP_STRUCT(DspDebug, 0x260); - -struct SharedMemory { - /// Padding - INSERT_PADDING_DSPWORDS(0x400); - - DspStatus dsp_status; - - DspDebug dsp_debug; - - FinalMixSamples final_samples; - - SourceStatus source_statuses; - - Compressor compressor; - - DspConfiguration dsp_configuration; - - IntermediateMixSamples intermediate_mix_samples; - - SourceConfiguration source_configurations; - - AdpcmCoefficients adpcm_coefficients; - - struct { - INSERT_PADDING_DSPWORDS(0x100); - } unknown10; - - struct { - INSERT_PADDING_DSPWORDS(0xC0); - } unknown11; - - struct { - INSERT_PADDING_DSPWORDS(0x180); - } unknown12; - - struct { - INSERT_PADDING_DSPWORDS(0xA); - } unknown13; - - struct { - INSERT_PADDING_DSPWORDS(0x13A3); - } unknown14; - - u16_le frame_counter; -}; -ASSERT_DSP_STRUCT(SharedMemory, 0x8000); - -union DspMemory { - std::array<u8, 0x80000> raw_memory; - struct { - u8 unused_0[0x50000]; - SharedMemory region_0; - u8 unused_1[0x18000]; - SharedMemory region_1; - u8 unused_2[0x8000]; - }; -}; -static_assert(offsetof(DspMemory, region_0) == region0_offset, - "DSP region 0 is at the wrong offset"); -static_assert(offsetof(DspMemory, region_1) == region1_offset, - "DSP region 1 is at the wrong offset"); - -extern DspMemory g_dsp_memory; - -// Structures must have an offset that is a multiple of two. -static_assert(offsetof(SharedMemory, frame_counter) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, source_configurations) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, source_statuses) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, adpcm_coefficients) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, dsp_configuration) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, dsp_status) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, final_samples) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, intermediate_mix_samples) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, compressor) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, dsp_debug) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, unknown10) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, unknown11) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, unknown12) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, unknown13) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); -static_assert(offsetof(SharedMemory, unknown14) % 2 == 0, - "Structures in DSP::HLE::SharedMemory must be 2-byte aligned"); - -#undef INSERT_PADDING_DSPWORDS -#undef ASSERT_DSP_STRUCT - -/// Initialize DSP hardware -void Init(); - -/// Shutdown DSP hardware -void Shutdown(); - -/** - * Perform processing and updates state of current shared memory buffer. - * This function is called every audio tick before triggering the audio interrupt. - * @return Whether an audio interrupt should be triggered this frame. - */ -bool Tick(); - -/** - * 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); - -/** - * Enables/Disables audio-stretching. - * Audio stretching is an enhancement that stretches audio to match emulation - * speed to prevent stuttering at the cost of some audio latency. - * @param enable true to enable, false to disable. - */ -void EnableStretching(bool enable); - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/filter.cpp b/src/audio_core/hle/filter.cpp deleted file mode 100644 index b24a79b89..000000000 --- a/src/audio_core/hle/filter.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// 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 deleted file mode 100644 index 5350e2857..000000000 --- a/src/audio_core/hle/filter.h +++ /dev/null @@ -1,117 +0,0 @@ -// 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 biquad 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/mixers.cpp b/src/audio_core/hle/mixers.cpp deleted file mode 100644 index 6cc81dfca..000000000 --- a/src/audio_core/hle/mixers.cpp +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstddef> - -#include "audio_core/hle/common.h" -#include "audio_core/hle/dsp.h" -#include "audio_core/hle/mixers.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/math_util.h" - -namespace DSP { -namespace HLE { - -void Mixers::Reset() { - current_frame.fill({}); - state = {}; -} - -DspStatus Mixers::Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples, - IntermediateMixSamples& write_samples, - const std::array<QuadFrame32, 3>& input) { - ParseConfig(config); - - AuxReturn(read_samples); - AuxSend(write_samples, input); - - MixCurrentFrame(); - - return GetCurrentStatus(); -} - -void Mixers::ParseConfig(DspConfiguration& config) { - if (!config.dirty_raw) { - return; - } - - if (config.mixer1_enabled_dirty) { - config.mixer1_enabled_dirty.Assign(0); - state.mixer1_enabled = config.mixer1_enabled != 0; - LOG_TRACE(Audio_DSP, "mixers mixer1_enabled = %hu", config.mixer1_enabled); - } - - if (config.mixer2_enabled_dirty) { - config.mixer2_enabled_dirty.Assign(0); - state.mixer2_enabled = config.mixer2_enabled != 0; - LOG_TRACE(Audio_DSP, "mixers mixer2_enabled = %hu", config.mixer2_enabled); - } - - if (config.volume_0_dirty) { - config.volume_0_dirty.Assign(0); - state.intermediate_mixer_volume[0] = config.volume[0]; - LOG_TRACE(Audio_DSP, "mixers volume[0] = %f", config.volume[0]); - } - - if (config.volume_1_dirty) { - config.volume_1_dirty.Assign(0); - state.intermediate_mixer_volume[1] = config.volume[1]; - LOG_TRACE(Audio_DSP, "mixers volume[1] = %f", config.volume[1]); - } - - if (config.volume_2_dirty) { - config.volume_2_dirty.Assign(0); - state.intermediate_mixer_volume[2] = config.volume[2]; - LOG_TRACE(Audio_DSP, "mixers volume[2] = %f", config.volume[2]); - } - - if (config.output_format_dirty) { - config.output_format_dirty.Assign(0); - state.output_format = config.output_format; - LOG_TRACE(Audio_DSP, "mixers output_format = %zu", - static_cast<size_t>(config.output_format)); - } - - if (config.headphones_connected_dirty) { - config.headphones_connected_dirty.Assign(0); - // Do nothing. (Note: Whether headphones are connected does affect coefficients used for - // surround sound.) - LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected); - } - - if (config.dirty_raw) { - LOG_DEBUG(Audio_DSP, "mixers remaining_dirty=%x", config.dirty_raw); - } - - config.dirty_raw = 0; -} - -static s16 ClampToS16(s32 value) { - return static_cast<s16>(MathUtil::Clamp(value, -32768, 32767)); -} - -static std::array<s16, 2> AddAndClampToS16(const std::array<s16, 2>& a, - const std::array<s16, 2>& b) { - return {ClampToS16(static_cast<s32>(a[0]) + static_cast<s32>(b[0])), - ClampToS16(static_cast<s32>(a[1]) + static_cast<s32>(b[1]))}; -} - -void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples) { - // TODO(merry): Limiter. (Currently we're performing final mixing assuming a disabled limiter.) - - switch (state.output_format) { - case OutputFormat::Mono: - std::transform( - current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(), - [gain](const std::array<s16, 2>& accumulator, - const std::array<s32, 4>& sample) -> std::array<s16, 2> { - // Downmix to mono - s16 mono = ClampToS16(static_cast<s32>( - (gain * sample[0] + gain * sample[1] + gain * sample[2] + gain * sample[3]) / - 2)); - // Mix into current frame - return AddAndClampToS16(accumulator, {mono, mono}); - }); - return; - - case OutputFormat::Surround: - // TODO(merry): Implement surround sound. - // fallthrough - - case OutputFormat::Stereo: - std::transform( - current_frame.begin(), current_frame.end(), samples.begin(), current_frame.begin(), - [gain](const std::array<s16, 2>& accumulator, - const std::array<s32, 4>& sample) -> std::array<s16, 2> { - // Downmix to stereo - s16 left = ClampToS16(static_cast<s32>(gain * sample[0] + gain * sample[2])); - s16 right = ClampToS16(static_cast<s32>(gain * sample[1] + gain * sample[3])); - // Mix into current frame - return AddAndClampToS16(accumulator, {left, right}); - }); - return; - } - - UNREACHABLE_MSG("Invalid output_format %zu", static_cast<size_t>(state.output_format)); -} - -void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) { - // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to - // QuadFrame32. - - if (state.mixer1_enabled) { - for (size_t sample = 0; sample < samples_per_frame; sample++) { - for (size_t channel = 0; channel < 4; channel++) { - state.intermediate_mix_buffer[1][sample][channel] = - read_samples.mix1.pcm32[channel][sample]; - } - } - } - - if (state.mixer2_enabled) { - for (size_t sample = 0; sample < samples_per_frame; sample++) { - for (size_t channel = 0; channel < 4; channel++) { - state.intermediate_mix_buffer[2][sample][channel] = - read_samples.mix2.pcm32[channel][sample]; - } - } - } -} - -void Mixers::AuxSend(IntermediateMixSamples& write_samples, - const std::array<QuadFrame32, 3>& input) { - // NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to - // QuadFrame32. - - state.intermediate_mix_buffer[0] = input[0]; - - if (state.mixer1_enabled) { - for (size_t sample = 0; sample < samples_per_frame; sample++) { - for (size_t channel = 0; channel < 4; channel++) { - write_samples.mix1.pcm32[channel][sample] = input[1][sample][channel]; - } - } - } else { - state.intermediate_mix_buffer[1] = input[1]; - } - - if (state.mixer2_enabled) { - for (size_t sample = 0; sample < samples_per_frame; sample++) { - for (size_t channel = 0; channel < 4; channel++) { - write_samples.mix2.pcm32[channel][sample] = input[2][sample][channel]; - } - } - } else { - state.intermediate_mix_buffer[2] = input[2]; - } -} - -void Mixers::MixCurrentFrame() { - current_frame.fill({}); - - for (size_t mix = 0; mix < 3; mix++) { - DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix], - state.intermediate_mix_buffer[mix]); - } - - // TODO(merry): Compressor. (We currently assume a disabled compressor.) -} - -DspStatus Mixers::GetCurrentStatus() const { - DspStatus status; - status.unknown = 0; - status.dropped_frames = 0; - return status; -} - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h deleted file mode 100644 index bf4e865ae..000000000 --- a/src/audio_core/hle/mixers.h +++ /dev/null @@ -1,61 +0,0 @@ -// 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" - -namespace DSP { -namespace HLE { - -class Mixers final { -public: - Mixers() { - Reset(); - } - - void Reset(); - - DspStatus Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples, - IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input); - - StereoFrame16 GetOutput() const { - return current_frame; - } - -private: - StereoFrame16 current_frame = {}; - - using OutputFormat = DspConfiguration::OutputFormat; - - struct { - std::array<float, 3> intermediate_mixer_volume = {}; - - bool mixer1_enabled = false; - bool mixer2_enabled = false; - std::array<QuadFrame32, 3> intermediate_mix_buffer = {}; - - OutputFormat output_format = OutputFormat::Stereo; - - } state; - - /// INTERNAL: Update our internal state based on the current config. - void ParseConfig(DspConfiguration& config); - /// INTERNAL: Read samples from shared memory that have been modified by the ARM11. - void AuxReturn(const IntermediateMixSamples& read_samples); - /// INTERNAL: Write samples to shared memory for the ARM11 to modify. - void AuxSend(IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input); - /// INTERNAL: Mix current_frame. - void MixCurrentFrame(); - /// INTERNAL: Downmix from quadraphonic to stereo based on status.output_format and accumulate - /// into current_frame. - void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples); - /// INTERNAL: Generate DspStatus based on internal state. - DspStatus GetCurrentStatus() const; -}; - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/pipe.cpp b/src/audio_core/hle/pipe.cpp deleted file mode 100644 index 24074a514..000000000 --- a/src/audio_core/hle/pipe.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <array> -#include <vector> -#include "audio_core/hle/dsp.h" -#include "audio_core/hle/pipe.h" -#include "common/assert.h" -#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>, NUM_DSP_PIPE> pipe_data; - -void ResetPipes() { - for (auto& data : pipe_data) { - data.clear(); - } - dsp_state = DspState::Off; -} - -std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) { - 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 {}; - } - - if (length > UINT16_MAX) { // Can only read at most UINT16_MAX from the pipe - LOG_ERROR(Audio_DSP, "length of %u greater than max of %u", length, UINT16_MAX); - return {}; - } - - std::vector<u8>& data = pipe_data[pipe_index]; - - if (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 = static_cast<u32>(data.size()); - } - - if (length == 0) - return {}; - - std::vector<u8> ret(data.begin(), data.begin() + length); - data.erase(data.begin(), data.begin() + length); - return ret; -} - -size_t GetPipeReadableSize(DspPipe 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[pipe_index].size(); -} - -static void WriteU16(DspPipe pipe_number, u16 value) { - 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); -} - -static void AudioPipeWriteStructAddresses() { - // These struct addresses are DSP dram addresses. - // See also: DSP_DSP::ConvertProcessAddressFromDspDram - static const std::array<u16, 15> struct_addresses = { - 0x8000 + offsetof(SharedMemory, frame_counter) / 2, - 0x8000 + offsetof(SharedMemory, source_configurations) / 2, - 0x8000 + offsetof(SharedMemory, source_statuses) / 2, - 0x8000 + offsetof(SharedMemory, adpcm_coefficients) / 2, - 0x8000 + offsetof(SharedMemory, dsp_configuration) / 2, - 0x8000 + offsetof(SharedMemory, dsp_status) / 2, - 0x8000 + offsetof(SharedMemory, final_samples) / 2, - 0x8000 + offsetof(SharedMemory, intermediate_mix_samples) / 2, - 0x8000 + offsetof(SharedMemory, compressor) / 2, - 0x8000 + offsetof(SharedMemory, dsp_debug) / 2, - 0x8000 + offsetof(SharedMemory, unknown10) / 2, - 0x8000 + offsetof(SharedMemory, unknown11) / 2, - 0x8000 + offsetof(SharedMemory, unknown12) / 2, - 0x8000 + offsetof(SharedMemory, unknown13) / 2, - 0x8000 + offsetof(SharedMemory, unknown14) / 2, - }; - - // Begin with a u16 denoting the number of structs. - WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size())); - // Then write the struct addresses. - for (u16 addr : struct_addresses) { - WriteU16(DspPipe::Audio, addr); - } - // Signal that we have data on this pipe. - Service::DSP_DSP::SignalPipeInterrupt(DspPipe::Audio); -} - -void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) { - switch (pipe_number) { - case DspPipe::Audio: { - if (buffer.size() != 4) { - LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length %zu was written", - buffer.size()); - return; - } - - enum class StateChange { - Initialize = 0, - Shutdown = 1, - Wakeup = 2, - Sleep = 3, - }; - - // The difference between Initialize and Wakeup is that Input state is maintained - // when sleeping but isn't when turning it off and on again. (TODO: Implement this.) - // Waking up from sleep garbles some of the structs in the memory region. (TODO: - // Implement this.) Applications store away the state of these structs before - // sleeping and reset it back after wakeup on behalf of the DSP. - - switch (static_cast<StateChange>(buffer[0])) { - case StateChange::Initialize: - LOG_INFO(Audio_DSP, "Application has requested initialization of DSP hardware"); - ResetPipes(); - AudioPipeWriteStructAddresses(); - dsp_state = DspState::On; - break; - case StateChange::Shutdown: - LOG_INFO(Audio_DSP, "Application has requested shutdown of DSP hardware"); - dsp_state = DspState::Off; - break; - case StateChange::Wakeup: - LOG_INFO(Audio_DSP, "Application has requested wakeup of DSP hardware"); - ResetPipes(); - AudioPipeWriteStructAddresses(); - dsp_state = DspState::On; - break; - case StateChange::Sleep: - LOG_INFO(Audio_DSP, "Application has requested sleep of DSP hardware"); - UNIMPLEMENTED(); - dsp_state = DspState::Sleeping; - break; - default: - LOG_ERROR(Audio_DSP, - "Application has requested unknown state transition of DSP hardware %hhu", - buffer[0]); - dsp_state = DspState::Off; - break; - } - - return; - } - default: - LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", - static_cast<size_t>(pipe_number)); - UNIMPLEMENTED(); - return; - } -} - -DspState GetDspState() { - return dsp_state; -} - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/pipe.h b/src/audio_core/hle/pipe.h deleted file mode 100644 index ac053c029..000000000 --- a/src/audio_core/hle/pipe.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <vector> -#include "common/common_types.h" - -namespace DSP { -namespace HLE { - -/// Reset the pipes by setting pipe positions back to the beginning. -void ResetPipes(); - -enum class DspPipe { - Debug = 0, - Dma = 1, - Audio = 2, - Binary = 3, -}; -constexpr size_t NUM_DSP_PIPE = 8; - -/** - * Reads `length` bytes from the DSP pipe identified with `pipe_number`. - * @note Can read up to the maximum value of a u16 in bytes (65,535). - * @note IF an error is encoutered with either an invalid `pipe_number` or `length` value, an empty - * vector will be returned. - * @note IF `length` is set to 0, an empty vector will be returned. - * @note IF `length` is greater than the amount of data available, this function will only read the - * available amount. - * @param pipe_number a `DspPipe` - * @param length the number of bytes to read. The max is 65,535 (max of u16). - * @returns a vector of bytes from the specified pipe. On error, will be empty. - */ -std::vector<u8> PipeRead(DspPipe pipe_number, u32 length); - -/** - * How much data is left in pipe - * @param pipe_number The Pipe ID - * @return The amount of data remaning in the pipe. This is the maximum length PipeRead will return. - */ -size_t GetPipeReadableSize(DspPipe pipe_number); - -/** - * Write to a DSP pipe. - * @param pipe_number The Pipe ID - * @param buffer The data to write to the pipe. - */ -void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer); - -enum class DspState { - Off, - On, - Sleeping, -}; - -/// Get the state of the DSP -DspState GetDspState(); - -} // namespace HLE -} // namespace DSP diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp deleted file mode 100644 index c12287700..000000000 --- a/src/audio_core/hle/source.cpp +++ /dev/null @@ -1,349 +0,0 @@ -// 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", source_id); - } - - 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", source_id); - } - - 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)); - } - - u32_dsp play_position = {}; - if (config.play_position_dirty && config.play_position != 0) { - config.play_position_dirty.Assign(0); - play_position = config.play_position; - // play_position applies only to the embedded buffer, and defaults to 0 w/o a dirty bit - // This will be the starting sample for the first time the buffer is played. - } - - 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, - play_position, - false, - }); - LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu start=%u", - config.physical_address, config.length, config.buffer_id, - static_cast<u32>(config.play_position)); - } - - if (config.loop_related_dirty && config.loop_related != 0) { - config.loop_related_dirty.Assign(0); - LOG_WARNING(Audio_DSP, "Unhandled complex loop with loop_related=0x%08x", - static_cast<u32>(config.loop_related)); - } - - 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, - {}, // 0 in u32_dsp - false, - }); - 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; - } - - switch (state.interpolation_mode) { - case InterpolationMode::None: - AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier, - current_frame, frame_position); - break; - case InterpolationMode::Linear: - AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier, - current_frame, frame_position); - break; - case InterpolationMode::Polyphase: - // TODO(merry): Implement polyphase interpolation - LOG_DEBUG(Audio_DSP, "Polyphase interpolation unimplemented; falling back to linear"); - AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier, - current_frame, frame_position); - break; - default: - UNIMPLEMENTED(); - break; - } - } - state.next_sample_number += static_cast<u32>(frame_position); - - 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; - - Buffer buf = state.input_queue.top(); - - // if we're in a loop, the current sound keeps playing afterwards, so leave the queue alone - if (!buf.is_looping) { - state.input_queue.pop(); - } - - if (buf.adpcm_dirty) { - state.adpcm_state.yn1 = buf.adpcm_yn[0]; - state.adpcm_state.yn2 = buf.adpcm_yn[1]; - } - - 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; - } - - // the first playthrough starts at play_position, loops start at the beginning of the buffer - state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; - state.next_sample_number = state.current_sample_number; - state.current_buffer_id = buf.buffer_id; - state.buffer_update = buf.from_queue && !buf.has_played; - - buf.has_played = true; - - 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 deleted file mode 100644 index c4d2debc2..000000000 --- a/src/audio_core/hle/source.h +++ /dev/null @@ -1,149 +0,0 @@ -// 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; - u32_dsp play_position; // = 0; - bool has_played; // = false; - }; - - 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; - AudioInterp::StereoBuffer16 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 deleted file mode 100644 index 83573d772..000000000 --- a/src/audio_core/interpolate.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// 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, until we consume all of the input. -/// Three adjacent samples are passed to fn each step. -template <typename Function> -static void StepOverSamples(State& state, StereoBuffer16& input, float rate, - DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) { - ASSERT(rate > 0); - - if (input.empty()) - return; - - input.insert(input.begin(), {state.xn2, state.xn1}); - - const u64 step_size = static_cast<u64>(rate * scale_factor); - u64 fposition = state.fposition; - size_t inputi = 0; - - while (outputi < output.size()) { - inputi = static_cast<size_t>(fposition / scale_factor); - - if (inputi + 2 >= input.size()) { - inputi = input.size() - 2; - break; - } - - u64 fraction = fposition & scale_mask; - output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]); - - fposition += step_size; - } - - state.xn2 = input[inputi]; - state.xn1 = input[inputi + 1]; - state.fposition = fposition - inputi * scale_factor; - - input.erase(input.begin(), std::next(input.begin(), inputi + 2)); -} - -void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, - size_t& outputi) { - StepOverSamples( - state, input, rate, output, outputi, - [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; }); -} - -void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, - size_t& outputi) { - // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware. - StepOverSamples(state, input, rate, output, outputi, - [](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 deleted file mode 100644 index 8dff6111a..000000000 --- a/src/audio_core/interpolate.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <deque> -#include "audio_core/hle/common.h" -#include "common/common_types.h" - -namespace AudioInterp { - -/// A variable length buffer of signed PCM16 stereo samples. -using StereoBuffer16 = std::deque<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] - /// Current fractional position. - u64 fposition = 0; -}; - -/** - * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. - * @param state Interpolation state. - * @param input Input buffer. - * @param rate Stretch factor. Must be a positive non-zero value. - * rate > 1.0 performs decimation and rate < 1.0 performs upsampling. - * @param output The resampled audio buffer. - * @param outputi The index of output to start writing to. - */ -void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, - size_t& outputi); - -/** - * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. - * @param state Interpolation state. - * @param input Input buffer. - * @param rate Stretch factor. Must be a positive non-zero value. - * rate > 1.0 performs decimation and rate < 1.0 performs upsampling. - * @param output The resampled audio buffer. - * @param outputi The index of output to start writing to. - */ -void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, - size_t& outputi); - -} // namespace AudioInterp diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h deleted file mode 100644 index c732926a2..000000000 --- a/src/audio_core/null_sink.h +++ /dev/null @@ -1,34 +0,0 @@ -// 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 s16*, size_t) override {} - - size_t SamplesInQueue() const override { - return 0; - } - - void SetDevice(int device_id) override {} - - std::vector<std::string> GetDeviceList() const override { - return {}; - } -}; - -} // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp deleted file mode 100644 index 933c5f16d..000000000 --- a/src/audio_core/sdl2_sink.cpp +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <list> -#include <numeric> -#include <SDL.h> -#include "audio_core/audio_core.h" -#include "audio_core/sdl2_sink.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "core/settings.h" - -namespace AudioCore { - -struct SDL2Sink::Impl { - unsigned int sample_rate = 0; - - SDL_AudioDeviceID audio_device_id = 0; - - std::list<std::vector<s16>> queue; - - static void Callback(void* impl_, u8* buffer, int buffer_size_in_bytes); -}; - -SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) { - if (SDL_Init(SDL_INIT_AUDIO) < 0) { - LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed with: %s", SDL_GetError()); - impl->audio_device_id = 0; - return; - } - - SDL_AudioSpec desired_audiospec; - SDL_zero(desired_audiospec); - desired_audiospec.format = AUDIO_S16; - desired_audiospec.channels = 2; - desired_audiospec.freq = native_sample_rate; - desired_audiospec.samples = 512; - desired_audiospec.userdata = impl.get(); - desired_audiospec.callback = &Impl::Callback; - - SDL_AudioSpec obtained_audiospec; - SDL_zero(obtained_audiospec); - - int device_count = SDL_GetNumAudioDevices(0); - device_list.clear(); - for (int i = 0; i < device_count; ++i) { - device_list.push_back(SDL_GetAudioDeviceName(i, 0)); - } - - const char* device = nullptr; - - if (device_count >= 1 && Settings::values.audio_device_id != "auto" && - !Settings::values.audio_device_id.empty()) { - device = Settings::values.audio_device_id.c_str(); - } - - impl->audio_device_id = SDL_OpenAudioDevice(device, false, &desired_audiospec, - &obtained_audiospec, SDL_AUDIO_ALLOW_ANY_CHANGE); - if (impl->audio_device_id <= 0) { - LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with code %d for device \"%s\"", - impl->audio_device_id, Settings::values.audio_device_id.c_str()); - return; - } - - impl->sample_rate = obtained_audiospec.freq; - - // SDL2 audio devices start out paused, unpause it: - SDL_PauseAudioDevice(impl->audio_device_id, 0); -} - -SDL2Sink::~SDL2Sink() { - if (impl->audio_device_id <= 0) - return; - - SDL_CloseAudioDevice(impl->audio_device_id); -} - -unsigned int SDL2Sink::GetNativeSampleRate() const { - if (impl->audio_device_id <= 0) - return native_sample_rate; - - return impl->sample_rate; -} - -std::vector<std::string> SDL2Sink::GetDeviceList() const { - return device_list; -} - -void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) { - if (impl->audio_device_id <= 0) - return; - - SDL_LockAudioDevice(impl->audio_device_id); - impl->queue.emplace_back(samples, samples + sample_count * 2); - SDL_UnlockAudioDevice(impl->audio_device_id); -} - -size_t SDL2Sink::SamplesInQueue() const { - if (impl->audio_device_id <= 0) - return 0; - - SDL_LockAudioDevice(impl->audio_device_id); - - size_t total_size = std::accumulate(impl->queue.begin(), impl->queue.end(), - static_cast<size_t>(0), [](size_t sum, const auto& buffer) { - // Division by two because each stereo sample is made of - // two s16. - return sum + buffer.size() / 2; - }); - - SDL_UnlockAudioDevice(impl->audio_device_id); - - return total_size; -} - -void SDL2Sink::SetDevice(int device_id) { - this->device_id = device_id; -} - -void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) { - Impl* impl = reinterpret_cast<Impl*>(impl_); - - size_t remaining_size = static_cast<size_t>(buffer_size_in_bytes) / - sizeof(s16); // Keep track of size in 16-bit increments. - - while (remaining_size > 0 && !impl->queue.empty()) { - if (impl->queue.front().size() <= remaining_size) { - memcpy(buffer, impl->queue.front().data(), impl->queue.front().size() * sizeof(s16)); - buffer += impl->queue.front().size() * sizeof(s16); - remaining_size -= impl->queue.front().size(); - impl->queue.pop_front(); - } else { - memcpy(buffer, impl->queue.front().data(), remaining_size * sizeof(s16)); - buffer += remaining_size * sizeof(s16); - impl->queue.front().erase(impl->queue.front().begin(), - impl->queue.front().begin() + remaining_size); - remaining_size = 0; - } - } - - if (remaining_size > 0) { - memset(buffer, 0, remaining_size * sizeof(s16)); - } -} - -} // namespace AudioCore diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h deleted file mode 100644 index bcc725369..000000000 --- a/src/audio_core/sdl2_sink.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <memory> -#include "audio_core/sink.h" - -namespace AudioCore { - -class SDL2Sink final : public Sink { -public: - SDL2Sink(); - ~SDL2Sink() override; - - unsigned int GetNativeSampleRate() const override; - - void EnqueueSamples(const s16* samples, size_t sample_count) override; - - size_t SamplesInQueue() const override; - - std::vector<std::string> GetDeviceList() const override; - void SetDevice(int device_id) override; - -private: - struct Impl; - std::unique_ptr<Impl> impl; - int device_id; - std::vector<std::string> device_list; -}; - -} // namespace AudioCore diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h deleted file mode 100644 index c69cb2c74..000000000 --- a/src/audio_core/sink.h +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <vector> -#include "common/common_types.h" - -namespace AudioCore { - -/** - * This class is an interface for an audio sink. An audio sink accepts samples in stereo signed - * PCM16 format to be output. Sinks *do not* handle resampling and expect the correct sample rate. - * They are dumb outputs. - */ -class Sink { -public: - virtual ~Sink() = default; - - /// The native rate of this sink. The sink expects to be fed samples that respect this. (Units: - /// samples/sec) - virtual unsigned int GetNativeSampleRate() const = 0; - - /** - * Feed stereo samples to sink. - * @param samples Samples in interleaved stereo PCM16 format. - * @param sample_count Number of samples. - */ - virtual void EnqueueSamples(const s16* samples, size_t sample_count) = 0; - - /// Samples enqueued that have not been played yet. - virtual std::size_t SamplesInQueue() const = 0; - - /** - * Sets the desired output device. - * @param device_id ID of the desired device. - */ - virtual void SetDevice(int device_id) = 0; - - /// Returns the list of available devices. - virtual std::vector<std::string> GetDeviceList() const = 0; -}; - -} // namespace diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp deleted file mode 100644 index 6972395af..000000000 --- a/src/audio_core/sink_details.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <memory> -#include <vector> -#include "audio_core/null_sink.h" -#include "audio_core/sink_details.h" -#ifdef HAVE_SDL2 -#include "audio_core/sdl2_sink.h" -#endif -#include "common/logging/log.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 = { -#ifdef HAVE_SDL2 - {"sdl2", []() { return std::make_unique<SDL2Sink>(); }}, -#endif - {"null", []() { return std::make_unique<NullSink>(); }}, -}; - -const SinkDetails& GetSinkDetails(std::string sink_id) { - 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 (sink_id == "auto" || iter == g_sink_details.end()) { - if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str()); - } - // Auto-select. - // g_sink_details is ordered in terms of desirability, with the best choice at the front. - iter = g_sink_details.begin(); - } - - return *iter; -} - -} // namespace AudioCore diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h deleted file mode 100644 index 9d3735171..000000000 --- a/src/audio_core/sink_details.h +++ /dev/null @@ -1,29 +0,0 @@ -// 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; - -const SinkDetails& GetSinkDetails(std::string sink_id); - -} // namespace AudioCore diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp deleted file mode 100644 index 437cf9752..000000000 --- a/src/audio_core/time_stretch.cpp +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <chrono> -#include <cmath> -#include <vector> -#include <SoundTouch.h> -#include "audio_core/audio_core.h" -#include "audio_core/time_stretch.h" -#include "common/common_types.h" -#include "common/logging/log.h" -#include "common/math_util.h" - -using steady_clock = std::chrono::steady_clock; - -namespace AudioCore { - -constexpr double MIN_RATIO = 0.1; -constexpr double MAX_RATIO = 100.0; - -static double ClampRatio(double ratio) { - return MathUtil::Clamp(ratio, MIN_RATIO, MAX_RATIO); -} - -constexpr double MIN_DELAY_TIME = 0.05; // Units: seconds -constexpr double MAX_DELAY_TIME = 0.25; // Units: seconds -constexpr size_t DROP_FRAMES_SAMPLE_DELAY = 16000; // Units: samples - -constexpr double SMOOTHING_FACTOR = 0.007; - -struct TimeStretcher::Impl { - soundtouch::SoundTouch soundtouch; - - steady_clock::time_point frame_timer = steady_clock::now(); - size_t samples_queued = 0; - - double smoothed_ratio = 1.0; - - double sample_rate = static_cast<double>(native_sample_rate); -}; - -std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) { - // This is a very simple algorithm without any fancy control theory. It works and is stable. - - double ratio = CalculateCurrentRatio(); - ratio = CorrectForUnderAndOverflow(ratio, samples_in_queue); - impl->smoothed_ratio = - (1.0 - SMOOTHING_FACTOR) * impl->smoothed_ratio + SMOOTHING_FACTOR * ratio; - impl->smoothed_ratio = ClampRatio(impl->smoothed_ratio); - - // SoundTouch's tempo definition the inverse of our ratio definition. - impl->soundtouch.setTempo(1.0 / impl->smoothed_ratio); - - std::vector<s16> samples = GetSamples(); - if (samples_in_queue >= DROP_FRAMES_SAMPLE_DELAY) { - samples.clear(); - LOG_TRACE(Audio, "Dropping frames!"); - } - return samples; -} - -TimeStretcher::TimeStretcher() : impl(std::make_unique<Impl>()) { - impl->soundtouch.setPitch(1.0); - impl->soundtouch.setChannels(2); - impl->soundtouch.setSampleRate(native_sample_rate); - Reset(); -} - -TimeStretcher::~TimeStretcher() { - impl->soundtouch.clear(); -} - -void TimeStretcher::SetOutputSampleRate(unsigned int sample_rate) { - impl->sample_rate = static_cast<double>(sample_rate); - impl->soundtouch.setRate(static_cast<double>(native_sample_rate) / impl->sample_rate); -} - -void TimeStretcher::AddSamples(const s16* buffer, size_t num_samples) { - impl->soundtouch.putSamples(buffer, static_cast<uint>(num_samples)); - impl->samples_queued += num_samples; -} - -void TimeStretcher::Flush() { - impl->soundtouch.flush(); -} - -void TimeStretcher::Reset() { - impl->soundtouch.setTempo(1.0); - impl->soundtouch.clear(); - impl->smoothed_ratio = 1.0; - impl->frame_timer = steady_clock::now(); - impl->samples_queued = 0; - SetOutputSampleRate(native_sample_rate); -} - -double TimeStretcher::CalculateCurrentRatio() { - const steady_clock::time_point now = steady_clock::now(); - const std::chrono::duration<double> duration = now - impl->frame_timer; - - const double expected_time = - static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate); - const double actual_time = duration.count(); - - double ratio; - if (expected_time != 0) { - ratio = ClampRatio(actual_time / expected_time); - } else { - ratio = impl->smoothed_ratio; - } - - impl->frame_timer = now; - impl->samples_queued = 0; - - return ratio; -} - -double TimeStretcher::CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const { - const size_t min_sample_delay = static_cast<size_t>(MIN_DELAY_TIME * impl->sample_rate); - const size_t max_sample_delay = static_cast<size_t>(MAX_DELAY_TIME * impl->sample_rate); - - if (sample_delay < min_sample_delay) { - // Make the ratio bigger. - ratio = ratio > 1.0 ? ratio * ratio : sqrt(ratio); - } else if (sample_delay > max_sample_delay) { - // Make the ratio smaller. - ratio = ratio > 1.0 ? sqrt(ratio) : ratio * ratio; - } - - return ClampRatio(ratio); -} - -std::vector<s16> TimeStretcher::GetSamples() { - uint available = impl->soundtouch.numSamples(); - - std::vector<s16> output(static_cast<size_t>(available) * 2); - - impl->soundtouch.receiveSamples(output.data(), available); - - return output; -} - -} // namespace AudioCore diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h deleted file mode 100644 index c98b16705..000000000 --- a/src/audio_core/time_stretch.h +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <memory> -#include <vector> -#include "common/common_types.h" - -namespace AudioCore { - -class TimeStretcher final { -public: - TimeStretcher(); - ~TimeStretcher(); - - /** - * Set sample rate for the samples that Process returns. - * @param sample_rate The sample rate. - */ - void SetOutputSampleRate(unsigned int sample_rate); - - /** - * Add samples to be processed. - * @param sample_buffer Buffer of samples in interleaved stereo PCM16 format. - * @param num_samples Number of samples. - */ - void AddSamples(const s16* sample_buffer, size_t num_samples); - - /// Flush audio remaining in internal buffers. - void Flush(); - - /// Resets internal state and clears buffers. - void Reset(); - - /** - * Does audio stretching and produces the time-stretched samples. - * Timer calculations use sample_delay to determine how much of a margin we have. - * @param sample_delay How many samples are buffered downstream of this module and haven't been - * played yet. - * @return Samples to play in interleaved stereo PCM16 format. - */ - std::vector<s16> Process(size_t sample_delay); - -private: - struct Impl; - std::unique_ptr<Impl> impl; - - /// INTERNAL: ratio = wallclock time / emulated time - double CalculateCurrentRatio(); - /// INTERNAL: If we have too many or too few samples downstream, nudge ratio in the appropriate - /// direction. - double CorrectForUnderAndOverflow(double ratio, size_t sample_delay) const; - /// INTERNAL: Gets the time-stretched samples from SoundTouch. - std::vector<s16> GetSamples(); -}; - -} // namespace AudioCore |