音楽を奏でるプログラムの解説(1)
前回掲載したソース・コードを数回にわたって解説していきます。
音符、小節および楽譜を表現するデータ構造は、 先に説明したように二重の配列で構成しています。 ここでは、それぞれの構造について typedefによって型を宣言しています。
//==============================================================
// 楽譜構造体の宣言
//==============================================================
typedef struct {
const byte length; // 音符の長さ
const byte period; // MTIMMODに与える音程
} Sono; // 音符構造体
typedef const Sono * __paged SonoP; // 音符へのポインタ
typedef SonoP Phrase; // 小節の構造
typedef const Phrase * __paged PhraseP; // 小節へのポインタ
typedef PhraseP Music; // 楽譜の構造
typedef const Music * __paged MusicP; // 楽譜へのポインタ
ほとんどすべての型に constと__pagedの 指示子がついています。 constを付けると、 その配列はROMに配置されます。 また、__pagedを付けると、 ページング・ウィンドウを使用したメモリ・アクセスを行う プログラムを作成してくれます。
音程データの宣言について音程は、MTIMMODレジスタに与えられる値が そのまま記入されます。 これらの値を定数配列に直接書き込んでもかまいませんが、 値を直接書き込むといくつか問題が起こります。
- 記入した数値が誤っていても気づきにくい。
記入された数値がちょっと間違っていても 数として正しければコンパイラはエラーも警告も出しません。 したがって、人間が数値を一つ一つ検証しなくてはなりません。
- 数値の書き換えに手間がかかる。
楽譜データに記入した音程データは、 MTIMモジュールのクロック周波数250kHzを基にした 値です。 そのため、ほかのアプリケーションで違う周波数を使いたくなった場合には、 数値を書き直す必要があります。 数値が直接記入してあった場合、書き換えにかかる手間は膨大になります。
このような理由から、 このプログラムでは音程データをいったん定数変数として宣言しておいて、 楽譜データでは、この定数変数を使用することとしました。
//============================================================== // 音程データの宣言 // MTIMモジュールのクロックとして周波数250kHz、周期4usecの // クロックを使用します。下限周波数は、489Hzです。 //============================================================== const long P_base = 250000L / 2; // 250kHz const byte P_H2 = (P_base/ 494)-1; // 494Hz const byte P_D3s = (P_base/ 622)-1; // 622Hz const byte P_E3 = (P_base/ 659)-1; // 659Hz const byte P_F3s = (P_base/ 740)-1; // 740Hz const byte P_G3s = (P_base/ 831)-1; // 831Hz const byte P_A3 = (P_base/ 880)-1; // 880Hz const byte P_H3 = (P_base/ 988)-1; // 988Hz const byte P_C4s = (P_base/1109)-1; // 1109Hz const byte P_D4s = (P_base/1245)-1; // 1245Hz const byte P_E4 = (P_base/1319)-1; // 1319Hz const byte P_F4s = (P_base/1480)-1; // 1480Hz const byte P_G4s = (P_base/1661)-1; // 1661Hz
変数名は、下のようなルールに従って命名されています。
これらの定数変数は、 コンパイラによってプログラムの中に直接埋め込まれてしまうので、 「変数」という名前は付いていますが、 どこかのアドレスに配置されるわけではありません。
楽譜データについて楽譜データのサンプルとして、 ショパンによる「別れの曲」を用意しました。
#pragma CONST_SEG __PAGED_SEG PAGED_ROM
//==============================================================
// 楽譜データ
// ショパン「別れの曲」
//==============================================================
const byte TEMPO = 9; // 曲のテンポ
const Sono phrase_adieu1[] = {
8*TEMPO, P_H2,
8*TEMPO, P_E3, // 1
4*TEMPO, P_D3s,
4*TEMPO, P_E3,
0,0
};
const Sono phrase_adieu2[] = {
20*TEMPO, P_F3s, // 2;10
4*TEMPO, P_G3s,
4*TEMPO, P_G3s,
4*TEMPO, P_F3s,
20*TEMPO, P_G3s, // 3;11
:
const Sono phrase_adieu3[] = {
:
const Sono phrase_adieu4[] = {
:
const Sono phrase_adieu5[] = {
:
16*TEMPO, P_G3s, // 20
8*TEMPO, P_G3s,
0,0
};
const Phrase music_adieu[] = {
phrase_adieu1,
phrase_adieu2,
phrase_adieu3,
phrase_adieu2,
phrase_adieu4,
phrase_adieu5,
(Phrase)0
};
上のプログラムは、一部省略しています。 冒頭でTEMPOという定数変数が定義されていますが、 この変数は曲のテンポを決めている変数です。 楽譜データのすべての音の長さデータは、 この倍数になっているので、 TEMPOの値を変えるだけで 曲全体のテンポを調整することができます。 この楽譜データでは、 8分音符を8*TEMPOと定義しています。
原曲は、一分間に8分音符が100個並ぶテンポなので、 TEMPOに与えるべき値は、
60sec÷100÷8msec÷8 = 9.375
と計算されます。 ここから、TEMPOは9と定義しています。
曲中、小節phrase_adieu2は二回使われています。 このように、曲中に繰り返しなどがあった場合には、 二段構成の配列にしておくと定数配列容量を減らすことができるという メリットもあります。
次回もプログラムの解説を続けます。
