1 /* winmm audio driver for ft2play
2 **
3 ** Warning: This might not be 100% thread-safe or lock-safe!
4 */
5 
6 #define WIN32_LEAN_AND_MEAN
7 
8 #include <stdint.h>
9 #include <stdbool.h>
10 #include <stdlib.h>
11 #include <windows.h>
12 #include <mmsystem.h>
13 #include "../../pmp_mix.h"
14 
15 #define MIX_BUF_NUM 4
16 
17 static volatile BOOL mixerOpened, mixerBusy, mixerLocked;
18 static uint8_t currBuffer;
19 static int16_t *audioBuffer[MIX_BUF_NUM];
20 static int32_t bufferSize;
21 static HANDLE hThread, hAudioSem;
22 static WAVEHDR waveBlocks[MIX_BUF_NUM];
23 static HWAVEOUT hWave;
24 
mixThread(LPVOID lpParam)25 static DWORD WINAPI mixThread(LPVOID lpParam)
26 {
27 	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
28 	while (mixerOpened)
29 	{
30 		WAVEHDR *waveBlock = &waveBlocks[currBuffer];
31 
32 		if (!mixerLocked)
33 		{
34 			mixerBusy = true;
35 			mix_UpdateBuffer((int16_t *)waveBlock->lpData, bufferSize); // pmp_mix.c function
36 			mixerBusy = false;
37 		}
38 
39 		waveOutWrite(hWave, waveBlock, sizeof (WAVEHDR));
40 		currBuffer = (currBuffer + 1) % MIX_BUF_NUM;
41 		WaitForSingleObject(hAudioSem, INFINITE); // wait for buffer fill request
42 	}
43 
44 	return 0;
45 
46 	(void)lpParam;
47 }
48 
waveProc(HWAVEOUT hWaveOut,UINT uMsg,DWORD_PTR dwInstance,DWORD_PTR dwParam1,DWORD_PTR dwParam2)49 static void CALLBACK waveProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
50 {
51 	if (uMsg == WOM_DONE)
52 		ReleaseSemaphore(hAudioSem, 1, NULL);
53 
54 	(void)hWaveOut;
55 	(void)uMsg;
56 	(void)dwInstance;
57 	(void)dwParam1;
58 	(void)dwParam2;
59 }
60 
lockMixer(void)61 void lockMixer(void)
62 {
63 	mixerLocked = true;
64 	while (mixerBusy);
65 }
66 
unlockMixer(void)67 void unlockMixer(void)
68 {
69 	mixerBusy = false;
70 	mixerLocked = false;
71 }
72 
closeMixer(void)73 void closeMixer(void)
74 {
75 	mixerOpened = false;
76 	mixerBusy = false;
77 
78 	if (hAudioSem != NULL)
79 		ReleaseSemaphore(hAudioSem, 1, NULL);
80 
81 	if (hThread != NULL)
82 	{
83 		WaitForSingleObject(hThread, INFINITE);
84 		CloseHandle(hThread);
85 		hThread = NULL;
86 	}
87 
88 	if (hAudioSem != NULL)
89 	{
90 		CloseHandle(hAudioSem);
91 		hAudioSem = NULL;
92 	}
93 
94 	if (hWave != NULL)
95 	{
96 		waveOutReset(hWave);
97 
98 		for (int32_t i = 0; i < MIX_BUF_NUM; i++)
99 		{
100 			if (waveBlocks[i].dwUser != 0xFFFF)
101 				waveOutUnprepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR));
102 		}
103 
104 		waveOutClose(hWave);
105 		hWave = NULL;
106 	}
107 
108 	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
109 	{
110 		if (audioBuffer[i] != NULL)
111 		{
112 			free(audioBuffer[i]);
113 			audioBuffer[i] = NULL;
114 		}
115 	}
116 }
117 
openMixer(int32_t mixingFrequency,int32_t mixingBufferSize)118 bool openMixer(int32_t mixingFrequency, int32_t mixingBufferSize)
119 {
120 	DWORD threadID;
121 	WAVEFORMATEX wfx;
122 
123 	// don't unprepare headers on error
124 	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
125 		waveBlocks[i].dwUser = 0xFFFF;
126 
127 	closeMixer();
128 	bufferSize = mixingBufferSize;
129 
130 	ZeroMemory(&wfx, sizeof (wfx));
131 	wfx.nSamplesPerSec = mixingFrequency;
132 	wfx.wBitsPerSample = 16;
133 	wfx.nChannels = 2;
134 	wfx.wFormatTag = WAVE_FORMAT_PCM;
135 	wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
136 	wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
137 
138 	if (waveOutOpen(&hWave, WAVE_MAPPER, &wfx, (DWORD_PTR)&waveProc, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
139 		goto omError;
140 
141 	// create semaphore for buffer fill requests
142 	hAudioSem = CreateSemaphore(NULL, MIX_BUF_NUM - 1, MIX_BUF_NUM, NULL);
143 	if (hAudioSem == NULL)
144 		goto omError;
145 
146 	// allocate WinMM mix buffers
147 	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
148 	{
149 		audioBuffer[i] = (int16_t *)calloc(mixingBufferSize, wfx.nBlockAlign);
150 		if (audioBuffer[i] == NULL)
151 			goto omError;
152 	}
153 
154 	// initialize WinMM mix headers
155 	memset(waveBlocks, 0, sizeof (waveBlocks));
156 	for (int32_t i = 0; i < MIX_BUF_NUM; i++)
157 	{
158 		waveBlocks[i].lpData = (LPSTR)audioBuffer[i];
159 		waveBlocks[i].dwBufferLength = mixingBufferSize * wfx.nBlockAlign;
160 		waveBlocks[i].dwFlags = WHDR_DONE;
161 
162 		if (waveOutPrepareHeader(hWave, &waveBlocks[i], sizeof (WAVEHDR)) != MMSYSERR_NOERROR)
163 			goto omError;
164 	}
165 
166 	currBuffer = 0;
167 	mixerOpened = true;
168 
169 	hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)mixThread, NULL, 0, &threadID);
170 	if (hThread == NULL)
171 		goto omError;
172 
173 	return TRUE;
174 
175 omError:
176 	closeMixer();
177 	return FALSE;
178 }
179