0
101 Nov 27, 2008 at 21:31

Hello,

I am using Visual Studio Express 2008 to try and generate a tone.

I have got as far as setting up the DirectSound object and creating the buffer, but don’t know where to go from there. I don’t want to play a .wav file at the moment, just generate a tone.

Here is the code so far:

#include "stdafx.h"
#include <string>
#include <iostream>
#include <Dsound.h>
#define _USE_MATH_DEFINES
#define _WIN32_DCOM
#include <cmath>

using namespace std;

HWND hwnd;
HRESULT hr;
LPDIRECTSOUNDBUFFER lpdsbuffer;
LPVOID lpvWrite;
DWORD  dwLength;
LPDIRECTSOUND lpds;

WAVEFORMATEX wf;
DSBUFFERDESC dsbdesc;

int main()
{
CoInitialize(NULL);

//Create DirectSound object...
if ( FAILED ( DirectSoundCreate ( NULL , &lpds , NULL ) ) ) return 2;

//Get console window handle, feed it to the SetCooperativeLevel function
SetConsoleTitle( ( LPCWSTR ) L"Title" );
hwnd = FindWindow( NULL, ( LPCWSTR ) L"Title" );
if ( FAILED ( lpds->SetCooperativeLevel( hwnd , DSSCL_NORMAL ) ) ) return 8;

//Setup buffer...
memset ( &dsbdesc, 0, sizeof ( DSBUFFERDESC ) );
dsbdesc.dwSize = sizeof ( DSBUFFERDESC );
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbdesc.dwBufferBytes = 0;     //must be 0 for primary buffer
dsbdesc.lpwfxFormat = NULL;    //must be null for primary buffer

//Wave format...
memset ( &wf, 0, sizeof ( WAVEFORMATEX ) );
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = 2;
wf.nSamplesPerSec = 44100;
wf.wBitsPerSample = 16;
wf.nBlockAlign = 4;
wf.nAvgBytesPerSec = 176400;

//Create buffer...
if ( FAILED ( lpds->CreateSoundBuffer( &dsbdesc , &lpdsbuffer , NULL )   ) return 3;

//Set buffer WaveFormat...
lpdsbuffer->SetFormat ( &wf );

//------------------------------------------------

system("PAUSE");

return 0;
}


What do I need to do next to get a simple buzzing noise to play?

How would I vary the frequency of the tone on the fly?

#### 7 Replies

0
167 Nov 27, 2008 at 22:55

First of all, if you just want to generate a simple tone, I would go ahead and set wf.nChannels to 1. That way, it will be mono and you won’t have to worry about the extra complication of stereo (not that stereo is very complicated).

Second, in DirectSound you can’t play sounds directly with the primary buffer, so you’ll need to construct a secondary buffer. You will write data into the secondary buffer and then instruct DirectSound to play it. Actually it can be useful to have two secondary buffers so you can generate sound into one while playing the other. But this might not be necessary for your application.

As for generating a sound, you’ll need to use a sine wave. Something like this:

#include <cmath>
...
short * pBuffer;    // the locked secondary buffer to generate into
int bufferLength;    // number of samples in buffer
...
float frequency = 440.0f;    // frequency in Hz.  440 = concert A.
float volume = 0.5f;    // volume as fraction of the maximum possible volume
for (int i = 0; i < bufferLength; ++i)
{
// calculate sine wave
float time = i / 44100.0f;    // calculate time in seconds
float wave = volume * sinf(2 * M_PI * time * frequency);

// convert it to a short to store in the sound buffer
pBuffer[i] = static_cast<short>(wave * 32768.0f);
}


This should produce a tone you can hear when the buffer plays. You can alter the frequency and volume simply by changing the appropriate variables and regenerating the buffer.

You can also experiment with different shapes of waves, e.g. using a square, triangle, or sawtooth wave instead of a sine, to see what effect it has on the sound. Also, you can experiment with adding sines of different frequencies together.

0
101 Nov 28, 2008 at 16:15

Okay, this is the code so far (I basically copied and pasted it from another thread on this forum). It plays a tone.

#include "stdafx.h"

#include <dsound.h>
#define _USE_MATH_DEFINES
#define _WIN32_DCOM
#include <cmath>
#include <string>
#include <iostream>
#include <time.h>

using namespace std;

LPDIRECTSOUND8 lpds;
LPDIRECTSOUNDBUFFER lpdsbuffer;
WAVEFORMATEX wfx;
DSBUFFERDESC dsbdesc;
HWND hwnd;
short* buffer = new short[ 44100 * 2 ];
bool playing = false;

int SampleRate = 44100;

//*************************
//
//*************************
HWND GetConsoleHwnd( void )
{
SetConsoleTitle( ( LPCWSTR ) L"Title" );
hwnd = FindWindow( NULL, ( LPCWSTR ) L"Title" );
return(hwnd);
}

//*************************
//
//*************************
HRESULT createSoundObject( void )
{
HRESULT hr;
hr = DirectSoundCreate8( NULL , &lpds , NULL );
hr = CoInitializeEx( NULL , 0 );
hr = lpds->SetCooperativeLevel( hwnd , DSSCL_NORMAL );
return hr;
}

//*************************
//
//*************************
WAVEFORMATEX setWaveFormat( void )
{
WAVEFORMATEX wfx;
memset( &wfx, 0, sizeof( WAVEFORMATEX ) );
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.wBitsPerSample = 16;
wfx.nSamplesPerSec = SampleRate; //44100
wfx.nBlockAlign = 4;
wfx.nAvgBytesPerSec = SampleRate * 4;
return wfx;
}

//*************************
//
//*************************
DSBUFFERDESC setBufferDescription()
{
DSBUFFERDESC dsbdesc;
memset( &dsbdesc, 0, sizeof ( DSBUFFERDESC ) );
dsbdesc.dwSize = sizeof ( DSBUFFERDESC );
dsbdesc.dwFlags = DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_GETCURRENTPOSITION2;
dsbdesc.dwBufferBytes = 1024;
dsbdesc.lpwfxFormat = &wfx;//<- Set wave format
return dsbdesc;
}

//*************************
//
//*************************
HRESULT createSecondarySoundBuffer( void )
{
HRESULT hr = lpds->CreateSoundBuffer( &dsbdesc , &lpdsbuffer , NULL );
return hr;
}

//*************************
//
//*************************
HRESULT fillSoundBuffer( bool first )
{
bool firsthalf = first;
LPVOID lpvWrite;
DWORD  dwLength;
HRESULT hr = lpdsbuffer->Lock( 0 , dsbdesc.dwBufferBytes , &lpvWrite , &dwLength , NULL , NULL , DSBLOCK_ENTIREBUFFER );
if ( SUCCEEDED ( hr ) )
{
memcpy( lpvWrite , buffer , dwLength );
hr = lpdsbuffer->Unlock( lpvWrite , dwLength , NULL , NULL );
}
return hr;
}

//*************************
//
//*************************
BOOL fillBufferWithWaveForm( string form ){

if( form == "sine" )
{
short* ptr = buffer;
unsigned short i;
float amp = 1.0f;
float tmp;
short output;
for( i=0 ; i < 44100 ; i++ )
{
tmp = amp * sinf(100.0f * 2.0f * M_PI * ( ( float ) i / 44100.0f ) );
output = ( short )( tmp*32767 );
ptr[0] = output;
ptr[1] = output;
ptr += 2;
}
return true;
}
}

//*************************
//
//*************************
void SetupSound()
{
HRESULT hr;
BOOL formFound = false;
string waveform = "sine";

// Rename the ConsoleWindow and retrieve its
// handle by looking up its new name in Windows
hwnd = GetConsoleHwnd();

// Create the Basic LPDIRECTSOUND8 object
hr = createSoundObject();

// Set the default wave format (hard coded)
wfx = setWaveFormat();

// Set the default buffer description (hard coded)
dsbdesc = setBufferDescription();

// Create the secondary sound buffer
hr = createSecondarySoundBuffer();

// Generate a waveform and put it into the playback array
formFound = fillBufferWithWaveForm(waveform);

// fill the secondary sound buffer entirely with the playback array data
hr = fillSoundBuffer( true );

// Start playback at position 0 and loop this sound until explicitely stopped
lpdsbuffer->SetCurrentPosition( 0 );
//DSBPLAY_DEFAULT
//DSBPLAY_LOOPING
}

//*************************
//
//*************************
int _tmain( int argc , char* argv[] )
{

SetupSound();
lpdsbuffer->Play( 0 , 0 , DSBPLAY_LOOPING );

lpdsbuffer->SetFrequency(1300);//<---- not working!!!

system("PAUSE");
lpdsbuffer->Release();
lpds->Release();
return 0;
}


However I can’t seem to get the frequency to change by calling lpdsbuffer->SetFrequency(). Is this some bug with DirectSound?

0
167 Nov 28, 2008 at 19:07

SetFrequency() is intended to change the sample rate with which the buffer is played back. In your case, SetFrequency(44100) would play it at the original frequency at which the wave is generated; SetFrequency(88200) would play it at twice the frequency, etc.

There are limits on how much you can change the frequency using this method. You should look at this page to see how to find the limits.

In any case, it’s better to change the frequency by regenerating the buffer rather than using SetFrequency. See my previous post for details.

0
101 Nov 29, 2008 at 21:37

I can’t get SetFrequency to do anything at all. Incrementing the data fed into the buffer with a for-next loop doesn’t produce a smooth transition.

0
167 Nov 29, 2008 at 22:20

What do you mean by “does something strange”?

Varying the frequency really is quite simple, you just need to slow down and take the time to look for bugs and figure out *why* something doesn’t work rather than abandoning it and trying something completely different.

Now, let’s forget about SetFrequency for the moment and just focus on generating sine waves ourselves. If you look at the code you posted, the “fillBufferWithWaveForm” function is doing almost exactly what the code I posted does, only the frequency is a constant 100.0f.

So, how about you start with the code you posted, make sure it works and you can run it and hear the sound (it should be a very low note), then just change that 100.0f to a 200.0f and run it again. You should hear a higher note (an octave higher to be precise).

0
101 Nov 29, 2008 at 22:25

I am currently using and was refering to your code. It does work, but not as I expected.

The reason why I need SetFrequency is that when I try something like:

lpdsbuffer->Play( 0 , 0 , DSBPLAY_LOOPING );

for ( int i=0 ; i < 500 ; i++ )
{
Sleep(1);
frequency += 5;
formFound = fillBufferWithWaveForm(waveform);
hr = fillSoundBuffer( true );
}


There is not the smooth transition between frequencies which I imagine the DirectSound code would provide.

0
167 Nov 29, 2008 at 22:53

Okay. The problem with the smooth transition is something that can be dealt with.

I think the problem is likely that when you change frequencies, the end of the sine wave from one frequency doesn’t match the beginning of the sine wave from the next frequency. There is a sudden jump in the amplitude, which can result in a clicking or popping sound as the frequency changes.

To solve this you’ll have to do two things. First of all, you’ll need to use two secondary buffers, and “ping-pong” between them - that is, set them up so they are played one after the other, and continuously write data into the one that is not playing. The reason for this is that if you have just one buffer, you have to overwrite it while it’s playing (as you’re currently doing), and with no idea where the “play head” is, there’s no way to make the ends of the two sines match up. I don’t know exactly how to set up the two buffers this way in DirectSound, but I presume it lets you set up a callback function to be called when it finishes with a buffer.

The second thing you have to do use use a phase accumulator, a global variable that tracks the phase of the sine and allows it to be maintained across buffers and across frequency changes. Your FillBuffer function would then look something like this:

float phase = 0;    // global variable
...
for (int i = 0; i < bufferLength; ++i)
{
// calculate sine wave
float wave = volume * sinf(phase);
phase += 2 * M_PI / 44100.0f * frequency;

// convert it to a short to store in the sound buffer
pBuffer[i] = static_cast<short>(wave * 32768.0f);
}


This way the phase will stay continuous despite any changes in frequency.