use crate::{
core::{
algebra::Matrix4,
math::{aabb::AxisAlignedBoundingBox, m4x4_approx_eq},
pool::Handle,
reflect::prelude::*,
uuid::{uuid, Uuid},
variable::InheritableVariable,
visitor::prelude::*,
TypeUuidProvider,
},
define_with,
scene::{
base::{Base, BaseBuilder},
graph::Graph,
node::{Node, NodeTrait, SyncContext, UpdateContext},
},
};
pub use fyrox_sound::{
buffer::{
loader::{SoundBufferImportOptions, SoundBufferLoader},
DataSource, SoundBuffer, SoundBufferResource, SoundBufferResourceLoadError,
},
bus::*,
context::{DistanceModel, SAMPLE_RATE},
dsp::{filters::*, DelayLine},
effects::*,
engine::SoundEngine,
error::SoundError,
hrtf::HrirSphere,
renderer::{hrtf::*, Renderer},
source::Status,
};
use crate::scene::Scene;
use fyrox_graph::BaseSceneGraph;
use fyrox_resource::state::ResourceState;
use fyrox_sound::source::SoundSource;
use std::{
cell::Cell,
ops::{Deref, DerefMut},
time::Duration,
};
pub mod context;
pub mod listener;
#[derive(Visit, Reflect, Debug)]
pub struct Sound {
base: Base,
#[reflect(setter = "set_buffer")]
buffer: InheritableVariable<Option<SoundBufferResource>>,
#[reflect(setter = "set_play_once")]
play_once: InheritableVariable<bool>,
#[reflect(min_value = 0.0, step = 0.05)]
#[reflect(setter = "set_gain")]
gain: InheritableVariable<f32>,
#[reflect(min_value = -1.0, max_value = 1.0, step = 0.05)]
#[reflect(setter = "set_panning")]
panning: InheritableVariable<f32>,
#[reflect(setter = "set_status")]
pub(crate) status: InheritableVariable<Status>,
#[reflect(setter = "set_looping")]
looping: InheritableVariable<bool>,
#[reflect(min_value = 0.0, step = 0.05)]
#[reflect(setter = "set_pitch")]
pitch: InheritableVariable<f64>,
#[reflect(min_value = 0.0, step = 0.05)]
#[reflect(setter = "set_radius")]
radius: InheritableVariable<f32>,
#[reflect(min_value = 0.0, step = 0.05)]
#[reflect(setter = "set_max_distance")]
max_distance: InheritableVariable<f32>,
#[reflect(min_value = 0.0, step = 0.05)]
#[reflect(setter = "set_rolloff_factor")]
rolloff_factor: InheritableVariable<f32>,
#[visit(optional)]
#[reflect(setter = "set_playback_time", min_value = 0.0)]
playback_time: InheritableVariable<f32>,
#[reflect(setter = "set_spatial_blend")]
spatial_blend: InheritableVariable<f32>,
#[visit(optional)]
#[reflect(
description = "A name of a sound effect to which the sound will attach to when instantiated."
)]
audio_bus: InheritableVariable<String>,
#[reflect(hidden)]
#[visit(skip)]
pub(crate) native: Cell<Handle<SoundSource>>,
}
impl Deref for Sound {
type Target = Base;
fn deref(&self) -> &Self::Target {
&self.base
}
}
impl DerefMut for Sound {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.base
}
}
impl Default for Sound {
fn default() -> Self {
Self {
base: Default::default(),
buffer: InheritableVariable::new_modified(None),
play_once: InheritableVariable::new_modified(false),
gain: InheritableVariable::new_modified(1.0),
panning: InheritableVariable::new_modified(0.0),
status: InheritableVariable::new_modified(Status::Stopped),
looping: InheritableVariable::new_modified(false),
pitch: InheritableVariable::new_modified(1.0),
radius: InheritableVariable::new_modified(10.0),
max_distance: InheritableVariable::new_modified(f32::MAX),
rolloff_factor: InheritableVariable::new_modified(1.0),
playback_time: Default::default(),
spatial_blend: InheritableVariable::new_modified(1.0),
audio_bus: InheritableVariable::new_modified(AudioBusGraph::PRIMARY_BUS.to_string()),
native: Default::default(),
}
}
}
impl Clone for Sound {
fn clone(&self) -> Self {
Self {
base: self.base.clone(),
buffer: self.buffer.clone(),
play_once: self.play_once.clone(),
gain: self.gain.clone(),
panning: self.panning.clone(),
status: self.status.clone(),
looping: self.looping.clone(),
pitch: self.pitch.clone(),
radius: self.radius.clone(),
max_distance: self.max_distance.clone(),
rolloff_factor: self.rolloff_factor.clone(),
playback_time: self.playback_time.clone(),
spatial_blend: self.spatial_blend.clone(),
audio_bus: self.audio_bus.clone(),
native: Default::default(),
}
}
}
impl TypeUuidProvider for Sound {
fn type_uuid() -> Uuid {
uuid!("28621735-8cd1-4fad-8faf-ecd24bf8aa99")
}
}
impl Sound {
pub fn set_buffer(
&mut self,
buffer: Option<SoundBufferResource>,
) -> Option<SoundBufferResource> {
self.buffer.set_value_and_mark_modified(buffer)
}
pub fn buffer(&self) -> Option<SoundBufferResource> {
(*self.buffer).clone()
}
pub fn set_play_once(&mut self, play_once: bool) -> bool {
self.play_once.set_value_and_mark_modified(play_once)
}
pub fn is_play_once(&self) -> bool {
*self.play_once
}
pub fn set_spatial_blend(&mut self, k: f32) -> f32 {
self.spatial_blend
.set_value_and_mark_modified(k.clamp(0.0, 1.0))
}
pub fn spatial_blend(&self) -> f32 {
*self.spatial_blend
}
pub fn set_gain(&mut self, gain: f32) -> f32 {
self.gain.set_value_and_mark_modified(gain)
}
pub fn gain(&self) -> f32 {
*self.gain
}
pub fn set_panning(&mut self, panning: f32) -> f32 {
self.panning
.set_value_and_mark_modified(panning.clamp(-1.0, 1.0))
}
pub fn panning(&self) -> f32 {
*self.panning
}
pub fn set_status(&mut self, status: Status) -> Status {
let prev = self.status();
match status {
Status::Stopped => self.stop(),
Status::Playing => self.play(),
Status::Paused => self.pause(),
}
prev
}
pub fn status(&self) -> Status {
*self.status
}
pub fn play(&mut self) {
self.status.set_value_and_mark_modified(Status::Playing);
}
pub fn try_play(&mut self) -> bool {
if *self.status == Status::Playing {
false
} else {
self.play();
true
}
}
pub fn pause(&mut self) {
self.status.set_value_and_mark_modified(Status::Paused);
}
pub fn set_looping(&mut self, looping: bool) -> bool {
self.looping.set_value_and_mark_modified(looping)
}
pub fn is_looping(&self) -> bool {
*self.looping
}
pub fn set_pitch(&mut self, pitch: f64) -> f64 {
self.pitch.set_value_and_mark_modified(pitch.abs())
}
pub fn pitch(&self) -> f64 {
*self.pitch
}
pub fn stop(&mut self) {
self.status.set_value_and_mark_modified(Status::Stopped);
}
pub fn playback_time(&self) -> f32 {
*self.playback_time
}
pub fn set_playback_time(&mut self, time: f32) -> f32 {
self.playback_time.set_value_and_mark_modified(time)
}
pub fn set_radius(&mut self, radius: f32) -> f32 {
self.radius.set_value_and_mark_modified(radius)
}
pub fn radius(&self) -> f32 {
*self.radius
}
pub fn set_rolloff_factor(&mut self, rolloff_factor: f32) -> f32 {
self.rolloff_factor
.set_value_and_mark_modified(rolloff_factor)
}
pub fn rolloff_factor(&self) -> f32 {
*self.rolloff_factor
}
pub fn set_max_distance(&mut self, max_distance: f32) -> f32 {
self.max_distance.set_value_and_mark_modified(max_distance)
}
pub fn max_distance(&self) -> f32 {
*self.max_distance
}
pub fn set_audio_bus(&mut self, name: String) {
self.audio_bus.set_value_and_mark_modified(name);
}
pub fn audio_bus(&self) -> &str {
&self.audio_bus
}
}
impl NodeTrait for Sound {
crate::impl_query_component!();
fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
AxisAlignedBoundingBox::unit()
}
fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
self.local_bounding_box()
.transform(&self.global_transform())
}
fn id(&self) -> Uuid {
Self::type_uuid()
}
fn on_removed_from_graph(&mut self, graph: &mut Graph) {
graph
.sound_context
.remove_sound(self.native.get(), &self.name);
self.native.set(Default::default());
}
fn sync_native(&self, _self_handle: Handle<Node>, context: &mut SyncContext) {
context.sound_context.sync_to_sound(
_self_handle,
self,
context.switches.and_then(|s| s.node_overrides.as_ref()),
)
}
fn sync_transform(&self, new_global_transform: &Matrix4<f32>, context: &mut SyncContext) {
if !m4x4_approx_eq(new_global_transform, &self.global_transform()) {
context.sound_context.set_sound_position(self);
}
}
fn is_alive(&self) -> bool {
if self.is_play_once() {
self.status() != Status::Stopped
} else {
true
}
}
fn update(&mut self, context: &mut UpdateContext) {
context.sound_context.sync_with_sound(self);
}
fn validate(&self, _scene: &Scene) -> Result<(), String> {
match self.buffer.as_ref() {
Some(buffer) => {
let header = buffer.header();
match header.state {
ResourceState::Pending { .. } | ResourceState::Ok(_) => Ok(()),
ResourceState::LoadError { ref error, .. } => {
match &error.0 {
None => Err("Sound buffer is failed to load, the reason is unknown!"
.to_string()),
Some(err) => {
Err(format!("Sound buffer is failed to load. Reason: {:?}", err))
}
}
}
}
}
None => Err("Sound buffer is not set, the sound won't play!".to_string()),
}
}
}
pub struct SoundBuilder {
base_builder: BaseBuilder,
buffer: Option<SoundBufferResource>,
play_once: bool,
gain: f32,
panning: f32,
status: Status,
looping: bool,
pitch: f64,
radius: f32,
max_distance: f32,
rolloff_factor: f32,
playback_time: Duration,
spatial_blend: f32,
audio_bus: String,
}
impl SoundBuilder {
pub fn new(base_builder: BaseBuilder) -> Self {
Self {
base_builder,
buffer: None,
play_once: false,
gain: 1.0,
panning: 0.0,
status: Status::Stopped,
looping: false,
pitch: 1.0,
radius: 10.0,
max_distance: f32::MAX,
rolloff_factor: 1.0,
spatial_blend: 1.0,
playback_time: Default::default(),
audio_bus: AudioBusGraph::PRIMARY_BUS.to_string(),
}
}
define_with!(
fn with_buffer(buffer: Option<SoundBufferResource>)
);
define_with!(
fn with_play_once(play_once: bool)
);
define_with!(
fn with_gain(gain: f32)
);
define_with!(
fn with_panning(panning: f32)
);
define_with!(
fn with_status(status: Status)
);
define_with!(
fn with_looping(looping: bool)
);
define_with!(
fn with_pitch(pitch: f64)
);
define_with!(
fn with_radius(radius: f32)
);
define_with!(
fn with_max_distance(max_distance: f32)
);
define_with!(
fn with_rolloff_factor(rolloff_factor: f32)
);
define_with!(
fn with_spatial_blend_factor(spatial_blend: f32)
);
define_with!(
fn with_playback_time(playback_time: Duration)
);
define_with!(
fn with_audio_bus(audio_bus: String)
);
#[must_use]
pub fn build_sound(self) -> Sound {
Sound {
base: self.base_builder.build_base(),
buffer: self.buffer.into(),
play_once: self.play_once.into(),
gain: self.gain.into(),
panning: self.panning.into(),
status: self.status.into(),
looping: self.looping.into(),
pitch: self.pitch.into(),
radius: self.radius.into(),
max_distance: self.max_distance.into(),
rolloff_factor: self.rolloff_factor.into(),
playback_time: self.playback_time.as_secs_f32().into(),
spatial_blend: self.spatial_blend.into(),
audio_bus: self.audio_bus.into(),
native: Default::default(),
}
}
#[must_use]
pub fn build_node(self) -> Node {
Node::new(self.build_sound())
}
pub fn build(self, graph: &mut Graph) -> Handle<Node> {
graph.add_node(self.build_node())
}
}