Arduino PWM Sound Output
https://diyelectromusic.com/2021/07/27/arduino-pwm-sound-output/
の #機械翻訳
これまで、Mozzi 合成ライブラリを使っていくつかの音声出力方法(それぞれ異なる音質)を試してきましたが、実際に PWM(パルス幅変調)そのものを直接扱ったことはなかったので、そろそろもっと深く調べてみようと思いました。
パート2では、Arduino の別のピンを使う代替版を紹介しています。
こちらの投稿では、出力シールドの設計図も公開しています。
https://gyazo.com/b20687345c5e1ccd3d06494c22ba8097
本プロジェクトで扱う主要な Arduino チュートリアル:
Arduino Foundations PWM
Sparkfun “What is Pulse-width Modulation”
Secrets of Arduino PWM
PWM DAC
Working with Atmel AVR Microcontroller Basic Pulse Width Modulation (PWM) Peripheral
Arduino を初めてお使いの方は、「Getting Started」ページをご覧ください。
部品リスト
Arduino Uno
10 kΩ ポテンショメータ
220 Ω 抵抗 ×1 と 8 Ω スピーカー、または中古のヘッドホンスピーカー
(オプション)Arduino PWM 出力フィルター回路
ブレッドボードとジャンパーワイヤ
(オプション)出力波形を観測するためのオシロスコープ
回路構成
https://gyazo.com/fc20595882a41eb9f376de3616e4e04b
音声出力はピン 9 から行われるため、ピン 9 をスピーカー+抵抗または出力回路に接続し、リターン側(GND)を Arduino の GND に接続してください。
私は Arduino PWM 出力フィルター回路シールド(Arduino PWM Output Filter Circuit)を使い、その出力を小型のポータブルアンプとスピーカーに接続しています。
議論
Arduino、より正確にはその基盤となっている ATmega328 には、パルス幅変調(PWM)信号を生成するためのいくつかのオプションがあります。 “Secrets of Arduino PWM” で説明されているように、IO ピンを手動でタイミングよくオン/オフすることで “手作業” でも PWM を生成できますが、マイクロコントローラの真価はこうした機能をハードウェアに内蔵している点にあります。
一言で言えば、パルス幅変調pulse-width modulation (PWM)とは、波形の周期(合成波形の幅)は一定に保ちつつ、その周期内で HIGH(オン)と LOW(オフ)の相対的な“幅”を可変にした矩形波と考えられます。
なぜこれが有用かというと、たとえば HIGH が 5V、LOW が 0V で、波形の 25% の時間だけ HIGH、残り 75% を LOW に繰り返すと、平均すると出力電圧は 5V の 25%、つまり約 1.25V になるからです。この原理を使えば、0V から 5V の任意の平均電圧を作り出せるため、デジタル出力を“おおよそ”アナログ出力のように見せかけることができます。
もちろん実際はもっと複雑で、PWM 信号はきれいな波形とは言えません。しかしこの原理を応用すれば、LED の明るさ制御やサーボモーターの位置制御などに加え、ここではオーディオ用に“擬似アナログ”出力を生成し、音声信号として変調できるわけです。
PWM ピンと analogWrite
PWM 信号の基本周波数にこだわらないのであれば、Arduino の analogWrite() 関数を使って“擬似アナログ出力”を生成できます(メインの analogWrite リファレンスページ参照)。
Arduino Uno では、490Hz または 980Hz(ピンによって異なる)の基本周波数で、ピン 3、5、6、9、10、11 に PWM 出力が可能です。これらは ATmega328 の 3 つのタイマー出力に接続されています。しかし音声波形を生成する場合、この単純な analogWrite() だけでは不十分で、自前でタイマーを設定する必要があります。
基本周波数:
490 Hz → ピン9, 10 (Timer 1) / 3, 11(Timer 2)
980 Hz → ピン5,6 (Timer 0)
Timer 0 でも PWM は生成できますが、このタイマーは delay() や millis() 関数で使われているため、本稿では扱いません。
残るは Timer 1 と Timer 2
Timer 1:16 ビットタイマー。PWM 出力はピン D9(OC1A)および D10(OC1B)。
Timer 2:8 ビットタイマー。PWM 出力はピン D11(OC2A)および D3(OC2B)。
一般的な仕組みとしては、タイマーを 0 から「TOP」値(モードによって固定または可変)までカウントアップするよう設定し、次に OCR1A や OCR2B といった比較レジスタを使って「比較値」を設定します。タイマーがこの比較値に達し、その後通過すると、対応するピンの出力状態が切り替わります(例えば OCR1A はピン D9 の OC1A を制御)。その後は再び TOP 値までカウントを続けます。以降の挙動は選択したモードによって異なります。
PWM 動作モード
ATmega328 のデータシートに定義されている多くのモードやオプションがあり、上述の各種チュートリアルでも解説されていますが、要約すると以下のような設定項目があります。
TOP 値を固定にするか設定可能にするかを選択する。
どの出力(つまりどの比較レジスタ)が有効かを選択する。両タイマーとも A 出力と B 出力を持ち、どちらか一方、または両方を有効にできる。
基本的なカウント周波数を設定し、それがメイン MCU クロック(Arduino Uno では 16MHz)とどう関係するかを決める。
カウンタが TOP 値に達したときの動作を決める—すぐに 0 にリセットして再上昇するのか、それともカウントダウンを開始するのか。
関連割り込み(オーバーフロー、比較 A、比較 B など)を有効にするかを選択する。
ご覧のとおり、多くのオプションがあり非常に柔軟に構成できますが、それだけに各モードの設定や選択肢、それが何を意味するかを理解するのはややこしくなりがちです。
PWM に関して主要なモードは次の三つです。
Fast PWM
Phase Correct PWM
Phase and Frequency Correct PWM
どれを選ぶかは一筋縄ではいきませんが、Atmel の “Working with the Atmel PWM Peripheral” が詳しく書かれており、要点をまとめるとこうなります。
Fast PWM:カウンタが BOTTOM(0)から TOP までカウントアップしたら即座にリセットし、またアップカウントを繰り返すモードです。そのため、Phase Correct 等の上下カウントを行うモードに比べて最大 2 倍の周波数で動作します。欠点は、比較値が変わるたびに波形の“位相”がリセット位置に戻ることで、A/B 両出力を使う場合などに波形のタイミングが揃ってしまう点です。
https://gyazo.com/b51159353310f2a805f431981d120dce
Phase correct PWM(位相正確PWM)
このモードでは、カウンタがBOTTOM(最小値)からTOP(最大値)までカウントアップし、さらに再びBOTTOMまでカウントダウンする必要があるため、Fast PWMの半分の周波数で動作します。
ただし利点として、比較値に到達した際の出力切り替えがアップカウント時とダウンカウント時の両方で行われるため、比較周波数が変化しても信号の“位相”が維持されます。
両方の出力(A出力とB出力)を使用している場合は、各パルスの“中心”が常に同じ位置に保たれることになります。
https://gyazo.com/3f4a771564b7084822223edc48a70b44
Phase and Frequency Correct PWM
これは基本的に Phase correct PWM と同じ動作をしますが、比較マッチがカウントの TOP(最大値)ではなく BOTTOM(0)で有効になる点が異なります。このモードにより、生成される波形は常にカウントの中心を対称に保つようになります。
これは、PWM の基本周波数(すなわち TOP 値)を頻繁に変更したい場合にのみ意味を持ちます。TOP 値を常に一定に保つのであれば、動作的にはほぼ Phase correct PWM と同じと考えて差し支えありません。
以下の図で、Fast PWM と 2 つの位相正確モードの違い、およびなぜ周波数が半分になるのかを確認できます。
https://gyazo.com/d7472074dde438375e2185aec6baa281
PWM をオーディオに使う
基本モードについて説明したので、次は PWM をオーディオ生成にどう活用するかを考える必要があります。基本的なアイデアは、サンプルを格納したウェーブテーブルを用意し、特定のサンプルレートでそれらのサンプルを PWM ハードウェアに出力することです。実際には、複数のタイマーと周波数が関係してきます。具体的には以下の三点です。
基本 PWM レート
タイマーのクロック源、カウントする TOP 値、さらにモード(Fast PWM がPhase correct PWMの2倍の速さでカウントすることを思い出してください)によって決まります。
サンプルレート
サンプルを「再生」するために PWM ハードウェアへ「送る」速度です。
結果として得られるオーディオ周波数
サンプルレートで一連の波形サンプルを PWM ハードウェアに送り込むことで生まれる音の周波数です。これは波形1周期あたりのサンプル数に依存する、いわば「帰結的特性emergent property」です。
もうひとつ考慮すべき変数があります──PWM の分解能です。これは PWM ハードウェアが「理解できる」サンプルの段階数を指します。たとえば、基本 PWM レートがカウンタを 0 から 255 までカウントして「全レンジ」を生成する場合、比較マッチ値を 0 に設定すると出力は単純に 0V、比較マッチ値を 255 に設定すると常に HIGH、すなわち Arduino Uno では 5V が得られます。値を 128 に設定すると、出力はサイクルの半分が HIGH、残り半分が LOW になり、平均すると約 2.5V 相当になります。この HIGH になっている時間の割合をデューティサイクルと呼びます(上記チュートリアル参照)。50% のデューティサイクルは「半々」の意味です。
Duty 100% : 常にON (HIGH)
Duty 0%:常にOFF (LOW)
したがって、この例での PWM 分解能は 256 です。0(無音)から 255(最大出力)までの 256 段階を選べるわけで、これは TOP 値として用いるカウンタの範囲に直接対応しています。なお、TOP 値が示す出力電圧の意味は常に「全レンジ」すなわち「常に HIGH」(Arduino Uno では安定した 5V)であり、ここで問題にしているのはそこに至るまでの「ステップ数」のことだという点に注意してください。
オーディオ周波数
特定のオーディオ信号から逆算すると、どのようなタイミングやカウンタ値が必要になるかがわかります。
たとえば、256 個の値で定義された波形(正弦波でも三角波でもノコギリ波でも構いません)を使って「コンサートA concert A」である440Hzの音を鳴らすとします。1サイクル分の256サンプルを出力し、それを1秒間に440回繰り返す必要があるため、サンプル再生レートは
440 × 256 = 112,640 サンプル/秒(約112.64kHz)
となります。つまり、約8マイクロ秒ごとに1サンプルを出力しなければなりません。
Arduino を 16MHz で動作させている場合、そのようなタイミングを実現するようタイマーを設定することは十分に可能です。しかし、もしウェーブテーブルの値を隔回(半分だけ)出力するのであれば、440Hz で再生するのに必要なのは 128 サンプルだけですから、サンプルレートは約 56kHz(16μs ごとに 1 サンプル)で済むことになります。これにより、テーブルの長さや読み出し速度が要件にどのように影響するかがはっきりとわかります。
とはいえ、PWM オーディオ向けの設定を選ぶ際には、次のように逆順で考えるのが一般的です。
実用的な基本 PWM レートを選ぶ
実用的なサンプル再生レートを選ぶ
適切なサイズのウェーブテーブルを選ぶ
PWM レート
最も利用価値の高い PWM レートは、システムのクロック周波数との特定の比率になるものです。タイマーには「プリスケーラ」を設定する機能があり、これによってタイマーの駆動クロックをシステムクロックから分周できます。プリスケーラを設定しない(=プリスケーラ=1)場合、タイマーはシステムクロックと同じ速度、すなわち Arduino Uno では 16MHz で動作します。一方、プリスケーラを 8 に設定すると、タイマーはシステムクロックを 8 で割った速度、つまり 16MHz ÷ 8 = 2MHz でカウントします。
このカウント速度と、タイマーがカウントアップする「TOP」値によって、基本 PWM 周波数が決まります。例としては以下の通りです。
プリスケーラなし、TOP=255
→ 基本 PWM 周波数 = 16MHz ÷ (255+1) = 16MHz ÷ 256 = 62.5kHz
プリスケーラ=8、TOP=255
→ 基本 PWM 周波数 = (16MHz ÷ 8) ÷ 256 = 2MHz ÷ 256 = 7.8125kHz
プリスケーラなし、TOP=1023
→ 基本 PWM 周波数 = 16MHz ÷ (1023+1) = 16MHz ÷ 1024 = 15.625kHz
…といった具合に、プリスケーラと TOP 値の組み合わせで任意の PWM 周波数を得ることができます。
しかし、これだけでは全体像とは言えません。Fast PWM モードでは上記の周波数がそのまま適用されますが、位相正確 PWM モード(Phase Correct PWM および Phase and Frequency Correct PWM)では、カウンタが上方向にカウントアップした後に再びカウントダウンする必要があるため、実際の PWM 周波数はさらに半分になります。
たとえばプリスケーラなし、TOP=255 の場合、位相正確 PWM モードでの基本 PWM 周波数は
(16MHz ÷ 256) ÷ 2 = 31.25kHz
となります。
先の 3 つの例では、PWM の分解能(レゾリューション)はそれぞれ 256、256、1024 でした。つまり最初の二例では、比較マッチ値を 0~255 の範囲で設定することで 0~5V の全レンジ出力を得られます。最後の例では TOP=1023 とすることで比較マッチ値を 0~1023 の範囲で設定でき、より細かい 1024 段階の分解能が得られ、出力の精度が向上します。
もうひとつ留意すべき点は、PWM 信号自体はあくまで基本周波数のパルス/矩形波であるため、その基本周波数成分が出力にハーモニクスとして現れることです。62.5kHz や 31.25kHz であれば通常は可聴域を超えているためほとんど気づきません(もちろんエイリアシングの問題は考慮すべきですが本質的には別論点です)。しかし、7.8kHz(または位相正確モードでのその半分)は明確に可聴帯域内にあり、高い音調の倍音として聞こえてしまいます。
したがって、PWM の基本周波数は慎重に選定する必要があり、出力に対して何らかのフィルタリングを施すことが非常に有効です。
サンプルレート
これまでサンプルレートに関していくつか触れてきましたが、256 サンプルのウェーブテーブルを 440Hz で出力するには約 112kHz のサンプルレートが必要になることを見ました。基本的には、生成したい最高音の周波数の少なくとも2倍以上の速度でウェーブテーブルを出力できるサンプルレートを確保することが最も重要です。ただし、サンプル数そのものも考慮しなければなりません。
ウェーブテーブルを読み出して出力する速度(再生周波数)は、必ずしもサンプルレートと同じである必要はありません。前述のように、もしテーブルのサンプルを1つおきにスキップして出力すれば、出力周波数は即座に2倍になり、音程が1オクターブ上がります。
同様に、各サンプルを2回ずつ出力すれば、出力される音の周波数は自動的に半分になり、音程が1オクターブ下がります。
一般的な手法としては、サンプルレートとウェーブテーブルのサイズを固定しつつ、ウェーブテーブルへアクセスするインデックスの増分量を変えて再生周波数を調整します。この方法では、低い周波数では小数点単位でインデックスを進めることで同じサンプルを複数回再生し、高い周波数ではテーブルを飛ばし読みすることが可能になります。
この仕組みの基本については「Arduino R2R Digital Audio – Part 3」で解説されている「ダイレクト・デジタル・シンセシス(DDS)」の議論を参照してください。さらに詳しく知りたい方は以下の資料もご覧ください。
Electronics-Notes: “What is DDS”
Brent Edstrom 著 “Arduino for Musicians” 内 “Audio Output and Sound Synthesis”
PWM とサンプルレートの選択
ひとつのタイマーだけを使って、前述の基本周波数で PWM 信号を生成し、そのタイマーのオーバーフロー(TOP 到達時)で割り込みを発生させて次のサンプルを出力する、という構成は十分に可能です。実際、この方法はマイコンのリソースをうまく活用できますが、いくつかの制約もあります。
解像度を固定したい場合
たとえば 8 ビット解像度(256 段階)を前提とするなら、サンプルレートはシステムクロックの整数倍としてしか設定できません。つまり、プリスケーラで分周して TOP 値を 255 に固定するといった組み合わせに制限されます。
計算しやすいサンプルレートを使いたい場合
マイクロコントローラでは、一般に 2 のべき乗のレート(16,384Hz や 32,768Hz など)を使うと処理が高速かつ効率的になります。しかしこうしたレートは、プリスケーラ設定と TOP 値の組み合わせでしか実現できないため、サンプル解像度(TOP 値)を自由に 256 段階に正確固定できるわけではなく、必ずしも「きっかり 8 ビット分」の分解能が得られるとは限らなくなります。
私たちはすでに最初の例を持っています。0–255 の分解能で「位相正確」PWM 設定を使うことで 31.25kHz の周波数が得られることを見てきました。しかし、ウェーブテーブルを正確なオーディオ周波数で再生するためのインデックスを算出するには、31.25kHz を用いた計算を行う必要があります。パフォーマンス上の理由から、興味のある音高に対応する各周波数について必要な数値を前もって計算しておくのが一般的でしょう。
たとえば、「コンサート A(440Hz)」に戻ると、サンプルレートを 31.25kHz と仮定した場合、テーブルを正しい速度で読み出すには以下の計算を行います。
必要なサンプル出力周波数 = 必要なオーディオ周波数 × ウェーブテーブルサイズ
必要なサンプル出力周期 = 1 ÷ (必要なオーディオ周波数 × ウェーブテーブルサイズ)
サンプルレートの “ティック” ごとに進めるウェーブテーブルの増分 = サンプル周期 ÷ 必要なサンプル出力周期
では、数値を当てはめると以下のようになります。
必要なサンプル出力周波数 = 440 × 256 = 112.64 kHz
サンプル出力の周期 = 1 ÷ 112640 ≒ 8.878 μs
サンプルレート(31.25 kHz)の “ティック” ごとの周期 = 1 ÷ 31250 = 32 μs
各ティックで進めるインクリメント量 = 32 ÷ 8.878 ≒ 3.604
計算式をまとめると:
インクリメント量 = (1/サンプルレート) / (1/(オーディオ周波数 × ウェーブテーブルサイズ))
または整理して:
インクリメント量 = 必要なサンプル出力周波数 × ウェーブテーブルサイズ ÷ サンプルレート
このようにして、異なる周波数ごとにインクリメント量を前もって計算し、テーブルとして用意できます。
A4 (440Hz) = 440 × 256 ÷ 31250 ≒ 3.60448
C4 (261.6Hz) = 261.6 × 256 ÷ 31250 ≒ 2.1430272
C5 (523.25Hz) = 523.25 × 256 ÷ 31250 ≒ 4.286464
… 以下同様
なお、これらのインクリメント値は小数部を保持することで精度を維持できる点に注意してください。
もし逆に、計算しやすい周波数から始めるとすると、たとえば 32768Hz(2^15)の PWM 動作を次のように設定できます。
プリスケーラなし に設定し、タイマーを 16MHz でカウントさせる。
出力したい周波数 32768Hz を得るには、タイマーをある値でリセットする必要があり、その値は
16MHz ÷ 32768 = 488
で求められる。
Fast PWM モードでは、この「TOP 値」を 488(実際にはレジスタには 487 を設定、後述)にする。
位相正確 PWM モードでは、カウントアップ→ダウンを行うため TOP 値を 488÷2 = 244 に設定する。
実際には ATmega328 のデータシート(セクション19.9.3)によると、Fast PWM モードの周波数は次の式で定義されています(N はプリスケーラ、プリスケーラなしなら N=1)。
FreqPWM = Freq_clock / (N × (1 + TOP))
これを逆に解くと、
TOP = Freq_clock / (N × FreqPWM) − 1
となるので、この場合は
TOP = 16MHz / (1 × 32768) − 1 = 487
となります。
一方、位相正確 PWM モード(セクション19.9.4/19.9.5)の式は、
FreqPWM = Freq_clock / (2 × N × TOP)
なので、
TOP = Freq_clock / (2 × N × FreqPWM)
すなわち
TOP = 16MHz / (2 × 1 × 32768) = 244
となります。
ただし、この設定ではもはや「ちょうど 8 ビット分(0–255)の分解能」には収まらない点に注意してください。
私たちは次のどちらかを選ぶ必要があります。
位相正確モードで 244 を使うことで分解能の一部を“失う”選択肢があります――つまり波形の値を 0 から 244 の範囲だけで生成するのが理にかなっています。もし 244 を超えて 8 ビットの最大値 255 まで行くと、範囲内にスケーリングし直さない限り出力がクリッピングされてしまいます。
あるいは Fast PWM モードでは“ほぼ 9 ビット”の分解能を得られます。しかしこれは、マイクロコントローラで 8 ビットに次ぐデータサイズである 16 ビットのサンプルを使わねばならず、その場合ウェーブテーブルのストレージ要件が倍増します。あるいは 8 ビット分解能を受け入れ、最大 5V のうち 0V から 255/488(約 2.6V)までしか出力できないことを許容する必要があります。
これを回避する唯一の方法は、異なる 2 つのタイマーを使うことです。1 つは PWM 信号そのものを生成し、もう 1 つは必要なサンプルレートを実現するための割り込みをトリガーします。Arduino Uno では十分に可能で、他にタイマーを使う必要がなければ問題ありません。また、この場合はサンプルレート生成用タイマーをよりシンプルな PWM 非対応のカウンタモード(例:「CTC」モード)で動かすことができます。
一般的には、PWM 基本周波数を要求されるサンプルレートより高く設定しておくほうがよいです――例えばフィルタをかけたときに波形がずっときれいになります。したがって単一のタイマーで済ませる場合は、PWM 周波数を要求するサンプルレートの 2 倍に設定し、割り込みごとではなく隔回でサンプルを出力するという手法があります。
信号のバイアス
これまでの議論では、出力値が常に 0 から PWM の分解能までの範囲(たとえば 8 ビットなら 0~255)であると仮定していました。しかし、通常オーディオ波形は正負の値をとります。ここでは一般に次のいずれかの方法を取ります。
「最大値/2」を「ゼロ点」とみなす
たとえば 0~255 の信号なら 128 を「基準ゼロ」とし、128 より大きい値を波形の正のサイクル、128 より小さい値を負のサイクルとします。
符号付き値を使う
8 ビット信号の場合は –128 ~ +127 の範囲を扱い、PWM ハードウェアに出力するときにバイアスとして「+128」を加えます。
PWM 信号は 0V と 5V の間をさまざまな比率で切り替えるため、実際のオーディオ出力には常に「正の DC バイアス」が乗ります。理想的には、この DC バイアスを除去して真の ± 信号に戻すためにコンデンサを通して出力するべきです。
精度の維持
これまでのように、0から255までの整数列だけで扱っていると、特にウェーブテーブルへのインデックスの増分を計算する際に出力の精度を十分に確保できません。そこで合成コードでは「固定小数点演算」がよく使われます。詳細には立ち入りませんが、基本的には元の値の高い倍数で計算を行う方法です。
一般的な例としては、16ビット値を使い、上位8ビットを本来扱いたい8ビット値、下位8ビットを“計算用の小数部”として使う、いわゆる8.8固定小数点演算があります。こうすると、計算中のすべての値は最終的に必要な値の256倍になりますが、そのぶん精度を維持しやすくなります。
最終的に8ビット値として使う段階では、256で割って元のスケールに戻すことを忘れないでください。これはビットを右に8ビットシフトするだけなので、比較的高速に処理できます。
最終的な8ビット値 = (8.8固定小数点値) >> 8
この方法は、ウェーブテーブルのインデックス用アキュムレータに特に有効です。インデックス値を8.8固定小数点演算で算出し、次のサンプルを取り出す際には上位8ビットを256要素のウェーブテーブルのインデックスとして使う、という流れで利用します。
コード
この例では、以下の構成を採用しています:
サンプルおよび PWM 周波数として 32768Hz を使用。
Timer 1 を Fast PWM モード、プリスケーラなしで動作させ、ICR1 を TOP 値として 487 に設定。
Timer 1 のオーバーフロー割り込みで「サンプル出力」ルーチンをトリガー。
比較値として OCR1A を使用し、OC1A(Uno のピン 9)が PWM 出力となるように設定。
setup() ルーチン内であらかじめ計算した値域 0~255 の 256 要素からなる 8 ビット・ウェーブテーブルを使用。
A0 に接続したポテンショメータで、読み値 0~1023 を周波数 220Hz~1243Hz にマッピング。
これは主に「Arduino R2R Digital Audio – Part 3」で用いたデジタル合成手法と同様のものであり、Mozzi の “standard plus” モードで使われていると考えられるタイマーおよび PWM 設定を利用しています。
別の例として、Arduino PWM Sound Output – Part 2では Timer 2 とピン 3 を使った実装があります。
この実装の特性は以下の通りです。
サンプルレート:32,768Hz
PWM 振幅(ピーク・ツー・ピーク):0~488
DC オフセットバイアス:244
ウェーブテーブルの値域:0~255
ソースファイルの先頭にある #define で、以下の四種類の波形から選択できます。
0 = 正弦波
1 = ノコギリ波
2 = 三角波
3 = 矩形波
矩形波は本来、Arduino の tone() 関数で直接 IO ピンに出力したほうが効率的ですが、完全性を保つためにこの方式も用意されています。
テスト出力
このコードでは、以下の三つの「テスト出力」が行われます。
起動時のウェーブテーブル出力
setup() の最初で、ウェーブテーブルの内容をシリアルポートに出力し、Arduino Serial Plotter でそのままプロットできる形式になっています。
25% デューティー比の PWM 出力確認
波形生成を開始する前に、PWM を 5 秒間 25% のデューティー比で動作させます。これによってコアの PWM 周波数などが正しく動作しているかを確認できます。
440Hz テストトーン再生
その後さらに 5 秒間、コンサート A(440Hz)の波形を再生し、測定器あるいは耳(実際に聞いて)で周波数が正確かをチェックします。
テストが完了すると、ポテンショメータの値に基づいた周波数の波形出力に切り替わります。
下の写真では、まずフィルター前に 32,768Hz の 25% PWM 信号をキャプチャし、その後フィルター後の 440Hz テストトーンを捉えています。なお、私の安価なデジタルオシロスコープはフィルター後の PWM 信号の周波数を正確に測定するのが苦手で、正直あまり向いていませんが、440Hz の正弦波の大まかな形状は確認できます。実際にチューニングフォークと合わせてみても、しっかり“ピッチが合って”聞こえます。
https://gyazo.com/99f0119bf42ffe746656dfc1d036dfc5https://gyazo.com/3b5f6493ca595e512973adcca6641c79
Github
締めくくりの考察
ここには多くの内容がありますが、正直なところ、自分自身で本質をしっかり理解したかったので、細部まで掘り下げてみました。
もしさらに詳しく読みたい、あるいはもっと“経験豊富な”方の解説を聞きたい場合は、以下をお勧めします:
“Arduino for Musicians”(Brent Edstrom) – 第9章 “Audio output and sound synthesis”
“Auduino Music and Audio Projects”(Mike Cook) – 第10章~第12章 “The Anatomy of a Sound”、“Square Waves”、“Other Wave Shapes”
もちろん、本投稿の冒頭でリンクしている各チュートリアル記事もご参照ください。
Kevin