рефактор
This commit is contained in:
@@ -22,17 +22,21 @@ export class AudioEngine {
|
||||
/**
|
||||
* Инициализация AudioContext (требует взаимодействия пользователя)
|
||||
*/
|
||||
private initAudio(): boolean {
|
||||
private async initAudio(): Promise<boolean> {
|
||||
try {
|
||||
if (!this.audioContext) {
|
||||
this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
|
||||
const AudioContextClass = (window as any).AudioContext || (window as any).webkitAudioContext;
|
||||
this.audioContext = new AudioContextClass();
|
||||
}
|
||||
|
||||
if (this.audioContext.state === 'suspended') {
|
||||
this.audioContext.resume();
|
||||
const context = this.audioContext;
|
||||
if (!context) return false;
|
||||
|
||||
if (context.state === 'suspended') {
|
||||
await context.resume();
|
||||
}
|
||||
|
||||
return this.audioContext.state === 'running';
|
||||
return context.state === 'running';
|
||||
} catch (error) {
|
||||
console.warn('[Audio] Ошибка инициализации:', error);
|
||||
return false;
|
||||
@@ -104,18 +108,26 @@ export class AudioEngine {
|
||||
* Обновление уровня сигнала
|
||||
* @param level - уровень сигнала (0-3)
|
||||
*/
|
||||
public updateLevel(level: number): void {
|
||||
public async updateLevel(level: number): Promise<void> {
|
||||
// Инициализация при первом вызове
|
||||
if (!this.audioContext) {
|
||||
if (!this.initAudio()) {
|
||||
const initialized = await this.initAudio();
|
||||
if (!initialized) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const context = this.audioContext;
|
||||
if (!context) return;
|
||||
|
||||
// Если AudioContext не готов, пробуем восстановить
|
||||
if (this.audioContext?.state !== 'running') {
|
||||
this.audioContext?.resume();
|
||||
return;
|
||||
if (context.state !== 'running') {
|
||||
try {
|
||||
await context.resume();
|
||||
} catch (error) {
|
||||
console.warn('[Audio] Ошибка возобновления контекста:', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Приводим уровень к числу
|
||||
@@ -260,9 +272,14 @@ export class AudioEngine {
|
||||
console.log('[Audio] Звук остановлен');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ПУБЛИЧНЫЕ МЕТОДЫ
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Проверка, активен ли звук
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
public isActive(): boolean {
|
||||
return this.isPlaying && this.currentLevel > 0;
|
||||
}
|
||||
@@ -270,6 +287,7 @@ export class AudioEngine {
|
||||
/**
|
||||
* Получение текущей частоты
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
public getCurrentFrequency(): number {
|
||||
return this.currentFrequency;
|
||||
}
|
||||
@@ -277,6 +295,7 @@ export class AudioEngine {
|
||||
/**
|
||||
* Получение текущего уровня
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
public getCurrentLevel(): number {
|
||||
return this.currentLevel;
|
||||
}
|
||||
@@ -284,11 +303,12 @@ export class AudioEngine {
|
||||
/**
|
||||
* Полная остановка и освобождение ресурсов
|
||||
*/
|
||||
public dispose(): void {
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
public async dispose(): Promise<void> {
|
||||
this.stopSound();
|
||||
if (this.audioContext) {
|
||||
try {
|
||||
this.audioContext.close();
|
||||
await this.audioContext.close();
|
||||
} catch (e) {
|
||||
// Игнорируем
|
||||
}
|
||||
@@ -300,5 +320,6 @@ export class AudioEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// Экспортируем одиночный экземпляр
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export const audioEngine = new AudioEngine();
|
||||
@@ -1,140 +1,138 @@
|
||||
// chart.js
|
||||
export function drawChart(canvas: HTMLCanvasElement, history: number[]): void {
|
||||
if (!canvas || !history?.length) return;
|
||||
if (!canvas || !history?.length) return;
|
||||
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const displayHeight = rect.height || 200;
|
||||
const displayWidth = rect.width || 800;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const displayHeight = rect.height || 200;
|
||||
canvas.width = rect.width || 800;
|
||||
canvas.height = displayHeight;
|
||||
|
||||
canvas.width = displayWidth;
|
||||
canvas.height = displayHeight;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
|
||||
const w = canvas.width;
|
||||
const h = canvas.height;
|
||||
const padLeft = 45;
|
||||
const padRight = 15;
|
||||
const padTop = 10;
|
||||
const padBottom = 25;
|
||||
const plotW = w - padLeft - padRight;
|
||||
const plotH = h - padTop - padBottom;
|
||||
|
||||
const padLeft = 45;
|
||||
const padRight = 15;
|
||||
const padTop = 10;
|
||||
const padBottom = 25;
|
||||
const plotW = w - padLeft - padRight;
|
||||
const plotH = h - padTop - padBottom;
|
||||
// Фон
|
||||
ctx.fillStyle = "#0a0f0a";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// Фон
|
||||
ctx.fillStyle = "#0a0f0a";
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
// Сетка
|
||||
const maxVal = 63;
|
||||
const gridLines = 4;
|
||||
|
||||
// Сетка
|
||||
const maxVal = 63;
|
||||
const gridLines = 4;
|
||||
ctx.strokeStyle = "#0d2a0d";
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 4]);
|
||||
|
||||
ctx.strokeStyle = "#0d2a0d";
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.setLineDash([4, 4]);
|
||||
for (let i = 0; i <= gridLines; i++) {
|
||||
const val = (maxVal / gridLines) * i;
|
||||
const y = padTop + plotH - (val / maxVal) * plotH;
|
||||
|
||||
for (let i = 0; i <= gridLines; i++) {
|
||||
const val = (maxVal / gridLines) * i;
|
||||
const y = padTop + plotH - (val / maxVal) * plotH;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padLeft, y);
|
||||
ctx.lineTo(w - padRight, y);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padLeft, y);
|
||||
ctx.lineTo(w - padRight, y);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "11px monospace";
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(Math.round(val).toString(), padLeft - 8, y + 4);
|
||||
}
|
||||
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Подписи осей
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "bold 11px monospace";
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(12, padTop + plotH / 2);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("ЗНАЧЕНИЕ", 0, 0);
|
||||
ctx.restore();
|
||||
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("БУФЕР ПРИНЯТЫХ ДАННЫХ", padLeft + plotW / 2, h - 4);
|
||||
|
||||
const hasSignal = history.some(v => v > 0);
|
||||
|
||||
if (!hasSignal) {
|
||||
ctx.fillStyle = "#ff4444";
|
||||
ctx.font = "14px monospace";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("Ожидание сигнала...", w / 2, h / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
let plotData = history;
|
||||
if (history.length > plotW) {
|
||||
const step = history.length / plotW;
|
||||
plotData = [];
|
||||
for (let i = 0; i < plotW; i++) {
|
||||
const idx = Math.floor(i * step);
|
||||
plotData.push(history[idx]);
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "11px monospace";
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(Math.round(val).toString(), padLeft - 8, y + 4);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "#e6852d";
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.shadowColor = "#e6852d";
|
||||
ctx.shadowBlur = 3;
|
||||
|
||||
ctx.beginPath();
|
||||
const stepX = plotW / Math.max(plotData.length - 1, 1);
|
||||
|
||||
for (let i = 0; i < plotData.length; i++) {
|
||||
const x = padLeft + i * stepX;
|
||||
const y = padTop + plotH - (plotData[i] / maxVal) * plotH;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
if (history.length > 0) {
|
||||
const lastVal = history[history.length - 1];
|
||||
const lastX = padLeft + (plotData.length - 1) * stepX;
|
||||
const lastY = padTop + plotH - (lastVal / maxVal) * plotH;
|
||||
|
||||
ctx.fillStyle = "#c46b1f";
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
const labelText = `${lastVal}`;
|
||||
const labelWidth = ctx.measureText(labelText).width;
|
||||
|
||||
ctx.fillStyle = "rgba(0,0,0,0.8)";
|
||||
ctx.fillRect(lastX - labelWidth / 2 - 4, lastY - 22, labelWidth + 8, 18);
|
||||
ctx.setLineDash([]);
|
||||
|
||||
// Подписи осей
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "bold 12px monospace";
|
||||
ctx.font = "bold 11px monospace";
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(12, padTop + plotH / 2);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(labelText, lastX, lastY - 8);
|
||||
ctx.fillText("ЗНАЧЕНИЕ", 0, 0);
|
||||
ctx.restore();
|
||||
|
||||
const peak = Math.max(...history);
|
||||
const min = Math.min(...history);
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("БУФЕР ПРИНЯТЫХ ДАННЫХ", padLeft + plotW / 2, h - 4);
|
||||
|
||||
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||
ctx.fillRect(w - 120, padTop, 110, 42);
|
||||
const hasSignal = history.some(v => v > 0);
|
||||
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "10px monospace";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(`Пик: ${peak}`, w - 110, padTop + 14);
|
||||
ctx.fillText(`Мин: ${min}`, w - 110, padTop + 28);
|
||||
ctx.fillText(`Точек: ${history.length}`, w - 110, padTop + 42);
|
||||
}
|
||||
if (!hasSignal) {
|
||||
ctx.fillStyle = "#ff4444";
|
||||
ctx.font = "14px monospace";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText("Ожидание сигнала...", w / 2, h / 2);
|
||||
return;
|
||||
}
|
||||
|
||||
let plotData = history;
|
||||
if (history.length > plotW) {
|
||||
const step = history.length / plotW;
|
||||
plotData = [];
|
||||
for (let i = 0; i < plotW; i++) {
|
||||
const idx = Math.floor(i * step);
|
||||
plotData.push(history[idx]);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.strokeStyle = "#e6852d";
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.shadowColor = "#e6852d";
|
||||
ctx.shadowBlur = 3;
|
||||
|
||||
ctx.beginPath();
|
||||
const stepX = plotW / Math.max(plotData.length - 1, 1);
|
||||
|
||||
for (let i = 0; i < plotData.length; i++) {
|
||||
const x = padLeft + i * stepX;
|
||||
const y = padTop + plotH - (plotData[i] / maxVal) * plotH;
|
||||
if (i === 0) ctx.moveTo(x, y);
|
||||
else ctx.lineTo(x, y);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
if (history.length > 0) {
|
||||
const lastVal = history[history.length - 1];
|
||||
const lastX = padLeft + (plotData.length - 1) * stepX;
|
||||
const lastY = padTop + plotH - (lastVal / maxVal) * plotH;
|
||||
|
||||
ctx.fillStyle = "#c46b1f";
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
const labelText = `${lastVal}`;
|
||||
const labelWidth = ctx.measureText(labelText).width;
|
||||
|
||||
ctx.fillStyle = "rgba(0,0,0,0.8)";
|
||||
ctx.fillRect(lastX - labelWidth / 2 - 4, lastY - 22, labelWidth + 8, 18);
|
||||
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "bold 12px monospace";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(labelText, lastX, lastY - 8);
|
||||
|
||||
const peak = Math.max(...history);
|
||||
const min = Math.min(...history);
|
||||
|
||||
ctx.fillStyle = "rgba(0,0,0,0.7)";
|
||||
ctx.fillRect(w - 120, padTop, 110, 42);
|
||||
|
||||
ctx.fillStyle = "#00ff88";
|
||||
ctx.font = "10px monospace";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(`Пик: ${peak}`, w - 110, padTop + 14);
|
||||
ctx.fillText(`Мин: ${min}`, w - 110, padTop + 28);
|
||||
ctx.fillText(`Точек: ${history.length}`, w - 110, padTop + 42);
|
||||
}
|
||||
}
|
||||
@@ -1,192 +1,243 @@
|
||||
// state.ts - Peak Holder алгоритм с таймером
|
||||
|
||||
interface AppState {
|
||||
maxPoints: number;
|
||||
signalHistory: number[];
|
||||
lastBufferSize: number;
|
||||
maxPoints: number;
|
||||
signalHistory: number[];
|
||||
lastBufferSize: number;
|
||||
|
||||
// Peak Holder
|
||||
peakValue: number; // Текущее удерживаемое пиковое значение амплитуды
|
||||
currentValue: number; // Текущее реальное значение амплитуды
|
||||
peakTimer: number | null; // Таймер для сброса пика амплитуды
|
||||
peakHoldTime: number; // Время удержания пика в миллисекундах
|
||||
// Peak Holder
|
||||
peakValue: number; // Текущее удерживаемое пиковое значение амплитуды
|
||||
currentValue: number; // Текущее реальное значение амплитуды
|
||||
peakTimer: number | null; // Таймер для сброса пика амплитуды
|
||||
peakHoldTime: number; // Время удержания пика в миллисекундах
|
||||
|
||||
// Peak Holder для уровня сигнала
|
||||
peakSignalLevel: number;
|
||||
currentSignalLevel: number;
|
||||
signalPeakTimer: number | null;
|
||||
signalPeakHoldTime: number;
|
||||
// Peak Holder для уровня сигнала
|
||||
peakSignalLevel: number;
|
||||
currentSignalLevel: number;
|
||||
signalPeakTimer: number | null;
|
||||
signalPeakHoldTime: number;
|
||||
|
||||
hasPeak: boolean;
|
||||
hasPeak: boolean;
|
||||
}
|
||||
|
||||
type PeakHolderListener = () => void;
|
||||
|
||||
export const state: AppState = {
|
||||
maxPoints: 1000,
|
||||
signalHistory: [],
|
||||
lastBufferSize: 0,
|
||||
maxPoints: 1000,
|
||||
signalHistory: [],
|
||||
lastBufferSize: 0,
|
||||
|
||||
peakValue: 0,
|
||||
currentValue: 0,
|
||||
peakTimer: null,
|
||||
peakHoldTime: 3000, // 3 секунды
|
||||
peakValue: 0,
|
||||
currentValue: 0,
|
||||
peakTimer: null,
|
||||
peakHoldTime: 3000, // 3 секунды
|
||||
|
||||
peakSignalLevel: 0,
|
||||
currentSignalLevel: 0,
|
||||
signalPeakTimer: null,
|
||||
signalPeakHoldTime: 3000,
|
||||
peakSignalLevel: 0,
|
||||
currentSignalLevel: 0,
|
||||
signalPeakTimer: null,
|
||||
signalPeakHoldTime: 3000,
|
||||
|
||||
hasPeak: false,
|
||||
hasPeak: false,
|
||||
};
|
||||
|
||||
let peakHolderListener: PeakHolderListener | null = null;
|
||||
|
||||
/** Обновление UI при сбросе peak holder по таймеру (между опросами API). */
|
||||
export function setPeakHolderListener(listener: PeakHolderListener | null): void {
|
||||
peakHolderListener = listener;
|
||||
// ============================================
|
||||
// ПУБЛИЧНЫЕ ФУНКЦИИ
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Установка времени удержания пика сигнала
|
||||
* @param ms - время в миллисекундах
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function setSignalPeakHoldTime(ms: number): void {
|
||||
state.signalPeakHoldTime = ms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение информации о пике сигнала
|
||||
* @returns { peak: number; current: number; timerActive: boolean }
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function getSignalPeakInfo(): { peak: number; current: number; timerActive: boolean } {
|
||||
return {
|
||||
peak: state.peakSignalLevel,
|
||||
current: state.currentSignalLevel,
|
||||
timerActive: state.signalPeakTimer !== null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление нового значения сигнала в историю
|
||||
* @param value - значение сигнала
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function addSignal(value: number): void {
|
||||
state.signalHistory.push(value);
|
||||
if (state.signalHistory.length > state.maxPoints) {
|
||||
state.signalHistory = state.signalHistory.slice(-state.maxPoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Установка слушателя для обновления пика
|
||||
* @param listener - функция-слушатель
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function setPeakHolderListener(listener: PeakHolderListener | null): void {
|
||||
peakHolderListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление пикового значения амплитуды
|
||||
* @param target - текущее значение
|
||||
* @returns пиковое значение
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function updatePeakHolder(target: number): number {
|
||||
// Обновляем текущее значение
|
||||
state.currentValue = target;
|
||||
|
||||
// Если новое значение больше текущего пика
|
||||
if (target > state.peakValue) {
|
||||
// Устанавливаем новый пик
|
||||
state.peakValue = target;
|
||||
state.hasPeak = true;
|
||||
scheduleAmplitudePeakRelease();
|
||||
} else if (target < state.peakValue && state.peakTimer === null) {
|
||||
// Пик выше текущего значения, но таймер пропал — перезапускаем удержание
|
||||
scheduleAmplitudePeakRelease();
|
||||
} // Возвращаем текущий пик
|
||||
return state.peakValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление пикового уровня сигнала
|
||||
* @param level - текущий уровень
|
||||
* @returns пиковый уровень
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function updateSignalPeakHolder(level: number): number {
|
||||
// Обновляем текущее значение
|
||||
state.currentSignalLevel = level;
|
||||
|
||||
// Если новый уровень больше пика — устанавливаем новый пик
|
||||
if (level > state.peakSignalLevel) {
|
||||
state.peakSignalLevel = level;
|
||||
state.hasPeak = true;
|
||||
scheduleSignalPeakRelease();
|
||||
} else if (level < state.peakSignalLevel && state.signalPeakTimer === null) {
|
||||
scheduleSignalPeakRelease();
|
||||
}
|
||||
|
||||
// Возвращаем текущий пик
|
||||
return state.peakSignalLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительный сброс пика сигнала
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function forceResetSignalPeak(): void {
|
||||
if (state.signalPeakTimer !== null) {
|
||||
clearTimeout(state.signalPeakTimer);
|
||||
state.signalPeakTimer = null;
|
||||
}
|
||||
state.peakSignalLevel = 0;
|
||||
state.currentSignalLevel = 0;
|
||||
state.hasPeak = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительный сброс всех пиков
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function forceResetPeak(): void {
|
||||
if (state.peakTimer !== null) {
|
||||
clearTimeout(state.peakTimer);
|
||||
state.peakTimer = null;
|
||||
}
|
||||
state.peakValue = 0;
|
||||
state.currentValue = 0;
|
||||
state.hasPeak = false;
|
||||
forceResetSignalPeak();
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавление массива значений сигнала
|
||||
* @param values - массив значений
|
||||
* @param bufferSize - размер буфера
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function addSignals(values: number[], bufferSize: number): void {
|
||||
if (!Array.isArray(values) || values.length === 0) return;
|
||||
|
||||
const newBytes = bufferSize - state.lastBufferSize;
|
||||
|
||||
if (newBytes > 0 && newBytes <= values.length) {
|
||||
const fresh = values.slice(-newBytes);
|
||||
const decoded = fresh.map(b => b & 0x3F);
|
||||
state.signalHistory.push(...decoded);
|
||||
}
|
||||
|
||||
state.lastBufferSize = bufferSize;
|
||||
|
||||
if (state.signalHistory.length > state.maxPoints) {
|
||||
state.signalHistory = state.signalHistory.slice(-state.maxPoints);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сброс отслеживания буфера
|
||||
*/
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export function resetBufferTracking(): void {
|
||||
state.lastBufferSize = 0;
|
||||
forceResetPeak();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ВНУТРЕННИЕ ФУНКЦИИ
|
||||
// ============================================
|
||||
|
||||
function notifyPeakHolderChange(): void {
|
||||
if (peakHolderListener) {
|
||||
peakHolderListener();
|
||||
}
|
||||
if (peakHolderListener) {
|
||||
peakHolderListener();
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleAmplitudePeakRelease(): void {
|
||||
// Сбрасываем старый таймер
|
||||
if (state.peakTimer !== null) {
|
||||
clearTimeout(state.peakTimer);
|
||||
state.peakTimer = null;
|
||||
}
|
||||
// Сбрасываем старый таймер
|
||||
if (state.peakTimer !== null) {
|
||||
clearTimeout(state.peakTimer);
|
||||
state.peakTimer = null;
|
||||
}
|
||||
|
||||
// Запускаем новый таймер на 3 секунды
|
||||
state.peakTimer = window.setTimeout(() => {
|
||||
state.peakValue = state.currentValue;
|
||||
state.peakTimer = null;
|
||||
state.hasPeak = false;
|
||||
console.log(`[Peak] Сброс пика до: ${state.peakValue}`);
|
||||
notifyPeakHolderChange();
|
||||
}, state.peakHoldTime);
|
||||
// Запускаем новый таймер на 3 секунды
|
||||
state.peakTimer = window.setTimeout(() => {
|
||||
state.peakValue = state.currentValue;
|
||||
state.peakTimer = null;
|
||||
state.hasPeak = false;
|
||||
console.log(`[Peak] Сброс пика до: ${state.peakValue}`);
|
||||
notifyPeakHolderChange();
|
||||
}, state.peakHoldTime);
|
||||
}
|
||||
|
||||
function scheduleSignalPeakRelease(): void {
|
||||
// Сбрасываем старый таймер
|
||||
if (state.signalPeakTimer !== null) {
|
||||
clearTimeout(state.signalPeakTimer);
|
||||
state.signalPeakTimer = null;
|
||||
}
|
||||
// Сбрасываем старый таймер
|
||||
if (state.signalPeakTimer !== null) {
|
||||
clearTimeout(state.signalPeakTimer);
|
||||
state.signalPeakTimer = null;
|
||||
}
|
||||
|
||||
// Запускаем новый таймер на 3 секунды
|
||||
state.signalPeakTimer = window.setTimeout(() => {
|
||||
// Сбрасываем пик к текущему уровню
|
||||
state.peakSignalLevel = state.currentSignalLevel;
|
||||
state.signalPeakTimer = null;
|
||||
state.hasPeak = false;
|
||||
console.log(`[SignalPeak] Сброс пика до: ${state.peakSignalLevel}`);
|
||||
notifyPeakHolderChange();
|
||||
}, state.signalPeakHoldTime);
|
||||
}
|
||||
|
||||
// ========== ФУНКЦИИ ДЛЯ АМПЛИТУДЫ ==========
|
||||
|
||||
export function updatePeakHolder(target: number): number {
|
||||
// Обновляем текущее значение
|
||||
state.currentValue = target;
|
||||
|
||||
// Если новое значение больше текущего пика
|
||||
if (target > state.peakValue) {
|
||||
// Устанавливаем новый пик
|
||||
state.peakValue = target;
|
||||
state.hasPeak = true;
|
||||
scheduleAmplitudePeakRelease();
|
||||
} else if (target < state.peakValue && state.peakTimer === null) {
|
||||
// Пик выше текущего значения, но таймер пропал — перезапускаем удержание
|
||||
scheduleAmplitudePeakRelease();
|
||||
} // Возвращаем текущий пик
|
||||
return state.peakValue;
|
||||
}
|
||||
|
||||
// ========== ФУНКЦИИ ДЛЯ УРОВНЯ СИГНАЛА ==========
|
||||
|
||||
export function updateSignalPeakHolder(level: number): number {
|
||||
// Обновляем текущее значение
|
||||
state.currentSignalLevel = level;
|
||||
|
||||
// Если новый уровень больше пика — устанавливаем новый пик
|
||||
if (level > state.peakSignalLevel) {
|
||||
state.peakSignalLevel = level;
|
||||
state.hasPeak = true;
|
||||
scheduleSignalPeakRelease();
|
||||
} else if (level < state.peakSignalLevel && state.signalPeakTimer === null) {
|
||||
scheduleSignalPeakRelease();
|
||||
}
|
||||
|
||||
// Возвращаем текущий пик
|
||||
return state.peakSignalLevel;
|
||||
}
|
||||
|
||||
// ========== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ==========
|
||||
|
||||
export function forceResetSignalPeak(): void {
|
||||
if (state.signalPeakTimer !== null) {
|
||||
clearTimeout(state.signalPeakTimer);
|
||||
state.signalPeakTimer = null;
|
||||
}
|
||||
state.peakSignalLevel = 0;
|
||||
state.currentSignalLevel = 0;
|
||||
state.hasPeak = false;
|
||||
}
|
||||
|
||||
export function setSignalPeakHoldTime(ms: number): void {
|
||||
state.signalPeakHoldTime = ms;
|
||||
}
|
||||
|
||||
export function getSignalPeakInfo(): { peak: number; current: number; timerActive: boolean } {
|
||||
return {
|
||||
peak: state.peakSignalLevel,
|
||||
current: state.currentSignalLevel,
|
||||
timerActive: state.signalPeakTimer !== null,
|
||||
};
|
||||
}
|
||||
|
||||
export function forceResetPeak(): void {
|
||||
if (state.peakTimer !== null) {
|
||||
clearTimeout(state.peakTimer);
|
||||
state.peakTimer = null;
|
||||
}
|
||||
state.peakValue = 0;
|
||||
state.currentValue = 0;
|
||||
state.hasPeak = false;
|
||||
forceResetSignalPeak();
|
||||
}
|
||||
|
||||
export function addSignal(value: number): void {
|
||||
state.signalHistory.push(value);
|
||||
if (state.signalHistory.length > state.maxPoints) {
|
||||
state.signalHistory = state.signalHistory.slice(-state.maxPoints);
|
||||
}
|
||||
}
|
||||
|
||||
export function addSignals(values: number[], bufferSize: number): void {
|
||||
if (!Array.isArray(values) || values.length === 0) return;
|
||||
|
||||
const newBytes = bufferSize - state.lastBufferSize;
|
||||
|
||||
if (newBytes > 0 && newBytes <= values.length) {
|
||||
const fresh = values.slice(-newBytes);
|
||||
const decoded = fresh.map(b => b & 0x3F);
|
||||
state.signalHistory.push(...decoded);
|
||||
}
|
||||
|
||||
state.lastBufferSize = bufferSize;
|
||||
|
||||
if (state.signalHistory.length > state.maxPoints) {
|
||||
state.signalHistory = state.signalHistory.slice(-state.maxPoints);
|
||||
}
|
||||
}
|
||||
|
||||
export function resetBufferTracking(): void {
|
||||
state.lastBufferSize = 0;
|
||||
forceResetPeak();
|
||||
}
|
||||
// Запускаем новый таймер на 3 секунды
|
||||
state.signalPeakTimer = window.setTimeout(() => {
|
||||
// Сбрасываем пик к текущему уровню
|
||||
state.peakSignalLevel = state.currentSignalLevel;
|
||||
state.signalPeakTimer = null;
|
||||
state.hasPeak = false;
|
||||
console.log(`[SignalPeak] Сброс пика до: ${state.peakSignalLevel}`);
|
||||
notifyPeakHolderChange();
|
||||
}, state.signalPeakHoldTime);
|
||||
}
|
||||
Reference in New Issue
Block a user