Files
itgmania212121/stepmania/src/arch/Sound/DSoundHelpers.cpp
T

490 lines
14 KiB
C++
Raw Normal View History

2003-02-16 04:02:21 +00:00
#include "../../global.h"
2002-12-13 08:33:25 +00:00
#include "DSoundHelpers.h"
2002-12-21 07:21:49 +00:00
#include "../../RageUtil.h"
#include "../../RageLog.h"
2002-12-13 08:33:25 +00:00
2004-01-17 05:21:24 +00:00
#if defined(_WINDOWS)
2003-11-14 17:17:36 +00:00
#include <mmsystem.h>
2004-01-17 05:21:24 +00:00
#endif
2003-02-20 21:57:28 +00:00
#define DIRECTSOUND_VERSION 0x0700
2002-12-21 07:21:49 +00:00
#include <dsound.h>
2003-01-08 00:44:17 +00:00
#include <math.h>
2002-12-21 07:37:34 +00:00
#pragma comment(lib, "dsound.lib")
2004-03-05 02:41:43 +00:00
BOOL CALLBACK DSound::EnumCallback( LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext )
2003-02-17 02:37:38 +00:00
{
2004-03-05 02:41:43 +00:00
LOG->Info( "DirectSound Driver: %s (%s)", lpcstrDescription, lpcstrModule );
2003-02-17 02:37:38 +00:00
if(lpGuid)
2004-03-05 02:41:43 +00:00
LOG->Info( " ID: {%8.8x-%4.4x-%4.4x-%6.6x}", lpGuid->Data1, lpGuid->Data2, lpGuid->Data3, lpGuid->Data4 );
2003-02-17 02:37:38 +00:00
return TRUE;
}
void DSound::SetPrimaryBufferMode()
{
#ifndef _XBOX
DSBUFFERDESC format;
2004-03-05 02:41:43 +00:00
memset( &format, 0, sizeof(format) );
format.dwSize = sizeof(format);
format.dwFlags = DSBCAPS_PRIMARYBUFFER;
format.dwBufferBytes = 0;
format.lpwfxFormat = NULL;
IDirectSoundBuffer *buf;
HRESULT hr = this->GetDS()->CreateSoundBuffer(&format, &buf, NULL);
/* hr */
if( FAILED(hr) )
{
LOG->Warn(hr_ssprintf(hr, "Couldn't create primary buffer"));
return;
}
WAVEFORMATEX waveformat;
waveformat.cbSize = 0;
waveformat.wFormatTag = WAVE_FORMAT_PCM;
waveformat.wBitsPerSample = WORD(16);
waveformat.nChannels = WORD(2);
waveformat.nSamplesPerSec = DWORD(44100);
waveformat.nBlockAlign = WORD(4);
waveformat.nAvgBytesPerSec = 44100 * waveformat.nBlockAlign;
// Set the primary buffer's format
hr = IDirectSoundBuffer_SetFormat( buf, &waveformat );
if( FAILED(hr) )
2004-03-05 02:41:43 +00:00
LOG->Warn( hr_ssprintf(hr, "SetFormat on primary buffer") );
2003-07-31 01:11:42 +00:00
/* MS docs:
*
* When there are no sounds playing, DirectSound stops the mixer engine and halts DMA
* (direct memory access) activity. If your application has frequent short intervals of
* silence, the overhead of starting and stopping the mixer each time a sound is played
* may be worse than the DMA overhead if you kept the mixer active. Also, some sound
* hardware or drivers may produce unwanted audible artifacts from frequent starting and
* stopping of playback. If your application is playing audio almost continuously with only
* short breaks of silence, you can force the mixer engine to remain active by calling the
* IDirectSoundBuffer::Play method for the primary buffer. The mixer will continue to run
* silently.
*
* However, I just added the above code and I don't want to change more until it's tested.
*/
// buf->Play(0, 0, DSBPLAY_LOOPING);
buf->Release();
#endif
}
2003-02-17 02:37:38 +00:00
2002-12-21 07:21:49 +00:00
DSound::DSound()
2002-12-13 08:33:25 +00:00
{
2002-12-21 07:21:49 +00:00
HRESULT hr;
2002-12-13 08:33:25 +00:00
#ifndef _XBOX
// Initialize COM
if( FAILED( hr = CoInitialize( NULL ) ) )
RageException::ThrowNonfatal(hr_ssprintf(hr, "CoInitialize"));
#endif
// Create IDirectSound using the primary sound device
if( FAILED( hr = DirectSoundCreate( NULL, &ds, NULL ) ) )
RageException::ThrowNonfatal(hr_ssprintf(hr, "DirectSoundCreate"));
#ifndef _XBOX
2003-02-17 02:37:38 +00:00
DirectSoundEnumerate(EnumCallback, 0);
2002-12-21 07:21:49 +00:00
/* Try to set primary mixing privileges */
2004-03-05 02:41:43 +00:00
hr = ds->SetCooperativeLevel( GetDesktopWindow(), DSSCL_PRIORITY );
#endif
SetPrimaryBufferMode();
2002-12-13 08:33:25 +00:00
}
2002-12-21 07:21:49 +00:00
DSound::~DSound()
2002-12-13 08:33:25 +00:00
{
ds->Release();
#ifndef _XBOX
CoUninitialize();
#endif
2002-12-21 07:21:49 +00:00
}
bool DSound::IsEmulated() const
{
#ifndef _XBOX
2002-12-21 07:21:49 +00:00
/* Don't bother wasting time trying to create buffers if we're
* emulated. This also gives us better diagnostic information. */
DSCAPS Caps;
Caps.dwSize = sizeof(Caps);
HRESULT hr;
2004-03-05 02:41:43 +00:00
if( FAILED(hr = ds->GetCaps(&Caps)) )
2002-12-21 07:21:49 +00:00
{
2004-03-05 02:41:43 +00:00
LOG->Warn( hr_ssprintf(hr, "ds->GetCaps failed") );
2002-12-21 07:21:49 +00:00
/* This is strange, so let's be conservative. */
return true;
}
return !!(Caps.dwFlags & DSCAPS_EMULDRIVER);
#else
return false;
#endif
2002-12-21 07:21:49 +00:00
}
2004-03-05 02:41:43 +00:00
DSoundBuf::DSoundBuf( DSound &ds, DSoundBuf::hw hardware,
int channels_, int samplerate_, int samplebits_, int writeahead_ )
2002-12-21 07:21:49 +00:00
{
channels = channels_;
samplerate = samplerate_;
samplebits = samplebits_;
2004-02-15 04:34:51 +00:00
writeahead = writeahead_ * bytes_per_frame();
2004-01-22 06:55:18 +00:00
volume = 0;
2002-12-21 07:21:49 +00:00
buffer_locked = false;
2004-01-19 22:21:57 +00:00
last_cursor_pos = write_cursor = buffer_bytes_filled = 0;
LastPosition = 0;
playing = false;
2002-12-21 07:21:49 +00:00
2002-12-31 01:08:35 +00:00
/* The size of the actual DSound buffer. This can be large; we generally
* won't fill it completely. */
buffersize = 1024*64;
2004-01-22 23:04:59 +00:00
buffersize = max( buffersize, writeahead );
2002-12-21 07:21:49 +00:00
2002-12-13 08:33:25 +00:00
WAVEFORMATEX waveformat;
2004-03-05 02:41:43 +00:00
memset( &waveformat, 0, sizeof(waveformat) );
2002-12-13 08:33:25 +00:00
waveformat.cbSize = sizeof(waveformat);
waveformat.wFormatTag = WAVE_FORMAT_PCM;
2003-04-25 04:10:18 +00:00
bool NeedCtrlFrequency = false;
2004-03-05 02:41:43 +00:00
if( samplerate == DYNAMIC_SAMPLERATE )
2003-04-25 04:10:18 +00:00
{
samplerate = 44100;
NeedCtrlFrequency = true;
}
2002-12-21 07:21:49 +00:00
int bytes = samplebits/8;
waveformat.wBitsPerSample = WORD(samplebits);
2002-12-13 08:33:25 +00:00
waveformat.nChannels = WORD(channels);
waveformat.nSamplesPerSec = DWORD(samplerate);
waveformat.nBlockAlign = WORD(bytes*channels);
waveformat.nAvgBytesPerSec = samplerate * bytes*channels;
2002-12-21 07:21:49 +00:00
/* Try to create the secondary buffer */
DSBUFFERDESC format;
2004-03-05 02:41:43 +00:00
memset( &format, 0, sizeof(format) );
2002-12-21 07:21:49 +00:00
format.dwSize = sizeof(format);
#ifdef _XBOX
2003-11-13 00:39:36 +00:00
format.dwFlags = 0;
#else
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
#endif
#ifndef _XBOX
2003-01-08 01:36:06 +00:00
/* Don't use DSBCAPS_STATIC. It's meant for static buffers, and we
* only use streaming buffers. */
2004-03-05 02:41:43 +00:00
if( hardware == HW_HARDWARE )
2002-12-21 07:21:49 +00:00
format.dwFlags |= DSBCAPS_LOCHARDWARE;
2003-01-08 01:36:06 +00:00
else
2002-12-21 07:21:49 +00:00
format.dwFlags |= DSBCAPS_LOCSOFTWARE;
#endif
2004-03-05 02:41:43 +00:00
if( NeedCtrlFrequency )
2003-04-25 04:10:18 +00:00
format.dwFlags |= DSBCAPS_CTRLFREQUENCY;
2002-12-21 07:21:49 +00:00
format.dwBufferBytes = buffersize;
#ifndef _XBOX
2002-12-21 07:21:49 +00:00
format.dwReserved = 0;
2003-11-13 00:39:36 +00:00
#else
2004-03-05 02:41:43 +00:00
DSMIXBINVOLUMEPAIR dsmbvp[8] =
{
{ DSMIXBIN_FRONT_LEFT, DSBVOLUME_MAX}, // left channel
{ DSMIXBIN_FRONT_RIGHT, DSBVOLUME_MAX}, // right channel
{ DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX}, // left channel
{ DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX}, // right channel
{ DSMIXBIN_BACK_LEFT, DSBVOLUME_MAX}, // left channel
{ DSMIXBIN_BACK_RIGHT, DSBVOLUME_MAX}, // right channel
{ DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX}, // left channel
{ DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX} // right channel
};
2003-11-13 00:39:36 +00:00
DSMIXBINS dsmb;
dsmb.dwMixBinCount = 8;
dsmb.lpMixBinVolumePairs = dsmbvp;
format.lpMixBins = &dsmb;
#endif
2003-11-13 00:39:36 +00:00
2002-12-21 07:21:49 +00:00
format.lpwfxFormat = &waveformat;
2004-03-05 02:41:43 +00:00
HRESULT hr = ds.GetDS()->CreateSoundBuffer( &format, &buf, NULL );
if( FAILED(hr) )
RageException::ThrowNonfatal( hr_ssprintf(hr, "CreateSoundBuffer failed") );
2002-12-21 07:21:49 +00:00
#ifndef _XBOX
2003-01-08 01:36:06 +00:00
/* I'm not sure this should ever be needed, but ... */
DSBCAPS bcaps;
bcaps.dwSize=sizeof(bcaps);
hr = buf->GetCaps(&bcaps);
2004-03-05 02:41:43 +00:00
if( FAILED(hr) )
2003-01-08 01:36:06 +00:00
RageException::Throw(hr_ssprintf(hr, "buf->GetCaps"));
2004-03-05 02:41:43 +00:00
if( int(bcaps.dwBufferBytes) != buffersize )
2003-01-08 01:36:06 +00:00
{
2004-03-05 02:41:43 +00:00
LOG->Warn( "bcaps.dwBufferBytes (%i) != buffersize(%i); adjusting", bcaps.dwBufferBytes, buffersize );
2003-01-08 01:36:06 +00:00
buffersize = bcaps.dwBufferBytes;
2004-03-05 02:41:43 +00:00
writeahead = min( writeahead, buffersize );
2003-01-08 01:36:06 +00:00
}
#endif
}
2003-04-25 04:10:18 +00:00
void DSoundBuf::SetSampleRate(int hz)
{
samplerate = hz;
2004-03-05 02:41:43 +00:00
HRESULT hr = buf->SetFrequency( hz );
if( FAILED(hr) )
RageException::Throw( hr_ssprintf(hr, "buf->SetFrequency(%i)", hz) );
2003-04-25 04:10:18 +00:00
}
void DSoundBuf::SetVolume(float vol)
{
ASSERT(vol >= 0);
ASSERT(vol <= 1);
2003-02-19 23:49:57 +00:00
if( vol == 0 )
vol = 0.001f; // fix log10f(0) == -INF
2003-01-08 00:44:17 +00:00
float vl2 = log10f(vol) / log10f(2); /* vol log 2 */
2004-01-22 06:55:18 +00:00
/* Volume is a multiplier; SetVolume wants attenuation in thousands of a decibel. */
const int new_volume = max( int(1000 * vl2), DSBVOLUME_MIN );
if( volume == new_volume )
return;
volume = new_volume;
buf->SetVolume( volume );
2002-12-21 07:21:49 +00:00
}
/* Determine if "pos" is between "start" and "end", for a circular buffer. */
2004-03-05 02:41:43 +00:00
static bool contained( int start, int end, int pos )
{
2004-03-05 02:41:43 +00:00
if( end >= start ) /* start ... pos ... end */
return start <= pos && pos <= end;
else
return pos >= start || pos <= end;
}
2002-12-21 07:21:49 +00:00
DSoundBuf::~DSoundBuf()
{
buf->Release();
}
2004-03-05 02:41:43 +00:00
bool DSoundBuf::get_output_buf( char **buffer, unsigned *bufsiz, int chunksize )
2002-12-21 07:21:49 +00:00
{
ASSERT(!buffer_locked);
2004-02-15 04:34:51 +00:00
chunksize *= bytes_per_frame();
2004-01-31 02:36:10 +00:00
DWORD cursorstart, cursorend;
2002-12-21 07:21:49 +00:00
HRESULT result;
/* It's easiest to think of the cursor as a block, starting and ending at
* the two values returned by GetCurrentPosition, that we can't write to. */
2004-03-05 02:41:43 +00:00
result = buf->GetCurrentPosition( &cursorstart, &cursorend );
#ifndef _XBOX
if ( result == DSERR_BUFFERLOST )
{
2002-12-21 07:21:49 +00:00
buf->Restore();
2004-03-05 02:41:43 +00:00
result = buf->GetCurrentPosition( &cursorstart, &cursorend );
2002-12-21 07:21:49 +00:00
}
if ( result != DS_OK )
{
2004-03-05 02:41:43 +00:00
LOG->Warn( hr_ssprintf(result, "DirectSound::GetCurrentPosition failed") );
2002-12-21 07:21:49 +00:00
return false;
}
#endif
2002-12-21 07:21:49 +00:00
/* Some cards (Creative AudioPCI) have a no-write area even when not playing. I'm not
* sure what that means, but it breaks the assumption that we can fill the whole writeahead
* when prebuffering. */
if( !playing )
cursorend = cursorstart;
2004-01-22 23:04:59 +00:00
/* Update buffer_bytes_filled. */
{
int first_byte_filled = write_cursor-buffer_bytes_filled;
if( first_byte_filled < 0 )
first_byte_filled += buffersize; /* unwrap */
/* The number of bytes that have been played since the last time we got here: */
int bytes_played = cursorstart - first_byte_filled;
if( bytes_played < 0 )
bytes_played += buffersize; /* unwrap */
2004-01-22 23:04:59 +00:00
buffer_bytes_filled -= bytes_played;
buffer_bytes_filled = max( 0, buffer_bytes_filled );
2004-01-22 23:04:59 +00:00
}
2002-12-21 07:21:49 +00:00
2002-12-31 01:08:35 +00:00
/* XXX We can figure out if we've underrun, and increase the write-ahead
* when it happens. Two problems:
* 1. It's ugly to wait until we actually underrun. (We could store the
* write-ahead, though.)
* 2. We don't want a random underrun (eg. virus scanner starts) to
* permanently increase our write-ahead. We want the smallest possible
* that will give us reliable audio in normal conditions. I'm not sure
* of a robust way to do this.
2002-12-31 01:08:35 +00:00
*
* Also, writeahead should be a static (all buffers write ahead the same
* amount); writeahead in the ctor should be a hint only (initial value),
* and the sound driver should query a sound to get the current writeahead
* in GetLatencySeconds().
*/
2002-12-31 01:08:35 +00:00
{
int first_byte_filled = write_cursor-buffer_bytes_filled;
2004-01-22 23:04:59 +00:00
if( first_byte_filled < 0 )
first_byte_filled += buffersize; /* unwrap */
/* Data between the play cursor and the write cursor is committed to be
2004-01-22 23:04:59 +00:00
* played. If we don't actually have data there, we've underrun.
* (If buffer_bytes_filled == buffersize, then it's full, and can't be
2004-02-15 04:34:51 +00:00
* an underrun.)
*/
/* We're already underrunning, which means the play cursor has passed valid
* data. Let's move the cursor forward. */
2004-02-25 00:14:30 +00:00
if( buffer_bytes_filled < buffersize &&
2004-01-22 23:04:59 +00:00
(!contained(first_byte_filled, write_cursor, cursorstart) ||
!contained(first_byte_filled, write_cursor, cursorend)) )
{
2003-11-01 18:10:15 +00:00
int missed_by = cursorend - write_cursor;
wrap( missed_by, buffersize );
2004-01-22 23:04:59 +00:00
LOG->Trace("underrun %p: %i..%i filled but cursor at %i..%i (missed it by %i) %i/%i",
this, first_byte_filled, write_cursor, cursorstart, cursorend,
missed_by, buffer_bytes_filled, buffersize);
/* Pretend the space between the play and write cursor is filled
* with data, and continue filling from there. */
int no_write_zone_size = cursorend - cursorstart;
if( no_write_zone_size < 0 )
no_write_zone_size += buffersize; /* unwrap */
buffer_bytes_filled = no_write_zone_size;
write_cursor = cursorend;
}
2002-12-31 01:08:35 +00:00
}
/* If we already have enough bytes written ahead, stop. */
2004-03-05 02:41:43 +00:00
if( buffer_bytes_filled > writeahead )
2002-12-31 01:08:35 +00:00
return false;
2004-01-22 23:04:59 +00:00
int num_bytes_empty = writeahead-buffer_bytes_filled;
/* num_bytes_empty is the amount of free buffer space. If it's
2002-12-21 07:21:49 +00:00
* too small, come back later. */
2004-03-05 02:41:43 +00:00
if( num_bytes_empty < chunksize )
2002-12-21 07:21:49 +00:00
return false;
/* I don't want to deal with DSound's split-circular-buffer locking stuff, so clamp
2002-12-21 07:21:49 +00:00
* the writing space at the end of the physical buffer. */
num_bytes_empty = min(num_bytes_empty, buffersize - write_cursor);
/* Don't fill more than one chunk at a time. This reduces the maximum
* amount of time until we give data; that way, if we're short on time,
* we'll give some data soon instead of lots of data later. */
num_bytes_empty = min(num_bytes_empty, chunksize);
// LOG->Trace("gave %i at %i (%i, %i) %i filled", num_bytes_empty, write_cursor, cursor, write, buffer_bytes_filled);
2002-12-31 01:08:35 +00:00
2002-12-21 07:21:49 +00:00
/* Lock the audio buffer. */
2003-11-13 00:39:36 +00:00
#ifdef _XBOX
2004-03-05 02:41:43 +00:00
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, NULL, 0 );
2003-11-13 00:39:36 +00:00
#else
2004-02-07 20:50:42 +00:00
DWORD junk;
2004-03-05 02:41:43 +00:00
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, &junk, 0 );
2003-11-13 00:39:36 +00:00
#endif
#ifndef _XBOX
2004-03-05 02:41:43 +00:00
if ( result == DSERR_BUFFERLOST )
{
2002-12-21 07:21:49 +00:00
buf->Restore();
2004-03-05 02:41:43 +00:00
result = buf->Lock( write_cursor, num_bytes_empty, (LPVOID *)buffer, (DWORD *) bufsiz, NULL, &junk, 0 );
2002-12-21 07:21:49 +00:00
}
#endif
2004-03-05 02:41:43 +00:00
if ( result != DS_OK )
{
LOG->Warn( hr_ssprintf(result, "Couldn't lock the DirectSound buffer.") );
2002-12-21 07:21:49 +00:00
return false;
}
write_cursor += num_bytes_empty;
2004-03-05 02:41:43 +00:00
if( write_cursor >= buffersize )
write_cursor -= buffersize;
2002-12-21 07:21:49 +00:00
buffer_bytes_filled += num_bytes_empty;
2002-12-21 07:21:49 +00:00
/* Increment last_cursor_pos to point at where the data we're about to
* ask for will actually be played. */
2004-01-12 02:19:56 +00:00
last_cursor_pos += num_bytes_empty / bytes_per_frame();
2002-12-21 07:21:49 +00:00
buffer_locked = true;
return true;
}
2004-03-05 02:41:43 +00:00
void DSoundBuf::release_output_buf( char *buffer, unsigned bufsiz )
2002-12-21 07:21:49 +00:00
{
2004-03-05 02:41:43 +00:00
buf->Unlock( buffer, bufsiz, NULL, 0 );
2002-12-21 07:21:49 +00:00
buffer_locked = false;
}
2004-01-19 22:21:57 +00:00
int64_t DSoundBuf::GetPosition() const
2002-12-21 07:21:49 +00:00
{
DWORD cursor, junk;
2004-03-05 02:41:43 +00:00
buf->GetCurrentPosition( &cursor, &junk );
2004-01-12 02:19:56 +00:00
int cursor_frames = int(cursor) / bytes_per_frame();
int write_cursor_frames = write_cursor / bytes_per_frame();
2002-12-21 07:21:49 +00:00
2002-12-29 00:19:05 +00:00
int frames_behind = write_cursor_frames - cursor_frames;
2004-01-19 22:21:57 +00:00
/* frames_behind will be 0 if we're called before the buffer starts playing:
* both write_cursor_frames and cursor_frames will be 0. */
if( frames_behind < 0 )
2002-12-21 07:21:49 +00:00
frames_behind += buffersize_frames(); /* unwrap */
2004-01-19 22:21:57 +00:00
int64_t ret = last_cursor_pos - frames_behind;
2002-12-21 07:21:49 +00:00
/* Failsafe: never return a value smaller than we've already returned.
* This can happen once in a while in underrun conditions. */
2004-03-05 02:41:43 +00:00
ret = max( LastPosition, ret );
2002-12-21 07:21:49 +00:00
LastPosition = ret;
return ret;
}
void DSoundBuf::Play()
{
if( playing )
return;
2004-03-05 02:41:43 +00:00
buf->Play( 0, 0, DSBPLAY_LOOPING );
playing = true;
2002-12-21 07:21:49 +00:00
}
void DSoundBuf::Stop()
{
if( !playing )
return;
2002-12-21 07:21:49 +00:00
buf->Stop();
buf->SetCurrentPosition(0);
2004-02-18 08:23:33 +00:00
2004-01-19 22:21:57 +00:00
last_cursor_pos = write_cursor = buffer_bytes_filled = 0;
2004-01-20 08:41:20 +00:00
LastPosition = 0;
2004-02-18 08:23:33 +00:00
/* When stopped and rewound, the play and write cursors should both be 0. */
DWORD play, write;
buf->GetCurrentPosition( &play, &write );
RAGE_ASSERT_M( play == 0 && write == 0, ssprintf("%i, %i", play, write) );
playing = false;
2002-12-13 08:33:25 +00:00
}
/*
* Copyright (c) 2002 by the person(s) listed below. All rights reserved.
*
* Glenn Maynard
*/