[NTVDM] Revert r69435.
[reactos.git] / reactos / subsystems / mvdm / ntvdm / hardware / sound / speaker.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/hardware/sound/speaker.c
5 * PURPOSE: PC Speaker emulation
6 * PROGRAMMERS: Hermes Belusca-Maito (hermes.belusca@sfr.fr)
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "ntvdm.h"
12
13 #define NDEBUG
14 #include <debug.h>
15
16 #include "speaker.h"
17 #include "hardware/pit.h"
18
19 /* Extra PSDK/NDK Headers */
20 #include <ndk/iofuncs.h>
21
22 /* DDK Driver Headers */
23 #include <ntddbeep.h>
24
25 /* PRIVATE VARIABLES **********************************************************/
26
27 static HANDLE hBeep = NULL;
28
29 static LARGE_INTEGER FreqCount, CountStart;
30 static ULONG PulseTickCount = 0, FreqPulses = 0;
31
32 #define SPEAKER_RESPONSE 200 // in milliseconds
33
34 #define MIN_AUDIBLE_FREQ 20 // BEEP_FREQUENCY_MINIMUM
35 #define MAX_AUDIBLE_FREQ 20000 // BEEP_FREQUENCY_MAXIMUM
36 #define CLICK_FREQ 100
37
38
39 /* PRIVATE FUNCTIONS **********************************************************/
40
41 static
42 VOID
43 MakeBeep(ULONG Frequency,
44 ULONG Duration)
45 {
46 static ULONG LastFrequency = 0, LastDuration = 0;
47
48 IO_STATUS_BLOCK IoStatusBlock;
49 BEEP_SET_PARAMETERS BeepSetParameters;
50
51 /* A null frequency means we stop beeping */
52 if (Frequency == 0) Duration = 0;
53
54 /*
55 * Do nothing if we are replaying exactly the same sound
56 * (this avoids hiccups due to redoing the same beeps).
57 */
58 if (Frequency == LastFrequency && Duration == LastDuration) return;
59
60 /*
61 * For small durations we automatically reset the beep so
62 * that we can replay short beeps like clicks immediately.
63 */
64 if (Duration < 10)
65 {
66 LastFrequency = 0;
67 LastDuration = 0;
68 }
69 else
70 {
71 LastFrequency = Frequency;
72 LastDuration = Duration;
73 }
74
75 /* Set the data and do the beep */
76 BeepSetParameters.Frequency = Frequency;
77 BeepSetParameters.Duration = Duration;
78
79 NtDeviceIoControlFile(hBeep,
80 NULL,
81 NULL,
82 NULL,
83 &IoStatusBlock,
84 IOCTL_BEEP_SET,
85 &BeepSetParameters,
86 sizeof(BeepSetParameters),
87 NULL,
88 0);
89 }
90
91 static
92 VOID PulseSample(VOID)
93 {
94 static ULONG Pulses = 0, CountStartTick = 0, LastPulsesFreq = 0;
95 ULONG LastPulseTickCount, CurrPulsesFreq;
96 LARGE_INTEGER Counter;
97 LONGLONG Elapsed;
98
99 /*
100 * Check how far away was the previous pulse and
101 * if it was >= 200ms away then restart counting.
102 */
103 LastPulseTickCount = PulseTickCount;
104 PulseTickCount = GetTickCount();
105 if (PulseTickCount - LastPulseTickCount >= SPEAKER_RESPONSE)
106 {
107 CountStart.QuadPart = 0;
108 Pulses = 0;
109 FreqPulses = 0;
110 return;
111 }
112
113 /* We have closely spaced pulses. Start counting. */
114 if (CountStart.QuadPart == 0)
115 {
116 NtQueryPerformanceCounter(&CountStart, NULL);
117 CountStartTick = PulseTickCount;
118 Pulses = 0;
119 FreqPulses = 0;
120 return;
121 }
122
123 /* A pulse is ongoing */
124 ++Pulses;
125
126 /* We require some pulses to have some statistics */
127 if (PulseTickCount - CountStartTick <= (SPEAKER_RESPONSE >> 1)) return;
128
129 /* Get count time */
130 NtQueryPerformanceCounter(&Counter, NULL);
131
132 /*
133 * Get the number of speaker hundreds of microseconds that have passed
134 * since we started counting.
135 */
136 Elapsed = (Counter.QuadPart - CountStart.QuadPart) * 10000 / FreqCount.QuadPart;
137 if (Elapsed == 0) ++Elapsed;
138
139 /* Update counting for next pulses */
140 CountStart = Counter;
141 CountStartTick = PulseTickCount;
142
143 // HACKHACK!! I need to check why we need to double the number
144 // of pulses in order to have the correct frequency...
145 Pulses <<= 1;
146
147 /* Get the current pulses frequency */
148 CurrPulsesFreq = 10000 * Pulses / Elapsed;
149
150 /* Round the current pulses frequency up and align */
151 if ((CurrPulsesFreq & 0x0F) > 7) CurrPulsesFreq += 0x10;
152 CurrPulsesFreq &= ~0x0F;
153
154 /* Reinitialize frequency counters if necessary */
155 if (LastPulsesFreq == 0) LastPulsesFreq = CurrPulsesFreq;
156 if (FreqPulses == 0) FreqPulses = LastPulsesFreq;
157
158 /* Fix up the current pulses frequency if needed */
159 if (LastPulsesFreq != 0 && CurrPulsesFreq == 0)
160 CurrPulsesFreq = LastPulsesFreq;
161
162 /*
163 * Magic begins there...
164 */
165 #define UABS(x) (ULONG)((LONG)(x) < 0 ? -(LONG)(x) : (x))
166 if (UABS(CurrPulsesFreq - LastPulsesFreq) > 7)
167 {
168 /*
169 * This can be a "large" fluctuation so ignore it for now, but take
170 * it into account if it happens to be a real frequency change.
171 */
172 CurrPulsesFreq = (CurrPulsesFreq + LastPulsesFreq) >> 1;
173 }
174 else
175 {
176 // FreqPulses = ((FreqPulses << 2) + LastPulsesFreq + CurrPulsesFreq) / 6;
177 FreqPulses = ((FreqPulses << 1) + LastPulsesFreq + CurrPulsesFreq) >> 2;
178 }
179
180 /* Round the pulses frequency up and align */
181 if ((FreqPulses & 0x0F) > 7) FreqPulses += 0x10;
182 FreqPulses &= ~0x0F;
183
184 DPRINT("FreqPulses = %d, LastPulsesFreq = %d, CurrPulsesFreq = %d, Pulses = %d, Elapsed = %d\n",
185 FreqPulses, LastPulsesFreq, CurrPulsesFreq, Pulses, Elapsed);
186
187 LastPulsesFreq = CurrPulsesFreq;
188 Pulses = 0;
189 }
190
191
192 /* PUBLIC FUNCTIONS ***********************************************************/
193
194 // SpeakerPulse
195 VOID SpeakerChange(UCHAR Port61hValue)
196 {
197 static BOOLEAN OldSpeakerOff = TRUE;
198
199 BOOLEAN Timer2Gate = !!(Port61hValue & 0x01);
200 BOOLEAN SpeakerOn = !!(Port61hValue & 0x02);
201
202 DPRINT("SpeakerChange -- Timer2Gate == %s ; SpeakerOn == %s\n",
203 Timer2Gate ? "true" : "false", SpeakerOn ? "true" : "false");
204
205 if (Timer2Gate)
206 {
207 if (SpeakerOn)
208 {
209 /* Start beeping */
210 ULONG Frequency = (PIT_BASE_FREQUENCY / PitGetReloadValue(2));
211 if (Frequency < MIN_AUDIBLE_FREQ || MAX_AUDIBLE_FREQ < Frequency)
212 Frequency = 0;
213
214 MakeBeep(Frequency, INFINITE);
215 }
216 else
217 {
218 /* Stop beeping */
219 MakeBeep(0, 0);
220 }
221 }
222 else
223 {
224 if (SpeakerOn)
225 {
226 if (OldSpeakerOff)
227 {
228 OldSpeakerOff = FALSE;
229 PulseSample();
230 }
231
232 if (FreqPulses >= MIN_AUDIBLE_FREQ)
233 MakeBeep(FreqPulses, INFINITE);
234 else if (CountStart.QuadPart != 0)
235 MakeBeep(CLICK_FREQ, 1); /* Click */
236 else
237 MakeBeep(0, 0); /* Stop beeping */
238 }
239 else
240 {
241 OldSpeakerOff = TRUE;
242
243 /*
244 * Check how far away was the previous pulse and if
245 * it was >= (200 + eps) ms away then stop beeping.
246 */
247 if (GetTickCount() - PulseTickCount >= SPEAKER_RESPONSE + (SPEAKER_RESPONSE >> 3))
248 {
249 CountStart.QuadPart = 0;
250 FreqPulses = 0;
251
252 /* Stop beeping */
253 MakeBeep(0, 0);
254 }
255 }
256 }
257 }
258
259 VOID SpeakerInitialize(VOID)
260 {
261 NTSTATUS Status;
262 UNICODE_STRING BeepDevice;
263 OBJECT_ATTRIBUTES ObjectAttributes;
264 IO_STATUS_BLOCK IoStatusBlock;
265
266 /* Retrieve the performance frequency and initialize the timer ticks */
267 NtQueryPerformanceCounter(&CountStart, &FreqCount);
268 if (FreqCount.QuadPart == 0)
269 {
270 wprintf(L"FATAL: Performance counter not available\n");
271 }
272
273 /* Open the BEEP device */
274 RtlInitUnicodeString(&BeepDevice, L"\\Device\\Beep");
275 InitializeObjectAttributes(&ObjectAttributes, &BeepDevice, 0, NULL, NULL);
276 Status = NtCreateFile(&hBeep,
277 FILE_READ_DATA | FILE_WRITE_DATA,
278 &ObjectAttributes,
279 &IoStatusBlock,
280 NULL,
281 0,
282 FILE_SHARE_READ | FILE_SHARE_WRITE,
283 FILE_OPEN_IF,
284 0,
285 NULL,
286 0);
287 if (!NT_SUCCESS(Status))
288 {
289 DPRINT1("Failed to open the Beep driver, Status 0x%08lx\n", Status);
290 // hBeep = INVALID_HANDLE_VALUE;
291 }
292 }
293
294 VOID SpeakerCleanup(VOID)
295 {
296 NtClose(hBeep);
297 }
298
299 /* EOF */