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