あーさん日記

https://akkera102.sakura.ne.jp/gbadev/ の中の人

DirectSound (大雑把な説明2)

サンプルにバグがあったので修正しました。
申し訳ないのですが昨日、ダウンロードした方は落とし直してください。


前回はwavファイルの書式を駆け足で覗いてみましたが、
次にGBAで使う書式を覗いてみることにします。

DOSプロンプトで実行
sox 1sec.wav -r 16384 -c 1 -B -s -1 1sec.raw
dir 1sec.raw

2015/10/17  xx:xx            16,384 1sec.raw

無音の1秒ファイルをsoxに掛けた結果、
0x00で埋まった16,384サイズのファイルが出来上がりました。

GBAは「16384Hz モノラル 8bit(符号付き)」形式で鳴らす予定なので
ヘッダがない、素のデータのみ、ということがわかると思います。
さて具材の準備は整ったので早速サンプルコードを見ていきましょう。


音を鳴らす為、GBAの機能(vblank割り込み、タイマー、DMA、FIFO)を
たくさん使います。ただ決まりごとの部分が多いので難易度は相当低いと思います。

EWRAM_CODE void SndInit(void)
{
	_Memset(&Snd, 0x00, sizeof(ST_SND) * 2);

	REG_TM0CNT_L   = 0xffff - (SND_CPU_CLOCK / SND_AUDIO_RATE);
	REG_TM1CNT_L   = 0xffff - (SND_CPU_CLOCK / SND_AUDIO_RATE);

	REG_SOUNDCNT_X = SNDSTAT_ENABLE;
	REG_SOUNDCNT_L = 0;
	REG_SOUNDCNT_H = SNDA_RESET_FIFO | SNDB_RESET_FIFO | SNDA_VOL_100 | SNDB_VOL_100 | SNDA_TIMER0 | SNDB_TIMER1;


	SndPlay(SND_ID_BGM, (s8*)&music01_raw, (u32)music01_raw_size, -6, TRUE);
}

初期化処理(snd_arm.cの16行目)です。
ポイントはやはりREG_TM0CNT_L、REG_TM1CNT_Lでしょう。
0の方はBGM、1の方はSE(効果音)を担当させるため、同じようなことを2回書いています。
defineしている部分を数字で置き換えると

REG_TM0CNT_L = 0xffff - ((16 * 1024 * 1024) / (16 * 1024))
REG_TM0CNT_L = 0xffff - 16,777,216 / 16,384
REG_TM0CNT_L = 0xffff - 1024
REG_TM0CNT_L = 0xfbff

となります。数字はともかく
「CPUクロック / サウンドレート」の意味することはなんでしょうか。
GBAは1秒を16,777,216クロックという単位で動いており、その基準に合わせないといけません。
サウンドデータは1秒を16,384分割したデータであり、別の言い方をするなら
1秒の間に16,384バイトのデータを流し込む必要があります。

16,777,216Hz / 16,384Hz = 1024Hz

1024Hzの間に1バイトづつ、16384回流し込めばうまくいく寸法です。

REG_TM0CNT_L = 0xffff - 1024

とするのは、タイマーの桁あふれタイミング(割り込み)を利用したい為、
引き算をしているわけです。うまい仕組みですよね。


次のREG_SOUNDCNT関連の3行はお決まり事なので説明は飛ばします。
最後の

	SndPlay(SND_ID_BGM, (s8*)&music01_raw, (u32)music01_raw_size, -6, TRUE);

については、音楽を鳴らす関数となっています。気になる引数は-6でしょうか。

IWRAM_CODE void SndPlay(u32 id, s8* pDat, u32 size, s32 adjust, bool isLoop)
{
	ST_SND* p = &Snd[id];

	p->pDat      = pDat;
	p->size      = size;
	p->frameSize = (size * 60) / SND_AUDIO_RATE + adjust;
	p->frameCnt  = 0;
	p->isLoop    = isLoop;
	p->act       = SND_ACT_START;
}

p->frameSizeは、音を停止させる数値が入っています。
単位はvblankです。vblankは1秒に60回。仮に1秒のサウンドを代入してみると

	p->frameSize = (16384 * 60) / 16384 // adjustは0とする

となり、60。vblank毎に値を減らしていき、0になったらサウンドを停止させる、
という仕組みになります。細かい事はソースコードを読んでください。
「-6(s32 adjust)」というのはvblank回数を意図的に減らす仕組みのことです。

なぜ必要なのか覚えていないのですが(^^;
音のお尻の部分を削っているわけなので終端に何かあるのでしょう・・・。
ノイズが乗ったらちょっと弄ってみてください。普段は0でいいかもしれません。
次に

IWRAM_CODE void SndIntrBgmStart(void)
{
	REG_TM0CNT_H = 0;
	REG_DMA1CNT  = 0;

	DMA1COPY(Snd[SND_ID_BGM].pDat, &REG_FIFO_A, DMA_SPECIAL | DMA32 | DMA_REPEAT | DMA_SRC_INC | DMA_DST_FIXED);
	REG_TM0CNT_H    = TIMER_START;
	REG_SOUNDCNT_H |= (SNDA_R_ENABLE | SNDA_L_ENABLE | SNDA_RESET_FIFO);

	Snd[SND_ID_BGM].frameCnt = Snd[SND_ID_BGM].frameSize;
}

IWRAM_CODE void SndIntrBgmStop(void)
{
	REG_SOUNDCNT_H &= ~(SNDA_R_ENABLE | SNDA_L_ENABLE);
	REG_TM1CNT_H    = 0;
	REG_DMA1CNT     = 0;
}

vblank内での再生、停止処理ですが、これもハードウェア的お決まりなので
これはこういうもんだと思ってください。ハードウェアの仕様がソースコード
固定させちゃったものです。たぶん、順番を変えるだけでも破綻する可能性があるので
ご注意ください。

コンパイル環境があるのでしたらsnd_arm.hの11行目。

#define SND_AUDIO_RATE		(16 * 1024)

を以下のどちらかに変えてみてください。
意図する結果になるかぜひ確認してみることをオススメします。

#define SND_AUDIO_RATE		(16 * 1024 * 2)
#define SND_AUDIO_RATE		(16 * 1024 / 2)