From 4d367c7e4c96aca6e1c31ae794fc3263140ff2c0 Mon Sep 17 00:00:00 2001 From: Martin Klang Date: Sun, 25 Oct 2026 10:20:48 +0000 Subject: [PATCH 1/5] Dockerfile: install hvcc's python2 deps into python2, not python3 heavy.mk runs the pingdynasty hvcc fork under python2.7, whose requirements (enum==0.4.4, Jinja2==2.7.3) are python2 packages. The previous 'pip3 install -r Tools/hvcc/requirements.txt || true' installed them into python3 and swallowed any error, so python2.7 never had 'enum' and every Heavy/PD build failed with 'ImportError: No module named enum'. ubuntu 20.04's python2 ships without pip, so bootstrap pip for python2 (get-pip.py 2.7) and install the fork's requirements into python2. python3 is kept: emscripten 2.0.10's emcc execs 'python3 emcc.py', so the 'make web' / WASM path needs it (verified: hiding python3 breaks emcc). Verified on OWL2: HeavyTest.pd builds via the py2 Heavy path (patch.bin) AND make web builds patch.js via the py3 emcc path. --- Dockerfile | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f834fb2..3c33f70a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -175,8 +175,17 @@ RUN mkdir -p Tools \ RUN ln -sf /usr/local/bin/soul /owl/Tools/soul \ && ln -sf /usr/local/bin/FirmwareSender /owl/Tools/FirmwareSender -# hvcc deps (non-fatal). -RUN pip3 install --no-cache-dir -r Tools/hvcc/requirements.txt || true +# hvcc deps. The pingdynasty hvcc fork runs under python2.7 (heavy.mk invokes +# `python2.7 hvcc.py`), and its requirements (enum==0.4.4, Jinja2==2.7.3) are +# python2 packages. ubuntu 20.04 ships `python2` WITHOUT pip, so bootstrap pip +# for python2 first, then install the fork's requirements into python2 — not +# python3 (a `pip3 install` here left python2.7 without `enum` and silently +# broke every Heavy/PD build). +RUN curl -fsSL https://bootstrap.pypa.io/pip/2.7/get-pip.py -o /tmp/get-pip.py \ + && python2 /tmp/get-pip.py \ + && python2 -m pip install --no-cache-dir -r Tools/hvcc/requirements.txt \ + && rm -f /tmp/get-pip.py \ + && python2 -c "import enum, jinja2; print('hvcc py2 deps OK')" # Pre-build the patch libraries for the target platform so a later # `make PATCHNAME=X patch` links immediately (no separate `make libs` needed). From 273b54dcab674b46f8de5be970f27bd42820fec0 Mon Sep 17 00:00:00 2001 From: Martin Klang Date: Sun, 25 Oct 2026 11:44:59 +0000 Subject: [PATCH 2/5] PatchRun: process the whole WAV file, not just the first half WavFile::getNumberOfSamples() returns frames per channel, but PatchRun used it as the int16 count of the interleaved buffer. For the stereo input that PatchRun requires, the end pointer landed halfway through the data, so the second half of every output file was the unprocessed input. Span frames*channels, and step the loop guard by a whole interleaved block. Verified with HeavyTest on a 1 s stereo file: 47872 of the 48000 int16s in the second half matched the input before the fix, 1 after. --- TestPatches/PatchRun.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TestPatches/PatchRun.cpp b/TestPatches/PatchRun.cpp index 5b78fb9c..e76f0279 100644 --- a/TestPatches/PatchRun.cpp +++ b/TestPatches/PatchRun.cpp @@ -62,8 +62,10 @@ int main(int argc, char** argv){ ASSERT(wav.getBitsPerSample() == 16, "Incorrect number of bits per sample in input file"); ASSERT(wav.isValid(), "Invalid wav"); int16_t* src = (int16_t*)wav.getData(); - int16_t* end = src+wav.getNumberOfSamples(); - while(src+BLOCKSIZE <= end){ + // getNumberOfSamples() is frames per channel; the buffer holds + // frames*channels interleaved int16s. + int16_t* end = src+wav.getNumberOfSamples()*channels; + while(src+BLOCKSIZE*channels <= end){ samples->split16(src, BLOCKSIZE); processor.patch->processAudio(*samples); samples->comb16(src); From b4d97314cb04380c7917c6def31ffbf80dde1035 Mon Sep 17 00:00:00 2001 From: Martin Klang Date: Sun, 25 Oct 2026 11:44:47 +0000 Subject: [PATCH 3/5] SmoothValue: make lambda a float for every value type lambda is a dimensionless coefficient, but SmoothValue declared it as T. For int types it was initialised from the Q12 DEFAULT_LAMBDA (3686), and update() mixed value*3686 + x*(1-3686) without ever rescaling, so integer smoothing produced garbage. PatchProcessor also assigned the float result of normal() into the int lambda, truncating it. - SmoothValue::lambda is float (default 0.9f); the (lambda, init) constructor and normal() take/return float. - PatchProcessor::getParameter keeps l as float. - Remove SimpleValue::DEFAULT_LAMBDA, which nothing uses any more. - Replace the doc comment's unimplemented int formula with the single implemented one, and correct the mean-lag note. - int/weirdint tests assert the actual behaviour: float lambda, one-step smoothing, monotonic convergence and the truncated settling value. --- LibSource/SimpleValue.cpp | 6 ------ LibSource/SimpleValue.h | 3 --- LibSource/SmoothValue.h | 16 ++++++++++------ Source/PatchProcessor.cpp | 2 +- TestPatches/SmoothValueTestPatch.hpp | 26 ++++++++++++++++++-------- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/LibSource/SimpleValue.cpp b/LibSource/SimpleValue.cpp index d06c6e7a..5c9f2436 100644 --- a/LibSource/SimpleValue.cpp +++ b/LibSource/SimpleValue.cpp @@ -1,19 +1,13 @@ #include "SimpleValue.h" #include "math.h" -/** - * The int constants are meant to provide reasonable defaults for use - * with OWL parameters, which are generally 12 bit fixed point, i.e. Q12. - */ #define Q12_FACTOR ((float)(1<<12)) -template<> const float SimpleValue::DEFAULT_LAMBDA = 0.9; template<> const float SimpleValue::DEFAULT_DELTA = 0.02; template<> const float SimpleValue::DEFAULT_EXP_C = 1; template<> const float SimpleValue::DEFAULT_EXP_K = 2*M_LN2; template<> const float SimpleValue::DEFAULT_LOG_C = 1.6487212707; template<> const float SimpleValue::DEFAULT_LOG_K = 5.74033482823*M_LN2; -template<> const int SimpleValue::DEFAULT_LAMBDA = (int)(0.9*Q12_FACTOR); template<> const int SimpleValue::DEFAULT_DELTA = (int)(0.02*Q12_FACTOR); template<> const int SimpleValue::DEFAULT_EXP_C = (int)(1*Q12_FACTOR); template<> const int SimpleValue::DEFAULT_EXP_K = (int)(2*M_LN2*Q12_FACTOR); diff --git a/LibSource/SimpleValue.h b/LibSource/SimpleValue.h index a60e3713..6773c0a5 100644 --- a/LibSource/SimpleValue.h +++ b/LibSource/SimpleValue.h @@ -48,7 +48,6 @@ class SimpleValue { operator T(){ return get(); } - static const T DEFAULT_LAMBDA; static const T DEFAULT_DELTA; static const T DEFAULT_EXP_C; static const T DEFAULT_EXP_K; @@ -57,13 +56,11 @@ class SimpleValue { }; // prevent -Wundefined-var-template warnings -template<> const float SimpleValue::DEFAULT_LAMBDA; template<> const float SimpleValue::DEFAULT_DELTA; template<> const float SimpleValue::DEFAULT_EXP_C; template<> const float SimpleValue::DEFAULT_EXP_K; template<> const float SimpleValue::DEFAULT_LOG_C; template<> const float SimpleValue::DEFAULT_LOG_K; -template<> const int SimpleValue::DEFAULT_LAMBDA; template<> const int SimpleValue::DEFAULT_DELTA; template<> const int SimpleValue::DEFAULT_EXP_C; template<> const int SimpleValue::DEFAULT_EXP_K; diff --git a/LibSource/SmoothValue.h b/LibSource/SmoothValue.h index b1665756..4b22831b 100644 --- a/LibSource/SmoothValue.h +++ b/LibSource/SmoothValue.h @@ -5,19 +5,23 @@ /** * Applies exponential smoothing to a scalar value. - * y(n) = lambda*y(n-1) + (1.0-lambda)*y(n) for floats - * y(n) = (lambda*y(n-1) + y(n))/(lambda+1) for ints + * y(n) = lambda*y(n-1) + (1-lambda)*x(n), with 0 <= lambda < 1, + * where x(n) is the new input and y(n) the smoothed output. * Smoothing is applied when the value is written to, not when it is read. - * todo: adjust for sampling frequency / blocksize (time delay of the response (mean) is 1/α data points) + * + * lambda is a dimensionless coefficient and is always a float, independent of + * the value type T. + * todo: adjust lambda for sample rate / blocksize; for input weight + * alpha = 1-lambda the mean lag of the response is lambda/(1-lambda) samples. * https://en.wikipedia.org/wiki/Exponential_smoothing */ template> class SmoothValue : public Value { public: - T lambda = Value::DEFAULT_LAMBDA; + float lambda = 0.9f; SmoothValue() {} SmoothValue(T value) : Value(value) {} - SmoothValue(T lambda, T init) : Value(init), lambda(lambda) {} + SmoothValue(float lambda, T init) : Value(init), lambda(lambda) {} SmoothValue(const SmoothValue& other) : Value(other), lambda(other.lambda) {} void update(T x){ Value::update(Value::get()*lambda + x*(1.0f - lambda)); @@ -45,7 +49,7 @@ class SmoothValue : public Value { operator T(){ return Value::get(); } - static T normal(float lambda, int blocksize); + static float normal(float lambda, int blocksize); }; template class SmoothValue; diff --git a/Source/PatchProcessor.cpp b/Source/PatchProcessor.cpp index a0257ed8..189da5fc 100644 --- a/Source/PatchProcessor.cpp +++ b/Source/PatchProcessor.cpp @@ -136,7 +136,7 @@ PatchParameter PatchProcessor::getParameter(const char* name, T min, T max, T if(parameters[pid] != NULL) delete parameters[pid]; ParameterUpdater* updater = NULL; - T l = SmoothValue::normal(lambda, blocksize); + float l = SmoothValue::normal(lambda, blocksize); T d = StiffValue::normal(delta)*abs(max-min); if(skew == 1.0){ if(lambda == 0.0 && delta == 0.0){ diff --git a/TestPatches/SmoothValueTestPatch.hpp b/TestPatches/SmoothValueTestPatch.hpp index 1252126a..8cb8099e 100644 --- a/TestPatches/SmoothValueTestPatch.hpp +++ b/TestPatches/SmoothValueTestPatch.hpp @@ -23,24 +23,34 @@ class SmoothValueTestPatch : public TestPatch { CHECK_CLOSE(a, -1, DEFAULT_TOLERANCE); // arrives near target } { -// typedef SmoothValue SmoothInt; TEST("int"); SmoothValue> a = 5; CHECK_CLOSE(a, 5, DEFAULT_TOLERANCE); - CHECK_CLOSE(a.lambda, 0.9, DEFAULT_TOLERANCE); // default lambda - a = 0; + CHECK_CLOSE(a.lambda, 0.9, DEFAULT_TOLERANCE); a.lambda = 0.6; - CHECK_CLOSE(a, 5*0.9, DEFAULT_TOLERANCE); - for(int i=0; i<32; ++i) + a = 0; + CHECK_CLOSE(a, 3, DEFAULT_TOLERANCE); + int prev = a; + for(int i=0; i<32; ++i){ a = -10; - CHECK_CLOSE(a, -10, DEFAULT_TOLERANCE); + CHECK((int)a <= prev); + prev = a; + } + CHECK_CLOSE(a, -8, DEFAULT_TOLERANCE); } { TEST("weirdint"); SmoothInt a = 5; CHECK_CLOSE(a, 5, DEFAULT_TOLERANCE); - CHECK_CLOSE(a.lambda, 0.9, DEFAULT_TOLERANCE); // default lambda - a = 0; + CHECK_CLOSE(a.lambda, 0.9, DEFAULT_TOLERANCE); + a.lambda = 0.6; + int prev = a; + for(int i=0; i<32; ++i){ + a = 100; + CHECK((int)a >= prev); + prev = a; + } + CHECK_CLOSE(a, 98, DEFAULT_TOLERANCE); } } }; From 96624c079d8bbeb512d02de4aeb53c59232c2097 Mon Sep 17 00:00:00 2001 From: Martin Klang Date: Sun, 25 Oct 2026 12:59:25 +0000 Subject: [PATCH 4/5] SmoothValue: define SmoothValue::normal() and StiffValue::normal() Both were declared, and called from PatchProcessor::getParameter, but their definitions went away with SmoothValue.cpp in the SimpleValue refactor. Any patch that calls getFloatParameter() or getIntParameter() failed to link with an undefined reference (e.g. BeatCounter, BigJotReverb). - SmoothValue::normal(lambda, blocksize) returns powf(lambda, blocksize/128), so a lambda given for 128-sample blocks keeps the same time constant at other block sizes and stays below 1. - StiffValue::normal(delta) returns delta as a float, so an int parameter's fractional delta survives until it is scaled by the range. - Test the normal() values. --- LibSource/SmoothValue.h | 8 ++++++-- TestPatches/SmoothValueTestPatch.hpp | 8 ++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/LibSource/SmoothValue.h b/LibSource/SmoothValue.h index 4b22831b..52690adb 100644 --- a/LibSource/SmoothValue.h +++ b/LibSource/SmoothValue.h @@ -49,7 +49,9 @@ class SmoothValue : public Value { operator T(){ return Value::get(); } - static float normal(float lambda, int blocksize); + static float normal(float lambda, int blocksize){ + return powf(lambda, blocksize/128.0f); + } }; template class SmoothValue; @@ -96,7 +98,9 @@ class StiffValue : public Value { operator T(){ return Value::get(); } - static T normal(float delta); + static float normal(float delta){ + return delta; + } }; template class StiffValue; diff --git a/TestPatches/SmoothValueTestPatch.hpp b/TestPatches/SmoothValueTestPatch.hpp index 8cb8099e..a7fd8677 100644 --- a/TestPatches/SmoothValueTestPatch.hpp +++ b/TestPatches/SmoothValueTestPatch.hpp @@ -52,6 +52,14 @@ class SmoothValueTestPatch : public TestPatch { } CHECK_CLOSE(a, 98, DEFAULT_TOLERANCE); } + { + TEST("normal"); + CHECK_CLOSE(SmoothFloat::normal(0.9, 128), 0.9, DEFAULT_TOLERANCE); + CHECK_CLOSE(SmoothFloat::normal(0.9, 64), sqrtf(0.9), DEFAULT_TOLERANCE); + CHECK_CLOSE(SmoothInt::normal(0.81, 256), 0.81*0.81, DEFAULT_TOLERANCE); + CHECK(SmoothFloat::normal(0.95, 16) < 1); + CHECK_CLOSE(StiffInt::normal(0.5), 0.5, DEFAULT_TOLERANCE); + } } }; From 7e40c465bcce88336a622c3081221010c19f6fce Mon Sep 17 00:00:00 2001 From: Martin Klang Date: Sun, 25 Oct 2026 14:40:52 +0100 Subject: [PATCH 5/5] Update HISTORY --- HISTORY.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 929843eb..e7c8ccf4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,11 @@ +v23.1 +----- + +* Fixed missing SmoothValue and StiffValue::normal definitions +* Fixed SmoothValue to use float lambda +* Fixed Dockerfile hvcc build + + v23.0 -----