#author("2023-05-19T22:15:36+09:00","","")
** DirectSound1 [#w29c50a4]
音を鳴らすには、様々なハードウェア機能を使って実現します。DirectSoundはその極地といっても過言ではありません。ここでは面倒ではありますけれど、ガチガチに決まったらルールに従ってサンプルコードを追いかける形になります。さて、音とは何かというところから始めようと思います。

** 音とは [#zb4e8281]
普段何気なく聞いている音楽。その記録媒体の1つコンパクトディスク(CD)は、バイナリデータの集まりです。円盤の1秒は44100分割されていて、1分割分の音の高さは2バイトであり、ステレオの2チャンネルで構成されています。これをwavファイルフォーマットに置き換えると以下になります。

 44100(Hz) * 2バイト(16bit) * 2(ステレオ)* 1(秒) + 44(ヘッダ) = 176,444バイト

#ref(1.png,nolink)

wavファイルの中身はヘッダ部とデータ部に分かれています。ヘッダは0x00-0x43までの44バイト。残りのデータは0x0000で埋まり、ファイルサイズは176,444バイトとなっています。音の高さ2バイト(16bit)の中身は、符号付きsigned(-32768 ~ +32767, 無音は0)となっています。

** 440Hzのラ音 [#wf9db66e]
次に音を人工的に作ってみましょう。以下にpythonのソースコードを載せますが話したいことの横道になりそうなので読まなくても結構です。
 import numpy as np
 import wave
 import struct
 from matplotlib import pylab as plt
 
 fname = '1sec_440Hz.wav'
 
 fs  = 44100
 f   = 440
 sec = 1
 A   = 32767
 samples = sec * fs
 
 t = np.linspace(0, sec, samples)
 s = A * np.sin(2 * np.pi * f * t)
 s = s.astype(np.int16)
 
 data = struct.pack("h" * samples , *s)
 
 wf = wave.open(fname, 'w')
 wf.setnchannels(1)
 wf.setsampwidth(2)
 wf.setframerate(fs)
 wf.writeframes(data)
 wf.close()
 
 plt.plot(s[0:441])
 plt.show()
 
このプログラムは440Hzのドレミファでいうラ音を1秒間モノラル出力したものです。以下のグラフは横軸0〜441(44100 / 100)。縦軸は繰り返しの説明になりますが、2バイト(16bit)、符号付きsigned(-32768 ~ +32767, 無音は0)です。この440Hzとは1秒間の振動数を表しています。ドなら262、レなら294、ミなら330といった感じです。githubのresフォルダにwavファイルを入れてありますので聞いたことない方はぜひ視聴してください。無茶苦茶シブいシブ柿みたいな音という感想です。

#ref(2.png,nolink,80%)

-音階
|ドレミ記譜法 | ABC記譜法 | 周波数 |
|ド   | C | 262 |
|レ   | D | 294 |
|ミ   | E | 330 |
|ファ | F | 349 |
|ソ   | G | 392 |
|ラ   | A | 440 |
|シ   | B | 494 |
|ド   | C | 523 |

ドレミを比較してみると振動幅が短くなっているのがわかると思います。

#ref(3.png,nolink,80%)

** GBAで鳴らしてみる [#e950c7a3]
GBAはモノラル、符号付き8bit signed(-128 ~ +128, 無音は0)です。またGBATEKでは次のようにアドバイスしています。

  The Sample Rate
  
  The GBA hardware does internally re-sample all sound output to 32.768kHz
  (default SOUNDBIAS setting).
  
  It'd thus do not make much sense to use higher DMA/Timer rates.
  Best re-sampling accuracy can be gained by using DMA/Timer rates of
  32.768kHz, 16.384kHz, or 8.192kHz (ie. fragments of the physical output rate).

適当翻訳すると「GBAは32.768kHzで音が鳴るから1秒間を32768Hz, 16384Hz, 8192Hz」のどれかにした方がいいぜ、といっています。例でいえば、画像ファイルのアスペクト比を無視して縮小をするんじゃないぞ、と忠告してもらっているようなものです。

** 加工方法 [#q4014486]
またもpython先生の出番です。さきほどのサンプルコードを横幅1秒間44100→16384にして、縦幅signed 16bit→8bitに変更します。
 import numpy as np
 import wave
 import struct
 from matplotlib import pylab as plt
 
 
 list_file = ['1_c3.wav', '2_d3.wav', '3_e3.wav', '4_f3.wav', '5_g3.wav', '6_a3.wav', '7_b3.wav', '8_c4.wav']
 list_hz = [262, 294, 330, 349, 392, 440, 494, 523]
 
 fs  = 44100
 sec = 1
 A   = 32767
 samples = sec * fs
 
 for i in range(len(list_hz)):
 	# Wav用
 	t = np.linspace(0, sec, samples)
 	s = A * np.sin(2 * np.pi * list_hz[i] * t)
 	s = s.astype(np.int16)
 
 	data = struct.pack("h" * samples , *s)
 
 	wf = wave.open(list_file[i], 'w')
 	wf.setnchannels(1)
 	wf.setsampwidth(2)
 	wf.setframerate(fs)
 	wf.writeframes(data)
 	wf.close()
  
	plt.plot(s[0:441])
	plt.show()
 
 
 	# GBA用
 	fs_gba = 16384
 	samples_gba = sec * fs_gba
 
 	t = np.linspace(0, sec, fs_gba)
 	s = A * np.sin(2 * np.pi * list_hz[i] * t)
 	s = s.astype(np.int16)
 
 	for j in range(len(s)):
 		s[j] = s[j] >> 8
 
 	data = struct.pack("b" * samples_gba , *s)
 
 	wf = open(list_file[i][:-4]+'.bin', 'wb')
 	wf.write(data)
 	wf.close()
 
 	plt.plot(s[0:163])
 	plt.show()

#ref(4.png,nolink,80%)
縦幅の数字変更-128 ~ 127を確認できると思います。波形はおんなじですね。(^^;

|  | wav(windows用) | bin(GBA用) |
|ファイルサイズ | 88,200+ヘッダ分44バイト | 16,384バイト |

** GBAのソースコード [#b0206fc8]
- snd.arm.h
 #ifndef __SND_H__
 #define __SND_H__
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 #include "lib/gba.h"
 
 //---------------------------------------------------------------------------
 #define SND_CPU_CLOCK			(16 * 1024 * 1024)
 #define SND_AUDIO_RATE			16384
 #define SND_FRAQ			(SND_CPU_CLOCK / SND_AUDIO_RATE)
 
 
 enum {
 	SND_ID_BGM = 0,
 	SND_ID_SE,
 };
 
 enum {
 	SND_ACT_DONOTHING = 0,
 	SND_ACT_START,
 	SND_ACT_STOP,
 	SND_ACT_PLAY,
 };
 
 
 //---------------------------------------------------------------------------
 typedef struct {
 	u32  act;
 	s32  cnt;
 
 	u8*  data;
 	u32  size;
 	s32  frameCnt;
 	bool isLoop;
 } ST_SND;
 
 
 //---------------------------------------------------------------------------
 EWRAM_CODE void SndInit(void);
 
 IWRAM_CODE void SndPlay(u32 id, u8* data, u32 size, s32 adjust, bool isLoop);
 IWRAM_CODE void SndPlayBgm(u8* data, u32 size, s32 adjust, bool isLoop);
 IWRAM_CODE void SndPlaySe(u8* data, u32 size, s32 adjust, bool isLoop);
 
 IWRAM_CODE void SndStopBgm(void);
 IWRAM_CODE void SndStopSe(void);
 
 IWRAM_CODE bool SndIsPlayBgm(void);
 IWRAM_CODE bool SndIsPlaySe(void);
 
 IWRAM_CODE void SndIntrBgm(void);
 IWRAM_CODE void SndIntrSe(void);
 
 IWRAM_CODE void SndIntrBgmStart(void);
 IWRAM_CODE void SndIntrBgmStop(void);
 IWRAM_CODE void SndIntrSeStart(void);
 IWRAM_CODE void SndIntrSeStop(void);
 
 
 #ifdef __cplusplus
 }
 #endif
 #endif

- snd.arm.c
 #include "snd.arm.h"
 
 
 // BGM Timer0, DMA1
 // SE  Timer1, DMA2
 
 //---------------------------------------------------------------------------
 ST_SND Snd[2];
 
 
 //---------------------------------------------------------------------------
 EWRAM_CODE void SndInit()
 {
 	_Memset(&Snd, 0x00, sizeof(ST_SND) * 2);
 
 	REG_TM0CNT_L   = 0x10000 - SND_FRAQ;
 	REG_TM1CNT_L   = 0x10000 - SND_FRAQ;
 
 	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;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndPlay(u32 id, u8* data, u32 size, s32 adjust, bool isLoop)
 {
 	ST_SND* p = &Snd[id];
 
 	p->act      = SND_ACT_START;
 	p->cnt      = 0;
 
 	p->data     = data;
 	p->size     = size;
 	p->frameCnt = (size * 60) / SND_AUDIO_RATE + adjust;
 	p->isLoop   = isLoop;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndPlayBgm(u8* data, u32 size, s32 adjust, bool isLoop)
 {
 	SndPlay(SND_ID_BGM, data, size, adjust, isLoop);
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndPlaySe(u8* data, u32 size, s32 adjust, bool isLoop)
 {
 	SndPlay(SND_ID_SE, data, size, adjust, isLoop);
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndStopBgm(void)
 {
 	Snd[SND_ID_BGM].act = SND_ACT_STOP;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndStopSe(void)
 {
 	Snd[SND_ID_SE].act = SND_ACT_STOP;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE bool SndIsPlayBgm(void)
 {
 	return (Snd[SND_ID_BGM].act == SND_ACT_PLAY) ? TRUE : FALSE;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE bool SndIsPlaySe(void)
 {
 	return (Snd[SND_ID_SE].act == SND_ACT_PLAY) ? TRUE : FALSE;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndIntrBgm(void)
 {
 	ST_SND* p = &Snd[0];
 
 	switch(p->act)
 	{
 	case SND_ACT_DONOTHING:
 		return;
 
 	case SND_ACT_START:
 start:
 		SndIntrBgmStart();
 		p->act = SND_ACT_PLAY;
 		return;
 
 	case SND_ACT_STOP:
 stop:
 		SndIntrBgmStop();
 		p->act = SND_ACT_DONOTHING;
 		return;
 
 	case SND_ACT_PLAY:
 		p->cnt--;
 
 		if(p->cnt <= 0)
 		{
 			if(p->isLoop == TRUE)
 			{
 				goto start;
 			}
 			else
 			{
 				goto stop;
 			}
 		}
 		return;
 
 	default:
 		SystemError("[Err] SndIntrBgm");
 		break;
 	}
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndIntrSe(void)
 {
 	ST_SND* p = &Snd[1];
 
 	switch(p->act)
 	{
 	case SND_ACT_DONOTHING:
 		return;
 
 	case SND_ACT_START:
 start:
 		SndIntrSeStart();
 		p->act = SND_ACT_PLAY;
 		return;
 
 	case SND_ACT_STOP:
 stop:
 		SndIntrSeStop();
 		p->act = SND_ACT_DONOTHING;
 		return;
 
 	case SND_ACT_PLAY:
 		p->cnt--;
 
 		if(p->cnt <= 0)
 		{
 			if(p->isLoop == TRUE)
 			{
 				goto start;
 			}
 			else
 			{
 				goto stop;
 			}
 		}
 		return;
 
 	default:
 		SystemError("[Err] SndIntrSe");
 		break;
 	}
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndIntrBgmStart(void)
 {
 	REG_TM0CNT_H = 0;
 	REG_DMA1CNT  = 0;
 
 	DMA1COPY(Snd[0].data, &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[0].cnt = Snd[0].frameCnt;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndIntrBgmStop(void)
 {
 	REG_SOUNDCNT_H &= ~(SNDA_R_ENABLE | SNDA_L_ENABLE);
 	REG_TM1CNT_H    = 0;
 	REG_DMA1CNT     = 0;
 
 	Snd[0].cnt = 0;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndIntrSeStart(void)
 {
 	REG_TM1CNT_H = 0;
 	REG_DMA2CNT  = 0;
 
 	DMA2COPY(Snd[1].data, &REG_FIFO_B, DMA_SPECIAL | DMA32 | DMA_REPEAT | DMA_SRC_INC | DMA_DST_FIXED);
 	REG_TM1CNT_H = TIMER_START;
 	REG_SOUNDCNT_H |= (SNDB_R_ENABLE | SNDB_L_ENABLE | SNDB_RESET_FIFO);
 
 	Snd[1].cnt = Snd[1].frameCnt;
 }
 //---------------------------------------------------------------------------
 IWRAM_CODE void SndIntrSeStop(void)
 {
 	REG_SOUNDCNT_H &= ~(SNDB_R_ENABLE | SNDB_L_ENABLE);
 	REG_TM1CNT_H    = 0;
 	REG_DMA2CNT     = 0;
 
 	Snd[1].cnt = 0;	
 }

BGMはTimer0, DMA1、SEはTimer1, DMA2を担当しています。最初は初期化についてです。

 EWRAM_CODE void SndInit()
 {
 	_Memset(&Snd, 0x00, sizeof(ST_SND) * 2);
 
 	REG_TM0CNT_L   = 0x10000 - SND_FRAQ;
 	REG_TM1CNT_L   = 0x10000 - SND_FRAQ;
 
 	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;
 }

タイマーの割り込みを行うカウンタをセットしています。1秒間のCPUクロック数16,777,216を、1秒間の音データ数16384で割り算しています。

 16777216 / 16384 = 1024

すると1024という数字が出てきました。言い方を変えると1024クロック毎タイマー割り込みをさせ、1バイトづつ送ると1秒後に16384バイト送ったことになります。次にDirectSoundレジスタについてです。

** SOUNDCNT_X [#vd638371]
 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 v olume 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 4 000082h 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 = SNDSTAT_ENABLE;

FIFOを使うためにEnableにしています。FIFOとは音データを扱うメモリ領域です。DMAやTimerと連動してデータの受け渡しを行います。

** SOUNDCNT_L [#r8e980d7]
 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 = 0;

初期化の時点では鳴らないようにしています。

** SOUNDCNT_H [#v03b3f87]
 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 = SNDA_RESET_FIFO | SNDB_RESET_FIFO | SNDA_VOL_100 | SNDB_VOL_100 | SNDA_TIMER0 | SNDB_TIMER1;

define定義どおりです。Sound A, BにTimerなどの設定をしています。

 IWRAM_CODE void SndIntrBgmStart(void)
 {
 	REG_TM0CNT_H = 0;
 	REG_DMA1CNT  = 0;
 
 	DMA1COPY(Snd[0].data, &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[0].cnt = Snd[0].frameCnt;
 }

ここはもうガチガチの作法というかこれ以外に書きようがありません。DMA1では転送元を音楽データ、転送先をFIFOメモリ領域を指定します。FIFOは32bit(4バイト)の容量しかありません。ここではタイマー割り込み、DMA、FIFOの組み合わせによって音を鳴らしています。

** FIFO_A [#jf02146d]
 40000A0h - FIFO_A_L - Sound A FIFO, Data 0 and Data 1 (W)
 40000A2h - FIFO_A_H - Sound A FIFO, Data 2 and Data 3 (W)
 These two registers may receive 32bit (4 bytes) of audio data
 (Data 0-3, Data 0 being located in least significant byte which is replayed first).
 Internally, the capacity of the FIFO is 8 x 32bit (32 bytes),
 allowing to buffer a small amount of samples. As the name says (First In First Out),
 oldest data is replayed first.

 IWRAM_CODE void SndIntrBgmStop(void)
 {
 	REG_SOUNDCNT_H &= ~(SNDA_R_ENABLE | SNDA_L_ENABLE);
 	REG_TM1CNT_H    = 0;
 	REG_DMA1CNT     = 0;
 
 	Snd[0].cnt = 0;
 }



** 動作画面 [#l222045a]
#ref(5.png,nolink)

** 履歴 [#l5bd00ff]
- 2023/05/17
- 2019/01/05

トップ   一覧 検索 最終更新   ヘルプ   最終更新のRSS