フロントエンドで音声データを扱うときの基礎知識

最近音声データを触ることが多かったので、基本的な用語の説明だけしていく。

## sampleRate
単位 : Hz
1秒間のデータを分割する数。
6000Hzだと、0.01秒ごとに分割。
この分割した1つ1つのデータをサンプリングと呼ぶ。
CDは44.1KHz。ChromeのStreamingも44.1KHz。

## bitDepth
単位 : bit
デフォルト : 16bit
サンプリングに対して与える情報量。
何bit与えるかを示す。

## channels

音をどの程度分けるか。多いほど立体感のある音が作れる。
1 なら モノラル, 2 なら ステレオ。
基本的には、音声なら 1, 音楽なら 2 以上。

## sampleRateを修正するJavaScript

48KHzを16KHz(LINEAR16)で保存するには、 `downSampleBuffer(buff, 48000, 16000)` で呼び出す。

```javascript
export const downsampleBuffer = (buffer, sampleRate, outSampleRate) => {
  if (outSampleRate > sampleRate) {
    console.error('downsampling rate show be smaller than original sample rate');
  }

  const sampleRateRatio = sampleRate / outSampleRate;
  const newLength = Math.round(buffer.length / sampleRateRatio);
  const result = new Int16Array(newLength);
  let offsetResult = 0;
  let offsetBuffer = 0;
  // bpsを縮める処理 (n byte分のデータを合算して、n byteで割る)
  while (offsetResult < result.length) {
    const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);

    // 次のoffsetまでの合計を保存
    let accum = 0;
    let count = 0;
    for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i += 1) {
      accum += buffer[i];
      count += 1;
    }

    // 16進数で保存 (LINEAR16でストリーミングするため)
    result[offsetResult] = Math.min(1, accum / count) * 0x7FFF;
    offsetResult += 1;
    offsetBuffer = nextOffsetBuffer;
  }
  return result.buffer;
};

```


## PCM (pulse code modulation)
圧縮していない生データのこと。
拡張子 raw はPCMである。
rawデータに sampleRate, bitDepth, channelsなどのメタ情報をヘッダに加えたものがwavファイル。 と思っていたが、wavファイルはrawデータ以外も使えるらしい。

https://tech.jspnet.biz/knowledge/open.knowledge/view/9?keyword=PCM


## WAVファイルのヘッダ情報

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14275/8c0f4621-5f33-cb79-7f92-4d32b76257d5.png)
> http://soundfile.sapp.org/doc/WaveFormat/

## Int16Array (16bitのデータをwavに変換するJavaScript)

```javascript
export const createWavHeader = (view, config) => {
  const {
    sampleRate, bitDepth, channel, dataLength,
  } = config;
  writeUTFBytes(view, 0, 'RIFF');

  // file length
  view.setUint32(4, dataLength + 32, true);
  // set file size at the end
  writeUTFBytes(view, 8, 'WAVE');
  // FMT sub-chunk
  writeUTFBytes(view, 12, 'fmt ');
  // chunk size
  view.setUint32(16, 16, true);
  // format code
  view.setUint16(20, 1, true);
  // channels : (current use monoral : 1)
  view.setUint16(22, channel, true);
  // sampling rate
  view.setUint32(24, sampleRate, true);
  // data rate
  view.setUint32(28, sampleRate * 2 * channel, true);
  // data block size
  view.setUint16(32, channel * 2, true);
  // bits per sample (bitDepth)
  view.setUint16(34, bitDepth, true);
  // data sub-chunk
  writeUTFBytes(view, 36, 'data');
  // DUMMY data chunk length (set real value on export)
  view.setUint32(40, dataLength, true);
};

export const exportWAV = (
  channel, buffers /* Int16Array */, wavConf = { sampleRate: AUDIO_SAMPLE_RATE, bitDepth: AUDIO_BIT_DEPTH }
) => {
  const dataLength = buffers.length * channel * 2;
  const buffer = new ArrayBuffer(44 + dataLength);
  const view = new DataView(buffer);

  // copy WAV header data into the array buffer
  createWavHeader(view, { ...wavConf, channel, dataLength });

  // write audio data
  writeFromInputBuffers(view, channel, 44, buffers);
  return new Blob([view], { type: 'audio/wav' });
};

```

## 音声認識系でよく利用される形で録音

```
$ brew install sox
```

音声認識サービスの自動認証は、wavファイルのヘッダ形式を見て、データが違うとエラーになる。

- LINEAR16(16bit)
- 16kHz
- モノラル

```
rec --rate 16k --bits 16 --channels 1 test.wav
```

30秒だけ録音

```
rec --rate 16k --bits 16 --channels 3 test.wav trim 0 30
```

## ハイレゾ（ High-Resolution Audio）
ビットレートがめちゃくちゃ高いぜということ。

|凄さ|sampleRate(KHz)|bitDepth(bit)|channels|bitRate(kbps)|
|---|---|---|---|---|
通常 |44.1 | 16 | 1 | 705.6 
通常 |44.1 | 16 | 1 | 705.6 
ハイレゾ1 |96 | 24 | 2 | 4608
ハイレゾ2 |192| 24 | 2 | 9216