diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 0221d8f3a4..a74cfd2633 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -34,6 +34,7 @@ target_sources(common PRIVATE
StringUtil.cpp
Timer.cpp
ThreadPool.cpp
+ WAVWriter.cpp
WindowInfo.cpp
emitter/avx.cpp
emitter/bmi.cpp
@@ -102,6 +103,7 @@ target_sources(common PRIVATE
Threading.h
ThreadPool.h
TraceLog.h
+ WAVWriter.h
WindowInfo.h
emitter/cpudetect_internal.h
emitter/implement/dwshift.h
diff --git a/common/WAVWriter.cpp b/common/WAVWriter.cpp
new file mode 100644
index 0000000000..884d7f779c
--- /dev/null
+++ b/common/WAVWriter.cpp
@@ -0,0 +1,129 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "common/PrecompiledHeader.h"
+
+#include "common/WAVWriter.h"
+#include "common/FileSystem.h"
+#include "common/Console.h"
+
+#pragma pack(push, 1)
+struct WAV_HEADER
+{
+ u32 chunk_id; // RIFF
+ u32 chunk_size;
+ u32 format; // WAVE
+
+ struct FormatChunk
+ {
+ u32 chunk_id; // "fmt "
+ u32 chunk_size;
+ u16 audio_format; // pcm = 1
+ u16 num_channels;
+ u32 sample_rate;
+ u32 byte_rate;
+ u16 block_align;
+ u16 bits_per_sample;
+ } fmt_chunk;
+
+ struct DataChunkHeader
+ {
+ u32 chunk_id; // "data "
+ u32 chunk_size;
+ } data_chunk_header;
+};
+#pragma pack(pop)
+
+using namespace Common;
+
+WAVWriter::WAVWriter() = default;
+
+WAVWriter::~WAVWriter()
+{
+ if (IsOpen())
+ Close();
+}
+
+bool WAVWriter::Open(const char* filename, u32 sample_rate, u32 num_channels)
+{
+ if (IsOpen())
+ Close();
+
+ m_file = FileSystem::OpenCFile(filename, "wb");
+ if (!m_file)
+ return false;
+
+ m_sample_rate = sample_rate;
+ m_num_channels = num_channels;
+
+ if (!WriteHeader())
+ {
+ Console.Error("Failed to write header to file");
+ m_sample_rate = 0;
+ m_num_channels = 0;
+ std::fclose(m_file);
+ m_file = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void WAVWriter::Close()
+{
+ if (!IsOpen())
+ return;
+
+ if (std::fseek(m_file, 0, SEEK_SET) != 0 || !WriteHeader())
+ Console.Error("Failed to re-write header on file, file may be unplayable");
+
+ std::fclose(m_file);
+ m_file = nullptr;
+ m_sample_rate = 0;
+ m_num_channels = 0;
+ m_num_frames = 0;
+}
+
+void WAVWriter::WriteFrames(const s16* samples, u32 num_frames)
+{
+ const u32 num_frames_written =
+ static_cast(std::fwrite(samples, sizeof(s16) * m_num_channels, num_frames, m_file));
+ if (num_frames_written != num_frames)
+ Console.Error("Only wrote %u of %u frames to output file", num_frames_written, num_frames);
+
+ m_num_frames += num_frames_written;
+}
+
+bool WAVWriter::WriteHeader()
+{
+ const u32 data_size = sizeof(SampleType) * m_num_channels * m_num_frames;
+
+ WAV_HEADER header = {};
+ header.chunk_id = 0x46464952; // 0x52494646
+ header.chunk_size = sizeof(WAV_HEADER) - 8 + data_size;
+ header.format = 0x45564157; // 0x57415645
+ header.fmt_chunk.chunk_id = 0x20746d66; // 0x666d7420
+ header.fmt_chunk.chunk_size = sizeof(header.fmt_chunk) - 8;
+ header.fmt_chunk.audio_format = 1;
+ header.fmt_chunk.num_channels = static_cast(m_num_channels);
+ header.fmt_chunk.sample_rate = m_sample_rate;
+ header.fmt_chunk.byte_rate = m_sample_rate * m_num_channels * sizeof(SampleType);
+ header.fmt_chunk.block_align = static_cast(m_num_channels * sizeof(SampleType));
+ header.fmt_chunk.bits_per_sample = 16;
+ header.data_chunk_header.chunk_id = 0x61746164; // 0x64617461
+ header.data_chunk_header.chunk_size = data_size;
+
+ return (std::fwrite(&header, sizeof(header), 1, m_file) == 1);
+}
diff --git a/common/WAVWriter.h b/common/WAVWriter.h
new file mode 100644
index 0000000000..e9b014e153
--- /dev/null
+++ b/common/WAVWriter.h
@@ -0,0 +1,48 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "common/Pcsx2Defs.h"
+#include
+
+namespace Common
+{
+ class WAVWriter
+ {
+ public:
+ WAVWriter();
+ ~WAVWriter();
+
+ __fi u32 GetSampleRate() const { return m_sample_rate; }
+ __fi u32 GetNumChannels() const { return m_num_channels; }
+ __fi u32 GetNumFrames() const { return m_num_frames; }
+ __fi bool IsOpen() const { return (m_file != nullptr); }
+
+ bool Open(const char* filename, u32 sample_rate, u32 num_channels);
+ void Close();
+
+ void WriteFrames(const s16* samples, u32 num_frames);
+
+ private:
+ using SampleType = s16;
+
+ bool WriteHeader();
+
+ std::FILE* m_file = nullptr;
+ u32 m_sample_rate = 0;
+ u32 m_num_channels = 0;
+ u32 m_num_frames = 0;
+ };
+} // namespace Common
\ No newline at end of file
diff --git a/common/common.vcxproj b/common/common.vcxproj
index a2f90bcea3..ffd9c0f0a3 100644
--- a/common/common.vcxproj
+++ b/common/common.vcxproj
@@ -98,6 +98,7 @@
+
@@ -196,6 +197,7 @@
+
diff --git a/common/common.vcxproj.filters b/common/common.vcxproj.filters
index d71e5af636..d34353b4fd 100644
--- a/common/common.vcxproj.filters
+++ b/common/common.vcxproj.filters
@@ -208,6 +208,9 @@
Source Files
+
+ Source Files
+
@@ -492,6 +495,9 @@
Header Files
+
+ Header Files
+