feed method
Dá um pedaço de som. Devolve o que fazer a seguir.
Implementation
VadStep feed(Uint8List chunk, {bool speaking = false}) {
final n = chunk.lengthInBytes ~/ 2;
if (n == 0) return const VadStep.idle();
final rms = rmsOf(chunk);
final ms = (n * 1000) ~/ sampleRate;
frames++;
// Primeiros pedaços: aprender o silêncio deste sítio, sem nunca passar de
// [maxBase], e sem deitar fora o som (se já estava a falar, conta).
if (frames <= 3 && !speaking) {
base = base == 0 ? math.min(rms, maxBase) : math.min(base, rms);
}
// ⚠️ AS DUAS FASQUIAS, e são diferentes de propósito.
//
// A CORTAR (ela a falar): manda o chão medido, não o ruído da sala. Estava
// a multiplicar o ruído por 8, e numa sala com fundo 0,036 a fasquia ia a
// 0,29: nem uma voz a 0,16 a calava, apesar de o chão dizer 0,09. Duas
// vezes o ruído chega para acompanhar uma sala barulhenta.
//
// A OUVIR (ela calada): acompanha o ruído, mas COM TETO. Sem teto, num
// café a fasquia chegava a 0,38 e o dono tinha de gritar. A voz de quem
// fala ao pé do telemóvel dá 0,12 a 0,25; o teto fica em 0,10.
final speakThr = speaking
? math.max(base * 2.0, bargeFloor)
: math.min(math.max(base * 3.2, 0.014), tetoOuvir);
final quietThr = math.max(base * 1.8, 0.008);
// O ruído da sala acompanha-se: desce depressa (ficou tudo calado) e sobe
// devagar (ligaram o ar condicionado, entraram no carro). Nunca aprende
// com som alto o bastante para ser voz, senão passava a ignorar-te.
if (!capturing && !speaking) {
if (rms < base) {
base = base * 0.8 + rms * 0.2;
} else if (rms < speakThr) {
base = base * 0.98 + rms * 0.02;
}
}
if (speaking) {
final thr = speakThr;
if (perfil.length < 40) perfil.add(rms);
// Só conta como interrupção o que for VOZ. Um estalo de tecla é alto,
// mas não tem tom nenhum.
final alto = rms > thr;
if (rms > maxEnquantoFala) maxEnquantoFala = rms;
final tom = alto ? periodicidade(chunk) : 0.0;
if (tom > tomDaInterrupcao) tomDaInterrupcao = tom;
if (tom >= bargeVozMin) {
_vozHaMs = 0;
} else {
_vozHaMs += ms;
}
final ehVoz = alto && _vozHaMs <= vozValeMs;
if (ehVoz) {
speechMs += ms;
quietMs = 0;
_corrida += ms;
if (_corrida > _maiorCorrida) _maiorCorrida = _corrida;
// Tempo que chegue E um bocado seguido que só a voz consegue fazer.
if (speechMs >= bargeMs && _maiorCorrida >= bargeRunMs) return const VadStep.bargeIn();
} else {
_corrida = 0;
// Uma pausa curta não apaga a conta: entre sílabas há silêncio. Só uma
// pausa a sério é que diz que aquilo foi um barulho e não alguém a
// falar connosco.
quietMs += ms;
if (quietMs > bargeGapMs) {
speechMs = 0;
quietMs = 0;
_maiorCorrida = 0;
}
}
return const VadStep.idle();
}
if (rms > speakThr) {
capturing = true;
speechMs += ms;
quietMs = 0;
if (rms > picoDaFrase) picoDaFrase = rms;
final tf = periodicidade(chunk);
if (tf > tomDaFrase) tomDaFrase = tf;
} else if (capturing) {
if (rms < quietThr) quietMs += ms;
}
if (capturing) {
_sum += rms;
if (rms < _min) _min = rms;
if (rms > _max) _max = rms;
_n++;
}
if (!capturing) return const VadStep.idle();
// Barulho de fundo apanhado cedo: alguns segundos sem uma única pausa E
// sempre ao mesmo nível. Ninguém fala assim; uma ventoinha faz exatamente
// isto. Não se espera pelos 15 segundos para perceber.
final plano = _min > 0 && _max < _min * flatRatio;
if (speechMs > noiseAfterMs && quietMs == 0 && plano) return _noise();
if (quietMs <= quietToCloseMs && speechMs <= maxSpeechMs) return const VadStep.capturing();
final spoke = speechMs;
final nivel = _n > 0 ? _sum / _n : 0.0;
// Quinze segundos sem UMA pausa não é ninguém a falar: é ruído constante
// (uma ventoinha, o motor do carro, uma sala cheia). Ninguém fala assim.
// Sobe-se o silêncio de referência para esse nível e deixa de se confundir.
if (spoke > maxSpeechMs && quietMs < 200) return _noise();
// Fim de frase. Só vale a pena enviar se houver mesmo fala.
return VadStep.done(spokeMs: spoke, enough: spoke >= minSpeechMs, level: nivel);
}