directsound plase help

directsound plase help
by on (#40635)
I ask for help on this since i cant implement good directsound playing

I use a directsound buffer to store data and is played with DSBPLAY_LOOPING.

I use a custom write cursor that starts at play cursor position (0 for it first time) when the dsound buffer is first played. When an emulator frame has passed i call a Cawrite() function that tell me how many buffer bytes free there are. Basically it returns play_cursor - custom_write; if the play cursor has wrapped it does buffer_size - customwrite + play_cursor.

Suppouse i need 1300 bytes free in the buffer for this frame. Naturally i should wait for CanWrite() to return that value. But the thing is that it there is no way to get exactly 1300 bytes free.

I don't want to use any pre-made lib that communicates with dsound.

So, how do you i implement dicctsound sound in your emus? or maybe a more general question how do you implement sound in your emus?

p.s:
I know that there is not low level support in Windows Vista for DirectSound, but i choose it for backwards compatibility.

by on (#40640)
The easiest answer is to just wait for >= 1300. You don't have to completely fill the buffer if you don't want... if CanWrite returns something like 1400, just write the 1300 and leave the extra 100 empty to carry into next frame.

by on (#40645)
It seems is it a sound sync? problem
I tell this becouse i dont get clear sound.

Since i dont't have a finished apu im only outputting what it's in square 1 envelope. I suppouse i should hear clear tones.

As you said i just write the samples i got. Then i do a:

custom_write = (custom_write + bytes_written) % buffer_size;

im using 8 bit and 22050. So getting samples every 81~

I tought maybe the problem was i wasn't advancing my custom write cursor well, since if we have for example: 700 samples generated and 1000 bytes free, the custom_write will advance 700 leaving 300.
So i decided to to advance by "bytes_free", but i still got bad sound.
So i tought "since im testing" i will advance by "bytes_free" and will write "mute" sound to the exceding bytes of the source buffer.

I can't get clear sound.

by on (#40647)
I always recommend that one make small test programs to isolate these problems. In this case, I recommend making a separate program that just tries to play a clean sine wave, without any emulator code. Then you can tear the code up until you find the problem.

by on (#40648)
Anes wrote:
Since i dont't have a finished apu im only outputting what it's in square 1 envelope. I suppouse i should hear clear tones.


Are you sure it's a sound streaming problem and not an APU problem? If I were you, I'd do this directsound stuff in a seperate program, and have it output something you can't screw up (like a simple square or sine wave -- no emulation or anything involved). Then once you have it working there, you can carry the code over to your emu.

In any event... if by "only outputting the envelope" you mean you're not emulating the duty cycle, then you won't hear anything. Without "motion" in the sound wave there is no sound wave.

Quote:
custom_write = (custom_write + bytes_written) % buffer_size;


This is right.

If you're getting crackling or otherwise broken sound, it might also be because of buffer underrun (try increasing the latency / make the sound buffer bigger).

But again -- I really suggest seperating what's a streaming problem from what's an emu problem. Make a seperate program that just streams a simple wave.


edit -- blargg is so fast! ;D

by on (#40649)
Quote:
I always recommend that one make small test programs to isolate these problems. In this case, I recommend making a separate program that just tries to play a clean sine wave, without any emulator code. Then you can tear the code up until you find the problem.


It's true i haven't done that.
I don't know too much about sound. Can you paste the algorithm to make a sine wave? thx.

by on (#40650)
Anes wrote:
I don't know too much about sound. Can you paste the algorithm to make a sine wave? thx.


Code:
output_sample = sin(2*PI * n * desired_freq_in_Hz / sample_rate) * amplitude;


Increase 'n' after every output sample, [snip]

A tone you can test with (desired_freq_in_Hz) is 440.0 Hz (Concert A). Middle C is something like 216.6 Hz or something.. I forget.

Also: 'amplitude' is basically the volume. sin() returns a value between -1 and 1, multiplying it by a fixed value just scales it up to make it louder.


edit -- crap I'm not so sure about the 'wrap at sample_rate' bit anymore. That's one thing I kept forgetting. You don't have to have it wrap at all. It will get out of sync eventually if you don't, but only if you leave the program running for hours.

edit again -- yeah crap -- ignore the 'wrap at sample_rate' bit. That's wrong. I forget the right way to wrap. Anyway wrapping isn't important unless you let the program run really long like I said before.

by on (#40651)
Code:
// Generates next sample of sine wave, using a 16-bit sample range
int gen_sine()
{
    static double r;
    r += 2 * 3.14159 / sample_rate * 440;
    return (int) (10000 * sin( r ));
}

// Example:
short buf [buf_size];
for ( int i = 0; i < buf_size; ++i )
    buf [i] = gen_sine();
play_samples( buf, buf_size );

by on (#40655)
I tested my dsound engine isolated and i hear distorded sound tone. I should hear a clean one i guess.
I really don't know what to do. I checked and re-checked everything and no way.

what i do is:

Code:
// buffer has been filled .... then

Play_buffer();
while (CanWrite() < (buf_size * 2));
Write_to_ds_buffer((char *) buf, buf_size * 2); //here it updates custom write cursor


i multiply by 2 since its 16-bit. I cast to (char *) since inside the routine it manage the buffer as a chunk of bytes.

I know you don't haven't seen my dsound c interface, but its very simple and i checked and re-checked. WTF!!

by on (#40659)
It's impossible for us to diagnose from that little bit of info.

I made a class that uses DSound a while ago -- I threw together a quick program to go with it. You don't have to use the class as-is if you don't want (but you're certainly welcome to), but if nothing else it's a working example of how to stream sound with DSound.

http://disch.arc-nova.org/dsoundexample.zip

by on (#40665)
i inspected your dsound code and found the problem. I chose to use your SoundOut if you this doesn't bother you.

Now i have the problem of syncing to sound. I do:

Code:
while (PCSoundOut->CanWrite() < index * 2);
{
Sleep(1);
}


Where index are the samples generated and * 2 is cos im working with 16-bit ones.

But sometimes the emulation goes faster and then it reduces speed again.

any help?

by on (#40667)
Quote:
I chose to use your SoundOut if you this doesn't bother you

Doesn't bother me at all. Feel free. ^^

I've found that framerate regulation can be rather tricky if you want it done well.

In my past experiences, I've found that you need to have a window of time that you update in -- but don't update outside that window (if the time is earlier than that window, you'd sleep, if the time is later, you'd skip frames to catch up). If you have the window too small, you'll get speed issues with the emu constantly speeding up, then slowing down (like you describe) because it's trying to keep correcting itself in order to fall perfectly into the small window (which may never happen). A larger window gives more wiggleroom so it doesn't have to constantly correct itself.

But if you're trying with very low latencies you might run into problems (if your window is bigger than you have latency for, you'll never skip frames). So one of the things to try if you're having sync issues is to up the latency to something you know should work (100 ms should be high enough for most anything, but you can up it to 250 or something to be extra-sure).

Here's some code snippits from my emu which does this:

Code:
void Emulator::RegulateFrameRate_Sound()
{
   // get number of samples in a frame
   s32 samps = nes->GetApproxNaturalAudio();

   // get time that can be written
   s32 dif = snd->CanWrite() - samps;

   if(dif < 0)   // not yet time for next frame
   {
      ::GlbSleep(1);
      return;
   }

   if(dif >= (samps*nSkipFramesBehind))
   {
      if(nFramesSkipped >= nMaxFrameSkip)
         ResyncFrameTime(0);
      else
      {
         ++nFramesSkipped;
         DoFrame(0,1);
         return;
      }
   }

   nFramesSkipped = 0;
   DoFrame(1,1);
}


now for some explanations:

'nSkipFramesBehind' is the "window" I was describing above that indicates how many frames behind the emu must fall before it starts skipping to catch up. I usually have this set to 5 or so.

GetApproxNaturalAudio() returns the number of bytes of audio my emu core is expected to produce next frame. With 16-bit, 44100 Hz, 60 FPS, this would be around (44100 * 2 / 60) = 1470. I call a seperate function rather than using a fixed value in case the user changed the samplerate, or it's in PAL mode, or something like that.

DoFrame(0,1) emulates a frame and does not output video (frame is skipped) -- DoFrame(1,1) emulates and outputs video (normal frame). Don't worry about the second parameter here, it's irrelevent to this.

ResyncFrameTime() "resets" my timing stuff (for syncing to audio, this isn't much -- just resets nFramesSkipped to zero).


The above pasted routine is meant to be called repeatedly during program idle time when a ROM is loaded and emulation isn't paused. For a Windows program you could put it in a while loop like so:

Code:
while(runprogram)
{
  if(emulation_paused || no_rom_loaded)
    Sleep(1);
  else
    RegulateFrameRate_Sound();

  ProcessMessageQueue();
}

by on (#40677)
ok very useful info. thx.