首页>
技术资讯>
详情

掌上游戏机开发指南 GBA探索日记(9)

2016-05-28 来源:CloudBest 阅读量: 171
关键词: 程序设计


  这一节我们讨论如何正常结束声音播放的问题。不要觉得这是个小问题,当初这个小问题难倒了我们所有研究GBA的爱好者。我总觉得这一点似乎是GBA设计上的缺陷,但是最后我们可以通过使用VBlank中断还解决这个问题。本篇结束的时候,我还会给出完整声音播放的代码。这样,你甚至不必了解声音播放的细节就可以实现声音的播放功能了。
  
  由于声音播放使用的DMA不能根据WORD COUNT来控制传输的数据量,同时又不能在传输完毕的时候产生DMA中断,所以我们无法知道什么时候DMA把我们的音频数据传输完毕。这样,我们必须自己另外加一计时器,来计算什么时候声音播放结束。当然,你可以使用Timer来做,但是另外再设置个Timer似乎就麻烦了点,而且Timer的精度过高,也不好计算长时间的声音播放。依照官方开发包中的做法,我们就拿VBlank中断作为声音播放的计时器。
  
  VBlank中断是在硬件扫描屏幕V方向扫描线的时候产生的硬件中断。它是严格的每秒59.78次(接近60次/s)
  
  下面我们来看看这段修改后的播放声音的代码。需要注意拿他和前一节的播放代码来比较一下,看看他们之间有什么不同。
  
  这个PlayDirectSoundA的播发函数多了一个参数u32 time.它就是用来控制声音结束的时间参数。它的单位是秒(对于一般的声音播放来说,秒单位应该是足够精确了)。
  
  soundATime = time * 59.727;
  
  这条代码是将实际播放时间的秒数转换成一共要产生VBlank的中断数。前面说过,我们将使用VBlank来判断我们的声音是否播放结束。而VBlank中断的频率是59.727Hz,也就是说一秒中要产生59.727次。比如说如果我们的这段声音要播放5秒钟,那么播放过程中一共要产生5*59.727次VBlank中断。这样,我们只要使用一个计数器,每次VBlank中断产生的时候计数器自加1,当它等于5*59.727的时候,那么声音也就应该结束了。
  
  其它的代码就差不多和前一节的PlayDirectSoundA差不多了。
  
  ////////////////////////////////////PlayDirectSoundA/////////////////////////////////////
  
  // time是播发时间,单位是秒数
  
  void PlayDirectSoundA(u8 *sound, u16 sampleRate, u32 length,u32 time)
  
  {
  
  //Stop any previous sample
  
  soundAPlaying = 0;
  
  *(vu16 *)REG_TM0CNT_H = 0;
  
  *(vu16 *)REG_TM0CNT_L = 0;
  
  *(vu32 *)REG_DMA1SAD = 0;
  
  *(vu16 *)REG_DMA1CNT_H = 0;
  
  *(vu32 *)REG_DMA1DAD = 0;
  
  //Output DirectSound A to right channel
  
  *(vu16 *)REG_SOUNDCNT_H |= DSOUND_A_RIGHT_CHANNEL| DSOUND_A_TIMER_0 | DSOUND_A_LEFT_CHANNEL | DSOUND_A_FIFO_RESET | DSOUND_A_OUTPUT_FULL;
  
  //Enable all sound
  
  *(vu16 *)REG_SOUNDCNT_X |= SOUND_MASTER_ENABLE;
  
  //DMA1 Source Addresss
  
  *(vu32 *)REG_DMA1SAD = (u32)sound;
  
  //Set sound A's current sound
  
  soundA = sound;
  
  //Set the length, looping, etc
  
  soundALength = length;
  
  // 讲time秒转换成V-Blank中断次数,V-Blank中断是59.727Hz
  
  soundATime = time * 59.727;
  
  soundACurrent = 0;
  
  soundASampleRate = sampleRate;
  
  //DMA1 Destination Address (REG_SGFIFOA)
  
  *(vu32 *)REG_DMA1DAD = 0x40000A0;
  
  //Write 32 bits into 0x040000A0 (REG_SGFIF0A) every VSync
  
  *(vu32 *)REG_DMA1CNT = DMA_DEST_FIXED | DMA_REPEATE | DMA_32 | DMA_TIMEING_SYNC_TO_DISPLAY | DMA_ENABLE;
  
  //Sample Rate
  
  *(vu16 *)REG_TM0CNT_L = 65536 - (16777216/sampleRate);
  
  //Enable the timer
  
  *(vu16 *)REG_TM0CNT_H = TIMER_ENABLE | TIMER_IRQ;
  
  //The sound is playing
  
  soundAPlaying = 1;
  
  }
  
  在这里我们增加了一个函数UpdateDirectSoundA.它的作用是判断Direct Sound A是否播放完毕的函数.它的代码很简单.每调用它一次,当前计数器soundACurrent自加1,同时判断是否播放完毕.
  
  /////////////////////////////////////UpdateDirectSoundA////////////////////////////////
  
  void UpdateDirectSoundA(void)
  
  {
  
  // if now is not playing sound A,return
  
  if(!soundAPlaying)
  
  return;
  
  // Increase the Current timer
  
  soundACurrent +=1;
  
  // If sound A play time is up,close the sound A
  
  if(soundACurrent >= soundATime)
  
  CloseDirectSoundA();
  
  }
  
  需要注意的是,这个函数的调用必须是放在VBlank中断的响应函数里.比如像下面一样,把它的调用放在Interrupt中处理VBlank的部分里.这样我们才能保证它是每1/59.727秒被调用一次.
  
  关于中断的部分,请看看前面的<>有关中断的部分.
  
  void Interrupts(void)
  
  {
  
  u16 Int_Flag;
  
  REG_IME = 0x00;      // Disable interrupts
  
  Int_Flag = REG_IF;     // Read the interrupt flags
  
  if((REG_IF & 1) == 1)
  
  {
  
  UpdateDirectSoundA();
  
  }
  
  REG_IF = Int_Flag;     // Write back the interrupt flags
  
  REG_IME = 1;    // Re-Enable interrups
  
  }
  
  好了.这个如何控制声音播放结束的问题总算解决了.其实我们也可以

热门推荐 查看更多