Skip to content
Open
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
9 changes: 9 additions & 0 deletions plugins/buffr_glitch/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic
Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.1] - 2024-07-11

### Added

- Added precise mode support. In this mode, buffers are occasionally shorter by one
sample, making the sound closer to the requested frequency. If it's high
enough, buffers of just one size don't allow high precision.
(Right now mode changes apply only to notes played afterwards.)

## [0.2.0] - 2023-01-17

### Added
Expand Down
2 changes: 1 addition & 1 deletion plugins/buffr_glitch/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "buffr_glitch"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"]
license = "GPL-3.0-or-later"
Expand Down
35 changes: 33 additions & 2 deletions plugins/buffr_glitch/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ pub struct RingBuffer {
crossfade_length: usize,
/// See [`BufferStatus`].
buffer_status: BufferStatus,

/// Whether to use the classic behavior or more precise one with longer and shorter buffers.
precise_mode: bool,
/// The 'phase' of picking between a longer and a shorter buffer. The longer buffer is used if
/// phase is nonnegative, the shorter buffer is used otherwise.
buffer_phase: f32,
/// The difference between the true note period and the shorter buffer size, in samples.
period_delta: f32,
}

#[derive(Debug, Default, Clone, Copy)]
Expand Down Expand Up @@ -93,7 +101,9 @@ impl RingBuffer {
pub fn prepare_playback(&mut self, frequency: f32, crossfade_ms: f32) {
nih_debug_assert!(frequency > 0.0);
nih_debug_assert!(crossfade_ms >= 0.0);
let note_period_samples = (frequency.recip() * self.sample_rate).ceil() as usize;

let note_true_period_samples = frequency.recip() * self.sample_rate;
let note_period_samples = note_true_period_samples.ceil() as usize;

// This buffer doesn't need to be cleared since the data is not read until the entire buffer
// has been recorded to
Expand All @@ -108,6 +118,12 @@ impl RingBuffer {
self.crossfade_length =
((crossfade_ms * self.sample_rate).ceil() as usize).min(note_period_samples);
self.buffer_status = BufferStatus::Recording;

// When precise mode is on, buffers might occasionally loop shorter by one sample;
// `buffer_phase` and `period_delta` keep track of when.
self.buffer_phase = 0.0f32;
// TODO: Might end up useful to set delta that is too close to zero to an exact zero.
self.period_delta = note_true_period_samples - note_true_period_samples.floor();
}

/// Read or write a sample from or to the ring buffer, and return the output. On the first loop
Expand Down Expand Up @@ -140,9 +156,24 @@ impl RingBuffer {
if channel_idx == self.audio_buffers.len() - 1 {
self.next_sample_pos += 1;

if self.next_sample_pos == self.audio_buffers[0].len() {
// Note there is no messing with buffer size when buffer isn't ready yet.
let shorter_buffer = self.precise_mode
&& self.buffer_status == BufferStatus::Ready
&& self.buffer_phase < 0.0;
let buffer_len = self.audio_buffers[0].len() - (if shorter_buffer { 1 } else { 0 });

if self.next_sample_pos == buffer_len {
self.next_sample_pos = 0;

if self.precise_mode {
// In the rare case when the period is an exact integer, delta is zero and
// the exact buffer size is used always, buffer_phase also being always zero.
if self.buffer_phase > 0.0 {
self.buffer_phase -= 1.0f32;
}
self.buffer_phase += self.period_delta;
}

self.buffer_status = match self.buffer_status {
BufferStatus::Recording if self.crossfade_length > 0 => {
BufferStatus::Crossfading
Expand Down
8 changes: 8 additions & 0 deletions plugins/buffr_glitch/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ struct BuffrGlitchParams {
/// ieration.
#[id = "crossfade_ms"]
crossfade_ms: FloatParam,

/// Turns on precise frequencies mode with buffers of two sizes.
#[id = "precise_frequency"]
precise_frequency: BoolParam,
}

impl Default for BuffrGlitch {
Expand Down Expand Up @@ -171,6 +175,8 @@ impl Default for BuffrGlitchParams {
// This doesn't need smoothing because the value is set when the note is held down and cannot be changed afterwards
.with_unit(" ms")
.with_step_size(0.001),

precise_frequency: BoolParam::new("Precise Frequency", false),
}
}
}
Expand Down Expand Up @@ -415,6 +421,8 @@ impl Voice {
util::midi_note_to_freq(midi_note_id) * 2.0f32.powi(params.octave_shift.value());
self.buffer
.prepare_playback(note_frequency, params.crossfade_ms.value());

self.buffer.precise_mode = params.precise_frequency.value();
}

/// Start releasing the note.
Expand Down