ラズパイとNodejsとnaudiodonでデジタル・オーディオ信号処理をやるとこうなる!(2020年準備版)
こんにちは!
10年以上も前にやっていた仕事の知識が、令和になった今更、趣味や仕事に役立っているので、もしかしてこのために昔あれをやっていたのか!と、最近驚きを持って受け入れている、しずかなかずしです。
デジタル・オーディオ信号処理と聞いて何を思い浮かべますか?
私は10年以上も前に、そんな用語で表現されるような仕事をしてました。今はすっかり離れていましたが、その頃の知識が最近役立っています。ソフトウェアという流行り廃りの激しい世の中。このような古い知識でも何かの足しにはなるものですね。
さて、前回はUSB超小型マイクがRaspberry piで動作するのか?というお話でした。
Windows用の超小型マイクも、USB Audio Classという標準的なものであれば、Linuxでも問題なく使えるという内容。ポイントは、ALSAというLinuxのアーキテクチャ。これに則っているので、同じくUSB Audio Classに標準対応しているWindows PCで使えるUSBマイクならLinuxで使えそうです。標準対応というのは言い換えると追加のドライバーのインストール無しで使える、という事です。
今回は、Raspberry Piを使って極小USBマイクを、Nodejsのプログラムから操作します。Nodejs でオーディオをファイルに録音してみようという企画です。
Nodejs+naudiodonでオーディオ信号処理
なぜ、Nodejsなのでしょう?
答えは、最近私が趣味でJavaScriptにハマっているからです。それ以上でも、それ以下でもありません。
だから、小さな非力なボード型のコンピューターであるRaspberry Piであろうと、Nodejsを使ってJavaScriptでプログラミングしたいのです。
昔はオーディオ信号処理のような大量のデータを限られた時間に処理しなければならないようなタスクは、C言語やC++の独壇場でした。
私も以前仕事で信号処理のプログラムを書くのは、当時のあたり前、C++でした。
ところが最近は、AIのニューラルネットワークで行う大量のデータを使った演算もPythonのような非コンパイラ言語(スクリプト言語)で扱えるようになってきました。
そうなると、我らのスクリプト言語JavaScriptでもきっとできるに違いない。
少し検索してみたところ、Nodejs で オーディオ信号を扱うのは naudiodonというモジュールが使えそうです。
一般的に、USBマイクに入ったアナログ信号はADコンバーターでデジタル信号になります。このデータはリアルタイムにどんどんコンピューターに取り込まれます。
そして、その録音データをJavaScriptで扱うためにはJavaScriptの配列にデータが格納される必要があります。
naudiodonというモジュールは、LinuxのALSAが受け取ったデジタル・オーディオデータを定期的に一定サイズのJavaScriptの配列に格納してくれて、私の作るJavaScriptのプログラムに渡す、仲介業者のようなイメージ。
naudiodonをRaspberry Piで使うためには
naudiodonというモジュールは、JavaScriptだけで書かれているモジュールではありません。USBという物理的なデバイスとやり取りをする関係で、C言語で書かれたコードでデバイスにアクセスし、それをJavaScriptへの橋渡しをするようなものになっているようです。具体的には、PortAudioというクロスプラットフォームのオーディオ処理を行うライブラリ経由でALSAへアクセスするようなのです。そして、naudiodonの配布パッケージは、PortAudioのARM Linux向けのバイナリファイルを含んだ状態で配布されているようです。
イメージ的にはこんな感じの構成になっている(推測含む)
そのため、パッケージをインストールするコマンド(npm install naudiodon)を実行すると、実行するシステムに合わせてC言語のコードのコンパイルが走ります。
つまり、単なるJavaScriptモジュールのインストールではなく、実行する環境(OS)に応じて必要なバイナリにコンパイルされます。
これが曲者。
私は、Raspberry Pi向けのコンパイルができずに少々嵌りました。
npm installを実行すると、NodejsとPortAudioをつなぐソースコード(Adaptation layerとでもいうのか)をコンパイルします。
しかし、Raspberry Piでこの作業を行うと、実行時にPa_GetVersionInfoがないと言われます。
/home/pi/.nvm/versions/node/v13.14.0/bin/node: symbol lookup error: /home/pi/dev/myCoolProject/node_modules/naudiodon/build/Release/naudiodon.node: undefined symbol: Pa_GetVersionInfo
これは困った…。PortAudioのARM用のライブラリが古いのか??
仕方がないので、PortAudioの最新のソースコードを持ってきてコンパイルしました。
コンパイルは、以下のような作業になります。
git clone https://git.assembla.com/portaudio.git
cd portaudio
./configure
make clean
make
注意点としては、PortAudioのビルドには、ALSAの開発用モジュール(libasound2-dev)が必要。
sudo apt install libasound2-dev
できたライブラリ(libportaudio.so.2)をnode_modulesディレクトリの適切な場所にコピーします。
cp ~/portaudio/lib/.libs/libportaudio.so.2 node_modules/naudiodon/build/Release/
オーディオ・データをJSでファイルに取り込む
naudiodonが使えるようになったところで、JavaScriptで録音。録音データをファイルに落としてみましょう。
ファイルへの書き込みはNodejsのFSという組み込みのライブラリを利用します。ここでファイルに落とすときの、ファイルフォーマットはヘッダがつかないrawデータの形式です。
JavaScript(Nodejs)の良いところは非同期処理が非常に書きやすいこと。リアルタイムに処理するには、非同期で1つの処理に時間がかからないように作るのがポイントになるのです。
という訳で、ビット長が16bit、サンプリング周波数が44.1kHz、チャンネル数は1chで録音するプログラムを書いてみました。
サンプリング周波数などのパラメータは、再生するときに音を正しく再現するのに必要なものですが、rawデータ形式はこういったパラメータが付加されないフォーマット。一般的に音楽を録音するファイルフォーマットであるMP3やWAVといったファイル単体で正しく再生できるフォーマットでは、ビット長やサンプリング周波数はファイルのヘッダ部分に格納されいます。だからちゃんと、音の再現が可能なんですね。次回はこの辺りのパラメータを詳しく解説しますのでお楽しみに。
完成したコードは、こんな感じになりました。
そして、取得したデータがこちら。Audacityというオーディオエディタならrawフォーマットが読み込めます。プログラムで録音したときの通り、16bitと、44.1kHz(44100Hz)を正しく設定します。
再生すると、ちゃんと録音されたことがわかりますね。
ちなみに、ビット長 16bit、サンプリング周波数44.1kHzはCDと同等音質です。CDの場合はステレオ収録なのでチャンネル数は2チャンネルですが、今回はアナログのマイク入力なので1チャンネルで録音しています。
オーディオ・データ処理のおさらい
今回は、取得したデータをそのままファイルに書き込みました。
しかし、録音されたデータの音量を調べたり加工するのはどうやるのでしょう??
それをリアルタイムでやるには??
ここが本当の信号処理で、実際にプログラミングするには配列の中(上のプログラムではchunk変数)に音声データがどのように並んでいるかを理解する必要があります。
ということで次回は、音声データの形式の理解とJavaScriptで音声信号を分析や加工する際のデータの扱い方を解説します。