618 lines
12 KiB
C++
618 lines
12 KiB
C++
#define STRICT
|
|
#include <windows.h>
|
|
#include "out_wave.h"
|
|
#include "api.h"
|
|
#include "waveout.h"
|
|
#include "resource.h"
|
|
#include <mmreg.h>
|
|
#pragma intrinsic(memset, memcpy)
|
|
#define SYNC_IN EnterCriticalSection(&sync);
|
|
#define SYNC_OUT LeaveCriticalSection(&sync);
|
|
|
|
#define GET_TIME timeGetTime()
|
|
|
|
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
|
|
// TODO(fbarchard): Add additional masks for 7.2 and beyond.
|
|
};
|
|
|
|
WaveOut::~WaveOut()
|
|
{
|
|
if (hThread)
|
|
{
|
|
SYNC_IN
|
|
die=1;
|
|
|
|
SYNC_OUT;
|
|
SetEvent(hEvent);
|
|
WaitForSingleObject(hThread,INFINITE);
|
|
}
|
|
if (hEvent) CloseHandle(hEvent);
|
|
DeleteCriticalSection(&sync);
|
|
killwaveout();
|
|
if (buffer) LocalFree(buffer);
|
|
hdr_free_list(hdrs);
|
|
hdr_free_list(hdrs_free);
|
|
}
|
|
|
|
DWORD WINAPI WaveOut::ThreadProc(WaveOut * p)
|
|
{
|
|
p->thread();
|
|
return 0;
|
|
}
|
|
|
|
void WaveOut::thread()
|
|
{
|
|
SetThreadPriority(GetCurrentThread(),THREAD_PRIORITY_TIME_CRITICAL);
|
|
while(hWo)
|
|
{
|
|
WaitForSingleObject(hEvent,INFINITE);
|
|
SYNC_IN;
|
|
if (die) {SYNC_OUT; break;}
|
|
|
|
|
|
for(HEADER * h=hdrs;h;)
|
|
{
|
|
if (h->hdr.dwFlags&WHDR_DONE)
|
|
{
|
|
n_playing--;
|
|
buf_size_used-=h->hdr.dwBufferLength;
|
|
last_time=GET_TIME;
|
|
waveOutUnprepareHeader(hWo,&h->hdr,sizeof(WAVEHDR));
|
|
HEADER* f=h;
|
|
h=h->next;
|
|
hdr_free(f);
|
|
}
|
|
else h=h->next;
|
|
}
|
|
|
|
|
|
/* if (needflush)
|
|
{
|
|
flush();
|
|
if (!paused) waveOutRestart(hWo);
|
|
needflush=0;
|
|
}*/
|
|
|
|
if (!paused && newpause)
|
|
{
|
|
paused=1;
|
|
if (hWo) waveOutPause(hWo);
|
|
p_time=GET_TIME-last_time;
|
|
}
|
|
|
|
if (paused && !newpause)
|
|
{
|
|
paused=0;
|
|
if (hWo) waveOutRestart(hWo);
|
|
last_time = GET_TIME-p_time;
|
|
}
|
|
|
|
|
|
UINT limit;
|
|
if (needplay) limit=0;
|
|
else if (!n_playing)
|
|
{
|
|
limit=prebuf;
|
|
if (limit<avgblock) limit=avgblock;
|
|
}
|
|
else if (buf_size_used<(buf_size>>1) || n_playing<3) limit=minblock;//skipping warning, blow whatever we have
|
|
else limit=avgblock;//just a block
|
|
|
|
while(data_written>limit)
|
|
{
|
|
UINT d=(data_written > maxblock) ? maxblock : data_written;
|
|
d-=d%fmt_align;
|
|
if (!d) break;
|
|
data_written-=d;
|
|
buf_size_used+=d;
|
|
|
|
HEADER * h=hdr_alloc();
|
|
h->hdr.dwBytesRecorded=h->hdr.dwBufferLength=d;
|
|
h->hdr.lpData=buffer+write_ptr;
|
|
write_ptr+=d;
|
|
if (write_ptr>buf_size)
|
|
{
|
|
write_ptr-=buf_size;
|
|
memcpy(buffer+buf_size,buffer,write_ptr);
|
|
}
|
|
|
|
n_playing++;
|
|
if (use_altvol) do_altvol(h->hdr.lpData,d);
|
|
waveOutPrepareHeader(hWo,&h->hdr,sizeof(WAVEHDR));
|
|
waveOutWrite(hWo,&h->hdr,sizeof(WAVEHDR));//important: make all waveOutWrite calls from *our* thread to keep win2k/xp happy
|
|
if (n_playing==1) last_time=GET_TIME;
|
|
#if 0
|
|
{
|
|
char t[128] = {0};
|
|
wsprintf(t,"block size: %u, limit used %u\n", d,limit);
|
|
OutputDebugString(t);
|
|
}
|
|
#endif
|
|
}
|
|
needplay=0;
|
|
|
|
if (!data_written && !n_playing && closeonstop) killwaveout();
|
|
|
|
SYNC_OUT;
|
|
}
|
|
killwaveout();
|
|
}
|
|
|
|
int WaveOut::WriteData(const void * _data,UINT size)
|
|
{
|
|
|
|
SYNC_IN;
|
|
if (paused) //$!#@!
|
|
{
|
|
SYNC_OUT;
|
|
return 0;
|
|
}
|
|
|
|
const char * data=(const char*)_data;
|
|
|
|
{
|
|
UINT cw=CanWrite();
|
|
if (size>cw)
|
|
{
|
|
size=cw;
|
|
}
|
|
}
|
|
|
|
UINT written=0;
|
|
while(size>0)
|
|
{
|
|
UINT ptr=(data_written + write_ptr)%buf_size;
|
|
UINT delta=size;
|
|
if (ptr+delta>buf_size) delta=buf_size-ptr;
|
|
memcpy(buffer+ptr,data,delta);
|
|
data+=delta;
|
|
size-=delta;
|
|
written+=delta;
|
|
data_written+=delta;
|
|
}
|
|
SYNC_OUT; // sync out first to prevent a ping-pong condition
|
|
if (written) SetEvent(hEvent);//new shit, time to update
|
|
return (int)written;
|
|
}
|
|
|
|
void WaveOut::flush()//in sync
|
|
{
|
|
waveOutReset(hWo);
|
|
|
|
while(hdrs)
|
|
{
|
|
if (hdrs->hdr.dwFlags & WHDR_PREPARED)
|
|
{
|
|
waveOutUnprepareHeader(hWo,&hdrs->hdr,sizeof(WAVEHDR));
|
|
}
|
|
hdr_free(hdrs);
|
|
}
|
|
reset_shit();
|
|
}
|
|
|
|
void WaveOut::Flush()
|
|
{
|
|
/* SYNC_IN;
|
|
needflush=1;
|
|
SetEvent(hEvent);
|
|
SYNC_OUT;
|
|
while(needflush) Sleep(1);*/
|
|
SYNC_IN;//no need to sync this to our thread
|
|
flush();
|
|
if (!paused) waveOutRestart(hWo);
|
|
SYNC_OUT;
|
|
}
|
|
|
|
|
|
void WaveOut::ForcePlay()
|
|
{
|
|
SYNC_IN;//needs to be done in our thread
|
|
if (!paused) {needplay=1;SetEvent(hEvent);}
|
|
SYNC_OUT;
|
|
// while(needplay) Sleep(1);
|
|
}
|
|
|
|
WaveOut::WaveOut()
|
|
{
|
|
#ifndef TINY_DLL //TINY_DLL has its own new operator with zeroinit
|
|
memset(&hWo,0,sizeof(*this)-((char*)&hWo-(char*)this));
|
|
#endif
|
|
myvol=-666;
|
|
mypan=-666;
|
|
InitializeCriticalSection(&sync);
|
|
}
|
|
|
|
int WaveOut::open(WaveOutConfig * cfg)
|
|
{
|
|
fmt_sr = cfg->sr;
|
|
fmt_bps = cfg->bps;
|
|
fmt_nch = cfg->nch;
|
|
fmt_align = ( fmt_bps >> 3 ) * fmt_nch;
|
|
fmt_mul = fmt_align * fmt_sr;
|
|
|
|
use_volume=cfg->use_volume;
|
|
use_altvol=cfg->use_altvol;
|
|
use_resetvol=cfg->resetvol;
|
|
|
|
if (!use_volume)
|
|
use_altvol=use_resetvol=0;
|
|
else if (use_altvol)
|
|
{
|
|
use_resetvol=0;
|
|
use_volume=0;
|
|
}
|
|
|
|
WAVEFORMATEX wfx=
|
|
{
|
|
WAVE_FORMAT_PCM,
|
|
(WORD)fmt_nch,
|
|
fmt_sr,
|
|
fmt_mul,
|
|
(WORD)fmt_align,
|
|
(WORD)fmt_bps,
|
|
0
|
|
};
|
|
|
|
if (!hEvent) hEvent=CreateEvent(0,0,0,0);
|
|
|
|
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;
|
|
MMRESULT mr = waveOutOpen(&hWo, (UINT)(cfg->dev-1), reinterpret_cast<LPCWAVEFORMATEX>(&wfxe), (DWORD_PTR)hEvent, 0, CALLBACK_EVENT);
|
|
|
|
|
|
if (mr)
|
|
{
|
|
WCHAR full_error[1024], * _fe = full_error;
|
|
WCHAR poo[MAXERRORLENGTH] = { 0 };
|
|
WCHAR* e = poo;
|
|
|
|
if (waveOutGetErrorTextW(mr,poo,MAXERRORLENGTH)!=MMSYSERR_NOERROR)
|
|
{
|
|
WASABI_API_LNGSTRINGW_BUF(IDS_UNKNOWN_MMSYSTEM_ERROR, poo, MAXERRORLENGTH);
|
|
}
|
|
char * e2=0, e2Buf[1024] = {0};
|
|
switch(mr)
|
|
{
|
|
case 32:
|
|
wsprintfW(_fe, WASABI_API_LNGSTRINGW(IDS_UNSUPPORTED_PCM_FORMAT), fmt_sr, fmt_bps, fmt_nch);
|
|
//fixme: some broken drivers blow mmsystem032 for no reason, with "standard" 44khz/16bps/stereo, need better error message when pcm format isnt weird
|
|
while(_fe && *_fe) _fe++;
|
|
e2="";
|
|
break;
|
|
case 4:
|
|
e2=WASABI_API_LNGSTRING_BUF(IDS_ANOTHER_PROGRAM_IS_USING_THE_SOUNDCARD,e2Buf,1024);
|
|
break;
|
|
case 2:
|
|
e2=WASABI_API_LNGSTRING_BUF(IDS_NO_SOUND_DEVICES_FOUND,e2Buf,1024);
|
|
break;
|
|
case 20:
|
|
e2=WASABI_API_LNGSTRING_BUF(IDS_INTERNAL_DRIVER_ERROR,e2Buf,1024);
|
|
break;
|
|
case 7:
|
|
e2=WASABI_API_LNGSTRING_BUF(IDS_REINSTALL_SOUNDCARD_DRIVERS,e2Buf,1024);
|
|
break;
|
|
//case 8: fixme
|
|
}
|
|
if (e2)
|
|
{
|
|
wsprintfW(_fe, WASABI_API_LNGSTRINGW(IDS_ERROR_CODE_WINDOWS_ERROR_MESSAGE), e2, mr, e);
|
|
}
|
|
else
|
|
{
|
|
wsprintfW(_fe, WASABI_API_LNGSTRINGW(IDS_ERROR_CODE), e, mr);
|
|
}
|
|
cfg->SetError(full_error);
|
|
return 0;
|
|
}
|
|
|
|
|
|
buf_size=MulDiv(cfg->buf_ms,fmt_mul,1000);
|
|
|
|
maxblock = 0x10000;
|
|
minblock = 0x100;
|
|
avgblock = buf_size>>4;
|
|
if (maxblock>buf_size>>2) maxblock=buf_size>>2;
|
|
if (avgblock>maxblock) avgblock=maxblock;
|
|
if (maxblock<minblock) maxblock=minblock;
|
|
if (avgblock<minblock) avgblock=minblock;
|
|
|
|
|
|
buffer = (char*)LocalAlloc(LPTR,buf_size+maxblock);//extra space at the end of the buffer
|
|
|
|
prebuf = MulDiv(cfg->prebuf,fmt_mul,1000);
|
|
if (prebuf>buf_size) prebuf=buf_size;
|
|
|
|
n_playing=0;
|
|
|
|
waveOutRestart(hWo);
|
|
reset_shit();
|
|
|
|
|
|
if (use_resetvol) waveOutGetVolume(hWo,&orgvol);
|
|
|
|
if (myvol!=-666 || mypan!=-666) update_vol();
|
|
|
|
|
|
{
|
|
DWORD dw;
|
|
hThread=CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadProc,this,0,&dw);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WaveOut::GetLatency(void)
|
|
{
|
|
SYNC_IN;
|
|
int r=0;
|
|
if (hWo)
|
|
{
|
|
r=MulDiv(buf_size_used+data_written,1000,(fmt_bps>>3)*fmt_nch*fmt_sr);
|
|
if (paused) r-=p_time;
|
|
else if (n_playing) r-=GET_TIME-last_time;
|
|
if (r<0) r=0;
|
|
}
|
|
SYNC_OUT;
|
|
return r;
|
|
}
|
|
|
|
|
|
void WaveOut::SetVolume(int v)
|
|
{
|
|
SYNC_IN;
|
|
myvol=v;
|
|
update_vol();
|
|
SYNC_OUT;
|
|
}
|
|
|
|
void WaveOut::SetPan(int p)
|
|
{
|
|
SYNC_IN;
|
|
mypan=p;
|
|
update_vol();
|
|
SYNC_OUT;
|
|
}
|
|
|
|
void WaveOut::update_vol()
|
|
{
|
|
if (hWo && use_volume)
|
|
{
|
|
if (myvol==-666) myvol=255;
|
|
if (mypan==-666) mypan=0;
|
|
DWORD left,right;
|
|
left=right=myvol|(myvol<<8);
|
|
if (mypan<0) right=(right*(128+mypan))>>7;
|
|
else if (mypan>0) left=(left*(128-mypan))>>7;
|
|
waveOutSetVolume(hWo,left|(right<<16));
|
|
}
|
|
}
|
|
|
|
void WaveOut::reset_shit()
|
|
{
|
|
n_playing=0;
|
|
data_written=0;
|
|
buf_size_used=0;
|
|
last_time=0;
|
|
//last_time=GET_TIME;
|
|
}
|
|
|
|
void WaveOut::Pause(int s)
|
|
{
|
|
SYNC_IN;
|
|
newpause=s?1:0;//needs to be done in our thread to keep stupid win2k/xp happy
|
|
|
|
SYNC_OUT;
|
|
SetEvent(hEvent);
|
|
while(paused!=newpause) Sleep(1);
|
|
}
|
|
|
|
void WaveOut::killwaveout()
|
|
{
|
|
if (hWo)
|
|
{
|
|
flush();
|
|
if (use_resetvol) waveOutSetVolume(hWo,orgvol);
|
|
waveOutClose(hWo);
|
|
hWo=0;
|
|
}
|
|
}
|
|
|
|
int WaveOut::CanWrite()
|
|
{
|
|
SYNC_IN;
|
|
int rv=paused ? 0 : buf_size-buf_size_used-data_written;
|
|
SYNC_OUT;
|
|
return rv;
|
|
}
|
|
|
|
WaveOut * WaveOut::Create(WaveOutConfig * cfg)
|
|
{
|
|
WaveOut * w=new WaveOut;
|
|
if (w->open(cfg)<=0)
|
|
{
|
|
delete w;
|
|
w=0;
|
|
}
|
|
return w;
|
|
}
|
|
|
|
|
|
WaveOut::HEADER * WaveOut::hdr_alloc()
|
|
{
|
|
HEADER * r;
|
|
if (hdrs_free)
|
|
{
|
|
r=hdrs_free;
|
|
hdrs_free=hdrs_free->next;
|
|
}
|
|
else
|
|
{
|
|
r=new HEADER;
|
|
}
|
|
r->next=hdrs;
|
|
hdrs=r;
|
|
memset(&r->hdr,0,sizeof(WAVEHDR));
|
|
return r;
|
|
}
|
|
|
|
void WaveOut::hdr_free(HEADER * h)
|
|
{
|
|
HEADER ** p=&hdrs;
|
|
while(p && *p)
|
|
{
|
|
if (*p==h)
|
|
{
|
|
*p = (*p)->next;
|
|
break;
|
|
}
|
|
else p=&(*p)->next;
|
|
}
|
|
|
|
h->next=hdrs_free;
|
|
hdrs_free=h;
|
|
}
|
|
|
|
void WaveOut::hdr_free_list(HEADER * h)
|
|
{
|
|
while(h)
|
|
{
|
|
HEADER * t=h->next;
|
|
delete h;
|
|
h=t;
|
|
}
|
|
}
|
|
|
|
bool WaveOut::PrintState(char * z)
|
|
{
|
|
bool rv;
|
|
SYNC_IN;
|
|
if (!hWo) rv=0;
|
|
else
|
|
{
|
|
rv=1;
|
|
wsprintfA(z,WASABI_API_LNGSTRING(IDS_DATA_FORMAT),fmt_sr,fmt_bps,fmt_nch);
|
|
while(z && *z) z++;
|
|
wsprintfA(z,WASABI_API_LNGSTRING(IDS_BUFFER_STATUS),buf_size,n_playing);
|
|
while(z && *z) z++;
|
|
wsprintfA(z,WASABI_API_LNGSTRING(IDS_LATENCY),GetLatency());
|
|
// while(z && *z) z++;
|
|
// wsprintf(z,"Data written: %u KB",MulDiv((int)total_written,(fmt_bps>>3)*fmt_nch,1024));
|
|
}
|
|
SYNC_OUT;
|
|
return rv;
|
|
}
|
|
|
|
void WaveOutConfig::SetError(const WCHAR * x)
|
|
{
|
|
error=(WCHAR*)LocalAlloc(LPTR,lstrlenW(x+1));
|
|
lstrcpyW(error,x);
|
|
}
|
|
|
|
void WaveOut::do_altvol_i(char * ptr,UINT max,UINT start,UINT d,int vol)
|
|
{
|
|
UINT p=start*(fmt_bps>>3);
|
|
while(p<max)
|
|
{
|
|
void * z=ptr+p;
|
|
switch(fmt_bps)
|
|
{
|
|
case 8:
|
|
*(BYTE*)z=0x80^(BYTE)MulDiv(0x80^*(BYTE*)z,vol,255);
|
|
break;
|
|
case 16:
|
|
*(short*)z=(short)MulDiv(*(short*)z,vol,255);
|
|
break;
|
|
case 24:
|
|
{
|
|
long l=0;
|
|
memcpy(&l,z,3);
|
|
if (l&0x800000) l|=0xFF000000;
|
|
l=MulDiv(l,vol,255);
|
|
memcpy(z,&l,3);
|
|
}
|
|
break;
|
|
case 32:
|
|
*(long*)z=MulDiv(*(long*)z,vol,255);
|
|
break;
|
|
}
|
|
p+=d*(fmt_bps>>3);
|
|
}
|
|
}
|
|
|
|
void WaveOut::do_altvol(char * ptr,UINT s)
|
|
{
|
|
int mixvol=(myvol==-666) ? 255 : myvol;
|
|
int mixpan=(mypan==-666) ? 0 : mypan;
|
|
if (mixvol==255 && (fmt_nch!=2 || mixpan==0)) return;
|
|
if (fmt_nch==2)
|
|
{
|
|
int rv=mixvol,lv=mixvol;
|
|
if (mixpan<0)
|
|
{//-128..0
|
|
rv=MulDiv(rv,mixpan+128,128);
|
|
}
|
|
else if (mixpan>0)
|
|
{
|
|
lv=MulDiv(rv,128-mixpan,128);
|
|
}
|
|
do_altvol_i(ptr,s,0,2,lv);
|
|
do_altvol_i(ptr,s,1,2,rv);
|
|
}
|
|
else
|
|
{
|
|
do_altvol_i(ptr,s,0,1,mixvol);
|
|
}
|
|
}
|
|
|
|
bool WaveOut::IsClosed()
|
|
{
|
|
SYNC_IN;
|
|
bool rv=hWo ? 0 : 1;
|
|
SYNC_OUT;
|
|
return rv;
|
|
}
|