This repository has been archived on 2026-08-27. You can view files and clone it, but cannot push or open issues or pull requests.
gopher64-netplay/src/ui/audio.rs
Logan McNaughton c44ab9602c
GBC RTC support (#853)
* GBC RTC support

* more

* more

* more

* more

* more

* more
2026-05-08 14:03:13 +00:00

185 lines
5.8 KiB
Rust

use crate::device;
use crate::ui;
static ADJUST_LOCKED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
pub fn init(device: &mut device::Device) {
ui::sdl_init(sdl3_sys::init::SDL_INIT_AUDIO);
init_game_audio(device);
}
unsafe extern "C" fn audio_callback(
_userdata: *mut std::ffi::c_void,
audio_stream: *mut sdl3_sys::audio::SDL_AudioStream,
additional_amount: i32,
_total_amount: i32,
) {
if additional_amount > 0 {
unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
audio_stream,
vec![0; additional_amount as usize].as_ptr() as *const std::ffi::c_void,
additional_amount,
)
};
adjust_audio_frequency(audio_stream, -0.0005);
}
}
pub fn init_game_audio(device: &mut device::Device) {
let game_audio_spec = sdl3_sys::audio::SDL_AudioSpec {
format: sdl3_sys::audio::SDL_AUDIO_S16LE,
freq: device.ai.freq as i32,
channels: 2,
};
device.ui.audio.audio_stream = unsafe {
sdl3_sys::audio::SDL_OpenAudioDeviceStream(
sdl3_sys::audio::SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK,
&game_audio_spec,
None,
std::ptr::null_mut(),
)
};
if device.ui.audio.audio_stream.is_null() {
panic!("Could not create audio stream");
}
if !unsafe {
sdl3_sys::audio::SDL_SetAudioStreamGain(device.ui.audio.audio_stream, device.ui.audio.gain)
&& sdl3_sys::audio::SDL_SetAudioStreamGetCallback(
device.ui.audio.audio_stream,
Some(audio_callback),
std::ptr::null_mut(),
)
} {
panic!("Could not initialize audio stream");
}
if device.ai.regs[device::ai::AI_STATUS_REG] & device::ai::AI_STATUS_BUSY != 0 {
resume_game_audio(&mut device.ui);
} else {
pause_game_audio(&mut device.ui);
}
}
pub fn close(ui: &mut ui::Ui) {
close_game_audio(ui);
}
pub fn resume_game_audio(ui: &mut ui::Ui) {
unsafe {
sdl3_sys::audio::SDL_ResumeAudioStreamDevice(ui.audio.audio_stream);
}
}
pub fn pause_game_audio(ui: &mut ui::Ui) {
unsafe {
sdl3_sys::audio::SDL_PauseAudioStreamDevice(ui.audio.audio_stream);
}
}
pub fn close_game_audio(ui: &mut ui::Ui) {
unsafe {
if !ui.audio.audio_stream.is_null() {
sdl3_sys::audio::SDL_DestroyAudioStream(ui.audio.audio_stream);
ui.audio.audio_stream = std::ptr::null_mut();
}
}
}
pub fn lower_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) - 0.05;
if ui.audio.gain < 0.0 {
ui.audio.gain = 0.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
}
ui::video::onscreen_message(
&format!("Audio volume: {:.0}%", ui.audio.gain * 100.0),
ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT,
);
}
pub fn raise_audio_volume(ui: &mut ui::Ui) {
unsafe {
ui.audio.gain = sdl3_sys::audio::SDL_GetAudioStreamGain(ui.audio.audio_stream) + 0.05;
if ui.audio.gain > 2.0 {
ui.audio.gain = 2.0;
}
sdl3_sys::audio::SDL_SetAudioStreamGain(ui.audio.audio_stream, ui.audio.gain);
}
ui::video::onscreen_message(
&format!("Audio volume: {:.0}%", ui.audio.gain * 100.0),
ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT,
);
}
unsafe extern "C" fn lock_callback(
_userdata: *mut std::ffi::c_void,
_id: sdl3_sys::timer::SDL_TimerID,
_interval: u32,
) -> u32 {
ADJUST_LOCKED.store(false, std::sync::atomic::Ordering::Relaxed);
0
}
fn adjust_audio_frequency(audio_stream: *mut sdl3_sys::audio::SDL_AudioStream, frequency: f32) {
if ADJUST_LOCKED.load(std::sync::atomic::Ordering::Relaxed) {
return;
}
ADJUST_LOCKED.store(true, std::sync::atomic::Ordering::Relaxed);
unsafe {
sdl3_sys::timer::SDL_AddTimer(1000, Some(lock_callback), std::ptr::null_mut());
let current_ratio = sdl3_sys::everything::SDL_GetAudioStreamFrequencyRatio(audio_stream);
sdl3_sys::everything::SDL_SetAudioStreamFrequencyRatio(
audio_stream,
(current_ratio + frequency).clamp(0.995, 1.005),
);
/*
println!(
"Adjusted audio frequency ratio to {}",
sdl3_sys::everything::SDL_GetAudioStreamFrequencyRatio(audio_stream)
);
*/
}
}
pub fn play_audio(device: &device::Device, dram_addr: usize, length: u64) {
if device.ui.audio.audio_stream.is_null() {
return;
}
let mut primary_buffer: Vec<i16> = vec![0; length as usize / 2];
let mut i = 0;
while i < length as usize / 2 {
// Left channel
primary_buffer[i] = *device.rdram.mem.get(dram_addr + (i * 2) + 2).unwrap_or(&0) as i16
| ((*device.rdram.mem.get(dram_addr + (i * 2) + 3).unwrap_or(&0) as i16) << 8);
// Right channel
primary_buffer[i + 1] = *device.rdram.mem.get(dram_addr + (i * 2)).unwrap_or(&0) as i16
| ((*device.rdram.mem.get(dram_addr + (i * 2) + 1).unwrap_or(&0) as i16) << 8);
i += 2;
}
let audio_queued =
unsafe { sdl3_sys::audio::SDL_GetAudioStreamQueued(device.ui.audio.audio_stream) } as f64;
let samples_per_frame = device.ai.freq as f64 * device.vi.frame_time * 4.0;
let max_latency = samples_per_frame * 8.0;
if audio_queued < max_latency {
unsafe {
sdl3_sys::audio::SDL_PutAudioStreamData(
device.ui.audio.audio_stream,
primary_buffer.as_ptr() as *const std::ffi::c_void,
primary_buffer.len() as i32 * 2,
)
};
} else if device.vi.enable_speed_limiter {
adjust_audio_frequency(device.ui.audio.audio_stream, 0.0005);
}
}