今回のテーマは、「グラフィックイコライザ風表示装置(レベルメータ)」です。
でも、先にエクスキューズしておきますが、「気分だけ💛」です……(1^_^;)。勿論、ちゃんとグラフィックイコライザ風レベルメータになるような回路とプログラムを組んだので、「なんちゃって」とか「見た目だけ」とは、ちょっと違います。
確かに、グラフィックイコライザ風レベルメータです。ただちょっと、実験結果に納得が出来ていないだけです……(1;_:)。
その辺りの顛末については、以下を読み進めてみてください。
グラフィックイコライザというのは、↓みたいなものです。
グラフィックイコライザの画像検索(別ウインドウが開きます)
サウンド信号の中で、特定の周波数帯のレベルを上げたり下げたりして音を調整する装置みたいですね。今回Arduinoで作るのはこのような装置ではないので、正確には「グラフィックイコライザ」を作るのではありません。今回作るのは、その表示装置の方です。
可聴域周波数20〜20k[Hz]を何等分かして、その周波数範囲のサウンドレベルをバーにして表示します。サウンドレベルについては「#10-1 サウンドレベルメータを作る」を復習してみてください。
PCのソフトウェアなどで実行する場合には、恐らく、フーリエ変換などの信号処理で周波数毎のレベルを求めて表示するのだと思いますが、非力なArduinoではこれをリアルタイムで実施するのは困難でしょう。そこで、アナログ回路で帯域通過フィルタ(BPF;Band Pass Filter)を作り、入力サウンド信号を周波数毎に分離して、各々をArduinoのアナログ入力端子から取り込む方式にしようと思います。
今回も、表示は8×8 LEDを使います(下図)。
8×8[ドット]表示器なので、8チャンネルにするのが一番フィットするのですが、Arduino UNOの場合、アナログ入力が6本しかないので8チャンネルには対応できません。ATmega328P-AU(32TQFP版)またはMU(32MLF)等を搭載したPro Miniなどの機体であれば、8チャンネルのアナログ入力を使用することが出来ますが、今度は8チャンネル分のアナログBPFを作るのがとっても大変です。
……この先読み進んでいただくとわかると思います。
可聴域を何等分かして周波数帯毎にBPFをかけるので、8等分とか、16等分とか、チャンネル数を増やすと、それだけ作成する回路規模が大きくなり、工作の難易度が上がります。従ってこのページでは、4等分(4チャンネル)に決めて進めます。
これまでにも、コンデンサCと抵抗Rを組み合わせて、低域通過フィルタLPFと高域通過フィルタHPFを使ってきました。今回はその応用として、LPFで高周波数側を遮断、HPFで低周波数側を遮断することで、その間の周波数帯だけを通過する帯域通過フィルタBPFとします。
回路は以下の様になります。
この回路は、R01+C01でLPF、C02+R02でHPF、C02+R02+R03でレベルシフト回路を形成しています。
これで1チャンネル分の回路です。筆者の場合、手持ちのコンデンサにバリエーションが無いので、上図の様にC=0.1μFに決め打ちして、所望の遮断周波数となるように抵抗値の方を調整します。
さて、この回路を4チャンネル分作らないとなりません。
可聴域(20〜20k[Hz])を4分割するため、以下図の様に、5つの周波数(fc0〜fc4)を等間隔で設定します。
各々、以下の様にしてBPFを作って行きます。
本来なら、fc0=20Hz、fc4=20kHzに設定して、対数軸で等分割にすれば良いのですが、いくつか試してみたところ、1)低すぎる音や高すぎる音はスピーカから出力されない(聞こえない)、2)必要な遮断周波数を作り出すための適切な抵抗値が作れない、等の問題にぶつかったため、ちと変則的ですが以下の様に決めました。
# | fc[Hz] | C[μF] | R[Ω] | 備考 |
0 | 32 | 0.1 | 50,000 | 100k//100k[Ω] |
1 | 145 | 11,000 | 10k+1k[Ω] | |
2 | 683 | 2,330 | 2k+330[Ω] | |
3 | 3,183 | 500 | 1k//1k[Ω] | |
4 | 14,469 | 110 | 220//220[Ω] |
筆者手持ち抵抗の組み合わせ(2並列または2直列)で実現可能な遮断周波数となるように調整しました。
3並列、2並列×2直列など、複雑に組み合わせれば、どんな抵抗値でも作りだせるのですが、その場合、この後の実回路の構築で大変な思いをすることになると思います。上の2並列または2直列でさえ、相当大変でした……。
LTspiceを使ってシミュレーションしてみましょう。以下、回路図となります。
シミュレーション結果は以下となりました。
通常の時間軸シミュレーション結果と異なって、横軸に周波数[Hz]、縦軸に減衰率[dB]をプロットした波形となります。上図から、以下の値を読み取ることが出来ました。
フィルタ(BPF)を通すことで、サウンド信号が約56%に減衰してしまうことがシミュレーションからわかったため、マイク(サウンドセンサのアナログ出力、下図)からの出力を増幅してからBPFに通します。
増幅回路は、オペアンプLM358(下図)の反転増幅回路を使います。
BPF以外の周辺回路図は以下の様になりました。
増幅率は−Rf/Rs=5倍にしてあります。減衰率が約56%なので、最低でも2倍以上の増幅率にしましょう。あとは、元々マイクの出力信号はとても小さい(〜数100mV程度)ため、Arduinoのアナログ入力で取り込んだ時、それなりの振幅で取り込めるように5倍という増幅率にしてあります。波形が歪まない程度に、もう少し増幅率を上げておいても良いと思います。
ここまでは良いのですよ。机上の設計とシミュレーションで、「この回路定数にすると、こうなる」っていう、それだけの話ですから。ここからいよいよ、実装です。まずは、部品表を確認しておきましょう。
No | 部品 | 定格 | 数量 | 備考 |
1 | コンデンサ | 0.1μF | 8 | セラミック |
2 | 抵抗 | 220Ω | 2 | 1/4[W]程度 |
3 | 330Ω | 3 | ||
4 | 1kΩ | 9 | ||
5 | 2kΩ | 3 | ||
6 | 10kΩ | 3 | ||
7 | 100kΩ | 4 |
結構な部品数です。と言っても、上の表に記載したのは、BPF回路図に必要な部品だけですので、ご注意ください。別途、周辺回路図用の部品が必要です。
ブレッドボードとは言えども、さすがにこれだけの部品を実装しようとすると無計画にはできないので、PowerPointを使って配置・配線図を作って見ました。
うっかり、抵抗値を生成するのに3並列とか2並列×2直列とか、複雑な組み合わせを使ってしまうと、大変な事になるのは自明でしょう?
基本的には、手持ちの抵抗(2並列または2直列以下)で作り出せるBPFになるように、各遮断周波数を調整することが必要です。ただ、注意していただきたいのは、目的の抵抗に対して偏差の大きな抵抗値を使ってしまうと、通過域中心周波数での減衰率にばらつきが出てしまうことです。
同程度の音量なのに、減衰率の大きな周波数帯のレベルが小さめに表示されてしまうことになるので、その点にはご注意を。回路定数を決定したら、LTspiceのシミュレーションで減衰率や中心周波数を確認してから使った方が良いと思います。
※実は、プログラム(gra_equ.ino、次節で解説)の方で、チャンネル毎にVP-Pの最大値を設定できる様に対応するので、多少、減衰率が異なってもソフトウェアで調整可能です。
プログラムの方は、「#10-1 サウンドレベルメータを作る」で作ったプログラム(lev_meter.ino)が1チャンネル分なので、これを4倍化すると基本的には終了です。
ただし、今回は周波数帯毎に4チャンネルのサウンド信号を取り込む必要があるので、サンプリングレートを意識してコーディングしてください。標本化定理によると、周波数f[Hz]の信号を取り込む(サンプリングする)場合、2×f[Hz]以上の周波数でサンプリングしないと、元の信号(周波数f[Hz])を再現できません。よって、各チャンネルは以下の周波数でサンプリングしてください。
チャンネル |
通過域中心 周波数[Hz] |
サンプリング | |
周波数[Hz] | 周期[μs] | ||
A0 | 96 | 192 | 5,200 |
A1 | 440 | 880 | 1,100 |
A2 | 2,100 | 4,200 | 240 |
A3 | 9,600 | 19,200 | 52 |
Arduino UNOの10ビットAD変換器は、デフォルトでは変換時間104μsなので、そもそも、チャンネルA3のサンプリング(52μs周期)に耐えられません。ただ、変換精度は低下しますが、より速いクロックで変換することは可能なので、「#10-1 サウンドレベルメータを作る」で作成した、adc_start()関数およびadc_read()関数を拡張して、1段階速いクロックで変換させます。
Arduinoコアライブラリ(analogRead()関数)のデフォルトでは、fcpu/128のクロックを使って変換時間約104μsでしたが、一段高速化してfcpu/64クロックを使うと変換時間が約52μsとなります。図らずも、チャンネルA3のサンプリング周期と一致していますので、以下の様に連続してアナログデータを読み込めば、52μs周期でサンプリングしたことになります。
adc_start (ana_LEV3,adc_FCPU_64);
for (idx3=0; idx3<4; idx3++)
{
// 9.6kHz sampling
dat=adc_read ();
if (idx3 + 1<4)
adc_start (ana_LEV3,adc_FCPU_64);
geq_buf3[int (geq_ptr3)]=dat;
geq_ptr3++;
if (geq_ptr3>=geq_RNG_MAX)
geq_ptr3=0;
}
上のコードでは、52μs周期で4回サンプリングしています。結果、測定時間は52μs×4回=208μsとなります。ここで30μs時間待ちすると208μs+32μs=240μsとなって、次のチャンネルA2のサンプリングタイミングになります。
この要領で、時分割で次々とチャンネルA0までサンプリングして行くことにしました(下図)。
96Hzのサンプリングを4回実行したところで、各チャンネルのレベルを計算して8×8 LEDに表示します。これをひたすら繰り返します。
※サンプリングレートは「2×f[Hz]以上」で良いので、「時間待ち」は無理に入れなくても良いかも知れません。
完成したプログラムをリンクしておきます。
完成プログラム:gra_equ.ino
Arduino UNOへコンパイル&書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
Arduino UNOに回路(周辺回路とBPF回路)を接続し、プログラム(gra_equ.ino)を書き込むと、その状態で既にグラフィックイコライザ風レベルメータになっています。マイクに向けて、適切な音楽などを再生してみてください。以下、実験中の様子です。
まずは、BPFのテストから行っておきたいと思います。4チャンネルの各通過域中心周波数を音階に当てはめると、以下の様になります。
そこで、以下の様なMMLファイルを作り、Olive+のスクリプトmml2wav.olvを使ってWAVファイルに変換しておきます。
# BPF×4
# f0=96Hz
G22
# f1=440Hz
A42
# f2=2,100Hz
C72
# f3≒9,600Hzなので、2×f3以上のサンプリングレートが必要。
D92
スクリプトの使い方や音階と周波数の関係などについては「鍵盤ハモニカで演奏する」に説明があります。
ダウンロードしたままのmml2wav.olvでは、サンプリングレートが低すぎて、高音のD9(9,600Hz)が再現できません。変換する場合は、スクリプト(mml2wav.olv)内サンプリングレートの設定値を以下の様に変更して使ってください。
var nSamplesPerSec=44100/2; // サンプリングレート
音の周波数×2倍以上にする必要があります。上の設定だと、22,050Hz>2×9,600Hzです。
変換してみたWAVファイルをリンクしておきます。
非圧縮のWAVだとファイルサイズが大きいので、音の長さを短めにしてあります。各自、必要な長さで作成してください。
この音(WAVファイル)をPCなどで再生して、スピーカにサウンドセンサのマイクを近づけておきます。低音から順に、各チャンネルの中心周波数で鳴らしているので、8×8 LEDの左列から順にレベルが高く観測されるはずです(以下期待値)。
ところが……(1x_x):
と、色々と怪しいところはあるけれど、どこをどう確認して、どう対策すれば良いやら全くわからず、降参〜。やっぱりアナログは難しいなぁ……(このセリフ何度目だか……)。シミュレーションと理論上は期待通りだったのだけれども、実物が付いて来ない。
見なかったことにして音楽を再生してみると、何となくそれらしくは反応しているみたい。よって、「気分はグラフィックイコライザ風レベルメータ」ってことでm(1__)m。温かい心でご覧ください。
実験サンプル:【 】内はシリアルモニタからの送信データ
【vpp,300,300,300,200】
OK+ Vpp0=297[mV] 61[LSB]
OK+ Vpp1=297[mV] 61[LSB]
OK+ Vpp2=297[mV] 61[LSB]
OK: Vpp3=195[mV] 40[LSB]
【min,16】
OK: Vpp(MIN)=-36.12, -36.12, -36.12, -36.12[dB]
チャンネル毎に、VP-P値を調整してみました。個体差があるかもしれないので、各自、調整して見てください。
※YouTubeのフリー音楽(BGM)を鳴らした時の様子です。
机上で考えていた間は、「これなら行ける!」って、思ったのですよ、実際。音楽をかけてみると、それらしく反応しているみたいなので満更でもないのだけれど、テストパタンでは期待値と一致せず。でした、とさ。