Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions crates/firewheel-core/src/diff/leaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@ use crate::{
#[cfg(feature = "musical_transport")]
use crate::clock::{DurationMusical, InstantMusical};

impl Diff for () {
fn diff<E: EventQueue>(&self, _baseline: &Self, _path: PathBuilder, _event_queue: &mut E) {}
}

impl Patch for () {
type Patch = ();

fn patch(data: &ParamData, _path: &[u32]) -> Result<Self::Patch, PatchError> {
match data {
ParamData::None => Ok(()),
_ => Err(PatchError::InvalidData),
}
}

fn apply(&mut self, _patch: Self::Patch) {}
}

macro_rules! primitive_diff {
($ty:ty, $variant:ident) => {
impl Diff for $ty {
Expand Down
2 changes: 1 addition & 1 deletion crates/firewheel-core/src/diff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
//! #[derive(Diff, Patch, Clone, PartialEq)]
//! enum SoundSource {
//! Sample(ArcGc<dyn SampleResource>), // Will _not_ cause allocations in `Patch`.
//! Sample(ArcGc<dyn SampleResource + Send + Sync + 'static>), // Will _not_ cause allocations in `Patch`.
//! Frequency(f32),
//! }
//! ```
Expand Down
6 changes: 6 additions & 0 deletions crates/firewheel-core/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ impl Default for NodeID {
#[derive(Debug)]
pub struct NodeError(Box<dyn Error>);

impl NodeError {
pub const fn from_boxed(error: Box<dyn Error>) -> Self {
Self(error)
}
}

impl<E> From<E> for NodeError
where
E: Error + 'static,
Expand Down
83 changes: 61 additions & 22 deletions crates/firewheel-core/src/sample_resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bevy_platform::prelude::Vec;
use crate::collector::ArcGc;

/// Trait returning information about a resource of audio samples
pub trait SampleResourceInfo: Send + Sync + 'static {
pub trait SampleResourceInfo {
/// The number of channels in this resource.
fn num_channels(&self) -> NonZeroUsize;

Expand Down Expand Up @@ -45,12 +45,20 @@ pub trait SampleResource: SampleResourceInfo {
/// * `start_frame` - The sample (of a single channel of audio) in the
/// resource at which to start copying from. Not to be confused with video
/// frames.
///
/// If the length of `out_buffer_range` is all or partly out of bounds of
/// the resource, then the frames which are out of bounds will be left
/// untouched.
///
/// Returns the number of frames that were successfully filled. This may
/// be less than the length of `out_buffer_range` if the range is all or
/// partly out of bounds of the resource
fn fill_buffers(
&self,
out_buffer: &mut [&mut [f32]],
out_buffer_range: Range<usize>,
start_frame: u64,
);
) -> usize;
}

/// A resource of audio samples stored as de-interleaved f32 values.
Expand All @@ -59,18 +67,24 @@ pub trait SampleResourceF32: SampleResourceInfo {
fn channel(&self, i: usize) -> Option<&[f32]>;
}

impl<T: SampleResource> From<T> for ArcGc<dyn SampleResource> {
impl<T: SampleResource + Send + Sync + 'static> From<T>
for ArcGc<dyn SampleResource + Send + Sync + 'static>
{
fn from(value: T) -> Self {
ArcGc::new_unsized(|| {
bevy_platform::sync::Arc::new(value) as bevy_platform::sync::Arc<dyn SampleResource>
bevy_platform::sync::Arc::new(value)
as bevy_platform::sync::Arc<dyn SampleResource + Send + Sync + 'static>
})
}
}

impl<T: SampleResourceF32> From<T> for ArcGc<dyn SampleResourceF32> {
impl<T: SampleResourceF32 + Send + Sync + 'static> From<T>
for ArcGc<dyn SampleResourceF32 + Send + Sync + 'static>
{
fn from(value: T) -> Self {
ArcGc::new_unsized(|| {
bevy_platform::sync::Arc::new(value) as bevy_platform::sync::Arc<dyn SampleResourceF32>
bevy_platform::sync::Arc::new(value)
as bevy_platform::sync::Arc<dyn SampleResourceF32 + Send + Sync + 'static>
})
}
}
Expand All @@ -83,9 +97,10 @@ pub struct InterleavedResourceF32 {
}

impl InterleavedResourceF32 {
pub fn into_dyn_resource(self) -> ArcGc<dyn SampleResource> {
pub fn into_dyn_resource(self) -> ArcGc<dyn SampleResource + Send + Sync + 'static> {
ArcGc::new_unsized(|| {
bevy_platform::sync::Arc::new(self) as bevy_platform::sync::Arc<dyn SampleResource>
bevy_platform::sync::Arc::new(self)
as bevy_platform::sync::Arc<dyn SampleResource + Send + Sync + 'static>
})
}
}
Expand All @@ -110,15 +125,15 @@ impl SampleResource for InterleavedResourceF32 {
out_buffer: &mut [&mut [f32]],
out_buffer_range: Range<usize>,
start_frame: u64,
) {
) -> usize {
fill_buffers_interleaved(
out_buffer,
out_buffer_range,
start_frame,
self.channels,
&self.data,
self.len_frames() as usize,
);
)
}
}

Expand Down Expand Up @@ -149,14 +164,14 @@ impl SampleResource for Vec<Vec<f32>> {
out_buffer: &mut [&mut [f32]],
out_buffer_range: Range<usize>,
start_frame: u64,
) {
) -> usize {
fill_buffers_deinterleaved_f32(
out_buffer,
out_buffer_range,
start_frame,
self,
self[0].len(),
);
)
}
}

Expand All @@ -167,22 +182,26 @@ impl SampleResourceF32 for Vec<Vec<f32>> {
}

/// A helper method to fill buffers from a resource of interleaved samples.
///
/// Returns the number of frames that were successfully filled. This may
/// be less than the length of `out_buffer_range` if the range is all or
/// partly out of bounds of the resource
pub fn fill_buffers_interleaved<T: RawSample + Clone>(
out_buffer: &mut [&mut [f32]],
out_buffer_range: Range<usize>,
start_frame: u64,
channels: NonZeroUsize,
resource: &[T],
resource_len_frames: usize,
) {
) -> usize {
let channels = channels.get();

let Some((frames, start_frame)) = constrain_frames(
out_buffer_range.end - out_buffer_range.start,
start_frame,
resource_len_frames,
) else {
return;
return 0;
};

// Provide an optimized loop for stereo.
Expand All @@ -201,7 +220,7 @@ pub fn fill_buffers_interleaved<T: RawSample + Clone>(
*buf1_s = src_chunk[1].to_scaled_float();
}

return;
return frames;
}

let src_slice = &resource[start_frame * channels..(start_frame + frames) * channels];
Expand All @@ -216,21 +235,27 @@ pub fn fill_buffers_interleaved<T: RawSample + Clone>(
&mut out_ch[out_buffer_range.start..out_buffer_range.start + frames],
);
}

frames
}

/// A helper method to fill buffers from a resource of deinterleaved samples.
///
/// Returns the number of frames that were successfully filled. This may
/// be less than the length of `out_buffer_range` if the range is all or
/// partly out of bounds of the resource
pub fn fill_channel_deinterleaved<T: RawSample + Clone>(
out_buffer_channel: &mut [f32],
out_buffer_range: Range<usize>,
start_frame: u64,
resource_channel: &[T],
) {
) -> usize {
let Some((frames, start_frame)) = constrain_frames(
out_buffer_range.end - out_buffer_range.start,
start_frame,
resource_channel.len(),
) else {
return;
return 0;
};

let adapter = SequentialSlice::new(resource_channel, 1, frames).unwrap();
Expand All @@ -241,36 +266,50 @@ pub fn fill_channel_deinterleaved<T: RawSample + Clone>(
start_frame,
&mut out_buffer_channel[out_buffer_range.start..out_buffer_range.start + frames],
);

frames
}

/// A helper method to fill buffers from a resource of deinterleaved `f32` samples.
///
/// Returns the number of frames that were successfully filled. This may
/// be less than the length of `out_buffer_range` if the range is all or
/// partly out of bounds of the resource
pub fn fill_buffers_deinterleaved_f32<V: AsRef<[f32]>>(
out_buffer: &mut [&mut [f32]],
out_buffer_range: Range<usize>,
start_frame: u64,
resource_channels: &[V],
resource_len_frames: usize,
) {
) -> usize {
let Some((frames, start_frame)) = constrain_frames(
out_buffer_range.end - out_buffer_range.start,
start_frame,
resource_len_frames,
) else {
return;
return 0;
};

for (out_ch, in_ch) in out_buffer.iter_mut().zip(resource_channels.iter()) {
out_ch[out_buffer_range.start..out_buffer_range.start + frames]
.copy_from_slice(&in_ch.as_ref()[start_frame..start_frame + frames]);
}

frames
}

fn constrain_frames(
out_buffer_len: usize,
/// A helper to constrain the requested number of frames to the available frames
/// in the sample resource.
///
/// Returns `Some((available_frames, start_frame as usize))` if the range is all
/// or partly contained in the resource, or `None` if the range is fully outside
/// the resource (`available_frames == 0`).
pub fn constrain_frames(
requested_frames: usize,
start_frame: u64,
resource_len_frames: usize,
) -> Option<(usize, usize)> {
let frames = (out_buffer_len as u64)
let frames = (requested_frames as u64)
.min((resource_len_frames as u64).saturating_sub(start_frame)) as usize;
if frames == 0 {
None
Expand Down
2 changes: 1 addition & 1 deletion crates/firewheel-nodes/src/convolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub struct ConvolutionNode {
/// The impulse response to use.
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
#[cfg_attr(feature = "serde", serde(skip))]
pub impulse_response: Option<ArcGc<dyn SampleResourceF32>>,
pub impulse_response: Option<ArcGc<dyn SampleResourceF32 + Send + Sync + 'static>>,

/// Pause the convolution processing.
///
Expand Down
Loading
Loading