08 사운드(Sound) 제어

원문 : http://kkamagui.springnote.com/pages/423523

 

들어가기 전에...

 

1.채널 컨트롤 레지스터(Channel Control Register)

 NDS에는 16개의 사운드 채널이 존재하며 각 채널들은 독립적인 소리를 낼 수 있다. 스피커로 출력되는 소리는 각 채널에서 나는 소리의 합이다.

 IO 블럭은 첫번째 사운드 레지스터들이 0x4000400에서 0x400040F까지 존재하며 16번째 블럭은 0x40004F0에서 0x40004FF까지 존재한다. 사운드 레지스터에 대해서는 http://nocash.emubase.de/gbatek.htm#dssound 에서 자세히 볼 수 있다.

 위의 링크에 문서를 읽어보면 스피커가 2개이기 때문에 스테레오 사운드를 제공할 수 있다고 되어있다. 그런데 아래에 레지스터를 보면 스테레오 모드라는 것이 없다. 어떻게 스테레오를 구현하는 걸까? 곰곰히 생각해보니 스테레오로 Play 하려면 2개의 좌/우 스피커용 파일을 만들고 Panning을 이용해서 각각 넣는게 아닌가 하는 생각이 들었다. 실제로 그렇게 하는지는 의문이지만 가능은 하다는거 @0@)/~!!

 

40004x0h - ARM7 - SOUNDxCNT - Sound Channel X Control Register (R/W)

Bit0-6    Volume       (0..127=silent..loud)
  Bit7      Not used     (always zero)
  Bit8-9    Data Shift   (0=Normal, 1=Div2, 2=Div4, 3=Div16)
  Bit10-14  Not used     (always zero)
  Bit15     Hold         (0=Nothing, 1=Hold)               (?)
  Bit16-22  Panning      (0..127=left..right) (64=half volume on both speakers)
  Bit23     Not used     (always zero)
  Bit24-26  Wave Duty    (0..7) ;HIGH=(N+1)*12.5%, LOW=(7-N)*12.5% (PSG only)
  Bit27-28  Repeat Mode  (0=Manual, 1=Loop Infinite, 2=One-Shot, 3=Prohibited)
  Bit29-30  Format       (0=PCM8, 1=PCM16, 2=IMA-ADPCM, 3=PSG/Noise)
  Bit31     Start/Status (0=Stop, 1=Start/Busy)
All channels support ADPCM/PCM formats, PSG rectangular wave can be used only on channels 8..13, and white noise only on channels 14..15.

 위에서 보면 몇가지 재미있는 옵션들을 볼 수 있다. Panning 모드를 사용하면 스피커 한쪽에서 소리를 내는 것이 가능하며, Repeat 모드를 사용하면 계속해서 소리를 출력할 수 있다. Format 같은 경우 좀 특이하게 PCM/ADPCM/PGS-Noise 모드를 지원한다. 소리를 출력하기위한 포맷이 굳이 PCM이 아니어도 가능하다는 말이다.

 PSG라는 말이 나오는데, 아무래도 Pulse Sound Generator의 의미인것 같고 사인파 같은 파형을 출력하는 것 같다.

 

40004x4h - ARM7 - SOUNDxSAD - Sound Channel X Data Source Register (W)

Bit0-26  Source Address
  Bit27-31 Not used

 사운드 소스 레지스터로써 출력할 사운드가 어디에 위치하는지 주소를 넣어준다.

 

40004x8h - ARM7 - SOUNDxTMR - Sound Channel X Timer Register (W)

  Bit0-15  Timer Value, Sample frequency, timerval=-(16777216 / freq)

 The PSG Duty Cycles are composed of eight "samples", and so, the frequency for Rectangular Wave is 1/8th of the selected sample frequency.
For PSG Noise, the noise frequency is equal to the sample frequency.

 16777216의 값은 0x1000000와 같고 타이머 레지스터에 설정되어야 하는 값은 저 값을 -0x1000000의 값을 Frequency로 나눈 값으로 설정해야 한다. Frequency의 값은 11025와 같은 실제 Hz의 값을 넣어줘야 한다.

 

40004xAh - ARM7 - SOUNDxPNT - Sound Channel X Loopstart Register (W)

Bit0-15  Loop Start, Sample loop start position

 Loop로 반복할 때 시작 위치를 정해줄 수 있는 것 같은데... 자세한 건 테스트를 해봐야 겠다.

 

40004xCh - ARM7 - SOUNDxLEN - Sound Channel X Length Register (W)
The number of samples for N words is 4*N PCM8 samples, 2*N PCM16 samples, or 8*(N-1) ADPCM samples (the first word containing the ADPCM header). The Sound Length is not used in PSG mode.

Bit0-21  Sound length (counted in words, ie. N*4 bytes)
  Bit22-31 Not used
Minimum length is 4 words (16 bytes), smaller values (0..3 words) are interpreted as zero length (no sound output).

 Sound Length 레지스터에 입력되는 값은 Word 단위이다. ARM에서 Word 단위는 4Byte이므로 8bit Sample 같은 경우는 전체 길이를 4로 나누어줘야 하고 16bit의 Sample 같은 경우는 2로 나누어줘야 한다.

 

2.사운드 컨트롤 레지스터(Sound Control Register)

 각 채널별 설정이 아닌 전체에 적용되는 마스터의 성격을 가지는 레지스터이다.

4000500h - SOUNDCNT - ARM7 - Sound Control Register (R/W)

Bit0-6   Master Volume          (0..127=silent..loud)
  Bit7     Not used               (always zero)
  Bit8-9   Left Out      (probably selects Mixer or "Bypassed" channels?)
  Bit10-11 Right Out     (probably selects Mixer or "Bypassed" channels?)
  Bit12    Output Sound Channel 1 (0=To Mixer, 1=Bypass Mixer)
  Bit13    Output Sound Channel 3 (0=To Mixer, 1=Bypass Mixer)
  Bit14    Not used               (always zero)
  Bit15    Master Enable          (0=Disable, 1=Enable)

4000504h - SOUNDBIAS - ARM7 - Sound Bias Register (R/W)
Bit0-9   Sound Bias    (0..3FFh, usually 200h)
  Bit10-31 Not used      (always zero)
After applying the master volume, the signed left/right audio signals are in range -200h..+1FFh (with medium level zero), the Bias value is then added to convert the signed numbers into unsigned values (with medium level 200h).

The sampling frequency of the mixer is 1.04876 MHz with an amplitude resolution of 24 bits, but the sampling frequency after mixing with PWM modulation is 32.768 kHz with an amplitude resolution of 10 bits.

  그냥 살짝 볼 부분은 Master Enable과 Master Volume 부분인 것 같다. 나머지 부분은 그리 중요하지 않은 듯 하므로 패스~

 

3.구현

 사운드 부분은 ARM7에만 연결되어있으므로, 테스트를 위해 ARM7 코드를 손보거나 아니면 라이브러리를 통해 간접적으로 테스트 해야 한다. 소스가 간단하니 굳이 만들 필요는 없을 것 같고 libnds의 소스를 보도록 하자. 사운드 레지스터 관련 헤더파일은 \devkitPro\libnds\source\include\nds\arm7 폴더에 audio.h 파일을 보면 된다.


  1. #define SOUND_VOL(n) (n)
    #define SOUND_FREQ(n) ((-0x1000000 / (n)))
    #define SOUND_ENABLE BIT(15)
    #define SOUND_REPEAT    BIT(27)
    #define SOUND_ONE_SHOT  BIT(28)
    #define SOUND_FORMAT_16BIT (1<<29)
    #define SOUND_FORMAT_8BIT (0<<29)
    #define SOUND_FORMAT_PSG    (3<<29)
    #define SOUND_FORMAT_ADPCM  (2<<29)
    #define SOUND_16BIT      (1<<29)
    #define SOUND_8BIT       (0)
  2. #define SOUND_PAN(n) ((n) << 16)
  3. #define SCHANNEL_ENABLE BIT(31)
  4. //---------------------------------------------------------------------------------
    // registers
    //---------------------------------------------------------------------------------
    #define SCHANNEL_CR(n)    (*(vuint32*)(0x04000400 + ((n)<<4)))
    #define SCHANNEL_VOL(n)    (*(vuint8*)(0x04000400 + ((n)<<4)))
    #define SCHANNEL_PAN(n)    (*(vuint8*)(0x04000402 + ((n)<<4)))
    #define SCHANNEL_SOURCE(n)   (*(vuint32*)(0x04000404 + ((n)<<4)))
    #define SCHANNEL_TIMER(n)   (*(vint16*)(0x04000408 + ((n)<<4)))
    #define SCHANNEL_REPEAT_POINT(n) (*(vuint16*)(0x0400040A + ((n)<<4)))
    #define SCHANNEL_LENGTH(n)   (*(vuint32*)(0x0400040C + ((n)<<4)))
  5. #define SOUND_CR          (*(vuint16*)0x04000500)
    #define SOUND_MASTER_VOL  (*(vuint8*)0x04000500)
  6. //---------------------------------------------------------------------------------
    // not sure on the following
    //---------------------------------------------------------------------------------
    #define SOUND_BIAS        (*(vuint16*)0x04000504)
    #define SOUND508          (*(vuint16*)0x04000508)
    #define SOUND510          (*(vuint16*)0x04000510)
    #define SOUND514    (*(vuint16*)0x04000514)
    #define SOUND518          (*(vuint16*)0x04000518)
    #define SOUND51C          (*(vuint16*)0x0400051C)

 앞부분에서 설명했던 레지스터의 주소를 매크로로 정의해 놓은 것을 알 수 있다.

 실제 사용된 코드 부분은 \devkitPro\libnds\source\basicARM7\source 폴더의 defaultARM7.c 파일에서 볼 수 있다.

  1. //---------------------------------------------------------------------------------
    void startSound(int sampleRate, const void* data, u32 bytes, u8 channel, u8 vol,  u8 pan, u8 format) {
    //---------------------------------------------------------------------------------
     SCHANNEL_TIMER(channel)  = SOUND_FREQ(sampleRate);
     SCHANNEL_SOURCE(channel) = (u32)data;
     SCHANNEL_LENGTH(channel) = bytes >> 2 ; <== 4byte 즉 word 단위 값으로 넣기위해 4로 나누는 부분
     SCHANNEL_CR(channel)     = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_VOL(vol) | SOUND_PAN(pan) | (format==1?SOUND_8BIT:SOUND_16BIT);
    }

  2. //---------------------------------------------------------------------------------
    s32 getFreeSoundChannel() {
    //---------------------------------------------------------------------------------
     int i;
     for (i=0; i<16; i++) {
      if ( (SCHANNEL_CR(i) & SCHANNEL_ENABLE) == 0 ) return i;
     }
     return -1;
    }
  3. //---------------------------------------------------------------------------------
    void VblankHandler(void) {
    //---------------------------------------------------------------------------------
  4.  u32 i;

  5.  //sound code  :)
     TransferSound *snd = IPC->soundData;
     IPC->soundData = 0;
  6.  if (0 != snd) {
  7.   for (i=0; i<snd->count; i++) {
       s32 chan = getFreeSoundChannel();
  8.    if (chan >= 0) {
        startSound(snd->data[i].rate, snd->data[i].data, snd->data[i].len, chan, snd->data[i].vol, snd->data[i].pan, snd->data[i].format);
       }
      }
     }
  9. }

 위에서 보면 채널 레지스터 설정 시에 타이밍, 소스, 길이 모두 설정한 뒤에 채널 설정 레지스터를 설정하는 것을 볼 수 있다. 타이밍, 소스, 길이 레지스터를 설정하는 순서는 별 의미 없어보이나 채널 설정 레지스터를 설정하는 것은 가장 마지막에 해야 한다.

 

4.마치며...

 이제 사운드도 출력할 수 있게 되었다. 사운드 정보를 PCM으로 바꾸면 간단히 Play할 수 있으므로 examples 폴더에 있는 사운드 예제를 한번 돌려보자.

 

 

 

이 글은 스프링노트에서 작성되었습니다.

+ Recent posts