winamp/Src/Plugins/Output/out_ds/ds2.cpp

1372 lines
27 KiB
C++

//#define USE_LOG
//^^ for debug logging to c:\ds2.txt
#include "ds2.h"
#include <dsound.h>
#include <math.h>
#include <ks.h>
#include "ksmedia.h"
#include "../winamp/wa_ipc.h"
extern Out_Module mod;
static const int kMaxChannelsToMask = 8;
static const unsigned int kChannelsToMask[kMaxChannelsToMask + 1] =
{
0,
// 1 = Mono
SPEAKER_FRONT_CENTER,
// 2 = Stereo
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
// 3 = Stereo + Center
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER,
// 4 = Quad
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
// 5 = 5.0
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
// 6 = 5.1
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
// 7 = 6.1
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
SPEAKER_BACK_CENTER,
// 8 = 7.1
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT |
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY |
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT
// Add additional masks for 7.2 and beyond.
};
DS2::tDirectSoundCreate DS2::pDirectSoundCreate = 0;
#ifdef DS2_HAVE_DEVICES
DS2::tDirectSoundEnumerate DS2::pDirectSoundEnumerate = 0;
#endif
static UINT refresh_timer = 10;
static const GUID NULL_GUID;
HRESULT DS2::myDirectSoundCreate(const GUID* g, IDirectSound** out)
{
HRESULT r;
try
{
r = DS2::pDirectSoundCreate((!g || *g == NULL_GUID) ? (const GUID*)0 : g, out, 0);
}
catch (...)
{
*out = 0;
r = DSERR_GENERIC;
}
return r;
}
#define ftest(X) (!!(flags & FLAG_##X))
#define fset(X) flags|=FLAG_##X
#define funset(X) flags&=~FLAG_##X
#define fsetc(X,Y) {if (Y) fset(X); else funset(X);}
static HINSTANCE hdsound;
static bool g_delayed_deinit = 1;
static __int64 g_total_time;
static HANDLE g_hEvent;
static CriticalSection g_sync;
static bool g_quitting, g_quitting_waiting;
#define SYNCFUNC T_SYNC SYNC(g_sync);
void DS2::SYNC_IN() { g_sync.Enter(); }
void DS2::SYNC_OUT() { g_sync.Leave(); }
static DWORD last_rel_time;
static DWORD coop_mode;
IDirectSound* DS2::pDS = 0;
static IDirectSoundBuffer* pPrimary;
static UINT prim_bps, prim_nch, prim_sr;
static GUID cur_dev;
static DS2* ds2s = nullptr;
static HANDLE g_hThread;
static bool create_primary = 0;
#ifdef USE_LOG
static void _log_write(char* msg, DS2* foo)
{
char tmp[512];
SYSTEMTIME st;
GetSystemTime(&st);
wsprintf(tmp, "DS2: %02u:%02u.%03u %08x %s\n", st.wMinute, st.wSecond, st.wMilliseconds, foo, msg);
#if 1
static HANDLE hLog;
if (!hLog) hLog = CreateFile("c:\\ds2.txt", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
DWORD bw = 0;
WriteFile(hLog, tmp, strlen(tmp), &bw, 0);
#else
OutputDebugString(tmp);//bleh flood, getting holes in the log, blame micro$oft
#endif
}
#define log_write(x) _log_write(x,this)
#else
#define _log_write(x,y)
#define log_write(x)
#endif
static int calc_silence(float _db, int bps)//_db is in -0.1db
{
return (int)(pow(10.0, _db / (-20.0)) * pow(2.0, (double)bps));
}
void DS2::test_silence(char* buf, int len, int* first, int* last)
{
int bps = fmt_bps >> 3;
if (bps > 4 || bps < 1)
{
if (first) *first = 0;
if (last) *last = (len - fmt_nch * bps);
return;
}
int ptr = 0;
while (ptr < len)
{
int p = ptr;
UINT n;
for (n = 0; n < fmt_nch; n++)
{
int s;
void* _p = buf + p;
switch (bps)
{
case 1:
s = (UINT) * (BYTE*)_p - 0x80;
break;
case 2:
s = *(short*)_p;
break;
case 3:
{
long poo = 0;
memcpy(&poo, _p, 3);
if (poo & 0x800000) poo |= 0xFF000000;
s = poo;
}
break;
case 4:
s = *(long*)_p;
break;
}
if (s < 0) s = -s;
if (s > silence_delta)
{
if (first && *first < 0) *first = ptr;
if (last) *last = ptr;
}
p += bps;
}
ptr = p;
}
}
DS2::DS2(DS2config* cfg) : BlockList(cfg->bps == 8 ? 0x80 : 0)
#ifdef DS2_HAVE_FADES
, VolCtrl(cfg->volmode, cfg->logvol_min, cfg->logfades)
#endif
{
#ifdef _DEBUG
srand(GetTickCount());
serial = rand();
sync_n = 0;
#endif
next = ds2s;
ds2s = this;
wait = 0;
flags = 0;
LockCount = 0;
Underruns = 0;
fsetc(USE_CPU_MNGMNT, cfg->use_cpu_management);
refresh_timer = cfg->refresh;
if (refresh_timer < 1) refresh_timer = 1;
else if (refresh_timer > 100) refresh_timer = 100;
pDSB = 0;
myDS = 0;
}
DS2::~DS2()
{
log_write("~DS2");
SYNC_IN();
ds_kill();
SYNC_OUT();
}
DS2* DS2::Create(DS2config* cfg)
{
Init();
if (!hdsound) return 0;
_log_write("Create", 0);
SYNC_IN();
DS2* r = new DS2(cfg);
if (!r->Open(cfg))
{
delete r;
r = 0;
}
else SetEvent(g_hEvent);//wake update thread up
SYNC_OUT();
return r;
}
void DS2::ds_kill()
{
if (wait)
{
delete wait;
wait = 0;
}
if (pDSB)
{
if (ftest(PLAYING) && !ftest(PAUSED)) pDSB->Stop();
pDSB->Release();
pDSB = 0;
last_rel_time = GetTickCount();
}
if (myDS)
{
myDS->Release();
myDS = 0;
}
do_reset_vars();
//UGLY moved from destructor
DS2* foo = ds2s;
DS2** foo2 = &ds2s;
while (foo)
{
if (foo == this) { *foo2 = next; break; }
foo2 = &foo->next; foo = *foo2;
}
}
int DS2::WriteData(void* _data, UINT size, bool* killswitch)
{//note: calling code may or may not care about CanWrite() (but if they do, we wont sleep)
if (ftest(PAUSED)) return 0;
log_write("entering writedata");
char* data = (char*)_data;
size = _align_var(size);//avoid evil shit
SYNC_IN();
if (silence_delta >= 0)//no need to sync this
{
if (ftest(STARTSIL))
{
int first = -1;
test_silence(data, size, &first, 0);
if (first >= 0)
{
size -= first;
data += first;
funset(STARTSIL);
}
else
{
log_write("block was silent, leaving writedata");
SYNC_OUT();
return 1;
}
}
int last = -1;
test_silence(data, size, 0, &last);
if (last != -1)
{
log_write("WriteData / last_nonsil update");
last_nonsil = last + data_written + BlockList.DataSize();
}
}
log_write("WriteData");
BlockList.AddBlock(data, size);
if (data_buffered < clear_size) SetEvent(g_hEvent);
else while (!*killswitch && CanWrite() < 0)
{
SYNC_OUT();
Sleep(1);
log_write("WriteData");
SYNC_IN();
}
SYNC_OUT();
log_write("writedata done");
return 1;
}
int DS2::WriteDataNow(void* data, UINT size)
{
log_write("WriteDataNow");
SYNC_IN();
int cw = CanWrite();
int rv = 0;
if (cw > 0)
{
if (size > (UINT)cw) size = (UINT)cw;
if (ForceWriteData(data, size)) rv = size;
}
SYNC_OUT();
return rv;
}
int DS2::ForceWriteData(void* data, UINT size)
{
log_write("ForceWriteData");
SYNC_IN();
bool killswitch = 1;
int r = WriteData(data, size, &killswitch);
SYNC_OUT();
return r;
}
DWORD WINAPI DS2::ThreadFunc(void* zzz)
{
_log_write("ThreadFunc", 0);
SYNC_IN();
while (1)
{
DS2* foo = ds2s;
while (foo)
{
foo->flags &= ~FLAG_UPDATED;
foo = foo->next;
}
foo = ds2s;
while (foo)
{
if (!(foo->flags & FLAG_UPDATED) && foo->Update())
{//one *or more* of instances got deleted
foo = ds2s;
}
else
{
foo->flags |= FLAG_UPDATED;
foo = foo->next;
}
}
DWORD t = ds2s ? refresh_timer : (pDS ? 1000 : -1);
SYNC_OUT();
WaitForSingleObject(g_hEvent, t);
//use g_hEvent to wake thread up when something's going on
_log_write("ThreadFunc", 0);
SYNC_IN();
if (g_quitting) break;
if (!ds2s && pDS)
{
if (pPrimary) { pPrimary->Release(); pPrimary = 0; }
if (!g_delayed_deinit || GetTickCount() - last_rel_time > 3000)
{
pDS->Release();
pDS = 0;
}
}
}
while (ds2s) delete ds2s;
if (pPrimary) { pPrimary->Release(); pPrimary = 0; }
if (pDS) { pDS->Release(); pDS = 0; }
SYNC_OUT();
return 0;
}
//static void __cdecl __quit() {DS2::Quit(0);}
bool DS2::InitDLL()
{
if (!hdsound)
{
hdsound = LoadLibraryW(L"dsound.dll");
if (!hdsound) return false;//ouch
pDirectSoundCreate = (tDirectSoundCreate)GetProcAddress((HMODULE)hdsound, "DirectSoundCreate");
if (!pDirectSoundCreate) { FreeLibrary(hdsound); hdsound = 0; return false; }
#ifdef DS2_HAVE_DEVICES
pDirectSoundEnumerate = (tDirectSoundEnumerate)GetProcAddress((HMODULE)hdsound, "DirectSoundEnumerateW");
if (!pDirectSoundEnumerate) { pDirectSoundCreate = 0; FreeLibrary(hdsound); hdsound = 0; return false; }
#endif
}
return true;
}
void DS2::Init()
{
SYNC_IN();
InitDLL();
if (g_hThread || !hdsound) { SYNC_OUT(); return; }
pDS = 0;
ds2s = 0;
g_quitting = 0;
g_quitting_waiting = 0;
g_hEvent = CreateEvent(0, 0, 0, 0);
DWORD id;
g_hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, 0, 0, &id);
if (!g_hThread)
{
return;
}
else
{
SetThreadPriority(g_hThread, THREAD_PRIORITY_TIME_CRITICAL);
}
SYNC_OUT();
}
void DS2::Quit(bool wait)
{
if (!g_hThread) return;
g_quitting_waiting = 1;
if (wait) while (ds2s) Sleep(3);
g_quitting = 1;
SetEvent(g_hEvent);
WaitForSingleObject(g_hThread, INFINITE);
CloseHandle(g_hThread);
g_hThread = 0;
CloseHandle(g_hEvent);
g_hEvent = 0;
if (hdsound)
{
FreeLibrary(hdsound);
pDirectSoundCreate = 0;
#ifdef DS2_HAVE_DEVICES
pDirectSoundEnumerate = 0;
#endif
hdsound = 0;
}
}
void DS2::ds_stop()
{
log_write("ds_stop");
if (ftest(PLAYING))
{
if (pDSB)
{
pDSB->Stop();
pDSB->SetCurrentPosition(0);
}
}
do_reset_vars();
}
void DS2::update_pos()//AKA update P.O.S.
{
//called from Update(), no need for shit condition tests
DWORD play_pos, play_pos_w;
try
{
pDSB->GetCurrentPosition(&play_pos, &play_pos_w);
}
catch (...)
{
return;
}
#ifdef USE_LOG
char moo[256];
wsprintf(moo, "update_pos: %u %u (%u)", play_pos, play_pos_w, buf_size);
log_write(moo);
#endif
UINT write_pos = (UINT)(data_written % buf_size);
data_buffered = write_pos > play_pos ? write_pos - play_pos : write_pos + buf_size - play_pos;
#ifdef DS2_HAVE_FADES
VolCtrl.SetTime(GetCurPos());
VolCtrl.Apply(pDSB);
#endif
}
bool DS2::Update()//inside sync already
{
log_write("Update");
if (g_quitting_waiting && (!ftest(PLAYING) || ftest(PAUSED) || !pDSB))
{
delete this;
return 1;
}
if (!pDSB || ftest(PAUSED))
{
return 0;
}
{
UINT min_refresh = bytes2ms(clear_size) >> 1;
if (refresh_timer > min_refresh) refresh_timer = min_refresh;
}
if (ftest(PLAYING)) update_pos();
#ifdef USE_LOG
{
char foo[256];
wsprintf(foo, "Update: %u(%u)+%u / %u(%u)", (int)data_written, (int)data_written % buf_size, BlockList.DataSize(), (int)GetCurPos(), (int)GetCurPos() % buf_size);
log_write(foo);
}
#endif
if (!ftest(PLAYING) && data_written + BlockList.DataSize() >= (int)prebuf && !wait)
{
log_write("done prebuffering");
fset(NEED_PLAY_NOW);
}
DoLock();
if (wait)
{
#ifdef DS2_HAVE_FADES
if (wait->GetLatency() <= waitfade)
{
wait->FadeAndForget(waitfade);
wait = 0;
if (!ftest(PLAYING)) fset(NEED_PLAY_NOW);
}
#else
if (wait->GetLatency() <= 0)
{
delete wait;
wait = 0;
if (!ftest(PLAYING)) fset(NEED_PLAY_NOW);
}
#endif
}
if (ftest(NEED_PLAY_NOW) && data_buffered > 0/* && !(ftest(PLAYING)*/)
{
log_write("starting playback");
if (!ftest(PAUSED))
{
HRESULT res = pDSB->Play(0, 0, DSBPLAY_LOOPING);
if (FAILED(res))
{
if (res == DSERR_BUFFERLOST) pDSB->Restore();
return 0;
}
pos_delta = GetOutputTime(); pos_delta2 = data_written;
}
PostMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_OUTPUT_STARTED);
fset(PLAYING);
}
funset(NEED_PLAY_NOW);
if (ftest(PLAYING))
{
{
DWORD foo = 0;
pDSB->GetStatus(&foo);
if (foo & DSBSTATUS_BUFFERLOST)
pDSB->Restore();
if (foo != (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))
pDSB->Play(0, 0, DSBPLAY_LOOPING);
}
#ifdef DS2_HAVE_FADES
if (!VolCtrl.Fading())
{
if (ftest(FADEPAUSING)) { Pause(1); funset(FADEPAUSING); }
if (ftest(DIE_ON_STOP) || g_quitting_waiting)
{
delete this;
return 1;
}
}
#endif
if (data_buffered <= silence_buffered)
{
log_write("underrun");
ds_stop();
if (ftest(DIE_ON_STOP) || g_quitting_waiting)
{
delete this;
return 1;
}
else if (ftest(CLOSE_ON_STOP))
{
log_write("closeonstop");
ds_kill();
}
#ifdef DS2_HAVE_FADES
else if (ftest(FADEPAUSING)) { Pause(1); funset(FADEPAUSING); }
#endif
else Underruns++;
}
}
return 0;
}
int DS2::Open(DS2config* cfg)
{
log_write("Open");
// SYNCFUNC; //inside sync already
HRESULT hr;
g_delayed_deinit = cfg->delayed_shutdown;
if (cfg->sil_db > 0) { silence_delta = calc_silence(cfg->sil_db, (int)cfg->bps); fset(STARTSIL); }
else silence_delta = -1;
create_primary = cfg->create_primary;
UINT _p_bps = 0, _p_nch = 0, _p_sr = 0;
if (cfg->prim_override)
{
_p_bps = cfg->_p_bps;
_p_nch = cfg->_p_nch;
_p_sr = cfg->_p_sr;
}
if (cfg->guid != cur_dev && pDS)
{
pDS->Release();
pDS = 0;
}
if (!pDS)
{
log_write("Creating IDirectSound");
cur_dev = cfg->guid;
hr = myDirectSoundCreate(&cur_dev, &pDS);
if (!pDS)
{
#ifdef DS2_HAVE_DEVICES
cfg->SetErrorCodeMsgA(DsDevEnumGuid(cur_dev) ? WASABI_API_LNGSTRINGW(IDS_BAD_DS_DRIVER) : WASABI_API_LNGSTRINGW(IDS_DEVICE_NOT_FOUND_SELECT_ANOTHER), hr);
#else
cfg->SetErrorCodeMsg(WASABI_API_LNGSTRING(IDS_BAD_DS_DRIVER), hr);
#endif
return 0;
}
coop_mode = 0;
}
fmt_sr = (int)cfg->sr;
fmt_nch = (WORD)cfg->nch;
fmt_bps = (UINT)cfg->bps;
if ((signed)fmt_sr <= 0 || (signed)fmt_bps <= 0 || (signed)fmt_nch <= 0) return 0;
fmt_mul = fmt_sr * (fmt_bps >> 3) * fmt_nch;
if (!_p_bps) _p_bps = fmt_bps;
if (!_p_nch) _p_nch = fmt_nch;
if (!_p_sr) _p_sr = fmt_sr;
WAVEFORMATEX wfx =
{
WAVE_FORMAT_PCM,
(WORD)fmt_nch,
fmt_sr,
fmt_mul,
(WORD)(fmt_nch * (fmt_bps >> 3)),
(WORD)fmt_bps,
0
};
{
static DWORD coop_tab[3] = { DSSCL_NORMAL,DSSCL_PRIORITY,DSSCL_EXCLUSIVE };
DWORD new_coop = coop_tab[cfg->coop];
if (pPrimary && !create_primary)
{
pPrimary->Release();
pPrimary = 0;
}
if (coop_mode != new_coop)
{
if (FAILED(hr = pDS->SetCooperativeLevel(cfg->wnd, coop_mode = new_coop)))
{
pDS->Release(); pDS = 0;
cfg->SetErrorCodeMsgA(WASABI_API_LNGSTRINGW(IDS_ERROR_SETTING_DS_COOPERATIVE_LEVEL), hr);
return 0;
}
}
if (create_primary && !pPrimary)
{
DSBUFFERDESC desc =
{
sizeof(DSBUFFERDESC),
DSBCAPS_PRIMARYBUFFER,
0,
0,
0
};
pDS->CreateSoundBuffer(&desc, &pPrimary, 0);
prim_nch = prim_bps = prim_sr = 0;
}
if (pPrimary && (_p_bps != prim_bps || _p_nch != prim_nch || _p_sr != prim_sr))
{
WAVEFORMATEX wfx1 =
{
WAVE_FORMAT_PCM,
(WORD)_p_nch,
_p_sr,
_p_sr * (_p_bps >> 3) * _p_nch,
(WORD)(_p_nch * (_p_bps >> 3)),
(WORD)_p_bps,
0
};
pPrimary->SetFormat(&wfx1);
prim_bps = _p_bps;
prim_nch = _p_nch;
prim_sr = _p_sr;
}
}
UINT new_buf_ms = cfg->ms;
if (new_buf_ms < 100) new_buf_ms = 100;// <= DO NOT TOUCH
else if (new_buf_ms > 100000) new_buf_ms = 100000;
log_write("Done with IDirectSound, creating buffer");
buf_size = _align_var(ms2bytes(new_buf_ms));
prebuf = ms2bytes(cfg->preb);
if (prebuf > buf_size) prebuf = buf_size;
else if (prebuf < 0) prebuf = 0;
DSBUFFERDESC desc =
{
sizeof(DSBUFFERDESC),
DSBCAPS_GETCURRENTPOSITION2 |
DSBCAPS_STICKYFOCUS |
DSBCAPS_GLOBALFOCUS |
DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME
#ifdef DS2_HAVE_PITCH
| (cfg->have_pitch ? DSBCAPS_CTRLFREQUENCY : 0)
#endif
,
buf_size,
0,
&wfx
};
switch (cfg->mixing)
{
case DS2config::MIXING_FORCE_HARDWARE:
desc.dwFlags |= DSBCAPS_LOCHARDWARE;
break;
case DS2config::MIXING_FORCE_SOFTWARE:
desc.dwFlags |= DSBCAPS_LOCSOFTWARE;
break;
}
// TODO:If an attempt is made to create a buffer with the DSBCAPS_LOCHARDWARE flag on a system where hardware acceleration is not available, the method fails with either DSERR_CONTROLUNAVAIL or DSERR_INVALIDCALL, depending on the operating system.
do
{
WAVEFORMATEXTENSIBLE wfxe = { 0 };
wfxe.Format = wfx;
wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfxe.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
wfxe.Format.nChannels = fmt_nch;
wfxe.Format.nBlockAlign = (wfxe.Format.nChannels *
wfxe.Format.wBitsPerSample) / 8;
wfxe.Format.nAvgBytesPerSec = wfxe.Format.nBlockAlign *
wfxe.Format.nSamplesPerSec;
wfxe.Samples.wReserved = 0;
if (fmt_nch > kMaxChannelsToMask) {
wfxe.dwChannelMask = kChannelsToMask[kMaxChannelsToMask];
}
else {
wfxe.dwChannelMask = kChannelsToMask[fmt_nch];
}
wfxe.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
wfxe.Samples.wValidBitsPerSample = wfxe.Format.wBitsPerSample;
desc.lpwfxFormat = &wfxe.Format;
hr = pDS->CreateSoundBuffer(&desc, &pDSB, 0);
if (SUCCEEDED(hr))
{
hr = 0;
break;
}
} while (0);
if (FAILED(hr) || !pDSB)
{
cfg->SetErrorCodeMsgA(WASABI_API_LNGSTRINGW(IDS_ERROR_CREATING_DS_BUFFER), hr);
return 0;
}
pDS->AddRef();
myDS = pDS;
{
DSBCAPS caps;
memset(&caps, 0, sizeof(caps));
caps.dwSize = sizeof(caps);
pDSB->GetCaps(&caps);
if (caps.dwFlags & DSBCAPS_LOCSOFTWARE) fset(SWMIXED);
}
clear_size = ms2bytes(200);
if (clear_size > buf_size >> 2) clear_size = buf_size >> 2;
clear_size = _align_var(clear_size);
if (prebuf < clear_size + (clear_size >> 1)) prebuf = clear_size + (clear_size >> 1);
VolCtrl.Apply(pDSB);
reset_vars();
//pDSB->SetVolume(DSBVOLUME_MAX);
log_write("Open : done");
return 1;
}
void DS2::reset_vars()
{
pos_delta = 0; pos_delta2 = 0;
flags &= ~(FLAG_NEED_PLAY_NOW | FLAG_PLAYING);
data_buffered = 0;
data_written = 0;
silence_buffered = 0;
last_nonsil = -1;
BlockList.Reset();
VolCtrl.Reset();
}
void DS2::do_reset_vars()
{
g_total_time += GetOutputTime();
reset_vars();
}
bool DS2::DoLock()
{
void* p1 = 0, * p2 = 0;
DWORD s1 = 0, s2 = 0;
UINT LockSize = (UINT)BlockList.DataSize();
if (LockSize > 0 && silence_buffered)
{
data_written -= silence_buffered;
__int64 min = GetSafeWrite();
if (data_written < min) data_written = min;
silence_buffered = 0;
}
UINT MaxData = buf_size;
int FooScale = ftest(SWMIXED) ? 6 : 4;
MaxData = _align_var(MaxData - (MaxData >> FooScale));
UINT MaxLock = MaxData > data_buffered ? MaxData - data_buffered : 0;
if (!MaxLock) return 0;
if (
((ftest(PLAYING)) || ftest(NEED_PLAY_NOW))
&& data_buffered + LockSize < clear_size)
//underrun warning, put extra silence
LockSize = clear_size - data_buffered;
if (LockSize > MaxLock) LockSize = MaxLock;
if (LockSize == 0) return 0;//final check for useless locks
if (data_buffered > clear_size && LockSize<buf_size >> FooScale) return 0;
log_write("locking");//lock away!
while (1)
{
HRESULT hr = pDSB->Lock((UINT)(data_written % (__int64)buf_size), LockSize, &p1, &s1, &p2, &s2, 0);
if (SUCCEEDED(hr))
{
LockCount++;
UINT written;
written = (UINT)BlockList.DumpBlocks(p1, s1);
if (p2 && s2) written += (UINT)BlockList.DumpBlocks(p2, s2);
//note: we fill with silence when not enough data
UINT total = s1 + s2;
data_written = data_written + total;
data_buffered += total;
if (written > 0) silence_buffered = total - written;
else silence_buffered += total;
pDSB->Unlock(p1, s1, p2, s2);
break;
}
else if (hr == DSERR_BUFFERLOST) {
if (FAILED(pDSB->Restore())) break;
}
else break;
}
return 1;
}
int DS2::CanWrite()//result can be negative !
{
log_write("CanWrite");
SYNC_IN();
if (ftest(PAUSED)) { SYNC_OUT(); return 0; }
int rv;
int m = buf_size - (int)(data_buffered + BlockList.DataSize());
if (ftest(USE_CPU_MNGMNT) && ftest(PLAYING))// && data_written<buf_size && GetCurPos()<buf_size)
{
__int64 t = ((GetCurPos() - pos_delta) << 2) - (data_written - pos_delta2 + BlockList.DataSize());
rv = t > m ? m : (int)t;
}
else
{
rv = m;
}
if (wait) rv -= ms2bytes(wait->GetLatency());
#ifdef USE_LOG
char moo[256];
wsprintf(moo, "CanWrite : %i", rv);
log_write(moo);
#endif
SYNC_OUT();
return _align_var(rv);
}
void DS2::Pause(int new_state)
{
SYNC_IN();
#ifdef USE_LOG
log_write("Pause");
if (ftest(PAUSED)) log_write("is_paused");
if (new_state) log_write("new_state");
#endif
if (new_state && !ftest(PAUSED))
{//pause
log_write("pausing");
if (ftest(PLAYING) && pDSB)
{
pDSB->Stop();
#ifdef USE_LOG
char foo[256];
wsprintf(foo, "stopping buffer - %u", GetCurPos());
log_write(foo);
#endif
}
fset(PAUSED);
}
else if (!new_state)
{
if (ftest(PAUSED))
{//unpause
log_write("unpausing");
if (ftest(PLAYING)) fset(NEED_PLAY_NOW);
#ifdef DS2_HAVE_FADES
if (ftest(FADEPAUSE))
{
VolCtrl.SetTime(GetCurPos());
VolCtrl.SetFadeVol(ms2bytes(fadepause_time), fadepause_orgvol);
}
#endif
log_write("unpausing");
}
#ifdef DS2_HAVE_FADES
else if (ftest(FADEPAUSING))//abort fadeout
{
VolCtrl.SetTime(GetCurPos());
VolCtrl.SetFadeVol(VolCtrl.RelFade(ms2bytes(fadepause_time), fadepause_orgvol), fadepause_orgvol);
}
funset(FADEPAUSE);
funset(FADEPAUSING);
#endif
funset(PAUSED);
}
if (wait)
{
log_write("wait pause too");
wait->Pause(new_state);
}
log_write("pause done");
SYNC_OUT();
}
void DS2::SetVolume(double v)
{
SYNC_IN();
if (!ftest(DIE_ON_STOP) && pDSB)
{
VolCtrl.SetVolume(v);
VolCtrl.Apply(pDSB);
}
if (wait) wait->SetVolume(v);
SYNC_OUT();
}
void DS2::SetPan(double p)
{
SYNC_IN();
if (!ftest(DIE_ON_STOP) && pDSB)
{
VolCtrl.SetPan(p);
VolCtrl.Apply(pDSB);
}
if (wait) wait->SetPan(p);
SYNC_OUT();
}
UINT DS2::GetLatency()
{
SYNC_IN();
UINT bDataSize = (UINT)BlockList.DataSize();
int bytes;
if (bDataSize) bytes = data_buffered + (UINT)BlockList.DataSize();
else bytes = data_buffered - silence_buffered;
if (bytes < 0) bytes = 0;
UINT rv = bytes2ms((UINT)bytes);
if (wait) rv += wait->GetLatency();
#ifdef USE_LOG
{
char foo[128];
wsprintf(foo, "GetLatency: %u (%u %u)", rv, data_written - GetCurPos(), BlockList.DataSize());
log_write(foo);
}
#endif
SYNC_OUT();
return rv;
}
#ifdef DS2_HAVE_FADES
void DS2::Fade(UINT time, double destvol)
{
SYNC_IN();
VolCtrl.SetFadeVol(ms2bytes(time), destvol);
SYNC_OUT();
}
void DS2::FadeAndForget(UINT time)
{
SYNC_IN();
if (!pDSB || time == 0 || ftest(PAUSED) || (!data_written && !BlockList.DataSize()))
{
delete this;
}
else
{
fset(DIE_ON_STOP);
if (!ftest(PLAYING)) fset(NEED_PLAY_NOW);
__int64 fadetime = ms2bytes(time);
__int64 max = data_written + BlockList.DataSize() - GetCurPos();
if (max < 0) max = 0;
if (fadetime > max) fadetime = max;
VolCtrl.SetFadeVol(fadetime, 0);
}
SYNC_OUT();
}
void DS2::FadeX(UINT time, double dest)
{
SYNC_IN();
if (ftest(PAUSED) && ftest(FADEPAUSE))
{
fadepause_orgvol = dest;
}
VolCtrl.SetFadeVol(VolCtrl.RelFade(ms2bytes(time), dest), dest);
SYNC_OUT();
}
void DS2::FadePause(UINT time)
{
SYNC_IN();
if (!time)
{
Pause(1);
}
else
{
if (wait)
{
wait->FadeAndForget(time);
wait = 0;
}
if (!ftest(PLAYING))
{
fset(PAUSED);
}
else
{
fadepause_time = time;
fset(FADEPAUSE);
fset(FADEPAUSING);
fadepause_orgvol = VolCtrl.GetDestVol();
VolCtrl.SetFadeVol(ms2bytes(time), 0);
}
}
SYNC_OUT();
}
#endif
UINT DS2::InstanceCount()
{
_log_write("InstanceCount", 0);
SYNC_IN();
UINT rv = 0;
DS2* p = ds2s;
while (p) { rv++; p = p->next; }
SYNC_OUT();
return rv;
}
__int64 DS2::GetSafeWrite()
{
return GetCurPos() + clear_size + ms2bytes(refresh_timer);
}
void DS2::KillEndGap()
{
SYNC_IN();
if (silence_delta >= 0 && last_nonsil >= 0)
{
__int64 cp = GetSafeWrite();
if (cp < data_written)
{
__int64 dest = last_nonsil < cp ? cp : last_nonsil;
if (dest > data_written)
{//need to take data from blocklist
UINT s = (UINT)BlockList.DataSize();
char* temp0r = (char*)malloc(s);
BlockList.DumpBlocks(temp0r, s);
BlockList.Reset();
BlockList.AddBlock(temp0r, (UINT)(dest - data_written));
free(temp0r);
}
else
{
BlockList.Reset();
data_written = dest;
}
}
last_nonsil = -1;
fset(STARTSIL);
}
SYNC_OUT();
}
void DS2::Flush()
{
log_write("Flush");
SYNC_IN();
ds_stop();
SYNC_OUT();
}
void DS2::ForcePlay()
{
SYNC_IN();
if (!ftest(PAUSED) && !ftest(PLAYING) && !wait && data_buffered + BlockList.DataSize() > 0)
{
log_write("forceplay");
fset(NEED_PLAY_NOW);
}
SYNC_OUT();
}
void DS2::WaitFor(DS2* prev, UINT fade)
{
SYNC_IN();
if (wait) delete wait;
wait = prev;
#ifdef DS2_HAVE_FADES
waitfade = fade;
#endif
wait->flags |= FLAG_WAITED;
wait->ForcePlay();
SYNC_OUT();
}
void DS2::StartNewStream()
{
SYNC_IN();
if (last_nonsil > data_written + (UINT)BlockList.DataSize()) last_nonsil = data_written + (UINT)BlockList.DataSize();
pos_delta = GetCurPos(); pos_delta2 = data_written;
SYNC_OUT();
}
void DS2::SetCloseOnStop(bool b)
{
SYNC_IN();
log_write("setcloseonstop");
fsetc(CLOSE_ON_STOP, b);
if (b && !ftest(PLAYING)) ds_kill();
SYNC_OUT();
}
bool DS2::IsClosed()
{
SYNC_IN();
bool rv = pDSB ? 0 : 1;
SYNC_OUT();
return rv;
}
void DS2::GetRealtimeStat(DS2_REALTIME_STAT* stat)
{
log_write("GetRealtimeStat");
SYNC_IN();
__int64 curpos = GetCurPos();
stat->sr = fmt_sr;
stat->bps = fmt_bps;
stat->nch = fmt_nch;
stat->buf_size_bytes = buf_size;
stat->buf_size_ms = bytes2ms(buf_size);
stat->pos_play = (UINT)(curpos % buf_size);
stat->pos_write = (UINT)(data_written % buf_size);
stat->latency = data_buffered + (UINT)BlockList.DataSize();
if (stat->latency < 0) stat->latency = 0;
stat->latency_ms = bytes2ms(stat->latency);
stat->lock_count = LockCount;
stat->underruns = Underruns;
stat->bytes_async = BlockList.DataSize();
stat->bytes_written = data_written + BlockList.DataSize();
stat->bytes_played = curpos;
stat->have_primary_buffer = pPrimary ? true : false;
stat->current_device = cur_dev;
stat->vol_left = VolCtrl.Stat_GetVolLeft();
stat->vol_right = VolCtrl.Stat_GetVolRight();
if (pDSB)
{
DSBCAPS caps;
memset(&caps, 0, sizeof(caps));
caps.dwSize = sizeof(caps);
pDSB->GetCaps(&caps);
stat->dscaps_flags = caps.dwFlags;
}
else stat->dscaps_flags = 0;
if (pPrimary)
{
DSBCAPS caps;
memset(&caps, 0, sizeof(caps));
caps.dwSize = sizeof(caps);
pPrimary->GetCaps(&caps);
stat->dscaps_flags_primary = caps.dwFlags;
}
else stat->dscaps_flags_primary = 0;
stat->paused = !!ftest(PAUSED);
SYNC_OUT();
}
bool DS2::GetRealtimeStatStatic(DS2_REALTIME_STAT* stat)
{
bool rv = 0;
SYNC_IN();
if (ds2s) { ds2s->GetRealtimeStat(stat); rv = 1; }
SYNC_OUT();
return rv;
}
void DS2::SetTotalTime(__int64 z)
{
_log_write("SetTotalTime", 0);
SYNC_IN();
g_total_time = z;
SYNC_OUT();
}
__int64 DS2::GetTotalTime()
{
_log_write("GetTotalTime", 0);
SYNC_IN();
__int64 r = g_total_time;
DS2* p = ds2s;
while (p)
{
r += p->GetOutputTime();
p = p->next;
}
SYNC_OUT();
return r;
}
__int64 DS2::GetOutputTime()
{
if (!fmt_bps || !fmt_nch || !fmt_sr) return 0;
SYNC_IN();//need __int64, cant do bytes2ms
__int64 r = (GetCurPos()) / ((fmt_bps >> 3) * fmt_nch) * 1000 / fmt_sr;
SYNC_OUT();
return r;
}
#ifdef DS2_HAVE_PITCH
void DS2::SetPitch(double p)
{
SYNC_IN();
DWORD f = (DWORD)(p * (double)fmt_sr);
if (f < DSBFREQUENCY_MIN) f = DSBFREQUENCY_MIN;
else if (f > DSBFREQUENCY_MAX) f = DSBFREQUENCY_MAX;
if (pDSB) pDSB->SetFrequency(f);
SYNC_OUT();
}
#endif
#ifdef DS2_HAVE_DEVICES
GUID DS2::GetCurDev() { return cur_dev; }
#endif