2019/01/27

MBC1を使ってROM容量を増やす

ゲームボーイのプログラムROM容量は32Kバイトですが
ちょっと規模の大きいものを作ろうとすると32Kバイトでは足りなくなります。

そこで、MBC1によるバンク切り替えで
32Kバイトより大きな容量を使えるようにします。

バンク切り替えの仕組みですが、プログラム32Kバイトのうち
前半16Kバイトと後半16Kバイトに分けて、
前半は固定し、後半のみを16Kバイト単位で切り替えます。
切り替えるには SWITCH_ROM_MBC1(バンク番号) という
命令を使います。この命令は前半16Kバイトの
プログラム内で実行しなければなりません。

※ここではGBDKのサンプルプログラムを簡素化して紹介しています。

プログラム前半16Kバイト用のプログラムbanktest.cを作成します。

banktest.c
#include <gb.h>
#include <stdio.h>

void bank_1();
void bank_2();
void bank_3();

void bank_fixed()
{
  puts("I'm in fixed ROM");
}

void main()
{
  puts("Program Start...");

  bank_fixed();
  SWITCH_ROM_MBC1(1);
  bank_1();
  SWITCH_ROM_MBC1(2);
  bank_2();
  SWITCH_ROM_MBC1(3);
  bank_3();

  puts("The End...");
}

続いてプログラム後半16Kバイト用のバンク1のプログラムです。

bank_1()が呼ばれると、「I'm in ROM bank 1」と表示されます。

bank_1.c
#include <gb.h>
#include <stdio.h>

void bank_1() /* In ROM bank 1 */
{
  puts("I'm in ROM bank 1");
}

バンク2、3も同様に作ります。

bank_2.c
#include <gb.h>
#include <stdio.h>

void bank_2() /* In ROM bank 2 */
{
  puts("I'm in ROM bank 2");
}

bank_3.c
#include <gb.h>
#include <stdio.h>

void bank_3() /* In ROM bank 3 */
{
  puts("I'm in ROM bank 3");
}

まとめてコンパイルするバッチファイルを用意します。

make.bat
lcc -Wa-l -c -o banktest.o banktest.c
lcc -Wa-l -Wf-bo1 -c -o bank_1.o bank_1.c
lcc -Wa-l -Wf-bo2 -c -o bank_2.o bank_2.c
lcc -Wa-l -Wf-bo3 -c -o bank_3.o bank_3.c
lcc -Wl-m -Wl-yt1 -Wl-yo4 -o banktest.gb banktest.o bank_1.o bank_2.o bank_3.o

コマンドプロンプトからmake.batを実行すると
banktest.gbが出来上がります。

実行結果から、バンク1~3のプログラムが順に実行されたことが確認できます。

MBC1にはプログラムROMのバンク切り替えの他にバッテリーバックアップや
RAMバンクの機能もあるみたいですが、そこまでは調べていません。

BMPファイルを変換するアプリケーション

皆さん、楽しいゲームボーイ開発ライフをお過ごしでしょうか?

ゲームボーイ開発に欠かせないのはタイルデータを作るGBTDです。
GBTDでGBDKのC言語用のタイルデータを出力しないと
プログラムの中にデータを持たせる事が出来ません。

自分で描く分には構いませんが、デザイナーさんに描いてもらう際に
GBTDを使わせるのは正直なところ酷だと思います。

かといって、デザイナーさんから受け取った画像を
GBTDに入力し直すのもかなりの手間です。

そんな事がありましたので、BMPファイルをGBDKの
C言語用タイルデータに変換するアプリケーションを作りました。

BMP2GBDK.C (bmp2gbdk(v1.0).zip)

・使い方
1)
bmp2gbdk.exe を起動します。
2)
アプリケーションのウインドウが開きますので
BMPファイルをドラッグアンドドロップして下さい。
3)
ファイル名に準じたC言語用のソースファイルが生成されます。
例えば、Player.bmpならPlayer.cになります。
内部の変数名もPlayerになります。

・注意
1)
BMPファイルで使用する色数は必ず4色にして下さい。
使用されている色数をチェックし、RGBの各値の合計順に
明るさを判定します。
2)
BMPファイルの縦横ドットサイズは8の倍数にして下さい。
3)
通常は8x8モードで変換しますが、ファイル名に
“_spr”が含まれている場合は8x16モードで変換します。

・免責事項
このアプリケーションを使用する事によって生じた
いかなる損害に対しても作者は一切の責任を負いません。

2018/04/14

音を鳴らしてみよう(3)

仕上げにサウンドドライバを作りました。
簡易的なMMLを実装しています。

snd_drv.h (レジスタとトラック構造体の定義)
#include <gb.h>

// 波形メモリ音源のレジスタ定義
#define VW30_REG (*(UBYTE *)0xFF30) /* Sound register */
#define VW31_REG (*(UBYTE *)0xFF31) /* Sound register */
#define VW32_REG (*(UBYTE *)0xFF32) /* Sound register */
#define VW33_REG (*(UBYTE *)0xFF33) /* Sound register */
#define VW34_REG (*(UBYTE *)0xFF34) /* Sound register */
#define VW35_REG (*(UBYTE *)0xFF35) /* Sound register */
#define VW36_REG (*(UBYTE *)0xFF36) /* Sound register */
#define VW37_REG (*(UBYTE *)0xFF37) /* Sound register */
#define VW38_REG (*(UBYTE *)0xFF38) /* Sound register */
#define VW39_REG (*(UBYTE *)0xFF39) /* Sound register */
#define VW3A_REG (*(UBYTE *)0xFF3A) /* Sound register */
#define VW3B_REG (*(UBYTE *)0xFF3B) /* Sound register */
#define VW3C_REG (*(UBYTE *)0xFF3C) /* Sound register */
#define VW3D_REG (*(UBYTE *)0xFF3D) /* Sound register */
#define VW3E_REG (*(UBYTE *)0xFF3E) /* Sound register */
#define VW3F_REG (*(UBYTE *)0xFF3F) /* Sound register */

#define SND_TRACK_MAX  4
#define SND_FLAG_VW    0x01

/*
// チャンネル制御
NR52_REG = 0x8F;// b7:全チャンネルON=1 / b3-0:チャンネル4~1再生フラグON=F(1111)
NR50_REG = 0x77;// b7:左VinOF=0F / b6-4:左音量=7 / b3:右VinOFF=0 / b2-0:右音量=7
NR51_REG = 0xFF;// b7-4:チャンネル4~1左出力ON=F(1111) / b3-0:チャンネル4~1右出力ON=F(1111)
// チャンネル1(矩形波スイープあり)
NR10_REG = 0x00;// b6-4:スイープ時間=0 / b3:スイープ方向=0(上) / b2-0:スイープ変化量=0
NR11_REG = 0xC0;// b7-6:デューティ比=3 / b5-0:音長カウンタ=0
NR12_REG = 0xF0;// b7-4:初期音量=15 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間=0
NR13_REG = 0x0A;// b7-0:周波数(11ビットの下位8ビット)
NR14_REG = 0x86;// b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
// チャンネル2(矩形波)
NR21_REG = 0xC0;// b7-6:デューティ比=3 / b5-0:音長カウンタ=0
NR22_REG = 0xF0;// b7-4:初期音量=15 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間=0
NR23_REG = 0xB2;// b7-0:周波数(11ビットの下位8ビット)
NR24_REG = 0x86;// b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
// チャンネル3(波形メモリ音源)
NR30_REG = 0x80;// b7:出力ON=1
NR31_REG = 0x00;// b7-0:音長=0
NR32_REG = 0x20;// b6-5:音量 100%出力=1 ※ミュート(0%出力)=0、50%出力=2、25%出力=3
NR33_REG = 0x72;// b7-0:周波数(11ビットの下位8ビット)
NR34_REG = 0x86;// b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
// チャンネル4(ノイズ)
NR41_REG = 0x00;// b5-0:音長=0
NR42_REG = 0xF3;// b7-4:初期音量=15 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間=3
NR43_REG = 0x33;// b7-4:周波数シフト量=3 / b3:カウンタ単位(0:15/1:7)=0 / b2-0:周波数=3
NR44_REG = 0x80;// b7:開始フラグ=1 / b6:カウンタ再生フラグ=0
*/

struct st_track {
 UBYTE ch;   // チャンネル(1~4)
 UBYTE play; // 再生フラグ
 UBYTE on;   // ON/OFFフラグ(音符=1、休符=0)
 UBYTE vol;  // 音量(0~15)※チャンネル3の波形メモリ音源は0~3(ミュート(0%出力=0、100%出力=1、50%出力=2、25%出力=3)
 UBYTE duty; // デューティ比(0~3)
 UBYTE pan;  // パンポット(0~3)消音=0、左=1、右=2、両方=3
 UBYTE tmp;  // テンポ(1~8)
 UBYTE len;  // デフォルト音長
 UBYTE cnt;  // カウンタ
 UBYTE oct;  // オクターブ(0~5)
 UBYTE scl;  // 音階(0~11)C,C+,D,D+,E,F,F+,G,G+,A,A+,B
 UBYTE note; // オクターブ×音階(0~71)
 UBYTE flag; // フラグ b0:波形メモリ転送フラグ
 UBYTE *pp;  // 再生ポインタ
 UBYTE *lp;  // ループポインタ
 UWORD freq; // 周波数
};

snd_drv.c (ドライバ本体)
#include "snd_drv.h"

// トラック
struct st_track snd_track[SND_TRACK_MAX];


// 音程ごとの周波数
UWORD scale_frequency[] = {
 // C    C+   D    D+   E    F    F+   G    G+   A    A+   B // Oct
   44, 156, 262, 363, 457, 547, 631, 710, 786, 854, 923, 986,// 0
 1046,1102,1155,1205,1253,1297,1339,1379,1417,1452,1486,1517,// 1
 1546,1575,1602,1627,1650,1673,1694,1714,1732,1750,1767,1783,// 2
 1798,1812,1825,1837,1849,1860,1871,1881,1890,1899,1907,1915,// 3
 1923,1930,1936,1943,1949,1954,1959,1964,1969,1974,1978,1982,// 4
 1985,1988,1992,1995,1998,2001,2004,2006,2009,2011,2013,2015,// 5
 2017
};

// テンポごとの音長
UBYTE tempo_length[8][10] = {
 // 32    16    16.    8     8.    4     4.    2     2.    1    // Tempo
 {   1-1,  2-1,  3-1,  4-1,  6-1,  8-1, 12-1, 16-1, 24-1, 32-1},// 1
 {   2-1,  4-1,  6-1,  8-1, 12-1, 16-1, 24-1, 32-1, 48-1, 64-1},// 2
 {   3-1,  6-1,  9-1, 12-1, 18-1, 24-1, 36-1, 48-1, 72-1, 96-1},// 3
 {   4-1,  8-1, 12-1, 16-1, 24-1, 32-1, 48-1, 64-1, 96-1,128-1},// 4
 {   5-1, 10-1, 15-1, 20-1, 30-1, 40-1, 60-1, 80-1,120-1,160-1},// 5
 {   6-1, 12-1, 18-1, 24-1, 36-1, 48-1, 72-1, 96-1,144-1,192-1},// 6
 {   7-1, 14-1, 21-1, 28-1, 42-1, 56-1, 84-1,112-1,168-1,224-1},// 7
 {   8-1, 16-1, 24-1, 32-1, 48-1, 64-1, 96-1,128-1,182-1,256-1},// 8
};

// チャンネルごとのパンポット設定
UBYTE ch_panpot[4][4] = {
 // 消音  左    右    両方
 {  0x00, 0x10, 0x01, 0x11},// ch1
 {  0x00, 0x20, 0x02, 0x22},// ch2
 {  0x00, 0x40, 0x04, 0x44},// ch3
 {  0x00, 0x80, 0x08, 0x88},// ch4
};

// 音程ごとのノイズ設定(NR42(下位4ビット),NR43)
UWORD scale_noise[] = {
 //  C       C+      D       D+      E       F       F+      G       G+      A       A+      B  // Oct
 0x0016, 0x0026, 0x0036, 0x0046, 0x0043, 0x0053, 0x0063, 0x0073, 0x0070, 0x0080, 0x0090, 0x00A0,// 1
 0x0116, 0x0126, 0x0136, 0x0146, 0x0143, 0x0153, 0x0163, 0x0173, 0x0170, 0x0180, 0x0190, 0x01A0,// 2
 0x0216, 0x0226, 0x0236, 0x0246, 0x0243, 0x0253, 0x0263, 0x0273, 0x0270, 0x0280, 0x0290, 0x02A0,// 3
 0x0316, 0x0326, 0x0336, 0x0346, 0x0343, 0x0353, 0x0363, 0x0373, 0x0370, 0x0380, 0x0390, 0x03A0,// 4
 0x0416, 0x0426, 0x0436, 0x0446, 0x0443, 0x0453, 0x0463, 0x0473, 0x0470, 0x0480, 0x0490, 0x04A0,// 5
 0x0516, 0x0526, 0x0536, 0x0546, 0x0543, 0x0553, 0x0563, 0x0573, 0x0570, 0x0580, 0x0590, 0x05A0,// 6
 0x0616, 0x0626, 0x0636, 0x0646, 0x0643, 0x0653, 0x0663, 0x0673, 0x0670, 0x0680, 0x0690, 0x06A0,// 7
 0x0716, 0x0726, 0x0736, 0x0746, 0x0743, 0x0753, 0x0763, 0x0773, 0x0770, 0x0780, 0x0790, 0x07A0,// 8
};

// 波形メモリ音源のプリセット
UBYTE voluntary_wave[4][16] = {
 // 
 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},// 矩形波
 { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF},// ノコギリ波
 { 0x0b, 0xde, 0xef, 0xff, 0xff, 0xfe, 0xed, 0xb0, 0x04, 0x21, 0x10, 0x00, 0x00, 0x01, 0x12, 0x40},// 正弦波
 { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10},// 三角波
// { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},// 
};

// サウンド関連の全レジスタを初期化
void snd_reg_init()
{
 NR10_REG = 0;
 NR11_REG = 0;
 NR12_REG = 0;
 NR13_REG = 0;
 NR14_REG = 0;
 NR21_REG = 0;
 NR22_REG = 0;
 NR23_REG = 0;
 NR24_REG = 0;
 NR30_REG = 0;
 NR31_REG = 0;
 NR32_REG = 0;
 NR33_REG = 0;
 NR34_REG = 0;
 NR41_REG = 0;
 NR42_REG = 0;
 NR43_REG = 0;
 NR44_REG = 0;
 NR50_REG = 0;
 NR51_REG = 0;
 NR52_REG = 0;
}

// トラックデータの初期化
void snd_track_init()
{
 UBYTE i;
 for (i = 0; i < SND_TRACK_MAX; i++) {
  snd_track[i].ch = i+1; // チャンネル(1~4)
  snd_track[i].play = 0; // 再生フラグ
  snd_track[i].on   = 0; // ON/OFFフラグ
  snd_track[i].vol  = 0; // 音量(0~15)※チャンネル3の波形メモリ音源は0~3(ミュート(0%出力=0、100%出力=1、50%出力=2、25%出力=3)
  snd_track[i].duty = 0; // デューティ比(0~3)
  snd_track[i].pan  = 0; // パンポット(0~3)消音=0、左=1、右=2、両方=3
  snd_track[i].tmp  = 0; // テンポ(1~8)
  snd_track[i].len  = 0; // デフォルト音長
  snd_track[i].cnt  = 0; // カウンタ
  snd_track[i].oct  = 0; // オクターブ(0~5)
  snd_track[i].scl  = 0; // 音階(0~11)C,C+,D,D+,E,F,F+,G,G+,A,A+,B
  snd_track[i].note = 0; // オクターブ×音階(0~71)
  snd_track[i].flag = 0; // フラグ b0:波形メモリ転送フラグ
  snd_track[i].pp   = 0; // 再生ポインタ
  snd_track[i].lp   = 0; // ループポインタ
  snd_track[i].freq = 0; // 周波数
 }
}

// BGMリクエスト
void snd_req_bgm(UBYTE *ch1, UBYTE *ch2, UBYTE *ch3, UBYTE *ch4)
{
 if (ch1 != 0) {
  snd_track[0].ch   = 1;
  snd_track[0].play = 1;
  snd_track[0].on   = 0;
  snd_track[0].vol  = 15;
  snd_track[0].duty = 0;
  snd_track[0].pan  = 3;
  snd_track[0].tmp  = 4;
  snd_track[0].len  = 5;
  snd_track[0].cnt  = 0;
  snd_track[0].oct  = 3;
  snd_track[0].scl  = 0;
  snd_track[0].note = 0;
  snd_track[0].flag = 0;
  snd_track[0].pp   = ch1;
  snd_track[0].lp   = 0;
  snd_track[0].freq = 0;
 }
 if (ch2 != 0) {
  snd_track[1].ch   = 2;
  snd_track[1].play = 1;
  snd_track[1].on   = 0;
  snd_track[1].vol  = 15;
  snd_track[1].duty = 0;
  snd_track[1].pan  = 3;
  snd_track[1].tmp  = 4;
  snd_track[1].len  = 5;
  snd_track[1].cnt  = 0;
  snd_track[1].oct  = 3;
  snd_track[1].scl  = 0;
  snd_track[1].note = 0;
  snd_track[1].flag = 0;
  snd_track[1].pp   = ch2;
  snd_track[1].lp   = 0;
  snd_track[1].freq = 0;
 }
 if (ch3 != 0) {
  snd_track[2].ch   = 3;
  snd_track[2].play = 1;
  snd_track[2].on   = 0;
  snd_track[2].vol  = 15;
  snd_track[2].duty = 0;
  snd_track[2].pan  = 3;
  snd_track[2].tmp  = 4;
  snd_track[2].len  = 5;
  snd_track[2].cnt  = 0;
  snd_track[2].oct  = 3;
  snd_track[2].scl  = 0;
  snd_track[2].note = 0;
  snd_track[2].flag = 0;
  snd_track[2].pp   = ch3;
  snd_track[2].lp   = 0;
  snd_track[2].freq = 0;
 }
 if (ch4 != 0) {
  snd_track[3].ch   = 4;
  snd_track[3].play = 1;
  snd_track[3].on   = 0;
  snd_track[3].vol  = 15;
  snd_track[3].duty = 0;
  snd_track[3].pan  = 3;
  snd_track[3].tmp  = 4;
  snd_track[3].len  = 5;
  snd_track[3].cnt  = 0;
  snd_track[3].oct  = 3;
  snd_track[3].scl  = 0;
  snd_track[3].note = 0;
  snd_track[3].flag = 0;
  snd_track[3].pp   = ch4;
  snd_track[3].lp   = 0;
  snd_track[3].freq = 0;
 }

 // チャンネル制御
 // b7:全チャンネルON=1 / b3-0:チャンネル4~1再生中フラグは読込のみ
 NR52_REG = 0x80;
 // b7:左VinOF=0F / b6-4:左音量=7 / b3:右VinOFF=0 / b2-0:右音量=7
 NR50_REG = 0x77;
 // b7-4:チャンネル4~1左出力ON=F(1111) / b3-0:チャンネル4~1右出力ON=F(1111)
 NR51_REG = 0x00;
}

void snd_voluntary_wave_set(UBYTE num) {
 VW30_REG = voluntary_wave[num][0];
 VW31_REG = voluntary_wave[num][1];
 VW32_REG = voluntary_wave[num][2];
 VW33_REG = voluntary_wave[num][3];
 VW34_REG = voluntary_wave[num][4];
 VW35_REG = voluntary_wave[num][5];
 VW36_REG = voluntary_wave[num][6];
 VW37_REG = voluntary_wave[num][7];
 VW38_REG = voluntary_wave[num][8];
 VW39_REG = voluntary_wave[num][9];
 VW3A_REG = voluntary_wave[num][10];
 VW3B_REG = voluntary_wave[num][11];
 VW3C_REG = voluntary_wave[num][12];
 VW3D_REG = voluntary_wave[num][13];
 VW3E_REG = voluntary_wave[num][14];
 VW3F_REG = voluntary_wave[num][15];
}

UBYTE snd_conv_len(char *lens)
{
 UBYTE ret = 0;
 UBYTE num = 0;
 UBYTE dot = 0;
 while (1) {
  num = *lens++;
  if (num == '\0') {
   break;
  }
  if (num >= '0' && num <= '9') {
   ret *= 10;
   ret += (num - '0');
  }
  if (num == '.') {
   dot = 1;
  }
 }
 num = ret;
 switch (num) {
  case 32:
   ret = 0;
   break;
  case 16:
   ret = 1;
   break;
  case 8:
   ret = 3;
   break;
  case 4:
   ret = 5;
   break;
  case 2:
   ret = 7;
   break;
  case 1:
   ret = 9;
   break;
  default:
   ret = 0;
 }
 if (dot == 1) {
  ret++;
 }
 if (ret >= 10) {
  ret = 9;
 }
 if (num == 0 && dot == 1) {
  ret = 255; //付点のみ
 }
 return ret;
}

void snd_driver()
{
 struct st_track *snd_trk;
 UBYTE i;
 UBYTE mml;
 UBYTE note;
 UBYTE len;
 UBYTE tmp;
 UBYTE tie;
 UBYTE nr51;
 char atoi_str[4];
 for (i = 0; i < SND_TRACK_MAX; i++)
 {
  snd_trk = &snd_track[i];
  // トラック再生中?
  if (snd_trk->play == 0) {
   continue;
  }
  // 音長あり?
  snd_trk->cnt--;
  if (snd_trk->cnt != 255) {
   continue;
  }
  note = 0;
  tie = 0;
  len = snd_trk->len;
  while (note == 0) {
   mml = *snd_trk->pp++;
   if (mml == '\0') {
    if (snd_trk->lp != 0) {
     snd_trk->pp = snd_trk->lp;
     continue;
    } else {
     break;
    }
   }
   if (mml >= 'a' && mml <= 'g') {
    if (mml == 'c') {
     snd_trk->scl = 0;
    }
    if (mml == 'd') {
     snd_trk->scl = 2;
    }
    if (mml == 'e') {
     snd_trk->scl = 4;
    }
    if (mml == 'f') {
     snd_trk->scl = 5;
    }
    if (mml == 'g') {
     snd_trk->scl = 7;
    }
    if (mml == 'a') {
     snd_trk->scl = 9;
    }
    if (mml == 'b') {
     snd_trk->scl = 11;
    }
    snd_trk->on = 1;
    note = 1;
   }
   if (mml == 'r') {
    snd_trk->on = 0;
    note = 1;
   }
   if (mml == '^') {
    snd_trk->on = 1;
    tie = 1;
    note = 1;
   }
   if (note == 0) {
    if (mml == 'o') {
     mml = *snd_trk->pp++;
     atoi_str[0] = mml;
     atoi_str[1] = '\0';
     snd_trk->oct = atoi(atoi_str);
     if (snd_trk->oct >= 6) {
      snd_trk->oct = 5;
     }
     continue;
    }
    if (mml == '>') {
     snd_trk->oct++;
     if (snd_trk->oct >= 6) {
      snd_trk->oct = 5;
     }
     continue;
    }
    if (mml == '<') {
     snd_trk->oct--;
     if (snd_trk->oct == 255) {
      snd_trk->oct = 0;
     }
     continue;
    }
    if (mml == 'l') {
     tmp = 0;
     while (1) {
      mml = *snd_trk->pp++;
      if ((mml >= '0' && mml <= '9') || mml == '.') {
       atoi_str[tmp++] = mml;
      } else {
       snd_trk->pp--;
       break;
      }
     }
     if (tmp > 0) {
      atoi_str[tmp] = '\0';
      len = snd_trk->len = snd_conv_len(atoi_str);
     }
     continue;
    }
    if (mml == 't') {
     mml = *snd_trk->pp++;
     atoi_str[0] = mml;
     atoi_str[1] = '\0';
     snd_trk->tmp = atoi(atoi_str);
     if (snd_trk->tmp > 8) {
      snd_trk->tmp = 8;
     }
     if (snd_trk->tmp == 0) {
      snd_trk->tmp = 1;
     }
     continue;
    }
    if (mml == 'v') {
     mml = *snd_trk->pp++;
     atoi_str[0] = mml;
     atoi_str[1] = '\0';
     mml = *snd_trk->pp;
     // 2桁目が数字かどうか
     if (mml >= '0' && mml <= '9') {
      atoi_str[1] = mml;
      atoi_str[2] = '\0';
      snd_trk->pp++;
     }
     snd_trk->vol = atoi(atoi_str);
     if (snd_trk->vol > 15) {
      snd_trk->vol = 15;
     }
     continue;
    }
    if (mml == 'y') {
     mml = *snd_trk->pp++;
     atoi_str[0] = mml;
     atoi_str[1] = '\0';
     snd_trk->duty = atoi(atoi_str);
     if (snd_trk->ch != 3) {
      if (snd_trk->duty > 3) {
       snd_trk->duty = 3;
      }
     } else {
      if (snd_trk->duty > 3) {
       snd_trk->duty = 3;
      }
      snd_trk->flag |= SND_FLAG_VW;
     }
     continue;
    }
    if (mml == 'p') {
     mml = *snd_trk->pp++;
     atoi_str[0] = mml;
     atoi_str[1] = '\0';
     snd_trk->pan = atoi(atoi_str);
     if (snd_trk->pan > 3) {
      snd_trk->pan = 3;
     }
     continue;
    }
    if (mml == 'L') {
     snd_trk->lp = snd_trk->pp;
     continue;
    }
   }
   else {
    while(1) {
     mml = *snd_trk->pp;
     if (mml == '-') {
      snd_trk->scl--;
      snd_trk->pp++;
      continue;
     }
     if (mml == '+') {
      snd_trk->scl++;
      snd_trk->pp++;
      continue;
     }
     tmp = 0;
     while (1) {
      mml = *snd_trk->pp++;
      if ((mml >= '0' && mml <= '9') || mml == '.') {
       atoi_str[tmp++] = mml;
      } else {
       snd_trk->pp--;
       break;
      }
     }
     if (tmp > 0) {
      atoi_str[tmp] = '\0';
      tmp = snd_conv_len(atoi_str);
      if (tmp == 255) {
       len = snd_trk->len + 1;
      } else {
       len = tmp;
      }
     }
     break;
    }
    if (snd_trk->on == 1) {
     if (tie == 0) {
      if (snd_trk->ch != 4) {
       snd_trk->freq = scale_frequency[snd_trk->oct*12+snd_trk->scl];
      } else {
       snd_trk->freq = scale_noise[snd_trk->oct*12+snd_trk->scl];
      }
     }
    } else {
     snd_trk->freq = 0;
    }
    // 音長
    snd_trk->cnt = tempo_length[snd_trk->tmp][len];
   }
  }
  if (note == 1) {
   // チャンネル1(矩形波スイープあり)
   if (snd_trk->ch == 1) {
    if (snd_trk->on == 1) {
     // b6-4:スイープ時間=0 / b3:スイープ方向=0(上) / b2-0:スイープ変化量=0
     NR10_REG = 0x00;
     // b7-6:デューティ比=3 / b5-0:音長カウンタ=0
     NR11_REG = snd_trk->duty << 6;
     // b7-4:初期音量=15 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間=0
     NR12_REG = snd_trk->vol << 4;
     // b7-0:周波数(11ビットの下位8ビット)
     NR13_REG = (UBYTE)(snd_trk->freq & 0x00FF);
     // b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
     NR14_REG = (UBYTE)((snd_trk->freq >> 8) & 0x00FF) + 0x80;
     // チャンネル毎の左右オン
     tmp = ch_panpot[snd_trk->ch-1][snd_trk->pan];
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0xee) ^ tmp;
    } else {
     NR13_REG = 0;
     NR14_REG = 0;
     // チャンネル毎の左右オフ
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0xee);
    }
   }
   // チャンネル2(矩形波)
   if (snd_trk->ch == 2) {
    if (snd_trk->on == 1) {
     // b7-6:デューティ比=3 / b5-0:音長カウンタ=0
     NR21_REG = snd_trk->duty << 6;
     // b7-4:初期音量=15 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間=0
     NR22_REG = snd_trk->vol << 4;
     // b7-0:周波数(11ビットの下位8ビット)
     NR23_REG = (UBYTE)(snd_trk->freq & 0x00FF);
     // b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
     NR24_REG = (UBYTE)((snd_trk->freq >> 8) & 0x00FF) + 0x80;
     // チャンネル毎の左右オン
     tmp = ch_panpot[snd_trk->ch-1][snd_trk->pan];
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0xdd) ^ tmp;
    } else {
     NR23_REG = 0;
     NR24_REG = 0;
     // チャンネル毎の左右オフ
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0xdd);
    }
   }
   // チャンネル3(波形メモリ音源)
   if (snd_trk->ch == 3) {
    if (snd_trk->on == 1) {
     // 波形メモリ転送フラグ
     if (snd_trk->flag & SND_FLAG_VW) {
      tmp = SND_FLAG_VW ^ 0xFF;
      snd_trk->flag &= tmp;
      snd_voluntary_wave_set(snd_trk->duty);
     }
     // b7:出力ON=1
     NR30_REG = 0x80;
     // b7-0:音長=0
     NR31_REG = 0x00;
     // b6-5:音量 100%出力=1 ※ミュート(0%出力)=0、50%出力=2、25%出力=3
     //NR32_REG = snd_trk->vol << 5;
     tmp = 0;
     if (snd_trk->vol > 0) {
      tmp = 0x60;
     }
     if (snd_trk->vol > 4) {
      tmp = 0x40;
     }
     if (snd_trk->vol > 8) {
      tmp = 0x20;
     }
     NR32_REG = tmp;
     // b7-0:周波数(11ビットの下位8ビット)
     NR33_REG = (UBYTE)(snd_trk->freq & 0x00FF);
     // b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
     NR34_REG = (UBYTE)((snd_trk->freq >> 8) & 0x00FF) + 0x80;
     // チャンネル毎の左右オン
     tmp = ch_panpot[snd_trk->ch-1][snd_trk->pan];
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0xbb) ^ tmp;
    } else {
     NR30_REG = 0;
     NR32_REG = 0;
     NR33_REG = 0;
     NR34_REG = 0;
     // チャンネル毎の左右オフ
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0xbb);
    }
   }
   // チャンネル4(ノイズ)
   if (snd_trk->ch == 4) {
    if (snd_trk->on == 1) {
     // b5-0:音長=0
     NR41_REG = 0x00;
     // b7-4:初期音量 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間
     tmp = (UBYTE)((snd_trk->freq >> 8) & 0x00FF);
     NR42_REG = tmp + (snd_trk->vol << 4);
     // b7-4:周波数シフト量 / b3:カウンタ単位(0:15/1:7)=0 / b2-0:周波数
     NR43_REG = (UBYTE)(snd_trk->freq & 0x00FF);
     // b7:開始フラグ=1 / b6:カウンタ再生フラグ=0
     NR44_REG = 0x80;
     // チャンネル毎の左右オン
     tmp = ch_panpot[snd_trk->ch-1][snd_trk->pan];
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0x77) ^ tmp;
    } else {
     NR42_REG = 0;
     NR43_REG = 0;
     NR44_REG = 0;
     // チャンネル毎の左右オフ
     nr51 = NR51_REG;
     NR51_REG = (nr51 & 0x77);
    }
   }
  } else {
   snd_trk->play = 0;
   // チャンネル1(矩形波スイープあり)
   if (snd_trk->ch == 1) {
    nr51 = NR51_REG;
    NR51_REG = (nr51 & 0xee);
   }
   // チャンネル2(矩形波)
   if (snd_trk->ch == 2) {
    nr51 = NR51_REG;
    NR51_REG = (nr51 & 0xdd);
   }
   // チャンネル3(波形メモリ音源)
   if (snd_trk->ch == 3) {
    nr51 = NR51_REG;
    NR51_REG = (nr51 & 0xbb);
   }
   // チャンネル4(ノイズ)
   if (snd_trk->ch == 4) {
    nr51 = NR51_REG;
    NR51_REG = (nr51 & 0x77);
   }
  }
 }
}

Sound.c
#include <stdio.h>
#include <gb.h>

// サウンドドライバ
#include "snd_drv.c"

// タイルデータ
#include "Ascii_misaki.c"

UBYTE input_on, input_old, input_edge, input_trg;// キー入力用

UBYTE music1_1[] = {"t5p1v9y1 L l2o4c<b->e-.l4d- ce-a-b-e-2.e f2<b-.>c16d-16 e-2<a2> d-2l8c<b-a-gb-2a-2"};
UBYTE music1_2[] = {"t5p2v8y2 L l8o3a-e-a-e-ge-ge- a-e-a-e-b-e-b-e- a-e-b-e->c<a->d<a- gb-gb-gb-gb- gb-gb-ge-ge- a-e-a-e-e-ce-c fd-fd-d-d-d-d- d-e-d-e-c2"};
UBYTE music1_3[] = {"t5p3v8y2 L l2o2a->d- c<g l4a-gf>f l2e-<e- d->d- c<f <b->e-<l4a->a-<a-r"};
UBYTE music1_4[] = {"t5p3v7   L l4o1d>c<d8d8>c< d>c<d8d8>c< d>c<d8d8>c< d>c<d8d8>c8c8 >>c+<<c<d8d8>c< d>c<d8d8>c< d>c<d8d8>c8<d8 >>>c+<<<d>>>c+<<<d16d16d+8"};

void proc()
{
 if (input_trg & J_A) {
  gotoxy(5, 2);
  print("PLAY");
  // BGM再生
  snd_req_bgm(music1_1, music1_2, music1_3, music1_4);
 }

 if (input_trg & J_B) {
  gotoxy(5, 2);
  print("INIT");
  snd_reg_init();// サウンドレジスタ初期化
  snd_track_init();
 }

 // サウンドドライバ
 snd_driver();

}

// VBL割込で呼ばれる
void vbl_isr(void)
{
 // キー入力
 input_old = input_on;
 input_on = joypad();
 input_edge = input_on ^ input_old;
 input_trg = input_on & input_edge;
 
 proc();
}

void main()
{
 DISPLAY_OFF;// 画面全体を非表示
 HIDE_BKG;// BGを非表示
 HIDE_SPRITES;// スプライトを非表示

 print(" ");// ダミー描画
 set_bkg_data(32, 64, AsciiBgLabel);// アスキー文字

// SHOW_SPRITES;// スプライトを表示
 SHOW_BKG;// BGを表示
 DISPLAY_ON;// 画面全体を表示

 disable_interrupts();// 割込無効
 add_VBL(vbl_isr);// VBL割込に追加
 enable_interrupts();// 割込有効
 set_interrupts(VBL_IFLAG);// VBL割込セット

 snd_reg_init();// サウンドレジスタ初期化
 snd_track_init();

 // メインループ(無くても良いけど何となく)
 while(1) {
  delay(1000UL);
 }
}

以下がMMLのマニュアルです。

音程コマンド
cdefgabr(+/-)数値
数値には音長を指定します。
音長は32、16、16.、8、8.、4、4.、2、2.、1の10通りです。
音長を省略した場合は基準音長(lコマンド)になります。
.(付点)のみを指定した場合は基準音長に付点を加えたものになりますが
基準音長が32と1の時は無効になります。
また、基準音長が付点音長の場合の動作は未確認です。
rは休符です。
チャンネル4(ノイズ)では音程ごとにscale_noiseの値を再生します。
頻度の高いものを割り当て直すと便利です。

基準音長コマンド
l数値
数値には音長を指定します。

タイコマンド
^数値
数値には音長を指定します。
音を延ばしたい時に指定します。音長の省略は不可です。

オクターブコマンド
o数値
オクターブの指定は0~5です。
>で現在のオクターブを1つ上げます。
<で現在のオクターブを1つ下げます。

テンポコマンド
t数値
テンポの指定は1~8です。
テンポ1の32分音符が1フレームになります。
テンポ1の4分音符が8フレームになります。
テンポ2の8分音符も8フレームになります。
テンポ4の16分音符も8フレームになります。
テンポ8の32分音符も8フレームになります。
テンポを音長をうまく指定すれば連符も表現できます。

音量コマンド
v数値
音量の指定は0~15です。
※マスター音量の指定コマンドは実装していません。

パンポットコマンド
p数値
パンポットの指定は0~3です。
0:消音
1:左
2:右
3:両方

音色コマンド
y数値
音色の指定は0~3です。
チャンネル1・2(矩形波)ではデューティ比の指定になります。
チャンネル3(波形メモリ)ではvoluntary_waveのプリセットになります。
チャンネル4(ノイズ)では無効になります。

ループポイント設定コマンド
L
MMLの最後まで再生してから指定のループポイントに戻ります。
Lを設定していなければループせずに再生を終了します。

プログラムではsnd_driver()を毎フレーム呼び出すようにして
snd_req_bgm()で4トラックのMML文字列の先頭アドレスを指定すれば
再生されます。トラック指定が不要な場合は0を指定します。

こんなところでしょうか。
エンベロープやスイープ、ディレイビブラート、デチューン、リピートなどの機能は
今回は実装しませんでした。また、効果音による上書き再生も実装しませんでした。

なお、MMLの解釈は処理に負荷がかかるため
MMLをサウンド用データにコンバートするツールを用意した方が良いです。

実機では動作確認していませんので、ノイズ対策等は行っていません。
ちなみにサンプルの曲はベートーヴェンのピアノソナタ第8番「悲愴」です。

MMLデータをテーブル化して、曲番号をリクエストして
再生できるようにすると良いでしょう。