243 lines
7.8 KiB
TypeScript
243 lines
7.8 KiB
TypeScript
// 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);
|
||
} |