File size: 2,915 Bytes
0fea377
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
const SAMPLING_RATE = 16_000;

const Microphone = {
  mounted() {
    this.mediaRecorder = null;

    this.el.addEventListener("mousedown", (event) => {
      this.startRecording();
    });

    this.el.addEventListener("mouseup", (event) => {
      this.stopRecording();
    });
  },

  startRecording() {
    this.audioChunks = [];

    navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
      this.mediaRecorder = new MediaRecorder(stream);

      this.mediaRecorder.addEventListener("dataavailable", (event) => {
        if (event.data.size > 0) {
          this.audioChunks.push(event.data);
        }
      });

      this.mediaRecorder.start();
    });
  },

  stopRecording() {
    this.mediaRecorder.addEventListener("stop", (event) => {
      if (this.audioChunks.length === 0) return;

      const audioBlob = new Blob(this.audioChunks);

      audioBlob.arrayBuffer().then((buffer) => {
        const context = new AudioContext({ sampleRate: SAMPLING_RATE });

        context.decodeAudioData(buffer, (audioBuffer) => {
          const pcmBuffer = audioBufferToPcm(audioBuffer);
          const buffer = convertEndianness32(
            pcmBuffer,
            getEndianness(),
            this.el.dataset.endianness
          );
          this.upload("audio", [new Blob([buffer])]);
        });
      });
    });

    this.mediaRecorder.stop();
  },

  isRecording() {
    return this.mediaRecorder && this.mediaRecorder.state === "recording";
  },
};

function audioBufferToPcm(audioBuffer) {
  const numChannels = audioBuffer.numberOfChannels;
  const length = audioBuffer.length;

  const size = Float32Array.BYTES_PER_ELEMENT * length;
  const buffer = new ArrayBuffer(size);

  const pcmArray = new Float32Array(buffer);

  const channelDataBuffers = Array.from({ length: numChannels }, (x, channel) =>
    audioBuffer.getChannelData(channel)
  );

  // Average all channels upfront, so the PCM is always mono

  for (let i = 0; i < pcmArray.length; i++) {
    let sum = 0;

    for (let channel = 0; channel < numChannels; channel++) {
      sum += channelDataBuffers[channel][i];
    }

    pcmArray[i] = sum / numChannels;
  }

  return buffer;
}

function convertEndianness32(buffer, from, to) {
  if (from === to) {
    return buffer;
  }

  // If the endianness differs, we swap bytes accordingly
  for (let i = 0; i < buffer.byteLength / 4; i++) {
    const b1 = buffer[i];
    const b2 = buffer[i + 1];
    const b3 = buffer[i + 2];
    const b4 = buffer[i + 3];
    buffer[i] = b4;
    buffer[i + 1] = b3;
    buffer[i + 2] = b2;
    buffer[i + 3] = b1;
  }

  return buffer;
}

function getEndianness() {
  const buffer = new ArrayBuffer(2);
  const int16Array = new Uint16Array(buffer);
  const int8Array = new Uint8Array(buffer);

  int16Array[0] = 1;

  if (int8Array[0] === 1) {
    return "little";
  } else {
    return "big";
  }
}

export default Microphone;