発端

    • Jupyter Lab で音を録りたい。

localhost で動かすなら、sounddevice で録音できるが、遠隔サーバで動く Jupyter Lab ではそうもいかない
クライアントPCのウェブブラウザで音を録れたらいいな。Web Audio APIに手を出すか!

ipywebrtc は欲しいものに近いのだが、やはり ScriptProcessor を使っているようである。

現状では、ScriptProcessor を使った例を公開しているウェブページが多い印象。
映像はいいから、音声をサクッと手に入れたいのだ。

Jupyter extension の開発

ipyaudioworklet という Jupyter Lab Extension 作りました。
正確には、ipywidgets の custom widgets なのかもしれませんが、Jupyter Lab で動かすことしか考えていません。

 

Binder を使って、デモとして、録音GUIの表示と、録音後のちょっとしたアプリケーション例を公開しています。

 

ipywidgets に依存しているので、Google colabでは、多分動かないと思われます。

ウリ

    • どこかの開発ノード上で動かしている Jupyter Lab を使っていても、

 

    • クライアントPCのマイクで収録した音を、

 

    • OPUS encoding されたWAVファイルではなく、生のPCMとして録音できるし、

 

    一発で numpy.ndarray(dtype=numpy.float32) として音声データ列が手に入るよ

という言葉が刺さる音声研究者は多い・・・と思っています。

オフィシャル寄りな情報源

だいたい、以下を参考にしながら作ることになりました。

 

ただ、公式の情報通りに各ブラウザが実装しているとも限らない。。。

興味深かった点

どうやら最近の API では、Auto Gain Control, noise suppression とか echo cancel が内蔵(?)されているんですね。
明示的に無効化しないと、自動ですべて有効化されるようです。(デバイス依存?)

ipyaudioworklet では、あえて無効化しましたが・・・

開発上のつらかった点

Jupyter Lab と Web Audio API が混ざることで、つらさの化学反応が発生します。

以下、乱文。

主に Web Audio API 関係

OSやブラウザごとに挙動が違いすぎる、の一言に尽きるのです。
例えば:

sampleRate を変える実装は実現できるのだろうか・・・?

    • 基本的には、AudioContext と MediaDevices.getUseMedia() の constraints の両方で sampleRate を指定すればよさそうだが、まぁ動かない。

そもそも AudioContext で指定したところで、返ってくるインスタンスは sampleRate が固定される。

getUserMedia の constraints でsampleRateを指定するようなサンプルが、巷にあふれかえっているが、すごくヤバい。
– 何を指定しようが AudioContext のsampleRate依存で録っている動きをするブラウザがある。
– そして、AudioTrack インスタンス の getSettings() の戻り値を確認すると、constraints で指定した値を返してくれたりする。(だが、この数字はまったく意味がない)

44,100 Hz を指定したつもりが、実際は 48,000 Hz で録られている、なんてのもある。
そのぐらいの差だと、自分の音声とかを聞きなれていない人は「こんなものか」と思ってしまうのではないか。

AudioContext はデフォルトのまま生成されるがままに利用、getUserMedia の constraints の sampleRate には AudioContextインスタンスのsampleRate を与える、という実装が一番無難な形だった。

32 bit float で音データが得られるが、ハードウェア的に量子化ビット数が 32 bit というわけではない

    • 量子化ビット数が 16 bit だろうが 24 bit だろうが、ともかく得られるのは -1 ~ +1 というだけ。

16 bitの整数を32768($=2^{15}$)で割った実数を手に入れたところで、収録時点の量子化ビット数は16 bitなわけで。。。

getUserMedia の後で、実際に設定された量子化ビット数(sampleSize)を手に入れられるが、これまたブラウザ依存であり、値を返すブラウザと、そんな項目すら返してくれないブラウザが存在する。
結局のところ、constraints で sampleSize は与えられそうだが、sampleRateに関する挙動を思うと、正しく働いているのかどうか、定かではない。

Noise supplession 等の機能は面白い、が、、、チャネル数が変わってしまう

マルチチャネル信号を処理して、シングルチャネル信号を作ってると考えれば、それはそう、と納得できる。
だが・・・

autoGainControl, echoCancellation, noiseSuppression を False にした場合、チャネル数がマチマチになる

ブラウザによって、2チャネル返ってきたり、1チャネル返ってきたり。
constraints でチャネルを 1 にしたら 1 にしてほしい。(exact指定しても効かない)

無駄が多いなと思いつつ、ipyaudioworklet では、全チャネルを平均する処理を強制している。

Jupyter Lab (or, ipywidget extension) 関係

    • Jupyter Lab 側の公式のチュートリアルを実行しようとすると、日本語圏のWindowsユーザは、おそらくテンプレを用意することすらできない

参考:https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html

copier の問題。

https://peps.python.org/pep-0540/ を参考に、windows端末なら set PYTHONUTF8=1 で環境変数設定すれば、問題なく作れるようになる。

Jupyter Labチュートリアルが難しい

これは、自分の理解力&技術力がなさすぎるせいなので、どうしようもない。。

ipywidgets のチュートリアルに沿って作ると比較的楽だとわかったので、そちらに移行。

参考:https://github.com/jupyter-widgets/widget-ts-cookiecutter

化学反応

    • Web Audio API のいくつかについては、HTTPSコンテキスト が必要とされている。

氾濫している ScriptWorkletProcessor なら考えなくてもよい
Jupyter Lab の拡張機能として動かしたいのだから、Jupyter Lab でもSSL証明書を与えないといけない

適切な管理者がいるような Jupyter Hub Lab でも使ってない限り、http で使い続ける人が多いのでは。(音の研究者では、そんなこと考える人は限りなくレアと思われる。)
オレオレ証明書の作成経験があるとどうにかなる。(そんな経験がある、オーディオエンジニアがどれぐらいいるというのか・・・?)

AudioWorkletProcessor の実装は、呼び出し側とは独立した JavaScript ファイルとして用意する必要がある

ipywidget の custom widget テンプレで typescript による実装を選ぶと、webpack がもれなく1つのjsファイルにまとめて minify してくれるし、なんならファイル名もユニークな名前にしてくれる。(固定の名前で参照できない。)
強引な方法で独立した js ファイルをextensionディレクトリにコピーするという荒業で回避はできる。(もっといい方法がありそうだが、これもウェブ系の開発知識不足でどうにもできない。)

おわりに

音のことをよくわかっていて、かつ、Web系の開発の知識も豊富で、かつ、Windows & Linux (& Mac) にある程度精通していて、かつ、サーバ管理もどんと来い、みたいな人材が世の中にもっと増えたらいいな、と思いました。

Acknoledgements

素晴らしい先人の開発者の皆様に感謝します。

 

广告
将在 10 秒后关闭
bannerAds