diff --git a/BizHawk.Emulation.Common/LibRetroEmulator.cs b/BizHawk.Emulation.Common/LibRetroEmulator.cs index 2fa975d7fa..f98d26b792 100644 --- a/BizHawk.Emulation.Common/LibRetroEmulator.cs +++ b/BizHawk.Emulation.Common/LibRetroEmulator.cs @@ -212,7 +212,9 @@ namespace BizHawk.Emulation.Common { IsLagFrame = true; Frame++; + nsamprecv = 0; retro.retro_run(); + Console.WriteLine("[{0}]", nsamprecv); } public int Frame { get; private set; } @@ -406,8 +408,13 @@ namespace BizHawk.Emulation.Common short[] sampbuff = new short[0]; + // debug + int nsamprecv = 0; + void SetupResampler(double fps, double sps) { + Console.WriteLine("FPS {0} SPS {1}", fps, sps); + // todo: more precise? uint spsnum = (uint)sps * 1000; uint spsden = (uint)1000; @@ -418,6 +425,7 @@ namespace BizHawk.Emulation.Common void retro_audio_sample(short left, short right) { resampler.EnqueueSample(left, right); + nsamprecv++; } uint retro_audio_sample_batch(IntPtr data, uint frames) @@ -426,6 +434,7 @@ namespace BizHawk.Emulation.Common sampbuff = new short[frames * 2]; Marshal.Copy(data, sampbuff, 0, (int)(frames * 2)); resampler.EnqueueSamples(sampbuff, (int)frames); + nsamprecv += (int)frames; // what is the return from this used for? return frames; } diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs new file mode 100644 index 0000000000..b5c1c7a4f2 --- /dev/null +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES +{ + public static class LibQuickNES + { + public const string dllname = "libquicknes.dll"; + + /// + /// create a new quicknes context + /// + /// NULL on failure + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_new(); + /// + /// destroy a quicknes context + /// + /// context previously returned from qn_new() + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_delete(IntPtr e); + /// + /// load an ines file + /// + /// context + /// file + /// length of file + /// + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_loadines(IntPtr e, byte[] data, int length); + /// + /// set audio sample rate + /// + /// context + /// hz + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_set_sample_rate(IntPtr e, int rate); + /// + /// get required min dimensions of output video buffer (8bpp) + /// + /// context + /// width + /// height + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_get_image_dimensions(IntPtr e, ref int width, ref int height); + /// + /// set output video buffer that will be used for all subsequent renders until replaced + /// + /// context + /// 8bpp, at least as big as qn_get_image_dimensions() + /// byte pitch + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_set_pixels(IntPtr e, byte[] dest, int pitch); + /// + /// emulate a single frame + /// + /// context + /// pad 1 input + /// pad 2 input + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_emulate_frame(IntPtr e, int pad1, int pad2); + /// + /// get number of times joypad was read in most recent frame + /// + /// context + /// 0 means lag + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern int qn_get_joypad_read_count(IntPtr e); + /// + /// get audio info for most recent frame + /// + /// context + /// number of samples actually created + /// 1 for mono, 2 for stereo + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_get_audio_info(IntPtr e, ref int sample_count, ref int chan_count); + /// + /// get audio for most recent frame. must not be called more than once per frame! + /// + /// context + /// sample buffer + /// length to read into sample buffer + /// length actually read + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern int qn_read_audio(IntPtr e, short[] dest, int max_samples); + /// + /// reset the console + /// + /// context + /// true for powercycle, false for reset button + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_reset(IntPtr e, bool hard); + /// + /// get the required byte size of a savestate + /// + /// context + /// size is returned + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_state_size(IntPtr e, ref int size); + /// + /// save state to buffer + /// + /// context + /// buffer + /// length of buffer + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_state_save(IntPtr e, byte[] dest, int size); + /// + /// load state from buffer + /// + /// context + /// buffer + /// length of buffer + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_state_load(IntPtr e, byte[] src, int size); + /// + /// query battery ram state + /// + /// context + /// true if battery backup sram exists + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern bool qn_has_battery_ram(IntPtr e); + /// + /// query battery ram size + /// + /// context + /// size is returned + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_battery_ram_size(IntPtr e, ref int size); + /// + /// save battery ram to buffer + /// + /// context + /// buffer + /// size + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_battery_ram_save(IntPtr e, byte[] dest, int size); + /// + /// load battery ram from buffer + /// + /// context + /// buffer + /// size + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_battery_ram_load(IntPtr e, byte[] src, int size); + /// + /// clear battery ram + /// + /// context + /// string error + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_battery_ram_clear(IntPtr e); + /// + /// set sprite limit; does not affect emulation + /// + /// context + /// 0 to hide, 8 for normal, 64 for all + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_set_sprite_limit(IntPtr e, int n); + + /// + /// handle "string error" as returned by some quicknes functions + /// + /// + public static void ThrowStringError(IntPtr p) + { + if (p == IntPtr.Zero) + return; + string s = Marshal.PtrToStringAnsi(p); + throw new InvalidOperationException("LibQuickNES error: " + s); + } + } +} diff --git a/quicknes/bizinterface.cpp b/quicknes/bizinterface.cpp new file mode 100644 index 0000000000..23207a48c5 --- /dev/null +++ b/quicknes/bizinterface.cpp @@ -0,0 +1,162 @@ +#include +#include +#include "nes_emu/Nes_Emu.h" + +// simulate the write so we'll know how long the buffer needs to be +class Sim_Writer : public Data_Writer +{ + long size_; +public: + Sim_Writer():size_(0) { } + error_t write(const void *, long size) + { + size_ += size; + return 0; + } + long size() const { return size_; } +}; + + +#define EXPORT extern "C" __declspec(dllexport) + +EXPORT Nes_Emu *qn_new() +{ + return new Nes_Emu(); +} + +EXPORT void qn_delete(Nes_Emu *e) +{ + delete e; +} + +EXPORT const char *qn_loadines(Nes_Emu *e, const void *data, int length) +{ + Mem_File_Reader r = Mem_File_Reader(data, length); + Auto_File_Reader a = Auto_File_Reader(r); + return e->load_ines(a); +} + +EXPORT const char *qn_set_sample_rate(Nes_Emu *e, int rate) +{ + return e->set_sample_rate(rate); +} + +EXPORT void qn_get_image_dimensions(Nes_Emu *e, int *width, int *height) +{ + if (width) + *width = e->buffer_width; + if (height) + *height = e->buffer_height(); +} + +EXPORT void qn_set_pixels(Nes_Emu *e, void *dest, int pitch) +{ + e->set_pixels(dest, pitch); +} + +EXPORT const char *qn_emulate_frame(Nes_Emu *e, int pad1, int pad2) +{ + return e->emulate_frame(pad1, pad2); +} + +EXPORT int qn_get_joypad_read_count(Nes_Emu *e) +{ + return e->frame().joypad_read_count; +} + +EXPORT void qn_get_audio_info(Nes_Emu *e, int *sample_count, int *chan_count) +{ + if (sample_count) + *sample_count = e->frame().sample_count; + if (chan_count) + *chan_count = e->frame().chan_count; +} + +EXPORT int qn_read_audio(Nes_Emu *e, short *dest, int max_samples) +{ + return e->read_samples(dest, max_samples); +} + +EXPORT void qn_reset(Nes_Emu *e, int hard) +{ + e->reset(hard); +} + +EXPORT const char *qn_state_size(Nes_Emu *e, int *size) +{ + Sim_Writer w = Sim_Writer(); + Auto_File_Writer a = Auto_File_Writer(w); + const char *ret = e->save_state(a); + if (size) + *size = w.size(); + return ret; +} + +EXPORT const char *qn_state_save(Nes_Emu *e, void *dest, int size) +{ + Mem_Writer w = Mem_Writer(dest, size, 0); + Auto_File_Writer a = Auto_File_Writer(w); + const char *ret = e->save_state(a); + if (!ret && w.size() != size) + return "Buffer Underrun!"; + return ret; +} + +EXPORT const char *qn_state_load(Nes_Emu *e, const void *src, int size) +{ + Mem_File_Reader r = Mem_File_Reader(src, size); + Auto_File_Reader a = Auto_File_Reader(r); + return e->load_state(a); +} + +EXPORT int qn_has_battery_ram(Nes_Emu *e) +{ + return e->has_battery_ram(); +} + +EXPORT const char *qn_battery_ram_size(Nes_Emu *e, int *size) +{ + Sim_Writer w = Sim_Writer(); + Auto_File_Writer a = Auto_File_Writer(w); + const char *ret = e->save_battery_ram(a); + if (size) + *size = w.size(); + return ret; +} + +EXPORT const char *qn_battery_ram_save(Nes_Emu *e, void *dest, int size) +{ + Mem_Writer w = Mem_Writer(dest, size, 0); + Auto_File_Writer a = Auto_File_Writer(w); + const char *ret = e->save_battery_ram(a); + if (!ret && w.size() != size) + return "Buffer Underrun!"; + return ret; +} + +EXPORT const char *qn_battery_ram_load(Nes_Emu *e, const void *src, int size) +{ + Mem_File_Reader r = Mem_File_Reader(src, size); + Auto_File_Reader a = Auto_File_Reader(r); + return e->load_battery_ram(a); +} + +EXPORT const char *qn_battery_ram_clear(Nes_Emu *e) +{ + int size = 0; + const char *ret = qn_battery_ram_size(e, &size); + if (ret) + return ret; + void *data = std::malloc(size); + if (!data) + return "Out of Memory!"; + std::memset(data, 0xff, size); + ret = qn_battery_ram_load(e, data, size); + std::free(data); + return ret; +} + +EXPORT void qn_set_sprite_limit(Nes_Emu *e, int n) +{ + e->set_sprite_mode((Nes_Emu::sprite_mode_t)n); +} diff --git a/quicknes/libquicknes/libquicknes.vcxproj b/quicknes/libquicknes/libquicknes.vcxproj index 1ba1106a78..63d052808a 100644 --- a/quicknes/libquicknes/libquicknes.vcxproj +++ b/quicknes/libquicknes/libquicknes.vcxproj @@ -11,6 +11,7 @@ + @@ -85,7 +86,7 @@ Disabled 4244;4800;4804;4996 $(ProjectDir)\.. - _WINDLL;%(PreprocessorDefinitions);DLLEXPORT=__declspec(dllexport) + _WINDLL;%(PreprocessorDefinitions);DISABLE_AUTO_FILE true @@ -102,7 +103,7 @@ true 4244;4800;4804;4996 $(ProjectDir)\.. - _WINDLL;%(PreprocessorDefinitions);DLLEXPORT=__declspec(dllexport) + _WINDLL;%(PreprocessorDefinitions);DISABLE_AUTO_FILE true diff --git a/quicknes/libquicknes/libquicknes.vcxproj.filters b/quicknes/libquicknes/libquicknes.vcxproj.filters index e4c5c91b74..73b372c67e 100644 --- a/quicknes/libquicknes/libquicknes.vcxproj.filters +++ b/quicknes/libquicknes/libquicknes.vcxproj.filters @@ -144,5 +144,8 @@ Source Files\fex + + Source Files + \ No newline at end of file