ATMEGA328を搭載した Arduino Duemilanove 互換機で音をPWM D/A変換出力するシンセサイザーライブラリです。
ブログ: PWMDAC_Synthライブラリ (コメントはこちらへ)
最近の Arduino では tone() 関数が搭載され、単音の矩形波を簡単に出せるようになりました。 しかし、この方法では和音を出したり、矩形波以外の波形を出すのは困難です。
このPWMDAC_Synthライブラリを使えば6重和音まで出せて、しかも矩形波だけでなく 正弦波、三角波、のこぎり波、ユーザー定義の波形も出すことができ、 さらにADSRエンベロープによる減衰で「ポーン」という感じの音色にすることもできます。
人間の可聴周波数は 20Hz ~ 20kHz 程度ですが、 ArduinoのPWM周波数はデフォルトで約490Hzになっているため、 そのままでは 1kHz 前後の高い音を乗せることができません。 高い音を乗せるためにはPWM周波数をもっと上げる必要がありますが、 そのためにはAVRマイコンのレジスタを設定し直す必要があります。
このライブラリでは、AVRマイコンのレジスタを設定し直して PWM周波数を最大(30kHz以上すなわち超音波の領域)に設定し、 高い可聴周波数の音も乗せられるようにします。
最大化されたPWM周波数に応じ、数十マイクロ秒ごとの正確なタイミングで割り込みサービスルーチン ISR() が呼び出されます。 この ISR() 呼び出しのタイミングで、波形テーブルからその瞬間の現在位置を読み出し、パルス幅に置き換えることによってPWM出力します。 波形はプログラムメモリ(PROGMEM)上の波形テーブルに格納します。
同時発音数だけパルス幅を足し算することで、和音を出すこともできるようになっています。 ただし処理時間にそれほど余裕がないため、処理時間のかかる命令を極力避けて最適化したうえで、 限られた同時発音数で発音させることになります。
Arduino用MIDI ライブラリと組み合わせることで簡易MIDI音源を作ることも可能です。 付属のサンプルプログラム(Arduino IDE の「スケッチの例」からコンパイル可)に実装例がありますので参照してください。 なお、MIDI入出力の回路についてはCAmiDionの回路図を参照してください。
中に入っている README.txt を参照してください。関数定義の詳細については PWMDAC_Synth.h もあわせて参照してください。
Rev. | Tiempo | Autor | Mensaje RSS |
3c6fdeed | 2020-04-18 23:56:21 | Akiyoshi Kamide | Doc/keywords update, and macro refactored |
817c85c7 | 2020-04-14 01:14:12 | Akiyoshi Kamide | Performance dramatically revived by specifying #pragma GC... |
870b0852 | 2020-04-06 05:18:26 | Akiyoshi Kamide | Supported the compilation on Arduino IDE on Linux - Refe... |
8fb01690 | 2019-03-31 01:36:20 | Akiyoshi Kamide | ドキュメントの日付を最新化 |
4db8be16 | 2019-03-31 01:28:42 | Akiyoshi Kamide | 波形計算の見直し(シェパードトーンを7→8オクターブに変更、... |
fcbe8ac3 | 2017-05-14 23:53:39 | Akiyoshi Kamide | ・チャンネル優先度指定に対応(特定のMIDIチャンネルについ... |
48db0187 | 2017-05-11 00:00:36 | Akiyoshi Kamide | スケッチの例をCAmiDionのスケッチから抜粋して大幅拡充 |
38ba7d32 | 2017-05-06 21:57:55 | Akiyoshi Kamide | 応答性の改善 ・ボイスアサイン優先度計算の軽量化(見るボリ... |
6d811271 | 2016-06-11 13:25:25 | Akiyoshi Kamide | [BUGFIX] RPNで受信したベンド幅(Pitch Bend Sensitivity)... |
cc32489e | 2016-04-23 23:45:17 | Akiyoshi Kamide | systemReset() による初期化動作が不十分だったのを修正 |
[PWMDAC_Synth - PWM DAC synthesizer library for Arduino] ver.20200418 https://osdn.jp/users/kamide/pf/PWMDAC_Synth/wiki/FrontPage これは、CAmiDion http://www.yk.rim.or.jp/~kamide/music/chordhelper/hardware/ の2号機以降で実装した音源をライブラリとして独立させた、 Arduino互換機用のPWMシンセサイザライブラリです。 ●Arduinoで楽器を作ろうとして、こんな問題にぶち当たったことはありませんか? ・音を出すなら tone() が手っ取り早いけど、和音が出ない… ・analogWrite() で PWM 出力しても、パルス波の周波数が490Hzと 低すぎるから、1kHz前後の音なんて乗せられない、どうしよう… そんな問題も、AVRのプリスケーラ(分周器)の設定を変えて パルス波の周波数を最大にすれば一気に解決。 このライブラリは、規則正しい最速の周期で発生させたタイマー割り込みで、 その瞬間の波の高さに応じ、PWMのパルス幅を毎回リアルタイムに素早く更新する ことにより、矩形波だけでなく、色々な波形の音を出力します。 複数のボイスを使った多重加算によりPWMのパルス幅を計算しているので、 和音の出力も可能です。 外部音源チップなしで Arduino 本体そのものにシンセサイザの音源を 実装したいときに活用してください。 ●インストール 展開した PWMDAC_Synth フォルダを、Windows の マイドキュメント\Arduino\libraries\ に置くだけでインストールできます。 置いた後、Arduino IDE のメニュー [ファイル] → [スケッチの例] に PWMDAC_Synth が現れることを確認してください。 ●使い方 基本的な使い方を下記に示します。なお、ノートオン、ノートオフなど、音の出し方については Arduino IDE のメニューからたどれる「スケッチの例」を参考にしてください。 【サンプルコード】 // // 必要に応じ、PWMDAC_Synth.h をインクルードする前に #define で下記を指定できます。 // #define PWMDAC_OUTPUT_PIN 3 // PWM出力ピン番号(省略可:下記「●出力ピン(PWM)」参照) #define PWMDAC_POLYPHONY 6 // 同時発音数(省略可:下記「●同時発音数」を参照) #define PWMDAC_NOTE_A_FREQUENCY 440 // A音のチューニング周波数(省略可:デフォルト440Hz) #define PWMDAC_CHANNEL_PRIORITY_SUPPORT // 特定チャンネルの優先度を上げる機能を有効化(使わない場合は省略) #include <PWMDAC_Synth.h> // // 波形テーブルを、必要な分だけ定義します。 // PWMDAC_CREATE_WAVETABLE(squareWavetable, PWMDAC_SQUARE_WAVE); // 矩形波 PWMDAC_CREATE_WAVETABLE(triangleWavetable, PWMDAC_TRIANGLE_WAVE); // 三角波 PWMDAC_CREATE_WAVETABLE(sineWavetable, PWMDAC_SINE_WAVE); // 正弦波 PWMDAC_CREATE_WAVETABLE(shepardToneSineWavetable, PWMDAC_SHEPARD_TONE); // シェパードトーン(無限音階) PWMDAC_CREATE_WAVETABLE(sawtoothWavetable, PWMDAC_SAWTOOTH_WAVE); // のこぎり波 // // 波形とエンベロープパラメータ(ADSR)を Instrument 構造体に束ねて // プログラムメモリ領域に音色データを生成します。 // 内側の {} にはADSRの値を逆順で指定します(値の範囲については後述の「●音色変更」を参照)。 // PROGMEM const Instrument instrument = {sawtoothWavetable, {9, 0, 11, 4}}; // // 生成した音色を指定してPWMDAC_Synthの実体(インスタンス)を生成します。 // MIDIチャンネル1〜16がすべてこの音色に初期化されます。 // PWMDAC_CREATE_INSTANCE(&instrument); setup() { PWMDACSynth::setup(); // 初期化(必須) #if defined(PWMDAC_CHANNEL_PRIORITY_SUPPORT) // 必要に応じて、メロディパートが入っているMIDIチャンネルの優先度を高くします //(詳細については後述の「●チャンネル優先度指定」を参照)。 PWMDACSynth::getChannel(1)->setPriority(0xC0); #endif } loop() { PWMDACSynth::update(); // 状態更新(必須) // ▲ これは、減衰などのADSRエンベロープ形状の現在位置を一つ進めたり、 // モジュレーションによって変化する時間ごとの周波数を更新するための関数です。 // 呼び出し頻度は自分でカウンタを用意するなどの方法でコントロールしてください。 } ●同時発音数(ボイス数) デフォルトは6重和音です。 PWMDAC_POLYPHONY に数値を設定してコンパイルし直すことで増減可能です。 ※ 同時発音数を増やしていくと、その分 ISR() (Interrupt Service Routine: 割り込みサービスルーチン)における ループ回数が増え、多忙になってきます。あまりに多忙過ぎて loop() を実行する 暇がなくなると、動作不能に陥ります。そうなる寸前が、同時発音数の事実上の上限です。 ※ ISR() などで動作速度が要求されるため、PWMDAC_Synth.h には #pragma GCC optimize ("-O3") が指定されています。コンパイルの最適化基準を、このライブラリだけ速度優先にするためです。 特定のMIDIチャンネルに対しボイスアサインの優先度を上げることもできます。 後述の「●チャンネル優先度指定」を参照。 ●出力ピン(PWM) PWM音声出力用として指定できるピン番号は 3,9,10,11 のいずれかです。 デフォルトのピン番号は 3 です。 3,11 を選択した場合は TIMER2、9,10 を選択した場合は TIMER1 が使われます。 なお、ピン番号 5,6 も PWM 端子ですが、これを指定してもコンパイルエラーになり、 利用できません。これは TIMER0 用の ISR() が、Arduino の millis() 関数を 実装するためすでに使われていて、再定義できなかったためです。 PWM 出力ピンは、そのまま PC の LINE IN などにつないでも一応音は聞こえますが、 ローパスフィルタで可聴周波数だけを通すようにすると、より聞きやすくなります。 ●MIDIチャンネル操作 PWMDACSynth::getChannel() で MIDIチャンネル番号(1〜16) ⇔ MIDIチャンネルへのポインタ を相互変換できます。MIDIチャンネルへのポインタを介して、 チャンネル単位に持たせるパラメータを操作できます。 (操作方法については PWMDAC_Synth.h を参照) なお、ノートオン、ノートオフ、ピッチベンドなど、今出ている音 (アサインされているボイス)に即座に反映させるには、PWMDACSynth のほうで 用意しているメンバ関数を呼び出し、チャンネルとボイスを同時に更新する必要があります。 ●MIDI関数 PWMDACSynth で定義されたいくつかの static オブジェクトは、 MIDIライブラリの MIDI.setHandleXxxx() に直接指定できる形式になっています。 ただし、接続相手のMIDIデバイスによっては、NOTE OFF の代わりに velocity=0 の NOTE ON を送ってくることがあるので、その場合だけ NOTE OFF を呼び出すといった工夫が必要になります。 MIDIチャンネル番号は、MIDIライブラリに合わせて 1〜16 の範囲で 指定するように作ってあります。 ●音色変更 波形テーブルとADSRエンベロープパラメータを、それぞれ MidiChannel クラスの wavetable と envelope[] に指定することで、音色を変更できます。 また、Instrument 構造体定数をプログラムメモリ領域に作り、それを programChange() に指定するだけで、波形とエンベロープを一度に設定できます。 MIDIのプログラムチェンジを実装するときに便利です。 【波形】 wavetable 波形テーブルは、要素数256のbyte型PROGMEM配列として生成します。 それを wavetable に指定することで、波形を切り替えることができます。 波形関数のマクロを PWMDAC_CREATE_WAVETABLE() マクロに指定するか、 またはそれにならって自分で配列を定義することで、任意の波形を生成することが可能です。 ※ 波高は(255÷同時発音数:デフォルト6重和音のとき42)を超えないようにしてください。 同時発音数いっぱいに鳴らしたときに最大音量255を超えてオーバーフローし、ノイズが 発生する恐れがあります。 【ADSRエンベロープ】 envelope[] ADSR_ATTACK_VALUE - アタック時間(0〜15、大きいほどノートオン直後の立ち上がりがゆっくり) ADSR_DECAY_VALUE - ディケイ時間(0〜15、大きいほどノートオン後の減衰がゆっくり) ADSR_SUSTAIN_VALUE - サスティンレベル(0〜255、減衰が止まったあと維持する音量) ADSR_RELEASE_VALUE - リリース時間(0〜15、大きいほどノートオフ後の減衰がゆっくり) 実時間は loop() 内で update() を呼び出す頻度によって変わります。 ●チャンネル優先度指定 この機能は、特定のMIDIチャンネル(例:メロディパート)の音が他のMIDIチャンネルの音に かき消される現象が目立つ場合に、そのMIDIチャンネルに対するボイスアサインの優先度を 高くするための機能です。 この機能を使うには PWMDAC_CHANNEL_PRIORITY_SUPPORT を #define する必要があります。 #define PWMDAC_CHANNEL_PRIORITY_SUPPORT PWMDACSynth::getChannel(チャンネル番号)->setPriority(チャンネル優先度); チャンネル優先度には 0x00(0、最低:デフォルト)〜 0xFF(255、最高)を指定できます。 基本的な考え方として、ADSRエンベロープで変動した音量の最も小さいボイスから順に、 他のMIDIチャンネルに譲ることを原則としています。 そのうえで、チャンネル優先度のビット反転値(1の補数)を「音量閾値」とし、 それを超える音量のボイスを、そのチャンネルがより優先的に占有し続けるようにしています。 優先度をあまり高くしすぎると、十分小さい音量にもかかわらずボイスを占有し続けてしまい、 多重和音感が失われることがありますので、適宜調整してください。 作者:@きよし - Akiyoshi Kamide http://www.yk.rim.or.jp/~kamide/