restructure

This commit is contained in:
Alex Janka 2023-03-17 14:24:11 +11:00
parent 8edf466271
commit 608817e3b7
3 changed files with 210 additions and 203 deletions

52
gb-emu/src/audio.rs Normal file
View file

@ -0,0 +1,52 @@
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Stream,
};
use futures::executor;
use gb_emu_lib::connect::{AudioOutput, DownsampleType};
const FRAMES_TO_BUFFER: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
pub fn create_output() -> (AudioOutput, Stream) {
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("no output device available");
let mut supported_configs_range = device
.supported_output_configs()
.expect("error while querying configs");
let config = supported_configs_range
.next()
.expect("no supported config?!")
.with_max_sample_rate();
let sample_rate = config.sample_rate().0;
let (output, mut rx) =
AudioOutput::new(sample_rate as f32, true, FRAMES_TO_BUFFER, DOWNSAMPLE_TYPE);
let stream = device
.build_output_stream(
&config.config(),
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
for v in data.chunks_exact_mut(2) {
match executor::block_on(rx.pop()) {
Some(a) => v.copy_from_slice(&a),
None => panic!("Audio queue disconnected!"),
}
}
},
move |err| {
// react to errors here.
println!("audio error: {err}");
},
None,
)
.unwrap();
stream.play().unwrap();
(output, stream)
}

View file

@ -1,26 +1,18 @@
#![feature(let_chains)] #![feature(let_chains)]
use std::sync::mpsc::channel;
use clap::{ArgGroup, Parser}; use clap::{ArgGroup, Parser};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Stream,
};
use futures::executor;
use gb_emu_lib::{ use gb_emu_lib::{
connect::{ connect::{EmulatorMessage, EmulatorOptions, RomFile, SerialTarget},
AudioOutput, DownsampleType, EmulatorMessage, EmulatorOptions, JoypadState, Renderer,
RomFile, SerialTarget,
},
util::scale_buffer,
EmulatorCore, EmulatorCore,
}; };
use gilrs::{ use gilrs::Gilrs;
ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks},
Button, Gilrs, use std::sync::mpsc::channel;
}; use window::WindowRenderer;
use minifb::{Key, Window, WindowOptions};
mod audio;
mod window;
/// Gameboy (DMG-A/B/C) emulator /// Gameboy (DMG-A/B/C) emulator
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -88,7 +80,7 @@ fn main() {
ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap(); ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap();
let (output, _stream) = create_audio_output(); let (output, _stream) = audio::create_output();
let options = EmulatorOptions::new( let options = EmulatorOptions::new(
WindowRenderer::new(factor, Some(Gilrs::new().unwrap())), WindowRenderer::new(factor, Some(Gilrs::new().unwrap())),
@ -117,188 +109,3 @@ fn main() {
}, },
} }
} }
const FRAMES_TO_BUFFER: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
fn create_audio_output() -> (AudioOutput, Stream) {
let host = cpal::default_host();
let device = host
.default_output_device()
.expect("no output device available");
let mut supported_configs_range = device
.supported_output_configs()
.expect("error while querying configs");
let config = supported_configs_range
.next()
.expect("no supported config?!")
.with_max_sample_rate();
let sample_rate = config.sample_rate().0;
let (output, mut rx) =
AudioOutput::new(sample_rate as f32, true, FRAMES_TO_BUFFER, DOWNSAMPLE_TYPE);
let stream = device
.build_output_stream(
&config.config(),
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
for v in data.chunks_exact_mut(2) {
match executor::block_on(rx.pop()) {
Some(a) => v.copy_from_slice(&a),
None => panic!("Audio queue disconnected!"),
}
}
},
move |err| {
// react to errors here.
println!("audio error: {err}");
},
None,
)
.unwrap();
stream.play().unwrap();
(output, stream)
}
struct WindowRenderer {
window: Option<Window>,
scaled_buf: Vec<u32>,
width: usize,
height: usize,
factor: usize,
gamepad_handler: Option<Gilrs>,
joypad_state: JoypadState,
current_rumble: bool,
}
impl WindowRenderer {
fn new(factor: usize, gamepad_handler: Option<Gilrs>) -> Self {
Self {
window: None,
scaled_buf: vec![],
width: 0,
height: 0,
factor,
gamepad_handler,
joypad_state: JoypadState::default(),
current_rumble: false,
}
}
}
impl Renderer<u32> for WindowRenderer {
fn prepare(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
self.window = Some(
Window::new(
"Gameboy",
width * self.factor,
height * self.factor,
WindowOptions::default(),
)
.unwrap(),
);
}
fn display(&mut self, buffer: &[u32]) {
if let Some(ref mut window) = self.window {
self.scaled_buf = scale_buffer(buffer, self.width, self.height, self.factor);
window
.update_with_buffer(
&self.scaled_buf,
self.width * self.factor,
self.height * self.factor,
)
.unwrap();
}
}
fn set_title(&mut self, title: String) {
if let Some(ref mut window) = self.window {
window.set_title(&title);
}
}
fn latest_joypad_state(&mut self) -> JoypadState {
self.joypad_state.reset();
if let Some(ref mut gamepad_handler) = self.gamepad_handler {
while let Some(event) = gamepad_handler.next_event() {
if let gilrs::EventType::ButtonPressed(button, _) = event.event {
match button {
Button::DPadDown => self.joypad_state.down = true,
Button::DPadUp => self.joypad_state.up = true,
Button::DPadLeft => self.joypad_state.left = true,
Button::DPadRight => self.joypad_state.right = true,
Button::Start => self.joypad_state.start = true,
Button::Select => self.joypad_state.select = true,
Button::East => self.joypad_state.a = true,
Button::South => self.joypad_state.b = true,
_ => {}
}
}
}
for (_, pad) in gamepad_handler.gamepads() {
self.joypad_state.down |= pad.is_pressed(Button::DPadDown);
self.joypad_state.up |= pad.is_pressed(Button::DPadUp);
self.joypad_state.left |= pad.is_pressed(Button::DPadLeft);
self.joypad_state.right |= pad.is_pressed(Button::DPadRight);
self.joypad_state.start |= pad.is_pressed(Button::Start);
self.joypad_state.select |= pad.is_pressed(Button::Select);
self.joypad_state.a |= pad.is_pressed(Button::East);
self.joypad_state.b |= pad.is_pressed(Button::South);
}
}
if let Some(window) = &self.window {
let keys = window.get_keys();
self.joypad_state.down |= keys.contains(&Key::Down) || keys.contains(&Key::S);
self.joypad_state.up |= keys.contains(&Key::Up) || keys.contains(&Key::W);
self.joypad_state.left |= keys.contains(&Key::Left) || keys.contains(&Key::A);
self.joypad_state.right |= keys.contains(&Key::Right) || keys.contains(&Key::D);
self.joypad_state.start |= keys.contains(&Key::Equal);
self.joypad_state.select |= keys.contains(&Key::Minus);
self.joypad_state.a |= keys.contains(&Key::Apostrophe);
self.joypad_state.b |= keys.contains(&Key::Semicolon);
}
self.joypad_state
}
fn set_rumble(&mut self, rumbling: bool) {
if rumbling != self.current_rumble && let Some(ref mut gamepad_handler) = self.gamepad_handler {
self.current_rumble = rumbling;
let ids = gamepad_handler
.gamepads()
.filter_map(|(id, gp)| if gp.is_ff_supported() { Some(id) } else { None })
.collect::<Vec<_>>();
if ids.is_empty() {
return;
}
let magnitude = if rumbling { 0xFF } else { 0x0 };
EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude },
scheduling: Replay {
after: Ticks::from_ms(0),
play_for: Ticks::from_ms(16),
with_delay: Ticks::from_ms(0),
},
envelope: Default::default(),
})
.gamepads(&ids)
.finish(gamepad_handler)
.unwrap();
}
}
}

148
gb-emu/src/window.rs Normal file
View file

@ -0,0 +1,148 @@
use gb_emu_lib::{
connect::{JoypadState, Renderer},
util::scale_buffer,
};
use gilrs::{
ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks},
Button, Gilrs,
};
use minifb::{Key, Window, WindowOptions};
pub struct WindowRenderer {
window: Option<Window>,
scaled_buf: Vec<u32>,
width: usize,
height: usize,
factor: usize,
gamepad_handler: Option<Gilrs>,
joypad_state: JoypadState,
current_rumble: bool,
}
impl WindowRenderer {
pub fn new(factor: usize, gamepad_handler: Option<Gilrs>) -> Self {
Self {
window: None,
scaled_buf: vec![],
width: 0,
height: 0,
factor,
gamepad_handler,
joypad_state: JoypadState::default(),
current_rumble: false,
}
}
}
impl Renderer<u32> for WindowRenderer {
fn prepare(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
self.window = Some(
Window::new(
"Gameboy",
width * self.factor,
height * self.factor,
WindowOptions::default(),
)
.unwrap(),
);
}
fn display(&mut self, buffer: &[u32]) {
if let Some(ref mut window) = self.window {
self.scaled_buf = scale_buffer(buffer, self.width, self.height, self.factor);
window
.update_with_buffer(
&self.scaled_buf,
self.width * self.factor,
self.height * self.factor,
)
.unwrap();
}
}
fn set_title(&mut self, title: String) {
if let Some(ref mut window) = self.window {
window.set_title(&title);
}
}
fn latest_joypad_state(&mut self) -> JoypadState {
self.joypad_state.reset();
if let Some(ref mut gamepad_handler) = self.gamepad_handler {
while let Some(event) = gamepad_handler.next_event() {
if let gilrs::EventType::ButtonPressed(button, _) = event.event {
match button {
Button::DPadDown => self.joypad_state.down = true,
Button::DPadUp => self.joypad_state.up = true,
Button::DPadLeft => self.joypad_state.left = true,
Button::DPadRight => self.joypad_state.right = true,
Button::Start => self.joypad_state.start = true,
Button::Select => self.joypad_state.select = true,
Button::East => self.joypad_state.a = true,
Button::South => self.joypad_state.b = true,
_ => {}
}
}
}
for (_, pad) in gamepad_handler.gamepads() {
self.joypad_state.down |= pad.is_pressed(Button::DPadDown);
self.joypad_state.up |= pad.is_pressed(Button::DPadUp);
self.joypad_state.left |= pad.is_pressed(Button::DPadLeft);
self.joypad_state.right |= pad.is_pressed(Button::DPadRight);
self.joypad_state.start |= pad.is_pressed(Button::Start);
self.joypad_state.select |= pad.is_pressed(Button::Select);
self.joypad_state.a |= pad.is_pressed(Button::East);
self.joypad_state.b |= pad.is_pressed(Button::South);
}
}
if let Some(window) = &self.window {
let keys = window.get_keys();
self.joypad_state.down |= keys.contains(&Key::Down) || keys.contains(&Key::S);
self.joypad_state.up |= keys.contains(&Key::Up) || keys.contains(&Key::W);
self.joypad_state.left |= keys.contains(&Key::Left) || keys.contains(&Key::A);
self.joypad_state.right |= keys.contains(&Key::Right) || keys.contains(&Key::D);
self.joypad_state.start |= keys.contains(&Key::Equal);
self.joypad_state.select |= keys.contains(&Key::Minus);
self.joypad_state.a |= keys.contains(&Key::Apostrophe);
self.joypad_state.b |= keys.contains(&Key::Semicolon);
}
self.joypad_state
}
fn set_rumble(&mut self, rumbling: bool) {
if rumbling != self.current_rumble && let Some(ref mut gamepad_handler) = self.gamepad_handler {
self.current_rumble = rumbling;
let ids = gamepad_handler
.gamepads()
.filter_map(|(id, gp)| if gp.is_ff_supported() { Some(id) } else { None })
.collect::<Vec<_>>();
if ids.is_empty() {
return;
}
let magnitude = if rumbling { 0xFF } else { 0x0 };
EffectBuilder::new()
.add_effect(BaseEffect {
kind: BaseEffectType::Strong { magnitude },
scheduling: Replay {
after: Ticks::from_ms(0),
play_for: Ticks::from_ms(16),
with_delay: Ticks::from_ms(0),
},
envelope: Default::default(),
})
.gamepads(&ids)
.finish(gamepad_handler)
.unwrap();
}
}
}