GBサウンドです。DMG(Dot Matrix Game)サウンドとも呼ばれる機能は、初代GameBoyから継承されている機能です。正直なところGBと互換性がある為、Pan Docなどのドキュメントを見た方が早いかもしれません。このページでは仕様というより、プログラム的視点で書かせていただこうと思います。
前回、DirectSoundでは音の中身を図(波形)で説明をしました。一方、GBサウンドはサウンド1~4まで存在していて、レジスタがひしめき合っています。パラメータの内容をいくら文章ベースで説明したとしても、耳で記憶する以上に近道はないと思っています。HUGETrackerで単音を鳴らしてやってみた方がかなり有意義です。単純な話、Trackerでやっていることを今からプログラムで説明していきます。説明の順番はサウンド2→1→3→4です。それでは音を鳴らすレジスタを見ていきましょう。
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にします。しないと音が出ません。
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にします。
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
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カードリッジを差しても遊べるわけですから当然な話ですよね。
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ドライバはテーブルから音符を抽出しています。
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をセットすれば、長さをのパラメータが有効になります。数字が大きいほど音が長くなるかというと逆です。注意してください。
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の割合を決めます。
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)
各パラメータと波形の関係は以下のとおりです。初期音量、減衰か増幅か、変化量を設定します。
Init | 0 | |
Dir | 1 | |
Step | 7 | |
Init | 15 | |
Dir | 0 | |
Step | 7 |
ちなみに以下のようにすると音が出ませんので注意してください。
Init | 0 | |
Dir | 0 | |
Step | 7 |
主役と、引き立て役が3つあるということがわかります。
主役 | Frequency | 周波数 |
引き立て役1 | Sound length | 音の長さ |
引き立て役2 | Wave Pattern Duty | デューティ比 |
引き立て役3 | Envelope | 初期音量、減衰増幅、変化量 |
実際にレジスタに直接値を入れてみるサンプルを用意しました。操作してください。
#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(¶m, 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; } } }
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さんの翻訳を引用しました。
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)にクリップされます。
PSGのチャンネル1〜4は内部的に262.144kHzで生成されており、DMAのサウンドA〜Bは理論的には16.78MHzまでのタイマーレートで生成可能です。しかし、最終的に出力される音は、32.768kHzのレートで、9bitの深さ(上記の10bitの値を2で割った値)にリサンプリングされます。必要に応じて、32.768kHzよりも高いレートをSOUNDBIASレジスタで選択することができますが、その場合は9bitよりも小さい深さになります。
さて、いよいよ実際の出力です。GBAは2つの電圧(LowとHigh)しか出力できませんが、この「ビット」はシステムクロック速度(16.78MHz)で出力されます。デフォルトの32.768kHzのサンプリングレートを使用した場合、1サンプルあたり512ビットが出力されます(512*32K=16M)。各サンプル値(9ビット範囲、N=0~511)は、N個の下位ビットと512~N個の上位ビットで出力されます。このようにして得られた「ノイズ」は、コンデンサやスピーカー、そして人間の聴覚によって平滑化され、32kHzのサンプリングレートで9ビットの電圧をD/A変換したクリーンな音になります。
通常、きれいな音を出力するには200hを使用します。音が出ない期間(PWM回路がロービットのみを出力することで、消費電力を抑えたり、32KHzのノイズを防いだりしている)は、000hの値を使用すると良いでしょう。またSoundBias機能(SWI19h)を使用すると,ハードスクラッチノイズを発生させずに,ゆっくりとBIASレベルを増減させることができます。