524 lines
14 KiB
Rust
524 lines
14 KiB
Rust
extern crate pulse_simple;
|
|
mod envelope;
|
|
mod length;
|
|
mod square;
|
|
mod wave;
|
|
|
|
use self::pulse_simple::Playback;
|
|
use std::sync::{
|
|
atomic::{AtomicBool, Ordering},
|
|
Arc, Mutex,
|
|
};
|
|
use std::thread;
|
|
|
|
const OUTPUT_SAMPLE_RATE: usize = 48100;
|
|
//const OUTPUT_SAMPLE_RATE: usize = 32768;
|
|
|
|
/// Represents an 'audio module', transforming an input sample to an output sample
|
|
trait AudioModule<T> {
|
|
fn transform(&self, sample: T) -> T;
|
|
}
|
|
|
|
/// Clockable audio components
|
|
trait AudioComponent {
|
|
fn clock(&mut self);
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Channel1 {
|
|
// Square 1: Sweep -> Timer -> Duty -> Length Counter -> Envelope -> Mixer
|
|
wave_gen: square::SquareWaveGenerator,
|
|
length_counter: length::LengthCounter<u8>,
|
|
envelope: envelope::VolumeEnvelope,
|
|
|
|
tick_state: u8,
|
|
|
|
stored_regs: [u8; 5],
|
|
}
|
|
|
|
impl Channel1 {
|
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
|
match addr {
|
|
0xFF10 => {
|
|
self.wave_gen.set_sweep_reg(val);
|
|
self.stored_regs[0] = val;
|
|
}
|
|
0xFF11 => {
|
|
self.length_counter.set_length(val & 0x3F);
|
|
self.wave_gen.set_duty_cycle(val >> 6);
|
|
self.stored_regs[1] = val;
|
|
}
|
|
0xFF12 => {
|
|
self.envelope.set_register(val);
|
|
self.stored_regs[2] = val;
|
|
}
|
|
0xFF13 => {
|
|
// Set lower frequency
|
|
self.wave_gen.set_lower_freq(val);
|
|
self.stored_regs[3] = val;
|
|
}
|
|
0xFF14 => {
|
|
self.stored_regs[4] = val;
|
|
// Set higher
|
|
self.wave_gen.set_higher_freq(val & 0b111);
|
|
|
|
if val & (1 << 7) != 0 {
|
|
self.wave_gen.reset();
|
|
self.envelope.start();
|
|
}
|
|
|
|
self.length_counter.set_enabled(val & 1 << 6);
|
|
}
|
|
_ => panic!("Channel1: Write not supported: {:04X} = {:02X}", addr, val),
|
|
}
|
|
}
|
|
|
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
match addr {
|
|
0xFF10..=0xFF14 => self.stored_regs[(addr - 0xFF10) as usize],
|
|
_ => {
|
|
println!("Channel1 does not support reading ({:04X})", addr);
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn sample_clock(&mut self) {
|
|
self.wave_gen.clock();
|
|
}
|
|
|
|
pub fn sample(&self) -> u8 {
|
|
let input = self.wave_gen.sample();
|
|
// Follow the chain
|
|
let input = self.length_counter.transform(input);
|
|
self.envelope.transform(input)
|
|
}
|
|
|
|
pub fn clock(&mut self) {
|
|
if self.tick_state % 2 == 0 {
|
|
self.length_counter.clock();
|
|
}
|
|
|
|
if self.tick_state == 2 || self.tick_state == 6 {
|
|
self.wave_gen.sweep_clock();
|
|
}
|
|
|
|
if self.tick_state == 7 {
|
|
self.envelope.clock();
|
|
}
|
|
// TODO: Sweep
|
|
// TODO: Sweep in adition mode + overflow? stop.
|
|
self.tick_state += 1;
|
|
|
|
if self.tick_state == 8 {
|
|
self.tick_state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Channel2 {
|
|
// Square 2: Timer -> Duty -> Length Counter -> Envelope -> Mixer
|
|
wave_gen: square::SquareWaveGenerator,
|
|
length_counter: length::LengthCounter<u8>,
|
|
envelope: envelope::VolumeEnvelope,
|
|
|
|
tick_state: u8,
|
|
}
|
|
|
|
impl Channel2 {
|
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
|
match addr {
|
|
0xFF16 => {
|
|
self.length_counter.set_length(val & 0x3F);
|
|
self.wave_gen.set_duty_cycle(val >> 6);
|
|
}
|
|
0xFF17 => {
|
|
self.envelope.set_register(val);
|
|
}
|
|
0xFF18 => {
|
|
// Set lower frequency
|
|
self.wave_gen.set_lower_freq(val);
|
|
}
|
|
0xFF19 => {
|
|
// Set higher
|
|
self.wave_gen.set_higher_freq(val & 0b111);
|
|
|
|
if val & (1 << 7) != 0 {
|
|
self.wave_gen.reset();
|
|
self.envelope.start();
|
|
}
|
|
|
|
self.length_counter.set_enabled(val & 1 << 6);
|
|
}
|
|
_ => panic!("Channel2: Write not supported: {:04X} = {:02X}", addr, val),
|
|
}
|
|
}
|
|
|
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
use crate::sound::square::DutyCycle::*;
|
|
match addr {
|
|
0xFF16 => match self.wave_gen.duty_cycle {
|
|
Duty0 => 0, // 12.5%
|
|
Duty1 => 0b0100_0000, // 25.0%
|
|
Duty2 => 0b1000_0000, // 50.0%
|
|
Duty3 => 0b1100_0000, // 75.0%
|
|
},
|
|
_ => {
|
|
println!("Channel2 does not support reading ({:04X})", addr);
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn sample_clock(&mut self) {
|
|
self.wave_gen.clock();
|
|
}
|
|
|
|
pub fn sample(&self) -> u8 {
|
|
let input = self.wave_gen.sample();
|
|
// Follow the chain
|
|
let input = self.length_counter.transform(input);
|
|
self.envelope.transform(input)
|
|
}
|
|
|
|
pub fn clock(&mut self) {
|
|
if self.tick_state % 2 == 0 {
|
|
self.length_counter.clock();
|
|
}
|
|
|
|
if self.tick_state == 2 || self.tick_state == 6 {
|
|
// TODO: Sweep
|
|
}
|
|
|
|
if self.tick_state == 7 {
|
|
self.envelope.clock();
|
|
}
|
|
|
|
self.tick_state += 1;
|
|
|
|
if self.tick_state == 8 {
|
|
self.tick_state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum Channel3VolumeSetting {
|
|
Muted,
|
|
Original,
|
|
Half,
|
|
Quarter,
|
|
}
|
|
|
|
impl From<u8> for Channel3VolumeSetting {
|
|
fn from(o: u8) -> Self {
|
|
use Channel3VolumeSetting::*;
|
|
match o {
|
|
0 => Muted,
|
|
1 => Original,
|
|
2 => Half,
|
|
3 => Quarter,
|
|
_ => unreachable!("Invalid channel volume setting"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Channel3VolumeSetting {
|
|
fn default() -> Self {
|
|
Self::Muted
|
|
}
|
|
}
|
|
|
|
impl Channel3VolumeSetting {
|
|
fn transform(&self, sample: u8) -> u8 {
|
|
match self {
|
|
Self::Muted => 0,
|
|
Self::Original => sample,
|
|
Self::Half => sample >> 1,
|
|
Self::Quarter => sample >> 2,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Channel3 {
|
|
// Timer -> Wave -> Length Counter -> Volume -> Mixer
|
|
wave_gen: wave::WaveGenerator,
|
|
length_counter: length::LengthCounter<u8>,
|
|
volume_ctrl: Channel3VolumeSetting,
|
|
tick_state: u8,
|
|
}
|
|
|
|
impl Channel3 {
|
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
|
// OFF flag -> "bit 7 of NR30"
|
|
match addr {
|
|
0xFF1A => {
|
|
self.wave_gen.enabled = (val & (1 << 7)) > 0;
|
|
}
|
|
0xFF1B => {
|
|
self.length_counter.set_length(val);
|
|
}
|
|
0xFF1C => self.volume_ctrl = ((val >> 5) & 0b11).into(),
|
|
0xFF1D => {
|
|
// Set lower frequency
|
|
self.wave_gen.set_lower_freq(val);
|
|
}
|
|
0xFF1E => {
|
|
// Set higher
|
|
self.wave_gen.set_higher_freq(val & 0b111);
|
|
|
|
if val & (1 << 7) != 0 {
|
|
self.wave_gen.reset();
|
|
}
|
|
|
|
self.length_counter.set_enabled(val & (1 << 6));
|
|
}
|
|
0xFF30..=0xFF3F => {
|
|
self.wave_gen.set_sample_pair((addr - 0xFF30) as usize, val);
|
|
}
|
|
_ => panic!("Channel3: Write not supported: {:04X} = {:02X}", addr, val),
|
|
}
|
|
}
|
|
|
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
match addr {
|
|
0xFF30..=0xFF3F => self.wave_gen.get_sample_pair((addr - 0xFF30) as usize),
|
|
_ => panic!("Channel3 does not support reading ({:04X})", addr),
|
|
}
|
|
}
|
|
|
|
pub fn sample_clock(&mut self) {
|
|
self.wave_gen.clock();
|
|
}
|
|
|
|
pub fn sample(&self) -> u8 {
|
|
let input = self.wave_gen.sample();
|
|
// TODO(?): Follow the chain
|
|
// let input = self.length_counter.transform(input);
|
|
self.volume_ctrl.transform(input)
|
|
}
|
|
|
|
pub fn clock(&mut self) {
|
|
if self.tick_state % 2 == 0 {
|
|
self.length_counter.clock();
|
|
}
|
|
|
|
if self.tick_state == 2 || self.tick_state == 6 {
|
|
// TODO: Sweep
|
|
}
|
|
|
|
if self.tick_state == 7 {
|
|
// self.envelope.clock();
|
|
}
|
|
|
|
self.tick_state += 1;
|
|
|
|
if self.tick_state == 8 {
|
|
self.tick_state = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct Sound {
|
|
channel1: Channel1,
|
|
channel2: Channel2,
|
|
channel3: Channel3,
|
|
|
|
sound_channel_volume_control: u8,
|
|
sound_output_terminal_selector: u8,
|
|
enabled: u8,
|
|
}
|
|
|
|
pub struct SoundManager {
|
|
pub sound_object: Arc<Mutex<Sound>>,
|
|
handle: Option<std::thread::JoinHandle<()>>,
|
|
do_exit: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl Drop for SoundManager {
|
|
fn drop(&mut self) {
|
|
self.do_exit.store(true, Ordering::Relaxed);
|
|
self.handle.take().and_then(|h| h.join().ok());
|
|
}
|
|
}
|
|
|
|
impl SoundManager {
|
|
pub fn new() -> Self {
|
|
let mut res = SoundManager {
|
|
sound_object: Arc::new(Mutex::new(Sound::new())),
|
|
handle: None,
|
|
do_exit: Arc::new(AtomicBool::new(false)),
|
|
};
|
|
|
|
res.launch_thread();
|
|
res
|
|
}
|
|
|
|
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 _);
|
|
|
|
// 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();
|
|
|
|
// 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[..]);
|
|
}
|
|
std::thread::sleep(std::time::Duration::from_millis(1));
|
|
}
|
|
})
|
|
.unwrap(),
|
|
);
|
|
}
|
|
}
|
|
|
|
impl Sound {
|
|
pub fn new() -> Sound {
|
|
Sound::default()
|
|
}
|
|
|
|
pub fn clock(&mut self) {
|
|
self.channel1.clock();
|
|
self.channel2.clock();
|
|
self.channel3.clock();
|
|
}
|
|
|
|
pub fn sample_clock(&mut self) {
|
|
self.channel1.sample_clock();
|
|
self.channel2.sample_clock();
|
|
self.channel3.sample_clock();
|
|
}
|
|
|
|
pub fn sample(&self) -> (u8, u8) {
|
|
// Some basic mixing
|
|
// Output value
|
|
let mut s = (0, 0);
|
|
let s01_volume = self.sound_channel_volume_control & 7;
|
|
let s02_volume = (self.sound_channel_volume_control >> 4) & 7;
|
|
|
|
/*
|
|
if self.sound_channel_volume_control & 0b1000 != 0 {
|
|
|
|
VIN!
|
|
}
|
|
|
|
if self.sound_channel_volume_control & 0b1000_0000 != 0 {
|
|
VIN!
|
|
}
|
|
*/
|
|
|
|
let c1_sample = self.channel1.sample();
|
|
let c2_sample = self.channel2.sample();
|
|
let c3_sample = self.channel3.sample();
|
|
let c4_sample = 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
s.0 *= s01_volume + 1;
|
|
s.1 *= s02_volume + 1;
|
|
s
|
|
}
|
|
|
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
|
// Bit 7 of NR52 = 0 -> everything reset
|
|
// If all sound off no mode register can be set
|
|
// println!("Snd: {:04X} = {:02X} ({:08b})", addr, val, val);
|
|
match addr {
|
|
0xFF10..=0xFF14 => self.channel1.write_byte(addr, val),
|
|
0xFF16..=0xFF19 => self.channel2.write_byte(addr, val),
|
|
0xFF1A..=0xFF1E => self.channel3.write_byte(addr, val),
|
|
// TODO: Channel 4
|
|
0xFF24 => self.sound_channel_volume_control = val,
|
|
0xFF25 => self.sound_output_terminal_selector = val,
|
|
0xFF26 => self.enabled = val,
|
|
0xFF30..=0xFF3F => self.channel3.write_byte(addr, val),
|
|
|
|
_ => {
|
|
println!("Sound: Write {:02X} to {:04X} unsupported", val, addr);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
// println!("RD {:04X}", addr);
|
|
match addr {
|
|
0xFF10..=0xFF14 => self.channel1.read_byte(addr),
|
|
0xFF16..=0xFF19 => self.channel2.read_byte(addr),
|
|
0xFF1A..=0xFF1E => self.channel3.read_byte(addr),
|
|
|
|
0xFF24 => self.sound_channel_volume_control,
|
|
0xFF25 => self.sound_output_terminal_selector,
|
|
0xFF26 => self.enabled,
|
|
0xFF30..=0xFF3F => self.channel3.read_byte(addr),
|
|
|
|
_ => panic!("Sound: Read from {:04X} unsupported", addr),
|
|
}
|
|
}
|
|
}
|