Created
August 31, 2018 17:46
-
-
Save BlizzCrafter/122ed55ea01d57eecd2d9d4b439ff5bf to your computer and use it in GitHub Desktop.
Using LibretroApi with MonoGame.Forms
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.Xna.Framework; | |
using Microsoft.Xna.Framework.Audio; | |
using Microsoft.Xna.Framework.Graphics; | |
using Microsoft.Xna.Framework.Input; | |
using MonoGame.Forms.Controls; | |
using System; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
namespace LibMGTest | |
{ | |
public class CoreControl : UpdateWindow | |
{ | |
Wrapper _libretro; | |
private const string _core = "snes9x_libretro.dll"; | |
public int FrameBufferScale = 1; | |
Texture2D _frameBuffer; | |
SystemAVInfo _avinfo; | |
private int _sampleRate; | |
private int _channels = 2; | |
private int _bufferSize = 300; | |
private float[,] _workingBuffer; | |
private byte[] _audioBuffer; | |
private int bytesPerSample = 2; | |
SoundEffect sound; | |
private bool Initialized = false; | |
private bool DrawFrameBuffer = true; | |
protected override void Initialize() | |
{ | |
base.Initialize(); | |
Editor.RenderTargetsRefreshed += Editor_RenderTargetsRefreshed; | |
_libretro = new Wrapper(_core); | |
_libretro.Init(); | |
_libretro.LoadGame("s2.sfc"); | |
_avinfo = _libretro.GetAVInfo(); | |
_frameBuffer = new Texture2D(GraphicsDevice, (int)_avinfo.geometry.base_width, (int)_avinfo.geometry.base_height); | |
_sampleRate = (int)_libretro.GetAVInfo().timing.sample_rate; | |
_audioBuffer = new byte[_channels * _bufferSize * bytesPerSample]; | |
_workingBuffer = new float[_channels, _bufferSize]; | |
sound = new SoundEffect(_audioBuffer, _sampleRate, AudioChannels.Stereo); | |
Editor.ShowFPS = true; | |
Editor.ShowCursorPosition = false; | |
Editor.SetDisplayStyle = MonoGame.Forms.Services.GFXService.DisplayStyle.TopRight; | |
Initialized = true; | |
} | |
private void Editor_RenderTargetsRefreshed() | |
{ | |
DrawFrameBuffer = true; | |
} | |
protected override void OnResize(EventArgs e) | |
{ | |
base.OnResize(e); | |
if (Initialized) DrawFrameBuffer = false; | |
} | |
protected override void Update(GameTime gameTime) | |
{ | |
Libretro.RetroSetInputState(RetroInputState); | |
_libretro.Update(); | |
SubmitBuffer(); | |
sound.Play(); | |
base.Update(gameTime); | |
} | |
protected override void Draw() | |
{ | |
base.Draw(); | |
Editor.spriteBatch.Begin(); | |
if (DrawFrameBuffer) _frameBuffer.SetData(ProcessFramebuffer(_libretro.GetFramebuffer(), (uint)_libretro.GetAVInfo().geometry.base_width, (uint)_libretro.GetAVInfo().geometry.base_height)); | |
Editor.spriteBatch.Draw(_frameBuffer, new Rectangle( | |
(Editor.graphics.PresentationParameters.BackBufferWidth / 2) - ((int)_avinfo.geometry.base_width * FrameBufferScale) / 2, | |
(Editor.graphics.PresentationParameters.BackBufferHeight / 2) - ((int)_avinfo.geometry.base_height * FrameBufferScale) / 2, | |
(int)_avinfo.geometry.base_width * FrameBufferScale, | |
(int)_avinfo.geometry.base_height * FrameBufferScale), Color.White); | |
Editor.spriteBatch.DrawString(Editor.Font, "XBOX-360 Controller Input", new Vector2(10, 10), Color.Yellow); | |
Editor.spriteBatch.End(); | |
Editor.DrawDisplay(); | |
} | |
protected Color[] ProcessFramebuffer(Pixel[] frameBuffer, uint width, uint height) | |
{ | |
Color[] image = new Color[width * height]; | |
if (frameBuffer != null) | |
{ | |
image = new Color[width * height]; | |
for (int i = 0; i < width * height; i++) | |
{ | |
if (frameBuffer[i] != null) | |
{ | |
image[i] = new Color(frameBuffer[i].Red, frameBuffer[i].Green, frameBuffer[i].Blue); | |
frameBuffer[i] = null; | |
} | |
} | |
} | |
return image; | |
} | |
private void FillWorkingBuffer() | |
{ | |
IntPtr data; | |
data = _libretro.GetSoundBuffer().data; | |
int i; | |
for (i = 0; i < _libretro.GetSoundBuffer().frames; i++) | |
{ | |
short chunk = Marshal.ReadInt16(data); | |
_workingBuffer[0, i] = (float)chunk / short.MaxValue; | |
data = data + (sizeof(short)); | |
chunk = Marshal.ReadInt16(data); | |
_workingBuffer[1, i] = (float)chunk / short.MaxValue; | |
data = data + (sizeof(short)); | |
} | |
} | |
private void SubmitBuffer() | |
{ | |
FillWorkingBuffer(); | |
ConvertBuffer(_workingBuffer, _audioBuffer); | |
} | |
/// <summary> | |
/// Converts a bi-dimensional (Channel, Samples) floating point buffer to a PCM (byte) buffer with interleaved channels | |
/// </summary> | |
private void ConvertBuffer(float[,] from, byte[] to) | |
{ | |
const int bytesPerSample = 2; | |
int channels = from.GetLength(0); | |
int bufferSize = from.GetLength(1); | |
// Make sure the buffer sizes are correct | |
Debug.Assert(to.Length == bufferSize * channels * bytesPerSample, "Buffer sizes are mismatched."); | |
for (int i = 0; i < bufferSize; i++) | |
{ | |
for (int c = 0; c < channels; c++) | |
{ | |
// First clamp the value to the [-1.0..1.0] range | |
float floatSample = MathHelper.Clamp(from[c, i], -1.0f, 1.0f); | |
// Convert it to the 16 bit [short.MinValue..short.MaxValue] range | |
short shortSample = (short)(floatSample >= 0.0f ? floatSample * short.MaxValue : floatSample * short.MinValue * -1); | |
// Calculate the right index based on the PCM format of interleaved samples per channel [L-R-L-R] | |
int index = i * channels * bytesPerSample + c * bytesPerSample; | |
// Store the 16 bit sample as two consecutive 8 bit values in the buffer with regard to endian-ness | |
if (!BitConverter.IsLittleEndian) | |
{ | |
to[index] = (byte)(shortSample >> 8); | |
to[index + 1] = (byte)shortSample; | |
} | |
else | |
{ | |
to[index] = (byte)shortSample; | |
to[index + 1] = (byte)(shortSample >> 8); | |
} | |
} | |
} | |
} | |
public static unsafe short RetroInputState(uint port, uint device, uint index, uint id) | |
{ | |
//port = 1; | |
//device = 1; | |
if (id == 0) return GamePad.GetState((int)port).Buttons.A == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 1) return GamePad.GetState((int)port).Buttons.X == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 2) return GamePad.GetState((int)port).Buttons.Back == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 3) return GamePad.GetState((int)port).Buttons.Start == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 4) return GamePad.GetState((int)port).DPad.Up == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 5) return GamePad.GetState((int)port).DPad.Down == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 6) return GamePad.GetState((int)port).DPad.Left == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 7) return GamePad.GetState((int)port).DPad.Right == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 8) return GamePad.GetState((int)port).Buttons.B == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 9) return GamePad.GetState((int)port).Buttons.Y == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 10) return GamePad.GetState((int)port).Buttons.LeftShoulder == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 11) return GamePad.GetState((int)port).Buttons.RightShoulder == ButtonState.Pressed ? (short)1 : (short)0; | |
if (id == 12) return GamePad.GetState((int)port).Buttons.LeftStick == ButtonState.Pressed ? (short)1 : (short)0; | |
return 0; | |
} | |
//public static float[] RetroAudio = new float[2048]; | |
//public static int batch = 0; | |
//public static bool leftspeaker = true; | |
//public static float[] myAudio = new float[2048]; | |
//private unsafe void RetroAudioSampleBatch(Int16* data, uint frames) | |
//{ | |
// // Unity plays audio at 44100Hz, Snes = 32000Hz. So the output of this code | |
// // is really High-pitched.. To make it sound normal I adjusted the AudioSource's | |
// // 'Pitch' value to 0.33f on each Speaker; | |
// for (int i = 0; i < (int)frames; i++) | |
// { | |
// Int16 chunk = Marshal.ReadInt16((IntPtr)data); | |
// data = data + (sizeof(Int16)); | |
// float value = (((float)chunk * 0.5f)) / 32768.0f; | |
// if (value > 1.0f) value = 1.0f; | |
// if (value < -1.0f) value = -1.0f; | |
// //value = value * 0.9f; | |
// if (leftspeaker == true) | |
// { | |
// RetroAudio[batch] = value; | |
// batch++; | |
// } | |
// if (leftspeaker == false) | |
// { | |
// RetroAudio[batch] = value; | |
// batch++; | |
// } | |
// leftspeaker = !leftspeaker; | |
// if (batch >= 2048) | |
// { | |
// myAudio = RetroAudio; | |
// batch = 0; | |
// } | |
// } | |
// //short converted = BitConverter.ToInt16(RetroAudio); | |
// return; | |
//} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment