use anyhow::Result;
use crate::midi_shim::{MIDIShim, MIDI_VOLUME};
use crate::utils::{hz_to_note, FactoredState};
use crate::config::Config;
use crate::strict;
use midly::num::u4;
const AY_AFINE: u8 = 0x00;
const AY_ACOARSE: u8 = 0x01;
const AY_BFINE: u8 = 0x02;
const AY_BCOARSE: u8 = 0x03;
const AY_CFINE: u8 = 0x04;
const AY_CCOARSE: u8 = 0x05;
const AY_NOISEPER: u8 = 0x06;
const AY_ENABLE: u8 = 0x07;
const AY_AVOL: u8 = 0x08;
const AY_BVOL: u8 = 0x09;
const AY_CVOL: u8 = 0x0A;
const AY_EFINE: u8 = 0x0B;
const AY_ECOARSE: u8 = 0x0C;
const AY_ESHAPE: u8 = 0x0D;
const AY_PORTA: u8 = 0x0E;
const AY_PORTB: u8 = 0x0F;
pub(crate) const MIDI_CHANNEL_PSG_BASE: u4 = u4::new(0x0A);
fn hz_ay(fnum: f64, clock: u32) -> f64 {
if fnum == 0.0 {
0.0
} else {
f64::from(clock / 16) / fnum
}
}
pub(crate) struct AY8910State {
attenuation_2: [u8; 7],
factored: FactoredState,
}
pub(crate) struct AY8910<'config> {
state: AY8910State,
config: &'config Config,
}
impl<'config> AY8910<'config> {
pub(crate) fn new<'c: 'config>(config: &'c Config, opt_state: Option<AY8910State>) -> Self {
AY8910 {
state: opt_state.unwrap_or_default(),
config,
}
}
pub(crate) fn command_handle(
&mut self,
chip_id: u8,
reg: u8,
data: u8,
clock: u32,
midi: &mut MIDIShim,
) -> Result<()> {
match reg {
AY_AFINE | AY_ACOARSE | AY_BFINE | AY_BCOARSE | AY_CFINE | AY_CCOARSE => {
process_tuning(reg, chip_id, &mut self.state, data, clock, midi)?;
},
AY_AVOL | AY_BVOL | AY_CVOL => {
process_vol(reg, chip_id, data, &mut self.state, midi);
},
AY_ENABLE => {
process_enable(data, chip_id, &mut self.state, midi)?;
},
AY_NOISEPER => (),
AY_EFINE | AY_ECOARSE => (),
AY_ESHAPE => (),
AY_PORTA | AY_PORTB => (),
_ => strict!("Invalid Register"),
}
Ok(())
}
}
impl Default for AY8910State {
fn default() -> Self {
AY8910State {
attenuation_2: [0xFF; 7],
factored: Default::default(),
}
}
}
fn process_enable(data: u8, chip_id: u8, state: &mut AY8910State, midi: &mut MIDIShim) -> Result<()> {
let mut temp_byte = !data;
for byte_channel in 0x0..=0x5 {
let channel = u4::from(byte_channel);
let channel_ptr = (chip_id * 0x6 + channel.as_int()) as usize;
state.factored.note_on_1[channel_ptr] =
state.factored.note_on_2[channel_ptr];
state.factored.note_on_2[channel_ptr] = (temp_byte & 0x1) != 0;
temp_byte /= 0x2;
if state.factored.note_on_1[channel_ptr]
!= state.factored.note_on_2[channel_ptr]
{
let midi_channel = if chip_id != 0 {
u4::new(0x00)
} else {
MIDI_CHANNEL_PSG_BASE
} + channel;
if state.factored.note_on_2[channel_ptr] {
midi.do_note_on(
state.factored.note_2[channel_ptr],
state.factored.note_2[channel_ptr],
midi_channel,
&mut state.factored.midi_note[channel_ptr],
&mut state.factored.midi_wheel[channel_ptr],
Some(255),
None,
)?;
} else if state.factored.midi_note[channel_ptr] != 0xFF {
midi.note_off_write(
midi_channel,
state.factored.midi_note[channel_ptr],
0x00.into(),
);
state.factored.midi_note[channel_ptr] = 0xFF.into();
}
}
}
Ok(())
}
fn process_vol(reg: u8, chip_id: u8, data: u8, state: &mut AY8910State, midi: &mut MIDIShim) {
let channel = u4::from(reg - AY_AVOL);
let channel_ptr = (chip_id * 0x3 + channel.as_int()) as usize;
let midi_channel = if chip_id != 0 {
u4::from(0x00)
} else {
MIDI_CHANNEL_PSG_BASE
} + channel;
if data & 0x10 != 0 {
state.attenuation_2[channel_ptr] = 0xFF;
state.factored.midi_volume[0] = 0x7F.into();
} else {
state.attenuation_2[channel_ptr] = data & 0xF;
state.factored.midi_volume[0] = (state.attenuation_2[channel_ptr] << 3).into();
}
midi.controller_write(
midi_channel,
MIDI_VOLUME,
state.factored.midi_volume[0],
);
}
fn process_tuning(
reg: u8,
chip_id: u8,
state: &mut AY8910State,
data: u8,
clock: u32,
midi: &mut MIDIShim,
) -> Result<()> {
let channel = u4::from(reg >> 1);
let channel_ptr = (chip_id * 0x3 + channel.as_int()) as usize;
let midi_channel = if chip_id != 0 {
u4::from(0x00)
} else {
MIDI_CHANNEL_PSG_BASE
} + channel;
state.factored.fnum_1[channel.as_int() as usize] =
state.factored.fnum_2[channel.as_int() as usize];
if reg & 0x1 != 0 {
state.factored.fnum_2[channel_ptr] =
(state.factored.fnum_2[channel_ptr] & 0xFF)
| ((data as u32 & 0xF) << 8);
} else {
state.factored.fnum_2[channel_ptr] = (state.factored.fnum_2
[channel_ptr]
& 0xF00) | data as u32;
}
state.factored.hz_1[channel_ptr] =
state.factored.hz_2[channel_ptr];
state.factored.hz_2[channel_ptr] = hz_ay(
state.factored.fnum_2[channel_ptr].into(),
clock,
);
state.factored.note_1[channel_ptr] =
state.factored.note_2[channel_ptr];
let mut temp_note = hz_to_note(state.factored.hz_2[channel_ptr]);
if temp_note >= 0x80.into() {
temp_note = 0x7F.into()
}
state.factored.note_2[channel_ptr] = temp_note;
if state.factored.note_1[channel_ptr]
!= state.factored.note_2[channel_ptr]
{
midi.do_note_on(
state.factored.note_1[channel_ptr],
state.factored.note_2[channel_ptr],
midi_channel,
&mut state.factored.midi_note[channel_ptr],
&mut state.factored.midi_wheel[channel_ptr],
None,
None,
)?;
}
Ok(())
}
pub(crate) fn ay_vol_to_db(tl: u8) -> f32 {
if tl > 0x0 {
(tl - 0xF).into()
} else {
-400.0 }
}