PICを使って直接サウンドを発生させようかという試みです。
このページでの製作物は以下のとおりです。
蛙計画 | PIC16F627A | 白鍵1オクターブのミニ楽器 |
蛙計画 - 黒鍵版 - | PIC16F627A / PIC16F716 | 黒鍵を含めた1オクターブのミニ楽器 |
蛙計画 8ピン版 | PIC12F615 | 8ピンPICを使った白鍵盤ミニ楽器。 |
ケロゴールT - 蛙オルゴール - | PIC12F615 / PIC12F683 | モノラル・オルゴール |
ケロゴールU ー ヴェロシティ対応 - | PIC12F683 | ヴェロシティ対応モノラル・オルゴール |
ポリ蛙 - PICはPSGの夢を見るか - | PIC16F648A | MIDI8音のミニ楽器(モノx8) |
小ピンのPICで音を出す場合、それほど多くの選択肢はありません。 とはいえ、根本的な方法の違いで二種類の選択があります。
出したい音程に合わせて周波数を変えながら出力する方法です。基本的にはピンあたり単音の出力となります。
一番簡単な方法です。周期に合わせてオンオフするだけ。
該当するピンをタイマーを使ってオンオフします。
CCP(PWM)の周波数を変更して音程を作ります。同時にPWMの幅が1/2になるように変更します。PWMの周期が十分な分解能を取れるか?がポイントです。
周波数を変えながら波形を読み出して出力します。確か、最初の頃のアカイのサンプラーがこの方式でした。波形のデータをDAC出力する必要があります。
ソフトウェアでPWMをかけながら、周波数可変で出力します。たぶん、間に合いません。
CCP(PWM)の周波数を変えながら、音程を作ります。同時にPWMの幅で波形を表します。PWMの周期が十分な分解能を取れるか?がポイントです。
これも、周波数を変えながら音程をつくり、同時にDACに波形の値を送ります。
要するに、PCM出力ってやつです。内部で波形を作って、その時々の値を書き込んでいく。一つの出力に複数のものを混ぜることが出来ます。また、周波数固定なので後ろのフィルターは固定でよいなど利点が多い方式です。
GPIOを使ってソフトPWMをやればいい。時間的にはこれは間に合いそうもありません。ソフトも、ちょっと面倒っぽいのですが…
実際に検討して見なければわかりませんが、コストも安いので有力。ステレオは難しそう。 CCPの周波数が十分に取れるかが、問題になりそう。
安く上げるならば、ラダー回路。奢れば、DACのICを使用します(高そう)。
オーディオ用のシリアルDACが比較的安価に入手できるので使えそうです。うまく内蔵モジュールが使えれば、インタフェースは楽そうですが、そうでないときは…
ステレオになるし、ビット数も多いので、PICが負けそうです。
ざっと列挙しましたが、ここから、実現の可能性を探る必要があるわけですが、どうなることか…
とりあえず、PICで音を出すというと、誰でもがやるGPIOのオンオフで音を出す方法。よく工作の本に載っているけれど、音程が1バイト精度だと、とても変で、悲しいです。
音量の増減は出来ないので、あきらめる。といっても、昔、オフコンでバッハの演奏をしたときには、出力のデューティを変えて音量を表現していました。当然、倍音も変わるけど、まぁ、味で。というものでした。
ハードウェはどのPICでも大差ない。どうせ端子一つに抵抗をぶら下げてスピーカーを鳴らすだけ。当面は、この簡単なハードウェアで十分でしょう。クロックだけは精度を出すために最速の20MHz。絶対音程にこだわるならば、外部の水晶発信子で十分でしょう。
プログラムを組む前に、どのぐらいの精度が必要で、どのぐらいやれるのか、見積もってみたい。
まず音程の基準に使われるA3(時報のラ)は周波数が、440Hz。それから1セント、高い音、5セント高い音…を計算してみます。
A3(440Hz)はギターで言えば、1弦5フレットの音。バイオリンの2弦開放に相当します。ここで、1セント誤差(人間の感知限界?)ならば、周期差が1.5μS。何とか許せる5セントで、6.6μS。
バイオリンの1弦のハイポジションのAが、A5(1760Hz)。これを見てみましょう。
1セント誤差では0.3μS。5セントでは、1.6μSとなります。
20MHzのPICでソフトループを作るとして、PICの命令単位は、1サイクルが、0.2μS。高域では、精度的にかなり苦しいことがわかります。
管楽器などではさらに、もう1オクターブぐらいは欲しいところ。A6(3520Hz)ならば、次の通り。
ここまで来ると、20MHzのPICのソフトループでは1セント誤差は無理です。
ここまで見たように、周期、周波数差はオクターブ上がる毎に1/2となります。同じセント誤差を保つためには、高域では短い時間が要求され、低域では、それがゆるくなります。
上の議論から、音程を数字で表すためには、1オクターブの範囲の表があればいいことがわかります。1オクターブだけ十分な精度で計算できれば、音程を高くするには、オクターブ毎に1/2倍、低くするには、2倍すれば良いからです。
では、周期を(つまり、音程を)レジスタで1オクターブの範囲で記述するとき、どのぐらいのビット数があれば、いいのでしょうか。
実際には、音程の位置の置き方などで多少の誤差がありますが、アバウトに見てみましょう。
例えば、8ビットの周期レジスタがあるとして、これを平均律に設定したときの誤差を計算します。最高音では値を0x80であったとします。(オクターブ下では丁度倍(0x100)になるのでレジスタを使い切ります。)
同様にして、13ビット幅まで計算してみます。
計算では整数への丸めで四捨五入を行いました。単純に誤差1セントに入っているのは、11ビット精度の場合。しかし、音程の開始位置を都合に合わせて微調整してもうまく動くのは、最小ビットのセント幅が1セント以下になる、12ビット精度の場合です。さらに、半音の開始位置をずらしても動くようにするには、もう1ビットの余裕を取って、13ビット精度が欲しいところです。
ここで計算したレジスタに、オクターブごとにつまり、2倍単位で調整できる言わばプリスケーラを合わせれば、1セント単位での精度が確保できることになります。
なんだかんだ理屈を言っても、それだけでは、音が出ない。とりあえず、音が出ればいいじゃないか。
丁度、学研の「小学一年生」で、白鍵オクターブのサイバー手袋ピアノが話題になっているので、同じようなものをPICで試してみようと思います。「蛙の唄」が弾ければいいということで、蛙計画。まずは、チップの選定から。
できるだけピン数少なくしたい。外付けのICは使いたくない。スイッチ入力が8。PWMが1。というのが、最低条件。それから、サンプリング周波数を高くするため、クロックは外付け。これに電源とグランドで最低限13ピンです。ROMの容量が多ければ、複雑な波形を実現できるけれど、正弦波ベースならば、少しで十分。拡張性を考えるかどうかが、ちょっと、考えどころ。
785がオペアンプが入っているので、PWM出力の処理に使えそうだけれど、未入手。結局、お値段を考えると、また、627/648になりそう。とりあえず、容量の小さい627(1kW)で最初は作ってみて、足りなければ、また、考えることにします。
同時発音数1。減衰のEGを付けるつもりですが、とりあえず、矩形波ならば、乗算無しでも実現できそう。ピッチは正確に出したい。
信号 | i/o | 用途 | 備考 |
---|---|---|---|
RA0 | o | NC | 未使用 |
RA1 | o | NC | 未使用 |
RA2 | o | NC | 未使用 |
RA3 | o | NC | 未使用 |
RA4 | i | キー入力。4.7kでプルアップ | ファ |
RA5 | i | MCLR_N | リセット端子。1kでプルアップ。 |
RA6 | i | xtal | セラロック(20MHz) |
RA7 | i | xtal | 同上 |
RB0 | i | キー入力 | ド |
RB1 | i | キー入力 | レ |
RB2 | i | キー入力 | ミ |
RB3 | o | CCP出力 | ドライブ回路へ |
RB4 | i | キー入力 | ソ |
RB5 | i | キー入力 | ラ |
RB6 | i | キー入力 | シ |
RB7 | i | キー入力 | ド |
左が暫定のピン割り当て。キー入力は、内蔵プルアップの使えるRBポートを使用します。残念ながらCCPがRB3にあるので、ひとつだけRAポートにキー入力を割り当てました。これは、外部プルアップが必要になります。MIDI入力するならば、使えないPBが増えるのですが、今回はないので、こんなもの。
発音はCCP出力を簡単なトランジスタ回路を通して、秋月のフィルムスピーカーwithトイレットペーパー芯をドライブします。経験上は、ローパスフィルタなど入れなくてもそこそこ鳴るはずです。
動作は単純で、TIMER2でCCPを駆動し、TIMER2の割り込みを定常的にかけ、ここで、割り込み周期に相当するだけ、出力波形の位相を進めます。同様に減衰だけのEGも最大値から割り込み周期分だけ減算。波形が矩形波なので、波形の正負に応じて、EGの値を出力するというものです。
地面のプログラムでは、キーを定期的にスキャンし、発音が必要な場合は、位相増分をセットし、トリガーフラグを立てるだけです。
右の画像がブレッドボードに組んだもの。テストクリップの先には秋月のフィルムスピーカーが繋がっています。
プログラムをPIC16F627Aに焼いて、試しました。まずは、最初の版(kaeru627.hex)です。出音は単調になるかと思ったのですが、リング変調がかかったような金属的な感じです。エンベロープは単調減少で約1秒。
サンプリング周波数がおよそ20kHzなので、フィルタをかけないと、干渉しているようです。オモチャとしては、これでいい気もします。
最初の版では、音域が中央C(60)からのオクターブですが、もうすこし、可愛くということで、1オクターブ上げた二番目の版(kaeru627_02.hex)を作りました。
サイクル波形(コルグで言うところのDC波形ですね)を再生するようにしました。まずは、基本でしょうということで、正弦波を組み込んであります。EGは二版までと同じ減衰のみのものです。
TIMER2の割り込みで位相計算、EG計算をし、位相から波形テーブルを引いて波形値を出し、EGの値と乗算してCCPにセットするようにしました。データのビット幅は8ビットで、PWM周期は156.3kHz。TIMER2のポストスケーラを1:4にしてデータの更新は19.63kHzで行っています。
割り込み周期がCPUサイクルで256サイクル。割り込み中の処理はおよそ100サイクルで終了するので、負荷としては4割ぐらいです。乗算が重いのですが、縦積みにするなどでサイクル数を若干稼いでいます。
プログラム容量は132wで、波形テーブルが256wです。EEPROMにキーに応じた周波数テーブルが32バイト。
実際に動作させると、きれいな可愛い音がします。音量は激減したので、ドライブ回路の抵抗を小さくして、さらに、スピーカーにはトイレットペーパーの芯のエンクロージャーを付けました。
現状の白鍵盤対応の回路図を掲載しました。。
キー入力は、グランドに落とすとオンです。RBはプルアップ必要なし、RA4だけプルアップしています CCPの出力は、トランジスタ(2SC1815)でフィルムスピーカーを鳴らします。乱暴な回路ですが一応音が出ます。
フィルムスピーカーは秋月で@100で販売されていたもので、8Ω負荷。小さなスピーカーなら何でもいいかもしれません。
回路図には描いてありませんが、外部から供給する5V電源の入り口には10〜47μF程度のケミカルコンデンサを入れてください。 また、PICの電源端子とグランド端子の間に、積層セラミックコンデンサ0.1μFを必ず付けてください。
音程の精度は、クリスタル(セラロック)の精度で決まります。この回路図ではセラロックを使っていますが、 高い精度を求めるなら、クリスタルを付けてください。この場合には、7〜20pF程度のセラミックコンデンサを付加する必要があります。容量はクリスタルによるのでご使用になるクリスタルのデータシートを調べて決めてください。
乱暴な回路ですが、一応音が出ます。
PIC16F627A用第一版のHEX | kaeru627.hex | 最初のもの |
PIC16F627A用第二版のHEX | kaeru627_01.hex | 第一版から発音を1オクターブ上げたもの |
PIC16F627A用第三版のHEX | kaeru627_02.hex | 正弦波で鳴るもの。 |
PIC16F627A用回路図(白鍵版) | kaeru_02.pdf | 白鍵版の回路図です。ここでのhexすべてに対応した同一の回路です。 |
軽い気持ちで黒鍵も追加しようとしたのですが、なんと、ピン数が足りませんでした。オクターブで12本ですが、一番上にドがあるので、13本の入力が必要になります。
外付け回路が複雑になるのが嫌で、20ピンのPICにしようかとも考えたのですが、お値段がちょっと上がるのが悔しい。結局、外付けの簡易マトリックス回路にしました。白鍵盤だけの場合には、回路を大幅に省略して、最初の版と同じ程度の配線で動きます。
20ピンのPICでの回路はシンプルになるのですが、プルアップ抵抗の数が増えるので、部品点数としては、簡易マトリックスといい勝負です。小信号用のダイオードは、纏め買いすると@1程度ですから、コスト的にもプルアップ抵抗を付けるのと変わりません。
キースキャンの方法が若干変わっただけで、白鍵版とほとんど変わりません。キーはおよそ、7mSぐらいに一度、白鍵スキャン、黒鍵スキャンを行い、連続して二度同じ値の場合に、キーを有効とみなします。
大きく変わったのは、お安いPIC16F716への移植を考慮して、EEPROMの使用をやめたこと。アセンブラを相対アドレスモード、つまり、リンカーを使うモードで記述していることです。
実は秋月で、PIC16F716を入手しました。なんと、@110というお手ごろなお値段。18ピンなので、ピン配置に関しては置き換えて使用できます。ADCの精度が一般的な16Fシリーズの10ビットと異なり8ビット。CCPは一つですが、ドライブ用に4出力までできるタイプです。ROM2kW、RAM128bytesでEEPROMなし(627A用ソフトでにEEPROMを使ってしまったので、ちょっと残念)。お値段を考えると使わない手はありません。問題があるとすると書き込み環境で、秋月のライターではサポートされていません。マルツのライターでは書き込みできるようです。また、マイクロチップのライターも安価に手に入るようなので、あまり気にしなくてもいいかもしれません。
PIC16F627AからPIC16F716への移植は、主要部分は、ほとんど、変更不要。問題になったのは、PIC16F716のRAMが少ないこと、PIC16F627Aでは起動時に周波数テーブルをBANK1のRAMへ複写していたのですが、PIC16F716のBANK1は容量が足りませんでした。仕方が無いので、起動時の読み込みをやめ、都度、プログラムメモリからテーブル読み込みをするように変更しました。また、初期化処理の一部も変更してあります(レジスタ名称の違いだけ)。
書き込みは、最近入手したICD2。
PIC16F716を使用することで、ちょっとだけ、お安くなりました。
信号 | i/o | 用途 | 備考 |
---|---|---|---|
RA0 | o | NC | 未使用 |
RA1 | o | NC | 未使用 |
RA2 | o | SCAN2 | 黒鍵スキャン出力 |
RA3 | o | SCAN1 | 白鍵スキャン出力 |
RA4 | i | キー入力。4.7kでプルアップ | ファ / ファ# |
RA5 | i | MCLR_N | リセット端子。1kでプルアップ。 |
RA6 | i | xtal | セラロック(20MHz) |
RA7 | i | xtal | 同上 |
RB0 | i | キー入力 | ド / ド# |
RB1 | i | キー入力 | レ / レ# |
RB2 | i | キー入力 | ミ |
RB3 | o | CCP出力 | ドライブ回路へ |
RB4 | i | キー入力 | ソ / ソ# |
RB5 | i | キー入力 | ラ / ラ# |
RB6 | i | キー入力 | シ |
RB7 | i | キー入力 | ド |
PICのピン割り当ては左表の通りです。白鍵版との相違は、RA3:白鍵スキャン出力 と、RA2:黒鍵スキャン出力 を使用していることです。
掲載の回路図の通り、キー入力回路は、単純なマトリックス回路になっています。
キーをスキャンするときは、白鍵スキャン信号(RA3)、黒鍵スキャン信号(RA2)のどちらかをLOWで出力します。これを入力ポートで読んでLOWの場合にキーがメークしていると判断します。
ダイオードは、同じポートの二つのキーがオンになったときに、スキャン信号がショートしないように分離する役割をしています。
回路図には書いていませんが、5V電源入力の入り口側に10〜47μFの電解コンデンサを付けてください。また、PICの電源とグランドの間にできるだけ近接して、0.1μFの積層セラミックコンデンサを付けてください。
白鍵のみのハードウェアを作るときは、以下の二点だけが図面と異なります。
ということで、白鍵のみの場合は、回路が非常に単純になります。
秋月のC基板二枚で組み立てたのが右の画像。回路が簡単なので、一枚の基板にも載るのですが、鍵盤部分だけ独立させて、別の用途でも使えるようにしました。
二枚の基板は縦積みになっています。上の基板が鍵盤基板で、写真では見えない下の基板が本体基板。二枚の基板は、二列のピンコネクタで連結しています。四隅をプラスティックポストで止めると、高さがぴったりでした。
下に見えるのがフィルムスピーカーとトイレットペーパー芯で組み立てたスピーカーです。このスピーカー、ケーブルが取れやすいので簡単に修理できるようにエンクロージャ(トレペ芯)は前面に取り付けてあります。
電源は5VのスイッチングACアダプタ。
これが、鍵盤部分のクローズアップ。基板の上の方の、半田面にオスのコネクタを付けて、二階建てになるようにしてあります。片面基板なので、半田面にコネクタを付けるのに苦労しました。結局、コネクタを接着。配線に単線を使って固定してあります。
鍵盤に相当するスイッチは、秋月で100個700円で売っているタクトスイッチ。各色取り混ぜて入っているのですが、普通に、白と黒を使いました。ちょっと、硬いし、殺風景。もっと、いいスイッチを使えば、格好いいのだけれど。
最後の画像が本体回路。部品点数が少ないので、スカスカです。
上方にある2列のピンソケットに鍵盤基板を挿します。
コネクタの下に並んでいるのがキーマトリック用のダイオード。左側に直付けで5V電源入力。電源の入り口には一応、電解コンデンサが寝転んでいます(最初、縦に置いて鍵盤基板とぶつかってしまいました)。
PICの左にあるのが、パスコン。上にあるのが、セラロック(発振子)。基板の右下にスピーカードライブ用のトランジスタ(2SC1815)。
PIC16F627A用第一版のHEX | kaeru627_03.hex | 最初のもの |
PIC16F627A用第二版のHEX | kaeru627_04.hex | 音量補正版。暫定 |
PIC16F716用第一版のHEX | kaeru716_03.hex | 最初のもの |
PIC16F716用第二版のHEX | kaeru716_04.hex | 音量補正版。暫定 |
PIC16F627A用回路図(黒鍵版) | kaeru_03.pdf | 黒鍵版の回路図です。PIC16F716もこの回路で動作します。 |
PIC16F627A用回路図(黒鍵版) 白鍵盤のみ使用 | kaeru_03white.pdf | 白鍵のみを実装する際の回路図です。同じソフトを用いながら、部品点数が少ないものです。PIC16F716もこの回路で動作します。 |
白鍵版を8ピンのPICに移植をします。CPUはCCPが載っている手持ちのもので、PIC12F683か、PIC12F615を使う予定。615は@100と安価なのが魅力。683はプログラム容量が倍の2kWで、フラットパッケージ版も持っているから、これもいいですね。
外付けのハードウェアを最小にとどめて実現したい。このためにはキーの入力回路がネックです。 これをどのように解決するか、試行錯誤します。
基本的なアイディアは、AD入力の電圧で、キーを判別すること。検討対象は次の通り。
抵抗を直列につなぎ、抵抗と抵抗の間にスイッチを介してADに繋ぐものです。一番簡単なのですが、二つ以上の接点がメークしたとき、値が不正になります。
ダイオードを順方向につなぎ、ダイオードとダイオードの間をスイッチを介してADに繋ぐものです。ダイオードの順方向の降下電圧がほぼ一定なことを利用します。二つ以上の接点がメークした場合は、電圧の低い方の接点が優先されます。難点は、電流量によってダイオードの降下電圧が実際には変わること。
FETを使った定電流回路に抵抗を直列に繋いで、抵抗と抵抗の間にスイッチを介してADに繋ぐものです。この方法も、二つ以上の接点がメークしても正しく動くはず。定電流回路で流す電流がFETのばらつきで大きく変わるのが難点。調整用の抵抗などが必要になりそうです。
まずは、簡単そうなダイオードの回路を検討します。ダイオードを直列につなぎ、4.7kΩの抵抗で電源に接続しました。使用したダイオードは、秋月でまとめ売りしている1N4148です。
測定結果は次の通り
GNDからの ダイオード数 | 電圧A | 差分 | 電圧B | ADval_A | ADval_B |
---|---|---|---|---|---|
1 | 0.532 | 0.532 | 0.612 | 26 | 30 |
2 | 1.065 | 0.533 | 1.210 | 52 | 60 |
3 | 1.601 | 0.536 | 1.795 | 79 | 89 |
4 | 2.132 | 0.531 | 2.356 | 105 | 117 |
5 | 2.666 | 0.534 | 2.893 | 132 | 143 |
6 | 3.201 | 0.535 | 3.400 | 158 | 168 |
7 | 3.734 | 0.533 | 3.862 | 185 | 191 |
8 | 4.260 | 0.526 | 4.260 | 211 | 211 |
表の電圧Aは、単純に測定したもの。電圧Bは、一番電位の高い端子(8)と測定端子をショートして測定したもので、このときが一番誤差が出るはずのものです。
差分は、個々のダイオードのVFに相当するもの。
電圧5.14Vから、PICの内部の値0-255に変換するとわかりやすいので、表に含めたのが、ADval_A(電圧A)、とADval_B(電圧B)です。
大まかに言えば、検出すべき電圧は、ADval_AとADval_Bの間にあるので、この表の値を見る限り、なんとか、判別できそうです。とりあえず、有望。
実際には、PICの入力インピーダンスの関係などで、抵抗値(ここでは、4.7kΩ)をもっと小さくする必要がありそうでが、これは、PICに繋いでから検討することにします。電源電圧の影響も大きいから、この辺もネックかもしれません。
信号 | i/o | 用途 | 備考 |
---|---|---|---|
GP0 | o | AN0 | キー入力回路へ |
GP1 | o | NC | 未使用 |
GP2 | o | CCP | スピーカードライブ回路へ |
GP3 | i | MCLR_N | リセット |
GP4 | i | xtal | セラロック20MHz |
GP5 | i | xtal | 同上 |
使用するCPUは8ピンのPIC12F615。ピン割り当ては左の通りです。
キー入力は、上で検討したダイオードスタック方式を使用しました。ダイオードの順方向降下電圧(VF)がほぼ一定であることを利用してキーに応じた電圧をPICのADCで読み取って、キーを決めています。ダイオードの特性に依存するので、ダイオードは、1N4148を使用してください。
抵抗の分圧を利用する方法と異なり、キーを二つ押した場合は、低音優先となる回路です。全部のキーがオフの場合には、電源から吊っている1MΩからの電源電圧となります。
ダイオードのVFが、実際には一定でないので、うまく、範囲を設定できるか心配でした。しかし、実際に作ってみたら、動作したので、良しとします。
8ピンPICでも動作するというのがミソですが、同じ白鍵だけなら、PIC16F716/PIC16F627A版の方が配線も簡単ですね。
回路図には書いていませんが、5V電源入力の入り口側に10〜47μFの電解コンデンサを付けてください。また、PICの電源とグランドの間にできるだけ近接して、0.1μFの積層セラミックコンデンサを付けてください。
16F627A/16F716版から、単純に移植しました。大きく変わったのはキー処理で、ハードウェアで優先処理がされるために、むしろ単純になっています。内容は、ADCから読み取った電圧を順次低い方から検査し、キー番号を決めるもので、該当しない大きな電圧はキーがオフだと判断しています。
キーのチャッタリング対策を施してありますが、スイッチによっては、不十分かもしれません。また、電源投入時、一度音が出ますが、これは、ご愛嬌ということで…
PIC12F615用第一版のHEX | kaeru615_01.hex | 最初のもの |
PIC12F615用第二版のHEX | kaeru615_02.hex | 音量補正しました |
回路図 | kaeru615_01.pdf | ダイオードスタックを使ったキー判別をしています。 |
作ったものは流用しないとつまらない。ということで、8ピン蛙を改造して単音のオルゴールを作ってみます。容量の制限があるのが、難点ですが、とりあえず、PIC12F615版です。
音色の方も少しは、懲りたいので、ADSRのエンベロープ処理を仕込み、波形も倍音の多いものに変更します。音色を複数入れられれば良いのですが、曲の領域を確保するだけでも手一杯。これは、いずれ、CPUを変更して考えることにします。
信号 | i/o | 用途 | 備考 |
---|---|---|---|
GP0 | o | SW1 | START/STOP タクトスイッチ |
GP1 | o | LED | モニターLED |
GP2 | o | CCP | スピーカードライブ回路へ |
GP3 | i | SW2 | リピート切り替えスライドスイッチ |
GP4 | i | xtal | セラロック20MHz |
GP5 | i | xtal | 同上 |
基本的には、CCP出力があればいいのですが、一応、簡単な操作機能を付け加えます。SW1は、START/STOPは曲の演奏開始と終了。ここにはタクトスイッチを使います。SW2は、スライドスイッチなど、オン/オフのどちらかに固定されるスイッチを使い、繰り返し演奏を指定します。GP1が余っているので、モニターLEDとし、発音(ゲートオン)中にLEDが点灯するようにします。
SW1は、内部のプルアップを行い、グランドとショートするとメークするようにします。SW2は、4.7kオームで外側でプルアップ。グランド(ショート)でリピートあり、5V(オープン)でリピート無しとします。
GP1のLEDは、220Ω程度の抵抗に直列にLEDをつなぎ、グランドに落とします。GP1の端子がHIになると点灯する仕様です。
回路図には描いていませんが、電源の入り口に10〜47μFの電解コンデンサ、PICの電源とグランドに近接して0.1μFの積層セラミックコンデンサを付けてください。
新たに、ADSRのエンベロープ機能を付けました。とりあえず、エンベロープカーブは、直線です。あと、乗算一回分を処理に加えれば減衰曲線ができるのですが、当座は直線でもよいかと思います。
CPUの負荷に影響があるのが、この辺の処理。現在割り込みは、256命令に一回発生し、割り込み内の処理では最長153命令かかっています。現在の方法だと、このぐらいが処理の限界かもしれません。
もう一つの変更点は、周波数計算の方法。オルゴールとなると音域が広くなるので、単にテーブルに格納すると、PIC12F165では、とても、入りきれません。そこで、一番高いほうのオクターブだけをテーブルに持ち、オクターブ下がる毎に、1ビット右シフトしています。
内蔵曲を演奏するのに簡単なシーケンサーを持っています。ステップ時間を一定(6.55mS)にして、これを単位としてティックカウントをデータに含めます。具体的な形式は、一つのイベントを2バイト一組で次のように表しています。
ticktimeは、前のイベントからのティックカウント数。codeはイベントを表します。
CODE | 内容 |
---|---|
0x00-0x7f | ノートオン。値はMIDIのノート番号 |
0x80 | ノートオフ |
0xf8 | ダミーイベント |
0xff | シーケンス終了コード |
ノートオフは、一つのコードで表しているので、音程のスタックができず、発音の優先順位は、後着優先。ダミーイベントは、ティックカウントがオーバーフローしたときにダミーで出力するためのものです。シーケンスの最後には必ず0xffのシーケンス終了コードを入れます。
楽曲の制作はMIDIシーケンサーで行い、format0のスタンダード・MIDIファイルから、手持ちのユーティリティで変換後、さらに、スクリプトを使ってこの形式に変換します。容量的に100ステップぐらいしか余裕が無いので、手で変換しても、大したことはありません。
メロディ単音なので、曲は、チャンネル1にだけ、書いておきます。
サンプル曲はフォスターの「草競馬」です。
音色は持続音系のもので、かまぼこ型のエンベロープ、倍音はやや多めにしました。
PIC12F615と同じ内容のPIC12F683版を作りました。初期化が一部違うだけでほとんど同じ内容。曲データの拡張に備えてWAVEデータをメモリの大きい方に飛ばしてあります。手を抜いて、曲データと音色は変えてありません。
さすがにプログラムメモリが2kWあるので、曲の大きさは500ステップ(1kW)程度まで書けます。
アセンブルでの曲の組み換えを取りやめました。後出のケロゴールUと同様に、スクリプトで曲を組み込んだへHEXファイルを作成するようにする予定です。
PIC12F615用第一版のHEX | kaeru615_10.hex | 単音オルゴール。(草競売) |
PIC12F683用第一版のHEX | kaeru683_10.hex | 単音オルゴール。(草競売) |
回路図 | kaeru615_10.pdf | PIC12F615とPIC12F683で兼用のハードです。図面中の615を683に読み替えてください。 |
ケロゴールTは、エンベロープこそかかっていますが、常に一定の音量で鳴るもの。せめて、ヴェロシティの変化で音量を変化させるぐらいのことはしたいものです。乗算が一度入るのでCPU負荷が大きくなってしまいますが、ギリギリ、何とか対応できそうです。使用するPICは12F683。615では残念ながら楽曲データが少なくなりすぎます。
ハードウェアはケロゴールTのものをそのまま、使用します。
ソフトウェアでの主要な変更は、割り込み中にEGの値に音量を乗算すること。この変更自体は小さなものですが、これに伴って、シーケースデータとその取り扱いが変わりました。
シーケンスデータの形式は次のように3バイト組になりました。
ticktimeは、前のイベントからのティックカウント数。codeはイベント、velocityは発音時の音量を表します。
CODE | 内容 |
---|---|
0x00-0x7f | ノートオン。値はMIDIのノート番号 |
0x80 | ノートオフ |
0xf8 | ダミーイベント |
0xff | シーケンス終了コード |
ノートオフは、一つのコードで表しているので、音程のスタックができず、発音の優先順位は、後着優先。ダミーイベントは、ティックカウントがオーバーフローしたときにダミーで出力するためのものです。シーケンスの最後には必ず0xffのシーケンス終了コードを入れます。
ベロシティはノートオンの場合にのみ有効です。メロディ単音なので、曲は、チャンネル1にだけ、書いておきます。
サンプル曲はスコット・ジョップリンの「エンタテナー」。結局、入りきらなくて、イントロ、Aメロ、Bメロだけ入っています。音色は減衰音で、サイン波にしました。
とりあえず、曲の入れ替えをするスクリプト(ts.exeで実行)を作りました。「お楽しみプログラム」にあるユーティリティを使用します。
指定したformat0のMIDIファイルから内部形式に変換し、kaeru683_20b.hexをパッチして、曲に組み込み、kero.hexを出力します。このkero.hexをライターでPIC12F683に焼きこみます。
曲に割り当てられている領域は、ワードアドレス(プログラム上でのアドレス)では、0x280から0x6cfまでの1104ワード。一つのイベントで3つ取るので、368イベント。ノートオン/ノートオフで1音当たり2イベントなので、およそ180音が容量となります。
_env midifile "!=%(execq('selfile -h変換元のファイルを開く -fmidifile(format0);*.mid;'));" _if ISENV midifile goto :cont1 _end :cont1 ; ; 曲を読み込んでバイナリデータ化する smf2q1 !@midifile|q12p1 -t6.5536|bin2tf -f[4]b; +|tfcalc +" #1; +case(#2=144:if(#4=0,128,#3), + #2=255:255, + true:248 + ); +#4*2;lineno; +"|repargtf "-c %1; $34 %2; $34 %3; $34" | +asc2bin -d -otune.bin ; ; サイズの検査 ; _if (filesize("tune.bin") > 2208) goto :szerror _end ; ; ファイルによるパッチを行う ; hex2bin kaeru683_20b.hex -ocode.bin binpatch code.bin -p$500:$ff[$8a0] -f$500:tune.bin -n bin2hex code.bin -pp -okero.hex mb "complete" -n _end ; :szerror mb "データのサイズが大きい" -n _end
PIC12F683用第一版のHEX | kaeru683_20b.hex | 単音オルゴール。(エンタテナー) |
回路図 | kaeru615_10.pdf | ケロゴールTと同じものです。図面中の615を683に読み替えてください。 |
回路の簡略化のためにスピーカーを直接ドライブしていましたが、ライン出力ぐらいのクオリティは確保したい。出力回路の改善を試みます。出力にローパスフィルターを付けて、もし、スピーカーをドライブするならば、オーディオアンプを付加するぐらいで、かなり、音質が改善されるはずですが…。
PSGを使うと、3音ポリの出力を得られますから、これをプログラムの目標に。出力波形は当然、矩形波で、エンベロープ無し。出力方法としては、
チャンネル分だけ、加算してから、PWMで出力。矩形波なので、各チャンネル最低2ビットあればよいことになります。
IOポートにチャンネルごと別に出力。外部でそれぞれをアナログ回路でミックス。少しだけソフトの負担が軽くなるはず。
の二つが考えられます。とりあえずは、PWM案で。
この音源は、外部からMIDIで鳴らすことにするつもりです。内部の処理を簡略化するため、モノラル音源を発音数分のチャンネルに割り振ります。処理も速度優先のため、後着優先で行います。
音程は平均律でとり、実用的な精度を確保し、ピッチベンドに対応の予定。精度を取るとテーブルサイズが足りなくなるので、1/16半音か、1/32半音程度の精度で考えています。
MIDIメッセージの処理は、割り込み内で行わず、地面で処理。割り込みは、発音に専念させるためです。このため、多少、MIDIの応答が悪くなるはず。
PWM方式では、矩形波なのだから、音量は、単に、天井と床の値を、現在の音量値なり、ヴェロシティなりにしてやれば十分。この場合は、ヴェロシティあるいは、ヴォリュームコントロールに対応できます。
CPUの条件は、USART/CCPの装備と、プログラムサイズが大きいこと。プログラムサイズの見積もりは、結局のところピッチベンドの精度によりますが、たぶん、4kWあれば、1/32半音か1/64半音の処理は実装できそうです。
とりあえず、MIDI入力して4音以上発音するものを作って、周辺機能は後から考えます。ということで、当座の暫定CPUは、いつもどおり、PIC16F648A。
一番容量を取りそうな周波数テーブル(位相増分テーブル)を検討します。
一つの方法は、一つのテーブルでまかなう方法。テーブル自体1オクターブ分を持つとして、1/64半音単位で持つと、テーブルのエントリー数は、64x12=768エントリー。とりあえず、1エントリー当たり4バイトすれば、3072バイト(ステップ)。かなり、容量を圧迫しますが、なんとか、入りそう(駄目なら、1/32半音単位に方向転換。これなら半分の1536)。
もう一つは、二つのテーブルを組み合わせる方法。CPUが十分に速ければ、半音単位の12エントリーのテーブルと、半音内の分割テーブル、例えば、64エントリーを持って、音程を決めるときに乗算すれば良いので、大幅にテーブルサイズが小さくなるし、半音内の分割を上げる余裕もあります。
通常の発音と、ピッチベンドに対応するだけなら、この二つのテーブルを用いて、多少の演算時間に目をつぶっても実用範囲にはいる可能性があります。
LFOやEGを内蔵すると、二番目の方法では、割り込み内で、都度、周波数計算をする必要があるので、20MHz程度のPICでは苦しそうです。
とりあえず、巨大テーブル方式を取るものとして、テーブルの計算を行います。
CCPは10ビットで、19.53kHzで動作させます。テーブルは、一番周波数の高いオクターブ、C8(120)からの12音に対して作成します。周波数が高いものを選ぶのは、周波数が高いほど位相増分が大きいためです。
1/64半音の精度の表なので、3kw強。これ、PICのバンク単位(2kw)を超えているのでちょっと使いづらいです。やはり小さくした方がいいのかも。
下のボタンを押すと、アセンブラのソースを出力します。ディレクティブはpitchとなっているので適宜マクロを定義してください。
PICのマクロを書いておきます。
pitch macro val dt low (val&0xffff),high (val&0xffff),upper (val),upper (val>>8) endm
信号 | i/o | 用途 | 備考 |
---|---|---|---|
RA0 | o | NC | 未使用 |
RA1 | o | NC | 未使用 |
RA2 | o | NC | 未使用 |
RA3 | o | NC | 未使用 |
RA4 | o | NC | 未使用 |
RA5 | i | MCLR_N | リセット端子。1kでプルアップ。 |
RA6 | i | xtal | 水晶(20MHz) |
RA7 | i | xtal | 同上 |
RB0 | i | SW | チャンネルスイッチ(8音版のみ) |
RB1 | i | RX | MIDI入力 |
RB2 | o | TX | MIDI出力(未使用) |
RB3 | o | CCP出力 | 音声出力 |
RB4 | o | NC | 未使用 |
RB5 | o | NC | 未使用 |
RB6 | o | NC | 未使用 |
RB7 | o | NC | 未使用 |
当面、最低限必要な機能から、左の割り当て表をまとめました。要するに、MIDI入力と、音声出力。8音版のチャンネルセレクト(1-8チャンネル/9-16チャンネル切り替え)を追加してあります。一応、絶対音程の精度を求めて、発信子はセラロックではなく、20MHzの水晶を載せることにしました。
PWM出力のフィルターは、パッシブのLPFを三段。何回か試行錯誤して、定数を変えました。初めは抵抗が大きすぎて出力が小さくなりすぎたので、これを小さくし、ついでにコンデンサは、手持ちが沢山ある0.1μFの積層セラミック(音質無視?)にしてあります。
右の画像が組み立てた基板。後述の、ver.1.02対応のものです。電源は電池の006Pにして、三端子レギュレータXC6202P502TBを使用しています。これは、秋月で@50のロードロップの5Vのもの。便宜のために基板上に電源スイッチと、外部から5Vを供給するためのジャンパーも設けてあります。
基板の右側にPIC16F648A。中央やや左下がフォトカプラPC910。PICの下にごちゃごちゃとあるのが出力回路。積層セラミックコンデンサを使ったので回路が実装面積が小さくなりました。
とりあえず、4音版のプログラムを書き、ブレッドボード上に組んで、PCのMIDIシーケンサで演奏してみました。
4チャンネルモノで、演奏できたし、ピッチベンドレンジの設定もうまく動き、ピッチベンドも思った以上に滑らかに動きました。しかし、ここで、大問題。低音は良いのですが、高音域で音が濁ります。
C(60)からのオクターブぐらいまでは問題ないのですが、C(72)からのオクターブぐらいから、音が濁ったり、震えたりします。C(72)はおよそ、520Hzぐらい。内部のサンプリング周波数は19kHzぐらいなので、9kHzぐらいが上限になるはずなのですが…
結局、9kHzの上限というのは、サイン波でのこと。矩形波は奇数次の倍音を多く含むので、これがサンプリング周波数と干渉するようです。聴いた感じも、折り返しのノイズにとてもよく似ています。
もし録音再生を19kHzで行うならば、録音時に9kHz以上を強烈にカットしなければいけないけれど、内部で矩形波を発生させると、このフィルター処理が無い分けです。これは出力時にLPFをかけても、取れない歪み。結局、矩形波を発生させるときにLPFをかければいい理屈だけど、これだと、プログラムが重くなって、シンプルさが失われます(発音数も減ってしまう)。
本質的な変更をするならば、分周方式にすればいいはず。固定周波数の影響を排除して動作可能なはず。もし、本格的なDCOなどを作るならば、こちらのほうが良さそうです。回路も重くなりそうだし、今後の研究課題です。
とりあえず、方向転換して、再生音をサイン波(など倍音の少ない音)に変更。矩形波の場合は、ベロシティの計算をチョンボして乗算を避けることができたのだけれど、サイン波など任意波形の場合は、ベロシティは、サンプル毎に乗算をするしかないので(もちろんメモリーさえあればテーブルで代用も可だけれど)、発音数を確保するためには、ベロシティは割愛するしかないようです。
実際にコーディングして実行すると、サイン波では折り返しノイズを避けることができました。サイン波では味気ないので多少倍音を付加した音にするとA6(105)から折り返しノイズが出てしまいます。実用的には、この音域でも十分かもしれません。
資料には、サイン波版と、倍音付加版の双方を掲載しました。
デフォルトのベンドレンジは2.ベンドレンジの設定はRPNで行う。設定可能は範囲は0-12。
ベンドの精度は、1/64半音。
発音数の限界を求めて、処理を簡略化しました。結局、8音同時発音が可能になりました。
処理の変わったところを列挙しておきます。
まず、障害になったのは、発音のための構造体の配列が一つのバンクに入らなくなったこと。結局生き別れにして、各構造体へのポインタ配列で対応するという、迂遠な方法で逃げました。
速度を稼ぐために、PWMのビット数を8ビットにしました。この結果、2バイトで行っていたオーディオデータの処理が1バイトになり、割り込み内の計算が軽くなりました。
これも速度を上げるがための、苦肉の策。音質は低下したはずですが、もともとの音質も良くないのでこれで良しとしました。
電源投入時に、1-8chと9-16chをRB0ポートを見て切り替えるようにしました。PICを二個用意すれば、モノx16チャンネルの音源になります。
このバージョンの仕様は以下のとおり。
ポートのRB0で切り替えます。
RB0 オープン:1-8ch
RB0 グランド:9-16ch
デフォルトのベンドレンジは2.ベンドレンジの設定はRPNで行う。設定可能は範囲は0-12。 ベンドの精度は、1/64半音。
演奏サンプルです。いずれも、ベタ打ちなので表現は粗雑ですが、音源の雰囲気はわかると思います。
クリックが入るところなど、オルガンのようです。久々に録音したので、レベルのセットなど難があり、ちょっと、ノイジーになってしまいました。ラインの出力レベルが低すぎますね。
スコット・ジョップリンの代表作です。8音をフルに使っています。
クラシック・ギターのナポレオン・コスタの曲をそのまま打ち込みました。5音を使っています。途中ちょっとこけているのはミスです。とりあえずサンプルということで。
回路図 | poly648_00.pdf | 暫定の回路。ver.1.01に対応。 |
4音ポリ ver.1.01 | poly648_01a.hex | 正弦波版 |
4音ポリ ver.1.01 | poly648_01b.hex | 倍音付加版 |
回路図 | poly648_02b.pdf | ver.1.02に対応。出力回路の定数を変更しました。 |
回路図(旧) | poly648_02.pdf | ver.1.02に対応。旧版です。 |
8音ポリ ver.1.02 | poly648_02.hex | 正弦波版です。 |
当面の問題は、出力が小さすぎること。ライン出力というには申し訳ないレベルです。サンプルの録音では、レコーダー側のプリアンプに頑張ってもらいすぎました。
暫定的に、現在掲載している回路図(
ここで、ポリ蛙は一段落したわけですが、同じハードウェアでもう少しいろいろ遊べそうです。
倍音が多いと起きる問題を回避するために、サンプリング周波数を高くする。当然、発音数は減ってくるのですが…
ベロシティ対応、ポルタメント、LFOなどを組み込むと面白そうです。サンプリング周波数を高く出来れば、倍音の多い波形でもなんとか聞けそうですし、もし、処理的にきつければ、モノ・ベース専用というのも面白いかもしれません。これは、十分実用的に使えそうです。
実際に使用してみると、キーボードなどで演奏するには、やはり、チャンネル一つで、複数音が出たほうが使いやすいです。メモリの空きを何とか都合できれば、何とかなりそうな気がしています。
MIDI入力を外して、オルゴールが出来そう。楽曲を格納するために、CPUを大きくする必要があるかも。
この間、固定サンプリング方式で作ってきたけれど、分周式も試してみたい。
どの程度のハードウェアが要求されるのか、検討しておきたいと思います。
オーディオ信号をサンプリングし、PWMで出力してみたい。簡単なエフェクターが作れるかも?と実験をするつもり。
まずは、入力したものをそのまま出力する単純なものから始めます。