From 92d915b585ad969a567218a60adbed80fb715204 Mon Sep 17 00:00:00 2001 From: Sour Date: Fri, 15 Mar 2019 12:58:30 -0400 Subject: [PATCH] Audio: Added sound recorder --- Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 +++ Core/SoundMixer.cpp | 21 +++++++- Core/SoundMixer.h | 6 +++ Core/SoundResampler.cpp | 2 +- Core/WaveRecorder.cpp | 85 +++++++++++++++++++++++++++++++++ Core/WaveRecorder.h | 21 ++++++++ InteropDLL/RecordApiWrapper.cpp | 5 ++ UI/Forms/frmMain.Designer.cs | 34 +++++++++++++ UI/Forms/frmMain.cs | 25 ++++++++++ UI/Interop/RecordApi.cs | 4 +- 11 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 Core/WaveRecorder.cpp create mode 100644 Core/WaveRecorder.h 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();