diff --git a/BizHawk.Client.Common/Global.cs b/BizHawk.Client.Common/Global.cs index 21fd1902e6..4b8c82a35b 100644 --- a/BizHawk.Client.Common/Global.cs +++ b/BizHawk.Client.Common/Global.cs @@ -164,6 +164,8 @@ namespace BizHawk.Client.Common return SystemInfo.ChannelF; case "O2": return SystemInfo.O2; + case "MAME": + return SystemInfo.MAME; } } } diff --git a/BizHawk.Client.Common/OpenAdvanced.cs b/BizHawk.Client.Common/OpenAdvanced.cs index e1ddd48609..71f5da2883 100644 --- a/BizHawk.Client.Common/OpenAdvanced.cs +++ b/BizHawk.Client.Common/OpenAdvanced.cs @@ -109,7 +109,7 @@ namespace BizHawk.Client.Common public Token token = new Token(); public string TypeName { get { return "Libretro"; } } - public string DisplayName { get { return $"{Path.GetFileNameWithoutExtension(token.CorePath)}:{token.Path}"; } } + public string DisplayName { get { return $"{Path.GetFileNameWithoutExtension(token.CorePath)}: {token.Path}"; } } public string SimplePath { get { return token.Path; } } public void Deserialize(string str) @@ -192,7 +192,7 @@ namespace BizHawk.Client.Common public string Path; public string TypeName { get { return "MAME"; } } - public string DisplayName { get { return Path; } } + public string DisplayName { get { return $"{TypeName}: {Path}"; } } public string SimplePath { get { return Path; } } public void Deserialize(string str) diff --git a/BizHawk.Client.Common/RomLoader.cs b/BizHawk.Client.Common/RomLoader.cs index cc11adcb0b..b02974892c 100644 --- a/BizHawk.Client.Common/RomLoader.cs +++ b/BizHawk.Client.Common/RomLoader.cs @@ -172,7 +172,7 @@ namespace BizHawk.Client.Common return false; } - public AdvancedRomLoaderType AdvancedLoader { get; set; } + public IOpenAdvanced OpenAdvanced { get; set; } private bool HandleArchiveBinding(HawkFile file) { @@ -271,7 +271,7 @@ namespace BizHawk.Client.Common { // MAME uses these extensions for arcade ROMs, but also accepts all sorts of variations of archives, folders, and files. if we let archive loader handle this, it won't know where to stop, since it'd require MAME's ROM database (which contains ROM names and blob hashes) to look things up, and even then it might be confused by archive/folder structure // so assume the user provides the proper ROM directly, and handle possible errors later - if (AdvancedLoader == AdvancedRomLoaderType.MAMELaunchGame) + if (OpenAdvanced is OpenAdvanced_MAME) { file.NonArchiveExtensions = new[] { ".zip", ".7z" }; } @@ -295,7 +295,7 @@ namespace BizHawk.Client.Common { string ext = null; - if (AdvancedLoader == AdvancedRomLoaderType.LibretroLaunchGame) + if (OpenAdvanced is OpenAdvanced_Libretro) { string codePathPart = Path.GetFileNameWithoutExtension(nextComm.LaunchLibretroCore); @@ -1159,7 +1159,9 @@ namespace BizHawk.Client.Common nextEmulator.CoreComm.RomStatusDetails = "PSX etc."; break; case "Arcade": - nextEmulator = new MAME(nextComm, file.Directory, file.CanonicalName); + string gameName = ""; + nextEmulator = new MAME(nextComm, file.Directory, file.CanonicalName, out gameName); + rom.GameInfo.Name = gameName; break; case "GEN": if (Global.Config.CoreForcingViaGameDB && game.ForcedCore?.ToLower() == "pico") diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 0f587323d7..3c7136246a 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -306,7 +306,6 @@ namespace BizHawk.Client.EmuHawk private void OpenRomMenuItem_Click(object sender, EventArgs e) { - AdvancedLoader = AdvancedRomLoaderType.None; OpenRom(); } @@ -318,9 +317,7 @@ namespace BizHawk.Client.EmuHawk return; } - AdvancedLoader = oac.Result; - - if (AdvancedLoader == AdvancedRomLoaderType.LibretroLaunchNoGame) + if (oac.Result == AdvancedRomLoaderType.LibretroLaunchNoGame) { var argsNoGame = new LoadRomArgs { @@ -334,16 +331,16 @@ namespace BizHawk.Client.EmuHawk var filter = RomFilter; - if (AdvancedLoader == AdvancedRomLoaderType.LibretroLaunchGame) + if (oac.Result == AdvancedRomLoaderType.LibretroLaunchGame) { args.OpenAdvanced = new OpenAdvanced_Libretro(); filter = oac.SuggestedExtensionFilter; } - else if (AdvancedLoader == AdvancedRomLoaderType.ClassicLaunchGame) + else if (oac.Result == AdvancedRomLoaderType.ClassicLaunchGame) { args.OpenAdvanced = new OpenAdvanced_OpenRom(); } - else if (AdvancedLoader == AdvancedRomLoaderType.MAMELaunchGame) + else if (oac.Result == AdvancedRomLoaderType.MAMELaunchGame) { args.OpenAdvanced = new OpenAdvanced_MAME(); filter = "MAME Arcade ROMs (*.zip)|*.zip"; diff --git a/BizHawk.Client.EmuHawk/MainForm.cs b/BizHawk.Client.EmuHawk/MainForm.cs index 9066688aac..cd2456b431 100644 --- a/BizHawk.Client.EmuHawk/MainForm.cs +++ b/BizHawk.Client.EmuHawk/MainForm.cs @@ -596,8 +596,6 @@ namespace BizHawk.Client.EmuHawk // runloop won't exec lua public bool SuppressLua { get; set; } - public AdvancedRomLoaderType AdvancedLoader { get; set; } - public long MouseWheelTracker { get; private set; } private int? _pauseOnFrame; @@ -3530,7 +3528,7 @@ namespace BizHawk.Client.EmuHawk ChoosePlatform = ChoosePlatformForRom, Deterministic = deterministic, MessageCallback = GlobalWin.OSD.AddMessage, - AdvancedLoader = AdvancedLoader + OpenAdvanced = args.OpenAdvanced }; Global.FirmwareManager.RecentlyServed.Clear(); @@ -3548,6 +3546,7 @@ namespace BizHawk.Client.EmuHawk IOpenAdvanced ioa = args.OpenAdvanced; var oa_openrom = ioa as OpenAdvanced_OpenRom; + var oa_mame = ioa as OpenAdvanced_MAME; var oa_retro = ioa as OpenAdvanced_Libretro; var ioa_retro = ioa as IOpenAdvancedLibretro; @@ -3591,6 +3590,11 @@ namespace BizHawk.Client.EmuHawk oa_openrom.Path = loader.CanonicalFullPath; } + if (oa_mame != null) + { + oa_mame.Path = loader.CanonicalFullPath; + } + if (result) { string openAdvancedArgs = $"*{OpenAdvancedSerializer.Serialize(ioa)}"; diff --git a/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs b/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs index 3ab5a03b47..e9b2e63a81 100644 --- a/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs +++ b/BizHawk.Client.EmuHawk/OpenAdvancedChooser.cs @@ -127,7 +127,7 @@ namespace BizHawk.Client.EmuHawk SuggestedExtensionFilter = filter; Result = AdvancedRomLoaderType.LibretroLaunchGame; - DialogResult = DialogResult.OK; + DialogResult = DialogResult.OK; Close(); } diff --git a/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs b/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs index 7cdb09dc00..e781a9dc8e 100644 --- a/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs +++ b/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs @@ -19,7 +19,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME singleInstance: false)] public partial class MAME : IEmulator, IVideoProvider, ISoundProvider { - public MAME(CoreComm comm, string dir, string file) + public MAME(CoreComm comm, string dir, string file, out string gamename) { ServiceProvider = new BasicServiceProvider(this); @@ -29,6 +29,8 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME MAMEThread = new Thread(ExecuteMAMEThread); AsyncLaunchMAME(); + + gamename = gameName; } #region Properties @@ -49,7 +51,6 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME public int BufferHeight { get; private set; } = 240; public int VsyncNumerator { get; private set; } = 60; public int VsyncDenominator { get; private set; } = 1; - private int samplesPerFrame => (int)Math.Round(sampleRate / this.VsyncRate()); #endregion @@ -61,8 +62,8 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME private SortedDictionary fieldsPorts = new SortedDictionary(); private IController Controller = NullController.Instance; private int[] frameBuffer = new int[0]; - private short[] audioBuffer = new short[0]; private Queue audioSamples = new Queue(); + private decimal dAudioSamples = 0; private int sampleRate = 44100; private bool paused = true; private bool exiting = false; @@ -70,6 +71,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME private int numSamples = 0; private string gameDirectory; private string gameFilename; + private string gameName = "Arcade"; private LibMAME.PeriodicCallbackDelegate periodicCallback; private LibMAME.SoundCallbackDelegate soundCallback; private LibMAME.BootCallbackDelegate bootCallback; @@ -123,12 +125,45 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME } } + /* + * GetSamplesSync() and MAME + * + * MAME generates samples 50 times per second, regardless of the VBlank + * rate of the emulated machine. It then uses complicated logic to + * output the required amount of audio to the OS driver and to the AVI, + * where it's meant to tie flashed samples to video frame duration. + * + * I'm doing my own logic here for now. I grab MAME's audio buffer + * whenever it's filled (MAMESoundCallback()) and enqueue it. + * + * Whenever Hawk wants new audio, I dequeue it, but with a little quirk. + * Since sample count per frame may not align with frame duration, I + * subtract the entire decimal fraction of "required" samples from total + * samples. I check if the fractional reminder of total samples is > 0.5 + * by rounding it. I invert it to see what number I should add to the + * integer representation of "required" samples, to compensate for + * misalignment between fractional and integral "required" samples. + * + * TODO: Figure out how MAME does this and maybe use their method instead. + */ public void GetSamplesSync(out short[] samples, out int nsamp) { - nsamp = samplesPerFrame; - samples = new short[samplesPerFrame * 2]; + decimal dSamplesPerFrame = (decimal)sampleRate * VsyncDenominator / VsyncNumerator; - for (int i = 0; i < samplesPerFrame * 2; i++) + if (audioSamples.Any()) + { + dAudioSamples -= dSamplesPerFrame; + int remainder = (int)Math.Round(dAudioSamples - Math.Truncate(dAudioSamples)) ^ 1; + nsamp = (int)Math.Round(dSamplesPerFrame) + remainder; + } + else + { + nsamp = (int)Math.Round(dSamplesPerFrame); + } + + samples = new short[nsamp * 2]; + + for (int i = 0; i < nsamp * 2; i++) { if (audioSamples.Any()) { @@ -291,6 +326,18 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME $"MAMEHawk is { version }"); } + private void UpdateGameName() + { + int lengthInBytes; + IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetGameName, out lengthInBytes); + gameName = Marshal.PtrToStringAnsi(ptr, lengthInBytes); + + if (!LibMAME.mame_lua_free_string(ptr)) + { + Console.WriteLine("LibMAME ERROR: string buffer wasn't freed"); + } + } + #endregion #region Callbacks @@ -369,6 +416,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME for (int i = 0; i < numSamples; i++) { audioSamples.Enqueue(*(pSample + i)); + dAudioSamples++; } } @@ -384,6 +432,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME CheckVersions(); GetInputFields(); Update(); + UpdateGameName(); MAMEStartupComplete.Set(); } @@ -454,6 +503,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME public const string Unpause = "emu.unpause()"; public const string Exit = "manager:machine():exit()"; public const string GetVersion = "return emu.app_version()"; + public const string GetGameName = "return manager:machine():system().description"; public const string GetPixels = "return manager:machine():video():pixels()"; public const string GetSamples = "return manager:machine():sound():samples()"; public const string GetFrameNumber = "return select(2, next(manager:machine().screens)):frame_number()";