#include "out_wave.h" #include "api.h" #include "resource.h" #include "waveout.h" #include "../winamp/wa_ipc.h" #include "../nu/AutoWide.h" #ifdef HAVE_SSRC #include "ssrc\ssrc.h" static Resampler_base * pSSRC; #endif static bool gapless_stop; static WaveOut * pWO; static __int64 total_written; static int pos_delta; static UINT canwrite_hack; // wasabi based services for localisation support api_service *WASABI_API_SVC = 0; api_language *WASABI_API_LNG = 0; api_application *WASABI_API_APP = 0; HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; void _init(); //cfg_prebuf now in ms ! UINT cfg_dev = 0, cfg_buf_ms = 2000, cfg_prebuf = 200, cfg_trackhack = 200; bool cfg_volume = 1, cfg_altvol = 0, cfg_resetvol = 0; static int fmt_sr, fmt_bps, fmt_nch; static int volume = 255, pan = 0; #ifdef HAVE_SSRC UINT cfg_dither = 1, cfg_resample_freq = 48000, cfg_resample_bps = 1; bool cfg_fast = 1; UINT cfg_pdf = 1; UINT bps_tab[3] = {8, 16, 24}; static bool finished, use_finish; static UINT resample_freq; static UINT resample_bps; static void do_ssrc_create() { pSSRC = SSRC_create(fmt_sr, resample_freq, fmt_bps, resample_bps, fmt_nch, cfg_dither, cfg_pdf, cfg_fast, 0); finished = 0; use_finish = cfg_trackhack == 0 ? 1 : 0; } #endif void do_cfg(bool s); static CRITICAL_SECTION sync; //various funky time consuming stuff going on, better protect ourselves with a critical section here, resampler doesnt have its own one #define SYNC_IN EnterCriticalSection(&sync); #define SYNC_OUT LeaveCriticalSection(&sync); void Config(HWND); int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message) { MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0}; msgbx.lpszText = message; msgbx.lpszCaption = title; msgbx.lpszIcon = MAKEINTRESOURCEW(102); msgbx.hInstance = GetModuleHandle(0); msgbx.dwStyle = MB_USERICON; msgbx.hwndOwner = parent; return MessageBoxIndirectW(&msgbx); } void About(HWND hwndParent) { wchar_t message[1024] = {0}, text[1024] = {0}; WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_WAVEOUT_OLD,text,1024); wsprintfW(message, WASABI_API_LNGSTRINGW(IDS_ABOUT_TEXT), mod.description, __DATE__); DoAboutMessageBox(hwndParent,text,message); } static void Init() { if (!IsWindow(mod.hMainWindow)) return; // loader so that we can get the localisation service api for use WASABI_API_SVC = (api_service*)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_API_SERVICE); if (WASABI_API_SVC == (api_service*)1) WASABI_API_SVC = NULL; if (!WASABI_API_SVC || WASABI_API_SVC == (api_service *)1) return; waServiceFactory *sf = WASABI_API_SVC->service_getServiceByGuid(languageApiGUID); if (sf) WASABI_API_LNG = reinterpret_cast(sf->getInterface()); sf = WASABI_API_SVC->service_getServiceByGuid(applicationApiServiceGuid); if (sf) WASABI_API_APP = reinterpret_cast(sf->getInterface()); // need to have this initialised before we try to do anything with localisation features WASABI_API_START_LANG(mod.hDllInstance,OutWaveLangGUID); static wchar_t szDescription[256]; swprintf(szDescription,256,WASABI_API_LNGSTRINGW(IDS_NULLSOFT_WAVEOUT),OUT_WAVE_VER); mod.description = (char*)szDescription; } static int inited; static void Quit() { if (inited) { if (pWO) { delete pWO; pWO = 0; } #ifdef HAVE_SSRC if (pSSRC) { delete pSSRC; pSSRC = 0; } #endif do_cfg(1); inited = 0; } } static void reset_stuff() { canwrite_hack = 0; pos_delta = 0; total_written = 0; gapless_stop = 0; } void _init() { if (!inited) { inited = 1; do_cfg(0); if (cfg_dev > waveOutGetNumDevs()) cfg_dev = 0; } } static void _write(char * data, int size); int Open(int sr, int nch, int bps, int bufferlenms, int prebufferms) { _init(); SYNC_IN; if (pWO) //"gapless" track change (or someone forgot to close output) { pWO->SetCloseOnStop(0); //turn off self-destruct on out-of-PCM-data if (!pWO->IsClosed()) //has it run out of PCM data or not ? if yes, we can only delete and create new one { if (sr != fmt_sr || nch != fmt_nch || bps != fmt_bps) //tough shit, new pcm format { //wait-then-close, dont cut previous track #ifdef HAVE_SSRC if (!pSSRC && !finished) { use_finish = 1; _write(0, 0); } #endif while (pWO->GetLatency() > 0) Sleep(1); } else { //successful gapless track change. yay. reset_stuff(); #ifdef HAVE_SSRC if (pSSRC) { if (finished) { delete pSSRC; do_ssrc_create(); } else use_finish = cfg_trackhack == 0 ? 1 : 0; } #endif int r = pWO->GetMaxLatency(); SYNC_OUT; return r; } } #ifdef HAVE_SSRC if (pSSRC) { delete pSSRC; pSSRC = 0; } #endif delete pWO; } WaveOutConfig * cfg = new WaveOutConfig; //avoid crazy crt references with creating cfg on stack, keep TINY_DLL config happy cfg->SetBuffer(cfg_buf_ms, cfg_prebuf); cfg->SetDevice(cfg_dev); cfg->SetVolumeSetup(cfg_volume, cfg_altvol, cfg_resetvol); fmt_sr = sr; fmt_nch = nch; fmt_bps = bps; #ifdef HAVE_SSRC resample_freq = cfg_resample_freq; if (resample_freq < 6000) resample_freq = 6000; else if (resample_freq > 192000) resample_freq = 192000; resample_bps = bps_tab[cfg_resample_bps]; if (fmt_sr == (int)resample_freq && fmt_bps == (int)resample_bps) { cfg->SetPCM(fmt_sr, fmt_nch, fmt_bps); } else { do_ssrc_create(); } if (!pSSRC) cfg->SetPCM(sr, nch, bps); else cfg->SetPCM(resample_freq, nch, resample_bps); #else//!HAVE_SSRC cfg->SetPCM(sr, nch, bps); #endif pWO = WaveOut::Create(cfg); if (!pWO) { const WCHAR *error = cfg->GetError(); if (error) { WCHAR err[128] = {0}, temp[128] = {0}; swprintf(err,128,WASABI_API_LNGSTRINGW(IDS_ERROR),WASABI_API_LNGSTRINGW_BUF(IDS_NULLSOFT_WAVEOUT_OLD,temp,128)); MessageBoxW(mod.hMainWindow, error, err, MB_ICONERROR); } } else { reset_stuff(); } delete cfg; if (pWO) { int r = pWO->GetMaxLatency(); SYNC_OUT; return r; } else { #ifdef HAVE_SSRC if (pSSRC) { delete pSSRC; pSSRC = 0; } #endif SYNC_OUT; return -1; } } #ifdef HAVE_SSRC static UINT ssrc_extra_latency; static void _write(char * data, int size) { if (pWO) { if (pSSRC) { if (!finished) { UINT nsiz; if (data > 0) pSSRC->Write(data, (UINT)size); else if (use_finish) { finished = 1; pSSRC->Finish(); } data = (char*)pSSRC->GetBuffer(&nsiz); UINT nsiz1 = nsiz; while (nsiz) //ugly { int wr = pWO->WriteData(data, nsiz); if (wr > 0) { data += wr; nsiz -= wr; if (!nsiz) break; } ssrc_extra_latency = MulDiv(nsiz, 1000, resample_freq * fmt_nch * (resample_bps >> 3)); SYNC_OUT; Sleep(1); //shouldnt happen anymore since canwrite works correctly SYNC_IN; } pSSRC->Read(nsiz1); ssrc_extra_latency = 0; } } else { pWO->WriteData(data, size); } total_written += size / ((fmt_bps >> 3) * fmt_nch); } } #endif int Write(char *data, int size) { SYNC_IN; gapless_stop = 0; canwrite_hack = 0; // decrypt, if necessary #ifdef HAVE_SSRC _write(data, size); #else if (pWO) { pWO->WriteData(data, size); total_written += size / ((fmt_bps >> 3) * fmt_nch); } #endif SYNC_OUT; return 0; } void Close() { SYNC_IN; if (pWO) { if (gapless_stop) //end-of-song stop, dont close yet, use gapless hacks { pWO->SetCloseOnStop(1); //will self-destruct when out of PCM data to play, has no more than 200ms in buffer } else //regular stop (user action) { delete pWO; pWO = 0; #ifdef HAVE_SSRC if (pSSRC) { delete pSSRC; pSSRC = 0; } #endif } } SYNC_OUT; } int CanWrite() { int r; SYNC_IN; if (pWO) { #ifdef HAVE_SSRC if (pSSRC) { r = MulDiv(pWO->CanWrite() - (resample_bps >> 3) * fmt_nch, fmt_bps * fmt_sr, resample_freq * resample_bps) - pSSRC->GetDataInInbuf(); if (r < 0) r = 0; } else #endif r = pWO->CanWrite(); if (++canwrite_hack > 2) pWO->ForcePlay(); //avoid constant-small-canwrite-while-still-prebuffering snafu } else r = 0; SYNC_OUT; return r; } int IsPlaying() { //this is called only when decoding is done unless some input plugin dev is really nuts about making useless calls SYNC_IN; if (pWO) { #ifdef HAVE_SSRC _write(0, 0); #endif pWO->ForcePlay(); //evil short files: make sure that output has started if ((UINT)pWO->GetLatency() > cfg_trackhack) //cfg_trackhack used to be 200ms constant { //just for the case some input plugin dev is actually nuts about making useless calls or user presses stop/prev/next when decoding is finished, we don't activate gapless_stop here gapless_stop = 0; SYNC_OUT; return 1; } else { //ok so looks like we're really near the end-of-track, time to do gapless track switch mumbo-jumbo gapless_stop = 1; SYNC_OUT; return 0; //hack: make the input plugin think that we're done with current track } } else { SYNC_OUT; return 0; } } int Pause(int new_state) { int rv; SYNC_IN; if (pWO) { rv = pWO->IsPaused(); pWO->Pause(new_state); } else rv = 0; SYNC_OUT; return rv; } void Flush(int pos) { SYNC_IN; if (pWO) pWO->Flush(); #ifdef HAVE_SSRC if (pSSRC) { delete pSSRC; do_ssrc_create(); } #endif reset_stuff(); pos_delta = pos; SYNC_OUT; } void SetVolume(int v) { SYNC_IN; if (v != -666) { volume = v; } if (pWO) pWO->SetVolume(volume); SYNC_OUT; } void SetPan(int p) { SYNC_IN; pan = p; if (pWO) pWO->SetPan(pan); SYNC_OUT; } int get_written_time() //this ignores high 32bits of total_written { return MulDiv((int)total_written, 1000, fmt_sr); } int GetWrittenTime() { int r; SYNC_IN; r = pWO ? pos_delta + get_written_time() : 0; SYNC_OUT; return r; } int GetOutputTime() { int r; SYNC_IN; r = pWO ? (pos_delta + get_written_time()) - pWO->GetLatency() : 0; #ifdef HAVE_SSRC if (pSSRC) r -= ssrc_extra_latency ? ssrc_extra_latency : pSSRC->GetLatency(); #endif SYNC_OUT; return r; } static int Validate(int dummy1, int dummy2, short key, char dummy4); static int NewWrite(int len, char *buf); Out_Module mod = { OUT_VER_U, #ifdef HAVE_SSRC NAME" SSRC", #else 0, #endif 1471482036, //could put different one for SSRC config but i'm too lazy and this shit doesnt seem used anymore anyway 0, 0, Config, About, Init, Quit, Open, Close, Write, CanWrite, IsPlaying, Pause, SetVolume, SetPan, Flush, GetOutputTime, GetWrittenTime, }; HMODULE thisMod=0; Out_Module *(*waveGetter)(HINSTANCE) = 0; HMODULE inWMDLL = 0; BOOL APIENTRY DllMain(HANDLE hMod, DWORD r, void*) { if (r == DLL_PROCESS_ATTACH) { thisMod=(HMODULE)hMod; DisableThreadLibraryCalls((HMODULE)hMod); InitializeCriticalSection(&sync); } else if (r == DLL_PROCESS_DETACH) { DeleteCriticalSection(&sync); if (inWMDLL) { FreeLibrary(inWMDLL); // potentially unsafe, we'll see ... inWMDLL = 0; } } return TRUE; } extern "C" { __declspec( dllexport ) Out_Module * winampGetOutModule() { inWMDLL = GetModuleHandleW(L"in_wm.dll"); if (inWMDLL) { waveGetter = (Out_Module * (*)(HINSTANCE))GetProcAddress(inWMDLL, "GetWave"); if (waveGetter) return waveGetter(thisMod); } return &mod; } } bool get_waveout_state(char * z) { if (pWO) return pWO->PrintState(z); else return 0; }