modularise audio output
This commit is contained in:
parent
953964ff34
commit
7f62f15eab
|
@ -1,4 +1,6 @@
|
|||
pub use crate::processor::memory::mmio::joypad::JoypadState;
|
||||
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
|
||||
use futures::executor;
|
||||
|
||||
pub enum EmulatorMessage {
|
||||
Stop,
|
||||
|
@ -15,3 +17,27 @@ pub trait Renderer {
|
|||
|
||||
fn set_rumble(&mut self, _rumbling: bool) {}
|
||||
}
|
||||
|
||||
pub struct AudioOutput {
|
||||
pub sample_rate: u32,
|
||||
pub send_rb: AsyncHeapProducer<f32>,
|
||||
}
|
||||
|
||||
impl AudioOutput {
|
||||
pub fn new(sample_rate: u32) -> (Self, AsyncHeapConsumer<f32>) {
|
||||
let rb_len = sample_rate as usize / 60;
|
||||
|
||||
let rb = AsyncHeapRb::<f32>::new(rb_len);
|
||||
let (mut send_rb, rx) = rb.split();
|
||||
|
||||
executor::block_on(send_rb.push_iter(vec![0.; rb_len - 1].into_iter())).unwrap();
|
||||
|
||||
(
|
||||
Self {
|
||||
sample_rate,
|
||||
send_rb,
|
||||
},
|
||||
rx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -10,7 +10,7 @@ use crate::{
|
|||
processor::memory::Memory,
|
||||
util::{pause, print_cycles},
|
||||
};
|
||||
use connect::{EmulatorMessage, Renderer};
|
||||
use connect::{AudioOutput, EmulatorMessage, Renderer};
|
||||
use once_cell::sync::OnceCell;
|
||||
use processor::{memory::Rom, Cpu};
|
||||
use std::{
|
||||
|
@ -51,6 +51,7 @@ pub fn init(
|
|||
receiver: Receiver<EmulatorMessage>,
|
||||
options: Options,
|
||||
mut window: Box<dyn Renderer>,
|
||||
output: AudioOutput,
|
||||
tile_window: Option<Box<dyn Renderer>>,
|
||||
) {
|
||||
VERBOSE.set(options.verbose).unwrap();
|
||||
|
@ -92,7 +93,14 @@ pub fn init(
|
|||
};
|
||||
|
||||
let mut cpu = Cpu::new(
|
||||
Memory::init(bootrom, rom, window, options.connect_serial, tile_window),
|
||||
Memory::init(
|
||||
bootrom,
|
||||
rom,
|
||||
window,
|
||||
output,
|
||||
options.connect_serial,
|
||||
tile_window,
|
||||
),
|
||||
bootrom_enabled,
|
||||
);
|
||||
|
||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -3,8 +3,13 @@
|
|||
use std::sync::mpsc::channel;
|
||||
|
||||
use clap::{ArgGroup, Parser};
|
||||
use cpal::{
|
||||
traits::{DeviceTrait, HostTrait, StreamTrait},
|
||||
Stream,
|
||||
};
|
||||
use futures::executor;
|
||||
use gb_emu::{
|
||||
connect::{EmulatorMessage, JoypadState, Renderer},
|
||||
connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer},
|
||||
util::scale_buffer,
|
||||
};
|
||||
use gilrs::{
|
||||
|
@ -90,14 +95,59 @@ fn main() {
|
|||
|
||||
ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap();
|
||||
|
||||
let (output, _stream) = create_audio_output();
|
||||
|
||||
gb_emu::init(
|
||||
receiver,
|
||||
options,
|
||||
Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))),
|
||||
output,
|
||||
tile_window,
|
||||
);
|
||||
}
|
||||
|
||||
fn create_audio_output() -> (AudioOutput, Stream) {
|
||||
let host = cpal::default_host();
|
||||
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.expect("no output device available");
|
||||
|
||||
let mut supported_configs_range = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs");
|
||||
let config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_max_sample_rate();
|
||||
|
||||
let sample_rate = config.sample_rate().0;
|
||||
|
||||
let (output, mut rx) = AudioOutput::new(sample_rate);
|
||||
|
||||
let stream = device
|
||||
.build_output_stream(
|
||||
&config.config(),
|
||||
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
|
||||
for v in data {
|
||||
match executor::block_on(rx.pop()) {
|
||||
Some(a) => *v = a,
|
||||
None => panic!("Audio queue disconnected!"),
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| {
|
||||
// react to errors here.
|
||||
println!("audio error: {err}");
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
stream.play().unwrap();
|
||||
|
||||
(output, stream)
|
||||
}
|
||||
|
||||
struct WindowRenderer {
|
||||
window: Option<Window>,
|
||||
scaled_buf: Vec<u32>,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer};
|
||||
pub use self::rom::Rom;
|
||||
use crate::{
|
||||
connect::{JoypadState, Renderer},
|
||||
connect::{AudioOutput, JoypadState, Renderer},
|
||||
processor::SplitRegister,
|
||||
verbose_println, Cpu,
|
||||
};
|
||||
|
@ -34,6 +34,7 @@ impl Memory {
|
|||
bootrom: Option<Vec<u8>>,
|
||||
rom: Rom,
|
||||
window: Box<dyn Renderer>,
|
||||
output: AudioOutput,
|
||||
connect_serial: bool,
|
||||
tile_window: Option<Box<dyn Renderer>>,
|
||||
) -> Self {
|
||||
|
@ -53,7 +54,7 @@ impl Memory {
|
|||
dma_addr: 0xFF,
|
||||
joypad: Joypad::default(),
|
||||
gpu: Gpu::new(window, tile_window),
|
||||
apu: Apu::init_default(),
|
||||
apu: Apu::new(output),
|
||||
serial,
|
||||
timers: Timer::init(),
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use self::types::{Channels, DacSample, Mixer, VinEnable, Volume};
|
||||
use crate::{
|
||||
connect::AudioOutput,
|
||||
constants::CLOCK_SPEED,
|
||||
processor::memory::Address,
|
||||
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 itertools::izip;
|
||||
use samplerate::{ConverterType, Samplerate};
|
||||
|
@ -44,39 +40,22 @@ pub struct Apu {
|
|||
mixer: Mixer,
|
||||
div_apu: u8,
|
||||
buffer: Vec<DacSample>,
|
||||
device: Device,
|
||||
config: StreamConfig,
|
||||
stream: Option<Stream>,
|
||||
send_rb: Option<AsyncHeapProducer<f32>>,
|
||||
converter: Samplerate,
|
||||
output: AudioOutput,
|
||||
}
|
||||
|
||||
impl Default for Apu {
|
||||
fn default() -> Self {
|
||||
let host = cpal::default_host();
|
||||
|
||||
let device = host
|
||||
.default_output_device()
|
||||
.expect("no output device available");
|
||||
|
||||
let mut supported_configs_range = device
|
||||
.supported_output_configs()
|
||||
.expect("error while querying configs");
|
||||
let config = supported_configs_range
|
||||
.next()
|
||||
.expect("no supported config?!")
|
||||
.with_max_sample_rate();
|
||||
const CYCLES_PER_FRAME: usize = 70224;
|
||||
|
||||
impl Apu {
|
||||
pub fn new(output: AudioOutput) -> Self {
|
||||
let converter = Samplerate::new(
|
||||
ConverterType::ZeroOrderHold,
|
||||
CLOCK_SPEED as u32,
|
||||
config.sample_rate().0,
|
||||
config.channels().into(),
|
||||
output.sample_rate,
|
||||
2,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let config = config.config();
|
||||
|
||||
Self {
|
||||
apu_enable: true,
|
||||
channels: Channels::default(),
|
||||
|
@ -84,57 +63,10 @@ impl Default for Apu {
|
|||
mixer: Mixer::default(),
|
||||
div_apu: 0,
|
||||
buffer: vec![],
|
||||
device,
|
||||
config,
|
||||
stream: None,
|
||||
send_rb: None,
|
||||
converter,
|
||||
output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const CYCLES_PER_FRAME: usize = 70224;
|
||||
|
||||
impl Apu {
|
||||
pub fn init_default() -> Self {
|
||||
let mut r = Self::default();
|
||||
r.init();
|
||||
r
|
||||
}
|
||||
|
||||
pub fn init(&mut self) {
|
||||
let sample_rate = self.config.sample_rate.0;
|
||||
let rb_len = sample_rate as usize / 60;
|
||||
|
||||
let rb = AsyncHeapRb::<f32>::new(rb_len);
|
||||
let (mut tx, mut rx) = rb.split();
|
||||
|
||||
executor::block_on(tx.push_iter(vec![0.; rb_len - 1].into_iter())).unwrap();
|
||||
|
||||
self.send_rb = Some(tx);
|
||||
|
||||
let stream = self
|
||||
.device
|
||||
.build_output_stream(
|
||||
&self.config,
|
||||
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
|
||||
for v in data {
|
||||
match executor::block_on(rx.pop()) {
|
||||
Some(a) => *v = a,
|
||||
None => panic!("Audio queue disconnected!"),
|
||||
}
|
||||
}
|
||||
},
|
||||
move |err| {
|
||||
// react to errors here.
|
||||
println!("audio error: {err}");
|
||||
},
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
stream.play().unwrap();
|
||||
self.stream = Some(stream);
|
||||
}
|
||||
|
||||
pub fn div_apu_tick(&mut self) {
|
||||
self.div_apu = self.div_apu.wrapping_add(1);
|
||||
|
@ -179,19 +111,17 @@ impl Apu {
|
|||
}
|
||||
|
||||
fn next_audio(&mut self) {
|
||||
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();
|
||||
}
|
||||
let converted = self
|
||||
.converter
|
||||
.process(
|
||||
&self
|
||||
.buffer
|
||||
.drain(..)
|
||||
.flat_map(|v| v.mixed(&self.mixer))
|
||||
.collect::<Vec<f32>>(),
|
||||
)
|
||||
.unwrap();
|
||||
executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap();
|
||||
}
|
||||
|
||||
pub fn get_register(&self, addr: Address) -> u8 {
|
||||
|
|
Loading…
Reference in a new issue