Compare commits

...

8 Commits

Author SHA1 Message Date
b7c1d9b8a0 ppu: Fix window 2020-02-21 00:15:09 +01:00
bdf51448f7 Do not fatal error out on y-mirroring 2020-02-21 00:12:40 +01:00
078da395ed Fix color bug
Oh man, that was way harder to track down that I want to admit.
Couple of hours wasted, yeeeah.
2020-02-20 23:41:11 +01:00
195d94ddb0 audio: improve it by essentially switching to i8 2020-02-20 21:19:14 +01:00
cd48f4c4c8 Make palette OOBs not fatal 2020-02-20 20:20:55 +01:00
2fb40e7b10 even more complains 2020-02-20 20:18:54 +01:00
f07a0032eb Remove old unnecessary cmp hack 2020-02-20 20:13:36 +01:00
4cf6225133 Address some of clippys concerns 2020-02-20 20:11:02 +01:00
9 changed files with 302 additions and 260 deletions

View File

@ -4,7 +4,11 @@ version = "0.1.0"
authors = ["Kevin Hamacher <kevin.hamacher@rub.de>"]
edition = '2018'
[features]
default = ["audio"]
audio = ["pulse-simple"]
[dependencies]
sdl2 = "*"
libc = "*"
pulse-simple = "*"
pulse-simple = { version = "*", optional = true }

View File

@ -141,12 +141,7 @@ impl CPU {
fn adc_r(&mut self, val: u8) {
let old: u8 = self.regs[REG_A];
let mut new: u8 = old;
let c: u8;
if self.flags & FLAG_C == FLAG_C {
c = 1;
} else {
c = 0;
}
let c = if self.flags & FLAG_C == FLAG_C { 1 } else { 0 };
new = new.wrapping_add(val);
new = new.wrapping_add(c);
self.regs[REG_A] = new;
@ -172,12 +167,7 @@ impl CPU {
fn sbc_r(&mut self, val: u8) {
let old: u8 = self.regs[REG_A];
let mut new: u8 = old as u8;
let c: u8;
if self.flags & FLAG_C == FLAG_C {
c = 1;
} else {
c = 0;
}
let c = if self.flags & FLAG_C == FLAG_C { 1 } else { 0 };
new = new.wrapping_sub(val);
new = new.wrapping_sub(c);
@ -227,12 +217,11 @@ impl CPU {
println!("RLC {}", REG_NAMES[reg_id]);
}
let nval: u8;
if val & 0x80 == 0x80 {
nval = val << 1 | 1;
let nval = if val & 0x80 == 0x80 {
val << 1 | 1
} else {
nval = val << 1;
}
val << 1
};
self.set_8bit_reg(reg_id, nval);
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
@ -266,12 +255,7 @@ impl CPU {
}
let carry = self.flags & FLAG_C > 0;
let nval: u8;
if !carry {
nval = val << 1;
} else {
nval = val << 1 | 1;
}
let nval = if !carry { val << 1 } else { val << 1 | 1 };
self.set_8bit_reg(reg_id, nval);
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
self.set_clear_flag(FLAG_Z, nval == 0);
@ -287,12 +271,7 @@ impl CPU {
}
let carry = self.flags & FLAG_C > 0;
let v: u8;
if !carry {
v = val >> 1;
} else {
v = (val >> 1) | 0x80;
}
let v = if !carry { val >> 1 } else { (val >> 1) | 0x80 };
self.set_8bit_reg(reg_id, v);
self.set_clear_flag(FLAG_C, val & 1 == 1);
self.set_clear_flag(FLAG_Z, v == 0);
@ -788,7 +767,7 @@ impl CPU {
self.clear_flag(FLAG_N);
// Some magic formula
self.set_clear_flag(FLAG_C, val1 as usize + val2 as usize & 0x10000 == 0x10000);
self.set_clear_flag(FLAG_C, (val1 as usize + val2 as usize) & 0x10000 == 0x10000);
self.set_clear_flag(FLAG_H, (val1 ^ val2 ^ res) & 0x1000 == 0x1000);
8
}
@ -948,10 +927,8 @@ impl CPU {
// Check for interrupts.
if self.ime {
self.check_interrupts(true);
} else if self.halted {
if self.check_interrupts(false) {
self.halted = false;
}
} else if self.halted && self.check_interrupts(false) {
self.halted = false;
}
let mut cycles: u8 = 255;
@ -961,13 +938,27 @@ impl CPU {
instruction = self.read_byte(self.ip);
if self.debug {
print!(
"{:#06x}: [SP: {:#04X}] i={:02X}. ",
"{:#06X}: [SP: {:#04X}] i={:02X}. ",
&self.ip, &self.sp, &instruction
);
for i in 0..6 {
print!("{}: {:02X} ", REG_NAMES[i], self.get_8bit_reg(i));
/*
for (idx, reg) in REG_NAMES.iter().enumerate() {
print!("{}: {:02X} ", reg, self.get_8bit_reg(idx));
}
print!("A: {:02X} ", self.regs[REG_A]);
*/
print!(
"AF={:02X}{:02X} BC={:02X}{:02X} DE={:02X}{:02X} HL={:02X}{:02X} ",
self.get_8bit_reg(REG_N_A),
self.get_8bit_reg(REG_N_F),
self.get_8bit_reg(REG_N_B),
self.get_8bit_reg(REG_N_C),
self.get_8bit_reg(REG_N_D),
self.get_8bit_reg(REG_N_E),
self.get_8bit_reg(REG_N_H),
self.get_8bit_reg(REG_N_L)
);
print!("I: {:02X} ", self.interconnect.read_byte(0xFFFF));
// Flags
@ -1000,9 +991,10 @@ impl CPU {
let carry = val & 0x80 == 0x80;
self.set_clear_flag(FLAG_C, carry);
if !carry {
self.regs[REG_A] = self.regs[REG_A] << 1;
self.regs[REG_A] <<= 1;
} else {
self.regs[REG_A] = self.regs[REG_A] << 1 | 1;
self.regs[REG_A] <<= 1;
self.regs[REG_A] |= 1;
}
self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N);
@ -1038,9 +1030,10 @@ impl CPU {
let val = self.regs[REG_A];
self.set_clear_flag(FLAG_C, val & 1 == 1);
if val & 1 == 0 {
self.regs[REG_A] = self.regs[REG_A] >> 1;
self.regs[REG_A] >>= 1;
} else {
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80;
self.regs[REG_A] >>= 1;
self.regs[REG_A] |= 0x80;
}
self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N);
@ -1069,9 +1062,10 @@ impl CPU {
let val = self.regs[REG_A];
self.set_clear_flag(FLAG_C, val & 0x80 == 0x80);
if !carry {
self.regs[REG_A] = self.regs[REG_A] << 1;
self.regs[REG_A] <<= 1;
} else {
self.regs[REG_A] = self.regs[REG_A] << 1 | 1;
self.regs[REG_A] <<= 1;
self.regs[REG_A] |= 1;
}
self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N);
@ -1108,9 +1102,10 @@ impl CPU {
let val = self.regs[REG_A];
self.set_clear_flag(FLAG_C, val & 1 == 1);
if !carry {
self.regs[REG_A] = self.regs[REG_A] >> 1;
self.regs[REG_A] >>= 1;
} else {
self.regs[REG_A] = self.regs[REG_A] >> 1 | 0x80;
self.regs[REG_A] >>= 1;
self.regs[REG_A] |= 0x80;
}
self.clear_flag(FLAG_Z);
self.clear_flag(FLAG_N);
@ -1577,12 +1572,11 @@ impl CPU {
if self.debug {
println!("ADD SP, {:02X}", arg);
}
let t: u16;
if arg > 0 {
t = self.sp.wrapping_add(arg as u16);
let t = if arg > 0 {
self.sp.wrapping_add(arg as u16)
} else {
t = self.sp.wrapping_sub((-arg) as u16);
}
self.sp.wrapping_sub((-arg) as u16)
};
let sp = self.sp;
self.clear_flag(FLAG_N);
self.clear_flag(FLAG_Z);

View File

@ -65,7 +65,7 @@ const MONOCHROME_PALETTE: &'static [[u8; 3]; 4] = &[
#[derive(Copy, Clone)]
struct CgbPalette([u8; 8]);
impl CgbPalette {
fn get_color(&self, n: usize) -> sdl2::pixels::Color {
fn get_color(self, n: usize) -> sdl2::pixels::Color {
if n == 0 {
return sdl2::pixels::Color::RGB(255, 255, 255);
}
@ -73,15 +73,34 @@ impl CgbPalette {
let r = (v & 0b1_1111) as u8;
let g = ((v >> 5) & 0b1_1111) as u8;
let b = ((v >> 10) & 0b1_1111) as u8;
// According to some code:
// Real colors:
let r = r as u16;
let g = g as u16;
let b = b as u16;
let mapped_r = ((r * 13 + g * 2 + b) >> 1) as u8;
let mapped_g = ((g * 3 + b) << 1) as u8;
let mapped_b = ((r * 3 + g * 2 + b * 11) >> 1) as u8;
sdl2::pixels::Color::RGB(mapped_r, mapped_g, mapped_b)
if false {
sdl2::pixels::Color::RGB(r << 3, g << 3, b << 3)
} else {
// According to some code:
// Real colors:
let r = r as u16;
let g = g as u16;
let b = b as u16;
let mapped_r = ((r * 13 + g * 2 + b) >> 1) as u8;
let mapped_g = ((g * 3 + b) << 1) as u8;
let mapped_b = ((r * 3 + g * 2 + b * 11) >> 1) as u8;
sdl2::pixels::Color::RGB(mapped_r, mapped_g, mapped_b)
}
}
}
impl std::fmt::Display for CgbPalette {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Palette: ")?;
for n in 0..4 {
let v = ((self.0[2 * n + 1] as u16) << 8) | (self.0[2 * n] as u16);
let r = (v & 0b1_1111) as u8;
let g = ((v >> 5) & 0b1_1111) as u8;
let b = ((v >> 10) & 0b1_1111) as u8;
write!(f, "{:02X}{:02X}{:02X} ", r, g, b)?;
}
write!(f, "")
}
}
@ -127,7 +146,7 @@ impl Default for Pixel {
}
}
#[derive(Debug)]
#[derive(Debug, PartialEq)]
enum DisplayMode {
ReadOAMMemory,
ReadFullMemory,
@ -368,11 +387,16 @@ impl Display {
self.background_palette_autoinc = (val >> 7) != 0;
}
0xFF69 => {
if self.current_mode == DisplayMode::ReadFullMemory {
println!("Trying to write to palette memory while being accessed!");
}
let idx = self.background_palette_index as usize;
if idx < 64 {
self.background_palette_cgb[idx / 8].0[idx % 8] = val;
} else {
panic!("OOB palette w/ autoinc");
println!("OOB palette w/ autoinc");
return;
}
if self.background_palette_autoinc {
self.background_palette_index += 1;
@ -387,7 +411,8 @@ impl Display {
if idx < 64 {
self.object_palette_cgb[idx / 8].0[idx % 8] = val;
} else {
panic!("OOB obj palette w/ autoinc");
println!("OOB obj palette w/ autoinc");
return;
}
if self.object_palette_autoinc {
self.object_palette_index += 1;
@ -405,7 +430,7 @@ impl Display {
}
self.windowx = val;
}
0xFF4f => self.vram_bank = val,
0xFF4F => self.vram_bank = val,
_ => panic!("Display: Write {:02X} to {:04X} unsupported", val, addr),
}
}
@ -560,15 +585,14 @@ impl Display {
}
}
fn render_sprites(&mut self, sprites: &Vec<Sprite>) {
fn render_sprites(&mut self, sprites: &[Sprite]) {
if self.control & CTRL_BG_SPRITE_ENABLE > 0 {
let mut num_rendered: u8 = 0;
for i in 0..39 {
for sprite in sprites.iter().take(39) {
// Gameboy limitation
if num_rendered >= 10 {
break;
}
let sprite = &sprites[i];
// Skip hidden sprites
if sprite.is_hidden() {
@ -583,13 +607,10 @@ impl Display {
// Is this sprite on the current line?
let wide_mode = self.control & CTRL_BG_SPRITE_SIZE > 0;
let actual_h = match wide_mode {
true => 16,
false => 8,
};
let actual_h = if wide_mode { 16 } else { 8 };
if sprite.is_y_flipped() {
panic!("Sorry, no y flip support yet");
eprintln!("Sorry, no y flip support yet, rendering as-is");
}
if y.wrapping_add(actual_h) > render_y && y <= render_y {
@ -621,10 +642,7 @@ impl Display {
}
}
let x_maybe_flipped: u8 = match sprite.is_x_flipped() {
true => x_o ^ 7,
false => x_o,
};
let x_maybe_flipped: u8 = if sprite.is_x_flipped() { x_o ^ 7 } else { x_o };
b1 = (tile_line_1 & 1 << (7 - x_o)) > 0;
b2 = (tile_line_2 & 1 << (7 - x_o)) > 0;
@ -661,7 +679,7 @@ impl Display {
PixelOrigin::Sprite,
);
*/
let c = ((b1 as u8) * 2 + b2 as u8) as usize;
let c = ((b2 as u8) * 2 + b1 as u8) as usize;
if c == 0 {
continue;
}
@ -735,16 +753,7 @@ impl Display {
// This is the non-CGB priority.
// Smaller x coord = higher.
use std::cmp;
queue.sort_by(|x, y| {
if x.x > y.x {
cmp::Ordering::Greater
} else if x.x < y.x {
cmp::Ordering::Less
} else {
cmp::Ordering::Equal
}
});
queue.sort_by(|x, y| x.x.cmp(&y.x));
queue.reverse();
// Render background
@ -780,8 +789,7 @@ impl Display {
// Obtain tile information
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
// TODO: Colored background
let addr = tile_base_addr + (tile_offset_y as usize) * 2;
let addr = tile_base_addr + (tile_offset_y as usize) * 2; // two bytes per row
let tile_line_1 = vram[addr];
let tile_line_2 = vram[addr + 1];
@ -801,7 +809,7 @@ impl Display {
let b2: bool = (tile_line_2 & b2) != 0;
// Lookup the color
let c = ((b1 as u8) * 2 + b2 as u8) as usize;
let c = ((b2 as u8) * 2 + b1 as u8) as usize;
let origin = match c {
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
_ => PixelOrigin::Background,
@ -835,87 +843,95 @@ impl Display {
if (self.control & CTRL_WND_DISPLAY_ENABLE) == CTRL_WND_DISPLAY_ENABLE {
// Draw 'window' over the background.
// Screen coordinates of the top left corner are WX-7, WY
// Quick check if the window is visible at all.
if self.windowx < 167 && self.windowy < 144 {
//let rx = self.windowx.wrapping_add(7);
let rx = 7u8.wrapping_sub(self.windowx);
let ry = render_y.wrapping_add(self.windowy);
for r_x in 0..160u8 {
let render_x = r_x.wrapping_add(rx);
// Absolute render coordinates
let tile_index_x: u8 = render_x >> 3;
let tile_index_y: u8 = ry >> 3;
let tile_offset_x: u8 = render_x & 7;
let tile_offset_y: u8 = ry & 7;
let window_y_offset = render_y as i16 - self.windowy as i16;
if self.windowx < 7 {
eprintln!("Window X position < 7, bailing out");
} else if window_y_offset >= 144 || window_y_offset < 0 {
// Not visible
} else {
let window_y_offset = window_y_offset as u8;
let window_x_offset = self.windowx - 7;
let vram_offset: usize =
window_map + (tile_index_y as usize) * 32 + tile_index_x as usize;
for r_x in 0..(160u8 - window_x_offset) {
let render_x = r_x.wrapping_add(window_x_offset);
// Absolute render coordinates
let tile_index_x: u8 = render_x >> 3;
let tile_index_y: u8 = window_y_offset >> 3;
let tile_offset_x: u8 = render_x & 7;
let tile_offset_y: u8 = window_y_offset & 7;
// Obtain tile ID in this area
let tile_id = self.vram0[vram_offset];
let vram_offset: usize =
window_map + (tile_index_y as usize) * 32 + tile_index_x as usize;
let bg_attribs = self.get_background_attribute(
//tile_index_x as usize, tile_index_y as usize
vram_offset,
);
// Obtain tile ID in this area
let tile_id = self.vram0[vram_offset];
// Get BG map attributes
let vram = if bg_attribs.vram_bank_number() == 0 {
&*self.vram0
} else {
&*self.vram1
};
let bg_attribs = self.get_background_attribute(
//tile_index_x as usize, tile_index_y as usize
vram_offset,
);
// TODO: Priority
let tile_offset_x = if bg_attribs.vertical_flip() {
7 ^ tile_offset_x
} else {
tile_offset_x
};
let tile_offset_y = if bg_attribs.horizontal_flip() {
7 ^ tile_offset_y
} else {
tile_offset_y
};
// Get BG map attributes
let vram = if bg_attribs.vram_bank_number() == 0 {
&*self.vram0
} else {
&*self.vram1
};
// Obtain tile information
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2;
let tile_line_1 = vram[tile_addr];
let tile_line_2 = vram[tile_addr + 1];
// TODO: Priority
let tile_offset_x = if bg_attribs.vertical_flip() {
7 ^ tile_offset_x
} else {
tile_offset_x
};
let tile_offset_y = if bg_attribs.horizontal_flip() {
7 ^ tile_offset_y
} else {
tile_offset_y
};
// Get the correct bit
let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0;
let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0;
// Obtain tile information
let tile_base_addr: usize = self.get_bg_window_tile_addr(tile_id);
let tile_addr = tile_base_addr + (tile_offset_y as usize) * 2;
let tile_line_1 = vram[tile_addr];
let tile_line_2 = vram[tile_addr + 1];
let c = (b1 as u8) * 2 + b2 as u8;
let origin = match c {
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
_ => PixelOrigin::Window,
};
let c = self.background_palette_cgb[bg_attribs.palette_number()]
.get_color(c as usize);
self.set_pixel(render_x, render_y, c, origin);
/*
let lookup: [u8; 4] = [
self.background_palette & 3,
(self.background_palette >> 2) & 3,
(self.background_palette >> 4) & 3,
(self.background_palette >> 6) & 3,
];
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize];
// Get the correct bit
let b1: bool = (tile_line_1 & 1 << (7 - tile_offset_x)) > 0;
let b2: bool = (tile_line_2 & 1 << (7 - tile_offset_x)) > 0;
// Draw stuff. We're currently only in monochrome mode
let origin = match c {
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
_ => PixelOrigin::Window,
};
self.set_pixel(
render_x,
render_y,
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]),
origin,
);
*/
let c = (b2 as u8) * 2 + b1 as u8;
let origin = match c {
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
_ => PixelOrigin::Window,
};
let c = self.background_palette_cgb[bg_attribs.palette_number()]
.get_color(c as usize);
self.set_pixel(render_x, render_y, c, origin);
/*
let lookup: [u8; 4] = [
self.background_palette & 3,
(self.background_palette >> 2) & 3,
(self.background_palette >> 4) & 3,
(self.background_palette >> 6) & 3,
];
let entry = MONOCHROME_PALETTE[lookup[c as usize] as usize];
// Draw stuff. We're currently only in monochrome mode
let origin = match c {
0 => PixelOrigin::Empty, // Hack so that objects will be in front of it.
_ => PixelOrigin::Window,
};
self.set_pixel(
render_x,
render_y,
sdl2::pixels::Color::RGB(entry[0], entry[1], entry[2]),
origin,
);
*/
}
}
}
}

View File

@ -82,7 +82,7 @@ pub struct Interconnect {
impl Interconnect {
pub fn new(bios: Box<[u8]>, rom: Box<[u8]>, save_file: Option<String>) -> Interconnect {
Interconnect {
bios: bios,
bios,
cartridge: cartridge::Cartridge::new(rom, save_file),
ram: vec![0; WRAM_SIZE].into_boxed_slice(),
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),

View File

@ -1,5 +1,7 @@
// let's try to write our own, awesome emulator.
// gameboy (color?)
// To make things more readable at points
#![allow(clippy::identity_op)]
use std::env;
use std::fs;
@ -24,10 +26,11 @@ fn main() {
} else {
let bios_path = &args[1];
let rom_path = &args[2];
let mut save_file: Option<String> = None;
if args.len() == 4 {
save_file = Some(args[3].clone());
}
let save_file = if args.len() == 4 {
Some(args[3].clone())
} else {
None
};
let bios = read_file(&bios_path).unwrap();
let rom = read_file(&rom_path).unwrap();

View File

@ -22,7 +22,7 @@ pub struct NoMBC {
impl NoMBC {
pub fn new(rom: Box<[u8]>, ram: Box<[u8]>) -> NoMBC {
NoMBC { rom: rom, ram: ram }
NoMBC { rom, ram }
}
}

View File

@ -71,9 +71,9 @@ impl VolumeEnvelope {
impl AudioModule<u8> for VolumeEnvelope {
fn transform(&self, sample: u8) -> u8 {
if sample > 0 {
self.current_volume
128 + self.current_volume
} else {
0
128 - self.current_volume
}
}
}

View File

@ -1,16 +1,21 @@
#[cfg(feature = "audio")]
extern crate pulse_simple;
mod envelope;
mod length;
mod square;
mod wave;
#[cfg(feature = "audio")]
use self::pulse_simple::Playback;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
#[cfg(feature = "audio")]
use std::thread;
use std::convert::TryFrom;
const OUTPUT_SAMPLE_RATE: usize = 48100;
//const OUTPUT_SAMPLE_RATE: usize = 32768;
@ -233,10 +238,11 @@ impl Default for Channel3VolumeSetting {
impl Channel3VolumeSetting {
fn transform(&self, sample: u8) -> u8 {
match self {
Self::Muted => 0,
Self::Original => sample,
Self::Half => sample >> 1,
Self::Quarter => sample >> 2,
Self::Muted => 128,
// Actually making it louder as the wave src is quite silent.
Self::Original => ((sample - 120) << 1) + 112,
Self::Half => sample,
Self::Quarter => ((sample - 120) >> 1) + 124,
}
}
}
@ -296,7 +302,7 @@ impl Channel3 {
pub fn sample(&self) -> u8 {
let input = self.wave_gen.sample();
// TODO(?): Follow the chain
// let input = self.length_counter.transform(input);
let input = self.length_counter.transform(input);
self.volume_ctrl.transform(input)
}
@ -309,10 +315,6 @@ impl Channel3 {
// TODO: Sweep
}
if self.tick_state == 7 {
// self.envelope.clock();
}
self.tick_state += 1;
if self.tick_state == 8 {
@ -336,6 +338,7 @@ pub struct SoundManager {
pub sound_object: Arc<Mutex<Sound>>,
handle: Option<std::thread::JoinHandle<()>>,
do_exit: Arc<AtomicBool>,
sound_enabled: bool,
}
impl Drop for SoundManager {
@ -351,6 +354,7 @@ impl SoundManager {
sound_object: Arc::new(Mutex::new(Sound::new())),
handle: None,
do_exit: Arc::new(AtomicBool::new(false)),
sound_enabled: true,
};
res.launch_thread();
@ -359,48 +363,57 @@ impl SoundManager {
fn launch_thread(&mut self) {
let obj = self.sound_object.clone();
if false {
return;
}
let do_exit = self.do_exit.clone();
self.handle = Some(
thread::Builder::new()
.name("Audio".into())
.spawn(move || {
// PulseAudio playback object
let playback = Playback::new("GBC", "Output", None, (OUTPUT_SAMPLE_RATE) as _);
#[cfg(feature = "audio")]
{
if self.sound_enabled {
self.handle = Some(
thread::Builder::new()
.name("Audio".into())
.spawn(move || {
// PulseAudio playback object
let playback =
Playback::new("GBC", "Output", None, (OUTPUT_SAMPLE_RATE) as _);
// Counter, used for calling the 512 Hz timer
let mut counter = 0;
// Counter, used for calling the 512 Hz timer
let mut counter = 0;
while !do_exit.load(Ordering::Relaxed) {
for _ in 0..100 {
let (s1, s2) = {
let mut c_obj = obj.lock().unwrap();
while !do_exit.load(Ordering::Relaxed) {
for _ in 0..100 {
if let Some((s1, s2)) = {
let mut c_obj = obj.lock().unwrap();
// Check for 512 Hz timer
if counter >= (OUTPUT_SAMPLE_RATE / 512) {
c_obj.clock();
counter = 0;
} else {
counter += 1;
// Check for 512 Hz timer
if counter >= (OUTPUT_SAMPLE_RATE / 512) {
c_obj.clock();
counter = 0;
} else {
counter += 1;
}
// Sample clock
c_obj.sample_clock();
// Get sample
c_obj.sample()
} {
let samps = [[s1, s2]];
playback.write(&samps[..]);
} else {
let samps = [[128, 128]];
playback.write(&samps[..]);
}
}
// Sample clock
c_obj.sample_clock();
// Get sample
c_obj.sample()
};
let samps = [[s1, s2]];
playback.write(&samps[..]);
}
std::thread::sleep(std::time::Duration::from_millis(1));
}
})
.unwrap(),
);
std::thread::sleep(std::time::Duration::from_millis(1));
}
})
.unwrap(),
);
} else {
println!("Sound will be redirected to /dev/null ;)");
self.handle = None;
}
}
}
}
@ -421,7 +434,11 @@ impl Sound {
self.channel3.sample_clock();
}
pub fn sample(&self) -> (u8, u8) {
pub fn sample(&self) -> Option<(u8, u8)> {
if self.enabled == 0 {
return None;
}
// Some basic mixing
// Output value
let mut s = (0, 0);
@ -442,47 +459,55 @@ impl Sound {
let c1_sample = self.channel1.sample();
let c2_sample = self.channel2.sample();
let c3_sample = self.channel3.sample();
let c4_sample = 0;
let c4_sample = 128;
if self.enabled != 0 {
if self.sound_output_terminal_selector & 1 > 0 {
// Output Channel1
s.0 += c1_sample;
}
if self.sound_output_terminal_selector & 2 > 0 {
// Output Channel2
s.0 += c2_sample;
}
if self.sound_output_terminal_selector & 4 > 0 {
// Output Channel3
s.0 += c3_sample;
}
if self.sound_output_terminal_selector & 8 > 0 {
// Output Channel4
s.0 += c4_sample;
}
if self.sound_output_terminal_selector & 0x10 > 0 {
// Output Channel1
s.1 += c1_sample;
}
if self.sound_output_terminal_selector & 0x20 > 0 {
// Output Channel2
s.1 += c2_sample;
}
if self.sound_output_terminal_selector & 0x40 > 0 {
// Output Channel3
s.1 += c3_sample;
}
if self.sound_output_terminal_selector & 0x80 > 0 {
// Output Channel4
s.1 += c4_sample;
}
if self.sound_output_terminal_selector & 1 > 0 {
// Output Channel1
s.0 += i16::try_from(c1_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 2 > 0 {
// Output Channel2
s.0 += i16::try_from(c2_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 4 > 0 {
// Output Channel3
s.0 += i16::try_from(c3_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 8 > 0 {
// Output Channel4
s.0 += i16::try_from(c4_sample).unwrap() - 128;
}
s.0 *= s01_volume + 1;
s.1 *= s02_volume + 1;
s
if self.sound_output_terminal_selector & 0x10 > 0 {
// Output Channel1
s.1 += i16::try_from(c1_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x20 > 0 {
// Output Channel2
s.1 += i16::try_from(c2_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x40 > 0 {
// Output Channel3
s.1 += i16::try_from(c3_sample).unwrap() - 128;
}
if self.sound_output_terminal_selector & 0x80 > 0 {
// Output Channel4
s.1 += i16::try_from(c4_sample).unwrap() - 128;
}
// TODO: This is too loud (overflowing u8) for some reason, so we're
// backing off a little bit from this.
/*
if s.0 > 30 || s.1 > 30 {
println!("Would overflow! s={:?} s1vol={} s2vol={}", s, s01_volume, s02_volume);
}
*/
//s.0 *= i16::try_from(s01_volume).unwrap(); // + 1;
//s.1 *= i16::try_from(s02_volume).unwrap(); // + 1;
let s = (128 + s.0, 128 + s.1);
let s = (u8::try_from(s.0).unwrap(), u8::try_from(s.1).unwrap());
Some(s)
}
pub fn write_byte(&mut self, addr: u16, val: u8) {

View File

@ -6,11 +6,11 @@ use crate::sound::OUTPUT_SAMPLE_RATE;
struct SamplePair(u8);
impl SamplePair {
fn first(&self) -> u8 {
fn first(self) -> u8 {
self.0 >> 4
}
fn second(&self) -> u8 {
fn second(self) -> u8 {
self.0 & 0x0F
}
}
@ -66,11 +66,11 @@ impl WaveGenerator {
let sample = &self.samples[position / 2];
match position % 2 {
0 => sample.first(),
_ => sample.second(),
0 => sample.first() + 120,
_ => sample.second() + 120,
}
} else {
0
128
}
}