// state.ts - Peak Holder алгоритм с таймером interface AppState { maxPoints: number; signalHistory: number[]; lastBufferSize: number; // Peak Holder peakValue: number; // Текущее удерживаемое пиковое значение амплитуды currentValue: number; // Текущее реальное значение амплитуды peakTimer: number | null; // Таймер для сброса пика амплитуды peakHoldTime: number; // Время удержания пика в миллисекундах // Peak Holder для уровня сигнала peakSignalLevel: number; currentSignalLevel: number; signalPeakTimer: number | null; signalPeakHoldTime: number; hasPeak: boolean; } type PeakHolderListener = () => void; export const state: AppState = { maxPoints: 1000, signalHistory: [], lastBufferSize: 0, peakValue: 0, currentValue: 0, peakTimer: null, peakHoldTime: 3000, // 3 секунды peakSignalLevel: 0, currentSignalLevel: 0, signalPeakTimer: null, signalPeakHoldTime: 3000, hasPeak: false, }; let peakHolderListener: PeakHolderListener | null = null; // ============================================ // ПУБЛИЧНЫЕ ФУНКЦИИ // ============================================ /** * Установка времени удержания пика сигнала * @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(); } } function scheduleAmplitudePeakRelease(): void { // Сбрасываем старый таймер 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); } function scheduleSignalPeakRelease(): void { // Сбрасываем старый таймер 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); }