Hatena::ブログ(Diary)

滴了庵日録 このページをアンテナに追加 RSSフィード

2013/12/07(Sat)

GR-SAKURA用FM音源シールドライブラリ

GR-SAKURAでYM2203(OPN)を制御するFM音源シールドのライブラリとサンプルスケッチを公開します。ソフトの開発環境はHEW+GCC+E1です。Webコンパイラでもたぶん使えると思いますが、未確認です。

ファイル

YM2203_MMLplayer.h/cppMMLを解釈してYM2203で演奏するプレイヤーのクラス
下記のYM2203クラスをラップします。
YM2203.h/cppYM2203を直接制御するクラス
YM2203_Timbre.h/cppYM2203の音色構造体
gr_sketch.cppサンプルスケッチ

なお、YM2203_MMLplayerクラスではタイミング制御のためにTMR0によるタイマ割り込みを利用しています。このため、割り込みベクタとハンドラを定義している intvect.c に下記の修正を施す必要があります。

// TMR0_CMIA0
// void Excep_TMR0_CMIA0(void){ }
↑1500行めあたりのこの行をコメントアウトします。

YM2203_MMLplayerクラスの使用方法

下記ヘッダファイルをインクルードし、グローバルインスタンスMMLplayerが提供するメソッドをコールします。

#include "YM2203_MMLplayer.h"

以下に、メソッドの仕様を記します。

void MMLplayer.begin()

YM2203デバイスおよびMMLプレイヤーを初期化します。setup()でコールします。

void setup()
{
    MMLplayer.begin();
}

void MMLplayer.setTempo(int bpm)

テンポを設定します。全チャンネル共通です。

  • bpm テンポ。1分間の拍数を指定します。
    MMLplayer.setTempo(58);  //  アダージョ
    MMLplayer.setTempo(72);  //  アンダンテ
    MMLplayer.setTempo(92);  //  モデラート
    MMLplayer.setTempo(132); //  アレグロ

void MMLplayer.setVolume(int ch, int volume)

チャンネルごとに音量を設定します。

  • ch チャンネル番号。0〜2がFM音源、3〜5がSSG音源です。
  • volume 音量。0〜15で指定します。0が最小、15が最大の音量です。

チャンネル番号は、0〜5の数字の他、下記の定数でも指定できます。

定数名意味
FM_CH10FM音源チャンネル1
FM_CH21FM音源チャンネル2
FM_CH32FM音源チャンネル3
SSG_CH_A3SSG音源チャンネルA
SSG_CH_B4SSG音源チャンネルB
SSG_CH_C5SSG音源チャンネルC
    MMLplayer.setVolume(FM_CH1, 14);
    MMLplayer.setVolume(FM_CH2, 11);
    MMLplayer.setVolume(FM_CH3, 8);
    MMLplayer.setVolume(SSG_CH_A, 11);
    MMLplayer.setVolume(SSG_CH_B, 8);
    MMLplayer.setVolume(SSG_CH_C, 8);

void MMLplayer.setEnvelope(int ch, int type, int interval)

チャンネルごとにエンベロープを設定します。(SSG音源のみ)

  • ch チャンネル番号。3〜5のSSG音源のみ有効。
  • type エンベロープの形状。(下記)
  • interval エンベロープの周期 (0〜65535)。周期は interval * 256 usecになります。

エンベロープの形状は、8〜15の値によって下図のように設定されます。表中の波形図のTが、エンベロープの周期です。

エンベロープについての詳細は、FM音源とPSG音源の基礎YM2203のレジスタ設定(PSG音源編)を参照してください。

エンベロープ形状

※ エンベロープと音量を同時に設定することはできません。MMLplayer.setVolume関数を呼ぶとエンベロープの設定は無効になり、MMLplayer.setEnvelope関数を呼ぶと音量の設定は無効になります。

MMLplayer.setToneNoise(int ch, int mode)

チャンネルごとにトーン/ノイズのミックスを設定します。(SSG音源のみ)

  • ch チャンネル番号。3〜5のSSG音源のみ有効。
  • mode トーン/ノイズ モードの指定。(下記)

SSGチャンネルの出力は、modeに下記の定数を与えることにより、(1)トーンのみ (2)ノイズのみ (3)トーン+ノイズ のいずれかを設定できます。

TONE_MODEトーンのみ出力
NOISE_MODE ノイズのみ出力
TONE_NOISE_MODE トーン+ノイズを出力

void MMLplayer.setTimbre(int ch, YM2203_Timbre *timbre)

チャンネルごとに音色を設定します。(FM音源のみ)

  • ch チャンネル番号。0〜2のFM音源のみ有効。
  • timbre 音色データ構造体をポインタで渡します。

音色の設定には、YM2203_Timbre構造体を用います。AR,DR,SR,RR,SL,TL,KS,ML,DTについては、4つのオペレータに対するパラメータをまとめて設定するメソッドを用意しました。

音色の各パラメータについての詳細は、FM音源とPSG音源の基礎YM2203のレジスタ設定(FM音源編)を参照してください。

    // エレキベースの音色定義
    YM2203_Timbre tmbEBass;
    tmbEBass.algorithm = 2;
    tmbEBass.feedback  = 5;
    tmbEBass.opMask    = MASK_ALL;
    tmbEBass.setAR(31, 31, 31, 31);
    tmbEBass.setDR( 8, 14, 16, 12);
    tmbEBass.setSR( 0,  6,  3,  5);
    tmbEBass.setRR( 0,  9,  0,  8);
    tmbEBass.setSL( 3,  2,  2,  2);
    tmbEBass.setTL(34, 42, 20,  0);
    tmbEBass.setKS( 0,  0,  0,  0);
    tmbEBass.setML( 0,  8,  0,  1);
    tmbEBass.setDT( 3,  0, -3,  0);
    // 音色の設定
    MMLplayer.setTimbre(FM_CH1, &tmbEBass);
algorithm

 アルゴリズム。0〜7。4つのオペレータのつなぎ方を指定します。

feedback

 フィードバック。0〜7。オペレータ1のセルフ・フィードバック量を指定します。

opMask

 オペレータマスク。0〜15。4つの各オペレータの有効/無効を指定します。

setAR( op1, op2, op3, op4 )

 4つのオペレータに対して、アタック・レイトを指定します。各0〜31。

setDR( op1, op2, op3, op4 )

 4つのオペレータに対して、ディケイ・レイトを指定します。各0〜31。

setSR( op1, op2, op3, op4 )

 4つのオペレータに対して、サステイン・レイトを指定します。各0〜31。

setRR( op1, op2, op3, op4 )

 4つのオペレータに対して、リリース・レイトを指定します。各0〜15。

setSL( op1, op2, op3, op4 )

 4つのオペレータに対して、サステイン・レベルを指定します。各0〜15。

setTL( op1, op2, op3, op4 )

 4つのオペレータに対して、トータル・レベルを指定します。各0〜127。

setKS( op1, op2, op3, op4 )

 4つのオペレータに対して、キース・ケールを指定します。各0〜3。

setML( op1, op2, op3, op4 )

 4つのオペレータに対して、マルチプルを指定します。各0〜15。

setDT( op1, op2, op3, op4 )

 4つのオペレータに対して、デチューンを指定します。各0〜7

void MMLplayer.setGateTime(int ch, int gateTime)

チャンネルごとにゲートタイムの割合を設定します。

  • ch チャンネル番号。0〜2がFM音源、3〜5がSSG音源です。
  • gateTime ゲートタイムの長さ。1〜8で設定します。

ステップタイムに対するゲートタイムの長さの比を設定します。ステップタイムとは、音符の長さで指定される時間です。ゲートタイムとは、実際に音が出ている時間です。ステップタイム8に対するゲートタイムの長さで設定します。ステップタイムはつまり1が最も短く、8が最も長くなります。

void MMLplayer.setNote(int ch, char* note)

チャンネルごとに楽譜データをMMLで設定します。

  • ch チャンネル番号。0〜2がFM音源、3〜5がSSG音源です。
  • note MML文字列をポインタで渡します。

このライブラリで使用できるMMLはN-88BASICのMMLの下位互換であり、下記のコマンドに対応しています。

CDEFGABドレミファソラシを表します。後に音符の長さを指定できます。たとえば8分音符のドならC8。
R休符。これも長さを指定できます。
1,2,4,8,16,32,3,6,12,24音符の長さ。たとえば8は8分音符。
.付点。たとえばC4.は付点4分音符のドを表します。
+(#)シャープ。たとえばC+またはC#でド#を表します。
-フラット。たとえばC-でド♭を表します。
Oオクターブを指定します。1〜8。
Lデフォルトの音長を指定します。
Qゲートタイムの割合を指定します。音符の長さのn/8のあいだ音が出力されます。
V音量を指定します。0〜15。
>オクターブを上げます。
<オクターブを下げます。
&タイまたはスラー。
@音色を選びます。
※データは作成中 
T※未対応 テンポを指定します。BPM。
{ }※未対応 連符。

void MMLplayer.play(void)

設定された楽譜データを演奏します。

このメソッドは非同期型です。演奏を開始すると関数から戻ってきます。以降はバックグランドで演奏を継続します。

void MMLplayer.stop(void)

演奏を強制停止します。

bool MMLplayer.isPlaying(void)

演奏中か否かを返します。

このメソッドは、MMLplayer.play()で演奏を開始した場合に、演奏の終了をチェックするのに用います。

    // 楽譜の設定
    char *note1,*note2,*note3;
    note1 = (char*)"L8Q7O4V14"
                   "DBAGD4RDDBAGE4REE>C<BAF+4R>DDDC<AB4RD"
                   "DBAGD4RDDBADE4REE>C<BA>DDDDEDC<AGR>D4";
    note2 = (char*)"L8Q4O5V13"
                   "RRRRRD16C+16D16C+16DRRRRRE16D+16E16D+16E"
                   "RRRRRF+16F16F+16F16F+RRRRRD16D+16E16D+16D"
                   "RRRRRD16C+16D16C+16DRRRRRE16D+16E16D+16E"
                   "RRRRRF+16F16F+16F16F+RRRRRRD4";
    note3 = (char*)"L8Q8O4V14"
                   "G4D4G4D4G4AB>C4<G4>C4<G4A4D4A4D4GDEF+"
                   "G4D4G4D4G4AB>C4<G4>C4<G4A4D4ADEF+G4D4";
    MMLplayer.setNote(FM_CH1, note1);
    MMLplayer.setNote(FM_CH2, note2);
    MMLplayer.setNote(FM_CH3, note3);
    MMLplayer.setNote(SSG_CH_A, ""); // 使用しない場合は空文字列
    MMLplayer.setNote(SSG_CH_B, ""); // 使用しない場合は空文字列
    MMLplayer.setNote(SSG_CH_C, ""); // 使用しない場合は空文字列
    // 演奏を開始する
    MMLplayer.play();
    // 演奏が終わるまでLEDを点滅
    int blink = 1;
    while( MMLplayer.isPlaying() ){
        digitalWrite(PIN_LED0, blink);
        delay(500);
        blink = 1-blink;
        // スイッチ押したら停止
        if (digitalRead(PIN_SW) == 0){
            MMLplayer.stop();
        }
    }

void MMLplayer.playAndWait(void)

設定された楽譜データを演奏します。

このメソッドは同期型です。演奏が終了するまで関数から戻ってきません。

    // 演奏する		
    MMLplayer.playAndWait();
    // 演奏が終わってからLED点灯
    digitalWrite(PIN_LED0, 1);

今後の対応予定

  • 未対応のMMLコマンドの実装
  • プリセット音色の作成
  • Android端末との通信
  • Androidアプリの作成