// https://github.com/floooh/sokol/blob/master/sokol_audio.h // https://github.com/norse-rs/audir/blob/master/audir/src/wasapi/mod.rs use crate::PlaySoundParams; pub use crate::mixer::Playback; use winapi::shared::guiddef::{CLSID, IID}; use winapi::shared::ksmedia; use winapi::shared::minwindef::*; use winapi::shared::mmreg::*; use winapi::um::audioclient::*; use winapi::um::audiosessiontypes::*; use winapi::um::combaseapi::*; use winapi::um::mmdeviceapi::*; use winapi::um::objbase::*; use winapi::um::synchapi::*; use winapi::um::winbase::*; use std::sync::mpsc; // thanks sokol_audio! // https://github.com/floooh/sokol/blob/master/sokol_audio.h#L559 static IID_IAudioClient: IID = IID { Data1: 0x1cb9ad4c, Data2: 0xdbfa, Data3: 0x4c32, Data4: [0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2], }; static IID_IMMDeviceEnumerator: IID = IID { Data1: 0xa95664d2, Data2: 0x9614, Data3: 0x4f35, Data4: [0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6], }; static CLSID_IMMDeviceEnumerator: CLSID = CLSID { Data1: 0xbcde0395, Data2: 0xe52f, Data3: 0x467c, Data4: [0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e], }; static IID_IAudioRenderClient: IID = IID { Data1: 0xf294acfc, Data2: 0x3146, Data3: 0x4483, Data4: [0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2], }; static IID_Devinterface_Audio_Render: IID = IID { Data1: 0xe6327cad, Data2: 0xdcec, Data3: 0x4949, Data4: [0xae, 0x8a, 0x99, 0x1e, 0x97, 0x6a, 0x79, 0xd2], }; static IID_IActivateAudioInterface_Completion_Handler: IID = IID { Data1: 0x94ea2b94, Data2: 0xe9cc, Data3: 0x49e0, Data4: [0xc0, 0xff, 0xee, 0x64, 0xca, 0x8f, 0x5b, 0x90], }; mod consts { pub const CHANNELS: u32 = 2; pub const SAMPLE_RATE: u32 = 44100; pub const BUFFER_FRAMES: u32 = 4096; } unsafe fn audio_thread(mut mixer: crate::mixer::Mixer) { CoInitializeEx(std::ptr::null_mut(), COINIT_MULTITHREADED); let buffer_end_event = CreateEventA(std::ptr::null_mut(), FALSE, FALSE, std::ptr::null()); assert!(buffer_end_event.is_null() == false); let mut device_enumerator: *mut IMMDeviceEnumerator = std::ptr::null_mut(); let hr = CoCreateInstance( &CLSID_IMMDeviceEnumerator, std::ptr::null_mut(), CLSCTX_ALL, &IID_IMMDeviceEnumerator, &mut device_enumerator as *mut _ as _, ); assert!(hr >= 0, "CoCreatInstance failed"); let mut device: *mut IMMDevice = std::ptr::null_mut(); let hr = (*device_enumerator).GetDefaultAudioEndpoint(eRender, eConsole, &mut device); assert!(hr >= 0, "GetDefaultAudioEndPoint failed"); let mut audio_client: *mut IAudioClient = std::ptr::null_mut(); let hr = (*device).Activate( &IID_IAudioClient, CLSCTX_ALL, std::ptr::null_mut(), &mut audio_client as *mut _ as _, ); assert!(hr >= 0, "Device Activate failed"); let mut state = 0; (*device).GetState(&mut state); assert!( state & DEVICE_STATE_ACTIVE != 0, "Default device not active" ); let format = WAVEFORMATEX { nChannels: consts::CHANNELS as _, nSamplesPerSec: consts::SAMPLE_RATE as _, wFormatTag: WAVE_FORMAT_EXTENSIBLE, wBitsPerSample: 32, nBlockAlign: consts::CHANNELS as u16 * 4, nAvgBytesPerSec: consts::CHANNELS as u32 * consts::SAMPLE_RATE as u32 * 4, cbSize: (std::mem::size_of::() - std::mem::size_of::()) as _, }; const FRONT_LEFT: u32 = 0b0001; const FRONT_RIGHT: u32 = 0b0010; let format_extensible = WAVEFORMATEXTENSIBLE { Format: format, Samples: 4 * 8, dwChannelMask: FRONT_LEFT | FRONT_RIGHT, SubFormat: ksmedia::KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, }; // https://docs.microsoft.com/en-us/windows/win32/coreaudio/audclnt-streamflags-xxx-constants const AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM: u32 = 0x80000000; const AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY: u32 = 0x08000000; let dur = consts::BUFFER_FRAMES as f64 / (consts::SAMPLE_RATE as f64 * 1.0 / 10000000.0); let hr = (*audio_client).Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY, dur as _, 0, &format_extensible as *const _ as _, std::ptr::null(), ); if hr < 0 { println!("Error code: 0x{:x}", hr as u32); } assert!(hr >= 0, "audio_client.Initialize failed"); let mut dst_buffer_frames = 0; let hr = (*audio_client).GetBufferSize(&mut dst_buffer_frames); assert!(hr >= 0, "GetBufferSize failed"); let mut render_client: *mut IAudioRenderClient = std::ptr::null_mut(); let hr = (*audio_client).GetService(&IID_IAudioRenderClient, &mut render_client as *mut _ as _); assert!( hr >= 0, "sokol_audio wasapi: audio client GetService failed" ); let hr = (*audio_client).SetEventHandle(buffer_end_event); assert!(hr >= 0, "SetEventHandle failed"); (*audio_client).Start(); loop { WaitForSingleObject(buffer_end_event, INFINITE); let mut padding = 0; if (*audio_client).GetCurrentPadding(&mut padding) < 0 { continue; } let num_frames = dst_buffer_frames - padding; if num_frames == 0 { continue; } let mut wasapi_buffer: *mut u8 = std::ptr::null_mut(); if (*render_client).GetBuffer(num_frames, &mut wasapi_buffer) < 0 { continue; } assert!(wasapi_buffer.is_null() == false); let buffer = std::slice::from_raw_parts_mut( wasapi_buffer as *mut f32, num_frames as usize * consts::CHANNELS as usize, ); mixer.fill_audio_buffer(buffer, num_frames as _); (*render_client).ReleaseBuffer(num_frames, 0); } } pub struct AudioContext { pub(crate) mixer_ctrl: crate::mixer::MixerControl, } impl AudioContext { pub fn new() -> AudioContext { use crate::mixer::Mixer; let (mixer_builder, mixer_ctrl) = Mixer::new(); std::thread::spawn(move || unsafe { audio_thread(mixer_builder.build()); }); AudioContext { mixer_ctrl } } } pub struct Sound { sound_id: u32, } impl Sound { pub fn load(ctx: &AudioContext, data: &[u8]) -> Sound { let sound_id = ctx.mixer_ctrl.load(data); Sound { sound_id } } pub fn play(&self, ctx: &AudioContext, params: PlaySoundParams) -> Playback { ctx.mixer_ctrl.play(self.sound_id, params) } pub fn stop(&self, ctx: &AudioContext) { ctx.mixer_ctrl.stop_all(self.sound_id); } pub fn set_volume(&self, ctx: &AudioContext, volume: f32) { ctx.mixer_ctrl.set_volume_all(self.sound_id, volume); } pub fn delete(&self, ctx: &AudioContext) { ctx.mixer_ctrl.delete(self.sound_id); } }