From 2778dab92637e4509e187a05965c311d445ea9e3 Mon Sep 17 00:00:00 2001 From: Metacube Date: Sun, 5 Apr 2026 18:14:17 +0200 Subject: [PATCH] Add Natural+ mode with new needle ballistics formula --- vu1_gui.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/vu1_gui.py b/vu1_gui.py index 3fbe65e..758aa5f 100644 --- a/vu1_gui.py +++ b/vu1_gui.py @@ -410,6 +410,7 @@ body { + @@ -450,6 +451,11 @@ body { 300ms Anstiegszeit, ~1% Überschwingen
Der Filter ist die Nadelphysik. +
@@ -562,10 +568,11 @@ function setMode(mode){ fetch('/set/mode/'+mode); document.getElementById('dualband-box').style.display=mode==='dualband'?'block':'none'; document.getElementById('iec-box').style.display=mode==='iec_true'?'block':'none'; - document.getElementById('physics-ctrls').style.display=mode==='iec_true'?'none':'block'; - ['full','dual','iec'].forEach(m=>{ + document.getElementById('natural-box').style.display=mode==='natural_formula'?'block':'none'; + document.getElementById('physics-ctrls').style.display=(mode==='iec_true'||mode==='natural_formula')?'none':'block'; + ['full','dual','iec','natural'].forEach(m=>{ const btn=document.getElementById('mode-'+m); - const act=(m==='full'&&mode==='full')||(m==='dual'&&mode==='dualband')||(m==='iec'&&mode==='iec_true'); + const act=(m==='full'&&mode==='full')||(m==='dual'&&mode==='dualband')||(m==='iec'&&mode==='iec_true')||(m==='natural'&&mode==='natural_formula'); btn.classList.toggle('active', act); }); } @@ -1064,6 +1071,9 @@ class PhysicsVU: self._iec_z = np.zeros(2) self._iec_level = 0.0 + # Natural+ Formel (neue Ballistik) + self._natural_env = 0.0 + # ── Biquad helpers ── def _calc_biquad(self, fc): w0 = 2.0 * math.pi * fc / self.sample_rate @@ -1270,6 +1280,50 @@ class PhysicsVU: self.peak_pos = max(0.0, min(100.0, self.peak_pos)) return self.needle_pos, self.peak_pos + # Natural+ (eigene Formel): + # 1) getrennte Attack/Release-Hüllkurve + # 2) 2.-Ordnung Nadelmodell für natürliches Ein-/Ausschwingen + if self.mode == 'natural_formula': + target = self._rms_to_percent(self._latest_rms) + + atk_t = 0.085 # schneller Angriff + rel_t = 0.650 # weicher Rücklauf + alpha_a = 1.0 - math.exp(-dt / atk_t) + alpha_r = 1.0 - math.exp(-dt / rel_t) + alpha = alpha_a if target > self._natural_env else alpha_r + self._natural_env += (target - self._natural_env) * alpha + + # 2nd-order Needle (kritisch nahe gedämpft) + stiffness = 58.0 + damping = 14.5 + acc = stiffness * (self._natural_env - self.needle_pos) - damping * self.needle_vel + self.needle_vel += acc * dt + self.needle_pos += self.needle_vel * dt + self.needle_pos = max(0.0, min(100.0, self.needle_pos)) + + # Bei sehr kleinen Pegeln ruhig auf 0 setzen (kein Mikrozappeln) + if self._natural_env < 0.3 and self.needle_pos < 0.3: + self._natural_env = 0.0 + self.needle_pos = 0.0 + self.needle_vel *= 0.6 + + self._target = self._natural_env + self._f_spring = 0.0 + self._f_damping = 0.0 + self._f_gravity = 0.0 + + peak_t = self._rms_to_percent(self._latest_peak) + if peak_t > self.peak_pos: + self.peak_pos = peak_t + self.peak_hold_t = 1.2 + else: + if self.peak_hold_t > 0: + self.peak_hold_t -= dt + else: + self.peak_pos -= 16.0 * dt + self.peak_pos = max(0.0, min(100.0, self.peak_pos)) + return self.needle_pos, self.peak_pos + # Target if self.mode == 'dualband': target = min(100.0, self._rms_to_percent(self._latest_rms_lo)*self.band_lo_weight + @@ -1414,7 +1468,10 @@ def set_param(param, value): if not meter: return jsonify({"ok": False}) if param == 'mode': meter.mode = value - if value == 'iec_true': meter._iec_z[:] = 0 + if value == 'iec_true': + meter._iec_z[:] = 0 + if value == 'natural_formula': + meter._natural_env = meter.needle_pos elif param == 'monitor': meter.monitor = value == '1' elif param == 'bypass': @@ -1447,7 +1504,7 @@ def reset(): meter.needle_pos=0; meter.needle_vel=0; meter.peak_pos=0 meter.mode='full'; meter.monitor=False; meter.bypass=False; meter.solo='off' meter.band_crossover=250; meter.band_lo_weight=0.6; meter.band_hi_weight=0.4 - meter._iec_z[:]=0; meter._iec_level=0 + meter._iec_z[:]=0; meter._iec_level=0; meter._natural_env=0 return jsonify({"ok": True})