From 2945069de03fe6d68d5a2e41da87f97f856ea53b Mon Sep 17 00:00:00 2001 From: oz Date: Tue, 26 Jan 2021 17:54:54 +0300 Subject: [PATCH] WaveMix v3. --- Doc/FuncStats.xlsx | Bin 39324 -> 39390 bytes SpaceCadetPinball/Sound.cpp | 10 - SpaceCadetPinball/Sound.h | 2 - SpaceCadetPinball/WaveMix.cpp | 881 +++++++++++++++++++++++++++++++++- SpaceCadetPinball/WaveMix.h | 67 ++- 5 files changed, 912 insertions(+), 48 deletions(-) diff --git a/Doc/FuncStats.xlsx b/Doc/FuncStats.xlsx index 880365d9a6a3fe2a3337cbd35b1bfeadd8bac1ca..78ee3f0b5dfcc22f6a8dbd5fecd831f8c4634ff2 100644 GIT binary patch delta 30738 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 NYf>0)(I=r^^M4&3#)bd@ diff --git a/SpaceCadetPinball/Sound.cpp b/SpaceCadetPinball/Sound.cpp index 6097d31..ae1456f 100644 --- a/SpaceCadetPinball/Sound.cpp +++ b/SpaceCadetPinball/Sound.cpp @@ -143,16 +143,6 @@ void Sound::Close() } } -int Sound::SubFactor(int a1, int a2) -{ - return a1 - a2; -} - -int Sound::AddFactor(int a1, int a2) -{ - return a1 + a2; -} - void Sound::PlaySound(MIXWAVE* wavePtr, int minChannel, int maxChannel, unsigned int dwFlags, __int16 loops) { MIXPLAYPARAMS mixParams{}; diff --git a/SpaceCadetPinball/Sound.h b/SpaceCadetPinball/Sound.h index 5eea4dd..ec427ff 100644 --- a/SpaceCadetPinball/Sound.h +++ b/SpaceCadetPinball/Sound.h @@ -10,8 +10,6 @@ public: static void Activate(); static void Deactivate(); static void Close(); - static int SubFactor(int a1, int a2); - static int AddFactor(int a1, int a2); static void PlaySound(MIXWAVE* wavePtr, int minChannel, int maxChannel, unsigned int dwFlags, __int16 loops); static MIXWAVE* LoadWaveFile(LPCSTR lpName); static void FreeSound(MIXWAVE* wave); diff --git a/SpaceCadetPinball/WaveMix.cpp b/SpaceCadetPinball/WaveMix.cpp index 2c3f53c..c32ce8d 100644 --- a/SpaceCadetPinball/WaveMix.cpp +++ b/SpaceCadetPinball/WaveMix.cpp @@ -1,6 +1,8 @@ #include "pch.h" #include "WaveMix.h" +#include "pinball.h" + int WaveMix::initialized_flag; char WaveMix::FileName[276]; CHANNELNODE WaveMix::channel_nodes[MAXQUEUEDWAVES]; @@ -163,6 +165,42 @@ int WaveMix::FreeWave(HANDLE hMixSession, MIXWAVE* lpMixWave) int WaveMix::Activate(HANDLE hMixSession, bool fActivate) { + GLOBALS* globals = SessionToGlobalDataPtr(hMixSession); + Globals = globals; + if (!globals) + return 5; + if (fActivate) + { + if (GlobalsActive) + return GlobalsActive != globals ? 4 : 0; + if (globals->SettingsDialogActiveFlag) + return 12; + GlobalsActive = globals; + sndPlaySoundA(nullptr, 0); + auto result = GetWaveDevice(); + if (result) + { + GlobalsActive = nullptr; + return result; + } + Globals->fActive = 1; + SetWaveOutPosition(Globals->dwCurrentSample); + XWAVEHDR* xHDR; + do + xHDR = GetWaveBlock(); + while (MixerPlay(xHDR, 1)); + } + else + { + if (globals->fActive) + { + Globals->dwCurrentSample = MyWaveOutGetPosition(globals->hWaveOut, globals->fGoodGetPos); + } + ReleaseWaveDevice(globals); + Globals->fActive = 0; + if (Globals == GlobalsActive) + GlobalsActive = nullptr; + } return 0; } @@ -324,6 +362,108 @@ int WaveMix::ReadConfigSettings(MIXCONFIG* lpConfig) return 1; } + UINT deviceId; + if (static_cast(lpConfig->dwFlags) >= 0) + { + deviceId = GetPrivateProfileIntA("general", "WaveOutDevice", 0, FileName); + } + else + { + deviceId = lpConfig->wDeviceID; + } + Globals->wDeviceID = deviceId; + if (Globals->wDeviceID >= waveDeviceCount) + Globals->wDeviceID = 0; + + if (waveOutGetDevCapsA(Globals->wDeviceID, &Globals->WaveoutCaps, 0x34u) + || !RemoveInvalidIniNameCharacters(Globals->WaveoutCaps.szPname)) + { + lstrcpyA(Globals->WaveoutCaps.szPname, "Unkown Device"); + } + + if (!ReadRegistryToGetMachineSpecificInfSection(Globals->wDeviceID, Globals->szDevicePName, 96)) + { + lstrcpyA(Globals->szDevicePName, GetOperatingSystemPrefix()); + lstrcatA(Globals->szDevicePName, Globals->WaveoutCaps.szPname); + } + + auto remix = GetPrivateProfileIntA("default", "Remix", 1, FileName); + auto goodWavePos = GetPrivateProfileIntA("default", "GoodWavePos", DefaultGoodWavePos(Globals->wDeviceID), + FileName); + auto waveBlocks = GetPrivateProfileIntA("default", "WaveBlocks", 3, FileName); + auto samplesPerSec = GetPrivateProfileIntA("default", "SamplesPerSec", 11, FileName); + if ((Globals->WaveoutCaps.dwSupport & 0x10) != 0) + { + if (!ShowDebugDialogs) + return 0; + wsprintfA(string_buffer, + "%s is a syncronous (blocking) wave output device. This will not permit audio to play while other applications are running.", + Globals->WaveoutCaps.szPname); + MessageBoxA(nullptr, string_buffer, "WavMix32", 0x40u); + return 0; + } + if (GetPrivateProfileIntA("not compatible", Globals->szDevicePName, 0, FileName)) + { + if (!ShowDebugDialogs) + return 0; + wsprintfA(string_buffer, "%s is not compatible with the realtime wavemixer.", Globals->szDevicePName); + MessageBoxA(nullptr, string_buffer, "WavMix32", 0x40u); + return 0; + } + + Globals->pfnRemix = GetPrivateProfileIntA(Globals->szDevicePName, "Remix", remix, FileName) != 2 + ? ResetRemix + : NoResetRemix; + Globals->fGoodGetPos = GetPrivateProfileIntA(Globals->szDevicePName, "GoodWavePos", goodWavePos, FileName) != 0; + Globals->WaveBlockCount = GetPrivateProfileIntA(Globals->szDevicePName, "WaveBlocks", waveBlocks, FileName); + if (Globals->WaveBlockCount < 2) + Globals->WaveBlockCount = 2; + else if (Globals->WaveBlockCount > 10) + Globals->WaveBlockCount = 10; + + Globals->PauseBlocks = GetPrivateProfileIntA(Globals->szDevicePName, "PauseBlocks", + DefaultPauseBlocks(Globals->WaveBlockCount), FileName); + if (Globals->PauseBlocks >= 0) + { + if (Globals->PauseBlocks > Globals->WaveBlockCount) + Globals->PauseBlocks = Globals->WaveBlockCount; + } + else + { + Globals->PauseBlocks = 0; + } + + auto samplesPerSec2 = GetPrivateProfileIntA(Globals->szDevicePName, "SamplesPerSec", samplesPerSec, FileName); + auto channels = GetPrivateProfileIntA(Globals->szDevicePName, "Channels", 1, FileName); + Globals->PCM.wf.nChannels = channels; + if (channels) + { + if (channels > 2u) + Globals->PCM.wf.nChannels = 2; + } + else + { + Globals->PCM.wf.nChannels = 1; + } + DWORD nAvgBytesPerSec; + if (samplesPerSec2 == 22) + { + Globals->PCM.wf.nSamplesPerSec = 22050; + nAvgBytesPerSec = 22050 * Globals->PCM.wf.nChannels; + } + else if (samplesPerSec2 == 44) + { + Globals->PCM.wf.nSamplesPerSec = 44100; + nAvgBytesPerSec = 44100 * Globals->PCM.wf.nChannels; + } + else + { + nAvgBytesPerSec = 11025 * Globals->PCM.wf.nChannels; + } + Globals->PCM.wf.nAvgBytesPerSec = nAvgBytesPerSec; + auto waveBlockLen2 = GetPrivateProfileIntA("default", "WaveBlockLen", 0, FileName); + Globals->dwWaveBlockLen = FigureOutDMABufferSize(waveBlockLen2, &Globals->PCM); + Configure(Globals, nullptr, lpConfig, nullptr, 0); return 1; } @@ -421,20 +561,18 @@ int WaveMix::DefaultPauseBlocks(int waveBlocks) int WaveMix::Configure(GLOBALS* hMixSession, HWND hWndParent, MIXCONFIG* lpConfig, int* flag1Ptr, int saveConfigFlag) { - MIXCONFIG mixConfigLocal; + MIXCONFIG mixConfigLocal{}; + tagWAVEOUTCAPSA pwoc{}; - auto hMixSession2 = hMixSession; auto mixConfig = lpConfig; - auto hMixSession3 = hMixSession; - auto flag1Ptr_2 = flag1Ptr; auto someFlag1 = 0; - auto globals1 = SessionToGlobalDataPtr(hMixSession); - Globals = globals1; - if (!globals1) + auto globals = SessionToGlobalDataPtr(hMixSession); + Globals = globals; + if (!globals) return 5; - if (globals1->fActive) + if (globals->fActive) return 4; - if (globals1->SettingsDialogActiveFlag) + if (globals->SettingsDialogActiveFlag) return 12; FlushChannel(hMixSession, -1, 1u); @@ -443,9 +581,169 @@ int WaveMix::Configure(GLOBALS* hMixSession, HWND hWndParent, MIXCONFIG* lpConfi mixConfigLocal.wSize = 30; mixConfigLocal.dwFlags = 1023; GetConfig(static_cast(hMixSession), &mixConfigLocal); + auto dialog = MakeSettingsDlgTemplate(); + if (!dialog) + return 1; + Globals->SettingsDialogActiveFlag = 1; + auto dlgResult = DialogBoxIndirectParamA(HModule, &dialog->Dialog, hWndParent, SettingsDlgProc, + (LPARAM)&mixConfigLocal); + DestroySettingsDlgTemplate(dialog); + Globals->SettingsDialogActiveFlag = 0; + if (dlgResult != 1) + return 1; + if (Globals->dwWaveBlockLen == mixConfigLocal.WaveBlockLen) + mixConfigLocal.dwFlags &= 0xFFFFFFF7; + + mixConfig = &mixConfigLocal; + } + if (!mixConfig->dwFlags) + return 1; + + globals = SessionToGlobalDataPtr(hMixSession); + Globals = globals; + if (!globals) + return 5; + + auto dwFlags = mixConfig->dwFlags; + if ((dwFlags & 0x100) != 0) + ShowDebugDialogs = mixConfig->ShowDebugDialogs != 0; + if ((dwFlags & 0x80u) != 0) + { + if (mixConfig->wDeviceID != globals->wDeviceID) + { + auto result = waveOutGetDevCapsA(mixConfig->wDeviceID, &pwoc, 0x34u); + if (result) + return result; + memcpy(&Globals->WaveoutCaps, &pwoc, sizeof Globals->WaveoutCaps); + Globals->wDeviceID = mixConfig->wDeviceID; + if (Globals->WaveoutCaps.wChannels == 1 && Globals->PCM.wf.nChannels == 2) + { + Globals->PCM.wf.nChannels = 1; + someFlag1 = 1; + Globals->PCM.wf.nBlockAlign = 1; + Globals->PCM.wf.nAvgBytesPerSec = Globals->PCM.wf.nSamplesPerSec; + } + lstrcpyA(globals->szDevicePName, pwoc.szPname); + if (!RemoveInvalidIniNameCharacters(Globals->szDevicePName)) + lstrcpyA(Globals->szDevicePName, "Unkown Device"); + if (!ReadRegistryToGetMachineSpecificInfSection(Globals->wDeviceID, Globals->szDevicePName, 96)) + { + lstrcpyA(Globals->szDevicePName, GetOperatingSystemPrefix()); + lstrcatA(Globals->szDevicePName, Globals->WaveoutCaps.szPname); + } + } } - return 1; + if ((dwFlags & 1) != 0) + { + if (mixConfig->wChannels <= 1u) + { + if (globals->PCM.wf.nChannels == 2) + someFlag1 = 1; + globals->PCM.wf.nChannels = 1; + globals->PCM.wf.nBlockAlign = 1; + globals->PCM.wf.nAvgBytesPerSec = globals->PCM.wf.nSamplesPerSec; + } + if (globals->WaveoutCaps.wChannels > 1u) + { + if (globals->PCM.wf.nChannels == 1) + someFlag1 = 1; + globals->PCM.wf.nChannels = 2; + globals->PCM.wf.nBlockAlign = 2; + globals->PCM.wf.nAvgBytesPerSec = 2 * globals->PCM.wf.nSamplesPerSec; + } + } + + if ((dwFlags & 2) != 0) + { + auto nAvgBytesPerSec = 0u; + if (mixConfig->wSamplingRate == 22) + { + if (globals->PCM.wf.nSamplesPerSec != 22050) + someFlag1 = 1; + globals->PCM.wf.nSamplesPerSec = 22050; + nAvgBytesPerSec = 22050 * globals->PCM.wf.nChannels; + } + else + { + if (mixConfig->wSamplingRate == 44) + { + if (globals->PCM.wf.nSamplesPerSec != 44100) + someFlag1 = 1; + globals->PCM.wf.nSamplesPerSec = 44100; + nAvgBytesPerSec = 44100 * globals->PCM.wf.nChannels; + } + else + { + if (globals->PCM.wf.nSamplesPerSec != 11025) + someFlag1 = 1; + globals->PCM.wf.nSamplesPerSec = 11025; + nAvgBytesPerSec = 11025 * globals->PCM.wf.nChannels; + } + } + globals->PCM.wf.nAvgBytesPerSec = nAvgBytesPerSec; + } + + if ((dwFlags & 4) != 0) + { + auto v24 = 2; + globals->WaveBlockCount = mixConfig->WaveBlockCount; + if (mixConfig->WaveBlockCount < 2) + globals->WaveBlockCount = 2; + else if (mixConfig->WaveBlockCount > 10) + globals->WaveBlockCount = 10; + if (globals->PauseBlocks >= 0) + { + if (globals->PauseBlocks > globals->WaveBlockCount) + globals->PauseBlocks = globals->WaveBlockCount; + } + else + { + globals->PauseBlocks = 0; + } + } + + if (someFlag1) + globals->dwWaveBlockLen = FigureOutDMABufferSize(0, &globals->PCM); + + if ((dwFlags & 8) != 0) + { + if (mixConfig->WaveBlockLen) + globals->dwWaveBlockLen = mixConfig->WaveBlockLen; + } + + if (globals->dwWaveBlockLen < 344) + globals->dwWaveBlockLen = 344; + else if (globals->dwWaveBlockLen > 11025) + globals->dwWaveBlockLen = 11025; + + auto dwFlags2 = dwFlags; + if ((dwFlags & 0x10) != 0) + globals->CmixPtr = cmixit; + + if ((dwFlags2 & 0x20) != 0) + { + globals->pfnRemix = !mixConfig->ResetMixDefaultFlag ? NoResetRemix : ResetRemix; + } + + if ((dwFlags2 & 0x40) != 0) + globals->fGoodGetPos = mixConfig->GoodWavePos != 0; + + if ((dwFlags2 & 0x200) != 0) + { + globals->PauseBlocks = mixConfig->PauseBlocks; + if (mixConfig->PauseBlocks > globals->WaveBlockCount) + globals->PauseBlocks = globals->WaveBlockCount; + } + + GetConfig(hMixSession, mixConfig); + if (flag1Ptr) + *flag1Ptr = someFlag1; + if (saveConfigFlag) + SaveConfigSettings(dwFlags); + if (ShowDebugDialogs) + ShowCurrentSettings(); + return 0; } int WaveMix::GetConfig(HANDLE hMixSession, MIXCONFIG* lpConfig) @@ -461,11 +759,11 @@ int WaveMix::GetConfig(HANDLE hMixSession, MIXCONFIG* lpConfig) if ((dwFlags & 1) != 0) lpConfig->wChannels = globals->PCM.wf.nChannels; if ((dwFlags & 2) != 0) - lpConfig->wSamplingRate = 11 * (globals->PCM.wf.nSamplesPerSec / 0x2B11); + lpConfig->wSamplingRate = static_cast(11 * (globals->PCM.wf.nSamplesPerSec / 0x2B11)); if ((dwFlags & 4) != 0) lpConfig->WaveBlockCount = globals->WaveBlockCount; if ((dwFlags & 8) != 0) - lpConfig->WaveBlockLen = globals->WaveBlockLen; + lpConfig->WaveBlockLen = static_cast(globals->dwWaveBlockLen); if ((dwFlags & 0x10) != 0) lpConfig->CmixPtrDefaultFlag = globals->CmixPtr == cmixit; if ((dwFlags & 0x20) != 0) @@ -481,14 +779,14 @@ int WaveMix::GetConfig(HANDLE hMixSession, MIXCONFIG* lpConfig) return 0; } -unsigned WaveMix::MyWaveOutGetPosition(HWAVEOUT hwo, int fGoodGetPos) +unsigned WaveMix::MyWaveOutGetPosition(HWAVEOUT hWaveOut, int fGoodGetPos) { mmtime_tag pmmt{}; if (!fGoodGetPos) return (timeGetTime() - Globals->dwBaseTime) * Globals->PCM.wf.nAvgBytesPerSec / 0x3E8 & 0xFFFFFFF8; - pmmt.wType = 4; - waveOutGetPosition(hwo, &pmmt, 0xCu); + pmmt.wType = TIME_BYTES; + waveOutGetPosition(hWaveOut, &pmmt, 0xCu); return Globals->pfnSampleAdjust(pmmt.u.ms, Globals->dwWaveOutPos); } @@ -520,6 +818,37 @@ int WaveMix::ResetRemix(DWORD dwRemixSamplePos, CHANNELNODE* channel) AddToPlayingQueue(block); } + auto dwCurrentSamplePrev = Globals->dwCurrentSample; + MyWaveOutReset(Globals->hWaveOut); + Globals->dwCurrentSample = dwCurrentSamplePrev; + auto pauseFlag = 0; + if (Globals->PauseBlocks > 0) + { + waveOutPause(Globals->hWaveOut); + pauseFlag = 1; + } + + auto pauseCount = 0; + for (auto xHDR = play_queue.first; xHDR; xHDR = xHDR->QNext) + { + if (waveOutWrite(Globals->hWaveOut, &xHDR->wh, sizeof(WAVEHDR))) + { + if (ShowDebugDialogs) + MessageBoxA(nullptr, "Failed to write block to device", "WavMix32", 0x30u); + xHDR->fAvailable = 1; + RemoveFromPlayingQueue(xHDR); + } + if (pauseFlag) + { + if (++pauseCount >= Globals->PauseBlocks) + { + waveOutRestart(Globals->hWaveOut); + pauseFlag = 0; + } + } + } + if (pauseFlag) + waveOutRestart(Globals->hWaveOut); return 1; } @@ -585,7 +914,7 @@ XWAVEHDR* WaveMix::GetWaveBlock() XWAVEHDR* result = Globals->WaveBlockArray[index]; result->fAvailable = 0; - result->wh.dwBufferLength = Globals->WaveBlockLen; + result->wh.dwBufferLength = Globals->dwWaveBlockLen; result->wh.lpData = reinterpret_cast(&result[1]); result->g = GlobalsActive; return result; @@ -629,7 +958,7 @@ int WaveMix::MixerPlay(XWAVEHDR* lpXWH, int fWriteBlocks) auto currentSample = Globals->dwCurrentSample; auto dataPtr = reinterpret_cast(lpXWH->wh.lpData); - auto waveBlockLen = Globals->WaveBlockLen; + auto waveBlockLen = Globals->dwWaveBlockLen; while (waveBlockLen) { if (currentSample >= minStartPos) @@ -666,7 +995,7 @@ int WaveMix::MixerPlay(XWAVEHDR* lpXWH, int fWriteBlocks) if (waveCount) { auto dataLength = endBlockPosition - currentSample; - Globals->CmixPtr(dataPtr, play_data, play_volume, waveCount, dataLength); + Globals->CmixPtr(dataPtr, play_data, play_volume, waveCount, static_cast(dataLength)); dataPtr += dataLength; waveBlockLen -= dataLength; @@ -713,11 +1042,11 @@ int WaveMix::MixerPlay(XWAVEHDR* lpXWH, int fWriteBlocks) } lpXWH->dwWavePos = Globals->dwCurrentSample; - Globals->dwCurrentSample += Globals->WaveBlockLen; + Globals->dwCurrentSample += Globals->dwWaveBlockLen; if (fWriteBlocks) { AddToPlayingQueue(lpXWH); - if (waveOutWrite(Globals->hWaveOut, &lpXWH->wh, 0x20u)) + if (waveOutWrite(Globals->hWaveOut, &lpXWH->wh, sizeof(WAVEHDR))) { if (ShowDebugDialogs) MessageBoxA(nullptr, "Failed to write block to device", "WavMix32", 0x30u); @@ -743,6 +1072,508 @@ XWAVEHDR* WaveMix::AddToPlayingQueue(XWAVEHDR* lpXWH) return play_queue.first; } +void WaveMix::MyWaveOutReset(HWAVEOUT hWaveOut) +{ + auto position = MyWaveOutGetPosition(hWaveOut, Globals->fGoodGetPos); + waveOutReset(hWaveOut); + SetWaveOutPosition(position); +} + +void WaveMix::SetWaveOutPosition(unsigned newPosition) +{ + DWORD position; + mmtime_tag pmmt{}; + + pmmt.wType = TIME_BYTES; + if (Globals->hWaveOut) + { + waveOutGetPosition(Globals->hWaveOut, &pmmt, 0xCu); + position = pmmt.u.ms; /*Todo: check union ms vs sample with TIME_BYTES*/ + } + else + { + position = 0; + } + + if (position < newPosition) + { + Globals->dwWaveOutPos = newPosition - position; + Globals->pfnSampleAdjust = AddFactor; + } + else + { + Globals->dwWaveOutPos = position - newPosition; + Globals->pfnSampleAdjust = SubFactor; + } + Globals->dwCurrentSample = newPosition; + Globals->dwBaseTime = timeGetTime() - 1000 * newPosition / Globals->PCM.wf.nAvgBytesPerSec; +} + +DWORD WaveMix::SubFactor(DWORD a1, DWORD a2) +{ + return a1 - a2; +} + +DWORD WaveMix::AddFactor(DWORD a1, DWORD a2) +{ + return a1 + a2; +} + +dialog_template* WaveMix::MakeSettingsDlgTemplate() +{ + auto size = 0u; + dialog_template* temp = MakeDlgTemplate(&size, 0x80C80080, 0, 0, 212, 132, L"WavMix32 Settings (Ver %X.%X Static)"); + temp = AddDlgControl(&size, temp, 128, 65537, 1, 155, 5, 50, 14, L"&Ok"); + temp = AddDlgControl(&size, temp, 128, 0x10000, 2, 155, 25, 50, 14, L"&Cancel"); + temp = AddDlgControl(&size, temp, 130, 0, 1012, 5, 5, 115, 8, L"Number of WaveBlocks (%d-%d):"); + temp = AddDlgControl(&size, temp, 130, 0, 1013, 5, 20, 115, 8, L"Size of WaveBlocks (%d-%d):"); + temp = AddDlgControl(&size, temp, 130, 0, 0xFFFF, 5, 35, 102, 8, L"Playback Frequency (11,22,44):"); + temp = AddDlgControl(&size, temp, 130, 0, 0xFFFF, 5, 80, 57, 8, L"Playback Device:"); + temp = AddDlgControl(&size, temp, 130, 0, 1014, 5, 50, 115, 8, L"Number of Pause Blocks (%d-%d):"); + temp = AddDlgControl(&size, temp, 130, 0, 1015, 5, 65, 63, 8, L"Max Channels = %d"); + temp = AddDlgControl(&size, temp, 129, 8454272, 1003, 125, 5, 25, 12, L"-1"); + temp = AddDlgControl(&size, temp, 129, 8454272, 1007, 125, 20, 25, 12, L"-1"); + temp = AddDlgControl(&size, temp, 129, 8454272, 1008, 125, 35, 25, 12, L"-1"); + temp = AddDlgControl(&size, temp, 129, 8454272, 1011, 125, 50, 25, 12, L"-1"); + temp = AddDlgControl(&size, temp, 133, 2162946, 1009, 65, 80, 140, 60, L"No Devices"); + temp = AddDlgControl(&size, temp, 128, 65539, 1000, 5, 101, 40, 10, L"Stereo"); + temp = AddDlgControl(&size, temp, 128, 65539, 1001, 55, 101, 60, 10, L"Reset Remix"); + temp = AddDlgControl(&size, temp, 128, 65539, 1010, 125, 101, 78, 10, L"Show Debug Dialogs"); + temp = AddDlgControl(&size, temp, 128, 65539, 1005, 5, 117, 40, 10, L"CMixit"); + return AddDlgControl(&size, temp, 128, 65539, 1004, 55, 117, 75, 10, L"Good Get Position"); +} + +dialog_template* WaveMix::MakeDlgTemplate(unsigned* totalSize, unsigned style, short x, short y, short cx, short cy, + const wchar_t* String) +{ + auto dlgSize = 2 * wcslen(String) + 24; + *totalSize = dlgSize; + if ((dlgSize & 3) != 0) + *totalSize = dlgSize - (dlgSize & 3) + 4; + + auto hGlobal = GlobalAlloc(GHND, *totalSize); + auto dlgTemplate = static_cast(GlobalLock(hGlobal)); + if (dlgTemplate) + { + dlgTemplate->Dialog.dwExtendedStyle = 0; + dlgTemplate->Dialog.cdit = 0; + dlgTemplate->Dialog.style = style | 0x10000000; + dlgTemplate->Dialog.x = x; + dlgTemplate->Dialog.y = y; + dlgTemplate->Dialog.cx = cx; + dlgTemplate->Dialog.cy = cy; + memcpy(dlgTemplate->Header, String, 2 * wcslen(String) + 2); + } + return dlgTemplate; +} + +dialog_template* WaveMix::AddDlgControl(unsigned* totalSize, dialog_template* dlgTemplate, short idClass, + unsigned style, WORD id, short x, short y, short cx, short cy, + const wchar_t* String) +{ + dialog_template* dlgTemplate2 = dlgTemplate; + if (dlgTemplate) + { + auto prevSize = *totalSize; + *totalSize += 2 * wcslen(String) + 25; + if ((*totalSize & 3) != 0) + *totalSize = *totalSize - (*totalSize & 3) + 4; + + GlobalUnlock(GlobalHandle(dlgTemplate2)); + unsigned int newSize = *totalSize; + HGLOBAL hGlobal = GlobalReAlloc(GlobalHandle(dlgTemplate2), newSize, 0x42u); + dlgTemplate2 = static_cast(GlobalLock(hGlobal)); + if (dlgTemplate2) + { + auto dlgItem = (dialog_item_template*)((char*)dlgTemplate2 + prevSize); + dlgItem->Item.dwExtendedStyle = 0; + dlgItem->Item.style = style | 0x50000000; + dlgItem->Item.x = x; + dlgItem->Item.y = y; + dlgItem->Item.cx = cx; + dlgItem->Item.cy = cy; + dlgItem->Item.id = id; + dlgItem->sysClass = 0xFFFF; + dlgItem->idClass = idClass; + wcscpy_s(dlgItem->Header, wcslen(String) + 1, String); + ++dlgTemplate2->Dialog.cdit; + } + } + return dlgTemplate2; +} + +void WaveMix::DestroySettingsDlgTemplate(LPCVOID pMem) +{ + if (pMem) + { + GlobalUnlock(GlobalHandle(pMem)); + GlobalFree(GlobalHandle(pMem)); + } +} + +int WaveMix::Settings_OnInitDialog(HWND hWnd, int wParam, MIXCONFIG* lpMixconfig) +{ + tagWAVEOUTCAPSA pwoc{}; + CHAR String[256]; + + GetWindowTextA(hWnd, String, 256); + wsprintfA(string_buffer, String, 2, 81); + SetWindowTextA(hWnd, string_buffer); + SetWindowLongA(hWnd, -21, (LONG)lpMixconfig); + SendMessageA(GetDlgItem(hWnd, 1000), 0xF1u, lpMixconfig->wChannels > 1u, 0); + SendMessageA(GetDlgItem(hWnd, 1001), 0xF1u, lpMixconfig->ResetMixDefaultFlag != 0, 0); + SendMessageA(GetDlgItem(hWnd, 1004), 0xF1u, lpMixconfig->GoodWavePos != 0, 0); + SendMessageA(GetDlgItem(hWnd, 1005), 0xF1u, lpMixconfig->CmixPtrDefaultFlag != 0, 0); + SendMessageA(GetDlgItem(hWnd, 1010), 0xF1u, lpMixconfig->ShowDebugDialogs != 0, 0); + EnableWindow(GetDlgItem(hWnd, 1005), 0); + SendMessageA(GetDlgItem(hWnd, 1003), 0xC5u, 2u, 0); + SendMessageA(GetDlgItem(hWnd, 1007), 0xC5u, 4u, 0); + SendMessageA(GetDlgItem(hWnd, 1008), 0xC5u, 2u, 0); + SendMessageA(GetDlgItem(hWnd, 1011), 0xC5u, 2u, 0); + GetWindowTextA(GetDlgItem(hWnd, 1012), String, 100); + wsprintfA(string_buffer, String, 2, 10); + SetWindowTextA(GetDlgItem(hWnd, 1012), string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1013), String, 100); + wsprintfA(string_buffer, String, 344, 11025); + SetWindowTextA(GetDlgItem(hWnd, 1013), string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1014), String, 100); + wsprintfA(string_buffer, String, 0, 10); + SetWindowTextA(GetDlgItem(hWnd, 1014), string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1015), String, 100); + wsprintfA(string_buffer, String, 16); + SetWindowTextA(GetDlgItem(hWnd, 1015), string_buffer); + wsprintfA(string_buffer, "%d", lpMixconfig->WaveBlockCount); + SetWindowTextA(GetDlgItem(hWnd, 1003), string_buffer); + wsprintfA(string_buffer, "%d", lpMixconfig->WaveBlockLen); + SetWindowTextA(GetDlgItem(hWnd, 1007), string_buffer); + wsprintfA(string_buffer, "%d", lpMixconfig->wSamplingRate); + SetWindowTextA(GetDlgItem(hWnd, 1008), string_buffer); + wsprintfA(string_buffer, "%d", lpMixconfig->PauseBlocks); + SetWindowTextA(GetDlgItem(hWnd, 1011), string_buffer); + + signed int uDeviceID = 0; + signed int deviceCount = waveOutGetNumDevs(); + if (deviceCount > 0) + { + do + { + MMRESULT getResult = waveOutGetDevCapsA(uDeviceID, &pwoc, 0x34u); + if (getResult) + { + wsprintfA(string_buffer, "waveOutGetDevCaps failed (err %u) for device %d", getResult, uDeviceID); + MessageBoxA(nullptr, string_buffer, "WavMix32", 0x40u); + } + else + { + wsprintfA(string_buffer, "%d: %s", uDeviceID, pwoc.szPname); + SendMessageA(GetDlgItem(hWnd, 1009), 0x143u, 0, (LPARAM)string_buffer); + } + ++uDeviceID; + } + while (uDeviceID < deviceCount); + } + SendMessageA(GetDlgItem(hWnd, 1009), 0x14Eu, lpMixconfig->wDeviceID, 0); + return 1; +} + +int WaveMix::Settings_OnCommand(HWND hWnd, int command, int lParam, int wParam) +{ + auto userData = (MIXCONFIG*)GetWindowLongA(hWnd, -21); + if (command == 1) + { + if (userData) + { + userData->wChannels = (SendMessageA(GetDlgItem(hWnd, 1000), 0xF0u, 0, 0) != 0) + 1; + userData->ResetMixDefaultFlag = SendMessageA(GetDlgItem(hWnd, 1001), 0xF0u, 0, 0) != 0; + userData->GoodWavePos = SendMessageA(GetDlgItem(hWnd, 1004), 0xF0u, 0, 0) != 0; + userData->ShowDebugDialogs = SendMessageA(GetDlgItem(hWnd, 1010), 0xF0u, 0, 0) != 0; + userData->CmixPtrDefaultFlag = SendMessageA(GetDlgItem(hWnd, 1005), 0xF0u, 0, 0) != 0; + GetWindowTextA(GetDlgItem(hWnd, 1003), string_buffer, 10); + userData->WaveBlockCount = atoi(string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1007), string_buffer, 10); + userData->WaveBlockLen = atoi(string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1008), string_buffer, 10); + userData->wSamplingRate = atoi(string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1011), string_buffer, 10); + userData->PauseBlocks = atoi(string_buffer); + GetWindowTextA(GetDlgItem(hWnd, 1009), string_buffer, 10); + userData->wDeviceID = isdigit(string_buffer[0]) ? atoi(string_buffer) : 0; + EndDialog(hWnd, 1); + } + } + else + { + if (command != 2) + return 0; + EndDialog(hWnd, 0); + } + return 1; +} + +int WaveMix::ReadRegistryToGetMachineSpecificInfSection(unsigned wDeviceId, LPSTR lpString1, int maxLength) +{ + HKEY phkResult; + CHAR SubKey[52]; + + int result = 0; + int osPrefixLength = lstrlenA(GetOperatingSystemPrefix()); + if (maxLength < osPrefixLength + 10) + return 0; + wsprintfA(SubKey, "Device%u", wDeviceId); + if (RegOpenKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\WaveMix", &phkResult)) + return 0; + lstrcpyA(lpString1, GetOperatingSystemPrefix()); + LONG cbData = maxLength - osPrefixLength; + if (!RegQueryValueA(phkResult, SubKey, &lpString1[osPrefixLength], &cbData) && cbData > 0) + result = 1; + RegCloseKey(phkResult); + return result; +} + +#pragma warning (disable : 4996)/*Original uses GetVersion*/ +const char* WaveMix::GetOperatingSystemPrefix() +{ + if (GetVersion() < 0x80000000) + return "WinNT:"; + if (GetVersion() >= 0x80000000 && static_cast(GetVersion()) >= 4u) + return "Win95:"; + if (GetVersion() >= 0x80000000 && static_cast(GetVersion()) < 4u) + return "Win31:"; + return "OS_X"; /*The next big thing: waveOut on OSX*/ +} +#pragma warning (default : 4996)/*Original uses GetVersion*/ + +unsigned WaveMix::FigureOutDMABufferSize(unsigned waveBlockLen, PCMWAVEFORMAT* pcm) +{ + unsigned int result = waveBlockLen; + if (!waveBlockLen) + result = pcm->wf.nSamplesPerSec * pcm->wf.nChannels * (pcm->wBitsPerSample >> 3) >> 4; + if (result < 344) + result = 344; + if (result > 11025) + result = 11025; + return result; +} + +int WaveMix::NoResetRemix(DWORD dwRemixSamplePos, CHANNELNODE* channel) +{ + Pump(); + return 1; +} + +void WaveMix::SaveConfigSettings(unsigned dwFlags) +{ + if ((dwFlags & 0x80u) != 0) + { + wsprintfA(string_buffer, "%d", Globals->wDeviceID); + WritePrivateProfileStringA("general", "WaveOutDevice", string_buffer, FileName); + } + if ((dwFlags & 1) != 0) + { + wsprintfA(string_buffer, "%d", Globals->PCM.wf.nChannels); + WritePrivateProfileStringA(Globals->szDevicePName, "Channels", string_buffer, FileName); + } + if ((dwFlags & 2) != 0) + { + wsprintfA(string_buffer, "%d", static_cast(11 * (Globals->PCM.wf.nSamplesPerSec / 0x2B11))); + WritePrivateProfileStringA(Globals->szDevicePName, "SamplesPerSec", string_buffer, FileName); + } + if ((dwFlags & 4) != 0) + { + wsprintfA(string_buffer, "%d", Globals->WaveBlockCount); + WritePrivateProfileStringA(Globals->szDevicePName, "WaveBlocks", string_buffer, FileName); + } + if ((dwFlags & 8) != 0) + { + wsprintfA(string_buffer, "%d", LOWORD(WaveMix::Globals->dwWaveBlockLen)); + WritePrivateProfileStringA(Globals->szDevicePName, "WaveBlockLen", string_buffer, FileName); + } + if ((dwFlags & 0x10) != 0) + { + wsprintfA(string_buffer, "%d", Globals->CmixPtr == cmixit); + WritePrivateProfileStringA(Globals->szDevicePName, "CMixit", string_buffer, FileName); + } + if ((dwFlags & 0x20) != 0) + { + wsprintfA(string_buffer, "%d", (Globals->pfnRemix != ResetRemix) + 1); + WritePrivateProfileStringA(Globals->szDevicePName, "Remix", string_buffer, FileName); + } + if ((dwFlags & 0x40) != 0) + { + wsprintfA(string_buffer, "%d", Globals->fGoodGetPos == 0); + WritePrivateProfileStringA(Globals->szDevicePName, "GoodWavePos", string_buffer, FileName); + } + if ((dwFlags & 0x100) != 0) + { + wsprintfA(string_buffer, "%d", ShowDebugDialogs != 0); + WritePrivateProfileStringA("general", "ShowDebugDialogs", string_buffer, FileName); + } + if ((dwFlags & 0x200) != 0) + { + wsprintfA(string_buffer, "%d", Globals->PauseBlocks); + WritePrivateProfileStringA(Globals->szDevicePName, "PauseBlocks", string_buffer, FileName); + } +} + +void WaveMix::ShowCurrentSettings() +{ + tagWAVEOUTCAPSA pwoc{}; + + if (waveOutGetDevCapsA(Globals->wDeviceID, &pwoc, 0x34u) || !RemoveInvalidIniNameCharacters(pwoc.szPname)) + lstrcpyA(pwoc.szPname, "Unknown Device"); + auto cmixitType = "cmixit"; + if (Globals->CmixPtr != cmixit) + cmixitType = "386 mixit"; + auto getGood = "TRUE"; + if (!Globals->fGoodGetPos) + getGood = "FALSE"; + auto remixType = "Reset"; + if (Globals->pfnRemix != ResetRemix) + remixType = "NoReset"; + wsprintfA( + string_buffer, + "Using:\t%s.\n" + "\tRemix = %s\n" + "\tGoodGetPos=%s\n" + "\t%d ping pong wave blocks\n" + "\tWave block len = %lu bytes\n" + "\tpfnMixit=%s\n" + "\tSamplesPerSec=%lu,\n" + "\tChannels=%d\n" + "\tPauseBlocks=%d", + pwoc.szPname, + remixType, + getGood, + Globals->WaveBlockCount, + Globals->dwWaveBlockLen, + cmixitType, + Globals->PCM.wf.nSamplesPerSec, + Globals->PCM.wf.nChannels, + Globals->PauseBlocks); + MessageBoxA(nullptr, string_buffer, "WavMix32", 0x40u); +} + +unsigned WaveMix::GetWaveDevice() +{ + WAVEFORMATEX pwfx{}; + + if (Globals->hWaveOut) + return 0; + HWND window = CreateWindowExA(0, "WavMix32", pinball::WindowName, 0x8000000u, 0, 0, 0, 0, nullptr, nullptr, HModule, + nullptr); + GLOBALS* globals = Globals; + Globals->hWndApp = window; + if (!window) + { + if (ShowDebugDialogs) + MessageBoxA(nullptr, "Failed to create callback window.", "WavMix32", 0x30u); + return 1; + } + pwfx.wFormatTag = globals->PCM.wf.wFormatTag; + pwfx.nChannels = globals->PCM.wf.nChannels; + pwfx.nSamplesPerSec = globals->PCM.wf.nSamplesPerSec; + pwfx.nAvgBytesPerSec = globals->PCM.wf.nAvgBytesPerSec; + pwfx.nBlockAlign = globals->PCM.wf.nBlockAlign; + pwfx.wBitsPerSample = globals->PCM.wBitsPerSample; + pwfx.cbSize = 0; + unsigned int openResult = waveOutOpen(&globals->hWaveOut, 0xFFFFFFFF, &pwfx, + reinterpret_cast(globals->hWndApp), 0, + 0x10000u); + if (openResult) + { + DestroyWindow(Globals->hWndApp); + Globals->hWndApp = nullptr; + return openResult; + } + if (AllocWaveBlocks(Globals->hWaveOut, block_array1) + && AllocWaveBlocks(Globals->hWaveOut, block_array2)) + { + Globals->WaveBlockArray = block_array1; + return 0; + } + FreeWaveBlocks(Globals->hWaveOut, block_array1); + FreeWaveBlocks(Globals->hWaveOut, block_array2); + waveOutClose(Globals->hWaveOut); + Globals->hWaveOut = nullptr; + DestroyWindow(Globals->hWndApp); + return 1; +} + +void WaveMix::FreeWaveBlocks(HWAVEOUT hwo, XWAVEHDR** waveBlocks) +{ + for (int i = 0; i < 10; ++i) + { + auto blockPtr = &waveBlocks[i]; + if (*blockPtr) + { + waveOutUnprepareHeader(hwo, &(*blockPtr)->wh, sizeof(WAVEHDR)); + GlobalUnlock(GlobalHandle(*blockPtr)); + GlobalFree(GlobalHandle(*blockPtr)); + *blockPtr = nullptr; + } + } +} + +int WaveMix::AllocWaveBlocks(HWAVEOUT hwo, XWAVEHDR** waveBlocks) +{ + for (int i = 0; i < 10; ++i) + { + auto xHDR = static_cast(GlobalLock(GlobalAlloc(GMEM_SHARE, 0x2B41u))); + waveBlocks[i] = xHDR; + if (!xHDR) + { + if (ShowDebugDialogs) + MessageBoxA( + nullptr, + "Unable to allocate memory for waveform data. Try making more memory available by closing other applications.", + "WavMix32", + 0x30u); + for (int j = i - 1; j >= 0; --j) + { + GlobalUnlock(GlobalHandle(waveBlocks[j])); + GlobalFree(GlobalHandle(waveBlocks[j])); + waveBlocks[j] = nullptr; + } + return 0; + } + xHDR->wh.lpData = (LPSTR)&xHDR[1]; + xHDR->wh.dwBufferLength = Globals->dwWaveBlockLen; + xHDR->wh.dwFlags = 0; + xHDR->wh.dwLoops = 0; + xHDR->fAvailable = 1; + xHDR->dwWavePos = 0; + } + int index = 0; + while (!waveOutPrepareHeader(hwo, &waveBlocks[index]->wh, sizeof(WAVEHDR))) + { + waveBlocks[index++]->wh.dwFlags |= 1u; + if (index >= 10) + return 1; + } + if (ShowDebugDialogs) + MessageBoxA(nullptr, "Unable to prepare wave header.", "WavMix32", 0x30u); + FreeWaveBlocks(hwo, waveBlocks); + return 0; +} + +void WaveMix::ReleaseWaveDevice(GLOBALS* globals) +{ + if (globals->fActive) + { + if (globals->hWaveOut) + { + MyWaveOutReset(globals->hWaveOut); + DestroyPlayQueue(); + FreeWaveBlocks(globals->hWaveOut, block_array1); + FreeWaveBlocks(globals->hWaveOut, block_array2); + waveOutClose(globals->hWaveOut); + globals->hWaveOut = nullptr; + DestroyWindow(globals->hWndApp); + globals->hWndApp = nullptr; + } + } +} + void WaveMix::cmixit(unsigned __int8* lpDest, unsigned __int8** rgWaveSrc, volume_struct* volume, int iNumWaves, unsigned __int16 length) { @@ -755,3 +1586,13 @@ LRESULT WaveMix::WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) Pump(); return 0; } + +INT_PTR WaveMix::SettingsDlgProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + if (Msg == 272) + return Settings_OnInitDialog(hWnd, wParam, (MIXCONFIG*)lParam); + if (Msg != 273) + return 0; + Settings_OnCommand(hWnd, static_cast(wParam), lParam, HIWORD(wParam)); + return 1; +} diff --git a/SpaceCadetPinball/WaveMix.h b/SpaceCadetPinball/WaveMix.h index 6abfea4..6f24469 100644 --- a/SpaceCadetPinball/WaveMix.h +++ b/SpaceCadetPinball/WaveMix.h @@ -58,13 +58,13 @@ struct MIXCONFIG DWORD dwFlags; WORD wChannels; WORD wSamplingRate; - __int16 WaveBlockCount; - __int16 WaveBlockLen; + unsigned __int16 WaveBlockCount; + unsigned __int16 WaveBlockLen; __int16 CmixPtrDefaultFlag; unsigned __int16 ResetMixDefaultFlag; unsigned __int16 GoodWavePos; unsigned __int16 wDeviceID; - __int16 PauseBlocks; + unsigned __int16 PauseBlocks; __int16 ShowDebugDialogs; HKEY RegistryKey; }; @@ -88,20 +88,13 @@ struct GLOBALS { WORD wMagic1; __int16 unknown0; - int unknown1; + HWND hWndApp; int unknown2; HWAVEOUT hWaveOut; int fActive; int SettingsDialogActiveFlag; unsigned int wDeviceID; - int unknown7; - int unknown8; - int unknown9; - int unknown10; - int unknown11; - int unknown12; - int unknown13; - int unknown14; + char szDevicePName[32]; int unknown15; int unknown16; int unknown17; @@ -156,23 +149,39 @@ struct GLOBALS int unknown93; int unknown94; PCMWAVEFORMAT PCM; - int WaveBlockLen; + DWORD dwWaveBlockLen; int WaveBlockCount; int PauseBlocks; XWAVEHDR** WaveBlockArray; DWORD dwCurrentSample; DWORD dwBaseTime; int fGoodGetPos; - int dwWaveOutPos; + DWORD dwWaveOutPos; void (*CmixPtr)(unsigned __int8* lpDest, unsigned __int8** rgWaveSrc, volume_struct* volume, int iNumWaves, unsigned __int16 length); int (* pfnRemix)(DWORD, CHANNELNODE*); - int (* pfnSampleAdjust)(int, int); + DWORD (* pfnSampleAdjust)(DWORD, DWORD); int unknown110; __int16 wMagic2; __int16 unknown112; }; +struct dialog_template +{ + DLGTEMPLATE Dialog; + WORD menu; + WORD windowClass; + WCHAR Header[1]; +}; + +struct dialog_item_template +{ + DLGITEMTEMPLATE Item; + WORD sysClass; + WORD idClass; + WCHAR Header[1]; +}; + class WaveMix { @@ -204,7 +213,7 @@ private: static int DefaultPauseBlocks(int waveBlocks); static int Configure(GLOBALS* hMixSession, HWND hWndParent, MIXCONFIG* lpConfig, int* flag1Ptr, int saveConfigFlag); static int GetConfig(HANDLE hMixSession, MIXCONFIG* lpConfig); - static unsigned MyWaveOutGetPosition(HWAVEOUT hwo, int fGoodGetPos); + static unsigned MyWaveOutGetPosition(HWAVEOUT hWaveOut, int fGoodGetPos); static void FreeChannelNode(CHANNELNODE* channel); static int ResetRemix(DWORD dwRemixSamplePos, CHANNELNODE* channel); static XWAVEHDR* RemoveFromPlayingQueue(XWAVEHDR* lpXWH); @@ -213,9 +222,35 @@ private: static XWAVEHDR* GetWaveBlock(); static int MixerPlay(XWAVEHDR* lpXWH, int fWriteBlocks); static XWAVEHDR* AddToPlayingQueue(XWAVEHDR* lpXWH); + static void MyWaveOutReset(HWAVEOUT hWaveOut); + static void SetWaveOutPosition(unsigned int newPosition); + static DWORD SubFactor(DWORD a1, DWORD a2); + static DWORD AddFactor(DWORD a1, DWORD a2); + static dialog_template* MakeSettingsDlgTemplate(); + static dialog_template* MakeDlgTemplate(unsigned* totalSize, unsigned style, __int16 x, __int16 y, __int16 cx, + __int16 cy, + const wchar_t* String); + static dialog_template* AddDlgControl(unsigned int* totalSize, dialog_template* dlgTemplate, __int16 idClass, + unsigned style, + WORD id, __int16 x, __int16 y, __int16 cx, __int16 cy, + const wchar_t* String); + static void DestroySettingsDlgTemplate(LPCVOID pMem); + static int Settings_OnInitDialog(HWND hWnd, int wParam, MIXCONFIG* lpMixconfig); + static int Settings_OnCommand(HWND hWnd, int command, int lParam, int wParam); + static int ReadRegistryToGetMachineSpecificInfSection(unsigned wDeviceId, LPSTR lpString1, int maxLength); + static const char* GetOperatingSystemPrefix(); + static unsigned int FigureOutDMABufferSize(unsigned int waveBlockLen, PCMWAVEFORMAT* pcm); + static int NoResetRemix(DWORD dwRemixSamplePos, CHANNELNODE* channel); + static void SaveConfigSettings(unsigned dwFlags); + static void ShowCurrentSettings(); + static unsigned int GetWaveDevice(); + static void FreeWaveBlocks(HWAVEOUT hwo, XWAVEHDR** waveBlocks); + static int AllocWaveBlocks(HWAVEOUT hwo, XWAVEHDR** waveBlocks); + static void ReleaseWaveDevice(GLOBALS* globals); static void cmixit(unsigned __int8* lpDest, unsigned __int8** rgWaveSrc, volume_struct* volume, int iNumWaves, unsigned __int16 length); static LRESULT __stdcall WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); + static INT_PTR __stdcall SettingsDlgProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam); static int initialized_flag; static char FileName[276];