GBサウンド1

GBサウンドです。DMG(Dot Matrix Game)サウンドとも呼ばれる機能は、初代GameBoyから継承されています。正直なところGBと互換性がある為、Pan Docなどのドキュメントを見た方が早いかもしれません。このページでは仕様というより、プログラマー的視点で書かせていただこうと思います。

ことはじめ

前回、DirectSoundでは音の中身を図(波形)で説明をしました。一方、GBサウンドはサウンド1~4まで存在していて、レジスタがひしめき合っています。パラメータの内容をいくら文章で説明したとしても、耳で記憶する以上に近道はないと思っています。ぶっちゃけHUGETrackerで単音を鳴らしてやってみた方がかなり有意義です。Trackerにできることを今からプログラムで説明していきます。説明の順番はサウンド2→1→3→4です。それでは音を鳴らすレジスタを見ていきましょう。

REG_SOUNDCNT_X

4000084h - SOUNDCNT_X (NR52) - Sound on/off (R/W)
Bits 0-3 are automatically set when starting sound output, and are automatically cleared when a sound ends.
(Ie. when the length expires, as far as length is enabled. The bits are NOT reset when an volume envelope ends.)
  Bit        Expl.
  0     R    Sound 1 ON flag (Read Only)
  1     R    Sound 2 ON flag (Read Only)
  2     R    Sound 3 ON flag (Read Only)
  3     R    Sound 4 ON flag (Read Only)
  4-6   -    Not used
  7     R/W  PSG/FIFO Master Enable (0=Disable, 1=Enable) (Read/Write)
  8-31  -    Not used
While Bit 7 is cleared, both PSG and FIFO sounds are disabled, and all PSG registers at 4000060h..4000081h are reset to zero
(and must be re-initialized after re-enabling sound).
However, registers 4000082h and 4000088h are kept read/write-able
(of which, 4000082h has no function when sound is off, whilst 4000088h does work even when sound is off).
REG_SOUNDCNT_X = 0x80;	// turn on sound circuit

まず、Bit7をPSG/FIFO Master Enableにします。しないと音が出ません。

REG_SOUNDCNT_L

4000080h - SOUNDCNT_L (NR50, NR51) - Channel L/R Volume/Enable (R/W)
  Bit        Expl.
  0-2   R/W  Sound 1-4 Master Volume RIGHT (0-7)
  3     -    Not used
  4-6   R/W  Sound 1-4 Master Volume LEFT (0-7)
  7     -    Not used
  8-11  R/W  Sound 1-4 Enable Flags RIGHT (each Bit 8-11, 0=Disable, 1=Enable)
  12-15 R/W  Sound 1-4 Enable Flags LEFT (each Bit 12-15, 0=Disable, 1=Enable)
REG_SOUNDCNT_L = 0x2277; // full volume, enable sound 2 to left and right

2進数は0010 0010 0111 0111bなので、Master Volume RIGHT, LEFTを0x7に、Sound 2 Enable Flags RIGHT, LEFTをEnableにします。

REG_SOUNDCNT_H

4000082h - SOUNDCNT_H (GBA only) - DMA Sound Control/Mixing (R/W)
  Bit        Expl.
  0-1   R/W  Sound # 1-4 Volume   (0=25%, 1=50%, 2=100%, 3=Prohibited)
  2     R/W  DMA Sound A Volume   (0=50%, 1=100%)
  3     R/W  DMA Sound B Volume   (0=50%, 1=100%)
  4-7   -    Not used
  8     R/W  DMA Sound A Enable RIGHT (0=Disable, 1=Enable)
  9     R/W  DMA Sound A Enable LEFT  (0=Disable, 1=Enable)
  10    R/W  DMA Sound A Timer Select (0=Timer 0, 1=Timer 1)
  11    W?   DMA Sound A Reset FIFO   (1=Reset)
  12    R/W  DMA Sound B Enable RIGHT (0=Disable, 1=Enable)
  13    R/W  DMA Sound B Enable LEFT  (0=Disable, 1=Enable)
  14    R/W  DMA Sound B Timer Select (0=Timer 0, 1=Timer 1)
  15    W?   DMA Sound B Reset FIFO   (1=Reset)
REG_SOUNDCNT_H = 2; // Overall output ratio - Full

ボリュームを100%にしています。

準備

まとめたのが以下のコードです。サウンド2を使いたい場合はこの形が基本になります。次が本番です。

REG_SOUNDCNT_X = 0x80;		// turn on sound circuit
REG_SOUNDCNT_L = 0x2277;	// full volume, enable sound 2 to left and right
REG_SOUNDCNT_H = 2;		// Overall output ratio - Full

REG_SOUND2CNT_L、REG_SOUND2CNT_H

4000068h - SOUND2CNT_L (NR21, NR22) - Channel 2 Duty/Length/Envelope (R/W)
  Bit        Expl.
  0-5   W    Sound length; units of (64-n)/256s  (0-63)
  6-7   R/W  Wave Pattern Duty                   (0-3, see below)
  8-10  R/W  Envelope Step-Time; units of n/64s  (1-7, 0=No Envelope)
  11    R/W  Envelope Direction                  (0=Decrease, 1=Increase)
  12-15 R/W  Initial Volume of envelope          (1-15, 0=No Sound)
Wave Duty:
  0: 12.5% ( -_______-_______-_______ )
  1: 25%   ( --______--______--______ )
  2: 50%   ( ----____----____----____ ) (normal)
  3: 75%   ( ------__------__------__ )
The Length value is used only if Bit 6 in NR14 is set.
400006Ch - SOUND2CNT_H (NR23, NR24) - Channel 2 Frequency/Control (R/W)
  Bit        Expl.
  0-10  W    Frequency; 131072/(2048-n)Hz  (0-2047)
  11-13 -    Not used
  14    R/W  Length Flag  (1=Stop output when length in NR11 expires)
  15    W    Initial      (1=Restart Sound)
  16-31 -    Not used

音を作り出す重要な部分です。NR21, NR22などの記載はGameboyのレジスタ表記で互換性を持ちます。GBA本体にGBカードリッジを差しても遊べるわけですから当然な話ですよね。

Frequency

400006Ch - SOUND2CNT_H (NR23, NR24) - Channel 2 Frequency/Control (R/W)
0-10  W    Frequency; 131072/(2048-n)Hz  (0-2047)

主役はFrequency(周波数)で、他は引き立て役と考えれば話が簡単かもしれません。0-2047という数字を式の通り入れてHzを算出するわけですけれど、世の中にはすでにドレミ音階のテーブルが存在しています。

u16 FreqTable[12*6] = {
//         C      C+       D      D+       E       F      F+       G      G+       A      A+       B
          44,    156,    262,    363,    457,    547,    631,    710,    786,    854,    923,    986,   // o3 
        1046,   1102,   1155,   1205,   1253,   1297,   1339,   1379,   1417,   1452,   1486,   1517,   // o4
        1546,   1575,   1602,   1627,   1650,   1673,   1694,   1714,   1732,   1750,   1767,   1783,   // o5
        1798,   1812,   1825,   1837,   1849,   1860,   1871,   1881,   1890,   1899,   1907,   1915,   // o6
        1923,   1930,   1936,   1943,   1949,   1954,   1959,   1964,   1969,   1974,   1978,   1982,   // o7
        1985,   1988,   1992,   1995,   1998,   2001,   2004,   2006,   2009,   2011,   2013,   2015,   // o8
};

オクターブ5のラ音、o5A=1750を計算してみると131072 / (2048-1750) = 131072 / 298 = 439.838...という数字が出てきます。四捨五入すれば440Hzです。TrackerやMMLドライバはテーブルから音符を抽出しています。

Sound length

4000068h - SOUND2CNT_L (NR21, NR22) - Channel 2 Duty/Length/Envelope (R/W)
  0-5   W    Sound length; units of (64-n)/256s  (0-63)
400006Ch - SOUND2CNT_H (NR23, NR24) - Channel 2 Frequency/Control (R/W)
  14    R/W  Length Flag  (1=Stop output when length in NR11 expires)

Length Flagをセットすれば長さをのパラメータが有効になります。セットしない場合は有効になりません。なお数字が大きいほど音が長くなるかというと逆です。注意してください。

Wave Pattern Duty

4000068h - SOUND2CNT_L (NR21, NR22) - Channel 2 Duty/Length/Envelope (R/W)
  6-7   R/W  Wave Pattern Duty                   (0-3, see below)

Wave Duty:
  0: 12.5% ( -_______-_______-_______ )
  1: 25%   ( --______--______--______ )
  2: 50%   ( ----____----____----____ ) (normal)
  3: 75%   ( ------__------__------__ )

波形のON, OFFの割合を決めます。

Envelope

4000068h - SOUND2CNT_L (NR21, NR22) - Channel 2 Duty/Length/Envelope (R/W)
  8-10  R/W  Envelope Step-Time; units of n/64s  (1-7, 0=No Envelope)
  11    R/W  Envelope Direction                  (0=Decrease, 1=Increase)
  12-15 R/W  Initial Volume of envelope          (1-15, 0=No Sound)

各パラメータと波形の関係は以下のとおりです。初期音量、減衰増幅、変化量を設定します。

Init0
1.png
Dir1
Step7
Init15
2.png
Dir0
Step7

ちなみに以下のようにすると音が出ませんので注意してください。

Init0
4.png
Dir0
Step7

まとめ

パラメータには主役と引き立て役があって、表にするとこんな感じです。

主役Frequency周波数
引き立て役1Sound length音の長さ
引き立て役2Wave Pattern Dutyデューティ比
引き立て役3Envelope初期音量、減衰増幅、変化量

テスト

実際にレジスタに直接値を入れてみるサンプルを用意しました。

3.png
#include "lib/gba.h"
#include "irq.arm.h"
#include "key.h"
#include "mode3.h"

//---------------------------------------------------------------------------

typedef struct {
	s32 cur;	// 現在値
	s32 min;
	s32 max;
	s32 adj;	// 増減値
} ST_PARAM;

//---------------------------------------------------------------------------
const u16 freq[12*6] = {
	// C      C+       D      D+       E       F      F+       G      G+       A      A+       B
	  44,    156,    262,    363,    457,    547,    631,    710,    786,    854,    923,    986,   // o3
	1046,   1102,   1155,   1205,   1253,   1297,   1339,   1379,   1417,   1452,   1486,   1517,   // o4
	1546,   1575,   1602,   1627,   1650,   1673,   1694,   1714,   1732,   1750,   1767,   1783,   // o5
	1798,   1812,   1825,   1837,   1849,   1860,   1871,   1881,   1890,   1899,   1907,   1915,   // o6
	1923,   1930,   1936,   1943,   1949,   1954,   1959,   1964,   1969,   1974,   1978,   1982,   // o7
	1985,   1988,   1992,   1995,   1998,   2001,   2004,   2006,   2009,   2011,   2013,   2015,   // o8
};

//---------------------------------------------------------------------------
void DrawParam(ST_PARAM* p)
{
	Mode3DrawPrintf(20,  7+0, "%04X", p[0].cur);
	Mode3DrawPrintf(20,  7+1, "%04X", p[1].cur);
	Mode3DrawPrintf(20,  7+2, "%04X", p[2].cur);
	Mode3DrawPrintf(20,  7+3, "%04X", p[3].cur);
	Mode3DrawPrintf(20,  7+4, "%04X", p[4].cur);
	Mode3DrawPrintf(20,  7+5, "%04X", freq[p[5].cur]);
	Mode3DrawPrintf(20,  7+6, "%04X", p[6].cur);

	const char noteTbl[][3] = {"C ", "C+", "D ", "D+", "E ", "F ", "F+", "G ", "G+", "A ", "A+", "B "};
	s32 octave = Div(p[5].cur, 12);
	s32 note   = Mod(p[5].cur, 12);

	Mode3DrawPrintf(25, 7+5, "o%d%s", octave + 3, noteTbl[note]);

	switch(p[1].cur)
	{
	case 0: Mode3DrawStr(25, 8, "(12.5)"); break;
	case 1: Mode3DrawStr(25, 8, "(25)  "); break;
	case 2: Mode3DrawStr(25, 8, "(50)  "); break;
	case 3: Mode3DrawStr(25, 8, "(75)  "); break;
	}

	if(p[3].cur == 0) Mode3DrawStr(25, 4+6, "(Dec)");
	else              Mode3DrawStr(25, 4+6, "(Inc)");

	switch(p[7].cur)
	{
	case 0: Mode3DrawStr(20, 14, "9-bits/32768 Hz "); break;
	case 1: Mode3DrawStr(20, 14, "8-bits/65536 Hz "); break;
	case 2: Mode3DrawStr(20, 14, "7-bits/131072 Hz"); break;
	case 3: Mode3DrawStr(20, 14, "6-bits/262144 Hz"); break;
	}

	u16 L, H;

	L = (p[4].cur<<12) + (p[3].cur<<11) + (p[2].cur<< 8) + (p[1].cur<< 6) + (p[0].cur);
	H = (p[6].cur<<14) + (freq[p[5].cur]);

	Mode3DrawPrintf(33, 0, "%04X", L);
	Mode3DrawPrintf(33, 1, "%04X", H);
}
//---------------------------------------------------------------------------
int main(void)
{
	REG_WSCNT = 0x4317;

	Mode3Init();
	IrqInit();
	KeyInit();


	ST_PARAM param[8];

	bool isSnd = FALSE;
	s16 sel = 0;

	_Memset(&param, 0x00, sizeof(param));

	param[ 0].cur = 0x0;
	param[ 1].cur = 0x0;
	param[ 2].cur = 0x7;
	param[ 3].cur = 0x0;
	param[ 4].cur = 0xF;
	param[ 5].cur = 0x0;
	param[ 6].cur = 0x0;
	param[ 7].cur = 0x3;

	param[ 0].min = 0x0;
	param[ 1].min = 0x0;
	param[ 2].min = 0x0;
	param[ 3].min = 0x0;
	param[ 4].min = 0x0;
	param[ 5].min = 0x0;
	param[ 6].min = 0x0;
	param[ 7].min = 0x0;

	param[ 0].max = 0x3f;
	param[ 1].max = 0x3;
	param[ 2].max = 0x7;
	param[ 3].max = 0x1;
	param[ 4].max = 0xF;
	param[ 5].max = 12*6-1;
	param[ 6].max = 0x1;
	param[ 7].max = 0x3;

	param[ 0].adj = 0x1;
	param[ 1].adj = 0x1;
	param[ 2].adj = 0x1;
	param[ 3].adj = 0x1;
	param[ 4].adj = 0x1;
	param[ 5].adj = 0x1;
	param[ 6].adj = 0x1;
	param[ 7].adj = 0x1;

	Mode3DrawStr(2,  0, "0x04000068 (REG_SOUND2CNT_L) = ");
	Mode3DrawStr(2,  1, "0x0400006C (REG_SOUND2CNT_H) = ");

	Mode3DrawStr(2,  7, "Sound Length    :");
	Mode3DrawStr(2,  8, "Wave Duty Cycle :");
	Mode3DrawStr(2,  9, "Envlp Step Time :");
	Mode3DrawStr(2, 10, "Envlp Step Dir  :");
	Mode3DrawStr(2, 11, "Envlp Init Vol  :");
	Mode3DrawStr(2, 12, "Frequency       :");
	Mode3DrawStr(2, 13, "Length Flag     :");
	Mode3DrawStr(2, 14, "Resampling Freq :");

	Mode3DrawStr(0, 7+sel, ">");
	DrawParam(param);

	REG_SOUNDCNT_X = 0x80;		// turn on sound circuit
	REG_SOUNDCNT_L = 0x2277;	// full volume, enable sound 2 to left and right
	REG_SOUNDCNT_H = 2;		// Overall output ratio - Full

	for(;;)
	{
	    VBlankIntrWait();

		KeyExec();
		u16 rep = KeyGetRep();
		u16 trg = KeyGetTrg();

		if(rep & KEY_UP && sel >  0)
		{
			Mode3DrawStr(0, 7+sel, " ");
			sel--;
			Mode3DrawStr(0, 7+sel, ">");
		}

		if(rep & KEY_DOWN  && sel < 7)
		{
			Mode3DrawStr(0, 7+sel, " ");
			sel++;
			Mode3DrawStr(0, 7+sel, ">");
		}

		if((rep & KEY_RIGHT) && (param[sel].cur < param[sel].max))
		{
				param[sel].cur += param[sel].adj;
				DrawParam(param);

				isSnd = TRUE;
		}

		if((rep & KEY_LEFT) && (param[sel].cur > param[sel].min))
		{
				param[sel].cur -= param[sel].adj;
				DrawParam(param);

				isSnd = TRUE;
		}

		if(isSnd == TRUE || trg & KEY_A)
		{
			u16 B, L, H;

			B = (param[7].cur<<14) + (REG_SOUNDBIAS & 0x3fff);
			L = (param[4].cur<<12) + (param[3].cur<<11) + (param[2].cur<< 8) + (param[1].cur<< 6) + (param[0].cur);
			H = (param[6].cur<<14) + freq[param[5].cur];

			REG_SOUNDBIAS   = B;
			REG_SOUND2CNT_L = L;
			REG_SOUND2CNT_H = H + TRIFREQ_RESET;

			isSnd = FALSE;
		}
	}
}

REG_SOUNDBIAS

4000088h - SOUNDBIAS - Sound PWM Control (R/W, see below)
This register controls the final sound output. The default setting is 0200h, it is normally not required to change this value.
  Bit        Expl.
  0     -    Not used
  1-9   R/W  Bias Level (Default=100h, converting signed samples into unsigned)
  10-13 -    Not used
  14-15 R/W  Amplitude Resolution/Sampling Cycle (Default=0, see below)
  16-31 -    Not used
Amplitude Resolution/Sampling Cycle (0-3):
  0  9bit / 32.768kHz   (Default, best for DMA channels A,B)
  1  8bit / 65.536kHz
  2  7bit / 131.072kHz
  3  6bit / 262.144kHz  (Best for PSG channels 1-4)
For more information on this register, read the descriptions below.

最後にREG_SOUNDBIASについてですが難しいのでgbadev-jaさんの翻訳を引用します。

Max Output Levels (with max volume settings)

2つのFIFOは、それぞれ出力範囲の最大値(±200h)に対応しています。 4つのPSGは、それぞれ出力範囲の4分の1(±80h)に対応します。6つのチャンネルの現在の出力レベルは、ハードウェアによって加算されます。つまり、FIFOとPSGを合わせると、出力範囲の3分の1(±600h)に達します。BIAS値はその符号付きの値に加えられます。デフォルトのBIAS(200h)では、可能な範囲は-400h~+800hとなりますが、符号なし10ビットの出力範囲0~3FFhを超える値は、MinMax(0,3FFh)にクリップされます。

Resampling to 32.768kHz / 9bit (default)

PSGのチャンネル1〜4は内部的に262.144kHzで生成されており、DMAのサウンドA〜Bは理論的には16.78MHzまでのタイマーレートで生成可能です。しかし、最終的に出力される音は、32.768kHzのレートで、9bitの深さ(上記の10bitの値を2で割った値)にリサンプリングされます。必要に応じて、32.768kHzよりも高いレートをSOUNDBIASレジスタで選択することができますが、その場合は9bitよりも小さい深さになります。

PWM (Pulse Width Modulation) Output 16.78MHz / 1bit

さて、いよいよ実際の出力です。GBAは2つの電圧(LowとHigh)しか出力できませんが、この「ビット」はシステムクロック速度(16.78MHz)で出力されます。デフォルトの32.768kHzのサンプリングレートを使用した場合、1サンプルあたり512ビットが出力されます(512*32K=16M)。各サンプル値(9ビット範囲、N=0~511)は、N個の下位ビットと512~N個の上位ビットで出力されます。このようにして得られた「ノイズ」は、コンデンサやスピーカー、そして人間の聴覚によって平滑化され、32kHzのサンプリングレートで9ビットの電圧をD/A変換したクリーンな音になります。

BIASレベルの変更について

通常、きれいな音を出力するには200hを使用します。音が出ない期間(PWM回路がロービットのみを出力することで、消費電力を抑えたり、32KHzのノイズを防いだりしている)は、000hの値を使用すると良いでしょう。またSoundBias機能(SWI19h)を使用すると,ハードスクラッチノイズを発生させずに,ゆっくりとBIASレベルを増減させることができます。

履歴


添付ファイル: file4.png 25件 [詳細] file3.png 23件 [詳細] file2.png 25件 [詳細] file1.png 28件 [詳細]

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2023-05-30 (火) 11:19:15