1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

kintoneで録音してWAVファイルを保存する

Last updated at Posted at 2021-08-27

kintoneで録音したい!
というわけで

下記サイトを参考に作ってみました。

出来上がったもの(音量注意)

アプリの準備

フィールドは一つだけ。

| フィールド種類 | フィールドコード |
|:-:|:-:|:-:|
| 添付ファイル | 音声 |

image.png

image.png

JavaScript

ライブラリの準備お忘れなく

kintone REST API Client
https://unpkg.com/@kintone/rest-api-client@2.0.14/umd/KintoneRestAPIClient.min.js

kintone UI Component
https://unpkg.com/kintone-ui-component/umd/kuc.min.js

Luxon
https://js.cybozu.com/luxon/2.0.1/luxon.min.js

また、音声ファイルにプレイヤーをつける方法はコチラを参考にどうぞ。

一覧表示後イベントでボタン設置

録音開始ボタンと録音停止ボタンを設置します。

// 一覧画面のメニュー下にボタン設置する
const sp = kintone.app.getHeaderSpaceElement();
const fname = new Kuc.Text({
  placeholder: "音声ファイル名",
  suffix: ".wav",
  textAlign: "left",
  visible: true,
  disabled: false,
});
const startButton = new Kuc.Button({
  text: "🔴録音開始",
  type: "submit",
});
const stopButton = new Kuc.Button({
  text: "⬛録音停止",
  type: "submit",
  disabled: true,
});

sp?.appendChild(fname);
sp?.appendChild(startButton);
sp?.appendChild(stopButton);

参考サイトのソースをコピーしたものを貼り付けていく

ボタン設置の次の行から
冒頭で紹介した参考サイト(ブラウザで録音してwavで保存
からコピーしてきてESLintに怒られたところを直したりしたコードを貼り付けます。

// 録音に必要な変数など宣言
let audio_sample_rate = null;
let scriptProcessor = null;
let audioContext = null;
const audioData = [];
const bufferSize = 1024;

// 録音データを保存する
const saveAudio = async () => {
  // 録音データを加工する
  // kintoneに保存する
}

録音データを加工する

冒頭で紹介したこちらのページを参考に録音した音声データをファイルにします。

// 録音データを加工する
// viewに文字を1文字ずつ書き込む
const writeString = (v, offset, string) => {
  for (let i = 0; i < string.length; i++) {
    v.setUint8(offset + i, string.charCodeAt(i));
  }
};

// float(32bit)を16bitに変換する
const floatTo16BitPCM = (output, offset, input) => {
  for (let i = 0; i < input.length; i++, offset += 2) {
    const s = Math.max(-1, Math.min(1, input[i]));
    output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  }
};

// 録音終了
await audioContext.close();

// audioDataを1次元配列にする。
const sampleLength = audioData.reduce((a, c) => a + c.length, 0);
const samples = new Float32Array(sampleLength);
let sampleIdx = 0;
for (let i = 0; i < audioData.length; i++) {
  for (let j = 0; j < audioData[i].length; j++) {
    samples[sampleIdx] = audioData[i][j];
    sampleIdx++;
  }
}

// PCM 16bit なので、1要素あたり 16bit = 2Byte つまり要素数*2でwavのチャンクサイズを計算する
const wavsize = samples.length * 2;
const buffer = new ArrayBuffer(44 + wavsize);
const view = new DataView(buffer);

writeString(view, 0, "RIFF"); // RIFF識別子 RIFFヘッダ 4Byte
view.setUint32(4, 32 + wavsize, true); // チャンクサイズ (44Byte -8 )+ データサイズ 4Byte
writeString(view, 8, "WAVE"); // フォーマット WAVEヘッダ 4Byte
writeString(view, 12, "fmt "); // fmt識別子 4Byte
view.setUint32(16, 16, true); // fmtチャンクのバイト数 4Byte
view.setUint16(20, 1, true); // 音声フォーマットID 2Byte
view.setUint16(22, 1, true); // チャンネル数 2Byte
view.setUint32(24, audio_sample_rate, true); // サンプリング周波数 4Byte
view.setUint32(28, audio_sample_rate * 2, true); // 1秒あたりバイト数の平均 データ速度 4Byte
view.setUint16(32, 2, true); // ブロックサイズ 2Byte
view.setUint16(34, 16, true); // ビット/サンプル サンプルあたりのビット数 2Byte
writeString(view, 36, "data"); // サブチャンク識別子 data 4Byte
view.setUint32(40, wavsize, true); // サブチャンクサイズ 波形データのバイト数 4Byte
floatTo16BitPCM(view, 44, samples); // 波形データ

kintoneに音声ファイルを保存

kintone REST API Clientのファイル保存メソッドを使って、
作成した音声データをアップロードします。

// kintoneに保存する
const client = new KintoneRestAPIClient();
const date = luxon.DateTime.local();
const FILE = {
  name: fname.value
    ? fname.value + "_" + date.toFormat("yyyyMMddHHmmss") + ".wav"
    : "音声_" + date.toFormat("yyyyMMddHHmmss") + ".wav",
  data: new Blob([view], { type: "audio/wav" }),
};
const { fileKey } = await client.file.uploadFile({
  file: FILE,
});
const { id } = await client.record.addRecord({
  app: kintone.app.getId(),
  record: {
    音声: {
      value: [{ fileKey }],
    },
  },
});

// 保存処理が終わったらリロード
location.reload();

録音開始ボタンと録音停止ボタンの処理

// 録音開始ボタン
startButton.addEventListener("click", function () {
  startButton.text = "💖録音中💖";
  stopButton.disabled = false;
  // getUserMedia
  const stream = navigator.mediaDevices
    .getUserMedia({ audio: true, video: false })
    .then(handleSuccess);
});

// 録音停止ボタン
stopButton.addEventListener("click", function () {
  saveAudio();
});

参考サイトのコード残りを貼り付ける

再び・・・冒頭で紹介した参考サイト(ブラウザで録音してwavで保存
からコピーしてきてESLintに怒られたところを直していったコードを貼り付けます。
MediaRecorder - Web API | MDNをご参照ください。

// save audio data
const onAudioProcess = (e) => {
  const input = e.inputBuffer.getChannelData(0);
  const bufferData = new Float32Array(bufferSize);
  for (let i = 0; i < bufferSize; i++) {
    bufferData[i] = input[i];
  }
  audioData.push(bufferData);
};

// getusermedia
const handleSuccess = (stream) => {
  audioContext = new AudioContext();
  audio_sample_rate = audioContext.sampleRate;
  scriptProcessor = audioContext.createScriptProcessor(bufferSize, 1, 1);
  const mediastreamsource = audioContext.createMediaStreamSource(stream);
  mediastreamsource.connect(scriptProcessor);
  scriptProcessor.onaudioprocess = onAudioProcess;
  scriptProcessor.connect(audioContext.destination);
};

全体的なコードはコチラ

お好みで保存時にファイル名を変えてみたり、コメントを追加してみたり、色々できそうな気がします。

まとめ

楽器の練習を録音していちいちkintoneに保存するのが面倒だったので作ってみました。
録音の重要な部分は参考サイトのコードをほぼほぼコピーして作りましたが
なんとかこれで、自分の演奏を客観的に見る(?)ことができるゾ!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?