use crate::{
context::{self, DistanceModel, SoundContext},
listener::Listener,
renderer::render_source_2d_only,
source::SoundSource,
};
use fyrox_core::{
log::Log,
reflect::prelude::*,
uuid::{uuid, Uuid},
visitor::{Visit, VisitResult, Visitor},
TypeUuidProvider,
};
use fyrox_resource::untyped::ResourceKind;
use fyrox_resource::{
io::ResourceIo,
loader::{BoxedLoaderFuture, LoaderPayload, ResourceLoader},
state::LoadError,
Resource, ResourceData,
};
use hrtf::HrirSphere;
use std::{error::Error, ops::Deref};
use std::{fmt::Debug, fmt::Formatter, path::PathBuf, sync::Arc};
use std::{fmt::Display, path::Path};
pub struct HrtfError(pub hrtf::HrtfError);
impl std::error::Error for HrtfError {}
impl From<hrtf::HrtfError> for HrtfError {
fn from(value: hrtf::HrtfError) -> Self {
Self(value)
}
}
impl From<HrtfError> for hrtf::HrtfError {
fn from(value: HrtfError) -> Self {
value.0
}
}
impl Deref for HrtfError {
type Target = hrtf::HrtfError;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Debug for HrtfError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Display for HrtfError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.0 {
hrtf::HrtfError::IoError(error) => Display::fmt(error, f),
hrtf::HrtfError::InvalidFileFormat => f.write_str("Invalid file format"),
hrtf::HrtfError::InvalidLength(n) => write!(f, "Invalid length {n}"),
}
}
}
#[derive(Clone, Debug, Default, Reflect)]
pub struct HrtfRenderer {
hrir_resource: Option<HrirSphereResource>,
#[reflect(hidden)]
processor: Option<hrtf::HrtfProcessor>,
}
impl Visit for HrtfRenderer {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
Log::verify(self.hrir_resource.visit("HrirResource", &mut region));
Ok(())
}
}
impl HrtfRenderer {
pub fn new(hrir_sphere_resource: HrirSphereResource) -> Self {
Self {
processor: Some(hrtf::HrtfProcessor::new(
{
let sphere = hrir_sphere_resource.data_ref().hrir_sphere.clone().unwrap();
sphere
},
SoundContext::HRTF_INTERPOLATION_STEPS,
SoundContext::HRTF_BLOCK_LEN,
)),
hrir_resource: Some(hrir_sphere_resource),
}
}
pub fn set_hrir_sphere_resource(&mut self, resource: Option<HrirSphereResource>) {
self.hrir_resource = resource;
self.processor = None;
}
pub fn hrir_sphere_resource(&self) -> Option<HrirSphereResource> {
self.hrir_resource.clone()
}
pub(crate) fn render_source(
&mut self,
source: &mut SoundSource,
listener: &Listener,
distance_model: DistanceModel,
out_buf: &mut [(f32, f32)],
) {
if self.processor.is_none() {
if let Some(resource) = self.hrir_resource.as_ref() {
let mut header = resource.state();
if let Some(hrir) = header.data() {
self.processor = Some(hrtf::HrtfProcessor::new(
hrir.hrir_sphere.clone().unwrap(),
SoundContext::HRTF_INTERPOLATION_STEPS,
SoundContext::HRTF_BLOCK_LEN,
));
}
}
}
render_source_2d_only(source, out_buf);
let new_distance_gain = source.gain()
* source.spatial_blend()
* source.calculate_distance_gain(listener, distance_model);
let new_sampling_vector = source.calculate_sampling_vector(listener);
if let Some(processor) = self.processor.as_mut() {
processor.process_samples(hrtf::HrtfContext {
source: &source.frame_samples,
output: out_buf,
new_sample_vector: hrtf::Vec3::new(
new_sampling_vector.x,
new_sampling_vector.y,
new_sampling_vector.z,
),
prev_sample_vector: hrtf::Vec3::new(
source.prev_sampling_vector.x,
source.prev_sampling_vector.y,
source.prev_sampling_vector.z,
),
prev_left_samples: &mut source.prev_left_samples,
prev_right_samples: &mut source.prev_right_samples,
prev_distance_gain: source.prev_distance_gain.unwrap_or(new_distance_gain),
new_distance_gain,
});
}
source.prev_sampling_vector = new_sampling_vector;
source.prev_distance_gain = Some(new_distance_gain);
}
}
#[derive(Reflect, Default, Clone, Visit)]
pub struct HrirSphereResourceData {
#[reflect(hidden)]
#[visit(skip)]
hrir_sphere: Option<HrirSphere>,
}
impl Debug for HrirSphereResourceData {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HrirSphereResourceData").finish()
}
}
impl TypeUuidProvider for HrirSphereResourceData {
fn type_uuid() -> Uuid {
uuid!("c92a0fa3-0ed3-49a9-be44-8f06271c6be2")
}
}
impl ResourceData for HrirSphereResourceData {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, _path: &Path) -> Result<(), Box<dyn Error>> {
Err("Saving is not supported!".to_string().into())
}
fn can_be_saved(&self) -> bool {
false
}
fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
Some(Box::new(self.clone()))
}
}
pub struct HrirSphereLoader;
impl ResourceLoader for HrirSphereLoader {
fn extensions(&self) -> &[&str] {
&["hrir"]
}
fn data_type_uuid(&self) -> Uuid {
<HrirSphereResourceData as TypeUuidProvider>::type_uuid()
}
fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> BoxedLoaderFuture {
Box::pin(async move {
let reader = io.file_reader(&path).await.map_err(LoadError::new)?;
let hrir_sphere = HrirSphere::new(reader, context::SAMPLE_RATE)
.map_err(HrtfError::from)
.map_err(LoadError::new)?;
Ok(LoaderPayload::new(HrirSphereResourceData {
hrir_sphere: Some(hrir_sphere),
}))
})
}
}
pub type HrirSphereResource = Resource<HrirSphereResourceData>;
pub trait HrirSphereResourceExt {
fn from_hrir_sphere(hrir_sphere: HrirSphere, kind: ResourceKind) -> Self;
}
impl HrirSphereResourceExt for HrirSphereResource {
fn from_hrir_sphere(hrir_sphere: HrirSphere, kind: ResourceKind) -> Self {
Resource::new_ok(
Uuid::new_v4(),
kind,
HrirSphereResourceData {
hrir_sphere: Some(hrir_sphere),
},
)
}
}