#author("2023-07-26T10:26:22+09:00;2023-05-30T11:19:15+09:00","","")
#author("2023-07-27T11:41:12+09:00;2023-05-30T11:19:15+09:00","","")
* GBサウンド1 [#hf856e08]
GBサウンドです。DMG(Dot Matrix Game)サウンドとも呼ばれる機能は、初代GameBoyから継承されています。正直なところGBと互換性がある為、[[Pan Doc:https://gbdev.io/pandocs/]]などのドキュメントを見た方が早いかもしれません。このページでは仕様というより、プログラマー的視点で書かせていただこうと思います。

** ことはじめ [#ged14e57]

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


** REG_SOUNDCNT_X [#a8236713]
 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 [#yf3ad0a2]
 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 [#bd20ba81]
 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%にしています。

** 準備 [#gb3950c2]
まとめたのが以下のコードです。サウンド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[#r65ffdd1]

 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 [#p47cc99e]

 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を算出するわけですけれど、世の中にはすでに[[ドレミ音階のテーブル:http://www.devrs.com/gb/files/sndtab.html]]が存在しています。


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

*** Sound length [#g2a97616]

 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 [#v8a2a834]

 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 [#k9a80322]

 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)

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

|RIGHT:|RIGHT:|RIGHT:|c
|Init|  0 |#ref(1.png,nolink)|
|Dir |  1 |~|
|Step|  7 |~|
|Init| 15 |#ref(2.png,nolink)|
|Dir |  0 |~|
|Step|  7 |~|

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

|RIGHT:|RIGHT:|RIGHT:|c
|Init|  0 |#ref(4.png,nolink)|
|Dir |  0 |~|
|Step|  7 |~|

** まとめ [#gb3950c2]

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

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

** テスト [#z44c4a03]
実際にレジスタに直接値を入れてみるサンプルを用意しました。

#ref(3.png,nolink)

 #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 [#me6a5e38]

 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さんの翻訳:https://github.com/pokemium/gba-docs-ja/]]を引用します。

*** Max Output Levels (with max volume settings) [#q6a8f472]
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) [#wd0c00ed]
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 [#j2e3b43e]
さて、いよいよ実際の出力です。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レベルの変更について [#u854cfc1]
通常、きれいな音を出力するには200hを使用します。音が出ない期間(PWM回路がロービットのみを出力することで、消費電力を抑えたり、32KHzのノイズを防いだりしている)は、000hの値を使用すると良いでしょう。またSoundBias機能(SWI19h)を使用すると,ハードスクラッチノイズを発生させずに,ゆっくりとBIASレベルを増減させることができます。

** 履歴 [#k390a23f]
- 2023/05/28

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS