diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj
index eb5ccb6..71b822a 100644
--- a/Core/Core.vcxproj
+++ b/Core/Core.vcxproj
@@ -125,6 +125,7 @@
+
@@ -189,6 +190,7 @@
+
{78FEF1A1-6DF1-4CBB-A373-AE6FA7CE5CE0}
diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters
index a1b4f8c..ab37802 100644
--- a/Core/Core.vcxproj.filters
+++ b/Core/Core.vcxproj.filters
@@ -245,6 +245,9 @@
Misc
+
+ Misc
+
@@ -396,6 +399,9 @@
Misc
+
+ Misc
+
diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp
index f480c29..0f1d9b9 100644
--- a/Core/SoundMixer.cpp
+++ b/Core/SoundMixer.cpp
@@ -5,6 +5,7 @@
#include "SoundResampler.h"
#include "RewindManager.h"
#include "VideoRenderer.h"
+#include "WaveRecorder.h"
#include "../Utilities/Equalizer.h"
#include "../Utilities/blip_buf.h"
@@ -73,8 +74,11 @@ void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount)
out = _sampleBuffer;
}
- bool isRecording = _console->GetVideoRenderer()->IsRecording() /* TODO || _waveRecorder*/;
+ bool isRecording = _waveRecorder || _console->GetVideoRenderer()->IsRecording();
if(isRecording) {
+ if(_waveRecorder) {
+ _waveRecorder->WriteSamples(out, count, cfg.SampleRate, true);
+ }
_console->GetVideoRenderer()->AddRecordingSound(out, count, cfg.SampleRate);
}
@@ -109,4 +113,19 @@ void SoundMixer::ProcessEqualizer(int16_t* samples, uint32_t sampleCount)
double SoundMixer::GetRateAdjustment()
{
return _resampler->GetRateAdjustment();
+}
+
+void SoundMixer::StartRecording(string filepath)
+{
+ _waveRecorder.reset(new WaveRecorder(filepath, _console->GetSettings()->GetAudioConfig().SampleRate, true));
+}
+
+void SoundMixer::StopRecording()
+{
+ _waveRecorder.reset();
+}
+
+bool SoundMixer::IsRecording()
+{
+ return _waveRecorder.get() != nullptr;
}
\ No newline at end of file
diff --git a/Core/SoundMixer.h b/Core/SoundMixer.h
index bba6317..7837438 100644
--- a/Core/SoundMixer.h
+++ b/Core/SoundMixer.h
@@ -5,6 +5,7 @@
class Console;
class Equalizer;
class SoundResampler;
+class WaveRecorder;
class SoundMixer
{
@@ -13,6 +14,7 @@ private:
Console *_console;
unique_ptr _equalizer;
unique_ptr _resampler;
+ shared_ptr _waveRecorder;
int16_t *_sampleBuffer = nullptr;
void ProcessEqualizer(int16_t *samples, uint32_t sampleCount);
@@ -27,4 +29,8 @@ public:
void RegisterAudioDevice(IAudioDevice *audioDevice);
AudioStatistics GetStatistics();
double GetRateAdjustment();
+
+ void StartRecording(string filepath);
+ void StopRecording();
+ bool IsRecording();
};
diff --git a/Core/SoundResampler.cpp b/Core/SoundResampler.cpp
index facfbc6..483e122 100644
--- a/Core/SoundResampler.cpp
+++ b/Core/SoundResampler.cpp
@@ -27,7 +27,7 @@ double SoundResampler::GetRateAdjustment()
double SoundResampler::GetTargetRateAdjustment()
{
AudioConfig cfg = _console->GetSettings()->GetAudioConfig();
- bool isRecording = _console->GetVideoRenderer()->IsRecording() /* TODO || _waveRecorder */;
+ bool isRecording = _console->GetSoundMixer()->IsRecording() || _console->GetVideoRenderer()->IsRecording();
if(!isRecording && !cfg.DisableDynamicSampleRate) {
//Don't deviate from selected sample rate while recording
//TODO: Have 2 output streams (one for recording, one for the speakers)
diff --git a/Core/WaveRecorder.cpp b/Core/WaveRecorder.cpp
new file mode 100644
index 0000000..28ae37e
--- /dev/null
+++ b/Core/WaveRecorder.cpp
@@ -0,0 +1,85 @@
+#include "stdafx.h"
+#include "WaveRecorder.h"
+#include "MessageManager.h"
+
+WaveRecorder::WaveRecorder(string outputFile, uint32_t sampleRate, bool isStereo)
+{
+ _stream = ofstream(outputFile, ios::out | ios::binary);
+ _outputFile = outputFile;
+ _streamSize = 0;
+ _sampleRate = sampleRate;
+ _isStereo = isStereo;
+ WriteHeader();
+
+ MessageManager::DisplayMessage("SoundRecorder", "SoundRecorderStarted", _outputFile);
+}
+
+WaveRecorder::~WaveRecorder()
+{
+ CloseFile();
+}
+
+void WaveRecorder::WriteHeader()
+{
+ _stream << "RIFF";
+ uint32_t size = 0;
+ _stream.write((char*)&size, sizeof(size));
+
+ _stream << "WAVE";
+ _stream << "fmt ";
+
+ uint32_t chunkSize = 16;
+ _stream.write((char*)&chunkSize, sizeof(chunkSize));
+
+ uint16_t format = 1; //PCM
+ uint16_t channelCount = _isStereo ? 2 : 1;
+ uint16_t bytesPerSample = 2;
+ uint16_t blockAlign = channelCount * bytesPerSample;
+ uint32_t byteRate = _sampleRate * channelCount * bytesPerSample;
+ uint16_t bitsPerSample = bytesPerSample * 8;
+
+ _stream.write((char*)&format, sizeof(format));
+ _stream.write((char*)&channelCount, sizeof(channelCount));
+ _stream.write((char*)&_sampleRate, sizeof(_sampleRate));
+ _stream.write((char*)&byteRate, sizeof(byteRate));
+
+ _stream.write((char*)&blockAlign, sizeof(blockAlign));
+ _stream.write((char*)&bitsPerSample, sizeof(bitsPerSample));
+
+ _stream << "data";
+ _stream.write((char*)&size, sizeof(size));
+}
+
+bool WaveRecorder::WriteSamples(int16_t * samples, uint32_t sampleCount, uint32_t sampleRate, bool isStereo)
+{
+ if(_sampleRate != sampleRate || _isStereo != isStereo) {
+ //Format changed, stop recording
+ CloseFile();
+ return false;
+ } else {
+ uint32_t sampleBytes = sampleCount * (isStereo ? 4 : 2);
+ _stream.write((char*)samples, sampleBytes);
+ _streamSize += sampleBytes;
+ return true;
+ }
+}
+
+void WaveRecorder::UpdateSizeValues()
+{
+ _stream.seekp(4, ios::beg);
+ uint32_t fileSize = _streamSize + 36;
+ _stream.write((char*)&fileSize, sizeof(fileSize));
+
+ _stream.seekp(40, ios::beg);
+ _stream.write((char*)&_streamSize, sizeof(_streamSize));
+}
+
+void WaveRecorder::CloseFile()
+{
+ if(_stream && _stream.is_open()) {
+ UpdateSizeValues();
+ _stream.close();
+
+ MessageManager::DisplayMessage("SoundRecorder", "SoundRecorderStopped", _outputFile);
+ }
+}
diff --git a/Core/WaveRecorder.h b/Core/WaveRecorder.h
new file mode 100644
index 0000000..1cb3dab
--- /dev/null
+++ b/Core/WaveRecorder.h
@@ -0,0 +1,21 @@
+#include "stdafx.h"
+
+class WaveRecorder
+{
+private:
+ std::ofstream _stream;
+ uint32_t _streamSize;
+ uint32_t _sampleRate;
+ bool _isStereo;
+ string _outputFile;
+
+ void WriteHeader();
+ void UpdateSizeValues();
+ void CloseFile();
+
+public:
+ WaveRecorder(string outputFile, uint32_t sampleRate, bool isStereo);
+ ~WaveRecorder();
+
+ bool WriteSamples(int16_t* samples, uint32_t sampleCount, uint32_t sampleRate, bool isStereo);
+};
\ No newline at end of file
diff --git a/InteropDLL/RecordApiWrapper.cpp b/InteropDLL/RecordApiWrapper.cpp
index 911eba9..b934f72 100644
--- a/InteropDLL/RecordApiWrapper.cpp
+++ b/InteropDLL/RecordApiWrapper.cpp
@@ -1,6 +1,7 @@
#include "stdafx.h"
#include "../Core/Console.h"
#include "../Core/VideoRenderer.h"
+#include "../Core/SoundMixer.h"
extern shared_ptr _console;
enum class VideoCodec;
@@ -10,4 +11,8 @@ extern "C"
DllExport void __stdcall AviRecord(char* filename, VideoCodec codec, uint32_t compressionLevel) { _console->GetVideoRenderer()->StartRecording(filename, codec, compressionLevel); }
DllExport void __stdcall AviStop() { _console->GetVideoRenderer()->StopRecording(); }
DllExport bool __stdcall AviIsRecording() { return _console->GetVideoRenderer()->IsRecording(); }
+
+ DllExport void __stdcall WaveRecord(char* filename) { _console->GetSoundMixer()->StartRecording(filename); }
+ DllExport void __stdcall WaveStop() { _console->GetSoundMixer()->StopRecording(); }
+ DllExport bool __stdcall WaveIsRecording() { return _console->GetSoundMixer()->IsRecording(); }
}
\ No newline at end of file
diff --git a/UI/Forms/frmMain.Designer.cs b/UI/Forms/frmMain.Designer.cs
index f6900ee..8c14a49 100644
--- a/UI/Forms/frmMain.Designer.cs
+++ b/UI/Forms/frmMain.Designer.cs
@@ -132,6 +132,9 @@
this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem();
this.pnlRenderer = new System.Windows.Forms.Panel();
this.ctrlRecentGames = new Mesen.GUI.Controls.ctrlRecentGames();
+ this.mnuSoundRecorder = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuWaveRecord = new System.Windows.Forms.ToolStripMenuItem();
+ this.mnuWaveStop = new System.Windows.Forms.ToolStripMenuItem();
this.mnuMain.SuspendLayout();
this.pnlRenderer.SuspendLayout();
this.SuspendLayout();
@@ -751,6 +754,7 @@
// toolsToolStripMenuItem
//
this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.mnuSoundRecorder,
this.mnuVideoRecorder,
this.toolStripMenuItem11,
this.mnuLogWindow,
@@ -930,6 +934,33 @@
this.ctrlRecentGames.TabIndex = 1;
this.ctrlRecentGames.Visible = false;
//
+ // mnuSoundRecorder
+ //
+ this.mnuSoundRecorder.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.mnuWaveRecord,
+ this.mnuWaveStop});
+ this.mnuSoundRecorder.Image = global::Mesen.GUI.Properties.Resources.Microphone;
+ this.mnuSoundRecorder.Name = "mnuSoundRecorder";
+ this.mnuSoundRecorder.Size = new System.Drawing.Size(159, 22);
+ this.mnuSoundRecorder.Text = "Sound Recorder";
+ this.mnuSoundRecorder.DropDownOpening += new System.EventHandler(this.mnuSoundRecorder_DropDownOpening);
+ //
+ // mnuWaveRecord
+ //
+ this.mnuWaveRecord.Image = global::Mesen.GUI.Properties.Resources.Record;
+ this.mnuWaveRecord.Name = "mnuWaveRecord";
+ this.mnuWaveRecord.Size = new System.Drawing.Size(155, 22);
+ this.mnuWaveRecord.Text = "Record...";
+ this.mnuWaveRecord.Click += new System.EventHandler(this.mnuWaveRecord_Click);
+ //
+ // mnuWaveStop
+ //
+ this.mnuWaveStop.Image = global::Mesen.GUI.Properties.Resources.MediaStop;
+ this.mnuWaveStop.Name = "mnuWaveStop";
+ this.mnuWaveStop.Size = new System.Drawing.Size(155, 22);
+ this.mnuWaveStop.Text = "Stop Recording";
+ this.mnuWaveStop.Click += new System.EventHandler(this.mnuWaveStop_Click);
+ //
// frmMain
//
this.AllowDrop = true;
@@ -1057,5 +1088,8 @@
private System.Windows.Forms.ToolStripMenuItem mnuAviRecord;
private System.Windows.Forms.ToolStripMenuItem mnuAviStop;
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem11;
+ private System.Windows.Forms.ToolStripMenuItem mnuSoundRecorder;
+ private System.Windows.Forms.ToolStripMenuItem mnuWaveRecord;
+ private System.Windows.Forms.ToolStripMenuItem mnuWaveStop;
}
}
\ No newline at end of file
diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs
index fae49fe..e5838dd 100644
--- a/UI/Forms/frmMain.cs
+++ b/UI/Forms/frmMain.cs
@@ -452,5 +452,30 @@ namespace Mesen.GUI.Forms
mnuAviRecord.Enabled = EmuRunner.IsRunning() && !RecordApi.AviIsRecording();
mnuAviStop.Enabled = EmuRunner.IsRunning() && RecordApi.AviIsRecording();
}
+
+ private void mnuWaveRecord_Click(object sender, EventArgs e)
+ {
+ using(SaveFileDialog sfd = new SaveFileDialog()) {
+ sfd.SetFilter(ResourceHelper.GetMessage("FilterWave"));
+ sfd.InitialDirectory = ConfigManager.WaveFolder;
+ //TODO
+ //sfd.FileName = InteropEmu.GetRomInfo().GetRomName() + ".wav";
+ if(sfd.ShowDialog(this) == DialogResult.OK) {
+ RecordApi.WaveRecord(sfd.FileName);
+ }
+ }
+ }
+
+ private void mnuWaveStop_Click(object sender, EventArgs e)
+ {
+ RecordApi.WaveStop();
+ }
+
+ private void mnuSoundRecorder_DropDownOpening(object sender, EventArgs e)
+ {
+ mnuSoundRecorder.Enabled = EmuRunner.IsRunning();
+ mnuWaveRecord.Enabled = EmuRunner.IsRunning() && !RecordApi.WaveIsRecording();
+ mnuWaveStop.Enabled = EmuRunner.IsRunning() && RecordApi.WaveIsRecording();
+ }
}
}
diff --git a/UI/Interop/RecordApi.cs b/UI/Interop/RecordApi.cs
index 6f38ff6..4298243 100644
--- a/UI/Interop/RecordApi.cs
+++ b/UI/Interop/RecordApi.cs
@@ -16,11 +16,11 @@ namespace Mesen.GUI
[DllImport(DllPath)] public static extern void AviStop();
[DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool AviIsRecording();
- /*[DllImport(DllPath)] public static extern void WaveRecord([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename);
+ [DllImport(DllPath)] public static extern void WaveRecord([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string filename);
[DllImport(DllPath)] public static extern void WaveStop();
[DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool WaveIsRecording();
- [DllImport(DllPath)] public static extern void MoviePlay([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename);
+ /*[DllImport(DllPath)] public static extern void MoviePlay([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string filename);
[DllImport(DllPath)] public static extern void MovieRecord(ref RecordMovieOptions options);
[DllImport(DllPath)] public static extern void MovieStop();
[DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MoviePlaying();