
Former-commit-id: 93db7bc11bff9a48f4d66e0a378cd77ab42ca197 Former-commit-id: a6ce714c2a6a4112ff30d748c0686b1b2da41c6b
183 lines
5 KiB
Rust
183 lines
5 KiB
Rust
use std::cell::RefCell;
|
|
use std::rc::Rc;
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
use wasm_bindgen::Clamped;
|
|
|
|
use js_sys::Float32Array;
|
|
|
|
use web_sys::AudioContext;
|
|
use web_sys::CanvasRenderingContext2d;
|
|
|
|
use rustboyadvance_core::keypad as gba_keypad;
|
|
use rustboyadvance_core::prelude::*;
|
|
use rustboyadvance_utils::audio::AudioRingBuffer;
|
|
|
|
use bit::BitIndex;
|
|
|
|
#[wasm_bindgen]
|
|
pub struct Emulator {
|
|
gba: GameBoyAdvance,
|
|
interface: Rc<RefCell<Interface>>,
|
|
}
|
|
|
|
struct Interface {
|
|
frame: Vec<u8>,
|
|
keyinput: u16,
|
|
sample_rate: i32,
|
|
audio_ctx: AudioContext,
|
|
audio_ring_buffer: AudioRingBuffer,
|
|
}
|
|
|
|
impl Drop for Interface {
|
|
fn drop(&mut self) {
|
|
let _ = self.audio_ctx.clone();
|
|
}
|
|
}
|
|
|
|
impl Interface {
|
|
fn new(audio_ctx: AudioContext) -> Result<Interface, JsValue> {
|
|
Ok(Interface {
|
|
frame: vec![0; 240 * 160 * 4],
|
|
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
|
|
sample_rate: audio_ctx.sample_rate() as i32,
|
|
audio_ctx: audio_ctx,
|
|
audio_ring_buffer: Default::default(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl VideoInterface for Interface {
|
|
fn render(&mut self, buffer: &[u32]) {
|
|
// TODO optimize
|
|
for i in 0..buffer.len() {
|
|
let color = buffer[i];
|
|
self.frame[4 * i + 0] = ((color >> 16) & 0xff) as u8;
|
|
self.frame[4 * i + 1] = ((color >> 8) & 0xff) as u8;
|
|
self.frame[4 * i + 2] = (color & 0xff) as u8;
|
|
self.frame[4 * i + 3] = 255;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn convert_sample(s: i16) -> f32 {
|
|
(s as f32) / 32767_f32
|
|
}
|
|
|
|
impl AudioInterface for Interface {
|
|
fn get_sample_rate(&self) -> i32 {
|
|
self.sample_rate
|
|
}
|
|
|
|
fn push_sample(&mut self, samples: &[i16]) {
|
|
let prod = self.audio_ring_buffer.producer();
|
|
for s in samples.iter() {
|
|
let _ = prod.push(*s);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl InputInterface for Interface {
|
|
fn poll(&mut self) -> u16 {
|
|
self.keyinput
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl Emulator {
|
|
#[wasm_bindgen(constructor)]
|
|
pub fn new(bios: &[u8], rom: &[u8]) -> Result<Emulator, JsValue> {
|
|
let audio_ctx = web_sys::AudioContext::new()?;
|
|
let interface = Rc::new(RefCell::new(Interface::new(audio_ctx)?));
|
|
|
|
let gamepak = GamepakBuilder::new()
|
|
.take_buffer(rom.to_vec().into_boxed_slice())
|
|
.without_backup_to_file()
|
|
.build()
|
|
.unwrap();
|
|
|
|
let gba = GameBoyAdvance::new(
|
|
bios.to_vec().into_boxed_slice(),
|
|
gamepak,
|
|
interface.clone(),
|
|
interface.clone(),
|
|
interface.clone(),
|
|
);
|
|
|
|
Ok(Emulator { gba, interface })
|
|
}
|
|
|
|
pub fn skip_bios(&mut self) {
|
|
self.gba.skip_bios();
|
|
}
|
|
|
|
pub fn run_frame(&mut self, ctx: &CanvasRenderingContext2d) -> Result<(), JsValue> {
|
|
self.gba.frame();
|
|
let mut frame_buffer = &mut self.interface.borrow_mut().frame;
|
|
let data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
|
|
Clamped(&mut frame_buffer),
|
|
240,
|
|
160,
|
|
)
|
|
.unwrap();
|
|
ctx.put_image_data(&data, 0.0, 0.0)
|
|
}
|
|
|
|
fn map_key(event_key: &str) -> Option<gba_keypad::Keys> {
|
|
match event_key {
|
|
"Enter" => Some(gba_keypad::Keys::Start),
|
|
"Backspace" => Some(gba_keypad::Keys::Select),
|
|
"ArrowUp" => Some(gba_keypad::Keys::Up),
|
|
"ArrowDown" => Some(gba_keypad::Keys::Down),
|
|
"ArrowLeft" => Some(gba_keypad::Keys::Left),
|
|
"ArrowRight" => Some(gba_keypad::Keys::Right),
|
|
"z" => Some(gba_keypad::Keys::ButtonB),
|
|
"x" => Some(gba_keypad::Keys::ButtonA),
|
|
"a" => Some(gba_keypad::Keys::ButtonL),
|
|
"s" => Some(gba_keypad::Keys::ButtonR),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn key_down(&mut self, event_key: &str) {
|
|
debug!("Key down: {}", event_key);
|
|
let mut interface = self.interface.borrow_mut();
|
|
if let Some(key) = Emulator::map_key(event_key) {
|
|
interface.keyinput.set_bit(key as usize, false);
|
|
}
|
|
}
|
|
|
|
pub fn key_up(&mut self, event_key: &str) {
|
|
debug!("Key up: {}", event_key);
|
|
let mut interface = self.interface.borrow_mut();
|
|
if let Some(key) = Emulator::map_key(event_key) {
|
|
interface.keyinput.set_bit(key as usize, true);
|
|
}
|
|
}
|
|
|
|
pub fn test_fps(&mut self) {
|
|
use rustboyadvance_utils::FpsCounter;
|
|
|
|
let mut fps_counter = FpsCounter::default();
|
|
|
|
self.gba.skip_bios();
|
|
for _ in 0..6000 {
|
|
self.gba.frame();
|
|
if let Some(fps) = fps_counter.tick() {
|
|
info!("FPS: {}", fps);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn collect_audio_samples(&self) -> Result<Float32Array, JsValue> {
|
|
let mut interface = self.interface.borrow_mut();
|
|
|
|
let consumer = interface.audio_ring_buffer.consumer();
|
|
let mut samples = Vec::with_capacity(consumer.len());
|
|
while let Some(sample) = consumer.pop() {
|
|
samples.push(convert_sample(sample));
|
|
}
|
|
|
|
Ok(Float32Array::from(samples.as_slice()))
|
|
}
|
|
}
|