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

マイコンでFM音源の再生(後編)

FM音源の後編です。

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個よりも増やすとか、サンプリング周波数を上げるとか、 色々考えられますが、そもそも考え方自体が間違えている可能性もあるので、色々試してみてください。

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