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