diff --git a/src/connect/mod.rs b/src/connect/mod.rs index f39c13b..14c4ad9 100644 --- a/src/connect/mod.rs +++ b/src/connect/mod.rs @@ -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, +} + +impl AudioOutput { + pub fn new(sample_rate: u32) -> (Self, AsyncHeapConsumer) { + let rb_len = sample_rate as usize / 60; + + let rb = AsyncHeapRb::::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, + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index fb83bcb..ec64863 100644 --- a/src/lib.rs +++ b/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, options: Options, mut window: Box, + output: AudioOutput, tile_window: Option>, ) { 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, ); diff --git a/src/main.rs b/src/main.rs index c3ba034..de1dec5 100644 --- a/src/main.rs +++ b/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, scaled_buf: Vec, diff --git a/src/processor/memory.rs b/src/processor/memory.rs index bfe012e..72c4d82 100644 --- a/src/processor/memory.rs +++ b/src/processor/memory.rs @@ -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>, rom: Rom, window: Box, + output: AudioOutput, connect_serial: bool, tile_window: Option>, ) -> 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(), } diff --git a/src/processor/memory/mmio/apu.rs b/src/processor/memory/mmio/apu.rs index 61e83e2..70115be 100644 --- a/src/processor/memory/mmio/apu.rs +++ b/src/processor/memory/mmio/apu.rs @@ -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, - device: Device, - config: StreamConfig, - stream: Option, - send_rb: Option>, 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::::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::>(), - ) - .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::>(), + ) + .unwrap(); + executor::block_on(self.output.send_rb.push_slice(&converted)).unwrap(); } pub fn get_register(&self, addr: Address) -> u8 {