sound question

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
sound question
by on (#2164)
i know this is not an emulation question, but which is the best way of daling with directsound, cos i create soundbuffers (not primary) i lock, copymemory to the buffer pointer, unlock and play..

it seems this give me problems sound has glitches..

any help?

by on (#2165)
basic step-by-step:

1) DirectSoundCreate
2) SetCooperativeLevel
3) CreateSoundBuffer (primary buffer)
4) SetFormat (primary buffer)
5) CreateSoundBuffer (secondary buffer)
6) Flush Secondary buffer (lock, clear, unlock)
7) Fill Secondary buffer (lock, write sound data, unlock)
8) Play (secondary buffer -- with DSBPLAY_LOOPING)
9) Constantly poll play cursor with GetCurrentPosition and track how much of the buffer is emptied. When you have enough empty space to write more sound data:
10) Write new sound data to secondary buffer (lock, write, unlock)
11) Repeat steps 9 and 10 for as long as you want sound playing
12) Stop (secondary buffer)
13) Release (secondary buffer)
14) Release (primary buffer)
15) Release (Direct Sound object)


you will have to keep track of where in the buffer you want to write to (step 10). You do not have to stop the buffer to lock it (and you shouldn't if you want flowing sound).

Flushing the buffer (step 6) is optional -- if you fill it with sound data before you play it, you don't need to flush it. You can skip step 7 and just start playing the secondary buffer (but if you do this you will have to flush the buffer or it will make a god awful noise).

Always be sure to check for the DSERR_BUFFERLOST return value and call Restore when needed. If the user switches to another app and causes your DirectSound to collapse on itself and you don't correct it, the user will be forced to restart your emu to get sound back.

I made a C++ wrapper class which does a decent job of fronting all this stuff -- only thing is it always assumes 16-bit sound (no 8-bit). You can use it if you please, or you can just use it as an example:

http://www.geocities.com/disch_/SoundOut.zip

by on (#2166)
OK i understand nearly all the steps, but what do you mean with flush?
does it mean that i have to clear (set to 0) the sound buffer?

by on (#2167)
Quite interesting, Disch. I'm looking for a decent sound driver to replace Allegro... I'm going to give a try too... ;)

by on (#2168)
Anes: yeah, you'd fill the sound buffer with silence (for 16-bit sound this would be 0, but for 8-bit sound this would be $80)


Fx3: Feel free to use. It's Windows-only though (DirectSound), and it only does 16-bit sound in mono or stereo (you could add 8-bit sound easily enough though -- just have to make minor changes to SetFormat and Flush).

I probably should have made a readme or something for that thing... anyway:

1) Initialize
2) SetFormat
3) Start
4) continually call CanWrite to see how many bytes of sound you can write to the buffer
5) call Lock; 'len' is the number of bytes you want to lock, -1 will lock as much as possible (will use what's returned from CanWrite)
6) write to sound buffers
7) call Unlock, and tell it how many bytes you actually wrote to the buffer(s).
8) Repeat steps 4-7 to stream sound
9) Stop
10) Destroy

by on (#2169)
It's been a long time since I switched to 16-bit output... ;) No 8-bit sound is required. I'll let you know anything new..

by on (#2171)
More question on you step-by-step:

step 9: should i do dwWriteCursor - dwPlayCursor
step 10: lock it with DSBLOCK_FROMWRITECURSOR?

i make you all these question cos im a beginner in DSound and i really dont know how to handle it.

Thanks in advance

by on (#2172)
When you lock, should you always use -1, or what? When you lock, what does that do? If I locked 3 bytes, would they be the first 3 free samples in the buffer and I'd only be able to write 3 samples?

by on (#2173)
http://nintencer.fobby.net/sexyal-29may2005.zip

You can use this small library to help you if you'd like.

Instruct your C compiler to define HAVE_DIRECTSOUND. Compile sexyal.c, smallc.c, convert.c, and drivers/dsound.c.

You will need to write floating-point PCM audio data, in the range of [0:1](0.0000... through 1.0000..., inclusive).

You can have 1 channel(mono) or 2 channels(stereo, interleaved).

Example code:

#include "sexyal/sexyal.h"

static SexyAL *Interface;
static SexyAL_device *Output;
static SexyAL_format format;
static SexyAL_buffering buffering;
static SexyAL_enumtype *DriverTypes;
static int CurDriverIndex = 0;

uint32 GetMaxSound(void)
{
return(buffering.totalsize);
}

uint32 GetWriteSound(void)
{
return(Output->CanWrite(Output));
}

void WriteSound(float *Buffer, int Count)
{
//printf("%d\n",Output->CanWrite(Output));
Output->Write(Output, Buffer, Count);
}

int InitSound(int *rate, int channels)
{
memset(&format,0,sizeof(format));
memset(&buffering,0,sizeof(buffering));

Interface = (SexyAL *)SexyAL_Init(0);
DriverTypes = Interface->EnumerateTypes(Interface);

format.sampformat=SEXYAL_FMT_PCMFLOAT;
format.channels=channels;
format.rate=*rate;
buffering.fragcount=buffering.fragsize=0;
buffering.ms=soundbufsize;

if(!(Output=Interface->Open(Interface,NULL,&format,&buffering, DriverTypes[CurDriverIndex].type)))
{
Interface->Destroy(Interface);
Interface=0;
return(0);
}

format.sampformat=SEXYAL_FMT_PCMFLOAT;
format.channels=channels;
format.byteorder=0;

Output->SetConvert(Output,&format);

*rate = format.rate
return(1);
}

int KillSound(void)
{
if(Output)
Output->Close(Output);
if(Interface)
Interface->Destroy(Interface);
Interface=0;
if(!Output) return(0);
Output=0;
return(1);
}

by on (#2174)
Anes:

The DirectSound buffer is conceptually circular. DSound will start from a point on the buffer and move slowly around the cirlcle playing the sound data if finds. That point is marked as the Play Cursor. The Write Cursor is always just a little bit ahead of the Play Cursor -- its purpose is to signal where it's safe to write. The area between the Play and Write cursors is 'unsafe' to write to when the surface is playing (although that's hard to avoid sometimes -- like if your program is running behind or if your latency is too low. Worst thing that will happen is the sound will crackle or get distorted -- which would happen anyway under those circumstances. So yeah try to avoid writing between the cursors, but it isn't the end of the world if you do.)

Your job is to follow the Play cursor so that as it plays the sound data, you write new sound data to the buffer, so that when the play cursor makes it around full circle, it won't play data it already played -- it will play new sound data (streaming the sound smoothly)

I made an example in paint which outlines the concept:

http://www.geocities.com/disch_/ds.png

P = is the Play Cursor
W = is the Write Cursor
X = is where your program will want to write to next (DSound does not track this! You will have to track it on your own)

The green area of the circle is sound data that is ready to play. The red area is what you should avoid writing to, and the yellow area is sound data that has already played and should be replaced (if the Play Cursor hits the yellow portion it will play sound it already played, which will make your computer sound like a very fast broken record)


Drag:

Already answered this on IRC, but yeah. When you lock 3 samples, you can only write to those 3 samples on the buffer. In my example code when you specify -1 for the length to lock, it will lock as much as can be written (it will call CanWrite to see how many byte to lock).

by on (#2175)
Disch, you didn't answer about the parameters of:
void CSoundOut::Lock(int len, void** bufa,DWORD* siza,void** bufb,DWORD* sizb)

Two buffers... Could you clarify it?

by on (#2176)
Assume you have a sound buffer that's 1000 bytes in size. And let's say you lock 200 bytes starting at the 900'th byte in the buffer. That would go past the end of the buffer -- but since the buffer is conceptually circular, you would want it to wrap around to the start of the buffer. When this happens, something kind of like the following would happen:

http://www.geocities.com/disch_/ds2.png

The green area of the bottom figure represents which area you're trying to lock with the above example numbers -- as you can see, it is two seperate areas of the buffer. Because it's two seperate areas, you need two seperate pointers.

bufa is the pointer to the first section to fill (labeled A on that figure)
siza is the number of bytes you can write to bufa
bufb is the pointer to the second section (labeled B on that figure)
sizb is the number of bytes you write to bufb

fill bufa first, then bufb (if it needs filling).

by on (#2177)
Disch:

well tell me if what im doing is right (cos i wanna write my own interface, thats cos im not using yours, althought it help me a lot :) )

when the buffer is "wrapped" i write to where to points write cursor (W) until the end of buffer, and then i write the remainder (its well written? i mean in english) bytes at the beginning of the buffer the size it left.

I always check if write buffer + buffersize to write will not split, if not split i write all the buffersize (pointer 1 as you defined) and if its wraped i split the buffer and write to, you know what i mentioned above.

The sound its better for me now, but it still have some glitches.

One Question:
DX docs says that one should not write between the play and write cursors

could be that my problem?

by the way.. do you have a basic step-by-step on how to apply synth band limit for people like me dont doesnt understand to much (and dont wanna to read an entire book about the topic and.. dont understand what blargg says in his page?)

thanks..

by on (#2178)
Anes, look at this example:

Buffer size = 1024 bytes (your sound buffer, okay?)

The 1st update needs 200 bytes (or samples), and the current pointer to buffer is at 900. It will overflow 1024, agree? So, you take bufferA as 900 (size=124), then bufferB as 0 (beginning, size=100). Got it? :) Anyway, contact me by msn.

by on (#2180)
I can't really follow what you're doing so I can't say what you're doing wrong -- although maybe I could try to explain things a little clearer.

I refer once again to this diagram:
http://www.geocities.com/disch_/ds.png


You should not be writing your sound at the write cursor. If that's what you're doing you'll have underrun problems. The write cursor is always placed just a bit ahead of the play cursor -- which means the data you write to the write cursor will be played almost immediately! (bad)

You want to keep as much distance between the play cursor and the sound you're writing as possible. If the play cursor ever catches up to where you're writing (X in above diagram... NOT W), you'll suffer from what's known as buffer underrun -- and what will happen is that sound which already has played will play again (making your app sound like a broken record)

The write cursor has ABSOLUTLY NO VALUE other than letting you know where you shouldn't write (you shouldn't write between the play and write cursors -- the red highlighted portion in my above diagram). It should not be any indicator of where you're writing sound data -- you should be writing your sound data WELL AHEAD of the write cursor.

Lemme try and explain the diagram a little better:

Green Area on circle = fresh sound data (what is ready to play)
Yellow Area on circle = stale sound data (has already played -- you don't want it to play again)
Red Area of circle = "off limits" area -- don't write here (just the area between P and W).

Circle 1 shows how things should look when you first start playing your buffer. It's completely filled with sound, and ready to play.

After you start playing the buffer, P and W will start moving clockwise around the circle, playing the sound data (turning green to yellow). This is shown by Circle 2.

Your program's job is to keep an eye on how much of that sound has turned yellow, and to keep replacing the yellow with green (writing new sound data up to the play cursor). The 'X' mark on the circle is the point you last wrote you (DirectSound does not keep track of this!! You will have to do it yourself! This is not the write cursor!). You will end up sort of having 'X' follow 'P' around the circle, replacing the stale yellow sound data with fresh new green sound data. This is shown by Circle 3

As you can see from the diagram, there should always be a decent amount of green space between P and X. If P ever hits a yellow portion, your sound will distort.

As for the buffer wrapping, this just happens (it's not shown in that diagram). All you have to do to manage this is use both pointers you get from DSound. First you fill the buffer1 pointer you get from Lock, then buffer2.

The wrapping doesn't have to have any affect on which areas you lock! Just lock with a starting point and a length like you would for any other lock -- and if you get two pointers, so be it. Just write to both of them.


As for BL-synth, I'm not really in the mood to try and explain it right now. Though I understand that Blargg's docs can be a little difficult to understand when you're new to the topic.