modularise audio output

This commit is contained in:
Alex Janka 2023-03-03 18:08:28 +11:00
parent 953964ff34
commit 7f62f15eab
5 changed files with 109 additions and 94 deletions

View file

@ -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,
)
}
}

View file

@ -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,
);

View file

@ -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>,

View file

@ -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(),
}

View file

@ -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 {