audio kinda works

This commit is contained in:
Alex Janka 2023-02-17 11:01:38 +11:00
parent e60426fe0f
commit 7a28655604
7 changed files with 73 additions and 63 deletions

31
Cargo.lock generated
View file

@ -24,6 +24,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "async-ringbuf"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85fa5f7f80e4d4356056f1ccf628c57872ea6f7faae4ab9dc7a8208376c461c2"
dependencies = [
"futures",
"ringbuf",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -233,6 +243,15 @@ dependencies = [
"windows 0.44.0",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "cty"
version = "0.2.2"
@ -389,11 +408,14 @@ dependencies = [
name = "gb-emu"
version = "0.1.0"
dependencies = [
"async-ringbuf",
"clap",
"cpal",
"futures",
"gilrs",
"minifb",
"rand",
"ringbuf",
"samplerate",
"spin_sleep",
]
@ -1063,6 +1085,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "ringbuf"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93ca10b9c9e53ac855a2d6953bce34cef6edbac32c4b13047a4d59d67299420a"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"

View file

@ -13,3 +13,6 @@ rand = "0.8.5"
gilrs = "0.10.1"
samplerate = "0.2.4"
cpal = "0.15.0"
ringbuf = "0.3.2"
async-ringbuf = "0.1.2"
futures = "0.3.26"

View file

@ -1,4 +1,4 @@
#![feature(exclusive_range_pattern, let_chains, slice_flatten)]
#![feature(exclusive_range_pattern, let_chains, slice_flatten, async_closure)]
mod processor;
mod util;

View file

@ -160,7 +160,6 @@ impl Cpu {
}
self.gpu.mode = DrawMode::VBlank;
self.render_window();
self.next_audio();
self.memory.set(0xFF0F, set_bit(self.memory.get(0xFF0F), 0));
}

View file

@ -10,12 +10,13 @@ use crate::{
},
util::{get_bit, set_or_clear_bit},
};
use async_ringbuf::{AsyncHeapProducer, AsyncHeapRb};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Device, Stream, StreamConfig,
};
use futures::executor;
use samplerate::{ConverterType, Samplerate};
use std::sync::mpsc::{channel, Sender};
mod channels;
mod types;
@ -53,7 +54,8 @@ pub struct Apu {
device: Device,
config: StreamConfig,
stream: Option<Stream>,
send_buffer: Option<Sender<Vec<[f32; 2]>>>,
send_rb: Option<AsyncHeapProducer<f32>>,
converter: Samplerate,
}
impl Default for Apu {
@ -73,6 +75,14 @@ impl Default for Apu {
.with_max_sample_rate()
.config();
let converter = Samplerate::new(
ConverterType::ZeroOrderHold,
CLOCK_SPEED as u32,
config.sample_rate.0,
2,
)
.unwrap();
Self {
mem: [0x0; MEM_SIZE],
apu_enable: true,
@ -84,58 +94,34 @@ impl Default for Apu {
device,
config,
stream: None,
send_buffer: None,
send_rb: None,
converter,
}
}
}
const SCALE: u32 = 4;
const CYCLES_PER_FRAME: usize = 70224;
impl Apu {
pub fn init(&mut self) {
let sample_rate = self.config.sample_rate.0;
let ratio = (CLOCK_SPEED as f32) / ((sample_rate as f32) * 4.);
let rb_len = sample_rate as usize;
let (tx, rx) = channel::<Vec<[f32; 2]>>();
let other = tx.clone();
self.send_buffer = Some(tx);
let rb = AsyncHeapRb::<f32>::new(rb_len);
let (tx, mut rx) = rb.split();
self.send_rb = Some(tx);
let stream = self
.device
.build_output_stream(
&self.config,
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
let buf_length = data.len() / 2;
let gbspeed_buf_length = (ratio * (buf_length as f32)) as usize;
let mut audio = vec![];
while let Ok(mut e) = rx.recv() {
audio.append(&mut e);
if audio.len() >= gbspeed_buf_length {
break;
for v in data {
if let Some(a) = executor::block_on(rx.pop()) {
*v = a;
}
}
other.send(audio.split_off(gbspeed_buf_length)).unwrap();
let target = sample_rate * SCALE;
let converter =
Samplerate::new(ConverterType::Linear, target, sample_rate, 2).unwrap();
let naive_buf_length = buf_length * (SCALE as usize);
let mut downsampled = vec![[0.; 2]; naive_buf_length];
let target_step = audio.len() as f64 / naive_buf_length as f64;
let mut index = 0.;
for val in &mut downsampled {
(*val).clone_from(&audio[index as usize]);
index += target_step;
}
data.copy_from_slice(
&converter
.process(&downsampled.into_iter().flatten().collect::<Vec<_>>())
.unwrap(),
);
},
move |err| {
// react to errors here.
@ -173,17 +159,24 @@ impl Apu {
.map(|(one, two)| DacSample { one, two })
.collect(),
);
if self.buffer.len() >= CYCLES_PER_FRAME {
self.next_audio();
}
}
fn next_audio(&mut self) {
if let Some(send) = &self.send_buffer {
send.send(
self.buffer
.drain(..)
.map(|v| v.mixed(&self.mixer))
.collect(),
)
.unwrap();
if let Some(ref mut send) = self.send_rb {
let converted = self
.converter
.process(
&self
.buffer
.drain(..)
.flat_map(|v| v.mixed(&self.mixer))
.collect::<Vec<f32>>(),
)
.unwrap();
executor::block_on(send.push_slice(&converted)).unwrap();
}
}
@ -306,8 +299,4 @@ impl Cpu {
pub fn advance_apu_clock(&mut self, steps: usize) {
self.memory.apu.tick(steps);
}
pub fn next_audio(&mut self) {
self.memory.apu.next_audio();
}
}

View file

@ -72,7 +72,6 @@ impl Cpu {
if self.halted {
self.increment_timers(1);
self.sleep(interrupt_cycles + 1);
return;
}
@ -96,7 +95,6 @@ impl Cpu {
let cycles = self.run_opcode(opcode);
self.memory.user_mode = false;
self.increment_timers(cycles);
self.sleep(interrupt_cycles + cycles);
}
fn next_opcode(&mut self) -> u8 {

View file

@ -2,7 +2,6 @@ use crate::{
processor::Cpu,
util::{get_bit, set_bit},
};
use std::time::Duration;
pub(super) struct Timers {
div_counter: usize,
@ -67,15 +66,6 @@ impl Cpu {
}
}
pub(super) fn sleep(&mut self, machine_cycles: u8) {
let secs = ((machine_cycles as f64) * 4.) / CLOCK_SPEED as f64;
if let Some(remaining) =
Duration::from_secs_f64(secs).checked_sub(self.cycle_start.elapsed())
{
spin_sleep::sleep(remaining);
}
}
fn timer_scale(&self) -> (bool, usize) {
let timer_control = self.memory.get(0xFF07);
let timer_enable = get_bit(timer_control, 2);