Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
run: cargo fmt --all -- --check

- name: Clippy check
run: cargo clippy --all-targets --all-features
run: cargo clippy --all-targets --no-default-features --features "log trace"

tests:
runs-on: ubuntu-latest
Expand All @@ -39,4 +39,4 @@ jobs:
toolchain: nightly

- name: Run tests
run: cargo test --package aliusnes
run: cargo test --package aliusnes --no-default-features
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
/Cargo.lock
/Cargo.lock
/aliusnes/src/apu/spc700/ipl_boot.rom
2 changes: 2 additions & 0 deletions aliusnes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
default = ["apu"]
apu = []
log = ["simplelog"]
trace = ["log"]

Expand Down
146 changes: 146 additions & 0 deletions aliusnes/src/apu/dsp/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::apu::dsp::voice::Voice;

mod voice;

bitfield! {
#[derive(Clone, Copy)]
struct Flags(u8) {
pub noise_frequency: u8 @ 0..=4,
pub disable_echo_write: bool @ 5,
pub mute_all: bool @ 6,
pub soft_reset: bool @ 7,
}
}

pub(crate) struct Dsp {
dsp_addr: usize,
voices: [Voice; 8],
left_main_channel_volume: i8,
right_main_channel_volume: i8,
left_echo_volume: i8,
right_echo_volume: i8,
key_on: u8,
key_off: u8,
flags: Flags,
end_flag_channel_mask: u8,
echo_feedback: i8,

unused: u8,

pitch_modulation_channel_mask: u8,
noise_enabled_channel_mask: u8,
echo_enabled_channel_mask: u8,

pointer_to_sample_directory: u8,
pointer_to_echo_buffer: u8,
echo_delay: u8,
echo_filter_coefficients: [i8; 8],
}

impl Dsp {
pub(crate) fn new() -> Dsp {
Dsp {
dsp_addr: 0,
voices: [Voice::new(); 8],
left_main_channel_volume: 0,
right_main_channel_volume: 0,
left_echo_volume: 0,
right_echo_volume: 0,
key_on: 0,
key_off: 0,
flags: Flags(0),
end_flag_channel_mask: 0,
echo_feedback: 0,
unused: 0,
pitch_modulation_channel_mask: 0,
noise_enabled_channel_mask: 0,
echo_enabled_channel_mask: 0,
pointer_to_sample_directory: 0,
pointer_to_echo_buffer: 0,
echo_delay: 0,
echo_filter_coefficients: [0; 8],
}
}

pub(crate) fn set_dsp_addr(&mut self, value: u8) {
self.dsp_addr = value.into();
}

pub(crate) fn read_dsp_addr(&self) -> u8 {
self.dsp_addr as u8
}

pub(crate) fn read(&self) -> u8 {
let addr = self.dsp_addr & 0x7F;
let index = addr >> 4;

match addr & 0xF {
low_nibble @ 0x0..=0x9 => return self.voices[index].read(low_nibble),
0xC => match index {
0x0 => return self.left_main_channel_volume as u8,
0x1 => return self.right_main_channel_volume as u8,
0x2 => return self.left_echo_volume as u8,
0x3 => return self.right_echo_volume as u8,
0x4 => return self.key_on,
0x5 => return self.key_off,
0x6 => return self.flags.0,
0x7 => return self.end_flag_channel_mask,
_ => (),
},
0xD => match index {
0x0 => return self.echo_feedback as u8,
0x1 => return self.unused,
0x2 => return self.pitch_modulation_channel_mask,
0x3 => return self.noise_enabled_channel_mask,
0x4 => return self.echo_enabled_channel_mask,
0x5 => return self.pointer_to_sample_directory,
0x6 => return self.pointer_to_echo_buffer,
0x7 => return self.echo_delay,
_ => (),
},
0xF => return self.echo_filter_coefficients[index] as u8,
_ => (),
}

println!("Read from invalid DSP register: {:#04X}", self.dsp_addr);
0
}

pub(crate) fn write(&mut self, data: u8) {
if self.dsp_addr >= 0x80 {
return;
}
let addr = self.dsp_addr & 0x7F;
let index = addr >> 4;

match addr & 0xF {
low_nibble @ 0x0..=0x7 => self.voices[index].write(low_nibble, data),
0xC => match index {
0x0 => self.left_main_channel_volume = data as i8,
0x1 => self.right_main_channel_volume = data as i8,
0x2 => self.left_echo_volume = data as i8,
0x3 => self.right_echo_volume = data as i8,
0x4 => self.key_on = data,
0x5 => self.key_off = data,
0x6 => self.flags = Flags(data),
_ => (),
},
0xD => match index {
0x0 => self.echo_feedback = data as i8,
0x1 => self.unused = data,
0x2 => self.pitch_modulation_channel_mask = data,
0x3 => self.noise_enabled_channel_mask = data,
0x4 => self.echo_enabled_channel_mask = data,
0x5 => self.pointer_to_sample_directory = data,
0x6 => self.pointer_to_echo_buffer = data,
0x7 => self.echo_delay = data,
_ => (),
},
0xF => self.echo_filter_coefficients[index] = data as i8,
_ => (),
}
}

#[allow(dead_code)]
pub(crate) fn output_sample(&mut self, _ram: &[u8]) {}
}
140 changes: 140 additions & 0 deletions aliusnes/src/apu/dsp/voice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::hint::unreachable_unchecked;

use crate::utils::int_traits::ManipulateU16;

bitfield! {
#[derive(Clone, Copy)]
struct Adsr(u16) {
attack_rate: u8 @ 0..=3,
decay_rate: u8 @ 4..=6,
enabled: bool @ 7,
sustain_rate: u8 @ 8..=12,
sustain_level: u8 @ 13..=15,
}
}

bitfield! {
#[derive(Clone, Copy)]
struct Gain(u8) {
fixed_volume: u8 @ 0..=6,
rate: u8 @ 0..=4,
mode: u8 @ 5..=6,
use_custom: bool @ 7,
}
}

bitfield! {
struct ControlBlock(u8) {
end: bool @ 0,
loop_sample: bool @ 1,
filter: u8 @ 2..=3,
left_shift: u8 @ 4..=7,
}
}

const FILTER_TABLE: [[f32; 2]; 4] = [
[0.0, 0.0],
[15.0 / 16.0, 0.0],
[61.0 / 32.0, 15.0 / 16.0],
[115.0 / 64.0, 13.0 / 16.0],
];

#[derive(Clone, Copy)]
pub struct Voice {
left_channel_volume: i8,
right_channel_volume: i8,
sample_pitch: u16,
sample_source_entry: u8,
adsr: Adsr,
gain: Gain,
current_envelope: u8,
current_sample: u8,
}

impl Voice {
pub(crate) fn new() -> Voice {
Voice {
left_channel_volume: 0,
right_channel_volume: 0,
sample_pitch: 0,
sample_source_entry: 0,
adsr: Adsr(0),
gain: Gain(0),
current_envelope: 0,
current_sample: 0,
}
}

pub(crate) fn read(&self, low_nibble: usize) -> u8 {
match low_nibble {
0x0 => self.left_channel_volume as u8,
0x1 => self.right_channel_volume as u8,
0x2 => self.sample_pitch.low_byte(),
0x3 => self.sample_pitch.high_byte(),
0x4 => self.sample_source_entry,
0x5 => self.adsr.0.low_byte(),
0x6 => self.adsr.0.high_byte(),
0x7 => self.gain.0,
0x8 => self.current_envelope,
0x9 => self.current_sample,
_ => unsafe { unreachable_unchecked() },
}
}

pub(crate) fn write(&mut self, low_nibble: usize, data: u8) {
match low_nibble {
0x0 => self.left_channel_volume = data as i8,
0x1 => self.right_channel_volume = data as i8,
0x2 => self.sample_pitch.set_low_byte(data),
0x3 => self.sample_pitch.set_high_byte(data),
0x4 => self.sample_source_entry = data,
0x5 => self.adsr.0.set_low_byte(data),
0x6 => self.adsr.0.set_high_byte(data),
0x7 => self.gain = Gain(data),
_ => unsafe { unreachable_unchecked() },
}
}

#[allow(dead_code)]
fn sample_entry(&self, dir: u16) -> u16 {
dir * 0x100 + self.sample_source_entry as u16 * 4
}

#[allow(dead_code)]
fn decode_brr_block(encoded: [u8; 9], old: &mut f32, older: &mut f32) -> [i16; 16] {
// First block is a control block
let control = ControlBlock(encoded[0]);

let mut decoded = [0; 16];

let control_filter = control.filter() as usize;

// Samples are stored from encoded[1] to encoded[8]
for i in 0..16 {
// Sample values range from -8 to +7
// and are stored high-endian.
let sample_byte = encoded[1 + (i / 2)];
let nibble = (sample_byte >> (4 * ((i & 1) ^ 1))) & 0x0F;

// Sign extend from 4 bits to 16 bits.
let raw_sample: i16 = (((nibble as i8) << 4) >> 4).into();

// Apply left shift value to convert to 15bit sample.
// TODO when shift amount is > 12, it seems it behaves as
// shift=12 and nibble=(nibble SAR 3)
let shifted_sample = (raw_sample << control.left_shift()) >> 1;

// Apply control filter to the sample
let filtered = f32::from(shifted_sample)
+ *old * FILTER_TABLE[control_filter][0]
+ *older * FILTER_TABLE[control_filter][1];

*older = *old;
*old = filtered;

decoded[i] = filtered as i16;
}

decoded
}
}
Loading
Loading