Skip to content

Commit

Permalink
Add libretro frontend with support for RetroArch
Browse files Browse the repository at this point in the history
This feature includes:

Support for building for android using the NDK

Generate .info metadata file at compile time in order to make it less confusing for usage

Improve speed of the joypad handling in general by pulling only when necessary
previously it pulled every constant number of frames, now it pulls once every frame

cycle_frame will return on vblank event to achieve a more accurate emulation
  • Loading branch information
alloncm committed Jun 21, 2024
1 parent 116abff commit 09997cb
Show file tree
Hide file tree
Showing 42 changed files with 506 additions and 106 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ members = [
"sdl",
"core",
"rpi",
"common"
"common",
"libretro"
]

[workspace.package]
version = "4.0.0"
authors = ["alloncm <alloncm@gmail.com>"]
rust-version = "1.70" # cause of once cell
rust-version = "1.73" # cause of cargo-ndk used to build for android platform
14 changes: 12 additions & 2 deletions Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
default_to_workspace = false

[tasks.all]
dependencies = ["test", "sdl", "rpios", "rpibm"]
dependencies = ["test", "sdl", "rpios", "rpibm", "libretro"]

[tasks.test]
command = "cargo"
Expand Down Expand Up @@ -57,4 +57,14 @@ install_crate = {rustup_component_name = "llvm-tools-preview"}
[tasks.install_rust_src]
toolchain = "nightly"
command = "rustup"
args = ["component", "add", "rust-src"]
args = ["component", "add", "rust-src"]

[tasks.libretro]
command = "cargo"
args = ["build", "--release", "--package", "magenboy_libretro"]
dependencies = ["libretro_android"]

[tasks.libretro_android]
install_crate = {crate_name = "cargo-ndk", binary = "cargo", test_arg = "ndk"}
command = "cargo"
args = ["ndk", "--target=aarch64-linux-android", "build", "--release", "--package", "magenboy_libretro"]
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ rustup component add llvm-tools-preview

3. Builds the image

### Libretro

See - [LibretroDocs](docs/Libretro.md)

## Running

### Desktop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ pub trait AudioResampler{
}

pub trait ResampledAudioDevice<AR:AudioResampler> : AudioDevice{
const VOLUME:Sample = 10 as Sample;

fn push_buffer(&mut self, buffer:&[StereoSample; BUFFER_SIZE]){
let resample = self.get_resampler().resample(buffer);
for sample in resample{
let(buffer, index) = self.get_audio_buffer();
buffer[*index] = sample.left_sample * Self::VOLUME;
buffer[*index + 1] = sample.left_sample * Self::VOLUME;
buffer[*index] = sample.left_sample;
buffer[*index + 1] = sample.right_sample;
*index += 2;
if *index == BUFFER_SIZE{
*index = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use magenboy_core::apu::audio_device::{BUFFER_SIZE, DEFAULT_SAPMPLE, Sample, StereoSample};
use magenboy_core::apu::audio_device::{BUFFER_SIZE, StereoSample};
use super::audio_resampler::AudioResampler;


Expand All @@ -12,15 +12,6 @@ pub struct ManualAudioResampler{
skip_to_use:u32,
}

impl ManualAudioResampler{
fn interpolate_sample(samples:&[StereoSample])->StereoSample{
let interpulated_left_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.left_sample) / samples.len() as Sample;
let interpulated_right_sample = samples.iter().fold(DEFAULT_SAPMPLE, |acc, x| acc + x.right_sample) / samples.len() as Sample;

return StereoSample{left_sample: interpulated_left_sample, right_sample: interpulated_right_sample};
}
}

impl AudioResampler for ManualAudioResampler{
fn new(original_frequency:u32, target_frequency:u32)->Self{
// Calling round in order to get the nearest integer and resample as precise as possible
Expand Down Expand Up @@ -59,7 +50,7 @@ impl AudioResampler for ManualAudioResampler{
self.sampling_counter += 1;

if self.sampling_counter == self.skip_to_use {
let interpolated_sample = Self::interpolate_sample(&self.sampling_buffer);
let interpolated_sample = StereoSample::interpolate(&self.sampling_buffer);
self.sampling_counter = 0;
self.sampling_buffer.clear();

Expand Down
6 changes: 6 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ cfg_if::cfg_if!{ if #[cfg(feature = "std")] {
pub mod mbc_handler;
pub mod mpmc_gfx_device;
pub mod logging;
pub mod audio{
mod audio_resampler;
mod manual_audio_resampler;
pub use audio_resampler::*;
pub use manual_audio_resampler::*;
}
}}

pub mod menu;
Expand Down
2 changes: 1 addition & 1 deletion common/src/mbc_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn try_get_save_data(name:&String)->Option<Vec<u8>>{
}
}

pub fn release_mbc<'a>(program_name:&String, mbc: &'a dyn Mbc){
pub fn release_mbc<'a>(program_name:&String, mbc: &'a mut dyn Mbc){
if mbc.has_battery(){
while fs::write(format!("{}{}", program_name, ".sav"), mbc.get_ram()).is_err() {}
info!("saved succesfully");
Expand Down
2 changes: 1 addition & 1 deletion common/src/scale.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extern void scale_buffer(const uint16_t* input_buffer, int input_buffer_width, i
const uint16_t pixel = ((uint16_t)blue) | (((uint16_t)green) << 5) | (((uint16_t)red) << 11);

output_buffer[output_offset_counter * 2] = (uint8_t)(pixel >> 8);
output_buffer[(output_offset_counter * 2) + 1] = (uint8_t)(pixel && 0xFF);
output_buffer[(output_offset_counter * 2) + 1] = (uint8_t)(pixel & 0xFF);
output_offset_counter++;
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/src/apu/apu_registers_updater.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use super::{
square_sample_producer::SquareSampleProducer,
volume_envelop::VolumeEnvlope,
wave_sample_producer::WaveSampleProducer,
sound_utils::NUMBER_OF_CHANNELS
NUMBER_OF_CHANNELS
};


Expand Down
16 changes: 11 additions & 5 deletions core/src/apu/audio_device.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
use super::NUMBER_OF_CHANNELS;

pub type Sample = i16;
pub const DEFAULT_SAPMPLE:Sample = 0 as Sample;
const MAX_MASTER_VOLUME:Sample = 8;
pub const SAMPLE_MAX: Sample = Sample::MAX / (MAX_MASTER_VOLUME * NUMBER_OF_CHANNELS as Sample);

pub const BUFFER_SIZE:usize = 2048;
pub const BUFFER_SIZE:usize = 0x2000;

#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct StereoSample{
pub left_sample:Sample,
Expand All @@ -13,11 +18,12 @@ impl StereoSample{
pub const fn const_defualt()->Self{
Self{left_sample:DEFAULT_SAPMPLE, right_sample:DEFAULT_SAPMPLE}
}
}

impl Clone for StereoSample{
fn clone(&self) -> Self {
Self{left_sample:self.left_sample,right_sample:self.right_sample}
pub fn interpolate(samples:&[StereoSample])->StereoSample{
let left_sample = (samples.iter().fold(0, |acc, x| acc + x.left_sample as i64) / samples.len() as i64) as Sample;
let right_sample = (samples.iter().fold(0, |acc, x| acc + x.right_sample as i64) / samples.len() as i64) as Sample;

return StereoSample{left_sample, right_sample};
}
}

Expand Down
30 changes: 16 additions & 14 deletions core/src/apu/channel.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use super::audio_device::{DEFAULT_SAPMPLE, Sample};
use super::sample_producer::SampleProducer;
use super::timer::Timer;
use super::{audio_device::{Sample, DEFAULT_SAPMPLE, SAMPLE_MAX}, sample_producer::*, timer::Timer};

pub struct Channel<Procuder: SampleProducer>{
pub enabled:bool,
Expand Down Expand Up @@ -50,20 +48,24 @@ impl<Procuder: SampleProducer> Channel<Procuder>{

pub fn get_audio_sample(&mut self)->Sample{
if self.enabled{

let sample = if self.timer.cycle(){
if self.timer.cycle(){
self.timer.update_cycles_to_tick(self.sample_producer.get_updated_frequency_ticks(self.frequency));
self.sample_producer.produce() as Sample
let digital_sample = self.sample_producer.produce();
self.last_sample = Self::convert_digital_to_analog(digital_sample);
}
else{
self.last_sample
};

self.last_sample = sample;

return self.last_sample;
}
else{
self.last_sample = DEFAULT_SAPMPLE;
}

return self.last_sample;
}

fn convert_digital_to_analog(digital_sample:u8)->Sample{
const RATIO:Sample = SAMPLE_MAX / MAX_DIGITAL_SAMPLE as Sample;

return DEFAULT_SAPMPLE;
// Expandibg the sample to the full range of the Sample type while still
// allowing furhter calculations without overflowing
return (digital_sample as Sample - (MAX_DIGITAL_SAMPLE as Sample / 2)) * RATIO;
}
}
2 changes: 1 addition & 1 deletion core/src/apu/gb_apu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use super::{
sound_terminal::SoundTerminal,
square_sample_producer::SquareSampleProducer,
wave_sample_producer::WaveSampleProducer,
sound_utils::NUMBER_OF_CHANNELS
NUMBER_OF_CHANNELS
};

pub struct GbApu<Device: AudioDevice>{
Expand Down
2 changes: 1 addition & 1 deletion core/src/apu/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ pub mod freq_sweep;
pub mod volume_envelop;
pub mod noise_sample_producer;

mod sound_utils;
mod apu_registers_updater;

pub use apu_registers_updater::*;

pub(self) const NUMBER_OF_CHANNELS:usize = 4;
4 changes: 4 additions & 0 deletions core/src/apu/sample_producer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/// Max value for digital sample according to the Pandocs
pub const MAX_DIGITAL_SAMPLE:u8 = 0xF;

pub trait SampleProducer{
/// Produces a digital sample in range of 0 to 0xF
fn produce(&mut self)->u8;
fn get_updated_frequency_ticks(&self, freq:u16)->u16;
fn reset(&mut self);
Expand Down
2 changes: 1 addition & 1 deletion core/src/apu/sound_terminal.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{audio_device::{DEFAULT_SAPMPLE, Sample}, sound_utils::NUMBER_OF_CHANNELS};
use super::{audio_device::{DEFAULT_SAPMPLE, Sample}, NUMBER_OF_CHANNELS};

type ChannelMask = u16;

Expand Down
8 changes: 0 additions & 8 deletions core/src/apu/sound_utils.rs

This file was deleted.

11 changes: 8 additions & 3 deletions core/src/apu/square_sample_producer.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use super::{sample_producer::SampleProducer, sound_utils::DUTY_TABLE};
use super::freq_sweep::FreqSweep;
use super::volume_envelop::VolumeEnvlope;
use super::{freq_sweep::FreqSweep, sample_producer::SampleProducer, volume_envelop::VolumeEnvlope};

const DUTY_TABLE:[[u8; 8]; 4] = [
[0,0,0,0,0,0,0,1],
[1,0,0,0,0,0,0,1],
[1,0,0,0,0,1,1,1],
[0,1,1,1,1,1,1,0],
];

pub struct SquareSampleProducer{
pub wave_duty:u8,
Expand Down
9 changes: 6 additions & 3 deletions core/src/keypad/joypad_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use super::{joypad_provider::JoypadProvider, joypad::Joypad, button::Button};


pub struct JoypadHandler<JP:JoypadProvider>{
pub register:u8,

register:u8,
joypad:Joypad,
joypad_provider:JP,
}
Expand All @@ -20,7 +19,9 @@ impl<JP:JoypadProvider> JoypadHandler<JP>{

pub fn poll_joypad_state(&mut self){
self.joypad_provider.provide(&mut self.joypad);
}

pub fn get_register(&mut self) -> u8{
let buttons = (self.register & BIT_5_MASK) == 0;
let directions = (self.register & BIT_4_MASK) == 0;

Expand All @@ -36,8 +37,10 @@ impl<JP:JoypadProvider> JoypadHandler<JP>{
flip_bit_u8(&mut self.register, 2, !self.joypad.buttons[Button::Up as usize]);
flip_bit_u8(&mut self.register, 3, !self.joypad.buttons[Button::Down as usize]);
}
}

return self.register;
}

pub fn set_register(&mut self, value:u8){
self.register &= 0b1100_1111; // Reset bit 4 & 5
self.register |= value & 0b0011_0000; // Seting the bits
Expand Down
8 changes: 3 additions & 5 deletions core/src/machine/gameboy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice, GFX:GfxDevice> GameBoy<'a, JP, AD, G
}

pub fn cycle_frame(&mut self){
while self.mmu.m_cycle_counter < CYCLES_PER_FRAME{
self.mmu.poll_joypad_state();

self.mmu.poll_joypad_state();

while !self.mmu.consume_vblank_event(){
//CPU
let mut cpu_cycles_passed = 1;
if !self.cpu.halt && !self.mmu.dma_block_cpu(){
Expand All @@ -72,8 +72,6 @@ impl<'a, JP:JoypadProvider, AD:AudioDevice, GFX:GfxDevice> GameBoy<'a, JP, AD, G
self.mmu.cycle(interrupt_cycles);
}
}

self.mmu.m_cycle_counter = 0;
}

fn execute_opcode(&mut self)->u8{
Expand Down
2 changes: 1 addition & 1 deletion core/src/mmu/carts/mbc1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub struct Mbc1<'a>{
}

impl<'a> Mbc for Mbc1<'a>{
fn get_ram(&self) ->&[u8] {
fn get_ram(&mut self) ->&mut [u8] {
self.ram
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/mmu/carts/mbc3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct Mbc3<'a>{

impl<'a> Mbc for Mbc3<'a>{

fn get_ram(&self) ->&[u8] {
fn get_ram(&mut self) ->&mut [u8] {
self.ram
}

Expand Down
4 changes: 2 additions & 2 deletions core/src/mmu/carts/mbc5.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ pub struct Mbc5<'a>{
}

impl<'a> Mbc for Mbc5<'a> {
fn get_ram(&self)->&[u8] {
&self.ram
fn get_ram(&mut self)->&mut [u8] {
self.ram
}

fn has_battery(&self)->bool {
Expand Down
Loading

0 comments on commit 09997cb

Please sign in to comment.