diff --git a/cmd/server/web/js/audio.ts b/cmd/server/web/js/audio.ts index 2c07fe7..40c6628 100644 --- a/cmd/server/web/js/audio.ts +++ b/cmd/server/web/js/audio.ts @@ -6,9 +6,14 @@ export class AudioEngine { private gainNode: GainNode | null = null; private isPlaying: boolean = false; private currentLevel: number = 0; + private currentFrequency: number = 800; private fadeTimeout: number | null = null; - private readonly FREQUENCY: number = 800; // 800 Гц private readonly FADE_TIME: number = 0.05; // 50 мс для плавного старта/остановки + private readonly FREQUENCY_MAP: Record = { + 1: 800, // Уровень 1 → 800 Гц + 2: 1000, // Уровень 2 → 1000 Гц + 3: 1200 // Уровень 3 → 1200 Гц + }; constructor() { // Инициализация при первом использовании @@ -34,19 +39,51 @@ export class AudioEngine { } } + /** + * Получение частоты для уровня + */ + private getFrequencyForLevel(level: number): number { + return this.FREQUENCY_MAP[level] || 800; + } + + /** + * Обновление частоты осциллятора + */ + private updateFrequency(frequency: number): void { + if (!this.oscillator) return; + + try { + // Плавное изменение частоты за 50 мс + const currentTime = this.audioContext?.currentTime || 0; + this.oscillator.frequency.exponentialRampToValueAtTime( + frequency, + currentTime + this.FADE_TIME + ); + this.currentFrequency = frequency; + console.log(`[Audio] Частота: ${frequency} Гц`); + } catch (error) { + // Если не удалось плавно, устанавливаем сразу + this.oscillator.frequency.value = frequency; + this.currentFrequency = frequency; + } + } + /** * Создание звукового графа */ - private createSoundGraph(): void { + private createSoundGraph(level: number): void { if (!this.audioContext) return; // Останавливаем старый звук this.stopSound(); + const frequency = this.getFrequencyForLevel(level); + this.currentFrequency = frequency; + // Создаём осциллятор this.oscillator = this.audioContext.createOscillator(); this.oscillator.type = 'sine'; - this.oscillator.frequency.value = this.FREQUENCY; + this.oscillator.frequency.value = frequency; // Создаём усилитель this.gainNode = this.audioContext.createGain(); @@ -60,7 +97,7 @@ export class AudioEngine { this.oscillator.start(); this.isPlaying = true; - console.log('[Audio] Звук создан, частота:', this.FREQUENCY, 'Гц'); + console.log(`[Audio] Звук создан, частота: ${frequency} Гц, уровень: ${level}`); } /** @@ -89,11 +126,18 @@ export class AudioEngine { return; } + const oldLevel = this.currentLevel; this.currentLevel = newLevel; + // Получаем частоту для нового уровня + const newFrequency = this.getFrequencyForLevel(newLevel); + // Если уровень > 0 и звук не создан — создаём if (newLevel > 0 && !this.oscillator) { - this.createSoundGraph(); + this.createSoundGraph(newLevel); + // Устанавливаем громкость после создания + this.setVolume(newLevel); + return; } // Если уровень 0 — выключаем звук @@ -102,36 +146,55 @@ export class AudioEngine { return; } - // Если звук не играет — включаем + // Если звук не играет — включаем заново if (!this.isPlaying || !this.oscillator) { - this.createSoundGraph(); + this.createSoundGraph(newLevel); + this.setVolume(newLevel); + return; } - // Расчёт громкости: уровень 1 = 15%, 2 = 30%, 3 = 50% + // Изменяем частоту, если она изменилась + if (newFrequency !== this.currentFrequency) { + this.updateFrequency(newFrequency); + } + + // Обновляем громкость + this.setVolume(newLevel); + + console.log(`[Audio] Уровень: ${oldLevel} → ${newLevel}, Частота: ${newFrequency} Гц`); + } + + /** + * Установка громкости для уровня + */ + private setVolume(level: number): void { + if (!this.gainNode || !this.audioContext) return; + + // Расчёт громкости: уровень 1 = 10%, 2 = 25%, 3 = 45% const volumeMap: Record = { 1: 0.10, 2: 0.25, 3: 0.45 }; - const targetVolume = volumeMap[newLevel] || 0; + const targetVolume = volumeMap[level] || 0; - // Плавно меняем громкость - if (this.gainNode) { + try { const currentGain = this.gainNode.gain.value; // Если звук выключен или очень тихий, включаем сразу if (currentGain < 0.01) { - this.gainNode.gain.setValueAtTime(targetVolume, this.audioContext!.currentTime); + this.gainNode.gain.setValueAtTime(targetVolume, this.audioContext.currentTime); } else { // Плавное изменение за 50 мс this.gainNode.gain.exponentialRampToValueAtTime( Math.max(targetVolume, 0.001), - this.audioContext!.currentTime + this.FADE_TIME + this.audioContext.currentTime + this.FADE_TIME ); } + } catch (error) { + // fallback + this.gainNode.gain.value = targetVolume; } - - console.log(`[Audio] Уровень: ${newLevel}, Громкость: ${(targetVolume * 100).toFixed(0)}%`); } /** @@ -146,9 +209,14 @@ export class AudioEngine { this.fadeTimeout = null; } - // Плавно убавляем громкость за 100 мс - const currentTime = this.audioContext.currentTime; - this.gainNode.gain.exponentialRampToValueAtTime(0.001, currentTime + this.FADE_TIME * 2); + try { + // Плавно убавляем громкость за 100 мс + const currentTime = this.audioContext.currentTime; + this.gainNode.gain.exponentialRampToValueAtTime(0.001, currentTime + this.FADE_TIME * 2); + } catch (error) { + // fallback + this.gainNode.gain.value = 0; + } // Останавливаем звук после затухания this.fadeTimeout = window.setTimeout(() => { @@ -199,6 +267,20 @@ export class AudioEngine { return this.isPlaying && this.currentLevel > 0; } + /** + * Получение текущей частоты + */ + public getCurrentFrequency(): number { + return this.currentFrequency; + } + + /** + * Получение текущего уровня + */ + public getCurrentLevel(): number { + return this.currentLevel; + } + /** * Полная остановка и освобождение ресурсов */ @@ -213,6 +295,7 @@ export class AudioEngine { this.audioContext = null; } this.currentLevel = 0; + this.currentFrequency = 800; console.log('[Audio] Ресурсы освобождены'); } }