audio kinda works
This commit is contained in:
parent
e60426fe0f
commit
7a28655604
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue