Skip to content

Instantly share code, notes, and snippets.

@BlizzCrafter
Created August 31, 2018 17:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BlizzCrafter/122ed55ea01d57eecd2d9d4b439ff5bf to your computer and use it in GitHub Desktop.
Save BlizzCrafter/122ed55ea01d57eecd2d9d4b439ff5bf to your computer and use it in GitHub Desktop.
Using LibretroApi with MonoGame.Forms
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