diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp
index cb34c2532d..3675970917 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.cpp
@@ -17,6 +17,7 @@
 
 #include "UCode_AX.h"
 #include "../../DSP.h"
+#include "FileUtil.h"
 
 #define AX_GC
 #include "UCode_AX_Voice.h"
@@ -29,6 +30,8 @@ CUCode_AX::CUCode_AX(DSPHLE* dsp_hle, u32 crc)
 	WARN_LOG(DSPHLE, "Instantiating CUCode_AX: crc=%08x", crc);
 	m_rMailHandler.PushMail(DSP_INIT);
 	DSP::GenerateDSPInterruptFromDSPEmu(DSP::INT_DSP);
+
+	LoadResamplingCoefficients();
 }
 
 CUCode_AX::~CUCode_AX()
@@ -40,6 +43,27 @@ CUCode_AX::~CUCode_AX()
 	m_rMailHandler.Clear();
 }
 
+void CUCode_AX::LoadResamplingCoefficients()
+{
+	m_coeffs_available = false;
+
+	std::string filename = File::GetUserPath(D_GCUSER_IDX) + "dsp_coef.bin";
+	if (!File::Exists(filename))
+		return;
+
+	if (File::GetSize(filename) != 0x1000)
+		return;
+
+	FILE* fp = fopen(filename.c_str(), "rb");
+	fread(m_coeffs, 1, 0x1000, fp);
+	fclose(fp);
+
+	for (u32 i = 0; i < 0x800; ++i)
+		m_coeffs[i] = Common::swap16(m_coeffs[i]);
+
+	m_coeffs_available = true;
+}
+
 void CUCode_AX::SpawnAXThread(CUCode_AX* self)
 {
 	self->AXThread();
@@ -405,7 +429,8 @@ void CUCode_AX::ProcessPBList(u32 pb_addr)
 		{
 			ApplyUpdatesForMs(pb, curr_ms);
 
-			ProcessVoice(pb, buffers, ConvertMixerControl(pb.mixer_control));
+			ProcessVoice(pb, buffers, ConvertMixerControl(pb.mixer_control),
+			             m_coeffs_available ? m_coeffs : NULL);
 
 			// Forward the buffers
 			for (u32 i = 0; i < sizeof (buffers.ptrs) / sizeof (buffers.ptrs[0]); ++i)
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
index 7158c197e8..68248fef60 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX.h
@@ -114,6 +114,14 @@ protected:
 	std::condition_variable m_cmdlist_cv;
 	std::mutex m_cmdlist_mutex;
 
+	// Table of coefficients for polyphase sample rate conversion.
+	// The coefficients aren't always available (they are part of the DSP DROM)
+	// so we also need to know if they are valid or not.
+	bool m_coeffs_available;
+	s16 m_coeffs[0x800];
+
+	void LoadResamplingCoefficients();
+
 	// Copy a command list from memory to our temp buffer
 	void CopyCmdList(u32 addr, u16 size);
 
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
index 2377839e86..1fde3f74b4 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AXWii.cpp
@@ -259,7 +259,8 @@ void CUCode_AXWii::ProcessPBList(u32 pb_addr)
 		if (!ReadPB(pb_addr, pb))
 			break;
 
-		ProcessVoice(pb, buffers, ConvertMixerControl(HILO_TO_32(pb.mixer_control)));
+		ProcessVoice(pb, buffers, ConvertMixerControl(HILO_TO_32(pb.mixer_control)),
+		             m_coeffs_available ? m_coeffs : NULL);
 
 		WritePB(pb_addr, pb);
 		pb_addr = HILO_TO_32(pb.next_pb);
diff --git a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h
index f731fc5360..649ad3c37f 100644
--- a/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h
+++ b/Source/Core/Core/Src/HW/DSPHLE/UCodes/UCode_AX_Voice.h
@@ -245,13 +245,54 @@ u16 AcceleratorGetSample()
 
 // Read SAMPLES_PER_FRAME input samples from ARAM, decoding and converting rate
 // if required.
-void GetInputSamples(PB_TYPE& pb, s16* samples)
+void GetInputSamples(PB_TYPE& pb, s16* samples, const s16* coeffs)
 {
 	u32 cur_addr = HILO_TO_32(pb.audio_addr.cur_addr);
 	AcceleratorSetup(&pb, &cur_addr);
 
-	// TODO: support polyphase interpolation if coefficients are available.
-	if (pb.src_type == SRCTYPE_POLYPHASE || pb.src_type == SRCTYPE_LINEAR)
+	// If DSP DROM coefficients are available, support polyphase resampling.
+	if (coeffs && pb.src_type == SRCTYPE_POLYPHASE)
+	{
+		s16 temp[4];
+		u32 idx = 0;
+
+		u32 ratio = HILO_TO_32(pb.src.ratio);
+		u32 curr_pos = pb.src.cur_addr_frac;
+
+		temp[idx++ & 3] = pb.src.last_samples[0];
+		temp[idx++ & 3] = pb.src.last_samples[1];
+		temp[idx++ & 3] = pb.src.last_samples[2];
+		temp[idx++ & 3] = pb.src.last_samples[3];
+
+		for (u32 i = 0; i < SAMPLES_PER_FRAME; ++i)
+		{
+			curr_pos += ratio;
+			while (curr_pos >= 0x10000)
+			{
+				temp[idx++ & 3] = AcceleratorGetSample();
+				curr_pos -= 0x10000;
+			}
+
+			u16 curr_pos_frac = ((curr_pos & 0xFFFF) >> 9) << 2;
+			const s16* c = &coeffs[pb.coef_select * 0x200 + curr_pos_frac];
+
+			s64 t0 = temp[idx++ & 3];
+			s64 t1 = temp[idx++ & 3];
+			s64 t2 = temp[idx++ & 3];
+			s64 t3 = temp[idx++ & 3];
+
+			s64 samp = (t0 * c[0] + t1 * c[1] + t2 * c[2] + t3 * c[3]) >> 15;
+
+			samples[i] = (s16)samp;
+		}
+
+		pb.src.last_samples[3] = temp[--idx & 3];
+		pb.src.last_samples[2] = temp[--idx & 3];
+		pb.src.last_samples[1] = temp[--idx & 3];
+		pb.src.last_samples[0] = temp[--idx & 3];
+		pb.src.cur_addr_frac = curr_pos & 0xFFFF;
+	}
+	else if (pb.src_type == SRCTYPE_LINEAR || (!coeffs && pb.src_type == SRCTYPE_POLYPHASE))
 	{
 		// Convert the input to a higher or lower sample rate using a linear
 		// interpolation algorithm. The input to output ratio is set in
@@ -351,7 +392,7 @@ s16 LowPassFilter(s16* samples, u32 count, s16 yn1, u16 a0, u16 b0)
 
 // Process 1ms of audio (for AX GC) or 3ms of audio (for AX Wii) from a PB and
 // mix it to the output buffers.
-void ProcessVoice(PB_TYPE& pb, const AXBuffers& buffers, AXMixControl mctrl)
+void ProcessVoice(PB_TYPE& pb, const AXBuffers& buffers, AXMixControl mctrl, const s16* coeffs)
 {
 	// If the voice is not running, nothing to do.
 	if (!pb.running)
@@ -359,7 +400,7 @@ void ProcessVoice(PB_TYPE& pb, const AXBuffers& buffers, AXMixControl mctrl)
 
 	// Read input samples, performing sample rate conversion if needed.
 	s16 samples[SAMPLES_PER_FRAME];
-	GetInputSamples(pb, samples);
+	GetInputSamples(pb, samples, coeffs);
 
 	// Apply a global volume ramp using the volume envelope parameters.
 	for (u32 i = 0; i < SAMPLES_PER_FRAME; ++i)