パラメトリックイコライザ付きUSB-DAC

ポイント

とかくピュアオーディオ界隈は不思議な世界である。そもそもスピーカは原理的に低音再生が苦手で、補正のないフラットな電気信号を印加すると、どうしても低音が弱くなる。そこで、電気レベルから総合的に調整すれば良いものを、いったいなんの競技なのか、界隈ではなにがなんでも、スピーカだけでどうにかするのが良しとされている。結果、バスレフ型スピーカのように共振の助けを借りて無理に低音を増強する方法がまかり通っている。そういう根本的なおかしさを残したまま、聞こえもしないハイレゾなど、スペックを追うところも妙である。

一方、自分としてはこちらの記事のように、共振要素を極力配した「デッドな」スピーカーを基本とし、必然的に生じる低音のレベル低下をイコライザで補う方法を好んで使用している。この方法では、コーンの大きさとストロークから低音の絶対的音量が制約されるが、そもそもそんなに大きな音で音楽を聞くことがないので問題ない。また、リスニング時の音圧にはスピーカーだけでなく、部屋の大きさや形状、壁面の材質が大きく影響する。これをイコライザ等で補正する音場補正(ルームアコースティック補正)の重要性も認知されてきている。

イコライジングにはこちらで紹介しているようなノイズが乗らないパッシブイコライザのほか、再生機(PC等)のソフトで行う方法がある。しかし、パッシブイコライザはそのイコライジングの自由度が低いことや、細かな調整・合わせ込みが難しいという問題がある。一方、ソフトでのイコライジングは、1つのスピーカーにいろんな機器を繋ぎ変えて使う場合などで不便だ。さらに、音場補正が可能な外付けのイコライザはかなり高価になる。

そこで今回、調整自由度の高いパラメトリックイコライザを内蔵したUSB-DAC(USB接続し、音声をアナログ信号に変換する音声出力デバイス)を制作した。音楽を聞きながら簡単に微調整が出来、設定が保存されるので、どの機器を繋ぎ変えても同じイコライジングがかかる。つまりこのDACはスピーカーの特性とセットで、一体の完成された再生機器となるわけだ。

ねらい

USB-DACはPC本体の音声出力よりも高音質な再生装置として、いまやすっかり定着した(従来はレコードやCD等からの再生を良しとする風潮があり、PCからの再生など・・と言われたものだが、便利さにはだれも逆らえないのだろう。隔世の感である)。それらの多くはハイレゾ(16bit/48kHzよりも高度な標本化・量子化を用いたデジタルオーディオ)に対応しており、最近では安価なものでも32bit/768kHz対応が謳われていたりする。しかし、そんなもの聴感上、とても意味があるとは思えない。そもそも自身は音源として、よくてCD、たいていはMP3やストリーミング動画など圧縮音声を中心としておりまったく意味がないし、それよりは前述のように物理的な限界や部屋の影響を解決するほうが先である。

実のところ、USB-DAC の最大のメリットは、そのUSB接続にあるのではないだろうか。ノートPC・スマホを問わず、USBであれば他の機器や電源(充電)と一緒に、一括で脱着できる。アナログだと抜き差しのときに大きなノイズが出ることがあるが、USBならそれもない。結局のところ、スピーカーシステムへの「入口」としてUSBを使えば大抵の機器を接続でき、そのDACの中にイコライザがあれば、アンプ・スピーカーとセットで調整済みの「HiFi再生システム」として一体として扱える。そこで、以下のような条件のDACを使うことを考えた。

さて、このような条件で商品をまず探したが、それがなかなかない。高級な据え置き型DACや、スマホとセットで用いるポータブルDACはほとんど、イコライザを内蔵していない。また前述のようにルームアコースティック補正ができる機器は5万円程度からと、かなり高価になる。そこで今回、勉強がてら自作してみることにした。

制作

パーツの選択

USB-DAC を自作する人が割と多いことは認識していた。それらにはいくつかの方向性があり、その1つに、市販のキットを用いてケースだけを自作したり、アナログ回路部分に凝ったりするものがある。この場合、USBで信号を受け取る部分とDACが一体化されたチップがよく用いられ、この方法ではイコライザ等の機能追加ができない。もう1つの方法はマイコンを用いるものである。マイコンがUSBデバイスとして動作し、抽出したデジタル音声信号をDACチップへと流し込んで再生する。この方法はプログラミングが可能で、そのプログラムに工夫を施せばイコライジングも可能ということになる。

そこでまずはマイコンの選定を行った。まず、そのマイコンがUSBデバイス機能に対応している必要がある。Arduino (ATmega) Pro micro系は性能からしてオーディオ信号を扱うことは難しく、一方、ESP32は性能は十分だがUSBデバイス機能に対応しているもの(例:ESP32C3)が少なく、USBオーディオを扱うための情報も乏しい。結局、一番情報が多く、確実に作れそうなのはやや非力だが、Raspberry Pi Pico という結論になった。

Raspberry Pi Pico ではDAC チップを用いず、単体で 1bit DAC として動作させる方法もある。進化を続けており、ハイレゾはおろか、DSDフォーマットの再生も可能になっているが、資料(Interface 誌記事)を購入して読んだところ RP2040 チップを究極的に使い込んでおり、イコライジング機能を追加すると破綻する可能性があるので断念し、DACチップを用いることにした。多くの汎用DAC基板が売られているが、最も簡単に構成できそうなものとして、Pi Pico と同サイズで、差し込むだけで使えるWaveshare Pico Audioを選択した。さらに液晶ディスプレイについても同メーカのWaveshare Pico LCD 2を用い、3段重ねにすることではんだ付けなしに制作できるようにした。もちろん購入前に、これら2つのボードの利用ポートが重複していないことを確認済みである。このLCDボードには画面の4隅に4つのボタンがあり、これらでイコライザの操作ができるだろうことも狙っての選択である。

音楽再生

まずは Pico Audio ボードだけを用いて音楽再生を確認する。ただし、Raspberry Pi Pico の純正品を持っていなかったので、それよりも多機能だが互換性が高いと言われる VCC-GND Studio 製 YD-RP2040 を用いた。しかし、実はこれが最初のトラブルの原因で、Waveshare が公開しているコンパイル済みバイナリを焼くとUSBオーディオデバイスとして認識されるのに音が出ず、故障を疑い、購入先の Switch Science に問い合わせたところ、もう1台送ってくれることになった。同時にこちらでも純正品の Pi Pico ボードを用意しテストしたところ、純正品では難なく動作。確認不足を恥じるとともに、Switch Science から送ってもらった代替品は購入させてもらった。

YD-RP2040 で動作しない原因は電源バスにあり、Waveshare 社の Pico 用ボードはいずれも、Pin 39 から電力の供給を受ける。しかし、YD-RP2040 はこの部分の回路が違い、39番ピンは電力の入力にしか使えず外部への供給はできない。このことはこちらの末尾でも指摘されており、回路図を確認した結果、Pin 40 と Pin 39 (Vout と Vin)をハンダ等でブリッジ(上の写真の赤丸内)すれば良いことがわかり、無事動作させることが出来た。

純正品の Pi Pico を用いれば良い話ではあるが、Pi Pico はUSBポートが MicroUSB で、いまとなっては不便だし、スマホからの再生にも向かない。また、YD-RP2040 にはリセットボタンがあり、抜き差しすることなくファームウェアの焼き直しができるのも大きなメリットである。

イコライザ実装

液晶モジュールを搭載する前に、RP2040 マイコンでどの程度のイコライジング計算が可能かどうか確かめる。そのためには開発環境が必要だが、RP2040 マイコンでUSBオーディオを操作する開発を Arduino IDE で行う方法が見つからず、スタンダードな pico-SDK を用いることにした。一連の pico-SDK のファイル群(pico-sdk, pico-extras, pico-examples, pico-playground) には usb_sound_card というサンプルがあり、先に述べた Waveshare 提供のバイナリファイルはもとより、Interface誌のプログラムもUSB受信部分はこれをベースにしている。CMake は使ったことがほとんどなく調べながらだが、サンプルを修正し(GPIOポートの設定を変えなければWaveshare Pico Audioでは動かない)、ここぞという部分にイコライザ機能を組み込んでみた。このときは、フィルタ係数はこちらで計算し、YD-RP2040上のボタンでイコライザのON/OFFを行って効果を確認する。

サンプルによると、USBからデータを受け取ってDACチップへI2S通信で送り出す部分は、デュアルコアの RP2040 のうち1つのコアに専従させており、その中に配列 in[] から out[] へ、音量調整のための定数倍をしつつ、データをコピーしている部分がある。

  int16_t *out = (int16_t *) audio_buffer->buffer->bytes;
  int16_t *in = (int16_t *) usb_buffer->data;
  for (int i = 0; i < audio_buffer->sample_count * 2; i+=2) {
    out[i  ] = (filterR(in[i  ]) * vol2) >> 15u;
    out[i+1] = (filterL(in[i+1]) * vol2) >> 15u;
  }

この部分で上のように関数 filterR(), filterL() にデータを渡し、次の双二次フィルタ(Biquad フィルタ)によりイコライジングをかける。下図は双二次フィルタのブロック線図で、離散システムの解析に用いられるZ変換などの理論に基づくものだが、そんな理論を知らなくても理解できる。$z^{-1}$とある部分は1サンプル前の値を記憶するメモリーで、入力に対して出力が1サンプルぶん遅れることを意味する。三角形の部分は単なる定数倍で、それらの値を中央の⊕で加算しているだけである。

双二次フィルタは、係数(上の図の$b_0, b_1, b_2, a_1, a_2$)を変えるだけでさまざまな特性のフィルタを作ることができる便利なものだが、ここではピーキングフィルタのみとする。それでも、特定の周波数を強めたり弱めたりするほか、その影響範囲の幅を調整したり、中心周波数をずらしたりできるので、少数でもかなり自由度の高い調整ができる(これをパラメトリックイコライザと呼ぶ)。以下のプログラムは上の図をそのまま実装したもので、EQ_CHは直列に並べる双二次フィルタの個数であり、それぞれの係数は構造体bc[i]に格納している。swはイコライザをON/OFFするスイッチである。

#define SHIFT_C 20 // fixed point for coeffs
#define SHIFT_V 16 // fixed point for value

static void calcBinCoeffs(void) {
  for(int i = 0; i < EQ_CH; i++) {
    coeff[i] = calcCoeffs((double)currentFreq, setting.freqCenter[i] , setting.bandWidth[i], setting.gain[i]);
    bc[i].a1 = coeff[i].a1 * (1 << SHIFT_C) + 0.5;
    bc[i].a2 = coeff[i].a2 * (1 << SHIFT_C) + 0.5;
    bc[i].b0 = coeff[i].b0 * (1 << SHIFT_C) + 0.5;
    bc[i].b1 = coeff[i].b1 * (1 << SHIFT_C) + 0.5;
    bc[i].b2 = coeff[i].b2 * (1 << SHIFT_C) + 0.5;
  }
  gainBC = (int64_t) ((double)(1 << SHIFT_V) / pow(10, setting.totalGain/20));
}

static int32_t filterL(int64_t in) {
  static int64_t in1[EQ_CH], in2[EQ_CH], out1[EQ_CH], out2[EQ_CH], out;
    
  if(!sw) {
    return in;
  }
  in <<= SHIFT_V;
  for(int i = 0; i < EQ_CH; i++) {
    out = bc[i].b0 * in + bc[i].b1 * in1[i] + bc[i].b2 * in2[i] - bc[i].a1 * out1[i] - bc[i].a2 * out2[i];
    out >>= SHIFT_C;
    in2[i] = in1[i];    in1[i] = in;
    out2[i] = out1[i];  out1[i] = out;
    in = out;
  }
  return out / gainBC;
}

ここでデータ型をどのようにするのかが問題になる。RP2040 には浮動小数点ユニットが搭載されていないため、floatdoubleなどの浮動小数点数で実装すると時間がかかりすぎる。そこでまず32bit 整数を用いた固定小数点計算で実装したが、精度が十分でなく不安定だった。音声信号はもともと16bitであり、積も32bitに収まるはずだが、実際には双二次フィルタの係数には近い値が多く、それらの差を取ることで桁落ちが発生する。計算結果も入力値より大きくなるものがあり、結局、64bit 整数(int64_t)を用いることにした。

上のソースコードで SHIFT_C, SHIFT_Vとあるのは固定小数点演算に関する定数であり、最終的には係数はq20(小数点以下20bit), 入力値やレジスタ値はq16で実装した。coeff[i]は実数値のフィルタ係数であり、これを関数calcBinCoeffs()内で固定小数点の係数bc[i]に変換している。また最初に入力値をSHIFT_Vで大きくしておき、計算が終わったときにgainBC(のちに述べる、トータルゲインの補正が含まれている)で補正している。

このような処理の結果、48kHzオーディオではサンプル時間内にステレオで4段までの処理が可能であった。最初は使い勝手を考えて2段のパラメトリックイコライザを搭載することにしていたが、4チャンネルの補正ができれば音場補正にも使用できると気づき、4チャンネルに変更した。

液晶表示

次は液晶画面の制御である。いったんオーディオ関係を省き、液晶画面を動作させるサンプルを焼いて動作を確認。次にサンプルプログラムを確認した。幸い、同じくpico-SDKで開発されており相性は良い。サンプルには比較的高度なグラフィックライブラリ、LVGLが使われていたが、それほど高度なインタフェースを作る予定はなく、アニメーションのためのタイマー制御関係がオーディオ再生とバッティングするのも避けたかったので、それらを省いて最小限の処理で済ますことにした。具体的には、液晶へビットマップデータを流し込む部分を取り出し、それだけを用いて実装した。

ここで障害になったのは液晶画面の初期化とオーディオ周りの処理の関係である。最小限の処理に限定しても、液晶を動かすとオーディオ再生ができなかったり、逆になったりする。ポートが重複しなくても、内部の同じハードウェアを双方に利用しようとすると動かないはずなので、これは無理なのかも・・と思いながら、互いの処理の順序関係を入れ替えながら試していると、どうにか、両立できるポイントが見つかった。また、オーディオ周りの処理はすべて core 1 に投げて専従させているので、オーディオ再生中は core 0 がまるまる休んでいることも幸いであった。つまり、グラフィックス処理のためにある程度の時間がかかっても、オーディオ再生には影響しないということになる。

相前後して、インタフェースによるイコライザのON/OFFについてもテストした。このボタンの処理を core 1 のオーディオ処理中に加えるとちゃんと動作したが、core 0 の側につけると動かない・・グローバル変数なのに・・ということがあった。しばらく悩んだが、あっさり、そのスイッチの役目を果たす bool型のグローバル変数に volatile修飾子をつけると動作。コンパイラの最適化にやられていたのかもしれない。

フィルタ係数計算・周波数特性表示

ここまで来ると、技術的課題はあらかた片付いたことになるので、最終的なインタフェースの作成へと進む。そのためにはフィルタのパラメータから双二次フィルタの係数を計算する部分と、それによる周波数特性をグラフ表示する部分を作らなくてはならない。フィルタ係数の計算法はネット上に多数の情報があり、今回は双二次フィルタで実現できるフィルタのうちピーキングフィルタしか使わないので、こちらなどを参考に作成した。

問題は、周波数特性の表示である。双二次フィルタの周波数特性はその伝達関数、

$H(z) = $$\frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{a_0 + a_1 z^{-1} + a_2 z^{-2}}$

の $z$ に $e^{j \omega T_s} = \cos(\omega T_s) + j \cdot \sin(\omega T_s)$を代入すれば求められる(なお$j$は虚数単位で、数学では$i$が用いられるが、工学分野では電流との混乱を避けるため$j$が用いられる。また、先の実装では$a_0 = 1$である)が、これを計算するには複素数の演算が必要となる。なにかライブラリを使えばよいのかもしれないが、依存するライセンスが増えるのも面倒なので、プログラミングの演習課題によくあるように構造体で複素数型Complexとその処理関数群(以下のtoComp(), add(), mul(), inv()等)を作成し、以下のようにして複素ゲインを求めた。実際には複数の双二次フィルタを連接しているので、それらのフィルタの複素ゲインの積を求め、その絶対値を対数変換してグラフにプロットする。

Complex calcResponse(BiQuadCoeffs c, double freq, double fs) {
  double om = 2.0 * M_PI *  freq / fs;
  Complex x, y, z;

  z.re = cos(om);
  z.im = sin(om);
  z = inv(z);

  x = toComp(c.b0);
  x = add(x, mul(toComp(c.b1), z));
  x = add(x, mul(toComp(c.b2), mul(z, z)));

  y = toComp(c.a0);
  y = add(y, mul(toComp(c.a1), z));
  y = add(y, mul(toComp(c.a2), mul(z, z)));

  return mul(x, inv(y));
}

ビットマップの配列をクリアし座標軸等を書き込んでから、1点ずつこの計算を実数演算で行う必要があるため、操作性に影響があるぐらい遅かったらどうしようか・・と考えたのは、幸いにして杞憂だった。思えば、たった300点前後の計算である。演算は瞬間的に完了し、むしろボタン操作のチャタリング防止処理の必要があるぐらいだった。

データ保存機能と操作インタフェース作成

せっかくイコライジングが出来ても、それが電源断で保存されなければ実用には厳しい。そこで Raspberry Pi Pico でのフラッシュ書き込みについて調べ、こちらを参考にして実装した。しかし単体では動作しても、音楽再生と組み合わせると動かない。いろいろ試したが、結局、音楽再生(core 1 の動作)を止めなければ保存が出来なかった。また保存後、再生を再開するのも難しかったため、やむなく、設定保存後すぐにリセットをかけるようにするしかなかった。再起動しても数秒で復帰するので大きな問題はないが、音声再生中のときは、ほかの出力デバイスに音声が切り替わってしまうことがあり、大きな制限事項の1つである。

画面に文字を表示する必要もあるが、前述のように描画にはビットマップ転送しか使わなかったため、フォントデータを自前で準備することにした。最終的には、こちらのフォント(ライセンスフリー)を元に、データをプログラムに埋め込んだ。以下の動画は操作の様子である。

イコライザはそれぞれにゲイン・中心周波数・バンド幅の3つのパラメータがある。それに加え、トータルゲインの調整もつけた。イコライジングの結果、ゲインが高すぎると16bitからのオーバーフローが生じてノイズが出るので、ゲイン全体が下げられたほうが便利だからである。加えて前述のデータ保存と、もとのフラットな特性に戻す機能をつけた。左下ボタンでモードを切り替え、右の上下ボタンで値の増減と保存・リセットを行う。左上のボタンでイコライザのON/OFFをいつでもできるようにして、効果を確かめやすくした。また、操作なしに10秒経過すると液晶のバックライトが消灯し、キー操作で復帰するようにもなっている。

その他細かい点だが、最初のサンプルに備わっていなかったミュート機能(PC側でミュートすると、音量値を保ったまま、別途ミュートフラグが送られてくる)も追加した。そのとき音量は瞬間的に変化せず、少しフェードイン・フェードアウトするようになっている。

USB-audio フィードバックの実装

USB-audioでは、再生クロックの生成はデバイス側が行う。それに対しPC側がデータを一方的に送りつけると、PCとUSB-DACの僅かなクロック周波数の違いにより、バッファが徐々にいっぱいになったり空になったりして、音が僅かに途切れ、ノイズとなって聞こえることがある。これを防ぐ仕組みがフィードバックで、バッファの埋まり具合に応じてDAC側からPC側へデータ送信ペースの補正量を返信する。しかし、pico-SDK のサンプルコード usb_sound_card にはその仕組みが実装されていないので、ソースコードを読み込んで実装してみた。

Raspberry Pi Pico はUSB経由で受信したデータを一旦バッファに入れる。そのバッファの中身は一定周期で取り出され、DACへと送られる。そのため、そのバッファの空き具合を見てPC側へのフィードバック値を計算する必要がある。このバッファはリスト構造で数珠つなぎ式に管理されているので、以下のコードで空きバッファの個数を数えることにした。

static int countFreeBuffers(void) {
  int i = 0;
  
  audio_buffer_t *audio_buffer = producer_pool->free_list;
  while(audio_buffer != NULL) {
    audio_buffer = audio_buffer->next;
    i++;
  }
  return i;
}

このリスト構造のための関数群(pico-extras/src/common/pico_audio/audio.cpp)でバッファの残り個数を常時管理するのが筋だと思うが、そうなっていないため、個数もわずかなので愚直に毎回、数えることにした。この残り個数がバッファ総数の半分に近づくよう、feedback の値を0.1%程度の範囲内で調整する。

たったこれだけなのだが、試してみると遅い方への制御はできるのだが、速める方の制御がうまく行かず、音がブツ切れになる。これにしばらく悩んだが、原因は各バッファの大きさだった。先に「ペースを調整する」と書いたが、実際にはペースは一定で、一度に送るデータ量を調整するようになっている。1ミリ秒ごとに送られるパケット中のサンプル数は、48kHzサンプリングの場合48個だが、これを47〜49個の範囲で変化させることでペースを調整しているのだ。しかし、もとのコードが48サンプルを超えると動かないようになっており、そこを探して修正する必要があった。

具体的には、DACへ送出するためのバッファサイズがaudio_new_producer_pool()関数呼び出し部分に48とハードコーディングされており、これを少し増やす。また、USB経由でデータを受け取る部分はAUDIO_MAX_PACKET_SIZEマクロで計算しており、ここもやはり48サンプルまでとなるような書きぶりだったので、マクロを修正して少し増やしたところ、無事にデータを受け渡しできるようになった。バッファ数をリアルタイム表示するようにして見てみると、徐々にバッファの使用量が全体の半分に近づいていくことが確認できた。

この、サンプル usb_sound_card へのフィードバック実装は望まれながらもまだ良いものが見当たらないので、ミュート機能とともにRaspberry Pi財団のリポジトリにプルリクエストをかけた

Raspberry Pi Pico 2

上記のように Raspberry Pi Pico (RP2040) で一通りの機能は実装できたが、イコライザのチャンネル数が4chまでとなるため、ある程度の音場補正は可能であるものの、この手のデバイスとしては少ないと言わざるを得ない。そこで、高速化したという後継マイコンの Raspberry Pi Pico 2 (RP2350) をテストしてみた。

下馬評では RP2040 に対しおおよそ1.5倍の性能があるということだが、イコライザ以外の処理(USBからデータを受け取ったり、I2Sにデータを送り込んだりする処理)に要する時間が短くなるため、そのぶんイコライザ処理に使える時間が増える。テストの結果、8ch程度なら安定に動作することがわかった(10chでも動作したが、ボリュームの制御が不安定になるため8chまでとした)。これなら(ハイレゾ非対応であることを除けば)商用の音場補正用デバイスに遜色ない補正が可能である。

RP2350は浮動小数点演算ユニット(FPU)も搭載しているため、すべて float で処理できるかも、と考えてテストしたが、この場合は3chまでの処理しか出来なかったため、RP2040 の場合と同様に64ビット固定小数点演算のままにしてある。しかしFPUの効果はほかにあり、RP2040 では音楽再生中のUIの反応が鈍くなる(音楽を止めれば速くなる)のに対し、RP2350では音楽再生の有無にかかわらずキビキビと動作するというメリットもあった。

ケースの作成

最後に筐体を作成した。といっても簡単なもので、操作ボタンを薄板で繋いでガタつかないようにしたり、穴とケースの境界線をあわせて造形・組み立てを容易にしたり、といったぐらいである。厚みがあるゴロゴロした形状となったが、もとのボードの選択からしてやむを得ない部分がある(他のDACボードを使って薄型化してもいいかもしれないが)。

最後に、全ソース・データをGithubで公開して終わりとなった。純正のピンヘッダ取り付け済み Raspberry Pi Pico ボードを使った場合、はんだ付けを一切行う必要がない。またコンパイル済みのバイナリファイルを使えば、開発ツールなしに Raspberry Pi Pico にプログラムを焼き込めるので、だれでも簡単に同じものを作ることができる。

ルームアコースティック補正

最後に、マイクとスピーカ、音響測定ソフトウェア REWを用いて、スピーカーからの出力音声がどのようにイコライジングできるか確かめた。

まず、ルーム補正の必要性について調べる。マイクをスピーカー(フルレンジスピーカーのため、音を発するのは1箇所)の直前に置いた場合と、離した位置で測定した場合との比較を上に示す。グラフの細かな波打ち(どちらのグラフも同じ 1/12oct. のスムージングをかけている)の違いも気になるが、それよりも聴感上大きな影響を持つのは、250, 500, 900Hz あたりに見られるギャップである。室内の定在波の影響により生じていると思われるこれを補正するのが目的となる。

リスニングポジションにマイクを設置し、まず、イコライザOFFで測定をする。その周波数特性(青線)に対し、補正後の目標とする周波数特性(黒線)を設定する。イコライザの個数なども設定すると、REWが自動的にイコライザを配分し、パラメータを決めてくれる。その結果が "EQ filters" のウィンドウ内に表示されている(グラフ上の 1, 2, ..., 8 と数字が書かれている部分が、フィルタが置かれた周波数に対応する)。また赤線は、そのフィルタを適用したときの、補正後の「予測値」である。

つぎに、そのフィルタのパラメータを今回製作したDACにセットする。計測にはSuperlux ECM888Bを用いた。その後、再び音響測定をした結果が上の図である。青線(補正前)に比べ、赤線(補正後)がフラットになっていることが分かる。また、さきの予測値のグラフと見比べると、補正後の実測値が予測値に対して非常によく一致しており、イコライザが意図通りに働いていることもわかる。

聴感的には、この補正結果で音楽を聞くと、最初はとても平凡で「つまらない」感じがする。それもそのはずで、フラットに補正しているから平凡なはずである。しかししばらくリスニングを続けると、その魅力に気づけると思う。とにかく聞き疲れしない、気持ち良いリスニングを長く続けられる。また音楽のジャンルを選ばず、万能なイコライジングにも感じられる。

このDACはハイレゾ音源にこそ対応していないが、通常のユーザが視聴するようにCDやMP3などの音源を聞いたり、ストリーミングの映画やアニメを見るのには十分かと思う。

使用するマイクについて

補正にはフラットな周波数特性を持つように作られた測定用マイクを用いることが望ましい。しかし、コンデンサマイクは元来、特性がかなりフラットなため、部屋形状による定在波が大きく影響する中低音領域であれば、ある程度の補正が可能である。上のグラフは、計測用マイク(赤:Superlux ECM888B)に加え、一般用のマイク2種(緑:ELECOM HS-MC02UBK、青:Hollyland LARK MAX Lavalier Microphone HL-OLM02)をスピーカー直前のほぼ同じ場所において計測した例である(入力デバイスは同一)。200〜2kHzの範囲では、細かな凹凸も含め、非常によく一致していることがわかる。それより上の領域ではマイク周囲の筐体形状やユニットの向きなどの影響を受けやすく、大きくずれてしまう。この領域ではマイクの機種間の差よりも、マイク設置位置の細かな違いのほうが大きな違いとなって現れると考えて良い。だが、中低域はマイクの動作原理的にずれが発生しにくいため、高音や低音の音量は別途調整することにし、ピークやギャップなどのルーム補正に限って行うと割り切るのも良いだろう。なお低域のずれは、音声入力インタフェースの差異による違いの可能性も高い(この測定では、計測用マイクと一般用マイクでインタフェースが異なっている)。