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

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
78
79
80
81
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が使えない)