LispからPortAudioでデバイス情報を得る

Common Lispからcl-portaudioを使ってPortAudioを呼び出して音を再生したいのですが、まずはデバイス情報を得るところまでやりました。

cl-portaudioを利用するには例によってQuicklispを使うと便利みたいです。

(ql:quickload :cl-portaudio)

とはいえ、bodge-sndfileのように依存ライブラリを自動的にインストールする仕組みにはなっていないようで、自分でPortAudioはインストールしておく必要があります。

libportaudioのインストール

MacだとHomebrewを使ってbrew install portaudioとすると(.bashrcなり.zshrcなりにちゃんとbrew関連の記述があれば)勝手にライブラリへのパスも通るみたいで、なんの問題もなくcl-portaudioもインストールできました。Windowsでは引っかかったのでメモを残しておきます。

ふだんはmacOSを使っているためWindowsの仕組みがよくわかっていないので、そもそもソースコードで配布されているライブラリをどうやってコンパイルすればいいのか、というところから分かっていません。Cygwin上でビルドしたら使えるだろうと思って試行錯誤しましたが、なかなかうまくいきませんでした。Redditでも似たような問題にぶつかっている人がいて、いくつか解決策が提案されていました。そのうちのひとつ、場当たり的かつ危険な解決策をやってみました。

バイナリ配布されているLibPortAudioをダウンロードしてきて、32 bit版(libportaudio32bit.dll)をC:\Windows\SysWOW64に、64 bit版(libportaudio64bit.dll)をC:\Windows\System32に入れるというものです(32と64が逆じゃなく、このようになってます*1)。ファイル名はどちらもlibportaudio.dllに改名しました。どこかから手に入れたバイナリをシステムに管理者権限で入れるのは気持ち悪いだけでなく、とても危険な行為なんですが、これで動いたので、とりあえずはこのままにしておきます。賢明な読者諸氏は真似しないように。

(2023-08-18追記:REPLで下記のプログラムを実行するフォルダ(つまり作業ディレクトリ)にlibportaudio.dllを入れておくことでも動作しました。)

バイス情報を得る

(2023-08-18追記:下記と同じことをする(pa:print-devices)という関数が用意されていました。ドキュメントは隅まで読まないとですね。)

音の入出力をやる前に、オーディオ・デバイスの情報を確認する必要があります。(pa:with-audio)はPortAudioの初期化と終了を面倒見てくれるので、そのあいだに色々と処理を入れてあげるようです。pa:get-version-textでPortAudioのバージョン情報、pa:get-device-countで接続されているデバイスの数がわかります。デバイス数が分かれば、その回数だけループを回してデバイス情報をとってきます(pa:get-device-info)。デバイス情報からは、ホストAPI、サンプリング周波数、入出力チャンネル数、レイテンシーなどの情報が得られるので、それを一覧表にして出力します。

(pa:with-audio
  (format t "~A~%~%" (pa:get-version-text))
  (dotimes (k (pa:get-device-count))
    (let ((dinfo (pa:get-device-info k)))
      (format t (concatenate 'string
                             "~A: ~A~%"
                             "            Host API: ~A~%"
                             "       Sampling Freq: ~A Hz~%"
                             "  Number of Channels: ~A in / ~A out~%"
                             "       Input Latency: ~A - ~A ms~%"
                             "      Output Latency: ~A - ~A ms~%~%")
              k
              (pa:device-info-name dinfo)
              (pa:host-api-info-name (pa:get-host-api-info
                                      (pa:device-info-host-api dinfo)))
              (pa:device-info-default-sample-rate dinfo)
              (pa:device-info-max-input-channels dinfo)
              (pa:device-info-max-output-channels dinfo)
              (round (* 1000 (pa:device-info-default-low-input-latency dinfo)))
              (round (* 1000 (pa:device-info-default-high-input-latency dinfo)))
              (round (* 1000 (pa:device-info-default-low-output-latency dinfo)))
              (round (* 1000 (pa:device-info-default-high-output-latency dinfo)))))))

上記を実行すると以下のような出力が得られます(一部だけ抜粋しています)。以下はWindowsデスクトップ機で実行したものですが、これはフロントパネルについているライン入力(緑端子)についての情報です。デフォルトで44100 Hzの標本化周波数、2チャンネル入力で、レイテンシーは10~30ミリ秒程度ということがわかります。(先頭の17はデバイス番号です)

17: Line in at front panel (Green) (Line in at front panel (Green))
            Host API: Windows WDM-KS
       Sampling Freq: 44100.0d0 Hz
  Number of Channels: 2 in / 0 out
       Input Latency: 10 - 40 ms
      Output Latency: 10 - 40 ms

他にも、Line 6 Sonic Port VXというオーディオインターフェースはASIO対応していて、2イン・2アウトのフルデュプレックスだということもわかります。ここで得られているレイテンシーの値を信用していいのか分かりませんが、調子が良ければ6ミリ秒くらいまで下がるらしいことが読み取れます。

12: ASIO Sonic Port VX
            Host API: ASIO
       Sampling Freq: 44100.0d0 Hz
  Number of Channels: 2 in / 2 out
       Input Latency: 6 - 46 ms
      Output Latency: 6 - 46 ms

Macの場合

一方、Macで同じプログラムを実行して、出てきた表示を一部抜粋してみると次のようになりました。内蔵スピーカと内蔵マイクは別々のデバイスなので、フルデュプレックスとしては使えない一方で、Sonic Port VXはWindowsの時と同じような感じです。レイテンシーの値が異なりますが、ASIOとCore Audioの違いや、指定したサンプリング周波数の違いが影響しているのだと思います。(内蔵スピーカには入力チャンネルがないはずなのに入力レイテンシー情報があるのは不思議ですね)

2: MacBook Proのスピーカー
            Host API: Core Audio
       Sampling Freq: 48000.0d0 Hz
  Number of Channels: 0 in / 2 out
       Input Latency: 10 - 100 ms
      Output Latency: 19 - 28 ms
4: Sonic Port VX
            Host API: Core Audio
       Sampling Freq: 48000.0d0 Hz
  Number of Channels: 2 in / 2 out
       Input Latency: 10 - 100 ms
      Output Latency: 5 - 14 ms

*1:WOW64はWindows 32-bit On Windows 64-bitの略で、32ビット用のバイナリを64ビット上で動かすためのエミュレータのようです。歴史的経緯によってSystem32には64ビット用のバイナリを入れることになっていて、名前が紛らわしいです。参考:WOW64 - Wikipedia