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 requests, time, argparse, numpy as np, sys, math
import threading, json, psutil import threading, json, psutil
from pathlib import Path
from flask import Flask, render_template_string, jsonify, request as flask_request from flask import Flask, render_template_string, jsonify, request as flask_request
try: try:
@@ -28,6 +29,7 @@ disk_dial_uid = None
running = False running = False
current_level = 0 current_level = 0
current_peak = 0 current_peak = 0
SETTINGS_FILE = Path(__file__).with_name("vu1_audio_settings.json")
# ══════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════
# HTML / CSS / JS — Skeuomorphes VU-Meter # HTML / CSS / JS — Skeuomorphes VU-Meter
@@ -456,6 +458,11 @@ body {
Hüllkurve mit separatem Attack/Release + 2.-Ordnung Nadelmodell<br> Hüllkurve mit separatem Attack/Release + 2.-Ordnung Nadelmodell<br>
Natürliches Einschwingen, weniger Zappeln, weicher Rücklauf. Natürliches Einschwingen, weniger Zappeln, weicher Rücklauf.
</div> </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 --> <!-- Physics sliders -->
<div id="physics-ctrls"> <div id="physics-ctrls">
@@ -669,7 +676,7 @@ function drawVU(level, peak){
// Center pivot point (inside canvas so needle is always visible) // Center pivot point (inside canvas so needle is always visible)
const cx = w/2; const cx = w/2;
const cy = h - 12; const cy = h - 22;
const radius = Math.min(w * 0.45, h * 0.78); const radius = Math.min(w * 0.45, h * 0.78);
// Scale arc (immer obere Hälfte, links -> rechts) // Scale arc (immer obere Hälfte, links -> rechts)
@@ -1076,6 +1083,9 @@ class PhysicsVU:
# Natural+ Formel (neue Ballistik) # Natural+ Formel (neue Ballistik)
self._natural_env = 0.0 self._natural_env = 0.0
# Natural+ Formel (neue Ballistik)
self._natural_env = 0.0
# ── Biquad helpers ── # ── Biquad helpers ──
def _calc_biquad(self, fc): def _calc_biquad(self, fc):
w0 = 2.0 * math.pi * fc / self.sample_rate w0 = 2.0 * math.pi * fc / self.sample_rate
@@ -1372,6 +1382,34 @@ class PhysicsVU:
return self.needle_pos, self.peak_pos 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 # Flask Routes
# ══════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════
@@ -1419,6 +1457,7 @@ def set_device(which, dev_id):
if meter.start(): if meter.start():
running = True running = True
threading.Thread(target=update_loop, daemon=True).start() 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}) return jsonify({"ok": True})
@app.route('/toggle') @app.route('/toggle')
@@ -1484,7 +1523,6 @@ def set_param(param, value):
meter.mode = value meter.mode = value
if value == 'iec_true': if value == 'iec_true':
meter._iec_z[:] = 0 meter._iec_z[:] = 0
meter._iec_fast = meter._latest_peak
if value == 'natural_formula': if value == 'natural_formula':
meter._natural_env = meter.needle_pos meter._natural_env = meter.needle_pos
elif param == 'monitor': elif param == 'monitor':
@@ -1519,7 +1557,7 @@ def reset():
meter.needle_pos=0; meter.needle_vel=0; meter.peak_pos=0 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.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.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}) return jsonify({"ok": True})
@@ -1559,7 +1597,7 @@ def update_loop():
# ── Main ── # ── Main ──
def main(): def main():
global client, dial_uid, meter global client, dial_uid, meter, running
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--api-key", required=True) parser.add_argument("--api-key", required=True)
@@ -1569,8 +1607,13 @@ def main():
parser.add_argument("--port", type=int, default=8080) parser.add_argument("--port", type=int, default=8080)
args = parser.parse_args() args = parser.parse_args()
app.config['audio_device_in'] = args.audio_device saved = load_audio_settings()
app.config['audio_device_out'] = args.output_device 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) client = VU1Client(api_key=args.api_key)
dials = client.get_dials() dials = client.get_dials()
@@ -1594,7 +1637,19 @@ def main():
globals()['disk_dial_uid'] = others[1] globals()['disk_dial_uid'] = others[1]
print(f"✅ Disk-Dial: ({others[1][:8]}…)") 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"\n🎛️ VU1 Meter Web GUI v3")
print(f"{'='*40}") print(f"{'='*40}")