Source: tasks/audioCalibrator.js

/* eslint-disable no-await-in-loop */
import AudioRecorder from './audioRecorder';
import PythonServerAPI from '../server/PythonServerAPI';
import {sleep, saveToCSV} from '../utils';

/**
 * Provides methods for calibrating the user's speakers
 * @extends AudioRecorder
 */
class AudioCalibrator extends AudioRecorder {
  /**
   *
   */
  constructor(numCaptures = 1, numMLSPerCapture = 1) {
    super();
    this.numCaptures = numCaptures;
    this.numMLSPerCapture = numMLSPerCapture;
    this.pyServerAPI = new PythonServerAPI();
  }

  /** @private */
  #isCalibrating = false;

  /** @private */
  sourceAudioContext;

  /** @protected */
  numCalibratingRounds;

  /** @protected */
  numSuccessfulCaptured = 0;

  /** @private */
  sourceSamplingRate;

  /** @protected */
  calibrationNodes = [];

  /** @protected */
  localAudio;

  /**
   * Called when a call is received.
   * Creates a local audio DOM element and attaches it to the page.
   */
  createLocalAudio = targetElement => {
    this.localAudio = document.createElement('audio');
    this.localAudio.setAttribute('id', 'localAudio');
    targetElement.appendChild(this.localAudio);
  };

  /**
   *
   * @param {MediaStream} stream
   * @param {Function} playCalibrationAudio - (async) function that plays the calibration audio
   * @param {*} beforePlay - (async) function that is called before playing the audio
   * @param {*} beforeRecord - (async) function that is called before recording
   * @param {*} duringRecord - (async) function that is called while recording
   * @param {*} afterRecord  - (async) function that is called after recording
   */
  calibrationSteps = async (
    stream,
    playCalibrationAudio,
    beforePlay = async () => {},
    beforeRecord = async () => {},
    duringRecord = async () => {},
    afterRecord = async () => {}
  ) => {
    this.numSuccessfulCaptured = 0;

    // do something before recording such as awaiting a certain amount of time
    await beforeRecord();

    // calibration loop
    while (this.numSuccessfulCaptured < this.numCaptures) {
      // do something before playing such as using the MLS to fill the buffers
      await beforePlay();

      // start recording
      await this.startRecording(stream);

      // play calibration audio
      playCalibrationAudio();

      // do something during the recording such as sleep n amount of time
      await duringRecord();

      // when done, stop recording
      await this.stopRecording();

      // do something after recording such as start processing values
      await afterRecord();

      // eslint-disable-next-line no-await-in-loop
      this.calibrationNodes = [];
      await sleep(3);
    }
  };

  /**
   * Getter for the isCalibrating property.
   * @public
   * @returns {Boolean} - True if the audio is being calibrated, false otherwise.
   */
  getCalibrationStatus = () => this.#isCalibrating;

  /**
   * Set the sampling rate to the value received from the listener
   * @param {*} sinkSamplingRate
   */
  setSamplingRates = samplingRate => {
    this.sinkSamplingRate = samplingRate;
    this.sourceSamplingRate = samplingRate;
    this.emit('update', {message: `sampling at ${samplingRate}Hz...`});
  };

  sampleRatesSet = () => this.sourceSamplingRate && this.sinkSamplingRate;

  addCalibrationNode = node => {
    this.calibrationNodes.push(node);
  };

  makeNewSourceAudioContext = () => {
    const options = {
      sampleRate: this.sourceSamplingRate,
    };

    this.sourceAudioContext = new (window.AudioContext ||
      window.webkitAudioContext ||
      window.audioContext)(options);

    return this.sourceAudioContext;
  };

  /**
   * Download the result of the calibration roudns
   */
  downloadData = () => {
    const recordings = this.getAllRecordedSignals();
    const i = recordings.length - 1;
    saveToCSV(recordings[i], `recordedMLSignal_${i}.csv`);
  };
}

export default AudioCalibrator;