package com.ts3client.ui; import javax.swing.JComponent; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; /** * Horizontal audio level meter (dBFS) with an optional VAD threshold marker. * The filled portion turns green once the level crosses the threshold, giving * immediate visual feedback while tuning voice activation. */ public final class LevelMeter extends JComponent { private static final double MIN_DB = -70.0; private static final double MAX_DB = 0.0; private volatile double levelDb = MIN_DB; private volatile double thresholdDb = -45.0; private volatile boolean showThreshold = true; public LevelMeter() { setPreferredSize(new Dimension(240, 18)); } public void setLevel(double db) { this.levelDb = db; repaint(); } public void setThreshold(double db) { this.thresholdDb = db; repaint(); } public void setShowThreshold(boolean show) { this.showThreshold = show; repaint(); } private int dbToX(double db, int w) { double clamped = Math.max(MIN_DB, Math.min(MAX_DB, db)); return (int) ((clamped - MIN_DB) / (MAX_DB - MIN_DB) * w); } @Override protected void paintComponent(Graphics g0) { Graphics2D g = (Graphics2D) g0; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int w = getWidth(); int h = getHeight(); g.setColor(new Color(0x2B2B2B)); g.fillRoundRect(0, 0, w - 1, h - 1, 6, 6); int level = dbToX(levelDb, w - 2); boolean over = levelDb >= thresholdDb; g.setColor(over ? Theme.TALKING : new Color(0x5A9BD4)); g.fillRoundRect(1, 1, Math.max(0, level), h - 3, 5, 5); if (showThreshold) { int tx = dbToX(thresholdDb, w - 2); g.setColor(new Color(0xF0C419)); g.fillRect(tx, 1, 2, h - 3); } } }