From 7eef966f6e0a0e6f68a3676ce274bc108c980ea8 Mon Sep 17 00:00:00 2001 From: Mk Date: Tue, 26 May 2026 13:13:13 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20voice=20chat=20=E2=80=94=20fade=20in/out?= =?UTF-8?q?=20chunks,=20bigger=20buffer,=20volume=20boost=20to=20reduce=20?= =?UTF-8?q?bubbling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game.js | 47 +++++++++++++++++++++++++++++++++-------------- index.html | 2 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/game.js b/game.js index aa4c81c..5b67ec3 100644 --- a/game.js +++ b/game.js @@ -1355,8 +1355,8 @@ function customConfirm(msg, onYes) { console.log('[voice] AudioContext state:', audioCtx.state, 'sampleRate:', audioCtx.sampleRate); const source = audioCtx.createMediaStreamSource(voiceStream); - voiceProcessor = audioCtx.createScriptProcessor(2048, 1, 1); - console.log('[voice] ScriptProcessor created, bufferSize=2048'); + voiceProcessor = audioCtx.createScriptProcessor(4096, 1, 1); + console.log('[voice] ScriptProcessor created, bufferSize=4096'); voiceProcessor.onaudioprocess = (e) => { if (!voiceActive) return; @@ -1394,25 +1394,44 @@ function customConfirm(msg, onYes) { console.error('[voice] Socket connect error:', err.message); }); - voiceSocket.on('voice_in', (payload) => { - // Воспроизводим входящий голос — raw PCM int16 - const { data, meta, volume } = payload; - if (!audioCtx || audioCtx.state === 'closed') return; - console.log('[voice] voice_in from', meta.name, 'volume:', volume, 'bytes:', data.byteLength); - - const int16 = new Int16Array(data); - const float32 = new Float32Array(int16.length); - for (let i = 0; i < int16.length; i++) { - float32[i] = int16[i] / (int16[i] < 0 ? 0x8000 : 0x7FFF) * (volume || 1); + // Очередь воспроизведения голоса — склеиваем чанки без щелчков + const voiceQueue = []; + let voicePlaying = false; + function playVoiceChunk(float32, volume) { + const FADE = 64; // сэмплов для плавного перехода + // Fade in начало + for (let i = 0; i < FADE && i < float32.length; i++) { + float32[i] *= i / FADE; + } + // Fade out конец + for (let i = 0; i < FADE && i < float32.length; i++) { + float32[float32.length - 1 - i] *= i / FADE; } const buf = audioCtx.createBuffer(1, float32.length, 24000); buf.getChannelData(0).set(float32); const src = audioCtx.createBufferSource(); src.buffer = buf; const gain = audioCtx.createGain(); - gain.gain.value = 1; + gain.gain.value = Math.min(1, volume * 1.5); // усилить тихий голос src.connect(gain).connect(audioCtx.destination); - src.start(); + // Склеиваем: начинаем сразу после предыдущего чанка + const when = voicePlaying ? audioCtx.currentTime + 0.02 : audioCtx.currentTime; + voicePlaying = true; + src.start(when); + src.onended = () => { voicePlaying = false; }; + } + + voiceSocket.on('voice_in', (payload) => { + // Воспроизводим входящий голос — raw PCM int16 + const { data, meta, volume } = payload; + if (!audioCtx || audioCtx.state === 'closed') return; + + const int16 = new Int16Array(data); + const float32 = new Float32Array(int16.length); + for (let i = 0; i < int16.length; i++) { + float32[i] = int16[i] / (int16[i] < 0 ? 0x8000 : 0x7FFF); + } + playVoiceChunk(float32, volume || 1); // Индикатор speakingIndicator.style.display = 'block'; diff --git a/index.html b/index.html index 3b45c69..0a78f09 100644 --- a/index.html +++ b/index.html @@ -92,6 +92,6 @@ - +