Channel 1 and 2 almost work correctly; Channel 3 is still annoying
This commit is contained in:
parent
9ddd8ac21c
commit
fc15b398ba
@ -5,4 +5,5 @@ authors = ["Kevin Hamacher <kevin.hamacher@rub.de>"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sdl2 = "0.19.0"
|
sdl2 = "0.19.0"
|
||||||
libc = "*"
|
libc = "0.2.14"
|
||||||
|
pulse-simple = "1.0.0"
|
||||||
|
|||||||
@ -43,7 +43,7 @@ pub struct Interconnect {
|
|||||||
ram: Box<[u8]>,
|
ram: Box<[u8]>,
|
||||||
hiram: Box<[u8]>,
|
hiram: Box<[u8]>,
|
||||||
wram_bank: u8,
|
wram_bank: u8,
|
||||||
sound: sound::Sound,
|
sound: sound::SoundManager,
|
||||||
display: display::Display,
|
display: display::Display,
|
||||||
interrupt: u8,
|
interrupt: u8,
|
||||||
interrupt_request_flags: u8,
|
interrupt_request_flags: u8,
|
||||||
@ -74,7 +74,7 @@ impl Interconnect {
|
|||||||
ram: vec![0; WRAM_SIZE].into_boxed_slice(),
|
ram: vec![0; WRAM_SIZE].into_boxed_slice(),
|
||||||
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
|
hiram: vec![0; HIRAM_SIZE].into_boxed_slice(),
|
||||||
wram_bank: 1,
|
wram_bank: 1,
|
||||||
sound: sound::Sound::new(),
|
sound: sound::SoundManager::new(),
|
||||||
display: display::Display::new(),
|
display: display::Display::new(),
|
||||||
// Refactor those
|
// Refactor those
|
||||||
interrupt_request_flags: 0,
|
interrupt_request_flags: 0,
|
||||||
@ -133,9 +133,6 @@ impl Interconnect {
|
|||||||
pub fn tick(&mut self, cycles: u8) {
|
pub fn tick(&mut self, cycles: u8) {
|
||||||
self.display.tick(cycles as u16);
|
self.display.tick(cycles as u16);
|
||||||
self.timer.tick(cycles as u16);
|
self.timer.tick(cycles as u16);
|
||||||
for _ in 0..cycles {
|
|
||||||
self.sound.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.display.vblank_interrupt() {
|
if self.display.vblank_interrupt() {
|
||||||
self.interrupt_request_flags |= INTERRUPT_DISPLAY_VBLANK;
|
self.interrupt_request_flags |= INTERRUPT_DISPLAY_VBLANK;
|
||||||
@ -238,9 +235,9 @@ impl Interconnect {
|
|||||||
self.interrupt_request_flags
|
self.interrupt_request_flags
|
||||||
},
|
},
|
||||||
0xFF10 ... 0xFF26 => {
|
0xFF10 ... 0xFF26 => {
|
||||||
self.sound.read_byte(addr)
|
self.sound.sound_object.lock().unwrap().read_byte(addr)
|
||||||
},
|
},
|
||||||
0xFF30 ... 0xFF3F => self.sound.read_byte(addr),
|
0xFF30 ... 0xFF3F => self.sound.sound_object.lock().unwrap().read_byte(addr),
|
||||||
0xFF40 ... 0xFF4B => {
|
0xFF40 ... 0xFF4B => {
|
||||||
self.display.read_byte(addr)
|
self.display.read_byte(addr)
|
||||||
},
|
},
|
||||||
@ -308,9 +305,9 @@ impl Interconnect {
|
|||||||
self.interrupt_request_flags = val;
|
self.interrupt_request_flags = val;
|
||||||
}
|
}
|
||||||
0xFF10 ... 0xFF26 => {
|
0xFF10 ... 0xFF26 => {
|
||||||
self.sound.write_byte(addr, val);
|
self.sound.sound_object.lock().unwrap().write_byte(addr, val);
|
||||||
},
|
},
|
||||||
0xFF30 ... 0xFF3F => self.sound.write_byte(addr, val),
|
0xFF30 ... 0xFF3F => self.sound.sound_object.lock().unwrap().write_byte(addr, val),
|
||||||
// Exclude DMA transfer, we will do this below
|
// Exclude DMA transfer, we will do this below
|
||||||
0xFF40 ... 0xFF45 | 0xFF47 ... 0xFF4B => {
|
0xFF40 ... 0xFF45 | 0xFF47 ... 0xFF4B => {
|
||||||
self.display.write_byte(addr, val);
|
self.display.write_byte(addr, val);
|
||||||
|
|||||||
617
src/sound.rs
617
src/sound.rs
@ -1,617 +0,0 @@
|
|||||||
const TICK_RATE: usize = 4194304;
|
|
||||||
const OUTPUT_SAMPLE_RATE: usize = 32768;
|
|
||||||
const TICKS_PER_SAMPLE: usize = TICK_RATE / OUTPUT_SAMPLE_RATE;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum EnvelopeDirection {
|
|
||||||
Increase,
|
|
||||||
Decrease
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
|
||||||
struct VolumeEnvelope(u8);
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl VolumeEnvelope {
|
|
||||||
fn initial_volume(&self) -> u8 {
|
|
||||||
self.0 >> 4
|
|
||||||
}
|
|
||||||
|
|
||||||
fn envelope_direction(&self) -> EnvelopeDirection {
|
|
||||||
match self.0 & (1 << 3) {
|
|
||||||
0 => EnvelopeDirection::Decrease,
|
|
||||||
_ => EnvelopeDirection::Increase,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn nr_of_envelope_sweep(&self) -> u8 {
|
|
||||||
self.0 & 0x7
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_nr_of_envelope_sweep(&mut self, nr: u8) {
|
|
||||||
self.0 &= !0x7;
|
|
||||||
self.0 |= nr & 0x7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for VolumeEnvelope {
|
|
||||||
fn from(val: u8) -> VolumeEnvelope {
|
|
||||||
VolumeEnvelope(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<u8> for VolumeEnvelope {
|
|
||||||
fn into(self) -> u8 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VolumeEnvelope {
|
|
||||||
fn default() -> VolumeEnvelope {
|
|
||||||
VolumeEnvelope(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait OutputChannel {
|
|
||||||
fn sample(&mut self) -> i8;
|
|
||||||
fn clock_length_counter(&mut self); // 256 Hz
|
|
||||||
fn clock_volume_envelope(&mut self) -> Option<EnvelopeDirection>; // 64 Hz
|
|
||||||
|
|
||||||
// A square channel's frequency timer period is set to (2048-frequency)*4.
|
|
||||||
// Four duty cycles are available, each waveform taking 8 frequency timer clocks to cycle through:
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ChannelMode {
|
|
||||||
CounterMode,
|
|
||||||
ConsecutiveMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ChannelMode {
|
|
||||||
fn default() -> Self {
|
|
||||||
ChannelMode::CounterMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tone & Sweep
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Channel1 {
|
|
||||||
// MMaped registers
|
|
||||||
sweep_register: u8,
|
|
||||||
sound_length: u8,
|
|
||||||
volume_envelope: VolumeEnvelope,
|
|
||||||
active_volume_envelope: VolumeEnvelope,
|
|
||||||
frequency_low: u8,
|
|
||||||
frequency_high: u8,
|
|
||||||
|
|
||||||
// Registeres used for sound generation
|
|
||||||
current_phase: f32,
|
|
||||||
// Depending on sound_length
|
|
||||||
//sound_enabled: bool,
|
|
||||||
// Current envelope volume
|
|
||||||
envelope_volume: u8,
|
|
||||||
current_mode: ChannelMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputChannel for Channel1 {
|
|
||||||
/*
|
|
||||||
According to some specification page:
|
|
||||||
gb = 2048 - (131072 / Hz)
|
|
||||||
Hz = 131072 / (2048 - gb)
|
|
||||||
*/
|
|
||||||
fn sample(&mut self) -> i8 {
|
|
||||||
// Right now just assume that we have 100% on time
|
|
||||||
// I also don't know right now how to disable channel1 :(
|
|
||||||
let freq = self.current_frequency();
|
|
||||||
// sample rate 128? Dunno!
|
|
||||||
self.current_phase += 1f32 / (2048f32 * TICKS_PER_SAMPLE as f32);
|
|
||||||
let sample = 2.0 * 3.1415 * freq * self.current_phase;
|
|
||||||
let volume = (127f32 * self.envelope_volume as f32 / 16f32) as i8;
|
|
||||||
|
|
||||||
if sample.sin() > 0f32 {
|
|
||||||
volume
|
|
||||||
} else {
|
|
||||||
-1 * volume
|
|
||||||
}
|
|
||||||
|
|
||||||
//(sample.sin() * volume as f32) as i8
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clock_length_counter(&mut self) {
|
|
||||||
if self.frequency_high & (1 << 6) > 0 {
|
|
||||||
// Use sound length
|
|
||||||
let sound_length = self.sound_length & 0x3F;
|
|
||||||
if sound_length > 0 {
|
|
||||||
self.sound_length &= 0xC0;
|
|
||||||
self.sound_length |= sound_length - 1;
|
|
||||||
// self.sound_enabled = true;
|
|
||||||
} else {
|
|
||||||
// self.sound_enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn clock_volume_envelope(&mut self) -> Option<EnvelopeDirection> {
|
|
||||||
if self.active_volume_envelope.nr_of_envelope_sweep() > 0 {
|
|
||||||
let left = self.active_volume_envelope.nr_of_envelope_sweep();
|
|
||||||
self.active_volume_envelope.set_nr_of_envelope_sweep(left - 1);
|
|
||||||
match self.active_volume_envelope.envelope_direction() {
|
|
||||||
EnvelopeDirection::Decrease => {
|
|
||||||
if self.envelope_volume > 0 {
|
|
||||||
self.envelope_volume -= 1;
|
|
||||||
println!("CH1: Decreased envelope volume to {} (steps left: {})", self.envelope_volume, left-1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
EnvelopeDirection::Increase => {
|
|
||||||
if self.envelope_volume < 0xF {
|
|
||||||
self.envelope_volume += 1;
|
|
||||||
println!("CH1: Increased envelope volume to {}", self.envelope_volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(self.active_volume_envelope.envelope_direction())
|
|
||||||
} else {
|
|
||||||
match self.current_mode {
|
|
||||||
ChannelMode::CounterMode => {
|
|
||||||
self.envelope_volume = 0;
|
|
||||||
}
|
|
||||||
ChannelMode::ConsecutiveMode => {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Channel1 {
|
|
||||||
fn current_frequency(&self) -> f32 {
|
|
||||||
let full_freq = ((self.frequency_high as u16 & 0x7) << 8) | self.frequency_low as u16;
|
|
||||||
131072f32 / (2048f32 - full_freq as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, val: u8) {
|
|
||||||
match addr {
|
|
||||||
0xFF10 => {
|
|
||||||
println!("WARNING: Sound0 sweep not supported yet");
|
|
||||||
self.sweep_register = val
|
|
||||||
},
|
|
||||||
0xFF11 => {
|
|
||||||
// println!("Channel1: Sound length set to {} (wave pattern: {})", val & 0x3F, val >> 6);
|
|
||||||
self.sound_length = val;
|
|
||||||
},
|
|
||||||
0xFF12 => {
|
|
||||||
self.volume_envelope = val.into();
|
|
||||||
},
|
|
||||||
0xFF13 => self.frequency_low = val,
|
|
||||||
0xFF14 => {
|
|
||||||
self.frequency_high = val;
|
|
||||||
if val & (1 << 7) > 0 {
|
|
||||||
println!("CH1: started");
|
|
||||||
self.active_volume_envelope = self.volume_envelope.clone();
|
|
||||||
self.envelope_volume = self.active_volume_envelope.initial_volume();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_mode = match val & (1 << 6) {
|
|
||||||
0 => ChannelMode::CounterMode,
|
|
||||||
_ => ChannelMode::ConsecutiveMode,
|
|
||||||
};
|
|
||||||
|
|
||||||
// self.dump();
|
|
||||||
self.frequency_high &= 0x7;
|
|
||||||
},
|
|
||||||
_ => panic!("Invalid addr for channel1 write: {:04X}", addr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
match addr {
|
|
||||||
0xFF10 => self.sweep_register,
|
|
||||||
0xFF11 => self.sound_length,
|
|
||||||
0xFF12 => self.volume_envelope.0,
|
|
||||||
// 0xFF13 => self.frequency_low, // R/O
|
|
||||||
0xFF14 => self.frequency_high & (1 << 6), // Only one bit readable
|
|
||||||
_ => panic!("Tried to read invalid register {:04X}", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tone
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Channel2 {
|
|
||||||
sound_length: u8,
|
|
||||||
volume_envelope: VolumeEnvelope,
|
|
||||||
active_volume_envelope: VolumeEnvelope,
|
|
||||||
frequency_low: u8,
|
|
||||||
frequency_high: u8,
|
|
||||||
|
|
||||||
// Registeres used for sound generation
|
|
||||||
current_phase: f32,
|
|
||||||
// Depending on sound_length
|
|
||||||
// sound_enabled: bool,
|
|
||||||
// Current envelope volume
|
|
||||||
envelope_volume: u8,
|
|
||||||
current_mode: ChannelMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputChannel for Channel2 {
|
|
||||||
/*
|
|
||||||
According to some specification page:
|
|
||||||
gb = 2048 - (131072 / Hz)
|
|
||||||
Hz = 131072 / (2048 - gb)
|
|
||||||
*/
|
|
||||||
fn sample(&mut self) -> i8 {
|
|
||||||
// Right now just assume that we have 100% on time
|
|
||||||
// I also don't know right now how to disable channel1 :(
|
|
||||||
let freq = self.current_frequency();
|
|
||||||
// sample rate 128? Dunno!
|
|
||||||
self.current_phase += 1f32 / (2048f32 * TICKS_PER_SAMPLE as f32);
|
|
||||||
let sample = 2.0 * 3.1415 * freq * self.current_phase;
|
|
||||||
// let volume = (127f32 * self.envelope_volume as f32 / 16f32) as i8;
|
|
||||||
|
|
||||||
if sample.sin() > 0f32 {
|
|
||||||
40
|
|
||||||
} else {
|
|
||||||
-40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn clock_length_counter(&mut self) {
|
|
||||||
if self.frequency_high & (1 << 6) > 0 {
|
|
||||||
// Use sound length
|
|
||||||
let sound_length = self.sound_length & 0x3F;
|
|
||||||
if sound_length > 0 {
|
|
||||||
self.sound_length &= 0xC0;
|
|
||||||
self.sound_length |= sound_length - 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn clock_volume_envelope(&mut self) -> Option<EnvelopeDirection> {
|
|
||||||
if self.active_volume_envelope.nr_of_envelope_sweep() > 0 {
|
|
||||||
let left = self.active_volume_envelope.nr_of_envelope_sweep();
|
|
||||||
self.active_volume_envelope.set_nr_of_envelope_sweep(left - 1);
|
|
||||||
match self.active_volume_envelope.envelope_direction() {
|
|
||||||
EnvelopeDirection::Decrease => {
|
|
||||||
if self.envelope_volume > 0 {
|
|
||||||
self.envelope_volume -= 1;
|
|
||||||
println!("CH2: Decreased envelope volume to {} (steps left: {})", self.envelope_volume, left-1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
EnvelopeDirection::Increase => {
|
|
||||||
if self.envelope_volume < 0xF {
|
|
||||||
self.envelope_volume += 1;
|
|
||||||
println!("CH2: Increased envelope volume to {}", self.envelope_volume);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(self.active_volume_envelope.envelope_direction())
|
|
||||||
} else {
|
|
||||||
match self.current_mode {
|
|
||||||
ChannelMode::CounterMode => {
|
|
||||||
self.envelope_volume = 0;
|
|
||||||
}
|
|
||||||
ChannelMode::ConsecutiveMode => {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Channel2 {
|
|
||||||
fn current_frequency(&self) -> f32 {
|
|
||||||
let full_freq = ((self.frequency_high as u16 & 0x7) << 8) | self.frequency_low as u16;
|
|
||||||
131072f32 / (2048f32 - full_freq as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, val: u8) {
|
|
||||||
match addr {
|
|
||||||
0xFF16 => self.sound_length = val,
|
|
||||||
0xFF17 => {
|
|
||||||
self.volume_envelope = val.into();
|
|
||||||
},
|
|
||||||
0xFF18 => self.frequency_low = val,
|
|
||||||
0xFF19 => {
|
|
||||||
self.frequency_high = val;
|
|
||||||
if self.frequency_high & 0x80 == 0x80 {
|
|
||||||
println!("CH2: Started");
|
|
||||||
self.active_volume_envelope = self.volume_envelope.clone();
|
|
||||||
self.envelope_volume = self.active_volume_envelope.initial_volume();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_mode = match val & (1 << 6) {
|
|
||||||
0 => ChannelMode::CounterMode,
|
|
||||||
_ => ChannelMode::ConsecutiveMode,
|
|
||||||
};
|
|
||||||
self.frequency_high &= 0x7;
|
|
||||||
},
|
|
||||||
_ => panic!("Invalid addr for channel2 write: {:04X}", addr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
match addr {
|
|
||||||
0xFF16 => self.sound_length,
|
|
||||||
0xFF17 => self.volume_envelope.0,
|
|
||||||
// 0xFF18 => self.frequency_low, // R/O
|
|
||||||
0xFF19 => self.frequency_high & (1 << 6), // Only one bit readable
|
|
||||||
_ => panic!("Tried to read invalid register {:04X}", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wave output
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Channel3 {
|
|
||||||
enable: u8,
|
|
||||||
sound_length: u8,
|
|
||||||
output_level: u8,
|
|
||||||
frequency_low: u8,
|
|
||||||
frequency_high: u8,
|
|
||||||
|
|
||||||
wave_pattern_data: [u8; 16],
|
|
||||||
|
|
||||||
// Registeres used for sound generation
|
|
||||||
ticks_since_enable: usize,
|
|
||||||
|
|
||||||
current_pattern_offset: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputChannel for Channel3 {
|
|
||||||
fn sample(&mut self) -> i8 {
|
|
||||||
if self.enable > 0 {
|
|
||||||
self.ticks_since_enable += 1;
|
|
||||||
let val = self.wave_pattern_data[self.current_pattern_offset/2];
|
|
||||||
let samp = if val & 1 == 0 {
|
|
||||||
val >> 4
|
|
||||||
} else {
|
|
||||||
val & 0x0F
|
|
||||||
};
|
|
||||||
|
|
||||||
let freq = self.current_frequency();
|
|
||||||
if (TICK_RATE as f32 / self.ticks_since_enable as f32) < (freq as f32) {
|
|
||||||
self.current_pattern_offset += 1;
|
|
||||||
self.current_pattern_offset %= 32;
|
|
||||||
self.ticks_since_enable = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let samp = match self.output_level >> 5 {
|
|
||||||
0 => 0,
|
|
||||||
1 => samp,
|
|
||||||
2 => samp >> 1,
|
|
||||||
_ => samp >> 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
let samp = samp << 4;
|
|
||||||
|
|
||||||
samp as i8
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clock_length_counter(&mut self) {
|
|
||||||
if self.frequency_high & (1 << 6) > 0 {
|
|
||||||
// Use sound length
|
|
||||||
let sound_length = self.sound_length & 0x3F;
|
|
||||||
if sound_length > 0 {
|
|
||||||
self.sound_length &= 0xC0;
|
|
||||||
self.sound_length |= sound_length - 1;
|
|
||||||
// self.sound_enabled = true;
|
|
||||||
} else {
|
|
||||||
// self.sound_enabled = false;
|
|
||||||
self.enable = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn clock_volume_envelope(&mut self) -> Option<EnvelopeDirection> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Channel3 {
|
|
||||||
fn current_frequency(&self) -> f32 {
|
|
||||||
let full_freq = ((self.frequency_high as u16 & 0x7) << 8) | self.frequency_low as u16;
|
|
||||||
(2048f32 - full_freq as f32) * 2f32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, val: u8) {
|
|
||||||
match addr {
|
|
||||||
0xFF1A => {self.enable = val; self.ticks_since_enable = 0},
|
|
||||||
0xFF1B => self.sound_length = val,
|
|
||||||
0xFF1C => self.output_level = val,
|
|
||||||
0xFF1D => self.frequency_low = val,
|
|
||||||
0xFF1E => {
|
|
||||||
self.frequency_high = val;
|
|
||||||
// TODO: Check for start
|
|
||||||
println!("Starting sound3?");
|
|
||||||
self.frequency_high &= 0x7;
|
|
||||||
},
|
|
||||||
0xFF30...0xFF3F => {
|
|
||||||
self.wave_pattern_data[addr as usize - 0xFF30] = val;
|
|
||||||
}
|
|
||||||
_ => panic!("Invalid addr for channel3 write: {:04X}", addr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
match addr {
|
|
||||||
0xFF1A => self.enable,
|
|
||||||
0xFF1B => self.sound_length,
|
|
||||||
0xFF1C => self.output_level,
|
|
||||||
//0xFF1D => self.frequency_low,
|
|
||||||
0xFF1E => self.frequency_high & 1 << 6,
|
|
||||||
0xFF30...0xFF3F => {
|
|
||||||
self.wave_pattern_data[addr as usize - 0xFF30]
|
|
||||||
}
|
|
||||||
_ => panic!("Tried to read invalid register {:04X}", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Noise output
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct Channel4 {
|
|
||||||
sound_length: u8,
|
|
||||||
volume_envelope: VolumeEnvelope,
|
|
||||||
polynominal_counter: u8,
|
|
||||||
counter_mode: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Channel4 {
|
|
||||||
fn write_byte(&mut self, addr: u16, val: u8) {
|
|
||||||
match addr {
|
|
||||||
0xFF20 => self.sound_length = val,
|
|
||||||
0xFF21 => self.volume_envelope = val.into(),
|
|
||||||
0xFF22 => self.polynominal_counter = val,
|
|
||||||
0xFF23 => self.counter_mode = val,
|
|
||||||
_ => panic!("Invalid addr for channel4 write: {:04X}", addr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
match addr {
|
|
||||||
0xFF20 => self.sound_length,
|
|
||||||
0xFF21 => self.volume_envelope.0,
|
|
||||||
0xFF22 => self.polynominal_counter,
|
|
||||||
0xFF23 => self.counter_mode,
|
|
||||||
_ => panic!("Tried to read invalid register {:04X}", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct Sound {
|
|
||||||
enabled: u8,
|
|
||||||
|
|
||||||
channel1: Channel1,
|
|
||||||
channel2: Channel2,
|
|
||||||
channel3: Channel3,
|
|
||||||
channel4: Channel4,
|
|
||||||
|
|
||||||
sound_channel_volume_control: u8,
|
|
||||||
sound_output_terminal_selector: u8,
|
|
||||||
|
|
||||||
ticks: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sound {
|
|
||||||
pub fn new() -> Sound {
|
|
||||||
Sound::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tick(&mut self) {
|
|
||||||
self.ticks += 1;
|
|
||||||
// Every 64 Hz volume envelope => all 65536 ticks
|
|
||||||
// Every 256 Hz length check => all 16384 ticks
|
|
||||||
|
|
||||||
if self.ticks % 65536 == 0 {
|
|
||||||
self.channel1.clock_volume_envelope();
|
|
||||||
self.channel2.clock_volume_envelope();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.ticks % 16384 == 0 {
|
|
||||||
self.channel1.clock_length_counter();
|
|
||||||
self.channel2.clock_length_counter();
|
|
||||||
self.channel3.clock_length_counter();
|
|
||||||
}
|
|
||||||
|
|
||||||
// How high is our rate? 32768?
|
|
||||||
if self.ticks % TICKS_PER_SAMPLE == 0 {
|
|
||||||
// Mix channels.
|
|
||||||
let mut s01: i16 = 0;
|
|
||||||
let mut s02: i16 = 0;
|
|
||||||
|
|
||||||
let s01_volume = self.sound_channel_volume_control & 7;
|
|
||||||
let s02_volume = (self.sound_channel_volume_control >> 4) & 7;
|
|
||||||
|
|
||||||
// Take samples from the channels
|
|
||||||
let c1_sample = self.channel1.sample();
|
|
||||||
let c2_sample = self.channel2.sample();
|
|
||||||
// let c3_sample = self.channel3.sample();
|
|
||||||
// let c4_sample = self.channel4.sample();
|
|
||||||
|
|
||||||
//let c1_sample = 0;
|
|
||||||
//let c2_sample = 0;
|
|
||||||
let c3_sample = 0;
|
|
||||||
|
|
||||||
// Check S01
|
|
||||||
if s01_volume > 0 {
|
|
||||||
if self.sound_output_terminal_selector & 1 > 0 {
|
|
||||||
// Output Channel1
|
|
||||||
s01 += c1_sample as _;
|
|
||||||
}
|
|
||||||
if self.sound_output_terminal_selector & 2 > 0 && false {
|
|
||||||
// Output Channel2
|
|
||||||
s01 += c2_sample as _;
|
|
||||||
}
|
|
||||||
if self.sound_output_terminal_selector & 4 > 0 {
|
|
||||||
// Output Channel3
|
|
||||||
s01 += c3_sample as _;
|
|
||||||
}
|
|
||||||
if self.sound_output_terminal_selector & 8 > 0 {
|
|
||||||
// Output Channel4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s02_volume > 0 {
|
|
||||||
if self.sound_output_terminal_selector & 0x10 > 0 && false {
|
|
||||||
// Output Channel1
|
|
||||||
s02 += c1_sample as _;
|
|
||||||
}
|
|
||||||
if self.sound_output_terminal_selector & 0x20 > 0 {
|
|
||||||
// Output Channel2
|
|
||||||
s02 += c2_sample as _;
|
|
||||||
}
|
|
||||||
if self.sound_output_terminal_selector & 0x40 > 0 {
|
|
||||||
// Output Channel3
|
|
||||||
s02 += c3_sample as _;
|
|
||||||
}
|
|
||||||
if self.sound_output_terminal_selector & 0x80 > 0 {
|
|
||||||
// Output Channel4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("S:{}:{}", s01 >> 0, s02 >> 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
|
||||||
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),
|
|
||||||
0xFF20...0xFF23 => self.channel4.write_byte(addr, val),
|
|
||||||
|
|
||||||
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),
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
panic!("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),
|
|
||||||
0xFF20...0xFF23 => self.channel4.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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
111
src/sound/envelope.rs
Normal file
111
src/sound/envelope.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// Implements the envelopes
|
||||||
|
use sound::{AudioComponent, AudioModule};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum EnvelopeDirection {
|
||||||
|
Increase,
|
||||||
|
Decrease
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EnvelopeDirection {
|
||||||
|
fn default() -> Self {
|
||||||
|
EnvelopeDirection::Decrease
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a volume envelope for the sound channels
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct VolumeEnvelope{
|
||||||
|
/// Accessible via the corresponding memory location, used as template
|
||||||
|
/// when the sound is enabled
|
||||||
|
register_value: u8,
|
||||||
|
|
||||||
|
/// Amount of clocks required for one step
|
||||||
|
clocks_per_step: u8,
|
||||||
|
|
||||||
|
/// Clocks occured since last step
|
||||||
|
clocks: u8,
|
||||||
|
|
||||||
|
/// Current volume
|
||||||
|
current_volume: u8,
|
||||||
|
|
||||||
|
/// Envelope direction
|
||||||
|
direction: EnvelopeDirection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl VolumeEnvelope {
|
||||||
|
/// Returns the intiial volume of an envelope operation
|
||||||
|
pub fn initial_volume(&self) -> u8 {
|
||||||
|
self.register_value >> 4
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the envelope direction
|
||||||
|
pub fn envelope_direction(&self) -> EnvelopeDirection {
|
||||||
|
match self.register_value & (1 << 3) {
|
||||||
|
0 => EnvelopeDirection::Decrease,
|
||||||
|
_ => EnvelopeDirection::Increase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of sweep steps
|
||||||
|
pub fn envelope_sweep_count(&self) -> u8 {
|
||||||
|
self.register_value & 7
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
self.current_volume = self.initial_volume();
|
||||||
|
self.clocks_per_step = self.envelope_sweep_count();
|
||||||
|
self.direction = self.envelope_direction();
|
||||||
|
self.clocks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_register(&mut self, register: u8) {
|
||||||
|
self.register_value = register & !(1 << 6);
|
||||||
|
if register & (1 << 6) != 0 {
|
||||||
|
self.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioModule<u8> for VolumeEnvelope {
|
||||||
|
fn transform(&self, sample: u8) -> u8 {
|
||||||
|
// Use a linear volume gain
|
||||||
|
let volume = 255f32 * self.current_volume as f32 / 15 as f32;
|
||||||
|
if sample > 0 {
|
||||||
|
volume as u8
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioComponent for VolumeEnvelope {
|
||||||
|
fn clock(&mut self) {
|
||||||
|
if self.clocks_per_step > 0 {
|
||||||
|
self.clocks += 1;
|
||||||
|
if self.clocks == self.clocks_per_step {
|
||||||
|
self.clocks = 0;
|
||||||
|
self.current_volume = match self.direction {
|
||||||
|
EnvelopeDirection::Decrease => {
|
||||||
|
if self.current_volume > 0 {
|
||||||
|
self.current_volume - 1
|
||||||
|
} else {
|
||||||
|
self.clocks_per_step = 0;
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EnvelopeDirection::Increase => {
|
||||||
|
if self.current_volume < 15 {
|
||||||
|
self.current_volume + 1
|
||||||
|
} else {
|
||||||
|
self.clocks_per_step = 0;
|
||||||
|
15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/sound/length.rs
Normal file
67
src/sound/length.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Implement the length counter
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use sound::{AudioComponent, AudioModule};
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct LengthCounter<T> where T: Default + Debug {
|
||||||
|
// Do we need an additional enabled flag?
|
||||||
|
counter: T,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> LengthCounter<T> where T: Default + Debug {
|
||||||
|
pub fn set_length(&mut self, value: T) {
|
||||||
|
self.counter = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_enabled(&mut self, val: u8) {
|
||||||
|
self.enabled = val > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl AudioModule<u8> for LengthCounter<u16> {
|
||||||
|
fn transform(&self, sample: u8) -> u8 {
|
||||||
|
if self.enabled {
|
||||||
|
if self.counter > 0 {
|
||||||
|
sample
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sample
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioComponent for LengthCounter<u16> {
|
||||||
|
fn clock(&mut self) {
|
||||||
|
if self.enabled && self.counter > 0 {
|
||||||
|
self.counter -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is the same as above, there is something we should be able to do about
|
||||||
|
// this
|
||||||
|
impl AudioModule<u8> for LengthCounter<u8> {
|
||||||
|
fn transform(&self, sample: u8) -> u8 {
|
||||||
|
if self.enabled {
|
||||||
|
if self.counter > 0 {
|
||||||
|
sample
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sample
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioComponent for LengthCounter<u8> {
|
||||||
|
fn clock(&mut self) {
|
||||||
|
if self.enabled && self.counter > 0 {
|
||||||
|
self.counter -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
381
src/sound/mod.rs
Normal file
381
src/sound/mod.rs
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
extern crate pulse_simple;
|
||||||
|
mod envelope;
|
||||||
|
mod square;
|
||||||
|
mod length;
|
||||||
|
mod wave;
|
||||||
|
|
||||||
|
use self::pulse_simple::Playback;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
//use std::f64::consts::PI;
|
||||||
|
|
||||||
|
//const TICK_RATE: usize = 4194304;
|
||||||
|
const OUTPUT_SAMPLE_RATE: usize = 32768;
|
||||||
|
//const OUTPUT_SAMPLE_RATE: usize = 1024;
|
||||||
|
//const TICKS_PER_SAMPLE: usize = TICK_RATE / OUTPUT_SAMPLE_RATE;
|
||||||
|
|
||||||
|
trait AudioModule<T> {
|
||||||
|
fn transform(&self, sample: T) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channel1 {
|
||||||
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||||
|
match addr {
|
||||||
|
0xFF10 => {
|
||||||
|
println!("Channel1: Sweep not implemented yet :<");
|
||||||
|
}
|
||||||
|
0xFF11 => {
|
||||||
|
self.length_counter.set_length(val & 0x3F);
|
||||||
|
self.wave_gen.set_duty_cycle(val >> 6);
|
||||||
|
}
|
||||||
|
0xFF12 => {
|
||||||
|
self.envelope.set_register(val);
|
||||||
|
}
|
||||||
|
0xFF13 => {
|
||||||
|
// Set lower frequency
|
||||||
|
self.wave_gen.set_lower_freq(val);
|
||||||
|
}
|
||||||
|
0xFF14 => {
|
||||||
|
// Set higher
|
||||||
|
self.wave_gen.set_higher_freq(val);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_ => panic!("Channel1 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();
|
||||||
|
// 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, 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);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
match addr {
|
||||||
|
_ => panic!("Channel2 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();
|
||||||
|
// 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, Default)]
|
||||||
|
struct Channel3 {
|
||||||
|
// Timer -> Wave -> Length Counter -> Volume -> Mixer
|
||||||
|
wave_gen: wave::WaveGenerator,
|
||||||
|
length_counter: length::LengthCounter<u8>,
|
||||||
|
// TODO: Volume
|
||||||
|
|
||||||
|
tick_state: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Channel3 {
|
||||||
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||||
|
println!("C3 WB: {:04X} = {:02X} ({:08b})", addr, val, val);
|
||||||
|
match addr {
|
||||||
|
0xFF1A => {
|
||||||
|
self.wave_gen.enabled = (val & (1 << 7)) > 0;
|
||||||
|
}
|
||||||
|
0xFF1B => {
|
||||||
|
self.length_counter.set_length(val);
|
||||||
|
}
|
||||||
|
0xFF1C => {
|
||||||
|
// Output volume, TODO.
|
||||||
|
}
|
||||||
|
0xFF1D => {
|
||||||
|
// Set lower frequency
|
||||||
|
self.wave_gen.set_lower_freq(val);
|
||||||
|
}
|
||||||
|
0xFF1E => {
|
||||||
|
// Set higher
|
||||||
|
self.wave_gen.set_higher_freq(val);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
_ => 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();
|
||||||
|
// Follow the chain
|
||||||
|
let input = self.length_counter.transform(input);
|
||||||
|
// self.envelope.transform(input)
|
||||||
|
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>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SoundManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let res = SoundManager {
|
||||||
|
sound_object: Arc::new(Mutex::new(Sound::new())),
|
||||||
|
};
|
||||||
|
|
||||||
|
res.launch_thread();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn launch_thread(&self) {
|
||||||
|
let obj = self.sound_object.clone();
|
||||||
|
thread::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;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let mut c_obj = obj.lock().unwrap();
|
||||||
|
|
||||||
|
c_obj.channel1.sample_clock();
|
||||||
|
c_obj.channel2.sample_clock();
|
||||||
|
c_obj.channel3.sample_clock();
|
||||||
|
|
||||||
|
// let s = c_obj.sample();
|
||||||
|
let s1 = c_obj.channel1.sample();
|
||||||
|
let s2 = c_obj.channel2.sample();
|
||||||
|
let s3 = c_obj.channel3.sample();
|
||||||
|
let samps = [[s3 as u8, s3 as u8]];
|
||||||
|
playback.write(&samps[..]);
|
||||||
|
|
||||||
|
// Check for 512 Hz timer
|
||||||
|
if counter >= (OUTPUT_SAMPLE_RATE / 512) {
|
||||||
|
c_obj.clock();
|
||||||
|
counter = 0;
|
||||||
|
} else {
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No sleep needed, it seems like the playback.write is blocking
|
||||||
|
// thread::sleep(time::Duration::new(0, sleep_duration));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(&self) -> u8 {
|
||||||
|
// TODO: Mixing
|
||||||
|
(self.channel1.sample() >> 1) + (self.channel2.sample() >> 1) + (self.channel3.sample() >> 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_byte(&mut self, addr: u16, val: u8) {
|
||||||
|
// 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),
|
||||||
|
|
||||||
|
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),
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
// panic!("Sound: Write {:02X} to {:04X} unsupported", val, addr);
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
src/sound/square.rs
Normal file
103
src/sound/square.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Square wave generator
|
||||||
|
use sound::AudioComponent;
|
||||||
|
use sound::OUTPUT_SAMPLE_RATE;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DutyCycle {
|
||||||
|
Duty0, // 12.5%
|
||||||
|
Duty1, // 25.0%
|
||||||
|
Duty2, // 50.0%
|
||||||
|
Duty3, // 75.0%
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DutyCycle {
|
||||||
|
fn default() -> Self {
|
||||||
|
DutyCycle::Duty2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SquareWaveGenerator {
|
||||||
|
frequency: f32,
|
||||||
|
time: f32,
|
||||||
|
duty_cycle: DutyCycle,
|
||||||
|
|
||||||
|
gb_reg_freq: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SquareWaveGenerator {
|
||||||
|
fn update_frequency(&mut self) {
|
||||||
|
self.frequency = 131072f32 / ((2048 - self.gb_reg_freq) as f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lower_freq(&mut self, freq: u8) {
|
||||||
|
self.gb_reg_freq &= 0xFF00;
|
||||||
|
self.gb_reg_freq |= freq as u16;
|
||||||
|
self.update_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_higher_freq(&mut self, freq: u8) {
|
||||||
|
self.gb_reg_freq &= 0x00FF;
|
||||||
|
self.gb_reg_freq |= ((freq & 7) as u16) << 8;
|
||||||
|
self.update_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.time = 0f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self) -> u8 {
|
||||||
|
if self.frequency > 0f32 {
|
||||||
|
let mut temp = self.time * self.frequency;
|
||||||
|
temp -= temp.trunc();
|
||||||
|
|
||||||
|
match self.duty_cycle {
|
||||||
|
DutyCycle::Duty0 => {
|
||||||
|
if temp < 0.125 {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DutyCycle::Duty1 => {
|
||||||
|
if temp < 0.25 {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DutyCycle::Duty2 => {
|
||||||
|
if temp < 0.5 {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DutyCycle::Duty3 => {
|
||||||
|
if temp < 0.75 {
|
||||||
|
255
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_duty_cycle(&mut self, bits: u8) {
|
||||||
|
self.duty_cycle = match bits {
|
||||||
|
0 => DutyCycle::Duty0,
|
||||||
|
1 => DutyCycle::Duty1,
|
||||||
|
2 => DutyCycle::Duty2,
|
||||||
|
_ => DutyCycle::Duty3,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioComponent for SquareWaveGenerator {
|
||||||
|
fn clock(&mut self) {
|
||||||
|
self.time += 1f32 / (OUTPUT_SAMPLE_RATE as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/sound/wave.rs
Normal file
81
src/sound/wave.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Square wave generator
|
||||||
|
use sound::AudioComponent;
|
||||||
|
use sound::OUTPUT_SAMPLE_RATE;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct SamplePair(u8);
|
||||||
|
|
||||||
|
impl SamplePair {
|
||||||
|
fn first(&self) -> u8 {
|
||||||
|
self.0 >> 4
|
||||||
|
}
|
||||||
|
|
||||||
|
fn second(&self) -> u8 {
|
||||||
|
self.0 & 0x0F
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for SamplePair {
|
||||||
|
fn from(val: u8) -> SamplePair {
|
||||||
|
SamplePair(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct WaveGenerator {
|
||||||
|
frequency: f32,
|
||||||
|
time: f32,
|
||||||
|
gb_reg_freq: u16,
|
||||||
|
|
||||||
|
pub enabled: bool,
|
||||||
|
samples: [SamplePair; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WaveGenerator {
|
||||||
|
fn update_frequency(&mut self) {
|
||||||
|
self.frequency = ((2048 - self.gb_reg_freq) * 2) as _;
|
||||||
|
self.time = 0f32;
|
||||||
|
println!("Freq = {}", self.frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lower_freq(&mut self, freq: u8) {
|
||||||
|
self.gb_reg_freq &= 0xFF00;
|
||||||
|
self.gb_reg_freq |= freq as u16;
|
||||||
|
self.update_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_higher_freq(&mut self, freq: u8) {
|
||||||
|
self.gb_reg_freq &= 0x00FF;
|
||||||
|
self.gb_reg_freq |= ((freq & 7) as u16) << 8;
|
||||||
|
self.update_frequency();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.time = 0f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self) -> u8 {
|
||||||
|
if self.enabled && self.frequency > 0f32 {
|
||||||
|
let temp = self.time * self.frequency;
|
||||||
|
let position = temp.trunc() as usize % 32;
|
||||||
|
|
||||||
|
let sample = &self.samples[position / 2];
|
||||||
|
match position % 2 {
|
||||||
|
0 => sample.first(),
|
||||||
|
_ => sample.second(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sample_pair(&mut self, nr: usize, val: u8) {
|
||||||
|
self.samples[nr] = val.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioComponent for WaveGenerator {
|
||||||
|
fn clock(&mut self) {
|
||||||
|
self.time += 1f32 / (OUTPUT_SAMPLE_RATE as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user