Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc74447ff6 | |||
| 4de361d3f9 | |||
| dc359a9e61 | |||
| df028d82d9 | |||
| 32e2bc5794 | |||
| 4dedcc9ec2 |
+78
-8
@@ -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
|
||||
@@ -449,7 +451,12 @@ body {
|
||||
<b>IEC True Ballistics</b> — Lobdell-Modell<br>
|
||||
|x| → Biquad LPF 2.224 Hz, Q 0.6053<br>
|
||||
300ms Anstiegszeit, ~1% Überschwingen<br>
|
||||
<span style="color:#555">Der Filter <i>ist</i> die Nadelphysik.</span>
|
||||
<span style="color:#555">Der Filter <i>ist</i> die Nadelphysik (+ kurzer Transient-Assist).</span>
|
||||
</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>
|
||||
<div class="iec-box" id="natural-box" style="display:none">
|
||||
<b>Natural+</b> — neue Ballistikformel<br>
|
||||
@@ -672,8 +679,8 @@ function drawVU(level, peak){
|
||||
const cy = h - 22;
|
||||
const radius = Math.min(w * 0.45, h * 0.78);
|
||||
|
||||
// Scale arc
|
||||
const arcStart = Math.PI + 0.35;
|
||||
// Scale arc (immer obere Hälfte, links -> rechts)
|
||||
const arcStart = -2.80;
|
||||
const arcEnd = -0.35;
|
||||
|
||||
// Draw scale markings
|
||||
@@ -1070,6 +1077,11 @@ class PhysicsVU:
|
||||
self._iec_coeffs = self._calc_biquad_lpf(2.224, 0.6053)
|
||||
self._iec_z = np.zeros(2)
|
||||
self._iec_level = 0.0
|
||||
self._iec_fast = 0.0
|
||||
self.iec_transient_boost = 0.22
|
||||
|
||||
# Natural+ Formel (neue Ballistik)
|
||||
self._natural_env = 0.0
|
||||
|
||||
# Natural+ Formel (neue Ballistik)
|
||||
self._natural_env = 0.0
|
||||
@@ -1156,7 +1168,19 @@ class PhysicsVU:
|
||||
if self.mode == 'iec_true':
|
||||
rect = np.abs(mono).astype(np.float64)
|
||||
filt = self._biquad_process(self._iec_coeffs, self._iec_z, rect)
|
||||
self._iec_level = float(filt[-1])
|
||||
# Kleiner schneller Pfad gegen subjektiven Bass-Delay
|
||||
block_dt = max(1.0 / self.sample_rate, len(rect) / self.sample_rate)
|
||||
block_peak = float(np.max(rect))
|
||||
atk_t = 0.012
|
||||
rel_t = 0.140
|
||||
alpha_a = 1.0 - math.exp(-block_dt / atk_t)
|
||||
alpha_r = 1.0 - math.exp(-block_dt / rel_t)
|
||||
alpha = alpha_a if block_peak > self._iec_fast else alpha_r
|
||||
self._iec_fast += (block_peak - self._iec_fast) * alpha
|
||||
|
||||
iec_slow = float(filt[-1])
|
||||
self._iec_level = ((1.0 - self.iec_transient_boost) * iec_slow +
|
||||
self.iec_transient_boost * self._iec_fast)
|
||||
|
||||
# ── Audio Output ──
|
||||
# Monitor: Signal hören (mit Filter/Solo je nach Mode)
|
||||
@@ -1358,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
|
||||
# ══════════════════════════════════════════════════════════════
|
||||
@@ -1405,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')
|
||||
@@ -1544,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)
|
||||
@@ -1554,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()
|
||||
@@ -1579,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}")
|
||||
|
||||
Reference in New Issue
Block a user