// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! Generic sound source. //! //! # Overview //! //! Sound source is responsible for sound playback. //! //! # Usage //! //! Generic sound source can be constructed using GenericSourceBuilder like this: //! //! ```no_run //! use std::sync::{Arc, Mutex}; //! use fyrox_sound::buffer::SoundBufferResource; //! use fyrox_sound::pool::Handle; //! use fyrox_sound::source::{SoundSource, Status}; //! use fyrox_sound::source::SoundSourceBuilder; //! use fyrox_sound::context::SoundContext; //! //! fn make_source(context: &mut SoundContext, buffer: SoundBufferResource) -> Handle { //! let source = SoundSourceBuilder::new() //! .with_buffer(buffer) //! .with_status(Status::Playing) //! .build() //! .unwrap(); //! context.state().add_source(source) //! } //! ``` #![allow(clippy::float_cmp)] use crate::{ buffer::{streaming::StreamingBuffer, SoundBuffer, SoundBufferResource}, bus::AudioBusGraph, context::DistanceModel, error::SoundError, listener::Listener, }; use fyrox_core::{ algebra::Vector3, log::Log, reflect::prelude::*, visitor::{Visit, VisitResult, Visitor}, }; use std::time::Duration; /// Status (state) of sound source. #[derive(Eq, PartialEq, Copy, Clone, Debug, Reflect, Visit)] #[reflect(type_uuid = "1980bded-86cd-4eff-a5db-bab729bdb3ad")] #[repr(u32)] pub enum Status { /// Sound is stopped - it won't produces any sample and won't load mixer. This is default /// state of all sound sources. Stopped = 0, /// Sound is playing. Playing = 1, /// Sound is paused, it can stay in this state any amount if time. Playback can be continued by /// setting `Playing` status. Paused = 2, } /// See module info. #[derive(Debug, Clone, PartialEq, Reflect, Visit)] #[reflect(type_uuid = "1beb0bbc-72fb-42a1-9e78-5d246c84fdfe")] pub struct SoundSource { name: String, #[reflect(hidden)] buffer: Option, // Read position in the buffer in samples. Differs from `playback_pos` if buffer is streaming. // In case of streaming buffer its maximum value will be some fixed value which is // implementation defined. It can be less than zero, this happens when we are in the process // of reading next block in streaming buffer (see also prev_buffer_sample). #[reflect(hidden)] buf_read_pos: f64, // Real playback position in samples. #[reflect(hidden)] playback_pos: f64, #[reflect(min_value = 0.0, step = 0.05)] panning: f32, #[reflect(min_value = 0.0, step = 0.05)] pitch: f64, #[reflect(min_value = 0.0, step = 0.05)] gain: f32, looping: bool, #[reflect(min_value = 0.0, max_value = 1.0, step = 0.05)] spatial_blend: f32, status: Status, #[visit(optional)] pub(crate) bus: String, play_once: bool, // Here we use Option because when source is just created it has no info about it // previous left and right channel gains. We can't set it to 1.0 for example // because it would give incorrect results: a sound would just start as loud as it // can be with no respect to real distance attenuation (or what else affects channel // gain). So if these are None engine will set correct values first and only then it // will start interpolation of gain. #[reflect(hidden)] #[visit(skip)] pub(crate) last_left_gain: Option, #[reflect(hidden)] #[visit(skip)] pub(crate) last_right_gain: Option, #[reflect(hidden)] #[visit(skip)] pub(crate) frame_samples: Vec<(f32, f32)>, // This sample is used when doing linear interpolation between two blocks of streaming buffer. #[reflect(hidden)] #[visit(skip)] prev_buffer_sample: (f32, f32), #[reflect(min_value = 0.0, step = 0.05)] radius: f32, position: Vector3, #[reflect(min_value = 0.0, step = 0.05)] max_distance: f32, #[reflect(min_value = 0.0, step = 0.05)] rolloff_factor: f32, // Some data that needed for iterative overlap-save convolution. #[reflect(hidden)] #[visit(skip)] pub(crate) prev_left_samples: Vec, #[reflect(hidden)] #[visit(skip)] pub(crate) prev_right_samples: Vec, #[reflect(hidden)] #[visit(skip)] pub(crate) prev_sampling_vector: Vector3, #[reflect(hidden)] #[visit(skip)] pub(crate) prev_distance_gain: Option, } impl Default for SoundSource { fn default() -> Self { Self { name: Default::default(), buffer: None, buf_read_pos: 0.0, playback_pos: 0.0, panning: 0.0, pitch: 1.0, gain: 1.0, spatial_blend: 1.0, looping: false, status: Status::Stopped, bus: "Master".to_string(), play_once: false, last_left_gain: None, last_right_gain: None, frame_samples: Default::default(), prev_buffer_sample: (0.0, 0.0), radius: 1.0, position: Vector3::new(0.0, 0.0, 0.0), max_distance: f32::MAX, rolloff_factor: 1.0, prev_left_samples: Default::default(), prev_right_samples: Default::default(), prev_sampling_vector: Vector3::new(0.0, 0.0, 1.0), prev_distance_gain: None, } } } impl SoundSource { /// Sets new name of the sound source. pub fn set_name>(&mut self, name: N) { name.as_ref().clone_into(&mut self.name); } /// Returns the name of the sound source. pub fn name(&self) -> &str { &self.name } /// Returns the name of the sound source. pub fn name_owned(&self) -> String { self.name.to_owned() } /// Sets spatial blend factor. It defines how much the source will be 2D and 3D sound at the same /// time. Set it to 0.0 to make the sound fully 2D and 1.0 to make it fully 3D. Middle values /// will make sound proportionally 2D and 3D at the same time. pub fn set_spatial_blend(&mut self, k: f32) { self.spatial_blend = k.clamp(0.0, 1.0); } /// Returns spatial blend factor. pub fn spatial_blend(&self) -> f32 { self.spatial_blend } /// Changes buffer of source. Returns old buffer. Source will continue playing from beginning, old /// position will be discarded. pub fn set_buffer( &mut self, buffer: Option, ) -> Result, SoundError> { self.buf_read_pos = 0.0; self.playback_pos = 0.0; // If we already have streaming buffer assigned make sure to decrease use count // so it can be reused later on if needed. if let Some(buffer) = self.buffer.clone() { if let Some(SoundBuffer::Streaming(streaming)) = buffer.state().data() { streaming.use_count = streaming.use_count.saturating_sub(1); } } if let Some(buffer) = buffer.clone() { match buffer.state().data() { None => return Err(SoundError::BufferFailedToLoad), Some(locked_buffer) => { if locked_buffer.duration() == Duration::ZERO { panic!("Zero duration buffer: {:?}", locked_buffer); } // Check new buffer if streaming - it must not be used by anyone else. if let SoundBuffer::Streaming(ref mut streaming) = *locked_buffer { if streaming.use_count != 0 { return Err(SoundError::StreamingBufferAlreadyInUse); } streaming.use_count += 1; } } } } Ok(std::mem::replace(&mut self.buffer, buffer)) } /// Returns current buffer if any. pub fn buffer(&self) -> Option { self.buffer.clone() } /// Marks buffer for single play. It will be automatically destroyed when it will finish playing. /// /// # Notes /// /// Make sure you not using handles to "play once" sounds, attempt to get reference of "play once" sound /// may result in panic if source already deleted. Looping sources will never be automatically deleted /// because their playback never stops. pub fn set_play_once(&mut self, play_once: bool) { self.play_once = play_once; } /// Returns true if this source is marked for single play, false - otherwise. pub fn is_play_once(&self) -> bool { self.play_once } /// Sets new gain (volume) of sound. Value should be in 0..1 range, but it is not clamped /// and larger values can be used to "overdrive" sound. /// /// # Notes /// /// Physical volume has non-linear scale (logarithmic) so perception of sound at 0.25 gain /// will be different if logarithmic scale was used. pub fn set_gain(&mut self, gain: f32) -> &mut Self { self.gain = gain; self } /// Returns current gain (volume) of sound. Value is in 0..1 range. pub fn gain(&self) -> f32 { self.gain } /// Sets panning coefficient. Value must be in -1..+1 range. Where -1 - only left channel will be audible, /// 0 - both, +1 - only right. pub fn set_panning(&mut self, panning: f32) -> &mut Self { self.panning = panning.clamp(-1.0, 1.0); self } /// Returns current panning coefficient in -1..+1 range. For more info see `set_panning`. Default value is 0. pub fn panning(&self) -> f32 { self.panning } /// Returns status of sound source. pub fn status(&self) -> Status { self.status } /// Changes status to `Playing`. pub fn play(&mut self) -> &mut Self { self.status = Status::Playing; self } /// Changes status to `Paused` pub fn pause(&mut self) -> &mut Self { self.status = Status::Paused; self } /// Enabled or disables sound looping. Looping sound will never stop by itself, but can be stopped or paused /// by calling `stop` or `pause` methods. Useful for music, ambient sounds, etc. pub fn set_looping(&mut self, looping: bool) -> &mut Self { self.looping = looping; self } /// Returns looping status. pub fn is_looping(&self) -> bool { self.looping } /// Sets sound pitch. Defines "tone" of sounds. Default value is 1.0 pub fn set_pitch(&mut self, pitch: f64) -> &mut Self { self.pitch = pitch.abs(); self } /// Returns pitch of sound source. pub fn pitch(&self) -> f64 { self.pitch } /// Stops sound source. Automatically rewinds streaming buffers. pub fn stop(&mut self) -> Result<(), SoundError> { self.status = Status::Stopped; self.buf_read_pos = 0.0; self.playback_pos = 0.0; if let Some(buffer) = self.buffer.as_ref() { if let Some(SoundBuffer::Streaming(streaming)) = buffer.state().data() { streaming.rewind()?; } } Ok(()) } /// Sets position of source in world space. pub fn set_position(&mut self, position: Vector3) -> &mut Self { self.position = position; self } /// Returns positions of source. pub fn position(&self) -> Vector3 { self.position } /// Sets radius of imaginable sphere around source in which no distance attenuation is applied. pub fn set_radius(&mut self, radius: f32) -> &mut Self { self.radius = radius; self } /// Returns radius of source. pub fn radius(&self) -> f32 { self.radius } /// Sets rolloff factor. Rolloff factor is used in distance attenuation and has different meaning /// in various distance models. It is applicable only for InverseDistance and ExponentDistance /// distance models. See DistanceModel docs for formulae. pub fn set_rolloff_factor(&mut self, rolloff_factor: f32) -> &mut Self { self.rolloff_factor = rolloff_factor; self } /// Returns rolloff factor. pub fn rolloff_factor(&self) -> f32 { self.rolloff_factor } /// Sets maximum distance until which distance gain will be applicable. Basically it doing this /// min(max(distance, radius), max_distance) which clamps distance in radius..max_distance range. /// From listener's perspective this will sound like source has stopped decreasing its volume even /// if distance continue to grow. pub fn set_max_distance(&mut self, max_distance: f32) -> &mut Self { self.max_distance = max_distance; self } /// Returns max distance. pub fn max_distance(&self) -> f32 { self.max_distance } /// Sets new name of the target audio bus. The name must be valid, otherwise the sound won't play! /// Default is [`AudioBusGraph::PRIMARY_BUS`]. pub fn set_bus>(&mut self, bus: S) { bus.as_ref().clone_into(&mut self.bus); } /// Return the name of the target audio bus. pub fn bus(&self) -> &str { &self.bus } // Distance models were taken from OpenAL Specification because it looks like they're // standard in industry and there is no need to reinvent it. // https://www.openal.org/documentation/openal-1.1-specification.pdf pub(crate) fn calculate_distance_gain( &self, listener: &Listener, distance_model: DistanceModel, ) -> f32 { let distance = self .position .metric_distance(&listener.position()) .clamp(self.radius, self.max_distance); match distance_model { DistanceModel::None => 1.0, DistanceModel::InverseDistance => { self.radius / (self.radius + self.rolloff_factor * (distance - self.radius)) } DistanceModel::LinearDistance => { 1.0 - self.radius * (distance - self.radius) / (self.max_distance - self.radius) } DistanceModel::ExponentDistance => (distance / self.radius).powf(-self.rolloff_factor), } } pub(crate) fn calculate_panning(&self, listener: &Listener) -> f32 { (listener.position() - self.position) .try_normalize(f32::EPSILON) // Fallback to look axis will give zero panning which will result in even // gain in each channels (as if there was no panning at all). .unwrap_or_else(|| listener.look_axis()) .dot(&listener.ear_axis()) } pub(crate) fn calculate_sampling_vector(&self, listener: &Listener) -> Vector3 { let to_self = listener.position() - self.position; (listener.basis() * to_self) .try_normalize(f32::EPSILON) // This is ok to fallback to (0, 0, 1) vector because it's given // in listener coordinate system. .unwrap_or_else(|| Vector3::new(0.0, 0.0, 1.0)) } /// Returns playback duration. pub fn playback_time(&self) -> Duration { if let Some(buffer) = self.buffer.as_ref() { if let Some(buffer) = buffer.state().data() { return Duration::from_secs_f64(self.playback_pos / (buffer.sample_rate() as f64)); } } Duration::from_secs(0) } /// Sets playback duration. pub fn set_playback_time(&mut self, time: Duration) { if let Some(buffer) = self.buffer.as_ref() { if let Some(buffer) = buffer.state().data() { if let SoundBuffer::Streaming(ref mut streaming) = *buffer { // Make sure decoder is at right position. if streaming .time_seek(time.clamp(Duration::from_secs(0), streaming.duration())) .is_err() { Log::warn("error while setting decoder position"); } } // Set absolute position first. self.playback_pos = (time.as_secs_f64() * buffer.sample_rate as f64) .clamp(0.0, buffer.duration().as_secs_f64()); // Then adjust buffer read position. self.buf_read_pos = match *buffer { SoundBuffer::Streaming(ref mut streaming) => { // Make sure to load correct data into buffer from decoder. streaming.read_next_block(); // Streaming sources has different buffer read position because // buffer contains only small portion of data. self.playback_pos % (StreamingBuffer::STREAM_SAMPLE_COUNT as f64) } SoundBuffer::Generic(_) => self.playback_pos, }; assert!( self.buf_read_pos * (buffer.channel_count() as f64) < buffer.samples().len() as f64 ); } } } pub(crate) fn render(&mut self, sample_rate: u32, amount: usize) { if self.frame_samples.capacity() < amount { self.frame_samples = Vec::with_capacity(amount); } self.frame_samples.clear(); if let Some(buffer) = self.buffer.clone() { let mut state = buffer.state(); if let Some(buffer) = state.data() { if self.status == Status::Playing && !buffer.is_empty() { self.render_playing(sample_rate, buffer, amount); } } } // Fill the remaining part of frame_samples. self.frame_samples.resize(amount, (0.0, 0.0)); } fn render_playing(&mut self, sample_rate: u32, buffer: &mut SoundBuffer, amount: usize) { let mut count = 0; loop { count += self.render_until_block_end(sample_rate, buffer, amount - count); if count == amount { break; } let channel_count = buffer.channel_count(); let len = buffer.samples().len(); let mut end_reached = true; if let SoundBuffer::Streaming(streaming) = buffer { // Means that this is the last available block. if len != channel_count * StreamingBuffer::STREAM_SAMPLE_COUNT { let _ = streaming.rewind(); } else { end_reached = false; } self.prev_buffer_sample = get_last_sample(streaming); streaming.read_next_block(); } if end_reached { self.buf_read_pos = 0.0; self.playback_pos = 0.0; if !self.looping { self.status = Status::Stopped; return; } } else { self.buf_read_pos -= len as f64 / channel_count as f64; } } } // Renders until the end of the block or until amount samples is written and returns // the number of written samples. fn render_until_block_end( &mut self, sample_rate: u32, buffer: &mut SoundBuffer, mut amount: usize, ) -> usize { // Important coefficient for runtime resampling. It is used to modify playback speed // of a source in order to match output device sampling rate. PCM data can be stored // in various sampling rates (22050 Hz, 44100 Hz, 88200 Hz, etc.) but output device // is running at fixed sampling rate (usually 44100 Hz). For example if we we'll feed // data to device with rate of 22050 Hz but device is running at 44100 Hz then we'll // hear that sound will have high pitch (2.0), to fix that we'll just pre-multiply // playback speed by 0.5. // However such auto-resampling has poor quality, but it is fast. let resampling_multiplier = buffer.sample_rate as f64 / f64::from(sample_rate); let step = self.pitch * resampling_multiplier; if step == 1.0 { if self.buf_read_pos < 0.0 { // This can theoretically happen if we change pitch on the fly. self.frame_samples.push(self.prev_buffer_sample); self.buf_read_pos = 0.0; amount -= 1; } // Fast-path for common case when there is no resampling and no pitch change. let from = self.buf_read_pos as usize; let buffer_len = buffer.samples.len() / buffer.channel_count; let rendered = (buffer_len - from).min(amount); if buffer.channel_count == 2 { for i in from..from + rendered { self.frame_samples .push((buffer.samples[i * 2], buffer.samples[i * 2 + 1])) } } else { for i in from..from + rendered { self.frame_samples .push((buffer.samples[i], buffer.samples[i])) } } self.buf_read_pos += rendered as f64; self.playback_pos += rendered as f64; rendered } else { self.render_until_block_end_resample(buffer, amount, step) } } // Does linear resampling while rendering until the end of the block. fn render_until_block_end_resample( &mut self, buffer: &mut SoundBuffer, amount: usize, step: f64, ) -> usize { let mut rendered = 0; while self.buf_read_pos < 0.0 { // Interpolate between last sample of previous buffer and first sample of current // buffer. This is important, otherwise there will be quiet but audible pops // in the output. let w = (self.buf_read_pos - self.buf_read_pos.floor()) as f32; let cur_first_sample = if buffer.channel_count == 2 { (buffer.samples[0], buffer.samples[1]) } else { (buffer.samples[0], buffer.samples[0]) }; let l = self.prev_buffer_sample.0 * (1.0 - w) + cur_first_sample.0 * w; let r = self.prev_buffer_sample.1 * (1.0 - w) + cur_first_sample.1 * w; self.frame_samples.push((l, r)); self.buf_read_pos += step; self.playback_pos += step; rendered += 1; } // We want to keep global positions in f64, but use f32 in inner loops (this improves // code generation and performance at least on some systems), so we split the buf_read_pos // into integer and f32 part. let buffer_base_idx = self.buf_read_pos as usize; let mut buffer_rel_pos = (self.buf_read_pos - buffer_base_idx as f64) as f32; let start_buffer_rel_pos = buffer_rel_pos; let rel_step = step as f32; // We skip one last element because the hot loop resampling between current and next // element. Last elements are appended after the hot loop. let buffer_last = buffer.samples.len() / buffer.channel_count - 1; if buffer.channel_count == 2 { while rendered < amount { let (idx, w) = { let idx = buffer_rel_pos as usize; // This looks a bit complicated but fract() is quite a bit slower on x86, // because it turns into a function call on targets < SSE4.1, unlike aarch64) (idx + buffer_base_idx, buffer_rel_pos - idx as f32) }; if idx >= buffer_last { break; } let l = buffer.samples[idx * 2] * (1.0 - w) + buffer.samples[idx * 2 + 2] * w; let r = buffer.samples[idx * 2 + 1] * (1.0 - w) + buffer.samples[idx * 2 + 3] * w; self.frame_samples.push((l, r)); buffer_rel_pos += rel_step; rendered += 1; } } else { while rendered < amount { let (idx, w) = { let idx = buffer_rel_pos as usize; // See comment above. (idx + buffer_base_idx, buffer_rel_pos - idx as f32) }; if idx >= buffer_last { break; } let v = buffer.samples[idx] * (1.0 - w) + buffer.samples[idx + 1] * w; self.frame_samples.push((v, v)); buffer_rel_pos += rel_step; rendered += 1; } } self.buf_read_pos += (buffer_rel_pos - start_buffer_rel_pos) as f64; self.playback_pos += (buffer_rel_pos - start_buffer_rel_pos) as f64; rendered } pub(crate) fn frame_samples(&self) -> &[(f32, f32)] { &self.frame_samples } } fn get_last_sample(buffer: &StreamingBuffer) -> (f32, f32) { let len = buffer.samples.len(); if len == 0 { return (0.0, 0.0); } if buffer.channel_count == 2 { (buffer.samples[len - 2], buffer.samples[len - 1]) } else { (buffer.samples[len - 1], buffer.samples[len - 1]) } } impl Drop for SoundSource { fn drop(&mut self) { if let Some(buffer) = self.buffer.as_ref() { if let Some(SoundBuffer::Streaming(streaming)) = buffer.state().data() { streaming.use_count = streaming.use_count.saturating_sub(1); } } } } /// Allows you to construct generic sound source with desired state. /// /// # Usage /// /// ```no_run /// use std::sync::{Arc, Mutex}; /// use fyrox_sound::buffer::SoundBufferResource; /// use fyrox_sound::source::{SoundSourceBuilder}; /// use fyrox_sound::source::{Status, SoundSource}; /// /// fn make_sound_source(buffer: SoundBufferResource) -> SoundSource { /// SoundSourceBuilder::new() /// .with_buffer(buffer) /// .with_status(Status::Playing) /// .with_gain(0.5) /// .with_looping(true) /// .with_pitch(1.25) /// .build() /// .unwrap() /// } /// ``` pub struct SoundSourceBuilder { buffer: Option, gain: f32, pitch: f64, name: String, panning: f32, looping: bool, status: Status, play_once: bool, playback_time: Duration, radius: f32, position: Vector3, max_distance: f32, rolloff_factor: f32, spatial_blend: f32, bus: String, } impl Default for SoundSourceBuilder { fn default() -> Self { Self::new() } } impl SoundSourceBuilder { /// Creates new generic source builder with specified buffer. pub fn new() -> Self { Self { buffer: None, gain: 1.0, pitch: 1.0, name: Default::default(), panning: 0.0, looping: false, status: Status::Stopped, play_once: false, playback_time: Default::default(), radius: 1.0, position: Vector3::new(0.0, 0.0, 0.0), max_distance: f32::MAX, rolloff_factor: 1.0, spatial_blend: 1.0, bus: AudioBusGraph::PRIMARY_BUS.to_string(), } } /// Sets desired sound buffer to play. pub fn with_buffer(mut self, buffer: SoundBufferResource) -> Self { self.buffer = Some(buffer); self } /// Sets desired sound buffer to play. pub fn with_opt_buffer(mut self, buffer: Option) -> Self { self.buffer = buffer; self } /// See [`SoundSource::set_gain`] pub fn with_gain(mut self, gain: f32) -> Self { self.gain = gain; self } /// See [`SoundSource::set_spatial_blend`] pub fn with_spatial_blend_factor(mut self, k: f32) -> Self { self.spatial_blend = k.clamp(0.0, 1.0); self } /// See [`SoundSource::set_pitch`] pub fn with_pitch(mut self, pitch: f64) -> Self { self.pitch = pitch; self } /// See [`SoundSource::set_panning`] pub fn with_panning(mut self, panning: f32) -> Self { self.panning = panning; self } /// See [`SoundSource::set_looping`] pub fn with_looping(mut self, looping: bool) -> Self { self.looping = looping; self } /// Sets desired status of source. pub fn with_status(mut self, status: Status) -> Self { self.status = status; self } /// See `set_play_once` of SoundSource pub fn with_play_once(mut self, play_once: bool) -> Self { self.play_once = play_once; self } /// Sets desired name of the source. pub fn with_name>(mut self, name: N) -> Self { name.as_ref().clone_into(&mut self.name); self } /// Sets desired starting playback time. pub fn with_playback_time(mut self, time: Duration) -> Self { self.playback_time = time; self } /// See `set_position` of SpatialSource. pub fn with_position(mut self, position: Vector3) -> Self { self.position = position; self } /// See `set_radius` of SpatialSource. pub fn with_radius(mut self, radius: f32) -> Self { self.radius = radius; self } /// See `set_max_distance` of SpatialSource. pub fn with_max_distance(mut self, max_distance: f32) -> Self { self.max_distance = max_distance; self } /// See `set_rolloff_factor` of SpatialSource. pub fn with_rolloff_factor(mut self, rolloff_factor: f32) -> Self { self.rolloff_factor = rolloff_factor; self } /// Sets desired output bus for the sound source. pub fn with_bus>(mut self, bus: S) -> Self { self.bus = bus.as_ref().to_string(); self } /// Creates new instance of generic sound source. May fail if buffer is invalid. pub fn build(self) -> Result { let mut source = SoundSource { buffer: self.buffer.clone(), gain: self.gain, pitch: self.pitch, play_once: self.play_once, panning: self.panning, status: self.status, looping: self.looping, name: self.name, frame_samples: Default::default(), radius: self.radius, position: self.position, max_distance: self.max_distance, rolloff_factor: self.rolloff_factor, spatial_blend: self.spatial_blend, prev_left_samples: Default::default(), prev_right_samples: Default::default(), bus: self.bus, ..Default::default() }; source.set_buffer(self.buffer)?; source.set_playback_time(self.playback_time); Ok(source) } }