Merge pull request #1629 from kamiyo/FIR-resampler
High quality resampler
This commit is contained in:
commit
81eb9bdf16
|
@ -15,35 +15,89 @@
|
|||
// UGLINESS
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
|
||||
#if _M_SSE >= 0x301 && !(defined __GNUC__ && !defined __SSSE3__)
|
||||
#include <tmmintrin.h>
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
// Executed from sound stream thread
|
||||
unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, bool consider_framelimit)
|
||||
const float CMixer::LOW_WATERMARK = 1280;
|
||||
const float CMixer::MAX_FREQ_SHIFT = 200;
|
||||
const float CMixer::CONTROL_FACTOR = 0.2f;
|
||||
const float CMixer::CONTROL_AVG = 32;
|
||||
|
||||
const double CMixer::Resampler::LOWPASS_ROLLOFF = 0.9;
|
||||
const double CMixer::Resampler::KAISER_BETA = 6.0;
|
||||
const double CMixer::Resampler::BESSEL_EPSILON = 1e-21;
|
||||
|
||||
void CMixer::LinearMixerFifo::Interpolate(u32 left_input_index, float* left_output, float* right_output)
|
||||
{
|
||||
unsigned int currentSample = 0;
|
||||
*left_output = (1 - m_fraction) * m_float_buffer[left_input_index & INDEX_MASK]
|
||||
+ m_fraction * m_float_buffer[(left_input_index + 2) & INDEX_MASK];
|
||||
*right_output = (1 - m_fraction) * m_float_buffer[(left_input_index + 1) & INDEX_MASK]
|
||||
+ m_fraction * m_float_buffer[(left_input_index + 3) & INDEX_MASK];
|
||||
}
|
||||
|
||||
// Cache access in non-volatile variable
|
||||
// This is the only function changing the read value, so it's safe to
|
||||
// cache it locally although it's written here.
|
||||
// The writing pointer will be modified outside, but it will only increase,
|
||||
// so we will just ignore new written data while interpolating.
|
||||
// Without this cache, the compiler wouldn't be allowed to optimize the
|
||||
// interpolation loop.
|
||||
u32 indexR = Common::AtomicLoad(m_indexR);
|
||||
u32 indexW = Common::AtomicLoad(m_indexW);
|
||||
//see https://ccrma.stanford.edu/~jos/resample/Implementation.html
|
||||
void CMixer::WindowedSincMixerFifo::Interpolate(u32 left_input_index, float* left_output, float* right_output)
|
||||
{
|
||||
double left_temp = 0, right_temp = 0;
|
||||
|
||||
float numLeft = (float)(((indexW - indexR) & INDEX_MASK) / 2);
|
||||
m_numLeftI = (numLeft + m_numLeftI*(CONTROL_AVG-1)) / CONTROL_AVG;
|
||||
float offset = (m_numLeftI - LOW_WATERMARK) * CONTROL_FACTOR;
|
||||
if (offset > MAX_FREQ_SHIFT) offset = MAX_FREQ_SHIFT;
|
||||
if (offset < -MAX_FREQ_SHIFT) offset = -MAX_FREQ_SHIFT;
|
||||
// left wing of filter
|
||||
double left_wing_fraction = (m_fraction * Resampler::SAMPLES_PER_CROSSING);
|
||||
u32 left_wing_index = (u32) left_wing_fraction;
|
||||
left_wing_fraction -= left_wing_index;
|
||||
|
||||
//render numleft sample pairs to samples[]
|
||||
//advance indexR with sample position
|
||||
//remember fractional offset
|
||||
const Resampler& resampler = m_mixer->m_resampler;
|
||||
u32 current_index = left_input_index;
|
||||
while (left_wing_index < resampler.m_lowpass_filter.size())
|
||||
{
|
||||
double impulse = resampler.m_lowpass_filter[left_wing_index];
|
||||
impulse += resampler.m_lowpass_delta[left_wing_index] * left_wing_fraction;
|
||||
|
||||
left_temp += (float) m_float_buffer[current_index & INDEX_MASK] * impulse;
|
||||
right_temp += (float) m_float_buffer[(current_index + 1) & INDEX_MASK] * impulse;
|
||||
|
||||
left_wing_index += Resampler::SAMPLES_PER_CROSSING;
|
||||
current_index -= 2;
|
||||
}
|
||||
|
||||
// right wing of filter
|
||||
double right_wing_fraction = (1 - m_fraction) * Resampler::SAMPLES_PER_CROSSING;
|
||||
u32 right_wing_index = ((u32) right_wing_fraction) % Resampler::SAMPLES_PER_CROSSING;
|
||||
right_wing_fraction -= right_wing_index;
|
||||
|
||||
// we already used read_index for left wing
|
||||
current_index = left_input_index + 2;
|
||||
while (right_wing_index < resampler.m_lowpass_filter.size())
|
||||
{
|
||||
double impulse = resampler.m_lowpass_filter[right_wing_index];
|
||||
impulse += resampler.m_lowpass_delta[right_wing_index] * right_wing_fraction;
|
||||
|
||||
left_temp += (float) m_float_buffer[current_index & INDEX_MASK] * impulse;
|
||||
right_temp += (float) m_float_buffer[(current_index + 1) & INDEX_MASK] * impulse;
|
||||
|
||||
right_wing_index += Resampler::SAMPLES_PER_CROSSING;
|
||||
current_index += 2;
|
||||
}
|
||||
|
||||
*left_output = (float) left_temp;
|
||||
*right_output = (float) right_temp;
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::Mix(std::vector<float>& samples, u32 numSamples, bool consider_framelimit)
|
||||
{
|
||||
u32 current_sample = 0;
|
||||
|
||||
// Cache access in non-volatile variable so interpolation loop can be optimized
|
||||
u32 read_index = Common::AtomicLoad(m_read_index);
|
||||
const u32 write_index = Common::AtomicLoad(m_write_index);
|
||||
|
||||
// Sync input rate by fifo size
|
||||
float num_left = (float) (((write_index - read_index) & INDEX_MASK) / 2);
|
||||
m_num_left_i = (num_left + m_num_left_i * (CONTROL_AVG - 1)) / CONTROL_AVG;
|
||||
float offset = (m_num_left_i - LOW_WATERMARK) * CONTROL_FACTOR;
|
||||
MathUtil::Clamp(&offset, -MAX_FREQ_SHIFT, MAX_FREQ_SHIFT);
|
||||
|
||||
// adjust framerate with framelimit
|
||||
u32 framelimit = SConfig::GetInstance().m_Framelimit;
|
||||
float aid_sample_rate = m_input_sample_rate + offset;
|
||||
if (consider_framelimit && framelimit > 1)
|
||||
|
@ -51,134 +105,146 @@ unsigned int CMixer::MixerFifo::Mix(short* samples, unsigned int numSamples, boo
|
|||
aid_sample_rate = aid_sample_rate * (framelimit - 1) * 5 / VideoInterface::TargetRefreshRate;
|
||||
}
|
||||
|
||||
const u32 ratio = (u32)(65536.0f * aid_sample_rate / (float)m_mixer->m_sampleRate);
|
||||
// ratio = 1 / upscale_factor = stepsize for each sample
|
||||
// e.g. going from 32khz to 48khz is 1 / (3 / 2) = 2 / 3
|
||||
// note because of syncing and framelimit, ratio will rarely be exactly 2 / 3
|
||||
float ratio = aid_sample_rate / (float) m_mixer->m_sample_rate;
|
||||
|
||||
s32 lvolume = m_LVolume;
|
||||
s32 rvolume = m_RVolume;
|
||||
float l_volume = (float) m_lvolume / 255.f;
|
||||
float r_volume = (float) m_rvolume / 255.f;
|
||||
|
||||
// TODO: consider a higher-quality resampling algorithm.
|
||||
for (; currentSample < numSamples * 2 && ((indexW-indexR) & INDEX_MASK) > 2; currentSample += 2)
|
||||
// for each output sample pair (left and right),
|
||||
// linear interpolate between current and next sample
|
||||
// increment output sample position
|
||||
// increment input sample position by ratio, store fraction
|
||||
// QUESTION: do we need to check for NUM_CROSSINGS samples before we interpolate?
|
||||
// seems to work fine as is
|
||||
for (; current_sample < numSamples * 2 && ((write_index - read_index) & INDEX_MASK) > 0; current_sample += 2)
|
||||
{
|
||||
u32 indexR2 = indexR + 2; //next sample
|
||||
float l_output, r_output;
|
||||
|
||||
s16 l1 = Common::swap16(m_buffer[indexR & INDEX_MASK]); //current
|
||||
s16 l2 = Common::swap16(m_buffer[indexR2 & INDEX_MASK]); //next
|
||||
int sampleL = ((l1 << 16) + (l2 - l1) * (u16)m_frac) >> 16;
|
||||
sampleL = (sampleL * lvolume) >> 8;
|
||||
sampleL += samples[currentSample + 1];
|
||||
MathUtil::Clamp(&sampleL, -32767, 32767);
|
||||
samples[currentSample + 1] = sampleL;
|
||||
Interpolate(read_index, &l_output, &r_output);
|
||||
|
||||
s16 r1 = Common::swap16(m_buffer[(indexR + 1) & INDEX_MASK]); //current
|
||||
s16 r2 = Common::swap16(m_buffer[(indexR2 + 1) & INDEX_MASK]); //next
|
||||
int sampleR = ((r1 << 16) + (r2 - r1) * (u16)m_frac) >> 16;
|
||||
sampleR = (sampleR * rvolume) >> 8;
|
||||
sampleR += samples[currentSample];
|
||||
MathUtil::Clamp(&sampleR, -32767, 32767);
|
||||
samples[currentSample] = sampleR;
|
||||
samples[current_sample + 1] += l_volume * l_output;
|
||||
samples[current_sample] += r_volume * r_output;
|
||||
|
||||
m_frac += ratio;
|
||||
indexR += 2 * (u16)(m_frac >> 16);
|
||||
m_frac &= 0xffff;
|
||||
m_fraction += ratio;
|
||||
read_index += 2 * (s32) m_fraction;
|
||||
m_fraction = m_fraction - (s32) m_fraction;
|
||||
}
|
||||
|
||||
// Padding
|
||||
short s[2];
|
||||
s[0] = Common::swap16(m_buffer[(indexR - 1) & INDEX_MASK]);
|
||||
s[1] = Common::swap16(m_buffer[(indexR - 2) & INDEX_MASK]);
|
||||
s[0] = (s[0] * rvolume) >> 8;
|
||||
s[1] = (s[1] * lvolume) >> 8;
|
||||
for (; currentSample < numSamples * 2; currentSample += 2)
|
||||
// pad output if not enough input samples
|
||||
float s[2];
|
||||
s[0] = m_float_buffer[(read_index - 1) & INDEX_MASK] * r_volume;
|
||||
s[1] = m_float_buffer[(read_index - 2) & INDEX_MASK] * l_volume;
|
||||
for (; current_sample < numSamples * 2; current_sample += 2)
|
||||
{
|
||||
int sampleR = s[0] + samples[currentSample];
|
||||
MathUtil::Clamp(&sampleR, -32767, 32767);
|
||||
samples[currentSample] = sampleR;
|
||||
int sampleL = s[1] + samples[currentSample + 1];
|
||||
MathUtil::Clamp(&sampleL, -32767, 32767);
|
||||
samples[currentSample + 1] = sampleL;
|
||||
samples[current_sample] += s[0];
|
||||
samples[current_sample + 1] += s[1];
|
||||
}
|
||||
|
||||
// Flush cached variable
|
||||
Common::AtomicStore(m_indexR, indexR);
|
||||
|
||||
return numSamples;
|
||||
// update read index
|
||||
Common::AtomicStore(m_read_index, read_index);
|
||||
}
|
||||
|
||||
unsigned int CMixer::Mix(short* samples, unsigned int num_samples, bool consider_framelimit)
|
||||
// we NEED dithering going from float -> 16bit
|
||||
void CMixer::TriangleDither(float* l_sample, float* r_sample)
|
||||
{
|
||||
float left_dither = DITHER_NOISE;
|
||||
float right_dither = DITHER_NOISE;
|
||||
*l_sample = (*l_sample) + left_dither - m_l_dither_prev;
|
||||
*r_sample = (*r_sample) + right_dither - m_r_dither_prev;
|
||||
m_l_dither_prev = left_dither;
|
||||
m_r_dither_prev = right_dither;
|
||||
}
|
||||
|
||||
u32 CMixer::Mix(s16* samples, u32 num_samples, bool consider_framelimit)
|
||||
{
|
||||
if (!samples)
|
||||
return 0;
|
||||
|
||||
std::lock_guard<std::mutex> lk(m_csMixing);
|
||||
|
||||
memset(samples, 0, num_samples * 2 * sizeof(short));
|
||||
std::lock_guard<std::mutex> lk(m_cs_mixing);
|
||||
|
||||
if (PowerPC::GetState() != PowerPC::CPU_RUNNING)
|
||||
{
|
||||
// Silence
|
||||
memset(samples, 0, num_samples * 2 * sizeof(s16));
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
m_dma_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_streaming_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
m_wiimote_speaker_mixer.Mix(samples, num_samples, consider_framelimit);
|
||||
// reset float output buffer
|
||||
m_output_buffer.resize(num_samples * 2);
|
||||
std::fill_n(m_output_buffer.begin(), num_samples * 2, 0.f);
|
||||
|
||||
m_dma_mixer.Mix(m_output_buffer, num_samples, consider_framelimit);
|
||||
m_streaming_mixer.Mix(m_output_buffer, num_samples, consider_framelimit);
|
||||
m_wiimote_speaker_mixer.Mix(m_output_buffer, num_samples, consider_framelimit);
|
||||
|
||||
// dither and clamp
|
||||
for (u32 i = 0; i < num_samples * 2; i += 2)
|
||||
{
|
||||
float l_output = m_output_buffer[i + 1];
|
||||
float r_output = m_output_buffer[i];
|
||||
TriangleDither(&m_output_buffer[i + 1], &m_output_buffer[i]);
|
||||
|
||||
MathUtil::Clamp(&l_output, -1.f, 1.f);
|
||||
samples[i + 1] = FloatToSigned16(l_output);
|
||||
|
||||
MathUtil::Clamp(&r_output, -1.f, 1.f);
|
||||
samples[i] = FloatToSigned16(r_output);
|
||||
}
|
||||
|
||||
return num_samples;
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::PushSamples(const short *samples, unsigned int num_samples)
|
||||
void CMixer::MixerFifo::PushSamples(const s16* samples, u32 num_samples)
|
||||
{
|
||||
// Cache access in non-volatile variable
|
||||
// indexR isn't allowed to cache in the audio throttling loop as it
|
||||
// needs to get updates to not deadlock.
|
||||
u32 indexW = Common::AtomicLoad(m_indexW);
|
||||
u32 current_write_index = Common::AtomicLoad(m_write_index);
|
||||
|
||||
// Check if we have enough free space
|
||||
// indexW == m_indexR results in empty buffer, so indexR must always be smaller than indexW
|
||||
if (num_samples * 2 + ((indexW - Common::AtomicLoad(m_indexR)) & INDEX_MASK) >= MAX_SAMPLES * 2)
|
||||
if (num_samples * 2 + ((current_write_index - Common::AtomicLoad(m_read_index)) & INDEX_MASK) >= MAX_SAMPLES * 2)
|
||||
return;
|
||||
|
||||
// AyuanX: Actual re-sampling work has been moved to sound thread
|
||||
// to alleviate the workload on main thread
|
||||
// and we simply store raw data here to make fast mem copy
|
||||
int over_bytes = num_samples * 4 - (MAX_SAMPLES * 2 - (indexW & INDEX_MASK)) * sizeof(short);
|
||||
if (over_bytes > 0)
|
||||
// convert to float while copying to buffer
|
||||
for (u32 i = 0; i < num_samples * 2; ++i)
|
||||
{
|
||||
memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4 - over_bytes);
|
||||
memcpy(&m_buffer[0], samples + (num_samples * 4 - over_bytes) / sizeof(short), over_bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(&m_buffer[indexW & INDEX_MASK], samples, num_samples * 4);
|
||||
m_float_buffer[(current_write_index + i) & INDEX_MASK] = Signed16ToFloat(Common::swap16(samples[i]));
|
||||
}
|
||||
|
||||
Common::AtomicAdd(m_indexW, num_samples * 2);
|
||||
Common::AtomicAdd(m_write_index, num_samples * 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void CMixer::PushSamples(const short *samples, unsigned int num_samples)
|
||||
void CMixer::PushSamples(const s16* samples, u32 num_samples)
|
||||
{
|
||||
m_dma_mixer.PushSamples(samples, num_samples);
|
||||
if (m_log_dsp_audio)
|
||||
g_wave_writer_dsp.AddStereoSamplesBE(samples, num_samples);
|
||||
}
|
||||
|
||||
void CMixer::PushStreamingSamples(const short *samples, unsigned int num_samples)
|
||||
void CMixer::PushStreamingSamples(const s16* samples, u32 num_samples)
|
||||
{
|
||||
m_streaming_mixer.PushSamples(samples, num_samples);
|
||||
if (m_log_dtk_audio)
|
||||
g_wave_writer_dtk.AddStereoSamplesBE(samples, num_samples);
|
||||
}
|
||||
|
||||
void CMixer::PushWiimoteSpeakerSamples(const short *samples, unsigned int num_samples, unsigned int sample_rate)
|
||||
void CMixer::PushWiimoteSpeakerSamples(const s16* samples, u32 num_samples, u32 sample_rate)
|
||||
{
|
||||
short samples_stereo[MAX_SAMPLES * 2];
|
||||
s16 samples_stereo[MAX_SAMPLES * 2];
|
||||
|
||||
if (num_samples < MAX_SAMPLES)
|
||||
{
|
||||
m_wiimote_speaker_mixer.SetInputSampleRate(sample_rate);
|
||||
|
||||
for (unsigned int i = 0; i < num_samples; ++i)
|
||||
for (u32 i = 0; i < num_samples; ++i)
|
||||
{
|
||||
samples_stereo[i * 2] = Common::swap16(samples[i]);
|
||||
samples_stereo[i * 2 + 1] = Common::swap16(samples[i]);
|
||||
|
@ -188,33 +254,90 @@ void CMixer::PushWiimoteSpeakerSamples(const short *samples, unsigned int num_sa
|
|||
}
|
||||
}
|
||||
|
||||
void CMixer::SetDMAInputSampleRate(unsigned int rate)
|
||||
void CMixer::SetDMAInputSampleRate(u32 rate)
|
||||
{
|
||||
m_dma_mixer.SetInputSampleRate(rate);
|
||||
}
|
||||
|
||||
void CMixer::SetStreamInputSampleRate(unsigned int rate)
|
||||
void CMixer::SetStreamInputSampleRate(u32 rate)
|
||||
{
|
||||
m_streaming_mixer.SetInputSampleRate(rate);
|
||||
}
|
||||
|
||||
void CMixer::SetStreamingVolume(unsigned int lvolume, unsigned int rvolume)
|
||||
void CMixer::SetStreamingVolume(u32 lvolume, u32 rvolume)
|
||||
{
|
||||
m_streaming_mixer.SetVolume(lvolume, rvolume);
|
||||
}
|
||||
|
||||
void CMixer::SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume)
|
||||
void CMixer::SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume)
|
||||
{
|
||||
m_wiimote_speaker_mixer.SetVolume(lvolume, rvolume);
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::SetInputSampleRate(unsigned int rate)
|
||||
void CMixer::MixerFifo::SetInputSampleRate(u32 rate)
|
||||
{
|
||||
m_input_sample_rate = rate;
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::SetVolume(unsigned int lvolume, unsigned int rvolume)
|
||||
void CMixer::MixerFifo::SetVolume(u32 lvolume, u32 rvolume)
|
||||
{
|
||||
m_LVolume = lvolume + (lvolume >> 7);
|
||||
m_RVolume = rvolume + (rvolume >> 7);
|
||||
m_lvolume = lvolume;
|
||||
m_rvolume = rvolume;
|
||||
}
|
||||
|
||||
void CMixer::MixerFifo::GetVolume(u32* lvolume, u32* rvolume) const
|
||||
{
|
||||
*lvolume = m_lvolume;
|
||||
*rvolume = m_rvolume;
|
||||
}
|
||||
|
||||
// I_0(x) = summation((((x/2)^k) / k!)^2) for k from 0 to Infinity
|
||||
double CMixer::Resampler::ModBessel0th(const double x)
|
||||
{
|
||||
double sum = 1;
|
||||
s32 factorial_store = 1;
|
||||
double half_x = x / 2.f;
|
||||
double previous = 1;
|
||||
do
|
||||
{
|
||||
double temp = half_x / (double) factorial_store;
|
||||
temp *= temp;
|
||||
previous *= temp;
|
||||
sum += previous;
|
||||
factorial_store++;
|
||||
} while (previous >= BESSEL_EPSILON * sum);
|
||||
return sum;
|
||||
}
|
||||
|
||||
// one wing of FIR by using sinc * Kaiser window
|
||||
void CMixer::Resampler::PopulateFilterCoeff()
|
||||
{
|
||||
// Generate sinc table
|
||||
m_lowpass_filter[0] = LOWPASS_ROLLOFF;
|
||||
for (u32 i = 1; i < m_lowpass_filter.size(); ++i)
|
||||
{
|
||||
double temp = M_PI * (double) i / SAMPLES_PER_CROSSING;
|
||||
m_lowpass_filter[i] = sin(temp * LOWPASS_ROLLOFF) / temp;
|
||||
}
|
||||
|
||||
// use a Kaiser window
|
||||
// https://ccrma.stanford.edu/~jos/sasp/Kaiser_Window.html
|
||||
//
|
||||
double I0_beta = 1.0 / ModBessel0th(KAISER_BETA);
|
||||
double inside = 1.0 / (m_lowpass_filter.size() - 1);
|
||||
for (u32 i = 1; i < m_lowpass_filter.size(); ++i)
|
||||
{
|
||||
double temp = (double) i * inside;
|
||||
temp = 1.0 - temp * temp;
|
||||
temp = (temp < 0) ? 0 : temp;
|
||||
m_lowpass_filter[i] *= ModBessel0th(KAISER_BETA * sqrt(temp)) * I0_beta;
|
||||
}
|
||||
|
||||
// store deltas in delta table for faster lookup to interpolate impulse
|
||||
for (u32 i = 0; i < m_lowpass_filter.size() - 1; ++i)
|
||||
{
|
||||
m_lowpass_delta[i] = m_lowpass_filter[i + 1] - m_lowpass_filter[i];
|
||||
}
|
||||
m_lowpass_delta.back() = -1 * m_lowpass_filter.back();
|
||||
|
||||
}
|
|
@ -4,50 +4,56 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "AudioCommon/WaveFile.h"
|
||||
|
||||
// 16 bit Stereo
|
||||
#define MAX_SAMPLES (1024 * 2) // 64ms
|
||||
#define INDEX_MASK (MAX_SAMPLES * 2 - 1)
|
||||
|
||||
#define LOW_WATERMARK 1280 // 40 ms
|
||||
#define MAX_FREQ_SHIFT 200 // per 32000 Hz
|
||||
#define CONTROL_FACTOR 0.2f // in freq_shift per fifo size offset
|
||||
#define CONTROL_AVG 32
|
||||
|
||||
class CMixer {
|
||||
// Dither define
|
||||
#define DITHER_NOISE (rand() / (float) RAND_MAX - 0.5f)
|
||||
|
||||
class CMixer
|
||||
{
|
||||
public:
|
||||
CMixer(unsigned int BackendSampleRate)
|
||||
CMixer(u32 BackendSampleRate)
|
||||
: m_dma_mixer(this, 32000)
|
||||
, m_streaming_mixer(this, 48000)
|
||||
, m_wiimote_speaker_mixer(this, 3000)
|
||||
, m_sampleRate(BackendSampleRate)
|
||||
, m_sample_rate(BackendSampleRate)
|
||||
, m_log_dtk_audio(0)
|
||||
, m_log_dsp_audio(0)
|
||||
, m_speed(0)
|
||||
, m_l_dither_prev(0)
|
||||
, m_r_dither_prev(0)
|
||||
{
|
||||
INFO_LOG(AUDIO_INTERFACE, "Mixer is initialized");
|
||||
m_output_buffer.reserve(MAX_SAMPLES * 2);
|
||||
}
|
||||
|
||||
static const u32 MAX_SAMPLES = 2048;
|
||||
static const u32 INDEX_MASK = MAX_SAMPLES * 2 - 1;
|
||||
static const float LOW_WATERMARK;
|
||||
static const float MAX_FREQ_SHIFT;
|
||||
static const float CONTROL_FACTOR;
|
||||
static const float CONTROL_AVG;
|
||||
|
||||
virtual ~CMixer() {}
|
||||
|
||||
// Called from audio threads
|
||||
virtual unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
u32 Mix(s16* samples, u32 numSamples, bool consider_framelimit = true);
|
||||
|
||||
// Called from main thread
|
||||
virtual void PushSamples(const short* samples, unsigned int num_samples);
|
||||
virtual void PushStreamingSamples(const short* samples, unsigned int num_samples);
|
||||
virtual void PushWiimoteSpeakerSamples(const short* samples, unsigned int num_samples, unsigned int sample_rate);
|
||||
unsigned int GetSampleRate() const { return m_sampleRate; }
|
||||
virtual void PushSamples(const s16* samples, u32 num_samples);
|
||||
virtual void PushStreamingSamples(const s16* samples, u32 num_samples);
|
||||
virtual void PushWiimoteSpeakerSamples(const s16* samples, u32 num_samples, u32 sample_rate);
|
||||
u32 GetSampleRate() const { return m_sample_rate; }
|
||||
|
||||
void SetDMAInputSampleRate(unsigned int rate);
|
||||
void SetStreamInputSampleRate(unsigned int rate);
|
||||
void SetStreamingVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void SetWiimoteSpeakerVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
void SetDMAInputSampleRate(u32 rate);
|
||||
void SetStreamInputSampleRate(u32 rate);
|
||||
void SetStreamingVolume(u32 lvolume, u32 rvolume);
|
||||
void SetWiimoteSpeakerVolume(u32 lvolume, u32 rvolume);
|
||||
|
||||
virtual void StartLogDTKAudio(const std::string& filename)
|
||||
{
|
||||
|
@ -107,46 +113,98 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
std::mutex& MixerCritical() { return m_csMixing; }
|
||||
std::mutex& MixerCritical() { return m_cs_mixing; }
|
||||
|
||||
float GetCurrentSpeed() const { return m_speed; }
|
||||
void UpdateSpeed(volatile float val) { m_speed = val; }
|
||||
|
||||
protected:
|
||||
class MixerFifo {
|
||||
class MixerFifo
|
||||
{
|
||||
public:
|
||||
MixerFifo(CMixer *mixer, unsigned sample_rate)
|
||||
MixerFifo(CMixer* mixer, u32 sample_rate)
|
||||
: m_mixer(mixer)
|
||||
, m_input_sample_rate(sample_rate)
|
||||
, m_indexW(0)
|
||||
, m_indexR(0)
|
||||
, m_LVolume(256)
|
||||
, m_RVolume(256)
|
||||
, m_numLeftI(0.0f)
|
||||
, m_frac(0)
|
||||
, m_write_index(0)
|
||||
, m_read_index(0)
|
||||
, m_lvolume(255)
|
||||
, m_rvolume(255)
|
||||
, m_num_left_i(0.0f)
|
||||
, m_fraction(0.0f)
|
||||
{
|
||||
memset(m_buffer, 0, sizeof(m_buffer));
|
||||
srand((u32) time(nullptr));
|
||||
}
|
||||
void PushSamples(const short* samples, unsigned int num_samples);
|
||||
unsigned int Mix(short* samples, unsigned int numSamples, bool consider_framelimit = true);
|
||||
void SetInputSampleRate(unsigned int rate);
|
||||
void SetVolume(unsigned int lvolume, unsigned int rvolume);
|
||||
private:
|
||||
CMixer *m_mixer;
|
||||
unsigned m_input_sample_rate;
|
||||
short m_buffer[MAX_SAMPLES * 2];
|
||||
volatile u32 m_indexW;
|
||||
volatile u32 m_indexR;
|
||||
// Volume ranges from 0-256
|
||||
volatile s32 m_LVolume;
|
||||
volatile s32 m_RVolume;
|
||||
float m_numLeftI;
|
||||
u32 m_frac;
|
||||
virtual void Interpolate(u32 left_input_index, float* left_output, float* right_output) = 0;
|
||||
void PushSamples(const s16* samples, u32 num_samples);
|
||||
void Mix(std::vector<float>& samples, u32 numSamples, bool consider_framelimit = true);
|
||||
void SetInputSampleRate(u32 rate);
|
||||
void SetVolume(u32 lvolume, u32 rvolume);
|
||||
void GetVolume(u32* lvolume, u32* rvolume) const;
|
||||
|
||||
protected:
|
||||
CMixer* m_mixer;
|
||||
u32 m_input_sample_rate;
|
||||
|
||||
std::array<float, MAX_SAMPLES * 2> m_float_buffer;
|
||||
|
||||
volatile u32 m_write_index;
|
||||
volatile u32 m_read_index;
|
||||
|
||||
// Volume ranges from 0-255
|
||||
volatile u32 m_lvolume;
|
||||
volatile u32 m_rvolume;
|
||||
|
||||
float m_num_left_i;
|
||||
float m_fraction;
|
||||
};
|
||||
MixerFifo m_dma_mixer;
|
||||
MixerFifo m_streaming_mixer;
|
||||
MixerFifo m_wiimote_speaker_mixer;
|
||||
unsigned int m_sampleRate;
|
||||
|
||||
class LinearMixerFifo : public MixerFifo
|
||||
{
|
||||
public:
|
||||
LinearMixerFifo(CMixer* mixer, u32 sample_rate) : MixerFifo(mixer, sample_rate) {}
|
||||
void Interpolate(u32 left_input_index, float* left_output, float* right_output) override;
|
||||
};
|
||||
|
||||
class WindowedSincMixerFifo : public MixerFifo
|
||||
{
|
||||
public:
|
||||
WindowedSincMixerFifo(CMixer* mixer, u32 sample_rate) : MixerFifo(mixer, sample_rate) {}
|
||||
void Interpolate(u32 left_input_index, float* left_output, float* right_output) override;
|
||||
};
|
||||
|
||||
class Resampler
|
||||
{
|
||||
static const double LOWPASS_ROLLOFF;
|
||||
static const double KAISER_BETA;
|
||||
static const double BESSEL_EPSILON; // acceptable delta for Kaiser Window calculation
|
||||
|
||||
void PopulateFilterCoeff();
|
||||
double ModBessel0th(const double x);
|
||||
public:
|
||||
|
||||
static const u32 SAMPLES_PER_CROSSING = 4096;
|
||||
static const u32 NUM_CROSSINGS = 35;
|
||||
static const u32 WING_SIZE = SAMPLES_PER_CROSSING * (NUM_CROSSINGS - 1) / 2;
|
||||
|
||||
Resampler()
|
||||
{
|
||||
PopulateFilterCoeff();
|
||||
}
|
||||
|
||||
std::array<double, WING_SIZE> m_lowpass_filter;
|
||||
std::array<double, WING_SIZE> m_lowpass_delta;
|
||||
};
|
||||
|
||||
Resampler m_resampler;
|
||||
|
||||
WindowedSincMixerFifo m_dma_mixer;
|
||||
WindowedSincMixerFifo m_streaming_mixer;
|
||||
|
||||
// Linear interpolation seems to be the best for Wiimote 3khz -> 48khz, for now.
|
||||
// TODO: figure out why and make it work with the above FIR
|
||||
LinearMixerFifo m_wiimote_speaker_mixer;
|
||||
|
||||
u32 m_sample_rate;
|
||||
|
||||
WaveFileWriter g_wave_writer_dtk;
|
||||
WaveFileWriter g_wave_writer_dsp;
|
||||
|
@ -154,7 +212,26 @@ protected:
|
|||
bool m_log_dtk_audio;
|
||||
bool m_log_dsp_audio;
|
||||
|
||||
std::mutex m_csMixing;
|
||||
std::mutex m_cs_mixing;
|
||||
|
||||
volatile float m_speed; // Current rate of the emulation (1.0 = 100% speed)
|
||||
};
|
||||
|
||||
private:
|
||||
// converts [-32768, 32767] -> [-1.0, 1.0]
|
||||
static inline float Signed16ToFloat(const s16 s)
|
||||
{
|
||||
return (s > 0) ? (float) (s / (float) 0x7fff) : (float) (s / (float) 0x8000);
|
||||
}
|
||||
|
||||
// converts [-1.0, 1.0] -> [-32768, 32767]
|
||||
static inline s16 FloatToSigned16(const float f)
|
||||
{
|
||||
return (f > 0) ? (s16) (f * 0x7fff) : (s16) (f * 0x8000);
|
||||
}
|
||||
|
||||
void TriangleDither(float* l_sample, float* r_sample);
|
||||
|
||||
std::vector<float> m_output_buffer;
|
||||
float m_l_dither_prev;
|
||||
float m_r_dither_prev;
|
||||
};
|
Loading…
Reference in New Issue