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