| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- const socket = io();
- const state = {
- mode: "beamforming",
- gain_db: 0,
- agc: true,
- attack_ms: 6,
- release_ms: 280,
- noise_suppression: true,
- speech_gate: false,
- hum_filter: true,
- limiter: true,
- beam_clarity: true,
- hifi_mode: false,
- hifi_mic: "mic1",
- angle: 0,
- auto_beam: true,
- auto_angle: 0,
- speech_detected: false,
- monitor_on: false,
- monitor_source: "beam",
- sample_rate: 16000,
- recording: false,
- recDuration: 0,
- record_duration_sec: 0,
- buffers: {
- mic1: [],
- mic2: [],
- beam: [],
- mono_mix: [],
- },
- };
- const MAX_POINTS = 3500;
- const els = {
- status: document.getElementById("serverStatus"),
- audioStatus: document.getElementById("audioStatus"),
- waveMic1: document.getElementById("waveMic1"),
- waveMic2: document.getElementById("waveMic2"),
- waveBeam: document.getElementById("waveBeam"),
- beamBlock: document.getElementById("beamBlock"),
- vuMic1: document.getElementById("vuMic1"),
- vuMic2: document.getElementById("vuMic2"),
- vuBeam: document.getElementById("vuBeam"),
- gainDb: document.getElementById("gainDb"),
- gainValue: document.getElementById("gainValue"),
- agcEnabled: document.getElementById("agcEnabled"),
- agcControls: document.getElementById("agcControls"),
- attackMs: document.getElementById("attackMs"),
- attackValue: document.getElementById("attackValue"),
- releaseMs: document.getElementById("releaseMs"),
- releaseValue: document.getElementById("releaseValue"),
- noiseSuppression: document.getElementById("noiseSuppression"),
- speechGate: document.getElementById("speechGate"),
- humFilter: document.getElementById("humFilter"),
- limiterEnabled: document.getElementById("limiterEnabled"),
- beamClarity: document.getElementById("beamClarity"),
- hifiMode: document.getElementById("hifiMode"),
- hifiMic: document.getElementById("hifiMic"),
- beamControls: document.getElementById("beamControls"),
- beamAuto: document.getElementById("beamAuto"),
- manualBeamRow: document.getElementById("manualBeamRow"),
- beamAngle: document.getElementById("beamAngle"),
- angleValue: document.getElementById("angleValue"),
- autoAngleValue: document.getElementById("autoAngleValue"),
- speechState: document.getElementById("speechState"),
- monitorOn: document.getElementById("monitorOn"),
- monitorSource: document.getElementById("monitorSource"),
- sampleRate: document.getElementById("sampleRate"),
- recordBtn: document.getElementById("recordBtn"),
- recordSource: document.getElementById("recordSource"),
- recordDurationSec: document.getElementById("recordDurationSec"),
- recordTimer: document.getElementById("recordTimer"),
- recordingsBody: document.getElementById("recordingsBody"),
- };
- const monitorAudio = {
- context: null,
- nextTime: 0,
- };
- function clamp(value, min, max) {
- return Math.min(Math.max(value, min), max);
- }
- function formatDuration(totalSeconds) {
- const sec = Math.max(0, Math.floor(totalSeconds));
- const h = String(Math.floor(sec / 3600)).padStart(2, "0");
- const m = String(Math.floor((sec % 3600) / 60)).padStart(2, "0");
- const s = String(sec % 60).padStart(2, "0");
- return `${h}:${m}:${s}`;
- }
- function formatBytes(bytes) {
- if (bytes < 1024) return `${bytes} B`;
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
- return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
- }
- function pushWaveData(key, samples) {
- if (!Array.isArray(samples) || samples.length === 0) return;
- const target = state.buffers[key];
- target.push(...samples);
- if (target.length > MAX_POINTS) {
- target.splice(0, target.length - MAX_POINTS);
- }
- }
- function drawWave(canvas, samples, color) {
- const ctx = canvas.getContext("2d");
- const width = canvas.width;
- const height = canvas.height;
- ctx.clearRect(0, 0, width, height);
- ctx.strokeStyle = "rgba(140, 190, 200, 0.35)";
- ctx.lineWidth = 1;
- ctx.beginPath();
- ctx.moveTo(0, height / 2);
- ctx.lineTo(width, height / 2);
- ctx.stroke();
- if (!samples.length) return;
- const step = samples.length / width;
- ctx.strokeStyle = color;
- ctx.lineWidth = 1.7;
- ctx.beginPath();
- for (let x = 0; x < width; x += 1) {
- const idx = Math.floor(x * step);
- const sample = clamp(samples[idx] ?? 0, -1, 1);
- const y = (1 - (sample + 1) / 2) * height;
- if (x === 0) {
- ctx.moveTo(x, y);
- } else {
- ctx.lineTo(x, y);
- }
- }
- ctx.stroke();
- }
- function setVu(el, rms) {
- const db = 20 * Math.log10(Math.max(rms, 1e-6));
- const norm = clamp((db + 60) / 60, 0, 1);
- el.style.width = `${Math.round(norm * 100)}%`;
- }
- function ensureMonitorContext() {
- if (!monitorAudio.context) {
- const Ctx = window.AudioContext || window.webkitAudioContext;
- if (!Ctx) return null;
- monitorAudio.context = new Ctx({ sampleRate: 48000 });
- }
- return monitorAudio.context;
- }
- function stopMonitorPlayback() {
- const ctx = monitorAudio.context;
- if (!ctx) return;
- monitorAudio.nextTime = ctx.currentTime;
- }
- function decodePcm16Base64(base64Chunk) {
- if (!base64Chunk) return null;
- const binary = atob(base64Chunk);
- const len = binary.length;
- if (len < 2) return null;
- const samples = new Float32Array(Math.floor(len / 2));
- for (let i = 0, s = 0; i + 1 < len; i += 2, s += 1) {
- let value = (binary.charCodeAt(i) | (binary.charCodeAt(i + 1) << 8));
- if (value & 0x8000) value -= 0x10000;
- samples[s] = value / 32768.0;
- }
- return samples;
- }
- function playMonitorChunk(base64Chunk, sampleRate) {
- const chunk = decodePcm16Base64(base64Chunk);
- if (!chunk || chunk.length === 0) return;
- const ctx = ensureMonitorContext();
- if (!ctx) return;
- if (ctx.state === "suspended") {
- void ctx.resume();
- }
- const now = ctx.currentTime;
- if (monitorAudio.nextTime < now + 0.02) {
- monitorAudio.nextTime = now + 0.02;
- } else if (monitorAudio.nextTime > now + 0.6) {
- monitorAudio.nextTime = now + 0.05;
- }
- const buffer = ctx.createBuffer(1, chunk.length, sampleRate);
- buffer.copyToChannel(chunk, 0);
- const src = ctx.createBufferSource();
- src.buffer = buffer;
- src.connect(ctx.destination);
- src.start(monitorAudio.nextTime);
- monitorAudio.nextTime += buffer.duration;
- }
- function sendSettings() {
- const payload = {
- type: "settings",
- mode: state.mode,
- gain_db: state.gain_db,
- agc: state.agc,
- attack_ms: state.attack_ms,
- release_ms: state.release_ms,
- noise_suppression: state.noise_suppression,
- speech_gate: state.speech_gate,
- hum_filter: state.hum_filter,
- limiter: state.limiter,
- beam_clarity: state.beam_clarity,
- hifi_mode: state.hifi_mode,
- hifi_mic: state.hifi_mic,
- angle: state.angle,
- auto_beam: state.auto_beam,
- monitor_on: state.monitor_on,
- monitor_source: state.monitor_source,
- sample_rate: state.sample_rate,
- };
- socket.emit("client_message", payload);
- }
- function refreshControlVisibility() {
- const hifi = state.hifi_mode;
- const beamVisible = state.mode === "beamforming" && !hifi;
- els.beamControls.classList.toggle("hidden", !beamVisible);
- els.beamBlock.classList.toggle("hidden", !beamVisible);
- if (els.manualBeamRow) {
- els.manualBeamRow.classList.toggle("hidden", !beamVisible || state.auto_beam);
- }
- if (els.monitorSource) {
- els.monitorSource.disabled = hifi || !state.monitor_on;
- }
- els.agcControls.classList.toggle("hidden", hifi || !state.agc);
- document.querySelectorAll("input[name='mode']").forEach((radio) => {
- radio.disabled = hifi;
- });
- if (els.gainDb) els.gainDb.disabled = hifi;
- if (els.agcEnabled) els.agcEnabled.disabled = hifi;
- if (els.attackMs) els.attackMs.disabled = hifi || !state.agc;
- if (els.releaseMs) els.releaseMs.disabled = hifi || !state.agc;
- if (els.noiseSuppression) els.noiseSuppression.disabled = hifi;
- if (els.speechGate) els.speechGate.disabled = hifi;
- if (els.humFilter) els.humFilter.disabled = hifi;
- if (els.limiterEnabled) els.limiterEnabled.disabled = hifi;
- if (els.beamClarity) els.beamClarity.disabled = hifi;
- if (els.beamAuto) els.beamAuto.disabled = hifi || !beamVisible;
- if (els.beamAngle) els.beamAngle.disabled = hifi || !beamVisible || state.auto_beam;
- if (els.sampleRate) els.sampleRate.disabled = hifi;
- if (els.monitorOn) els.monitorOn.disabled = hifi;
- if (els.hifiMic) els.hifiMic.disabled = !hifi;
- if (els.recordSource) {
- if (hifi) {
- els.recordSource.value = "hifi_raw";
- els.recordSource.disabled = true;
- } else {
- els.recordSource.disabled = false;
- if (els.recordSource.value === "hifi_raw") {
- els.recordSource.value = "beam";
- }
- }
- }
- }
- function syncUiFromState() {
- document.querySelectorAll("input[name='mode']").forEach((radio) => {
- radio.checked = radio.value === state.mode;
- });
- els.gainDb.value = String(state.gain_db);
- els.gainValue.textContent = String(state.gain_db);
- els.agcEnabled.checked = state.agc;
- els.attackMs.value = String(state.attack_ms);
- els.attackValue.textContent = String(state.attack_ms);
- els.releaseMs.value = String(state.release_ms);
- els.releaseValue.textContent = String(state.release_ms);
- if (els.noiseSuppression) {
- els.noiseSuppression.checked = state.noise_suppression;
- }
- if (els.speechGate) {
- els.speechGate.checked = state.speech_gate;
- }
- if (els.humFilter) {
- els.humFilter.checked = state.hum_filter;
- }
- if (els.limiterEnabled) {
- els.limiterEnabled.checked = state.limiter;
- }
- if (els.beamClarity) {
- els.beamClarity.checked = state.beam_clarity;
- }
- if (els.hifiMode) {
- els.hifiMode.checked = state.hifi_mode;
- }
- if (els.hifiMic) {
- els.hifiMic.value = state.hifi_mic;
- }
- els.beamAngle.value = String(state.angle);
- els.angleValue.textContent = String(state.angle);
- if (els.beamAuto) {
- els.beamAuto.checked = state.auto_beam;
- }
- if (els.autoAngleValue) {
- els.autoAngleValue.textContent = Number(state.auto_angle).toFixed(1);
- }
- if (els.speechState) {
- els.speechState.textContent = state.speech_detected ? "mowa" : "cisza";
- }
- if (els.monitorOn) {
- els.monitorOn.checked = state.monitor_on;
- }
- if (els.monitorSource) {
- els.monitorSource.value = state.monitor_source;
- }
- els.sampleRate.value = String(state.sample_rate);
- els.recordBtn.classList.toggle("recording", state.recording);
- els.recordBtn.textContent = state.recording ? "STOP" : "RECORD";
- refreshControlVisibility();
- }
- async function loadStatus() {
- const response = await fetch("/api/status");
- const data = await response.json();
- if (data.settings) {
- state.mode = data.settings.mode;
- state.gain_db = data.settings.gain_db;
- state.agc = data.settings.agc;
- state.attack_ms = data.settings.attack_ms;
- state.release_ms = data.settings.release_ms;
- state.noise_suppression = Boolean(data.settings.noise_suppression);
- state.speech_gate = Boolean(data.settings.speech_gate);
- state.hum_filter = Boolean(data.settings.hum_filter);
- state.limiter = Boolean(data.settings.limiter);
- state.beam_clarity = Boolean(data.settings.beam_clarity);
- state.hifi_mode = Boolean(data.settings.hifi_mode);
- state.hifi_mic = data.settings.hifi_mic || "mic1";
- state.angle = data.settings.angle;
- state.auto_beam = Boolean(data.settings.auto_beam);
- state.monitor_on = Boolean(data.settings.monitor_on);
- state.monitor_source = data.settings.monitor_source || "beam";
- state.sample_rate = data.settings.sample_rate;
- }
- state.auto_angle = Number(data.auto_beam_angle_deg ?? state.angle ?? 0);
- state.speech_detected = false;
- state.recording = Boolean(data.recording);
- els.audioStatus.textContent = data.audio_error
- ? `Audio: blad (${data.audio_error})`
- : data.audio_running
- ? "Audio: aktywne"
- : "Audio: zatrzymane";
- syncUiFromState();
- }
- async function loadRecordings() {
- const response = await fetch("/api/recordings");
- const recordings = await response.json();
- els.recordingsBody.innerHTML = "";
- recordings.forEach((rec) => {
- const tr = document.createElement("tr");
- const nameTd = document.createElement("td");
- nameTd.textContent = rec.filename;
- const dateTd = document.createElement("td");
- dateTd.textContent = rec.date;
- const durTd = document.createElement("td");
- durTd.textContent = formatDuration(rec.duration_sec || 0);
- const sizeTd = document.createElement("td");
- sizeTd.textContent = formatBytes(rec.size_bytes || 0);
- const actionsTd = document.createElement("td");
- actionsTd.className = "actions";
- const playBtn = document.createElement("button");
- playBtn.className = "btn-small";
- playBtn.textContent = "Play";
- playBtn.addEventListener("click", () => {
- const audio = new Audio(`/api/recordings/${encodeURIComponent(rec.filename)}`);
- void audio.play();
- });
- const dlBtn = document.createElement("a");
- dlBtn.className = "btn-small";
- dlBtn.textContent = "Download";
- dlBtn.href = `/api/recordings/${encodeURIComponent(rec.filename)}`;
- const delBtn = document.createElement("button");
- delBtn.className = "btn-small danger";
- delBtn.textContent = "Delete";
- delBtn.addEventListener("click", async () => {
- await fetch(`/api/recordings/${encodeURIComponent(rec.filename)}`, {
- method: "DELETE",
- });
- await loadRecordings();
- });
- actionsTd.append(playBtn, dlBtn, delBtn);
- tr.append(nameTd, dateTd, durTd, sizeTd, actionsTd);
- els.recordingsBody.appendChild(tr);
- });
- }
- function bindControls() {
- document.querySelectorAll("input[name='mode']").forEach((radio) => {
- radio.addEventListener("change", () => {
- state.mode = radio.value;
- refreshControlVisibility();
- sendSettings();
- });
- });
- els.gainDb.addEventListener("input", () => {
- state.gain_db = Number(els.gainDb.value);
- els.gainValue.textContent = String(state.gain_db);
- });
- els.gainDb.addEventListener("change", sendSettings);
- els.agcEnabled.addEventListener("change", () => {
- state.agc = els.agcEnabled.checked;
- refreshControlVisibility();
- sendSettings();
- });
- els.attackMs.addEventListener("input", () => {
- state.attack_ms = Number(els.attackMs.value);
- els.attackValue.textContent = String(state.attack_ms);
- });
- els.attackMs.addEventListener("change", sendSettings);
- els.releaseMs.addEventListener("input", () => {
- state.release_ms = Number(els.releaseMs.value);
- els.releaseValue.textContent = String(state.release_ms);
- });
- els.releaseMs.addEventListener("change", sendSettings);
- if (els.noiseSuppression) {
- els.noiseSuppression.addEventListener("change", () => {
- state.noise_suppression = els.noiseSuppression.checked;
- sendSettings();
- });
- }
- if (els.speechGate) {
- els.speechGate.addEventListener("change", () => {
- state.speech_gate = els.speechGate.checked;
- sendSettings();
- });
- }
- if (els.humFilter) {
- els.humFilter.addEventListener("change", () => {
- state.hum_filter = els.humFilter.checked;
- sendSettings();
- });
- }
- if (els.limiterEnabled) {
- els.limiterEnabled.addEventListener("change", () => {
- state.limiter = els.limiterEnabled.checked;
- sendSettings();
- });
- }
- if (els.beamClarity) {
- els.beamClarity.addEventListener("change", () => {
- state.beam_clarity = els.beamClarity.checked;
- sendSettings();
- });
- }
- if (els.hifiMode) {
- els.hifiMode.addEventListener("change", () => {
- state.hifi_mode = els.hifiMode.checked;
- if (state.hifi_mode) {
- state.monitor_on = false;
- }
- refreshControlVisibility();
- sendSettings();
- });
- }
- if (els.hifiMic) {
- els.hifiMic.addEventListener("change", () => {
- state.hifi_mic = els.hifiMic.value;
- sendSettings();
- });
- }
- els.beamAngle.addEventListener("input", () => {
- state.angle = Number(els.beamAngle.value);
- els.angleValue.textContent = String(state.angle);
- });
- els.beamAngle.addEventListener("change", sendSettings);
- if (els.beamAuto) {
- els.beamAuto.addEventListener("change", () => {
- state.auto_beam = els.beamAuto.checked;
- refreshControlVisibility();
- sendSettings();
- });
- }
- els.sampleRate.addEventListener("change", () => {
- state.sample_rate = Number(els.sampleRate.value);
- sendSettings();
- });
- els.recordBtn.addEventListener("click", () => {
- if (!state.recording) {
- const duration = Number(els.recordDurationSec?.value || 0);
- state.record_duration_sec = Number.isFinite(duration) ? Math.max(0, duration) : 0;
- socket.emit("client_message", {
- type: "record_start",
- source: els.recordSource.value,
- duration_sec: state.record_duration_sec,
- });
- return;
- }
- socket.emit("client_message", { type: "record_stop" });
- });
- if (els.recordDurationSec) {
- els.recordDurationSec.addEventListener("change", () => {
- const duration = Number(els.recordDurationSec.value || 0);
- state.record_duration_sec = Number.isFinite(duration)
- ? clamp(duration, 0, 3600)
- : 0;
- els.recordDurationSec.value = String(Math.round(state.record_duration_sec));
- });
- }
- if (els.monitorOn) {
- els.monitorOn.addEventListener("change", () => {
- state.monitor_on = els.monitorOn.checked;
- if (state.monitor_on) {
- const ctx = ensureMonitorContext();
- if (ctx && ctx.state === "suspended") {
- void ctx.resume();
- }
- } else {
- stopMonitorPlayback();
- }
- refreshControlVisibility();
- sendSettings();
- });
- }
- if (els.monitorSource) {
- els.monitorSource.addEventListener("change", () => {
- state.monitor_source = els.monitorSource.value;
- sendSettings();
- });
- }
- }
- function animate() {
- drawWave(els.waveMic1, state.buffers.mic1, "#32b3ff");
- drawWave(els.waveMic2, state.buffers.mic2, "#50d27a");
- if (state.mode === "beamforming") {
- drawWave(els.waveBeam, state.buffers.beam, "#ff6d5a");
- }
- els.recordTimer.textContent = formatDuration(state.recDuration);
- requestAnimationFrame(animate);
- }
- function bindSocket() {
- socket.on("connect", () => {
- els.status.textContent = "WebSocket: polaczono";
- });
- socket.on("disconnect", () => {
- els.status.textContent = "WebSocket: rozlaczono";
- });
- socket.on("status", (payload) => {
- if (payload?.settings) {
- state.mode = payload.settings.mode;
- state.gain_db = payload.settings.gain_db;
- state.agc = payload.settings.agc;
- state.attack_ms = payload.settings.attack_ms;
- state.release_ms = payload.settings.release_ms;
- state.noise_suppression = Boolean(payload.settings.noise_suppression);
- state.speech_gate = Boolean(payload.settings.speech_gate);
- state.hum_filter = Boolean(payload.settings.hum_filter);
- state.limiter = Boolean(payload.settings.limiter);
- state.beam_clarity = Boolean(payload.settings.beam_clarity);
- state.hifi_mode = Boolean(payload.settings.hifi_mode);
- state.hifi_mic = payload.settings.hifi_mic || "mic1";
- state.angle = payload.settings.angle;
- state.auto_beam = Boolean(payload.settings.auto_beam);
- state.monitor_on = Boolean(payload.settings.monitor_on);
- state.monitor_source = payload.settings.monitor_source || "beam";
- state.sample_rate = payload.settings.sample_rate;
- syncUiFromState();
- }
- });
- socket.on("audio_data", (payload) => {
- const wasRecording = state.recording;
- pushWaveData("mic1", payload.mic1 || []);
- pushWaveData("mic2", payload.mic2 || []);
- pushWaveData("beam", payload.beam || []);
- pushWaveData("mono_mix", payload.mono_mix || []);
- setVu(els.vuMic1, payload.rms_mic1 || 0);
- setVu(els.vuMic2, payload.rms_mic2 || 0);
- setVu(els.vuBeam, payload.rms_beam || payload.rms_mono_mix || 0);
- state.recording = Boolean(payload.recording);
- state.recDuration = Number(payload.rec_duration || 0);
- const gateOpen = Boolean(payload.speech_gate_open ?? payload.speech_detected);
- state.speech_detected = gateOpen;
- state.hifi_mode = Boolean(payload.hifi_mode ?? state.hifi_mode);
- state.auto_angle = Number(payload.beam_angle_deg ?? state.auto_angle);
- state.monitor_on = Boolean(payload.monitor_on ?? state.monitor_on);
- state.monitor_source = payload.monitor_source || state.monitor_source;
- if (state.auto_beam) {
- state.angle = state.auto_angle;
- els.angleValue.textContent = Number(state.angle).toFixed(1);
- }
- if (els.autoAngleValue) {
- els.autoAngleValue.textContent = Number(state.auto_angle).toFixed(1);
- }
- if (els.speechState) {
- els.speechState.textContent = state.hifi_mode
- ? "hifi"
- : state.speech_detected
- ? "mowa/gate"
- : "cisza";
- }
- els.recordBtn.classList.toggle("recording", state.recording);
- els.recordBtn.textContent = state.recording ? "STOP" : "RECORD";
- if (state.monitor_on && payload.monitor_chunk_b64) {
- const sr = Number(payload.monitor_sr || state.sample_rate || 16000);
- playMonitorChunk(payload.monitor_chunk_b64, sr);
- }
- if (wasRecording && !state.recording) {
- state.recDuration = 0;
- void loadRecordings();
- }
- });
- socket.on("recordings_updated", async () => {
- await loadRecordings();
- });
- socket.on("server_ack", (payload) => {
- if (payload?.type === "record_started") {
- state.recording = true;
- els.recordBtn.classList.add("recording");
- els.recordBtn.textContent = "STOP";
- void loadRecordings();
- }
- if (payload?.type === "record_stopped") {
- state.recording = false;
- els.recordBtn.classList.remove("recording");
- els.recordBtn.textContent = "RECORD";
- state.recDuration = 0;
- void loadRecordings();
- }
- if (payload?.type === "settings_applied" && payload.settings) {
- state.mode = payload.settings.mode;
- state.gain_db = payload.settings.gain_db;
- state.agc = payload.settings.agc;
- state.attack_ms = payload.settings.attack_ms;
- state.release_ms = payload.settings.release_ms;
- state.noise_suppression = Boolean(payload.settings.noise_suppression);
- state.speech_gate = Boolean(payload.settings.speech_gate);
- state.hum_filter = Boolean(payload.settings.hum_filter);
- state.limiter = Boolean(payload.settings.limiter);
- state.beam_clarity = Boolean(payload.settings.beam_clarity);
- state.hifi_mode = Boolean(payload.settings.hifi_mode);
- state.hifi_mic = payload.settings.hifi_mic || "mic1";
- state.angle = payload.settings.angle;
- state.auto_beam = Boolean(payload.settings.auto_beam);
- state.monitor_on = Boolean(payload.settings.monitor_on);
- state.monitor_source = payload.settings.monitor_source || "beam";
- state.sample_rate = payload.settings.sample_rate;
- refreshControlVisibility();
- syncUiFromState();
- }
- });
- socket.on("server_error", (payload) => {
- if (payload?.message) {
- els.audioStatus.textContent = `Audio: blad (${payload.message})`;
- }
- });
- }
- async function init() {
- bindControls();
- bindSocket();
- await loadStatus();
- await loadRecordings();
- syncUiFromState();
- requestAnimationFrame(animate);
- }
- void init();
|