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データをテーブル化して、曲番号をリクエストして
再生できるようにすると良いでしょう。

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

今度は音程を変えましょう。
周波数のレジスタに音程ごとの周波数をセットします。
周波数の値についてはここを参考にしました。

プログラムはこんな感じです。
#include <stdio.h>
#include <gb.h>

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

// 音程に対応した周波数
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,// 2
 1046,1102,1155,1205,1253,1297,1339,1379,1417,1452,1486,1517,// 3
 1546,1575,1602,1627,1650,1673,1694,1714,1732,1750,1767,1783,// 4
 1798,1812,1825,1837,1849,1860,1871,1881,1890,1899,1907,1915,// 5
 1923,1930,1936,1943,1949,1954,1959,1964,1969,1974,1978,1982,// 6
 1985,1988,1992,1995,1998,2001,2004,2006,2009,2011,2013,2015,// 7
 2017
};

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

UBYTE scale;// 音程
UBYTE length;// 音長
UBYTE play_flg;// 再生フラグ

void bg_num_disp(UWORD n, UBYTE x, UBYTE y, UBYTE d);
void snd_reg_init();

// 変数初期化
void var_init()
{
 scale = 0;// 音程
 length = 0;// 音長
 play_flg = 0;// 再生フラグ
}

// サウンド処理
void proc_snd()
{
 UWORD frequency;
 // ファースト処理
 if (play_flg == 1) {
  // チャンネル制御
  // 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 = 0xFF;
  play_flg = 2;
  scale = 0;// 音程
  length = 0;// 音長
 }
 // 再生開始
 if (length == 0) {
  frequency = scale_frequency[scale];
  // チャンネル1(矩形波スイープあり)
  // b6-4:スイープ時間=0 / b3:スイープ方向=0(上) / b2-0:スイープ変化量=0
  NR10_REG = 0x00;
  // b7-6:デューティ比=3 / b5-0:音長カウンタ=0
  NR11_REG = 0xC0;
  // b7-4:初期音量=15 / b3:エンベロープ増減=0(減) / b2-0:エンベロープ単位時間=0
  NR12_REG = 0xF0;
  // b7-0:周波数(11ビットの下位8ビット)
  NR13_REG = (UBYTE)(frequency & 0x00FF);
  // b7:開始フラグ=1 / b6:カウンタ再生フラグ=0 / b2-0:周波数(11ビットの上位3ビット)
  NR14_REG = (UBYTE)((frequency >> 8) & 0x00FF) + 0x80;
  gotoxy(5, 3);
  printf("SCALE=");
  bg_num_disp(scale, 11, 3, 2);// BGへ数字描画
 }

 if (length++ > 15) {
  length = 0;
  if (scale++ >= 72) {
   scale = 0;
  }
 }
}

void proc()
{
 if (input_trg & J_A) {
  gotoxy(5, 2);
  print("PLAY");
  play_flg = 1;
  NR10_REG = 0x00;
  NR11_REG = 0xC0;
  NR12_REG = 0xF0;
  NR13_REG = 0x0A;
  NR14_REG = 0x86;
 }

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

 if (play_flg != 0) {
  proc_snd();// サウンド処理
 }

}

// 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();
}

// BGへ数字描画
// n=表示する数値 x=描画X座標(左端) y=描画Y座標 d=描画桁数
// 10で除算をやってるので処理が重いかも
void bg_num_disp(UWORD n, UBYTE x, UBYTE y, UBYTE d)
{
 UBYTE f = 1;// 最初の0は表示する
 do {
  d--;
  gotoxy(x+d, y);
  if (f == 0 && n == 0)
   setchar(0);// 空白
  else
   setchar(0x30U+(n%10));// 数字
  f = 0;
  if (n != 0) n /= 10;
 } while(d != 0);
}

// サウンド関連の全レジスタを初期化
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 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();// サウンドレジスタ初期化
 var_init();// 変数初期化

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

チャンネル1を鳴らしている所にチャンネル2・3も音程を変えて
鳴らせば和音になります。

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

またまた、久しぶりの更新です。

ゲームには何と言っても音関連が欠かせませんが
ゲームボーイのサウンドにはノイズやゾンビモードなど
厄介な問題が存在するため今まで避けてきました。
が、その辺りの事はとりあえず無視して音を鳴らしてみます。

といっても、音関係のレジスタに決まった値をセットするだけです。
資料となるページはこのあたりでしょうか。

では、プログラムを作ってみましょう。
#include <stdio.h>
#include <gb.h>

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

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

// サウンド関連の全レジスタを初期化
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 proc()
{
 if (input_trg & J_A) {
  gotoxy(5, 2);
  print("PLAY");
  // チャンネル制御
  NR52_REG = 0x80;// b7:全チャンネルON=1 / b3-0:チャンネル4~1再生中フラグは読込のみ
  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ビット)
 }

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

}

// 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();// サウンドレジスタ初期化

 // メインループ(無くても良いけど何となく)
 while(1) {
  delay(1000UL);
 }
}
Aボタンを押すと音が鳴ります(音量注意)。
Bボタンを押すと音が止まります。

他のチャンネルも鳴らしてみます。
  // チャンネル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:周波数=7
  NR44_REG = 0x80;// b7:開始フラグ=1 / b6:カウンタ再生フラグ=0

4チャンネル同時に鳴りました。