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