#![allow(missing_docs)]
extern crate alloc;
use alloc::sync::Arc;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Media::Audio::Apo::{
IAudioMediaType, IAudioProcessingObject, IAudioProcessingObjectConfiguration,
IAudioProcessingObjectConfiguration_Impl, IAudioProcessingObjectRT,
IAudioProcessingObjectRT_Impl, IAudioProcessingObject_Impl, IAudioSystemEffects,
IAudioSystemEffects2, IAudioSystemEffects2_Impl, IAudioSystemEffects3,
IAudioSystemEffects3_Impl, IAudioSystemEffects_Impl, APO_CONNECTION_DESCRIPTOR,
APO_CONNECTION_PROPERTY, APO_REG_PROPERTIES, AUDIO_SYSTEMEFFECT, AUDIO_SYSTEMEFFECT_STATE,
};
use windows_core::{implement, Ref, GUID};
use crate::instance::AnyApoInstance;
#[implement(
IAudioProcessingObject,
IAudioProcessingObjectConfiguration,
IAudioProcessingObjectRT,
IAudioSystemEffects,
IAudioSystemEffects2,
IAudioSystemEffects3
)]
pub struct ApoInstanceCom {
instance: Arc<dyn AnyApoInstance>,
}
impl ApoInstanceCom {
#[must_use]
pub fn new(instance: Arc<dyn AnyApoInstance>) -> Self {
crate::raw::exports::outstanding_inc();
Self { instance }
}
#[must_use]
pub fn instance(&self) -> &Arc<dyn AnyApoInstance> {
&self.instance
}
}
impl Drop for ApoInstanceCom {
fn drop(&mut self) {
crate::raw::exports::outstanding_dec();
}
}
impl IAudioProcessingObject_Impl for ApoInstanceCom_Impl {
fn Reset(&self) -> windows_core::Result<()> {
Ok(())
}
fn GetLatency(&self) -> windows_core::Result<i64> {
Ok(0)
}
fn GetRegistrationProperties(&self) -> windows_core::Result<*mut APO_REG_PROPERTIES> {
crate::raw::dispatch::get_registration_properties_siso(self.instance.as_ref())
}
fn Initialize(&self, _cbdatasize: u32, _pbydata: *const u8) -> windows_core::Result<()> {
crate::raw::dispatch::initialize(self.instance.as_ref())
}
fn IsInputFormatSupported(
&self,
_poppositeformat: Ref<IAudioMediaType>,
prequestedinputformat: Ref<IAudioMediaType>,
) -> windows_core::Result<IAudioMediaType> {
crate::raw::dispatch::negotiate_format(
self.instance.as_ref(),
prequestedinputformat,
crate::raw::media_type::NegotiationDirection::Input,
)
}
fn IsOutputFormatSupported(
&self,
_poppositeformat: Ref<IAudioMediaType>,
prequestedoutputformat: Ref<IAudioMediaType>,
) -> windows_core::Result<IAudioMediaType> {
crate::raw::dispatch::negotiate_format(
self.instance.as_ref(),
prequestedoutputformat,
crate::raw::media_type::NegotiationDirection::Output,
)
}
fn GetInputChannelCount(&self) -> windows_core::Result<u32> {
crate::raw::dispatch::get_input_channel_count(self.instance.as_ref())
}
}
impl IAudioProcessingObjectConfiguration_Impl for ApoInstanceCom_Impl {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn LockForProcess(
&self,
u32numinputconnections: u32,
ppinputconnections: *const *const APO_CONNECTION_DESCRIPTOR,
u32numoutputconnections: u32,
ppoutputconnections: *const *const APO_CONNECTION_DESCRIPTOR,
) -> windows_core::Result<()> {
unsafe {
crate::raw::dispatch::lock_for_process(
self.instance.as_ref(),
u32numinputconnections,
ppinputconnections,
u32numoutputconnections,
ppoutputconnections,
)
}
}
fn UnlockForProcess(&self) -> windows_core::Result<()> {
crate::raw::dispatch::unlock_for_process(self.instance.as_ref())
}
}
impl IAudioProcessingObjectRT_Impl for ApoInstanceCom_Impl {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn APOProcess(
&self,
u32numinputconnections: u32,
ppinputconnections: *const *const APO_CONNECTION_PROPERTY,
u32numoutputconnections: u32,
ppoutputconnections: *mut *mut APO_CONNECTION_PROPERTY,
) {
unsafe {
crate::raw::dispatch::apo_process(
self.instance.as_ref(),
u32numinputconnections,
ppinputconnections,
u32numoutputconnections,
ppoutputconnections,
);
}
}
fn CalcInputFrames(&self, u32outputframecount: u32) -> u32 {
u32outputframecount
}
fn CalcOutputFrames(&self, u32inputframecount: u32) -> u32 {
u32inputframecount
}
}
impl IAudioSystemEffects_Impl for ApoInstanceCom_Impl {}
impl IAudioSystemEffects2_Impl for ApoInstanceCom_Impl {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn GetEffectsList(
&self,
ppeffectsids: *mut *mut GUID,
pceffects: *mut u32,
event: HANDLE,
) -> windows_core::Result<()> {
crate::raw::dispatch::get_effects_list(
self.instance.as_ref(),
ppeffectsids,
pceffects,
event,
)
}
}
impl IAudioSystemEffects3_Impl for ApoInstanceCom_Impl {
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn GetControllableSystemEffectsList(
&self,
effects: *mut *mut AUDIO_SYSTEMEFFECT,
numeffects: *mut u32,
event: HANDLE,
) -> windows_core::Result<()> {
crate::raw::dispatch::get_controllable_system_effects_list(
self.instance.as_ref(),
effects,
numeffects,
event,
)
}
fn SetAudioSystemEffectState(
&self,
effectid: &GUID,
state: AUDIO_SYSTEMEFFECT_STATE,
) -> windows_core::Result<()> {
crate::raw::dispatch::set_audio_system_effect_state(self.instance.as_ref(), effectid, state)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::apo::{ApoCategory, ProcessInput, ProcessingObject};
use crate::buffer::BufferFlags;
use crate::clsid::Clsid;
use crate::error::HResult;
use crate::instance::ApoInstance;
use crate::realtime::{RealtimeContext, State};
use windows_core::HRESULT;
struct Dummy;
impl ProcessingObject for Dummy {
const CLSID: Clsid = Clsid::from_u128(0xFEDCBA98_7654_3210_FEDC_BA9876543210);
const NAME: &'static str = "dummy";
const COPYRIGHT: &'static str = "test";
const CATEGORY: ApoCategory = ApoCategory::Sfx;
fn new() -> Self {
Self
}
fn process(
&mut self,
_rt: &RealtimeContext,
input: ProcessInput<'_>,
output: &mut [f32],
) -> BufferFlags {
output.copy_from_slice(input.samples());
input.flags()
}
}
fn new_com() -> ApoInstanceCom {
ApoInstanceCom::new(Arc::new(ApoInstance::<Dummy>::new()))
}
#[test]
fn new_holds_a_zero_refcount_instance() {
let com = new_com();
assert_eq!(com.instance().refcount(), 0);
assert_eq!(com.instance().state(), State::Uninitialized);
}
#[test]
fn instance_accessor_round_trips() {
let com = new_com();
let arc1 = com.instance();
let arc2 = com.instance();
assert!(Arc::ptr_eq(arc1, arc2));
}
#[test]
fn is_input_format_supported_routes_float32_as_accept() {
use crate::format::Format;
use crate::raw::media_type::media_type_from_format;
use windows::Win32::Media::Audio::Apo::IAudioProcessingObject;
use windows_core::ComObject;
let apo: IAudioProcessingObject = ComObject::new(new_com()).into_interface();
let requested = media_type_from_format(&Format::pcm_float32(48_000, 1));
let answered = unsafe { apo.IsInputFormatSupported(None, &requested) }.unwrap();
let wf = unsafe { &*answered.GetAudioFormat() };
assert_eq!(
Format::from_waveformatex(wf),
Format::pcm_float32(48_000, 1)
);
}
#[test]
fn is_input_format_supported_suggests_float32_for_int16() {
use crate::format::{Format, WAVE_FORMAT_IEEE_FLOAT};
use crate::raw::media_type::media_type_from_format;
use windows::Win32::Media::Audio::Apo::IAudioProcessingObject;
use windows_core::ComObject;
let apo: IAudioProcessingObject = ComObject::new(new_com()).into_interface();
let requested = media_type_from_format(&Format::pcm_int16(48_000, 1));
let answered = unsafe { apo.IsInputFormatSupported(None, &requested) }.unwrap();
let wf = unsafe { &*answered.GetAudioFormat() };
let suggested = Format::from_waveformatex(wf);
assert_eq!(suggested.format_tag(), WAVE_FORMAT_IEEE_FLOAT);
assert_eq!(suggested.bits_per_sample(), 32);
assert_eq!(suggested.sample_rate(), 48_000);
assert_eq!(suggested.channels(), 1);
}
#[test]
fn get_input_channel_count_errors_before_lock() {
use windows::Win32::Media::Audio::Apo::IAudioProcessingObject;
use windows_core::ComObject;
let apo: IAudioProcessingObject = ComObject::new(new_com()).into_interface();
let err = unsafe { apo.GetInputChannelCount() }.unwrap_err();
assert_eq!(err.code(), HRESULT::from(HResult::APOERR_NOT_LOCKED));
}
#[test]
fn get_input_channel_count_reports_locked_channel_count() {
use crate::format::Format;
let com = new_com();
com.instance().initialize().unwrap();
com.instance()
.lock_for_process(
&Format::pcm_float32(48_000, 6),
&Format::pcm_float32(48_000, 1),
)
.unwrap();
use windows::Win32::Media::Audio::Apo::IAudioProcessingObject;
use windows_core::ComObject;
let apo: IAudioProcessingObject = ComObject::new(com).into_interface();
let count = unsafe { apo.GetInputChannelCount() }.unwrap();
assert_eq!(count, 6);
}
#[test]
fn get_registration_properties_routes_through_builder() {
use windows::Win32::Media::Audio::Apo::IAudioProcessingObject;
use windows::Win32::System::Com::CoTaskMemFree;
use windows_core::ComObject;
let apo: IAudioProcessingObject = ComObject::new(new_com()).into_interface();
let props = unsafe { apo.GetRegistrationProperties() }.unwrap();
assert!(!props.is_null());
unsafe {
assert_eq!(
crate::clsid::Clsid::from((*props).clsid),
<Dummy as ProcessingObject>::CLSID
);
CoTaskMemFree(Some(props.cast()));
}
}
#[test]
fn get_effects_list_reports_no_effects_for_default_dummy() {
use windows::Win32::Media::Audio::Apo::IAudioSystemEffects2;
use windows_core::{ComObject, GUID};
let api: IAudioSystemEffects2 = ComObject::new(new_com()).into_interface();
let mut list: *mut GUID = 0xDEAD_BEEF as *mut GUID;
let mut count: u32 = 0xDEAD_BEEF;
unsafe {
api.GetEffectsList(&mut list, &mut count, Default::default())
.expect("GetEffectsList failed");
}
assert!(list.is_null(), "empty list expected; got {list:p}");
assert_eq!(count, 0);
drop(api);
}
#[test]
fn get_effects_list_reports_advertised_effects() {
use crate::apo::SystemEffect;
use crate::instance::ApoInstance;
use windows::Win32::Media::Audio::Apo::IAudioSystemEffects2;
use windows::Win32::System::Com::CoTaskMemFree;
use windows_core::{ComObject, GUID};
const FX_A: Clsid = Clsid::from_u128(0xAAAAAAAA_BBBB_CCCC_DDDD_EEEEEEEEEEEE);
const FX_B: Clsid = Clsid::from_u128(0x11111111_2222_3333_4444_555555555555);
struct TwoEffects;
impl ProcessingObject for TwoEffects {
const CLSID: Clsid = Clsid::from_u128(0x12121212_3434_5656_7878_9A9A9A9A9A9A);
const NAME: &'static str = "two-effects";
const COPYRIGHT: &'static str = "test";
const CATEGORY: ApoCategory = ApoCategory::Sfx;
fn new() -> Self {
Self
}
fn system_effects(&self) -> &[SystemEffect] {
const E: [SystemEffect; 2] = [SystemEffect::new(FX_A), SystemEffect::new(FX_B)];
&E
}
fn process(
&mut self,
_rt: &RealtimeContext,
input: ProcessInput<'_>,
output: &mut [f32],
) -> BufferFlags {
output.copy_from_slice(input.samples());
input.flags()
}
}
let com = ApoInstanceCom::new(Arc::new(ApoInstance::<TwoEffects>::new()));
let api: IAudioSystemEffects2 = ComObject::new(com).into_interface();
let mut list: *mut GUID = core::ptr::null_mut();
let mut count: u32 = 0;
unsafe {
api.GetEffectsList(&mut list, &mut count, Default::default())
.expect("GetEffectsList failed");
}
assert_eq!(count, 2);
assert!(!list.is_null());
unsafe {
let a = *list;
let b = *list.add(1);
assert_eq!(Clsid::from(a), FX_A);
assert_eq!(Clsid::from(b), FX_B);
CoTaskMemFree(Some(list.cast()));
}
}
#[test]
fn get_controllable_system_effects_list_reports_user_state() {
use crate::apo::{SystemEffect, SystemEffectState};
use crate::instance::ApoInstance;
use windows::Win32::Media::Audio::Apo::{
IAudioSystemEffects3, AUDIO_SYSTEMEFFECT, AUDIO_SYSTEMEFFECT_STATE_OFF,
AUDIO_SYSTEMEFFECT_STATE_ON,
};
use windows::Win32::System::Com::CoTaskMemFree;
use windows_core::ComObject;
const FX_A: Clsid = Clsid::from_u128(0xBBBBBBBB_CCCC_DDDD_EEEE_FFFFFFFFFFFF);
const FX_B: Clsid = Clsid::from_u128(0x22222222_3333_4444_5555_666666666666);
struct ControllableEffects;
impl ProcessingObject for ControllableEffects {
const CLSID: Clsid = Clsid::from_u128(0x34343434_5656_7878_9A9A_BCBCBCBCBCBC);
const NAME: &'static str = "controllable";
const COPYRIGHT: &'static str = "test";
const CATEGORY: ApoCategory = ApoCategory::Sfx;
fn new() -> Self {
Self
}
fn system_effects(&self) -> &[SystemEffect] {
const E: [SystemEffect; 2] = [
SystemEffect::new(FX_A)
.with_controllable(true)
.with_state(SystemEffectState::On),
SystemEffect::new(FX_B)
.with_controllable(false)
.with_state(SystemEffectState::Off),
];
&E
}
fn process(
&mut self,
_rt: &RealtimeContext,
input: ProcessInput<'_>,
output: &mut [f32],
) -> BufferFlags {
output.copy_from_slice(input.samples());
input.flags()
}
}
let api: IAudioSystemEffects3 =
ComObject::new(ApoInstanceCom::new(Arc::new(ApoInstance::<
ControllableEffects,
>::new())))
.into_interface();
let mut list: *mut AUDIO_SYSTEMEFFECT = core::ptr::null_mut();
let mut count: u32 = 0;
unsafe {
api.GetControllableSystemEffectsList(&mut list, &mut count, None)
.expect("GetControllableSystemEffectsList failed");
}
assert_eq!(count, 2);
assert!(!list.is_null());
unsafe {
let a = *list;
let b = *list.add(1);
assert_eq!(Clsid::from(a.id), FX_A);
assert!(a.canSetState.as_bool());
assert_eq!(a.state, AUDIO_SYSTEMEFFECT_STATE_ON);
assert_eq!(Clsid::from(b.id), FX_B);
assert!(!b.canSetState.as_bool());
assert_eq!(b.state, AUDIO_SYSTEMEFFECT_STATE_OFF);
CoTaskMemFree(Some(list.cast()));
}
}
#[test]
fn set_audio_system_effect_state_dispatches_and_rejects_unknown_ids() {
use crate::apo::{SystemEffect, SystemEffectState};
use crate::instance::ApoInstance;
use core::cell::Cell;
use windows::Win32::Media::Audio::Apo::{
IAudioSystemEffects3, AUDIO_SYSTEMEFFECT_STATE_OFF, AUDIO_SYSTEMEFFECT_STATE_ON,
};
use windows_core::{ComObject, GUID};
const FX: Clsid = Clsid::from_u128(0xABCDABCD_1234_5678_9ABC_DEF012345678);
struct Toggleable {
last: Cell<Option<(Clsid, SystemEffectState)>>,
}
impl ProcessingObject for Toggleable {
const CLSID: Clsid = Clsid::from_u128(0x55555555_6666_7777_8888_999999999999);
const NAME: &'static str = "toggleable";
const COPYRIGHT: &'static str = "test";
const CATEGORY: ApoCategory = ApoCategory::Sfx;
fn new() -> Self {
Self {
last: Cell::new(None),
}
}
fn system_effects(&self) -> &[SystemEffect] {
const E: [SystemEffect; 1] = [SystemEffect::new(FX).with_controllable(true)];
&E
}
fn set_system_effect_state(&mut self, id: &Clsid, state: SystemEffectState) {
self.last.set(Some((*id, state)));
}
fn process(
&mut self,
_rt: &RealtimeContext,
input: ProcessInput<'_>,
output: &mut [f32],
) -> BufferFlags {
output.copy_from_slice(input.samples());
input.flags()
}
}
let inst = Arc::new(ApoInstance::<Toggleable>::new());
let api: IAudioSystemEffects3 =
ComObject::new(ApoInstanceCom::new(inst.clone())).into_interface();
let id: GUID = FX.into();
unsafe { api.SetAudioSystemEffectState(id, AUDIO_SYSTEMEFFECT_STATE_OFF) }
.expect("SetAudioSystemEffectState(known, Off) failed");
let unknown: GUID = Clsid::from_u128(0xDEAD_DEAD_DEAD_DEAD_DEAD_DEAD_DEAD_DEAD).into();
let err = unsafe { api.SetAudioSystemEffectState(unknown, AUDIO_SYSTEMEFFECT_STATE_ON) }
.expect_err("SetAudioSystemEffectState(unknown) should fail");
assert_eq!(err.code(), HRESULT::from(HResult::E_INVALIDARG));
}
}