第十五項 音響合成ライブラリMozzi
電子工作創作表現(2019/10/18)
スライドPDF
Arduino単体で音響合成を行う
- Arduino単体の機能で作れる音には限界がある
- 「Mozzi」を使うとかなり幅が広がる
今週から、何度かにわたって音響合成ライブラリのMozziを解説していこうと思います。以前Arduinoに圧電素子をつないで簡単な音を出してみるのをやってみましたが、Arduinoの基本ライブラリだけだとああいったビープ音を出すのが関の山という感じです。そこでもっと幅広い表現ができるようにと作られたのがこの「Mozzi」です。
Mozziとは?
- タイマー機能を活用した音響合成ライブラリ
- オシレータ・エンベロープ・LPFなど
Mozziは、ArduinoでPWMに使われるタイマー機能を使って実現された音響合成ライブラリです。オシレータやエンベロープなど、ベーシックなシンセサイズをするための機能が備わっています。
https://sensorium.github.io/Mozzi/に情報がまとめられています。
何故こんなことができるのか
- 1bitで表現し切る、仕組み的には「DSD」という方式に近い
- タイマーライブラリを使うので、PWMなどを併用する時は注意が必要
- 高速な音響処理を必要とする
中の仕組みを少しだけ話すと、PWMに使われているマイコンのタイマー機能を使い、ハイとローの切り替えを物凄く細かく切り替えていくことで実現しています。ハイレゾ音源等で使われる「DSD」という技術が近いそうです。
また、音響合成は常に出力を変化させ続けなくてはならないので、通常のanalogReadやanalogWriteの一部、またdelay関数などArduinoの主要な機能が一部使えなくなります。詳しくは後ほど解説しますが、楽器として音を出しながらNeoPixelをコントロールする、といったことは難しくなります。
なのではじめのうちは、Mozziを使用するArduinoは音響のみに用途を絞るのがおススメです。
インストール
- https://sensorium.github.io/Mozzi/download/
- Zipをダウンロードしてインストール
Mozziはライブラリマネージャに登録されていないので、「ライブラリのZIPファイルをダウンロードした後、スケッチ>ライブラリをインクルード>.ZIP形式のライブラリをインストール」からインストールをする必要があります。
インストールができたら、ファイル>スケッチ例にMozziが追加されるので、ここからサンプルファイルを開くことができるようになります。
Arduinoから線を出す
- 9番ピンとGNDにオーディオ出力を付ける(モノラル)
- PWMとして使いたいピンは3か11を使う
ハードウェア側について。Mozziでは9番ピンから音声出力が出るので、オーディオの端子は9番ピンとGNDに接続する必要があります。サンプル基板はイヤホン等で試せるよう3.5ミリのステレオジャックを使っていますが、RCA端子でも良いと思います。基板取付用端子はピン配置が特殊な形状をしていることが多いので、DIP化キットと呼ばれるものを使えばユニバーサル基板にもはんだ付けして使うことができます。(例:http://akizukidenshi.com/catalog/g/gK-06204/)
サインウェーブを鳴らす
- オシレータOscilでサイン波を鳴らす
- setup/loopに加えてupdateControl()とupdateAudio()が増える
まずは01.Basics>SineWaveのスケッチから、Mozziの基本構造を読み解いていきます。サンプルからコメントを省いたソースコードを以下に記載します。
Mozziで音を出すには、setupでstartMozziを呼び、loop内でaudioHookを呼ぶようにします。
updateAudioはオーディオ出力を決定する関数なので、ここは必要最低限の計算を書くようにします。
updateControlはボリュームやボタンのコントロール等の入力を反映させるためのコードを記述します。
#include <MozziGuts.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
#define CONTROL_RATE 64
void setup(){
startMozzi(CONTROL_RATE);
aSin.setFreq(440);
}
void updateControl(){
}
int updateAudio(){
return aSin.next();
}
void loop(){
audioHook();
}
Oscilオブジェクト
- オシレータを作るためのクラス。サイン波や矩形波のような波形データを用意する
- tablesフォルダに様々な波形のテーブルが用意されている
このコードで最も重要なのが、出す音の波形を決めるオシレータです。このクラスに出したい波形のデータを格納して、周波数を設定した後updateAudio内で呼び出すことで様々な音を発します。サンプルコードでは、2048バッファのサイン波を入れて、setupで440ヘルツに設定しています。
next関数を呼ぶごとに次の位相へと進んでいくので、updateAudioでnextを呼び続けることによってサイン波が出力され、440Hzの音がなるという仕組みです。この波形データはtablesフォルダの中にたくさん定義されていて、使うことができますし、ここのフォーマットにならえば自分で作ることも可能です。
2048や512のような数字はバッファサイズで、データの細かさなのでこの数値が大きいほど音の解像度は高くなります。ただしプログラム領域のメモリを多く消費するようになるので、この辺りはコンパイル時に使用量を確認しながら使うバッファサイズを決めるようにする必要があります。
//オシレータの例
#include <tables/sin2048_int8.h>
#include <tables/saw512_int8.h>
#include <tables/square_no_alias_2048_int8.h>
#include <tables/pinknoise8192_int8.h>
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA); //サイン波
Oscil <SAW512_NUM_CELLS, AUDIO_RATE> aSaw(SAW512_DATA); //ノコギリ波
Oscil <SQUARE_NO_ALIAS_2048_NUM_CELLS, AUDIO_RATE> aSq(SQUARE_NO_ALIAS_2048_DATA); //矩形波
Oscil <PINKNOISE8192_NUM_CELLS, AUDIO_RATE> pink(PINKNOISE8192_DATA); //ピンクノイズ
updateControlでパラメータを変更する
- アナログ入力はmozziAnalogRead()を使う
- updateControlが呼ばれる頻度はstartMozziのCONTROL_RATEで決定
このままではただ音が流れるだけなので、インタラクティブ性を持たせてみます。ボリューム抵抗の値を持ってくるにはanalogRead()関数を使っていましたが、Mozziでは音響処理を邪魔しないmozziAnalogReadという関数が別で用意されています。使い方はanalogReadと全く同じで、0番ピンから取得してきたアナログ入力の値を、setFreqでそのまま周波数としてつかいます。アナログ値は0~1023なので、1000hzまでのサイン波をグリグリと変化させることができます。
このupdateControlはupdateAudioよりも少ない頻度で呼ばれて、ソースコード頭にあるCONTROL_RATEで周波数が決定します。64hzか128hzくらいあれば十分でしょう。周波数は2の累乗を指定します。
void updateControl(){
int val = mozziAnalogRead(0);
aSin.setFreq(val);
}
足して和音を作ってみる
- もう一つOscilを用意して、二つを足した値をreturnする
- returnの値は-244 ~ 243の間でクリップする
単音のサイン波では味気ないですが、コントロールしながらオシレータを合成することもできます。
オシレータを二つ、ここではaSinとbSinを用意してnextの値を足せば2つのサイン波が合成されます。
updateAudioのreturnに入れる値はSTANDARDモードで-244~243の間にしなくてはならず、その範囲を超える値を入れるとバッファオーバーフローが起きてグリッチ音が鳴りだします。サイン波の範囲は先ほどのテーブルを見ると-127~127なので、二つが重なると最大254まで行く可能性があります。コンプレッサーのようなエフェクターがあれば収まるように叩けますが、そうもいかないのでここではザックリ4で割って音が割れないようにおさめています。
#include <MozziGuts.h>
#include <mozzi_midi.h>
#include <Oscil.h>
#include <tables/sin2048_int8.h>
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> aSin(SIN2048_DATA);
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> bSin(SIN2048_DATA);
#define CONTROL_RATE 64
void setup(){
startMozzi(CONTROL_RATE);
}
void updateControl(){
int val = mozziAnalogRead(0);
aSin.setFreq(val);
bSin.setFreq(int(val * 1.2));
}
int updateAudio(){
return int((aSin.next() + bSin.next()) / 4.0);
}
void loop(){
audioHook();
}