use crate::ay8910::MIDI_CHANNEL_PSG_BASE;
use crate::config::Config;
use crate::midi_shim::{
db_to_midi_vol, lin_to_db, MIDIShim, MIDI_PAN, MIDI_PAN_CENTER, MIDI_PAN_LEFT,
MIDI_PAN_RIGHT, MIDI_VOLUME,
};
use crate::strict;
use crate::utils::{hz_to_note, shift, FactoredState};
use anyhow::Result;
use midly::num::u4;
#[allow(dead_code)]
const NR10: u8 = 0x0;
const NR11: u8 = 0x1;
const NR12: u8 = 0x2;
const NR13: u8 = 0x3;
const NR14: u8 = 0x4;
const NR21: u8 = 0x6;
const NR22: u8 = 0x7;
const NR23: u8 = 0x8;
const NR24: u8 = 0x9;
const NR30: u8 = 0xA;
const NR31: u8 = 0xB;
const NR32: u8 = 0xC;
const NR33: u8 = 0xD;
const NR34: u8 = 0xE;
const NR41: u8 = 0x10;
const NR42: u8 = 0x11;
const NR43: u8 = 0x12;
const NR44: u8 = 0x13;
#[allow(dead_code)]
const NR50: u8 = 0x14;
const NR51: u8 = 0x15;
#[allow(dead_code)]
const NR52: u8 = 0x16;
fn hz_game_boy(fnum: u32) -> f64 {
131072.0 / f64::from(2048 - fnum)
}
fn hz_game_boy_noise(poly_cntr: u8) -> f64 {
let mut freq_div = f64::from(poly_cntr & 0x7);
if freq_div == 0.0 {
freq_div = 0.5
}
let shft_frq: u16 = ((poly_cntr & 0xF0) / 0x10).into();
524288.0 / freq_div / (2 ^ (shft_frq + 0x1)) as f64
}
pub(crate) struct GameBoyState {
envelope_1: [u8; 4],
envelope_2: [u8; 4],
duty_1: [u8; 4],
duty_2: [u8; 4],
gb_pan: [u8; 5],
factored: FactoredState,
}
impl Default for GameBoyState {
fn default() -> Self {
GameBoyState {
envelope_1: [0; 4],
envelope_2: [0xFF; 4],
duty_1: [0; 4],
duty_2: [0xFF; 4],
gb_pan: [0, 0, 0, 0, 0xFF],
factored: Default::default(),
}
}
}
pub(crate) struct GameBoy<'config> {
state: GameBoyState,
config: &'config Config,
}
impl<'config> GameBoy<'config> {
pub(crate) fn new<'c: 'config>(
config: &'c Config,
opt_state: Option<GameBoyState>,
) -> Self {
Self {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn init(&mut self, midi: &mut MIDIShim) {
midi.program_change_write(MIDI_CHANNEL_PSG_BASE + 0x02.into(), 0x50.into());
midi.program_change_write(MIDI_CHANNEL_PSG_BASE + 0x03.into(), 0x7F.into());
}
pub(crate) fn command_handle(
&mut self,
register: u8,
data: u8,
midi: &mut MIDIShim,
) -> Result<()> {
let temp_byte: u8;
if register >= 0x20 {
return Ok(()); }
let channel = register / 0x5;
let channel_ptr = channel as usize;
if (channel < 0x3 && self.config.sn76489_ch_disabled[channel_ptr])
|| (channel == 0x3 && self.config.sn76489_noise_disabled)
{
return Ok(());
}
let midi_channel = MIDI_CHANNEL_PSG_BASE + channel.into();
match register {
NR30 => {
self.state.factored.note_on_1[channel_ptr] = shift(
&mut self.state.factored.note_on_2[channel_ptr],
(data & 0x80) != 0,
);
if self.state.factored.note_on_1[channel_ptr]
!= self.state.factored.note_on_2[channel_ptr]
{
if self.state.factored.note_on_2[channel_ptr] {
midi.do_note_on(
self.state.factored.note_2[channel_ptr],
self.state.factored.note_2[channel_ptr],
midi_channel,
&mut self.state.factored.midi_note
[channel_ptr],
&mut self.state.factored.midi_wheel
[channel_ptr],
Some(255),
None,
)?;
} else if self.state.factored.midi_note[channel_ptr] != 0xFF
{
midi.note_off_write(
midi_channel,
self.state.factored.midi_note[channel_ptr],
0x00.into(),
);
self.state.factored.midi_note[channel_ptr] =
0xFF.into()
}
}
},
NR11 | NR21 | NR31 | NR41 => {
if register == NR11 || register == NR21 {
self.state.duty_1[channel_ptr] =
self.state.duty_2[channel_ptr];
self.state.duty_2[channel_ptr] = (data & 0xC0) / 0x40;
if self.state.duty_1[channel_ptr]
!= self.state.duty_2[channel_ptr]
{
temp_byte = 0x4F
+ (!self.state.duty_2[channel_ptr] & 0x3);
midi.program_change_write(
midi_channel,
temp_byte.into(),
);
}
}
},
NR12 | NR22 | NR32 | NR42 => {
envelope(
&mut self.state,
channel,
register,
data,
midi,
midi_channel,
);
},
NR13 | NR23 | NR33 | NR43 | NR14 | NR24 | NR34 | NR44 => {
noise(
register,
&mut self.state,
channel,
data,
midi_channel,
midi,
)?;
},
NR51 => gameboy_stereo(data, &mut self.state, midi)?,
_ => strict!("Invalid register"),
}
Ok(())
}
}
fn envelope(
state: &mut GameBoyState,
channel: u8,
register: u8,
data: u8,
midi: &mut MIDIShim,
midi_channel: u4,
) {
let channel_ptr = channel as usize;
state.envelope_1[channel_ptr] = state.envelope_2[channel_ptr];
if register != NR32 {
state.envelope_2[channel_ptr] = (data & 0xF0) / 0x10;
state.factored.midi_volume[0] = db_to_midi_vol(lin_to_db(
f64::from(state.envelope_2[channel_ptr]) / 0x0F as f64,
));
} else {
state.envelope_2[channel_ptr] = (data & 0x60) / 0x20;
state.factored.midi_volume[0] =
db_to_midi_vol(-f64::from(state.envelope_2[channel_ptr]) * 6.0);
}
if state.envelope_1[channel_ptr] != state.envelope_2[channel_ptr] {
midi.controller_write(
midi_channel,
MIDI_VOLUME,
state.factored.midi_volume[0],
);
}
}
fn noise(
register: u8,
state: &mut GameBoyState,
channel: u8,
data: u8,
midi_channel: u4,
midi: &mut MIDIShim,
) -> Result<()> {
if (register % 0x5) == 0x3 {
state.factored.fnum_lsb[channel as usize] = data;
} else if (register % 0x5) == 0x4 {
state.factored.fnum_msb[channel as usize] = data;
}
state.factored.fnum_1[channel as usize] = state.factored.fnum_2[channel as usize];
state.factored.fnum_2[channel as usize] = ((state.factored.fnum_msb[channel as usize]
as u32 & 0x7) << 8)
| state.factored.fnum_lsb[channel as usize] as u32;
state.factored.hz_1[channel as usize] = state.factored.hz_2[channel as usize];
if channel <= 0x1 {
state.factored.hz_2[channel as usize] =
hz_game_boy(state.factored.fnum_2[channel as usize]);
} else if channel == 0x2 {
state.factored.hz_2[channel as usize] =
hz_game_boy(state.factored.fnum_2[channel as usize]) / 2.0;
} else if channel == 0x3 {
state.factored.hz_2[channel as usize] =
hz_game_boy_noise(state.factored.fnum_lsb[channel as usize]) / 32.0;
}
state.factored.note_1[channel as usize] = state.factored.note_2[channel as usize];
let mut temp_note = hz_to_note(state.factored.hz_2[channel as usize]);
if temp_note >= 0x80.into() {
temp_note = 0x7F.into();
}
state.factored.note_2[channel as usize] = temp_note;
if (register % 0x5) == 0x4 {
if channel == 0x2 {
state.factored.note_on_1[channel as usize] =
state.factored.note_on_2[channel as usize]
} else {
state.factored.note_on_1[channel as usize] = false
}
state.factored.note_on_2[channel as usize] =
state.factored.note_on_1[channel as usize] || (data & 0x80) != 0;
if state.factored.note_on_1[channel as usize]
!= state.factored.note_on_2[channel as usize]
&& state.factored.note_on_2[channel as usize]
{
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
midi_channel,
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
Some(255),
None,
)?;
} else if state.factored.note_on_2[channel as usize]
&& state.factored.note_1[channel as usize]
!= state.factored.note_2[channel as usize]
{
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
midi_channel,
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
} else if state.factored.note_1[channel as usize] != state.factored.note_2[channel as usize]
{
midi.do_note_on(
state.factored.note_1[channel as usize],
state.factored.note_2[channel as usize],
midi_channel,
&mut state.factored.midi_note[channel as usize],
&mut state.factored.midi_wheel[channel as usize],
None,
None,
)?;
}
Ok(())
}
fn gameboy_stereo(data: u8, state: &mut GameBoyState, midi: &mut MIDIShim) -> Result<()> {
let mut pan_value = 0x00;
if state.gb_pan[0x4] == data {
return Ok(());
}
for current_bit in 0x0..=0x3 {
let channel_mask = 2 ^ current_bit;
if data & (channel_mask * 0x10) != 0 {
pan_value |= 0x1 }
if data & channel_mask != 0 {
pan_value |= 0x2 }
if state.gb_pan[current_bit as usize] != pan_value || state.gb_pan[0x4] == data {
let channel = MIDI_CHANNEL_PSG_BASE + current_bit.into();
match pan_value {
0x1 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_LEFT,
),
0x2 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_RIGHT,
),
0x3 => midi.controller_write(
channel,
MIDI_PAN,
MIDI_PAN_CENTER,
),
_ => strict!(),
};
}
state.gb_pan[current_bit as usize] = pan_value;
}
state.gb_pan[0x4] = data;
Ok(())
}