"""
01_noise_meter.py — Осциллограмма и метр громкости в реальном времени.

Показывает звук во времени (осциллограмма) и текущий уровень в дБFS —
относительная шкала, БЕЗ калибровки в дБ SPL (см. THEORY.md, раздел
"Почему нет абсолютных дБ SPL"). Это самый простой инструмент — с него
стоит начинать: включить, постучать по микрофону, сказать что-то — и
сразу видно, что "звук" на самом деле дрожащая волна давления во времени.

Запуск:
    python3 01_noise_meter.py [/dev/ttyUSB0]
"""
import sys
from collections import deque

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

from serial_stream import open_port, read_int_lines, EFFECTIVE_RATE_HZ

PORT = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyUSB0"
WINDOW_SAMPLES = 2048

FULL_SCALE = 32767.0  # 0 дБFS — максимум 16-битной шкалы после Ics43434::toSample16()


def main():
    ser = open_port(PORT)
    print(f"Подключено к {PORT}. Эффективная частота потока: {EFFECTIVE_RATE_HZ} Гц.")
    print("Если график не двигается — отправьте 'p' в Serial Monitor (режим Plotter).")

    waveform = deque([0] * WINDOW_SAMPLES, maxlen=WINDOW_SAMPLES)
    time_axis = np.arange(-WINDOW_SAMPLES, 0) / EFFECTIVE_RATE_HZ

    fig, (ax_wave, ax_meter) = plt.subplots(2, 1, figsize=(9, 6), height_ratios=[3, 1])
    fig.suptitle("ICS-43434 — осциллограмма и метр громкости (дБFS, относительная шкала)")

    (wave_line,) = ax_wave.plot(time_axis, list(waveform), lw=1)
    ax_wave.set_xlim(time_axis[0], time_axis[-1])
    ax_wave.set_ylim(-32768, 32767)
    ax_wave.set_xlabel("Время, с")
    ax_wave.set_ylabel("Амплитуда, LSB")
    ax_wave.grid(alpha=0.3)

    meter_bar = ax_meter.barh(0, -60, height=0.6, color="seagreen")[0]
    ax_meter.set_xlim(-60, 0)
    ax_meter.set_yticks([])
    ax_meter.set_xlabel("Уровень, дБFS (0 = клиппинг шкалы)")
    ax_meter.axvline(-6, color="orange", ls="--", lw=1, alpha=0.6)
    ax_meter.axvline(-1, color="red", ls="--", lw=1, alpha=0.6)
    meter_text = ax_meter.text(0.02, 0.5, "", transform=ax_meter.transAxes, va="center")

    fig.tight_layout(rect=[0, 0, 1, 0.95])

    def update(_frame):
        values = read_int_lines(ser)
        if not values:
            return wave_line, meter_bar, meter_text

        for v in values:
            waveform.append(v)

        data = np.array(waveform, dtype=np.float64)
        wave_line.set_ydata(data)

        rms = np.sqrt(np.mean(data[-1024:] ** 2))
        dbfs = 20.0 * np.log10(rms / FULL_SCALE) if rms > 0 else -80.0
        dbfs = max(dbfs, -60.0)

        meter_bar.set_width(dbfs + 60)
        meter_bar.set_x(-60)
        color = "seagreen" if dbfs < -20 else ("orange" if dbfs < -6 else "crimson")
        meter_bar.set_color(color)
        meter_text.set_text(f"{dbfs:5.1f} дБFS   RMS={rms:6.0f} LSB")

        return wave_line, meter_bar, meter_text

    FuncAnimation(fig, update, interval=50, blit=False, cache_frame_data=False)
    plt.show()
    ser.close()


if __name__ == "__main__":
    main()
