初期構想

以前、PIC16F1705でAF生成器を製作してみました。
このプログラムをそのまま利用して、音楽演奏もさせてみたいな・・・
と、思ったのが始まりです。
現状のプログラムでは単音の擬似サイン波しか生成できません。
それでも、これで音周波数の近似値を生成してみます。
それを使用して、簡単なMMLで演奏させてみようかな、
と言った軽い感じでプログラム構想が始まりましたが・・・
そこでいきなり大きな問題点が発覚
何と!、当局は楽譜が読めない、ただの素人だった (-_-;)

楽譜については「Piano Fan」と言うサイトを参考に勉強させて頂きました。
MMLについては、シャープ X1BASICのMMLを参考にしました。



ハードウエア

これが申し訳ない程何もありません・・・
PICキットのCPUモジュールにオーディオアンプを接続しただけです。
PICは16F1705で8MHZのX'TAL発振で、4xPLLで32MHZクロックとします。
内部8bitDACによる音楽出力、内部オペアンプをバッファとして使用。
何かさみしいので、5V駆動の低周波モノラルアンプを作ってみました。
音源と音符、MMLについて概要

ここではPIC単体での演奏実験ですので、ピアノ等の楽器の音は当然再現できません Hi
擬似サイン波のみで、それも1チャンネルだけです。
生成周波数制御(DAC制御)はAF生成器のプログラムをそのまま流用する事にしました。
オクターブは10段階としましたが、実際はそんなに必要無かったです。
よって、全部で120個程の周波数テーブルを用意(ソースファイル参照)
生成周波数は高くなる程、絶対音との周波数ズレが大きくなります。

各音符における音の長さは演奏前に演算し、専用変数に収納しておく事にしました。
これにより、演奏中における時間のかかる演算処理を回避しています。
音符は32音符~全音符までの10種類までで、音量の制御はできません。
速さ、TEMPOは16bitで指定しますが、どの位の速さまで追従できるか実験していません・・・
TEMPO=60で4分音符の全長が約1秒となります。
実際の発音時間は全長時間より一定の割合で短くしました。
音符における音長の3/4を発音、後半1/4を消音としています。
今の所、この発音・消音比率の変更はできず、固定値となっています。
ただ、消音をゼロにする事はできます。
このTEMPOから10種類の音符の時間を生成します。
時間はミリセカンドですが、割り切れないので、正確ではないです。
これに関するルーチン群が今回のメインプログラム的な感じです。

MMLコマンドの種類
C,D,E,F,G,A,B、#、R,O,T,0~9、を認識(すべて半角大文字)
♭(フラット)コマンドはありません。
C~B、#は1オクターブ内の音程で、例えばCやC#と続きます。
さらにそれに続く数値、C5やC#5等で、音符を表現(120種)
オクターブはOとそれに続く数値で表現(10段階)
休符はRとそれに続く数値で表現(10種)
発音/音長比率設定は、3/4(T4)と4/4(T0)の2種のみです。
かなり制限がありますが、発音も単純だし、こんなもんでしょうか
プログラマブル メロディICと言った所と思います・・・

プログラム概要

(1)DAC制御ルーチン
  AF生成器のルーチンを変更なくそのまま使用しています。
  割り込みルーチンに存在し、TIMER0によって駆動されます。
  詳細は「DAC制御ルーチン解説」を参照して下さい

(2)生成波形テーブル
  これもAF生成器のデータをそのまま使用しています。

(3)音長設定ルーチン(「ONPBASE」サブルーチン)
  事前に変数「TEMPOx」を設定し呼び出す事で演算します。
  設定できる音長は32分音符用~全音符用の10種類です。(付点は1つのみ対応)
  各音符の音長時間の演算結果は、下記の専用変数に保存
  さらに各音長時間を発音時間と消音時間とに分割
  それらもすべて専用変数に保存(ソースコード参照)
  演算部も含め、だらだらと長いルーチン集団になってしまいました。

 1)音長
                倍率   全音長変数 16bit(ms)     発音変数     消音変数
  出力 32分音符    OX=1  ONP0L、ONP0H       GON0L/GON0H  GOF0L/GOF0H
      16分音符    OX=2  ONP1L、ONP1H       GON1L/GON1H  GOF1L/GOF1H
      付点16分音符 OX=3  ONP2L、ONP2H       GON2L/GON2H  GOF2L/GOF2H
      8分音符     OX=4  ONP3L、ONP3H       GON3L/GON3H  GOF3L/GOF3H
      付点8分音符  OX=6  ONP4L、ONP4H       GON4L/GON4H  GOF4L/GOF4H
      4分音符     OX=8  ONP5L、ONP5H       GON5L/GON5H  GOF5L/GOF5H
      付点4分音符  OX=12  ONP6L、ONP6H       GON6L/GON6H  GOF6L/GOF6H
      2分音符     OX=16  ONP7L、ONP7H       GON7L/GON7H  GOF7L/GOF7H
      付点2分音符  OX=24  ONP8L、ONP8H       GON8L/GON8H  GOF8L/GOF8H
      全音符      OX=32  ONP9L、ONP9H       GON9L/GON9H  GOF9L/GOF9H
 2)計算式
   ONPxx(ms) = (7500 * OX) ÷ TEMPOx
   GONxx(ms) = ONPxx * 3 ÷ 4
   GOFxx(ms) = ONPxx ÷ 4
   ※ 式に各種処理時間の補正分は含まれません


(4)演奏ルーチン(「MUSIC」サブルーチン)
  擬似命令(HIGH、LOW)でMMLが記載されているアドレス(ラベル)を指定して呼び出します。
  MMLのエンドコード「00H」を取得すると終了します。
  ここは意外と簡単な構成(手抜きとも言う)となっています。

 1)プログラムの流れ
  ①音符等に対応するMMLコードブロックを取得
    1つのMMLコードブロックは下記の3バイトで構成されます
     a)1次コマンド(MLC0)=C,D,E,F,G,A,B,R,O,T
     b)2次コマンド(MLC1)=NULL、#
     c)3次コマンド(MLC2)=0、1、2、3、4、5、6、7、8、9
  ②音周波数表から対応する生成周波数データを取得
  ③DAC書き込みデータテーブル番号、TMR0リスタート値、プリスケーラ値の展開、専用変数に収納
  ④TEMPOx値から展開された発音時間を設定、ミュートOFF、可変ウエイトルーチン実行
  ⑤同様に、消音時間値(ミリセカンド)を設定、ミュートON、可変ウエイトルーチン実行
  ⑥ ①に戻る

 2)MMLの配置場所
  データ以外の処理プログラムでメモリのBANK0の殆どを使用しています。
  それ故、DACデータや音周波数データはBANK1内固定アドレスに存在します。
  よって、MMLコードはそれらの後の領域の指定アドレス以降に記述する必要があります。
  直接アドレス指定では無く、ラベルによるアドレス取得となります。
  BANK0領域に長いMMLを置くとアッセンブルエラーが発生します。
  以上を踏まえれば、かなり広いMMLデータ領域を確保できます。

(5)ミュート処理
   簡単な処理だと思っていたら、意外とこれが曲者でした・・・
  これがうまくできないと、音楽になりませんしね
  最初はFVRのON/OFFでの制御にしてみたら、大きくボツボツ雑音パルスが入り、まさに没
  次にオペアンプのON/OFF制御、そしてDAC制御でも、同じく雑音が入り、没
  色々レジスタを弄ってみたが、DAC関連レジスタでの制御はどうやら上手くない事が判明
  単純に割り込み禁止/許可によるミュート制御が最も雑音発生が少なかったです。
  とは言え、良く聞いていると、常に雑音が入るのでは無く、入る場所が決まっている感じです。
   今度は視点を変え、ミュートON/OFF信号をPICから出力、アンプ側でミュートを掛けてみる事に。
  で、ミュート付ミニアンプを即効で製作、実験してみました。
  回路はLM386を使用した、入力バイアスに電圧を加えてミュートするものです。
  すると、PIC出力のボツボツ雑音は削除できるみたいですが、こんどは別の問題が発生、
  少々入るミュートON/OFF時のノイズが1音符ごとに入るので意外とうるさいのです。
  無線機でのスケルチ回路等のミュート制御ならまったく問題無いのですが、ここではまるでだめでした・・・
  今度はアンプのAF入力ラインにダイオードSW回路を挿入、スイッチングノイズが・・・
  これではシンプルなハードウエアを標榜している当局の方針から、どんどん離れていきそう
  う~ん、どうしよう・・・
  結局、元のプログラムでの対処に戻り、オシロで生成している波形をしばらく漠然と観察していると・・・
  TMR0の変更は問題無く、プリスケーラの変更では、大きな不定パルスは発生しません。
  TBLを変えた刹那に波形が一番乱れ、大きな不定パルスが発生しています。
  うん、TBLを変えなきゃ良いんだな、と結論付け、TBL=64で88鍵分の音域をカバーと言う暴挙に出る事に。
  さすがにTBL64で全音域はカバーできませんが、88鍵以上の音域は、まず使用しないと思われます。
  ここからは、TBL64の各PS値の不足データ取得、発音データの修正と言う、地道な作業を開始
  一番よく使う、オクターブ4~5近辺はなるべくPSも変更しない等、工夫してみました。
  その甲斐あって、小さいポツポツ音は残るものの、耳障りな大きなボツボツ音は無くなりました。
  今回はここが一番のハマりポイントでしたが、何とか聞いていられるので、これで良しとします。

(6)可変ウエイト(「Delay」サブルーチン)
  XC8の「Delay_ms」関数と同じ様なサブルーチンで16bit整数でms数を指定します。
  やっつけ仕事で作っていますので、正確ではありません・・・ (^_^;)

(7)エラー処理およびその他
  MML構文エラーに対して、まったく救済処理をしていません
  エラー発生で、即演奏終了となります。
  デバッグ用にエラーコードおよびエラー発生時のコマンドブロック(最大3バイト)を記録するのみです。
  楽譜の再現性も低く、本来の楽譜から、かなり単純化した1パートの楽譜を起こして、MML化する事になります。
  もともと発音が単音なのと、当局自体が音楽関連能力が皆無なので、この辺はご容赦願います。

(8)サンプルMML
  ①愛のロマンス
  ②大きな古時計
  ③コンドルは飛んで行く
  ④春の小川(解説用)

  基本的にネットで音楽を探し、「MuseScore2」で1パートのみの単純化楽譜を作り、MML化しています。
  音符をビジュアルに配置しながら、発音も確認でき、半端な状態でも演奏できるので超便利です。
  しばらくPICプログラムやMMLの事など忘れて、弄って遊んでしまいました、意外と楽しい~
  とは言え、楽譜超初心者の当局にとって、「MuseScore2」はかなり敷居が高かったです・・・ (-_-;)
  それも単純なメロディの1パートのみを記述するだけなのに、悪戦苦闘。
  「できる」人にとっては、何の問題も無く記述できるのでしょうけどね。
  我々無線家にとって、回路図やプリントパターン等の記述みたいなものなのでしょうか?
  そこで1つ気が付いた事があります
  音楽の元になっている音や音階は物理学だな、と言う事でした・・・

専用MMLの記述方法

例として、言わずと知れた唱歌、「春の小川」を演奏させてみます



初期設定

  TEMPO=100
  T=4(デフォルト)

 歌詞

  春の小川は さらさら行くよ
  岸のすみれや れんげの花に
  すがたやさしく 色うつくしく
  咲けよ咲けよと ささやきながら

 楽譜からMMLへ変換

  は    あ  る  の  お  が  わ   は    さ   ら  さ  ら   ゆ  く  よ
  ミ     ソ  ラ  ソ  ミ   ソ  ド    ド    ラ   ラ  ソ  ミ   ド  レ  ミ  4分休符
  O4E5 G5 A5 G5  E5 G5 O5C5 C5  O4A5 A5 G5 E5  C5 D5 E5 R5 

  き   い し  の  す  み    れ   や    れ  ん  げ  の  は  な  に
  ミ   ソ ラ  ソ   ミ  ソ    ド    ド    ラ   ラ  ソ  ミ   レ  ミ  ド   4分休符
  E5 G5 A5 G5  E5 G5 O5C5  C5  O4A5 A5 G5 E5  D5 E5 C5 R5

  す  う  が  た  や  さ  し  く     い  ろ    う  つ   く  し  く
  レ  ミ  レ  ソ   ラ  ラ  ソ  ラ     ド  ド    シ  ラ   ソ  ソ  ド  4分休符
  D5 E5 D5 G5  A5 A5 G5 A5  O5C5 C5 O4B5 A5  G5 G5 E5 R5

  さ  あ  け  よ  さ  け    よ   と   さ  さ  や き   な が ら
  ミ  ソ  ラ  ラ   ミ  ソ    ド   ド   ラ  ラ  ソ  ミ   レ ミ  ド   4分休符
  E5 G5 A5 G5  E5 G5 O5C5 C5  A5 A5 G5 E5  D5 E5 C5 R5


プログラム

;メインループ
 mloop   MOVLW   LOW Harunoogawa
         MOVWF   MML_PT0
         MOVLW   HIGH Harunoogawa    ;MML_PTxにアドレスセット
         MOVWF   MML_PT1
         MOVLW   .100               ;Wレジスタに100(10進数)セット
         MOVWF   TEMPOL
         CLRF     TEMPOH            ;TEMPOxに100をセット
         CALL     ONPBASE           ;音符生成
         CALL     MUSIC              ;演奏
         CALL     wait1s
         CALL     wait1s              ;2秒ウエイト
         GOTO     mloop              ;繰り返し

;MML用データ領域
 Harunoogawa;;TEMPO=100
         DT   "O4E5G5A5G5 E5G5O5C5C5 O4A5A5G5E5 C5D5E5R5"
         DT   "E5G5A5G5 E5G5O5C5C5 O4A5A5G5E5 D5E5C5R5"
         DT   "D5E5D5G5 A5A5G5A5 O5C5C5O4B5A5 G5G5E5R5"
         DT   "E5G5A5G5 E5G5O5C5C5 O4A5A5G5E5 D5E5C5R5"
         DW   0


ソースファイルおよびその他


 PIC16F1705_DAC_MML01.asm


感想
 さて、「とりあえず動きました」感が半端無いです・・・
 何よりも発音が単純過ぎるのが最初の問題点なのは重々承知しております (-_-;)
 改良は何時になるのか不明です・・・

戻る  実験・製作の記録TOPへ戻る