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