feat/frontend #1
@@ -11,7 +11,7 @@ type LiveStreamState = {
|
|||||||
lastMessageAt: number | null;
|
lastMessageAt: number | null;
|
||||||
|
|
||||||
// WS frequency control
|
// WS frequency control
|
||||||
requestedHz: number; // 1..60
|
requestedHz: number; // 0.1-60
|
||||||
setRequestedHz: (hz: number) => void;
|
setRequestedHz: (hz: number) => void;
|
||||||
|
|
||||||
// Window selection
|
// Window selection
|
||||||
@@ -33,14 +33,14 @@ const PEAK_WINDOW_MS = 3_000;
|
|||||||
const WINDOW_OPTIONS_MS = [15_000, 30_000, 60_000, 120_000, 300_000] as const;
|
const WINDOW_OPTIONS_MS = [15_000, 30_000, 60_000, 120_000, 300_000] as const;
|
||||||
const DEFAULT_WINDOW_MS = 60_000;
|
const DEFAULT_WINDOW_MS = 60_000;
|
||||||
|
|
||||||
const MIN_HZ = 1;
|
const MIN_HZ = 0.1;
|
||||||
const MAX_HZ = 60;
|
const MAX_HZ = 60;
|
||||||
const DEFAULT_REQUESTED_HZ = 10;
|
const DEFAULT_REQUESTED_HZ = 10;
|
||||||
|
|
||||||
// If there are more than 600 points in selected window -> downsample
|
// If there are more than 600 points in selected window -> downsample
|
||||||
const MAX_CHART_POINTS = 600;
|
const MAX_CHART_POINTS = 600;
|
||||||
|
|
||||||
// UI updates not more than ~12 Hz (10–15 Hz recommended)
|
// UI updates not more than ~12 Hz
|
||||||
const UI_FLUSH_HZ = 12;
|
const UI_FLUSH_HZ = 12;
|
||||||
const UI_FLUSH_MS = Math.round(1000 / UI_FLUSH_HZ);
|
const UI_FLUSH_MS = Math.round(1000 / UI_FLUSH_HZ);
|
||||||
|
|
||||||
@@ -58,10 +58,12 @@ let lastSeenSample: AudioSample | null = null;
|
|||||||
|
|
||||||
function clampInt(v: number, min: number, max: number): number {
|
function clampInt(v: number, min: number, max: number): number {
|
||||||
if (!Number.isFinite(v)) return min;
|
if (!Number.isFinite(v)) return min;
|
||||||
return Math.max(min, Math.min(max, Math.trunc(v)));
|
return Math.max(min, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAllowedWindowMs(ms: number): ms is (typeof WINDOW_OPTIONS_MS)[number] {
|
function isAllowedWindowMs(
|
||||||
|
ms: number,
|
||||||
|
): ms is (typeof WINDOW_OPTIONS_MS)[number] {
|
||||||
return (WINDOW_OPTIONS_MS as readonly number[]).includes(ms);
|
return (WINDOW_OPTIONS_MS as readonly number[]).includes(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +83,8 @@ function buildWsUrl(base: string, hz: number): string {
|
|||||||
|
|
||||||
function trimPeakWindow(nowSampleMs: number): void {
|
function trimPeakWindow(nowSampleMs: number): void {
|
||||||
const cutoff = nowSampleMs - PEAK_WINDOW_MS;
|
const cutoff = nowSampleMs - PEAK_WINDOW_MS;
|
||||||
while (peakWindow.length && peakWindow[0]!.timeMs < cutoff) peakWindow.shift();
|
while (peakWindow.length && peakWindow[0]!.timeMs < cutoff)
|
||||||
|
peakWindow.shift();
|
||||||
|
|
||||||
// Safety cap: even at 60 Hz, 3 sec ~ 180 points; allow some jitter
|
// Safety cap: even at 60 Hz, 3 sec ~ 180 points; allow some jitter
|
||||||
if (peakWindow.length > 512) peakWindow = peakWindow.slice(-512);
|
if (peakWindow.length > 512) peakWindow = peakWindow.slice(-512);
|
||||||
@@ -255,4 +258,3 @@ export const useLiveStreamStore = create<LiveStreamState>()((set, get) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ export function DashboardPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="mx-auto w-full max-w-6xl p-4 md:p-6">
|
<div className="mx-auto w-full max-w-6xl p-4 md:p-6">
|
||||||
<header className="mb-4">
|
<header className="mb-4">
|
||||||
<h1 className="text-2xl font-semibold">STM32 Audio Dashboard</h1>
|
<h1 className="text-2xl font-semibold">STM32 Audio Analyze Dashboard</h1>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
WebSocket 10 Hz: rms_db [-50..0], freq_hz [20..8000]
|
Частоты: 129 Гц - 5,5 кГц; Громкость: -50 - 0 дБ
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { freqToNote } from "../../../entities/audioSample/lib/note";
|
import { freqToNote } from "../../../entities/audioSample/lib/note";
|
||||||
import { formatTimeHHMMSS, isStale } from "../../../shared/lib/time";
|
import { formatTimeHHMMSS, isStale } from "../../../shared/lib/time";
|
||||||
|
|
||||||
const HZ_OPTIONS = [1, 5, 10, 15, 30, 60] as const;
|
const HZ_OPTIONS: number[] = [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 15] as const;
|
||||||
|
|
||||||
const WINDOW_OPTIONS: Array<{ label: string; ms: number }> = [
|
const WINDOW_OPTIONS: Array<{ label: string; ms: number }> = [
|
||||||
{ label: "15s", ms: 15_000 },
|
{ label: "15s", ms: 15_000 },
|
||||||
|
|||||||
Reference in New Issue
Block a user