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; pub use crate::processor::memory::mmio::joypad::JoypadState;
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
use futures::executor;
pub enum EmulatorMessage { pub enum EmulatorMessage {
Stop, Stop,
@ -15,3 +17,27 @@ pub trait Renderer {
fn set_rumble(&mut self, _rumbling: bool) {} 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, processor::memory::Memory,
util::{pause, print_cycles}, util::{pause, print_cycles},
}; };
use connect::{EmulatorMessage, Renderer}; use connect::{AudioOutput, EmulatorMessage, Renderer};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use processor::{memory::Rom, Cpu}; use processor::{memory::Rom, Cpu};
use std::{ use std::{
@ -51,6 +51,7 @@ pub fn init(
receiver: Receiver<EmulatorMessage>, receiver: Receiver<EmulatorMessage>,
options: Options, options: Options,
mut window: Box<dyn Renderer>, mut window: Box<dyn Renderer>,
output: AudioOutput,
tile_window: Option<Box<dyn Renderer>>, tile_window: Option<Box<dyn Renderer>>,
) { ) {
VERBOSE.set(options.verbose).unwrap(); VERBOSE.set(options.verbose).unwrap();
@ -92,7 +93,14 @@ pub fn init(
}; };
let mut cpu = Cpu::new( 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, bootrom_enabled,
); );

View file

@ -3,8 +3,13 @@
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use clap::{ArgGroup, Parser}; use clap::{ArgGroup, Parser};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
Stream,
};
use futures::executor;
use gb_emu::{ use gb_emu::{
connect::{EmulatorMessage, JoypadState, Renderer}, connect::{AudioOutput, EmulatorMessage, JoypadState, Renderer},
util::scale_buffer, util::scale_buffer,
}; };
use gilrs::{ use gilrs::{
@ -90,14 +95,59 @@ fn main() {
ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap(); ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap();
let (output, _stream) = create_audio_output();
gb_emu::init( gb_emu::init(
receiver, receiver,
options, options,
Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))), Box::new(WindowRenderer::new(factor, Some(Gilrs::new().unwrap()))),
output,
tile_window, 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 { struct WindowRenderer {
window: Option<Window>, window: Option<Window>,
scaled_buf: Vec<u32>, scaled_buf: Vec<u32>,

View file

@ -1,7 +1,7 @@
use self::mmio::{Apu, Gpu, Joypad, Serial, Timer}; use self::mmio::{Apu, Gpu, Joypad, Serial, Timer};
pub use self::rom::Rom; pub use self::rom::Rom;
use crate::{ use crate::{
connect::{JoypadState, Renderer}, connect::{AudioOutput, JoypadState, Renderer},
processor::SplitRegister, processor::SplitRegister,
verbose_println, Cpu, verbose_println, Cpu,
}; };
@ -34,6 +34,7 @@ impl Memory {
bootrom: Option<Vec<u8>>, bootrom: Option<Vec<u8>>,
rom: Rom, rom: Rom,
window: Box<dyn Renderer>, window: Box<dyn Renderer>,
output: AudioOutput,
connect_serial: bool, connect_serial: bool,
tile_window: Option<Box<dyn Renderer>>, tile_window: Option<Box<dyn Renderer>>,
) -> Self { ) -> Self {
@ -53,7 +54,7 @@ impl Memory {
dma_addr: 0xFF, dma_addr: 0xFF,
joypad: Joypad::default(), joypad: Joypad::default(),
gpu: Gpu::new(window, tile_window), gpu: Gpu::new(window, tile_window),
apu: Apu::init_default(), apu: Apu::new(output),
serial, serial,
timers: Timer::init(), timers: Timer::init(),
} }

View file

@ -1,14 +1,10 @@
use self::types::{Channels, DacSample, Mixer, VinEnable, Volume}; use self::types::{Channels, DacSample, Mixer, VinEnable, Volume};
use crate::{ use crate::{
connect::AudioOutput,
constants::CLOCK_SPEED, constants::CLOCK_SPEED,
processor::memory::Address, processor::memory::Address,
util::{get_bit, set_or_clear_bit}, 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 futures::executor;
use itertools::izip; use itertools::izip;
use samplerate::{ConverterType, Samplerate}; use samplerate::{ConverterType, Samplerate};
@ -44,39 +40,22 @@ pub struct Apu {
mixer: Mixer, mixer: Mixer,
div_apu: u8, div_apu: u8,
buffer: Vec<DacSample>, buffer: Vec<DacSample>,
device: Device,
config: StreamConfig,
stream: Option<Stream>,
send_rb: Option<AsyncHeapProducer<f32>>,
converter: Samplerate, converter: Samplerate,
output: AudioOutput,
} }
impl Default for Apu { const CYCLES_PER_FRAME: usize = 70224;
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();
impl Apu {
pub fn new(output: AudioOutput) -> Self {
let converter = Samplerate::new( let converter = Samplerate::new(
ConverterType::ZeroOrderHold, ConverterType::ZeroOrderHold,
CLOCK_SPEED as u32, CLOCK_SPEED as u32,
config.sample_rate().0, output.sample_rate,
config.channels().into(), 2,
) )
.unwrap(); .unwrap();
let config = config.config();
Self { Self {
apu_enable: true, apu_enable: true,
channels: Channels::default(), channels: Channels::default(),
@ -84,57 +63,10 @@ impl Default for Apu {
mixer: Mixer::default(), mixer: Mixer::default(),
div_apu: 0, div_apu: 0,
buffer: vec![], buffer: vec![],
device,
config,
stream: None,
send_rb: None,
converter, 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) { pub fn div_apu_tick(&mut self) {
self.div_apu = self.div_apu.wrapping_add(1); self.div_apu = self.div_apu.wrapping_add(1);
@ -179,7 +111,6 @@ impl Apu {
} }
fn next_audio(&mut self) { fn next_audio(&mut self) {
if let Some(ref mut send) = self.send_rb {
let converted = self let converted = self
.converter .converter
.process( .process(
@ -190,8 +121,7 @@ impl Apu {
.collect::<Vec<f32>>(), .collect::<Vec<f32>>(),
) )
.unwrap(); .unwrap();
executor::block_on(send.push_slice(&converted)).unwrap(); executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap();
}
} }
pub fn get_register(&self, addr: Address) -> u8 { pub fn get_register(&self, addr: Address) -> u8 {