From c5d1e4cfc758732294ec9768d5e959d5514cf14c Mon Sep 17 00:00:00 2001 From: Souryo Date: Sun, 5 Jun 2016 14:36:20 -0400 Subject: [PATCH] Sound Recorder: Added .wav recording feature (Tools menu) --- Core/Console.cpp | 5 ++ Core/Core.vcxproj | 4 ++ Core/Core.vcxproj.filters | 78 ++++++++++---------- Core/MessageManager.cpp | 15 +++- Core/SoundMixer.cpp | 29 +++++++- Core/SoundMixer.h | 11 ++- Core/WaveRecorder.cpp | 85 ++++++++++++++++++++++ Core/WaveRecorder.h | 21 ++++++ GUI.NET/Config/ConfigManager.cs | 12 ++++ GUI.NET/Dependencies/Font.24.spritefont | Bin 486312 -> 486344 bytes GUI.NET/Dependencies/resources.en.xml | 1 + GUI.NET/Dependencies/resources.fr.xml | 14 ++-- GUI.NET/Dependencies/resources.ja.xml | 4 ++ GUI.NET/Forms/frmMain.Designer.cs | 86 ++++++++++++++++------- GUI.NET/Forms/frmMain.cs | 21 ++++++ GUI.NET/GUI.NET.csproj | 2 + GUI.NET/InteropEmu.cs | 4 ++ GUI.NET/Properties/Resources.Designer.cs | 20 ++++++ GUI.NET/Properties/Resources.resx | 6 ++ GUI.NET/Resources/Record.png | Bin 0 -> 242 bytes GUI.NET/Resources/microphone.png | Bin 0 -> 554 bytes InteropDLL/ConsoleWrapper.cpp | 5 ++ 22 files changed, 350 insertions(+), 73 deletions(-) create mode 100644 Core/WaveRecorder.cpp create mode 100644 Core/WaveRecorder.h create mode 100644 GUI.NET/Resources/Record.png create mode 100644 GUI.NET/Resources/microphone.png diff --git a/Core/Console.cpp b/Core/Console.cpp index 0073c019..6b1fcb35 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -23,6 +23,7 @@ Console::Console() Console::~Console() { Movie::Stop(); + SoundMixer::StopRecording(); } shared_ptr Console::GetInstance() @@ -140,6 +141,8 @@ uint32_t Console::GetCrc32() void Console::Reset(bool softReset) { Movie::Stop(); + SoundMixer::StopRecording(); + if(Instance->_initialized) { Console::Pause(); if(softReset) { @@ -155,6 +158,7 @@ void Console::Reset(bool softReset) void Console::ResetComponents(bool softReset) { Movie::Stop(); + SoundMixer::StopRecording(); _memoryManager->Reset(softReset); _ppu->Reset(); @@ -277,6 +281,7 @@ void Console::Run() } SoundMixer::StopAudio(); Movie::Stop(); + SoundMixer::StopRecording(); VideoDecoder::GetInstance()->StopThread(); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index e5867cc9..229af0dc 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -185,6 +185,7 @@ MultiThreadedDebugDLL true true + false Console @@ -213,6 +214,7 @@ MultiThreadedDebugDLL true true + false Console @@ -551,6 +553,7 @@ + @@ -611,6 +614,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index f8c4bd2f..77ea3fb5 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1,14 +1,6 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - {ff3c6e48-3987-41d2-8916-b588a457ff30} @@ -72,11 +64,12 @@ {1d706c9e-639f-4da0-a7ce-50d04b2b6a7e} + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + - - Header Files - Nes\Interfaces @@ -101,9 +94,6 @@ Nes\Mappers - - Header Files - Nes\Interfaces @@ -149,12 +139,6 @@ Debugger - - Header Files - - - Header Files - Nes\Interfaces @@ -185,9 +169,6 @@ Nes\APU - - Header Files - Nes\Mappers @@ -203,9 +184,6 @@ Nes\Interfaces - - Header Files - Nes\Mappers @@ -314,15 +292,9 @@ Nes\Mappers - - Header Files - Nes\Mappers - - Header Files - Nes\Mappers @@ -590,13 +562,40 @@ Nes + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Misc + + + Nes\Mappers + + + Nes\Mappers + + + Misc + - Source Files + Misc - Source Files + Misc Debugger @@ -611,19 +610,19 @@ Debugger - Source Files + Misc - Source Files + Misc - Source Files + Misc Debugger - Source Files + Misc VideoDecoder @@ -739,5 +738,8 @@ Nes + + Misc + \ No newline at end of file diff --git a/Core/MessageManager.cpp b/Core/MessageManager.cpp index be0cfe75..3ae79e36 100644 --- a/Core/MessageManager.cpp +++ b/Core/MessageManager.cpp @@ -15,6 +15,7 @@ std::unordered_map MessageManager::_enResources = { { "NetPlay", u8"Net Play" }, { "SaveStates", u8"Save States" }, { "ScreenshotSaved", u8"Screenshot Saved" }, + { "SoundRecorder", u8"Sound Recorder" }, { "Test", u8"Test" }, { "CheatApplied", u8"1 cheat applied." }, @@ -49,6 +50,8 @@ std::unordered_map MessageManager::_enResources = { { "SaveStateSaved", u8"State #%1 saved." }, { "ServerStarted", u8"Server started (Port: %1)" }, { "ServerStopped", u8"Server stopped" }, + { "SoundRecorderStarted", u8"Recording to: %1" }, + { "SoundRecorderStopped", u8"Recording saved to: %1" }, { "TestFileSavedTo", u8"Test file saved to: %1" }, { "UnsupportedMapper", u8"Unsupported mapper, cannot load game." }, @@ -69,6 +72,7 @@ std::unordered_map MessageManager::_frResources = { { "NetPlay", u8"Jeu en ligne" }, { "SaveStates", u8"Sauvegardes" }, { "ScreenshotSaved", u8"Capture d'écran" }, + { "SoundRecorder", u8"Enregistreur audio" }, { "Test", u8"Test" }, { "CheatApplied", u8"%1 code activé." }, @@ -90,7 +94,7 @@ std::unordered_map MessageManager::_frResources = { { "MovieMissingRom", u8"Le rom (%1) correspondant au film sélectionné est introuvable." }, { "MovieNewerVersion", u8"Impossible de charger un film qui a été créé avec une version plus récente de Mesen. Veuillez mettre à jour Mesen pour jouer ce film." }, { "MovieIncompatibleVersion", u8"Ce film est incompatible avec votre version de Mesen." }, - { "MoviePlaying", u8"Film démarré: %1" }, + { "MoviePlaying", u8"Film démarré : %1" }, { "MovieRecordingTo", u8"En cours d'enregistrement : %1" }, { "MovieSaved", u8"Film sauvegardé : %1" }, { "NetplayVersionMismatch", u8"%1 ne roule pas la même version de Mesen que vous et a été déconnecté automatiquement." }, @@ -101,9 +105,11 @@ std::unordered_map MessageManager::_frResources = { { "SaveStateLoaded", u8"Sauvegarde #%1 chargée." }, { "SaveStateNewerVersion", u8"Impossible de charger une sauvegarde qui a été créée avec une version plus récente de Mesen. Veuillez mettre à jour Mesen." }, { "SaveStateSaved", u8"Sauvegarde #%1 sauvegardée." }, - { "ServerStarted", u8"Le serveur a été démarré (Port: %1)" }, + { "ServerStarted", u8"Le serveur a été démarré (Port : %1)" }, { "ServerStopped", u8"Le serveur a été arrêté" }, - { "TestFileSavedTo", u8"Test sauvegardé: %1" }, + { "SoundRecorderStarted", u8"En cours d'enregistrement : %1" }, + { "SoundRecorderStopped", u8"Enregistrement audio sauvegardé : %1" }, + { "TestFileSavedTo", u8"Test sauvegardé : %1" }, { "UnsupportedMapper", u8"Ce mapper n'est pas encore supporté - le jeu ne peut pas être démarré." }, { "GoogleDrive", u8"Google Drive" }, @@ -123,6 +129,7 @@ std::unordered_map MessageManager::_jaResources = { { "NetPlay", u8"ネットプレー" }, { "SaveStates", u8"クイックセーブ" }, { "ScreenshotSaved", u8"スクリーンショット" }, + { "SoundRecorder", u8"サウンドレコーダー" }, { "Test", u8"テスト" }, { "CheatApplied", u8"チートコード%1個を有効にしました。" }, @@ -157,6 +164,8 @@ std::unordered_map MessageManager::_jaResources = { { "SaveStateSaved", u8"クイックセーブ%1をセーブしました。" }, { "ServerStarted", u8"サーバは起動しました (ポート: %1)" }, { "ServerStopped", u8"サーバは停止しました。" }, + { "SoundRecorderStarted", u8"%1に録音しています。" }, + { "SoundRecorderStopped", u8"録音を終了しました: %1" }, { "TestFileSavedTo", u8"Test file saved to: %1" }, { "UnsupportedMapper", u8"このMapperを使うゲームはロードできません。" }, diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index 118f1336..d0293df2 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -4,6 +4,8 @@ #include "CPU.h" IAudioDevice* SoundMixer::AudioDevice = nullptr; +unique_ptr SoundMixer::_waveRecorder; +SimpleLock SoundMixer::_waveRecorderLock; SoundMixer::SoundMixer() { @@ -71,7 +73,7 @@ void SoundMixer::PlayAudioBuffer(uint32_t time) size_t sampleCount = blip_read_samples(_blipBuf, _outputBuffer, SoundMixer::MaxSamplesPerFrame, 0); if(SoundMixer::AudioDevice) { //Apply low pass filter/volume reduction when in background (based on options) - if(EmulationSettings::CheckFlag(EmulationFlags::InBackground)) { + if(!_waveRecorder && EmulationSettings::CheckFlag(EmulationFlags::InBackground)) { if(EmulationSettings::CheckFlag(EmulationFlags::MuteSoundInBackground)) { _lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 0, 0); } else if(EmulationSettings::CheckFlag(EmulationFlags::ReduceSoundInBackground)) { @@ -100,6 +102,14 @@ void SoundMixer::PlayAudioBuffer(uint32_t time) } SoundMixer::AudioDevice->PlayBuffer(soundBuffer, (uint32_t)sampleCount, _sampleRate, isStereo); + if(_waveRecorder) { + _waveRecorderLock.AcquireSafe(); + if(_waveRecorder) { + if(!_waveRecorder->WriteSamples(soundBuffer, (uint32_t)sampleCount, _sampleRate, isStereo)) { + _waveRecorder.reset(); + } + } + } } if(EmulationSettings::GetSampleRate() != _sampleRate) { @@ -191,4 +201,21 @@ void SoundMixer::EndFrame(uint32_t time) _timestamps.clear(); memset(_channelOutput, 0, sizeof(_channelOutput)); +} + +void SoundMixer::StartRecording(string filepath) +{ + _waveRecorderLock.AcquireSafe(); + _waveRecorder.reset(new WaveRecorder(filepath, EmulationSettings::GetSampleRate(), EmulationSettings::GetStereoFilter() != StereoFilter::None)); +} + +void SoundMixer::StopRecording() +{ + _waveRecorderLock.AcquireSafe(); + _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 b876cf05..d7330a14 100644 --- a/Core/SoundMixer.h +++ b/Core/SoundMixer.h @@ -3,11 +3,13 @@ #include "EmulationSettings.h" #include "../Utilities/LowPassFilter.h" #include "../Utilities/blip_buf.h" +#include "../Utilities/SimpleLock.h" #include "IAudioDevice.h" #include "Snapshotable.h" #include "StereoPanningFilter.h" #include "StereoDelayFilter.h" #include "ReverbFilter.h" +#include "WaveRecorder.h" class SoundMixer : public Snapshotable { @@ -16,12 +18,15 @@ public: static const uint32_t BitsPerSample = 16; private: + static unique_ptr _waveRecorder; + static SimpleLock _waveRecorderLock; + static IAudioDevice* AudioDevice; static const uint32_t MaxSampleRate = 48000; static const uint32_t MaxSamplesPerFrame = MaxSampleRate / 60; static const uint32_t MaxChannelCount = 6; static const uint32_t ExpansionAudioIndex = MaxChannelCount - 1; - + AudioChannel _expansionAudioType; LowPassFilter _lowPassFilter; StereoPanningFilter _stereoPanning; @@ -63,6 +68,10 @@ public: void SetExpansionAudioType(AudioChannel channel); void AddExpansionAudioDelta(uint32_t time, int8_t delta); + static void StartRecording(string filepath); + static void StopRecording(); + static bool IsRecording(); + static void StopAudio(bool clearBuffer = false); static void RegisterAudioDevice(IAudioDevice *audioDevice); }; diff --git a/Core/WaveRecorder.cpp b/Core/WaveRecorder.cpp new file mode 100644 index 00000000..28ae37e5 --- /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 00000000..1cb3dabe --- /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/GUI.NET/Config/ConfigManager.cs b/GUI.NET/Config/ConfigManager.cs index 5ebd50e8..0690d9ef 100644 --- a/GUI.NET/Config/ConfigManager.cs +++ b/GUI.NET/Config/ConfigManager.cs @@ -58,6 +58,18 @@ namespace Mesen.GUI.Config } } + public static string WaveFolder + { + get + { + string waveFoler = Path.Combine(ConfigManager.HomeFolder, "Wave"); + if(!Directory.Exists(waveFoler)) { + Directory.CreateDirectory(waveFoler); + } + return waveFoler; + } + } + public static string SaveFolder { get diff --git a/GUI.NET/Dependencies/Font.24.spritefont b/GUI.NET/Dependencies/Font.24.spritefont index 760bd6910bfbf24b664d1c0189e417291b2d3a99..895badef6daa29662a57d56580c8e5fc93d60c6d 100644 GIT binary patch delta 27980 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 USgS+BX`T#qH!sacoo>PZ2ZM>zg8%>k delta 27297 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 diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index f237baf9..bd25a5ee 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -3,6 +3,7 @@ All Files (*.*)|*.* Movie files (*.mmo)|*.mmo|All Files (*.*)|*.* + Wave files (*.wav)|*.wav|All Files (*.*)|*.* Palette Files (*.pal)|*.pal|All Files (*.*)|*.* All supported formats (*.nes, *.zip, *.fds)|*.NES;*.ZIP;*.FDS|NES Roms (*.nes)|*.NES|Famicom Disk System Roms (*.fds)|*.FDS|ZIP Archives (*.zip)|*.ZIP|All (*.*)|*.* All supported formats (*.nes, *.zip, *.fds, *.ips)|*.NES;*.ZIP;*.IPS;*.FDS|NES Roms (*.nes)|*.NES|Famicom Disk System Roms (*.fds)|*.FDS|ZIP Archives (*.zip)|*.ZIP|IPS Patches (*.ips)|*.IPS|All (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index ce5bbfcc..62f38648 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -67,6 +67,9 @@ Du début du jeu De maintenant Arrêter + Enregistreur audio + Enregistrer... + Arrêter Codes Tests Run... @@ -307,11 +310,12 @@ Tous les fichiers (*.*)|*.* - Films (*.mmo)|*.mmo|All (*.*)|*.* - Fichier de palette (*.pal)|*.pal|All Files (*.*)|*.* - Tous les formats supportés (*.nes, *.zip, *.fds)|*.NES;*.ZIP;*.FDS|Roms de NES (*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers ZIP (*.zip)|*.ZIP|All (*.*)|*.* - Tous les formats supportés (*.nes, *.zip, *.fds, *.ips)|*.NES;*.ZIP;*.IPS;*.FDS|Roms de NES(*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers ZIP (*.zip)|*.ZIP|Fichiers IPS (*.ips)|*.IPS|All (*.*)|*.* - Fichiers de test (*.mtp)|*.mtp|All (*.*)|*.* + Films (*.mmo)|*.mmo|Tous les fichiers (*.*)|*.* + Fichiers wave (*.wav)|*.wav|Tous les fichiers (*.*)|*.* + Fichier de palette (*.pal)|*.pal|Tous les fichiers (*.*)|*.* + Tous les formats supportés (*.nes, *.zip, *.fds)|*.NES;*.ZIP;*.FDS|Roms de NES (*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers ZIP (*.zip)|*.ZIP|Tous les fichiers (*.*)|*.* + Tous les formats supportés (*.nes, *.zip, *.fds, *.ips)|*.NES;*.ZIP;*.IPS;*.FDS|Roms de NES(*.nes)|*.NES|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers ZIP (*.zip)|*.ZIP|Fichiers IPS (*.ips)|*.IPS|Tous les fichiers (*.*)|*.* + Fichiers de test (*.mtp)|*.mtp|Tous les fichiers (*.*)|*.* Continuer Pause diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index 8dfc5cbb..48d733e4 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -67,6 +67,9 @@ 最初から この時点から 停止 + サウンドレコーダー + 録音 + 停止 チートコード テスト Run... @@ -301,6 +304,7 @@ すべてのファイル (*.*)|*.* 動画 (*.mmo)|*.mmo|すべてのファイル (*.*)|*.* + WAVファイル (*.wav)|*.wav|すべてのファイル (*.*)|*.* パレットファイル (*.pal)|*.pal|すべてのファイル (*.*)|*.* 対応するすべてのファイル (*.nes, *.zip, *.fds)|*.NES;*.ZIP;*.FDS|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|ZIPファイル (*.zip)|*.ZIP|すべてのファイル (*.*)|*.* 対応するすべてのファイル (*.nes, *.zip, *.fds, *.ips)|*.NES;*.ZIP;*.IPS;*.FDS|ファミコンゲーム (*.nes)|*.NES|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|ZIPファイル (*.zip)|*.ZIP|IPSファイル (*.ips)|*.IPS|すべてのファイル (*.*)|*.* diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index 7f8a0c1a..f1edcb1d 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -99,6 +99,8 @@ namespace Mesen.GUI.Forms this.mnu2xSaiFilter = new System.Windows.Forms.ToolStripMenuItem(); this.mnuSuper2xSaiFilter = new System.Windows.Forms.ToolStripMenuItem(); this.mnuSuperEagleFilter = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem19 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuBilinearInterpolation = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem10 = new System.Windows.Forms.ToolStripSeparator(); this.mnuAudioConfig = new System.Windows.Forms.ToolStripMenuItem(); this.mnuInput = new System.Windows.Forms.ToolStripMenuItem(); @@ -130,6 +132,9 @@ namespace Mesen.GUI.Forms this.mnuRecordFromStart = new System.Windows.Forms.ToolStripMenuItem(); this.mnuRecordFromNow = new System.Windows.Forms.ToolStripMenuItem(); this.mnuStopMovie = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuSoundRecorder = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuWaveRecord = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuWaveStop = new System.Windows.Forms.ToolStripMenuItem(); this.mnuCheats = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem12 = new System.Windows.Forms.ToolStripSeparator(); this.mnuTests = new System.Windows.Forms.ToolStripMenuItem(); @@ -148,8 +153,6 @@ namespace Mesen.GUI.Forms this.mnuCheckForUpdates = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripMenuItem19 = new System.Windows.Forms.ToolStripSeparator(); - this.mnuBilinearInterpolation = new System.Windows.Forms.ToolStripMenuItem(); this.panelRenderer.SuspendLayout(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); @@ -407,7 +410,7 @@ namespace Mesen.GUI.Forms this.mnuShowFPS}); this.mnuEmulationSpeed.Image = global::Mesen.GUI.Properties.Resources.Speed; this.mnuEmulationSpeed.Name = "mnuEmulationSpeed"; - this.mnuEmulationSpeed.Size = new System.Drawing.Size(152, 22); + this.mnuEmulationSpeed.Size = new System.Drawing.Size(135, 22); this.mnuEmulationSpeed.Text = "Speed"; // // mnuEmuSpeedNormal @@ -506,7 +509,7 @@ namespace Mesen.GUI.Forms this.mnuFullscreen}); this.mnuVideoScale.Image = global::Mesen.GUI.Properties.Resources.Fullscreen; this.mnuVideoScale.Name = "mnuVideoScale"; - this.mnuVideoScale.Size = new System.Drawing.Size(152, 22); + this.mnuVideoScale.Size = new System.Drawing.Size(135, 22); this.mnuVideoScale.Text = "Video Size"; // // mnuScale1x @@ -587,7 +590,7 @@ namespace Mesen.GUI.Forms this.toolStripMenuItem19, this.mnuBilinearInterpolation}); this.mnuVideoFilter.Name = "mnuVideoFilter"; - this.mnuVideoFilter.Size = new System.Drawing.Size(152, 22); + this.mnuVideoFilter.Size = new System.Drawing.Size(135, 22); this.mnuVideoFilter.Text = "Video Filter"; // // mnuNoneFilter @@ -722,16 +725,29 @@ namespace Mesen.GUI.Forms this.mnuSuperEagleFilter.Text = "SuperEagle"; this.mnuSuperEagleFilter.Click += new System.EventHandler(this.mnuSuperEagleFilter_Click); // + // toolStripMenuItem19 + // + this.toolStripMenuItem19.Name = "toolStripMenuItem19"; + this.toolStripMenuItem19.Size = new System.Drawing.Size(203, 6); + // + // mnuBilinearInterpolation + // + this.mnuBilinearInterpolation.CheckOnClick = true; + this.mnuBilinearInterpolation.Name = "mnuBilinearInterpolation"; + this.mnuBilinearInterpolation.Size = new System.Drawing.Size(206, 22); + this.mnuBilinearInterpolation.Text = "Use Bilinear Interpolation"; + this.mnuBilinearInterpolation.Click += new System.EventHandler(this.mnuBilinearInterpolation_Click); + // // toolStripMenuItem10 // this.toolStripMenuItem10.Name = "toolStripMenuItem10"; - this.toolStripMenuItem10.Size = new System.Drawing.Size(149, 6); + this.toolStripMenuItem10.Size = new System.Drawing.Size(132, 6); // // mnuAudioConfig // this.mnuAudioConfig.Image = global::Mesen.GUI.Properties.Resources.Audio; this.mnuAudioConfig.Name = "mnuAudioConfig"; - this.mnuAudioConfig.Size = new System.Drawing.Size(152, 22); + this.mnuAudioConfig.Size = new System.Drawing.Size(135, 22); this.mnuAudioConfig.Text = "Audio"; this.mnuAudioConfig.Click += new System.EventHandler(this.mnuAudioConfig_Click); // @@ -739,7 +755,7 @@ namespace Mesen.GUI.Forms // this.mnuInput.Image = global::Mesen.GUI.Properties.Resources.Controller; this.mnuInput.Name = "mnuInput"; - this.mnuInput.Size = new System.Drawing.Size(152, 22); + this.mnuInput.Size = new System.Drawing.Size(135, 22); this.mnuInput.Text = "Input"; this.mnuInput.Click += new System.EventHandler(this.mnuInput_Click); // @@ -752,7 +768,7 @@ namespace Mesen.GUI.Forms this.mnuRegionDendy}); this.mnuRegion.Image = global::Mesen.GUI.Properties.Resources.Globe; this.mnuRegion.Name = "mnuRegion"; - this.mnuRegion.Size = new System.Drawing.Size(152, 22); + this.mnuRegion.Size = new System.Drawing.Size(135, 22); this.mnuRegion.Text = "Region"; // // mnuRegionAuto @@ -787,20 +803,20 @@ namespace Mesen.GUI.Forms // this.mnuVideoConfig.Image = global::Mesen.GUI.Properties.Resources.Video; this.mnuVideoConfig.Name = "mnuVideoConfig"; - this.mnuVideoConfig.Size = new System.Drawing.Size(152, 22); + this.mnuVideoConfig.Size = new System.Drawing.Size(135, 22); this.mnuVideoConfig.Text = "Video"; this.mnuVideoConfig.Click += new System.EventHandler(this.mnuVideoConfig_Click); // // toolStripMenuItem11 // this.toolStripMenuItem11.Name = "toolStripMenuItem11"; - this.toolStripMenuItem11.Size = new System.Drawing.Size(149, 6); + this.toolStripMenuItem11.Size = new System.Drawing.Size(132, 6); // // mnuPreferences // this.mnuPreferences.Image = global::Mesen.GUI.Properties.Resources.Cog; this.mnuPreferences.Name = "mnuPreferences"; - this.mnuPreferences.Size = new System.Drawing.Size(152, 22); + this.mnuPreferences.Size = new System.Drawing.Size(135, 22); this.mnuPreferences.Text = "Preferences"; this.mnuPreferences.Click += new System.EventHandler(this.mnuPreferences_Click); // @@ -809,6 +825,7 @@ namespace Mesen.GUI.Forms this.mnuTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.mnuNetPlay, this.mnuMovies, + this.mnuSoundRecorder, this.mnuCheats, this.toolStripMenuItem12, this.mnuTests, @@ -933,6 +950,7 @@ namespace Mesen.GUI.Forms // // mnuPlayMovie // + this.mnuPlayMovie.Image = global::Mesen.GUI.Properties.Resources.Play; this.mnuPlayMovie.Name = "mnuPlayMovie"; this.mnuPlayMovie.Size = new System.Drawing.Size(149, 22); this.mnuPlayMovie.Text = "Play..."; @@ -943,6 +961,7 @@ namespace Mesen.GUI.Forms this.mnuRecordFrom.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.mnuRecordFromStart, this.mnuRecordFromNow}); + this.mnuRecordFrom.Image = global::Mesen.GUI.Properties.Resources.Record; this.mnuRecordFrom.Name = "mnuRecordFrom"; this.mnuRecordFrom.Size = new System.Drawing.Size(149, 22); this.mnuRecordFrom.Text = "Record from..."; @@ -963,11 +982,38 @@ namespace Mesen.GUI.Forms // // mnuStopMovie // + this.mnuStopMovie.Image = global::Mesen.GUI.Properties.Resources.Stop; this.mnuStopMovie.Name = "mnuStopMovie"; this.mnuStopMovie.Size = new System.Drawing.Size(149, 22); this.mnuStopMovie.Text = "Stop"; this.mnuStopMovie.Click += new System.EventHandler(this.mnuStopMovie_Click); // + // 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(185, 22); + this.mnuSoundRecorder.Text = "Sound Recorder"; + // + // 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.Stop; + 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); + // // mnuCheats // this.mnuCheats.Name = "mnuCheats"; @@ -1105,19 +1151,6 @@ namespace Mesen.GUI.Forms this.mnuAbout.Text = "About"; this.mnuAbout.Click += new System.EventHandler(this.mnuAbout_Click); // - // toolStripMenuItem19 - // - this.toolStripMenuItem19.Name = "toolStripMenuItem19"; - this.toolStripMenuItem19.Size = new System.Drawing.Size(203, 6); - // - // mnuBilinearInterpolation - // - this.mnuBilinearInterpolation.CheckOnClick = true; - this.mnuBilinearInterpolation.Name = "mnuBilinearInterpolation"; - this.mnuBilinearInterpolation.Size = new System.Drawing.Size(206, 22); - this.mnuBilinearInterpolation.Text = "Use Bilinear Interpolation"; - this.mnuBilinearInterpolation.Click += new System.EventHandler(this.mnuBilinearInterpolation_Click); - // // frmMain // this.AllowDrop = true; @@ -1263,6 +1296,9 @@ namespace Mesen.GUI.Forms private System.Windows.Forms.ToolStripMenuItem mnuSuperEagleFilter; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem19; private System.Windows.Forms.ToolStripMenuItem mnuBilinearInterpolation; + private System.Windows.Forms.ToolStripMenuItem mnuSoundRecorder; + private System.Windows.Forms.ToolStripMenuItem mnuWaveRecord; + private System.Windows.Forms.ToolStripMenuItem mnuWaveStop; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 49dfbd38..5089c2d6 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -516,6 +516,10 @@ namespace Mesen.GUI.Forms mnuRecordFromStart.Enabled = _emuThread != null && !isNetPlayClient && !moviePlaying && !movieRecording; mnuRecordFromNow.Enabled = _emuThread != null && !moviePlaying && !movieRecording; + bool waveRecording = InteropEmu.WaveIsRecording(); + mnuWaveRecord.Enabled = _emuThread != null && !waveRecording; + mnuWaveStop.Enabled = _emuThread != null && waveRecording; + bool testRecording = InteropEmu.RomTestRecording(); mnuTestRun.Enabled = !netPlay && !moviePlaying && !movieRecording; mnuTestStopRecording.Enabled = _emuThread != null && testRecording; @@ -782,6 +786,23 @@ namespace Mesen.GUI.Forms RecordMovie(false); } + + private void mnuWaveRecord_Click(object sender, EventArgs e) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.Filter = ResourceHelper.GetMessage("FilterWave"); + sfd.InitialDirectory = ConfigManager.WaveFolder; + sfd.FileName = Path.GetFileNameWithoutExtension(InteropEmu.GetROMPath()) + ".wav"; + if(sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { + InteropEmu.WaveRecord(sfd.FileName); + } + } + + private void mnuWaveStop_Click(object sender, EventArgs e) + { + InteropEmu.WaveStop(); + } + private void mnuTestRun_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); diff --git a/GUI.NET/GUI.NET.csproj b/GUI.NET/GUI.NET.csproj index ab6187e0..3c330ca0 100644 --- a/GUI.NET/GUI.NET.csproj +++ b/GUI.NET/GUI.NET.csproj @@ -684,6 +684,8 @@ Always + + diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index a04f8fbb..e19db907 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -75,6 +75,10 @@ namespace Mesen.GUI [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MoviePlaying(); [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MovieRecording(); + [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 Int32 RomTestRun([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string filename); [DllImport(DLLPath)] public static extern void RomTestRecord([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string filename, [MarshalAs(UnmanagedType.I1)]bool reset); [DllImport(DLLPath)] public static extern void RomTestRecordFromMovie([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string testFilename, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string movieFilename); diff --git a/GUI.NET/Properties/Resources.Designer.cs b/GUI.NET/Properties/Resources.Designer.cs index a0cfc8b8..eef9863b 100644 --- a/GUI.NET/Properties/Resources.Designer.cs +++ b/GUI.NET/Properties/Resources.Designer.cs @@ -240,6 +240,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Microphone { + get { + object obj = ResourceManager.GetObject("Microphone", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -300,6 +310,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Record { + get { + object obj = ResourceManager.GetObject("Record", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/GUI.NET/Properties/Resources.resx b/GUI.NET/Properties/Resources.resx index 1c6c24a5..945f0bbd 100644 --- a/GUI.NET/Properties/Resources.resx +++ b/GUI.NET/Properties/Resources.resx @@ -205,4 +205,10 @@ ..\Resources\DownArrow.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\microphone.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\Record.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/GUI.NET/Resources/Record.png b/GUI.NET/Resources/Record.png new file mode 100644 index 0000000000000000000000000000000000000000..9e1534161131d76fb1d57ad2c3b778610f12f6fa GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL5ULAh?3y^w370~qEv>0#LT=By}Z;C1rt33 zJ=4@yqg0@pWKS2z5DWjaJ%M}&6gZss{$DzC($oyyycB!mO1F8sXV2Ivrm|{kdsx0m zk74}Z9J|0FrF+`Jqp<-tdKOa|^*h`7gAE=(=Z^77Y&$*gK1-9!4?EpTF^1MP981bP0l+XkK&znwy literal 0 HcmV?d00001 diff --git a/GUI.NET/Resources/microphone.png b/GUI.NET/Resources/microphone.png new file mode 100644 index 0000000000000000000000000000000000000000..12db59eba197d5ac1003dc17a019a22a40d5412e GIT binary patch literal 554 zcmV+_0@eMAP) z+X3N}C*CCiDVa6u$WpLDSKYYpNy`cIu7W3FXQ{m6fg1q%s{otWFi*| s1-ABjz0hvA**cL(&}TCI=KcsU00)%vjd`@z7ytkO07*qoM6N<$g1Y(l!vFvP literal 0 HcmV?d00001 diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index 3251ebb3..5a17d24e 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -15,6 +15,7 @@ #include "../Core/AutoRomTest.h" #include "../Core/FDS.h" #include "../Core/VsControlManager.h" +#include "../Core/SoundMixer.h" NES::Renderer *_renderer = nullptr; SoundManager *_soundManager = nullptr; @@ -205,6 +206,10 @@ namespace InteropEmu { DllExport bool __stdcall MoviePlaying() { return Movie::Playing(); } DllExport bool __stdcall MovieRecording() { return Movie::Recording(); } + DllExport void __stdcall WaveRecord(char* filename) { SoundMixer::StartRecording(filename); } + DllExport void __stdcall WaveStop() { SoundMixer::StopRecording(); } + DllExport bool __stdcall WaveIsRecording() { return SoundMixer::IsRecording(); } + DllExport int32_t __stdcall RomTestRun(char* filename) { AutoRomTest romTest;