diff --git a/includes/library/JamomaLowpassOnePole.h b/includes/library/JamomaLowpassOnePole.h index 263f472..5a6728d 100644 --- a/includes/library/JamomaLowpassOnePole.h +++ b/includes/library/JamomaLowpassOnePole.h @@ -16,12 +16,13 @@ namespace Jamoma { /** This AudioObject applies a basic low-pass filter to a Sample or SampleBundle. + Handy for use in smoothing control signals or damping high frequency components. */ class LowpassOnePole : public AudioObject { - AdaptingSampleBundle mY1 = {this}; ///< previous output sample for each channel - double mCoefficientF; ///< filter coefficient - double mOneMinusCoefficientF; ///< 1 - mCoefficientF + AdaptingSampleBundle mY1 = {this}; ///< previous output sample (for each channel) + double mA0; ///< gain coefficient + double mB1; ///< feedback coefficient public: static constexpr Classname classname = { "lowpass.1" }; @@ -34,12 +35,18 @@ namespace Jamoma { 0.5, Range(0.0, 1.0), [this]{ - mCoefficientF = coefficient; - mOneMinusCoefficientF = 1 - coefficient; + mA0 = coefficient; + mB1 = 1 - coefficient; } }; + /** Set filter coefficient using a cutoff frequency. + @see http://musicdsp.org/showArchiveComment.php?ArchiveID=237 + */ + Parameter frequency = { this, "frequency", 1000.0, [this]{coefficient = 1.0 - exp(-2.0 * 3.1415 * frequency / sampleRate);} }; + + /** This algorithm is an IIR filter, meaning that it relies on feedback. If the filter should not be producing any signal (such as turning audio off and then back on in a host) or if the feedback has become corrupted (such as might happen if a NaN is fed in) then it may be @@ -61,7 +68,7 @@ namespace Jamoma { Sample operator()(Sample x, int channel) { - Sample y = (x * mCoefficientF) + (mY1[channel][0] * mOneMinusCoefficientF); // compute next output sample + Sample y = (x * mA0) + (mY1[channel][0] * mB1); // compute next output sample ZeroDenormal(y); mY1[channel][0] = y; // update history diff --git a/tests/LowpassOnePole/LowpassOnePole.cpp b/tests/LowpassOnePole/LowpassOnePole.cpp index 69a9dd3..61cd6b4 100644 --- a/tests/LowpassOnePole/LowpassOnePole.cpp +++ b/tests/LowpassOnePole/LowpassOnePole.cpp @@ -132,6 +132,84 @@ class LowpassOnePoleTest { my_lowpass.coefficient = 1.2; mTest->TEST_ASSERT("high coefficient limiting", my_lowpass.coefficient == 1.0); + + + // Testing coefficient setting via frequency + // The following Max 7 patcher can be convenient as an interactive reference for the + // relationship between frequency response and coefficient + /* + +

+		 ----------begin_max5_patcher----------
+		 2115.3oc6asrjiZCEcc2eEJplEcRY6fdAlYURVjJUMS9BRkpKYiraxfAG.mz
+		 8L0ju8nm.1FroaaHdQVLF8BoiNb0U2G87k6uCtH6YQAD7dvuAt6tub+c2oaR
+		 0vc152A2vedYBuPOL3FQQAes.NwzWo34Rc6XOOWawQ5VxV7GSCpZrn7kDgtc
+		 WKY6JSDkkurUXVeHD761tR2sINU1odIw1F2xKW9Tb55GyEKKMuBwmMyaB.6o
+		 9kfzkIy7plms4hBQZIuLNKcuWiV+ZteZt3FnoWcjpwud+8pelzSNJU72xM+Q
+		 Tj34s4.zLvTfrzCSwx076.jYHJhIK7tUHv2CnTjm2rusEpze9kSkntoRJSSD
+		 gdFR7Xt7xokSH5H2ysskoCpziOogzCcdekd7I9CpzypjL4jzFcPdMzwD.bAO
+		 c8aVR.gYlGdGQLqxx2v0i1uZZx4aDkh7GEo7EFr4cZZjxLLNB0CdDeEE2Prt
+		 D2PCp3FK3MItwlqE2PgCj3VmzTmmJGVc5Lq.2qkln3A8T41jrx+oE1fE5Zju
+		 KJNak5bPQ7m06ejGlNwMW4qiSs2zp2TyqgKqITuBTH0vcgZ4MeGSx1mO1lEm
+		 ZlIFxMW4REFhD9BQhd+8q70owk6hDfGh9op6jhx1vkvnZT+bt3O2IRW9B3ge
+		 4yUi5.Fuw5JIx81.E6VT0lxJjpO.x9Vlkjka5vaFch6m.l7.Q81QI19T7xOk
+		 JLerTZTbcn2lOVKollk5j0k8lDmJZzopJOutaIbWH0o00auJNQpxS2AuLKZQ
+		 cOFNR9h7b82DuFHx1mHUKDgwdrV5sAlxVez7Jkl9jH2xXtY.XtGUqqvUXtqP
+		 fqfuq.yUf5JPbEvtBHWA2L6lX275lV2r5lT2b5lRbCj4.lCWNX4PkCTNL4fj
+		 CQN.YwiENVzXAiEKVnXQhEHVbXggEEVPXwfEBVDz3ZP45aW9ZiklaWayR6NO
+		 OwdJjzPaF5P8R0eQ0GmbeP0CSZVDrBuFyqqnQH9CvFeuk89AXi4TeLtV3aJh
+		 faHgY50J94cTGceZvz+dxd5keJRq5cJVaU8Ti4QSMptmZXjoAlgL2LjPyPPd
+		 ysuucdT37n8QSpwrcUTgcQgxmvp0FJeBqf.T9DVgDn7IrBPP80XNbAkOgUvC
+		 JeBqPIT9DVCVnp.rFzJ.XvCwAHBt4Gir7Xol+Ge9P1119K11MMatOpSMfAJo
+		 G7b0ujv+WEXkX3oOO8exIiSJF+1EO.+9UzDOZGV3wHCpEdzflV30au1olypi
+		 tgv3tXI7vxR3SyRCnilLuwzQSlwLU1E5l4k6v3xrMajxcGI.rpx.23TvSetE
+		 BidhXyzW1v52igMr1ffQc9A26JJgKWKTa6J+AU.2YJmwsXBsu5ABI5wyFY0.
+		 dyBaiiXCJGgoM4Hb3LV+3H7bbMoNpjDqMRZXiiGx3n6qmjPFIO+QmjZ8z1vd
+		 oqyqlNIoAKr2emzLq1t+j9ZtkQdWk5pfW84Gjc6Z7EiL.2h10sFRyjAayRDf
+		 jr+dqbv.qoxGSDjvK+1CpKv8l8q01g46caZZYUfolOyavuUAAdWax4jfgUio
+		 8PsQbmv56sJAFqKwCV.D6hldGBzVlyHCrNyfyPSCn4kD7nXdoSR.2HlL2l1W
+		 JDQK3K+DXYlX0p3kwxAAdXwin1RCHw6xUVfCZxLsYqYWGRrWhPl2iCIdWuqP
+		 9lofVuBAGNdWg32URlGtqPVykNbrmPA+Qu1DJPjqfPgMRkcKTb4ed6Tkv3nQ
+		 vY7yMiFgtRpDoxyqqediNA+DtWhivAmKwQTT64MhuYahNuQckxnB0HDEmNWQ
+		 CQdgXcFTzkw4KS5Nrnlvw8VBK59cz+nhROcHQOHBjsEUTZi61sGtajAZiZMS
+		 JIX8JuDl3cCTlqXeOHxjUh57UXVGn5VJ65Ck14ehHv5MC2U7WIrKK0Dx2W8q
+		 Nl45hX0OHcQjd+xrWhM0Tdpt8olwnP1IChqF5.ndbP2KqphTUsYpvTT0QU6X
+		 cEroBQWg.fibHg6RyCE+ehlGeS3+5slmNSY81m3EBvC47nXdZQm4rNUonOQ5
+		 MTDnJ5d2RZiFnTz7V0EgtTUQVSEQUmBIUrj9Ln+DW1szI25nbacFsQUGpf1o
+		 UVlBsSrrrOzN0xxpj2gLuK5jIMcnxLjKcoFUnSsoI0jyTSdTOVabmYBUq4wl
+		 3QiZHaJLo5J1LX5qqXye4b.rZ0UYyDre1TGcUQcXyuQrMRTxiSZyhwpvHnBr
+		 QpzHMcW+XdLO4UZL4Q+6sZXIkpeD1VpLZD7ERGAew+5QdQwKKmsU4SYwxmDa
+		 3uG3nyh30o.45Jx4kY4uGDIRyjaRSkenT9ccsTREf.31Hc+qBoqPmzwRd9Kc
+		 P0jyS017hgCeabcS2zzuo9j6A+kPqWdU66+AnHaW9R2dw8mlKnF.RRtTwnxs
+		 XyAQ1aPOEGIo9ltSDEWn7vHpamq5KdB75AdXdiFdX8AOGPhCId7o2V7yAhFs
+		 iG73AGzMF8biINqt5473Ib7NdguwNtStsvC0+l53kJkw2RvgdaAmaKcgz9n6
+		 gLd3AG1C7PGuyVj9nKTY1H.ON3g1W7fFG7zGcyGPhCp7SefC8lBN3w6lTbut
+		 oHXbszfdtOW9mAOahipibl5uoHlJ7Kz.cjBBo5+TPz0pWnq6N.cN8U3W2N.E
+		 FpBVDMP67muD5LasytCL95w2t8ujd4ZQgF7R2p+CSP7BlnqFmZppc6GlK9qX
+		 230nBxyktlVJ8KcWtwc2ms+2UDtIKRjmtK15moj1t25V7A9WV4.cIeqwYYIA
+		 shuKobeVsoy1RJ6iRm0AeLd8SprYZ1aSreTxERR5.GvgajSXriHzAo4q2+u.
+		 80vVA
+		 -----------end_max5_patcher-----------
+		 
+ + */ + + my_lowpass.sampleRate = 96000; + + my_lowpass.frequency = 1000.0; // hz + mTest->TEST_ASSERT("coefficient for 1K cutoff @ 96K fs", mTest->compare((double)my_lowpass.coefficient, 0.06335217076965427)); + + my_lowpass.frequency = 4000.0; // hz + mTest->TEST_ASSERT("coefficient for 1K cutoff @ 96K fs", mTest->compare((double)my_lowpass.coefficient, 0.23032864479520065)); + + my_lowpass.sampleRate = 44100; + + my_lowpass.frequency = 1000.0; // hz + mTest->TEST_ASSERT("coefficient for 1K cutoff @ 44.1K fs", mTest->compare((double)my_lowpass.coefficient, 0.132787865213287)); + + my_lowpass.frequency = 4000.0; // hz + mTest->TEST_ASSERT("coefficient for 1K cutoff @ 44.1K fs", mTest->compare((double)my_lowpass.coefficient, 0.43441043913502342)); + } };