sdl2: Use callbacks with ring buffer instead of audio queue.
Better latencies, and also fast-forward (aka Turbo mode) sounds normal this way. Former-commit-id: d1075087e847c765a871157a7973c897575ef4d7
This commit is contained in:
parent
cb54f4d1a3
commit
d8545dd8cd
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -212,6 +212,15 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug_stub_derive"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
|
@ -669,6 +678,11 @@ name = "rgb"
|
|||
version = "0.8.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rustboyadvance-ng"
|
||||
version = "0.1.0"
|
||||
|
@ -682,12 +696,14 @@ dependencies = [
|
|||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hexdump 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nom 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"spin_sleep 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1007,6 +1023,7 @@ dependencies = [
|
|||
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
|
||||
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||
"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f"
|
||||
"checksum debug_stub_derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b"
|
||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
"checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd"
|
||||
|
@ -1060,6 +1077,7 @@ dependencies = [
|
|||
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
|
||||
"checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828"
|
||||
"checksum rgb 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "4f089652ca87f5a82a62935ec6172a534066c7b97be003cc8f702ee9a7a59c92"
|
||||
"checksum ringbuf 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7c2b29d87cfbdce39849012bb5020fff88b8f01f4f5b55846a0b6ef360774eae"
|
||||
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
|
||||
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
"checksum rustyline 5.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67e12e40e0240de07f0dab4f4dd01bdb15d74dc977026d4ba91666c41c679ade"
|
||||
|
|
|
@ -26,6 +26,8 @@ zip = "0.5.3"
|
|||
ctrlc = "3.1.3"
|
||||
spin_sleep="0.3.7"
|
||||
bit-set = "0.5.1"
|
||||
ringbuf = "0.2.1"
|
||||
debug_stub_derive = "0.3.0"
|
||||
|
||||
[[bin]]
|
||||
name = "rba-sdl2"
|
||||
|
|
|
@ -467,12 +467,7 @@ impl Gpu {
|
|||
}
|
||||
|
||||
// Returns the new gpu state
|
||||
pub fn step(
|
||||
&mut self,
|
||||
cycles: usize,
|
||||
sb: &mut SysBus,
|
||||
irqs: &mut IrqBitmask,
|
||||
) {
|
||||
pub fn step(&mut self, cycles: usize, sb: &mut SysBus, irqs: &mut IrqBitmask) {
|
||||
self.cycles += cycles;
|
||||
|
||||
match self.state {
|
||||
|
|
|
@ -1,37 +1,34 @@
|
|||
pub type Sample = (i16, i16);
|
||||
use crate::{AudioInterface, StereoSample};
|
||||
|
||||
const PI: f32 = std::f32::consts::PI;
|
||||
|
||||
pub trait Resampler {
|
||||
fn push_sample(&mut self, s: Sample, output: &mut Vec<i16>);
|
||||
fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CosineResampler {
|
||||
last_in_sample: Sample,
|
||||
last_in_sample: StereoSample,
|
||||
phase: f32,
|
||||
pub in_freq: f32,
|
||||
out_freq: f32,
|
||||
}
|
||||
|
||||
fn cosine_interpolation(y1: Sample, y2: Sample, phase: f32) -> Sample {
|
||||
let y1_left = y1.0 as f32;
|
||||
let y1_right = y1.1 as f32;
|
||||
let y2_left = y2.0 as f32;
|
||||
let y2_right = y2.1 as f32;
|
||||
fn cosine_interpolation(y1: i16, y2: i16, phase: f32) -> i16 {
|
||||
let y1 = y1 as i32 as f32;
|
||||
let y2 = y2 as i32 as f32;
|
||||
|
||||
let mu2 = (1.0 - (PI * phase).cos()) / 2.0;
|
||||
|
||||
(
|
||||
(y2_left * (1.0 - mu2) + y1_left * mu2) as i16,
|
||||
(y2_right * (1.0 - mu2) + y1_right * mu2) as i16,
|
||||
)
|
||||
(y2 * (1.0 - mu2) + y1 * mu2) as i16
|
||||
}
|
||||
|
||||
impl Resampler for CosineResampler {
|
||||
fn push_sample(&mut self, s: Sample, output: &mut Vec<i16>) {
|
||||
fn push_sample(&mut self, s: StereoSample, audio: &mut dyn AudioInterface) {
|
||||
while self.phase < 1.0 {
|
||||
let x = cosine_interpolation(self.last_in_sample, s, self.phase);
|
||||
output.push(x.0);
|
||||
output.push(x.1);
|
||||
let left = cosine_interpolation(self.last_in_sample.0, s.0, self.phase);
|
||||
let right = cosine_interpolation(self.last_in_sample.1, s.1, self.phase);
|
||||
audio.push_sample((left, right));
|
||||
self.phase += self.in_freq / self.out_freq;
|
||||
}
|
||||
self.phase = self.phase - 1.0;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// TODO write tests or replace with a crate
|
||||
|
||||
const SOUND_FIFO_CAPACITY: usize = 32;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -31,10 +31,20 @@ struct DmaSoundChannel {
|
|||
fifo: SoundFifo,
|
||||
}
|
||||
|
||||
impl DmaSoundChannel {
|
||||
fn is_stereo_channel_enabled(&self, channel: usize) -> bool {
|
||||
match channel {
|
||||
0 => self.enable_left,
|
||||
1 => self.enable_right,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DmaSoundChannel {
|
||||
fn default() -> DmaSoundChannel {
|
||||
DmaSoundChannel {
|
||||
volume_shift: 1,
|
||||
volume_shift: 0,
|
||||
value: 0,
|
||||
enable_right: false,
|
||||
enable_left: false,
|
||||
|
@ -50,8 +60,12 @@ const REG_FIFO_A_H: u32 = REG_FIFO_A + 2;
|
|||
const REG_FIFO_B_L: u32 = REG_FIFO_B;
|
||||
const REG_FIFO_B_H: u32 = REG_FIFO_B + 2;
|
||||
|
||||
type AudioDeviceRcRefCell = Rc<RefCell<dyn AudioInterface>>;
|
||||
|
||||
#[derive(DebugStub)]
|
||||
pub struct SoundController {
|
||||
audio_device: Rc<RefCell<dyn AudioInterface>>,
|
||||
#[debug_stub = "AudioDeviceRcRefCell"]
|
||||
audio_device: AudioDeviceRcRefCell,
|
||||
|
||||
sample_rate_to_cpu_freq: usize, // how many "cycles" are a sample?
|
||||
last_sample_cycles: usize, // cycles count when we last provided a new sample.
|
||||
|
@ -90,7 +104,7 @@ pub struct SoundController {
|
|||
}
|
||||
|
||||
impl SoundController {
|
||||
pub fn new(audio_device: Rc<RefCell<dyn AudioInterface>>) -> SoundController {
|
||||
pub fn new(audio_device: AudioDeviceRcRefCell) -> SoundController {
|
||||
let resampler =
|
||||
CosineResampler::new(32768_f32, audio_device.borrow().get_sample_rate() as f32);
|
||||
SoundController {
|
||||
|
@ -250,13 +264,13 @@ impl SoundController {
|
|||
}
|
||||
|
||||
REG_FIFO_A_L | REG_FIFO_A_H => {
|
||||
self.dma_sound[0].fifo.write(((value >> 8) & 0xff) as i8);
|
||||
self.dma_sound[0].fifo.write((value & 0xff) as i8);
|
||||
self.dma_sound[0].fifo.write(((value >> 8) & 0xff) as i8);
|
||||
}
|
||||
|
||||
REG_FIFO_B_L | REG_FIFO_B_H => {
|
||||
self.dma_sound[1].fifo.write(((value >> 8) & 0xff) as i8);
|
||||
self.dma_sound[1].fifo.write((value & 0xff) as i8);
|
||||
self.dma_sound[1].fifo.write(((value >> 8) & 0xff) as i8);
|
||||
}
|
||||
|
||||
REG_SOUNDBIAS => self.sound_bias = value & 0xc3fe,
|
||||
|
@ -283,11 +297,11 @@ impl SoundController {
|
|||
|
||||
const FIFO_INDEX_TO_REG: [u32; 2] = [REG_FIFO_A, REG_FIFO_B];
|
||||
for fifo in 0..2 {
|
||||
let channel = &mut self.dma_sound[fifo];
|
||||
let dma = &mut self.dma_sound[fifo];
|
||||
|
||||
if timer_id == channel.timer_select {
|
||||
channel.value = channel.fifo.read();
|
||||
if channel.fifo.count() <= 16 {
|
||||
if timer_id == dma.timer_select {
|
||||
dma.value = dma.fifo.read();
|
||||
if dma.fifo.count() <= 16 {
|
||||
dmac.notify_sound_fifo(FIFO_INDEX_TO_REG[fifo]);
|
||||
}
|
||||
}
|
||||
|
@ -310,22 +324,19 @@ impl SoundController {
|
|||
|
||||
// time to push a new sample!
|
||||
|
||||
let mut sample = (0i16, 0i16);
|
||||
for i in 0..2 {
|
||||
let channel = &self.dma_sound[i];
|
||||
if channel.enable_left {
|
||||
sample.0 += ((channel.value as i16) << 8) >> channel.volume_shift;
|
||||
let mut sample = [0; 2];
|
||||
|
||||
for channel in 0..=1 {
|
||||
for dma in &mut self.dma_sound {
|
||||
if dma.is_stereo_channel_enabled(channel) {
|
||||
sample[channel] += dma.value as i16;
|
||||
}
|
||||
if channel.enable_right {
|
||||
sample.1 += ((channel.value as i16) << 8) >> channel.volume_shift;
|
||||
}
|
||||
}
|
||||
|
||||
self.resampler.push_sample(sample, &mut self.output_buffer);
|
||||
if self.output_buffer.len() >= 10000 {
|
||||
self.audio_device.borrow_mut().play(&self.output_buffer);
|
||||
self.output_buffer.clear();
|
||||
}
|
||||
let mut audio = self.audio_device.borrow_mut();
|
||||
self.resampler
|
||||
.push_sample((sample[0], sample[1]), &mut *audio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
#![feature(core_intrinsics)]
|
||||
#![feature(exclusive_range_pattern)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate debug_stub_derive;
|
||||
|
||||
#[macro_use]
|
||||
extern crate enum_primitive_derive;
|
||||
extern crate num;
|
||||
|
@ -34,11 +37,13 @@ pub trait VideoInterface {
|
|||
fn render(&mut self, buffer: &[u32]);
|
||||
}
|
||||
|
||||
pub type StereoSample = (i16, i16);
|
||||
|
||||
pub trait AudioInterface {
|
||||
fn get_sample_rate(&self) -> i32;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn play(&mut self, samples: &[i16]) {}
|
||||
fn push_sample(&mut self, samples: StereoSample) {}
|
||||
}
|
||||
|
||||
pub trait InputInterface {
|
||||
|
|
|
@ -1,40 +1,89 @@
|
|||
use sdl2;
|
||||
use sdl2::audio::{AudioQueue, AudioSpecDesired};
|
||||
use sdl2::audio::{AudioCallback, AudioDevice, AudioSpec, AudioSpecDesired};
|
||||
|
||||
use rustboyadvance_ng::AudioInterface;
|
||||
use rustboyadvance_ng::{AudioInterface, StereoSample};
|
||||
|
||||
extern crate ringbuf;
|
||||
use ringbuf::{Consumer, Producer, RingBuffer};
|
||||
|
||||
struct GbaAudioCallback {
|
||||
consumer: Consumer<StereoSample>,
|
||||
spec: AudioSpec,
|
||||
}
|
||||
|
||||
pub struct Sdl2AudioPlayer {
|
||||
pub device: AudioQueue<i16>,
|
||||
_device: AudioDevice<GbaAudioCallback>,
|
||||
producer: Producer<StereoSample>,
|
||||
freq: i32,
|
||||
}
|
||||
|
||||
impl AudioCallback for GbaAudioCallback {
|
||||
type Channel = i16;
|
||||
|
||||
fn callback(&mut self, out_samples: &mut [i16]) {
|
||||
let sample_count = out_samples.len() / 2;
|
||||
|
||||
for i in 0..sample_count {
|
||||
if let Some((left, right)) = self.consumer.pop() {
|
||||
out_samples[2 * i] = left * (1 << 4);
|
||||
out_samples[2 * i + 1] = right * (1 << 4);
|
||||
} else {
|
||||
out_samples[2 * i] = self.spec.silence as i16;
|
||||
out_samples[2 * i + 1] = self.spec.silence as i16;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioInterface for Sdl2AudioPlayer {
|
||||
fn get_sample_rate(&self) -> i32 {
|
||||
self.freq
|
||||
}
|
||||
|
||||
fn play(&mut self, samples: &[i16]) {
|
||||
self.device.queue(&samples);
|
||||
fn push_sample(&mut self, sample: StereoSample) {
|
||||
#![allow(unused_must_use)]
|
||||
self.producer.push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_audio_player(sdl: &sdl2::Sdl) -> Sdl2AudioPlayer {
|
||||
let audio_subsystem = sdl.audio().unwrap();
|
||||
|
||||
let desired_spec = AudioSpecDesired {
|
||||
freq: Some(44_100),
|
||||
channels: Some(2), // stereo
|
||||
samples: None,
|
||||
};
|
||||
|
||||
let audio_subsystem = sdl.audio().unwrap();
|
||||
|
||||
let mut freq = 0;
|
||||
|
||||
let mut producer: Option<Producer<StereoSample>> = None;
|
||||
|
||||
let device = audio_subsystem
|
||||
.open_queue::<i16, _>(None, &desired_spec)
|
||||
.open_playback(None, &desired_spec, |spec| {
|
||||
println!("Found audio device: {:?}", spec);
|
||||
freq = spec.freq;
|
||||
|
||||
// Create a thread-safe SPSC fifo
|
||||
let ringbuf_size = (spec.samples as usize) * 2;
|
||||
let rb = RingBuffer::<StereoSample>::new(ringbuf_size);
|
||||
let (prod, cons) = rb.split();
|
||||
|
||||
// move producer to the outer scope
|
||||
producer = Some(prod);
|
||||
|
||||
GbaAudioCallback {
|
||||
consumer: cons,
|
||||
spec,
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
println!("Found audio device: {:?}", device.spec());
|
||||
|
||||
let freq = device.spec().freq;
|
||||
device.resume();
|
||||
|
||||
Sdl2AudioPlayer { device, freq }
|
||||
Sdl2AudioPlayer {
|
||||
_device: device,
|
||||
freq,
|
||||
producer: producer.unwrap(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@ mod audio;
|
|||
mod input;
|
||||
mod video;
|
||||
|
||||
use audio::{create_audio_player, Sdl2AudioPlayer};
|
||||
use input::{create_input, Sdl2Input};
|
||||
use video::{create_video_interface, Sdl2Video};
|
||||
use audio::create_audio_player;
|
||||
use input::create_input;
|
||||
use video::create_video_interface;
|
||||
|
||||
extern crate rustboyadvance_ng;
|
||||
use rustboyadvance_ng::prelude::*;
|
||||
|
@ -85,7 +85,6 @@ fn main() {
|
|||
keycode: Some(Keycode::Space),
|
||||
..
|
||||
} => {
|
||||
audio.borrow_mut().device.clear(); // clear audio queue
|
||||
frame_limiter = true;
|
||||
}
|
||||
Event::KeyDown {
|
||||
|
|
Reference in a new issue