diff --git a/Source/Core/AudioCommon/Mixer.cpp b/Source/Core/AudioCommon/Mixer.cpp index 90f771d3fa..452e57c38c 100644 --- a/Source/Core/AudioCommon/Mixer.cpp +++ b/Source/Core/AudioCommon/Mixer.cpp @@ -15,35 +15,89 @@ // UGLINESS #include "Core/PowerPC/PowerPC.h" -#if _M_SSE >= 0x301 && !(defined __GNUC__ && !defined __SSSE3__) -#include +#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& 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 lk(m_csMixing); - - memset(samples, 0, num_samples * 2 * sizeof(short)); + std::lock_guard 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(); + +} \ No newline at end of file diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index ba9809d5bc..c4eb343f9f 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -4,50 +4,56 @@ #pragma once +#include #include #include +#include #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& 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 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 m_lowpass_filter; + std::array 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 m_output_buffer; + float m_l_dither_prev; + float m_r_dither_prev; +}; \ No newline at end of file