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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ A phase vocoder pitch shifter and time stretcher using the [RubberBand library](
### RubberBandStretcherBuf
A phase vocoder pitch shifter and time stretcher using the [RubberBand library](https://breakfastquay.com/rubberband/). This version writes the stretched audio to a buffer rather than outputting it directly.

### FIR
A flexible FIR filter that allows you to provide an arbitrary number of coefficients directly (as opposed to loading them in a buffer with Convolution3).

### ImpulseDropout
A modified version of Impulse that randomly drops a percentage of impulses, producing a stuttering effect.

Expand Down
14 changes: 11 additions & 3 deletions src/filters/FIR.schelp
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ related:: Classes/Convolution3, Classes/FOS, Classes/SOS, Classes/OneZero
categories:: Libraries>FlexUGens, UGens>Filters>Linear

Description::
A no-nonsense FIR filter where you provide your own coefficients. FIR duplicates the functionality
A flexible time-domain FIR filter where you provide your own coefficients. FIR duplicates the functionality
of link::Classes/Convolution3::. The difference is that FIR does not require you to load your
filter coefficients into a link::Classes/Buffer::. Instead, you can provide them directly as a
link::Classes/Ref:: in the same way as is done in link::Classes/DynKlank::.

FIR internally limits the number of coefficients to 32. This is because time-domain convolution only makes sense for
very short impulse responses--Frank Wefers writes that "the
theoretical break-even point where FFT-based convolution becomes
faster is between 16-32 samples."footnote::Frank Wefers, "Partitioned convolution algorithms
for real-time auralization," Logos Verlag Berlin (2015), 224.:: One potential use of this UGen is
to perform Savitsky-Golay filtering for control signal smoothing
(see link::https://en.wikipedia.org/wiki/Savitzky%E2%80%93Golay_filter:: for coefficient tables).

classmethods::

method::ar
method::ar, kr

argument::coefs
A link::Classes/Ref:: to a list of FIR filter coefficients. The number of coefficients can change
over time, but can never exceed the server block size (if it does, extra coefficients will be truncated).
over time, but can never exceed 32 (if it does, extra coefficients will be truncated).

argument::in
The input signal to filter
Expand Down
1 change: 1 addition & 0 deletions src/filters/filters.sc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

// A flexible FIR filter
FIR : UGen {
*ar {
arg coefs, in, mul=1, add=0;
Expand Down
10 changes: 5 additions & 5 deletions src/filters/fir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ File: fir.cpp
Author: Jeff Martin

Description:
A raw FIR filter
A flexible FIR filter

Copyright © 2026 by Jeffrey Martin. All rights reserved.
Website: https://www.jeffreymartincomposer.com
Expand All @@ -26,8 +26,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
extern InterfaceTable *ft;

FlexPlugins::FIR::FIR() {
m_z = static_cast<float*>(RTAlloc(mWorld, fullBufferSize() * sizeof(float)));
for (size_t i = 0; i < fullBufferSize(); i++) {
m_z = static_cast<float*>(RTAlloc(mWorld, MAX_COEFS * sizeof(float)));
for (size_t i = 0; i < MAX_COEFS; i++) {
m_z[i] = 0.f;
}
set_calc_function<FIR, &FIR::next>();
Expand All @@ -40,13 +40,13 @@ FlexPlugins::FIR::~FIR() {

void FlexPlugins::FIR::next(int inNumSamples) {
size_t numCoefs = static_cast<size_t>(mNumInputs - 1);
numCoefs = sc_clip(numCoefs, 0, static_cast<size_t>(fullBufferSize()));
numCoefs = sc_clip(numCoefs, 0, static_cast<size_t>(MAX_COEFS));
const float *inBuf = in(0);
float *outBuf = out(0);
for (size_t i = 0; i < inNumSamples; i++) {
float convResult = 0.f;
// unit delay
for (size_t j = fullBufferSize() - 1; j > 0; j--) {
for (size_t j = MAX_COEFS - 1; j > 0; j--) {
m_z[j] = m_z[j-1];
}
m_z[0] = inBuf[i];
Expand Down
4 changes: 3 additions & 1 deletion src/filters/fir.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ File: fir.hpp
Author: Jeff Martin

Description:
A raw FIR filter
A flexible FIR filter

Copyright © 2026 by Jeffrey Martin. All rights reserved.
Website: https://www.jeffreymartincomposer.com
Expand All @@ -26,6 +26,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.

#include "SC_PlugIn.hpp"

#define MAX_COEFS 32

namespace FlexPlugins {
class FIR : public SCUnit {
public:
Expand Down
26 changes: 14 additions & 12 deletions src/generators/ImpulseJitter.schelp
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ This value will be added to the output.

Examples::

Generating audio:
code::
(
SynthDef(\jitter, {
arg freq, jitterFrac, amp;
var sig;
sig = ImpulseJitter.ar(freq, 0.0, jitterFrac, amp);
sig = LPF.ar(sig, freq);
sig = LeakDC.ar(sig);
Out.ar(0, sig);
}).add;

Synth(\jitter, [\freq, 440.0, \jitterFrac, 0.1, \amp, -12.dbamp]);
)
{
var sig;
sig = ImpulseJitter.ar(440, 0, XLine.kr(1, 2, 5)-1);
Pan2.ar(sig);
}.play;
::

An increasingly noisy impulse train as a control signal:
code::
{
var sig;
sig = ImpulseJitter.kr(10, 0, XLine.kr(1, 2, 2) - 1);
}.plot(2);
::
6 changes: 3 additions & 3 deletions src/generators/arrayheap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "arrayheap.hpp"

// Inserts into the heap
int heapInsert(IntMinHeap* heap, int data) {
int FlexPlugins::heapInsert(IntMinHeap* heap, int data) {
if (heap->size == heap->maxSize) {
return 0;
} else {
Expand Down Expand Up @@ -54,7 +54,7 @@ int heapInsert(IntMinHeap* heap, int data) {
}

// Removes from the heap and returns the value popped. Returns 0 if the heap is empty.
int heapPop(IntMinHeap* heap) {
int FlexPlugins::heapPop(IntMinHeap* heap) {
if (heap->size > 1) {
int val = heap->heap[1];
heap->heap[1] = heap->heap[heap->size-1];
Expand Down Expand Up @@ -97,7 +97,7 @@ int heapPop(IntMinHeap* heap) {
}

// Safe peek at the top of the heap
int heapPeek(IntMinHeap* heap) {
int FlexPlugins::heapPeek(IntMinHeap* heap) {
if (heap->size > 1) {
return heap->heap[1];
} else {
Expand Down
32 changes: 17 additions & 15 deletions src/generators/arrayheap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <cstddef>

/// A min heap for ints.
typedef struct {
int* heap;
size_t size;
size_t maxSize;
} IntMinHeap;

/// Inserts into the heap
int heapInsert(IntMinHeap* heap, int data);

/// Removes from the heap and returns the value popped. Returns 0 if the heap is empty.
int heapPop(IntMinHeap* heap);

/// Safe peek at the top of the heap
int heapPeek(IntMinHeap* heap);
namespace FlexPlugins {
/// A min heap for ints.
typedef struct {
int* heap;
size_t size;
size_t maxSize;
} IntMinHeap;

/// Inserts into the heap
int heapInsert(IntMinHeap* heap, int data);

/// Removes from the heap and returns the value popped. Returns 0 if the heap is empty.
int heapPop(IntMinHeap* heap);

/// Safe peek at the top of the heap
int heapPeek(IntMinHeap* heap);
}
18 changes: 9 additions & 9 deletions src/pv/pvCFreeze.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,22 @@ void FlexPlugins::PV_CFreeze::next(int inNumSamples) {
// Pull random DC and nyquist magnitudes
p->dc = mDc[rgen.irand(mNumFrames)];
p->nyq = mNyq[rgen.irand(mNumFrames)];
for (int xxn = 0; xxn < mNumBins; xxn++) {
for (int k = 0; k < mNumBins; k++) {
// For each bin, grab a random magnitude and phase diff pair
int idx = rgen.irand(mNumFrames);
idx = idx * mNumBins + xxn;
p->bin[xxn].mag = mMags[idx];
mPhase[xxn] = sc_wrap(mPhase[xxn] + mPhaseDiffs[idx], 0.f, static_cast<float>(twopi));
p->bin[xxn].phase = mPhase[xxn];
idx = idx * mNumBins + k;
p->bin[k].mag = mMags[idx];
mPhase[k] = sc_wrap(mPhase[k] + mPhaseDiffs[idx], 0.f, static_cast<float>(twopi));
p->bin[k].phase = mPhase[k];
}
} else {
// We're writing to a circular buffer, so pull the current magnitude and phase diff arrays
float *currentMagArr = mMags + (mWritePtr * mNumBins);
float *currentPhaseDiffArr = mPhaseDiffs + (mWritePtr * mNumBins);
for (int xxn = 0; xxn < numbins; xxn++) {
currentMagArr[xxn] = p->bin[xxn].mag;
currentPhaseDiffArr[xxn] = sc_wrap(p->bin[xxn].phase - mPhase[xxn], 0.f, static_cast<float>(twopi));
mPhase[xxn] = p->bin[xxn].phase;
for (int k = 0; k < numbins; k++) {
currentMagArr[k] = p->bin[k].mag;
currentPhaseDiffArr[k] = sc_wrap(p->bin[k].phase - mPhase[k], 0.f, static_cast<float>(twopi));
mPhase[k] = p->bin[k].phase;
}
mDc[mWritePtr] = p->dc;
mNyq[mWritePtr] = p->nyq;
Expand Down
Loading
Loading