diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Output/out_wave/waveout.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Output/out_wave/waveout.cpp')
-rw-r--r-- | Src/Plugins/Output/out_wave/waveout.cpp | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/Src/Plugins/Output/out_wave/waveout.cpp b/Src/Plugins/Output/out_wave/waveout.cpp new file mode 100644 index 00000000..c69ef460 --- /dev/null +++ b/Src/Plugins/Output/out_wave/waveout.cpp @@ -0,0 +1,617 @@ +#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; +} |