Sources: Run clang-format on everything.
This commit is contained in:
parent
fe948af095
commit
dc8479928c
386 changed files with 19560 additions and 18080 deletions
|
@ -42,10 +42,18 @@ void Init() {
|
|||
}
|
||||
|
||||
void AddAddressSpace(Kernel::VMManager& address_space) {
|
||||
auto r0_vma = address_space.MapBackingMemory(DSP::HLE::region0_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
|
||||
auto r0_vma = address_space
|
||||
.MapBackingMemory(DSP::HLE::region0_base,
|
||||
reinterpret_cast<u8*>(&DSP::HLE::g_regions[0]),
|
||||
sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO)
|
||||
.MoveFrom();
|
||||
address_space.Reprotect(r0_vma, Kernel::VMAPermission::ReadWrite);
|
||||
|
||||
auto r1_vma = address_space.MapBackingMemory(DSP::HLE::region1_base, reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]), sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO).MoveFrom();
|
||||
auto r1_vma = address_space
|
||||
.MapBackingMemory(DSP::HLE::region1_base,
|
||||
reinterpret_cast<u8*>(&DSP::HLE::g_regions[1]),
|
||||
sizeof(DSP::HLE::SharedMemory), Kernel::MemoryState::IO)
|
||||
.MoveFrom();
|
||||
address_space.Reprotect(r1_vma, Kernel::VMAPermission::ReadWrite);
|
||||
}
|
||||
|
||||
|
@ -58,9 +66,9 @@ void SelectSink(std::string sink_id) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto iter = std::find_if(g_sink_details.begin(), g_sink_details.end(), [sink_id](const auto& sink_detail) {
|
||||
return sink_detail.id == sink_id;
|
||||
});
|
||||
auto iter =
|
||||
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
||||
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||
|
||||
if (iter == g_sink_details.end()) {
|
||||
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id");
|
||||
|
|
|
@ -12,7 +12,7 @@ class VMManager;
|
|||
|
||||
namespace AudioCore {
|
||||
|
||||
constexpr int native_sample_rate = 32728; ///< 32kHz
|
||||
constexpr int native_sample_rate = 32728; ///< 32kHz
|
||||
|
||||
/// Initialise Audio Core
|
||||
void Init();
|
||||
|
|
|
@ -15,22 +15,25 @@
|
|||
|
||||
namespace Codec {
|
||||
|
||||
StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, const std::array<s16, 16>& adpcm_coeff, ADPCMState& state) {
|
||||
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 }};
|
||||
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.
|
||||
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;
|
||||
int yn1 = state.yn1, yn2 = state.yn2;
|
||||
|
||||
const size_t NUM_FRAMES = (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up.
|
||||
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);
|
||||
|
@ -43,7 +46,8 @@ StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, cons
|
|||
// 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.
|
||||
// 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;
|
||||
|
@ -82,7 +86,8 @@ static s16 SignExtendS8(u8 x) {
|
|||
return static_cast<s16>(static_cast<s8>(x));
|
||||
}
|
||||
|
||||
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, const size_t sample_count) {
|
||||
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);
|
||||
|
@ -101,7 +106,8 @@ StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, con
|
|||
return ret;
|
||||
}
|
||||
|
||||
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, const size_t sample_count) {
|
||||
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);
|
||||
|
@ -118,5 +124,4 @@ StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, co
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -29,7 +29,8 @@ struct ADPCMState {
|
|||
* @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);
|
||||
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
|
||||
|
@ -37,7 +38,8 @@ StereoBuffer16 DecodeADPCM(const u8* const data, const size_t sample_count, cons
|
|||
* @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);
|
||||
StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data,
|
||||
const size_t sample_count);
|
||||
|
||||
/**
|
||||
* @param num_channels Number of channels
|
||||
|
@ -45,6 +47,6 @@ StereoBuffer16 DecodePCM8(const unsigned num_channels, const u8* const data, con
|
|||
* @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);
|
||||
|
||||
StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data,
|
||||
const size_t sample_count);
|
||||
};
|
||||
|
|
|
@ -13,23 +13,22 @@ namespace DSP {
|
|||
namespace HLE {
|
||||
|
||||
constexpr int num_sources = 24;
|
||||
constexpr int samples_per_frame = 160; ///< Samples per audio frame at native sample rate
|
||||
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>;
|
||||
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>
|
||||
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);
|
||||
});
|
||||
std::transform(frame.begin(), frame.end(), frame.begin(),
|
||||
[&filter](const auto& sample) { return filter.ProcessSample(sample); });
|
||||
}
|
||||
|
||||
} // namespace HLE
|
||||
|
|
|
@ -47,11 +47,9 @@ static SharedMemory& WriteRegion() {
|
|||
// 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)
|
||||
};
|
||||
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() {
|
||||
|
@ -62,14 +60,16 @@ static StereoFrame16 GenerateCurrentFrame() {
|
|||
|
||||
// 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]);
|
||||
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);
|
||||
write.dsp_status = mixers.Tick(read.dsp_configuration, read.intermediate_mix_samples,
|
||||
write.intermediate_mix_samples, intermediate_mixes);
|
||||
|
||||
StereoFrame16 output_frame = mixers.GetOutput();
|
||||
|
||||
|
@ -152,7 +152,8 @@ void Shutdown() {
|
|||
bool Tick() {
|
||||
StereoFrame16 current_frame = {};
|
||||
|
||||
// TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to shared memory region)
|
||||
// TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to
|
||||
// shared memory region)
|
||||
current_frame = GenerateCurrentFrame();
|
||||
|
||||
OutputCurrentFrame(current_frame);
|
||||
|
|
|
@ -30,7 +30,8 @@ namespace HLE {
|
|||
// 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
|
||||
// double-buffer. The frame counter is located as the very last u16 of each region and is
|
||||
// incremented
|
||||
// each audio tick.
|
||||
|
||||
constexpr VAddr region0_base = 0x1FF50000;
|
||||
|
@ -56,6 +57,7 @@ struct u32_dsp {
|
|||
void operator=(u32 new_value) {
|
||||
storage = Convert(new_value);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr u32 Convert(u32 value) {
|
||||
return (value << 16) | (value >> 16);
|
||||
|
@ -89,11 +91,13 @@ static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivial
|
|||
// #: 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
|
||||
// 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
|
||||
// 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
|
||||
|
@ -110,14 +114,17 @@ static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivial
|
|||
// 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)
|
||||
#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)
|
||||
#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 {
|
||||
|
@ -130,7 +137,8 @@ struct SourceConfiguration {
|
|||
BitField<0, 1, u32_le> format_dirty;
|
||||
BitField<1, 1, u32_le> mono_or_stereo_dirty;
|
||||
BitField<2, 1, u32_le> adpcm_coefficients_dirty;
|
||||
BitField<3, 1, u32_le> partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
|
||||
BitField<3, 1, u32_le>
|
||||
partial_embedded_buffer_dirty; ///< Tends to be set when a looped buffer is queued.
|
||||
BitField<4, 1, u32_le> partial_reset_flag;
|
||||
|
||||
BitField<16, 1, u32_le> enable_dirty;
|
||||
|
@ -138,7 +146,8 @@ struct SourceConfiguration {
|
|||
BitField<18, 1, u32_le> rate_multiplier_dirty;
|
||||
BitField<19, 1, u32_le> buffer_queue_dirty;
|
||||
BitField<20, 1, u32_le> loop_related_dirty;
|
||||
BitField<21, 1, u32_le> play_position_dirty; ///< Tends to also be set when embedded buffer is updated.
|
||||
BitField<21, 1, u32_le>
|
||||
play_position_dirty; ///< Tends to also be set when embedded buffer is updated.
|
||||
BitField<22, 1, u32_le> filters_enabled_dirty;
|
||||
BitField<23, 1, u32_le> simple_filter_dirty;
|
||||
BitField<24, 1, u32_le> biquad_filter_dirty;
|
||||
|
@ -164,11 +173,7 @@ struct SourceConfiguration {
|
|||
/// 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
|
||||
};
|
||||
enum class InterpolationMode : u8 { Polyphase = 0, Linear = 1, None = 2 };
|
||||
|
||||
InterpolationMode interpolation_mode;
|
||||
INSERT_PADDING_BYTES(1); ///< Interpolation related
|
||||
|
@ -191,7 +196,8 @@ struct SourceConfiguration {
|
|||
* 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
|
||||
* 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.
|
||||
*/
|
||||
|
@ -239,23 +245,24 @@ struct SourceConfiguration {
|
|||
/// Is a looping buffer.
|
||||
u8 is_looping;
|
||||
|
||||
/// This value is shown in SourceStatus::previous_buffer_id when this buffer has finished.
|
||||
/// 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
|
||||
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)
|
||||
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
|
||||
|
@ -268,16 +275,9 @@ struct SourceConfiguration {
|
|||
/// 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 MonoOrStereo : u16_le { Mono = 1, Stereo = 2 };
|
||||
|
||||
enum class Format : u16_le {
|
||||
PCM8 = 0,
|
||||
PCM16 = 1,
|
||||
ADPCM = 2
|
||||
};
|
||||
enum class Format : u16_le { PCM8 = 0, PCM16 = 1, ADPCM = 2 };
|
||||
|
||||
union {
|
||||
u16_le flags1_raw;
|
||||
|
@ -299,10 +299,11 @@ struct SourceConfiguration {
|
|||
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?
|
||||
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).
|
||||
/// Buffer id of embedded buffer (used as a buffer id in SourceStatus to reference this
|
||||
/// buffer).
|
||||
u16_le buffer_id;
|
||||
};
|
||||
|
||||
|
@ -313,11 +314,11 @@ 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
|
||||
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);
|
||||
};
|
||||
|
||||
|
@ -347,16 +348,13 @@ struct DspConfiguration {
|
|||
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
|
||||
/// 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
|
||||
};
|
||||
enum class OutputFormat : u16_le { Mono = 0, Stereo = 1, Surround = 2 };
|
||||
|
||||
OutputFormat output_format;
|
||||
|
||||
|
@ -388,8 +386,9 @@ struct DspConfiguration {
|
|||
u16_le enable;
|
||||
INSERT_PADDING_DSPWORDS(1);
|
||||
u16_le outputs;
|
||||
u32_dsp work_buffer_address; ///< The application allocates a block of memory for the DSP to use as a work buffer.
|
||||
u16_le frame_count; ///< Frames to delay by
|
||||
u32_dsp work_buffer_address; ///< The application allocates a block of memory for the DSP to
|
||||
/// use as a work buffer.
|
||||
u16_le frame_count; ///< Frames to delay by
|
||||
|
||||
// Coefficients
|
||||
s16_le g; ///< Fixed point with 7 fractional bits
|
||||
|
@ -506,21 +505,36 @@ ASSERT_DSP_STRUCT(SharedMemory, 0x8000);
|
|||
extern std::array<SharedMemory, 2> g_regions;
|
||||
|
||||
// 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");
|
||||
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
|
||||
|
|
|
@ -59,7 +59,8 @@ void SourceFilters::SimpleFilter::Reset() {
|
|||
b0 = 1 << 15;
|
||||
}
|
||||
|
||||
void SourceFilters::SimpleFilter::Configure(SourceConfiguration::Configuration::SimpleFilter config) {
|
||||
void SourceFilters::SimpleFilter::Configure(
|
||||
SourceConfiguration::Configuration::SimpleFilter config) {
|
||||
a1 = config.a1;
|
||||
b0 = config.b0;
|
||||
}
|
||||
|
@ -88,7 +89,8 @@ void SourceFilters::BiquadFilter::Reset() {
|
|||
b0 = 1 << 14;
|
||||
}
|
||||
|
||||
void SourceFilters::BiquadFilter::Configure(SourceConfiguration::Configuration::BiquadFilter config) {
|
||||
void SourceFilters::BiquadFilter::Configure(
|
||||
SourceConfiguration::Configuration::BiquadFilter config) {
|
||||
a1 = config.a1;
|
||||
a2 = config.a2;
|
||||
b0 = config.b0;
|
||||
|
|
|
@ -17,7 +17,9 @@ namespace HLE {
|
|||
/// Preprocessing filters. There is an independent set of filters for each Source.
|
||||
class SourceFilters final {
|
||||
public:
|
||||
SourceFilters() { Reset(); }
|
||||
SourceFilters() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// Reset internal state.
|
||||
void Reset();
|
||||
|
@ -54,7 +56,9 @@ private:
|
|||
bool biquad_filter_enabled;
|
||||
|
||||
struct SimpleFilter {
|
||||
SimpleFilter() { Reset(); }
|
||||
SimpleFilter() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// Resets internal state.
|
||||
void Reset();
|
||||
|
@ -80,7 +84,9 @@ private:
|
|||
} simple_filter;
|
||||
|
||||
struct BiquadFilter {
|
||||
BiquadFilter() { Reset(); }
|
||||
BiquadFilter() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
/// Resets internal state.
|
||||
void Reset();
|
||||
|
|
|
@ -20,11 +20,9 @@ void Mixers::Reset() {
|
|||
state = {};
|
||||
}
|
||||
|
||||
DspStatus Mixers::Tick(DspConfiguration& config,
|
||||
const IntermediateMixSamples& read_samples,
|
||||
IntermediateMixSamples& write_samples,
|
||||
const std::array<QuadFrame32, 3>& input)
|
||||
{
|
||||
DspStatus Mixers::Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples,
|
||||
IntermediateMixSamples& write_samples,
|
||||
const std::array<QuadFrame32, 3>& input) {
|
||||
ParseConfig(config);
|
||||
|
||||
AuxReturn(read_samples);
|
||||
|
@ -73,13 +71,15 @@ void Mixers::ParseConfig(DspConfiguration& config) {
|
|||
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));
|
||||
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.)
|
||||
// (Note: Whether headphones are connected does affect coefficients used for surround
|
||||
// sound.)
|
||||
LOG_TRACE(Audio_DSP, "mixers headphones_connected=%hu", config.headphones_connected);
|
||||
}
|
||||
|
||||
|
@ -94,11 +94,10 @@ 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]))
|
||||
};
|
||||
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) {
|
||||
|
@ -106,27 +105,33 @@ void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& sample
|
|||
|
||||
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> {
|
||||
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));
|
||||
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 AddAndClampToS16(accumulator, {mono, mono});
|
||||
});
|
||||
return;
|
||||
|
||||
case OutputFormat::Surround:
|
||||
// TODO(merry): Implement surround sound.
|
||||
// fallthrough
|
||||
// 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> {
|
||||
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 AddAndClampToS16(accumulator, {left, right});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -135,12 +140,14 @@ void Mixers::DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& sample
|
|||
}
|
||||
|
||||
void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
|
||||
// NOTE: read_samples.mix{1,2}.pcm32 annoyingly have their dimensions in reverse order to QuadFrame32.
|
||||
// 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];
|
||||
state.intermediate_mix_buffer[1][sample][channel] =
|
||||
read_samples.mix1.pcm32[channel][sample];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,14 +155,17 @@ void Mixers::AuxReturn(const IntermediateMixSamples& read_samples) {
|
|||
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];
|
||||
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.
|
||||
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];
|
||||
|
||||
|
@ -184,7 +194,8 @@ 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]);
|
||||
DownmixAndMixIntoCurrentFrame(state.intermediate_mixer_volume[mix],
|
||||
state.intermediate_mix_buffer[mix]);
|
||||
}
|
||||
|
||||
// TODO(merry): Compressor. (We currently assume a disabled compressor.)
|
||||
|
|
|
@ -20,10 +20,8 @@ public:
|
|||
|
||||
void Reset();
|
||||
|
||||
DspStatus Tick(DspConfiguration& config,
|
||||
const IntermediateMixSamples& read_samples,
|
||||
IntermediateMixSamples& write_samples,
|
||||
const std::array<QuadFrame32, 3>& input);
|
||||
DspStatus Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples,
|
||||
IntermediateMixSamples& write_samples, const std::array<QuadFrame32, 3>& input);
|
||||
|
||||
StereoFrame16 GetOutput() const {
|
||||
return current_frame;
|
||||
|
@ -53,7 +51,8 @@ private:
|
|||
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.
|
||||
/// 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;
|
||||
|
|
|
@ -44,8 +44,10 @@ std::vector<u8> PipeRead(DspPipe pipe_number, u32 length) {
|
|||
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());
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -95,8 +97,7 @@ static void AudioPipeWriteStructAddresses() {
|
|||
0x8000 + offsetof(SharedMemory, unknown11) / 2,
|
||||
0x8000 + offsetof(SharedMemory, unknown12) / 2,
|
||||
0x8000 + offsetof(SharedMemory, unknown13) / 2,
|
||||
0x8000 + offsetof(SharedMemory, unknown14) / 2
|
||||
};
|
||||
0x8000 + offsetof(SharedMemory, unknown14) / 2};
|
||||
|
||||
// Begin with a u16 denoting the number of structs.
|
||||
WriteU16(DspPipe::Audio, static_cast<u16>(struct_addresses.size()));
|
||||
|
@ -112,16 +113,12 @@ 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());
|
||||
LOG_ERROR(Audio_DSP, "DspPipe::Audio: Unexpected buffer length %zu was written",
|
||||
buffer.size());
|
||||
return;
|
||||
}
|
||||
|
||||
enum class StateChange {
|
||||
Initalize = 0,
|
||||
Shutdown = 1,
|
||||
Wakeup = 2,
|
||||
Sleep = 3
|
||||
};
|
||||
enum class StateChange { Initalize = 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.)
|
||||
|
@ -152,7 +149,9 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
|||
dsp_state = DspState::Sleeping;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Application has requested unknown state transition of DSP hardware %hhu", buffer[0]);
|
||||
LOG_ERROR(Audio_DSP,
|
||||
"Application has requested unknown state transition of DSP hardware %hhu",
|
||||
buffer[0]);
|
||||
dsp_state = DspState::Off;
|
||||
break;
|
||||
}
|
||||
|
@ -160,7 +159,8 @@ void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer) {
|
|||
return;
|
||||
}
|
||||
default:
|
||||
LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented", static_cast<size_t>(pipe_number));
|
||||
LOG_CRITICAL(Audio_DSP, "pipe_number = %zu unimplemented",
|
||||
static_cast<size_t>(pipe_number));
|
||||
UNIMPLEMENTED();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -15,20 +15,17 @@ 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
|
||||
};
|
||||
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 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.
|
||||
* @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.
|
||||
|
@ -49,11 +46,7 @@ size_t GetPipeReadableSize(DspPipe pipe_number);
|
|||
*/
|
||||
void PipeWrite(DspPipe pipe_number, const std::vector<u8>& buffer);
|
||||
|
||||
enum class DspState {
|
||||
Off,
|
||||
On,
|
||||
Sleeping
|
||||
};
|
||||
enum class DspState { Off, On, Sleeping };
|
||||
/// Get the state of the DSP
|
||||
DspState GetDspState();
|
||||
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
namespace DSP {
|
||||
namespace HLE {
|
||||
|
||||
SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
|
||||
SourceStatus::Status Source::Tick(SourceConfiguration::Configuration& config,
|
||||
const s16_le (&adpcm_coeffs)[16]) {
|
||||
ParseConfig(config, adpcm_coeffs);
|
||||
|
||||
if (state.enabled) {
|
||||
|
@ -47,7 +48,8 @@ void Source::Reset() {
|
|||
state = {};
|
||||
}
|
||||
|
||||
void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_le (&adpcm_coeffs)[16]) {
|
||||
void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
const s16_le (&adpcm_coeffs)[16]) {
|
||||
if (!config.dirty_raw) {
|
||||
return;
|
||||
}
|
||||
|
@ -82,7 +84,8 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
|||
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);
|
||||
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.
|
||||
}
|
||||
|
@ -90,37 +93,39 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
|||
|
||||
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); });
|
||||
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); });
|
||||
[](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); });
|
||||
[](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); });
|
||||
[](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());
|
||||
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) {
|
||||
|
@ -138,36 +143,38 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
|||
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));
|
||||
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));
|
||||
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));
|
||||
LOG_TRACE(Audio_DSP, "source_id=%zu mono_or_stereo=%zu", source_id,
|
||||
static_cast<size_t>(state.mono_or_stereo));
|
||||
}
|
||||
|
||||
if (config.embedded_buffer_dirty) {
|
||||
config.embedded_buffer_dirty.Assign(0);
|
||||
state.input_queue.emplace(Buffer{
|
||||
config.physical_address,
|
||||
config.length,
|
||||
static_cast<u8>(config.adpcm_ps),
|
||||
{ config.adpcm_yn[0], config.adpcm_yn[1] },
|
||||
config.adpcm_dirty.ToBool(),
|
||||
config.is_looping.ToBool(),
|
||||
config.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
false
|
||||
});
|
||||
LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu", config.physical_address, config.length, config.buffer_id);
|
||||
state.input_queue.emplace(Buffer{config.physical_address,
|
||||
config.length,
|
||||
static_cast<u8>(config.adpcm_ps),
|
||||
{config.adpcm_yn[0], config.adpcm_yn[1]},
|
||||
config.adpcm_dirty.ToBool(),
|
||||
config.is_looping.ToBool(),
|
||||
config.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
false});
|
||||
LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu",
|
||||
config.physical_address, config.length, config.buffer_id);
|
||||
}
|
||||
|
||||
if (config.buffer_queue_dirty) {
|
||||
|
@ -175,19 +182,18 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, const s16_l
|
|||
for (size_t i = 0; i < 4; i++) {
|
||||
if (config.buffers_dirty & (1 << i)) {
|
||||
const auto& b = config.buffers[i];
|
||||
state.input_queue.emplace(Buffer{
|
||||
b.physical_address,
|
||||
b.length,
|
||||
static_cast<u8>(b.adpcm_ps),
|
||||
{ b.adpcm_yn[0], b.adpcm_yn[1] },
|
||||
b.adpcm_dirty != 0,
|
||||
b.is_looping != 0,
|
||||
b.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
true
|
||||
});
|
||||
LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i, b.physical_address, b.length, b.buffer_id);
|
||||
state.input_queue.emplace(Buffer{b.physical_address,
|
||||
b.length,
|
||||
static_cast<u8>(b.adpcm_ps),
|
||||
{b.adpcm_yn[0], b.adpcm_yn[1]},
|
||||
b.adpcm_dirty != 0,
|
||||
b.is_looping != 0,
|
||||
b.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
true});
|
||||
LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i,
|
||||
b.physical_address, b.length, b.buffer_id);
|
||||
}
|
||||
}
|
||||
config.buffers_dirty = 0;
|
||||
|
@ -218,10 +224,13 @@ void Source::GenerateFrame() {
|
|||
break;
|
||||
}
|
||||
|
||||
const size_t size_to_copy = std::min(state.current_buffer.size(), current_frame.size() - frame_position);
|
||||
const size_t size_to_copy =
|
||||
std::min(state.current_buffer.size(), current_frame.size() - frame_position);
|
||||
|
||||
std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, current_frame.begin() + frame_position);
|
||||
state.current_buffer.erase(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy);
|
||||
std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy,
|
||||
current_frame.begin() + frame_position);
|
||||
state.current_buffer.erase(state.current_buffer.begin(),
|
||||
state.current_buffer.begin() + size_to_copy);
|
||||
|
||||
frame_position += size_to_copy;
|
||||
state.next_sample_number += static_cast<u32>(size_to_copy);
|
||||
|
@ -230,9 +239,9 @@ void Source::GenerateFrame() {
|
|||
state.filters.ProcessFrame(current_frame);
|
||||
}
|
||||
|
||||
|
||||
bool Source::DequeueBuffer() {
|
||||
ASSERT_MSG(state.current_buffer.empty(), "Shouldn't dequeue; we still have data in current_buffer");
|
||||
ASSERT_MSG(state.current_buffer.empty(),
|
||||
"Shouldn't dequeue; we still have data in current_buffer");
|
||||
|
||||
if (state.input_queue.empty())
|
||||
return false;
|
||||
|
@ -261,29 +270,34 @@ bool Source::DequeueBuffer() {
|
|||
break;
|
||||
case Format::ADPCM:
|
||||
DEBUG_ASSERT(num_channels == 1);
|
||||
state.current_buffer = Codec::DecodeADPCM(memory, buf.length, state.adpcm_coeffs, state.adpcm_state);
|
||||
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);
|
||||
LOG_WARNING(Audio_DSP,
|
||||
"source_id=%zu buffer_id=%hu length=%u: Invalid physical address 0x%08X",
|
||||
source_id, buf.buffer_id, buf.length, buf.physical_address);
|
||||
state.current_buffer.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (state.interpolation_mode) {
|
||||
case InterpolationMode::None:
|
||||
state.current_buffer = AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
state.current_buffer =
|
||||
AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
break;
|
||||
case InterpolationMode::Linear:
|
||||
state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
state.current_buffer =
|
||||
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
break;
|
||||
case InterpolationMode::Polyphase:
|
||||
// TODO(merry): Implement polyphase interpolation
|
||||
state.current_buffer = AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
state.current_buffer =
|
||||
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
|
@ -296,7 +310,8 @@ bool Source::DequeueBuffer() {
|
|||
state.buffer_update = buf.from_queue;
|
||||
|
||||
LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu",
|
||||
source_id, buf.buffer_id, buf.from_queue ? "true" : "false", state.current_buffer.size());
|
||||
source_id, buf.buffer_id, buf.from_queue ? "true" : "false",
|
||||
state.current_buffer.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,13 +40,17 @@ public:
|
|||
/**
|
||||
* 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.
|
||||
* @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]);
|
||||
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.
|
||||
* 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.
|
||||
*/
|
||||
|
@ -77,7 +81,7 @@ private:
|
|||
};
|
||||
|
||||
struct BufferOrder {
|
||||
bool operator() (const Buffer& a, const Buffer& b) const {
|
||||
bool operator()(const Buffer& a, const Buffer& b) const {
|
||||
// Lower buffer_id comes first.
|
||||
return a.buffer_id > b.buffer_id;
|
||||
}
|
||||
|
@ -134,7 +138,8 @@ private:
|
|||
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.
|
||||
/// 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();
|
||||
|
|
|
@ -17,7 +17,8 @@ constexpr u64 scale_mask = scale_factor - 1;
|
|||
/// Here we step over the input in steps of rate_multiplier, until we consume all of the input.
|
||||
/// Three adjacent samples are passed to fn each step.
|
||||
template <typename Function>
|
||||
static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, float rate_multiplier, Function fn) {
|
||||
static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input,
|
||||
float rate_multiplier, Function fn) {
|
||||
ASSERT(rate_multiplier > 0);
|
||||
|
||||
if (input.size() < 2)
|
||||
|
@ -63,22 +64,21 @@ static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input,
|
|||
}
|
||||
|
||||
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
||||
return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
|
||||
return x0;
|
||||
});
|
||||
return StepOverSamples(
|
||||
state, input, rate_multiplier,
|
||||
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
|
||||
}
|
||||
|
||||
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
||||
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
|
||||
return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
|
||||
return StepOverSamples(state, input, rate_multiplier, [](u64 fraction, const auto& x0,
|
||||
const auto& x1, const auto& x2) {
|
||||
// This is a saturated subtraction. (Verified by black-box fuzzing.)
|
||||
s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
|
||||
s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
|
||||
|
||||
return std::array<s16, 2> {
|
||||
static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
|
||||
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor)
|
||||
};
|
||||
return std::array<s16, 2>{static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
|
||||
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor)};
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,8 @@ struct State {
|
|||
* No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
|
||||
* @param input Input buffer.
|
||||
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling.
|
||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
|
||||
* performs upsampling.
|
||||
* @return The resampled audio buffer.
|
||||
*/
|
||||
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier);
|
||||
|
@ -33,7 +34,8 @@ StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multip
|
|||
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
|
||||
* @param input Input buffer.
|
||||
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 performs upsampling.
|
||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
|
||||
* performs upsampling.
|
||||
* @return The resampled audio buffer.
|
||||
*/
|
||||
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier);
|
||||
|
|
|
@ -19,7 +19,8 @@ public:
|
|||
return native_sample_rate;
|
||||
}
|
||||
|
||||
void EnqueueSamples(const s16*, size_t) override {}
|
||||
void EnqueueSamples(const s16*, size_t) override {
|
||||
}
|
||||
|
||||
size_t SamplesInQueue() const override {
|
||||
return 0;
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
#include "audio_core/audio_core.h"
|
||||
#include "audio_core/sdl2_sink.h"
|
||||
|
||||
#include <numeric>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include <numeric>
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
|
@ -45,7 +45,8 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
|
|||
SDL_AudioSpec obtained_audiospec;
|
||||
SDL_zero(obtained_audiospec);
|
||||
|
||||
impl->audio_device_id = SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
|
||||
impl->audio_device_id =
|
||||
SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
|
||||
if (impl->audio_device_id <= 0) {
|
||||
LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
|
||||
return;
|
||||
|
@ -86,11 +87,12 @@ size_t SDL2Sink::SamplesInQueue() const {
|
|||
|
||||
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;
|
||||
});
|
||||
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);
|
||||
|
||||
|
@ -100,7 +102,8 @@ size_t SDL2Sink::SamplesInQueue() const {
|
|||
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.
|
||||
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) {
|
||||
|
@ -111,7 +114,8 @@ void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes)
|
|||
} 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);
|
||||
impl->queue.front().erase(impl->queue.front().begin(),
|
||||
impl->queue.front().begin() + remaining_size);
|
||||
remaining_size = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,16 @@
|
|||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
* This class is an interface for an audio sink. An audio sink accepts samples in stereo signed PCM16 format to be output.
|
||||
* 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)
|
||||
/// 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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,9 +17,9 @@ 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>(); } },
|
||||
{"sdl2", []() { return std::make_unique<SDL2Sink>(); }},
|
||||
#endif
|
||||
{ "null", []() { return std::make_unique<NullSink>(); } },
|
||||
{"null", []() { return std::make_unique<NullSink>(); }},
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
|
|
@ -14,7 +14,8 @@ class Sink;
|
|||
|
||||
struct SinkDetails {
|
||||
SinkDetails(const char* id_, std::function<std::unique_ptr<Sink>()> factory_)
|
||||
: id(id_), factory(factory_) {}
|
||||
: id(id_), factory(factory_) {
|
||||
}
|
||||
|
||||
/// Name for this sink.
|
||||
const char* id;
|
||||
|
|
|
@ -26,8 +26,8 @@ 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 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;
|
||||
|
@ -48,7 +48,8 @@ std::vector<s16> TimeStretcher::Process(size_t samples_in_queue) {
|
|||
|
||||
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 =
|
||||
(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.
|
||||
|
@ -100,7 +101,8 @@ 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 expected_time =
|
||||
static_cast<double>(impl->samples_queued) / static_cast<double>(native_sample_rate);
|
||||
const double actual_time = duration.count();
|
||||
|
||||
double ratio;
|
||||
|
|
|
@ -37,7 +37,8 @@ public:
|
|||
/**
|
||||
* 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.
|
||||
* @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);
|
||||
|
@ -48,7 +49,8 @@ private:
|
|||
|
||||
/// 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.
|
||||
/// 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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue