WolfDavid commited on
Commit
1cb015d
·
1 Parent(s): f13c9e2

fix(01-07): fold an empty recording into the duration floor

Browse files

A push too short to produce a single audio callback was reported as a distinct
'no-audio' condition. It is a zero-length recording, so 'duration-floor' is both true
and actionable; 'no-audio' now means only stop() without a matching start(). Also
records why the standalone suite drives capture with browser audio processing off.

Files changed (1) hide show
  1. avatar/mic.js +15 -3
avatar/mic.js CHANGED
@@ -40,6 +40,7 @@ export const GATE = {
40
 
41
  /** Reject reasons. Tests assert on these exact strings, so they are exported data. */
42
  export const REJECT = {
 
43
  NO_AUDIO: 'no-audio',
44
  DURATION: 'duration-floor',
45
  RMS: 'rms-floor',
@@ -141,8 +142,10 @@ export function analyse(samples, sampleRate) {
141
  export function gate(samples, sampleRate) {
142
  const stats = analyse(samples, sampleRate);
143
  let reason = null;
144
- if (!samples || samples.length === 0) reason = REJECT.NO_AUDIO;
145
- else if (stats.durationMs < GATE.MIN_DURATION_MS) reason = REJECT.DURATION;
 
 
146
  else if (stats.rms < GATE.MIN_RMS) reason = REJECT.RMS;
147
  else if (!(stats.modulation >= GATE.MIN_MODULATION)) reason = REJECT.MODULATION;
148
  return { ok: reason === null, reason, ...stats };
@@ -185,7 +188,16 @@ async function resampleTo(samples, fromRate, toRate) {
185
  * @param {Function} [opts.emit] the facade event bus
186
  * @param {Function} [opts.onListening] called with true/false as capture really starts/stops
187
  * @param {Function} [opts.isBusy] returns true while the avatar is thinking or speaking
188
- * @param {boolean} [opts.processing] browser audio processing; see below
 
 
 
 
 
 
 
 
 
189
  */
190
  export function createMic({
191
  emit = () => {},
 
40
 
41
  /** Reject reasons. Tests assert on these exact strings, so they are exported data. */
42
  export const REJECT = {
43
+ /** stop() without a matching start(), or ASR returned nothing at all. */
44
  NO_AUDIO: 'no-audio',
45
  DURATION: 'duration-floor',
46
  RMS: 'rms-floor',
 
142
  export function gate(samples, sampleRate) {
143
  const stats = analyse(samples, sampleRate);
144
  let reason = null;
145
+ // An empty buffer is not a separate condition, it is a zero-length recording, and
146
+ // saying "duration-floor" for a push too short to produce a single audio callback is
147
+ // both true and more useful than a distinct empty-buffer reason nobody can action.
148
+ if (stats.durationMs < GATE.MIN_DURATION_MS) reason = REJECT.DURATION;
149
  else if (stats.rms < GATE.MIN_RMS) reason = REJECT.RMS;
150
  else if (!(stats.modulation >= GATE.MIN_MODULATION)) reason = REJECT.MODULATION;
151
  return { ok: reason === null, reason, ...stats };
 
188
  * @param {Function} [opts.emit] the facade event bus
189
  * @param {Function} [opts.onListening] called with true/false as capture really starts/stops
190
  * @param {Function} [opts.isBusy] returns true while the avatar is thinking or speaking
191
+ * @param {boolean} [opts.processing] echo cancellation / noise suppression / AGC
192
+ *
193
+ * A note on `processing`, which the standalone ASR suite turns OFF deliberately.
194
+ * Chrome's WebRTC audio processing is very good at steady broadband noise: measured on
195
+ * this project's own cafe fixture it drops RMS from 0.0577 to 0.0055, so the clip is
196
+ * rejected by the RMS floor before the modulation condition is ever consulted. That is a
197
+ * fine outcome in production and a terrible one in a test, because it would leave the
198
+ * third gate condition unexercised while looking green. The suite therefore verifies the
199
+ * gate in its PESSIMISTIC configuration - raw microphone, no browser help - which is what
200
+ * a browser without WebRTC processing actually hands us. Production keeps it on.
201
  */
202
  export function createMic({
203
  emit = () => {},