From 39c7065e37cbb998b467803da725d8a2c9303862 Mon Sep 17 00:00:00 2001 From: Erik Natanael Gustafsson Date: Wed, 10 Jan 2024 16:53:08 +0100 Subject: [PATCH] Enable starting a buffer in the middle --- knyst/src/buffer.rs | 4 ++++ knyst/src/gen/osc.rs | 28 ++++++++++++++++++++-------- knyst/src/resources.rs | 7 +++++++ knyst/src/sphere.rs | 4 ++++ knyst/src/time.rs | 32 +++++++++++++++++++++++++++++++- 5 files changed, 66 insertions(+), 9 deletions(-) diff --git a/knyst/src/buffer.rs b/knyst/src/buffer.rs index 38bbc39..e738484 100644 --- a/knyst/src/buffer.rs +++ b/knyst/src/buffer.rs @@ -292,6 +292,10 @@ impl Buffer { pub fn num_channels(&self) -> usize { self.num_channels } + /// The sample rate of the buffer. This depends on the loaded sound file or generated buffer and may be different from the sample rate of a graph playing the buffer. + pub fn sample_rate(&self) -> usize { + self.sample_rate as usize + } /// Returns the length of the buffer in seconds pub fn length_seconds(&self) -> f64 { self.num_frames / self.sample_rate diff --git a/knyst/src/gen/osc.rs b/knyst/src/gen/osc.rs index 01c02b2..f06e15e 100644 --- a/knyst/src/gen/osc.rs +++ b/knyst/src/gen/osc.rs @@ -5,6 +5,7 @@ use crate::buffer::Buffer; use crate::{ buffer::BufferKey, gen::{Gen, GenContext, GenState, StopAction}, + prelude::Superseconds, resources::{BufferId, IdOrKey, WavetableId, WavetableKey}, wavetable::{Wavetable, WavetablePhase, FRACTIONAL_PART, TABLE_SIZE}, Resources, Sample, SampleRate, @@ -131,6 +132,7 @@ pub struct BufferReader { /// true if the [`BufferReader`] should loop the buffer pub looping: bool, stop_action: StopAction, + start_time: Superseconds, } #[impl_gen] @@ -146,22 +148,28 @@ impl BufferReader { BufferReader { buffer_key: buffer.into(), read_pointer: 0.0, - base_rate: 0.0, // initialise to the correct value the first time process() is called + base_rate: 0.0, rate, finished: false, looping, stop_action, + start_time: Superseconds::ZERO, } } /// Jump back to the start of the buffer - pub fn reset(&mut self) { + fn reset(&mut self) { self.jump_to(0.0); } /// Jump to a specific point in the buffer in samples - pub fn jump_to(&mut self, new_pointer_pos: f64) { + fn jump_to(&mut self, new_pointer_pos: f64) { self.read_pointer = new_pointer_pos; self.finished = false; } + /// Jump to a specific point in the buffer in samples. Has to be called before processing starts. + pub fn start_at(mut self, start_time: Superseconds) -> Self { + self.start_time = start_time; + self + } /// Process block pub fn process( &mut self, @@ -180,15 +188,15 @@ impl BufferReader { if let IdOrKey::Key(buffer_key) = self.buffer_key { if let Some(buffer) = &mut resources.buffer(buffer_key) { // Initialise the base rate if it hasn't been set + // TODO: Move this to init? Would require the buffer to be inserted though, i.e. no inserting the buffer late. if self.base_rate == 0.0 { self.base_rate = buffer.buf_rate_scale(sample_rate); + // Also init start time since this would be the first block + let start_frame = self.start_time.to_samples(buffer.sample_rate() as u64); + self.jump_to(start_frame as f64); } for (i, o) in out.iter_mut().enumerate() { - let samples = buffer.get_interleaved((self.read_pointer) as usize); - *o = samples[0]; - // println!("out: {}", sample); - self.read_pointer += self.base_rate * self.rate; if self.read_pointer >= buffer.num_frames() { self.finished = true; if self.looping { @@ -196,9 +204,13 @@ impl BufferReader { } } if self.finished { - stop_sample = Some(i + 1); + stop_sample = Some(i); break; } + let samples = buffer.get_interleaved((self.read_pointer) as usize); + *o = samples[0]; + // println!("out: {}", sample); + self.read_pointer += self.base_rate * self.rate; } } else { // Output zeroes if the buffer doesn't exist. diff --git a/knyst/src/resources.rs b/knyst/src/resources.rs index 4d19764..2c7c915 100644 --- a/knyst/src/resources.rs +++ b/knyst/src/resources.rs @@ -12,6 +12,7 @@ use std::{collections::HashMap, hash::Hash, sync::atomic::AtomicU64}; use crate::{ buffer::{Buffer, BufferKey}, + prelude::Superseconds, wavetable::Wavetable, }; @@ -86,6 +87,7 @@ type IdType = u64; pub struct BufferId { id: IdType, channels: usize, + duration: Superseconds, } impl BufferId { @@ -94,12 +96,17 @@ impl BufferId { Self { id: NEXT_BUFFER_ID.fetch_add(1, std::sync::atomic::Ordering::Release), channels: buf.num_channels(), + duration: Superseconds::from_seconds_f64(buf.length_seconds()), } } /// Number of channels in the Buffer this id points to pub fn num_channels(&self) -> usize { self.channels } + /// The duration of this buffer at its native sample rate + pub fn duration(&self) -> Superseconds { + self.duration + } } /// Get a unique id for a Buffer from this by using `fetch_add` diff --git a/knyst/src/sphere.rs b/knyst/src/sphere.rs index eeb8af8..a0c1f3d 100644 --- a/knyst/src/sphere.rs +++ b/knyst/src/sphere.rs @@ -79,6 +79,7 @@ impl KnystSphere { .unwrap_or(settings.num_outputs), block_size: backend.block_size().unwrap_or(64), sample_rate: backend.sample_rate() as Sample, + ring_buffer_size: settings.scheduling_ring_buffer_capacity, ..Default::default() }; let graph: Graph = Graph::new(graph_settings); @@ -123,6 +124,8 @@ pub struct SphereSettings { pub num_outputs: usize, /// The latency added for time scheduled changes to the audio thread to allow enough time for events to take place. pub scheduling_latency: Duration, + /// The capacity of the ring buffer transferring changes to constant inputs to the audio thread. + pub scheduling_ring_buffer_capacity: usize, } impl Default for SphereSettings { @@ -133,6 +136,7 @@ impl Default for SphereSettings { scheduling_latency: Duration::from_millis(100), num_inputs: 2, num_outputs: 2, + scheduling_ring_buffer_capacity: 1000, } } } diff --git a/knyst/src/time.rs b/knyst/src/time.rs index 4e669ee..338ff34 100644 --- a/knyst/src/time.rs +++ b/knyst/src/time.rs @@ -16,7 +16,7 @@ pub static SUBBEAT_TESIMALS_PER_BEAT: u32 = 1_476_034_560; /// "tesimal" is a made up word to refer to a very short amount of time. /// /// Inspired by BillyDM's blog post https://billydm.github.io/blog/time-keeping/ -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde-derive", derive(serde::Serialize, serde::Deserialize))] pub struct Superseconds { seconds: u32, @@ -159,6 +159,36 @@ impl ops::Mul for Superseconds { Superseconds::new(seconds, subsample_tesimals as u32) } } +impl ops::Mul for Superseconds { + type Output = Self; + + fn mul(self, rhs: f64) -> Self::Output { + let seconds = self.to_seconds_f64() * rhs; + Superseconds::from_seconds_f64(seconds) + } +} +impl ops::Mul for f64 { + type Output = Superseconds; + + fn mul(self, rhs: Superseconds) -> Self::Output { + rhs * self + } +} +impl ops::Mul for Superseconds { + type Output = Self; + + fn mul(self, rhs: f32) -> Self::Output { + let seconds = self.to_seconds_f64() as f32 * rhs; + Superseconds::from_seconds_f64(seconds as f64) + } +} +impl ops::Mul for f32 { + type Output = Superseconds; + + fn mul(self, rhs: Superseconds) -> Self::Output { + rhs * self + } +} impl ops::MulAssign for Superseconds { fn mul_assign(&mut self, rhs: Superseconds) { *self = *self * rhs;