ブラウザ上で録音できるツールをflask + recorder.js + p5.js on TypeScript で作る

Web Audio APIのラッパーであるrecorder.jsを用いて簡易レコーダーを作成します。ブラウザ版Processingであるp5.jsをtsで書いてUI実装します。


Web Audio API のラッパーである recorder.js を用いて簡易レコーダーを作成します。ブラウザ版 Processing である p5.js を ts で書いて UI 実装します。

フィールドレコーディング:スタジオ外での自然音や環境音の録音 自然音や環境音を手軽に集めたい,そしてそれを PC へ送りリアルタイムに処理したい,といったニッチな要望に応えるものを作った感じです

完成イメージ

イメージといってもスクリーンショットなんでこういう感じで動きます。

animation screenshot

(たぶん)ササっと環境構築して動かせるので興味ある方は是非。

recorderjs でフロント側で音声録音する

GitHub: https://github.com/mattdiamond/Recorderjs

まずデモはこちらSimple Recorder.js demo

Web Audio API のラッパーみたいな感じでしょうか。AudioNode のインスタンスを渡せば簡単に録音スタート・ストップ・保存ができる,という優れモノ。

以下のような感じで録音開始の関数定義ができるので,任意のイベントで呼べば良い。

1
2
3
4
5
6
7
8
9
10
11
let recorder: Recorder

const startUserMedia = (stream: MediaStream) => {
  audio_context = new AudioContext()
  let input: AudioNode = audio_context.createMediaStreamSource(stream)
  recorder = new Recorder(input)
}

const startRecording = () => {
  recorder && recorder.record()
}

で,このRecorder.exportWAV()メソッド一発で wav の Blob オブジェクトが手に入るので,ソレを ajax で POST してあげれば良い。

1
2
3
4
5
6
7
8
9
10
11
12
13
recorder &&
  recorder.exportWAV((blob: Blob) => {
    let url = URL.createObjectURL(blob)
    let fd = new FormData()
    fd.append('data', blob)
    $.ajax({
      type: 'POST',
      url: '/',
      data: fd,
    }).done((data) => {
      recorder.clear()
    })
  })

flask で POST された wav ファイルを保存する

flask 側ではこんな感じに書けば良い。

1
2
3
4
5
6
7
8
9
10
from flask import Flask, jsonify, request


@app.route('/', methods=['POST'])
def uploaded_wav():
    fname = "sounds/" + datetime.now().strftime('%m%d%H%M%S') + ".wav"
    with open(f"{fname}", "wb") as f:
        f.write(request.files['data'].read())
    print(f"posted sound file: {fname}")
    return jsonify({"data": fname})

これでsounds/直下に1104235900.wavみたいなファイルがどんどん溜まっていく。

保存されたファイルのパスを osc で送る

個人的にこのアプリケーションをパフォーマンスで使用したいので,サウンドファイルが保存されたタイミングで osc にメッセージを飛ばしてみる。コレで例えばサーバとなっているローカルの PC で Max/MSP や Max for Live を用いたリアルタイムでのサウンドファイル読み込みがラクになる(と信じている)

pythonoscというパッケージを用いる。(pip install python-oscで入る)

python-osc PyPI: https://pypi.org/project/python-osc/

1
2
3
4
5
6
7
8
9
10
11
12
from pythonosc import dispatcher, osc_message_builder, osc_server, udp_client


address = "127.0.0.1"
port = 5050
client = udp_client.UDPClient(address, port)


def send_osc(msg):
    msg_obj = osc_message_builder.OscMessageBuilder(address=address)
    msg_obj.add_arg(msg)
    client.send(msg_obj.build())

これで良い。あとは上述のuploaded_wav()内でsend_osc(fname)してあげれば,ファイルパスがメッセージとして届く。Max なら[udpreceive 5050]しておけば open&sfplay~して再生できる。

p5.js

p5js.org: https://p5js.org/

DOM がいじれる Processing という感じで,Canvas 要素に描画するので CSS で複雑なアニメーションを描いているとかしなくても,canvas が動くブラウザなら良いしこっちのがラクかもしれないです。また,Web Editor(https://editor.p5js.org/) というものがあり,環境構築ナシで挙動が試せるので非常にとっかりやすいと思います。

TypeScript を導入するなら,まず以下のリポジトリを使うべきです(めっちゃラクだった)

Gaweph/p5-typescript-starter

Base starter project using p5js and typescript: Contribute to Gaweph/p5-typescript-starter development by creating an account on GitHub.

かつ,以下のエントリを参考にしました

あとは,ササっと書いていくだけです。例として UI の録音ボタンの部分のクラスをおいておきます…

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
class Button {
  private w: number
  private h: number
  private centerX: number
  private centerY: number
  private radius: number
  private isRecording: boolean
  private rectCircleRatio: number
  private progress: number // 0 ~ 300 value (about 5s)

  constructor(w: number, h: number, size: number) {
    this.w = w
    this.h = h
    this.centerX = w / 2
    this.centerY = h / 2
    this.radius = size
    this.isRecording = false
    this.rectCircleRatio = size / 2
    this.progress = 0
  }

  isTouched(x: number, y: number) {
    if ((x - this.centerX) ** 2 + (y - this.centerY) ** 2 < this.radius ** 2) {
      return true
    }
    return false
  }

  switchRecording() {
    this.isRecording = !this.isRecording
    console.log(`switched to recording: ${this.isRecording}`)
    if (this.isRecording) {
      startRecording()
    } else {
      this.progress = 0
      stopRecording()
    }
  }

  draw() {
    if (this.progress == 300) {
      this.progress = 0
      this.switchRecording()
    }
    if (this.isRecording) {
      if (this.rectCircleRatio > 5) {
        clear()
        this.rectCircleRatio -= 5
      }
      this.progress++
    } else {
      if (this.rectCircleRatio <= this.radius / 2) {
        clear()
        this.rectCircleRatio += 5
      }
    }
    drawCircleUI((this.progress * 2 * PI) / 300)
    noStroke()
    fill(mainColor)
    rect(
      this.centerX - this.radius / 2,
      this.centerY - this.radius / 2,
      this.radius,
      this.radius,
      this.rectCircleRatio,
    )
    // text
    fill(white)
    textAlign(CENTER, CENTER)
    textSize(16)
    if (this.isRecording) {
      text('STOP', this.centerX, this.centerY)
    } else {
      text('REC', this.centerX, this.centerY)
    }
  }
}

リポジトリ

atsukoba/AudioSampleRecorder

Audio recording on the Web using Web Audio API for remote real-time environmental sound collection. python3 and packages listed in requirements.txt nodejs and packages listed in package.json tmux ngrok recorder-js git clone -r https://github.com/atsukoba/AudioSampleRecorder.git npm install sh ngrok-install.sh then put your ngrok auth-token if using pip and HomeBrew, run this prepared script.

実際に活用できるので気が向いたらどうぞ。osc-webappと同じく,ngrok で https トンネルほって公開してます。(https じゃないと Web Audio API が使えない)