LoginSignup
1
0

ipyaudioworklet: Audio Worklet Processor を定義した custom ipywidgets を作った話

Last updated at Posted at 2023-11-11

発端

  • 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, noiseSuppressionFalse にした場合、チャネル数がマチマチになる
    • ブラウザによって、2チャネル返ってきたり、1チャネル返ってきたり。
    • constraints でチャネルを 1 にしたら 1 にしてほしい。(exact指定しても効かない)
  • 無駄が多いなと思いつつ、ipyaudioworklet では、全チャネルを平均する処理を強制している。

Jupyter Lab (or, ipywidget extension) 関係

化学反応

  • 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

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

1
0
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
0