PIC AVR 工作室->TopPage->実験くん->マイコンでFM音源

マイコンでFM音源の再生

ハードウェアPWMを内蔵したマイコン…今回はAT-MEGA48シリーズ…を使って、マイコン1個で FM音源機能を実現してしまおうという実験です。

FM音源とは

矩形波出力とFM音源

マイコンを使って(音階を持った)音楽を鳴らす方法として、比較的簡単に実現できて応用例も多いのは PWM機能を用いた矩形波出力でしょう。

矩形波出力と言ってもピンと来ない人もいらっしゃると思いますが、初代ファミコンの音などを思い浮かべて いただければ「あぁ、ああいう音ね」とご理解いただけるのではないでしょうか?あの音は、ICからスピーカーに 対してONとOFFの信号を連続して送ることで振動を発生し、スピーカーの振動が空気を震わせ、音として 聞こえるというわけです。

PWM内蔵のマイコンであれば、音程に相当する数値を内蔵PWMに指定するだけで自動的にONとOFFの信号を出すことが出来、 しかも一旦周波数を設定しておけば手放しで音程を出しつづけてくれるので、ソフトウェア側の処理負荷が軽くてお手軽です。 プログラム的にはPWMの発振周波数を20Hz~20000Hz程度に設定するだけで音程が表現できます。

一方この矩形波出力は「音色」がいつも一緒。音色を選ぶことが出来ません。ICの出力ピンをオン/オフに 切り替えるながら長方形の波形を出力し、その長方形の長さで音程を、長方形の高さで音量を制御する簡便な方式です。 ファミコンの音楽はどれも同じ音色しか出なかったのは、この矩形波の宿命というやつです。

つまり、矩形波出力では簡単な音楽を鳴らすことはできても本格的な音楽を鳴らすには表現力が乏しいということです。 ちょっとしたオルゴール的な用途にしか使えません。「音楽の演奏」というには矩形波ではちょっと役不足です。

音の3要素とFM音源

「音」は3つの要素から構成さます。「音量」「音程」「音色」がそれに相当します。

矩形波出力方式ではこのうち前の2者まではなんとか作り出すことができるのですが、「音色」はコントロールすることが出来ません。 FM音源というのは、コンピューター内部でこの「音色」まで造り出す音源です。

FM音源は、YMOはじめテクノ系音楽で用いられるようになり、伝説のキーボードシンセ=YAMAHAのDX-7 などたくさんのシンセサイザー機器に内蔵され、80年代にはPCやゲーム機にも効果音生成用に内蔵された結果 「ゲームミュージック」なる新たなサブカルチャーを作り出したりした、一時代を築いた音源です。

現在は、音源といえばサンプリング音源(PCM)が主流になってしまった感がありますが、今でも携帯電話の着メロ用などで 相変わらず使われつづけています。サンプリング音源の代表といえばCD、つまりコンパクトディスク。 音声を時系列に細分して数値化し、それをコード化してROM上に格納しておき、再生時には このデータを順にアナログ変換して出力する方法です。この方法はCDを見るとお判りのとおり大量のデータが必要になります。 1時間で約650MB程度でしょうか。

一方FM音源は再生時に「計算式」を元に音色を作り出しながら鳴らす方式なので、データ量の点でははるかに少なくて済むとう メリットがあります。それゆえに携帯電話にでもサクッと入ってしまうわけです。

そしてデータ量が少なくても大丈夫なので、容量の小さいマイコン程度でも比較的簡単に実現が可能かと思います。

「音色」について

音色って、一体なんなのでしょうか?

音色は、一言で言うと複数の波を重ね合わせたものです。

楽器や人間の声などの音色は、ベースになる波の周波数に対して、その整数倍となる周波数の波=「倍音成分」を重ね合わせることで 得ることが出来ます。

例えば、ピアノの鍵盤で「ド」のキーを叩くとおよそ262Hzの波形(基音)に加え、その整数倍の波形(倍音)が 出力されることになります。「ド」の場合、倍音成分の周波数は524Hz、768Hz、1048Hz、1310Hz… といった具合です(ちょっと不正確な値ですが)。

人間の耳はこの基音と倍音を足し合わせて出来上がった波形を「音色を持った一つの音」として聞く事ができるわけです。 楽器によって異なる音色は、この倍音成分の量や比率に起因します。2倍音が少なめだったり3倍音が多めだったり、 4倍音が少なめだったり…という違いが個々の楽器によって変わるわけです。

文字だと解り難いと思うので、少し可視化してみます。

先ほどの図です。これはFM音源生成の計算式を元にコンピューター画面上で波形をシミュレートしてみたグラフの例です。 音の1波形分に相当します。

このフニャフニャした波形が、基音と倍音を足し合わせて出来た一つの「音」の波形というわけです。

出力された波形は、上述のとおり「基音」と「倍音」を足し合わせたものなので、こんなイメージです↓。

なので、この波形を逆にスペクトル分析してみると、およそ以下のように基音と倍音が一定周期で現れることになります。 (グラフは正確ではありません。イメージです)

赤い線は基音成分、青い線は倍音成分です。基音成分が1本あり、その右側に倍音成分がたくさん並んでいるのが お判りいただけるかと思います。倍音成分は基音の周波数に対して整数倍の周波数(2倍、3倍、4倍…と続いていく)になっています。

これら赤い線、青い線の一つ一つは独立した正弦波ですが、これらのたくさんの正弦波を足し合わせたものが「音色を持った一つの音」として 聞こえるというわけです。この青い線が、部分的に長かったり短かったりすることで、色々な音色になって聞こえることになります。

こんな風に書くと、「FM音源ってちょっと難しそう」とか「FM音源ってこうやって正弦波を足し合わせる処理やるの?」とか思われる かもしれませんが、そうではありません。実際の動作原理、計算方法は驚くほど簡単です。このFM音源をマイコンを使って ソフトウェアで実際に鳴らしてみちゃおうというのがこのページの主旨です。

FM音源はYAMAHAがライセンスを持っているらしいのですが、ライセンスなどに関する詳しいことは wikipediaなどに載っているので省きます。 まぁ、商用に作るわけではなく自宅でサクッと実験する範囲ならライセンスも関係ないので、実験材料としては面白いのではないでしょうか。

当面のゴールについて

目指すところ

とにかく周波数変調を使って音を出すことだけを目指します。

ひとまず実験レベルってことでやってみたので、まずは簡単に音を鳴らすことを最優先とし、応用範囲については劣後させました。 というわけでまずは実際に音を出してみたのでその成果を先に挙げておきます。この音を出すことをこのページのゴールとします。

ひとまずキャリアとモジュレータの周波数比をそれぞれ1~7に設定し、変調の度合いを中くらいにしたものを録音してみました。 周波数比とはナンゾヤ?等といったお話については後述しますが、周波数比や変調度合い(モジュレータの出力レベル)は 自由に制御できるようにしました。

見送るところ

色んなアルゴリズムとか、エンベロープとか、LFOなどは見送ります。(用語の説明は後ほど)

多々あるアルゴリズム(キャリアとモジュレータの組み合わせ方…より複雑な音色を作れる)や エンベロープパターン(ギターやピアノの音が徐々に減衰していく等の表現)については組み込めてません。 キャリア=1個、モジュレータ=1個の最も単純なアルゴリズムだけとし、この周波数比と変調レベルを 変えながら音声合成します。倍音成分(つまり音質)を自由に変化させられるということを目指しました。和音もまだ出来ません。

またエンベロープとも絡みますが、トレモロやビブラートといったLFO機能なども未登載です。

あと、今回は4つの音程…「ド」「ミ」「ソ」「1オクターブ上のド」の4音を連続していますが、プログラムのロジック中に そのメロディー自体を組み込んであるため、今のままではそれ以外のメロディーは出せません。 まぁ、ここまで実現できていれば、この後の拡張は比較的簡単に行うことができるでしょう。

なにしろ、受動的に動作する「音源機能」とするか、それとも譜面(メロディー)データを内蔵して能動的に音を発する 「オルゴール的」とするかによって設計の方向性が大きく分かれてしまうので、その一歩手前までに留めました。

FM音源の原理

「基音+倍音」によって「音色」を造り出すということを上記で触れました。では、マイコンで延々と続く倍音を計算して 基音に重ね合わせていくのがFM音源かというと、そうではありません。そもそも倍音成分は有限個というわけではないので、 延々と重ね合わせるためには、無限大の処理速度が必要になってしまいますし。

そこで周波数変調(FM)というものを使うわけです。

FM音源とFMラジオ? 周波数変調について

残念ながら私は周波数変調についての専門家でもなく、かつ数学なんて「じんましん」がでて痒くて痒くてわしゃかーなわんので、 あまり正確な説明にならないかもしれませんが、私の頭の中にある周波数変調についてザックリまとめてみたいと思います。

FMといって一番最初に思い浮かべるのはなんと言ってもFMラジオの「FM」という言葉かと思います。そう。 FM音源の「FM」はFMラジオの「FM」と同じです。(同じというのは、変調の方式が一緒ということであって、 用途が同じという意味ではありません)

FM放送の電波

解り易いように、FMラジオの電波を可視化してみたいと思います。以下のプログラムを実行すると、FMラジオの電波として 送信されている波形のイメージが表示されます。…この時代にBASIC言語というのもイマイチですが、 この手の短いプログラムには使い勝手がいいのでご勘弁…

   10 CLS 3
   20 LINE (100,100)-(100,300),7
   30 LINE (100,200)-(450,200),7
   40 FOR R=0 TO 3.142*40 STEP 0.001
   50 Y = - SIN(R + 8*SIN(0.1*R)) * 100 + 200
   60 X = R /3.142 /40 * 300 + 100
   70 PSET (X,Y)
   80 NEXT

短いプログラムですが、こんな短いプログラムの中に周波数変調の処理が組み込めてしまっているわけです。しかもそのメインとなる 部分はこの中のたった1行。

   50 Y = - SIN(R + 8*SIN(0.1*R)) * 100 + 200

ここだけ。もっというと、Y = SIN(R + 8*SIN(0.1*R)) これだけ。

FM放送の電波では、この図ように波が密なところ(周波数が高い)と疎のところ(周波数が低い)を作って音声を送信しています。 その密度によって信号の高低を表現するのがFMラジオで使われている「周波数変調」という方式です。密ならHIGH、疎ならLOWなどと 決めておけば、受信側ではその周波数の高低から元の信号のHIGH、LOWを再生することができます。

周波数変調のキーポイントはこのプログラム中に隠されています。それは、sin関数の引数内にもう一つ入れ子構造になった sin関数です。

この内側のsin関数が無ければ、外側のsin関数は単純に正弦波を出力することになりますよね? 引数にsin関数を加えていること、 これが周波数変調のキーとなる部分です。内側のsin関数と外側のsin関数の各引数を見ていただくと、 その基本周波数を決めている変数「R」の係数が違っています。内側のsin関数は周波数が0.1倍されているのがお判り いただけるかと思います。外のsin関数よりも内側のsin関数の方がゆっくりとした波になっています。(この場合なら0.1倍…つまり10倍ゆっくり)

なお、この数値は視覚的に特徴が表れやすいように設定した数値なので、実際のFM放送でこの定数が 用いられているわけではありません。FM放送の周波数では外側のsin関数に78Mhzとか79.5Mhzなどを用い、 内側のsin関数の替わりに音声信号(20~20Khz程度)を使っているわけです。0.1倍どころではありませんが、 とにかく外側のSIN関数より内側のsin関数の周期の方がゆっくりした周波数だということがFM放送の方式のポイントです。

ラジオ放送の電波にFM(周波数変調)を用いるのは、上の図のように常に振幅が一定にできることがメリットになるからです。 AMラジオの場合、信号の強弱がそのまま音声の強弱になるわけですが、受信した電波の強弱は本当に元の音声信号の強弱なのか、 それとも電波の受信状態が悪いのか判断が付きません。FMの場合、振幅ではなく周波数の間隔の変化が音声信号の強弱となるので、 このような受信状態に起因するノイズや減衰を受けずに済む訳です。

FM音源の周波数変調

FM音源の周波数変調も、考え方は一緒です。大きく違うのは、さっきのプログラムで0.1倍となっていた係数の所です。

FM音源の場合、この係数のところに「1以上の整数」が用いられます。つまり外側のsin関数より内側のsin関数の方が 周期が短いということになります。しかも整数倍です。実際にサンプルプログラムを動かして見ます。

   10 CLS 3
   20 LINE (100,100)-(100,300),7
   30 LINE (100,200)-(450,200),7
   40 FOR R=0 TO 3.142*2 STEP 0.001
   50 Y = - SIN(R + 0.5*SIN(3*R)) * 100 + 200
   60 X = R /3.142 /2 * 300 + 100
   70 PSET (X,Y)
   80 NEXT

すると冒頭の図になるわけですね。内側の「R」の係数部分を見ると今度は「3」になっています。外側のsin関数と 内側のsin関数の周波数の比を設定しているところですが、「周波数比=3倍」を設定しているということを意味しています。

このようにすると、FM放送の変調方式とFM音源の処理方式は同じ事がわかります。もちろん、その目的は異なりますが。 計算式中の定数がちょっと違うだけなのですが、グラフ上では結構な違いになって現れて面白いところです。

さて、どうしてsin関数の内側にもう一個sin関数を入れると「倍音成分」が乗るのかについては、 数学の苦手な私には理論立てた説明がし切れません。詳しく知りたいと言う方は、 wikipediaなどを別途ご覧ください。 フーリエ変換すると、どうやら整数比の周波数を持ったsin関数の集合体になるっぽいです。

後もう一つ。なぜ音色は整数比の正弦波の集まりで構成されるのか…。詳説はしませんが、これは「定常波」とか「共鳴」 というキーワードで調べて頂くとピンと来るかもしれません。例えばギターの弦に生じる定常波…それぞれの波が必然的に整数倍となるはずかと。 ご興味のある方は是非検索してみてください。(1/2倍波なんていうのもあるんですが、その話は省きます)

数式について補足

先ほどのBISICプログラム中の数式の意味を、もう少し可視化して見えるように動画を作ってみました。 通常のsin関数の読み出しと、周波数変調でのsin関数の読み出しという観点で比較してみます。

通常のsin関数の読み出し方

周波数変調を掛けた場合のsin関数の読み出し方(周波数比=3を想定)

上の図は通常のsin関数の読み出し方。下の図は周波数変調を掛けた場合の読み出し方です。

縦の線がsin関数を読み出す引数だと思ってください。sin関数の引数内に入れ子状態のsin関数があるので、 このとおり行ったり戻ったりしながら読み出すことになり、ふにゃふにゃした曲線が読み出されることになります。

要は、マイコンにこれと同じ処理をさせればFM音源の音声合成処理ができちゃうわけです。

(なお、上下のムービーの開始と終了がシンクロしていない場合は、ブラウザの再読み込みで一旦読み直ししてみて下さい)

原理のまとめ

上記のとおり、周波数変調の原理について図と動画にまとめてみました。

周波数変調は、sin関数の引数にsin関数を加算することで元の波形に変調を掛ける方式で、特にFM音源の場合は 内側のsin関数の周波数を外側の整数倍にすることで行う、ということができるかと思います。 実際に今回MEGA48用に作ったファームウェアもこの計算方式そのままです。

まぁ、本格的なFM音源となると、内側のsin関数が1個だけというわけではなく2個、3個…と使われていたり、さらには sin関数の中のsin関数の中にさらにsin関数が…と直列的に変調を掛けて、物凄く複雑な変調を掛けることも行われます。 後述しますが、こういうsin関数同士の組み合わせ方を「アルゴリズム」と呼びます。楽器のタイプによってマッチするアルゴリズムを 選んだりして音作りをします。

また、一番外側のsin関数を「キャリア」、それより内側のsin関数をすべて「モジュレータ」といい、これらの「キャリア」と 「モジュレータ」をあわせて「オペレータ」といいます。(キャリアもモジュレータも、単機能としてはsin関数…つまり正弦波を 出力するだけという点では変わりなく、実際にFM音源のicの中ではどちらもオペレータという同一機能として組み込まれています)

言い換えると、アルゴリズムはオペレータの組み合わせ方法と同義です。

今回は音を出すことを最優先にするので、複雑なアルゴリズムは用いず、キャリア1個、モジュレータ1個という最も簡単な パターンだけに絞ります。

FM音源の実際

アルゴリズムを図で描いて解り易くする

多分、FM音源で音色を作る時に一番重要な概念を3つ挙げるとしたら「アルゴリズム」「モジュレータ」「キャリア」 でしょう。キャリアというのは先ほどの計算式で外側にあったsin関数のことで、モジュレータというのは 同様に内側にあったsin関数のことです。モジュレータのsin関数にさらにsin関数を内包することもできます。 この場合の内側のsin関数もモジュレータといいます。

そして、このモジュレータやキャリアの組み合わせ方のことをアルゴリズムといいます。アルゴリズムは変調の掛け方を 決定する大枠となります。楽器によってどの様なアルゴリズムを使えばいいのか向き不向きがあり、 向き不向きを考えながらアルゴリズムを選択して使います。

ちなみに、FM音源で音色を作るときにいちいちsin関数を引っ張り出していては大変なので、普通はもっと簡略化した 図でアルゴリズムを描き表します。具体的には、各オペレータを矢印で繋いで描き、どのオペレータの出力がどのオペレータに 入力されるのかを記します。先ほどのプログラムの変調方法を図示してみると…

こんな具合です。この図では、水色でモジュレータを、橙色でキャリアを描いてみました。外側のsin関数がキャリア なので右の四角、内側のsin関数がモジュレータなので左の四角です。なお、一般的に色は決まってません。 また、縦書きだったり横書きだったり、特にキッチリとしたキマリは無いようです。ただ、四角と矢印で描くのが一般的です。

モジュレータもキャリアも、単機能としてはsin波を出力しているだけと言う点で一緒なのは先述のとおりで、 機能的にはどちらも「オペレータ」と呼ばれています。今回MEGA48でお試しに作ってみるFM音源もオペレータが2個だけ で、アルゴリズムとしてはこのように直列に接続した形になります。図の右端に矢印が出ていると思いますが、これが 音声出力信号となり、アンプ、スピーカに繋ぐと音として聞く事が出来ます。

もう少し一般的なFM音源のアルゴリズムも眺めてみます。私が昔馴染んでいたYAMAHAのYM-2203を取り上げてみます。 YAMAHAのYM-2203には、1音あたりオペレータが4個あり、これが3音同時に発声することができるようになっていました。 (オペレータの数としては4×3=合計12個になりますね)

このYM-2203のアルゴリズムは以下の8種の中から選ぶことができます。

一つ一つ取り上げて解説することは出来ないので、特徴的なものだけ取り上げて触れてみます。

まず一番上のアルゴリズム0。これは、1~3番の3つのモジュレータを直列で繋いでからキャリア(4番)に接続して います。1~3番のモジュレータ直列接続によってとても複雑な変調を行っており、各アルゴリズムの中では最も複雑な倍音成分を生成できる モノです。

次にアルゴリズム4。これは2番と4番の二つがキャリアとして働いていて、それらにそれぞれ1個ずつモジュレータが ついています。今回MEGA48で作るFM音源のアルゴリズムが2つ並列しているのと大体一緒です。マンドリンのように1コースに2弦が 張られているような楽器を思い浮かべてください。1回音を鳴らすと2つのキャリア(弦に相当)から別々の音が出るイメージで、 例えば、キャリア(2番と4番)の周波数比を3度とか5度にしておけば、常に3度や5度の和音を奏でるような使い方も出来ます。

また、YM-2203はじめ一般的なFM音源ではエンベロープパターンが設定出来るので、1番+2番は弦を弾いた瞬間だけ 音を出し、その後急速に減衰させ、3番+4番は長く余韻を残す音に使うなどといった使い方も出来ます。このように アルゴリズム4はかなり使い勝手が良いです。(エンベロープについては後述)

アルゴリズム6番や7番は、モジュレータを伴わないキャリアが存在しています。当然それらのキャリアから出力されるのは 正弦波となります(厳密には、オペレータ1番にはフィードバック機能があるので、アルゴリズム7でも若干の変調は掛けられる)。 倍音によって音を作るというよりも、一つ一つのキャリアに別々のLFO(ビブラートやトレモロなど)をかけて音に厚みを 乗せるとか、そういう用途に使われます。

そのフィードバックについてです。YM-2203の場合オペレータ1番には出力側(右側)から入力側(左側)に線が繋がって いるのが見えるかと思います。これがフィードバックを表していて、オペレータ1番の出力が自分自身(オペレータ1番)に 対して変調をかける変調波として使われています。このような使い方をフィードバックといいます。

なおYM-2203ではオペレータが4個ですが、本格的なシンセではもっとたくさんのオペレータを組み合わせて 複雑な音色を作り出すことが可能になっています。

エンベロープとLFOの働き

「音色」と「音量」両方の調整に足を突っ込んだ機能に「エンベロープ」や「LFO」というものがあります。

エンベロープというのは、時間の経過によって出力される波の振幅を変化させる仕組みで、LFOというのは 振幅や周波数等を周期的に揺らすような仕組みです。

実際の楽器の音は基音と倍音のバランスだけで作られているわけではなく、時間の経過によって徐々に変化を生じます。

このような変化をシミュレートするのがエンベロープやLFOです。音量、音程、変調の度合いなどを時間軸方向で緩やかに変化させ、 音が徐々に減衰していく様子や、ビブラート、トレモロ、ワウなどがかかる様子もシミュレートできるようになっています。

今回のMEGA48用プログラムでは組み込みを見送りましたが、今後対応する時のために纏めておきます。

エンベロープを掛けると

まずこの絵の青い波線は、各オペレーターから出力される素の波形だと思ってください。

モジュレータ-からの出力であれキャリアからの出力であれ、オペレーター出力の素のままだとこのように振幅の最大値・最小値を 繋ぐとそれぞれ一直線になるような波形となって現れます。振幅が赤い線のところで一定、つまりキャリアなら音量が常に一定、モジュレータなら変調度合いが一定ということを表しています。

ところが、現実の音はこのような一直線の波形にはなりません。もうちょっと複雑です。

例えばギター。弦を弾くと、最初に大きな音が出て徐々に音が減衰して小さくなっていきます。下の図の赤い線 を思い浮かべるとしっくり来るかと思います。

このような音量の時間的変化や、変調量の時間的変化をシミュレートするのがエンベロープというものです。 各オペレータの出力にエンベロープを掛けると、そのオペレータの出力量がこの図のように 時間の流れに沿って変化することになります。

エンベロープの働き(形状)を規定するには4つのレート(attack,decay,sustain,release)と1つのレベル(sustain) という計5つのパラメータが使われます。これら4つのレートは各部の角度(正確には上下する時間)を、1つのレベルは 落差の幅を定義するものです。これらを指定することで信号の出力量を徐々に変化させることができるようになります。

ギターを例にして、キャリアの出力量にエンベロープを掛けることを考えてみます。 ピックで弾いた瞬間に大きな音が出て、その次の瞬間に一旦落ち込み、その後余韻を残しながら数秒間にわたって 徐々に音が減衰していき、でもミュートを掛けると一気に音が抑えられます。

ピックで弾いた瞬間に音が大きく出るのがattackの部分、その次に一旦急激に音が小さくなるのがdecayの部分、 数秒間近くにわたってゆっくり減衰していくのがsustainの部分、ミュートを掛けて一気に音量を 抑えるのがreleaseの部分に相当します。これが管楽器の場合だと、もう少しゆっくり音量が立ち上がって(attack)、 呼吸の続く限り同じ音量で伸びつづける(sustain)といった感じになるかと思います。

MIDIなどと繋いで鳴らした場合、例えばキーボードのキーを押した瞬間がattackに相当し、キーを話した瞬間がreleaseに相当します。

このようにattack,decay,sustain,releaseを用いたエンベロープの方式はADSR方式と呼ばれます。 (ADSR方式のエンベロープはFM音源に限らず、色々な方式で用いられているようで、かなり一般的な考え方みたいです)

キャリアにエンベロープを掛けた場合にはこのように音量が時間の経過によって変化していくことになりますが、 モジュレータにエンベロープを掛けた場合は変調の深さ(つまり倍音の量)が時間軸に沿って変化していくことになります。 一般的な楽器では、音の鳴り始めには倍音が一瞬多く出て、その後一旦急速に減少し、sustainの部分に相当するところでは 倍音の量は殆ど変わらない、という場合が多いようです。

LFOを掛けると

LFOとはLow Frequency Oscillator、つまり日本語では低周波発振器です。この場合の低周波とは可聴域よりも 低い周波数…つまり耳で聞き取ることが出来ない周波数=数ヘルツ前後のことを指します。

「耳に聞こえない周波数を発生してもしょうがないジャン!」と思う無かれ。ちゃんと用途があります。発生した 低周波を使って、各オペレータの出力量やベースとなる周波数(基音の周波数)を上下に周期的に振る効果を得るための 元となる波形として使います。コブシを効かせて歌うようなイメージです。

LFOが発生する波形はsin波だけでなく、例えばYM-2203ではノコギリ波や三角波、ランダム値のサンプル&ホールド などが選択できるようになっています。

具体的には、「ビブラート」や「トレモロ」「ワウ」などといった効果を得るのに使われます。それぞれ以下のような効果があります。

これ以外にも、左右のチャンネルに周期的に揺れを生じる効果(パンといいます)など色々な用途に使われるのですが、 私が馴染んだYM-2203はモノラル音源だったという事情もあり、使ったことがないので省きます。

もう少し解りやすく紐解いていきたいと思います。ホントは漫画にすると解りやすいんだろうと思うのですが、 上手く漫画に表現できない(特にワウが)ので、数式を使ってみようと思います。

例によってキャリア1個、モジュレータ1個の一番単純な周波数変調の式を例に挙げます。

まずはそのワウ。この数式では内側のsin関数の係数Wの部分が周期的に大きくなったり小さくなったりすることで 効果が生じます。内側のsin関数は周波数変調の度合い(=発生する倍音の量)を決めているので、倍音の量が周期的に 増えたり減ったりするわけです。YM-2203を例に取ると、このパラメータはオペレータ単位で設定出来るので、 たくさんのモジュレータを使うアルゴリズムでは当然一つ一つ別々に設定可能です。

次にトレモロ。この数式では外側のsin関数の係数Vの部分が周期的に大きくなったり小さくなったりすることで 効果が生じます。外側のsin関数は音量の大きさを決めているので、音量が周期的に増えたり減ったりするわけです。 ワウと同様オペレータ単位で設定出来るので、複数キャリアを使うアルゴリズムでも、キャリア単位に設定できます。

そしてビブラート。この数式では周波数を決めている変数Fの変化スピードが周期的に速くなったり遅くなったりすることで 効果が生じます。つまり、FにLFOの出力値を加算することで周波数自体を周期的に揺らすわけです。 Fは内側と外側両方(というかすべてのオペレータ)で共通に使われるベース周波数を規定しているので、 Fを揺らすということは倍音成分も(周波数比を保ったまま)一緒に周期的に揺れることになり、音色には影響を与えずに (周波数比を保ったまま)音程だけを上げたり下げたり繰り返すことになります。

YM-2203の場合、ワウやトレモロはamplitude modulation depthとamplitude modulation senseで指定するのですが、 これは上述のとおりオペレーター単位で設定可能です。一方、ビブラートはpitch modulation depthとpitch modulation sense で指定するのですが、これはアルゴリズム全体(4つのオペレータに共通)に設定されます。

一般的なFM音源のまとめ

以上、YM-2203を題材に一般的なFM音源がどんな仕組みで鳴っているのかを紐解いてみました。

一般的なFM音源には周波数変調の機能に加え、「エンベロープ」と「LFO」というものが登載されていて、そしてエンベロープは 音が鳴り始めてから減衰するまでをオペレータの出力量調整によって制御し、LFOは音量や音程、倍音の量などを 周期的に揺らす働きを持っていることを触れました。

FM音源では通常このような仕組みと、周波数変調による倍音生成を組み合わせて使うことで、現実の楽器に近い音色を シミュレートできるというわけです。

なお今回AVRで作るFM音源の実験ではキャリア1個+モジュレータ1個の単純な周波数変調による倍音成分の生成だけに留めます。 いつか、MIDI音源的な受動動作機能を作りたいと思っているので、その時に改めて組み込みに挑戦したいと思います。

周波数比について補足

周波数比はなぜ整数?

キャリアとモジュレータの周波数比を整数に設定すると、既に触れたように周波数変調によって倍音成分が生じます。 これは楽器内で発生する定常波をシミュレーションしている状況ということです。では周波数比を整数にしなかった場合はどうなるかというと…

当然ながら綺麗な整数比となるような倍音成分は発生しません。倍音ではない波形が生じることになります。倍音ではない波形というのは どういう状況なのでしょうか?

一般には打楽器の波形は決まったスペクトルを持ちません。特定のスペクトルを持つように作られているのがメロディー用の楽器、 特定のスペクトルを持たないのがリズム用の楽器ということになるでしょう。(スチールドラムのような音色はちょっと特殊ですが)

和音と周波数比

ある決まった周波数比(例えば3度とか5度とか)の時には人の耳に心地よく感じます。いわゆる協和音です。 例えば代表的なコード「C」。Cといえば「ドミソ」です。ドとミは3度、ミとソも3度。典型的な協和音です。

逆に特定の周波数比(2度とか7度とか)では心地悪く感じます。不協和音です。ドとレが2度、ドとシが7度ですね。

それらの心地よい和音を元に作られているのがいわゆる楽曲で使われている「コード」というもので、さらにあるコードから 次のコードに移り変わっていく流れを「コード進行」といいますが、主題からそれていくので詳しいことは省きます。

そしてギターやピアノのようにメロディーに使われる楽器には、こういった和音を構成できるものがもっぱら使われることになりますが、 一方、打楽器というのは特定のスペクトルを持ちません。

もしメロディーラインが出す周波数とリズムを刻む打楽器の周波数がたまたま2度とか7度とかの周波数比になれば 打楽器も当然不協和音となり得るのですが、実際は特定のスペクトルを持っていないため協和音とも不協和音ともなりえません。 そのため打楽器は「リズム楽器、パーカッション」として使えるわけです。

打楽器とFM音源

では、FM音源でキャリアとモジュレータの周波数比を整数以外に設定すると打楽器の音になるのでしょうか?

YM-2203では周波数比は整数にしか設定できないため、こういった実験が出来ませんでした。理屈から言うと 多分できるのではないかと思うので、このページの巻末あたりで実際に実験してみたいと思います。(しばし待たれよ!)

仕様の検討

さて、いろいろ理論をこねくり回したのでようやくモノを作ってみます。

仕様検討といっても、仕様自体はほぼ決まっているので、個々の機能要件を整理して、各機能のスペックを詰めていくという 感じで進めます。

機能要件の検討

マイコンでFM音源を作るときに必要となる各機能と、その各機能の接続方法についてまとめていきたいと思います。 出力方向から逆に辿っていって、必要な機能を洗い出してみます。

まずはアナログ音声の出力を行うのでアナログ出力機能が必要です。そして、正確に一定周期で音声データを順次出力するためには 割り込み機能を使って正確な時間間隔を得る必要があります。

次に、キャリアやモジュレータの波形計算にあたってはsin関数が必要になるので、sin関数の計算ロジックもしくは計算結果を 溜め込んだテーブルが必要になります。一般にマイコンにはコプロセッサなど登載していないので、処理能力的にはデータをROM上に テーブル化する方が現実的でしょう。

これらが周波数変調として最低限必要な機能ということになるかと思います。

さらに実際に音を鳴らすためには音源機能だけでなく最小限のメロディーデータが必要になるでしょう。 いわゆる「楽譜」ですね。マイコンで音楽を鳴らすとなると、受動的に音符のデータを受け取る(いわゆるFM音源的)動作をするか、 自身にメロディーのデータを内蔵して能動的に動作(オルゴール的)をするかのどちらかということになるかと思います。

今回はそのどちらかを作りたいという明確な目標があるわけではなく、どちらにでも応用が利くようにしたいというのが 正直なところなので、必要最低限の機能として「ドミソド」の4つの音を連続して鳴らすだけに留めたいと思います。

これらを踏まえて、処理フローを漫画にするとこんな感じでしょうか。

ご覧のとおり、メインループでは特に処理は行いません。

割り込みをトリガにして、カウンタをカウントアップします。カウンタはこの図では2つあり、一つは1波形内のどこまで 経過したのかを計るカウンタで、当然音程によって伸縮します。もう一つは音符1個分の長さを計るカウンタです。 これらのカウンタ値を元に割り込み処理内でモジュレータやキャリアに相当するsin関数の引数を算出、sin関数の 読み出しを行います。読み出されたデータはPWMを使ってアナログ出力します。

機能ブロックがおよそ見えたところで、マイコンの処理能力やメモリ容量等にフィットするように各スペックの 数値を考えていきます。

スペックの検討

思いつくところから一つずつ挙げていってみます。

PWM出力周波数

CDなどとほぼ同じ出力周波数を目指すと考えると、20000hz程度まで出力できるようにしたいところ。 するとサンプリング定理(標本化定理)によって約40000回/秒の頻度でデータ出力が必要となります。

PWMで40000回/秒の出力を行うとなると、AVRを20Mhz(20000000hz)のクロックで 動作させるとしたら、20000000÷40000=500クロック毎に1回以上のデータ出力(PWM出力)が必要となります。

ただし今回はハンダ付けするのが面倒ということも有り、動作ボードにPLAYER を使うことにするのですが、このボードはtimer1(16ビットカウンタ)に音声出力回路(ローパスフィルターとRCAコネクタ)が 接続されているので、timer1の特性を考慮した設定値とすることにします。

timer1をモード5(高速PWMモード、TOP値=0xFF)とし、コンペアマッチ割り込み無し、オーバーフロー割り込み無し、 プリスケーラ無しに設定し、256クロックに1回の割合でパルス出力を行うように設定することにします。すると最大では 78125回/秒(約39000hz)程度まで出力できることになります。ちょっとスペックに満たないのですが、見なかったことにします。

割り込み頻度

一方、PWMへデータ出力を行う計算のトリガには、残ったタイマー(timer0かtimer2…ともに8ビットタイマー) のどちらかで割り込みを掛けることになります。どっちでも良いんですが、ひとまずtimer2とします。 この割り込み頻度とPWM出力周波数のうち低い方が音質を決定付けることになります。

というわけで、割り込み頻度についてもCDクオリティーとして500クロックに1回程度の割り込みが必要になります。 8ビットタイマーを使って500クロック程度の頻度となる設定値を考えて見ます。

まずプリスケーラを使わない場合、TOP値=250とすると割り算が割り切れて簡単なので候補の1つとします。 プリスケーラを8、カウンタのTOP値=64にした場合は8×64=512クロックに1回の割り込みが 生じます。これをもう一つの候補とします。

前者は80000回/秒(40000Hz)まで出力が可能。後者は39062.5回/秒(19531.25hz)まで 出力可能。うーん、今回は実験なので高周波まで出力できる方を選びたいところ。20000Hzを死守することに します。(まぁ、後述するとおりローパスフィルターがそこまで出してくれないのであまり拘っても意味無いんですが)

それに、本当は割り込み~割り込みの間隔は広く取った方が複雑な処理をたくさん詰め込むことが出来て良いのですが、 今回は実験ということでスペック重視ということにします。

そもそも、PWMで音声出力するならどうせ8ビット精度が精々なので、音声をtimer0かtimer2に、割り込みをtimer1に したほうが設計はしやすいと思います。PLAYERを設計する時にはそこまで考えてなかったなぁ…

sin関数計算

周波数変調による音声出力のベースになるのはsin関数ということになるので、何らかの方法で正弦比を求める 必要があります。

仮に浮動小数点計算をソフトウェアで行うと仮定すると、以前16Mhz動作のarduinoと、20MhzのPIC16F877(CCS-C)の 比較実験(ブログの該当記事に飛びます)を してみたとき、arduinoでもsin関数の実行を毎秒約3000回こなすことが判りました。20Mhzなら4000回近く 行くかと思います。

さすがにCCS-C+PIC16F877よりは速いAVRですが、毎秒4000回では精々2000Hzがやっと。しかも 1回の計算でこれだから、キャリアの計算だけでも2000Hz。ダメだこりゃ。

というわけで、あらかじめ計算した結果をROM上にテーブルにして置くことにします。問題はそのデータ数とデータ幅。

まずデータ幅ですが、PWM出力となると既に8ビット(256段階)が精々ということが判っているので、これにあわせる ことにします。

データ数ですが、例えば440Hzの音を20Mhzクロックで考えると、単順に割り算すると1波形あたり45000個ほどの データが必要となります。じゃぁ、45000個のデータを用意すれば良いのかというとそうではなく、データの精度はデータ幅 (8ビット)しかないので、そんなにたくさんのデータを用意してもオーバースペックになるだけです。 8ビット幅のデータを綺麗に出すためには、ザッと計算すると800個程度で十分ということになります。(なぜ800なのかは ちょこっと考えてみてください)

ここで一つ考える必要が。計算式中の割り算のこと。クロック数(正確には割り込みの発生した数)から、周波数比とか 音程とかによって割り算の「余り」を求める必要があります。この「余り」がsin関数の引数、すなわち 「ノコギリ波の歯1個分」の中の位置というわけです。

2進数の世界では、定数で割り算するだけなら”逆数の掛け算”で逃げることもできるから、megaシリーズなら ハードウェア乗算命令でなんとでもできるのですが、”余りの計算”をモロにロジックで出そうとするととっても厄介。 でもちょっと工夫して「割られる数(sin関数1波形分の個数)」を2のべき乗個にしておけば、掛け算の積から 下位数ビットを抜き出すことで算出できます。特に1波形を256個ということにしてしまえば、下位1バイトを取り出すだけで 余りの計算ができちゃいます。そうすればAND(論理積)の計算も不要。これが一番ラクチンです。

ほんとは800個程度に持っていきたかったのですが、とりあえず音を出すことを最優先とし、処理が簡単な「256個」で 我慢することにしたいと思います。

1波形を256個で済ますのは良いとして、忘れてはならないのは自然数表示(0~255)と 2の補数表示(-128~+127)のことです。モジュレータの出力は「内部形式」の2の補数表示でいいのですが、 キャリアの出力はPWMの仕様に合わせて自然数表示にする必要があります。

まぁ、テーブルは2の補数表示にしておいて、キャリアだけ127(128?)足すんでもいいんだけど、どうせ メモリは余ってるし足す処理時間も勿体無いので、両方の表示方法のテーブルを用意することにします。

というわけで、sin関数はROM上のテーブルとして、8ビット幅のデータを256個ずつとし、2の補数表示と 自然数表示の2パターンを用意する事とします。あと留意点としては、念のために257番目(0に戻るはずのところ)を 参照してしまった場合に破綻しないように、ここにもデータを確保しておくことにします。(安全策)

ローパスフィルターのカットオフ周波数

ローパスフィルターのカットオフ周波数は、本当は20000Hzにするのが当たり前だと思うんですが、 PLAYERを作ったときに、わたしのオウチに在った抵抗とコンデンサ、有り合せで組める範囲内で現実的な 数値を採用したため、約10000Hzになってしまっています。まさかハイスペックな音を出そうなんて 考えてなかったので…。

いまさら定数の計算をやり直したり、半田ごてを握ったり、部品を買いに行ったりするのは面倒なので、とりあえず このまま進めることにします。高音がちょっとなまった感じになるかもしれませんが、とりあえず見なかったことにします。 気になる人は定数の見直しをしてください。

なおヘタに高い周波数まで出るようにしてしまうと、それを受けるオーディオアンプが異常発振して壊れるかも 知れないので、やはり20000Hz程度に抑えておくのがよろしいかと思います。

回路図とプログラム

前章まででやるべきことが決まったので、具体化していきます。

回路図

基本的にはPLAYER上で動くように考えているので、 回路図はPLAYERそのままなのですが音を出すには余計な部分がたくさんあるので、必要な部分だけを 残して整理した図を載せておきます。(縮小表示なので、別窓で開きなおすか保存してからご覧ください)

簡単な解説:PB2端子から音声が出力されます。ローパスフィルター回路を通してRCAジャックで出力します。 カットオフ周波数は約10000Hzなので、気になる人は適当に修正してお使いください。

プログラム

アセンブラで組みました。今後拡張していく時に、ICの限界までチューニングするとどの程度の機能が 盛り込めるのかを考えるための基礎数値とするためです。

FM音源(MEGA48用)アーカイブ(クリックすると開きます)

「初期値の設定」のところの「周波数比のクリア」というコメント文がある部分で、レジスタファイル”data1"に 代入する数値を修正すると周波数比が変更できます(目下、周波数比に2を設定してあります)。

そのすぐ下の「変調度合い」のところを0~255で設定すると変調の深さを設定できます。 0で変調無し、255で倍音がいっぱい出ます。(いや、本当は255以上の数を指定することもできるような気がしますが)

コメント文がちょっと間違えていたりするんですが、あまり気にしないで下さい。(実験用なので…)

せっかくPLAYER用に作ったので、実は実行の最中に十字ボタンで周波数比と変調度合いを変更できるように したバージョンも作ったのですが、FM音源の本題からちょっとズレるのでひとまずお蔵入りにしておきます。 (もし見たいという方はその旨ご連絡ください)

今後の課題

今回は周波数変調の処理をマイコンで行って音を出すということを最優先しましたが、ほぼ期待通りの結果がでました。 SIN波1波形で256個のサンプルしかないので、音質はどうかなぁ…と思ったのですが、可もなく不可もなくといった 感じで、まぁ、256個でも悪くは無いんじゃないかな、と思いました。

ただ、現在のプログラムではドミソドの4音を繰り返すだけなので、このままでは役に立ちません。

この先の発展形としては、マイコン内部に楽譜データを保有させておいてオルゴール的に音楽を鳴らすもの、 そしてMIDI音源のように受動的にデータを受け取って鳴らすものの2つのパターンが考えられるかと 思います。

オルゴール的に

オルゴール的に鳴らすとしたら、1個のマイコンで同時に使う音色の数、同時に鳴らす音の数が多くなるので、 意外に処理能力が必要になります。今回、2オペレータ(モジュレータ1個+キャリア1個)でも1サンプルあたり 50クロック以上は必要と判りましたが、20Mhzのマイコンで20000Hzの音を出すとしたら全音の 計算を500クロック程度(20Mhz÷20000Hz÷2)必要となるので、同時に発声可能なのは 4~5個、いっぱい詰め込んでも7~8個は厳しいでしょう。

その範囲で鳴らせる曲ならオルゴール的に使っても結構実用になるのではないでしょうか? 今回はメインロジックが空回し処理になっているので、ここに音符を扱う処理を組み込めば楽譜どおりに 音楽を鳴らすのは難しくないかと思います。

MIDI音源のような音源機能として

一方、MIDI音源的に鳴らす場合を考えてみます。MIDI音源的に鳴らすというと、通常繋ぐ機器は1個。 つまり、弾いてる間は1種類。時々音色を変えることがありますが、まぁ1種類の音で和音が弾ければOKかと。 複雑な和音となるとどうなるか微妙ですが、通常の3~5和音のコードなら問題ないかと思います。

厄介なのは、MIDIデータの解釈の部分。ここはまだあまり詳しい仕様が解ってないのであとでお勉強ですが、 むかーしむかし、音源を自分で作ったらどんな処理かな?って想像したときに難しそうだなと思ったのは コードのうちの1音(例えばギターなら第1弦)を鳴らしているときに別の音を鳴らそうとしたら、 ちゃんと別のオペレーターにアサインしないといけないわけですが、そこらへんの制御はどうやって いるんだろう?ってお話。MIDIの信号の仕様を読み解いたらそこらへんは判ってくるような気がする んだけどな。一方、MIDI信号の速度は結構遅いので、通信処理は処理負荷の観点では軽そうな気がします。

というわけで、「オルゴール」「音源」の2つの方向性で発展できればいいなぁと思います。 個人的にはやっぱり後者で遊びたいな。

スペクトル表示

音の高さを一定に保つようにプログラムをちょっと修正して、周波数比0~7の場合それぞれについてFFTを掛け、 スペクトルを表示してみました。低い方の「ド」の音です。ちなみに周波数比0は変調が掛かってない状態です。 つまり単純な正弦波です。

-30dbより下はどうやらノイズのようです。っていうか、ノイズだらけですね。まだオシロ使い慣れてないので… (それ以上に、単純に回路から発生するノイズが大きいのかも知れませんが)

いずれにしても、周波数比を変化させると同じ所に山が現れるので、それなりに倍音成分が生成されているような感じは見て取れました。

おまけ:打楽器系の音について

打楽器といわれる音は、上でも触れたように一般には整数倍の綺麗な倍音というものがありません。

そこで、キャリアとモジュレータの周波数比を整数倍にならないように設定したら打楽器の様な音が出せるのでは? と思ったわけです。そういうわけで実験してみました。

やり方です。マイコンですから、完全に非整数倍とすることは出来ません擬似的な非整数倍ってことで考えます。 周波数比はプログラム内部では1バイトのデータなので、素数的な数値で出来るだけ大きなものを使って幾つか試して見ます。 こうすると、全く同じ波形に戻るまでには、例えば440Hzの時なら「1/440×周波数比」秒の時間を要するはずです。 周波数比が適当に大きければ(3桁くらい)0.1秒以上の時間がかかる計算です。そうなると、人間の可聴域より低くなって、 多分非整数倍とみなせるだろう…と。

で、幾つかの周波数比を試してみたところ、なんとなく期待した様な結果にはなりませんでした。

スチールドラムみたいな金属的な音やシロフォンの音色の様な複雑な音色がするんだけど、 期待してた様な(故)樋口宗孝氏のドラムさばき的な音は出ませんでした。

まぁ、ドミソドの4音、しかも数百~千Hz程度がキャリアーに充てられているので、ベードラのズンズンした音は 出るはず無いんですが、それにしてもなんともいえない「音色を持った音」。多分整数倍の倍音成分を持ってるんじゃないかなぁ?

今回は、アルゴリズムは「キャリア1、モジュレータ1」の単純なものだし基音の周波数バリエーションも少ないので、 コレだけでは打楽器的な音が出ないとは言い切れないんですが、今回実験してみた範囲では「いかにも打楽器」という 音には至りませんでした…。ざんねん。

サンプル音29:周波数比=29の場合(音が出ます:mp3形式)

周波数比を29に設定してみた時の音です。変な金属音といった感じでしょうか。

ご興味のある方は、是非一度色んなアルゴリズムでも実験してみて下さい。もしかしたら、たくさんのモジュレータが複雑に入り混じると イイカンジになるんじゃかいかなとか、sin関数の精度を現状の256個よりも増やすとか、サンプリング周波数を上げるとか、 色々考えられますが、そもそも考え方自体が間違えている可能性もあるので、色々試してみてください。

といった感じで、今回の実験を締めくくりたいと思います。