It is currently Sun Dec 17, 2017 8:32 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 7 posts ] 
Author Message
 Post subject: apu dac conversion
PostPosted: Fri Jan 14, 2005 10:23 am 
Offline
User avatar

Joined: Tue Dec 21, 2004 8:35 pm
Posts: 600
Location: Argentina
im trying to emulate the apu, well only square 1 by now. And i have a few questions. i dont know how exactly works a 4 bit DAC, and i dont know how to convert 4 bit dac input to windows PCM.

_________________
ANes


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 14, 2005 11:19 am 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
To my knowledge the DAC is what converts the digital data to an actual analog sound wave. PCs themselves have DACs that do the same thing... so you don't have to worry about this conversion.

In short... whatever gets passed to the DAC... that's what you output when outputting wavs in Windows.


Top
 Profile  
 
 Post subject: dac
PostPosted: Fri Jan 14, 2005 2:21 pm 
Offline
User avatar

Joined: Tue Dec 21, 2004 8:35 pm
Posts: 600
Location: Argentina
im still confused, maybe it could be that the code of my apu is not right. Im using the api "waveOutWrite" 8 bit mono and the value that send it to play is the actual value "recived" in NES dac. I run smario and it seem the music "rithm" is ok, but the tones aren't.

What could it be? :?

_________________
ANes


Top
 Profile  
 
 Post subject:
PostPosted: Fri Jan 14, 2005 5:50 pm 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
waveOut will probably give you a higher latency than you'd like... trying to go low latency will result in crackling and pops. Higher latency will sound like the sound is delayed -- like for example, when mario jumps, you wouldn't hear the sound effect until some time later. But for testing purposes I suppose it's good (it's certainly easier to work with than DirectSound)

Anyway... are you downsampling properly? The NES outputs ~1789772 samples a second... whereas PCs typically only do 44100 (assuming 44KHz output). You essentially have to combine the output for several cycles into one sample.

The simplest way to do this (but very very low quality way) is to only take the output every X cycles:

X = CPU_CLOCK / SAMPLERATE

On NTSC, the CPU_CLOCK is 1789772.7272
If you're doing 44100 samples per second... SAMPLERATE is 44100
So in this situation... you'd only take output once every ~40.58 CPU (or pAPU) cycles.

A higher quality way is to combine all the output for ~40.58 cycles and output the average (aka linear interpolation). This is also easy to do and makes a much higher quality sound.

Even better sounds can come from other methods... like applying a FIR filter, or using the Band-limited synth techniques blargg covers on his page ( http://www.slack.net/~ant/bl-synth/ )


Top
 Profile  
 
 Post subject: apu
PostPosted: Fri Jan 14, 2005 6:13 pm 
Offline
User avatar

Joined: Tue Dec 21, 2004 8:35 pm
Posts: 600
Location: Argentina
thanks man, that helped me a lot

_________________
ANes


Top
 Profile  
 
 Post subject: not accurate
PostPosted: Sat Jan 15, 2005 1:50 pm 
Offline
User avatar

Joined: Tue Dec 21, 2004 8:35 pm
Posts: 600
Location: Argentina
my apu its still not accurate. maybe i missunderstad what you explain me.
I re-writed complety the code to emulate squareone, well its was very simple in the emulation loop i call "CheckAPUReg" to check if there is a write to $4000, then i AND the value with 1111 (bits), i get the evelope, and if its equal to "0" i load a global envelope variable with 080H (middle point of PCM, that means NO sound). If envelope is != 0 i load the enveloope variable with the current envelope value (i know maybe this value is not correct, since the envelope not only depends of $4000).

On the same emulate loop func. i call "PlaySquareOneDac", which plays what ever its in the global Envelope variable. So "CheckApuReg" and "PlaySquareOneDac" are continously called.

Doing this again i can hear the "rithm" ok, but not the "tones". I have been trying diferent methods, like take care of play every 40 cpu cycles, nearly the value that you calculated, buffering, etc. and still cant hear some nearly accurate sound

Well, i want to mean with all this that im complety lost.
If you can help me to "order" on how to emulate the APU, althought Square One, it will appreciate it.

Thanks in advance

_________________
ANes


Top
 Profile  
 
 Post subject:
PostPosted: Sat Jan 15, 2005 2:52 pm 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
Simplest way I can explain:

Keep vars for a CPU timestamp and an APU timestamp. To keep things simple... let's say your CPU timestamp counts in CPU cycles (if the timestamp is 0, after you run LDA #$xx -- a 2 cycle instruction, your timestamp would be 2)

On writes to APU registers... Call a 'RunAPU()' function or something of the sort... which catches the APU up to the current CPU timestamp. Example: your APU timestamp is 10, your CPU timestamp is 100, and the game just wrote to $4000... so you'd run the APU for 90 cycles. Once the APU is caught up... apply the changes that the write performed.

In RunAPU, you'd emulate all your sound channels and actually produce your sound. Assuming you're using that simplest, low quality method I described earlier... RunAPU might look something like this:

Code:
void RunAPU()
{
  int minticks;
  int doticks;
  int soundout;

  doticks = nCPUTimestamp - nAPUTimestamp;
  nAPUTimestamp = nCPUTimestamp;

  while(doticks > 0)
  {
    minticks = ceil( fTicksUntilNextSample );
    if(minticks > doticks)
      minticks = doticks;

    doticks -= minticks;

    ClockSquare1( minticks );

    fTicksUntilNextSample -= minticks;
    if( fTicksUntilNextSample <= 0 )
    {
      fTicksUntilNextSample += fTicksPerSample;

      soundout = 0;
      soundout += GetSquare1Output();

      //clip at 8-bits
      if(soundout < -128)  soundout = -128;
      if(soundout >  127)  soundout =  127;

      //convert to 8-bit unsigned (instead of signed)
      soundout ^= 0x80;

      OutputSample( soundout );
    }
  }
}


fTicksPerSample would be that CPU_CLOCK / SAMPLERATE value (~40.58 @ 44100 Hz). fTicksUntilNextSample is a counter which, as the name implies, tracks how many cycles need to pass before you output another sample. For this example to work decently these would probably have to be floating point to avoid roundoff (you don't want to round off to 40 or 41... since that might bend the pitch of the sound). There are ways to do things without using any floating point vars (which would provide a better performance)... but the concept is easiest to show this way.

ClockSquare1() would be the function that does your emulation for Square 1. Like... clocking the Programmable Timer and updating the Duty Cycle and all that jazz. The actual output of the channel is represented here by GetSquare1Output()... which would return the output. OutputSample() would be where you'd buffer the generated sample (and send to waveOut or whatever).

Note this method isn't really optimized but it should provide you with a concept to help you get things working. Also note that this example leaves out the APU frame thingamajig (which clocks the Sweep Units, Length Counters, Decay Units, etc)... but I wouldn't worry about that stuff until after you get the main sound working.

Also... the ~40.58 cycle thing is only if you're outputting at 44100 Hz. If you try this and the sound is still way offkey... doublecheck your samplerate and make sure it's 44100 Hz.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 7 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group