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
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
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
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 
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 
295 VOID SpeakerCleanup(VOID)
296 {
297     NtClose(hBeep);
298 }
299 
300 /* EOF */
301