AudioCommon/Mixer: Fix integrated GBA sample rate calculation and inverted stereo channels.
Give MixerFifo a variable sample rate dividend. Handle byte-swapping and stereo channel construction outside of MixerFifo to better handle the custom layouts of each stream.
This commit is contained in:
1 parent
6ba804416a
commit
e7e6c0990d
3 files changed
+87
-73
No files matched your search
@@ -6,6 +6,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
#include "AudioCommon/Enums.h"
|
#include "AudioCommon/Enums.h"
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
@@ -67,8 +68,7 @@ void Mixer::MixerFifo::Mix(s16* samples, std::size_t num_samples)
|
|||||||
|
|
||||||
// We need at least a double because the index jump has 24 bits of fractional precision.
|
// We need at least a double because the index jump has 24 bits of fractional precision.
|
||||||
const double out_sample_rate = m_mixer->m_output_sample_rate;
|
const double out_sample_rate = m_mixer->m_output_sample_rate;
|
||||||
double in_sample_rate =
|
double in_sample_rate = double(m_input_sample_rate_dividend) / m_input_sample_rate_divisor;
|
||||||
static_cast<double>(FIXED_SAMPLE_RATE_DIVIDEND) / m_input_sample_rate_divisor;
|
|
||||||
|
|
||||||
const double emulation_speed = m_mixer->m_config_emulation_speed;
|
const double emulation_speed = m_mixer->m_config_emulation_speed;
|
||||||
if (!m_mixer->m_config_audio_preserve_pitch && 0 < emulation_speed && emulation_speed != 1.0)
|
if (!m_mixer->m_config_audio_preserve_pitch && 0 < emulation_speed && emulation_speed != 1.0)
|
||||||
@@ -208,29 +208,17 @@ std::size_t Mixer::MixSurround(float* samples, std::size_t num_samples)
|
|||||||
return num_samples;
|
return num_samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixer::MixerFifo::PushSamples(const s16* samples, std::size_t num_samples)
|
|
||||||
{
|
|
||||||
while (num_samples-- > 0)
|
|
||||||
{
|
|
||||||
const s16 l = m_little_endian ? samples[1] : Common::swap16(samples[1]);
|
|
||||||
const s16 r = m_little_endian ? samples[0] : Common::swap16(samples[0]);
|
|
||||||
samples += 2;
|
|
||||||
|
|
||||||
m_next_buffer[m_next_buffer_index] = StereoPair(l, r);
|
|
||||||
m_next_buffer_index = (m_next_buffer_index + 1) & GRANULE_MASK;
|
|
||||||
|
|
||||||
// The granules overlap by 50%, so we need to enqueue the
|
|
||||||
// next buffer every time we fill half of the samples.
|
|
||||||
if (m_next_buffer_index == 0 || m_next_buffer_index == m_next_buffer.size() / 2)
|
|
||||||
Enqueue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mixer::PushSamples(const s16* samples, std::size_t num_samples)
|
void Mixer::PushSamples(const s16* samples, std::size_t num_samples)
|
||||||
{
|
{
|
||||||
if (IsOutputSampleRateValid())
|
if (IsOutputSampleRateValid())
|
||||||
{
|
{
|
||||||
m_dma_mixer.PushSamples(samples, num_samples);
|
// Big-endian RL-orderered stereo samples.
|
||||||
|
|
||||||
|
while (num_samples--)
|
||||||
|
{
|
||||||
|
m_dma_mixer.PushSample(Common::swap16(samples[1]), Common::swap16(samples[0]));
|
||||||
|
samples += 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_log_dsp_audio)
|
if (m_log_dsp_audio)
|
||||||
@@ -246,7 +234,13 @@ void Mixer::PushStreamingSamples(const s16* samples, std::size_t num_samples)
|
|||||||
{
|
{
|
||||||
if (IsOutputSampleRateValid())
|
if (IsOutputSampleRateValid())
|
||||||
{
|
{
|
||||||
m_streaming_mixer.PushSamples(samples, num_samples);
|
// Big-endian RL-orderered stereo samples.
|
||||||
|
|
||||||
|
while (num_samples--)
|
||||||
|
{
|
||||||
|
m_streaming_mixer.PushSample(Common::swap16(samples[1]), Common::swap16(samples[0]));
|
||||||
|
samples += 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_log_dtk_audio)
|
if (m_log_dtk_audio)
|
||||||
@@ -264,24 +258,13 @@ void Mixer::PushWiimoteSpeakerSamples(const s16* samples, std::size_t num_sample
|
|||||||
if (!IsOutputSampleRateValid())
|
if (!IsOutputSampleRateValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Max 20 bytes/speaker report, may be 4-bit ADPCM so multiply by 2
|
// WiimoteEmu produces host-endian mono samples.
|
||||||
static constexpr std::size_t MAX_SPEAKER_SAMPLES = 20 * 2;
|
|
||||||
std::array<s16, MAX_SPEAKER_SAMPLES * 2> samples_stereo;
|
|
||||||
|
|
||||||
ASSERT_MSG(AUDIO, num_samples <= MAX_SPEAKER_SAMPLES,
|
m_wiimote_speaker_mixer.SetInputSampleRateDivisor(sample_rate_divisor);
|
||||||
"num_samples would overflow samples_stereo: {} > {}", num_samples,
|
|
||||||
MAX_SPEAKER_SAMPLES);
|
for (const s16 sample : std::span{samples, num_samples})
|
||||||
if (num_samples <= MAX_SPEAKER_SAMPLES)
|
|
||||||
{
|
{
|
||||||
m_wiimote_speaker_mixer.SetInputSampleRateDivisor(sample_rate_divisor);
|
m_wiimote_speaker_mixer.PushSample(sample, sample);
|
||||||
|
|
||||||
for (std::size_t i = 0; i < num_samples; ++i)
|
|
||||||
{
|
|
||||||
samples_stereo[i * 2] = samples[i];
|
|
||||||
samples_stereo[i * 2 + 1] = samples[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
m_wiimote_speaker_mixer.PushSamples(samples_stereo.data(), num_samples);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,24 +275,13 @@ void Mixer::PushSkylanderPortalSamples(const u8* samples, std::size_t num_sample
|
|||||||
|
|
||||||
// Skylander samples are always supplied as 64 bytes, 32 x 16 bit samples
|
// Skylander samples are always supplied as 64 bytes, 32 x 16 bit samples
|
||||||
// The portal speaker is 1 channel, so duplicate and play as stereo audio
|
// The portal speaker is 1 channel, so duplicate and play as stereo audio
|
||||||
static constexpr std::size_t MAX_PORTAL_SPEAKER_SAMPLES = 32;
|
|
||||||
std::array<s16, MAX_PORTAL_SPEAKER_SAMPLES * 2> samples_stereo;
|
|
||||||
|
|
||||||
ASSERT_MSG(AUDIO, num_samples <= MAX_PORTAL_SPEAKER_SAMPLES,
|
while (num_samples--)
|
||||||
"num_samples is not less or equal to 32: {} > {}", num_samples,
|
|
||||||
MAX_PORTAL_SPEAKER_SAMPLES);
|
|
||||||
|
|
||||||
if (num_samples <= MAX_PORTAL_SPEAKER_SAMPLES)
|
|
||||||
{
|
{
|
||||||
for (std::size_t i = 0; i < num_samples; ++i)
|
// Little-endian data.
|
||||||
{
|
const s16 sample = u16(samples[0] | u16(samples[1] << 8u));
|
||||||
const s16 sample =
|
m_skylander_portal_mixer.PushSample(sample, sample);
|
||||||
static_cast<u16>(samples[i * 2 + 1]) << 8 | static_cast<u16>(samples[i * 2]);
|
samples += 2;
|
||||||
samples_stereo[i * 2] = sample;
|
|
||||||
samples_stereo[i * 2 + 1] = sample;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_skylander_portal_mixer.PushSamples(samples_stereo.data(), num_samples);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +290,13 @@ void Mixer::PushGBASamples(std::size_t device_number, const s16* samples, std::s
|
|||||||
if (!IsOutputSampleRateValid())
|
if (!IsOutputSampleRateValid())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
m_gba_mixers[device_number].PushSamples(samples, num_samples);
|
// Integrated GBA pushes host-endian LR-ordered stereo samples.
|
||||||
|
|
||||||
|
while (num_samples--)
|
||||||
|
{
|
||||||
|
m_gba_mixers[device_number].PushSample(samples[0], samples[1]);
|
||||||
|
samples += 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixer::SetDMAInputSampleRateDivisor(u32 rate_divisor)
|
void Mixer::SetDMAInputSampleRateDivisor(u32 rate_divisor)
|
||||||
@@ -331,9 +309,9 @@ void Mixer::SetStreamInputSampleRateDivisor(u32 rate_divisor)
|
|||||||
m_streaming_mixer.SetInputSampleRateDivisor(rate_divisor);
|
m_streaming_mixer.SetInputSampleRateDivisor(rate_divisor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixer::SetGBAInputSampleRateDivisors(std::size_t device_number, u32 rate_divisor)
|
void Mixer::SetGBAInputSampleRate(std::size_t device_number, u32 sample_rate)
|
||||||
{
|
{
|
||||||
m_gba_mixers[device_number].SetInputSampleRateDivisor(rate_divisor);
|
m_gba_mixers[device_number].SetInputSampleRateDivisor(GBA_SAMPLE_RATE_DIVIDEND / sample_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mixer::SetStreamingVolume(u32 lvolume, u32 rvolume)
|
void Mixer::SetStreamingVolume(u32 lvolume, u32 rvolume)
|
||||||
@@ -442,6 +420,16 @@ void Mixer::MixerFifo::DoState(PointerWrap& p)
|
|||||||
p.Do(m_RVolume);
|
p.Do(m_RVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Mixer::MixerFifo::SetInputSampleRateDividend(u32 rate_dividend)
|
||||||
|
{
|
||||||
|
m_input_sample_rate_dividend = rate_dividend;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 Mixer::MixerFifo::GetInputSampleRateDividend() const
|
||||||
|
{
|
||||||
|
return m_input_sample_rate_dividend;
|
||||||
|
}
|
||||||
|
|
||||||
void Mixer::MixerFifo::SetInputSampleRateDivisor(u32 rate_divisor)
|
void Mixer::MixerFifo::SetInputSampleRateDivisor(u32 rate_divisor)
|
||||||
{
|
{
|
||||||
m_input_sample_rate_divisor = rate_divisor;
|
m_input_sample_rate_divisor = rate_divisor;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "AudioCommon/WaveFile.h"
|
#include "AudioCommon/WaveFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Config/Config.h"
|
#include "Common/Config/Config.h"
|
||||||
|
#include "Common/Inline.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ public:
|
|||||||
|
|
||||||
void SetDMAInputSampleRateDivisor(u32 rate_divisor);
|
void SetDMAInputSampleRateDivisor(u32 rate_divisor);
|
||||||
void SetStreamInputSampleRateDivisor(u32 rate_divisor);
|
void SetStreamInputSampleRateDivisor(u32 rate_divisor);
|
||||||
void SetGBAInputSampleRateDivisors(std::size_t device_number, u32 rate_divisor);
|
void SetGBAInputSampleRate(std::size_t device_number, u32 sample_rate);
|
||||||
|
|
||||||
void SetStreamingVolume(u32 lvolume, u32 rvolume);
|
void SetStreamingVolume(u32 lvolume, u32 rvolume);
|
||||||
void SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume);
|
void SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume);
|
||||||
@@ -100,23 +101,42 @@ private:
|
|||||||
using Granule = std::array<StereoPair, GRANULE_SIZE>;
|
using Granule = std::array<StereoPair, GRANULE_SIZE>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MixerFifo(Mixer* mixer, u32 sample_rate_divisor, bool little_endian)
|
MixerFifo(Mixer* mixer, u32 sample_rate_divisor,
|
||||||
: m_mixer(mixer), m_input_sample_rate_divisor(sample_rate_divisor),
|
u32 sample_rate_dividend = FIXED_SAMPLE_RATE_DIVIDEND)
|
||||||
m_little_endian(little_endian)
|
: m_mixer(mixer), m_input_sample_rate_dividend(sample_rate_dividend),
|
||||||
|
m_input_sample_rate_divisor(sample_rate_divisor)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
void DoState(PointerWrap& p);
|
void DoState(PointerWrap& p);
|
||||||
void PushSamples(const s16* samples, std::size_t num_samples);
|
|
||||||
|
DOLPHIN_FORCE_INLINE void PushSample(s16 left, s16 right)
|
||||||
|
{
|
||||||
|
m_next_buffer[m_next_buffer_index] = {left, right};
|
||||||
|
m_next_buffer_index = (m_next_buffer_index + 1) & GRANULE_MASK;
|
||||||
|
|
||||||
|
// The granules overlap by 50%, so we need to enqueue the
|
||||||
|
// next buffer every time we fill half of the samples.
|
||||||
|
if (m_next_buffer_index == 0 || m_next_buffer_index == m_next_buffer.size() / 2)
|
||||||
|
Enqueue();
|
||||||
|
}
|
||||||
|
|
||||||
void Mix(s16* samples, std::size_t num_samples);
|
void Mix(s16* samples, std::size_t num_samples);
|
||||||
|
|
||||||
|
void SetInputSampleRateDividend(u32 rate_dividend);
|
||||||
|
u32 GetInputSampleRateDividend() const;
|
||||||
|
|
||||||
void SetInputSampleRateDivisor(u32 rate_divisor);
|
void SetInputSampleRateDivisor(u32 rate_divisor);
|
||||||
u32 GetInputSampleRateDivisor() const;
|
u32 GetInputSampleRateDivisor() const;
|
||||||
|
|
||||||
void SetVolume(u32 lvolume, u32 rvolume);
|
void SetVolume(u32 lvolume, u32 rvolume);
|
||||||
std::pair<s32, s32> GetVolume() const;
|
std::pair<s32, s32> GetVolume() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mixer* m_mixer;
|
Mixer* m_mixer;
|
||||||
|
|
||||||
|
// All non-GBA MixerFifo instances use FIXED_SAMPLE_RATE_DIVIDEND.
|
||||||
|
u32 m_input_sample_rate_dividend;
|
||||||
u32 m_input_sample_rate_divisor;
|
u32 m_input_sample_rate_divisor;
|
||||||
bool m_little_endian;
|
|
||||||
|
|
||||||
Granule m_next_buffer{};
|
Granule m_next_buffer{};
|
||||||
std::size_t m_next_buffer_index = 0;
|
std::size_t m_next_buffer_index = 0;
|
||||||
@@ -144,14 +164,21 @@ private:
|
|||||||
|
|
||||||
void RefreshConfig();
|
void RefreshConfig();
|
||||||
|
|
||||||
MixerFifo m_dma_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 32000, false};
|
MixerFifo m_dma_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 32000};
|
||||||
MixerFifo m_streaming_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, false};
|
MixerFifo m_streaming_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000};
|
||||||
MixerFifo m_wiimote_speaker_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000, true};
|
MixerFifo m_wiimote_speaker_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 3000};
|
||||||
MixerFifo m_skylander_portal_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 8000, true};
|
MixerFifo m_skylander_portal_mixer{this, FIXED_SAMPLE_RATE_DIVIDEND / 8000};
|
||||||
std::array<MixerFifo, 4> m_gba_mixers{MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
|
|
||||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
|
// GBAs generally use a 65536 sample rate which is not a factor of our FIXED_SAMPLE_RATE_DIVIDEND.
|
||||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true},
|
static constexpr u32 GBA_SAMPLE_RATE_DIVIDEND = 0x1000000;
|
||||||
MixerFifo{this, FIXED_SAMPLE_RATE_DIVIDEND / 48000, true}};
|
|
||||||
|
std::array<MixerFifo, 4> m_gba_mixers{
|
||||||
|
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||||
|
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||||
|
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||||
|
MixerFifo{this, GBA_SAMPLE_RATE_DIVIDEND / 65536, GBA_SAMPLE_RATE_DIVIDEND},
|
||||||
|
};
|
||||||
|
|
||||||
u32 m_output_sample_rate;
|
u32 m_output_sample_rate;
|
||||||
|
|
||||||
AudioCommon::SurroundDecoder m_surround_decoder;
|
AudioCommon::SurroundDecoder m_surround_decoder;
|
||||||
|
|||||||
@@ -435,8 +435,7 @@ void Core::SetAVStream()
|
|||||||
m_stream.audioRateChanged = [](mAVStream* stream, unsigned rate) {
|
m_stream.audioRateChanged = [](mAVStream* stream, unsigned rate) {
|
||||||
auto* core = static_cast<AVStream*>(stream)->core;
|
auto* core = static_cast<AVStream*>(stream)->core;
|
||||||
auto* const sound_stream = core->m_system.GetSoundStream();
|
auto* const sound_stream = core->m_system.GetSoundStream();
|
||||||
sound_stream->GetMixer()->SetGBAInputSampleRateDivisors(
|
sound_stream->GetMixer()->SetGBAInputSampleRate(core->m_device_number, rate);
|
||||||
core->m_device_number, Mixer::FIXED_SAMPLE_RATE_DIVIDEND / rate);
|
|
||||||
};
|
};
|
||||||
m_stream.postAudioBuffer = [](mAVStream* stream, mAudioBuffer* audio_buffer) {
|
m_stream.postAudioBuffer = [](mAVStream* stream, mAudioBuffer* audio_buffer) {
|
||||||
size_t sample_count = mAudioBufferAvailable(audio_buffer);
|
size_t sample_count = mAudioBufferAvailable(audio_buffer);
|
||||||
|
|||||||
Reference in new issue
Block a user