Skip to content

Commit

Permalink
Enable starting a buffer in the middle
Browse files Browse the repository at this point in the history
  • Loading branch information
ErikNatanael committed Jan 10, 2024
1 parent 530126c commit 39c7065
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 9 deletions.
4 changes: 4 additions & 0 deletions knyst/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 20 additions & 8 deletions knyst/src/gen/osc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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]
Expand All @@ -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,
Expand All @@ -180,25 +188,29 @@ 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 {
self.reset();
}
}
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.
Expand Down
7 changes: 7 additions & 0 deletions knyst/src/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::{collections::HashMap, hash::Hash, sync::atomic::AtomicU64};

use crate::{
buffer::{Buffer, BufferKey},
prelude::Superseconds,
wavetable::Wavetable,
};

Expand Down Expand Up @@ -86,6 +87,7 @@ type IdType = u64;
pub struct BufferId {
id: IdType,
channels: usize,
duration: Superseconds,
}

impl BufferId {
Expand All @@ -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`
Expand Down
4 changes: 4 additions & 0 deletions knyst/src/sphere.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
}
}
}
32 changes: 31 additions & 1 deletion knyst/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -159,6 +159,36 @@ impl ops::Mul<Superseconds> for Superseconds {
Superseconds::new(seconds, subsample_tesimals as u32)
}
}
impl ops::Mul<f64> 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<Superseconds> for f64 {
type Output = Superseconds;

fn mul(self, rhs: Superseconds) -> Self::Output {
rhs * self
}
}
impl ops::Mul<f32> 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<Superseconds> for f32 {
type Output = Superseconds;

fn mul(self, rhs: Superseconds) -> Self::Output {
rhs * self
}
}
impl ops::MulAssign<Superseconds> for Superseconds {
fn mul_assign(&mut self, rhs: Superseconds) {
*self = *self * rhs;
Expand Down

0 comments on commit 39c7065

Please sign in to comment.