9 Commits

Author SHA1 Message Date
admin fc74447ff6 Merge pull request #7 from metacube2/codex/add-natural-movement-to-vu-meter-tpnx0m
Add Natural+ ballistics, IEC transient-assist, persistent audio-device settings and UI/canvas tweaks
2026-04-05 20:43:04 +02:00
admin 4de361d3f9 Merge branch 'main' into codex/add-natural-movement-to-vu-meter-tpnx0m 2026-04-05 20:42:29 +02:00
admin dc359a9e61 Persist selected audio devices and auto-start meter on launch 2026-04-05 20:26:14 +02:00
admin df028d82d9 Merge pull request #5 from metacube2/codex/add-natural-movement-to-vu-meter-slvt30
Add Natural+ ballistic mode, UI preset, IEC transient assist and VU redraw tweaks
2026-04-05 19:13:50 +02:00
admin 32e2bc5794 Merge branch 'main' into codex/add-natural-movement-to-vu-meter-slvt30 2026-04-05 19:13:38 +02:00
admin 4dedcc9ec2 Fix VU needle direction and keep swing within visible area 2026-04-05 19:13:11 +02:00
admin 3c86e198a4 Merge pull request #3 from metacube2/codex/add-natural-movement-to-vu-meter
Add Natural VU preset and improve VU canvas positioning
2026-04-05 18:14:31 +02:00
admin 2778dab926 Add Natural+ mode with new needle ballistics formula 2026-04-05 18:14:17 +02:00
admin 7898fd07b8 Fix VU needle visibility by keeping pivot within canvas 2026-04-05 18:11:03 +02:00
+62 -7
View File
@@ -9,6 +9,7 @@ Audio-Passthrough und Physik-Visualisierung.
import requests, time, argparse, numpy as np, sys, math
import threading, json, psutil
from pathlib import Path
from flask import Flask, render_template_string, jsonify, request as flask_request
try:
@@ -28,6 +29,7 @@ disk_dial_uid = None
running = False
current_level = 0
current_peak = 0
SETTINGS_FILE = Path(__file__).with_name("vu1_audio_settings.json")
# ══════════════════════════════════════════════════════════════
# HTML / CSS / JS — Skeuomorphes VU-Meter
@@ -456,6 +458,11 @@ body {
Hüllkurve mit separatem Attack/Release + 2.-Ordnung Nadelmodell<br>
Natürliches Einschwingen, weniger Zappeln, weicher Rücklauf.
</div>
<div class="iec-box" id="natural-box" style="display:none">
<b>Natural+</b> — neue Ballistikformel<br>
Hüllkurve mit separatem Attack/Release + 2.-Ordnung Nadelmodell<br>
Natürliches Einschwingen, weniger Zappeln, weicher Rücklauf.
</div>
<!-- Physics sliders -->
<div id="physics-ctrls">
@@ -669,7 +676,7 @@ function drawVU(level, peak){
// Center pivot point (inside canvas so needle is always visible)
const cx = w/2;
const cy = h - 12;
const cy = h - 22;
const radius = Math.min(w * 0.45, h * 0.78);
// Scale arc (immer obere Hälfte, links -> rechts)
@@ -1076,6 +1083,9 @@ class PhysicsVU:
# Natural+ Formel (neue Ballistik)
self._natural_env = 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
@@ -1372,6 +1382,34 @@ class PhysicsVU:
return self.needle_pos, self.peak_pos
# ── Persistenz: zuletzt gewählte Audio-Geräte ──
def load_audio_settings():
defaults = {"audio_device_in": None, "audio_device_out": -1}
try:
if not SETTINGS_FILE.exists():
return defaults
data = json.loads(SETTINGS_FILE.read_text(encoding="utf-8"))
in_dev = data.get("audio_device_in", None)
out_dev = data.get("audio_device_out", -1)
if out_dev is None:
out_dev = -1
return {"audio_device_in": in_dev, "audio_device_out": out_dev}
except Exception as e:
print(f"⚠️ Konnte Audio-Settings nicht laden: {e}")
return defaults
def save_audio_settings(in_dev, out_dev):
try:
payload = {
"audio_device_in": in_dev,
"audio_device_out": out_dev if out_dev is not None else -1
}
SETTINGS_FILE.write_text(json.dumps(payload, indent=2), encoding="utf-8")
except Exception as e:
print(f"⚠️ Konnte Audio-Settings nicht speichern: {e}")
# ══════════════════════════════════════════════════════════════
# Flask Routes
# ══════════════════════════════════════════════════════════════
@@ -1419,6 +1457,7 @@ def set_device(which, dev_id):
if meter.start():
running = True
threading.Thread(target=update_loop, daemon=True).start()
save_audio_settings(app.config.get('audio_device_in'), app.config.get('audio_device_out', -1))
return jsonify({"ok": True})
@app.route('/toggle')
@@ -1484,7 +1523,6 @@ def set_param(param, value):
meter.mode = value
if value == 'iec_true':
meter._iec_z[:] = 0
meter._iec_fast = meter._latest_peak
if value == 'natural_formula':
meter._natural_env = meter.needle_pos
elif param == 'monitor':
@@ -1519,7 +1557,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_fast=0; meter._natural_env=0
meter._iec_z[:]=0; meter._iec_level=0; meter._natural_env=0
return jsonify({"ok": True})
@@ -1559,7 +1597,7 @@ def update_loop():
# ── Main ──
def main():
global client, dial_uid, meter
global client, dial_uid, meter, running
parser = argparse.ArgumentParser()
parser.add_argument("--api-key", required=True)
@@ -1569,8 +1607,13 @@ def main():
parser.add_argument("--port", type=int, default=8080)
args = parser.parse_args()
app.config['audio_device_in'] = args.audio_device
app.config['audio_device_out'] = args.output_device
saved = load_audio_settings()
chosen_in = args.audio_device if args.audio_device is not None else saved.get('audio_device_in')
chosen_out = args.output_device if args.output_device is not None else saved.get('audio_device_out', -1)
app.config['audio_device_in'] = chosen_in
app.config['audio_device_out'] = chosen_out if chosen_out is not None else -1
save_audio_settings(app.config['audio_device_in'], app.config['audio_device_out'])
client = VU1Client(api_key=args.api_key)
dials = client.get_dials()
@@ -1594,7 +1637,19 @@ def main():
globals()['disk_dial_uid'] = others[1]
print(f"✅ Disk-Dial: ({others[1][:8]}…)")
meter = PhysicsVU(device_in=args.audio_device, device_out=args.output_device)
out_for_meter = app.config['audio_device_out']
meter = PhysicsVU(
device_in=app.config['audio_device_in'],
device_out=out_for_meter if out_for_meter is not None and out_for_meter >= 0 else None
)
# Auto-Start mit gespeicherten Geräten
if meter.start():
running = True
threading.Thread(target=update_loop, daemon=True).start()
print("▶ Auto-Start aktiv (gespeicherte Audio-Ein-/Ausgänge)")
else:
print("⚠️ Auto-Start fehlgeschlagen; bitte Geräte prüfen und manuell starten.")
print(f"\n🎛️ VU1 Meter Web GUI v3")
print(f"{'='*40}")