From 8b84bd2f25fde36c2c28a28b52a780fba2e58946 Mon Sep 17 00:00:00 2001 From: Jeff Martin Date: Sat, 11 Jul 2026 19:57:24 -0500 Subject: [PATCH 1/3] started on fir --- CMakeLists.txt | 7 +++-- src/filters/CMakeLists.txt | 16 +++++++++++ src/filters/FIR.schelp | 39 +++++++++++++++++++++++++ src/filters/filters.cpp | 33 +++++++++++++++++++++ src/filters/filters.sc | 34 ++++++++++++++++++++++ src/filters/fir.cpp | 59 ++++++++++++++++++++++++++++++++++++++ src/filters/fir.hpp | 41 ++++++++++++++++++++++++++ 7 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 src/filters/CMakeLists.txt create mode 100644 src/filters/FIR.schelp create mode 100644 src/filters/filters.cpp create mode 100644 src/filters/filters.sc create mode 100644 src/filters/fir.cpp create mode 100644 src/filters/fir.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 46805f3..1bb31e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -98,6 +98,7 @@ if(MINGW) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mstackrealign") endif() +add_subdirectory(src/filters) add_subdirectory(src/generators) add_subdirectory(src/pv) add_subdirectory(src/rubberband) @@ -113,10 +114,11 @@ if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) endif() endif() -install(TARGETS generators pv rubberband +install(TARGETS filters generators pv rubberband LIBRARY DESTINATION Plugins ) install(FILES + src/filters/filters.sc src/generators/generators.sc src/pv/pv.sc src/rubberband/rubberband.sc @@ -124,6 +126,7 @@ install(FILES PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ ) install(FILES + src/filters/FIR.schelp src/generators/ImpulseDropout.schelp src/generators/ImpulseJitter.schelp src/generators/LoopPhasor.schelp @@ -139,7 +142,7 @@ install(FILES PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ ) if(SUPERNOVA) - install(TARGETS generators_supernova pv_supernova rubberband_supernova + install(TARGETS filters_supernova generators_supernova pv_supernova rubberband_supernova LIBRARY DESTINATION Plugins ) endif() diff --git a/src/filters/CMakeLists.txt b/src/filters/CMakeLists.txt new file mode 100644 index 0000000..690ad3a --- /dev/null +++ b/src/filters/CMakeLists.txt @@ -0,0 +1,16 @@ +# Create the project library +add_library(filters MODULE filters.cpp) +add_library(fir STATIC fir.cpp) +set_target_properties(fir PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_link_libraries(filters PRIVATE + fir +) + +if(SUPERNOVA) + add_library(filters_supernova MODULE filters.cpp) + set_property(TARGET filters_supernova + PROPERTY COMPILE_DEFINITIONS SUPERNOVA) + target_link_libraries(filters_supernova PRIVATE + fir + ) +endif() diff --git a/src/filters/FIR.schelp b/src/filters/FIR.schelp new file mode 100644 index 0000000..cc38930 --- /dev/null +++ b/src/filters/FIR.schelp @@ -0,0 +1,39 @@ +class:: FIR +summary:: No-nonsense FIR filter +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 +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::. + +classmethods:: + +method::ar + +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). + +argument::in +The input signal to filter + +argument::mul +The signal will be multiplied by this scalar + +argument::add +This scalar will be added to the signal + +Examples:: + +code:: +( +{ + var sig = Saw.ar(440); + sig = FIR.ar(`[0.2, 0.2, 0.2, 0.2, 0.2], sig); +}.play; +) +:: + diff --git a/src/filters/filters.cpp b/src/filters/filters.cpp new file mode 100644 index 0000000..d058f8b --- /dev/null +++ b/src/filters/filters.cpp @@ -0,0 +1,33 @@ +/* +File: filters.cpp +Author: Jeff Martin + +Description: +A collection of SuperCollider filter UGens + +Copyright © 2026 by Jeffrey Martin. All rights reserved. +Website: https://www.jeffreymartincomposer.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "SC_PlugIn.hpp" +#include "fir.hpp" + +InterfaceTable *ft; + +PluginLoad(flex_filters) { + ft = inTable; + registerUnit(ft, "FIR", false); +} diff --git a/src/filters/filters.sc b/src/filters/filters.sc new file mode 100644 index 0000000..ae66e18 --- /dev/null +++ b/src/filters/filters.sc @@ -0,0 +1,34 @@ +// File: filters.sc +// Author: Jeff Martin +// +// Description: +// A collection of SuperCollider filter UGens +// +// Copyright © 2026 by Jeffrey Martin. All rights reserved. +// Website: https://www.jeffreymartincomposer.com +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +FIR : UGen { + *ar { + arg coefs, in, mul=1, add=0; + coefs = coefs.multichannelExpandRef(1); + ^this.multiNewList(['audio', in, coefs]).madd(mul, add) + } + *new1 { + arg rate, in, coefs; + var derefCoefs = coefs.dereference; + ^super.new.rate_(rate).addToSynth.init([in] ++ derefCoefs) + } +} \ No newline at end of file diff --git a/src/filters/fir.cpp b/src/filters/fir.cpp new file mode 100644 index 0000000..2f00a76 --- /dev/null +++ b/src/filters/fir.cpp @@ -0,0 +1,59 @@ +/* +File: fir.cpp +Author: Jeff Martin + +Description: +A raw FIR filter + +Copyright © 2026 by Jeffrey Martin. All rights reserved. +Website: https://www.jeffreymartincomposer.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "fir.hpp" +extern InterfaceTable *ft; + +FlexPlugins::FIR::FIR() { + m_z = static_cast(RTAlloc(mWorld, fullBufferSize() * sizeof(float))); + for (size_t i = 0; i < fullBufferSize(); i++) { + m_z[i] = 0.f; + } + set_calc_function(); + next(1); +} + +FlexPlugins::FIR::~FIR() { + if (m_z) RTFree(mWorld, m_z); +} + +void FlexPlugins::FIR::next(int nSamples) { + size_t numCoefs = static_cast(mNumInputs - 1); + numCoefs = sc_clip(numCoefs, 0, static_cast(fullBufferSize())); + const float *inBuf = in(0); + float *outBuf = out(0); + for (size_t i = 0; i < nSamples; i++) { + float convResult = 0.f; + // unit delay + for (size_t j = fullBufferSize() - 1; j > 0; j--) { + m_z[j] = m_z[j-1]; + } + m_z[0] = inBuf[i]; + // convolve + for (size_t k = 0; k < numCoefs; k++) { + convResult += in0(1+k) * m_z[k]; + } + outBuf[i] = convResult; + } +} \ No newline at end of file diff --git a/src/filters/fir.hpp b/src/filters/fir.hpp new file mode 100644 index 0000000..0d65e00 --- /dev/null +++ b/src/filters/fir.hpp @@ -0,0 +1,41 @@ +/* +File: fir.hpp +Author: Jeff Martin + +Description: +A raw FIR filter + +Copyright © 2026 by Jeffrey Martin. All rights reserved. +Website: https://www.jeffreymartincomposer.com + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include "SC_PlugIn.hpp" + +namespace FlexPlugins { + class FIR : public SCUnit { + public: + FIR(); + ~FIR(); + + private: + void next(int nSamples); + + float *m_z; + size_t m_delaySize; + }; +} \ No newline at end of file From e056066d5fb4ab65049850d861ab6c50a4bf7bd0 Mon Sep 17 00:00:00 2001 From: Jeff Martin Date: Sun, 12 Jul 2026 13:34:06 -0500 Subject: [PATCH 2/3] standard version of fir works now --- src/filters/filters.cpp | 4 ++-- src/filters/filters.sc | 13 ++++++++++++- src/filters/fir.cpp | 41 +++++++++++++++++++++-------------------- src/filters/fir.hpp | 21 ++++++++------------- 4 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/filters/filters.cpp b/src/filters/filters.cpp index d058f8b..fb6b31a 100644 --- a/src/filters/filters.cpp +++ b/src/filters/filters.cpp @@ -22,12 +22,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "SC_PlugIn.hpp" +#include "SC_PlugIn.h" #include "fir.hpp" InterfaceTable *ft; PluginLoad(flex_filters) { ft = inTable; - registerUnit(ft, "FIR", false); + DefineDtorUnit(FIR); } diff --git a/src/filters/filters.sc b/src/filters/filters.sc index ae66e18..d0038bc 100644 --- a/src/filters/filters.sc +++ b/src/filters/filters.sc @@ -26,9 +26,20 @@ FIR : UGen { coefs = coefs.multichannelExpandRef(1); ^this.multiNewList(['audio', in, coefs]).madd(mul, add) } + *kr { + arg coefs, in, mul=1, add=0; + coefs = coefs.multichannelExpandRef(1); + ^this.multiNewList(['control', in, coefs]).madd(mul, add) + } *new1 { arg rate, in, coefs; - var derefCoefs = coefs.dereference; + var derefCoefs; + derefCoefs = coefs.dereference; + derefCoefs = derefCoefs.flop.flat; ^super.new.rate_(rate).addToSynth.init([in] ++ derefCoefs) } + init { + arg theInputs; + inputs = theInputs; + } } \ No newline at end of file diff --git a/src/filters/fir.cpp b/src/filters/fir.cpp index 2f00a76..f84e04f 100644 --- a/src/filters/fir.cpp +++ b/src/filters/fir.cpp @@ -23,37 +23,38 @@ along with this program. If not, see . */ #include "fir.hpp" +#include "SC_Unit.h" extern InterfaceTable *ft; -FlexPlugins::FIR::FIR() { - m_z = static_cast(RTAlloc(mWorld, fullBufferSize() * sizeof(float))); - for (size_t i = 0; i < fullBufferSize(); i++) { - m_z[i] = 0.f; +void FIR_Ctor(FIR *unit) { + unit->m_z = static_cast(RTAlloc(unit->mWorld, FULLBUFLENGTH * sizeof(float))); + for (size_t i = 0; i < FULLBUFLENGTH; i++) { + unit->m_z[i] = 0.f; } - set_calc_function(); - next(1); + SETCALC(FIR_next); + OUT0(0) = 0; } -FlexPlugins::FIR::~FIR() { - if (m_z) RTFree(mWorld, m_z); +void FIR_Dtor(FIR *unit) { + if (unit->m_z) RTFree(unit->mWorld, unit->m_z); } -void FlexPlugins::FIR::next(int nSamples) { - size_t numCoefs = static_cast(mNumInputs - 1); - numCoefs = sc_clip(numCoefs, 0, static_cast(fullBufferSize())); - const float *inBuf = in(0); - float *outBuf = out(0); - for (size_t i = 0; i < nSamples; i++) { +void FIR_next(FIR *unit, int inNumSamples) { + size_t numCoefs = static_cast(unit->mNumInputs - 1); + numCoefs = sc_clip(numCoefs, 0, static_cast(FULLBUFLENGTH)); + const float *inBuf = IN(0); + float *outBuf = OUT(0); + for (size_t xxi = 0; xxi < inNumSamples; xxi++) { float convResult = 0.f; // unit delay - for (size_t j = fullBufferSize() - 1; j > 0; j--) { - m_z[j] = m_z[j-1]; + for (size_t xxj = FULLBUFLENGTH - 1; xxj > 0; xxj--) { + unit->m_z[xxj] = unit->m_z[xxj-1]; } - m_z[0] = inBuf[i]; + unit->m_z[0] = inBuf[xxi]; // convolve - for (size_t k = 0; k < numCoefs; k++) { - convResult += in0(1+k) * m_z[k]; + for (size_t xxk = 0; xxk < numCoefs; xxk++) { + convResult += IN0(1+xxk) * unit->m_z[xxk]; } - outBuf[i] = convResult; + outBuf[xxi] = convResult; } } \ No newline at end of file diff --git a/src/filters/fir.hpp b/src/filters/fir.hpp index 0d65e00..0c563f3 100644 --- a/src/filters/fir.hpp +++ b/src/filters/fir.hpp @@ -24,18 +24,13 @@ along with this program. If not, see . #pragma once -#include "SC_PlugIn.hpp" +#include "SC_PlugIn.h" -namespace FlexPlugins { - class FIR : public SCUnit { - public: - FIR(); - ~FIR(); +struct FIR : public Unit { + float *m_z; + size_t m_delaySize; +}; - private: - void next(int nSamples); - - float *m_z; - size_t m_delaySize; - }; -} \ No newline at end of file +void FIR_Ctor(FIR *unit); +void FIR_Dtor(FIR *unit); +void FIR_next(FIR *unit, int inNumSamples); \ No newline at end of file From b184667a11a9d39d29e5f472373e2caf52864f2c Mon Sep 17 00:00:00 2001 From: Jeff Martin Date: Sun, 12 Jul 2026 13:52:03 -0500 Subject: [PATCH 3/3] refactored to class-based design --- src/filters/FIR.schelp | 21 +++++++++++++++++++-- src/filters/filters.cpp | 2 +- src/filters/fir.cpp | 35 +++++++++++++++++------------------ src/filters/fir.hpp | 16 +++++++++------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/filters/FIR.schelp b/src/filters/FIR.schelp index cc38930..0e25110 100644 --- a/src/filters/FIR.schelp +++ b/src/filters/FIR.schelp @@ -28,12 +28,29 @@ This scalar will be added to the signal Examples:: +A 5-tap lowpass filter code:: -( { var sig = Saw.ar(440); sig = FIR.ar(`[0.2, 0.2, 0.2, 0.2, 0.2], sig); }.play; -) +:: + +A 19-tap control signal smoother +code:: +{ + var sig; + sig = LFPulse.kr(10); + [sig, FIR.kr(`((1/19)!19), sig)]; +}.plot(0.3); +:: + +Changing coefficients +code:: +{ + var sig; + sig = LFPulse.kr(10); + [sig, FIR.kr(`[SinOsc.kr(10), SinOsc.kr(15)], sig)]; +}.plot(0.3); :: diff --git a/src/filters/filters.cpp b/src/filters/filters.cpp index fb6b31a..7877c63 100644 --- a/src/filters/filters.cpp +++ b/src/filters/filters.cpp @@ -29,5 +29,5 @@ InterfaceTable *ft; PluginLoad(flex_filters) { ft = inTable; - DefineDtorUnit(FIR); + registerUnit(ft, "FIR", false); } diff --git a/src/filters/fir.cpp b/src/filters/fir.cpp index f84e04f..c4216e1 100644 --- a/src/filters/fir.cpp +++ b/src/filters/fir.cpp @@ -23,37 +23,36 @@ along with this program. If not, see . */ #include "fir.hpp" -#include "SC_Unit.h" extern InterfaceTable *ft; -void FIR_Ctor(FIR *unit) { - unit->m_z = static_cast(RTAlloc(unit->mWorld, FULLBUFLENGTH * sizeof(float))); - for (size_t i = 0; i < FULLBUFLENGTH; i++) { - unit->m_z[i] = 0.f; +FIR::FIR() { + m_z = static_cast(RTAlloc(mWorld, fullBufferSize() * sizeof(float))); + for (size_t i = 0; i < fullBufferSize(); i++) { + m_z[i] = 0.f; } - SETCALC(FIR_next); - OUT0(0) = 0; + set_calc_function(); + next(1); } -void FIR_Dtor(FIR *unit) { - if (unit->m_z) RTFree(unit->mWorld, unit->m_z); +FIR::~FIR() { + if (m_z) RTFree(mWorld, m_z); } -void FIR_next(FIR *unit, int inNumSamples) { - size_t numCoefs = static_cast(unit->mNumInputs - 1); - numCoefs = sc_clip(numCoefs, 0, static_cast(FULLBUFLENGTH)); - const float *inBuf = IN(0); - float *outBuf = OUT(0); +void FIR::next(int inNumSamples) { + size_t numCoefs = static_cast(mNumInputs - 1); + numCoefs = sc_clip(numCoefs, 0, static_cast(fullBufferSize())); + const float *inBuf = in(0); + float *outBuf = out(0); for (size_t xxi = 0; xxi < inNumSamples; xxi++) { float convResult = 0.f; // unit delay - for (size_t xxj = FULLBUFLENGTH - 1; xxj > 0; xxj--) { - unit->m_z[xxj] = unit->m_z[xxj-1]; + for (size_t xxj = fullBufferSize() - 1; xxj > 0; xxj--) { + m_z[xxj] = m_z[xxj-1]; } - unit->m_z[0] = inBuf[xxi]; + m_z[0] = inBuf[xxi]; // convolve for (size_t xxk = 0; xxk < numCoefs; xxk++) { - convResult += IN0(1+xxk) * unit->m_z[xxk]; + convResult += in0(1+xxk) * m_z[xxk]; } outBuf[xxi] = convResult; } diff --git a/src/filters/fir.hpp b/src/filters/fir.hpp index 0c563f3..17368b7 100644 --- a/src/filters/fir.hpp +++ b/src/filters/fir.hpp @@ -24,13 +24,15 @@ along with this program. If not, see . #pragma once -#include "SC_PlugIn.h" +#include "SC_PlugIn.hpp" -struct FIR : public Unit { +class FIR : public SCUnit { +public: + FIR(); + ~FIR(); + +private: + void next(int inNumSamples); float *m_z; size_t m_delaySize; -}; - -void FIR_Ctor(FIR *unit); -void FIR_Dtor(FIR *unit); -void FIR_next(FIR *unit, int inNumSamples); \ No newline at end of file +}; \ No newline at end of file