デモの動画がYoutubeにあります。
またしてもQiitaに数多いらっしゃるプロフェッショナルの方々には何の役にも立たない話、かつ技術文書的な体裁からかけ離れただらだらとした散文で恐縮ですが1。
要旨
タイトルの通りですが、個人的にただPCで音楽を聴いてるだけではつまらないと常々思っていた訳で。
ふとした事からBass.dllという素晴らしいサウンド関係のライブラリを見つけてしまって、これで何かバーグラフ・スペアナ風のビジュアライザー的なものが何か作れないかと思ってしまったわけです。
作成前の考察とか
巷にスペアナ的ビジュアライザーのDIY的なものはハードウエア・ソフトウエアにかかわらずチラホラ見かけますが、
- 個別のプレーヤに専用で付属していて、曲ファイル毎に(手で・プレイリストで)ロードするタイプ。foobar2000のコンポーネントとかもこのタイプと言える。
- 音楽制作者向けという意味なのかマイクなどの入力デバイスから音を入力して表示するタイプ。
- ソフトウエアではなく実際にLine出力をArduidとか経由でLEDに表示するタイプ2。(ほとんどモノラルのみ対応)
- 外部小型LCDに表示するタイプ。(それならPCのモニターに表示すればいいじゃん?)
しか見つけられませんでした。
本当はハードウエア(上記3)がいいんですが、せめて16バンドxステレオ2chには対応したいな、と。バンドごとの表示分解能をそれなりにする、と考えるとLEDのマトリックスが16x分解能x2と大変なことになってしまう3。(だからこそ4.のパターンになるんでしょうけど。)
じゃあソフトウエアで我慢するか、となるわけですが、YoutubeとかSpotifyとか、あと私は使ってませんがApple MusicとかAmazon Prime Musicとかで聴いてる時(デバイスからの出力時)なんかに汎用的に使いたい。つまり上記1とか2ではダメなのです。
幸いWindows Vista以降はWASAPI(Windows Audio Session API)がふつー4になっていて、これのLoopback機能を使って鳴らしている曲のデータを横取り(キャプチャ)してくる手が使えます5。
標準でも"Windows.Media.Capture 名前空間"というのがあるのですが、さすがにセキュリティの問題6があるのかふつーは細かく使えない模様。慣れないUWPもしたくない。
さすがに自前でFFTとかいろいろスクラッチで書きたくないので調べていくうちに
というWASAPI Loopbackが使えそうな2種類のライブラリがみつかりました。
NAudioはMIT License、Bass.dllは独自ライセンスで、非商用個人は無料7です。
このうちBass.dllのほうに良さそうなスペアナのサンプルが公開されていたのでBass.dllを使うことにしたわけです。
並行してhvianna氏のaudioMotion-analyzerの素晴らしいデモを見つけてしまい、これもファイル読み込ませ型でJavascriptで書かれているのですが、ソースに良いカラースキームのサンプルが数種類あったのでこれも参考にさせてもらおうと。
方針とか
- h0uri氏のはスペクトラムのデータをWPFのユーザーコントロールに送ってXAMLでプログレスバー表示するとともにラインチャートを描いていますが、この表示をもう少しそれっぽく(偽LEDっぽく)したい。かつWPFはなるべくやめたい。
- 同じくこれは1ch(L+R Mix)64バンドですが、ステレオ2chにしたい。実用的8には16バンド/chぐらいあれば十分。できれば1chの表示もできるように残したい。
- 同じく44.1Hz固定なのをハイレゾ対応(照)したい。
- hvianna氏のソースを元に偽LEDカラーを切り替えられるようにしたい。
- 実機っぽく?各バンドの周波数を下に表示したい。
- L/Rチャンネルはその時の事情に合わせて左右と上下、両方切り替えて並べられるようにしたい。
- 実機みたいに?ピークホールドの表示をしたい。かつそれがパラパラと落ちるようにすればそれっぽい。
- ウィンドウ(Form)サイズは可変でサイズに合わせて画像も拡大縮小したい。
実装時(改造時)のポイントとか
実際のソース現物はGithubにあります。
L・Rチャンネルの分離
元のh0uri氏のAnalyzerクラスをゴリゴリ改造していくわけですが、最初に悩んだのは元のL+R Mix 1chをどうやってLとRに分離するか。
Bass(WASAPI)のドキュメントやサポート掲示板をみると、どうやらチャンネルで分離する場合は、BASS_WASAPI_GetData()
のBASS_DATA_FFT8192
の定数引数にBASS_DATA_FFT_INDIVIDUAL
をorするらしいと解りました9。こうすることによりL+R、L+R、L+R、...と送られてくるデータがL,R,L,R,L,R,...10になるらしい、と。
送るデータは1周波数バンド当たり2byteなので分解能としては256段階で、1回に送るデータの総量は1回に128byte(元は2byte x 64バンドモノラルだから)から2byte x 16バンド x 2chに変更、そのままL・R交互に送り表示側でLRを分離することにしました。
受け側でなぜか調べた話とLとRの順番が入れ替わっているような気がするのですが書き換え先のPictureBoxをLR入れ替えることでひとまず動いているので当面このまま。
あとでシリアル通信で外部のハードウエアにデータを送信したりすることも考えて一応モノラル1chでも送信できるように元のコードも残しました。
FFT部分の処理
Bass(wasapi).dllはGetData()
にBASS_DATA_FFT8192
とか指定するとデータをFFTしてくれる11ので便利なのですが、周波数なので当然X軸はlogスケールにしたい。がh0uri氏の元のロジック
バンド周波数 = Power(2, バンド位置 * 10.0 / バンド数-1) //バンド位置は0~15、とか
をそのまま使うと周波数の範囲が$2^0$~$2^{10}$、1Hz~1KHzになるような気がします。
人間の平均的な可聴限界範囲と言われる20Hz~20KHzにするためにバンド位置 * 10.0 / バンド数
の部分をPower(2,X)の逆関数(函数)で求めて20Hzはlog(20,2)
≒4.32ぐらい、20KHzはlog(20000,2)
≒14.29ぐらいにします。計算を簡単にするためにXは4.29~14.29を16分割12するような感じにしました。
音源のサンプリング周波数によってバンド位置がずれてしまうのでこれも実測13を元に適当に調整します。
描画とピークホールド
XAMLでプログレスバーを書き換えていた部分は、ふつーにデータ更新のイベントを表示用のFormクラスで受け取って左右それぞれのPictureBoxを書き換えることで問題無し。
描画はまともにLEDを1つ1つ全部グラフィックで書くと大変なので、ぶっとい点線(デフォルト30px)で描くのがポイント。レベルによって色を変えるのもグラデーションブラシで簡単にできるし軽くなります。このへんはhvianna氏のを参考にしてます。
デフォルト(サイズ伸縮なし)は実線部分3px、ブランク部分3pxで1レベルを表現、最初に見た目から表示の高さを128pxぐらい、と適当にフィーリングで決めたので128÷6≒21.33、切り上げて(3+3)x22レベル-314でPictureBoxのデフォルトの高さは最終的に129pxにしました。
データはバンド毎に2byte、つまり256段階で送られてくるので点線の実線部分とブランク部分の境界を計算してそこまで点線を引きます15。グラデーションブラシなので設定したレベルごとにグラデーションで勝手に色が変わります。Rainbow Colorの場合もグラデーションブラシの方向を90度変えることで簡単にできます。
ピークホールドは規定時間ごとの最大値を保存して1レベル分だけ描画します。規定時間の3/4が経過すると1レベルづつ落ちていきます。ホールドする時間や落ちるスピード(サイクル)を可変にするためにゴチャゴチャやってるのでこの辺は他以上にエレガントじゃないです。
最後に出来上がったBitmapをPictureBoxのサイズに合わせて伸縮貼り付けしてバーの表示は終了です。Center-HiやCenter-LowのFlip表示も文字通り貼り付け時のBitmap.RotateFlip()
で簡単。
タイマーは元の25msecを変えていないので理論上40FPSです。実際どうなのかは調べてませんが。
PictureBoxのレイアウトは「Formの大きさ→PictureBoxの大きさ」で決めるのと「PictureBoxの大きさ→Formの大きさ」で決めるのとが両方混在すると管理が難しいので「Formの大きさ→PictureBoxの大きさ」に統一。横レイアウトと縦レイアウトの変更も同様に変更後のFormのサイズを決めてからPictureBoxの位置を変更します。
バンドの周波数の数字表示も最大32個必要で位置合わせ・サイズ合わせに悩むところです。Center-Hi/Lowの表示でも位置が変わります。実際に描いてみない事には1つ1つのサイズもわかりませんので動的にLabelを作成して1つ1つ配置しています。PictureBoxの幅が小さくなると隣同士が重なってしまうのでその場合は2段に表示してできる限り読めるようにしています16。が結局この小細工も表示を消してしまえば関係なし(笑)。
出力デバイスの検索と切り替え対応
h0uri氏の実装では起動するごとにデバイスを全部探して最初に見つかったWASAPI loopbackに適応するデバイスを使用するのですが、探すときにサウンド系以外も含めたPCの全デバイスを調べるようなので17起動にやたらと時間がかかります。またPC側の複数のデバイスが使用可能でも最初に見つかったものしか使えません。
PCのマザボ内蔵サウンド含め、そうそうデバイスを入れ替えたりはしないので、最初に使う際には全部調べるのは仕方ないにしても、見つけた対応サウンドデバイスは設定ファイルに保存するようにして次回以降の起動時間を短縮しました18。外部のDACを使っている場合など、複数接続されている場合は選択できるようにもしています19。デバイス切り替え時はBass(wasapi).dllの初期化が必要なので可能であれば自動的に初期化、そうでなくても手で初期化できるようにしてあります。ただし、実際にデータを取るためにはもちろんWindows側(サウンドコントロールパネル)も手で切り替える必要があります。
ハイレゾ(48KHz+ PCM)対応
BassでWASAPIを使う場合、先にBass.dllをダミー?のパラメータで初期化してからBassWASAPIの初期化をする2段階らしいのですが、Bass.dllの初期化のところでサンプリング周波数にBASS_WASAPI_getDeviceInfo()
で取ってきたdevice.mixfreq
パラメータを正しくセットしないとresult
にfalse
が返ってきちゃうし、逆にBassWasapi.BASS_WASAPI_Init()
のほうはデフォルトの0で良かったりしてそこに気づくまで悩みました20。
あと当然「周波数が高くなる」=「書き換え頻度が多くなる」ので、バッファのサイズを3段階に調整。これをしないと高いサンプリング周波数で目まぐるしく(低いサンプリング周波数ではモッサリと)書き換わってしまいます。
でもこの対応でよくある44.1KHzからPCM仕様限界の384KHzまでそれほど不自然なく表示できるようになりました。
bit深度に関しては何も触ってませんが、Windows側を32bitとかに切り替えても問題なく表示できています。
そのほか
入ってます。
おしまいに
素晴らしいライブラリと先人の知恵により、いまさらですが無事車輪を再発明することができました。個人的趣味と興味と時間つぶしで作ったのでそれでいーんです。
ここまでやって、プログラム本体80KB・ライブラリ込みで900KB、私の8年前のオンボロPCでもCPUロード0.1%、プロセスメモリ90MBぐらいの軽いプログラムで済んでます。
スクリーンショットのようにFormサイズをうんと小さくして画面の隅にアクセサリとして置いておいたりするのもいいでしょう(画面の隅でチラチラ動くのが気にならない人専用ですが)。
最後にお手軽にさっと見てみたい奇特な方むけにReleaseビルドのバイナリのリンクも置いておきます。
https://github.com/osamusg/SpeAnaLED/releases
以上です。
-
こういう文章を書きたくて何か作ってるまである。全部読む人がほとんどいなくても。自分の外部記憶の意味もあるのでいいんです。 ↩
-
AliExpressとかBangoodとか中華でよく見かける。 ↩
-
あと費用も。 ↩
-
マニアやクリエータ向けにはASIOとかDSDとかもありますが... ↩ -
XBOX Game BarとかGForce Experienceのビデオキャプチャもこの方法? ↩
-
盗聴とかスパイウエアとかされちゃうからねぇ。 ↩
-
"BASS is free for non-commercial use. If you are a non-commercial entity (eg. an individual) and you are not making any money from your product (through sales, advertising, etc) then you can use BASS in it for free. Otherwise, one of the following licences will be required." ↩
-
自分的な、「見て楽しむ」という実用、という意味で。間違ってもこれを見て実際に何か判断をしてはいけない。 ↩
-
併せて元のバッファサイズの定数は
BASS_DATA_FFT4096
に。 ↩ -
インターリーブ(interleave)と言ってました。 ↩
-
窓関数はおそらくhan窓。 ↩
-
もちろん8バンド表示の時は8分割。 ↩
-
最後のブランクはいらないので-3。 ↩
-
オマケのレベルメーターも同じ方法。 ↩
-
それでもまだ右がはみ出たり、センター位置とズレたりしています。 ↩
-
WindowsからはそのデバイスがWASAPIデバイスかどうか事前に知る方法がないから? LCDモニタにイヤフォンジャックが付いてたりしたらHDMIのサウンドデバイスとかだし。 ↩
-
稀にWindowsがデバイス番号を振り直すことがあるみたいなのでその場合は設定ファイルの該当行を消すか、おもいっきり設定ファイルごと削除する必要がある。 ↩
-
その時に調べた対応サンプリング周波数も表示するのでハイレゾ民は悦に入ることができるオマケつき(笑)。 ↩
-
よく見たらドキュメントにそう書いてある。 ↩
-
タマに行儀の悪い音源があったり出力レベルの設定自体を自分で間違えたりして、音声のレベルが高すぎてリセットがかかりまくることがあるのでその場合はオートリセットを切る必要がある。 ↩