diff --git a/dist/index.html b/dist/index.html index 3ad13c6..c8f98d5 100644 --- a/dist/index.html +++ b/dist/index.html @@ -32,7 +32,7 @@ button:disabled { background:#ccc; cursor:not-allowed; }

🇮🇹 Lyngvo тренажёр итальянского произношения · для Google Chrome

- 1. Введите фразу  →  2. ▶ Эталон (прослушайте)  →  3. 🎙 Записать (произнесите)  →  4. 📊 Сравнить + 1. Введите фразу  →  2. ▶ Эталон (прослушайте)  →  3. 🎙 Записать (сделайте короткую паузу перед произнесением)  →  4. 📊 Сравнить
@@ -256,12 +256,15 @@ async function transcribe(blob) { if (!GROQ_API_KEY) return { text: '', error: 'no_key' }; const tStart = performance.now(); + // Обрезаем 0.2с в начале (щелчок) — декодируем → WAV + const trimmedBlob = await trimStartBlob(blob); + const form = new FormData(); - form.append('file', blob, 'audio.webm'); + form.append('file', trimmedBlob, 'audio.wav'); form.append('model', 'whisper-large-v3'); form.append('language', 'it'); - console.log('[HTTP] blob ' + (blob.size/1024).toFixed(1) + 'KB → multipart POST'); + console.log('[HTTP] blob ' + (trimmedBlob.size/1024).toFixed(1) + 'KB → multipart POST'); try { const r = await fetch('https://lang.kube5s.ru/openai/v1/audio/transcriptions', { @@ -392,23 +395,20 @@ function stopRecording() { if (mediaRecorder && mediaRecorder.state === 'recording') mediaRecorder.stop(); } -// ---------- Воспроизведение (с обрезкой тишины) ---------- +// ---------- Воспроизведение (с обрезкой 0.2с в начале) ---------- async function playTrimmed(blob) { - const t = await trimSilence(blob); const ctx = await getAudioCtx(); - let buf; - if (t) { - const offline = new OfflineAudioContext(1, t.data.length, t.sr); - buf = offline.createBuffer(1, t.data.length, t.sr); - buf.getChannelData(0).set(t.data); - } else { - const ab = await blob.arrayBuffer(); - buf = await ctx.decodeAudioData(ab); - } + const ab = await blob.arrayBuffer(); + const rawBuf = await ctx.decodeAudioData(ab); + const sr = rawBuf.sampleRate; + const data = rawBuf.getChannelData(0); + const cutSamples = Math.floor(sr * 0.2); + const trimmed = data.slice(cutSamples); + const buf = ctx.createBuffer(1, trimmed.length, sr); + buf.getChannelData(0).set(trimmed); const src = ctx.createBufferSource(); src.buffer = buf; const gain = ctx.createGain(); - // Fade-in 10мс — убирает щелчок при старте gain.gain.setValueAtTime(0, ctx.currentTime); gain.gain.linearRampToValueAtTime(1, ctx.currentTime + 0.01); src.connect(gain).connect(ctx.destination); @@ -608,6 +608,35 @@ async function trimSilence(blob) { } catch(e) { return null; } } +// ---------- WAV кодирование (для отправки в Whisper) ---------- +function encodeWAV(samples, sampleRate) { + const buf = new ArrayBuffer(44 + samples.length * 2); + const v = new DataView(buf); + const w = (o, s) => { for (let i=0;i