start method

Future<void> start({
  1. required void onChunk(
    1. Float32List chunk,
    2. bool isFinal
    ),
})

Implementation

Future<void> start({
  required void Function(Float32List chunk, bool isFinal) onChunk,
}) async {
  await stop();

  _recorder = AudioRecorder();

  final recordStream = await _recorder!.startStream(
    // We explicitly disable ALL hardware processing to ensure the "ه"
    // breathy sound isn't filtered out as background noise by Android.
    const RecordConfig(
      encoder: AudioEncoder.pcm16bits,
      sampleRate: recordSampleRate, // 16000
      numChannels: numChannels,
      autoGain: false,
      echoCancel: false,
      noiseSuppress: false,
    ),
  );

  _subscription = recordStream.listen((Uint8List rawData) {
    // Ensure 16-bit alignment for downstream operations
    if (rawData.offsetInBytes % 2 != 0) {
      rawData = Uint8List.fromList(rawData);
    }

    Uint8List allBytes;
    if (_frameBuffer.isEmpty) {
      allBytes = rawData;
    } else {
      allBytes = Uint8List(_frameBuffer.length + rawData.length);
      allBytes.setAll(0, _frameBuffer);
      allBytes.setAll(_frameBuffer.length, rawData);
    }

    int offset = 0;
    // Process in exact 480ms blocks
    while (allBytes.length - offset >= recordChunkBytes) {
      final byteView = Uint8List.view(
        allBytes.buffer,
        allBytes.offsetInBytes + offset,
        recordChunkBytes,
      );
      offset += recordChunkBytes;

      final int16samples = Int16List.view(
        byteView.buffer,
        byteView.offsetInBytes,
        recordChunkBytes ~/ bytesPerSample,
      );

      final float32Samples = Float32List(int16samples.length);

      // ── Direct Linear Conversion ──
      // Exactly matches the Python training pipeline: `wav.astype(np.float32) / 32768.0`
      for (int i = 0; i < int16samples.length; i++) {
        float32Samples[i] = int16samples[i] / 32768.0;
      }

      // Stream all audio directly to Sherpa ASR!
      onChunk(float32Samples, false);
    }

    // Keep the remainder for the next stream event
    if (offset < allBytes.length) {
      _frameBuffer = Uint8List.fromList(
        Uint8List.view(allBytes.buffer, allBytes.offsetInBytes + offset),
      );
    } else {
      _frameBuffer = Uint8List(0);
    }
  });
}